Code

Merge branch 'js/ls-files-x-doc'
authorJunio C Hamano <gitster@pobox.com>
Sat, 18 Sep 2010 21:46:44 +0000 (14:46 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sat, 18 Sep 2010 21:46:44 +0000 (14:46 -0700)
* js/ls-files-x-doc:
  ls-files documentation: reword for consistency
  git-ls-files.txt: clarify -x/--exclude option

Conflicts:
Documentation/git-ls-files.txt

1429 files changed:
.gitattributes
.gitignore
.mailmap
COPYING
Documentation/.gitignore
Documentation/Makefile
Documentation/RelNotes-1.5.0.1.txt [deleted file]
Documentation/RelNotes-1.5.0.2.txt [deleted file]
Documentation/RelNotes-1.5.0.3.txt [deleted file]
Documentation/RelNotes-1.5.0.4.txt [deleted file]
Documentation/RelNotes-1.5.0.5.txt [deleted file]
Documentation/RelNotes-1.5.0.6.txt [deleted file]
Documentation/RelNotes-1.5.0.7.txt [deleted file]
Documentation/RelNotes-1.5.0.txt [deleted file]
Documentation/RelNotes-1.5.1.1.txt [deleted file]
Documentation/RelNotes-1.5.1.2.txt [deleted file]
Documentation/RelNotes-1.5.1.3.txt [deleted file]
Documentation/RelNotes-1.5.1.4.txt [deleted file]
Documentation/RelNotes-1.5.1.5.txt [deleted file]
Documentation/RelNotes-1.5.1.6.txt [deleted file]
Documentation/RelNotes-1.5.1.txt [deleted file]
Documentation/RelNotes-1.5.2.1.txt [deleted file]
Documentation/RelNotes-1.5.2.2.txt [deleted file]
Documentation/RelNotes-1.5.2.3.txt [deleted file]
Documentation/RelNotes-1.5.2.4.txt [deleted file]
Documentation/RelNotes-1.5.2.5.txt [deleted file]
Documentation/RelNotes-1.5.2.txt [deleted file]
Documentation/RelNotes-1.5.3.1.txt [deleted file]
Documentation/RelNotes-1.5.3.2.txt [deleted file]
Documentation/RelNotes-1.5.3.3.txt [deleted file]
Documentation/RelNotes-1.5.3.4.txt [deleted file]
Documentation/RelNotes-1.5.3.5.txt [deleted file]
Documentation/RelNotes-1.5.3.6.txt [deleted file]
Documentation/RelNotes-1.5.3.7.txt [deleted file]
Documentation/RelNotes-1.5.3.8.txt [deleted file]
Documentation/RelNotes-1.5.3.txt [deleted file]
Documentation/RelNotes-1.5.4.1.txt [deleted file]
Documentation/RelNotes-1.5.4.2.txt [deleted file]
Documentation/RelNotes-1.5.4.3.txt [deleted file]
Documentation/RelNotes-1.5.4.4.txt [deleted file]
Documentation/RelNotes-1.5.4.5.txt [deleted file]
Documentation/RelNotes-1.5.4.6.txt [deleted file]
Documentation/RelNotes-1.5.4.7.txt [deleted file]
Documentation/RelNotes-1.5.4.txt [deleted file]
Documentation/RelNotes-1.5.5.1.txt [deleted file]
Documentation/RelNotes-1.5.5.2.txt [deleted file]
Documentation/RelNotes-1.5.5.3.txt [deleted file]
Documentation/RelNotes-1.5.5.4.txt [deleted file]
Documentation/RelNotes-1.5.5.5.txt [deleted file]
Documentation/RelNotes-1.5.5.6.txt [deleted file]
Documentation/RelNotes-1.5.5.txt [deleted file]
Documentation/RelNotes-1.5.6.1.txt [deleted file]
Documentation/RelNotes-1.5.6.2.txt [deleted file]
Documentation/RelNotes-1.5.6.3.txt [deleted file]
Documentation/RelNotes-1.5.6.4.txt [deleted file]
Documentation/RelNotes-1.5.6.5.txt [deleted file]
Documentation/RelNotes-1.5.6.6.txt [deleted file]
Documentation/RelNotes-1.5.6.txt [deleted file]
Documentation/RelNotes-1.6.0.1.txt [deleted file]
Documentation/RelNotes-1.6.0.2.txt [deleted file]
Documentation/RelNotes-1.6.0.3.txt [deleted file]
Documentation/RelNotes-1.6.0.4.txt [deleted file]
Documentation/RelNotes-1.6.0.5.txt [deleted file]
Documentation/RelNotes-1.6.0.6.txt [deleted file]
Documentation/RelNotes-1.6.0.txt [deleted file]
Documentation/RelNotes-1.6.1.1.txt [deleted file]
Documentation/RelNotes-1.6.1.2.txt [deleted file]
Documentation/RelNotes-1.6.1.3.txt [deleted file]
Documentation/RelNotes-1.6.1.4.txt [deleted file]
Documentation/RelNotes-1.6.1.txt [deleted file]
Documentation/RelNotes-1.6.2.1.txt [deleted file]
Documentation/RelNotes-1.6.2.2.txt [deleted file]
Documentation/RelNotes-1.6.2.3.txt [deleted file]
Documentation/RelNotes-1.6.2.4.txt [deleted file]
Documentation/RelNotes-1.6.2.5.txt [deleted file]
Documentation/RelNotes-1.6.2.txt [deleted file]
Documentation/RelNotes-1.6.3.txt [deleted file]
Documentation/RelNotes/1.5.0.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.0.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.0.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.0.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.0.5.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.0.6.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.0.7.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.0.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.1.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.1.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.1.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.1.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.1.5.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.1.6.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.2.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.2.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.2.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.2.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.2.5.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.3.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.3.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.3.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.3.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.3.5.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.3.6.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.3.7.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.3.8.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.4.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.4.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.4.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.4.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.4.5.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.4.6.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.4.7.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.5.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.5.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.5.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.5.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.5.5.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.5.6.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.5.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.6.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.6.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.6.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.6.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.6.5.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.6.6.txt [new file with mode: 0644]
Documentation/RelNotes/1.5.6.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.0.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.0.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.0.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.0.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.0.5.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.0.6.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.0.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.1.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.1.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.1.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.1.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.2.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.2.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.2.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.2.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.2.5.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.3.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.3.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.3.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.3.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.4.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.4.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.4.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.4.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.5.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.5.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.5.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.5.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.5.5.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.5.6.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.5.7.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.5.8.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.5.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.6.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.6.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.6.6.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.0.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.0.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.0.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.0.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.0.5.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.0.6.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.0.7.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.0.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.1.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.1.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.2.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.2.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.2.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.3.txt [new file with mode: 0644]
Documentation/SubmittingPatches
Documentation/asciidoc.conf
Documentation/blame-options.txt
Documentation/config.txt
Documentation/date-formats.txt [new file with mode: 0644]
Documentation/diff-format.txt
Documentation/diff-generate-patch.txt
Documentation/diff-options.txt
Documentation/docbook.xsl
Documentation/everyday.txt
Documentation/fetch-options.txt
Documentation/git-add.txt
Documentation/git-am.txt
Documentation/git-apply.txt
Documentation/git-archimport.txt
Documentation/git-archive.txt
Documentation/git-bisect-lk2009.txt [new file with mode: 0644]
Documentation/git-bisect.txt
Documentation/git-blame.txt
Documentation/git-branch.txt
Documentation/git-bundle.txt
Documentation/git-cat-file.txt
Documentation/git-check-ref-format.txt
Documentation/git-checkout-index.txt
Documentation/git-checkout.txt
Documentation/git-cherry-pick.txt
Documentation/git-cherry.txt
Documentation/git-citool.txt
Documentation/git-clean.txt
Documentation/git-clone.txt
Documentation/git-commit-tree.txt
Documentation/git-commit.txt
Documentation/git-config.txt
Documentation/git-cvsexportcommit.txt
Documentation/git-cvsimport.txt
Documentation/git-cvsserver.txt
Documentation/git-daemon.txt
Documentation/git-describe.txt
Documentation/git-diff-files.txt
Documentation/git-diff-index.txt
Documentation/git-diff-tree.txt
Documentation/git-diff.txt
Documentation/git-difftool.txt
Documentation/git-fast-export.txt
Documentation/git-fast-import.txt
Documentation/git-fetch-pack.txt
Documentation/git-fetch.txt
Documentation/git-filter-branch.txt
Documentation/git-fmt-merge-msg.txt
Documentation/git-for-each-ref.txt
Documentation/git-format-patch.txt
Documentation/git-fsck.txt
Documentation/git-gc.txt
Documentation/git-get-tar-commit-id.txt
Documentation/git-grep.txt
Documentation/git-gui.txt
Documentation/git-hash-object.txt
Documentation/git-help.txt
Documentation/git-http-backend.txt [new file with mode: 0644]
Documentation/git-http-fetch.txt
Documentation/git-http-push.txt
Documentation/git-imap-send.txt
Documentation/git-index-pack.txt
Documentation/git-init-db.txt
Documentation/git-init.txt
Documentation/git-instaweb.txt
Documentation/git-log.txt
Documentation/git-ls-files.txt
Documentation/git-ls-tree.txt
Documentation/git-mailinfo.txt
Documentation/git-mailsplit.txt
Documentation/git-merge-base.txt
Documentation/git-merge-file.txt
Documentation/git-merge-index.txt
Documentation/git-merge-one-file.txt
Documentation/git-merge.txt
Documentation/git-mergetool--lib.txt
Documentation/git-mergetool.txt
Documentation/git-mktree.txt
Documentation/git-mv.txt
Documentation/git-name-rev.txt
Documentation/git-notes.txt [new file with mode: 0644]
Documentation/git-pack-objects.txt
Documentation/git-pack-redundant.txt
Documentation/git-parse-remote.txt
Documentation/git-patch-id.txt
Documentation/git-peek-remote.txt
Documentation/git-prune-packed.txt
Documentation/git-prune.txt
Documentation/git-pull.txt
Documentation/git-push.txt
Documentation/git-quiltimport.txt
Documentation/git-read-tree.txt
Documentation/git-rebase.txt
Documentation/git-receive-pack.txt
Documentation/git-reflog.txt
Documentation/git-relink.txt
Documentation/git-remote-helpers.txt [new file with mode: 0644]
Documentation/git-remote.txt
Documentation/git-repack.txt
Documentation/git-replace.txt [new file with mode: 0644]
Documentation/git-request-pull.txt
Documentation/git-rerere.txt
Documentation/git-reset.txt
Documentation/git-rev-list.txt
Documentation/git-rev-parse.txt
Documentation/git-revert.txt
Documentation/git-rm.txt
Documentation/git-send-email.txt
Documentation/git-send-pack.txt
Documentation/git-sh-setup.txt
Documentation/git-shortlog.txt
Documentation/git-show-branch.txt
Documentation/git-show-index.txt
Documentation/git-show-ref.txt
Documentation/git-show.txt
Documentation/git-stash.txt
Documentation/git-status.txt
Documentation/git-submodule.txt
Documentation/git-svn.txt
Documentation/git-symbolic-ref.txt
Documentation/git-tag.txt
Documentation/git-tar-tree.txt
Documentation/git-update-index.txt
Documentation/git-upload-archive.txt
Documentation/git-upload-pack.txt
Documentation/git-var.txt
Documentation/git-verify-pack.txt
Documentation/git-verify-tag.txt
Documentation/git-web--browse.txt
Documentation/git-whatchanged.txt
Documentation/git-write-tree.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/gitcli.txt
Documentation/gitcore-tutorial.txt
Documentation/gitcvs-migration.txt
Documentation/gitdiffcore.txt
Documentation/githooks.txt
Documentation/gitignore.txt
Documentation/gitk.txt
Documentation/gitmodules.txt
Documentation/gitrepository-layout.txt
Documentation/gitrevisions.txt [new file with mode: 0644]
Documentation/gittutorial-2.txt
Documentation/gittutorial.txt
Documentation/gitworkflows.txt
Documentation/glossary-content.txt
Documentation/howto/maintain-git.txt
Documentation/howto/revert-a-faulty-merge.txt
Documentation/howto/revert-branch-rebase.txt
Documentation/howto/update-hook-example.txt
Documentation/i18n.txt
Documentation/install-webdoc.sh
Documentation/manpage-base-url.xsl.in [new file with mode: 0644]
Documentation/manpage-quote-apos.xsl [new file with mode: 0644]
Documentation/merge-config.txt
Documentation/merge-options.txt
Documentation/merge-strategies.txt
Documentation/pretty-formats.txt
Documentation/pretty-options.txt
Documentation/pt_BR/gittutorial.txt [new file with mode: 0644]
Documentation/pull-fetch-param.txt
Documentation/rev-list-options.txt
Documentation/revisions.txt [new file with mode: 0644]
Documentation/technical/api-directory-listing.txt
Documentation/technical/api-hash.txt
Documentation/technical/api-history-graph.txt
Documentation/technical/api-merge.txt [new file with mode: 0644]
Documentation/technical/api-parse-options.txt
Documentation/technical/api-remote.txt
Documentation/technical/api-run-command.txt
Documentation/technical/api-strbuf.txt
Documentation/technical/api-string-list.txt
Documentation/technical/api-tree-walking.txt
Documentation/technical/pack-protocol.txt
Documentation/technical/protocol-capabilities.txt [new file with mode: 0644]
Documentation/technical/protocol-common.txt [new file with mode: 0644]
Documentation/technical/racy-git.txt
Documentation/urls-remotes.txt
Documentation/urls.txt
Documentation/user-manual.txt
GIT-VERSION-GEN
INSTALL
Makefile
README
RelNotes
abspath.c
aclocal.m4 [new file with mode: 0644]
advice.c [new file with mode: 0644]
advice.h [new file with mode: 0644]
alias.c
archive-tar.c
archive.c
arm/sha1.c [deleted file]
arm/sha1.h [deleted file]
arm/sha1_arm.S [deleted file]
attr.c
attr.h
base85.c
bisect.c
bisect.h
blob.c
blob.h
block-sha1/sha1.c [new file with mode: 0644]
block-sha1/sha1.h [new file with mode: 0644]
branch.c
builtin-add.c [deleted file]
builtin-annotate.c [deleted file]
builtin-apply.c [deleted file]
builtin-archive.c [deleted file]
builtin-bisect--helper.c [deleted file]
builtin-blame.c [deleted file]
builtin-branch.c [deleted file]
builtin-bundle.c [deleted file]
builtin-cat-file.c [deleted file]
builtin-check-attr.c [deleted file]
builtin-check-ref-format.c [deleted file]
builtin-checkout-index.c [deleted file]
builtin-checkout.c [deleted file]
builtin-clean.c [deleted file]
builtin-clone.c [deleted file]
builtin-commit-tree.c [deleted file]
builtin-commit.c [deleted file]
builtin-config.c [deleted file]
builtin-count-objects.c [deleted file]
builtin-describe.c [deleted file]
builtin-diff-files.c [deleted file]
builtin-diff-index.c [deleted file]
builtin-diff-tree.c [deleted file]
builtin-diff.c [deleted file]
builtin-fast-export.c [deleted file]
builtin-fetch--tool.c [deleted file]
builtin-fetch-pack.c [deleted file]
builtin-fetch.c [deleted file]
builtin-fmt-merge-msg.c [deleted file]
builtin-for-each-ref.c [deleted file]
builtin-fsck.c [deleted file]
builtin-gc.c [deleted file]
builtin-grep.c [deleted file]
builtin-help.c [deleted file]
builtin-http-fetch.c [deleted file]
builtin-init-db.c [deleted file]
builtin-log.c [deleted file]
builtin-ls-files.c [deleted file]
builtin-ls-remote.c [deleted file]
builtin-ls-tree.c [deleted file]
builtin-mailinfo.c [deleted file]
builtin-mailsplit.c [deleted file]
builtin-merge-base.c [deleted file]
builtin-merge-file.c [deleted file]
builtin-merge-ours.c [deleted file]
builtin-merge-recursive.c [deleted file]
builtin-merge.c [deleted file]
builtin-mv.c [deleted file]
builtin-name-rev.c [deleted file]
builtin-pack-objects.c [deleted file]
builtin-pack-refs.c [deleted file]
builtin-prune-packed.c [deleted file]
builtin-prune.c [deleted file]
builtin-push.c [deleted file]
builtin-read-tree.c [deleted file]
builtin-receive-pack.c [deleted file]
builtin-reflog.c [deleted file]
builtin-remote.c [deleted file]
builtin-rerere.c [deleted file]
builtin-reset.c [deleted file]
builtin-rev-list.c [deleted file]
builtin-rev-parse.c [deleted file]
builtin-revert.c [deleted file]
builtin-rm.c [deleted file]
builtin-send-pack.c [deleted file]
builtin-shortlog.c [deleted file]
builtin-show-branch.c [deleted file]
builtin-show-ref.c [deleted file]
builtin-stripspace.c [deleted file]
builtin-symbolic-ref.c [deleted file]
builtin-tag.c [deleted file]
builtin-tar-tree.c [deleted file]
builtin-unpack-objects.c [deleted file]
builtin-update-index.c [deleted file]
builtin-update-ref.c [deleted file]
builtin-upload-archive.c [deleted file]
builtin-verify-pack.c [deleted file]
builtin-verify-tag.c [deleted file]
builtin-write-tree.c [deleted file]
builtin.h
builtin/add.c [new file with mode: 0644]
builtin/annotate.c [new file with mode: 0644]
builtin/apply.c [new file with mode: 0644]
builtin/archive.c [new file with mode: 0644]
builtin/bisect--helper.c [new file with mode: 0644]
builtin/blame.c [new file with mode: 0644]
builtin/branch.c [new file with mode: 0644]
builtin/bundle.c [new file with mode: 0644]
builtin/cat-file.c [new file with mode: 0644]
builtin/check-attr.c [new file with mode: 0644]
builtin/check-ref-format.c [new file with mode: 0644]
builtin/checkout-index.c [new file with mode: 0644]
builtin/checkout.c [new file with mode: 0644]
builtin/clean.c [new file with mode: 0644]
builtin/clone.c [new file with mode: 0644]
builtin/commit-tree.c [new file with mode: 0644]
builtin/commit.c [new file with mode: 0644]
builtin/config.c [new file with mode: 0644]
builtin/count-objects.c [new file with mode: 0644]
builtin/describe.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-tree.c [new file with mode: 0644]
builtin/diff.c [new file with mode: 0644]
builtin/fast-export.c [new file with mode: 0644]
builtin/fetch-pack.c [new file with mode: 0644]
builtin/fetch.c [new file with mode: 0644]
builtin/fmt-merge-msg.c [new file with mode: 0644]
builtin/for-each-ref.c [new file with mode: 0644]
builtin/fsck.c [new file with mode: 0644]
builtin/gc.c [new file with mode: 0644]
builtin/grep.c [new file with mode: 0644]
builtin/hash-object.c [new file with mode: 0644]
builtin/help.c [new file with mode: 0644]
builtin/index-pack.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-remote.c [new file with mode: 0644]
builtin/ls-tree.c [new file with mode: 0644]
builtin/mailinfo.c [new file with mode: 0644]
builtin/mailsplit.c [new file with mode: 0644]
builtin/merge-base.c [new file with mode: 0644]
builtin/merge-file.c [new file with mode: 0644]
builtin/merge-index.c [new file with mode: 0644]
builtin/merge-ours.c [new file with mode: 0644]
builtin/merge-recursive.c [new file with mode: 0644]
builtin/merge-tree.c [new file with mode: 0644]
builtin/merge.c [new file with mode: 0644]
builtin/mktag.c [new file with mode: 0644]
builtin/mktree.c [new file with mode: 0644]
builtin/mv.c [new file with mode: 0644]
builtin/name-rev.c [new file with mode: 0644]
builtin/notes.c [new file with mode: 0644]
builtin/pack-objects.c [new file with mode: 0644]
builtin/pack-redundant.c [new file with mode: 0644]
builtin/pack-refs.c [new file with mode: 0644]
builtin/patch-id.c [new file with mode: 0644]
builtin/prune-packed.c [new file with mode: 0644]
builtin/prune.c [new file with mode: 0644]
builtin/push.c [new file with mode: 0644]
builtin/read-tree.c [new file with mode: 0644]
builtin/receive-pack.c [new file with mode: 0644]
builtin/reflog.c [new file with mode: 0644]
builtin/remote.c [new file with mode: 0644]
builtin/replace.c [new file with mode: 0644]
builtin/rerere.c [new file with mode: 0644]
builtin/reset.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/revert.c [new file with mode: 0644]
builtin/rm.c [new file with mode: 0644]
builtin/send-pack.c [new file with mode: 0644]
builtin/shortlog.c [new file with mode: 0644]
builtin/show-branch.c [new file with mode: 0644]
builtin/show-ref.c [new file with mode: 0644]
builtin/stripspace.c [new file with mode: 0644]
builtin/symbolic-ref.c [new file with mode: 0644]
builtin/tag.c [new file with mode: 0644]
builtin/tar-tree.c [new file with mode: 0644]
builtin/unpack-file.c [new file with mode: 0644]
builtin/unpack-objects.c [new file with mode: 0644]
builtin/update-index.c [new file with mode: 0644]
builtin/update-ref.c [new file with mode: 0644]
builtin/update-server-info.c [new file with mode: 0644]
builtin/upload-archive.c [new file with mode: 0644]
builtin/var.c [new file with mode: 0644]
builtin/verify-pack.c [new file with mode: 0644]
builtin/verify-tag.c [new file with mode: 0644]
builtin/write-tree.c [new file with mode: 0644]
bundle.c
cache-tree.c
cache-tree.h
cache.h
color.c
color.h
combine-diff.c
command-list.txt
commit.c
commit.h
compat/basename.c [new file with mode: 0644]
compat/bswap.h [new file with mode: 0644]
compat/mingw.c
compat/mingw.h
compat/mkdtemp.c
compat/msvc.c [new file with mode: 0644]
compat/msvc.h [new file with mode: 0644]
compat/nedmalloc/License.txt [new file with mode: 0644]
compat/nedmalloc/Readme.txt [new file with mode: 0644]
compat/nedmalloc/malloc.c.h [new file with mode: 0644]
compat/nedmalloc/nedmalloc.c [new file with mode: 0644]
compat/nedmalloc/nedmalloc.h [new file with mode: 0644]
compat/regex/regcomp.c [new file with mode: 0644]
compat/regex/regex.c
compat/regex/regex.h
compat/regex/regex_internal.c [new file with mode: 0644]
compat/regex/regex_internal.h [new file with mode: 0644]
compat/regex/regexec.c [new file with mode: 0644]
compat/snprintf.c
compat/strtok_r.c [new file with mode: 0644]
compat/vcbuild/README [new file with mode: 0644]
compat/vcbuild/include/alloca.h [new file with mode: 0644]
compat/vcbuild/include/arpa/inet.h [new file with mode: 0644]
compat/vcbuild/include/dirent.h [new file with mode: 0644]
compat/vcbuild/include/grp.h [new file with mode: 0644]
compat/vcbuild/include/inttypes.h [new file with mode: 0644]
compat/vcbuild/include/netdb.h [new file with mode: 0644]
compat/vcbuild/include/netinet/in.h [new file with mode: 0644]
compat/vcbuild/include/netinet/tcp.h [new file with mode: 0644]
compat/vcbuild/include/pwd.h [new file with mode: 0644]
compat/vcbuild/include/sys/ioctl.h [new file with mode: 0644]
compat/vcbuild/include/sys/param.h [new file with mode: 0644]
compat/vcbuild/include/sys/poll.h [new file with mode: 0644]
compat/vcbuild/include/sys/select.h [new file with mode: 0644]
compat/vcbuild/include/sys/socket.h [new file with mode: 0644]
compat/vcbuild/include/sys/time.h [new file with mode: 0644]
compat/vcbuild/include/sys/utime.h [new file with mode: 0644]
compat/vcbuild/include/sys/wait.h [new file with mode: 0644]
compat/vcbuild/include/termios.h [new file with mode: 0644]
compat/vcbuild/include/unistd.h [new file with mode: 0644]
compat/vcbuild/include/utime.h [new file with mode: 0644]
compat/vcbuild/scripts/clink.pl [new file with mode: 0644]
compat/vcbuild/scripts/lib.pl [new file with mode: 0644]
compat/win32.h
compat/win32/pthread.c [new file with mode: 0644]
compat/win32/pthread.h [new file with mode: 0644]
compat/win32mmap.c
compat/winansi.c
config.c
config.mak.in
configure.ac
connect.c
contrib/buildsystems/Generators.pm [new file with mode: 0644]
contrib/buildsystems/Generators/QMake.pm [new file with mode: 0644]
contrib/buildsystems/Generators/Vcproj.pm [new file with mode: 0644]
contrib/buildsystems/engine.pl [new file with mode: 0644]
contrib/buildsystems/generate [new file with mode: 0644]
contrib/buildsystems/parse.pl [new file with mode: 0644]
contrib/ciabot/README [new file with mode: 0644]
contrib/ciabot/ciabot.py [new file with mode: 0755]
contrib/ciabot/ciabot.sh [new file with mode: 0755]
contrib/completion/git-completion.bash
contrib/emacs/git-blame.el
contrib/emacs/git.el
contrib/examples/builtin-fetch--tool.c [new file with mode: 0644]
contrib/examples/git-commit.sh
contrib/examples/git-fetch.sh
contrib/examples/git-merge.sh
contrib/examples/git-notes.sh [new file with mode: 0755]
contrib/examples/git-resolve.sh
contrib/examples/git-revert.sh
contrib/fast-import/git-p4
contrib/fast-import/import-directories.perl [new file with mode: 0755]
contrib/fast-import/import-tars.perl
contrib/fast-import/import-zips.py
contrib/git-resurrect.sh
contrib/hg-to-git/hg-to-git.py
contrib/hooks/post-receive-email [changed mode: 0644->0755]
contrib/p4import/git-p4import.py
contrib/svn-fe/.gitignore [new file with mode: 0644]
contrib/svn-fe/Makefile [new file with mode: 0644]
contrib/svn-fe/svn-fe.c [new file with mode: 0644]
contrib/svn-fe/svn-fe.txt [new file with mode: 0644]
contrib/workdir/git-new-workdir
convert.c
copy.c
csum-file.c
ctype.c
daemon.c
date.c
decorate.c
delta.h
diff-delta.c
diff-lib.c
diff-no-index.c
diff.c
diff.h
diffcore-break.c
diffcore-delta.c
diffcore-pickaxe.c
diffcore-rename.c
diffcore.h
dir.c
dir.h
editor.c
entry.c
environment.c
exec_cmd.c
fast-import.c
fetch-pack.h
fsck.c
fsck.h
git-add--interactive.perl
git-am.sh
git-bisect.sh
git-compat-util.h
git-cvsexportcommit.perl
git-cvsimport.perl
git-cvsserver.perl
git-difftool--helper.sh
git-difftool.perl
git-filter-branch.sh
git-gui/GIT-VERSION-GEN
git-gui/Makefile
git-gui/git-gui--askpass
git-gui/git-gui.sh
git-gui/lib/about.tcl
git-gui/lib/blame.tcl
git-gui/lib/branch_checkout.tcl
git-gui/lib/branch_create.tcl
git-gui/lib/branch_delete.tcl
git-gui/lib/branch_rename.tcl
git-gui/lib/browser.tcl
git-gui/lib/choose_font.tcl
git-gui/lib/choose_repository.tcl
git-gui/lib/choose_rev.tcl
git-gui/lib/class.tcl
git-gui/lib/console.tcl
git-gui/lib/database.tcl
git-gui/lib/diff.tcl
git-gui/lib/error.tcl
git-gui/lib/index.tcl
git-gui/lib/merge.tcl
git-gui/lib/option.tcl
git-gui/lib/remote_add.tcl
git-gui/lib/remote_branch_delete.tcl
git-gui/lib/search.tcl
git-gui/lib/shortcut.tcl
git-gui/lib/sshkey.tcl
git-gui/lib/status_bar.tcl
git-gui/lib/themed.tcl [new file with mode: 0644]
git-gui/lib/tools_dlg.tcl
git-gui/lib/transport.tcl
git-gui/lib/win32.tcl
git-gui/po/de.po
git-gui/po/el.po [new file with mode: 0644]
git-gui/po/fr.po
git-gui/po/git-gui.pot
git-gui/po/glossary/el.po [new file with mode: 0644]
git-gui/po/it.po
git-gui/po/ja.po
git-gui/po/ru.po
git-gui/po/sv.po
git-gui/windows/git-gui.sh
git-instaweb.sh
git-merge-octopus.sh
git-merge-one-file.sh
git-mergetool--lib.sh
git-mergetool.sh
git-parse-remote.sh [changed mode: 0755->0644]
git-pull.sh
git-rebase--interactive.sh
git-rebase.sh
git-remote-testgit.py [new file with mode: 0644]
git-repack.sh
git-request-pull.sh
git-send-email.perl
git-sh-setup.sh [changed mode: 0755->0644]
git-stash.sh
git-submodule.sh
git-svn.perl
git-web--browse.sh
git.c
git.spec.in
git_remote_helpers/.gitignore [new file with mode: 0644]
git_remote_helpers/Makefile [new file with mode: 0644]
git_remote_helpers/__init__.py [new file with mode: 0644]
git_remote_helpers/git/__init__.py [new file with mode: 0644]
git_remote_helpers/git/exporter.py [new file with mode: 0644]
git_remote_helpers/git/git.py [new file with mode: 0644]
git_remote_helpers/git/importer.py [new file with mode: 0644]
git_remote_helpers/git/non_local.py [new file with mode: 0644]
git_remote_helpers/git/repo.py [new file with mode: 0644]
git_remote_helpers/setup.py [new file with mode: 0644]
git_remote_helpers/util.py [new file with mode: 0644]
gitk-git/gitk
gitk-git/po/de.po
gitk-git/po/es.po
gitk-git/po/fr.po [new file with mode: 0644]
gitk-git/po/hu.po [new file with mode: 0644]
gitk-git/po/it.po
gitk-git/po/ja.po [new file with mode: 0644]
gitk-git/po/ru.po
gitk-git/po/sv.po
gitweb/INSTALL
gitweb/Makefile [new file with mode: 0644]
gitweb/README
gitweb/git-favicon.png [deleted file]
gitweb/git-logo.png [deleted file]
gitweb/gitweb.css [deleted file]
gitweb/gitweb.perl
gitweb/static/git-favicon.png [new file with mode: 0644]
gitweb/static/git-logo.png [new file with mode: 0644]
gitweb/static/gitweb.css [new file with mode: 0644]
gitweb/static/gitweb.js [new file with mode: 0644]
graph.c
graph.h
grep.c
grep.h
hash-object.c [deleted file]
help.c
help.h
hex.c [new file with mode: 0644]
http-backend.c [new file with mode: 0644]
http-fetch.c [new file with mode: 0644]
http-push.c
http-walker.c
http.c
http.h
ident.c
imap-send.c
index-pack.c [deleted file]
levenshtein.h
ll-merge.c
ll-merge.h
lockfile.c
log-tree.c
log-tree.h
mailmap.c
mailmap.h
match-trees.c
merge-file.c
merge-index.c [deleted file]
merge-recursive.c
merge-recursive.h
merge-tree.c [deleted file]
mktag.c [deleted file]
mktree.c [deleted file]
mozilla-sha1/sha1.c [deleted file]
mozilla-sha1/sha1.h [deleted file]
notes-cache.c [new file with mode: 0644]
notes-cache.h [new file with mode: 0644]
notes.c [new file with mode: 0644]
notes.h [new file with mode: 0644]
object.c
object.h
pack-check.c
pack-redundant.c [deleted file]
pack-refs.c
pack-revindex.c
pack-write.c
pack.h
pager.c
parse-options.c
parse-options.h
patch-delta.c
patch-id.c [deleted file]
path.c
perl/Git.pm
perl/Makefile
perl/Makefile.PL
pkt-line.c
pkt-line.h
preload-index.c
pretty.c
progress.c
quote.c
quote.h
reachable.c
read-cache.c
reflog-walk.c
reflog-walk.h
refs.c
refs.h
remote-curl.c [new file with mode: 0644]
remote.c
remote.h
replace_object.c [new file with mode: 0644]
rerere.c
rerere.h
resolve-undo.c [new file with mode: 0644]
resolve-undo.h [new file with mode: 0644]
revision.c
revision.h
run-command.c
run-command.h
send-pack.h
server-info.c
setup.c
sha1_file.c
sha1_name.c
shallow.c
shell.c
shortlog.h
show-index.c
sideband.c
strbuf.c
strbuf.h
string-list.c
string-list.h
submodule.c [new file with mode: 0644]
submodule.h [new file with mode: 0644]
symlinks.c
t/.gitignore
t/Makefile
t/README
t/aggregate-results.sh
t/diff-lib.sh
t/gitweb-lib.sh [new file with mode: 0644]
t/harness [new file with mode: 0755]
t/lib-cvs.sh [new file with mode: 0644]
t/lib-git-svn.sh
t/lib-httpd.sh
t/lib-httpd/apache.conf
t/lib-pager.sh [new file with mode: 0644]
t/lib-patch-mode.sh [new file with mode: 0644]
t/lib-prereq-FILEMODE.sh [new file with mode: 0644]
t/lib-rebase.sh
t/lib-t6000.sh [new file with mode: 0644]
t/t0000-basic.sh
t/t0001-init.sh
t/t0003-attributes.sh
t/t0004-unwritable.sh
t/t0005-signals.sh
t/t0006-date.sh [new file with mode: 0755]
t/t0020-crlf.sh
t/t0021-conversion.sh
t/t0022-crlf-rename.sh
t/t0025-crlf-auto.sh [new file with mode: 0755]
t/t0026-eol-config.sh [new file with mode: 0755]
t/t0040-parse-options.sh
t/t0050-filesystem.sh
t/t0061-run-command.sh [new file with mode: 0755]
t/t0080-vcs-svn.sh [new file with mode: 0755]
t/t0101-at-syntax.sh [new file with mode: 0755]
t/t1000-read-tree-m-3way.sh
t/t1001-read-tree-m-2way.sh
t/t1002-read-tree-m-u-2way.sh
t/t1004-read-tree-m-u-wf.sh
t/t1007-hash-object.sh
t/t1009-read-tree-new-index.sh [new file with mode: 0755]
t/t1010-mktree.sh [new file with mode: 0755]
t/t1011-read-tree-sparse-checkout.sh [new file with mode: 0755]
t/t1012-read-tree-df.sh [new file with mode: 0755]
t/t1020-subdirectory.sh
t/t1200-tutorial.sh
t/t1300-repo-config.sh
t/t1302-repo-version.sh
t/t1304-default-acl.sh [new file with mode: 0755]
t/t1402-check-ref-format.sh [new file with mode: 0755]
t/t1410-reflog.sh
t/t1411-reflog-show.sh
t/t1450-fsck.sh
t/t1501-worktree.sh
t/t1502-rev-parse-parseopt.sh
t/t1503-rev-parse-verify.sh
t/t1506-rev-parse-diagnosis.sh [new file with mode: 0755]
t/t1507-rev-parse-upstream.sh [new file with mode: 0755]
t/t1508-at-combinations.sh [new file with mode: 0755]
t/t1509-root-worktree.sh [new file with mode: 0755]
t/t1509/excludes [new file with mode: 0644]
t/t1509/prepare-chroot.sh [new file with mode: 0755]
t/t2000-checkout-cache-clash.sh
t/t2007-checkout-symlink.sh
t/t2012-checkout-last.sh
t/t2013-checkout-submodule.sh
t/t2014-switch.sh [new file with mode: 0755]
t/t2015-checkout-unborn.sh [new file with mode: 0755]
t/t2016-checkout-patch.sh [new file with mode: 0755]
t/t2017-checkout-orphan.sh [new file with mode: 0755]
t/t2018-checkout-branch.sh [new file with mode: 0755]
t/t2030-unresolve-info.sh [new file with mode: 0755]
t/t2101-update-index-reupdate.sh
t/t2102-update-index-symlinks.sh
t/t2104-update-index-skip-worktree.sh [new file with mode: 0755]
t/t2105-update-index-gitfile.sh [new file with mode: 0755]
t/t2106-update-index-assume-unchanged.sh [new file with mode: 0755]
t/t2200-add-update.sh
t/t2204-add-ignored.sh [new file with mode: 0755]
t/t2300-cd-to-toplevel.sh
t/t3000-ls-files-others.sh
t/t3001-ls-files-others-exclude.sh
t/t3020-ls-files-error-unmatch.sh
t/t3030-merge-recursive.sh
t/t3031-merge-criscross.sh [new file with mode: 0755]
t/t3060-ls-files-with-tree.sh
t/t3100-ls-tree-restrict.sh
t/t3101-ls-tree-dirname.sh
t/t3200-branch.sh
t/t3202-show-branch-octopus.sh
t/t3210-pack-refs.sh
t/t3300-funny-names.sh
t/t3301-notes.sh [new file with mode: 0755]
t/t3302-notes-index-expensive.sh [new file with mode: 0755]
t/t3303-notes-subtrees.sh [new file with mode: 0755]
t/t3304-notes-mixed.sh [new file with mode: 0755]
t/t3305-notes-fanout.sh [new file with mode: 0755]
t/t3306-notes-prune.sh [new file with mode: 0755]
t/t3307-notes-man.sh [new file with mode: 0755]
t/t3400-rebase.sh
t/t3402-rebase-merge.sh
t/t3404-rebase-interactive.sh
t/t3407-rebase-abort.sh
t/t3408-rebase-multi-line.sh
t/t3409-rebase-preserve-merges.sh
t/t3410-rebase-preserve-dropped-merges.sh
t/t3411-rebase-preserve-around-merges.sh
t/t3414-rebase-preserve-onto.sh [new file with mode: 0755]
t/t3415-rebase-autosquash.sh [new file with mode: 0755]
t/t3416-rebase-onto-threedots.sh [new file with mode: 0755]
t/t3417-rebase-whitespace-fix.sh [new file with mode: 0755]
t/t3418-rebase-continue.sh [new file with mode: 0755]
t/t3500-cherry.sh
t/t3501-revert-cherry-pick.sh
t/t3505-cherry-pick-empty.sh
t/t3506-cherry-pick-ff.sh [new file with mode: 0755]
t/t3507-cherry-pick-conflict.sh [new file with mode: 0755]
t/t3508-cherry-pick-many-commits.sh [new file with mode: 0755]
t/t3509-cherry-pick-merge-df.sh [new file with mode: 0755]
t/t3600-rm.sh
t/t3700-add.sh
t/t3701-add-interactive.sh
t/t3702-add-edit.sh [new file with mode: 0755]
t/t3800-mktag.sh
t/t3900-i18n-commit.sh
t/t3900/EUCJP.txt [deleted file]
t/t3900/ISO-8859-1.txt [deleted file]
t/t3900/ISO8859-1.txt [new file with mode: 0644]
t/t3900/eucJP.txt [new file with mode: 0644]
t/t3901-i18n-patch.sh
t/t3902-quoted.sh
t/t3903-stash.sh
t/t3904-stash-patch.sh [new file with mode: 0755]
t/t4002-diff-basic.sh
t/t4004-diff-rename-symlink.sh
t/t4006-diff-mode.sh
t/t4007-rename-3.sh
t/t4011-diff-symlink.sh
t/t4012-diff-binary.sh
t/t4013-diff-various.sh
t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^
t/t4013/diff.log_--decorate=full_--all [new file with mode: 0644]
t/t4013/diff.log_--decorate_--all
t/t4013/diff.log_-S_F_master [new file with mode: 0644]
t/t4013/diff.log_-m_-p_--first-parent_master [new file with mode: 0644]
t/t4013/diff.log_-m_-p_master [new file with mode: 0644]
t/t4013/diff.log_-p_--first-parent_master [new file with mode: 0644]
t/t4013/diff.show_--first-parent_master [new file with mode: 0644]
t/t4013/diff.show_-c_master [new file with mode: 0644]
t/t4013/diff.show_-m_master [new file with mode: 0644]
t/t4014-format-patch.sh
t/t4015-diff-whitespace.sh
t/t4016-diff-quote.sh
t/t4017-diff-retval.sh
t/t4018-diff-funcname.sh
t/t4019-diff-wserror.sh
t/t4020-diff-external.sh
t/t4021-format-patch-numbered.sh
t/t4023-diff-rename-typechange.sh
t/t4026-color.sh
t/t4027-diff-submodule.sh
t/t4030-diff-textconv.sh
t/t4031-diff-rewrite-binary.sh
t/t4034-diff-words.sh
t/t4037-diff-r-t-dirs.sh [new file with mode: 0755]
t/t4038-diff-combined.sh [new file with mode: 0755]
t/t4039-diff-assume-unchanged.sh [new file with mode: 0755]
t/t4040-whitespace-status.sh [new file with mode: 0755]
t/t4041-diff-submodule-option.sh [new file with mode: 0755]
t/t4042-diff-textconv-caching.sh [new file with mode: 0755]
t/t4043-diff-rename-binary.sh [new file with mode: 0755]
t/t4044-diff-index-unique-abbrev.sh [new file with mode: 0755]
t/t4045-diff-relative.sh [new file with mode: 0755]
t/t4102-apply-rename.sh
t/t4103-apply-binary.sh
t/t4104-apply-boundary.sh
t/t4107-apply-ignore-whitespace.sh [new file with mode: 0755]
t/t4111-apply-subdir.sh [new file with mode: 0755]
t/t4114-apply-typechange.sh
t/t4115-apply-symlink.sh
t/t4120-apply-popt.sh
t/t4122-apply-symlink-inside.sh
t/t4124-apply-ws-rule.sh
t/t4125-apply-ws-fuzz.sh
t/t4127-apply-same-fn.sh
t/t4128-apply-root.sh
t/t4129-apply-samemode.sh
t/t4131-apply-fake-ancestor.sh [new file with mode: 0755]
t/t4132-apply-removal.sh [new file with mode: 0755]
t/t4133-apply-filenames.sh [new file with mode: 0755]
t/t4134-apply-submodule.sh [new file with mode: 0755]
t/t4135-apply-weird-filenames.sh [new file with mode: 0755]
t/t4135/.gitignore [new file with mode: 0644]
t/t4135/add-plain.diff [new file with mode: 0644]
t/t4135/add-with backslash.diff [new file with mode: 0644]
t/t4135/add-with quote.diff [new file with mode: 0644]
t/t4135/add-with spaces.diff [new file with mode: 0644]
t/t4135/add-with tab.diff [new file with mode: 0644]
t/t4135/damaged.diff [new file with mode: 0644]
t/t4135/diff-plain.diff [new file with mode: 0644]
t/t4135/diff-with backslash.diff [new file with mode: 0644]
t/t4135/diff-with quote.diff [new file with mode: 0644]
t/t4135/diff-with spaces.diff [new file with mode: 0644]
t/t4135/diff-with tab.diff [new file with mode: 0644]
t/t4135/git-plain.diff [new file with mode: 0644]
t/t4135/git-with backslash.diff [new file with mode: 0644]
t/t4135/git-with quote.diff [new file with mode: 0644]
t/t4135/git-with spaces.diff [new file with mode: 0644]
t/t4135/git-with tab.diff [new file with mode: 0644]
t/t4135/make-patches [new file with mode: 0755]
t/t4150-am.sh
t/t4151-am-abort.sh
t/t4200-rerere.sh
t/t4201-shortlog.sh
t/t4202-log.sh
t/t4204-patch-id.sh
t/t4205-log-pretty-formats.sh [new file with mode: 0755]
t/t4206-log-follow-harder-copies.sh [new file with mode: 0755]
t/t4207-log-decoration-colors.sh [new file with mode: 0755]
t/t4253-am-keep-cr-dos.sh [new file with mode: 0755]
t/t4300-merge-tree.sh [new file with mode: 0755]
t/t5000-tar-tree.sh
t/t5001-archive-attr.sh
t/t5100-mailinfo.sh
t/t5100/.gitattributes [new file with mode: 0644]
t/t5100/0010 [deleted file]
t/t5100/info0014 [new file with mode: 0644]
t/t5100/info0014--scissors [new file with mode: 0644]
t/t5100/info0015 [new file with mode: 0644]
t/t5100/info0015--no-inbody-headers [new file with mode: 0644]
t/t5100/info0016 [new file with mode: 0644]
t/t5100/info0016--no-inbody-headers [new file with mode: 0644]
t/t5100/msg0014 [new file with mode: 0644]
t/t5100/msg0014--scissors [new file with mode: 0644]
t/t5100/msg0015 [new file with mode: 0644]
t/t5100/msg0015--no-inbody-headers [new file with mode: 0644]
t/t5100/msg0016 [new file with mode: 0644]
t/t5100/msg0016--no-inbody-headers [new file with mode: 0644]
t/t5100/patch0014 [new file with mode: 0644]
t/t5100/patch0014--scissors [new file with mode: 0644]
t/t5100/patch0015 [new file with mode: 0644]
t/t5100/patch0015--no-inbody-headers [new file with mode: 0644]
t/t5100/patch0016 [new file with mode: 0644]
t/t5100/patch0016--no-inbody-headers [new file with mode: 0644]
t/t5100/rfc2047-samples.mbox
t/t5100/sample.mbox
t/t5150-request-pull.sh [new file with mode: 0755]
t/t5300-pack-object.sh
t/t5302-pack-index.sh
t/t5303-pack-corruption-resilience.sh
t/t5304-prune.sh
t/t5400-send-pack.sh
t/t5401-update-hooks.sh
t/t5403-post-checkout-hook.sh
t/t5405-send-pack-rewind.sh
t/t5407-post-rewrite-hook.sh [new file with mode: 0755]
t/t5500-fetch-pack.sh
t/t5502-quickfetch.sh
t/t5503-tagfollow.sh
t/t5505-remote.sh
t/t5506-remote-groups.sh
t/t5510-fetch.sh
t/t5512-ls-remote.sh
t/t5514-fetch-multiple.sh [new file with mode: 0755]
t/t5516-fetch-push.sh
t/t5517-push-mirror.sh
t/t5518-fetch-exit-status.sh
t/t5520-pull.sh
t/t5521-pull-options.sh
t/t5522-pull-symlink.sh
t/t5523-push-upstream.sh [new file with mode: 0755]
t/t5524-pull-msg.sh [new file with mode: 0755]
t/t5525-fetch-tagopt.sh [new file with mode: 0755]
t/t5530-upload-pack-error.sh
t/t5531-deep-submodule-push.sh [new file with mode: 0755]
t/t5540-http-push.sh
t/t5541-http-push.sh [new file with mode: 0755]
t/t5550-http-fetch.sh
t/t5551-http-fetch.sh [new file with mode: 0755]
t/t5560-http-backend-noserver.sh [new file with mode: 0755]
t/t5561-http-backend.sh [new file with mode: 0755]
t/t556x_common [new file with mode: 0755]
t/t5601-clone.sh
t/t5700-clone-reference.sh
t/t5701-clone-local.sh
t/t5702-clone-options.sh
t/t5704-bundle.sh
t/t5705-clone-2gb.sh
t/t5706-clone-branch.sh [new file with mode: 0755]
t/t5800-remote-helpers.sh [new file with mode: 0755]
t/t6000lib.sh [deleted file]
t/t6001-rev-list-graft.sh
t/t6002-rev-list-bisect.sh
t/t6003-rev-list-topo-order.sh
t/t6006-rev-list-format.sh
t/t6007-rev-list-cherry-pick-file.sh
t/t6010-merge-base.sh
t/t6012-rev-list-simplify.sh
t/t6015-rev-list-show-all-parents.sh [new file with mode: 0755]
t/t6016-rev-list-graph-simplify-history.sh [new file with mode: 0755]
t/t6017-rev-list-stdin.sh [new file with mode: 0755]
t/t6018-rev-list-glob.sh [new file with mode: 0755]
t/t6019-rev-list-ancestry-path.sh [new file with mode: 0755]
t/t6020-merge-df.sh
t/t6022-merge-rename.sh
t/t6023-merge-file.sh
t/t6024-recursive-merge.sh
t/t6026-merge-attr.sh
t/t6028-merge-up-to-date.sh
t/t6029-merge-subtree.sh
t/t6030-bisect-porcelain.sh
t/t6031-merge-recursive.sh
t/t6033-merge-crlf.sh
t/t6035-merge-dir-to-symlink.sh [new file with mode: 0755]
t/t6036-recursive-corner-cases.sh [new file with mode: 0755]
t/t6037-merge-ours-theirs.sh [new file with mode: 0755]
t/t6038-merge-text-auto.sh [new file with mode: 0755]
t/t6040-tracking-info.sh
t/t6050-replace.sh [new file with mode: 0755]
t/t6101-rev-parse-parents.sh
t/t6120-describe.sh
t/t6200-fmt-merge-msg.sh
t/t6300-for-each-ref.sh
t/t7001-mv.sh
t/t7002-grep.sh [deleted file]
t/t7003-filter-branch.sh
t/t7004-tag.sh
t/t7005-editor.sh
t/t7006-pager.sh [new file with mode: 0755]
t/t7006/test-terminal.perl [new file with mode: 0755]
t/t7008-grep-binary.sh [new file with mode: 0755]
t/t7010-setup.sh
t/t7011-skip-worktree-reading.sh [new file with mode: 0755]
t/t7012-skip-worktree-writing.sh [new file with mode: 0755]
t/t7060-wtstatus.sh [new file with mode: 0755]
t/t7102-reset.sh
t/t7103-reset-bare.sh
t/t7105-reset-patch.sh [new file with mode: 0755]
t/t7110-reset-merge.sh [new file with mode: 0755]
t/t7111-reset-table.sh [new file with mode: 0755]
t/t7201-co.sh
t/t7300-clean.sh
t/t7400-submodule-basic.sh
t/t7401-submodule-summary.sh
t/t7403-submodule-sync.sh
t/t7405-submodule-merge.sh
t/t7406-submodule-update.sh [new file with mode: 0755]
t/t7407-submodule-foreach.sh [new file with mode: 0755]
t/t7408-submodule-reference.sh [new file with mode: 0755]
t/t7500-commit.sh
t/t7501-commit.sh
t/t7502-commit.sh
t/t7506-status-submodule.sh
t/t7508-status.sh
t/t7509-commit.sh [new file with mode: 0755]
t/t7600-merge.sh
t/t7602-merge-octopus-many.sh
t/t7604-merge-custom-message.sh
t/t7606-merge-custom.sh
t/t7607-merge-overwrite.sh
t/t7608-merge-messages.sh [new file with mode: 0755]
t/t7609-merge-co-error-msgs.sh [new file with mode: 0755]
t/t7610-mergetool.sh
t/t7700-repack.sh
t/t7701-repack-unpack-unreachable.sh
t/t7800-difftool.sh
t/t7810-grep.sh [new file with mode: 0755]
t/t7811-grep-open.sh [new file with mode: 0755]
t/t8003-blame.sh
t/t8005-blame-i18n.sh
t/t8005/cp1251.txt [deleted file]
t/t8005/euc-japan.txt [new file with mode: 0644]
t/t8005/sjis.txt
t/t8005/utf8.txt
t/t8006-blame-textconv.sh [new file with mode: 0755]
t/t8007-cat-file-textconv.sh [new file with mode: 0755]
t/t9001-send-email.sh
t/t9010-svn-fe.sh [new file with mode: 0755]
t/t9100-git-svn-basic.sh
t/t9101-git-svn-props.sh
t/t9102-git-svn-deep-rmdir.sh
t/t9103-git-svn-tracked-directory-removed.sh
t/t9104-git-svn-follow-parent.sh
t/t9105-git-svn-commit-diff.sh
t/t9106-git-svn-commit-diff-clobber.sh
t/t9107-git-svn-migrate.sh
t/t9108-git-svn-glob.sh
t/t9109-git-svn-multi-glob.sh
t/t9113-git-svn-dcommit-new-file.sh
t/t9114-git-svn-dcommit-merge.sh
t/t9115-git-svn-dcommit-funky-renames.sh
t/t9116-git-svn-log.sh
t/t9117-git-svn-init-clone.sh
t/t9118-git-svn-funky-branch-names.sh
t/t9119-git-svn-info.sh
t/t9120-git-svn-clone-with-percent-escapes.sh
t/t9122-git-svn-author.sh
t/t9123-git-svn-rebuild-with-rewriteroot.sh
t/t9124-git-svn-dcommit-auto-props.sh
t/t9125-git-svn-multi-glob-branch-names.sh
t/t9127-git-svn-partial-rebuild.sh
t/t9128-git-svn-cmd-branch.sh
t/t9129-git-svn-i18n-commitencoding.sh
t/t9130-git-svn-authors-file.sh
t/t9133-git-svn-nested-git-repo.sh
t/t9134-git-svn-ignore-paths.sh
t/t9135-git-svn-moved-branch-empty-file.sh
t/t9137-git-svn-dcommit-clobber-series.sh
t/t9138-git-svn-authors-prog.sh [new file with mode: 0755]
t/t9139-git-svn-non-utf8-commitencoding.sh [new file with mode: 0755]
t/t9140-git-svn-reset.sh [new file with mode: 0755]
t/t9141-git-svn-multiple-branches.sh [new file with mode: 0755]
t/t9142-git-svn-shallow-clone.sh [new file with mode: 0755]
t/t9143-git-svn-gc.sh [new file with mode: 0755]
t/t9144-git-svn-old-rev_map.sh [new file with mode: 0755]
t/t9145-git-svn-master-branch.sh [new file with mode: 0755]
t/t9146-git-svn-empty-dirs.sh [new file with mode: 0755]
t/t9150-svk-mergetickets.sh [new file with mode: 0755]
t/t9150/make-svk-dump [new file with mode: 0644]
t/t9150/svk-merge.dump [new file with mode: 0644]
t/t9151-svn-mergeinfo.sh [new file with mode: 0755]
t/t9151/.gitignore [new file with mode: 0644]
t/t9151/make-svnmerge-dump [new file with mode: 0644]
t/t9151/svn-mergeinfo.dump [new file with mode: 0644]
t/t9152-svn-empty-dirs-after-gc.sh [new file with mode: 0755]
t/t9153-git-svn-rewrite-uuid.sh [new file with mode: 0755]
t/t9153/svn.dump [new file with mode: 0644]
t/t9154-git-svn-fancy-glob.sh [new file with mode: 0755]
t/t9154/svn.dump [new file with mode: 0644]
t/t9155-git-svn-fetch-deleted-tag.sh [new file with mode: 0755]
t/t9156-git-svn-fetch-deleted-tag-2.sh [new file with mode: 0755]
t/t9200-git-cvsexportcommit.sh
t/t9300-fast-import.sh
t/t9301-fast-export.sh [deleted file]
t/t9301-fast-import-notes.sh [new file with mode: 0755]
t/t9350-fast-export.sh [new file with mode: 0755]
t/t9400-git-cvsserver-server.sh
t/t9401-git-cvsserver-crlf.sh
t/t9500-gitweb-standalone-no-errors.sh
t/t9501-gitweb-standalone-http-status.sh [new file with mode: 0755]
t/t9502-gitweb-standalone-parse-output.sh [new file with mode: 0755]
t/t9600-cvsimport.sh
t/t9601-cvsimport-vendor-branch.sh [new file with mode: 0755]
t/t9601/cvsroot/.gitattributes [new file with mode: 0644]
t/t9601/cvsroot/CVSROOT/.gitignore [new file with mode: 0644]
t/t9601/cvsroot/module/added-imported.txt,v [new file with mode: 0644]
t/t9601/cvsroot/module/imported-anonymously.txt,v [new file with mode: 0644]
t/t9601/cvsroot/module/imported-modified-imported.txt,v [new file with mode: 0644]
t/t9601/cvsroot/module/imported-modified.txt,v [new file with mode: 0644]
t/t9601/cvsroot/module/imported-once.txt,v [new file with mode: 0644]
t/t9601/cvsroot/module/imported-twice.txt,v [new file with mode: 0644]
t/t9602-cvsimport-branches-tags.sh [new file with mode: 0755]
t/t9602/README [new file with mode: 0644]
t/t9602/cvsroot/.gitattributes [new file with mode: 0644]
t/t9602/cvsroot/CVSROOT/.gitignore [new file with mode: 0644]
t/t9602/cvsroot/module/default,v [new file with mode: 0644]
t/t9602/cvsroot/module/sub1/default,v [new file with mode: 0644]
t/t9602/cvsroot/module/sub1/subsubA/default,v [new file with mode: 0644]
t/t9602/cvsroot/module/sub1/subsubB/default,v [new file with mode: 0644]
t/t9602/cvsroot/module/sub2/Attic/branch_B_MIXED_only,v [new file with mode: 0644]
t/t9602/cvsroot/module/sub2/default,v [new file with mode: 0644]
t/t9602/cvsroot/module/sub2/subsubA/default,v [new file with mode: 0644]
t/t9602/cvsroot/module/sub3/default,v [new file with mode: 0644]
t/t9603-cvsimport-patchsets.sh [new file with mode: 0755]
t/t9603/cvsroot/.gitattributes [new file with mode: 0644]
t/t9603/cvsroot/CVSROOT/.gitignore [new file with mode: 0644]
t/t9603/cvsroot/module/a,v [new file with mode: 0644]
t/t9603/cvsroot/module/b,v [new file with mode: 0644]
t/t9700-perl-git.sh
t/t9700/test.pl
t/test-lib.sh
tag.c
tag.h
templates/Makefile
templates/hooks--commit-msg.sample
templates/hooks--post-receive.sample
templates/hooks--post-update.sample
templates/hooks--pre-commit.sample
templates/hooks--pre-rebase.sample
templates/hooks--prepare-commit-msg.sample
templates/hooks--update.sample
templates/info--exclude
test-chmtime.c
test-date.c
test-delta.c
test-genrandom.c
test-index-version.c [new file with mode: 0644]
test-line-buffer.c [new file with mode: 0644]
test-obj-pool.c [new file with mode: 0644]
test-parse-options.c
test-run-command.c [new file with mode: 0644]
test-sha1.c
test-string-pool.c [new file with mode: 0644]
test-svn-fe.c [new file with mode: 0644]
test-treap.c [new file with mode: 0644]
thread-utils.c
thread-utils.h
trace.c
transport-helper.c [new file with mode: 0644]
transport.c
transport.h
tree-diff.c
tree-walk.c
tree-walk.h
unpack-file.c [deleted file]
unpack-trees.c
unpack-trees.h
update-server-info.c [deleted file]
upload-pack.c
url.c [new file with mode: 0644]
url.h [new file with mode: 0644]
usage.c
userdiff.c
userdiff.h
utf8.c
utf8.h
var.c [deleted file]
vcs-svn/LICENSE [new file with mode: 0644]
vcs-svn/fast_export.c [new file with mode: 0644]
vcs-svn/fast_export.h [new file with mode: 0644]
vcs-svn/line_buffer.c [new file with mode: 0644]
vcs-svn/line_buffer.h [new file with mode: 0644]
vcs-svn/line_buffer.txt [new file with mode: 0644]
vcs-svn/obj_pool.h [new file with mode: 0644]
vcs-svn/repo_tree.c [new file with mode: 0644]
vcs-svn/repo_tree.h [new file with mode: 0644]
vcs-svn/string_pool.c [new file with mode: 0644]
vcs-svn/string_pool.h [new file with mode: 0644]
vcs-svn/string_pool.txt [new file with mode: 0644]
vcs-svn/svndump.c [new file with mode: 0644]
vcs-svn/svndump.h [new file with mode: 0644]
vcs-svn/trp.h [new file with mode: 0644]
vcs-svn/trp.txt [new file with mode: 0644]
walker.c
walker.h
wrap-for-bin.sh [new file with mode: 0644]
wrapper.c
write_or_die.c
ws.c
wt-status.c
wt-status.h
xdiff-interface.c
xdiff-interface.h
xdiff/xdiff.h
xdiff/xdiffi.c
xdiff/xmerge.c
xdiff/xutils.c

index 6b9c715d21d5486e59083fb6071566aa6ecd4d42..5e98806c6cc246acef5f539ae191710a0c06ad3f 100644 (file)
@@ -1,2 +1,3 @@
 * whitespace=!indent,trail,space
-*.[ch] whitespace
+*.[ch] whitespace=indent,trail,space
+*.sh whitespace=indent,trail,space
index 41c0b20a76ce0fd47c7cafc51777336ce99ce1b0..20560b810b0471c3b7d012bf0f14d7885e2b83ec 100644 (file)
-GIT-BUILD-OPTIONS
-GIT-CFLAGS
-GIT-GUI-VARS
-GIT-VERSION-FILE
-git
-git-add
-git-add--interactive
-git-am
-git-annotate
-git-apply
-git-archimport
-git-archive
-git-bisect
-git-bisect--helper
-git-blame
-git-branch
-git-bundle
-git-cat-file
-git-check-attr
-git-check-ref-format
-git-checkout
-git-checkout-index
-git-cherry
-git-cherry-pick
-git-clean
-git-clone
-git-commit
-git-commit-tree
-git-config
-git-count-objects
-git-cvsexportcommit
-git-cvsimport
-git-cvsserver
-git-daemon
-git-diff
-git-diff-files
-git-diff-index
-git-diff-tree
-git-difftool
-git-difftool--helper
-git-describe
-git-fast-export
-git-fast-import
-git-fetch
-git-fetch--tool
-git-fetch-pack
-git-filter-branch
-git-fmt-merge-msg
-git-for-each-ref
-git-format-patch
-git-fsck
-git-fsck-objects
-git-gc
-git-get-tar-commit-id
-git-grep
-git-hash-object
-git-help
-git-http-fetch
-git-http-push
-git-imap-send
-git-index-pack
-git-init
-git-init-db
-git-instaweb
-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-file
-git-merge-tree
-git-merge-octopus
-git-merge-one-file
-git-merge-ours
-git-merge-recursive
-git-merge-resolve
-git-merge-subtree
-git-mergetool
-git-mergetool--lib
-git-mktag
-git-mktree
-git-name-rev
-git-mv
-git-pack-redundant
-git-pack-objects
-git-pack-refs
-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-rebase--interactive
-git-receive-pack
-git-reflog
-git-relink
-git-remote
-git-repack
-git-repo-config
-git-request-pull
-git-rerere
-git-reset
-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-show-ref
-git-stage
-git-stash
-git-status
-git-stripspace
-git-submodule
-git-svn
-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-archive
-git-upload-pack
-git-var
-git-verify-pack
-git-verify-tag
-git-web--browse
-git-whatchanged
-git-write-tree
-git-core-*/?*
-gitk-wish
-gitweb/gitweb.cgi
-test-chmtime
-test-ctype
-test-date
-test-delta
-test-dump-cache-tree
-test-genrandom
-test-match-trees
-test-parse-options
-test-path-utils
-test-sha1
-test-sigchain
-common-cmds.h
+/GIT-BUILD-OPTIONS
+/GIT-CFLAGS
+/GIT-GUI-VARS
+/GIT-VERSION-FILE
+/bin-wrappers/
+/git
+/git-add
+/git-add--interactive
+/git-am
+/git-annotate
+/git-apply
+/git-archimport
+/git-archive
+/git-bisect
+/git-bisect--helper
+/git-blame
+/git-branch
+/git-bundle
+/git-cat-file
+/git-check-attr
+/git-check-ref-format
+/git-checkout
+/git-checkout-index
+/git-cherry
+/git-cherry-pick
+/git-clean
+/git-clone
+/git-commit
+/git-commit-tree
+/git-config
+/git-count-objects
+/git-cvsexportcommit
+/git-cvsimport
+/git-cvsserver
+/git-daemon
+/git-diff
+/git-diff-files
+/git-diff-index
+/git-diff-tree
+/git-difftool
+/git-difftool--helper
+/git-describe
+/git-fast-export
+/git-fast-import
+/git-fetch
+/git-fetch--tool
+/git-fetch-pack
+/git-filter-branch
+/git-fmt-merge-msg
+/git-for-each-ref
+/git-format-patch
+/git-fsck
+/git-fsck-objects
+/git-gc
+/git-get-tar-commit-id
+/git-grep
+/git-hash-object
+/git-help
+/git-http-backend
+/git-http-fetch
+/git-http-push
+/git-imap-send
+/git-index-pack
+/git-init
+/git-init-db
+/git-instaweb
+/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-file
+/git-merge-tree
+/git-merge-octopus
+/git-merge-one-file
+/git-merge-ours
+/git-merge-recursive
+/git-merge-resolve
+/git-merge-subtree
+/git-mergetool
+/git-mergetool--lib
+/git-mktag
+/git-mktree
+/git-name-rev
+/git-mv
+/git-notes
+/git-pack-redundant
+/git-pack-objects
+/git-pack-refs
+/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-rebase--interactive
+/git-receive-pack
+/git-reflog
+/git-relink
+/git-remote
+/git-remote-curl
+/git-remote-http
+/git-remote-https
+/git-remote-ftp
+/git-remote-ftps
+/git-remote-testgit
+/git-repack
+/git-replace
+/git-repo-config
+/git-request-pull
+/git-rerere
+/git-reset
+/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-show-ref
+/git-stage
+/git-stash
+/git-status
+/git-stripspace
+/git-submodule
+/git-svn
+/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-archive
+/git-upload-pack
+/git-var
+/git-verify-pack
+/git-verify-tag
+/git-web--browse
+/git-whatchanged
+/git-write-tree
+/git-core-*/?*
+/gitk-git/gitk-wish
+/gitweb/GITWEB-BUILD-OPTIONS
+/gitweb/gitweb.cgi
+/gitweb/static/gitweb.min.*
+/test-chmtime
+/test-ctype
+/test-date
+/test-delta
+/test-dump-cache-tree
+/test-genrandom
+/test-index-version
+/test-line-buffer
+/test-match-trees
+/test-obj-pool
+/test-parse-options
+/test-path-utils
+/test-run-command
+/test-sha1
+/test-sigchain
+/test-string-pool
+/test-svn-fe
+/test-treap
+/common-cmds.h
 *.tar.gz
 *.dsc
 *.deb
-git.spec
+/git.spec
 *.exe
 *.[aos]
 *.py[co]
-config.mak
-autom4te.cache
-config.cache
-config.log
-config.status
-config.mak.autogen
-config.mak.append
-configure
-tags
-TAGS
-cscope*
+.depend/
+*.gcda
+*.gcno
+*.gcov
+/coverage-untested-functions
+/cover_db/
+/cover_db_html/
+*+
+/config.mak
+/autom4te.cache
+/config.cache
+/config.log
+/config.status
+/config.mak.autogen
+/config.mak.append
+/configure
+/tags
+/TAGS
+/cscope*
+*.obj
+*.lib
+*.sln
+*.suo
+*.ncb
+*.vcproj
+*.user
+*.idb
+*.pdb
+/Debug/
+/Release/
index 373476bdc03f718b4c01471dd9996ee4497f43a8..a8091eb5dfa430bf1b0537da47a31e7cf88d8622 100644 (file)
--- a/.mailmap
+++ b/.mailmap
@@ -5,6 +5,7 @@
 # same person appearing not to be so.
 #
 
+Alex Bennée <kernel-hacker@bennee.com>
 Alexander Gavrilov <angavrilov@gmail.com>
 Aneesh Kumar K.V <aneesh.kumar@gmail.com>
 Brian M. Carlson <sandals@crustytoothpaste.ath.cx>
@@ -15,6 +16,7 @@ Daniel Barkalow <barkalow@iabervon.org>
 David D. Kilzer <ddkilzer@kilzer.net>
 David Kågedal <davidk@lysator.liu.se>
 David S. Miller <davem@davemloft.net>
+Deskin Miller <deskinm@umich.edu>
 Dirk Süsserott <newsletter@dirk.my1.cc>
 Fredrik Kuivinen <freku045@student.liu.se>
 H. Peter Anvin <hpa@bonde.sc.orionmulti.com>
@@ -36,11 +38,13 @@ Li Hong <leehong@pku.edu.cn>
 Lukas Sandström <lukass@etek.chalmers.se>
 Martin Langhoff <martin@catalyst.net.nz>
 Michael Coleman <tutufan@gmail.com>
+Michael J Gruber <git@drmicha.warpmail.net> <michaeljgruber+gmane@fastmail.fm>
 Michael W. Olson <mwolson@gnu.org>
 Michele Ballabio <barra_cuda@katamail.com>
 Nanako Shiraishi <nanako3@bluebottle.com>
 Nanako Shiraishi <nanako3@lavabit.com>
 Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
+<nico@fluxnic.net> <nico@cam.org>
 Philippe Bruhat <book@cpan.org>
 Ramsay Allan Jones <ramsay@ramsay1.demon.co.uk>
 René Scharfe <rene.scharfe@lsrfire.ath.cx>
@@ -58,6 +62,7 @@ Uwe Kleine-König <ukleinek@informatik.uni-freiburg.de>
 Uwe Kleine-König <uzeisberger@io.fsforth.de>
 Uwe Kleine-König <zeisberg@informatik.uni-freiburg.de>
 Ville Skyttä <scop@xemacs.org>
+Vitaly "_Vi" Shukela <public_vi@tut.by>
 William Pursell <bill.pursell@gmail.com>
 YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
 anonymous <linux@horizon.com>
diff --git a/COPYING b/COPYING
index 6ff87c4664981e4397625791c8ea3bbb5f2279a3..536e55524db72bd2acf175208aef4f3dfc148d42 100644 (file)
--- a/COPYING
+++ b/COPYING
@@ -22,8 +22,8 @@
                    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
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  Everyone is permitted to copy and distribute verbatim copies
  of this license document, but changing it is not allowed.
 
@@ -36,7 +36,7 @@ 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
+the GNU Lesser General Public License instead.)  You can apply it to
 your programs, too.
 
   When we speak of free software, we are referring to freedom, not
@@ -76,7 +76,7 @@ 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
 
@@ -131,7 +131,7 @@ above, provided that you also meet all of these conditions:
     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
@@ -189,7 +189,7 @@ 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
@@ -246,7 +246,7 @@ 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
@@ -299,7 +299,7 @@ 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
@@ -324,10 +324,9 @@ the "copyright" line and a pointer to where the full notice is found.
     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
-
+    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.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
 Also add information on how to contact you by electronic and paper mail.
 
@@ -357,5 +356,5 @@ necessary.  Here is a sample; alter the names:
 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
+library.  If this is what you want to do, use the GNU Lesser General
 Public License instead of this License.
index d8edd904065fbc4bd06365ce378f57d4cd8f9f0d..1c3a9fead579a9b52037f1bbe245998db8a2f40b 100644 (file)
@@ -8,3 +8,4 @@ gitman.info
 howto-index.txt
 doc.dep
 cmds-*.txt
+manpage-base-url.xsl
index 7a8037f586773c00004e34079b48bb514db2515f..e117bc4315f6e288e89cf83fb80e876bee7a7dc2 100644 (file)
@@ -6,7 +6,7 @@ MAN5_TXT=gitattributes.txt gitignore.txt gitmodules.txt githooks.txt \
        gitrepository-layout.txt
 MAN7_TXT=gitcli.txt gittutorial.txt gittutorial-2.txt \
        gitcvs-migration.txt gitcore-tutorial.txt gitglossary.txt \
-       gitdiffcore.txt gitworkflows.txt
+       gitdiffcore.txt gitrevisions.txt gitworkflows.txt
 
 MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)
 MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT))
@@ -17,6 +17,7 @@ DOC_HTML=$(MAN_HTML)
 ARTICLES = howto-index
 ARTICLES += everyday
 ARTICLES += git-tools
+ARTICLES += git-bisect-lk2009
 # with their own formatting rules.
 SP_ARTICLES = howto/revert-branch-rebase howto/using-merge-subtree user-manual
 API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt)))
@@ -84,7 +85,7 @@ endif
 #
 
 ifdef ASCIIDOC8
-ASCIIDOC_EXTRA += -a asciidoc7compatible
+ASCIIDOC_EXTRA += -a asciidoc7compatible -a no-inline-literal
 endif
 ifdef DOCBOOK_XSL_172
 ASCIIDOC_EXTRA += -a git-asciidoc-no-roff
@@ -103,6 +104,25 @@ ifdef DOCBOOK_SUPPRESS_SP
 XMLTO_EXTRA += -m manpage-suppress-sp.xsl
 endif
 
+# Newer DocBook stylesheet emits warning cruft in the output when
+# this is not set, and if set it shows an absolute link.  Older
+# stylesheets simply ignore this parameter.
+#
+# Distros may want to use MAN_BASE_URL=file:///path/to/git/docs/
+# or similar.
+ifndef MAN_BASE_URL
+MAN_BASE_URL = file://$(htmldir)/
+endif
+XMLTO_EXTRA += -m manpage-base-url.xsl
+
+# If your target system uses GNU groff, it may try to render
+# apostrophes as a "pretty" apostrophe using unicode.  This breaks
+# cut&paste, so you should set GNU_ROFF to force them to be ASCII
+# apostrophes.  Unfortunately does not work with non-GNU roff.
+ifdef GNU_ROFF
+XMLTO_EXTRA += -m manpage-quote-apos.xsl
+endif
+
 SHELL_PATH ?= $(SHELL)
 # Shell quote;
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
@@ -184,7 +204,7 @@ install-pdf: pdf
 install-html: html
        '$(SHELL_PATH_SQ)' ./install-webdoc.sh $(DESTDIR)$(htmldir)
 
-../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
+../GIT-VERSION-FILE: FORCE
        $(QUIET_SUBDIR0)../ $(QUIET_SUBDIR1) GIT-VERSION-FILE
 
 -include ../GIT-VERSION-FILE
@@ -222,6 +242,7 @@ clean:
        $(RM) howto-index.txt howto/*.html doc.dep
        $(RM) technical/api-*.html technical/api-index.txt
        $(RM) $(cmds_txt) *.made
+       $(RM) manpage-base-url.xsl
 
 $(MAN_HTML): %.html : %.txt
        $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
@@ -229,7 +250,10 @@ $(MAN_HTML): %.html : %.txt
                $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $< && \
        mv $@+ $@
 
-%.1 %.5 %.7 : %.xml
+manpage-base-url.xsl: manpage-base-url.xsl.in
+       sed "s|@@MAN_BASE_URL@@|$(MAN_BASE_URL)|" $< > $@
+
+%.1 %.5 %.7 : %.xml manpage-base-url.xsl
        $(QUIET_XMLTO)$(RM) $@ && \
        xmlto -m $(MANPAGE_XSL) $(XMLTO_EXTRA) man $<
 
@@ -240,7 +264,9 @@ $(MAN_HTML): %.html : %.txt
        mv $@+ $@
 
 user-manual.xml: user-manual.txt user-manual.conf
-       $(QUIET_ASCIIDOC)$(ASCIIDOC) $(ASCIIDOC_EXTRA) -b docbook -d book $<
+       $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
+       $(ASCIIDOC) $(ASCIIDOC_EXTRA) -b docbook -d book -o $@+ $< && \
+       mv $@+ $@
 
 technical/api-index.txt: technical/api-index-skel.txt \
        technical/api-index.sh $(patsubst %,%.txt,$(API_DOCS))
@@ -253,8 +279,10 @@ $(patsubst %,%.html,$(API_DOCS) technical/api-index): %.html : %.txt
 XSLT = docbook.xsl
 XSLTOPTS = --xinclude --stringparam html.stylesheet docbook-xsl.css
 
-user-manual.html: user-manual.xml
-       $(QUIET_XSLTPROC)xsltproc $(XSLTOPTS) -o $@ $(XSLT) $<
+user-manual.html: user-manual.xml $(XSLT)
+       $(QUIET_XSLTPROC)$(RM) $@+ $@ && \
+       xsltproc $(XSLTOPTS) -o $@+ $(XSLT) $< && \
+       mv $@+ $@
 
 git.info: user-manual.texi
        $(QUIET_MAKEINFO)$(MAKEINFO) --no-split -o $@ user-manual.texi
@@ -313,4 +341,4 @@ quick-install-man:
 quick-install-html:
        '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REF) $(DESTDIR)$(htmldir)
 
-.PHONY: .FORCE-GIT-VERSION-FILE
+.PHONY: FORCE
diff --git a/Documentation/RelNotes-1.5.0.1.txt b/Documentation/RelNotes-1.5.0.1.txt
deleted file mode 100644 (file)
index fea3f99..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-GIT v1.5.0.1 Release Notes
-==========================
-
-Fixes since v1.5.0
-------------------
-
-* Documentation updates
-
-  - Clarifications and corrections to 1.5.0 release notes.
-
-  - The main documentation did not link to git-remote documentation.
-
-  - Clarified introductory text of git-rebase documentation.
-
-  - Converted remaining mentions of update-index on Porcelain
-    documents to git-add/git-rm.
-
-  - Some i18n.* configuration variables were incorrectly
-    described as core.*; fixed.
-
-* Bugfixes
-
-  - git-add and git-update-index on a filesystem on which
-    executable bits are unreliable incorrectly reused st_mode
-    bits even when the path changed between symlink and regular
-    file.
-
-  - git-daemon marks the listening sockets with FD_CLOEXEC so
-    that it won't be leaked into the children.
-
-  - segfault from git-blame when the mandatory pathname
-    parameter was missing was fixed; usage() message is given
-    instead.
-
-  - git-rev-list did not read $GIT_DIR/config file, which means
-    that did not honor i18n.logoutputencoding correctly.
-
-* Tweaks
-
-  - sliding mmap() inefficiently mmaped the same region of a
-    packfile with an access pattern that used objects in the
-    reverse order.  This has been made more efficient.
diff --git a/Documentation/RelNotes-1.5.0.2.txt b/Documentation/RelNotes-1.5.0.2.txt
deleted file mode 100644 (file)
index b061e50..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-GIT v1.5.0.2 Release Notes
-==========================
-
-Fixes since v1.5.0.1
---------------------
-
-* Bugfixes
-
-  - Automated merge conflict handling when changes to symbolic
-    links conflicted were completely broken.  The merge-resolve
-    strategy created a regular file with conflict markers in it
-    in place of the symbolic link.  The default strategy,
-    merge-recursive was even more broken.  It removed the path
-    that was pointed at by the symbolic link.  Both of these
-    problems have been fixed.
-
-  - 'git diff maint master next' did not correctly give combined
-    diff across three trees.
-
-  - 'git fast-import' portability fix for Solaris.
-
-  - 'git show-ref --verify' without arguments did not error out
-    but segfaulted.
-
-  - 'git diff :tracked-file `pwd`/an-untracked-file' gave an extra
-    slashes after a/ and b/.
-
-  - 'git format-patch' produced too long filenames if the commit
-    message had too long line at the beginning.
-
-  - Running 'make all' and then without changing anything
-    running 'make install' still rebuilt some files.  This
-    was inconvenient when building as yourself and then
-    installing as root (especially problematic when the source
-    directory is on NFS and root is mapped to nobody).
-
-  - 'git-rerere' failed to deal with two unconflicted paths that
-    sorted next to each other.
-
-  - 'git-rerere' attempted to open(2) a symlink and failed if
-    there was a conflict.  Since a conflicting change to a
-    symlink would not benefit from rerere anyway, the command
-    now ignores conflicting changes to symlinks.
-
-  - 'git-repack' did not like to pass more than 64 arguments
-    internally to underlying 'rev-list' logic, which made it
-    impossible to repack after accumulating many (small) packs
-    in the repository.
-
-  - 'git-diff' to review the combined diff during a conflicted
-    merge were not reading the working tree version correctly
-    when changes to a symbolic link conflicted.  It should have
-    read the data using readlink(2) but read from the regular
-    file the symbolic link pointed at.
-
-  - 'git-remote' did not like period in a remote's name.
-
-* Documentation updates
-
-  - added and clarified core.bare, core.legacyheaders configurations.
-
-  - updated "git-clone --depth" documentation.
-
-
-* Assorted git-gui fixes.
diff --git a/Documentation/RelNotes-1.5.0.3.txt b/Documentation/RelNotes-1.5.0.3.txt
deleted file mode 100644 (file)
index cd500f9..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-GIT v1.5.0.3 Release Notes
-==========================
-
-Fixes since v1.5.0.2
---------------------
-
-* Bugfixes
-
-  - 'git.el' honors the commit coding system from the configuration.
-
-  - 'blameview' in contrib/ correctly digs deeper when a line is
-    clicked.
-
-  - 'http-push' correctly makes sure the remote side has leading
-    path.  Earlier it started in the middle of the path, and
-    incorrectly.
-
-  - 'git-merge' did not exit with non-zero status when the
-    working tree was dirty and cannot fast forward.  It does
-    now.
-
-  - 'cvsexportcommit' does not lose yet-to-be-used message file.
-
-  - int-vs-size_t typefix when running combined diff on files
-    over 2GB long.
-
-  - 'git apply --whitespace=strip' should not touch unmodified
-    lines.
-
-  - 'git-mailinfo' choke when a logical header line was too long.
-
-  - 'git show A..B' did not error out.  Negative ref ("not A" in
-    this example) does not make sense for the purpose of the
-    command, so now it errors out.
-
-  - 'git fmt-merge-msg --file' without file parameter did not
-    correctly error out.
-
-  - 'git archimport' barfed upon encountering a commit without
-    summary.
-
-  - 'git index-pack' did not protect itself from getting a short
-    read out of pread(2).
-
-  - 'git http-push' had a few buffer overruns.
-
-  - Build dependency fixes to rebuild fetch.o when other headers
-    change.
-
-* Documentation updates
-
-  - user-manual updates.
-
-  - Options to 'git remote add' were described insufficiently.
-
-  - Configuration format.suffix was not documented.
-
-  - Other formatting and spelling fixes.
diff --git a/Documentation/RelNotes-1.5.0.4.txt b/Documentation/RelNotes-1.5.0.4.txt
deleted file mode 100644 (file)
index feefa5d..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-GIT v1.5.0.4 Release Notes
-==========================
-
-Fixes since v1.5.0.3
---------------------
-
-* Bugfixes
-
-  - git.el does not add duplicate sign-off lines.
-
-  - git-commit shows the full stat of the resulting commit, not
-    just about the files in the current directory, when run from
-    a subdirectory.
-
-  - "git-checkout -m '@{8 hours ago}'" had a funny failure from
-    eval; fixed.
-
-  - git-gui updates.
-
-* Documentation updates
-
-* User manual updates
diff --git a/Documentation/RelNotes-1.5.0.5.txt b/Documentation/RelNotes-1.5.0.5.txt
deleted file mode 100644 (file)
index eeec3d7..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-GIT v1.5.0.5 Release Notes
-==========================
-
-Fixes since v1.5.0.3
---------------------
-
-* Bugfixes
-
-  - git-merge (hence git-pull) did not refuse fast-forwarding
-    when the working tree had local changes that would have
-    conflicted with it.
-
-  - git.el does not add duplicate sign-off lines.
-
-  - git-commit shows the full stat of the resulting commit, not
-    just about the files in the current directory, when run from
-    a subdirectory.
-
-  - "git-checkout -m '@{8 hours ago}'" had a funny failure from
-    eval; fixed.
-
-  - git-gui updates.
-
-* Documentation updates
-
-* User manual updates
diff --git a/Documentation/RelNotes-1.5.0.6.txt b/Documentation/RelNotes-1.5.0.6.txt
deleted file mode 100644 (file)
index c02015a..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-GIT v1.5.0.6 Release Notes
-==========================
-
-Fixes since v1.5.0.5
---------------------
-
-* Bugfixes
-
-  - a handful small fixes to gitweb.
-
-  - build procedure for user-manual is fixed not to require locally
-    installed stylesheets.
-
-  - "git commit $paths" on paths whose earlier contents were
-    already updated in the index were failing out.
-
-* Documentation
-
-  - user-manual has better cross references.
-
-  - gitweb installation/deployment procedure is now documented.
diff --git a/Documentation/RelNotes-1.5.0.7.txt b/Documentation/RelNotes-1.5.0.7.txt
deleted file mode 100644 (file)
index 670ad32..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-GIT v1.5.0.7 Release Notes
-==========================
-
-Fixes since v1.5.0.6
---------------------
-
-* Bugfixes
-
-  - git-upload-pack failed to close unused pipe ends, resulting
-    in many zombies to hang around.
-
-  - git-rerere was recording the contents of earlier hunks
-    duplicated in later hunks.  This prevented resolving the same
-    conflict when performing the same merge the other way around.
-
-* Documentation
-
-  - a few documentation fixes from Debian package maintainer.
diff --git a/Documentation/RelNotes-1.5.0.txt b/Documentation/RelNotes-1.5.0.txt
deleted file mode 100644 (file)
index daf4bdb..0000000
+++ /dev/null
@@ -1,469 +0,0 @@
-GIT v1.5.0 Release Notes
-========================
-
-Old news
---------
-
-This section is for people who are upgrading from ancient
-versions of git.  Although all of the changes in this section
-happened before the current v1.4.4 release, they are summarized
-here in the v1.5.0 release notes for people who skipped earlier
-versions.
-
-As of git v1.5.0 there are some optional features that changes
-the repository to allow data to be stored and transferred more
-efficiently.  These features are not enabled by default, as they
-will make the repository unusable with older versions of git.
-Specifically, the available options are:
-
- - There is a configuration variable core.legacyheaders that
-   changes the format of loose objects so that they are more
-   efficient to pack and to send out of the repository over git
-   native protocol, since v1.4.2.  However, loose objects
-   written in the new format cannot be read by git older than
-   that version; people fetching from your repository using
-   older clients over dumb transports (e.g. http) using older
-   versions of git will also be affected.
-
-   To let git use the new loose object format, you have to
-   set core.legacyheaders to false.
-
- - Since v1.4.3, configuration repack.usedeltabaseoffset allows
-   packfile to be created in more space efficient format, which
-   cannot be read by git older than that version.
-
-   To let git use the new format for packfiles, you have to
-   set repack.usedeltabaseoffset to true.
-
-The above two new features are not enabled by default and you
-have to explicitly ask for them, because they make repositories
-unreadable by older versions of git, and in v1.5.0 we still do
-not enable them by default for the same reason.  We will change
-this default probably 1 year after 1.4.2's release, when it is
-reasonable to expect everybody to have new enough version of
-git.
-
- - 'git pack-refs' appeared in v1.4.4; this command allows tags
-   to be accessed much more efficiently than the traditional
-   'one-file-per-tag' format.  Older git-native clients can
-   still fetch from a repository that packed and pruned refs
-   (the server side needs to run the up-to-date version of git),
-   but older dumb transports cannot.  Packing of refs is done by
-   an explicit user action, either by use of "git pack-refs
-   --prune" command or by use of "git gc" command.
-
- - 'git -p' to paginate anything -- many commands do pagination
-   by default on a tty.  Introduced between v1.4.1 and v1.4.2;
-   this may surprise old timers.
-
- - 'git archive' superseded 'git tar-tree' in v1.4.3;
-
- - 'git cvsserver' was new invention in v1.3.0;
-
- - 'git repo-config', 'git grep', 'git rebase' and 'gitk' were
-   seriously enhanced during v1.4.0 timeperiod.
-
- - 'gitweb' became part of git.git during v1.4.0 timeperiod and
-   seriously modified since then.
-
- - reflog is an v1.4.0 invention.  This allows you to name a
-   revision that a branch used to be at (e.g. "git diff
-   master@{yesterday} master" allows you to see changes since
-   yesterday's tip of the branch).
-
-
-Updates in v1.5.0 since v1.4.4 series
--------------------------------------
-
-* Index manipulation
-
- - git-add is to add contents to the index (aka "staging area"
-   for the next commit), whether the file the contents happen to
-   be is an existing one or a newly created one.
-
- - git-add without any argument does not add everything
-   anymore.  Use 'git-add .' instead.  Also you can add
-   otherwise ignored files with an -f option.
-
- - git-add tries to be more friendly to users by offering an
-   interactive mode ("git-add -i").
-
- - git-commit <path> used to refuse to commit if <path> was
-   different between HEAD and the index (i.e. update-index was
-   used on it earlier).  This check was removed.
-
- - git-rm is much saner and safer.  It is used to remove paths
-   from both the index file and the working tree, and makes sure
-   you are not losing any local modification before doing so.
-
- - git-reset <tree> <paths>... can be used to revert index
-   entries for selected paths.
-
- - git-update-index is much less visible.  Many suggestions to
-   use the command in git output and documentation have now been
-   replaced by simpler commands such as "git add" or "git rm".
-
-
-* Repository layout and objects transfer
-
- - The data for origin repository is stored in the configuration
-   file $GIT_DIR/config, not in $GIT_DIR/remotes/, for newly
-   created clones.  The latter is still supported and there is
-   no need to convert your existing repository if you are
-   already comfortable with your workflow with the layout.
-
- - git-clone always uses what is known as "separate remote"
-   layout for a newly created repository with a working tree.
-
-   A repository with the separate remote layout starts with only
-   one default branch, 'master', to be used for your own
-   development.  Unlike the traditional layout that copied all
-   the upstream branches into your branch namespace (while
-   renaming their 'master' to your 'origin'), the new layout
-   puts upstream branches into local "remote-tracking branches"
-   with their own namespace. These can be referenced with names
-   such as "origin/$upstream_branch_name" and are stored in
-   .git/refs/remotes rather than .git/refs/heads where normal
-   branches are stored.
-
-   This layout keeps your own branch namespace less cluttered,
-   avoids name collision with your upstream, makes it possible
-   to automatically track new branches created at the remote
-   after you clone from it, and makes it easier to interact with
-   more than one remote repository (you can use "git remote" to
-   add other repositories to track).  There might be some
-   surprises:
-
-   * 'git branch' does not show the remote tracking branches.
-     It only lists your own branches.  Use '-r' option to view
-     the tracking branches.
-
-   * If you are forking off of a branch obtained from the
-     upstream, you would have done something like 'git branch
-     my-next next', because traditional layout dropped the
-     tracking branch 'next' into your own branch namespace.
-     With the separate remote layout, you say 'git branch next
-     origin/next', which allows you to use the matching name
-     'next' for your own branch.  It also allows you to track a
-     remote other than 'origin' (i.e. where you initially cloned
-     from) and fork off of a branch from there the same way
-     (e.g. "git branch mingw j6t/master").
-
-   Repositories initialized with the traditional layout continue
-   to work.
-
- - New branches that appear on the origin side after a clone is
-   made are also tracked automatically.  This is done with an
-   wildcard refspec "refs/heads/*:refs/remotes/origin/*", which
-   older git does not understand, so if you clone with 1.5.0,
-   you would need to downgrade remote.*.fetch in the
-   configuration file to specify each branch you are interested
-   in individually if you plan to fetch into the repository with
-   older versions of git (but why would you?).
-
- - Similarly, wildcard refspec "refs/heads/*:refs/remotes/me/*"
-   can be given to "git-push" command to update the tracking
-   branches that is used to track the repository you are pushing
-   from on the remote side.
-
- - git-branch and git-show-branch know remote tracking branches
-   (use the command line switch "-r" to list only tracked branches).
-
- - git-push can now be used to delete a remote branch or a tag.
-   This requires the updated git on the remote side (use "git
-   push <remote> :refs/heads/<branch>" to delete "branch").
-
- - git-push more aggressively keeps the transferred objects
-   packed.  Earlier we recommended to monitor amount of loose
-   objects and repack regularly, but you should repack when you
-   accumulated too many small packs this way as well.  Updated
-   git-count-objects helps you with this.
-
- - git-fetch also more aggressively keeps the transferred objects
-   packed.  This behavior of git-push and git-fetch can be
-   tweaked with a single configuration transfer.unpacklimit (but
-   usually there should not be any need for a user to tweak it).
-
- - A new command, git-remote, can help you manage your remote
-   tracking branch definitions.
-
- - You may need to specify explicit paths for upload-pack and/or
-   receive-pack due to your ssh daemon configuration on the
-   other end.  This can now be done via remote.*.uploadpack and
-   remote.*.receivepack configuration.
-
-
-* Bare repositories
-
- - Certain commands change their behavior in a bare repository
-   (i.e. a repository without associated working tree).  We use
-   a fairly conservative heuristic (if $GIT_DIR is ".git", or
-   ends with "/.git", the repository is not bare) to decide if a
-   repository is bare, but "core.bare" configuration variable
-   can be used to override the heuristic when it misidentifies
-   your repository.
-
- - git-fetch used to complain updating the current branch but
-   this is now allowed for a bare repository.  So is the use of
-   'git-branch -f' to update the current branch.
-
- - Porcelain-ish commands that require a working tree refuses to
-   work in a bare repository.
-
-
-* Reflog
-
- - Reflog records the history from the view point of the local
-   repository. In other words, regardless of the real history,
-   the reflog shows the history as seen by one particular
-   repository (this enables you to ask "what was the current
-   revision in _this_ repository, yesterday at 1pm?").  This
-   facility is enabled by default for repositories with working
-   trees, and can be accessed with the "branch@{time}" and
-   "branch@{Nth}" notation.
-
- - "git show-branch" learned showing the reflog data with the
-   new -g option.  "git log" has -g option to view reflog
-   entries in a more verbose manner.
-
- - git-branch knows how to rename branches and moves existing
-   reflog data from the old branch to the new one.
-
- - In addition to the reflog support in v1.4.4 series, HEAD
-   reference maintains its own log.  "HEAD@{5.minutes.ago}"
-   means the commit you were at 5 minutes ago, which takes
-   branch switching into account.  If you want to know where the
-   tip of your current branch was at 5 minutes ago, you need to
-   explicitly say its name (e.g. "master@{5.minutes.ago}") or
-   omit the refname altogether i.e. "@{5.minutes.ago}".
-
- - The commits referred to by reflog entries are now protected
-   against pruning.  The new command "git reflog expire" can be
-   used to truncate older reflog entries and entries that refer
-   to commits that have been pruned away previously with older
-   versions of git.
-
-   Existing repositories that have been using reflog may get
-   complaints from fsck-objects and may not be able to run
-   git-repack, if you had run git-prune from older git; please
-   run "git reflog expire --stale-fix --all" first to remove
-   reflog entries that refer to commits that are no longer in
-   the repository when that happens.
-
-
-* Crufts removal
-
- - We used to say "old commits are retrievable using reflog and
-   'master@{yesterday}' syntax as long as you haven't run
-   git-prune".  We no longer have to say the latter half of the
-   above sentence, as git-prune does not remove things reachable
-   from reflog entries.
-
- - There is a toplevel garbage collector script, 'git-gc', that
-   runs periodic cleanup functions, including 'git-repack -a -d',
-   'git-reflog expire', 'git-pack-refs --prune', and 'git-rerere
-   gc'.
-
- - The output from fsck ("fsck-objects" is called just "fsck"
-   now, but the old name continues to work) was needlessly
-   alarming in that it warned missing objects that are reachable
-   only from dangling objects.  This has been corrected and the
-   output is much more useful.
-
-
-* Detached HEAD
-
- - You can use 'git-checkout' to check out an arbitrary revision
-   or a tag as well, instead of named branches.  This will
-   dissociate your HEAD from the branch you are currently on.
-
-   A typical use of this feature is to "look around".  E.g.
-
-       $ git checkout v2.6.16
-       ... compile, test, etc.
-       $ git checkout v2.6.17
-       ... compile, test, etc.
-
- - After detaching your HEAD, you can go back to an existing
-   branch with usual "git checkout $branch".  Also you can
-   start a new branch using "git checkout -b $newbranch" to
-   start a new branch at that commit.
-
- - You can even pull from other repositories, make merges and
-   commits while your HEAD is detached.  Also you can use "git
-   reset" to jump to arbitrary commit, while still keeping your
-   HEAD detached.
-
-   Remember that a detached state is volatile, i.e. it will be forgotten
-   as soon as you move away from it with the checkout or reset command,
-   unless a branch is created from it as mentioned above.  It is also
-   possible to rescue a lost detached state from the HEAD reflog.
-
-
-* Packed refs
-
- - Repositories with hundreds of tags have been paying large
-   overhead, both in storage and in runtime, due to the
-   traditional one-ref-per-file format.  A new command,
-   git-pack-refs, can be used to "pack" them in more efficient
-   representation (you can let git-gc do this for you).
-
- - Clones and fetches over dumb transports are now aware of
-   packed refs and can download from repositories that use
-   them.
-
-
-* Configuration
-
- - configuration related to color setting are consolidated under
-   color.* namespace (older diff.color.*, status.color.* are
-   still supported).
-
- - 'git-repo-config' command is accessible as 'git-config' now.
-
-
-* Updated features
-
- - git-describe uses better criteria to pick a base ref.  It
-   used to pick the one with the newest timestamp, but now it
-   picks the one that is topologically the closest (that is,
-   among ancestors of commit C, the ref T that has the shortest
-   output from "git-rev-list T..C" is chosen).
-
- - git-describe gives the number of commits since the base ref
-   between the refname and the hash suffix.  E.g. the commit one
-   before v2.6.20-rc6 in the kernel repository is:
-
-       v2.6.20-rc5-306-ga21b069
-
-   which tells you that its object name begins with a21b069,
-   v2.6.20-rc5 is an ancestor of it (meaning, the commit
-   contains everything -rc5 has), and there are 306 commits
-   since v2.6.20-rc5.
-
- - git-describe with --abbrev=0 can be used to show only the
-   name of the base ref.
-
- - git-blame learned a new option, --incremental, that tells it
-   to output the blames as they are assigned.  A sample script
-   to use it is also included as contrib/blameview.
-
- - git-blame starts annotating from the working tree by default.
-
-
-* Less external dependency
-
- - We no longer require the "merge" program from the RCS suite.
-   All 3-way file-level merges are now done internally.
-
- - The original implementation of git-merge-recursive which was
-   in Python has been removed; we have a C implementation of it
-   now.
-
- - git-shortlog is no longer a Perl script.  It no longer
-   requires output piped from git-log; it can accept revision
-   parameters directly on the command line.
-
-
-* I18n
-
- - We have always encouraged the commit message to be encoded in
-   UTF-8, but the users are allowed to use legacy encoding as
-   appropriate for their projects.  This will continue to be the
-   case.  However, a non UTF-8 commit encoding _must_ be
-   explicitly set with i18n.commitencoding in the repository
-   where a commit is made; otherwise git-commit-tree will
-   complain if the log message does not look like a valid UTF-8
-   string.
-
- - The value of i18n.commitencoding in the originating
-   repository is recorded in the commit object on the "encoding"
-   header, if it is not UTF-8.  git-log and friends notice this,
-   and reencodes the message to the log output encoding when
-   displaying, if they are different.  The log output encoding
-   is determined by "git log --encoding=<encoding>",
-   i18n.logoutputencoding configuration, or i18n.commitencoding
-   configuration, in the decreasing order of preference, and
-   defaults to UTF-8.
-
- - Tools for e-mailed patch application now default to -u
-   behavior; i.e. it always re-codes from the e-mailed encoding
-   to the encoding specified with i18n.commitencoding.  This
-   unfortunately forces projects that have happily been using a
-   legacy encoding without setting i18n.commitencoding to set
-   the configuration, but taken with other improvement, please
-   excuse us for this very minor one-time inconvenience.
-
-
-* e-mailed patches
-
- - See the above I18n section.
-
- - git-format-patch now enables --binary without being asked.
-   git-am does _not_ default to it, as sending binary patch via
-   e-mail is unusual and is harder to review than textual
-   patches and it is prudent to require the person who is
-   applying the patch to explicitly ask for it.
-
- - The default suffix for git-format-patch output is now ".patch",
-   not ".txt".  This can be changed with --suffix=.txt option,
-   or setting the config variable "format.suffix" to ".txt".
-
-
-* Foreign SCM interfaces
-
- - git-svn now requires the Perl SVN:: libraries, the
-   command-line backend was too slow and limited.
-
- - the 'commit' subcommand of git-svn has been renamed to
-   'set-tree', and 'dcommit' is the recommended replacement for
-   day-to-day work.
-
- - git fast-import backend.
-
-
-* User support
-
- - Quite a lot of documentation updates.
-
- - Bash completion scripts have been updated heavily.
-
- - Better error messages for often used Porcelainish commands.
-
- - Git GUI.  This is a simple Tk based graphical interface for
-   common Git operations.
-
-
-* Sliding mmap
-
- - We used to assume that we can mmap the whole packfile while
-   in use, but with a large project this consumes huge virtual
-   memory space and truly huge ones would not fit in the
-   userland address space on 32-bit platforms.  We now mmap huge
-   packfile in pieces to avoid this problem.
-
-
-* Shallow clones
-
- - There is a partial support for 'shallow' repositories that
-   keeps only recent history.  A 'shallow clone' is created by
-   specifying how deep that truncated history should be
-   (e.g. "git clone --depth 5 git://some.where/repo.git").
-
-   Currently a shallow repository has number of limitations:
-
-   - Cloning and fetching _from_ a shallow clone are not
-     supported (nor tested -- so they might work by accident but
-     they are not expected to).
-
-   - Pushing from nor into a shallow clone are not expected to
-     work.
-
-   - Merging inside a shallow repository would work as long as a
-     merge base is found in the recent history, but otherwise it
-     will be like merging unrelated histories and may result in
-     huge conflicts.
-
-   but this would be more than adequate for people who want to
-   look at near the tip of a big project with a deep history and
-   send patches in e-mail format.
diff --git a/Documentation/RelNotes-1.5.1.1.txt b/Documentation/RelNotes-1.5.1.1.txt
deleted file mode 100644 (file)
index 9147121..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-GIT v1.5.1.1 Release Notes
-==========================
-
-Fixes since v1.5.1
-------------------
-
-* Documentation updates
-
-  - The --left-right option of rev-list and friends is documented.
-
-  - The documentation for cvsimport has been majorly improved.
-
-  - "git-show-ref --exclude-existing" was documented.
-
-* Bugfixes
-
-  - The implementation of -p option in "git cvsexportcommit" had
-    the meaning of -C (context reduction) option wrong, and
-    loosened the context requirements when it was told to be
-    strict.
-
-  - "git cvsserver" did not behave like the real cvsserver when
-    client side removed a file from the working tree without
-    doing anything else on the path.  In such a case, it should
-    restore it from the checked out revision.
-
-  - "git fsck" issued an alarming error message on detached
-    HEAD.  It is not an error since at least 1.5.0.
-
-  - "git send-email" produced of References header of unbounded length;
-    fixed this with line-folding.
-
-  - "git archive" to download from remote site should not
-    require you to be in a git repository, but it incorrectly
-    did.
-
-  - "git apply" ignored -p<n> for "diff --git" formatted
-    patches.
-
-  - "git rerere" recorded a conflict that had one side empty
-    (the other side adds) incorrectly; this made merging in the
-    other direction fail to use previously recorded resolution.
-
-  - t4200 test was broken where "wc -l" pads its output with
-    spaces.
-
-  - "git branch -m old new" to rename branch did not work
-    without a configuration file in ".git/config".
-
-  - The sample hook for notification e-mail was misnamed.
-
-  - gitweb did not show type-changing patch correctly in the
-    blobdiff view.
-
-  - git-svn did not error out with incorrect command line options.
-
-  - git-svn fell into an infinite loop when insanely long commit
-    message was found.
-
-  - git-svn dcommit and rebase was confused by patches that were
-    merged from another branch that is managed by git-svn.
-
-  - git-svn used to get confused when globbing remote branch/tag
-    spec (e.g. "branches = proj/branches/*:refs/remotes/origin/*")
-    is used and there was a plain file that matched the glob.
diff --git a/Documentation/RelNotes-1.5.1.2.txt b/Documentation/RelNotes-1.5.1.2.txt
deleted file mode 100644 (file)
index d884563..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-GIT v1.5.1.2 Release Notes
-==========================
-
-Fixes since v1.5.1.1
---------------------
-
-* Bugfixes
-
-  - "git clone" over http from a repository that has lost the
-    loose refs by running "git pack-refs" were broken (a code to
-    deal with this was added to "git fetch" in v1.5.0, but it
-    was missing from "git clone").
-
-  - "git diff a/ b/" incorrectly fell in "diff between two
-    filesystem objects" codepath, when the user most likely
-    wanted to limit the extent of output to two tracked
-    directories.
-
-  - git-quiltimport had the same bug as we fixed for
-    git-applymbox in v1.5.1.1 -- it gave an alarming "did not
-    have any patch" message (but did not actually fail and was
-    harmless).
-
-  - various git-svn fixes.
-
-  - Sample update hook incorrectly always refused requests to
-    delete branches through push.
-
-  - git-blame on a very long working tree path had buffer
-    overrun problem.
-
-  - git-apply did not like to be fed two patches in a row that created
-    and then modified the same file.
-
-  - git-svn was confused when a non-project was stored directly under
-    trunk/, branches/ and tags/.
-
-  - git-svn wants the Error.pm module that was at least as new
-    as what we ship as part of git; install ours in our private
-    installation location if the one on the system is older.
-
-  - An earlier update to command line integer parameter parser was
-    botched and made 'update-index --cacheinfo' completely useless.
-
-
-* Documentation updates
-
-  - Various documentation updates from J. Bruce Fields, Frank
-    Lichtenheld, Alex Riesen and others.  Andrew Ruder started a
-    war on undocumented options.
diff --git a/Documentation/RelNotes-1.5.1.3.txt b/Documentation/RelNotes-1.5.1.3.txt
deleted file mode 100644 (file)
index 876408b..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-GIT v1.5.1.3 Release Notes
-==========================
-
-Fixes since v1.5.1.2
---------------------
-
-* Bugfixes
-
-  - git-add tried to optimize by finding common leading
-    directories across its arguments but botched, causing very
-    confused behaviour.
-
-  - unofficial rpm.spec file shipped with git was letting
-    ETC_GITCONFIG set to /usr/etc/gitconfig.  Tweak the official
-    Makefile to make it harder for distro people to make the
-    same mistake, by setting the variable to /etc/gitconfig if
-    prefix is set to /usr.
-
-  - git-svn inconsistently stripped away username from the URL
-    only when svnsync_props was in use.
-
-  - git-svn got confused when handling symlinks on Mac OS.
-
-  - git-send-email was not quoting recipient names that have
-    period '.' in them.  Also it did not allow overriding
-    envelope sender, which made it impossible to send patches to
-    certain subscriber-only lists.
-
-  - built-in write_tree() routine had a sequence that renamed a
-    file that is still open, which some systems did not like.
-
-  - when memory is very tight, sliding mmap code to read
-    packfiles incorrectly closed the fd that was still being
-    used to read the pack.
-
-  - import-tars contributed front-end for fastimport was passing
-    wrong directory modes without checking.
-
-  - git-fastimport trusted its input too much and allowed to
-    create corrupt tree objects with entries without a name.
-
-  - git-fetch needlessly barfed when too long reflog action
-    description was given by the caller.
-
-Also contains various documentation updates.
diff --git a/Documentation/RelNotes-1.5.1.4.txt b/Documentation/RelNotes-1.5.1.4.txt
deleted file mode 100644 (file)
index df2f66c..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-GIT v1.5.1.4 Release Notes
-==========================
-
-Fixes since v1.5.1.3
---------------------
-
-* Bugfixes
-
-  - "git-http-fetch" did not work around a bug in libcurl
-    earlier than 7.16 (curl_multi_remove_handle() was broken).
-
-  - "git cvsserver" handles a file that was once removed and
-    then added again correctly.
-
-  - import-tars script (in contrib/) handles GNU tar archives
-    that contain pathnames longer than 100 bytes (long-link
-    extension) correctly.
-
-  - xdelta test program did not build correctly.
-
-  - gitweb sometimes tried incorrectly to apply function to
-    decode utf8 twice, resulting in corrupt output.
-
-  - "git blame -C" mishandled text at the end of a group of
-    lines.
-
-  - "git log/rev-list --boundary" did not produce output
-    correctly without --left-right option.
-
-  - Many documentation updates.
diff --git a/Documentation/RelNotes-1.5.1.5.txt b/Documentation/RelNotes-1.5.1.5.txt
deleted file mode 100644 (file)
index b0ab8eb..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-GIT v1.5.1.5 Release Notes
-==========================
-
-Fixes since v1.5.1.4
---------------------
-
-* Bugfixes
-
-  - git-send-email did not understand aliases file for mutt, which
-    allows leading whitespaces.
-
-  - git-format-patch emitted Content-Type and Content-Transfer-Encoding
-    headers for non ASCII contents, but failed to add MIME-Version.
-
-  - git-name-rev had a buffer overrun with a deep history.
-
-  - contributed script import-tars did not get the directory in
-    tar archives interpreted correctly.
-
-  - git-svn was reported to segfault for many people on list and
-    #git; hopefully this has been fixed.
-
-  - "git-svn clone" does not try to minimize the URL
-    (i.e. connect to higher level hierarchy) by default, as this
-    can prevent clone to fail if only part of the repository
-    (e.g. 'trunk') is open to public.
-
-  - "git checkout branch^0" did not detach the head when you are
-    already on 'branch'; backported the fix from the 'master'.
-
-  - "git-config section.var" did not correctly work when
-    existing configuration file had both [section] and [section "name"]
-    next to each other.
-
-  - "git clone ../other-directory" was fooled if the current
-    directory $PWD points at is a symbolic link.
-
-  - (build) tree_entry_extract() function was both static inline
-    and extern, which caused trouble compiling with Forte12
-    compilers on Sun.
-
-  - Many many documentation fixes and updates.
diff --git a/Documentation/RelNotes-1.5.1.6.txt b/Documentation/RelNotes-1.5.1.6.txt
deleted file mode 100644 (file)
index 55f3ac1..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-GIT v1.5.1.6 Release Notes
-==========================
-
-Fixes since v1.5.1.4
---------------------
-
-* Bugfixes
-
-  - git-send-email did not understand aliases file for mutt, which
-    allows leading whitespaces.
-
-  - git-format-patch emitted Content-Type and Content-Transfer-Encoding
-    headers for non ASCII contents, but failed to add MIME-Version.
-
-  - git-name-rev had a buffer overrun with a deep history.
-
-  - contributed script import-tars did not get the directory in
-    tar archives interpreted correctly.
-
-  - git-svn was reported to segfault for many people on list and
-    #git; hopefully this has been fixed.
-
-  - git-svn also had a bug to crash svnserve by sending a bad
-    sequence of requests.
-
-  - "git-svn clone" does not try to minimize the URL
-    (i.e. connect to higher level hierarchy) by default, as this
-    can prevent clone to fail if only part of the repository
-    (e.g. 'trunk') is open to public.
-
-  - "git checkout branch^0" did not detach the head when you are
-    already on 'branch'; backported the fix from the 'master'.
-
-  - "git-config section.var" did not correctly work when
-    existing configuration file had both [section] and [section "name"]
-    next to each other.
-
-  - "git clone ../other-directory" was fooled if the current
-    directory $PWD points at is a symbolic link.
-
-  - (build) tree_entry_extract() function was both static inline
-    and extern, which caused trouble compiling with Forte12
-    compilers on Sun.
-
-  - Many many documentation fixes and updates.
diff --git a/Documentation/RelNotes-1.5.1.txt b/Documentation/RelNotes-1.5.1.txt
deleted file mode 100644 (file)
index daed367..0000000
+++ /dev/null
@@ -1,371 +0,0 @@
-GIT v1.5.1 Release Notes
-========================
-
-Updates since v1.5.0
---------------------
-
-* Deprecated commands and options.
-
-  - git-diff-stages and git-resolve have been removed.
-
-* New commands and options.
-
-  - "git log" and friends take --reverse, which instructs them
-    to give their output in the order opposite from their usual.
-    They typically output from new to old, but with this option
-    their output would read from old to new.  "git shortlog"
-    usually lists older commits first, but with this option,
-    they are shown from new to old.
-
-  - "git log --pretty=format:<string>" to allow more flexible
-    custom log output.
-
-  - "git diff" learned --ignore-space-at-eol.  This is a weaker
-    form of --ignore-space-change.
-
-  - "git diff --no-index pathA pathB" can be used as diff
-    replacement with git specific enhancements.
-
-  - "git diff --no-index" can read from '-' (standard input).
-
-  - "git diff" also learned --exit-code to exit with non-zero
-    status when it found differences.  In the future we might
-    want to make this the default but that would be a rather big
-    backward incompatible change; it will stay as an option for
-    now.
-
-  - "git diff --quiet" is --exit-code with output turned off,
-    meant for scripted use to quickly determine if there is any
-    tree-level difference.
-
-  - Textual patch generation with "git diff" without -w/-b
-    option has been significantly optimized.  "git blame" got
-    faster because of the same change.
-
-  - "git log" and "git rev-list" has been optimized
-    significantly when they are used with pathspecs.
-
-  - "git branch --track" can be used to set up configuration
-    variables to help it easier to base your work on branches
-    you track from a remote site.
-
-  - "git format-patch --attach" now emits attachments.  Use
-    --inline to get an inlined multipart/mixed.
-
-  - "git name-rev" learned --refs=<pattern>, to limit the tags
-    used for naming the given revisions only to the ones
-    matching the given pattern.
-
-  - "git remote update" is to run "git fetch" for defined remotes
-    to update tracking branches.
-
-  - "git cvsimport" can now take '-d' to talk with a CVS
-    repository different from what are recorded in CVS/Root
-    (overriding it with environment CVSROOT does not work).
-
-  - "git bundle" can help sneaker-netting your changes between
-    repositories.
-
-  - "git mergetool" can help 3-way file-level conflict
-    resolution with your favorite graphical merge tools.
-
-  - A new configuration "core.symlinks" can be used to disable
-    symlinks on filesystems that do not support them; they are
-    checked out as regular files instead.
-
-  - You can name a commit object with its first line of the
-    message.  The syntax to use is ':/message text'.  E.g.
-
-    $ git show ":/object name: introduce ':/<oneline prefix>' notation"
-
-    means the same thing as:
-
-    $ git show 28a4d940443806412effa246ecc7768a21553ec7
-
-  - "git bisect" learned a new command "run" that takes a script
-    to run after each revision is checked out to determine if it
-    is good or bad, to automate the bisection process.
-
-  - "git log" family learned a new traversal option --first-parent,
-    which does what the name suggests.
-
-
-* Updated behavior of existing commands.
-
-  - "git-merge-recursive" used to barf when there are more than
-    one common ancestors for the merge, and merging them had a
-    rename/rename conflict.  This has been fixed.
-
-  - "git fsck" does not barf on corrupt loose objects.
-
-  - "git rm" does not remove newly added files without -f.
-
-  - "git archimport" allows remapping when coming up with git
-    branch names from arch names.
-
-  - git-svn got almost a rewrite.
-
-  - core.autocrlf configuration, when set to 'true', makes git
-    to convert CRLF at the end of lines in text files to LF when
-    reading from the filesystem, and convert in reverse when
-    writing to the filesystem.  The variable can be set to
-    'input', in which case the conversion happens only while
-    reading from the filesystem but files are written out with
-    LF at the end of lines.  Currently, which paths to consider
-    'text' (i.e. be subjected to the autocrlf mechanism) is
-    decided purely based on the contents, but the plan is to
-    allow users to explicitly override this heuristic based on
-    paths.
-
-  - The behavior of 'git-apply', when run in a subdirectory,
-    without --index nor --cached were inconsistent with that of
-    the command with these options.  This was fixed to match the
-    behavior with --index.  A patch that is meant to be applied
-    with -p1 from the toplevel of the project tree can be
-    applied with any custom -p<n> option.  A patch that is not
-    relative to the toplevel needs to be applied with -p<n>
-    option with or without --index (or --cached).
-
-  - "git diff" outputs a trailing HT when pathnames have embedded
-    SP on +++/--- header lines, in order to help "GNU patch" to
-    parse its output.  "git apply" was already updated to accept
-    this modified output format since ce74618d (Sep 22, 2006).
-
-  - "git cvsserver" runs hooks/update and honors its exit status.
-
-  - "git cvsserver" can be told to send everything with -kb.
-
-  - "git diff --check" also honors the --color output option.
-
-  - "git name-rev" used to stress the fact that a ref is a tag too
-    much, by saying something like "v1.2.3^0~22".  It now says
-    "v1.2.3~22" in such a case (it still says "v1.2.3^0" if it does
-    not talk about an ancestor of the commit that is tagged, which
-    makes sense).
-
-  - "git rev-list --boundary" now shows boundary markers for the
-    commits omitted by --max-age and --max-count condition.
-
-  - The configuration mechanism now reads $(prefix)/etc/gitconfig.
-
-  - "git apply --verbose" shows what preimage lines were wanted
-    when it couldn't find them.
-
-  - "git status" in a read-only repository got a bit saner.
-
-  - "git fetch" (hence "git clone" and "git pull") are less
-    noisy when the output does not go to tty.
-
-  - "git fetch" between repositories with many refs were slow
-    even when there are not many changes that needed
-    transferring.  This has been sped up by partially rewriting
-    the heaviest parts in C.
-
-  - "git mailinfo" which splits an e-mail into a patch and the
-    meta-information was rewritten, thanks to Don Zickus.  It
-    handles nested multipart better.  The command was broken for
-    a brief period on 'master' branch since 1.5.0 but the
-    breakage is fixed now.
-
-  - send-email learned configurable bcc and chain-reply-to.
-
-  - "git remote show $remote" also talks about branches that
-    would be pushed if you run "git push remote".
-
-  - Using objects from packs is now seriously optimized by clever
-    use of a cache.  This should be most noticeable in git-log
-    family of commands that involve reading many tree objects.
-    In addition, traversing revisions while filtering changes
-    with pathspecs is made faster by terminating the comparison
-    between the trees as early as possible.
-
-
-* Hooks
-
-  - The part to send out notification e-mails was removed from
-    the sample update hook, as it was not an appropriate place
-    to do so.  The proper place to do this is the new post-receive
-    hook.  An example hook has been added to contrib/hooks/.
-
-
-* Others
-
-  - git-revert, git-gc and git-cherry-pick are now built-ins.
-
-Fixes since v1.5.0
-------------------
-
-These are all in v1.5.0.x series.
-
-* Documentation updates
-
-  - Clarifications and corrections to 1.5.0 release notes.
-
-  - The main documentation did not link to git-remote documentation.
-
-  - Clarified introductory text of git-rebase documentation.
-
-  - Converted remaining mentions of update-index on Porcelain
-    documents to git-add/git-rm.
-
-  - Some i18n.* configuration variables were incorrectly
-    described as core.*; fixed.
-
-  - added and clarified core.bare, core.legacyheaders configurations.
-
-  - updated "git-clone --depth" documentation.
-
-  - user-manual updates.
-
-  - Options to 'git remote add' were described insufficiently.
-
-  - Configuration format.suffix was not documented.
-
-  - Other formatting and spelling fixes.
-
-  - user-manual has better cross references.
-
-  - gitweb installation/deployment procedure is now documented.
-
-
-* Bugfixes
-
-  - git-upload-pack closes unused pipe ends; earlier this caused
-    many zombies to hang around.
-
-  - git-rerere was recording the contents of earlier hunks
-    duplicated in later hunks.  This prevented resolving the same
-    conflict when performing the same merge the other way around.
-
-  - git-add and git-update-index on a filesystem on which
-    executable bits are unreliable incorrectly reused st_mode
-    bits even when the path changed between symlink and regular
-    file.
-
-  - git-daemon marks the listening sockets with FD_CLOEXEC so
-    that it won't be leaked into the children.
-
-  - segfault from git-blame when the mandatory pathname
-    parameter was missing was fixed; usage() message is given
-    instead.
-
-  - git-rev-list did not read $GIT_DIR/config file, which means
-    that did not honor i18n.logoutputencoding correctly.
-
-  - Automated merge conflict handling when changes to symbolic
-    links conflicted were completely broken.  The merge-resolve
-    strategy created a regular file with conflict markers in it
-    in place of the symbolic link.  The default strategy,
-    merge-recursive was even more broken.  It removed the path
-    that was pointed at by the symbolic link.  Both of these
-    problems have been fixed.
-
-  - 'git diff maint master next' did not correctly give combined
-    diff across three trees.
-
-  - 'git fast-import' portability fix for Solaris.
-
-  - 'git show-ref --verify' without arguments did not error out
-    but segfaulted.
-
-  - 'git diff :tracked-file `pwd`/an-untracked-file' gave an extra
-    slashes after a/ and b/.
-
-  - 'git format-patch' produced too long filenames if the commit
-    message had too long line at the beginning.
-
-  - Running 'make all' and then without changing anything
-    running 'make install' still rebuilt some files.  This
-    was inconvenient when building as yourself and then
-    installing as root (especially problematic when the source
-    directory is on NFS and root is mapped to nobody).
-
-  - 'git-rerere' failed to deal with two unconflicted paths that
-    sorted next to each other.
-
-  - 'git-rerere' attempted to open(2) a symlink and failed if
-    there was a conflict.  Since a conflicting change to a
-    symlink would not benefit from rerere anyway, the command
-    now ignores conflicting changes to symlinks.
-
-  - 'git-repack' did not like to pass more than 64 arguments
-    internally to underlying 'rev-list' logic, which made it
-    impossible to repack after accumulating many (small) packs
-    in the repository.
-
-  - 'git-diff' to review the combined diff during a conflicted
-    merge were not reading the working tree version correctly
-    when changes to a symbolic link conflicted.  It should have
-    read the data using readlink(2) but read from the regular
-    file the symbolic link pointed at.
-
-  - 'git-remote' did not like period in a remote's name.
-
-  - 'git.el' honors the commit coding system from the configuration.
-
-  - 'blameview' in contrib/ correctly digs deeper when a line is
-    clicked.
-
-  - 'http-push' correctly makes sure the remote side has leading
-    path.  Earlier it started in the middle of the path, and
-    incorrectly.
-
-  - 'git-merge' did not exit with non-zero status when the
-    working tree was dirty and cannot fast forward.  It does
-    now.
-
-  - 'cvsexportcommit' does not lose yet-to-be-used message file.
-
-  - int-vs-size_t typefix when running combined diff on files
-    over 2GB long.
-
-  - 'git apply --whitespace=strip' should not touch unmodified
-    lines.
-
-  - 'git-mailinfo' choke when a logical header line was too long.
-
-  - 'git show A..B' did not error out.  Negative ref ("not A" in
-    this example) does not make sense for the purpose of the
-    command, so now it errors out.
-
-  - 'git fmt-merge-msg --file' without file parameter did not
-    correctly error out.
-
-  - 'git archimport' barfed upon encountering a commit without
-    summary.
-
-  - 'git index-pack' did not protect itself from getting a short
-    read out of pread(2).
-
-  - 'git http-push' had a few buffer overruns.
-
-  - Build dependency fixes to rebuild fetch.o when other headers
-    change.
-
-  - git.el does not add duplicate sign-off lines.
-
-  - git-commit shows the full stat of the resulting commit, not
-    just about the files in the current directory, when run from
-    a subdirectory.
-
-  - "git-checkout -m '@{8 hours ago}'" had a funny failure from
-    eval; fixed.
-
-  - git-merge (hence git-pull) did not refuse fast-forwarding
-    when the working tree had local changes that would have
-    conflicted with it.
-
-  - a handful small fixes to gitweb.
-
-  - build procedure for user-manual is fixed not to require locally
-    installed stylesheets.
-
-  - "git commit $paths" on paths whose earlier contents were
-    already updated in the index were failing out.
-
-
-* Tweaks
-
-  - sliding mmap() inefficiently mmaped the same region of a
-    packfile with an access pattern that used objects in the
-    reverse order.  This has been made more efficient.
diff --git a/Documentation/RelNotes-1.5.2.1.txt b/Documentation/RelNotes-1.5.2.1.txt
deleted file mode 100644 (file)
index ebf20e2..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-GIT v1.5.2.1 Release Notes
-==========================
-
-Fixes since v1.5.2
-------------------
-
-* Bugfixes
-
-  - Temporary files that are used when invoking external diff
-    programs did not tolerate a long TMPDIR.
-
-  - git-daemon did not notice when it could not write into its
-    pid file.
-
-  - git-status did not honor core.excludesFile configuration like
-    git-add did.
-
-  - git-annotate did not work from a subdirectory while
-    git-blame did.
-
-  - git-cvsserver should have disabled access to a repository
-    with "gitcvs.pserver.enabled = false" set even when
-    "gitcvs.enabled = true" was set at the same time.  It
-    didn't.
-
-  - git-cvsimport did not work correctly in a repository with
-    its branch heads were packed with pack-refs.
-
-  - ident unexpansion to squash "$Id: xxx $" that is in the
-    repository copy removed incorrect number of bytes.
-
-  - git-svn misbehaved when the subversion repository did not
-    provide MD5 checksums for files.
-
-  - git rebase (and git am) misbehaved on commits that have '\n'
-    (literally backslash and en, not a linefeed) in the title.
-
-  - code to decode base85 used in binary patches had one error
-    return codepath wrong.
-
-  - RFC2047 Q encoding output by git-format-patch used '_' for a
-    space, which is not understood by some programs.  It uses =20
-    which is safer.
-
-  - git-fastimport --import-marks was broken; fixed.
-
-  - A lot of documentation updates, clarifications and fixes.
-
---
-exec >/var/tmp/1
-O=v1.5.2-65-g996e2d6
-echo O=`git describe refs/heads/maint`
-git shortlog --no-merges $O..refs/heads/maint
diff --git a/Documentation/RelNotes-1.5.2.2.txt b/Documentation/RelNotes-1.5.2.2.txt
deleted file mode 100644 (file)
index 7bfa341..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-GIT v1.5.2.2 Release Notes
-==========================
-
-Fixes since v1.5.2.1
---------------------
-
-* Usability fix
-
-  - git-gui is shipped with its updated blame interface.  It is
-    rumored that the older one was not just unusable but was
-    active health hazard, but this one is actually pretty.
-    Please see for yourself.
-
-* Bugfixes
-
-  - "git checkout fubar" was utterly confused when there is a
-    branch fubar and a tag fubar at the same time.  It correctly
-    checks out the branch fubar now.
-
-  - "git clone /path/foo" to clone a local /path/foo.git
-    repository left an incorrect configuration.
-
-  - "git send-email" correctly unquotes RFC 2047 quoted names in
-    the patch-email before using their values.
-
-  - We did not accept number of seconds since epoch older than
-    year 2000 as a valid timestamp.  We now interpret positive
-    integers more than 8 digits as such, which allows us to
-    express timestamps more recent than March 1973.
-
-  - git-cvsimport did not work when you have GIT_DIR to point
-    your repository at a nonstandard location.
-
-  - Some systems (notably, Solaris) lack hstrerror() to make
-    h_errno human readable; prepare a replacement
-    implementation.
-
-  - .gitignore file listed git-core.spec but what we generate is
-    git.spec, and nobody noticed for a long time.
-
-  - "git-merge-recursive" does not try to run file level merge
-    on binary files.
-
-  - "git-branch --track" did not create tracking configuration
-    correctly when the branch name had slash in it.
-
-  - The email address of the user specified with user.email
-    configuration was overridden by EMAIL environment variable.
-
-  - The tree parser did not warn about tree entries with
-    nonsense file modes, and assumed they must be blobs.
-
-  - "git log -z" without any other request to generate diff still
-    invoked the diff machinery, wasting cycles.
-
-* Documentation
-
-  - Many updates to fix stale or missing documentation.
-
-  - Although our documentation was primarily meant to be formatted
-    with AsciiDoc7, formatting with AsciiDoc8 is supported better.
diff --git a/Documentation/RelNotes-1.5.2.3.txt b/Documentation/RelNotes-1.5.2.3.txt
deleted file mode 100644 (file)
index addb229..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-GIT v1.5.2.3 Release Notes
-==========================
-
-Fixes since v1.5.2.2
---------------------
-
- * Bugfixes
-
-   - Version 2 pack index format was introduced in version 1.5.2
-     to support pack files that has offset that cannot be
-     represented in 32-bit.  The runtime code to validate such
-     an index mishandled such an index for an empty pack.
-
-   - Commit walkers (most notably, fetch over http protocol)
-     tried to traverse commit objects contained in trees (aka
-     subproject); they shouldn't.
-
-   - A build option NO_R_TO_GCC_LINKER was not explained in Makefile
-     comment correctly.
-
- * Documentation Fixes and Updates
-
-   - git-config --regexp was not documented properly.
-
-   - git-repack -a was not documented properly.
-
-   - git-remote -n was not documented properly.
diff --git a/Documentation/RelNotes-1.5.2.4.txt b/Documentation/RelNotes-1.5.2.4.txt
deleted file mode 100644 (file)
index 75cff47..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-GIT v1.5.2.4 Release Notes
-==========================
-
-Fixes since v1.5.2.3
---------------------
-
- * Bugfixes
-
-   - "git-gui" bugfixes, including a handful fixes to run it
-     better on Cygwin/MSYS.
-
-   - "git checkout" failed to switch back and forth between
-     branches, one of which has "frotz -> xyzzy" symlink and
-     file "xyzzy/filfre", while the other one has a file
-     "frotz/filfre".
-
-   - "git prune" used to segfault upon seeing a commit that is
-     referred to by a tree object (aka "subproject").
-
-   - "git diff --name-status --no-index" mishandled an added file.
-
-   - "git apply --reverse --whitespace=warn" still complained
-     about whitespaces that a forward application would have
-     introduced.
-
- * Documentation Fixes and Updates
-
-   - A handful documentation updates.
diff --git a/Documentation/RelNotes-1.5.2.5.txt b/Documentation/RelNotes-1.5.2.5.txt
deleted file mode 100644 (file)
index e8281c7..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-GIT v1.5.2.5 Release Notes
-==========================
-
-Fixes since v1.5.2.4
---------------------
-
- * Bugfixes
-
-   - "git add -u" had a serious data corruption problem in one
-     special case (when the changes to a subdirectory's files
-     consist only deletion of files).
-
-   - "git add -u <path>" did not work from a subdirectory.
-
-   - "git apply" left an empty directory after all its files are
-     renamed away.
-
-   - "git $anycmd foo/bar", when there is a file 'foo' in the
-     working tree, complained that "git $anycmd foo/bar --" form
-     should be used to disambiguate between revs and files,
-     which was completely bogus.
-
-   - "git checkout-index" and other commands that checks out
-     files to the work tree tried unlink(2) on directories,
-     which is a sane thing to do on sane systems, but not on
-     Solaris when you are root.
-
- * Documentation Fixes and Updates
-
-   - A handful documentation fixes.
diff --git a/Documentation/RelNotes-1.5.2.txt b/Documentation/RelNotes-1.5.2.txt
deleted file mode 100644 (file)
index e8328d0..0000000
+++ /dev/null
@@ -1,197 +0,0 @@
-GIT v1.5.2 Release Notes
-========================
-
-Updates since v1.5.1
---------------------
-
-* Plumbing level superproject support.
-
-  You can include a subdirectory that has an independent git
-  repository in your index and tree objects of your project
-  ("superproject").  This plumbing (i.e. "core") level
-  superproject support explicitly excludes recursive behaviour.
-
-  The "subproject" entries in the index and trees of a superproject
-  are incompatible with older versions of git.  Experimenting with
-  the plumbing level support is encouraged, but be warned that
-  unless everybody in your project updates to this release or
-  later, using this feature would make your project
-  inaccessible by people with older versions of git.
-
-* Plumbing level gitattributes support.
-
-  The gitattributes mechanism allows you to add 'attributes' to
-  paths in your project, and affect the way certain git
-  operations work.  Currently you can influence if a path is
-  considered a binary or text (the former would be treated by
-  'git diff' not to produce textual output; the latter can go
-  through the line endings conversion process in repositories
-  with core.autocrlf set), expand and unexpand '$Id$' keyword
-  with blob object name, specify a custom 3-way merge driver,
-  and specify a custom diff driver.  You can also apply
-  arbitrary filter to contents on check-in/check-out codepath
-  but this feature is an extremely sharp-edged razor and needs
-  to be handled with caution (do not use it unless you
-  understand the earlier mailing list discussion on keyword
-  expansion).  These conversions apply when checking files in
-  or out, and exporting via git-archive.
-
-* The packfile format now optionally supports 64-bit index.
-
-  This release supports the "version 2" format of the .idx
-  file.  This is automatically enabled when a huge packfile
-  needs more than 32-bit to express offsets of objects in the
-  pack.
-
-* Comes with an updated git-gui 0.7.1
-
-* Updated gitweb:
-
-  - can show combined diff for merges;
-  - uses font size of user's preference, not hardcoded in pixels;
-  - can now 'grep';
-
-* New commands and options.
-
-  - "git bisect start" can optionally take a single bad commit and
-    zero or more good commits on the command line.
-
-  - "git shortlog" can optionally be told to wrap its output.
-
-  - "subtree" merge strategy allows another project to be merged in as
-    your subdirectory.
-
-  - "git format-patch" learned a new --subject-prefix=<string>
-    option, to override the built-in "[PATCH]".
-
-  - "git add -u" is a quick way to do the first stage of "git
-    commit -a" (i.e. update the index to match the working
-    tree); it obviously does not make a commit.
-
-  - "git clean" honors a new configuration, "clean.requireforce".  When
-    set to true, this makes "git clean" a no-op, preventing you
-    from losing files by typing "git clean" when you meant to
-    say "make clean".  You can still say "git clean -f" to
-    override this.
-
-  - "git log" family of commands learned --date={local,relative,default}
-    option.  --date=relative is synonym to the --relative-date.
-    --date=local gives the timestamp in local timezone.
-
-* Updated behavior of existing commands.
-
-  - When $GIT_COMMITTER_EMAIL or $GIT_AUTHOR_EMAIL is not set
-    but $EMAIL is set, the latter is used as a substitute.
-
-  - "git diff --stat" shows size of preimage and postimage blobs
-    for binary contents.  Earlier it only said "Bin".
-
-  - "git lost-found" shows stuff that are unreachable except
-    from reflogs.
-
-  - "git checkout branch^0" now detaches HEAD at the tip commit
-    on the named branch, instead of just switching to the
-    branch (use "git checkout branch" to switch to the branch,
-    as before).
-
-  - "git bisect next" can be used after giving only a bad commit
-    without giving a good one (this starts bisection half-way to
-    the root commit).  We used to refuse to operate without a
-    good and a bad commit.
-
-  - "git push", when pushing into more than one repository, does
-    not stop at the first error.
-
-  - "git archive" does not insist you to give --format parameter
-    anymore; it defaults to "tar".
-
-  - "git cvsserver" can use backends other than sqlite.
-
-  - "gitview" (in contrib/ section) learned to better support
-    "git-annotate".
-
-  - "git diff $commit1:$path2 $commit2:$path2" can now report
-    mode changes between the two blobs.
-
-  - Local "git fetch" from a repository whose object store is
-    one of the alternates (e.g. fetching from the origin in a
-    repository created with "git clone -l -s") avoids
-    downloading objects unnecessarily.
-
-  - "git blame" uses .mailmap to canonicalize the author name
-    just like "git shortlog" does.
-
-  - "git pack-objects" pays attention to pack.depth
-    configuration variable.
-
-  - "git cherry-pick" and "git revert" does not use .msg file in
-    the working tree to prepare commit message; instead it uses
-    $GIT_DIR/MERGE_MSG as other commands do.
-
-* Builds
-
-  - git-p4import has never been installed; now there is an
-    installation option to do so.
-
-  - gitk and git-gui can be configured out.
-
-  - Generated documentation pages automatically get version
-    information from GIT_VERSION.
-
-  - Parallel build with "make -j" descending into subdirectory
-    was fixed.
-
-* Performance Tweaks
-
-  - Optimized "git-rev-list --bisect" (hence "git-bisect").
-
-  - Optimized "git-add $path" in a large directory, most of
-    whose contents are ignored.
-
-  - Optimized "git-diff-tree" for reduced memory footprint.
-
-  - The recursive merge strategy updated a worktree file that
-    was changed identically in two branches, when one of them
-    renamed it.  We do not do that when there is no rename, so
-    match that behaviour.  This avoids excessive rebuilds.
-
-  - The default pack depth has been increased to 50, as the
-    recent addition of delta_base_cache makes deeper delta chains
-    much less expensive to access.  Depending on the project, it was
-    reported that this reduces the resulting pack file by 10%
-    or so.
-
-
-Fixes since v1.5.1
-------------------
-
-All of the fixes in v1.5.1 maintenance series are included in
-this release, unless otherwise noted.
-
-* Bugfixes
-
-  - Switching branches with "git checkout" refused to work when
-    a path changes from a file to a directory between the
-    current branch and the new branch, in order not to lose
-    possible local changes in the directory that is being turned
-    into a file with the switch.  We now allow such a branch
-    switch after making sure that there is no locally modified
-    file nor un-ignored file in the directory.  This has not
-    been backported to 1.5.1.x series, as it is rather an
-    intrusive change.
-
-  - Merging branches that have a file in one and a directory in
-    another at the same path used to get quite confused.  We
-    handle such a case a bit more carefully, even though that is
-    still left as a conflict for the user to sort out.  This
-    will not be backported to 1.5.1.x series, as it is rather an
-    intrusive change.
-
-  - git-fetch had trouble with a remote with insanely large number
-    of refs.
-
-  - "git clean -d -X" now does not remove non-excluded directories.
-
-  - rebasing (without -m) a series that changes a symlink to a directory
-    in the middle of a path confused git-apply greatly and refused to
-    operate.
diff --git a/Documentation/RelNotes-1.5.3.1.txt b/Documentation/RelNotes-1.5.3.1.txt
deleted file mode 100644 (file)
index 7ff546c..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-GIT v1.5.3.1 Release Notes
-==========================
-
-Fixes since v1.5.3
-------------------
-
-This is solely to fix the generated RPM's dependencies.  We used
-to have git-p4 package but we do not anymore.  As suggested on
-the mailing list, this release makes git-core "Obsolete" git-p4,
-so that yum update would not complain.
diff --git a/Documentation/RelNotes-1.5.3.2.txt b/Documentation/RelNotes-1.5.3.2.txt
deleted file mode 100644 (file)
index 4bbde3c..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-GIT v1.5.3.2 Release Notes
-==========================
-
-Fixes since v1.5.3.1
---------------------
-
- * git-push sent thin packs by default, which was not good for
-   the public distribution server (no point in saving transfer
-   while pushing; no point in making the resulting pack less
-   optimum).
-
- * git-svn sometimes terminated with "Malformed network data" when
-   talking over svn:// protocol.
-
- * git-send-email re-issued the same message-id about 10% of the
-   time if you fired off 30 messages within a single second.
-
- * git-stash was not terminating the log message of commits it
-   internally creates with LF.
-
- * git-apply failed to check the size of the patch hunk when its
-   beginning part matched the remainder of the preimage exactly,
-   even though the preimage recorded in the hunk was much larger
-   (therefore the patch should not have applied), leading to a
-   segfault.
-
- * "git rm foo && git commit foo" complained that 'foo' needs to
-   be added first, instead of committing the removal, which was a
-   nonsense.
-
- * git grep -c said "/dev/null: 0".
-
- * git-add -u failed to recognize a blob whose type changed
-   between the index and the work tree.
-
- * The limit to rename detection has been tightened a lot to
-   reduce performance problems with a huge change.
-
- * cvsimport and svnimport barfed when the input tried to move
-   a tag.
-
- * "git apply -pN" did not chop the right number of directories.
-
- * "git svnimport" did not like SVN tags with funny characters in them.
-
- * git-gui 0.8.3, with assorted fixes, including:
-
-   - font-chooser on X11 was unusable with large number of fonts;
-   - a diff that contained a deleted symlink made it barf;
-   - an untracked symbolic link to a directory made it fart;
-   - a file with % in its name made it vomit;
-
-
-Documentation updates
----------------------
-
-User manual has been somewhat restructured.  I think the new
-organization is much easier to read.
diff --git a/Documentation/RelNotes-1.5.3.3.txt b/Documentation/RelNotes-1.5.3.3.txt
deleted file mode 100644 (file)
index d213846..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-GIT v1.5.3.3 Release Notes
-==========================
-
-Fixes since v1.5.3.2
---------------------
-
- * git-quiltimport did not like it when a patch described in the
-   series file does not exist.
-
- * p4 importer missed executable bit in some cases.
-
- * The default shell on some FreeBSD did not execute the
-   argument parsing code correctly and made git unusable.
-
- * git-svn incorrectly spawned pager even when the user
-   explicitly asked not to.
-
- * sample post-receive hook overquoted the envelope sender
-   value.
-
- * git-am got confused when the patch contained a change that is
-   only about type and not contents.
-
- * git-mergetool did not show our and their version of the
-   conflicted file when started from a subdirectory of the
-   project.
-
- * git-mergetool did not pass correct options when invoking diff3.
-
- * git-log sometimes invoked underlying "diff" machinery
-   unnecessarily.
diff --git a/Documentation/RelNotes-1.5.3.4.txt b/Documentation/RelNotes-1.5.3.4.txt
deleted file mode 100644 (file)
index b04b3a4..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-GIT v1.5.3.4 Release Notes
-==========================
-
-Fixes since v1.5.3.3
---------------------
-
- * Change to "git-ls-files" in v1.5.3.3 that was introduced to support
-   partial commit of removal better had a segfaulting bug, which was
-   diagnosed and fixed by Keith and Carl.
-
- * Performance improvements for rename detection has been backported
-   from the 'master' branch.
-
- * "git-for-each-ref --format='%(numparent)'" was not working
-   correctly at all, and --format='%(parent)' was not working for
-   merge commits.
-
- * Sample "post-receive-hook" incorrectly sent out push
-   notification e-mails marked as "From: " the committer of the
-   commit that happened to be at the tip of the branch that was
-   pushed, not from the person who pushed.
-
- * "git-remote" did not exit non-zero status upon error.
-
- * "git-add -i" did not respond very well to EOF from tty nor
-   bogus input.
-
- * "git-rebase -i" squash subcommand incorrectly made the
-   author of later commit the author of resulting commit,
-   instead of taking from the first one in the squashed series.
-
- * "git-stash apply --index" was not documented.
-
- * autoconfiguration learned that "ar" command is found as "gas" on
-   some systems.
diff --git a/Documentation/RelNotes-1.5.3.5.txt b/Documentation/RelNotes-1.5.3.5.txt
deleted file mode 100644 (file)
index 7ff1d5d..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-GIT v1.5.3.5 Release Notes
-==========================
-
-Fixes since v1.5.3.4
---------------------
-
- * Comes with git-gui 0.8.4.
-
- * "git-config" silently ignored options after --list; now it will
-   error out with a usage message.
-
- * "git-config --file" failed if the argument used a relative path
-   as it changed directories before opening the file.
-
- * "git-config --file" now displays a proper error message if it
-   cannot read the file specified on the command line.
-
- * "git-config", "git-diff", "git-apply" failed if run from a
-   subdirectory with relative GIT_DIR and GIT_WORK_TREE set.
-
- * "git-blame" crashed if run during a merge conflict.
-
- * "git-add -i" did not handle single line hunks correctly.
-
- * "git-rebase -i" and "git-stash apply" failed if external diff
-   drivers were used for one or more files in a commit.  They now
-   avoid calling the external diff drivers.
-
- * "git-log --follow" did not work unless diff generation (e.g. -p)
-   was also requested.
-
- * "git-log --follow -B" did not work at all.  Fixed.
-
- * "git-log -M -B" did not correctly handle cases of very large files
-   being renamed and replaced by very small files in the same commit.
-
- * "git-log" printed extra newlines between commits when a diff
-   was generated internally (e.g. -S or --follow) but not displayed.
-
- * "git-push" error message is more helpful when pushing to a
-   repository with no matching refs and none specified.
-
- * "git-push" now respects + (force push) on wildcard refspecs,
-   matching the behavior of git-fetch.
-
- * "git-filter-branch" now updates the working directory when it
-   has finished filtering the current branch.
-
- * "git-instaweb" no longer fails on Mac OS X.
-
- * "git-cvsexportcommit" didn't always create new parent directories
-   before trying to create new child directories.  Fixed.
-
- * "git-fetch" printed a scary (but bogus) error message while
-   fetching a tag that pointed to a tree or blob.  The error did
-   not impact correctness, only user perception.  The bogus error
-   is no longer printed.
-
- * "git-ls-files --ignored" did not properly descend into non-ignored
-   directories that themselves contained ignored files if d_type
-   was not supported by the filesystem.  This bug impacted systems
-   such as AFS.  Fixed.
-
- * Git segfaulted when reading an invalid .gitattributes file.  Fixed.
-
- * post-receive-email example hook was fixed for non-fast-forward
-   updates.
-
- * Documentation updates for supported (but previously undocumented)
-   options of "git-archive" and "git-reflog".
-
- * "make clean" no longer deletes the configure script that ships
-   with the git tarball, making multiple architecture builds easier.
-
- * "git-remote show origin" spewed a warning message from Perl
-   when no remote is defined for the current branch via
-   branch.<name>.remote configuration settings.
-
- * Building with NO_PERL_MAKEMAKER excessively rebuilt contents
-   of perl/ subdirectory by rewriting perl.mak.
-
- * http.sslVerify configuration settings were not used in scripted
-   Porcelains.
-
- * "git-add" leaked a bit of memory while scanning for files to add.
-
- * A few workarounds to squelch false warnings from recent gcc have
-   been added.
-
- * "git-send-pack $remote frotz" segfaulted when there is nothing
-   named 'frotz' on the local end.
-
- * "git-rebase --interactive" did not handle its "--strategy" option
-   properly.
diff --git a/Documentation/RelNotes-1.5.3.6.txt b/Documentation/RelNotes-1.5.3.6.txt
deleted file mode 100644 (file)
index 069a2b2..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-GIT v1.5.3.6 Release Notes
-==========================
-
-Fixes since v1.5.3.5
---------------------
-
- * git-cvsexportcommit handles root commits better.
-
- * git-svn dcommit used to clobber when sending a series of
-   patches.
-
- * git-svn dcommit failed after attempting to rebase when
-   started with a dirty index; now it stops upfront.
-
- * git-grep sometimes refused to work when your index was
-   unmerged.
-
- * "git-grep -A1 -B2" acted as if it was told to run "git -A1 -B21".
-
- * git-hash-object did not honor configuration variables, such as
-   core.compression.
-
- * git-index-pack choked on a huge pack on 32-bit machines, even when
-   large file offsets are supported.
-
- * atom feeds from git-web said "10" for the month of November.
-
- * a memory leak in commit walker was plugged.
-
- * When git-send-email inserted the original author's From:
-   address in body, it did not mark the message with
-   Content-type: as needed.
-
- * git-revert and git-cherry-pick incorrectly refused to start
-   when the work tree was dirty.
-
- * git-clean did not honor core.excludesfile configuration.
-
- * git-add mishandled ".gitignore" files when applying them to
-   subdirectories.
-
- * While importing a too branchy history, git-fastimport did not
-   honor delta depth limit properly.
-
- * Support for zlib implementations that lack ZLIB_VERNUM and definition
-   of deflateBound() has been added.
-
- * Quite a lot of documentation clarifications.
diff --git a/Documentation/RelNotes-1.5.3.7.txt b/Documentation/RelNotes-1.5.3.7.txt
deleted file mode 100644 (file)
index 2f69061..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-GIT v1.5.3.7 Release Notes
-==========================
-
-Fixes since v1.5.3.6
---------------------
-
- * git-send-email added 8-bit contents to the payload without
-   marking it as 8-bit in a CTE header.
-
- * "git-bundle create a.bndl HEAD" dereferenced the symref and
-   did not record the ref as 'HEAD'; this prevented a bundle
-   from being used as a normal source of git-clone.
-
- * The code to reject nonsense command line of the form
-   "git-commit -a paths..." and "git-commit --interactive
-   paths..." were broken.
-
- * Adding a signature that is not ASCII-only to an original
-   commit that is ASCII-only would make the result non-ASCII.
-   "git-format-patch -s" did not mark such a message correctly
-   with MIME encoding header.
-
- * git-add sometimes did not mark the resulting index entry
-   stat-clean.  This affected only cases when adding the
-   contents with the same length as the previously staged
-   contents, and the previous staging made the index entry
-   "racily clean".
-
- * git-commit did not honor GIT_INDEX_FILE the user had in the
-   environment.
-
- * When checking out a revision, git-checkout did not report where the
-   updated HEAD is if you happened to have a file called HEAD in the
-   work tree.
-
- * "git-rev-list --objects" mishandled a tree that points at a
-   submodule.
-
- * "git cvsimport" was not ready for packed refs that "git gc" can
-   produce and gave incorrect results.
-
- * Many scripted Porcelains were confused when you happened to have a
-   file called "HEAD" in your work tree.
-
-Also it contains updates to the user manual and documentation.
diff --git a/Documentation/RelNotes-1.5.3.8.txt b/Documentation/RelNotes-1.5.3.8.txt
deleted file mode 100644 (file)
index 0e3ff58..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-GIT v1.5.3.8 Release Notes
-==========================
-
-Fixes since v1.5.3.7
---------------------
-
- * Some documentation used "email.com" as an example domain.
-
- * git-svn fix to handle funky branch and project names going over
-   http/https correctly.
-
- * git-svn fix to tone down a needlessly alarming warning message.
-
- * git-clone did not correctly report errors while fetching over http.
-
- * git-send-email added redundant Message-Id: header to the outgoing
-   e-mail when the patch text already had one.
-
- * a read-beyond-end-of-buffer bug in configuration file updater was fixed.
-
- * git-grep used to show the same hit repeatedly for unmerged paths.
-
- * After amending the patch title in "git-am -i", the command did not
-   report the patch it applied with the updated title.
-
diff --git a/Documentation/RelNotes-1.5.3.txt b/Documentation/RelNotes-1.5.3.txt
deleted file mode 100644 (file)
index 0668d3c..0000000
+++ /dev/null
@@ -1,366 +0,0 @@
-GIT v1.5.3 Release Notes
-========================
-
-Updates since v1.5.2
---------------------
-
-* The commit walkers other than http are officially deprecated,
-  but still supported for now.
-
-* The submodule support has Porcelain layer.
-
-  Note that the current submodule support is minimal and this is
-  deliberately so.  A design decision we made is that operations
-  at the supermodule level do not recurse into submodules by
-  default.  The expectation is that later we would add a
-  mechanism to tell git which submodules the user is interested
-  in, and this information might be used to determine the
-  recursive behaviour of certain commands (e.g. "git checkout"
-  and "git diff"), but currently we haven't agreed on what that
-  mechanism should look like.  Therefore, if you use submodules,
-  you would probably need "git submodule update" on the
-  submodules you care about after running a "git checkout" at
-  the supermodule level.
-
-* There are a handful pack-objects changes to help you cope better
-  with repositories with pathologically large blobs in them.
-
-* For people who need to import from Perforce, a front-end for
-  fast-import is in contrib/fast-import/.
-
-* Comes with git-gui 0.8.2.
-
-* Comes with updated gitk.
-
-* New commands and options.
-
-  - "git log --date=<format>" can use more formats: iso8601, rfc2822.
-
-  - The hunk header output from "git diff" family can be customized
-    with the attributes mechanism.  See gitattributes(5) for details.
-
-  - "git stash" allows you to quickly save away your work in
-    progress and replay it later on an updated state.
-
-  - "git rebase" learned an "interactive" mode that let you
-    pick and reorder which commits to rebuild.
-
-  - "git fsck" can save its findings in $GIT_DIR/lost-found, without a
-    separate invocation of "git lost-found" command.  The blobs stored by
-    lost-found are stored in plain format to allow you to grep in them.
-
-  - $GIT_WORK_TREE environment variable can be used together with
-    $GIT_DIR to work in a subdirectory of a working tree that is
-    not located at "$GIT_DIR/..".
-
-  - Giving "--file=<file>" option to "git config" is the same as
-    running the command with GIT_CONFIG=<file> environment.
-
-  - "git log" learned a new option "--follow", to follow
-    renaming history of a single file.
-
-  - "git filter-branch" lets you rewrite the revision history of
-    specified branches. You can specify a number of filters to
-    modify the commits, files and trees.
-
-  - "git cvsserver" learned new options (--base-path, --export-all,
-    --strict-paths) inspired by "git daemon".
-
-  - "git daemon --base-path-relaxed" can help migrating a repository URL
-    that did not use to use --base-path to use --base-path.
-
-  - "git commit" can use "-t templatefile" option and commit.template
-    configuration variable to prime the commit message given to you in the
-    editor.
-
-  - "git submodule" command helps you manage the projects from
-    the superproject that contain them.
-
-  - In addition to core.compression configuration option,
-    core.loosecompression and pack.compression options can
-    independently tweak zlib compression levels used for loose
-    and packed objects.
-
-  - "git ls-tree -l" shows size of blobs pointed at by the
-    tree entries, similar to "/bin/ls -l".
-
-  - "git rev-list" learned --regexp-ignore-case and
-    --extended-regexp options to tweak its matching logic used
-    for --grep filtering.
-
-  - "git describe --contains" is a handier way to call more
-    obscure command "git name-rev --tags".
-
-  - "git gc --aggressive" tells the command to spend more cycles
-    to optimize the repository harder.
-
-  - "git repack" learned a "window-memory" limit which
-    dynamically reduces the window size to stay within the
-    specified memory usage.
-
-  - "git repack" can be told to split resulting packs to avoid
-    exceeding limit specified with "--max-pack-size".
-
-  - "git fsck" gained --verbose option.  This is really really
-    verbose but it might help you identify exact commit that is
-    corrupt in your repository.
-
-  - "git format-patch" learned --numbered-files option.  This
-    may be useful for MH users.
-
-  - "git format-patch" learned format.subjectprefix configuration
-    variable, which serves the same purpose as "--subject-prefix"
-    option.
-
-  - "git tag -n -l" shows tag annotations while listing tags.
-
-  - "git cvsimport" can optionally use the separate-remote layout.
-
-  - "git blame" can be told to see through commits that change
-    whitespaces and indentation levels with "-w" option.
-
-  - "git send-email" can be told not to thread the messages when
-    sending out more than one patches.
-
-  - "git send-email" can also be told how to find whom to cc the
-    message to for each message via --cc-cmd.
-
-  - "git config" learned NUL terminated output format via -z to
-    help scripts.
-
-  - "git add" learned "--refresh <paths>..." option to selectively refresh
-    the cached stat information.
-
-  - "git init -q" makes the command quieter.
-
-  - "git -p command" now has a cousin of opposite sex, "git --no-pager
-    command".
-
-* Updated behavior of existing commands.
-
-  - "gitweb" can offer multiple snapshot formats.
-
-    ***NOTE*** Unfortunately, this changes the format of the
-    $feature{snapshot}{default} entry in the per-site
-    configuration file 'gitweb_config.perl'.  It used to be a
-    three-element tuple that describe a single format; with the
-    new configuration item format, you only have to say the name
-    of the format ('tgz', 'tbz2' or 'zip').  Please update the
-    your configuration file accordingly.
-
-  - "git clone" uses -l (hardlink files under .git) by default when
-    cloning locally.
-
-  - URL used for "git clone" and friends can specify nonstandard SSH port
-    by using ssh://host:port/path/to/repo syntax.
-
-  - "git bundle create" can now create a bundle without negative refs,
-    i.e. "everything since the beginning up to certain points".
-
-  - "git diff" (but not the plumbing level "git diff-tree") now
-    recursively descends into trees by default.
-
-  - "git diff" does not show differences that come only from
-    stat-dirtiness in the form of "diff --git" header anymore.
-    It runs "update-index --refresh" silently as needed.
-
-  - "git tag -l" used to match tags by globbing its parameter as if it
-    has wildcard '*' on both ends, which made "git tag -l gui" to match
-    tag 'gitgui-0.7.0'; this was very annoying.  You now have to add
-    asterisk on the sides you want to wildcard yourself.
-
-  - The editor to use with many interactive commands can be
-    overridden with GIT_EDITOR environment variable, or if it
-    does not exist, with core.editor configuration variable.  As
-    before, if you have neither, environment variables VISUAL
-    and EDITOR are consulted in this order, and then finally we
-    fall back on "vi".
-
-  - "git rm --cached" does not complain when removing a newly
-    added file from the index anymore.
-
-  - Options to "git log" to affect how --grep/--author options look for
-    given strings now have shorter abbreviations.  -i is for ignore case,
-    and -E is for extended regexp.
-
-  - "git log" learned --log-size to show the number of bytes in
-    the log message part of the output to help qgit.
-
-  - "git log --name-status" does not require you to give "-r" anymore.
-    As a general rule, Porcelain commands should recurse when showing
-    diff.
-
-  - "git format-patch --root A" can be used to format everything
-    since the beginning up to A.  This was supported with
-    "git format-patch --root A A" for a long time, but was not
-    properly documented.
-
-  - "git svn dcommit" retains local merge information.
-
-  - "git svnimport" allows an empty string to be specified as the
-    trunk/ directory.  This is necessary to suck data from a SVN
-    repository that doe not have trunk/ branches/ and tags/ organization
-    at all.
-
-  - "git config" to set values also honors type flags like --bool
-    and --int.
-
-  - core.quotepath configuration can be used to make textual git
-    output to emit most of the characters in the path literally.
-
-  - "git mergetool" chooses its backend more wisely, taking
-    notice of its environment such as use of X, Gnome/KDE, etc.
-
-  - "gitweb" shows merge commits a lot nicer than before.  The
-    default view uses more compact --cc format, while the UI
-    allows to choose normal diff with any parent.
-
-  - snapshot files "gitweb" creates from a repository at
-    $path/$project/.git are more useful.  We use $project part
-    in the filename, which we used to discard.
-
-  - "git cvsimport" creates lightweight tags; there is no
-    interesting information we can record in an annotated tag,
-    and the handcrafted ones the old code created was not
-    properly formed anyway.
-
-  - "git push" pretends that you immediately fetched back from
-    the remote by updating corresponding remote tracking
-    branches if you have any.
-
-  - The diffstat given after a merge (or a pull) honors the
-    color.diff configuration.
-
-  - "git commit --amend" is now compatible with various message source
-    options such as -m/-C/-c/-F.
-
-  - "git apply --whitespace=strip" removes blank lines added at
-    the end of the file.
-
-  - "git fetch" over git native protocols with "-v" option shows
-    connection status, and the IP address of the other end, to
-    help diagnosing problems.
-
-  - We used to have core.legacyheaders configuration, when
-    set to false, allowed git to write loose objects in a format
-    that mimics the format used by objects stored in packs.  It
-    turns out that this was not so useful.  Although we will
-    continue to read objects written in that format, we do not
-    honor that configuration anymore and create loose objects in
-    the legacy/traditional format.
-
-  - "--find-copies-harder" option to diff family can now be
-    spelled as "-C -C" for brevity.
-
-  - "git mailsplit" (hence "git am") can read from Maildir
-    formatted mailboxes.
-
-  - "git cvsserver" does not barf upon seeing "cvs login"
-    request.
-
-  - "pack-objects" honors "delta" attribute set in
-    .gitattributes.  It does not attempt to deltify blobs that
-    come from paths with delta attribute set to false.
-
-  - "new-workdir" script (in contrib) can now be used with a
-    bare repository.
-
-  - "git mergetool" learned to use gvimdiff.
-
-  - "gitview" (in contrib) has a better blame interface.
-
-  - "git log" and friends did not handle a commit log message
-    that is larger than 16kB; they do now.
-
-  - "--pretty=oneline" output format for "git log" and friends
-    deals with "malformed" commit log messages that have more
-    than one lines in the first paragraph better.  We used to
-    show the first line, cutting the title at mid-sentence; we
-    concatenate them into a single line and treat the result as
-    "oneline".
-
-  - "git p4import" has been demoted to contrib status.  For
-    a superior option, checkout the "git p4" front end to
-    "git fast-import" (also in contrib).  The man page and p4
-    rpm have been removed as well.
-
-  - "git mailinfo" (hence "am") now tries to see if the message
-    is in utf-8 first, instead of assuming iso-8859-1, if
-    incoming e-mail does not say what encoding it is in.
-
-* Builds
-
-  - old-style function definitions (most notably, a function
-    without parameter defined with "func()", not "func(void)")
-    have been eradicated.
-
-  - "git tag" and "git verify-tag" have been rewritten in C.
-
-* Performance Tweaks
-
-  - "git pack-objects" avoids re-deltification cost by caching
-    small enough delta results it creates while looking for the
-    best delta candidates.
-
-  - "git pack-objects" learned a new heuristic to prefer delta
-    that is shallower in depth over the smallest delta
-    possible.  This improves both overall packfile access
-    performance and packfile density.
-
-  - diff-delta code that is used for packing has been improved
-    to work better on big files.
-
-  - when there are more than one pack files in the repository,
-    the runtime used to try finding an object always from the
-    newest packfile; it now tries the same packfile as we found
-    the object requested the last time, which exploits the
-    locality of references.
-
-  - verifying pack contents done by "git fsck --full" got boost
-    by carefully choosing the order to verify objects in them.
-
-  - "git read-tree -m" to read into an already populated index
-    has been optimized vastly.  The effect of this can be seen
-    when switching branches that have differences in only a
-    handful paths.
-
-  - "git add paths..." and "git commit paths..." has also been
-    heavily optimized.
-
-Fixes since v1.5.2
-------------------
-
-All of the fixes in v1.5.2 maintenance series are included in
-this release, unless otherwise noted.
-
-* Bugfixes
-
-  - "gitweb" had trouble handling non UTF-8 text with older
-    Encode.pm Perl module.
-
-  - "git svn" misparsed the data from the commits in the repository when
-    the user had "color.diff = true" in the configuration.  This has been
-    fixed.
-
-  - There was a case where "git svn dcommit" clobbered changes made on the
-    SVN side while committing multiple changes.
-
-  - "git-write-tree" had a bad interaction with racy-git avoidance and
-    gitattributes mechanisms.
-
-  - "git --bare command" overrode existing GIT_DIR setting and always
-    made it treat the current working directory as GIT_DIR.
-
-  - "git ls-files --error-unmatch" does not complain if you give the
-    same path pattern twice by mistake.
-
-  - "git init" autodetected core.filemode but not core.symlinks, which
-    made a new directory created automatically by "git clone" cumbersome
-    to use on filesystems that require these configurations to be set.
-
-  - "git log" family of commands behaved differently when run as "git
-    log" (no pathspec) and as "git log --" (again, no pathspec).  This
-    inconsistency was introduced somewhere in v1.3.0 series but now has
-    been corrected.
-
-  - "git rebase -m" incorrectly displayed commits that were skipped.
diff --git a/Documentation/RelNotes-1.5.4.1.txt b/Documentation/RelNotes-1.5.4.1.txt
deleted file mode 100644 (file)
index d4e44b8..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-GIT v1.5.4.1 Release Notes
-==========================
-
-Fixes since v1.5.4
-------------------
-
- * "git-commit -C $tag" used to work but rewrite in C done in
-   1.5.4 broke it.
-
- * An entry in the .gitattributes file that names a pattern in a
-   subdirectory of the directory it is in did not match
-   correctly (e.g. pattern "b/*.c" in "a/.gitattributes" should
-   match "a/b/foo.c" but it didn't).
-
- * Customized color specification was parsed incorrectly when
-   numeric color values are used.  This was fixed in 1.5.4.1.
-
diff --git a/Documentation/RelNotes-1.5.4.2.txt b/Documentation/RelNotes-1.5.4.2.txt
deleted file mode 100644 (file)
index 21d0df5..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-GIT v1.5.4.2 Release Notes
-==========================
-
-Fixes since v1.5.4
-------------------
-
- * The configuration parser was not prepared to see string
-   valued variables misspelled as boolean and segfaulted.
-
- * Temporary files left behind due to interrupted object
-   transfers were not cleaned up with "git prune".
-
- * "git config --unset" was confused when the unset variables
-   were spelled with continuation lines in the config file.
-
- * The merge message detection in "git cvsimport" did not catch
-   a message that began with "Merge...".
-
- * "git status" suggests "git rm --cached" for unstaging the
-   earlier "git add" before the initial commit.
-
- * "git status" output was incorrect during a partial commit.
-
- * "git bisect" refused to start when the HEAD was detached.
-
- * "git bisect" allowed a wildcard character in the commit
-   message expanded while writing its log file.
-
- * Manual pages were not formatted correctly with docbook xsl
-   1.72; added a workaround.
-
- * "git-commit -C $tag" used to work but rewrite in C done in
-   1.5.4 broke it.  This was fixed in 1.5.4.1.
-
- * An entry in the .gitattributes file that names a pattern in a
-   subdirectory of the directory it is in did not match
-   correctly (e.g. pattern "b/*.c" in "a/.gitattributes" should
-   match "a/b/foo.c" but it didn't).  This was fixed in 1.5.4.1.
-
- * Customized color specification was parsed incorrectly when
-   numeric color values are used.  This was fixed in 1.5.4.1.
-
- * http transport misbehaved when linked with curl-gnutls.
diff --git a/Documentation/RelNotes-1.5.4.3.txt b/Documentation/RelNotes-1.5.4.3.txt
deleted file mode 100644 (file)
index b0fc67f..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-GIT v1.5.4.3 Release Notes
-==========================
-
-Fixes since v1.5.4.2
---------------------
-
- * RPM spec used to pull in everything with 'git'.  This has been
-   changed so that 'git' package contains just the core parts,
-   and we now supply 'git-all' metapackage to slurp in everything.
-   This should match end user's expectation better.
-
- * When some refs failed to update, git-push reported "failure"
-   which was unclear if some other refs were updated or all of
-   them failed atomically (the answer is the former).  Reworded
-   the message to clarify this.
-
- * "git clone" from a repository whose HEAD was misconfigured
-   did not set up the remote properly.  Now it tries to do
-   better.
-
- * Updated git-push documentation to clarify what "matching"
-   means, in order to reduce user confusion.
-
- * Updated git-add documentation to clarify "add -u" operates in
-   the current subdirectory you are in, just like other commands.
-
- * git-gui updates to work on OSX and Windows better.
diff --git a/Documentation/RelNotes-1.5.4.4.txt b/Documentation/RelNotes-1.5.4.4.txt
deleted file mode 100644 (file)
index 323c1a8..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-GIT v1.5.4.4 Release Notes
-==========================
-
-Fixes since v1.5.4.3
---------------------
-
- * Building and installing with an overtight umask such as 077 made
-   installed templates unreadable by others, while the rest of the install
-   are done in a way that is friendly to umask 022.
-
- * "git cvsexportcommit -w $cvsdir" misbehaved when GIT_DIR is set to a
-   relative directory.
-
- * "git http-push" had an invalid memory access that could lead it to
-   segfault.
-
- * When "git rebase -i" gave control back to the user for a commit that is
-   marked to be edited, it just said "modify it with commit --amend",
-   without saying what to do to continue after modifying it.  Give an
-   explicit instruction to run "rebase --continue" to be more helpful.
-
- * "git send-email" in 1.5.4.3 issued a bogus empty In-Reply-To: header.
-
- * "git bisect" showed mysterious "won't bisect on seeked tree" error message.
-   This was leftover from Cogito days to prevent "bisect" starting from a
-   cg-seeked state.  We still keep the Cogito safety, but running "git bisect
-   start" when another bisect was in effect will clean up and start over.
-
- * "git push" with an explicit PATH to receive-pack did not quite work if
-   receive-pack was not on usual PATH.  We earlier fixed the same issue
-   with "git fetch" and upload-pack, but somehow forgot to do so in the
-   other direction.
-
- * git-gui's info dialog was not displayed correctly when the user tries
-   to commit nothing (i.e. without staging anything).
-
- * "git revert" did not properly fail when attempting to run with a
-   dirty index.
-
- * "git merge --no-commit --no-ff <other>" incorrectly made commits.
-
- * "git merge --squash --no-ff <other>", which is a nonsense combination
-   of options, was not rejected.
-
- * "git ls-remote" and "git remote show" against an empty repository
-   failed, instead of just giving an empty result (regression).
-
- * "git fast-import" did not handle a renamed path whose name needs to be
-   quoted, due to a bug in unquote_c_style() function.
-
- * "git cvsexportcommit" was confused when multiple files with the same
-   basename needed to be pushed out in the same commit.
-
- * "git daemon" did not send early errors to syslog.
-
- * "git log --merge" did not work well with --left-right option.
-
- * "git svn" prompted for client cert password every time it accessed the
-   server.
-
- * The reset command in "git fast-import" data stream was documented to
-   end with an optional LF, but it actually required one.
-
- * "git svn dcommit/rebase" did not honor --rewrite-root option.
-
-Also included are a handful documentation updates.
diff --git a/Documentation/RelNotes-1.5.4.5.txt b/Documentation/RelNotes-1.5.4.5.txt
deleted file mode 100644 (file)
index bbd130e..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-GIT v1.5.4.5 Release Notes
-==========================
-
-Fixes since v1.5.4.4
---------------------
-
- * "git fetch there" when the URL information came from the Cogito style
-   branches/there file did not update refs/heads/there (regression in
-   1.5.4).
-
- * Bogus refspec configuration such as "remote.there.fetch = =" were not
-   detected as errors (regression in 1.5.4).
-
- * You couldn't specify a custom editor whose path contains a whitespace
-   via GIT_EDITOR (and core.editor).
-
- * The subdirectory filter to "git filter-branch" mishandled a history
-   where the subdirectory becomes empty and then later becomes non-empty.
-
- * "git shortlog" gave an empty line if the original commit message was
-   malformed (e.g. a botched import from foreign SCM).  Now it finds the
-   first non-empty line and uses it for better information.
-
- * When the user fails to give a revision parameter to "git svn", an error
-   from the Perl interpreter was issued because the script lacked proper
-   error checking.
-
- * After "git rebase" stopped due to conflicts, if the user played with
-   "git reset" and friends, "git rebase --abort" failed to go back to the
-   correct commit.
-
- * Additional work trees prepared with git-new-workdir (in contrib/) did
-   not share git-svn metadata directory .git/svn with the original.
-
- * "git-merge-recursive" did not mark addition of the same path with
-   different filemodes correctly as a conflict.
-
- * "gitweb" gave malformed URL when pathinfo stype paths are in use.
-
- * "-n" stands for "--no-tags" again for "git fetch".
-
- * "git format-patch" did not detect the need to add 8-bit MIME header
-   when the user used format.header configuration.
-
- * "rev~" revision specifier used to mean "rev", which was inconsistent
-   with how "rev^" worked.  Now "rev~" is the same as "rev~1" (hence it
-   also is the same as "rev^1"), and "rev~0" is the same as "rev^0"
-   (i.e. it has to be a commit).
-
- * "git quiltimport" did not grok empty lines, lines in "file -pNNN"
-   format to specify the prefix levels and lines with trailing comments.
-
- * "git rebase -m" triggered pre-commit verification, which made
-   "rebase --continue" impossible.
-
-As usual, it also comes with many documentation fixes and clarifications.
diff --git a/Documentation/RelNotes-1.5.4.6.txt b/Documentation/RelNotes-1.5.4.6.txt
deleted file mode 100644 (file)
index 3e3c3e5..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-GIT v1.5.4.6 Release Notes
-==========================
-
-I personally do not think there is any reason anybody should want to
-run v1.5.4.X series these days, because 'master' version is always
-more stable than any tagged released version of git.
-
-This is primarily to futureproof "git-shell" to accept requests
-without a dash between "git" and subcommand name (e.g. "git
-upload-pack") which the newer client will start to make sometime in
-the future.
-
-Fixes since v1.5.4.5
---------------------
-
- * Command line option "-n" to "git-repack" was not correctly parsed.
-
- * Error messages from "git-apply" when the patchfile cannot be opened
-   have been improved.
-
- * Error messages from "git-bisect" when given nonsense revisions have
-   been improved.
-
- * reflog syntax that uses time e.g. "HEAD@{10 seconds ago}:path" did not
-   stop parsing at the closing "}".
-
- * "git rev-parse --symbolic-full-name ^master^2" printed solitary "^",
-   but it should print nothing.
-
- * "git apply" did not enforce "match at the beginning" correctly.
-
- * a path specification "a/b" in .gitattributes file should not match
-   "sub/a/b", but it did.
-
- * "git log --date-order --topo-order" did not override the earlier
-   date-order with topo-order as expected.
-
- * "git fast-export" did not export octopus merges correctly.
-
- * "git archive --prefix=$path/" mishandled gitattributes.
-
-As usual, it also comes with many documentation fixes and clarifications.
-
diff --git a/Documentation/RelNotes-1.5.4.7.txt b/Documentation/RelNotes-1.5.4.7.txt
deleted file mode 100644 (file)
index 9065a0e..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-GIT v1.5.4.7 Release Notes
-==========================
-
-Fixes since 1.5.4.7
--------------------
-
- * Removed support for an obsolete gitweb request URI, whose
-   implementation ran "git diff" Porcelain, instead of using plumbing,
-   which would have run an external diff command specified in the
-   repository configuration as the gitweb user.
diff --git a/Documentation/RelNotes-1.5.4.txt b/Documentation/RelNotes-1.5.4.txt
deleted file mode 100644 (file)
index f1323b6..0000000
+++ /dev/null
@@ -1,377 +0,0 @@
-GIT v1.5.4 Release Notes
-========================
-
-Removal
--------
-
- * "git svnimport" was removed in favor of "git svn".  It is still there
-   in the source tree (contrib/examples) but unsupported.
-
- * As git-commit and git-status have been rewritten, "git runstatus"
-   helper script lost all its users and has been removed.
-
-
-Temporarily disabled
---------------------
-
- * "git http-push" is known not to work well with cURL library older
-   than 7.16, and we had reports of repository corruption.  It is
-   disabled on such platforms for now.  Unfortunately, 1.5.3.8 shares
-   the same issue.  In other words, this does not mean you will be
-   fine if you stick to an older git release.  For now, please do not
-   use http-push from older git with cURL older than 7.16 if you
-   value your data. A proper fix will hopefully materialize in
-   later versions.
-
-
-Deprecation notices
--------------------
-
- * From v1.6.0, git will by default install dashed form of commands
-   (e.g. "git-commit") outside of users' normal $PATH, and will install
-   only selected commands ("git" itself, and "gitk") in $PATH.  This
-   implies:
-
-   - Using dashed forms of git commands (e.g. "git-commit") from the
-     command line has been informally deprecated since early 2006, but
-     now it officially is, and will be removed in the future.  Use
-     dash-less forms (e.g. "git commit") instead.
-
-   - Using dashed forms from your scripts, without first prepending the
-     return value from "git --exec-path" to the scripts' PATH, has been
-     informally deprecated since early 2006, but now it officially is.
-
-   - Use of dashed forms with "PATH=$(git --exec-path):$PATH; export
-     PATH" early in your script is not deprecated with this change.
-
-   Users are strongly encouraged to adjust their habits and scripts now
-   to prepare for this change.
-
- * The post-receive hook was introduced in March 2007 to supersede
-   the post-update hook, primarily to overcome the command line length
-   limitation of the latter.  Use of post-update hook will be deprecated
-   in future versions of git, starting from v1.6.0.
-
- * "git lost-found" was deprecated in favor of "git fsck"'s --lost-found
-   option, and will be removed in the future.
-
- * "git peek-remote" is deprecated, as "git ls-remote" was written in C
-   and works for all transports; "git peek-remote" will be removed in
-   the future.
-
- * "git repo-config" which was an old name for "git config" command
-   has been supported without being advertised for a long time.  The
-   next feature release will remove it.
-
- * From v1.6.0, the repack.usedeltabaseoffset config option will default
-   to true, which will give denser packfiles (i.e. more efficient storage).
-   The downside is that git older than version 1.4.4 will not be able
-   to directly use a repository packed using this setting.
-
- * From v1.6.0, the pack.indexversion config option will default to 2,
-   which is slightly more efficient, and makes repacking more immune to
-   data corruptions.  Git older than version 1.5.2 may revert to version 1
-   of the pack index with a manual "git index-pack" to be able to directly
-   access corresponding pack files.
-
-
-Updates since v1.5.3
---------------------
-
- * Comes with much improved gitk, with i18n.
-
- * Comes with git-gui 0.9.2 with i18n.
-
- * gitk is now merged as a subdirectory of git.git project, in
-   preparation for its i18n.
-
- * progress displays from many commands are a lot nicer to the eye.
-   Transfer commands show throughput data.
-
- * many commands that pay attention to per-directory .gitignore now do
-   so lazily, which makes the usual case go much faster.
-
- * Output processing for '--pretty=format:<user format>' has been
-   optimized.
-
- * Rename detection of diff family while detecting exact matches has
-   been greatly optimized.
-
- * Rename detection of diff family tries to make more natural looking
-   pairing.  Earlier, if multiple identical rename sources were
-   found in the preimage, the source used was picked pretty much at random.
-
- * Value "true" for color.diff and color.status configuration used to
-   mean "always" (even when the output is not going to a terminal).
-   This has been corrected to mean the same thing as "auto".
-
- * "git diff" Porcelain now respects diff.external configuration, which
-   is another way to specify GIT_EXTERNAL_DIFF.
-
- * "git diff" can be told to use different prefixes other than
-   "a/" and "b/" e.g. "git diff --src-prefix=l/ --dst-prefix=k/".
-
- * "git diff" sometimes did not quote paths with funny
-   characters properly.
-
- * "git log" (and any revision traversal commands) misbehaved
-   when --diff-filter is given but was not asked to actually
-   produce diff.
-
- * HTTP proxy can be specified per remote repository using
-   remote.*.httpproxy configuration, or global http.proxy configuration
-   variable.
-
- * Various Perforce importer updates.
-
- * Example update and post-receive hooks have been improved.
-
- * Any command that wants to take a commit object name can now use
-   ":/string" syntax to name a commit.
-
- * "git reset" is now built-in and its output can be squelched with -q.
-
- * "git reset --hard" does not make any sense in a bare
-   repository, but did not error out; fixed.
-
- * "git send-email" can optionally talk over ssmtp and use SMTP-AUTH.
-
- * "git rebase" learned --whitespace option.
-
- * In "git rebase", when you decide not to replay a particular change
-   after the command stopped with a conflict, you can say "git rebase
-   --skip" without first running "git reset --hard", as the command now
-   runs it for you.
-
- * "git rebase --interactive" mode can now work on detached HEAD.
-
- * Other minor to serious bugs in "git rebase -i" have been fixed.
-
- * "git rebase" now detaches head during its operation, so after a
-   successful "git rebase" operation, the reflog entry branch@{1} for
-   the current branch points at the commit before the rebase was
-   started.
-
- * "git rebase -i" also triggers rerere to help your repeated merges.
-
- * "git merge" can call the "post-merge" hook.
-
- * "git pack-objects" can optionally run deltification with multiple
-   threads.
-
- * "git archive" can optionally substitute keywords in files marked with
-   export-subst attribute.
-
- * "git cherry-pick" made a misguided attempt to repeat the original
-   command line in the generated log message, when told to cherry-pick a
-   commit by naming a tag that points at it.  It does not anymore.
-
- * "git for-each-ref" learned %(xxxdate:<date-format>) syntax to show the
-   various date fields in different formats.
-
- * "git gc --auto" is a low-impact way to automatically run a variant of
-   "git repack" that does not lose unreferenced objects (read: safer
-   than the usual one) after the user accumulates too many loose
-   objects.
-
- * "git clean" has been rewritten in C.
-
- * You need to explicitly set clean.requireForce to "false" to allow
-   "git clean" without -f to do any damage (lack of the configuration
-   variable used to mean "do not require -f option to lose untracked
-   files", but we now use the safer default).
-
- * The kinds of whitespace errors "git diff" and "git apply" notice (and
-   fix) can be controlled via 'core.whitespace' configuration variable
-   and 'whitespace' attribute in .gitattributes file.
-
- * "git push" learned --dry-run option to show what would happen if a
-   push is run.
-
- * "git push" does not update a tracking ref on the local side when the
-   remote refused to update the corresponding ref.
-
- * "git push" learned --mirror option.  This is to push the local refs
-   one-to-one to the remote, and deletes refs from the remote that do
-   not exist anymore in the repository on the pushing side.
-
- * "git push" can remove a corrupt ref at the remote site with the usual
-   ":ref" refspec.
-
- * "git remote" knows --mirror mode.  This is to set up configuration to
-   push into a remote repository to store local branch heads to the same
-   branch on the remote side, and remove branch heads locally removed
-   from local repository at the same time.  Suitable for pushing into a
-   back-up repository.
-
- * "git remote" learned "rm" subcommand.
-
- * "git cvsserver" can be run via "git shell".  Also, "cvs" is
-   recognized as a synonym for "git cvsserver", so that CVS users
-   can be switched to git just by changing their login shell.
-
- * "git cvsserver" acts more like receive-pack by running post-receive
-   and post-update hooks.
-
- * "git am" and "git rebase" are far less verbose.
-
- * "git pull" learned to pass --[no-]ff option to underlying "git
-   merge".
-
- * "git pull --rebase" is a different way to integrate what you fetched
-   into your current branch.
-
- * "git fast-export" produces data-stream that can be fed to fast-import
-   to reproduce the history recorded in a git repository.
-
- * "git add -i" takes pathspecs to limit the set of files to work on.
-
- * "git add -p" is a short-hand to go directly to the selective patch
-   subcommand in the interactive command loop and to exit when done.
-
- * "git add -i" UI has been colorized.  The interactive prompt
-   and menu can be colored by setting color.interactive
-   configuration.  The diff output (including the hunk picker)
-   are colored with color.diff configuration.
-
- * "git commit --allow-empty" allows you to create a single-parent
-   commit that records the same tree as its parent, overriding the usual
-   safety valve.
-
- * "git commit --amend" can amend a merge that does not change the tree
-   from its first parent.
-
- * "git commit" used to unconditionally strip comment lines that
-   began with '#' and removed excess blank lines.  This behavior has
-   been made configurable.
-
- * "git commit" has been rewritten in C.
-
- * "git stash random-text" does not create a new stash anymore.  It was
-   a UI mistake.  Use "git stash save random-text", or "git stash"
-   (without extra args) for that.
-
- * "git stash clear extra-text" does not clear the whole stash
-   anymore.  It is tempting to expect "git stash clear stash@{2}"
-   to drop only a single named stash entry, and it is rude to
-   discard everything when that is asked (but not provided).
-
- * "git prune --expire <time>" can exempt young loose objects from
-   getting pruned.
-
- * "git branch --contains <commit>" can list branches that are
-   descendants of a given commit.
-
- * "git log" learned --early-output option to help interactive GUI
-   implementations.
-
- * "git bisect" learned "skip" action to mark untestable commits.
-
- * "git bisect visualize" learned a shorter synonym "git bisect view".
-
- * "git bisect visualize" runs "git log" in a non-windowed
-   environments.  It also can be told what command to run (e.g. "git
-   bisect visualize tig").
-
- * "git format-patch" learned "format.numbered" configuration variable
-   to automatically turn --numbered option on when more than one commits
-   are formatted.
-
- * "git ls-files" learned "--exclude-standard" to use the canned set of
-   exclude files.
-
- * "git tag -a -f existing" begins the editor session using the existing
-   annotation message.
-
- * "git tag -m one -m bar" (multiple -m options) behaves similarly to
-   "git commit"; the parameters to -m options are formatted as separate
-   paragraphs.
-
- * The format "git show" outputs an annotated tag has been updated to
-   include "Tagger: " and "Date: " lines from the tag itself.  Strictly
-   speaking this is a backward incompatible change, but this is a
-   reasonable usability fix and people's scripts shouldn't have been
-   relying on the exact output from "git show" Porcelain anyway.
-
- * "git cvsimport" did not notice errors from underlying "cvsps"
-   and produced a corrupt import silently.
-
- * "git cvsexportcommit" learned -w option to specify and switch to the
-   CVS working directory.
-
- * "git checkout" from a subdirectory learned to use "../path" to allow
-   checking out a path outside the current directory without cd'ing up.
-
- * "git checkout" from and to detached HEAD leaves a bit more
-   information in the reflog.
-
- * "git send-email --dry-run" shows full headers for easier diagnosis.
-
- * "git merge-ours" is now built-in.
-
- * "git svn" learned "info" and "show-externals" subcommands.
-
- * "git svn" run from a subdirectory failed to read settings from the
-   .git/config.
-
- * "git svn" learned --use-log-author option, which picks up more
-   descriptive name from From: and Signed-off-by: lines in the commit
-   message.
-
- * "git svn" wasted way too much disk to record revision mappings
-   between svn and git; a new representation that is much more compact
-   for this information has been introduced to correct this.
-
- * "git svn" left temporary index files it used without cleaning them
-   up; this was corrected.
-
- * "git status" from a subdirectory now shows relative paths, which
-   makes copy-and-pasting for git-checkout/git-add/git-rm easier.  The
-   traditional behavior to show the full path relative to the top of
-   the work tree can be had by setting status.relativepaths
-   configuration variable to false.
-
- * "git blame" kept text for each annotated revision in core needlessly;
-   this has been corrected.
-
- * "git shortlog" learned to default to HEAD when the standard input is
-   a terminal and the user did not give any revision parameter.
-
- * "git shortlog" learned "-e" option to show e-mail addresses as well as
-   authors' names.
-
- * "git help" learned "-w" option to show documentation in browsers.
-
- * In addition there are quite a few internal clean-ups. Notably:
-
-   - many fork/exec have been replaced with run-command API,
-     brought from the msysgit effort.
-
-   - introduction and more use of the option parser API.
-
-   - enhancement and more use of the strbuf API.
-
- * Makefile tweaks to support HP-UX is in.
-
-Fixes since v1.5.3
-------------------
-
-All of the fixes in v1.5.3 maintenance series are included in
-this release, unless otherwise noted.
-
-These fixes are only in v1.5.4 and not backported to v1.5.3 maintenance
-series.
-
- * The way "git diff --check" behaves is much more consistent with the way
-   "git apply --whitespace=warn" works.
-
- * "git svn" talking with the SVN over HTTP will correctly quote branch
-   and project names.
-
- * "git config" did not work correctly on platforms that define
-   REG_NOMATCH to an even number.
-
- * Recent versions of AsciiDoc 8 has a change to break our
-   documentation; a workaround has been implemented.
-
- * "git diff --color-words" colored context lines in a wrong color.
diff --git a/Documentation/RelNotes-1.5.5.1.txt b/Documentation/RelNotes-1.5.5.1.txt
deleted file mode 100644 (file)
index 7de4197..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-GIT v1.5.5.1 Release Notes
-==========================
-
-Fixes since v1.5.5
-------------------
-
- * "git archive --prefix=$path/" mishandled gitattributes.
-
- * "git fetch -v" that fetches into FETCH_HEAD did not report the summary
-   the same way as done for updating the tracking refs.
-
- * "git svn" misbehaved when the configuration file customized the "git
-   log" output format using format.pretty.
-
- * "git submodule status" leaked an unnecessary error message.
-
- * "git log --date-order --topo-order" did not override the earlier
-   date-order with topo-order as expected.
-
- * "git bisect good $this" did not check the validity of the revision
-   given properly.
-
- * "url.<there>.insteadOf" did not work correctly.
-
- * "git clean" ran inside subdirectory behaved as if the directory was
-   explicitly specified for removal by the end user from the top level.
-
- * "git bisect" from a detached head leaked an unnecessary error message.
-
- * "git bisect good $a $b" when $a is Ok but $b is bogus should have
-   atomically failed before marking $a as good.
-
- * "git fmt-merge-msg" did not clean up leading empty lines from commit
-   log messages like "git log" family does.
-
- * "git am" recorded a commit with empty Subject: line without
-   complaining.
-
- * when given a commit log message whose first paragraph consists of
-   multiple lines, "git rebase" squashed it into a single line.
-
- * "git remote add $bogus_name $url" did not complain properly.
-
-Also comes with various documentation updates.
diff --git a/Documentation/RelNotes-1.5.5.2.txt b/Documentation/RelNotes-1.5.5.2.txt
deleted file mode 100644 (file)
index 391a7b0..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-GIT v1.5.5.2 Release Notes
-==========================
-
-Fixes since v1.5.5.1
---------------------
-
- * "git repack -n" was mistakenly made no-op earlier.
-
- * "git imap-send" wanted to always have imap.host even when use of
-   imap.tunnel made it unnecessary.
-
- * reflog syntax that uses time e.g. "HEAD@{10 seconds ago}:path" did not
-   stop parsing at the closing "}".
-
- * "git rev-parse --symbolic-full-name ^master^2" printed solitary "^",
-   but it should print nothing.
-
- * "git commit" did not detect when it failed to write tree objects.
-
- * "git fetch" sometimes transferred too many objects unnecessarily.
-
- * a path specification "a/b" in .gitattributes file should not match
-   "sub/a/b".
-
- * various gitweb fixes.
-
-Also comes with various documentation updates.
diff --git a/Documentation/RelNotes-1.5.5.3.txt b/Documentation/RelNotes-1.5.5.3.txt
deleted file mode 100644 (file)
index f22f98b..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-GIT v1.5.5.3 Release Notes
-==========================
-
-Fixes since v1.5.5.2
---------------------
-
- * "git send-email --compose" did not notice that non-ascii contents
-   needed some MIME magic.
-
- * "git fast-export" did not export octopus merges correctly.
-
-Also comes with various documentation updates.
diff --git a/Documentation/RelNotes-1.5.5.4.txt b/Documentation/RelNotes-1.5.5.4.txt
deleted file mode 100644 (file)
index 2d0279e..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-GIT v1.5.5.4 Release Notes
-==========================
-
-Fixes since v1.5.5.4
---------------------
-
- * "git name-rev --all" used to segfault.
diff --git a/Documentation/RelNotes-1.5.5.5.txt b/Documentation/RelNotes-1.5.5.5.txt
deleted file mode 100644 (file)
index 30fa361..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-GIT v1.5.5.5 Release Notes
-==========================
-
-I personally do not think there is any reason anybody should want to
-run v1.5.5.X series these days, because 'master' version is always
-more stable than any tagged released version of git.
-
-This is primarily to futureproof "git-shell" to accept requests
-without a dash between "git" and subcommand name (e.g. "git
-upload-pack") which the newer client will start to make sometime in
-the future.
diff --git a/Documentation/RelNotes-1.5.5.6.txt b/Documentation/RelNotes-1.5.5.6.txt
deleted file mode 100644 (file)
index d5e85cb..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-GIT v1.5.5.6 Release Notes
-==========================
-
-Fixes since 1.5.5.5
--------------------
-
- * Removed support for an obsolete gitweb request URI, whose
-   implementation ran "git diff" Porcelain, instead of using plumbing,
-   which would have run an external diff command specified in the
-   repository configuration as the gitweb user.
diff --git a/Documentation/RelNotes-1.5.5.txt b/Documentation/RelNotes-1.5.5.txt
deleted file mode 100644 (file)
index 2932212..0000000
+++ /dev/null
@@ -1,207 +0,0 @@
-GIT v1.5.5 Release Notes
-========================
-
-Updates since v1.5.4
---------------------
-
-(subsystems)
-
- * Comes with git-gui 0.10.1
-
-(portability)
-
- * We shouldn't ask for BSD group ownership semantics by setting g+s bit
-   on directories on older BSD systems that refuses chmod() by non root
-   users.  BSD semantics is the default there anyway.
-
- * Bunch of portability improvement patches coming from an effort to port
-   to Solaris has been applied.
-
-(performance)
-
- * On platforms with suboptimal qsort(3) implementation, there
-   is an option to use more reasonable substitute we ship with
-   our software.
-
- * New configuration variable "pack.packsizelimit" can be used
-   in place of command line option --max-pack-size.
-
- * "git fetch" over the native git protocol used to make a
-   connection to find out the set of current remote refs and
-   another to actually download the pack data.  We now use only
-   one connection for these tasks.
-
- * "git commit" does not run lstat(2) more than necessary
-   anymore.
-
-(usability, bells and whistles)
-
- * Bash completion script (in contrib) are aware of more commands and
-   options.
-
- * You can be warned when core.autocrlf conversion is applied in
-   such a way that results in an irreversible conversion.
-
- * A catch-all "color.ui" configuration variable can be used to
-   enable coloring of all color-capable commands, instead of
-   individual ones such as "color.status" and "color.branch".
-
- * The commands refused to take absolute pathnames where they
-   require pathnames relative to the work tree or the current
-   subdirectory.  They now can take absolute pathnames in such a
-   case as long as the pathnames do not refer outside of the
-   work tree.  E.g. "git add $(pwd)/foo" now works.
-
- * Error messages used to be sent to stderr, only to get hidden,
-   when $PAGER was in use.  They now are sent to stdout along
-   with the command output to be shown in the $PAGER.
-
- * A pattern "foo/" in .gitignore file now matches a directory
-   "foo".  Pattern "foo" also matches as before.
-
- * bash completion's prompt helper function can talk about
-   operation in-progress (e.g. merge, rebase, etc.).
-
- * Configuration variables "url.<usethis>.insteadof = <otherurl>" can be
-   used to tell "git-fetch" and "git-push" to use different URL than what
-   is given from the command line.
-
- * "git add -i" behaves better even before you make an initial commit.
-
- * "git am" refused to run from a subdirectory without a good reason.
-
- * After "git apply --whitespace=fix" fixes whitespace errors in a patch,
-   a line before the fix can appear as a context or preimage line in a
-   later patch, causing the patch not to apply.  The command now knows to
-   see through whitespace fixes done to context lines to successfully
-   apply such a patch series.
-
- * "git branch" (and "git checkout -b") to branch from a local branch can
-   optionally set "branch.<name>.merge" to mark the new branch to build on
-   the other local branch, when "branch.autosetupmerge" is set to
-   "always", or when passing the command line option "--track" (this option
-   was ignored when branching from local branches).  By default, this does
-   not happen when branching from a local branch.
-
- * "git checkout" to switch to a branch that has "branch.<name>.merge" set
-   (i.e. marked to build on another branch) reports how much the branch
-   and the other branch diverged.
-
- * When "git checkout" has to update a lot of paths, it used to be silent
-   for 4 seconds before it showed any progress report.  It is now a bit
-   more impatient and starts showing progress report early.
-
- * "git commit" learned a new hook "prepare-commit-msg" that can
-   inspect what is going to be committed and prepare the commit
-   log message template to be edited.
-
- * "git cvsimport" can now take more than one -M options.
-
- * "git describe" learned to limit the tags to be used for
-   naming with --match option.
-
- * "git describe --contains" now barfs when the named commit
-   cannot be described.
-
- * "git describe --exact-match" describes only commits that are tagged.
-
- * "git describe --long" describes a tagged commit as $tag-0-$sha1,
-   instead of just showing the exact tagname.
-
- * "git describe" warns when using a tag whose name and path contradict
-   with each other.
-
- * "git diff" learned "--relative" option to limit and output paths
-   relative to the current directory when working in a subdirectory.
-
- * "git diff" learned "--dirstat" option to show birds-eye-summary of
-   changes more concisely than "--diffstat".
-
- * "git format-patch" learned --cover-letter option to generate a cover
-   letter template.
-
- * "git gc" learned --quiet option.
-
- * "git gc" now automatically prunes unreachable objects that are two
-   weeks old or older.
-
- * "git gc --auto" can be disabled more easily by just setting gc.auto
-   to zero.  It also tolerates more packfiles by default.
-
- * "git grep" now knows "--name-only" is a synonym for the "-l" option.
-
- * "git help <alias>" now reports "'git <alias>' is alias to <what>",
-   instead of saying "No manual entry for git-<alias>".
-
- * "git help" can use different backends to show manual pages and this can
-   be configured using "man.viewer" configuration.
-
- * "gitk" does not restore window position from $HOME/.gitk anymore (it
-   still restores the size).
-
- * "git log --grep=<what>" learned "--fixed-strings" option to look for
-   <what> without treating it as a regular expression.
-
- * "git gui" learned an auto-spell checking.
-
- * "git push <somewhere> HEAD" and "git push <somewhere> +HEAD" works as
-   expected; they push the current branch (and only the current branch).
-   In addition, HEAD can be written as the value of "remote.<there>.push"
-   configuration variable.
-
- * When the configuration variable "pack.threads" is set to 0, "git
-   repack" auto detects the number of CPUs and uses that many threads.
-
- * "git send-email" learned to prompt for passwords
-   interactively.
-
- * "git send-email" learned an easier way to suppress CC
-   recipients.
-
- * "git stash" learned "pop" command, that applies the latest stash and
-   removes it from the stash, and "drop" command to discard the named
-   stash entry.
-
- * "git submodule" learned a new subcommand "summary" to show the
-   symmetric difference between the HEAD version and the work tree version
-   of the submodule commits.
-
- * Various "git cvsimport", "git cvsexportcommit", "git cvsserver",
-   "git svn" and "git p4" improvements.
-
-(internal)
-
- * Duplicated code between git-help and git-instaweb that
-   launches user's preferred browser has been refactored.
-
- * It is now easier to write test scripts that records known
-   breakages.
-
- * "git checkout" is rewritten in C.
-
- * "git remote" is rewritten in C.
-
- * Two conflict hunks that are separated by a very short span of common
-   lines are now coalesced into one larger hunk, to make the result easier
-   to read.
-
- * Run-command API's use of file descriptors is documented clearer and
-   is more consistent now.
-
- * diff output can be sent to FILE * that is different from stdout.  This
-   will help reimplementing more things in C.
-
-Fixes since v1.5.4
-------------------
-
-All of the fixes in v1.5.4 maintenance series are included in
-this release, unless otherwise noted.
-
- * "git-http-push" did not allow deletion of remote ref with the usual
-   "push <remote> :<branch>" syntax.
-
- * "git-rebase --abort" did not go back to the right location if
-   "git-reset" was run during the "git-rebase" session.
-
- * "git imap-send" without setting imap.host did not error out but
-   segfaulted.
diff --git a/Documentation/RelNotes-1.5.6.1.txt b/Documentation/RelNotes-1.5.6.1.txt
deleted file mode 100644 (file)
index 4864b16..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-GIT v1.5.6.1 Release Notes
-==========================
-
-Fixes since v1.5.6
-------------------
-
-* Last minute change broke loose object creation on AIX.
-
-* (performance fix) We used to make $GIT_DIR absolute path early in the
-  programs but keeping it relative to the current directory internally
-  gives 1-3 per-cent performance boost.
-
-* bash completion knows the new --graph option to git-log family.
-
-
-* git-diff -c/--cc showed unnecessary "deletion" lines at the context
-  boundary.
-
-* git-for-each-ref ignored %(object) and %(type) requests for tag
-  objects.
-
-* git-merge usage had a typo.
-
-* Rebuilding of git-svn metainfo database did not take rewriteRoot
-  option into account.
-
-* Running "git-rebase --continue/--skip/--abort" before starting a
-  rebase gave nonsense error messages.
diff --git a/Documentation/RelNotes-1.5.6.2.txt b/Documentation/RelNotes-1.5.6.2.txt
deleted file mode 100644 (file)
index 5902a85..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-GIT v1.5.6.2 Release Notes
-==========================
-
-Futureproof
------------
-
- * "git-shell" accepts requests without a dash between "git" and
-   subcommand name (e.g. "git upload-pack") which the newer client will
-   start to make sometime in the future.
-
-Fixes since v1.5.6.1
---------------------
-
-* "git clone" from a remote that is named with url.insteadOf setting in
-  $HOME/.gitconfig did not work well.
-
-* "git describe --long --tags" segfaulted when the described revision was
-  tagged with a lightweight tag.
-
-* "git diff --check" did not report the result via its exit status
-  reliably.
-
-* When remote side used to have branch 'foo' and git-fetch finds that now
-  it has branch 'foo/bar', it refuses to lose the existing remote tracking
-  branch and its reflog.  The error message has been improved to suggest
-  pruning the remote if the user wants to proceed and get the latest set
-  of branches from the remote, including such 'foo/bar'.
-
-* "git reset file" should mean the same thing as "git reset HEAD file",
-  but we required disambiguating -- even when "file" is not ambiguous.
-
-* "git show" segfaulted when an annotated tag that points at another
-  annotated tag was given to it.
-
-* Optimization for a large import via "git-svn" introduced in v1.5.6 had a
-  serious memory and temporary file leak, which made it unusable for
-  moderately large import.
-
-* "git-svn" mangled remote nickname used in the configuration file
-  unnecessarily.
diff --git a/Documentation/RelNotes-1.5.6.3.txt b/Documentation/RelNotes-1.5.6.3.txt
deleted file mode 100644 (file)
index 9426112..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-GIT v1.5.6.3 Release Notes
-==========================
-
-Fixes since v1.5.6.2
---------------------
-
-* Setting core.sharerepository to traditional "true" value was supposed to make
-  the repository group writable but should not affect permission for others.
-  However, since 1.5.6, it was broken to drop permission for others when umask is
-  022, making the repository unreadable by others.
-
-* Setting GIT_TRACE will report spawning of external process via run_command().
-
-* Using an object with very deep delta chain pinned memory needed for extracting
-  intermediate base objects unnecessarily long, leading to excess memory usage.
-
-* Bash completion script did not notice '--' marker on the command
-  line and tried the relatively slow "ref completion" even when
-  completing arguments after one.
-
-* Registering a non-empty blob racily and then truncating the working
-  tree file for it confused "racy-git avoidance" logic into thinking
-  that the path is now unchanged.
-
-* The section that describes attributes related to git-archive were placed
-  in a wrong place in the gitattributes(5) manual page.
-
-* "git am" was not helpful to the users when it detected that the committer
-  information is not set up properly yet.
-
-* "git clone" had a leftover debugging fprintf().
-
-* "git clone -q" was not quiet enough as it used to and gave object count
-  and progress reports.
-
-* "git clone" marked downloaded packfile with .keep; this could be a
-  good thing if the remote side is well packed but otherwise not,
-  especially for a project that is not really big.
-
-* "git daemon" used to call syslog() from a signal handler, which
-  could raise signals of its own but generally is not reentrant.  This
-  was fixed by restructuring the code to report syslog() after the handler
-  returns.
-
-* When "git push" tries to remove a remote ref, and corresponding
-  tracking ref is missing, we used to report error (i.e. failure to
-  remove something that does not exist).
-
-* "git mailinfo" (hence "git am") did not handle commit log messages in a
-  MIME multipart mail correctly.
-
-Contains other various documentation fixes.
diff --git a/Documentation/RelNotes-1.5.6.4.txt b/Documentation/RelNotes-1.5.6.4.txt
deleted file mode 100644 (file)
index d8968f1..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-GIT v1.5.6.4 Release Notes
-==========================
-
-Fixes since v1.5.6.3
---------------------
-
-* Various commands could overflow its internal buffer on a platform
-  with small PATH_MAX value in a repository that has contents with
-  long pathnames.
-
-* There wasn't a way to make --pretty=format:%<> specifiers to honor
-  .mailmap name rewriting for authors and committers.  Now you can with
-  %aN and %cN.
-
-* Bash completion wasted too many cycles; this has been optimized to be
-  usable again.
-
-* Bash completion lost ref part when completing something like "git show
-  pu:Makefile".
-
-* "git-cvsserver" did not clean up its temporary working area after annotate
-  request.
-
-* "git-daemon" called syslog() from its signal handler, which was a
-  no-no.
-
-* "git-fetch" into an empty repository used to remind that the fetch will
-   be huge by saying "no common commits", but this was an unnecessary
-   noise; it is already known by the user anyway.
-
-* "git-http-fetch" would have segfaulted when pack idx file retrieved
-  from the other side was corrupt.
-
-* "git-index-pack" used too much memory when dealing with a deep delta chain.
-
-* "git-mailinfo" (hence "git-am") did not correctly handle in-body [PATCH]
-  line to override the commit title taken from the mail Subject header.
-
-* "git-rebase -i -p" lost parents that are not involved in the history
-  being rewritten.
-
-* "git-rm" lost track of where the index file was when GIT_DIR was
-  specified as a relative path.
-
-* "git-rev-list --quiet" was not quiet as advertised.
-
-Contains other various documentation fixes.
diff --git a/Documentation/RelNotes-1.5.6.5.txt b/Documentation/RelNotes-1.5.6.5.txt
deleted file mode 100644 (file)
index 47ca172..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-GIT v1.5.6.5 Release Notes
-==========================
-
-Fixes since v1.5.6.4
---------------------
-
-* "git cvsimport" used to spit out "UNKNOWN LINE..." diagnostics to stdout.
-
-* "git commit -F filename" and "git tag -F filename" run from subdirectories
-  did not read the right file.
-
-* "git init --template=" with blank "template" parameter linked files
-  under root directories to .git, which was a total nonsense.  Instead, it
-  means "I do not want to use anything from the template directory".
-
-* "git diff-tree" and other diff plumbing ignored diff.renamelimit configuration
-  variable when the user explicitly asked for rename detection.
-
-* "git name-rev --name-only" did not work when "--stdin" option was in effect.
-
-* "git show-branch" mishandled its 8th branch.
-
-* Addition of "git update-index --ignore-submodules" that happened during
-  1.5.6 cycle broke "git update-index --ignore-missing".
-
-* "git send-email" did not parse charset from an existing Content-type:
-  header properly.
-
-Contains other various documentation fixes.
diff --git a/Documentation/RelNotes-1.5.6.6.txt b/Documentation/RelNotes-1.5.6.6.txt
deleted file mode 100644 (file)
index 79da23d..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-GIT v1.5.6.6 Release Notes
-==========================
-
-Fixes since 1.5.6.5
--------------------
-
- * Removed support for an obsolete gitweb request URI, whose
-   implementation ran "git diff" Porcelain, instead of using plumbing,
-   which would have run an external diff command specified in the
-   repository configuration as the gitweb user.
diff --git a/Documentation/RelNotes-1.5.6.txt b/Documentation/RelNotes-1.5.6.txt
deleted file mode 100644 (file)
index e143d8d..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-GIT v1.5.6 Release Notes
-========================
-
-Updates since v1.5.5
---------------------
-
-(subsystems)
-
-* Comes with updated gitk and git-gui.
-
-(portability)
-
-* git will build on AIX better than before now.
-
-* core.ignorecase configuration variable can be used to work better on
-  filesystems that are not case sensitive.
-
-* "git init" now autodetects the case sensitivity of the filesystem and
-  sets core.ignorecase accordingly.
-
-* cpio is no longer used; neither "curl" binary (libcurl is still used).
-
-(documentation)
-
-* Many freestanding documentation pages have been converted and made
-  available to "git help" (aka "man git<something>") as section 7 of
-  the manual pages. This means bookmarks to some HTML documentation
-  files may need to be updated (eg "tutorial.html" became
-  "gittutorial.html").
-
-(performance)
-
-* "git clone" was rewritten in C.  This will hopefully help cloning a
-  repository with insane number of refs.
-
-* "git rebase --onto $there $from $branch" used to switch to the tip of
-  $branch only to immediately reset back to $from, smudging work tree
-  files unnecessarily.  This has been optimized.
-
-* Object creation codepath in "git-svn" has been optimized by enhancing
-  plumbing commands git-cat-file and git-hash-object.
-
-(usability, bells and whistles)
-
-* "git add -p" (and the "patch" subcommand of "git add -i") can choose to
-  apply (or not apply) mode changes independently from contents changes.
-
-* "git bisect help" gives longer and more helpful usage information.
-
-* "git bisect" does not use a special branch "bisect" anymore; instead, it
-  does its work on a detached HEAD.
-
-* "git branch" (and "git checkout -b") can be told to set up
-  branch.<name>.rebase automatically, so that later you can say "git pull"
-  and magically cause "git pull --rebase" to happen.
-
-* "git branch --merged" and "git branch --no-merged" can be used to list
-  branches that have already been merged (or not yet merged) to the
-  current branch.
-
-* "git cherry-pick" and "git revert" can add a sign-off.
-
-* "git commit" mentions the author identity when you are committing
-  somebody else's changes.
-
-* "git diff/log --dirstat" output is consistent between binary and textual
-  changes.
-
-* "git filter-branch" rewrites signed tags by demoting them to annotated.
-
-* "git format-patch --no-binary" can produce a patch that lack binary
-  changes (i.e. cannot be used to propagate the whole changes) meant only
-  for reviewing.
-
-* "git init --bare" is a synonym for "git --bare init" now.
-
-* "git gc --auto" honors a new pre-auto-gc hook to temporarily disable it.
-
-* "git log --pretty=tformat:<custom format>" gives a LF after each entry,
-  instead of giving a LF between each pair of entries which is how
-  "git log --pretty=format:<custom format>" works.
-
-* "git log" and friends learned the "--graph" option to show the ancestry
-  graph at the left margin of the output.
-
-* "git log" and friends can be told to use date format that is different
-  from the default via 'log.date' configuration variable.
-
-* "git send-email" now can send out messages outside a git repository.
-
-* "git send-email --compose" was made aware of rfc2047 quoting.
-
-* "git status" can optionally include output from "git submodule
-  summary".
-
-* "git svn" learned --add-author-from option to propagate the authorship
-  by munging the commit log message.
-
-* new object creation and looking up in "git svn" has been optimized.
-
-* "gitweb" can read from a system-wide configuration file.
-
-(internal)
-
-* "git unpack-objects" and "git receive-pack" is now more strict about
-  detecting breakage in the objects they receive over the wire.
-
-
-Fixes since v1.5.5
-------------------
-
-All of the fixes in v1.5.5 maintenance series are included in
-this release, unless otherwise noted.
-
-And there are too numerous small fixes to otherwise note here ;-)
diff --git a/Documentation/RelNotes-1.6.0.1.txt b/Documentation/RelNotes-1.6.0.1.txt
deleted file mode 100644 (file)
index 49d7a1c..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-GIT v1.6.0.1 Release Notes
-==========================
-
-Fixes since v1.6.0
-------------------
-
-* "git diff --cc" did not honor content mangling specified by
-  gitattributes and core.autocrlf when reading from the work tree.
-
-* "git diff --check" incorrectly detected new trailing blank lines when
-  whitespace check was in effect.
-
-* "git for-each-ref" tried to dereference NULL when asked for '%(body)" on
-  a tag with a single incomplete line as its payload.
-
-* "git format-patch" peeked before the beginning of a string when
-  "format.headers" variable is empty (a misconfiguration).
-
-* "git help help" did not work correctly.
-
-* "git mailinfo" (hence "git am") was unhappy when MIME multipart message
-  contained garbage after the finishing boundary.
-
-* "git mailinfo" also was unhappy when the "From: " line only had a bare
-  e-mail address.
-
-* "git merge" did not refresh the index correctly when a merge resulted in
-  a fast-forward.
-
-* "git merge" did not resolve a truly trivial merges that can be done
-  without content level merges.
-
-* "git svn dcommit" to a repository with URL that has embedded usernames
-  did not work correctly.
-
-Contains other various documentation fixes.
diff --git a/Documentation/RelNotes-1.6.0.2.txt b/Documentation/RelNotes-1.6.0.2.txt
deleted file mode 100644 (file)
index 51b32f5..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-GIT v1.6.0.2 Release Notes
-==========================
-
-Fixes since v1.6.0.1
---------------------
-
-* Installation on platforms that needs .exe suffix to git-* programs were
-  broken in 1.6.0.1.
-
-* Installation on filesystems without symbolic links support did not
-  work well.
-
-* In-tree documentations and test scripts now use "git foo" form to set a
-  better example, instead of the "git-foo" form (which is an acceptable
-  form if you have "PATH=$(git --exec-path):$PATH" in your script)
-
-* Many commands did not use the correct working tree location when used
-  with GIT_WORK_TREE environment settings.
-
-* Some systems needs to use compatibility fnmach and regex libraries
-  independent from each other; the compat/ area has been reorganized to
-  allow this.
-
-
-* "git apply --unidiff-zero" incorrectly applied a -U0 patch that inserts
-  a new line before the second line.
-
-* "git blame -c" did not exactly work like "git annotate" when range
-  boundaries are involved.
-
-* "git checkout file" when file is still unmerged checked out contents from
-  a random high order stage, which was confusing.
-
-* "git clone $there $here/" with extra trailing slashes after explicit
-  local directory name $here did not work as expected.
-
-* "git diff" on tracked contents with CRLF line endings did not drive "less"
-  intelligently when showing added or removed lines.
-
-* "git diff --dirstat -M" did not add changes in subdirectories up
-  correctly for renamed paths.
-
-* "git diff --cumulative" did not imply "--dirstat".
-
-* "git for-each-ref refs/heads/" did not work as expected.
-
-* "git gui" allowed users to feed patch without any context to be applied.
-
-* "git gui" botched parsing "diff" output when a line that begins with two
-  dashes and a space gets removed or a line that begins with two pluses
-  and a space gets added.
-
-* "git gui" translation updates and i18n fixes.
-
-* "git index-pack" is more careful against disk corruption while completing
-  a thin pack.
-
-* "git log -i --grep=pattern" did not ignore case; neither "git log -E
-  --grep=pattern" triggered extended regexp.
-
-* "git log --pretty="%ad" --date=short" did not use short format when
-  showing the timestamp.
-
-* "git log --author=author" match incorrectly matched with the
-  timestamp part of "author " line in commit objects.
-
-* "git log -F --author=author" did not work at all.
-
-* Build procedure for "git shell" that used stub versions of some
-  functions and globals was not understood by linkers on some platforms.
-
-* "git stash" was fooled by a stat-dirty but otherwise unmodified paths
-  and refused to work until the user refreshed the index.
-
-* "git svn" was broken on Perl before 5.8 with recent fixes to reduce
-  use of temporary files.
-
-* "git verify-pack -v" did not work correctly when given more than one
-  packfile.
-
-Also contains many documentation updates.
-
---
-exec >/var/tmp/1
-O=v1.6.0.1-78-g3632cfc
-echo O=$(git describe maint)
-git shortlog --no-merges $O..maint
diff --git a/Documentation/RelNotes-1.6.0.3.txt b/Documentation/RelNotes-1.6.0.3.txt
deleted file mode 100644 (file)
index ae05778..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-GIT v1.6.0.3 Release Notes
-==========================
-
-Fixes since v1.6.0.2
---------------------
-
-* "git archive --format=zip" did not honor core.autocrlf while
-  --format=tar did.
-
-* Continuing "git rebase -i" was very confused when the user left modified
-  files in the working tree while resolving conflicts.
-
-* Continuing "git rebase -i" was also very confused when the user left
-  some staged changes in the index after "edit".
-
-* "git rebase -i" now honors the pre-rebase hook, just like the
-  other rebase implementations "git rebase" and "git rebase -m".
-
-* "git rebase -i" incorrectly aborted when there is no commit to replay.
-
-* Behaviour of "git diff --quiet" was inconsistent with "diff --exit-code"
-  with the output redirected to /dev/null.
-
-* "git diff --no-index" on binary files no longer outputs a bogus
-  "diff --git" header line.
-
-* "git diff" hunk header patterns with multiple elements separated by LF
-  were not used correctly.
-
-* Hunk headers in "git diff" default to using extended regular
-  expressions, fixing some of the internal patterns on non-GNU
-  platforms.
-
-* New config "diff.*.xfuncname" exposes extended regular expressions
-  for user specified hunk header patterns.
-
-* "git gc" when ejecting otherwise unreachable objects from packfiles into
-  loose form leaked memory.
-
-* "git index-pack" was recently broken and mishandled objects added by
-  thin-pack completion processing under memory pressure.
-
-* "git index-pack" was recently broken and misbehaved when run from inside
-  .git/objects/pack/ directory.
-
-* "git stash apply sash@{1}" was fixed to error out.  Prior versions
-  would have applied stash@{0} incorrectly.
-
-* "git stash apply" now offers a better suggestion on how to continue
-  if the working tree is currently dirty.
-
-* "git for-each-ref --format=%(subject)" fixed for commits with no
-  no newline in the message body.
-
-* "git remote" fixed to protect printf from user input.
-
-* "git remote show -v" now displays all URLs of a remote.
-
-* "git checkout -b branch" was confused when branch already existed.
-
-* "git checkout -q" once again suppresses the locally modified file list.
-
-* "git clone -q", "git fetch -q" asks remote side to not send
-  progress messages, actually making their output quiet.
-
-* Cross-directory renames are no longer used when creating packs.  This
-  allows more graceful behavior on filesystems like sshfs.
-
-* Stale temporary files under $GIT_DIR/objects/pack are now cleaned up
-  automatically by "git prune".
-
-* "git merge" once again removes directories after the last file has
-  been removed from it during the merge.
-
-* "git merge" did not allocate enough memory for the structure itself when
-  enumerating the parents of the resulting commit.
-
-* "git blame -C -C" no longer segfaults while trying to pass blame if
-   it encounters a submodule reference.
-
-* "git rm" incorrectly claimed that you have local modifications when a
-  path was merely stat-dirty.
-
-* "git svn" fixed to display an error message when 'set-tree' failed,
-   instead of a Perl compile error.
-
-* "git submodule" fixed to handle checking out a different commit
-  than HEAD after initializing the submodule.
-
-* The "git commit" error message when there are still unmerged
-  files present was clarified to match "git write-tree".
-
-* "git init" was confused when core.bare or core.sharedRepository are set
-  in system or user global configuration file by mistake.  When --bare or
-  --shared is given from the command line, these now override such
-  settings made outside the repositories.
-
-* Some segfaults due to uncaught NULL pointers were fixed in multiple
-  tools such as apply, reset, update-index.
-
-* Solaris builds now default to OLD_ICONV=1 to avoid compile warnings;
-  Solaris 8 does not define NEEDS_LIBICONV by default.
-
-* "Git.pm" tests relied on unnecessarily more recent version of Perl.
-
-* "gitweb" triggered undef warning on commits without log messages.
-
-* "gitweb" triggered undef warnings on missing trees.
-
-* "gitweb" now removes PATH_INFO from its URLs so users don't have
-  to manually set the URL in the gitweb configuration.
-
-* Bash completion removed support for legacy "git-fetch", "git-push"
-  and "git-pull" as these are no longer installed.  Dashless form
-  ("git fetch") is still however supported.
-
-Many other documentation updates.
diff --git a/Documentation/RelNotes-1.6.0.4.txt b/Documentation/RelNotes-1.6.0.4.txt
deleted file mode 100644 (file)
index d522661..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-GIT v1.6.0.4 Release Notes
-==========================
-
-Fixes since v1.6.0.3
---------------------
-
-* 'git add -p' said "No changes" when only binary files were changed.
-
-* 'git archive' did not work correctly in bare repositories.
-
-* 'git checkout -t -b newbranch' when you are on detached HEAD was broken.
-
-* when we refuse to detect renames because there are too many new or
-  deleted files, 'git diff' did not say how many there are.
-
-* 'git push --mirror' tried and failed to push the stash; there is no
-  point in sending it to begin with.
-
-* 'git push' did not update the remote tracking reference if the corresponding
-  ref on the remote end happened to be already up to date.
-
-* 'git pull $there $branch:$current_branch' did not work when you were on
-  a branch yet to be born.
-
-* when giving up resolving a conflicted merge, 'git reset --hard' failed
-  to remove new paths from the working tree.
-
-* 'git send-email' had a small fd leak while scanning directory.
-
-* 'git status' incorrectly reported a submodule directory as an untracked
-  directory.
-
-* 'git svn' used deprecated 'git-foo' form of subcommand invocation.
-
-* 'git update-ref -d' to remove a reference did not honor --no-deref option.
-
-* Plugged small memleaks here and there.
-
-* Also contains many documentation updates.
diff --git a/Documentation/RelNotes-1.6.0.5.txt b/Documentation/RelNotes-1.6.0.5.txt
deleted file mode 100644 (file)
index a08bb96..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-GIT v1.6.0.5 Release Notes
-==========================
-
-Fixes since v1.6.0.4
---------------------
-
-* "git checkout" used to crash when your HEAD was pointing at a deleted
-  branch.
-
-* "git checkout" from an un-checked-out state did not allow switching out
-  of the current branch.
-
-* "git diff" always allowed GIT_EXTERNAL_DIFF and --no-ext-diff was no-op for
-  the command.
-
-* Giving 3 or more tree-ish to "git diff" is supposed to show the combined
-  diff from second and subsequent trees to the first one, but the order was
-  screwed up.
-
-* "git fast-export" did not export all tags.
-
-* "git ls-files --with-tree=<tree>" did not work with options other
-  than -c, most notably with -m.
-
-* "git pack-objects" did not make its best effort to honor --max-pack-size
-  option when a single first object already busted the given limit and
-  placed many objects in a single pack.
-
-* "git-p4" fast import frontend was too eager to trigger its keyword expansion
-  logic, even on a keyword-looking string that does not have closing '$' on the
-  same line.
-
-* "git push $there" when the remote $there is defined in $GIT_DIR/branches/$there
-  behaves more like what cg-push from Cogito used to work.
-
-* when giving up resolving a conflicted merge, "git reset --hard" failed
-  to remove new paths from the working tree.
-
-* "git tag" did not complain when given mutually incompatible set of options.
-
-* The message constructed in the internal editor was discarded when "git
-  tag -s" failed to sign the message, which was often caused by the user
-  not configuring GPG correctly.
-
-* "make check" cannot be run without sparse; people may have meant to say
-  "make test" instead, so suggest that.
-
-* Internal diff machinery had a corner case performance bug that choked on
-  a large file with many repeated contents.
-
-* "git repack" used to grab objects out of packs marked with .keep
-  into a new pack.
-
-* Many unsafe call to sprintf() style varargs functions are corrected.
-
-* Also contains quite a few documentation updates.
diff --git a/Documentation/RelNotes-1.6.0.6.txt b/Documentation/RelNotes-1.6.0.6.txt
deleted file mode 100644 (file)
index 64ece1f..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-GIT v1.6.0.6 Release Notes
-==========================
-
-Fixes since 1.6.0.5
--------------------
-
- * "git fsck" had a deep recursion that wasted stack space.
-
- * "git fast-export" and "git fast-import" choked on an old style
-   annotated tag that lack the tagger information.
-
- * "git mergetool -- file" did not correctly skip "--" marker that
-   signals the end of options list.
-
- * "git show $tag" segfaulted when an annotated $tag pointed at a
-   nonexistent object.
-
- * "git show 2>error" when the standard output is automatically redirected
-   to the pager redirected the standard error to the pager as well; there
-   was no need to.
-
- * "git send-email" did not correctly handle list of addresses when
-   they had quoted comma (e.g. "Lastname, Givenname" <mail@addre.ss>).
-
- * Logic to discover branch ancestry in "git svn" was unreliable when
-   the process to fetch history was interrupted.
-
- * Removed support for an obsolete gitweb request URI, whose
-   implementation ran "git diff" Porcelain, instead of using plumbing,
-   which would have run an external diff command specified in the
-   repository configuration as the gitweb user.
-
-Also contains numerous documentation typofixes.
diff --git a/Documentation/RelNotes-1.6.0.txt b/Documentation/RelNotes-1.6.0.txt
deleted file mode 100644 (file)
index de7ef16..0000000
+++ /dev/null
@@ -1,258 +0,0 @@
-GIT v1.6.0 Release Notes
-========================
-
-User visible changes
---------------------
-
-With the default Makefile settings, most of the programs are now
-installed outside your $PATH, except for "git", "gitk" and
-some server side programs that need to be accessible for technical
-reasons.  Invoking a git subcommand as "git-xyzzy" from the command
-line has been deprecated since early 2006 (and officially announced in
-1.5.4 release notes); use of them from your scripts after adding
-output from "git --exec-path" to the $PATH is still supported in this
-release, but users are again strongly encouraged to adjust their
-scripts to use "git xyzzy" form, as we will stop installing
-"git-xyzzy" hardlinks for built-in commands in later releases.
-
-An earlier change to page "git status" output was overwhelmingly unpopular
-and has been reverted.
-
-Source changes needed for porting to MinGW environment are now all in the
-main git.git codebase.
-
-By default, packfiles created with this version uses delta-base-offset
-encoding introduced in v1.4.4.  Pack idx files are using version 2 that
-allows larger packs and added robustness thanks to its CRC checking,
-introduced in v1.5.2 and v1.4.4.5.  If you want to keep your repositories
-backwards compatible past these versions, set repack.useDeltaBaseOffset
-to false or pack.indexVersion to 1, respectively.
-
-We used to prevent sample hook scripts shipped in templates/ from
-triggering by default by relying on the fact that we install them as
-unexecutable, but on some filesystems, this approach does not work.
-They are now shipped with ".sample" suffix.  If you want to activate
-any of these samples as-is, rename them to drop the ".sample" suffix,
-instead of running "chmod +x" on them.  For example, you can rename
-hooks/post-update.sample to hooks/post-update to enable the sample
-hook that runs update-server-info, in order to make repositories
-friendly to dumb protocols (i.e. HTTP).
-
-GIT_CONFIG, which was only documented as affecting "git config", but
-actually affected all git commands, now only affects "git config".
-GIT_LOCAL_CONFIG, also only documented as affecting "git config" and
-not different from GIT_CONFIG in a useful way, is removed.
-
-The ".dotest" temporary area "git am" and "git rebase" use is now moved
-inside the $GIT_DIR, to avoid mistakes of adding it to the project by
-accident.
-
-An ancient merge strategy "stupid" has been removed.
-
-
-Updates since v1.5.6
---------------------
-
-(subsystems)
-
-* git-p4 in contrib learned "allowSubmit" configuration to control on
-  which branch to allow "submit" subcommand.
-
-* git-gui learned to stage changes per-line.
-
-(portability)
-
-* Changes for MinGW port have been merged, thanks to Johannes Sixt and
-  gangs.
-
-* Sample hook scripts shipped in templates/ are now suffixed with
-  *.sample.
-
-* perl's in-place edit (-i) does not work well without backup files on Windows;
-  some tests are rewritten to cope with this.
-
-(documentation)
-
-* Updated howto/update-hook-example
-
-* Got rid of usage of "git-foo" from the tutorial and made typography
-  more consistent.
-
-* Disambiguating "--" between revs and paths is finally documented.
-
-(performance, robustness, sanity etc.)
-
-* index-pack used too much memory when dealing with a deep delta chain.
-  This has been optimized.
-
-* reduced excessive inlining to shrink size of the "git" binary.
-
-* verify-pack checks the object CRC when using version 2 idx files.
-
-* When an object is corrupt in a pack, the object became unusable even
-  when the same object is available in a loose form,  We now try harder to
-  fall back to these redundant objects when able.  In particular, "git
-  repack -a -f" can be used to fix such a corruption as long as necessary
-  objects are available.
-
-* Performance of "git-blame -C -C" operation is vastly improved.
-
-* git-clone does not create refs in loose form anymore (it behaves as
-  if you immediately ran git-pack-refs after cloning).  This will help
-  repositories with insanely large number of refs.
-
-* core.fsyncobjectfiles configuration can be used to ensure that the loose
-  objects created will be fsync'ed (this is only useful on filesystems
-  that does not order data writes properly).
-
-* "git commit-tree" plumbing can make Octopus with more than 16 parents.
-  "git commit" has been capable of this for quite some time.
-
-(usability, bells and whistles)
-
-* even more documentation pages are now accessible via "man" and "git help".
-
-* A new environment variable GIT_CEILING_DIRECTORIES can be used to stop
-  the discovery process of the toplevel of working tree; this may be useful
-  when you are working in a slow network disk and are outside any working tree,
-  as bash-completion and "git help" may still need to run in these places.
-
-* By default, stash entries never expire.  Set reflogexpire in [gc
-  "refs/stash"] to a reasonable value to get traditional auto-expiration
-  behaviour back
-
-* Longstanding latency issue with bash completion script has been
-  addressed.  This will need to be backmerged to 'maint' later.
-
-* pager.<cmd> configuration variable can be used to enable/disable the
-  default paging behaviour per command.
-
-* "git-add -i" has a new action 'e/dit' to allow you edit the patch hunk
-  manually.
-
-* git-am records the original tip of the branch in ORIG_HEAD before it
-  starts applying patches.
-
-* git-apply can handle a patch that touches the same path more than once
-  much better than before.
-
-* git-apply can be told not to trust the line counts recorded in the input
-  patch but recount, with the new --recount option.
-
-* git-apply can be told to apply a patch to a path deeper than what the
-  patch records with --directory option.
-
-* git-archive can be told to omit certain paths from its output using
-  export-ignore attributes.
-
-* git-archive uses the zlib default compression level when creating
-  zip archive.
-
-* git-archive's command line options --exec and --remote can take their
-  parameters as separate command line arguments, similar to other commands.
-  IOW, both "--exec=path" and "--exec path" are now supported.
-
-* With -v option, git-branch describes the remote tracking statistics
-  similar to the way git-checkout reports by how many commits your branch
-  is ahead/behind.
-
-* git-branch's --contains option used to always require a commit parameter
-  to limit the branches with; it now defaults to list branches that
-  contains HEAD if this parameter is omitted.
-
-* git-branch's --merged and --no-merged option used to always limit the
-  branches relative to the HEAD, but they can now take an optional commit
-  argument that is used in place of HEAD.
-
-* git-bundle can read the revision arguments from the standard input.
-
-* git-cherry-pick can replay a root commit now.
-
-* git-clone can clone from a remote whose URL would be rewritten by
-  configuration stored in $HOME/.gitconfig now.
-
-* "git-clone --mirror" is a handy way to set up a bare mirror repository.
-
-* git-cvsserver learned to respond to "cvs co -c".
-
-* git-diff --check now checks leftover merge conflict markers.
-
-* "git-diff -p" learned to grab a better hunk header lines in
-  BibTex, Pascal/Delphi, and Ruby files and also pays attention to
-  chapter and part boundary in TeX documents.
-
-* When remote side used to have branch 'foo' and git-fetch finds that now
-  it has branch 'foo/bar', it refuses to lose the existing remote tracking
-  branch and its reflog.  The error message has been improved to suggest
-  pruning the remote if the user wants to proceed and get the latest set
-  of branches from the remote, including such 'foo/bar'.
-
-* fast-export learned to export and import marks file; this can be used to
-  interface with fast-import incrementally.
-
-* fast-import and fast-export learned to export and import gitlinks.
-
-* "gitk" left background process behind after being asked to dig very deep
-  history and the user killed the UI; the process is killed when the UI goes
-  away now.
-
-* git-rebase records the original tip of branch in ORIG_HEAD before it is
-  rewound.
-
-* "git rerere" can be told to update the index with auto-reused resolution
-  with rerere.autoupdate configuration variable.
-
-* git-rev-parse learned $commit^! and $commit^@ notations used in "log"
-  family.  These notations are available in gitk as well, because the gitk
-  command internally uses rev-parse to interpret its arguments.
-
-* git-rev-list learned --children option to show child commits it
-  encountered during the traversal, instead of showing parent commits.
-
-* git-send-mail can talk not just over SSL but over TLS now.
-
-* git-shortlog honors custom output format specified with "--pretty=format:".
-
-* "git-stash save" learned --keep-index option.  This lets you stash away the
-  local changes and bring the changes staged in the index to your working
-  tree for examination and testing.
-
-* git-stash also learned branch subcommand to create a new branch out of
-  stashed changes.
-
-* git-status gives the remote tracking statistics similar to the way
-  git-checkout reports by how many commits your branch is ahead/behind.
-
-* "git-svn dcommit" is now aware of auto-props setting the subversion user
-  has.
-
-* You can tell "git status -u" to even more aggressively omit checking
-  untracked files with --untracked-files=no.
-
-* Original SHA-1 value for "update-ref -d" is optional now.
-
-* Error codes from gitweb are made more descriptive where possible, rather
-  than "403 forbidden" as we used to issue everywhere.
-
-(internal)
-
-* git-merge has been reimplemented in C.
-
-
-Fixes since v1.5.6
-------------------
-
-All of the fixes in v1.5.6 maintenance series are included in
-this release, unless otherwise noted.
-
- * git-clone ignored its -u option; the fix needs to be backported to
-   'maint';
-
- * git-mv used to lose the distinction between changes that are staged
-   and that are only in the working tree, by staging both in the index
-   after moving such a path.
-
- * "git-rebase -i -p" rewrote the parents to wrong ones when amending
-   (either edit or squash) was involved, and did not work correctly
-   when fast forwarding.
-
diff --git a/Documentation/RelNotes-1.6.1.1.txt b/Documentation/RelNotes-1.6.1.1.txt
deleted file mode 100644 (file)
index 8c594ba..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-GIT v1.6.1.1 Release Notes
-==========================
-
-Fixes since v1.6.1
-------------------
-
-* "git add frotz/nitfol" when "frotz" is a submodule should have errored
-  out, but it didn't.
-
-* "git apply" took file modes from the patch text and updated the mode
-  bits of the target tree even when the patch was not about mode changes.
-
-* "git bisect view" on Cygwin did not launch gitk
-
-* "git checkout $tree" did not trigger an error.
-
-* "git commit" tried to remove COMMIT_EDITMSG from the work tree by mistake.
-
-* "git describe --all" complained when a commit is described with a tag,
-  which was nonsense.
-
-* "git diff --no-index --" did not trigger no-index (aka "use git-diff as
-  a replacement of diff on untracked files") behaviour.
-
-* "git format-patch -1 HEAD" on a root commit failed to produce patch
-  text.
-
-* "git fsck branch" did not work as advertised; instead it behaved the same
-  way as "git fsck".
-
-* "git log --pretty=format:%s" did not handle a multi-line subject the
-  same way as built-in log listers (i.e. shortlog, --pretty=oneline, etc.)
-
-* "git daemon", and "git merge-file" are more careful when freopen fails
-  and barf, instead of going on and writing to unopened filehandle.
-
-* "git http-push" did not like some RFC 4918 compliant DAV server
-  responses.
-
-* "git merge -s recursive" mistakenly overwritten an untracked file in the
-  work tree upon delete/modify conflict.
-
-* "git merge -s recursive" didn't leave the index unmerged for entries with
-  rename/delete conflicts.
-
-* "git merge -s recursive" clobbered untracked files in the work tree.
-
-* "git mv -k" with more than one erroneous paths misbehaved.
-
-* "git read-tree -m -u" hence branch switching incorrectly lost a
-  subdirectory in rare cases.
-
-* "git rebase -i" issued an unnecessary error message upon a user error of
-  marking the first commit to be "squash"ed.
-
-* "git shortlog" did not format a commit message with multi-line
-  subject correctly.
-
-Many documentation updates.
diff --git a/Documentation/RelNotes-1.6.1.2.txt b/Documentation/RelNotes-1.6.1.2.txt
deleted file mode 100644 (file)
index be37cbb..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-GIT v1.6.1.2 Release Notes
-==========================
-
-Fixes since v1.6.1.1
---------------------
-
-* The logic for rename detection in internal diff used by commands like
-  "git diff" and "git blame" has been optimized to avoid loading the same
-  blob repeatedly.
-
-* We did not allow writing out a blob that is larger than 2GB for no good
-  reason.
-
-* "git format-patch -o $dir", when $dir is a relative directory, used it
-  as relative to the root of the work tree, not relative to the current
-  directory.
-
-* v1.6.1 introduced an optimization for "git push" into a repository (A)
-  that borrows its objects from another repository (B) to avoid sending
-  objects that are available in repository B, when they are not yet used
-  by repository A.  However the code on the "git push" sender side was
-  buggy and did not work when repository B had new objects that are not
-  known by the sender.  This caused pushing into a "forked" repository
-  served by v1.6.1 software using "git push" from v1.6.1 sometimes did not
-  work.  The bug was purely on the "git push" sender side, and has been
-  corrected.
-
-* "git status -v" did not paint its diff output in colour even when
-  color.ui configuration was set.
-
-* "git ls-tree" learned --full-tree option to help Porcelain scripts that
-  want to always see the full path regardless of the current working
-  directory.
-
-* "git grep" incorrectly searched in work tree paths even when they are
-  marked as assume-unchanged.  It now searches in the index entries.
-
-* "git gc" with no grace period needlessly ejected packed but unreachable
-  objects in their loose form, only to delete them right away.
diff --git a/Documentation/RelNotes-1.6.1.3.txt b/Documentation/RelNotes-1.6.1.3.txt
deleted file mode 100644 (file)
index 6f0bde1..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-GIT v1.6.1.3 Release Notes
-==========================
-
-Fixes since v1.6.1.2
---------------------
-
-* "git diff --binary | git apply" pipeline did not work well when
-  a binary blob is changed to a symbolic link.
-
-* Some combinations of -b/-w/--ignore-space-at-eol to "git diff" did
-  not work as expected.
-
-* "git grep" did not pass the -I (ignore binary) option when
-  calling out an external grep program.
-
-* "git log" and friends include HEAD to the set of starting points
-  when --all is given.  This makes a difference when you are not
-  on any branch.
-
-* "git mv" to move an untracked file to overwrite a tracked
-  contents misbehaved.
-
-* "git merge -s octopus" with many potential merge bases did not
-  work correctly.
-
-* RPM binary package installed the html manpages in a wrong place.
-
-Also includes minor documentation fixes and updates.
-
-
---
-git shortlog --no-merges v1.6.1.2-33-gc789350..
diff --git a/Documentation/RelNotes-1.6.1.4.txt b/Documentation/RelNotes-1.6.1.4.txt
deleted file mode 100644 (file)
index 0ce6316..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-GIT v1.6.1.4 Release Notes
-==========================
-
-Fixes since v1.6.1.3
---------------------
-
-* .gitignore learned to handle backslash as a quoting mechanism for
-  comment introduction character "#".
-  This fix was first merged to 1.6.2.1.
-
-* "git fast-export" produced wrong output with some parents missing from
-  commits, when the history is clock-skewed.
-
-* "git fast-import" sometimes failed to read back objects it just wrote
-  out and aborted, because it failed to flush stale cached data.
-
-* "git-ls-tree" and "git-diff-tree" used a pathspec correctly when
-  deciding to descend into a subdirectory but they did not match the
-  individual paths correctly.  This caused pathspecs "abc/d ab" to match
-  "abc/0" ("abc/d" made them decide to descend into the directory "abc/",
-  and then "ab" incorrectly matched "abc/0" when it shouldn't).
-  This fix was first merged to 1.6.2.3.
-
-* import-zips script (in contrib) did not compute the common directory
-  prefix correctly.
-  This fix was first merged to 1.6.2.2.
-
-* "git init" segfaulted when given an overlong template location via
-  the --template= option.
-  This fix was first merged to 1.6.2.4.
-
-* "git repack" did not error out when necessary object was missing in the
-  repository.
-
-* git-repack (invoked from git-gc) did not work as nicely as it should in
-  a repository that borrows objects from neighbours via alternates
-  mechanism especially when some packs are marked with the ".keep" flag
-  to prevent them from being repacked.
-  This fix was first merged to 1.6.2.3.
-
-Also includes minor documentation fixes and updates.
-
---
-git shortlog --no-merges v1.6.1.3..
diff --git a/Documentation/RelNotes-1.6.1.txt b/Documentation/RelNotes-1.6.1.txt
deleted file mode 100644 (file)
index adb7cca..0000000
+++ /dev/null
@@ -1,286 +0,0 @@
-GIT v1.6.1 Release Notes
-========================
-
-Updates since v1.6.0
---------------------
-
-When some commands (e.g. "git log", "git diff") spawn pager internally, we
-used to make the pager the parent process of the git command that produces
-output.  This meant that the exit status of the whole thing comes from the
-pager, not the underlying git command.  We swapped the order of the
-processes around and you will see the exit code from the command from now
-on.
-
-(subsystems)
-
-* gitk can call out to git-gui to view "git blame" output; git-gui in turn
-  can run gitk from its blame view.
-
-* Various git-gui updates including updated translations.
-
-* Various gitweb updates from repo.or.cz installation.
-
-* Updates to emacs bindings.
-
-(portability)
-
-* A few test scripts used nonportable "grep" that did not work well on
-  some platforms, e.g. Solaris.
-
-* Sample pre-auto-gc script has OS X support.
-
-* Makefile has support for (ancient) FreeBSD 4.9.
-
-(performance)
-
-* Many operations that are lstat(3) heavy can be told to pre-execute
-  necessary lstat(3) in parallel before their main operations, which
-  potentially gives much improved performance for cold-cache cases or in
-  environments with weak metadata caching (e.g. NFS).
-
-* The underlying diff machinery to produce textual output has been
-  optimized, which would result in faster "git blame" processing.
-
-* Most of the test scripts (but not the ones that try to run servers)
-  can be run in parallel.
-
-* Bash completion of refnames in a repository with massive number of
-  refs has been optimized.
-
-* Cygwin port uses native stat/lstat implementations when applicable,
-  which leads to improved performance.
-
-* "git push" pays attention to alternate repositories to avoid sending
-  unnecessary objects.
-
-* "git svn" can rebuild an out-of-date rev_map file.
-
-(usability, bells and whistles)
-
-* When you mistype a command name, git helpfully suggests what it guesses
-  you might have meant to say.  help.autocorrect configuration can be set
-  to a non-zero value to accept the suggestion when git can uniquely
-  guess.
-
-* The packfile machinery hopefully is more robust when dealing with
-  corrupt packs if redundant objects involved in the corruption are
-  available elsewhere.
-
-* "git add -N path..." adds the named paths as an empty blob, so that
-  subsequent "git diff" will show a diff as if they are creation events.
-
-* "git add" gained a built-in synonym for people who want to say "stage
-  changes" instead of "add contents to the staging area" which amounts
-  to the same thing.
-
-* "git apply" learned --include=paths option, similar to the existing
-  --exclude=paths option.
-
-* "git bisect" is careful about a user mistake and suggests testing of
-  merge base first when good is not a strict ancestor of bad.
-
-* "git bisect skip" can take a range of commits.
-
-* "git blame" re-encodes the commit metainfo to UTF-8 from i18n.commitEncoding
-  by default.
-
-* "git check-attr --stdin" can check attributes for multiple paths.
-
-* "git checkout --track origin/hack" used to be a syntax error.  It now
-  DWIMs to create a corresponding local branch "hack", i.e. acts as if you
-  said "git checkout --track -b hack origin/hack".
-
-* "git checkout --ours/--theirs" can be used to check out one side of a
-  conflicting merge during conflict resolution.
-
-* "git checkout -m" can be used to recreate the initial conflicted state
-  during conflict resolution.
-
-* "git cherry-pick" can also utilize rerere for conflict resolution.
-
-* "git clone" learned to be verbose with -v
-
-* "git commit --author=$name" can look up author name from existing
-  commits.
-
-* output from "git commit" has been reworded in a more concise and yet
-  more informative way.
-
-* "git count-objects" reports the on-disk footprint for packfiles and
-  their corresponding idx files.
-
-* "git daemon" learned --max-connections=<count> option.
-
-* "git daemon" exports REMOTE_ADDR to record client address, so that
-  spawned programs can act differently on it.
-
-* "git describe --tags" favours closer lightweight tags than farther
-  annotated tags now.
-
-* "git diff" learned to mimic --suppress-blank-empty from GNU diff via a
-  configuration option.
-
-* "git diff" learned to put more sensible hunk headers for Python,
-  HTML and ObjC contents.
-
-* "git diff" learned to vary the a/ vs b/ prefix depending on what are
-  being compared, controlled by diff.mnemonicprefix configuration.
-
-* "git diff" learned --dirstat-by-file to count changed files, not number
-  of lines, when summarizing the global picture.
-
-* "git diff" learned "textconv" filters --- a binary or hard-to-read
-  contents can be munged into human readable form and the difference
-  between the results of the conversion can be viewed (obviously this
-  cannot produce a patch that can be applied, so this is disabled in
-  format-patch among other things).
-
-* "--cached" option to "git diff has an easier to remember synonym "--staged",
-  to ask "what is the difference between the given commit and the
-  contents staged in the index?"
-
-* "git for-each-ref" learned "refname:short" token that gives an
-  unambiguously abbreviated refname.
-
-* Auto-numbering of the subject lines is the default for "git
-  format-patch" now.
-
-* "git grep" learned to accept -z similar to GNU grep.
-
-* "git help" learned to use GIT_MAN_VIEWER environment variable before
-  using "man" program.
-
-* "git imap-send" can optionally talk SSL.
-
-* "git index-pack" is more careful against disk corruption while
-  completing a thin pack.
-
-* "git log --check" and "git log --exit-code" passes their underlying diff
-  status with their exit status code.
-
-* "git log" learned --simplify-merges, a milder variant of --full-history;
-  "gitk --simplify-merges" is easier to view than with --full-history.
-
-* "git log" learned "--source" to show what ref each commit was reached
-  from.
-
-* "git log" also learned "--simplify-by-decoration" to show the
-  birds-eye-view of the topology of the history.
-
-* "git log --pretty=format:" learned "%d" format element that inserts
-  names of tags that point at the commit.
-
-* "git merge --squash" and "git merge --no-ff" into an unborn branch are
-  noticed as user errors.
-
-* "git merge -s $strategy" can use a custom built strategy if you have a
-  command "git-merge-$strategy" on your $PATH.
-
-* "git pull" (and "git fetch") can be told to operate "-v"erbosely or
-  "-q"uietly.
-
-* "git push" can be told to reject deletion of refs with receive.denyDeletes
-  configuration.
-
-* "git rebase" honours pre-rebase hook; use --no-verify to bypass it.
-
-* "git rebase -p" uses interactive rebase machinery now to preserve the merges.
-
-* "git reflog expire branch" can be used in place of "git reflog expire
-  refs/heads/branch".
-
-* "git remote show $remote" lists remote branches one-per-line now.
-
-* "git send-email" can be given revision range instead of files and
-  maildirs on the command line, and automatically runs format-patch to
-  generate patches for the given revision range.
-
-* "git submodule foreach" subcommand allows you to iterate over checked
-  out submodules.
-
-* "git submodule sync" subcommands allows you to update the origin URL
-  recorded in submodule directories from the toplevel .gitmodules file.
-
-* "git svn branch" can create new branches on the other end.
-
-* "gitweb" can use more saner PATH_INFO based URL.
-
-(internal)
-
-* "git hash-object" learned to lie about the path being hashed, so that
-  correct gitattributes processing can be done while hashing contents
-  stored in a temporary file.
-
-* various callers of git-merge-recursive avoid forking it as an external
-  process.
-
-* Git class defined in "Git.pm" can be subclasses a bit more easily.
-
-* We used to link GNU regex library as a compatibility layer for some
-  platforms, but it turns out it is not necessary on most of them.
-
-* Some path handling routines used fixed number of buffers used alternately
-  but depending on the call depth, this arrangement led to hard to track
-  bugs.  This issue is being addressed.
-
-
-Fixes since v1.6.0
-------------------
-
-All of the fixes in v1.6.0.X maintenance series are included in this
-release, unless otherwise noted.
-
-* Porcelains implemented as shell scripts were utterly confused when you
-  entered to a subdirectory of a work tree from sideways, following a
-  symbolic link (this may need to be backported to older releases later).
-
-* Tracking symbolic links would work better on filesystems whose lstat()
-  returns incorrect st_size value for them.
-
-* "git add" and "git update-index" incorrectly allowed adding S/F when S
-  is a tracked symlink that points at a directory D that has a path F in
-  it (we still need to fix a similar nonsense when S is a submodule and F
-  is a path in it).
-
-* "git am" after stopping at a broken patch lost --whitespace, -C, -p and
-  --3way options given from the command line initially.
-
-* "git diff --stdin" used to take two trees on a line and compared them,
-  but we dropped support for such a use case long time ago.  This has
-  been resurrected.
-
-* "git filter-branch" failed to rewrite a tag name with slashes in it.
-
-* "git http-push" did not understand URI scheme other than opaquelocktoken
-  when acquiring a lock from the server (this may need to be backported to
-  older releases later).
-
-* After "git rebase -p" stopped with conflicts while replaying a merge,
- "git rebase --continue" did not work (may need to be backported to older
-  releases).
-
-* "git revert" records relative to which parent a revert was made when
-  reverting a merge.  Together with new documentation that explains issues
-  around reverting a merge and merging from the updated branch later, this
-  hopefully will reduce user confusion (this may need to be backported to
-  older releases later).
-
-* "git rm --cached" used to allow an empty blob that was added earlier to
-  be removed without --force, even when the file in the work tree has
-  since been modified.
-
-* "git push --tags --all $there" failed with generic usage message without
-  telling saying these two options are incompatible.
-
-* "git log --author/--committer" match used to potentially match the
-  timestamp part, exposing internal implementation detail.  Also these did
-  not work with --fixed-strings match at all.
-
-* "gitweb" did not mark non-ASCII characters imported from external HTML fragments
-  correctly.
-
---
-exec >/var/tmp/1
-O=v1.6.1-rc3-74-gf66bc5f
-echo O=$(git describe master)
-git shortlog --no-merges $O..master ^maint
diff --git a/Documentation/RelNotes-1.6.2.1.txt b/Documentation/RelNotes-1.6.2.1.txt
deleted file mode 100644 (file)
index dfa3641..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-GIT v1.6.2.1 Release Notes
-==========================
-
-Fixes since v1.6.2
-------------------
-
-* .gitignore learned to handle backslash as a quoting mechanism for
-  comment introduction character "#".
-
-* timestamp output in --date=relative mode used to display timestamps that
-  are long time ago in the default mode; it now uses "N years M months
-  ago", and "N years ago".
-
-* git-add -i/-p now works with non-ASCII pathnames.
-
-* "git hash-object -w" did not read from the configuration file from the
-  correct .git directory.
-
-* git-send-email learned to correctly handle multiple Cc: addresses.
diff --git a/Documentation/RelNotes-1.6.2.2.txt b/Documentation/RelNotes-1.6.2.2.txt
deleted file mode 100644 (file)
index fafa998..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-GIT v1.6.2.2 Release Notes
-==========================
-
-Fixes since v1.6.2.1
---------------------
-
-* A longstanding confusing description of what --pickaxe option of
-  git-diff does has been clarified in the documentation.
-
-* "git-blame -S" did not quite work near the commits that were given
-  on the command line correctly.
-
-* "git diff --pickaxe-regexp" did not count overlapping matches
-  correctly.
-
-* "git diff" did not feed files in work-tree representation to external
-  diff and textconv.
-
-* "git-fetch" in a repository that was not cloned from anywhere said
-  it cannot find 'origin', which was hard to understand for new people.
-
-* "git-format-patch --numbered-files --stdout" did not have to die of
-  incompatible options; it now simply ignores --numbered-files as no files
-  are produced anyway.
-
-* "git-ls-files --deleted" did not work well with GIT_DIR&GIT_WORK_TREE.
-
-* "git-read-tree A B C..." without -m option has been broken for a long
-  time.
-
-* git-send-email ignored --in-reply-to when --no-thread was given.
-
-* 'git-submodule add' did not tolerate extra slashes and ./ in the path it
-  accepted from the command line; it now is more lenient.
-
-* git-svn misbehaved when the project contained a path that began with
-  two dashes.
-
-* import-zips script (in contrib) did not compute the common directory
-  prefix correctly.
-
-* miscompilation of negated enum constants by old gcc (2.9) affected the
-  codepaths to spawn subprocesses.
-
-Many small documentation updates are included as well.
diff --git a/Documentation/RelNotes-1.6.2.3.txt b/Documentation/RelNotes-1.6.2.3.txt
deleted file mode 100644 (file)
index 4d3c1ac..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-GIT v1.6.2.3 Release Notes
-==========================
-
-Fixes since v1.6.2.2
---------------------
-
-* Setting an octal mode value to core.sharedrepository configuration to
-  restrict access to the repository to group members did not work as
-  advertised.
-
-* A fairly large and trivial memory leak while rev-list shows list of
-  reachable objects has been identified and plugged.
-
-* "git-commit --interactive" did not abort when underlying "git-add -i"
-  signaled a failure.
-
-* git-repack (invoked from git-gc) did not work as nicely as it should in
-  a repository that borrows objects from neighbours via alternates
-  mechanism especially when some packs are marked with the ".keep" flag
-  to prevent them from being repacked.
-
-Many small documentation updates are included as well.
diff --git a/Documentation/RelNotes-1.6.2.4.txt b/Documentation/RelNotes-1.6.2.4.txt
deleted file mode 100644 (file)
index f4bf1d0..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-GIT v1.6.2.4 Release Notes
-==========================
-
-Fixes since v1.6.2.3
---------------------
-
-* The configuration parser had a buffer overflow while parsing an overlong
-  value.
-
-* pruning reflog entries that are unreachable from the tip of the ref
-  during "git reflog prune" (hence "git gc") was very inefficient.
-
-* "git-add -p" lacked a way to say "q"uit to refuse staging any hunks for
-  the remaining paths.  You had to say "d" and then ^C.
-
-* "git-checkout <tree-ish> <submodule>" did not update the index entry at
-  the named path; it now does.
-
-* "git-fast-export" choked when seeing a tag that does not point at commit.
-
-* "git init" segfaulted when given an overlong template location via
-  the --template= option.
-
-* "git-ls-tree" and "git-diff-tree" used a pathspec correctly when
-  deciding to descend into a subdirectory but they did not match the
-  individual paths correctly.  This caused pathspecs "abc/d ab" to match
-  "abc/0" ("abc/d" made them decide to descend into the directory "abc/",
-  and then "ab" incorrectly matched "abc/0" when it shouldn't).
-
-* "git-merge-recursive" was broken when a submodule entry was involved in
-  a criss-cross merge situation.
-
-Many small documentation updates are included as well.
-
----
-exec >/var/tmp/1
-echo O=$(git describe maint)
-O=v1.6.2.3-38-g318b847
-git shortlog --no-merges $O..maint
diff --git a/Documentation/RelNotes-1.6.2.5.txt b/Documentation/RelNotes-1.6.2.5.txt
deleted file mode 100644 (file)
index b23f9e9..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-GIT v1.6.2.5 Release Notes
-==========================
-
-Fixes since v1.6.2.4
---------------------
-
-* "git apply" mishandled if you fed a git generated patch that renames
-  file A to B and file B to A at the same time.
-
-* "git diff -c -p" (and "diff --cc") did not expect to see submodule
-  differences and instead refused to work.
-
-* "git grep -e '('" segfaulted, instead of diagnosing a mismatched
-  parentheses error.
-
-* "git fetch" generated packs with offset-delta encoding when both ends of
-  the connection are capable of producing one; this cannot be read by
-  ancient git and the user should be able to disable this by setting
-  repack.usedeltabaseoffset configuration to false.
-
-
diff --git a/Documentation/RelNotes-1.6.2.txt b/Documentation/RelNotes-1.6.2.txt
deleted file mode 100644 (file)
index ad060f4..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-GIT v1.6.2 Release Notes
-========================
-
-With the next major release, "git push" into a branch that is
-currently checked out will be refused by default.  You can choose
-what should happen upon such a push by setting the configuration
-variable receive.denyCurrentBranch in the receiving repository.
-
-To ease the transition plan, the receiving repository of such a
-push running this release will issue a big warning when the
-configuration variable is missing.  Please refer to:
-
-  http://git.or.cz/gitwiki/GitFaq#non-bare
-  http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007
-
-for more details on the reason why this change is needed and the
-transition plan.
-
-For a similar reason, "git push $there :$killed" to delete the branch
-$killed in a remote repository $there, if $killed branch is the current
-branch pointed at by its HEAD, gets a large warning.  You can choose what
-should happen upon such a push by setting the configuration variable
-receive.denyDeleteCurrent in the receiving repository.
-
-
-Updates since v1.6.1
---------------------
-
-(subsystems)
-
-* git-svn updates.
-
-* gitweb updates, including a new patch view and RSS/Atom feed
-  improvements.
-
-* (contrib/emacs) git.el now has commands for checking out a branch,
-  creating a branch, cherry-picking and reverting commits; vc-git.el
-  is not shipped with git anymore (it is part of official Emacs).
-
-(performance)
-
-* pack-objects autodetects the number of CPUs available and uses threaded
-  version.
-
-(usability, bells and whistles)
-
-* automatic typo correction works on aliases as well
-
-* @{-1} is a way to refer to the last branch you were on.  This is
-  accepted not only where an object name is expected, but anywhere
-  a branch name is expected and acts as if you typed the branch name.
-  E.g. "git branch --track mybranch @{-1}", "git merge @{-1}", and
-  "git rev-parse --symbolic-full-name @{-1}" would work as expected.
-
-* When refs/remotes/origin/HEAD points at a remote tracking branch that
-  has been pruned away, many git operations issued warning when they
-  internally enumerated the refs.  We now warn only when you say "origin"
-  to refer to that pruned branch.
-
-* The location of .mailmap file can be configured, and its file format was
-  enhanced to allow mapping an incorrect e-mail field as well.
-
-* "git add -p" learned 'g'oto action to jump directly to a hunk.
-
-* "git add -p" learned to find a hunk with given text with '/'.
-
-* "git add -p" optionally can be told to work with just the command letter
-  without Enter.
-
-* when "git am" stops upon a patch that does not apply, it shows the
-  title of the offending patch.
-
-* "git am --directory=<dir>" and "git am --reject" passes these options
-  to underlying "git apply".
-
-* "git am" learned --ignore-date option.
-
-* "git blame" aligns author names better when they are spelled in
-  non US-ASCII encoding.
-
-* "git clone" now makes its best effort when cloning from an empty
-  repository to set up configuration variables to refer to the remote
-  repository.
-
-* "git checkout -" is a shorthand for "git checkout @{-1}".
-
-* "git cherry" defaults to whatever the current branch is tracking (if
-  exists) when the <upstream> argument is not given.
-
-* "git cvsserver" can be told not to add extra "via git-CVS emulator" to
-  the commit log message it serves via gitcvs.commitmsgannotation
-  configuration.
-
-* "git cvsserver" learned to handle 'noop' command some CVS clients seem
-  to expect to work.
-
-* "git diff" learned a new option --inter-hunk-context to coalesce close
-  hunks together and show context between them.
-
-* The definition of what constitutes a word for "git diff --color-words"
-  can be customized via gitattributes, command line or a configuration.
-
-* "git diff" learned --patience to run "patience diff" algorithm.
-
-* "git filter-branch" learned --prune-empty option that discards commits
-  that do not change the contents.
-
-* "git fsck" now checks loose objects in alternate object stores, instead
-  of misreporting them as missing.
-
-* "git gc --prune" was resurrected to allow "git gc --no-prune" and
-  giving non-default expiration period e.g. "git gc --prune=now".
-
-* "git grep -w" and "git grep" for fixed strings have been optimized.
-
-* "git mergetool" learned -y(--no-prompt) option to disable prompting.
-
-* "git rebase -i" can transplant a history down to root to elsewhere
-  with --root option.
-
-* "git reset --merge" is a new mode that works similar to the way
-  "git checkout" switches branches, taking the local changes while
-  switching to another commit.
-
-* "git submodule update" learned --no-fetch option.
-
-* "git tag" learned --contains that works the same way as the same option
-  from "git branch".
-
-
-Fixes since v1.6.1
-------------------
-
-All of the fixes in v1.6.1.X maintenance series are included in this
-release, unless otherwise noted.
-
-Here are fixes that this release has, but have not been backported to
-v1.6.1.X series.
-
-* "git-add sub/file" when sub is a submodule incorrectly added the path to
-  the superproject.
-
-* "git bundle" did not exclude annotated tags even when a range given
-  from the command line wanted to.
-
-* "git filter-branch" unnecessarily refused to work when you had
-  checked out a different commit from what is recorded in the superproject
-  index in a submodule.
-
-* "git filter-branch" incorrectly tried to update a nonexistent work tree
-  at the end when it is run in a bare repository.
-
-* "git gc" did not work if your repository was created with an ancient git
-  and never had any pack files in it before.
-
-* "git mergetool" used to ignore autocrlf and other attributes
-  based content rewriting.
-
-* branch switching and merges had a silly bug that did not validate
-  the correct directory when making sure an existing subdirectory is
-  clean.
-
-* "git -p cmd" when cmd is not a built-in one left the display in funny state
-  when killed in the middle.
diff --git a/Documentation/RelNotes-1.6.3.txt b/Documentation/RelNotes-1.6.3.txt
deleted file mode 100644 (file)
index 418c685..0000000
+++ /dev/null
@@ -1,182 +0,0 @@
-GIT v1.6.3 Release Notes
-========================
-
-With the next major release, "git push" into a branch that is
-currently checked out will be refused by default.  You can choose
-what should happen upon such a push by setting the configuration
-variable receive.denyCurrentBranch in the receiving repository.
-
-To ease the transition plan, the receiving repository of such a
-push running this release will issue a big warning when the
-configuration variable is missing.  Please refer to:
-
-  http://git.or.cz/gitwiki/GitFaq#non-bare
-  http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007
-
-for more details on the reason why this change is needed and the
-transition plan.
-
-For a similar reason, "git push $there :$killed" to delete the branch
-$killed in a remote repository $there, if $killed branch is the current
-branch pointed at by its HEAD, gets a large warning.  You can choose what
-should happen upon such a push by setting the configuration variable
-receive.denyDeleteCurrent in the receiving repository.
-
-When the user does not tell "git push" what to push, it has always
-pushed matching refs.  For some people it is unexpected, and a new
-configuration variable push.default has been introduced to allow
-changing a different default behaviour.  To advertise the new feature,
-a big warning is issued if this is not configured and a git push without
-arguments is attempted.
-
-
-Updates since v1.6.2
---------------------
-
-(subsystems)
-
-* various git-svn updates.
-
-* git-gui updates, including an update to Russian translation, and a
-  fix to an infinite loop when showing an empty diff.
-
-* gitk updates, including an update to Russian translation and improved Windows
-  support.
-
-(performance)
-
-* many uses of lstat(2) in the codepath for "git checkout" have been
-  optimized out.
-
-(usability, bells and whistles)
-
-* Boolean configuration variable yes/no can be written as on/off.
-
-* rsync:/path/to/repo can be used to run git over rsync for local
-  repositories.  It may not be useful in practice; meant primarily for
-  testing.
-
-* http transport learned to prompt and use password when fetching from or
-  pushing to http://user@host.xz/ URL.
-
-* (msysgit) progress output that is sent over the sideband protocol can
-  be handled appropriately in Windows console.
-
-* "--pretty=<style>" option to the log family of commands can now be
-  spelled as "--format=<style>".  In addition, --format=%formatstring
-  is a short-hand for --pretty=tformat:%formatstring.
-
-* "--oneline" is a synonym for "--pretty=oneline --abbrev-commit".
-
-* "--graph" to the "git log" family can draw the commit ancestry graph
-  in colors.
-
-* If you realize that you botched the patch when you are editing hunks
-  with the 'edit' action in git-add -i/-p, you can abort the editor to
-  tell git not to apply it.
-
-* @{-1} is a new way to refer to the last branch you were on introduced in
-  1.6.2, but the initial implementation did not teach this to a few
-  commands.  Now the syntax works with "branch -m @{-1} newname".
-
-* git-archive learned --output=<file> option.
-
-* git-archive takes attributes from the tree being archived; strictly
-  speaking, this is an incompatible behaviour change, but is a good one.
-  Use --worktree-attributes option to allow it to read attributes from
-  the work tree as before (deprecated git-tar tree command always reads
-  attributes from the work tree).
-
-* git-bisect shows not just the number of remaining commits whose goodness
-  is unknown, but also shows the estimated number of remaining rounds.
-
-* You can give --date=<format> option to git-blame.
-
-* "git-branch -r" shows HEAD symref that points at a remote branch in
-  interest of each tracked remote repository.
-
-* "git-branch -v -v" is a new way to get list of names for branches and the
-  "upstream" branch for them.
-
-* git-config learned -e option to open an editor to edit the config file
-  directly.
-
-* git-clone runs post-checkout hook when run without --no-checkout.
-
-* git-difftool is now part of the officially supported command, primarily
-  maintained by David Aguilar.
-
-* git-for-each-ref learned a new "upstream" token.
-
-* git-format-patch can be told to use attachment with a new configuration,
-  format.attach.
-
-* git-format-patch can be told to produce deep or shallow message threads.
-
-* git-format-patch can be told to always add sign-off with a configuration
-  variable.
-
-* git-format-patch learned format.headers configuration to add extra
-  header fields to the output.  This behaviour is similar to the existing
-  --add-header=<header> option of the command.
-
-* git-format-patch gives human readable names to the attached files, when
-  told to send patches as attachments.
-
-* git-grep learned to highlight the found substrings in color.
-
-* git-imap-send learned to work around Thunderbird's inability to easily
-  disable format=flowed with a new configuration, imap.preformattedHTML.
-
-* git-rebase can be told to rebase the series even if your branch is a
-  descendant of the commit you are rebasing onto with --force-rebase
-  option.
-
-* git-rebase can be told to report diffstat with the --stat option.
-
-* Output from git-remote command has been vastly improved.
-
-* "git remote update --prune $remote" updates from the named remote and
-  then prunes stale tracking branches.
-
-* git-send-email learned --confirm option to review the Cc: list before
-  sending the messages out.
-
-(developers)
-
-* Test scripts can be run under valgrind.
-
-* Test scripts can be run with installed git.
-
-* Makefile learned 'coverage' option to run the test suites with
-  coverage tracking enabled.
-
-* Building the manpages with docbook-xsl between 1.69.1 and 1.71.1 now
-  requires setting DOCBOOK_SUPPRESS_SP to work around a docbook-xsl bug.
-  This workaround used to be enabled by default, but causes problems
-  with newer versions of docbook-xsl.  In addition, there are a few more
-  knobs you can tweak to work around issues with various versions of the
-  docbook-xsl package.  See comments in Documentation/Makefile for details.
-
-* Support for building and testing a subset of git on a system without a
-  working perl has been improved.
-
-
-Fixes since v1.6.2
-------------------
-
-All of the fixes in v1.6.2.X maintenance series are included in this
-release, unless otherwise noted.
-
-Here are fixes that this release has, but have not been backported to
-v1.6.2.X series.
-
-* "git-apply" rejected a patch that swaps two files (i.e. renames A to B
-  and B to A at the same time).  May need to be backported by cherry
-  picking d8c81df and then 7fac0ee).
-
-* The initial checkout did not read the attributes from the .gitattribute
-  file that is being checked out.
-
-* git-gc spent excessive amount of time to decide if an object appears
-  in a locally existing pack (if needed, backport by merging 69e020a).
diff --git a/Documentation/RelNotes/1.5.0.1.txt b/Documentation/RelNotes/1.5.0.1.txt
new file mode 100644 (file)
index 0000000..fea3f99
--- /dev/null
@@ -0,0 +1,42 @@
+GIT v1.5.0.1 Release Notes
+==========================
+
+Fixes since v1.5.0
+------------------
+
+* Documentation updates
+
+  - Clarifications and corrections to 1.5.0 release notes.
+
+  - The main documentation did not link to git-remote documentation.
+
+  - Clarified introductory text of git-rebase documentation.
+
+  - Converted remaining mentions of update-index on Porcelain
+    documents to git-add/git-rm.
+
+  - Some i18n.* configuration variables were incorrectly
+    described as core.*; fixed.
+
+* Bugfixes
+
+  - git-add and git-update-index on a filesystem on which
+    executable bits are unreliable incorrectly reused st_mode
+    bits even when the path changed between symlink and regular
+    file.
+
+  - git-daemon marks the listening sockets with FD_CLOEXEC so
+    that it won't be leaked into the children.
+
+  - segfault from git-blame when the mandatory pathname
+    parameter was missing was fixed; usage() message is given
+    instead.
+
+  - git-rev-list did not read $GIT_DIR/config file, which means
+    that did not honor i18n.logoutputencoding correctly.
+
+* Tweaks
+
+  - sliding mmap() inefficiently mmaped the same region of a
+    packfile with an access pattern that used objects in the
+    reverse order.  This has been made more efficient.
diff --git a/Documentation/RelNotes/1.5.0.2.txt b/Documentation/RelNotes/1.5.0.2.txt
new file mode 100644 (file)
index 0000000..b061e50
--- /dev/null
@@ -0,0 +1,65 @@
+GIT v1.5.0.2 Release Notes
+==========================
+
+Fixes since v1.5.0.1
+--------------------
+
+* Bugfixes
+
+  - Automated merge conflict handling when changes to symbolic
+    links conflicted were completely broken.  The merge-resolve
+    strategy created a regular file with conflict markers in it
+    in place of the symbolic link.  The default strategy,
+    merge-recursive was even more broken.  It removed the path
+    that was pointed at by the symbolic link.  Both of these
+    problems have been fixed.
+
+  - 'git diff maint master next' did not correctly give combined
+    diff across three trees.
+
+  - 'git fast-import' portability fix for Solaris.
+
+  - 'git show-ref --verify' without arguments did not error out
+    but segfaulted.
+
+  - 'git diff :tracked-file `pwd`/an-untracked-file' gave an extra
+    slashes after a/ and b/.
+
+  - 'git format-patch' produced too long filenames if the commit
+    message had too long line at the beginning.
+
+  - Running 'make all' and then without changing anything
+    running 'make install' still rebuilt some files.  This
+    was inconvenient when building as yourself and then
+    installing as root (especially problematic when the source
+    directory is on NFS and root is mapped to nobody).
+
+  - 'git-rerere' failed to deal with two unconflicted paths that
+    sorted next to each other.
+
+  - 'git-rerere' attempted to open(2) a symlink and failed if
+    there was a conflict.  Since a conflicting change to a
+    symlink would not benefit from rerere anyway, the command
+    now ignores conflicting changes to symlinks.
+
+  - 'git-repack' did not like to pass more than 64 arguments
+    internally to underlying 'rev-list' logic, which made it
+    impossible to repack after accumulating many (small) packs
+    in the repository.
+
+  - 'git-diff' to review the combined diff during a conflicted
+    merge were not reading the working tree version correctly
+    when changes to a symbolic link conflicted.  It should have
+    read the data using readlink(2) but read from the regular
+    file the symbolic link pointed at.
+
+  - 'git-remote' did not like period in a remote's name.
+
+* Documentation updates
+
+  - added and clarified core.bare, core.legacyheaders configurations.
+
+  - updated "git-clone --depth" documentation.
+
+
+* Assorted git-gui fixes.
diff --git a/Documentation/RelNotes/1.5.0.3.txt b/Documentation/RelNotes/1.5.0.3.txt
new file mode 100644 (file)
index 0000000..cd500f9
--- /dev/null
@@ -0,0 +1,58 @@
+GIT v1.5.0.3 Release Notes
+==========================
+
+Fixes since v1.5.0.2
+--------------------
+
+* Bugfixes
+
+  - 'git.el' honors the commit coding system from the configuration.
+
+  - 'blameview' in contrib/ correctly digs deeper when a line is
+    clicked.
+
+  - 'http-push' correctly makes sure the remote side has leading
+    path.  Earlier it started in the middle of the path, and
+    incorrectly.
+
+  - 'git-merge' did not exit with non-zero status when the
+    working tree was dirty and cannot fast forward.  It does
+    now.
+
+  - 'cvsexportcommit' does not lose yet-to-be-used message file.
+
+  - int-vs-size_t typefix when running combined diff on files
+    over 2GB long.
+
+  - 'git apply --whitespace=strip' should not touch unmodified
+    lines.
+
+  - 'git-mailinfo' choke when a logical header line was too long.
+
+  - 'git show A..B' did not error out.  Negative ref ("not A" in
+    this example) does not make sense for the purpose of the
+    command, so now it errors out.
+
+  - 'git fmt-merge-msg --file' without file parameter did not
+    correctly error out.
+
+  - 'git archimport' barfed upon encountering a commit without
+    summary.
+
+  - 'git index-pack' did not protect itself from getting a short
+    read out of pread(2).
+
+  - 'git http-push' had a few buffer overruns.
+
+  - Build dependency fixes to rebuild fetch.o when other headers
+    change.
+
+* Documentation updates
+
+  - user-manual updates.
+
+  - Options to 'git remote add' were described insufficiently.
+
+  - Configuration format.suffix was not documented.
+
+  - Other formatting and spelling fixes.
diff --git a/Documentation/RelNotes/1.5.0.4.txt b/Documentation/RelNotes/1.5.0.4.txt
new file mode 100644 (file)
index 0000000..feefa5d
--- /dev/null
@@ -0,0 +1,22 @@
+GIT v1.5.0.4 Release Notes
+==========================
+
+Fixes since v1.5.0.3
+--------------------
+
+* Bugfixes
+
+  - git.el does not add duplicate sign-off lines.
+
+  - git-commit shows the full stat of the resulting commit, not
+    just about the files in the current directory, when run from
+    a subdirectory.
+
+  - "git-checkout -m '@{8 hours ago}'" had a funny failure from
+    eval; fixed.
+
+  - git-gui updates.
+
+* Documentation updates
+
+* User manual updates
diff --git a/Documentation/RelNotes/1.5.0.5.txt b/Documentation/RelNotes/1.5.0.5.txt
new file mode 100644 (file)
index 0000000..eeec3d7
--- /dev/null
@@ -0,0 +1,26 @@
+GIT v1.5.0.5 Release Notes
+==========================
+
+Fixes since v1.5.0.3
+--------------------
+
+* Bugfixes
+
+  - git-merge (hence git-pull) did not refuse fast-forwarding
+    when the working tree had local changes that would have
+    conflicted with it.
+
+  - git.el does not add duplicate sign-off lines.
+
+  - git-commit shows the full stat of the resulting commit, not
+    just about the files in the current directory, when run from
+    a subdirectory.
+
+  - "git-checkout -m '@{8 hours ago}'" had a funny failure from
+    eval; fixed.
+
+  - git-gui updates.
+
+* Documentation updates
+
+* User manual updates
diff --git a/Documentation/RelNotes/1.5.0.6.txt b/Documentation/RelNotes/1.5.0.6.txt
new file mode 100644 (file)
index 0000000..c02015a
--- /dev/null
@@ -0,0 +1,21 @@
+GIT v1.5.0.6 Release Notes
+==========================
+
+Fixes since v1.5.0.5
+--------------------
+
+* Bugfixes
+
+  - a handful small fixes to gitweb.
+
+  - build procedure for user-manual is fixed not to require locally
+    installed stylesheets.
+
+  - "git commit $paths" on paths whose earlier contents were
+    already updated in the index were failing out.
+
+* Documentation
+
+  - user-manual has better cross references.
+
+  - gitweb installation/deployment procedure is now documented.
diff --git a/Documentation/RelNotes/1.5.0.7.txt b/Documentation/RelNotes/1.5.0.7.txt
new file mode 100644 (file)
index 0000000..670ad32
--- /dev/null
@@ -0,0 +1,18 @@
+GIT v1.5.0.7 Release Notes
+==========================
+
+Fixes since v1.5.0.6
+--------------------
+
+* Bugfixes
+
+  - git-upload-pack failed to close unused pipe ends, resulting
+    in many zombies to hang around.
+
+  - git-rerere was recording the contents of earlier hunks
+    duplicated in later hunks.  This prevented resolving the same
+    conflict when performing the same merge the other way around.
+
+* Documentation
+
+  - a few documentation fixes from Debian package maintainer.
diff --git a/Documentation/RelNotes/1.5.0.txt b/Documentation/RelNotes/1.5.0.txt
new file mode 100644 (file)
index 0000000..daf4bdb
--- /dev/null
@@ -0,0 +1,469 @@
+GIT v1.5.0 Release Notes
+========================
+
+Old news
+--------
+
+This section is for people who are upgrading from ancient
+versions of git.  Although all of the changes in this section
+happened before the current v1.4.4 release, they are summarized
+here in the v1.5.0 release notes for people who skipped earlier
+versions.
+
+As of git v1.5.0 there are some optional features that changes
+the repository to allow data to be stored and transferred more
+efficiently.  These features are not enabled by default, as they
+will make the repository unusable with older versions of git.
+Specifically, the available options are:
+
+ - There is a configuration variable core.legacyheaders that
+   changes the format of loose objects so that they are more
+   efficient to pack and to send out of the repository over git
+   native protocol, since v1.4.2.  However, loose objects
+   written in the new format cannot be read by git older than
+   that version; people fetching from your repository using
+   older clients over dumb transports (e.g. http) using older
+   versions of git will also be affected.
+
+   To let git use the new loose object format, you have to
+   set core.legacyheaders to false.
+
+ - Since v1.4.3, configuration repack.usedeltabaseoffset allows
+   packfile to be created in more space efficient format, which
+   cannot be read by git older than that version.
+
+   To let git use the new format for packfiles, you have to
+   set repack.usedeltabaseoffset to true.
+
+The above two new features are not enabled by default and you
+have to explicitly ask for them, because they make repositories
+unreadable by older versions of git, and in v1.5.0 we still do
+not enable them by default for the same reason.  We will change
+this default probably 1 year after 1.4.2's release, when it is
+reasonable to expect everybody to have new enough version of
+git.
+
+ - 'git pack-refs' appeared in v1.4.4; this command allows tags
+   to be accessed much more efficiently than the traditional
+   'one-file-per-tag' format.  Older git-native clients can
+   still fetch from a repository that packed and pruned refs
+   (the server side needs to run the up-to-date version of git),
+   but older dumb transports cannot.  Packing of refs is done by
+   an explicit user action, either by use of "git pack-refs
+   --prune" command or by use of "git gc" command.
+
+ - 'git -p' to paginate anything -- many commands do pagination
+   by default on a tty.  Introduced between v1.4.1 and v1.4.2;
+   this may surprise old timers.
+
+ - 'git archive' superseded 'git tar-tree' in v1.4.3;
+
+ - 'git cvsserver' was new invention in v1.3.0;
+
+ - 'git repo-config', 'git grep', 'git rebase' and 'gitk' were
+   seriously enhanced during v1.4.0 timeperiod.
+
+ - 'gitweb' became part of git.git during v1.4.0 timeperiod and
+   seriously modified since then.
+
+ - reflog is an v1.4.0 invention.  This allows you to name a
+   revision that a branch used to be at (e.g. "git diff
+   master@{yesterday} master" allows you to see changes since
+   yesterday's tip of the branch).
+
+
+Updates in v1.5.0 since v1.4.4 series
+-------------------------------------
+
+* Index manipulation
+
+ - git-add is to add contents to the index (aka "staging area"
+   for the next commit), whether the file the contents happen to
+   be is an existing one or a newly created one.
+
+ - git-add without any argument does not add everything
+   anymore.  Use 'git-add .' instead.  Also you can add
+   otherwise ignored files with an -f option.
+
+ - git-add tries to be more friendly to users by offering an
+   interactive mode ("git-add -i").
+
+ - git-commit <path> used to refuse to commit if <path> was
+   different between HEAD and the index (i.e. update-index was
+   used on it earlier).  This check was removed.
+
+ - git-rm is much saner and safer.  It is used to remove paths
+   from both the index file and the working tree, and makes sure
+   you are not losing any local modification before doing so.
+
+ - git-reset <tree> <paths>... can be used to revert index
+   entries for selected paths.
+
+ - git-update-index is much less visible.  Many suggestions to
+   use the command in git output and documentation have now been
+   replaced by simpler commands such as "git add" or "git rm".
+
+
+* Repository layout and objects transfer
+
+ - The data for origin repository is stored in the configuration
+   file $GIT_DIR/config, not in $GIT_DIR/remotes/, for newly
+   created clones.  The latter is still supported and there is
+   no need to convert your existing repository if you are
+   already comfortable with your workflow with the layout.
+
+ - git-clone always uses what is known as "separate remote"
+   layout for a newly created repository with a working tree.
+
+   A repository with the separate remote layout starts with only
+   one default branch, 'master', to be used for your own
+   development.  Unlike the traditional layout that copied all
+   the upstream branches into your branch namespace (while
+   renaming their 'master' to your 'origin'), the new layout
+   puts upstream branches into local "remote-tracking branches"
+   with their own namespace. These can be referenced with names
+   such as "origin/$upstream_branch_name" and are stored in
+   .git/refs/remotes rather than .git/refs/heads where normal
+   branches are stored.
+
+   This layout keeps your own branch namespace less cluttered,
+   avoids name collision with your upstream, makes it possible
+   to automatically track new branches created at the remote
+   after you clone from it, and makes it easier to interact with
+   more than one remote repository (you can use "git remote" to
+   add other repositories to track).  There might be some
+   surprises:
+
+   * 'git branch' does not show the remote tracking branches.
+     It only lists your own branches.  Use '-r' option to view
+     the tracking branches.
+
+   * If you are forking off of a branch obtained from the
+     upstream, you would have done something like 'git branch
+     my-next next', because traditional layout dropped the
+     tracking branch 'next' into your own branch namespace.
+     With the separate remote layout, you say 'git branch next
+     origin/next', which allows you to use the matching name
+     'next' for your own branch.  It also allows you to track a
+     remote other than 'origin' (i.e. where you initially cloned
+     from) and fork off of a branch from there the same way
+     (e.g. "git branch mingw j6t/master").
+
+   Repositories initialized with the traditional layout continue
+   to work.
+
+ - New branches that appear on the origin side after a clone is
+   made are also tracked automatically.  This is done with an
+   wildcard refspec "refs/heads/*:refs/remotes/origin/*", which
+   older git does not understand, so if you clone with 1.5.0,
+   you would need to downgrade remote.*.fetch in the
+   configuration file to specify each branch you are interested
+   in individually if you plan to fetch into the repository with
+   older versions of git (but why would you?).
+
+ - Similarly, wildcard refspec "refs/heads/*:refs/remotes/me/*"
+   can be given to "git-push" command to update the tracking
+   branches that is used to track the repository you are pushing
+   from on the remote side.
+
+ - git-branch and git-show-branch know remote tracking branches
+   (use the command line switch "-r" to list only tracked branches).
+
+ - git-push can now be used to delete a remote branch or a tag.
+   This requires the updated git on the remote side (use "git
+   push <remote> :refs/heads/<branch>" to delete "branch").
+
+ - git-push more aggressively keeps the transferred objects
+   packed.  Earlier we recommended to monitor amount of loose
+   objects and repack regularly, but you should repack when you
+   accumulated too many small packs this way as well.  Updated
+   git-count-objects helps you with this.
+
+ - git-fetch also more aggressively keeps the transferred objects
+   packed.  This behavior of git-push and git-fetch can be
+   tweaked with a single configuration transfer.unpacklimit (but
+   usually there should not be any need for a user to tweak it).
+
+ - A new command, git-remote, can help you manage your remote
+   tracking branch definitions.
+
+ - You may need to specify explicit paths for upload-pack and/or
+   receive-pack due to your ssh daemon configuration on the
+   other end.  This can now be done via remote.*.uploadpack and
+   remote.*.receivepack configuration.
+
+
+* Bare repositories
+
+ - Certain commands change their behavior in a bare repository
+   (i.e. a repository without associated working tree).  We use
+   a fairly conservative heuristic (if $GIT_DIR is ".git", or
+   ends with "/.git", the repository is not bare) to decide if a
+   repository is bare, but "core.bare" configuration variable
+   can be used to override the heuristic when it misidentifies
+   your repository.
+
+ - git-fetch used to complain updating the current branch but
+   this is now allowed for a bare repository.  So is the use of
+   'git-branch -f' to update the current branch.
+
+ - Porcelain-ish commands that require a working tree refuses to
+   work in a bare repository.
+
+
+* Reflog
+
+ - Reflog records the history from the view point of the local
+   repository. In other words, regardless of the real history,
+   the reflog shows the history as seen by one particular
+   repository (this enables you to ask "what was the current
+   revision in _this_ repository, yesterday at 1pm?").  This
+   facility is enabled by default for repositories with working
+   trees, and can be accessed with the "branch@{time}" and
+   "branch@{Nth}" notation.
+
+ - "git show-branch" learned showing the reflog data with the
+   new -g option.  "git log" has -g option to view reflog
+   entries in a more verbose manner.
+
+ - git-branch knows how to rename branches and moves existing
+   reflog data from the old branch to the new one.
+
+ - In addition to the reflog support in v1.4.4 series, HEAD
+   reference maintains its own log.  "HEAD@{5.minutes.ago}"
+   means the commit you were at 5 minutes ago, which takes
+   branch switching into account.  If you want to know where the
+   tip of your current branch was at 5 minutes ago, you need to
+   explicitly say its name (e.g. "master@{5.minutes.ago}") or
+   omit the refname altogether i.e. "@{5.minutes.ago}".
+
+ - The commits referred to by reflog entries are now protected
+   against pruning.  The new command "git reflog expire" can be
+   used to truncate older reflog entries and entries that refer
+   to commits that have been pruned away previously with older
+   versions of git.
+
+   Existing repositories that have been using reflog may get
+   complaints from fsck-objects and may not be able to run
+   git-repack, if you had run git-prune from older git; please
+   run "git reflog expire --stale-fix --all" first to remove
+   reflog entries that refer to commits that are no longer in
+   the repository when that happens.
+
+
+* Crufts removal
+
+ - We used to say "old commits are retrievable using reflog and
+   'master@{yesterday}' syntax as long as you haven't run
+   git-prune".  We no longer have to say the latter half of the
+   above sentence, as git-prune does not remove things reachable
+   from reflog entries.
+
+ - There is a toplevel garbage collector script, 'git-gc', that
+   runs periodic cleanup functions, including 'git-repack -a -d',
+   'git-reflog expire', 'git-pack-refs --prune', and 'git-rerere
+   gc'.
+
+ - The output from fsck ("fsck-objects" is called just "fsck"
+   now, but the old name continues to work) was needlessly
+   alarming in that it warned missing objects that are reachable
+   only from dangling objects.  This has been corrected and the
+   output is much more useful.
+
+
+* Detached HEAD
+
+ - You can use 'git-checkout' to check out an arbitrary revision
+   or a tag as well, instead of named branches.  This will
+   dissociate your HEAD from the branch you are currently on.
+
+   A typical use of this feature is to "look around".  E.g.
+
+       $ git checkout v2.6.16
+       ... compile, test, etc.
+       $ git checkout v2.6.17
+       ... compile, test, etc.
+
+ - After detaching your HEAD, you can go back to an existing
+   branch with usual "git checkout $branch".  Also you can
+   start a new branch using "git checkout -b $newbranch" to
+   start a new branch at that commit.
+
+ - You can even pull from other repositories, make merges and
+   commits while your HEAD is detached.  Also you can use "git
+   reset" to jump to arbitrary commit, while still keeping your
+   HEAD detached.
+
+   Remember that a detached state is volatile, i.e. it will be forgotten
+   as soon as you move away from it with the checkout or reset command,
+   unless a branch is created from it as mentioned above.  It is also
+   possible to rescue a lost detached state from the HEAD reflog.
+
+
+* Packed refs
+
+ - Repositories with hundreds of tags have been paying large
+   overhead, both in storage and in runtime, due to the
+   traditional one-ref-per-file format.  A new command,
+   git-pack-refs, can be used to "pack" them in more efficient
+   representation (you can let git-gc do this for you).
+
+ - Clones and fetches over dumb transports are now aware of
+   packed refs and can download from repositories that use
+   them.
+
+
+* Configuration
+
+ - configuration related to color setting are consolidated under
+   color.* namespace (older diff.color.*, status.color.* are
+   still supported).
+
+ - 'git-repo-config' command is accessible as 'git-config' now.
+
+
+* Updated features
+
+ - git-describe uses better criteria to pick a base ref.  It
+   used to pick the one with the newest timestamp, but now it
+   picks the one that is topologically the closest (that is,
+   among ancestors of commit C, the ref T that has the shortest
+   output from "git-rev-list T..C" is chosen).
+
+ - git-describe gives the number of commits since the base ref
+   between the refname and the hash suffix.  E.g. the commit one
+   before v2.6.20-rc6 in the kernel repository is:
+
+       v2.6.20-rc5-306-ga21b069
+
+   which tells you that its object name begins with a21b069,
+   v2.6.20-rc5 is an ancestor of it (meaning, the commit
+   contains everything -rc5 has), and there are 306 commits
+   since v2.6.20-rc5.
+
+ - git-describe with --abbrev=0 can be used to show only the
+   name of the base ref.
+
+ - git-blame learned a new option, --incremental, that tells it
+   to output the blames as they are assigned.  A sample script
+   to use it is also included as contrib/blameview.
+
+ - git-blame starts annotating from the working tree by default.
+
+
+* Less external dependency
+
+ - We no longer require the "merge" program from the RCS suite.
+   All 3-way file-level merges are now done internally.
+
+ - The original implementation of git-merge-recursive which was
+   in Python has been removed; we have a C implementation of it
+   now.
+
+ - git-shortlog is no longer a Perl script.  It no longer
+   requires output piped from git-log; it can accept revision
+   parameters directly on the command line.
+
+
+* I18n
+
+ - We have always encouraged the commit message to be encoded in
+   UTF-8, but the users are allowed to use legacy encoding as
+   appropriate for their projects.  This will continue to be the
+   case.  However, a non UTF-8 commit encoding _must_ be
+   explicitly set with i18n.commitencoding in the repository
+   where a commit is made; otherwise git-commit-tree will
+   complain if the log message does not look like a valid UTF-8
+   string.
+
+ - The value of i18n.commitencoding in the originating
+   repository is recorded in the commit object on the "encoding"
+   header, if it is not UTF-8.  git-log and friends notice this,
+   and reencodes the message to the log output encoding when
+   displaying, if they are different.  The log output encoding
+   is determined by "git log --encoding=<encoding>",
+   i18n.logoutputencoding configuration, or i18n.commitencoding
+   configuration, in the decreasing order of preference, and
+   defaults to UTF-8.
+
+ - Tools for e-mailed patch application now default to -u
+   behavior; i.e. it always re-codes from the e-mailed encoding
+   to the encoding specified with i18n.commitencoding.  This
+   unfortunately forces projects that have happily been using a
+   legacy encoding without setting i18n.commitencoding to set
+   the configuration, but taken with other improvement, please
+   excuse us for this very minor one-time inconvenience.
+
+
+* e-mailed patches
+
+ - See the above I18n section.
+
+ - git-format-patch now enables --binary without being asked.
+   git-am does _not_ default to it, as sending binary patch via
+   e-mail is unusual and is harder to review than textual
+   patches and it is prudent to require the person who is
+   applying the patch to explicitly ask for it.
+
+ - The default suffix for git-format-patch output is now ".patch",
+   not ".txt".  This can be changed with --suffix=.txt option,
+   or setting the config variable "format.suffix" to ".txt".
+
+
+* Foreign SCM interfaces
+
+ - git-svn now requires the Perl SVN:: libraries, the
+   command-line backend was too slow and limited.
+
+ - the 'commit' subcommand of git-svn has been renamed to
+   'set-tree', and 'dcommit' is the recommended replacement for
+   day-to-day work.
+
+ - git fast-import backend.
+
+
+* User support
+
+ - Quite a lot of documentation updates.
+
+ - Bash completion scripts have been updated heavily.
+
+ - Better error messages for often used Porcelainish commands.
+
+ - Git GUI.  This is a simple Tk based graphical interface for
+   common Git operations.
+
+
+* Sliding mmap
+
+ - We used to assume that we can mmap the whole packfile while
+   in use, but with a large project this consumes huge virtual
+   memory space and truly huge ones would not fit in the
+   userland address space on 32-bit platforms.  We now mmap huge
+   packfile in pieces to avoid this problem.
+
+
+* Shallow clones
+
+ - There is a partial support for 'shallow' repositories that
+   keeps only recent history.  A 'shallow clone' is created by
+   specifying how deep that truncated history should be
+   (e.g. "git clone --depth 5 git://some.where/repo.git").
+
+   Currently a shallow repository has number of limitations:
+
+   - Cloning and fetching _from_ a shallow clone are not
+     supported (nor tested -- so they might work by accident but
+     they are not expected to).
+
+   - Pushing from nor into a shallow clone are not expected to
+     work.
+
+   - Merging inside a shallow repository would work as long as a
+     merge base is found in the recent history, but otherwise it
+     will be like merging unrelated histories and may result in
+     huge conflicts.
+
+   but this would be more than adequate for people who want to
+   look at near the tip of a big project with a deep history and
+   send patches in e-mail format.
diff --git a/Documentation/RelNotes/1.5.1.1.txt b/Documentation/RelNotes/1.5.1.1.txt
new file mode 100644 (file)
index 0000000..9147121
--- /dev/null
@@ -0,0 +1,65 @@
+GIT v1.5.1.1 Release Notes
+==========================
+
+Fixes since v1.5.1
+------------------
+
+* Documentation updates
+
+  - The --left-right option of rev-list and friends is documented.
+
+  - The documentation for cvsimport has been majorly improved.
+
+  - "git-show-ref --exclude-existing" was documented.
+
+* Bugfixes
+
+  - The implementation of -p option in "git cvsexportcommit" had
+    the meaning of -C (context reduction) option wrong, and
+    loosened the context requirements when it was told to be
+    strict.
+
+  - "git cvsserver" did not behave like the real cvsserver when
+    client side removed a file from the working tree without
+    doing anything else on the path.  In such a case, it should
+    restore it from the checked out revision.
+
+  - "git fsck" issued an alarming error message on detached
+    HEAD.  It is not an error since at least 1.5.0.
+
+  - "git send-email" produced of References header of unbounded length;
+    fixed this with line-folding.
+
+  - "git archive" to download from remote site should not
+    require you to be in a git repository, but it incorrectly
+    did.
+
+  - "git apply" ignored -p<n> for "diff --git" formatted
+    patches.
+
+  - "git rerere" recorded a conflict that had one side empty
+    (the other side adds) incorrectly; this made merging in the
+    other direction fail to use previously recorded resolution.
+
+  - t4200 test was broken where "wc -l" pads its output with
+    spaces.
+
+  - "git branch -m old new" to rename branch did not work
+    without a configuration file in ".git/config".
+
+  - The sample hook for notification e-mail was misnamed.
+
+  - gitweb did not show type-changing patch correctly in the
+    blobdiff view.
+
+  - git-svn did not error out with incorrect command line options.
+
+  - git-svn fell into an infinite loop when insanely long commit
+    message was found.
+
+  - git-svn dcommit and rebase was confused by patches that were
+    merged from another branch that is managed by git-svn.
+
+  - git-svn used to get confused when globbing remote branch/tag
+    spec (e.g. "branches = proj/branches/*:refs/remotes/origin/*")
+    is used and there was a plain file that matched the glob.
diff --git a/Documentation/RelNotes/1.5.1.2.txt b/Documentation/RelNotes/1.5.1.2.txt
new file mode 100644 (file)
index 0000000..d884563
--- /dev/null
@@ -0,0 +1,50 @@
+GIT v1.5.1.2 Release Notes
+==========================
+
+Fixes since v1.5.1.1
+--------------------
+
+* Bugfixes
+
+  - "git clone" over http from a repository that has lost the
+    loose refs by running "git pack-refs" were broken (a code to
+    deal with this was added to "git fetch" in v1.5.0, but it
+    was missing from "git clone").
+
+  - "git diff a/ b/" incorrectly fell in "diff between two
+    filesystem objects" codepath, when the user most likely
+    wanted to limit the extent of output to two tracked
+    directories.
+
+  - git-quiltimport had the same bug as we fixed for
+    git-applymbox in v1.5.1.1 -- it gave an alarming "did not
+    have any patch" message (but did not actually fail and was
+    harmless).
+
+  - various git-svn fixes.
+
+  - Sample update hook incorrectly always refused requests to
+    delete branches through push.
+
+  - git-blame on a very long working tree path had buffer
+    overrun problem.
+
+  - git-apply did not like to be fed two patches in a row that created
+    and then modified the same file.
+
+  - git-svn was confused when a non-project was stored directly under
+    trunk/, branches/ and tags/.
+
+  - git-svn wants the Error.pm module that was at least as new
+    as what we ship as part of git; install ours in our private
+    installation location if the one on the system is older.
+
+  - An earlier update to command line integer parameter parser was
+    botched and made 'update-index --cacheinfo' completely useless.
+
+
+* Documentation updates
+
+  - Various documentation updates from J. Bruce Fields, Frank
+    Lichtenheld, Alex Riesen and others.  Andrew Ruder started a
+    war on undocumented options.
diff --git a/Documentation/RelNotes/1.5.1.3.txt b/Documentation/RelNotes/1.5.1.3.txt
new file mode 100644 (file)
index 0000000..876408b
--- /dev/null
@@ -0,0 +1,45 @@
+GIT v1.5.1.3 Release Notes
+==========================
+
+Fixes since v1.5.1.2
+--------------------
+
+* Bugfixes
+
+  - git-add tried to optimize by finding common leading
+    directories across its arguments but botched, causing very
+    confused behaviour.
+
+  - unofficial rpm.spec file shipped with git was letting
+    ETC_GITCONFIG set to /usr/etc/gitconfig.  Tweak the official
+    Makefile to make it harder for distro people to make the
+    same mistake, by setting the variable to /etc/gitconfig if
+    prefix is set to /usr.
+
+  - git-svn inconsistently stripped away username from the URL
+    only when svnsync_props was in use.
+
+  - git-svn got confused when handling symlinks on Mac OS.
+
+  - git-send-email was not quoting recipient names that have
+    period '.' in them.  Also it did not allow overriding
+    envelope sender, which made it impossible to send patches to
+    certain subscriber-only lists.
+
+  - built-in write_tree() routine had a sequence that renamed a
+    file that is still open, which some systems did not like.
+
+  - when memory is very tight, sliding mmap code to read
+    packfiles incorrectly closed the fd that was still being
+    used to read the pack.
+
+  - import-tars contributed front-end for fastimport was passing
+    wrong directory modes without checking.
+
+  - git-fastimport trusted its input too much and allowed to
+    create corrupt tree objects with entries without a name.
+
+  - git-fetch needlessly barfed when too long reflog action
+    description was given by the caller.
+
+Also contains various documentation updates.
diff --git a/Documentation/RelNotes/1.5.1.4.txt b/Documentation/RelNotes/1.5.1.4.txt
new file mode 100644 (file)
index 0000000..df2f66c
--- /dev/null
@@ -0,0 +1,30 @@
+GIT v1.5.1.4 Release Notes
+==========================
+
+Fixes since v1.5.1.3
+--------------------
+
+* Bugfixes
+
+  - "git-http-fetch" did not work around a bug in libcurl
+    earlier than 7.16 (curl_multi_remove_handle() was broken).
+
+  - "git cvsserver" handles a file that was once removed and
+    then added again correctly.
+
+  - import-tars script (in contrib/) handles GNU tar archives
+    that contain pathnames longer than 100 bytes (long-link
+    extension) correctly.
+
+  - xdelta test program did not build correctly.
+
+  - gitweb sometimes tried incorrectly to apply function to
+    decode utf8 twice, resulting in corrupt output.
+
+  - "git blame -C" mishandled text at the end of a group of
+    lines.
+
+  - "git log/rev-list --boundary" did not produce output
+    correctly without --left-right option.
+
+  - Many documentation updates.
diff --git a/Documentation/RelNotes/1.5.1.5.txt b/Documentation/RelNotes/1.5.1.5.txt
new file mode 100644 (file)
index 0000000..b0ab8eb
--- /dev/null
@@ -0,0 +1,42 @@
+GIT v1.5.1.5 Release Notes
+==========================
+
+Fixes since v1.5.1.4
+--------------------
+
+* Bugfixes
+
+  - git-send-email did not understand aliases file for mutt, which
+    allows leading whitespaces.
+
+  - git-format-patch emitted Content-Type and Content-Transfer-Encoding
+    headers for non ASCII contents, but failed to add MIME-Version.
+
+  - git-name-rev had a buffer overrun with a deep history.
+
+  - contributed script import-tars did not get the directory in
+    tar archives interpreted correctly.
+
+  - git-svn was reported to segfault for many people on list and
+    #git; hopefully this has been fixed.
+
+  - "git-svn clone" does not try to minimize the URL
+    (i.e. connect to higher level hierarchy) by default, as this
+    can prevent clone to fail if only part of the repository
+    (e.g. 'trunk') is open to public.
+
+  - "git checkout branch^0" did not detach the head when you are
+    already on 'branch'; backported the fix from the 'master'.
+
+  - "git-config section.var" did not correctly work when
+    existing configuration file had both [section] and [section "name"]
+    next to each other.
+
+  - "git clone ../other-directory" was fooled if the current
+    directory $PWD points at is a symbolic link.
+
+  - (build) tree_entry_extract() function was both static inline
+    and extern, which caused trouble compiling with Forte12
+    compilers on Sun.
+
+  - Many many documentation fixes and updates.
diff --git a/Documentation/RelNotes/1.5.1.6.txt b/Documentation/RelNotes/1.5.1.6.txt
new file mode 100644 (file)
index 0000000..55f3ac1
--- /dev/null
@@ -0,0 +1,45 @@
+GIT v1.5.1.6 Release Notes
+==========================
+
+Fixes since v1.5.1.4
+--------------------
+
+* Bugfixes
+
+  - git-send-email did not understand aliases file for mutt, which
+    allows leading whitespaces.
+
+  - git-format-patch emitted Content-Type and Content-Transfer-Encoding
+    headers for non ASCII contents, but failed to add MIME-Version.
+
+  - git-name-rev had a buffer overrun with a deep history.
+
+  - contributed script import-tars did not get the directory in
+    tar archives interpreted correctly.
+
+  - git-svn was reported to segfault for many people on list and
+    #git; hopefully this has been fixed.
+
+  - git-svn also had a bug to crash svnserve by sending a bad
+    sequence of requests.
+
+  - "git-svn clone" does not try to minimize the URL
+    (i.e. connect to higher level hierarchy) by default, as this
+    can prevent clone to fail if only part of the repository
+    (e.g. 'trunk') is open to public.
+
+  - "git checkout branch^0" did not detach the head when you are
+    already on 'branch'; backported the fix from the 'master'.
+
+  - "git-config section.var" did not correctly work when
+    existing configuration file had both [section] and [section "name"]
+    next to each other.
+
+  - "git clone ../other-directory" was fooled if the current
+    directory $PWD points at is a symbolic link.
+
+  - (build) tree_entry_extract() function was both static inline
+    and extern, which caused trouble compiling with Forte12
+    compilers on Sun.
+
+  - Many many documentation fixes and updates.
diff --git a/Documentation/RelNotes/1.5.1.txt b/Documentation/RelNotes/1.5.1.txt
new file mode 100644 (file)
index 0000000..daed367
--- /dev/null
@@ -0,0 +1,371 @@
+GIT v1.5.1 Release Notes
+========================
+
+Updates since v1.5.0
+--------------------
+
+* Deprecated commands and options.
+
+  - git-diff-stages and git-resolve have been removed.
+
+* New commands and options.
+
+  - "git log" and friends take --reverse, which instructs them
+    to give their output in the order opposite from their usual.
+    They typically output from new to old, but with this option
+    their output would read from old to new.  "git shortlog"
+    usually lists older commits first, but with this option,
+    they are shown from new to old.
+
+  - "git log --pretty=format:<string>" to allow more flexible
+    custom log output.
+
+  - "git diff" learned --ignore-space-at-eol.  This is a weaker
+    form of --ignore-space-change.
+
+  - "git diff --no-index pathA pathB" can be used as diff
+    replacement with git specific enhancements.
+
+  - "git diff --no-index" can read from '-' (standard input).
+
+  - "git diff" also learned --exit-code to exit with non-zero
+    status when it found differences.  In the future we might
+    want to make this the default but that would be a rather big
+    backward incompatible change; it will stay as an option for
+    now.
+
+  - "git diff --quiet" is --exit-code with output turned off,
+    meant for scripted use to quickly determine if there is any
+    tree-level difference.
+
+  - Textual patch generation with "git diff" without -w/-b
+    option has been significantly optimized.  "git blame" got
+    faster because of the same change.
+
+  - "git log" and "git rev-list" has been optimized
+    significantly when they are used with pathspecs.
+
+  - "git branch --track" can be used to set up configuration
+    variables to help it easier to base your work on branches
+    you track from a remote site.
+
+  - "git format-patch --attach" now emits attachments.  Use
+    --inline to get an inlined multipart/mixed.
+
+  - "git name-rev" learned --refs=<pattern>, to limit the tags
+    used for naming the given revisions only to the ones
+    matching the given pattern.
+
+  - "git remote update" is to run "git fetch" for defined remotes
+    to update tracking branches.
+
+  - "git cvsimport" can now take '-d' to talk with a CVS
+    repository different from what are recorded in CVS/Root
+    (overriding it with environment CVSROOT does not work).
+
+  - "git bundle" can help sneaker-netting your changes between
+    repositories.
+
+  - "git mergetool" can help 3-way file-level conflict
+    resolution with your favorite graphical merge tools.
+
+  - A new configuration "core.symlinks" can be used to disable
+    symlinks on filesystems that do not support them; they are
+    checked out as regular files instead.
+
+  - You can name a commit object with its first line of the
+    message.  The syntax to use is ':/message text'.  E.g.
+
+    $ git show ":/object name: introduce ':/<oneline prefix>' notation"
+
+    means the same thing as:
+
+    $ git show 28a4d940443806412effa246ecc7768a21553ec7
+
+  - "git bisect" learned a new command "run" that takes a script
+    to run after each revision is checked out to determine if it
+    is good or bad, to automate the bisection process.
+
+  - "git log" family learned a new traversal option --first-parent,
+    which does what the name suggests.
+
+
+* Updated behavior of existing commands.
+
+  - "git-merge-recursive" used to barf when there are more than
+    one common ancestors for the merge, and merging them had a
+    rename/rename conflict.  This has been fixed.
+
+  - "git fsck" does not barf on corrupt loose objects.
+
+  - "git rm" does not remove newly added files without -f.
+
+  - "git archimport" allows remapping when coming up with git
+    branch names from arch names.
+
+  - git-svn got almost a rewrite.
+
+  - core.autocrlf configuration, when set to 'true', makes git
+    to convert CRLF at the end of lines in text files to LF when
+    reading from the filesystem, and convert in reverse when
+    writing to the filesystem.  The variable can be set to
+    'input', in which case the conversion happens only while
+    reading from the filesystem but files are written out with
+    LF at the end of lines.  Currently, which paths to consider
+    'text' (i.e. be subjected to the autocrlf mechanism) is
+    decided purely based on the contents, but the plan is to
+    allow users to explicitly override this heuristic based on
+    paths.
+
+  - The behavior of 'git-apply', when run in a subdirectory,
+    without --index nor --cached were inconsistent with that of
+    the command with these options.  This was fixed to match the
+    behavior with --index.  A patch that is meant to be applied
+    with -p1 from the toplevel of the project tree can be
+    applied with any custom -p<n> option.  A patch that is not
+    relative to the toplevel needs to be applied with -p<n>
+    option with or without --index (or --cached).
+
+  - "git diff" outputs a trailing HT when pathnames have embedded
+    SP on +++/--- header lines, in order to help "GNU patch" to
+    parse its output.  "git apply" was already updated to accept
+    this modified output format since ce74618d (Sep 22, 2006).
+
+  - "git cvsserver" runs hooks/update and honors its exit status.
+
+  - "git cvsserver" can be told to send everything with -kb.
+
+  - "git diff --check" also honors the --color output option.
+
+  - "git name-rev" used to stress the fact that a ref is a tag too
+    much, by saying something like "v1.2.3^0~22".  It now says
+    "v1.2.3~22" in such a case (it still says "v1.2.3^0" if it does
+    not talk about an ancestor of the commit that is tagged, which
+    makes sense).
+
+  - "git rev-list --boundary" now shows boundary markers for the
+    commits omitted by --max-age and --max-count condition.
+
+  - The configuration mechanism now reads $(prefix)/etc/gitconfig.
+
+  - "git apply --verbose" shows what preimage lines were wanted
+    when it couldn't find them.
+
+  - "git status" in a read-only repository got a bit saner.
+
+  - "git fetch" (hence "git clone" and "git pull") are less
+    noisy when the output does not go to tty.
+
+  - "git fetch" between repositories with many refs were slow
+    even when there are not many changes that needed
+    transferring.  This has been sped up by partially rewriting
+    the heaviest parts in C.
+
+  - "git mailinfo" which splits an e-mail into a patch and the
+    meta-information was rewritten, thanks to Don Zickus.  It
+    handles nested multipart better.  The command was broken for
+    a brief period on 'master' branch since 1.5.0 but the
+    breakage is fixed now.
+
+  - send-email learned configurable bcc and chain-reply-to.
+
+  - "git remote show $remote" also talks about branches that
+    would be pushed if you run "git push remote".
+
+  - Using objects from packs is now seriously optimized by clever
+    use of a cache.  This should be most noticeable in git-log
+    family of commands that involve reading many tree objects.
+    In addition, traversing revisions while filtering changes
+    with pathspecs is made faster by terminating the comparison
+    between the trees as early as possible.
+
+
+* Hooks
+
+  - The part to send out notification e-mails was removed from
+    the sample update hook, as it was not an appropriate place
+    to do so.  The proper place to do this is the new post-receive
+    hook.  An example hook has been added to contrib/hooks/.
+
+
+* Others
+
+  - git-revert, git-gc and git-cherry-pick are now built-ins.
+
+Fixes since v1.5.0
+------------------
+
+These are all in v1.5.0.x series.
+
+* Documentation updates
+
+  - Clarifications and corrections to 1.5.0 release notes.
+
+  - The main documentation did not link to git-remote documentation.
+
+  - Clarified introductory text of git-rebase documentation.
+
+  - Converted remaining mentions of update-index on Porcelain
+    documents to git-add/git-rm.
+
+  - Some i18n.* configuration variables were incorrectly
+    described as core.*; fixed.
+
+  - added and clarified core.bare, core.legacyheaders configurations.
+
+  - updated "git-clone --depth" documentation.
+
+  - user-manual updates.
+
+  - Options to 'git remote add' were described insufficiently.
+
+  - Configuration format.suffix was not documented.
+
+  - Other formatting and spelling fixes.
+
+  - user-manual has better cross references.
+
+  - gitweb installation/deployment procedure is now documented.
+
+
+* Bugfixes
+
+  - git-upload-pack closes unused pipe ends; earlier this caused
+    many zombies to hang around.
+
+  - git-rerere was recording the contents of earlier hunks
+    duplicated in later hunks.  This prevented resolving the same
+    conflict when performing the same merge the other way around.
+
+  - git-add and git-update-index on a filesystem on which
+    executable bits are unreliable incorrectly reused st_mode
+    bits even when the path changed between symlink and regular
+    file.
+
+  - git-daemon marks the listening sockets with FD_CLOEXEC so
+    that it won't be leaked into the children.
+
+  - segfault from git-blame when the mandatory pathname
+    parameter was missing was fixed; usage() message is given
+    instead.
+
+  - git-rev-list did not read $GIT_DIR/config file, which means
+    that did not honor i18n.logoutputencoding correctly.
+
+  - Automated merge conflict handling when changes to symbolic
+    links conflicted were completely broken.  The merge-resolve
+    strategy created a regular file with conflict markers in it
+    in place of the symbolic link.  The default strategy,
+    merge-recursive was even more broken.  It removed the path
+    that was pointed at by the symbolic link.  Both of these
+    problems have been fixed.
+
+  - 'git diff maint master next' did not correctly give combined
+    diff across three trees.
+
+  - 'git fast-import' portability fix for Solaris.
+
+  - 'git show-ref --verify' without arguments did not error out
+    but segfaulted.
+
+  - 'git diff :tracked-file `pwd`/an-untracked-file' gave an extra
+    slashes after a/ and b/.
+
+  - 'git format-patch' produced too long filenames if the commit
+    message had too long line at the beginning.
+
+  - Running 'make all' and then without changing anything
+    running 'make install' still rebuilt some files.  This
+    was inconvenient when building as yourself and then
+    installing as root (especially problematic when the source
+    directory is on NFS and root is mapped to nobody).
+
+  - 'git-rerere' failed to deal with two unconflicted paths that
+    sorted next to each other.
+
+  - 'git-rerere' attempted to open(2) a symlink and failed if
+    there was a conflict.  Since a conflicting change to a
+    symlink would not benefit from rerere anyway, the command
+    now ignores conflicting changes to symlinks.
+
+  - 'git-repack' did not like to pass more than 64 arguments
+    internally to underlying 'rev-list' logic, which made it
+    impossible to repack after accumulating many (small) packs
+    in the repository.
+
+  - 'git-diff' to review the combined diff during a conflicted
+    merge were not reading the working tree version correctly
+    when changes to a symbolic link conflicted.  It should have
+    read the data using readlink(2) but read from the regular
+    file the symbolic link pointed at.
+
+  - 'git-remote' did not like period in a remote's name.
+
+  - 'git.el' honors the commit coding system from the configuration.
+
+  - 'blameview' in contrib/ correctly digs deeper when a line is
+    clicked.
+
+  - 'http-push' correctly makes sure the remote side has leading
+    path.  Earlier it started in the middle of the path, and
+    incorrectly.
+
+  - 'git-merge' did not exit with non-zero status when the
+    working tree was dirty and cannot fast forward.  It does
+    now.
+
+  - 'cvsexportcommit' does not lose yet-to-be-used message file.
+
+  - int-vs-size_t typefix when running combined diff on files
+    over 2GB long.
+
+  - 'git apply --whitespace=strip' should not touch unmodified
+    lines.
+
+  - 'git-mailinfo' choke when a logical header line was too long.
+
+  - 'git show A..B' did not error out.  Negative ref ("not A" in
+    this example) does not make sense for the purpose of the
+    command, so now it errors out.
+
+  - 'git fmt-merge-msg --file' without file parameter did not
+    correctly error out.
+
+  - 'git archimport' barfed upon encountering a commit without
+    summary.
+
+  - 'git index-pack' did not protect itself from getting a short
+    read out of pread(2).
+
+  - 'git http-push' had a few buffer overruns.
+
+  - Build dependency fixes to rebuild fetch.o when other headers
+    change.
+
+  - git.el does not add duplicate sign-off lines.
+
+  - git-commit shows the full stat of the resulting commit, not
+    just about the files in the current directory, when run from
+    a subdirectory.
+
+  - "git-checkout -m '@{8 hours ago}'" had a funny failure from
+    eval; fixed.
+
+  - git-merge (hence git-pull) did not refuse fast-forwarding
+    when the working tree had local changes that would have
+    conflicted with it.
+
+  - a handful small fixes to gitweb.
+
+  - build procedure for user-manual is fixed not to require locally
+    installed stylesheets.
+
+  - "git commit $paths" on paths whose earlier contents were
+    already updated in the index were failing out.
+
+
+* Tweaks
+
+  - sliding mmap() inefficiently mmaped the same region of a
+    packfile with an access pattern that used objects in the
+    reverse order.  This has been made more efficient.
diff --git a/Documentation/RelNotes/1.5.2.1.txt b/Documentation/RelNotes/1.5.2.1.txt
new file mode 100644 (file)
index 0000000..ebf20e2
--- /dev/null
@@ -0,0 +1,53 @@
+GIT v1.5.2.1 Release Notes
+==========================
+
+Fixes since v1.5.2
+------------------
+
+* Bugfixes
+
+  - Temporary files that are used when invoking external diff
+    programs did not tolerate a long TMPDIR.
+
+  - git-daemon did not notice when it could not write into its
+    pid file.
+
+  - git-status did not honor core.excludesFile configuration like
+    git-add did.
+
+  - git-annotate did not work from a subdirectory while
+    git-blame did.
+
+  - git-cvsserver should have disabled access to a repository
+    with "gitcvs.pserver.enabled = false" set even when
+    "gitcvs.enabled = true" was set at the same time.  It
+    didn't.
+
+  - git-cvsimport did not work correctly in a repository with
+    its branch heads were packed with pack-refs.
+
+  - ident unexpansion to squash "$Id: xxx $" that is in the
+    repository copy removed incorrect number of bytes.
+
+  - git-svn misbehaved when the subversion repository did not
+    provide MD5 checksums for files.
+
+  - git rebase (and git am) misbehaved on commits that have '\n'
+    (literally backslash and en, not a linefeed) in the title.
+
+  - code to decode base85 used in binary patches had one error
+    return codepath wrong.
+
+  - RFC2047 Q encoding output by git-format-patch used '_' for a
+    space, which is not understood by some programs.  It uses =20
+    which is safer.
+
+  - git-fastimport --import-marks was broken; fixed.
+
+  - A lot of documentation updates, clarifications and fixes.
+
+--
+exec >/var/tmp/1
+O=v1.5.2-65-g996e2d6
+echo O=`git describe refs/heads/maint`
+git shortlog --no-merges $O..refs/heads/maint
diff --git a/Documentation/RelNotes/1.5.2.2.txt b/Documentation/RelNotes/1.5.2.2.txt
new file mode 100644 (file)
index 0000000..7bfa341
--- /dev/null
@@ -0,0 +1,61 @@
+GIT v1.5.2.2 Release Notes
+==========================
+
+Fixes since v1.5.2.1
+--------------------
+
+* Usability fix
+
+  - git-gui is shipped with its updated blame interface.  It is
+    rumored that the older one was not just unusable but was
+    active health hazard, but this one is actually pretty.
+    Please see for yourself.
+
+* Bugfixes
+
+  - "git checkout fubar" was utterly confused when there is a
+    branch fubar and a tag fubar at the same time.  It correctly
+    checks out the branch fubar now.
+
+  - "git clone /path/foo" to clone a local /path/foo.git
+    repository left an incorrect configuration.
+
+  - "git send-email" correctly unquotes RFC 2047 quoted names in
+    the patch-email before using their values.
+
+  - We did not accept number of seconds since epoch older than
+    year 2000 as a valid timestamp.  We now interpret positive
+    integers more than 8 digits as such, which allows us to
+    express timestamps more recent than March 1973.
+
+  - git-cvsimport did not work when you have GIT_DIR to point
+    your repository at a nonstandard location.
+
+  - Some systems (notably, Solaris) lack hstrerror() to make
+    h_errno human readable; prepare a replacement
+    implementation.
+
+  - .gitignore file listed git-core.spec but what we generate is
+    git.spec, and nobody noticed for a long time.
+
+  - "git-merge-recursive" does not try to run file level merge
+    on binary files.
+
+  - "git-branch --track" did not create tracking configuration
+    correctly when the branch name had slash in it.
+
+  - The email address of the user specified with user.email
+    configuration was overridden by EMAIL environment variable.
+
+  - The tree parser did not warn about tree entries with
+    nonsense file modes, and assumed they must be blobs.
+
+  - "git log -z" without any other request to generate diff still
+    invoked the diff machinery, wasting cycles.
+
+* Documentation
+
+  - Many updates to fix stale or missing documentation.
+
+  - Although our documentation was primarily meant to be formatted
+    with AsciiDoc7, formatting with AsciiDoc8 is supported better.
diff --git a/Documentation/RelNotes/1.5.2.3.txt b/Documentation/RelNotes/1.5.2.3.txt
new file mode 100644 (file)
index 0000000..addb229
--- /dev/null
@@ -0,0 +1,27 @@
+GIT v1.5.2.3 Release Notes
+==========================
+
+Fixes since v1.5.2.2
+--------------------
+
+ * Bugfixes
+
+   - Version 2 pack index format was introduced in version 1.5.2
+     to support pack files that has offset that cannot be
+     represented in 32-bit.  The runtime code to validate such
+     an index mishandled such an index for an empty pack.
+
+   - Commit walkers (most notably, fetch over http protocol)
+     tried to traverse commit objects contained in trees (aka
+     subproject); they shouldn't.
+
+   - A build option NO_R_TO_GCC_LINKER was not explained in Makefile
+     comment correctly.
+
+ * Documentation Fixes and Updates
+
+   - git-config --regexp was not documented properly.
+
+   - git-repack -a was not documented properly.
+
+   - git-remote -n was not documented properly.
diff --git a/Documentation/RelNotes/1.5.2.4.txt b/Documentation/RelNotes/1.5.2.4.txt
new file mode 100644 (file)
index 0000000..75cff47
--- /dev/null
@@ -0,0 +1,28 @@
+GIT v1.5.2.4 Release Notes
+==========================
+
+Fixes since v1.5.2.3
+--------------------
+
+ * Bugfixes
+
+   - "git-gui" bugfixes, including a handful fixes to run it
+     better on Cygwin/MSYS.
+
+   - "git checkout" failed to switch back and forth between
+     branches, one of which has "frotz -> xyzzy" symlink and
+     file "xyzzy/filfre", while the other one has a file
+     "frotz/filfre".
+
+   - "git prune" used to segfault upon seeing a commit that is
+     referred to by a tree object (aka "subproject").
+
+   - "git diff --name-status --no-index" mishandled an added file.
+
+   - "git apply --reverse --whitespace=warn" still complained
+     about whitespaces that a forward application would have
+     introduced.
+
+ * Documentation Fixes and Updates
+
+   - A handful documentation updates.
diff --git a/Documentation/RelNotes/1.5.2.5.txt b/Documentation/RelNotes/1.5.2.5.txt
new file mode 100644 (file)
index 0000000..e8281c7
--- /dev/null
@@ -0,0 +1,30 @@
+GIT v1.5.2.5 Release Notes
+==========================
+
+Fixes since v1.5.2.4
+--------------------
+
+ * Bugfixes
+
+   - "git add -u" had a serious data corruption problem in one
+     special case (when the changes to a subdirectory's files
+     consist only deletion of files).
+
+   - "git add -u <path>" did not work from a subdirectory.
+
+   - "git apply" left an empty directory after all its files are
+     renamed away.
+
+   - "git $anycmd foo/bar", when there is a file 'foo' in the
+     working tree, complained that "git $anycmd foo/bar --" form
+     should be used to disambiguate between revs and files,
+     which was completely bogus.
+
+   - "git checkout-index" and other commands that checks out
+     files to the work tree tried unlink(2) on directories,
+     which is a sane thing to do on sane systems, but not on
+     Solaris when you are root.
+
+ * Documentation Fixes and Updates
+
+   - A handful documentation fixes.
diff --git a/Documentation/RelNotes/1.5.2.txt b/Documentation/RelNotes/1.5.2.txt
new file mode 100644 (file)
index 0000000..e8328d0
--- /dev/null
@@ -0,0 +1,197 @@
+GIT v1.5.2 Release Notes
+========================
+
+Updates since v1.5.1
+--------------------
+
+* Plumbing level superproject support.
+
+  You can include a subdirectory that has an independent git
+  repository in your index and tree objects of your project
+  ("superproject").  This plumbing (i.e. "core") level
+  superproject support explicitly excludes recursive behaviour.
+
+  The "subproject" entries in the index and trees of a superproject
+  are incompatible with older versions of git.  Experimenting with
+  the plumbing level support is encouraged, but be warned that
+  unless everybody in your project updates to this release or
+  later, using this feature would make your project
+  inaccessible by people with older versions of git.
+
+* Plumbing level gitattributes support.
+
+  The gitattributes mechanism allows you to add 'attributes' to
+  paths in your project, and affect the way certain git
+  operations work.  Currently you can influence if a path is
+  considered a binary or text (the former would be treated by
+  'git diff' not to produce textual output; the latter can go
+  through the line endings conversion process in repositories
+  with core.autocrlf set), expand and unexpand '$Id$' keyword
+  with blob object name, specify a custom 3-way merge driver,
+  and specify a custom diff driver.  You can also apply
+  arbitrary filter to contents on check-in/check-out codepath
+  but this feature is an extremely sharp-edged razor and needs
+  to be handled with caution (do not use it unless you
+  understand the earlier mailing list discussion on keyword
+  expansion).  These conversions apply when checking files in
+  or out, and exporting via git-archive.
+
+* The packfile format now optionally supports 64-bit index.
+
+  This release supports the "version 2" format of the .idx
+  file.  This is automatically enabled when a huge packfile
+  needs more than 32-bit to express offsets of objects in the
+  pack.
+
+* Comes with an updated git-gui 0.7.1
+
+* Updated gitweb:
+
+  - can show combined diff for merges;
+  - uses font size of user's preference, not hardcoded in pixels;
+  - can now 'grep';
+
+* New commands and options.
+
+  - "git bisect start" can optionally take a single bad commit and
+    zero or more good commits on the command line.
+
+  - "git shortlog" can optionally be told to wrap its output.
+
+  - "subtree" merge strategy allows another project to be merged in as
+    your subdirectory.
+
+  - "git format-patch" learned a new --subject-prefix=<string>
+    option, to override the built-in "[PATCH]".
+
+  - "git add -u" is a quick way to do the first stage of "git
+    commit -a" (i.e. update the index to match the working
+    tree); it obviously does not make a commit.
+
+  - "git clean" honors a new configuration, "clean.requireforce".  When
+    set to true, this makes "git clean" a no-op, preventing you
+    from losing files by typing "git clean" when you meant to
+    say "make clean".  You can still say "git clean -f" to
+    override this.
+
+  - "git log" family of commands learned --date={local,relative,default}
+    option.  --date=relative is synonym to the --relative-date.
+    --date=local gives the timestamp in local timezone.
+
+* Updated behavior of existing commands.
+
+  - When $GIT_COMMITTER_EMAIL or $GIT_AUTHOR_EMAIL is not set
+    but $EMAIL is set, the latter is used as a substitute.
+
+  - "git diff --stat" shows size of preimage and postimage blobs
+    for binary contents.  Earlier it only said "Bin".
+
+  - "git lost-found" shows stuff that are unreachable except
+    from reflogs.
+
+  - "git checkout branch^0" now detaches HEAD at the tip commit
+    on the named branch, instead of just switching to the
+    branch (use "git checkout branch" to switch to the branch,
+    as before).
+
+  - "git bisect next" can be used after giving only a bad commit
+    without giving a good one (this starts bisection half-way to
+    the root commit).  We used to refuse to operate without a
+    good and a bad commit.
+
+  - "git push", when pushing into more than one repository, does
+    not stop at the first error.
+
+  - "git archive" does not insist you to give --format parameter
+    anymore; it defaults to "tar".
+
+  - "git cvsserver" can use backends other than sqlite.
+
+  - "gitview" (in contrib/ section) learned to better support
+    "git-annotate".
+
+  - "git diff $commit1:$path2 $commit2:$path2" can now report
+    mode changes between the two blobs.
+
+  - Local "git fetch" from a repository whose object store is
+    one of the alternates (e.g. fetching from the origin in a
+    repository created with "git clone -l -s") avoids
+    downloading objects unnecessarily.
+
+  - "git blame" uses .mailmap to canonicalize the author name
+    just like "git shortlog" does.
+
+  - "git pack-objects" pays attention to pack.depth
+    configuration variable.
+
+  - "git cherry-pick" and "git revert" does not use .msg file in
+    the working tree to prepare commit message; instead it uses
+    $GIT_DIR/MERGE_MSG as other commands do.
+
+* Builds
+
+  - git-p4import has never been installed; now there is an
+    installation option to do so.
+
+  - gitk and git-gui can be configured out.
+
+  - Generated documentation pages automatically get version
+    information from GIT_VERSION.
+
+  - Parallel build with "make -j" descending into subdirectory
+    was fixed.
+
+* Performance Tweaks
+
+  - Optimized "git-rev-list --bisect" (hence "git-bisect").
+
+  - Optimized "git-add $path" in a large directory, most of
+    whose contents are ignored.
+
+  - Optimized "git-diff-tree" for reduced memory footprint.
+
+  - The recursive merge strategy updated a worktree file that
+    was changed identically in two branches, when one of them
+    renamed it.  We do not do that when there is no rename, so
+    match that behaviour.  This avoids excessive rebuilds.
+
+  - The default pack depth has been increased to 50, as the
+    recent addition of delta_base_cache makes deeper delta chains
+    much less expensive to access.  Depending on the project, it was
+    reported that this reduces the resulting pack file by 10%
+    or so.
+
+
+Fixes since v1.5.1
+------------------
+
+All of the fixes in v1.5.1 maintenance series are included in
+this release, unless otherwise noted.
+
+* Bugfixes
+
+  - Switching branches with "git checkout" refused to work when
+    a path changes from a file to a directory between the
+    current branch and the new branch, in order not to lose
+    possible local changes in the directory that is being turned
+    into a file with the switch.  We now allow such a branch
+    switch after making sure that there is no locally modified
+    file nor un-ignored file in the directory.  This has not
+    been backported to 1.5.1.x series, as it is rather an
+    intrusive change.
+
+  - Merging branches that have a file in one and a directory in
+    another at the same path used to get quite confused.  We
+    handle such a case a bit more carefully, even though that is
+    still left as a conflict for the user to sort out.  This
+    will not be backported to 1.5.1.x series, as it is rather an
+    intrusive change.
+
+  - git-fetch had trouble with a remote with insanely large number
+    of refs.
+
+  - "git clean -d -X" now does not remove non-excluded directories.
+
+  - rebasing (without -m) a series that changes a symlink to a directory
+    in the middle of a path confused git-apply greatly and refused to
+    operate.
diff --git a/Documentation/RelNotes/1.5.3.1.txt b/Documentation/RelNotes/1.5.3.1.txt
new file mode 100644 (file)
index 0000000..7ff546c
--- /dev/null
@@ -0,0 +1,10 @@
+GIT v1.5.3.1 Release Notes
+==========================
+
+Fixes since v1.5.3
+------------------
+
+This is solely to fix the generated RPM's dependencies.  We used
+to have git-p4 package but we do not anymore.  As suggested on
+the mailing list, this release makes git-core "Obsolete" git-p4,
+so that yum update would not complain.
diff --git a/Documentation/RelNotes/1.5.3.2.txt b/Documentation/RelNotes/1.5.3.2.txt
new file mode 100644 (file)
index 0000000..4bbde3c
--- /dev/null
@@ -0,0 +1,58 @@
+GIT v1.5.3.2 Release Notes
+==========================
+
+Fixes since v1.5.3.1
+--------------------
+
+ * git-push sent thin packs by default, which was not good for
+   the public distribution server (no point in saving transfer
+   while pushing; no point in making the resulting pack less
+   optimum).
+
+ * git-svn sometimes terminated with "Malformed network data" when
+   talking over svn:// protocol.
+
+ * git-send-email re-issued the same message-id about 10% of the
+   time if you fired off 30 messages within a single second.
+
+ * git-stash was not terminating the log message of commits it
+   internally creates with LF.
+
+ * git-apply failed to check the size of the patch hunk when its
+   beginning part matched the remainder of the preimage exactly,
+   even though the preimage recorded in the hunk was much larger
+   (therefore the patch should not have applied), leading to a
+   segfault.
+
+ * "git rm foo && git commit foo" complained that 'foo' needs to
+   be added first, instead of committing the removal, which was a
+   nonsense.
+
+ * git grep -c said "/dev/null: 0".
+
+ * git-add -u failed to recognize a blob whose type changed
+   between the index and the work tree.
+
+ * The limit to rename detection has been tightened a lot to
+   reduce performance problems with a huge change.
+
+ * cvsimport and svnimport barfed when the input tried to move
+   a tag.
+
+ * "git apply -pN" did not chop the right number of directories.
+
+ * "git svnimport" did not like SVN tags with funny characters in them.
+
+ * git-gui 0.8.3, with assorted fixes, including:
+
+   - font-chooser on X11 was unusable with large number of fonts;
+   - a diff that contained a deleted symlink made it barf;
+   - an untracked symbolic link to a directory made it fart;
+   - a file with % in its name made it vomit;
+
+
+Documentation updates
+---------------------
+
+User manual has been somewhat restructured.  I think the new
+organization is much easier to read.
diff --git a/Documentation/RelNotes/1.5.3.3.txt b/Documentation/RelNotes/1.5.3.3.txt
new file mode 100644 (file)
index 0000000..d213846
--- /dev/null
@@ -0,0 +1,31 @@
+GIT v1.5.3.3 Release Notes
+==========================
+
+Fixes since v1.5.3.2
+--------------------
+
+ * git-quiltimport did not like it when a patch described in the
+   series file does not exist.
+
+ * p4 importer missed executable bit in some cases.
+
+ * The default shell on some FreeBSD did not execute the
+   argument parsing code correctly and made git unusable.
+
+ * git-svn incorrectly spawned pager even when the user
+   explicitly asked not to.
+
+ * sample post-receive hook overquoted the envelope sender
+   value.
+
+ * git-am got confused when the patch contained a change that is
+   only about type and not contents.
+
+ * git-mergetool did not show our and their version of the
+   conflicted file when started from a subdirectory of the
+   project.
+
+ * git-mergetool did not pass correct options when invoking diff3.
+
+ * git-log sometimes invoked underlying "diff" machinery
+   unnecessarily.
diff --git a/Documentation/RelNotes/1.5.3.4.txt b/Documentation/RelNotes/1.5.3.4.txt
new file mode 100644 (file)
index 0000000..b04b3a4
--- /dev/null
@@ -0,0 +1,35 @@
+GIT v1.5.3.4 Release Notes
+==========================
+
+Fixes since v1.5.3.3
+--------------------
+
+ * Change to "git-ls-files" in v1.5.3.3 that was introduced to support
+   partial commit of removal better had a segfaulting bug, which was
+   diagnosed and fixed by Keith and Carl.
+
+ * Performance improvements for rename detection has been backported
+   from the 'master' branch.
+
+ * "git-for-each-ref --format='%(numparent)'" was not working
+   correctly at all, and --format='%(parent)' was not working for
+   merge commits.
+
+ * Sample "post-receive-hook" incorrectly sent out push
+   notification e-mails marked as "From: " the committer of the
+   commit that happened to be at the tip of the branch that was
+   pushed, not from the person who pushed.
+
+ * "git-remote" did not exit non-zero status upon error.
+
+ * "git-add -i" did not respond very well to EOF from tty nor
+   bogus input.
+
+ * "git-rebase -i" squash subcommand incorrectly made the
+   author of later commit the author of resulting commit,
+   instead of taking from the first one in the squashed series.
+
+ * "git-stash apply --index" was not documented.
+
+ * autoconfiguration learned that "ar" command is found as "gas" on
+   some systems.
diff --git a/Documentation/RelNotes/1.5.3.5.txt b/Documentation/RelNotes/1.5.3.5.txt
new file mode 100644 (file)
index 0000000..7ff1d5d
--- /dev/null
@@ -0,0 +1,94 @@
+GIT v1.5.3.5 Release Notes
+==========================
+
+Fixes since v1.5.3.4
+--------------------
+
+ * Comes with git-gui 0.8.4.
+
+ * "git-config" silently ignored options after --list; now it will
+   error out with a usage message.
+
+ * "git-config --file" failed if the argument used a relative path
+   as it changed directories before opening the file.
+
+ * "git-config --file" now displays a proper error message if it
+   cannot read the file specified on the command line.
+
+ * "git-config", "git-diff", "git-apply" failed if run from a
+   subdirectory with relative GIT_DIR and GIT_WORK_TREE set.
+
+ * "git-blame" crashed if run during a merge conflict.
+
+ * "git-add -i" did not handle single line hunks correctly.
+
+ * "git-rebase -i" and "git-stash apply" failed if external diff
+   drivers were used for one or more files in a commit.  They now
+   avoid calling the external diff drivers.
+
+ * "git-log --follow" did not work unless diff generation (e.g. -p)
+   was also requested.
+
+ * "git-log --follow -B" did not work at all.  Fixed.
+
+ * "git-log -M -B" did not correctly handle cases of very large files
+   being renamed and replaced by very small files in the same commit.
+
+ * "git-log" printed extra newlines between commits when a diff
+   was generated internally (e.g. -S or --follow) but not displayed.
+
+ * "git-push" error message is more helpful when pushing to a
+   repository with no matching refs and none specified.
+
+ * "git-push" now respects + (force push) on wildcard refspecs,
+   matching the behavior of git-fetch.
+
+ * "git-filter-branch" now updates the working directory when it
+   has finished filtering the current branch.
+
+ * "git-instaweb" no longer fails on Mac OS X.
+
+ * "git-cvsexportcommit" didn't always create new parent directories
+   before trying to create new child directories.  Fixed.
+
+ * "git-fetch" printed a scary (but bogus) error message while
+   fetching a tag that pointed to a tree or blob.  The error did
+   not impact correctness, only user perception.  The bogus error
+   is no longer printed.
+
+ * "git-ls-files --ignored" did not properly descend into non-ignored
+   directories that themselves contained ignored files if d_type
+   was not supported by the filesystem.  This bug impacted systems
+   such as AFS.  Fixed.
+
+ * Git segfaulted when reading an invalid .gitattributes file.  Fixed.
+
+ * post-receive-email example hook was fixed for non-fast-forward
+   updates.
+
+ * Documentation updates for supported (but previously undocumented)
+   options of "git-archive" and "git-reflog".
+
+ * "make clean" no longer deletes the configure script that ships
+   with the git tarball, making multiple architecture builds easier.
+
+ * "git-remote show origin" spewed a warning message from Perl
+   when no remote is defined for the current branch via
+   branch.<name>.remote configuration settings.
+
+ * Building with NO_PERL_MAKEMAKER excessively rebuilt contents
+   of perl/ subdirectory by rewriting perl.mak.
+
+ * http.sslVerify configuration settings were not used in scripted
+   Porcelains.
+
+ * "git-add" leaked a bit of memory while scanning for files to add.
+
+ * A few workarounds to squelch false warnings from recent gcc have
+   been added.
+
+ * "git-send-pack $remote frotz" segfaulted when there is nothing
+   named 'frotz' on the local end.
+
+ * "git-rebase --interactive" did not handle its "--strategy" option
+   properly.
diff --git a/Documentation/RelNotes/1.5.3.6.txt b/Documentation/RelNotes/1.5.3.6.txt
new file mode 100644 (file)
index 0000000..069a2b2
--- /dev/null
@@ -0,0 +1,48 @@
+GIT v1.5.3.6 Release Notes
+==========================
+
+Fixes since v1.5.3.5
+--------------------
+
+ * git-cvsexportcommit handles root commits better.
+
+ * git-svn dcommit used to clobber when sending a series of
+   patches.
+
+ * git-svn dcommit failed after attempting to rebase when
+   started with a dirty index; now it stops upfront.
+
+ * git-grep sometimes refused to work when your index was
+   unmerged.
+
+ * "git-grep -A1 -B2" acted as if it was told to run "git -A1 -B21".
+
+ * git-hash-object did not honor configuration variables, such as
+   core.compression.
+
+ * git-index-pack choked on a huge pack on 32-bit machines, even when
+   large file offsets are supported.
+
+ * atom feeds from git-web said "10" for the month of November.
+
+ * a memory leak in commit walker was plugged.
+
+ * When git-send-email inserted the original author's From:
+   address in body, it did not mark the message with
+   Content-type: as needed.
+
+ * git-revert and git-cherry-pick incorrectly refused to start
+   when the work tree was dirty.
+
+ * git-clean did not honor core.excludesfile configuration.
+
+ * git-add mishandled ".gitignore" files when applying them to
+   subdirectories.
+
+ * While importing a too branchy history, git-fastimport did not
+   honor delta depth limit properly.
+
+ * Support for zlib implementations that lack ZLIB_VERNUM and definition
+   of deflateBound() has been added.
+
+ * Quite a lot of documentation clarifications.
diff --git a/Documentation/RelNotes/1.5.3.7.txt b/Documentation/RelNotes/1.5.3.7.txt
new file mode 100644 (file)
index 0000000..2f69061
--- /dev/null
@@ -0,0 +1,45 @@
+GIT v1.5.3.7 Release Notes
+==========================
+
+Fixes since v1.5.3.6
+--------------------
+
+ * git-send-email added 8-bit contents to the payload without
+   marking it as 8-bit in a CTE header.
+
+ * "git-bundle create a.bndl HEAD" dereferenced the symref and
+   did not record the ref as 'HEAD'; this prevented a bundle
+   from being used as a normal source of git-clone.
+
+ * The code to reject nonsense command line of the form
+   "git-commit -a paths..." and "git-commit --interactive
+   paths..." were broken.
+
+ * Adding a signature that is not ASCII-only to an original
+   commit that is ASCII-only would make the result non-ASCII.
+   "git-format-patch -s" did not mark such a message correctly
+   with MIME encoding header.
+
+ * git-add sometimes did not mark the resulting index entry
+   stat-clean.  This affected only cases when adding the
+   contents with the same length as the previously staged
+   contents, and the previous staging made the index entry
+   "racily clean".
+
+ * git-commit did not honor GIT_INDEX_FILE the user had in the
+   environment.
+
+ * When checking out a revision, git-checkout did not report where the
+   updated HEAD is if you happened to have a file called HEAD in the
+   work tree.
+
+ * "git-rev-list --objects" mishandled a tree that points at a
+   submodule.
+
+ * "git cvsimport" was not ready for packed refs that "git gc" can
+   produce and gave incorrect results.
+
+ * Many scripted Porcelains were confused when you happened to have a
+   file called "HEAD" in your work tree.
+
+Also it contains updates to the user manual and documentation.
diff --git a/Documentation/RelNotes/1.5.3.8.txt b/Documentation/RelNotes/1.5.3.8.txt
new file mode 100644 (file)
index 0000000..0e3ff58
--- /dev/null
@@ -0,0 +1,25 @@
+GIT v1.5.3.8 Release Notes
+==========================
+
+Fixes since v1.5.3.7
+--------------------
+
+ * Some documentation used "email.com" as an example domain.
+
+ * git-svn fix to handle funky branch and project names going over
+   http/https correctly.
+
+ * git-svn fix to tone down a needlessly alarming warning message.
+
+ * git-clone did not correctly report errors while fetching over http.
+
+ * git-send-email added redundant Message-Id: header to the outgoing
+   e-mail when the patch text already had one.
+
+ * a read-beyond-end-of-buffer bug in configuration file updater was fixed.
+
+ * git-grep used to show the same hit repeatedly for unmerged paths.
+
+ * After amending the patch title in "git-am -i", the command did not
+   report the patch it applied with the updated title.
+
diff --git a/Documentation/RelNotes/1.5.3.txt b/Documentation/RelNotes/1.5.3.txt
new file mode 100644 (file)
index 0000000..0668d3c
--- /dev/null
@@ -0,0 +1,366 @@
+GIT v1.5.3 Release Notes
+========================
+
+Updates since v1.5.2
+--------------------
+
+* The commit walkers other than http are officially deprecated,
+  but still supported for now.
+
+* The submodule support has Porcelain layer.
+
+  Note that the current submodule support is minimal and this is
+  deliberately so.  A design decision we made is that operations
+  at the supermodule level do not recurse into submodules by
+  default.  The expectation is that later we would add a
+  mechanism to tell git which submodules the user is interested
+  in, and this information might be used to determine the
+  recursive behaviour of certain commands (e.g. "git checkout"
+  and "git diff"), but currently we haven't agreed on what that
+  mechanism should look like.  Therefore, if you use submodules,
+  you would probably need "git submodule update" on the
+  submodules you care about after running a "git checkout" at
+  the supermodule level.
+
+* There are a handful pack-objects changes to help you cope better
+  with repositories with pathologically large blobs in them.
+
+* For people who need to import from Perforce, a front-end for
+  fast-import is in contrib/fast-import/.
+
+* Comes with git-gui 0.8.2.
+
+* Comes with updated gitk.
+
+* New commands and options.
+
+  - "git log --date=<format>" can use more formats: iso8601, rfc2822.
+
+  - The hunk header output from "git diff" family can be customized
+    with the attributes mechanism.  See gitattributes(5) for details.
+
+  - "git stash" allows you to quickly save away your work in
+    progress and replay it later on an updated state.
+
+  - "git rebase" learned an "interactive" mode that let you
+    pick and reorder which commits to rebuild.
+
+  - "git fsck" can save its findings in $GIT_DIR/lost-found, without a
+    separate invocation of "git lost-found" command.  The blobs stored by
+    lost-found are stored in plain format to allow you to grep in them.
+
+  - $GIT_WORK_TREE environment variable can be used together with
+    $GIT_DIR to work in a subdirectory of a working tree that is
+    not located at "$GIT_DIR/..".
+
+  - Giving "--file=<file>" option to "git config" is the same as
+    running the command with GIT_CONFIG=<file> environment.
+
+  - "git log" learned a new option "--follow", to follow
+    renaming history of a single file.
+
+  - "git filter-branch" lets you rewrite the revision history of
+    specified branches. You can specify a number of filters to
+    modify the commits, files and trees.
+
+  - "git cvsserver" learned new options (--base-path, --export-all,
+    --strict-paths) inspired by "git daemon".
+
+  - "git daemon --base-path-relaxed" can help migrating a repository URL
+    that did not use to use --base-path to use --base-path.
+
+  - "git commit" can use "-t templatefile" option and commit.template
+    configuration variable to prime the commit message given to you in the
+    editor.
+
+  - "git submodule" command helps you manage the projects from
+    the superproject that contain them.
+
+  - In addition to core.compression configuration option,
+    core.loosecompression and pack.compression options can
+    independently tweak zlib compression levels used for loose
+    and packed objects.
+
+  - "git ls-tree -l" shows size of blobs pointed at by the
+    tree entries, similar to "/bin/ls -l".
+
+  - "git rev-list" learned --regexp-ignore-case and
+    --extended-regexp options to tweak its matching logic used
+    for --grep filtering.
+
+  - "git describe --contains" is a handier way to call more
+    obscure command "git name-rev --tags".
+
+  - "git gc --aggressive" tells the command to spend more cycles
+    to optimize the repository harder.
+
+  - "git repack" learned a "window-memory" limit which
+    dynamically reduces the window size to stay within the
+    specified memory usage.
+
+  - "git repack" can be told to split resulting packs to avoid
+    exceeding limit specified with "--max-pack-size".
+
+  - "git fsck" gained --verbose option.  This is really really
+    verbose but it might help you identify exact commit that is
+    corrupt in your repository.
+
+  - "git format-patch" learned --numbered-files option.  This
+    may be useful for MH users.
+
+  - "git format-patch" learned format.subjectprefix configuration
+    variable, which serves the same purpose as "--subject-prefix"
+    option.
+
+  - "git tag -n -l" shows tag annotations while listing tags.
+
+  - "git cvsimport" can optionally use the separate-remote layout.
+
+  - "git blame" can be told to see through commits that change
+    whitespaces and indentation levels with "-w" option.
+
+  - "git send-email" can be told not to thread the messages when
+    sending out more than one patches.
+
+  - "git send-email" can also be told how to find whom to cc the
+    message to for each message via --cc-cmd.
+
+  - "git config" learned NUL terminated output format via -z to
+    help scripts.
+
+  - "git add" learned "--refresh <paths>..." option to selectively refresh
+    the cached stat information.
+
+  - "git init -q" makes the command quieter.
+
+  - "git -p command" now has a cousin of opposite sex, "git --no-pager
+    command".
+
+* Updated behavior of existing commands.
+
+  - "gitweb" can offer multiple snapshot formats.
+
+    ***NOTE*** Unfortunately, this changes the format of the
+    $feature{snapshot}{default} entry in the per-site
+    configuration file 'gitweb_config.perl'.  It used to be a
+    three-element tuple that describe a single format; with the
+    new configuration item format, you only have to say the name
+    of the format ('tgz', 'tbz2' or 'zip').  Please update the
+    your configuration file accordingly.
+
+  - "git clone" uses -l (hardlink files under .git) by default when
+    cloning locally.
+
+  - URL used for "git clone" and friends can specify nonstandard SSH port
+    by using ssh://host:port/path/to/repo syntax.
+
+  - "git bundle create" can now create a bundle without negative refs,
+    i.e. "everything since the beginning up to certain points".
+
+  - "git diff" (but not the plumbing level "git diff-tree") now
+    recursively descends into trees by default.
+
+  - "git diff" does not show differences that come only from
+    stat-dirtiness in the form of "diff --git" header anymore.
+    It runs "update-index --refresh" silently as needed.
+
+  - "git tag -l" used to match tags by globbing its parameter as if it
+    has wildcard '*' on both ends, which made "git tag -l gui" to match
+    tag 'gitgui-0.7.0'; this was very annoying.  You now have to add
+    asterisk on the sides you want to wildcard yourself.
+
+  - The editor to use with many interactive commands can be
+    overridden with GIT_EDITOR environment variable, or if it
+    does not exist, with core.editor configuration variable.  As
+    before, if you have neither, environment variables VISUAL
+    and EDITOR are consulted in this order, and then finally we
+    fall back on "vi".
+
+  - "git rm --cached" does not complain when removing a newly
+    added file from the index anymore.
+
+  - Options to "git log" to affect how --grep/--author options look for
+    given strings now have shorter abbreviations.  -i is for ignore case,
+    and -E is for extended regexp.
+
+  - "git log" learned --log-size to show the number of bytes in
+    the log message part of the output to help qgit.
+
+  - "git log --name-status" does not require you to give "-r" anymore.
+    As a general rule, Porcelain commands should recurse when showing
+    diff.
+
+  - "git format-patch --root A" can be used to format everything
+    since the beginning up to A.  This was supported with
+    "git format-patch --root A A" for a long time, but was not
+    properly documented.
+
+  - "git svn dcommit" retains local merge information.
+
+  - "git svnimport" allows an empty string to be specified as the
+    trunk/ directory.  This is necessary to suck data from a SVN
+    repository that doe not have trunk/ branches/ and tags/ organization
+    at all.
+
+  - "git config" to set values also honors type flags like --bool
+    and --int.
+
+  - core.quotepath configuration can be used to make textual git
+    output to emit most of the characters in the path literally.
+
+  - "git mergetool" chooses its backend more wisely, taking
+    notice of its environment such as use of X, Gnome/KDE, etc.
+
+  - "gitweb" shows merge commits a lot nicer than before.  The
+    default view uses more compact --cc format, while the UI
+    allows to choose normal diff with any parent.
+
+  - snapshot files "gitweb" creates from a repository at
+    $path/$project/.git are more useful.  We use $project part
+    in the filename, which we used to discard.
+
+  - "git cvsimport" creates lightweight tags; there is no
+    interesting information we can record in an annotated tag,
+    and the handcrafted ones the old code created was not
+    properly formed anyway.
+
+  - "git push" pretends that you immediately fetched back from
+    the remote by updating corresponding remote tracking
+    branches if you have any.
+
+  - The diffstat given after a merge (or a pull) honors the
+    color.diff configuration.
+
+  - "git commit --amend" is now compatible with various message source
+    options such as -m/-C/-c/-F.
+
+  - "git apply --whitespace=strip" removes blank lines added at
+    the end of the file.
+
+  - "git fetch" over git native protocols with "-v" option shows
+    connection status, and the IP address of the other end, to
+    help diagnosing problems.
+
+  - We used to have core.legacyheaders configuration, when
+    set to false, allowed git to write loose objects in a format
+    that mimics the format used by objects stored in packs.  It
+    turns out that this was not so useful.  Although we will
+    continue to read objects written in that format, we do not
+    honor that configuration anymore and create loose objects in
+    the legacy/traditional format.
+
+  - "--find-copies-harder" option to diff family can now be
+    spelled as "-C -C" for brevity.
+
+  - "git mailsplit" (hence "git am") can read from Maildir
+    formatted mailboxes.
+
+  - "git cvsserver" does not barf upon seeing "cvs login"
+    request.
+
+  - "pack-objects" honors "delta" attribute set in
+    .gitattributes.  It does not attempt to deltify blobs that
+    come from paths with delta attribute set to false.
+
+  - "new-workdir" script (in contrib) can now be used with a
+    bare repository.
+
+  - "git mergetool" learned to use gvimdiff.
+
+  - "gitview" (in contrib) has a better blame interface.
+
+  - "git log" and friends did not handle a commit log message
+    that is larger than 16kB; they do now.
+
+  - "--pretty=oneline" output format for "git log" and friends
+    deals with "malformed" commit log messages that have more
+    than one lines in the first paragraph better.  We used to
+    show the first line, cutting the title at mid-sentence; we
+    concatenate them into a single line and treat the result as
+    "oneline".
+
+  - "git p4import" has been demoted to contrib status.  For
+    a superior option, checkout the "git p4" front end to
+    "git fast-import" (also in contrib).  The man page and p4
+    rpm have been removed as well.
+
+  - "git mailinfo" (hence "am") now tries to see if the message
+    is in utf-8 first, instead of assuming iso-8859-1, if
+    incoming e-mail does not say what encoding it is in.
+
+* Builds
+
+  - old-style function definitions (most notably, a function
+    without parameter defined with "func()", not "func(void)")
+    have been eradicated.
+
+  - "git tag" and "git verify-tag" have been rewritten in C.
+
+* Performance Tweaks
+
+  - "git pack-objects" avoids re-deltification cost by caching
+    small enough delta results it creates while looking for the
+    best delta candidates.
+
+  - "git pack-objects" learned a new heuristic to prefer delta
+    that is shallower in depth over the smallest delta
+    possible.  This improves both overall packfile access
+    performance and packfile density.
+
+  - diff-delta code that is used for packing has been improved
+    to work better on big files.
+
+  - when there are more than one pack files in the repository,
+    the runtime used to try finding an object always from the
+    newest packfile; it now tries the same packfile as we found
+    the object requested the last time, which exploits the
+    locality of references.
+
+  - verifying pack contents done by "git fsck --full" got boost
+    by carefully choosing the order to verify objects in them.
+
+  - "git read-tree -m" to read into an already populated index
+    has been optimized vastly.  The effect of this can be seen
+    when switching branches that have differences in only a
+    handful paths.
+
+  - "git add paths..." and "git commit paths..." has also been
+    heavily optimized.
+
+Fixes since v1.5.2
+------------------
+
+All of the fixes in v1.5.2 maintenance series are included in
+this release, unless otherwise noted.
+
+* Bugfixes
+
+  - "gitweb" had trouble handling non UTF-8 text with older
+    Encode.pm Perl module.
+
+  - "git svn" misparsed the data from the commits in the repository when
+    the user had "color.diff = true" in the configuration.  This has been
+    fixed.
+
+  - There was a case where "git svn dcommit" clobbered changes made on the
+    SVN side while committing multiple changes.
+
+  - "git-write-tree" had a bad interaction with racy-git avoidance and
+    gitattributes mechanisms.
+
+  - "git --bare command" overrode existing GIT_DIR setting and always
+    made it treat the current working directory as GIT_DIR.
+
+  - "git ls-files --error-unmatch" does not complain if you give the
+    same path pattern twice by mistake.
+
+  - "git init" autodetected core.filemode but not core.symlinks, which
+    made a new directory created automatically by "git clone" cumbersome
+    to use on filesystems that require these configurations to be set.
+
+  - "git log" family of commands behaved differently when run as "git
+    log" (no pathspec) and as "git log --" (again, no pathspec).  This
+    inconsistency was introduced somewhere in v1.3.0 series but now has
+    been corrected.
+
+  - "git rebase -m" incorrectly displayed commits that were skipped.
diff --git a/Documentation/RelNotes/1.5.4.1.txt b/Documentation/RelNotes/1.5.4.1.txt
new file mode 100644 (file)
index 0000000..d4e44b8
--- /dev/null
@@ -0,0 +1,17 @@
+GIT v1.5.4.1 Release Notes
+==========================
+
+Fixes since v1.5.4
+------------------
+
+ * "git-commit -C $tag" used to work but rewrite in C done in
+   1.5.4 broke it.
+
+ * An entry in the .gitattributes file that names a pattern in a
+   subdirectory of the directory it is in did not match
+   correctly (e.g. pattern "b/*.c" in "a/.gitattributes" should
+   match "a/b/foo.c" but it didn't).
+
+ * Customized color specification was parsed incorrectly when
+   numeric color values are used.  This was fixed in 1.5.4.1.
+
diff --git a/Documentation/RelNotes/1.5.4.2.txt b/Documentation/RelNotes/1.5.4.2.txt
new file mode 100644 (file)
index 0000000..21d0df5
--- /dev/null
@@ -0,0 +1,43 @@
+GIT v1.5.4.2 Release Notes
+==========================
+
+Fixes since v1.5.4
+------------------
+
+ * The configuration parser was not prepared to see string
+   valued variables misspelled as boolean and segfaulted.
+
+ * Temporary files left behind due to interrupted object
+   transfers were not cleaned up with "git prune".
+
+ * "git config --unset" was confused when the unset variables
+   were spelled with continuation lines in the config file.
+
+ * The merge message detection in "git cvsimport" did not catch
+   a message that began with "Merge...".
+
+ * "git status" suggests "git rm --cached" for unstaging the
+   earlier "git add" before the initial commit.
+
+ * "git status" output was incorrect during a partial commit.
+
+ * "git bisect" refused to start when the HEAD was detached.
+
+ * "git bisect" allowed a wildcard character in the commit
+   message expanded while writing its log file.
+
+ * Manual pages were not formatted correctly with docbook xsl
+   1.72; added a workaround.
+
+ * "git-commit -C $tag" used to work but rewrite in C done in
+   1.5.4 broke it.  This was fixed in 1.5.4.1.
+
+ * An entry in the .gitattributes file that names a pattern in a
+   subdirectory of the directory it is in did not match
+   correctly (e.g. pattern "b/*.c" in "a/.gitattributes" should
+   match "a/b/foo.c" but it didn't).  This was fixed in 1.5.4.1.
+
+ * Customized color specification was parsed incorrectly when
+   numeric color values are used.  This was fixed in 1.5.4.1.
+
+ * http transport misbehaved when linked with curl-gnutls.
diff --git a/Documentation/RelNotes/1.5.4.3.txt b/Documentation/RelNotes/1.5.4.3.txt
new file mode 100644 (file)
index 0000000..b0fc67f
--- /dev/null
@@ -0,0 +1,27 @@
+GIT v1.5.4.3 Release Notes
+==========================
+
+Fixes since v1.5.4.2
+--------------------
+
+ * RPM spec used to pull in everything with 'git'.  This has been
+   changed so that 'git' package contains just the core parts,
+   and we now supply 'git-all' metapackage to slurp in everything.
+   This should match end user's expectation better.
+
+ * When some refs failed to update, git-push reported "failure"
+   which was unclear if some other refs were updated or all of
+   them failed atomically (the answer is the former).  Reworded
+   the message to clarify this.
+
+ * "git clone" from a repository whose HEAD was misconfigured
+   did not set up the remote properly.  Now it tries to do
+   better.
+
+ * Updated git-push documentation to clarify what "matching"
+   means, in order to reduce user confusion.
+
+ * Updated git-add documentation to clarify "add -u" operates in
+   the current subdirectory you are in, just like other commands.
+
+ * git-gui updates to work on OSX and Windows better.
diff --git a/Documentation/RelNotes/1.5.4.4.txt b/Documentation/RelNotes/1.5.4.4.txt
new file mode 100644 (file)
index 0000000..323c1a8
--- /dev/null
@@ -0,0 +1,66 @@
+GIT v1.5.4.4 Release Notes
+==========================
+
+Fixes since v1.5.4.3
+--------------------
+
+ * Building and installing with an overtight umask such as 077 made
+   installed templates unreadable by others, while the rest of the install
+   are done in a way that is friendly to umask 022.
+
+ * "git cvsexportcommit -w $cvsdir" misbehaved when GIT_DIR is set to a
+   relative directory.
+
+ * "git http-push" had an invalid memory access that could lead it to
+   segfault.
+
+ * When "git rebase -i" gave control back to the user for a commit that is
+   marked to be edited, it just said "modify it with commit --amend",
+   without saying what to do to continue after modifying it.  Give an
+   explicit instruction to run "rebase --continue" to be more helpful.
+
+ * "git send-email" in 1.5.4.3 issued a bogus empty In-Reply-To: header.
+
+ * "git bisect" showed mysterious "won't bisect on seeked tree" error message.
+   This was leftover from Cogito days to prevent "bisect" starting from a
+   cg-seeked state.  We still keep the Cogito safety, but running "git bisect
+   start" when another bisect was in effect will clean up and start over.
+
+ * "git push" with an explicit PATH to receive-pack did not quite work if
+   receive-pack was not on usual PATH.  We earlier fixed the same issue
+   with "git fetch" and upload-pack, but somehow forgot to do so in the
+   other direction.
+
+ * git-gui's info dialog was not displayed correctly when the user tries
+   to commit nothing (i.e. without staging anything).
+
+ * "git revert" did not properly fail when attempting to run with a
+   dirty index.
+
+ * "git merge --no-commit --no-ff <other>" incorrectly made commits.
+
+ * "git merge --squash --no-ff <other>", which is a nonsense combination
+   of options, was not rejected.
+
+ * "git ls-remote" and "git remote show" against an empty repository
+   failed, instead of just giving an empty result (regression).
+
+ * "git fast-import" did not handle a renamed path whose name needs to be
+   quoted, due to a bug in unquote_c_style() function.
+
+ * "git cvsexportcommit" was confused when multiple files with the same
+   basename needed to be pushed out in the same commit.
+
+ * "git daemon" did not send early errors to syslog.
+
+ * "git log --merge" did not work well with --left-right option.
+
+ * "git svn" prompted for client cert password every time it accessed the
+   server.
+
+ * The reset command in "git fast-import" data stream was documented to
+   end with an optional LF, but it actually required one.
+
+ * "git svn dcommit/rebase" did not honor --rewrite-root option.
+
+Also included are a handful documentation updates.
diff --git a/Documentation/RelNotes/1.5.4.5.txt b/Documentation/RelNotes/1.5.4.5.txt
new file mode 100644 (file)
index 0000000..bbd130e
--- /dev/null
@@ -0,0 +1,56 @@
+GIT v1.5.4.5 Release Notes
+==========================
+
+Fixes since v1.5.4.4
+--------------------
+
+ * "git fetch there" when the URL information came from the Cogito style
+   branches/there file did not update refs/heads/there (regression in
+   1.5.4).
+
+ * Bogus refspec configuration such as "remote.there.fetch = =" were not
+   detected as errors (regression in 1.5.4).
+
+ * You couldn't specify a custom editor whose path contains a whitespace
+   via GIT_EDITOR (and core.editor).
+
+ * The subdirectory filter to "git filter-branch" mishandled a history
+   where the subdirectory becomes empty and then later becomes non-empty.
+
+ * "git shortlog" gave an empty line if the original commit message was
+   malformed (e.g. a botched import from foreign SCM).  Now it finds the
+   first non-empty line and uses it for better information.
+
+ * When the user fails to give a revision parameter to "git svn", an error
+   from the Perl interpreter was issued because the script lacked proper
+   error checking.
+
+ * After "git rebase" stopped due to conflicts, if the user played with
+   "git reset" and friends, "git rebase --abort" failed to go back to the
+   correct commit.
+
+ * Additional work trees prepared with git-new-workdir (in contrib/) did
+   not share git-svn metadata directory .git/svn with the original.
+
+ * "git-merge-recursive" did not mark addition of the same path with
+   different filemodes correctly as a conflict.
+
+ * "gitweb" gave malformed URL when pathinfo stype paths are in use.
+
+ * "-n" stands for "--no-tags" again for "git fetch".
+
+ * "git format-patch" did not detect the need to add 8-bit MIME header
+   when the user used format.header configuration.
+
+ * "rev~" revision specifier used to mean "rev", which was inconsistent
+   with how "rev^" worked.  Now "rev~" is the same as "rev~1" (hence it
+   also is the same as "rev^1"), and "rev~0" is the same as "rev^0"
+   (i.e. it has to be a commit).
+
+ * "git quiltimport" did not grok empty lines, lines in "file -pNNN"
+   format to specify the prefix levels and lines with trailing comments.
+
+ * "git rebase -m" triggered pre-commit verification, which made
+   "rebase --continue" impossible.
+
+As usual, it also comes with many documentation fixes and clarifications.
diff --git a/Documentation/RelNotes/1.5.4.6.txt b/Documentation/RelNotes/1.5.4.6.txt
new file mode 100644 (file)
index 0000000..3e3c3e5
--- /dev/null
@@ -0,0 +1,43 @@
+GIT v1.5.4.6 Release Notes
+==========================
+
+I personally do not think there is any reason anybody should want to
+run v1.5.4.X series these days, because 'master' version is always
+more stable than any tagged released version of git.
+
+This is primarily to futureproof "git-shell" to accept requests
+without a dash between "git" and subcommand name (e.g. "git
+upload-pack") which the newer client will start to make sometime in
+the future.
+
+Fixes since v1.5.4.5
+--------------------
+
+ * Command line option "-n" to "git-repack" was not correctly parsed.
+
+ * Error messages from "git-apply" when the patchfile cannot be opened
+   have been improved.
+
+ * Error messages from "git-bisect" when given nonsense revisions have
+   been improved.
+
+ * reflog syntax that uses time e.g. "HEAD@{10 seconds ago}:path" did not
+   stop parsing at the closing "}".
+
+ * "git rev-parse --symbolic-full-name ^master^2" printed solitary "^",
+   but it should print nothing.
+
+ * "git apply" did not enforce "match at the beginning" correctly.
+
+ * a path specification "a/b" in .gitattributes file should not match
+   "sub/a/b", but it did.
+
+ * "git log --date-order --topo-order" did not override the earlier
+   date-order with topo-order as expected.
+
+ * "git fast-export" did not export octopus merges correctly.
+
+ * "git archive --prefix=$path/" mishandled gitattributes.
+
+As usual, it also comes with many documentation fixes and clarifications.
+
diff --git a/Documentation/RelNotes/1.5.4.7.txt b/Documentation/RelNotes/1.5.4.7.txt
new file mode 100644 (file)
index 0000000..9065a0e
--- /dev/null
@@ -0,0 +1,10 @@
+GIT v1.5.4.7 Release Notes
+==========================
+
+Fixes since 1.5.4.7
+-------------------
+
+ * Removed support for an obsolete gitweb request URI, whose
+   implementation ran "git diff" Porcelain, instead of using plumbing,
+   which would have run an external diff command specified in the
+   repository configuration as the gitweb user.
diff --git a/Documentation/RelNotes/1.5.4.txt b/Documentation/RelNotes/1.5.4.txt
new file mode 100644 (file)
index 0000000..f1323b6
--- /dev/null
@@ -0,0 +1,377 @@
+GIT v1.5.4 Release Notes
+========================
+
+Removal
+-------
+
+ * "git svnimport" was removed in favor of "git svn".  It is still there
+   in the source tree (contrib/examples) but unsupported.
+
+ * As git-commit and git-status have been rewritten, "git runstatus"
+   helper script lost all its users and has been removed.
+
+
+Temporarily disabled
+--------------------
+
+ * "git http-push" is known not to work well with cURL library older
+   than 7.16, and we had reports of repository corruption.  It is
+   disabled on such platforms for now.  Unfortunately, 1.5.3.8 shares
+   the same issue.  In other words, this does not mean you will be
+   fine if you stick to an older git release.  For now, please do not
+   use http-push from older git with cURL older than 7.16 if you
+   value your data. A proper fix will hopefully materialize in
+   later versions.
+
+
+Deprecation notices
+-------------------
+
+ * From v1.6.0, git will by default install dashed form of commands
+   (e.g. "git-commit") outside of users' normal $PATH, and will install
+   only selected commands ("git" itself, and "gitk") in $PATH.  This
+   implies:
+
+   - Using dashed forms of git commands (e.g. "git-commit") from the
+     command line has been informally deprecated since early 2006, but
+     now it officially is, and will be removed in the future.  Use
+     dash-less forms (e.g. "git commit") instead.
+
+   - Using dashed forms from your scripts, without first prepending the
+     return value from "git --exec-path" to the scripts' PATH, has been
+     informally deprecated since early 2006, but now it officially is.
+
+   - Use of dashed forms with "PATH=$(git --exec-path):$PATH; export
+     PATH" early in your script is not deprecated with this change.
+
+   Users are strongly encouraged to adjust their habits and scripts now
+   to prepare for this change.
+
+ * The post-receive hook was introduced in March 2007 to supersede
+   the post-update hook, primarily to overcome the command line length
+   limitation of the latter.  Use of post-update hook will be deprecated
+   in future versions of git, starting from v1.6.0.
+
+ * "git lost-found" was deprecated in favor of "git fsck"'s --lost-found
+   option, and will be removed in the future.
+
+ * "git peek-remote" is deprecated, as "git ls-remote" was written in C
+   and works for all transports; "git peek-remote" will be removed in
+   the future.
+
+ * "git repo-config" which was an old name for "git config" command
+   has been supported without being advertised for a long time.  The
+   next feature release will remove it.
+
+ * From v1.6.0, the repack.usedeltabaseoffset config option will default
+   to true, which will give denser packfiles (i.e. more efficient storage).
+   The downside is that git older than version 1.4.4 will not be able
+   to directly use a repository packed using this setting.
+
+ * From v1.6.0, the pack.indexversion config option will default to 2,
+   which is slightly more efficient, and makes repacking more immune to
+   data corruptions.  Git older than version 1.5.2 may revert to version 1
+   of the pack index with a manual "git index-pack" to be able to directly
+   access corresponding pack files.
+
+
+Updates since v1.5.3
+--------------------
+
+ * Comes with much improved gitk, with i18n.
+
+ * Comes with git-gui 0.9.2 with i18n.
+
+ * gitk is now merged as a subdirectory of git.git project, in
+   preparation for its i18n.
+
+ * progress displays from many commands are a lot nicer to the eye.
+   Transfer commands show throughput data.
+
+ * many commands that pay attention to per-directory .gitignore now do
+   so lazily, which makes the usual case go much faster.
+
+ * Output processing for '--pretty=format:<user format>' has been
+   optimized.
+
+ * Rename detection of diff family while detecting exact matches has
+   been greatly optimized.
+
+ * Rename detection of diff family tries to make more natural looking
+   pairing.  Earlier, if multiple identical rename sources were
+   found in the preimage, the source used was picked pretty much at random.
+
+ * Value "true" for color.diff and color.status configuration used to
+   mean "always" (even when the output is not going to a terminal).
+   This has been corrected to mean the same thing as "auto".
+
+ * "git diff" Porcelain now respects diff.external configuration, which
+   is another way to specify GIT_EXTERNAL_DIFF.
+
+ * "git diff" can be told to use different prefixes other than
+   "a/" and "b/" e.g. "git diff --src-prefix=l/ --dst-prefix=k/".
+
+ * "git diff" sometimes did not quote paths with funny
+   characters properly.
+
+ * "git log" (and any revision traversal commands) misbehaved
+   when --diff-filter is given but was not asked to actually
+   produce diff.
+
+ * HTTP proxy can be specified per remote repository using
+   remote.*.httpproxy configuration, or global http.proxy configuration
+   variable.
+
+ * Various Perforce importer updates.
+
+ * Example update and post-receive hooks have been improved.
+
+ * Any command that wants to take a commit object name can now use
+   ":/string" syntax to name a commit.
+
+ * "git reset" is now built-in and its output can be squelched with -q.
+
+ * "git reset --hard" does not make any sense in a bare
+   repository, but did not error out; fixed.
+
+ * "git send-email" can optionally talk over ssmtp and use SMTP-AUTH.
+
+ * "git rebase" learned --whitespace option.
+
+ * In "git rebase", when you decide not to replay a particular change
+   after the command stopped with a conflict, you can say "git rebase
+   --skip" without first running "git reset --hard", as the command now
+   runs it for you.
+
+ * "git rebase --interactive" mode can now work on detached HEAD.
+
+ * Other minor to serious bugs in "git rebase -i" have been fixed.
+
+ * "git rebase" now detaches head during its operation, so after a
+   successful "git rebase" operation, the reflog entry branch@{1} for
+   the current branch points at the commit before the rebase was
+   started.
+
+ * "git rebase -i" also triggers rerere to help your repeated merges.
+
+ * "git merge" can call the "post-merge" hook.
+
+ * "git pack-objects" can optionally run deltification with multiple
+   threads.
+
+ * "git archive" can optionally substitute keywords in files marked with
+   export-subst attribute.
+
+ * "git cherry-pick" made a misguided attempt to repeat the original
+   command line in the generated log message, when told to cherry-pick a
+   commit by naming a tag that points at it.  It does not anymore.
+
+ * "git for-each-ref" learned %(xxxdate:<date-format>) syntax to show the
+   various date fields in different formats.
+
+ * "git gc --auto" is a low-impact way to automatically run a variant of
+   "git repack" that does not lose unreferenced objects (read: safer
+   than the usual one) after the user accumulates too many loose
+   objects.
+
+ * "git clean" has been rewritten in C.
+
+ * You need to explicitly set clean.requireForce to "false" to allow
+   "git clean" without -f to do any damage (lack of the configuration
+   variable used to mean "do not require -f option to lose untracked
+   files", but we now use the safer default).
+
+ * The kinds of whitespace errors "git diff" and "git apply" notice (and
+   fix) can be controlled via 'core.whitespace' configuration variable
+   and 'whitespace' attribute in .gitattributes file.
+
+ * "git push" learned --dry-run option to show what would happen if a
+   push is run.
+
+ * "git push" does not update a tracking ref on the local side when the
+   remote refused to update the corresponding ref.
+
+ * "git push" learned --mirror option.  This is to push the local refs
+   one-to-one to the remote, and deletes refs from the remote that do
+   not exist anymore in the repository on the pushing side.
+
+ * "git push" can remove a corrupt ref at the remote site with the usual
+   ":ref" refspec.
+
+ * "git remote" knows --mirror mode.  This is to set up configuration to
+   push into a remote repository to store local branch heads to the same
+   branch on the remote side, and remove branch heads locally removed
+   from local repository at the same time.  Suitable for pushing into a
+   back-up repository.
+
+ * "git remote" learned "rm" subcommand.
+
+ * "git cvsserver" can be run via "git shell".  Also, "cvs" is
+   recognized as a synonym for "git cvsserver", so that CVS users
+   can be switched to git just by changing their login shell.
+
+ * "git cvsserver" acts more like receive-pack by running post-receive
+   and post-update hooks.
+
+ * "git am" and "git rebase" are far less verbose.
+
+ * "git pull" learned to pass --[no-]ff option to underlying "git
+   merge".
+
+ * "git pull --rebase" is a different way to integrate what you fetched
+   into your current branch.
+
+ * "git fast-export" produces data-stream that can be fed to fast-import
+   to reproduce the history recorded in a git repository.
+
+ * "git add -i" takes pathspecs to limit the set of files to work on.
+
+ * "git add -p" is a short-hand to go directly to the selective patch
+   subcommand in the interactive command loop and to exit when done.
+
+ * "git add -i" UI has been colorized.  The interactive prompt
+   and menu can be colored by setting color.interactive
+   configuration.  The diff output (including the hunk picker)
+   are colored with color.diff configuration.
+
+ * "git commit --allow-empty" allows you to create a single-parent
+   commit that records the same tree as its parent, overriding the usual
+   safety valve.
+
+ * "git commit --amend" can amend a merge that does not change the tree
+   from its first parent.
+
+ * "git commit" used to unconditionally strip comment lines that
+   began with '#' and removed excess blank lines.  This behavior has
+   been made configurable.
+
+ * "git commit" has been rewritten in C.
+
+ * "git stash random-text" does not create a new stash anymore.  It was
+   a UI mistake.  Use "git stash save random-text", or "git stash"
+   (without extra args) for that.
+
+ * "git stash clear extra-text" does not clear the whole stash
+   anymore.  It is tempting to expect "git stash clear stash@{2}"
+   to drop only a single named stash entry, and it is rude to
+   discard everything when that is asked (but not provided).
+
+ * "git prune --expire <time>" can exempt young loose objects from
+   getting pruned.
+
+ * "git branch --contains <commit>" can list branches that are
+   descendants of a given commit.
+
+ * "git log" learned --early-output option to help interactive GUI
+   implementations.
+
+ * "git bisect" learned "skip" action to mark untestable commits.
+
+ * "git bisect visualize" learned a shorter synonym "git bisect view".
+
+ * "git bisect visualize" runs "git log" in a non-windowed
+   environments.  It also can be told what command to run (e.g. "git
+   bisect visualize tig").
+
+ * "git format-patch" learned "format.numbered" configuration variable
+   to automatically turn --numbered option on when more than one commits
+   are formatted.
+
+ * "git ls-files" learned "--exclude-standard" to use the canned set of
+   exclude files.
+
+ * "git tag -a -f existing" begins the editor session using the existing
+   annotation message.
+
+ * "git tag -m one -m bar" (multiple -m options) behaves similarly to
+   "git commit"; the parameters to -m options are formatted as separate
+   paragraphs.
+
+ * The format "git show" outputs an annotated tag has been updated to
+   include "Tagger: " and "Date: " lines from the tag itself.  Strictly
+   speaking this is a backward incompatible change, but this is a
+   reasonable usability fix and people's scripts shouldn't have been
+   relying on the exact output from "git show" Porcelain anyway.
+
+ * "git cvsimport" did not notice errors from underlying "cvsps"
+   and produced a corrupt import silently.
+
+ * "git cvsexportcommit" learned -w option to specify and switch to the
+   CVS working directory.
+
+ * "git checkout" from a subdirectory learned to use "../path" to allow
+   checking out a path outside the current directory without cd'ing up.
+
+ * "git checkout" from and to detached HEAD leaves a bit more
+   information in the reflog.
+
+ * "git send-email --dry-run" shows full headers for easier diagnosis.
+
+ * "git merge-ours" is now built-in.
+
+ * "git svn" learned "info" and "show-externals" subcommands.
+
+ * "git svn" run from a subdirectory failed to read settings from the
+   .git/config.
+
+ * "git svn" learned --use-log-author option, which picks up more
+   descriptive name from From: and Signed-off-by: lines in the commit
+   message.
+
+ * "git svn" wasted way too much disk to record revision mappings
+   between svn and git; a new representation that is much more compact
+   for this information has been introduced to correct this.
+
+ * "git svn" left temporary index files it used without cleaning them
+   up; this was corrected.
+
+ * "git status" from a subdirectory now shows relative paths, which
+   makes copy-and-pasting for git-checkout/git-add/git-rm easier.  The
+   traditional behavior to show the full path relative to the top of
+   the work tree can be had by setting status.relativepaths
+   configuration variable to false.
+
+ * "git blame" kept text for each annotated revision in core needlessly;
+   this has been corrected.
+
+ * "git shortlog" learned to default to HEAD when the standard input is
+   a terminal and the user did not give any revision parameter.
+
+ * "git shortlog" learned "-e" option to show e-mail addresses as well as
+   authors' names.
+
+ * "git help" learned "-w" option to show documentation in browsers.
+
+ * In addition there are quite a few internal clean-ups. Notably:
+
+   - many fork/exec have been replaced with run-command API,
+     brought from the msysgit effort.
+
+   - introduction and more use of the option parser API.
+
+   - enhancement and more use of the strbuf API.
+
+ * Makefile tweaks to support HP-UX is in.
+
+Fixes since v1.5.3
+------------------
+
+All of the fixes in v1.5.3 maintenance series are included in
+this release, unless otherwise noted.
+
+These fixes are only in v1.5.4 and not backported to v1.5.3 maintenance
+series.
+
+ * The way "git diff --check" behaves is much more consistent with the way
+   "git apply --whitespace=warn" works.
+
+ * "git svn" talking with the SVN over HTTP will correctly quote branch
+   and project names.
+
+ * "git config" did not work correctly on platforms that define
+   REG_NOMATCH to an even number.
+
+ * Recent versions of AsciiDoc 8 has a change to break our
+   documentation; a workaround has been implemented.
+
+ * "git diff --color-words" colored context lines in a wrong color.
diff --git a/Documentation/RelNotes/1.5.5.1.txt b/Documentation/RelNotes/1.5.5.1.txt
new file mode 100644 (file)
index 0000000..7de4197
--- /dev/null
@@ -0,0 +1,44 @@
+GIT v1.5.5.1 Release Notes
+==========================
+
+Fixes since v1.5.5
+------------------
+
+ * "git archive --prefix=$path/" mishandled gitattributes.
+
+ * "git fetch -v" that fetches into FETCH_HEAD did not report the summary
+   the same way as done for updating the tracking refs.
+
+ * "git svn" misbehaved when the configuration file customized the "git
+   log" output format using format.pretty.
+
+ * "git submodule status" leaked an unnecessary error message.
+
+ * "git log --date-order --topo-order" did not override the earlier
+   date-order with topo-order as expected.
+
+ * "git bisect good $this" did not check the validity of the revision
+   given properly.
+
+ * "url.<there>.insteadOf" did not work correctly.
+
+ * "git clean" ran inside subdirectory behaved as if the directory was
+   explicitly specified for removal by the end user from the top level.
+
+ * "git bisect" from a detached head leaked an unnecessary error message.
+
+ * "git bisect good $a $b" when $a is Ok but $b is bogus should have
+   atomically failed before marking $a as good.
+
+ * "git fmt-merge-msg" did not clean up leading empty lines from commit
+   log messages like "git log" family does.
+
+ * "git am" recorded a commit with empty Subject: line without
+   complaining.
+
+ * when given a commit log message whose first paragraph consists of
+   multiple lines, "git rebase" squashed it into a single line.
+
+ * "git remote add $bogus_name $url" did not complain properly.
+
+Also comes with various documentation updates.
diff --git a/Documentation/RelNotes/1.5.5.2.txt b/Documentation/RelNotes/1.5.5.2.txt
new file mode 100644 (file)
index 0000000..391a7b0
--- /dev/null
@@ -0,0 +1,27 @@
+GIT v1.5.5.2 Release Notes
+==========================
+
+Fixes since v1.5.5.1
+--------------------
+
+ * "git repack -n" was mistakenly made no-op earlier.
+
+ * "git imap-send" wanted to always have imap.host even when use of
+   imap.tunnel made it unnecessary.
+
+ * reflog syntax that uses time e.g. "HEAD@{10 seconds ago}:path" did not
+   stop parsing at the closing "}".
+
+ * "git rev-parse --symbolic-full-name ^master^2" printed solitary "^",
+   but it should print nothing.
+
+ * "git commit" did not detect when it failed to write tree objects.
+
+ * "git fetch" sometimes transferred too many objects unnecessarily.
+
+ * a path specification "a/b" in .gitattributes file should not match
+   "sub/a/b".
+
+ * various gitweb fixes.
+
+Also comes with various documentation updates.
diff --git a/Documentation/RelNotes/1.5.5.3.txt b/Documentation/RelNotes/1.5.5.3.txt
new file mode 100644 (file)
index 0000000..f22f98b
--- /dev/null
@@ -0,0 +1,12 @@
+GIT v1.5.5.3 Release Notes
+==========================
+
+Fixes since v1.5.5.2
+--------------------
+
+ * "git send-email --compose" did not notice that non-ascii contents
+   needed some MIME magic.
+
+ * "git fast-export" did not export octopus merges correctly.
+
+Also comes with various documentation updates.
diff --git a/Documentation/RelNotes/1.5.5.4.txt b/Documentation/RelNotes/1.5.5.4.txt
new file mode 100644 (file)
index 0000000..2d0279e
--- /dev/null
@@ -0,0 +1,7 @@
+GIT v1.5.5.4 Release Notes
+==========================
+
+Fixes since v1.5.5.4
+--------------------
+
+ * "git name-rev --all" used to segfault.
diff --git a/Documentation/RelNotes/1.5.5.5.txt b/Documentation/RelNotes/1.5.5.5.txt
new file mode 100644 (file)
index 0000000..30fa361
--- /dev/null
@@ -0,0 +1,11 @@
+GIT v1.5.5.5 Release Notes
+==========================
+
+I personally do not think there is any reason anybody should want to
+run v1.5.5.X series these days, because 'master' version is always
+more stable than any tagged released version of git.
+
+This is primarily to futureproof "git-shell" to accept requests
+without a dash between "git" and subcommand name (e.g. "git
+upload-pack") which the newer client will start to make sometime in
+the future.
diff --git a/Documentation/RelNotes/1.5.5.6.txt b/Documentation/RelNotes/1.5.5.6.txt
new file mode 100644 (file)
index 0000000..d5e85cb
--- /dev/null
@@ -0,0 +1,10 @@
+GIT v1.5.5.6 Release Notes
+==========================
+
+Fixes since 1.5.5.5
+-------------------
+
+ * Removed support for an obsolete gitweb request URI, whose
+   implementation ran "git diff" Porcelain, instead of using plumbing,
+   which would have run an external diff command specified in the
+   repository configuration as the gitweb user.
diff --git a/Documentation/RelNotes/1.5.5.txt b/Documentation/RelNotes/1.5.5.txt
new file mode 100644 (file)
index 0000000..2932212
--- /dev/null
@@ -0,0 +1,207 @@
+GIT v1.5.5 Release Notes
+========================
+
+Updates since v1.5.4
+--------------------
+
+(subsystems)
+
+ * Comes with git-gui 0.10.1
+
+(portability)
+
+ * We shouldn't ask for BSD group ownership semantics by setting g+s bit
+   on directories on older BSD systems that refuses chmod() by non root
+   users.  BSD semantics is the default there anyway.
+
+ * Bunch of portability improvement patches coming from an effort to port
+   to Solaris has been applied.
+
+(performance)
+
+ * On platforms with suboptimal qsort(3) implementation, there
+   is an option to use more reasonable substitute we ship with
+   our software.
+
+ * New configuration variable "pack.packsizelimit" can be used
+   in place of command line option --max-pack-size.
+
+ * "git fetch" over the native git protocol used to make a
+   connection to find out the set of current remote refs and
+   another to actually download the pack data.  We now use only
+   one connection for these tasks.
+
+ * "git commit" does not run lstat(2) more than necessary
+   anymore.
+
+(usability, bells and whistles)
+
+ * Bash completion script (in contrib) are aware of more commands and
+   options.
+
+ * You can be warned when core.autocrlf conversion is applied in
+   such a way that results in an irreversible conversion.
+
+ * A catch-all "color.ui" configuration variable can be used to
+   enable coloring of all color-capable commands, instead of
+   individual ones such as "color.status" and "color.branch".
+
+ * The commands refused to take absolute pathnames where they
+   require pathnames relative to the work tree or the current
+   subdirectory.  They now can take absolute pathnames in such a
+   case as long as the pathnames do not refer outside of the
+   work tree.  E.g. "git add $(pwd)/foo" now works.
+
+ * Error messages used to be sent to stderr, only to get hidden,
+   when $PAGER was in use.  They now are sent to stdout along
+   with the command output to be shown in the $PAGER.
+
+ * A pattern "foo/" in .gitignore file now matches a directory
+   "foo".  Pattern "foo" also matches as before.
+
+ * bash completion's prompt helper function can talk about
+   operation in-progress (e.g. merge, rebase, etc.).
+
+ * Configuration variables "url.<usethis>.insteadof = <otherurl>" can be
+   used to tell "git-fetch" and "git-push" to use different URL than what
+   is given from the command line.
+
+ * "git add -i" behaves better even before you make an initial commit.
+
+ * "git am" refused to run from a subdirectory without a good reason.
+
+ * After "git apply --whitespace=fix" fixes whitespace errors in a patch,
+   a line before the fix can appear as a context or preimage line in a
+   later patch, causing the patch not to apply.  The command now knows to
+   see through whitespace fixes done to context lines to successfully
+   apply such a patch series.
+
+ * "git branch" (and "git checkout -b") to branch from a local branch can
+   optionally set "branch.<name>.merge" to mark the new branch to build on
+   the other local branch, when "branch.autosetupmerge" is set to
+   "always", or when passing the command line option "--track" (this option
+   was ignored when branching from local branches).  By default, this does
+   not happen when branching from a local branch.
+
+ * "git checkout" to switch to a branch that has "branch.<name>.merge" set
+   (i.e. marked to build on another branch) reports how much the branch
+   and the other branch diverged.
+
+ * When "git checkout" has to update a lot of paths, it used to be silent
+   for 4 seconds before it showed any progress report.  It is now a bit
+   more impatient and starts showing progress report early.
+
+ * "git commit" learned a new hook "prepare-commit-msg" that can
+   inspect what is going to be committed and prepare the commit
+   log message template to be edited.
+
+ * "git cvsimport" can now take more than one -M options.
+
+ * "git describe" learned to limit the tags to be used for
+   naming with --match option.
+
+ * "git describe --contains" now barfs when the named commit
+   cannot be described.
+
+ * "git describe --exact-match" describes only commits that are tagged.
+
+ * "git describe --long" describes a tagged commit as $tag-0-$sha1,
+   instead of just showing the exact tagname.
+
+ * "git describe" warns when using a tag whose name and path contradict
+   with each other.
+
+ * "git diff" learned "--relative" option to limit and output paths
+   relative to the current directory when working in a subdirectory.
+
+ * "git diff" learned "--dirstat" option to show birds-eye-summary of
+   changes more concisely than "--diffstat".
+
+ * "git format-patch" learned --cover-letter option to generate a cover
+   letter template.
+
+ * "git gc" learned --quiet option.
+
+ * "git gc" now automatically prunes unreachable objects that are two
+   weeks old or older.
+
+ * "git gc --auto" can be disabled more easily by just setting gc.auto
+   to zero.  It also tolerates more packfiles by default.
+
+ * "git grep" now knows "--name-only" is a synonym for the "-l" option.
+
+ * "git help <alias>" now reports "'git <alias>' is alias to <what>",
+   instead of saying "No manual entry for git-<alias>".
+
+ * "git help" can use different backends to show manual pages and this can
+   be configured using "man.viewer" configuration.
+
+ * "gitk" does not restore window position from $HOME/.gitk anymore (it
+   still restores the size).
+
+ * "git log --grep=<what>" learned "--fixed-strings" option to look for
+   <what> without treating it as a regular expression.
+
+ * "git gui" learned an auto-spell checking.
+
+ * "git push <somewhere> HEAD" and "git push <somewhere> +HEAD" works as
+   expected; they push the current branch (and only the current branch).
+   In addition, HEAD can be written as the value of "remote.<there>.push"
+   configuration variable.
+
+ * When the configuration variable "pack.threads" is set to 0, "git
+   repack" auto detects the number of CPUs and uses that many threads.
+
+ * "git send-email" learned to prompt for passwords
+   interactively.
+
+ * "git send-email" learned an easier way to suppress CC
+   recipients.
+
+ * "git stash" learned "pop" command, that applies the latest stash and
+   removes it from the stash, and "drop" command to discard the named
+   stash entry.
+
+ * "git submodule" learned a new subcommand "summary" to show the
+   symmetric difference between the HEAD version and the work tree version
+   of the submodule commits.
+
+ * Various "git cvsimport", "git cvsexportcommit", "git cvsserver",
+   "git svn" and "git p4" improvements.
+
+(internal)
+
+ * Duplicated code between git-help and git-instaweb that
+   launches user's preferred browser has been refactored.
+
+ * It is now easier to write test scripts that records known
+   breakages.
+
+ * "git checkout" is rewritten in C.
+
+ * "git remote" is rewritten in C.
+
+ * Two conflict hunks that are separated by a very short span of common
+   lines are now coalesced into one larger hunk, to make the result easier
+   to read.
+
+ * Run-command API's use of file descriptors is documented clearer and
+   is more consistent now.
+
+ * diff output can be sent to FILE * that is different from stdout.  This
+   will help reimplementing more things in C.
+
+Fixes since v1.5.4
+------------------
+
+All of the fixes in v1.5.4 maintenance series are included in
+this release, unless otherwise noted.
+
+ * "git-http-push" did not allow deletion of remote ref with the usual
+   "push <remote> :<branch>" syntax.
+
+ * "git-rebase --abort" did not go back to the right location if
+   "git-reset" was run during the "git-rebase" session.
+
+ * "git imap-send" without setting imap.host did not error out but
+   segfaulted.
diff --git a/Documentation/RelNotes/1.5.6.1.txt b/Documentation/RelNotes/1.5.6.1.txt
new file mode 100644 (file)
index 0000000..4864b16
--- /dev/null
@@ -0,0 +1,28 @@
+GIT v1.5.6.1 Release Notes
+==========================
+
+Fixes since v1.5.6
+------------------
+
+* Last minute change broke loose object creation on AIX.
+
+* (performance fix) We used to make $GIT_DIR absolute path early in the
+  programs but keeping it relative to the current directory internally
+  gives 1-3 per-cent performance boost.
+
+* bash completion knows the new --graph option to git-log family.
+
+
+* git-diff -c/--cc showed unnecessary "deletion" lines at the context
+  boundary.
+
+* git-for-each-ref ignored %(object) and %(type) requests for tag
+  objects.
+
+* git-merge usage had a typo.
+
+* Rebuilding of git-svn metainfo database did not take rewriteRoot
+  option into account.
+
+* Running "git-rebase --continue/--skip/--abort" before starting a
+  rebase gave nonsense error messages.
diff --git a/Documentation/RelNotes/1.5.6.2.txt b/Documentation/RelNotes/1.5.6.2.txt
new file mode 100644 (file)
index 0000000..5902a85
--- /dev/null
@@ -0,0 +1,40 @@
+GIT v1.5.6.2 Release Notes
+==========================
+
+Futureproof
+-----------
+
+ * "git-shell" accepts requests without a dash between "git" and
+   subcommand name (e.g. "git upload-pack") which the newer client will
+   start to make sometime in the future.
+
+Fixes since v1.5.6.1
+--------------------
+
+* "git clone" from a remote that is named with url.insteadOf setting in
+  $HOME/.gitconfig did not work well.
+
+* "git describe --long --tags" segfaulted when the described revision was
+  tagged with a lightweight tag.
+
+* "git diff --check" did not report the result via its exit status
+  reliably.
+
+* When remote side used to have branch 'foo' and git-fetch finds that now
+  it has branch 'foo/bar', it refuses to lose the existing remote tracking
+  branch and its reflog.  The error message has been improved to suggest
+  pruning the remote if the user wants to proceed and get the latest set
+  of branches from the remote, including such 'foo/bar'.
+
+* "git reset file" should mean the same thing as "git reset HEAD file",
+  but we required disambiguating -- even when "file" is not ambiguous.
+
+* "git show" segfaulted when an annotated tag that points at another
+  annotated tag was given to it.
+
+* Optimization for a large import via "git-svn" introduced in v1.5.6 had a
+  serious memory and temporary file leak, which made it unusable for
+  moderately large import.
+
+* "git-svn" mangled remote nickname used in the configuration file
+  unnecessarily.
diff --git a/Documentation/RelNotes/1.5.6.3.txt b/Documentation/RelNotes/1.5.6.3.txt
new file mode 100644 (file)
index 0000000..f61dd35
--- /dev/null
@@ -0,0 +1,52 @@
+GIT v1.5.6.3 Release Notes
+==========================
+
+Fixes since v1.5.6.2
+--------------------
+
+* Setting core.sharedrepository to traditional "true" value was supposed to make
+  the repository group writable but should not affect permission for others.
+  However, since 1.5.6, it was broken to drop permission for others when umask is
+  022, making the repository unreadable by others.
+
+* Setting GIT_TRACE will report spawning of external process via run_command().
+
+* Using an object with very deep delta chain pinned memory needed for extracting
+  intermediate base objects unnecessarily long, leading to excess memory usage.
+
+* Bash completion script did not notice '--' marker on the command
+  line and tried the relatively slow "ref completion" even when
+  completing arguments after one.
+
+* Registering a non-empty blob racily and then truncating the working
+  tree file for it confused "racy-git avoidance" logic into thinking
+  that the path is now unchanged.
+
+* The section that describes attributes related to git-archive were placed
+  in a wrong place in the gitattributes(5) manual page.
+
+* "git am" was not helpful to the users when it detected that the committer
+  information is not set up properly yet.
+
+* "git clone" had a leftover debugging fprintf().
+
+* "git clone -q" was not quiet enough as it used to and gave object count
+  and progress reports.
+
+* "git clone" marked downloaded packfile with .keep; this could be a
+  good thing if the remote side is well packed but otherwise not,
+  especially for a project that is not really big.
+
+* "git daemon" used to call syslog() from a signal handler, which
+  could raise signals of its own but generally is not reentrant.  This
+  was fixed by restructuring the code to report syslog() after the handler
+  returns.
+
+* When "git push" tries to remove a remote ref, and corresponding
+  tracking ref is missing, we used to report error (i.e. failure to
+  remove something that does not exist).
+
+* "git mailinfo" (hence "git am") did not handle commit log messages in a
+  MIME multipart mail correctly.
+
+Contains other various documentation fixes.
diff --git a/Documentation/RelNotes/1.5.6.4.txt b/Documentation/RelNotes/1.5.6.4.txt
new file mode 100644 (file)
index 0000000..d8968f1
--- /dev/null
@@ -0,0 +1,47 @@
+GIT v1.5.6.4 Release Notes
+==========================
+
+Fixes since v1.5.6.3
+--------------------
+
+* Various commands could overflow its internal buffer on a platform
+  with small PATH_MAX value in a repository that has contents with
+  long pathnames.
+
+* There wasn't a way to make --pretty=format:%<> specifiers to honor
+  .mailmap name rewriting for authors and committers.  Now you can with
+  %aN and %cN.
+
+* Bash completion wasted too many cycles; this has been optimized to be
+  usable again.
+
+* Bash completion lost ref part when completing something like "git show
+  pu:Makefile".
+
+* "git-cvsserver" did not clean up its temporary working area after annotate
+  request.
+
+* "git-daemon" called syslog() from its signal handler, which was a
+  no-no.
+
+* "git-fetch" into an empty repository used to remind that the fetch will
+   be huge by saying "no common commits", but this was an unnecessary
+   noise; it is already known by the user anyway.
+
+* "git-http-fetch" would have segfaulted when pack idx file retrieved
+  from the other side was corrupt.
+
+* "git-index-pack" used too much memory when dealing with a deep delta chain.
+
+* "git-mailinfo" (hence "git-am") did not correctly handle in-body [PATCH]
+  line to override the commit title taken from the mail Subject header.
+
+* "git-rebase -i -p" lost parents that are not involved in the history
+  being rewritten.
+
+* "git-rm" lost track of where the index file was when GIT_DIR was
+  specified as a relative path.
+
+* "git-rev-list --quiet" was not quiet as advertised.
+
+Contains other various documentation fixes.
diff --git a/Documentation/RelNotes/1.5.6.5.txt b/Documentation/RelNotes/1.5.6.5.txt
new file mode 100644 (file)
index 0000000..47ca172
--- /dev/null
@@ -0,0 +1,29 @@
+GIT v1.5.6.5 Release Notes
+==========================
+
+Fixes since v1.5.6.4
+--------------------
+
+* "git cvsimport" used to spit out "UNKNOWN LINE..." diagnostics to stdout.
+
+* "git commit -F filename" and "git tag -F filename" run from subdirectories
+  did not read the right file.
+
+* "git init --template=" with blank "template" parameter linked files
+  under root directories to .git, which was a total nonsense.  Instead, it
+  means "I do not want to use anything from the template directory".
+
+* "git diff-tree" and other diff plumbing ignored diff.renamelimit configuration
+  variable when the user explicitly asked for rename detection.
+
+* "git name-rev --name-only" did not work when "--stdin" option was in effect.
+
+* "git show-branch" mishandled its 8th branch.
+
+* Addition of "git update-index --ignore-submodules" that happened during
+  1.5.6 cycle broke "git update-index --ignore-missing".
+
+* "git send-email" did not parse charset from an existing Content-type:
+  header properly.
+
+Contains other various documentation fixes.
diff --git a/Documentation/RelNotes/1.5.6.6.txt b/Documentation/RelNotes/1.5.6.6.txt
new file mode 100644 (file)
index 0000000..79da23d
--- /dev/null
@@ -0,0 +1,10 @@
+GIT v1.5.6.6 Release Notes
+==========================
+
+Fixes since 1.5.6.5
+-------------------
+
+ * Removed support for an obsolete gitweb request URI, whose
+   implementation ran "git diff" Porcelain, instead of using plumbing,
+   which would have run an external diff command specified in the
+   repository configuration as the gitweb user.
diff --git a/Documentation/RelNotes/1.5.6.txt b/Documentation/RelNotes/1.5.6.txt
new file mode 100644 (file)
index 0000000..e143d8d
--- /dev/null
@@ -0,0 +1,115 @@
+GIT v1.5.6 Release Notes
+========================
+
+Updates since v1.5.5
+--------------------
+
+(subsystems)
+
+* Comes with updated gitk and git-gui.
+
+(portability)
+
+* git will build on AIX better than before now.
+
+* core.ignorecase configuration variable can be used to work better on
+  filesystems that are not case sensitive.
+
+* "git init" now autodetects the case sensitivity of the filesystem and
+  sets core.ignorecase accordingly.
+
+* cpio is no longer used; neither "curl" binary (libcurl is still used).
+
+(documentation)
+
+* Many freestanding documentation pages have been converted and made
+  available to "git help" (aka "man git<something>") as section 7 of
+  the manual pages. This means bookmarks to some HTML documentation
+  files may need to be updated (eg "tutorial.html" became
+  "gittutorial.html").
+
+(performance)
+
+* "git clone" was rewritten in C.  This will hopefully help cloning a
+  repository with insane number of refs.
+
+* "git rebase --onto $there $from $branch" used to switch to the tip of
+  $branch only to immediately reset back to $from, smudging work tree
+  files unnecessarily.  This has been optimized.
+
+* Object creation codepath in "git-svn" has been optimized by enhancing
+  plumbing commands git-cat-file and git-hash-object.
+
+(usability, bells and whistles)
+
+* "git add -p" (and the "patch" subcommand of "git add -i") can choose to
+  apply (or not apply) mode changes independently from contents changes.
+
+* "git bisect help" gives longer and more helpful usage information.
+
+* "git bisect" does not use a special branch "bisect" anymore; instead, it
+  does its work on a detached HEAD.
+
+* "git branch" (and "git checkout -b") can be told to set up
+  branch.<name>.rebase automatically, so that later you can say "git pull"
+  and magically cause "git pull --rebase" to happen.
+
+* "git branch --merged" and "git branch --no-merged" can be used to list
+  branches that have already been merged (or not yet merged) to the
+  current branch.
+
+* "git cherry-pick" and "git revert" can add a sign-off.
+
+* "git commit" mentions the author identity when you are committing
+  somebody else's changes.
+
+* "git diff/log --dirstat" output is consistent between binary and textual
+  changes.
+
+* "git filter-branch" rewrites signed tags by demoting them to annotated.
+
+* "git format-patch --no-binary" can produce a patch that lack binary
+  changes (i.e. cannot be used to propagate the whole changes) meant only
+  for reviewing.
+
+* "git init --bare" is a synonym for "git --bare init" now.
+
+* "git gc --auto" honors a new pre-auto-gc hook to temporarily disable it.
+
+* "git log --pretty=tformat:<custom format>" gives a LF after each entry,
+  instead of giving a LF between each pair of entries which is how
+  "git log --pretty=format:<custom format>" works.
+
+* "git log" and friends learned the "--graph" option to show the ancestry
+  graph at the left margin of the output.
+
+* "git log" and friends can be told to use date format that is different
+  from the default via 'log.date' configuration variable.
+
+* "git send-email" now can send out messages outside a git repository.
+
+* "git send-email --compose" was made aware of rfc2047 quoting.
+
+* "git status" can optionally include output from "git submodule
+  summary".
+
+* "git svn" learned --add-author-from option to propagate the authorship
+  by munging the commit log message.
+
+* new object creation and looking up in "git svn" has been optimized.
+
+* "gitweb" can read from a system-wide configuration file.
+
+(internal)
+
+* "git unpack-objects" and "git receive-pack" is now more strict about
+  detecting breakage in the objects they receive over the wire.
+
+
+Fixes since v1.5.5
+------------------
+
+All of the fixes in v1.5.5 maintenance series are included in
+this release, unless otherwise noted.
+
+And there are too numerous small fixes to otherwise note here ;-)
diff --git a/Documentation/RelNotes/1.6.0.1.txt b/Documentation/RelNotes/1.6.0.1.txt
new file mode 100644 (file)
index 0000000..49d7a1c
--- /dev/null
@@ -0,0 +1,36 @@
+GIT v1.6.0.1 Release Notes
+==========================
+
+Fixes since v1.6.0
+------------------
+
+* "git diff --cc" did not honor content mangling specified by
+  gitattributes and core.autocrlf when reading from the work tree.
+
+* "git diff --check" incorrectly detected new trailing blank lines when
+  whitespace check was in effect.
+
+* "git for-each-ref" tried to dereference NULL when asked for '%(body)" on
+  a tag with a single incomplete line as its payload.
+
+* "git format-patch" peeked before the beginning of a string when
+  "format.headers" variable is empty (a misconfiguration).
+
+* "git help help" did not work correctly.
+
+* "git mailinfo" (hence "git am") was unhappy when MIME multipart message
+  contained garbage after the finishing boundary.
+
+* "git mailinfo" also was unhappy when the "From: " line only had a bare
+  e-mail address.
+
+* "git merge" did not refresh the index correctly when a merge resulted in
+  a fast-forward.
+
+* "git merge" did not resolve a truly trivial merges that can be done
+  without content level merges.
+
+* "git svn dcommit" to a repository with URL that has embedded usernames
+  did not work correctly.
+
+Contains other various documentation fixes.
diff --git a/Documentation/RelNotes/1.6.0.2.txt b/Documentation/RelNotes/1.6.0.2.txt
new file mode 100644 (file)
index 0000000..e1e24b3
--- /dev/null
@@ -0,0 +1,87 @@
+GIT v1.6.0.2 Release Notes
+==========================
+
+Fixes since v1.6.0.1
+--------------------
+
+* Installation on platforms that needs .exe suffix to git-* programs were
+  broken in 1.6.0.1.
+
+* Installation on filesystems without symbolic links support did not
+  work well.
+
+* In-tree documentations and test scripts now use "git foo" form to set a
+  better example, instead of the "git-foo" form (which is an acceptable
+  form if you have "PATH=$(git --exec-path):$PATH" in your script)
+
+* Many commands did not use the correct working tree location when used
+  with GIT_WORK_TREE environment settings.
+
+* Some systems need to use compatibility fnmatch and regex libraries
+  independent from each other; the compat/ area has been reorganized to
+  allow this.
+
+
+* "git apply --unidiff-zero" incorrectly applied a -U0 patch that inserts
+  a new line before the second line.
+
+* "git blame -c" did not exactly work like "git annotate" when range
+  boundaries are involved.
+
+* "git checkout file" when file is still unmerged checked out contents from
+  a random high order stage, which was confusing.
+
+* "git clone $there $here/" with extra trailing slashes after explicit
+  local directory name $here did not work as expected.
+
+* "git diff" on tracked contents with CRLF line endings did not drive "less"
+  intelligently when showing added or removed lines.
+
+* "git diff --dirstat -M" did not add changes in subdirectories up
+  correctly for renamed paths.
+
+* "git diff --cumulative" did not imply "--dirstat".
+
+* "git for-each-ref refs/heads/" did not work as expected.
+
+* "git gui" allowed users to feed patch without any context to be applied.
+
+* "git gui" botched parsing "diff" output when a line that begins with two
+  dashes and a space gets removed or a line that begins with two pluses
+  and a space gets added.
+
+* "git gui" translation updates and i18n fixes.
+
+* "git index-pack" is more careful against disk corruption while completing
+  a thin pack.
+
+* "git log -i --grep=pattern" did not ignore case; neither "git log -E
+  --grep=pattern" triggered extended regexp.
+
+* "git log --pretty="%ad" --date=short" did not use short format when
+  showing the timestamp.
+
+* "git log --author=author" match incorrectly matched with the
+  timestamp part of "author " line in commit objects.
+
+* "git log -F --author=author" did not work at all.
+
+* Build procedure for "git shell" that used stub versions of some
+  functions and globals was not understood by linkers on some platforms.
+
+* "git stash" was fooled by a stat-dirty but otherwise unmodified paths
+  and refused to work until the user refreshed the index.
+
+* "git svn" was broken on Perl before 5.8 with recent fixes to reduce
+  use of temporary files.
+
+* "git verify-pack -v" did not work correctly when given more than one
+  packfile.
+
+Also contains many documentation updates.
+
+--
+exec >/var/tmp/1
+O=v1.6.0.1-78-g3632cfc
+echo O=$(git describe maint)
+git shortlog --no-merges $O..maint
diff --git a/Documentation/RelNotes/1.6.0.3.txt b/Documentation/RelNotes/1.6.0.3.txt
new file mode 100644 (file)
index 0000000..ae05778
--- /dev/null
@@ -0,0 +1,117 @@
+GIT v1.6.0.3 Release Notes
+==========================
+
+Fixes since v1.6.0.2
+--------------------
+
+* "git archive --format=zip" did not honor core.autocrlf while
+  --format=tar did.
+
+* Continuing "git rebase -i" was very confused when the user left modified
+  files in the working tree while resolving conflicts.
+
+* Continuing "git rebase -i" was also very confused when the user left
+  some staged changes in the index after "edit".
+
+* "git rebase -i" now honors the pre-rebase hook, just like the
+  other rebase implementations "git rebase" and "git rebase -m".
+
+* "git rebase -i" incorrectly aborted when there is no commit to replay.
+
+* Behaviour of "git diff --quiet" was inconsistent with "diff --exit-code"
+  with the output redirected to /dev/null.
+
+* "git diff --no-index" on binary files no longer outputs a bogus
+  "diff --git" header line.
+
+* "git diff" hunk header patterns with multiple elements separated by LF
+  were not used correctly.
+
+* Hunk headers in "git diff" default to using extended regular
+  expressions, fixing some of the internal patterns on non-GNU
+  platforms.
+
+* New config "diff.*.xfuncname" exposes extended regular expressions
+  for user specified hunk header patterns.
+
+* "git gc" when ejecting otherwise unreachable objects from packfiles into
+  loose form leaked memory.
+
+* "git index-pack" was recently broken and mishandled objects added by
+  thin-pack completion processing under memory pressure.
+
+* "git index-pack" was recently broken and misbehaved when run from inside
+  .git/objects/pack/ directory.
+
+* "git stash apply sash@{1}" was fixed to error out.  Prior versions
+  would have applied stash@{0} incorrectly.
+
+* "git stash apply" now offers a better suggestion on how to continue
+  if the working tree is currently dirty.
+
+* "git for-each-ref --format=%(subject)" fixed for commits with no
+  no newline in the message body.
+
+* "git remote" fixed to protect printf from user input.
+
+* "git remote show -v" now displays all URLs of a remote.
+
+* "git checkout -b branch" was confused when branch already existed.
+
+* "git checkout -q" once again suppresses the locally modified file list.
+
+* "git clone -q", "git fetch -q" asks remote side to not send
+  progress messages, actually making their output quiet.
+
+* Cross-directory renames are no longer used when creating packs.  This
+  allows more graceful behavior on filesystems like sshfs.
+
+* Stale temporary files under $GIT_DIR/objects/pack are now cleaned up
+  automatically by "git prune".
+
+* "git merge" once again removes directories after the last file has
+  been removed from it during the merge.
+
+* "git merge" did not allocate enough memory for the structure itself when
+  enumerating the parents of the resulting commit.
+
+* "git blame -C -C" no longer segfaults while trying to pass blame if
+   it encounters a submodule reference.
+
+* "git rm" incorrectly claimed that you have local modifications when a
+  path was merely stat-dirty.
+
+* "git svn" fixed to display an error message when 'set-tree' failed,
+   instead of a Perl compile error.
+
+* "git submodule" fixed to handle checking out a different commit
+  than HEAD after initializing the submodule.
+
+* The "git commit" error message when there are still unmerged
+  files present was clarified to match "git write-tree".
+
+* "git init" was confused when core.bare or core.sharedRepository are set
+  in system or user global configuration file by mistake.  When --bare or
+  --shared is given from the command line, these now override such
+  settings made outside the repositories.
+
+* Some segfaults due to uncaught NULL pointers were fixed in multiple
+  tools such as apply, reset, update-index.
+
+* Solaris builds now default to OLD_ICONV=1 to avoid compile warnings;
+  Solaris 8 does not define NEEDS_LIBICONV by default.
+
+* "Git.pm" tests relied on unnecessarily more recent version of Perl.
+
+* "gitweb" triggered undef warning on commits without log messages.
+
+* "gitweb" triggered undef warnings on missing trees.
+
+* "gitweb" now removes PATH_INFO from its URLs so users don't have
+  to manually set the URL in the gitweb configuration.
+
+* Bash completion removed support for legacy "git-fetch", "git-push"
+  and "git-pull" as these are no longer installed.  Dashless form
+  ("git fetch") is still however supported.
+
+Many other documentation updates.
diff --git a/Documentation/RelNotes/1.6.0.4.txt b/Documentation/RelNotes/1.6.0.4.txt
new file mode 100644 (file)
index 0000000..d522661
--- /dev/null
@@ -0,0 +1,39 @@
+GIT v1.6.0.4 Release Notes
+==========================
+
+Fixes since v1.6.0.3
+--------------------
+
+* 'git add -p' said "No changes" when only binary files were changed.
+
+* 'git archive' did not work correctly in bare repositories.
+
+* 'git checkout -t -b newbranch' when you are on detached HEAD was broken.
+
+* when we refuse to detect renames because there are too many new or
+  deleted files, 'git diff' did not say how many there are.
+
+* 'git push --mirror' tried and failed to push the stash; there is no
+  point in sending it to begin with.
+
+* 'git push' did not update the remote tracking reference if the corresponding
+  ref on the remote end happened to be already up to date.
+
+* 'git pull $there $branch:$current_branch' did not work when you were on
+  a branch yet to be born.
+
+* when giving up resolving a conflicted merge, 'git reset --hard' failed
+  to remove new paths from the working tree.
+
+* 'git send-email' had a small fd leak while scanning directory.
+
+* 'git status' incorrectly reported a submodule directory as an untracked
+  directory.
+
+* 'git svn' used deprecated 'git-foo' form of subcommand invocation.
+
+* 'git update-ref -d' to remove a reference did not honor --no-deref option.
+
+* Plugged small memleaks here and there.
+
+* Also contains many documentation updates.
diff --git a/Documentation/RelNotes/1.6.0.5.txt b/Documentation/RelNotes/1.6.0.5.txt
new file mode 100644 (file)
index 0000000..a08bb96
--- /dev/null
@@ -0,0 +1,56 @@
+GIT v1.6.0.5 Release Notes
+==========================
+
+Fixes since v1.6.0.4
+--------------------
+
+* "git checkout" used to crash when your HEAD was pointing at a deleted
+  branch.
+
+* "git checkout" from an un-checked-out state did not allow switching out
+  of the current branch.
+
+* "git diff" always allowed GIT_EXTERNAL_DIFF and --no-ext-diff was no-op for
+  the command.
+
+* Giving 3 or more tree-ish to "git diff" is supposed to show the combined
+  diff from second and subsequent trees to the first one, but the order was
+  screwed up.
+
+* "git fast-export" did not export all tags.
+
+* "git ls-files --with-tree=<tree>" did not work with options other
+  than -c, most notably with -m.
+
+* "git pack-objects" did not make its best effort to honor --max-pack-size
+  option when a single first object already busted the given limit and
+  placed many objects in a single pack.
+
+* "git-p4" fast import frontend was too eager to trigger its keyword expansion
+  logic, even on a keyword-looking string that does not have closing '$' on the
+  same line.
+
+* "git push $there" when the remote $there is defined in $GIT_DIR/branches/$there
+  behaves more like what cg-push from Cogito used to work.
+
+* when giving up resolving a conflicted merge, "git reset --hard" failed
+  to remove new paths from the working tree.
+
+* "git tag" did not complain when given mutually incompatible set of options.
+
+* The message constructed in the internal editor was discarded when "git
+  tag -s" failed to sign the message, which was often caused by the user
+  not configuring GPG correctly.
+
+* "make check" cannot be run without sparse; people may have meant to say
+  "make test" instead, so suggest that.
+
+* Internal diff machinery had a corner case performance bug that choked on
+  a large file with many repeated contents.
+
+* "git repack" used to grab objects out of packs marked with .keep
+  into a new pack.
+
+* Many unsafe call to sprintf() style varargs functions are corrected.
+
+* Also contains quite a few documentation updates.
diff --git a/Documentation/RelNotes/1.6.0.6.txt b/Documentation/RelNotes/1.6.0.6.txt
new file mode 100644 (file)
index 0000000..64ece1f
--- /dev/null
@@ -0,0 +1,33 @@
+GIT v1.6.0.6 Release Notes
+==========================
+
+Fixes since 1.6.0.5
+-------------------
+
+ * "git fsck" had a deep recursion that wasted stack space.
+
+ * "git fast-export" and "git fast-import" choked on an old style
+   annotated tag that lack the tagger information.
+
+ * "git mergetool -- file" did not correctly skip "--" marker that
+   signals the end of options list.
+
+ * "git show $tag" segfaulted when an annotated $tag pointed at a
+   nonexistent object.
+
+ * "git show 2>error" when the standard output is automatically redirected
+   to the pager redirected the standard error to the pager as well; there
+   was no need to.
+
+ * "git send-email" did not correctly handle list of addresses when
+   they had quoted comma (e.g. "Lastname, Givenname" <mail@addre.ss>).
+
+ * Logic to discover branch ancestry in "git svn" was unreliable when
+   the process to fetch history was interrupted.
+
+ * Removed support for an obsolete gitweb request URI, whose
+   implementation ran "git diff" Porcelain, instead of using plumbing,
+   which would have run an external diff command specified in the
+   repository configuration as the gitweb user.
+
+Also contains numerous documentation typofixes.
diff --git a/Documentation/RelNotes/1.6.0.txt b/Documentation/RelNotes/1.6.0.txt
new file mode 100644 (file)
index 0000000..de7ef16
--- /dev/null
@@ -0,0 +1,258 @@
+GIT v1.6.0 Release Notes
+========================
+
+User visible changes
+--------------------
+
+With the default Makefile settings, most of the programs are now
+installed outside your $PATH, except for "git", "gitk" and
+some server side programs that need to be accessible for technical
+reasons.  Invoking a git subcommand as "git-xyzzy" from the command
+line has been deprecated since early 2006 (and officially announced in
+1.5.4 release notes); use of them from your scripts after adding
+output from "git --exec-path" to the $PATH is still supported in this
+release, but users are again strongly encouraged to adjust their
+scripts to use "git xyzzy" form, as we will stop installing
+"git-xyzzy" hardlinks for built-in commands in later releases.
+
+An earlier change to page "git status" output was overwhelmingly unpopular
+and has been reverted.
+
+Source changes needed for porting to MinGW environment are now all in the
+main git.git codebase.
+
+By default, packfiles created with this version uses delta-base-offset
+encoding introduced in v1.4.4.  Pack idx files are using version 2 that
+allows larger packs and added robustness thanks to its CRC checking,
+introduced in v1.5.2 and v1.4.4.5.  If you want to keep your repositories
+backwards compatible past these versions, set repack.useDeltaBaseOffset
+to false or pack.indexVersion to 1, respectively.
+
+We used to prevent sample hook scripts shipped in templates/ from
+triggering by default by relying on the fact that we install them as
+unexecutable, but on some filesystems, this approach does not work.
+They are now shipped with ".sample" suffix.  If you want to activate
+any of these samples as-is, rename them to drop the ".sample" suffix,
+instead of running "chmod +x" on them.  For example, you can rename
+hooks/post-update.sample to hooks/post-update to enable the sample
+hook that runs update-server-info, in order to make repositories
+friendly to dumb protocols (i.e. HTTP).
+
+GIT_CONFIG, which was only documented as affecting "git config", but
+actually affected all git commands, now only affects "git config".
+GIT_LOCAL_CONFIG, also only documented as affecting "git config" and
+not different from GIT_CONFIG in a useful way, is removed.
+
+The ".dotest" temporary area "git am" and "git rebase" use is now moved
+inside the $GIT_DIR, to avoid mistakes of adding it to the project by
+accident.
+
+An ancient merge strategy "stupid" has been removed.
+
+
+Updates since v1.5.6
+--------------------
+
+(subsystems)
+
+* git-p4 in contrib learned "allowSubmit" configuration to control on
+  which branch to allow "submit" subcommand.
+
+* git-gui learned to stage changes per-line.
+
+(portability)
+
+* Changes for MinGW port have been merged, thanks to Johannes Sixt and
+  gangs.
+
+* Sample hook scripts shipped in templates/ are now suffixed with
+  *.sample.
+
+* perl's in-place edit (-i) does not work well without backup files on Windows;
+  some tests are rewritten to cope with this.
+
+(documentation)
+
+* Updated howto/update-hook-example
+
+* Got rid of usage of "git-foo" from the tutorial and made typography
+  more consistent.
+
+* Disambiguating "--" between revs and paths is finally documented.
+
+(performance, robustness, sanity etc.)
+
+* index-pack used too much memory when dealing with a deep delta chain.
+  This has been optimized.
+
+* reduced excessive inlining to shrink size of the "git" binary.
+
+* verify-pack checks the object CRC when using version 2 idx files.
+
+* When an object is corrupt in a pack, the object became unusable even
+  when the same object is available in a loose form,  We now try harder to
+  fall back to these redundant objects when able.  In particular, "git
+  repack -a -f" can be used to fix such a corruption as long as necessary
+  objects are available.
+
+* Performance of "git-blame -C -C" operation is vastly improved.
+
+* git-clone does not create refs in loose form anymore (it behaves as
+  if you immediately ran git-pack-refs after cloning).  This will help
+  repositories with insanely large number of refs.
+
+* core.fsyncobjectfiles configuration can be used to ensure that the loose
+  objects created will be fsync'ed (this is only useful on filesystems
+  that does not order data writes properly).
+
+* "git commit-tree" plumbing can make Octopus with more than 16 parents.
+  "git commit" has been capable of this for quite some time.
+
+(usability, bells and whistles)
+
+* even more documentation pages are now accessible via "man" and "git help".
+
+* A new environment variable GIT_CEILING_DIRECTORIES can be used to stop
+  the discovery process of the toplevel of working tree; this may be useful
+  when you are working in a slow network disk and are outside any working tree,
+  as bash-completion and "git help" may still need to run in these places.
+
+* By default, stash entries never expire.  Set reflogexpire in [gc
+  "refs/stash"] to a reasonable value to get traditional auto-expiration
+  behaviour back
+
+* Longstanding latency issue with bash completion script has been
+  addressed.  This will need to be backmerged to 'maint' later.
+
+* pager.<cmd> configuration variable can be used to enable/disable the
+  default paging behaviour per command.
+
+* "git-add -i" has a new action 'e/dit' to allow you edit the patch hunk
+  manually.
+
+* git-am records the original tip of the branch in ORIG_HEAD before it
+  starts applying patches.
+
+* git-apply can handle a patch that touches the same path more than once
+  much better than before.
+
+* git-apply can be told not to trust the line counts recorded in the input
+  patch but recount, with the new --recount option.
+
+* git-apply can be told to apply a patch to a path deeper than what the
+  patch records with --directory option.
+
+* git-archive can be told to omit certain paths from its output using
+  export-ignore attributes.
+
+* git-archive uses the zlib default compression level when creating
+  zip archive.
+
+* git-archive's command line options --exec and --remote can take their
+  parameters as separate command line arguments, similar to other commands.
+  IOW, both "--exec=path" and "--exec path" are now supported.
+
+* With -v option, git-branch describes the remote tracking statistics
+  similar to the way git-checkout reports by how many commits your branch
+  is ahead/behind.
+
+* git-branch's --contains option used to always require a commit parameter
+  to limit the branches with; it now defaults to list branches that
+  contains HEAD if this parameter is omitted.
+
+* git-branch's --merged and --no-merged option used to always limit the
+  branches relative to the HEAD, but they can now take an optional commit
+  argument that is used in place of HEAD.
+
+* git-bundle can read the revision arguments from the standard input.
+
+* git-cherry-pick can replay a root commit now.
+
+* git-clone can clone from a remote whose URL would be rewritten by
+  configuration stored in $HOME/.gitconfig now.
+
+* "git-clone --mirror" is a handy way to set up a bare mirror repository.
+
+* git-cvsserver learned to respond to "cvs co -c".
+
+* git-diff --check now checks leftover merge conflict markers.
+
+* "git-diff -p" learned to grab a better hunk header lines in
+  BibTex, Pascal/Delphi, and Ruby files and also pays attention to
+  chapter and part boundary in TeX documents.
+
+* When remote side used to have branch 'foo' and git-fetch finds that now
+  it has branch 'foo/bar', it refuses to lose the existing remote tracking
+  branch and its reflog.  The error message has been improved to suggest
+  pruning the remote if the user wants to proceed and get the latest set
+  of branches from the remote, including such 'foo/bar'.
+
+* fast-export learned to export and import marks file; this can be used to
+  interface with fast-import incrementally.
+
+* fast-import and fast-export learned to export and import gitlinks.
+
+* "gitk" left background process behind after being asked to dig very deep
+  history and the user killed the UI; the process is killed when the UI goes
+  away now.
+
+* git-rebase records the original tip of branch in ORIG_HEAD before it is
+  rewound.
+
+* "git rerere" can be told to update the index with auto-reused resolution
+  with rerere.autoupdate configuration variable.
+
+* git-rev-parse learned $commit^! and $commit^@ notations used in "log"
+  family.  These notations are available in gitk as well, because the gitk
+  command internally uses rev-parse to interpret its arguments.
+
+* git-rev-list learned --children option to show child commits it
+  encountered during the traversal, instead of showing parent commits.
+
+* git-send-mail can talk not just over SSL but over TLS now.
+
+* git-shortlog honors custom output format specified with "--pretty=format:".
+
+* "git-stash save" learned --keep-index option.  This lets you stash away the
+  local changes and bring the changes staged in the index to your working
+  tree for examination and testing.
+
+* git-stash also learned branch subcommand to create a new branch out of
+  stashed changes.
+
+* git-status gives the remote tracking statistics similar to the way
+  git-checkout reports by how many commits your branch is ahead/behind.
+
+* "git-svn dcommit" is now aware of auto-props setting the subversion user
+  has.
+
+* You can tell "git status -u" to even more aggressively omit checking
+  untracked files with --untracked-files=no.
+
+* Original SHA-1 value for "update-ref -d" is optional now.
+
+* Error codes from gitweb are made more descriptive where possible, rather
+  than "403 forbidden" as we used to issue everywhere.
+
+(internal)
+
+* git-merge has been reimplemented in C.
+
+
+Fixes since v1.5.6
+------------------
+
+All of the fixes in v1.5.6 maintenance series are included in
+this release, unless otherwise noted.
+
+ * git-clone ignored its -u option; the fix needs to be backported to
+   'maint';
+
+ * git-mv used to lose the distinction between changes that are staged
+   and that are only in the working tree, by staging both in the index
+   after moving such a path.
+
+ * "git-rebase -i -p" rewrote the parents to wrong ones when amending
+   (either edit or squash) was involved, and did not work correctly
+   when fast forwarding.
+
diff --git a/Documentation/RelNotes/1.6.1.1.txt b/Documentation/RelNotes/1.6.1.1.txt
new file mode 100644 (file)
index 0000000..8c594ba
--- /dev/null
@@ -0,0 +1,59 @@
+GIT v1.6.1.1 Release Notes
+==========================
+
+Fixes since v1.6.1
+------------------
+
+* "git add frotz/nitfol" when "frotz" is a submodule should have errored
+  out, but it didn't.
+
+* "git apply" took file modes from the patch text and updated the mode
+  bits of the target tree even when the patch was not about mode changes.
+
+* "git bisect view" on Cygwin did not launch gitk
+
+* "git checkout $tree" did not trigger an error.
+
+* "git commit" tried to remove COMMIT_EDITMSG from the work tree by mistake.
+
+* "git describe --all" complained when a commit is described with a tag,
+  which was nonsense.
+
+* "git diff --no-index --" did not trigger no-index (aka "use git-diff as
+  a replacement of diff on untracked files") behaviour.
+
+* "git format-patch -1 HEAD" on a root commit failed to produce patch
+  text.
+
+* "git fsck branch" did not work as advertised; instead it behaved the same
+  way as "git fsck".
+
+* "git log --pretty=format:%s" did not handle a multi-line subject the
+  same way as built-in log listers (i.e. shortlog, --pretty=oneline, etc.)
+
+* "git daemon", and "git merge-file" are more careful when freopen fails
+  and barf, instead of going on and writing to unopened filehandle.
+
+* "git http-push" did not like some RFC 4918 compliant DAV server
+  responses.
+
+* "git merge -s recursive" mistakenly overwritten an untracked file in the
+  work tree upon delete/modify conflict.
+
+* "git merge -s recursive" didn't leave the index unmerged for entries with
+  rename/delete conflicts.
+
+* "git merge -s recursive" clobbered untracked files in the work tree.
+
+* "git mv -k" with more than one erroneous paths misbehaved.
+
+* "git read-tree -m -u" hence branch switching incorrectly lost a
+  subdirectory in rare cases.
+
+* "git rebase -i" issued an unnecessary error message upon a user error of
+  marking the first commit to be "squash"ed.
+
+* "git shortlog" did not format a commit message with multi-line
+  subject correctly.
+
+Many documentation updates.
diff --git a/Documentation/RelNotes/1.6.1.2.txt b/Documentation/RelNotes/1.6.1.2.txt
new file mode 100644 (file)
index 0000000..be37cbb
--- /dev/null
@@ -0,0 +1,39 @@
+GIT v1.6.1.2 Release Notes
+==========================
+
+Fixes since v1.6.1.1
+--------------------
+
+* The logic for rename detection in internal diff used by commands like
+  "git diff" and "git blame" has been optimized to avoid loading the same
+  blob repeatedly.
+
+* We did not allow writing out a blob that is larger than 2GB for no good
+  reason.
+
+* "git format-patch -o $dir", when $dir is a relative directory, used it
+  as relative to the root of the work tree, not relative to the current
+  directory.
+
+* v1.6.1 introduced an optimization for "git push" into a repository (A)
+  that borrows its objects from another repository (B) to avoid sending
+  objects that are available in repository B, when they are not yet used
+  by repository A.  However the code on the "git push" sender side was
+  buggy and did not work when repository B had new objects that are not
+  known by the sender.  This caused pushing into a "forked" repository
+  served by v1.6.1 software using "git push" from v1.6.1 sometimes did not
+  work.  The bug was purely on the "git push" sender side, and has been
+  corrected.
+
+* "git status -v" did not paint its diff output in colour even when
+  color.ui configuration was set.
+
+* "git ls-tree" learned --full-tree option to help Porcelain scripts that
+  want to always see the full path regardless of the current working
+  directory.
+
+* "git grep" incorrectly searched in work tree paths even when they are
+  marked as assume-unchanged.  It now searches in the index entries.
+
+* "git gc" with no grace period needlessly ejected packed but unreachable
+  objects in their loose form, only to delete them right away.
diff --git a/Documentation/RelNotes/1.6.1.3.txt b/Documentation/RelNotes/1.6.1.3.txt
new file mode 100644 (file)
index 0000000..6f0bde1
--- /dev/null
@@ -0,0 +1,32 @@
+GIT v1.6.1.3 Release Notes
+==========================
+
+Fixes since v1.6.1.2
+--------------------
+
+* "git diff --binary | git apply" pipeline did not work well when
+  a binary blob is changed to a symbolic link.
+
+* Some combinations of -b/-w/--ignore-space-at-eol to "git diff" did
+  not work as expected.
+
+* "git grep" did not pass the -I (ignore binary) option when
+  calling out an external grep program.
+
+* "git log" and friends include HEAD to the set of starting points
+  when --all is given.  This makes a difference when you are not
+  on any branch.
+
+* "git mv" to move an untracked file to overwrite a tracked
+  contents misbehaved.
+
+* "git merge -s octopus" with many potential merge bases did not
+  work correctly.
+
+* RPM binary package installed the html manpages in a wrong place.
+
+Also includes minor documentation fixes and updates.
+
+
+--
+git shortlog --no-merges v1.6.1.2-33-gc789350..
diff --git a/Documentation/RelNotes/1.6.1.4.txt b/Documentation/RelNotes/1.6.1.4.txt
new file mode 100644 (file)
index 0000000..0ce6316
--- /dev/null
@@ -0,0 +1,44 @@
+GIT v1.6.1.4 Release Notes
+==========================
+
+Fixes since v1.6.1.3
+--------------------
+
+* .gitignore learned to handle backslash as a quoting mechanism for
+  comment introduction character "#".
+  This fix was first merged to 1.6.2.1.
+
+* "git fast-export" produced wrong output with some parents missing from
+  commits, when the history is clock-skewed.
+
+* "git fast-import" sometimes failed to read back objects it just wrote
+  out and aborted, because it failed to flush stale cached data.
+
+* "git-ls-tree" and "git-diff-tree" used a pathspec correctly when
+  deciding to descend into a subdirectory but they did not match the
+  individual paths correctly.  This caused pathspecs "abc/d ab" to match
+  "abc/0" ("abc/d" made them decide to descend into the directory "abc/",
+  and then "ab" incorrectly matched "abc/0" when it shouldn't).
+  This fix was first merged to 1.6.2.3.
+
+* import-zips script (in contrib) did not compute the common directory
+  prefix correctly.
+  This fix was first merged to 1.6.2.2.
+
+* "git init" segfaulted when given an overlong template location via
+  the --template= option.
+  This fix was first merged to 1.6.2.4.
+
+* "git repack" did not error out when necessary object was missing in the
+  repository.
+
+* git-repack (invoked from git-gc) did not work as nicely as it should in
+  a repository that borrows objects from neighbours via alternates
+  mechanism especially when some packs are marked with the ".keep" flag
+  to prevent them from being repacked.
+  This fix was first merged to 1.6.2.3.
+
+Also includes minor documentation fixes and updates.
+
+--
+git shortlog --no-merges v1.6.1.3..
diff --git a/Documentation/RelNotes/1.6.1.txt b/Documentation/RelNotes/1.6.1.txt
new file mode 100644 (file)
index 0000000..adb7cca
--- /dev/null
@@ -0,0 +1,286 @@
+GIT v1.6.1 Release Notes
+========================
+
+Updates since v1.6.0
+--------------------
+
+When some commands (e.g. "git log", "git diff") spawn pager internally, we
+used to make the pager the parent process of the git command that produces
+output.  This meant that the exit status of the whole thing comes from the
+pager, not the underlying git command.  We swapped the order of the
+processes around and you will see the exit code from the command from now
+on.
+
+(subsystems)
+
+* gitk can call out to git-gui to view "git blame" output; git-gui in turn
+  can run gitk from its blame view.
+
+* Various git-gui updates including updated translations.
+
+* Various gitweb updates from repo.or.cz installation.
+
+* Updates to emacs bindings.
+
+(portability)
+
+* A few test scripts used nonportable "grep" that did not work well on
+  some platforms, e.g. Solaris.
+
+* Sample pre-auto-gc script has OS X support.
+
+* Makefile has support for (ancient) FreeBSD 4.9.
+
+(performance)
+
+* Many operations that are lstat(3) heavy can be told to pre-execute
+  necessary lstat(3) in parallel before their main operations, which
+  potentially gives much improved performance for cold-cache cases or in
+  environments with weak metadata caching (e.g. NFS).
+
+* The underlying diff machinery to produce textual output has been
+  optimized, which would result in faster "git blame" processing.
+
+* Most of the test scripts (but not the ones that try to run servers)
+  can be run in parallel.
+
+* Bash completion of refnames in a repository with massive number of
+  refs has been optimized.
+
+* Cygwin port uses native stat/lstat implementations when applicable,
+  which leads to improved performance.
+
+* "git push" pays attention to alternate repositories to avoid sending
+  unnecessary objects.
+
+* "git svn" can rebuild an out-of-date rev_map file.
+
+(usability, bells and whistles)
+
+* When you mistype a command name, git helpfully suggests what it guesses
+  you might have meant to say.  help.autocorrect configuration can be set
+  to a non-zero value to accept the suggestion when git can uniquely
+  guess.
+
+* The packfile machinery hopefully is more robust when dealing with
+  corrupt packs if redundant objects involved in the corruption are
+  available elsewhere.
+
+* "git add -N path..." adds the named paths as an empty blob, so that
+  subsequent "git diff" will show a diff as if they are creation events.
+
+* "git add" gained a built-in synonym for people who want to say "stage
+  changes" instead of "add contents to the staging area" which amounts
+  to the same thing.
+
+* "git apply" learned --include=paths option, similar to the existing
+  --exclude=paths option.
+
+* "git bisect" is careful about a user mistake and suggests testing of
+  merge base first when good is not a strict ancestor of bad.
+
+* "git bisect skip" can take a range of commits.
+
+* "git blame" re-encodes the commit metainfo to UTF-8 from i18n.commitEncoding
+  by default.
+
+* "git check-attr --stdin" can check attributes for multiple paths.
+
+* "git checkout --track origin/hack" used to be a syntax error.  It now
+  DWIMs to create a corresponding local branch "hack", i.e. acts as if you
+  said "git checkout --track -b hack origin/hack".
+
+* "git checkout --ours/--theirs" can be used to check out one side of a
+  conflicting merge during conflict resolution.
+
+* "git checkout -m" can be used to recreate the initial conflicted state
+  during conflict resolution.
+
+* "git cherry-pick" can also utilize rerere for conflict resolution.
+
+* "git clone" learned to be verbose with -v
+
+* "git commit --author=$name" can look up author name from existing
+  commits.
+
+* output from "git commit" has been reworded in a more concise and yet
+  more informative way.
+
+* "git count-objects" reports the on-disk footprint for packfiles and
+  their corresponding idx files.
+
+* "git daemon" learned --max-connections=<count> option.
+
+* "git daemon" exports REMOTE_ADDR to record client address, so that
+  spawned programs can act differently on it.
+
+* "git describe --tags" favours closer lightweight tags than farther
+  annotated tags now.
+
+* "git diff" learned to mimic --suppress-blank-empty from GNU diff via a
+  configuration option.
+
+* "git diff" learned to put more sensible hunk headers for Python,
+  HTML and ObjC contents.
+
+* "git diff" learned to vary the a/ vs b/ prefix depending on what are
+  being compared, controlled by diff.mnemonicprefix configuration.
+
+* "git diff" learned --dirstat-by-file to count changed files, not number
+  of lines, when summarizing the global picture.
+
+* "git diff" learned "textconv" filters --- a binary or hard-to-read
+  contents can be munged into human readable form and the difference
+  between the results of the conversion can be viewed (obviously this
+  cannot produce a patch that can be applied, so this is disabled in
+  format-patch among other things).
+
+* "--cached" option to "git diff has an easier to remember synonym "--staged",
+  to ask "what is the difference between the given commit and the
+  contents staged in the index?"
+
+* "git for-each-ref" learned "refname:short" token that gives an
+  unambiguously abbreviated refname.
+
+* Auto-numbering of the subject lines is the default for "git
+  format-patch" now.
+
+* "git grep" learned to accept -z similar to GNU grep.
+
+* "git help" learned to use GIT_MAN_VIEWER environment variable before
+  using "man" program.
+
+* "git imap-send" can optionally talk SSL.
+
+* "git index-pack" is more careful against disk corruption while
+  completing a thin pack.
+
+* "git log --check" and "git log --exit-code" passes their underlying diff
+  status with their exit status code.
+
+* "git log" learned --simplify-merges, a milder variant of --full-history;
+  "gitk --simplify-merges" is easier to view than with --full-history.
+
+* "git log" learned "--source" to show what ref each commit was reached
+  from.
+
+* "git log" also learned "--simplify-by-decoration" to show the
+  birds-eye-view of the topology of the history.
+
+* "git log --pretty=format:" learned "%d" format element that inserts
+  names of tags that point at the commit.
+
+* "git merge --squash" and "git merge --no-ff" into an unborn branch are
+  noticed as user errors.
+
+* "git merge -s $strategy" can use a custom built strategy if you have a
+  command "git-merge-$strategy" on your $PATH.
+
+* "git pull" (and "git fetch") can be told to operate "-v"erbosely or
+  "-q"uietly.
+
+* "git push" can be told to reject deletion of refs with receive.denyDeletes
+  configuration.
+
+* "git rebase" honours pre-rebase hook; use --no-verify to bypass it.
+
+* "git rebase -p" uses interactive rebase machinery now to preserve the merges.
+
+* "git reflog expire branch" can be used in place of "git reflog expire
+  refs/heads/branch".
+
+* "git remote show $remote" lists remote branches one-per-line now.
+
+* "git send-email" can be given revision range instead of files and
+  maildirs on the command line, and automatically runs format-patch to
+  generate patches for the given revision range.
+
+* "git submodule foreach" subcommand allows you to iterate over checked
+  out submodules.
+
+* "git submodule sync" subcommands allows you to update the origin URL
+  recorded in submodule directories from the toplevel .gitmodules file.
+
+* "git svn branch" can create new branches on the other end.
+
+* "gitweb" can use more saner PATH_INFO based URL.
+
+(internal)
+
+* "git hash-object" learned to lie about the path being hashed, so that
+  correct gitattributes processing can be done while hashing contents
+  stored in a temporary file.
+
+* various callers of git-merge-recursive avoid forking it as an external
+  process.
+
+* Git class defined in "Git.pm" can be subclasses a bit more easily.
+
+* We used to link GNU regex library as a compatibility layer for some
+  platforms, but it turns out it is not necessary on most of them.
+
+* Some path handling routines used fixed number of buffers used alternately
+  but depending on the call depth, this arrangement led to hard to track
+  bugs.  This issue is being addressed.
+
+
+Fixes since v1.6.0
+------------------
+
+All of the fixes in v1.6.0.X maintenance series are included in this
+release, unless otherwise noted.
+
+* Porcelains implemented as shell scripts were utterly confused when you
+  entered to a subdirectory of a work tree from sideways, following a
+  symbolic link (this may need to be backported to older releases later).
+
+* Tracking symbolic links would work better on filesystems whose lstat()
+  returns incorrect st_size value for them.
+
+* "git add" and "git update-index" incorrectly allowed adding S/F when S
+  is a tracked symlink that points at a directory D that has a path F in
+  it (we still need to fix a similar nonsense when S is a submodule and F
+  is a path in it).
+
+* "git am" after stopping at a broken patch lost --whitespace, -C, -p and
+  --3way options given from the command line initially.
+
+* "git diff --stdin" used to take two trees on a line and compared them,
+  but we dropped support for such a use case long time ago.  This has
+  been resurrected.
+
+* "git filter-branch" failed to rewrite a tag name with slashes in it.
+
+* "git http-push" did not understand URI scheme other than opaquelocktoken
+  when acquiring a lock from the server (this may need to be backported to
+  older releases later).
+
+* After "git rebase -p" stopped with conflicts while replaying a merge,
+ "git rebase --continue" did not work (may need to be backported to older
+  releases).
+
+* "git revert" records relative to which parent a revert was made when
+  reverting a merge.  Together with new documentation that explains issues
+  around reverting a merge and merging from the updated branch later, this
+  hopefully will reduce user confusion (this may need to be backported to
+  older releases later).
+
+* "git rm --cached" used to allow an empty blob that was added earlier to
+  be removed without --force, even when the file in the work tree has
+  since been modified.
+
+* "git push --tags --all $there" failed with generic usage message without
+  telling saying these two options are incompatible.
+
+* "git log --author/--committer" match used to potentially match the
+  timestamp part, exposing internal implementation detail.  Also these did
+  not work with --fixed-strings match at all.
+
+* "gitweb" did not mark non-ASCII characters imported from external HTML fragments
+  correctly.
+
+--
+exec >/var/tmp/1
+O=v1.6.1-rc3-74-gf66bc5f
+echo O=$(git describe master)
+git shortlog --no-merges $O..master ^maint
diff --git a/Documentation/RelNotes/1.6.2.1.txt b/Documentation/RelNotes/1.6.2.1.txt
new file mode 100644 (file)
index 0000000..dfa3641
--- /dev/null
@@ -0,0 +1,19 @@
+GIT v1.6.2.1 Release Notes
+==========================
+
+Fixes since v1.6.2
+------------------
+
+* .gitignore learned to handle backslash as a quoting mechanism for
+  comment introduction character "#".
+
+* timestamp output in --date=relative mode used to display timestamps that
+  are long time ago in the default mode; it now uses "N years M months
+  ago", and "N years ago".
+
+* git-add -i/-p now works with non-ASCII pathnames.
+
+* "git hash-object -w" did not read from the configuration file from the
+  correct .git directory.
+
+* git-send-email learned to correctly handle multiple Cc: addresses.
diff --git a/Documentation/RelNotes/1.6.2.2.txt b/Documentation/RelNotes/1.6.2.2.txt
new file mode 100644 (file)
index 0000000..fafa998
--- /dev/null
@@ -0,0 +1,45 @@
+GIT v1.6.2.2 Release Notes
+==========================
+
+Fixes since v1.6.2.1
+--------------------
+
+* A longstanding confusing description of what --pickaxe option of
+  git-diff does has been clarified in the documentation.
+
+* "git-blame -S" did not quite work near the commits that were given
+  on the command line correctly.
+
+* "git diff --pickaxe-regexp" did not count overlapping matches
+  correctly.
+
+* "git diff" did not feed files in work-tree representation to external
+  diff and textconv.
+
+* "git-fetch" in a repository that was not cloned from anywhere said
+  it cannot find 'origin', which was hard to understand for new people.
+
+* "git-format-patch --numbered-files --stdout" did not have to die of
+  incompatible options; it now simply ignores --numbered-files as no files
+  are produced anyway.
+
+* "git-ls-files --deleted" did not work well with GIT_DIR&GIT_WORK_TREE.
+
+* "git-read-tree A B C..." without -m option has been broken for a long
+  time.
+
+* git-send-email ignored --in-reply-to when --no-thread was given.
+
+* 'git-submodule add' did not tolerate extra slashes and ./ in the path it
+  accepted from the command line; it now is more lenient.
+
+* git-svn misbehaved when the project contained a path that began with
+  two dashes.
+
+* import-zips script (in contrib) did not compute the common directory
+  prefix correctly.
+
+* miscompilation of negated enum constants by old gcc (2.9) affected the
+  codepaths to spawn subprocesses.
+
+Many small documentation updates are included as well.
diff --git a/Documentation/RelNotes/1.6.2.3.txt b/Documentation/RelNotes/1.6.2.3.txt
new file mode 100644 (file)
index 0000000..4d3c1ac
--- /dev/null
@@ -0,0 +1,22 @@
+GIT v1.6.2.3 Release Notes
+==========================
+
+Fixes since v1.6.2.2
+--------------------
+
+* Setting an octal mode value to core.sharedrepository configuration to
+  restrict access to the repository to group members did not work as
+  advertised.
+
+* A fairly large and trivial memory leak while rev-list shows list of
+  reachable objects has been identified and plugged.
+
+* "git-commit --interactive" did not abort when underlying "git-add -i"
+  signaled a failure.
+
+* git-repack (invoked from git-gc) did not work as nicely as it should in
+  a repository that borrows objects from neighbours via alternates
+  mechanism especially when some packs are marked with the ".keep" flag
+  to prevent them from being repacked.
+
+Many small documentation updates are included as well.
diff --git a/Documentation/RelNotes/1.6.2.4.txt b/Documentation/RelNotes/1.6.2.4.txt
new file mode 100644 (file)
index 0000000..f4bf1d0
--- /dev/null
@@ -0,0 +1,39 @@
+GIT v1.6.2.4 Release Notes
+==========================
+
+Fixes since v1.6.2.3
+--------------------
+
+* The configuration parser had a buffer overflow while parsing an overlong
+  value.
+
+* pruning reflog entries that are unreachable from the tip of the ref
+  during "git reflog prune" (hence "git gc") was very inefficient.
+
+* "git-add -p" lacked a way to say "q"uit to refuse staging any hunks for
+  the remaining paths.  You had to say "d" and then ^C.
+
+* "git-checkout <tree-ish> <submodule>" did not update the index entry at
+  the named path; it now does.
+
+* "git-fast-export" choked when seeing a tag that does not point at commit.
+
+* "git init" segfaulted when given an overlong template location via
+  the --template= option.
+
+* "git-ls-tree" and "git-diff-tree" used a pathspec correctly when
+  deciding to descend into a subdirectory but they did not match the
+  individual paths correctly.  This caused pathspecs "abc/d ab" to match
+  "abc/0" ("abc/d" made them decide to descend into the directory "abc/",
+  and then "ab" incorrectly matched "abc/0" when it shouldn't).
+
+* "git-merge-recursive" was broken when a submodule entry was involved in
+  a criss-cross merge situation.
+
+Many small documentation updates are included as well.
+
+---
+exec >/var/tmp/1
+echo O=$(git describe maint)
+O=v1.6.2.3-38-g318b847
+git shortlog --no-merges $O..maint
diff --git a/Documentation/RelNotes/1.6.2.5.txt b/Documentation/RelNotes/1.6.2.5.txt
new file mode 100644 (file)
index 0000000..b23f9e9
--- /dev/null
@@ -0,0 +1,21 @@
+GIT v1.6.2.5 Release Notes
+==========================
+
+Fixes since v1.6.2.4
+--------------------
+
+* "git apply" mishandled if you fed a git generated patch that renames
+  file A to B and file B to A at the same time.
+
+* "git diff -c -p" (and "diff --cc") did not expect to see submodule
+  differences and instead refused to work.
+
+* "git grep -e '('" segfaulted, instead of diagnosing a mismatched
+  parentheses error.
+
+* "git fetch" generated packs with offset-delta encoding when both ends of
+  the connection are capable of producing one; this cannot be read by
+  ancient git and the user should be able to disable this by setting
+  repack.usedeltabaseoffset configuration to false.
+
+
diff --git a/Documentation/RelNotes/1.6.2.txt b/Documentation/RelNotes/1.6.2.txt
new file mode 100644 (file)
index 0000000..ad060f4
--- /dev/null
@@ -0,0 +1,164 @@
+GIT v1.6.2 Release Notes
+========================
+
+With the next major release, "git push" into a branch that is
+currently checked out will be refused by default.  You can choose
+what should happen upon such a push by setting the configuration
+variable receive.denyCurrentBranch in the receiving repository.
+
+To ease the transition plan, the receiving repository of such a
+push running this release will issue a big warning when the
+configuration variable is missing.  Please refer to:
+
+  http://git.or.cz/gitwiki/GitFaq#non-bare
+  http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007
+
+for more details on the reason why this change is needed and the
+transition plan.
+
+For a similar reason, "git push $there :$killed" to delete the branch
+$killed in a remote repository $there, if $killed branch is the current
+branch pointed at by its HEAD, gets a large warning.  You can choose what
+should happen upon such a push by setting the configuration variable
+receive.denyDeleteCurrent in the receiving repository.
+
+
+Updates since v1.6.1
+--------------------
+
+(subsystems)
+
+* git-svn updates.
+
+* gitweb updates, including a new patch view and RSS/Atom feed
+  improvements.
+
+* (contrib/emacs) git.el now has commands for checking out a branch,
+  creating a branch, cherry-picking and reverting commits; vc-git.el
+  is not shipped with git anymore (it is part of official Emacs).
+
+(performance)
+
+* pack-objects autodetects the number of CPUs available and uses threaded
+  version.
+
+(usability, bells and whistles)
+
+* automatic typo correction works on aliases as well
+
+* @{-1} is a way to refer to the last branch you were on.  This is
+  accepted not only where an object name is expected, but anywhere
+  a branch name is expected and acts as if you typed the branch name.
+  E.g. "git branch --track mybranch @{-1}", "git merge @{-1}", and
+  "git rev-parse --symbolic-full-name @{-1}" would work as expected.
+
+* When refs/remotes/origin/HEAD points at a remote tracking branch that
+  has been pruned away, many git operations issued warning when they
+  internally enumerated the refs.  We now warn only when you say "origin"
+  to refer to that pruned branch.
+
+* The location of .mailmap file can be configured, and its file format was
+  enhanced to allow mapping an incorrect e-mail field as well.
+
+* "git add -p" learned 'g'oto action to jump directly to a hunk.
+
+* "git add -p" learned to find a hunk with given text with '/'.
+
+* "git add -p" optionally can be told to work with just the command letter
+  without Enter.
+
+* when "git am" stops upon a patch that does not apply, it shows the
+  title of the offending patch.
+
+* "git am --directory=<dir>" and "git am --reject" passes these options
+  to underlying "git apply".
+
+* "git am" learned --ignore-date option.
+
+* "git blame" aligns author names better when they are spelled in
+  non US-ASCII encoding.
+
+* "git clone" now makes its best effort when cloning from an empty
+  repository to set up configuration variables to refer to the remote
+  repository.
+
+* "git checkout -" is a shorthand for "git checkout @{-1}".
+
+* "git cherry" defaults to whatever the current branch is tracking (if
+  exists) when the <upstream> argument is not given.
+
+* "git cvsserver" can be told not to add extra "via git-CVS emulator" to
+  the commit log message it serves via gitcvs.commitmsgannotation
+  configuration.
+
+* "git cvsserver" learned to handle 'noop' command some CVS clients seem
+  to expect to work.
+
+* "git diff" learned a new option --inter-hunk-context to coalesce close
+  hunks together and show context between them.
+
+* The definition of what constitutes a word for "git diff --color-words"
+  can be customized via gitattributes, command line or a configuration.
+
+* "git diff" learned --patience to run "patience diff" algorithm.
+
+* "git filter-branch" learned --prune-empty option that discards commits
+  that do not change the contents.
+
+* "git fsck" now checks loose objects in alternate object stores, instead
+  of misreporting them as missing.
+
+* "git gc --prune" was resurrected to allow "git gc --no-prune" and
+  giving non-default expiration period e.g. "git gc --prune=now".
+
+* "git grep -w" and "git grep" for fixed strings have been optimized.
+
+* "git mergetool" learned -y(--no-prompt) option to disable prompting.
+
+* "git rebase -i" can transplant a history down to root to elsewhere
+  with --root option.
+
+* "git reset --merge" is a new mode that works similar to the way
+  "git checkout" switches branches, taking the local changes while
+  switching to another commit.
+
+* "git submodule update" learned --no-fetch option.
+
+* "git tag" learned --contains that works the same way as the same option
+  from "git branch".
+
+
+Fixes since v1.6.1
+------------------
+
+All of the fixes in v1.6.1.X maintenance series are included in this
+release, unless otherwise noted.
+
+Here are fixes that this release has, but have not been backported to
+v1.6.1.X series.
+
+* "git-add sub/file" when sub is a submodule incorrectly added the path to
+  the superproject.
+
+* "git bundle" did not exclude annotated tags even when a range given
+  from the command line wanted to.
+
+* "git filter-branch" unnecessarily refused to work when you had
+  checked out a different commit from what is recorded in the superproject
+  index in a submodule.
+
+* "git filter-branch" incorrectly tried to update a nonexistent work tree
+  at the end when it is run in a bare repository.
+
+* "git gc" did not work if your repository was created with an ancient git
+  and never had any pack files in it before.
+
+* "git mergetool" used to ignore autocrlf and other attributes
+  based content rewriting.
+
+* branch switching and merges had a silly bug that did not validate
+  the correct directory when making sure an existing subdirectory is
+  clean.
+
+* "git -p cmd" when cmd is not a built-in one left the display in funny state
+  when killed in the middle.
diff --git a/Documentation/RelNotes/1.6.3.1.txt b/Documentation/RelNotes/1.6.3.1.txt
new file mode 100644 (file)
index 0000000..2400b72
--- /dev/null
@@ -0,0 +1,10 @@
+GIT v1.6.3.1 Release Notes
+==========================
+
+Fixes since v1.6.3
+------------------
+
+* "git checkout -b new-branch" with a staged change in the index
+  incorrectly primed the in-index cache-tree, resulting a wrong tree
+  object to be written out of the index.  This is a grave regression
+  since the last 1.6.2.X maintenance release.
diff --git a/Documentation/RelNotes/1.6.3.2.txt b/Documentation/RelNotes/1.6.3.2.txt
new file mode 100644 (file)
index 0000000..b2f3f02
--- /dev/null
@@ -0,0 +1,61 @@
+GIT v1.6.3.2 Release Notes
+==========================
+
+Fixes since v1.6.3.1
+--------------------
+
+ * A few codepaths picked up the first few bytes from an sha1[] by
+   casting the (char *) pointer to (int *); GCC 4.4 did not like this,
+   and aborted compilation.
+
+ * Some unlink(2) failures went undiagnosed.
+
+ * The "recursive" merge strategy misbehaved when faced rename/delete
+   conflicts while coming up with an intermediate merge base.
+
+ * The low-level merge algorithm did not handle a degenerate case of
+   merging a file with itself using itself as the common ancestor
+   gracefully.  It should produce the file itself, but instead
+   produced an empty result.
+
+ * GIT_TRACE mechanism segfaulted when tracing a shell-quoted aliases.
+
+ * OpenBSD also uses st_ctimspec in "struct stat", instead of "st_ctim".
+
+ * With NO_CROSS_DIRECTORY_HARDLINKS, "make install" can be told not to
+   create hardlinks between $(gitexecdir)/git-$builtin_commands and
+   $(bindir)/git.
+
+ * command completion code in bash did not reliably detect that we are
+   in a bare repository.
+
+ * "git add ." in an empty directory complained that pathspec "." did not
+   match anything, which may be technically correct, but not useful.  We
+   silently make it a no-op now.
+
+ * "git add -p" (and "patch" action in "git add -i") was broken when
+   the first hunk that adds a line at the top was split into two and
+   both halves are marked to be used.
+
+ * "git blame path" misbehaved at the commit where path became file
+   from a directory with some files in it.
+
+ * "git for-each-ref" had a segfaulting bug when dealing with a tag object
+   created by an ancient git.
+
+ * "git format-patch -k" still added patch numbers if format.numbered
+   configuration was set.
+
+ * "git grep --color ''" did not terminate.  The command also had
+   subtle bugs with its -w option.
+
+ * http-push had a small use-after-free bug.
+
+ * "git push" was converting OFS_DELTA pack representation into less
+   efficient REF_DELTA representation unconditionally upon transfer,
+   making the transferred data unnecessarily larger.
+
+ * "git remote show origin" segfaulted when origin was still empty.
+
+Many other general usability updates around help text, diagnostic messages
+and documentation are included as well.
diff --git a/Documentation/RelNotes/1.6.3.3.txt b/Documentation/RelNotes/1.6.3.3.txt
new file mode 100644 (file)
index 0000000..1c28398
--- /dev/null
@@ -0,0 +1,38 @@
+GIT v1.6.3.3 Release Notes
+==========================
+
+Fixes since v1.6.3.2
+--------------------
+
+ * "git archive" running on Cygwin can get stuck in an infinite loop.
+
+ * "git daemon" did not correctly parse the initial line that carries
+   virtual host request information.
+
+ * "git diff --textconv" leaked memory badly when the textconv filter
+   errored out.
+
+ * The built-in regular expressions to pick function names to put on
+   hunk header lines for java and objc were very inefficiently written.
+
+ * in certain error situations git-fetch (and git-clone) on Windows didn't
+   detect connection abort and ended up waiting indefinitely.
+
+ * import-tars script (in contrib) did not import symbolic links correctly.
+
+ * http.c used CURLOPT_SSLKEY even on libcURL version 7.9.2, even though
+   it was only available starting 7.9.3.
+
+ * low-level filelevel merge driver used return value from strdup()
+   without checking if we ran out of memory.
+
+ * "git rebase -i" left stray closing parenthesis in its reflog message.
+
+ * "git remote show" did not show all the URLs associated with the named
+   remote, even though "git remote -v" did.  Made them consistent by
+   making the former show all URLs.
+
+ * "whitespace" attribute that is set was meant to detect all errors known
+   to git, but it told git to ignore trailing carriage-returns.
+
+Includes other documentation fixes.
diff --git a/Documentation/RelNotes/1.6.3.4.txt b/Documentation/RelNotes/1.6.3.4.txt
new file mode 100644 (file)
index 0000000..cad461b
--- /dev/null
@@ -0,0 +1,36 @@
+GIT v1.6.3.4 Release Notes
+==========================
+
+Fixes since v1.6.3.3
+--------------------
+
+ * "git add --no-ignore-errors" did not override configured
+   add.ignore-errors configuration.
+
+ * "git apply --whitespace=fix" did not fix trailing whitespace on an
+   incomplete line.
+
+ * "git branch" opened too many commit objects unnecessarily.
+
+ * "git checkout -f $commit" with a path that is a file (or a symlink) in
+   the work tree to a commit that has a directory at the path issued an
+   unnecessary error message.
+
+ * "git diff -c/--cc" was very inefficient in coalescing the removed lines
+   shared between parents.
+
+ * "git diff -c/--cc" showed removed lines at the beginning of a file
+   incorrectly.
+
+ * "git remote show nickname" did not honor configured
+   remote.nickname.uploadpack when inspecting the branches at the remote.
+
+ * "git request-pull" when talking to the terminal for a preview
+   showed some of the output in the pager.
+
+ * "git request-pull start nickname [end]" did not honor configured
+   remote.nickname.uploadpack when it ran git-ls-remote against the remote
+   repository to learn the current tip of branches.
+
+Includes other documentation updates and minor fixes.
+
diff --git a/Documentation/RelNotes/1.6.3.txt b/Documentation/RelNotes/1.6.3.txt
new file mode 100644 (file)
index 0000000..418c685
--- /dev/null
@@ -0,0 +1,182 @@
+GIT v1.6.3 Release Notes
+========================
+
+With the next major release, "git push" into a branch that is
+currently checked out will be refused by default.  You can choose
+what should happen upon such a push by setting the configuration
+variable receive.denyCurrentBranch in the receiving repository.
+
+To ease the transition plan, the receiving repository of such a
+push running this release will issue a big warning when the
+configuration variable is missing.  Please refer to:
+
+  http://git.or.cz/gitwiki/GitFaq#non-bare
+  http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007
+
+for more details on the reason why this change is needed and the
+transition plan.
+
+For a similar reason, "git push $there :$killed" to delete the branch
+$killed in a remote repository $there, if $killed branch is the current
+branch pointed at by its HEAD, gets a large warning.  You can choose what
+should happen upon such a push by setting the configuration variable
+receive.denyDeleteCurrent in the receiving repository.
+
+When the user does not tell "git push" what to push, it has always
+pushed matching refs.  For some people it is unexpected, and a new
+configuration variable push.default has been introduced to allow
+changing a different default behaviour.  To advertise the new feature,
+a big warning is issued if this is not configured and a git push without
+arguments is attempted.
+
+
+Updates since v1.6.2
+--------------------
+
+(subsystems)
+
+* various git-svn updates.
+
+* git-gui updates, including an update to Russian translation, and a
+  fix to an infinite loop when showing an empty diff.
+
+* gitk updates, including an update to Russian translation and improved Windows
+  support.
+
+(performance)
+
+* many uses of lstat(2) in the codepath for "git checkout" have been
+  optimized out.
+
+(usability, bells and whistles)
+
+* Boolean configuration variable yes/no can be written as on/off.
+
+* rsync:/path/to/repo can be used to run git over rsync for local
+  repositories.  It may not be useful in practice; meant primarily for
+  testing.
+
+* http transport learned to prompt and use password when fetching from or
+  pushing to http://user@host.xz/ URL.
+
+* (msysgit) progress output that is sent over the sideband protocol can
+  be handled appropriately in Windows console.
+
+* "--pretty=<style>" option to the log family of commands can now be
+  spelled as "--format=<style>".  In addition, --format=%formatstring
+  is a short-hand for --pretty=tformat:%formatstring.
+
+* "--oneline" is a synonym for "--pretty=oneline --abbrev-commit".
+
+* "--graph" to the "git log" family can draw the commit ancestry graph
+  in colors.
+
+* If you realize that you botched the patch when you are editing hunks
+  with the 'edit' action in git-add -i/-p, you can abort the editor to
+  tell git not to apply it.
+
+* @{-1} is a new way to refer to the last branch you were on introduced in
+  1.6.2, but the initial implementation did not teach this to a few
+  commands.  Now the syntax works with "branch -m @{-1} newname".
+
+* git-archive learned --output=<file> option.
+
+* git-archive takes attributes from the tree being archived; strictly
+  speaking, this is an incompatible behaviour change, but is a good one.
+  Use --worktree-attributes option to allow it to read attributes from
+  the work tree as before (deprecated git-tar tree command always reads
+  attributes from the work tree).
+
+* git-bisect shows not just the number of remaining commits whose goodness
+  is unknown, but also shows the estimated number of remaining rounds.
+
+* You can give --date=<format> option to git-blame.
+
+* "git-branch -r" shows HEAD symref that points at a remote branch in
+  interest of each tracked remote repository.
+
+* "git-branch -v -v" is a new way to get list of names for branches and the
+  "upstream" branch for them.
+
+* git-config learned -e option to open an editor to edit the config file
+  directly.
+
+* git-clone runs post-checkout hook when run without --no-checkout.
+
+* git-difftool is now part of the officially supported command, primarily
+  maintained by David Aguilar.
+
+* git-for-each-ref learned a new "upstream" token.
+
+* git-format-patch can be told to use attachment with a new configuration,
+  format.attach.
+
+* git-format-patch can be told to produce deep or shallow message threads.
+
+* git-format-patch can be told to always add sign-off with a configuration
+  variable.
+
+* git-format-patch learned format.headers configuration to add extra
+  header fields to the output.  This behaviour is similar to the existing
+  --add-header=<header> option of the command.
+
+* git-format-patch gives human readable names to the attached files, when
+  told to send patches as attachments.
+
+* git-grep learned to highlight the found substrings in color.
+
+* git-imap-send learned to work around Thunderbird's inability to easily
+  disable format=flowed with a new configuration, imap.preformattedHTML.
+
+* git-rebase can be told to rebase the series even if your branch is a
+  descendant of the commit you are rebasing onto with --force-rebase
+  option.
+
+* git-rebase can be told to report diffstat with the --stat option.
+
+* Output from git-remote command has been vastly improved.
+
+* "git remote update --prune $remote" updates from the named remote and
+  then prunes stale tracking branches.
+
+* git-send-email learned --confirm option to review the Cc: list before
+  sending the messages out.
+
+(developers)
+
+* Test scripts can be run under valgrind.
+
+* Test scripts can be run with installed git.
+
+* Makefile learned 'coverage' option to run the test suites with
+  coverage tracking enabled.
+
+* Building the manpages with docbook-xsl between 1.69.1 and 1.71.1 now
+  requires setting DOCBOOK_SUPPRESS_SP to work around a docbook-xsl bug.
+  This workaround used to be enabled by default, but causes problems
+  with newer versions of docbook-xsl.  In addition, there are a few more
+  knobs you can tweak to work around issues with various versions of the
+  docbook-xsl package.  See comments in Documentation/Makefile for details.
+
+* Support for building and testing a subset of git on a system without a
+  working perl has been improved.
+
+
+Fixes since v1.6.2
+------------------
+
+All of the fixes in v1.6.2.X maintenance series are included in this
+release, unless otherwise noted.
+
+Here are fixes that this release has, but have not been backported to
+v1.6.2.X series.
+
+* "git-apply" rejected a patch that swaps two files (i.e. renames A to B
+  and B to A at the same time).  May need to be backported by cherry
+  picking d8c81df and then 7fac0ee).
+
+* The initial checkout did not read the attributes from the .gitattribute
+  file that is being checked out.
+
+* git-gc spent excessive amount of time to decide if an object appears
+  in a locally existing pack (if needed, backport by merging 69e020a).
diff --git a/Documentation/RelNotes/1.6.4.1.txt b/Documentation/RelNotes/1.6.4.1.txt
new file mode 100644 (file)
index 0000000..e439e45
--- /dev/null
@@ -0,0 +1,46 @@
+GIT v1.6.4.1 Release Notes
+==========================
+
+Fixes since v1.6.4
+------------------
+
+ * An unquoted value in the configuration file, when it contains more than
+   one whitespaces in a row, got them replaced with a single space.
+
+ * "git am" used to accept a single piece of e-mail per file (not a mbox)
+   as its input, but multiple input format support in v1.6.4 broke it.
+   Apparently many people have been depending on this feature.
+
+ * The short help text for "git filter-branch" command was a single long
+   line, wrapped by terminals, and was hard to read.
+
+ * The "recursive" strategy of "git merge" segfaulted when a merge has
+   more than one merge-bases, and merging of these merge-bases involves
+   a rename/rename or a rename/add conflict.
+
+ * "git pull --rebase" did not use the right fork point when the
+   repository has already fetched from the upstream that rewinds the
+   branch it is based on in an earlier fetch.
+
+ * Explain the concept of fast-forward more fully in "git push"
+   documentation, and hint to refer to it from an error message when the
+   command refuses an update to protect the user.
+
+ * The default value for pack.deltacachesize, used by "git repack", is now
+   256M, instead of unbounded.  Otherwise a repack of a moderately sized
+   repository would needlessly eat into swap.
+
+ * Document how "git repack" (hence "git gc") interacts with a repository
+   that borrows its objects from other repositories (e.g. ones created by
+   "git clone -s").
+
+ * "git show" on an annotated tag lacked a delimiting blank line between
+   the tag itself and the contents of the object it tags.
+
+ * "git verify-pack -v" erroneously reported number of objects with too
+   deep delta depths as "chain length 0" objects.
+
+ * Long names of authors and committers outside US-ASCII were sometimes
+   incorrectly shown in "gitweb".
+
+Other minor documentation updates are included.
diff --git a/Documentation/RelNotes/1.6.4.2.txt b/Documentation/RelNotes/1.6.4.2.txt
new file mode 100644 (file)
index 0000000..c11ec01
--- /dev/null
@@ -0,0 +1,32 @@
+GIT v1.6.4.2 Release Notes
+==========================
+
+Fixes since v1.6.4.1
+--------------------
+
+* --date=relative output between 1 and 5 years ago rounded the number of
+    years when saying X years Y months ago, instead of rounding it down.
+
+* "git add -p" did not handle changes in executable bits correctly
+  (a regression around 1.6.3).
+
+* "git apply" did not honor GNU diff's convention to mark the creation/deletion
+  event with UNIX epoch timestamp on missing side.
+
+* "git checkout" incorrectly removed files in a directory pointed by a
+  symbolic link during a branch switch that replaces a directory with
+  a symbolic link.
+
+* "git clean -d -f" happily descended into a subdirectory that is managed by a
+  separate git repository.  It now requires two -f options for safety.
+
+* "git fetch/push" over http transports had two rather grave bugs.
+
+* "git format-patch --cover-letter" did not prepare the cover letter file
+  for use with non-ASCII strings when there are the series contributors with
+  non-ASCII names.
+
+* "git pull origin branch" and "git fetch origin && git merge origin/branch"
+  left different merge messages in the resulting commit.
+
+Other minor documentation updates are included.
diff --git a/Documentation/RelNotes/1.6.4.3.txt b/Documentation/RelNotes/1.6.4.3.txt
new file mode 100644 (file)
index 0000000..5643e65
--- /dev/null
@@ -0,0 +1,29 @@
+GIT v1.6.4.3 Release Notes
+==========================
+
+Fixes since v1.6.4.2
+--------------------
+
+* "git clone" from an empty repository gave unnecessary error message,
+  even though it did everything else correctly.
+
+* "git cvsserver" invoked git commands via "git-foo" style, which has long
+  been deprecated.
+
+* "git fetch" and "git clone" had an extra sanity check to verify the
+  presence of the corresponding *.pack file before downloading *.idx
+  file by issuing a HEAD request.  Github server however sometimes
+  gave 500 (Internal server error) response to HEAD even if a GET
+  request for *.pack file to the same URL would have succeeded, and broke
+  clone over HTTP from some of their repositories.  As a workaround, this
+  verification has been removed (as it is not absolutely necessary).
+
+* "git grep" did not like relative pathname to refer outside the current
+  directory when run from a subdirectory.
+
+* an error message from "git push" was formatted in a very ugly way.
+
+* "git svn" did not quote the subversion user name correctly when
+  running its author-prog helper program.
+
+Other minor documentation updates are included.
diff --git a/Documentation/RelNotes/1.6.4.4.txt b/Documentation/RelNotes/1.6.4.4.txt
new file mode 100644 (file)
index 0000000..0ead45f
--- /dev/null
@@ -0,0 +1,26 @@
+GIT v1.6.4.4 Release Notes
+==========================
+
+Fixes since v1.6.4.4
+--------------------
+
+* The workaround for Github server that sometimes gave 500 (Internal server
+  error) response to HEAD requests in 1.6.4.3 introduced a regression that
+  caused re-fetching projects over http to segfault in certain cases due
+  to uninitialized pointer being freed.
+
+* "git pull" on an unborn branch used to consider anything in the work
+  tree and the index discardable.
+
+* "git diff -b/w" did not work well on the incomplete line at the end of
+  the file, due to an incorrect hashing of lines in the low-level xdiff
+  routines.
+
+* "git checkout-index --prefix=$somewhere" used to work when $somewhere is
+  a symbolic link to a directory elsewhere, but v1.6.4.2 broke it.
+
+* "git unpack-objects --strict", invoked when receive.fsckobjects
+  configuration is set in the receiving repository of "git push", did not
+  properly check the objects, especially the submodule links, it received.
+
+Other minor documentation updates are included.
diff --git a/Documentation/RelNotes/1.6.4.txt b/Documentation/RelNotes/1.6.4.txt
new file mode 100644 (file)
index 0000000..7a90441
--- /dev/null
@@ -0,0 +1,147 @@
+GIT v1.6.4 Release Notes
+========================
+
+With the next major release, "git push" into a branch that is
+currently checked out will be refused by default.  You can choose
+what should happen upon such a push by setting the configuration
+variable receive.denyCurrentBranch in the receiving repository.
+
+To ease the transition plan, the receiving repository of such a
+push running this release will issue a big warning when the
+configuration variable is missing.  Please refer to:
+
+  http://git.or.cz/gitwiki/GitFaq#non-bare
+  http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007
+
+for more details on the reason why this change is needed and the
+transition plan.
+
+For a similar reason, "git push $there :$killed" to delete the branch
+$killed in a remote repository $there, if $killed branch is the current
+branch pointed at by its HEAD, gets a large warning.  You can choose what
+should happen upon such a push by setting the configuration variable
+receive.denyDeleteCurrent in the receiving repository.
+
+
+Updates since v1.6.3
+--------------------
+
+(subsystems)
+
+ * gitweb Perl style clean-up.
+
+ * git-svn updates, including a new --authors-prog option to map author
+   names by invoking an external program, 'git svn reset' to unwind
+   'git svn fetch', support for more than one branches, documenting
+   of the useful --minimize-url feature, new "git svn gc" command, etc.
+
+(portability)
+
+ * We feed iconv with "UTF-8" instead of "utf8"; the former is
+   understood more widely.  Similarly updated test scripts to use
+   encoding names more widely understood (e.g. use "ISO8859-1" instead
+   of "ISO-8859-1").
+
+ * Various portability fixes/workarounds for different vintages of
+   SunOS, IRIX, and Windows.
+
+ * Git-over-ssh transport on Windows supports PuTTY plink and TortoisePlink.
+
+(performance)
+
+ * Many repeated use of lstat() are optimized out in "checkout" codepath.
+
+ * git-status (and underlying git-diff-index --cached) are optimized
+   to take advantage of cache-tree information in the index.
+
+(usability, bells and whistles)
+
+ * "git add --edit" lets users edit the whole patch text to fine-tune what
+   is added to the index.
+
+ * "git am" accepts StGIT series file as its input.
+
+ * "git bisect skip" skips to a more randomly chosen place in the hope
+   to avoid testing a commit that is too close to a commit that is
+   already known to be untestable.
+
+ * "git cvsexportcommit" learned -k option to stop CVS keywords expansion
+
+ * "git fast-export" learned to handle history simplification more
+   gracefully.
+
+ * "git fast-export" learned an option --tag-of-filtered-object to handle
+   dangling tags resulting from history simplification more usefully.
+
+ * "git grep" learned -p option to show the location of the match using the
+   same context hunk marker "git diff" uses.
+
+ * https transport can optionally be told that the used client
+   certificate is password protected, in which case it asks the
+   password only once.
+
+ * "git imap-send" is IPv6 aware.
+
+ * "git log --graph" draws graphs more compactly by using horizontal lines
+   when able.
+
+ * "git log --decorate" shows shorter refnames by stripping well-known
+   refs/* prefix.
+
+ * "git push $name" honors remote.$name.pushurl if present before
+   using remote.$name.url.  In other words, the URL used for fetching
+   and pushing can be different.
+
+ * "git send-email" understands quoted aliases in .mailrc files (might
+   have to be backported to 1.6.3.X).
+
+ * "git send-email" can fetch the sender address from the configuration
+   variable "sendmail.from" (and "sendmail.<identity>.from").
+
+ * "git show-branch" can color its output.
+
+ * "add" and "update" subcommands to "git submodule" learned --reference
+   option to use local clone with references.
+
+ * "git submodule update" learned --rebase option to update checked
+   out submodules by rebasing the local changes.
+
+ * "gitweb" can optionally use gravatar to adorn author/committer names.
+
+(developers)
+
+ * A major part of the "git bisect" wrapper has moved to C.
+
+ * Formatting with the new version of AsciiDoc 8.4.1 is now supported.
+
+Fixes since v1.6.3
+------------------
+
+All of the fixes in v1.6.3.X maintenance series are included in this
+release, unless otherwise noted.
+
+Here are fixes that this release has, but have not been backported to
+v1.6.3.X series.
+
+ * "git diff-tree -r -t" used to omit new or removed directories from
+   the output.  df533f3 (diff-tree -r -t: include added/removed
+   directories in the output, 2009-06-13) may need to be cherry-picked
+   to backport this fix.
+
+ * The way Git.pm sets up a Repository object was not friendly to callers
+   that chdir around.  It now internally records the repository location
+   as an absolute path when autodetected.
+
+ * Removing a section with "git config --remove-section", when its
+   section header has a variable definition on the same line, lost
+   that variable definition.
+
+ * "git rebase -p --onto" used to always leave side branches of a merge
+   intact, even when both branches are subject to rewriting.
+
+ * "git repack" used to faithfully follow grafts and considered true
+   parents recorded in the commit object unreachable from the commit.
+   After such a repacking, you cannot remove grafts without corrupting
+   the repository.
+
+ * "git send-email" did not detect erroneous loops in alias expansion.
diff --git a/Documentation/RelNotes/1.6.5.1.txt b/Documentation/RelNotes/1.6.5.1.txt
new file mode 100644 (file)
index 0000000..309ba18
--- /dev/null
@@ -0,0 +1,20 @@
+GIT v1.6.5.1 Release Notes
+==========================
+
+Fixes since v1.6.5
+------------------
+
+ * An corrupt pack could make codepath to read objects into an
+   infinite loop.
+
+ * Download throughput display was always shown in KiB/s but on fast links
+   it is more appropriate to show it in MiB/s.
+
+ * "git grep -f filename" used uninitialized variable and segfaulted.
+
+ * "git clone -b branch" gave a wrong commit object name to post-checkout
+   hook.
+
+ * "git pull" over http did not work on msys.
+
+Other minor documentation updates are included.
diff --git a/Documentation/RelNotes/1.6.5.2.txt b/Documentation/RelNotes/1.6.5.2.txt
new file mode 100644 (file)
index 0000000..aa7ccce
--- /dev/null
@@ -0,0 +1,19 @@
+GIT v1.6.5.2 Release Notes
+==========================
+
+Fixes since v1.6.5.1
+--------------------
+
+ * Installation of templates triggered a bug in busybox when using tar
+   implementation from it.
+
+ * "git add -i" incorrectly ignored paths that are already in the index
+   if they matched .gitignore patterns.
+
+ * "git describe --always" should have produced some output even there
+   were no tags in the repository, but it didn't.
+
+ * "git ls-files" when showing tracked files incorrectly paid attention
+   to the exclude patterns.
+
+Other minor documentation updates are included.
diff --git a/Documentation/RelNotes/1.6.5.3.txt b/Documentation/RelNotes/1.6.5.3.txt
new file mode 100644 (file)
index 0000000..b2fad1b
--- /dev/null
@@ -0,0 +1,63 @@
+Git v1.6.5.3 Release Notes
+==========================
+
+Fixes since v1.6.5.2
+--------------------
+
+ * info/grafts file didn't ignore trailing CR at the end of lines.
+
+ * Packages generated on newer FC were unreadable by older versions of
+   RPM as the new default is to use stronger hash.
+
+ * output from "git blame" was unreadable when the file ended in an
+   incomplete line.
+
+ * "git add -i/-p" didn't handle deletion of empty files correctly.
+
+ * "git clone" takes up to two parameters, but did not complain when
+   given more arguments than necessary and silently ignored them.
+
+ * "git cvsimport" did not read files given as command line arguments
+   correctly when it is run from a subdirectory.
+
+ * "git diff --color-words -U0" didn't work correctly.
+
+ * The handling of blank lines at the end of file by "git diff/apply
+   --whitespace" was inconsistent with the other kinds of errors.
+   They are now colored, warned against, and fixed the same way as others.
+
+ * There was no way to allow blank lines at the end of file without
+   allowing extra blanks at the end of lines.  You can use blank-at-eof
+   and blank-at-eol whitespace error class to specify them separately.
+   The old trailing-space error class is now a short-hand to set both.
+
+ * "-p" option to "git format-patch" was supposed to suppress diffstat
+   generation, but it was broken since 1.6.1.
+
+ * "git imap-send" did not compile cleanly with newer OpenSSL.
+
+ * "git help -a" outside of a git repository was broken.
+
+ * "git ls-files -i" was supposed to be inverse of "git ls-files" without -i
+   with respect to exclude patterns, but it was broken since 1.6.5.2.
+
+ * "git ls-remote" outside of a git repository over http was broken.
+
+ * "git rebase -i" gave bogus error message when the command word was
+   misspelled.
+
+ * "git receive-pack" that is run in response to "git push" did not run
+   garbage collection nor update-server-info, but in larger hosting sites,
+   these almost always need to be run.  To help site administrators, the
+   command now runs "gc --auto" and "u-s-i" by setting receive.autogc
+   and receive.updateserverinfo configuration variables, respectively.
+
+ * Release notes spelled the package name with incorrect capitalization.
+
+ * "gitweb" did not escape non-ascii characters correctly in the URL.
+
+ * "gitweb" showed "patch" link even for merge commits.
+
+ * "gitweb" showed incorrect links for blob line numbers in pathinfo mode.
+
+Other minor documentation updates are included.
diff --git a/Documentation/RelNotes/1.6.5.4.txt b/Documentation/RelNotes/1.6.5.4.txt
new file mode 100644 (file)
index 0000000..d3a2a3e
--- /dev/null
@@ -0,0 +1,32 @@
+Git v1.6.5.4 Release Notes
+==========================
+
+Fixes since v1.6.5.3
+--------------------
+
+ * "git help" (without argument) used to check if you are in a directory
+   under git control. There was no breakage in behaviour per-se, but this
+   was unnecessary.
+
+ * "git prune-packed" gave progress output even when its standard error is
+   not connected to a terminal; this caused cron jobs that run it to
+   produce crufts.
+
+ * "git pack-objects --all-progress" is an option to ask progress output
+   from write-object phase _if_ progress output were to be produced, and
+   shouldn't have forced the progress output.
+
+ * "git apply -p<n> --directory=<elsewhere>" did not work well for a
+   non-default value of n.
+
+ * "git merge foo HEAD" was misparsed as an old-style invocation of the
+   command and produced a confusing error message.  As it does not specify
+   any other branch to merge, it shouldn't be mistaken as such.  We will
+   remove the old style "git merge <message> HEAD <commit>..."  syntax in
+   future versions, but not in this release,
+
+ * "git merge -m <message> <branch>..." added the standard merge message
+   on its own after user-supplied message, which should have overridden the
+   standard one.
+
+Other minor documentation updates are included.
diff --git a/Documentation/RelNotes/1.6.5.5.txt b/Documentation/RelNotes/1.6.5.5.txt
new file mode 100644 (file)
index 0000000..ecfc57d
--- /dev/null
@@ -0,0 +1,49 @@
+Git v1.6.5.5 Release Notes
+==========================
+
+Fixes since v1.6.5.4
+--------------------
+
+ * Manual pages can be formatted with older xmlto again.
+
+ * GREP_OPTIONS exported from user's environment could have broken
+   our scripted commands.
+
+ * In configuration files, a few variables that name paths can begin with
+   ~/ and ~username/ and they are expanded as expected.  This is not a
+   bugfix but 1.6.6 will have this and without backporting users cannot
+   easily use the same ~/.gitconfig across versions.
+
+ * "git diff -B -M" did the same computation to hash lines of contents
+   twice, and held onto memory after it has used the data in it
+   unnecessarily before it freed.
+
+ * "git diff -B" and "git diff --dirstat" was not counting newly added
+   contents correctly.
+
+ * "git format-patch revisions... -- path" issued an incorrect error
+   message that suggested to use "--" on the command line when path
+   does not exist in the current work tree (it is a separate matter if
+   it makes sense to limit format-patch with pathspecs like that
+   without using the --full-diff option).
+
+ * "git grep -F -i StRiNg" did not work as expected.
+
+ * Enumeration of available merge strategies iterated over the list of
+   commands in a wrong way, sometimes producing an incorrect result.
+
+ * "git shortlog" did not honor the "encoding" header embedded in the
+   commit object like "git log" did.
+
+ * Reading progress messages that come from the remote side while running
+   "git pull" is given precedence over reading the actual pack data to
+   prevent garbled progress message on the user's terminal.
+
+ * "git rebase" got confused when the log message began with certain
+   strings that looked like Subject:, Date: or From: header.
+
+ * "git reset" accidentally run in .git/ directory checked out the
+   work tree contents in there.
+
+
+Other minor documentation updates are included.
diff --git a/Documentation/RelNotes/1.6.5.6.txt b/Documentation/RelNotes/1.6.5.6.txt
new file mode 100644 (file)
index 0000000..a9eaf76
--- /dev/null
@@ -0,0 +1,23 @@
+Git v1.6.5.6 Release Notes
+==========================
+
+Fixes since v1.6.5.5
+--------------------
+
+ * "git add -p" had a regression since v1.6.5.3 that broke deletion of
+   non-empty files.
+
+ * "git archive -o o.zip -- Makefile" produced an archive in o.zip
+   but in POSIX tar format.
+
+ * Error message given to "git pull --rebase" when the user didn't give
+   enough clue as to what branch to integrate with still talked about
+   "merging with" the branch.
+
+ * Error messages given by "git merge" when the merge resulted in a
+   fast-forward still were in plumbing lingo, even though in v1.6.5
+   we reworded messages in other cases.
+
+ * The post-upload-hook run by upload-pack in response to "git fetch" has
+   been removed, due to security concerns (the hook first appeared in
+   1.6.5).
diff --git a/Documentation/RelNotes/1.6.5.7.txt b/Documentation/RelNotes/1.6.5.7.txt
new file mode 100644 (file)
index 0000000..dc5302c
--- /dev/null
@@ -0,0 +1,19 @@
+Git v1.6.5.7 Release Notes
+==========================
+
+Fixes since v1.6.5.6
+--------------------
+
+* If a user specifies a color for a <slot> (i.e. a class of things to show
+  in a particular color) that is known only by newer versions of git
+  (e.g. "color.diff.func" was recently added for upcoming 1.6.6 release),
+  an older version of git should just ignore them.  Instead we diagnosed
+  it as an error.
+
+* With help.autocorrect set to non-zero value, the logic to guess typos
+  in the subcommand name misfired and ran a random nonsense command.
+
+* If a command is run with an absolute path as a pathspec inside a bare
+  repository, e.g. "rev-list HEAD -- /home", the code tried to run
+  strlen() on NULL, which is the result of get_git_work_tree(), and
+  segfaulted.
diff --git a/Documentation/RelNotes/1.6.5.8.txt b/Documentation/RelNotes/1.6.5.8.txt
new file mode 100644 (file)
index 0000000..8b24beb
--- /dev/null
@@ -0,0 +1,28 @@
+Git v1.6.5.8 Release Notes
+==========================
+
+Fixes since v1.6.5.7
+--------------------
+
+* "git count-objects" did not handle packfiles that are bigger than 4G on
+  platforms with 32-bit off_t.
+
+* "git rebase -i" did not abort cleanly if it failed to launch the editor.
+
+* "git blame" did not work well when commit lacked the author name.
+
+* "git fast-import" choked when handling a tag that points at an object
+  that is not a commit.
+
+* "git reset --hard" did not work correctly when GIT_WORK_TREE environment
+  variable is used to point at the root of the true work tree.
+
+* "git grep" fed a buffer that is not NUL-terminated to underlying
+  regexec().
+
+* "git checkout -m other" while on a branch that does not have any commit
+  segfaulted, instead of failing.
+
+* "git branch -a other" should have diagnosed the command as an error.
+
+Other minor documentation updates are also included.
diff --git a/Documentation/RelNotes/1.6.5.txt b/Documentation/RelNotes/1.6.5.txt
new file mode 100644 (file)
index 0000000..ee141c1
--- /dev/null
@@ -0,0 +1,169 @@
+GIT v1.6.5 Release Notes
+========================
+
+In git 1.7.0, which was planned to be the release after 1.6.5, "git
+push" into a branch that is currently checked out will be refused by
+default.
+
+You can choose what should happen upon such a push by setting the
+configuration variable receive.denyCurrentBranch in the receiving
+repository.
+
+Also, "git push $there :$killed" to delete the branch $killed in a remote
+repository $there, when $killed branch is the current branch pointed at by
+its HEAD, will be refused by default.
+
+You can choose what should happen upon such a push by setting the
+configuration variable receive.denyDeleteCurrent in the receiving
+repository.
+
+To ease the transition plan, the receiving repository of such a
+push running this release will issue a big warning when the
+configuration variable is missing.  Please refer to:
+
+  http://git.or.cz/gitwiki/GitFaq#non-bare
+  http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007
+
+for more details on the reason why this change is needed and the
+transition plan.
+
+Updates since v1.6.4
+--------------------
+
+(subsystems)
+
+ * various updates to gitk, git-svn and gitweb.
+
+(portability)
+
+ * more improvements on mingw port.
+
+ * mingw will also give FRSX as the default value for the LESS
+   environment variable when the user does not have one.
+
+ * initial support to compile git on Windows with MSVC.
+
+(performance)
+
+ * On major platforms, the system can be compiled to use with Linus's
+   block-sha1 implementation of the SHA-1 hash algorithm, which
+   outperforms the default fallback implementation we borrowed from
+   Mozilla.
+
+ * Unnecessary inefficiency in deepening of a shallow repository has
+   been removed.
+
+ * "git clone" does not grab objects that it does not need (i.e.
+   referenced only from refs outside refs/heads and refs/tags
+   hierarchy) anymore.
+
+ * The "git" main binary used to link with libcurl, which then dragged
+   in a large number of external libraries.  When using basic plumbing
+   commands in scripts, this unnecessarily slowed things down.  We now
+   implement http/https/ftp transfer as a separate executable as we
+   used to.
+
+ * "git clone" run locally hardlinks or copies the files in .git/ to
+   newly created repository.  It used to give new mtime to copied files,
+   but this delayed garbage collection to trigger unnecessarily in the
+   cloned repository.  We now preserve mtime for these files to avoid
+   this issue.
+
+(usability, bells and whistles)
+
+ * Human writable date format to various options, e.g. --since=yesterday,
+   master@{2000.09.17}, are taught to infer some omitted input properly.
+
+ * A few programs gave verbose "advice" messages to help uninitiated
+   people when issuing error messages.  An infrastructure to allow
+   users to squelch them has been introduced, and a few such messages
+   can be silenced now.
+
+ * refs/replace/ hierarchy is designed to be usable as a replacement
+   of the "grafts" mechanism, with the added advantage that it can be
+   transferred across repositories.
+
+ * "git am" learned to optionally ignore whitespace differences.
+
+ * "git am" handles input e-mail files that has CRLF line endings sensibly.
+
+ * "git am" learned "--scissors" option to allow you to discard early part
+   of an incoming e-mail.
+
+ * "git archive -o output.zip" works without being told what format to
+   use with an explicit "--format=zip".option.
+
+ * "git checkout", "git reset" and "git stash" learned to pick and
+   choose to use selected changes you made, similar to "git add -p".
+
+ * "git clone" learned a "-b" option to pick a HEAD to check out
+   different from the remote's default branch.
+
+ * "git clone" learned --recursive option.
+
+ * "git clone" from a local repository on a different filesystem used to
+   copy individual object files without preserving the old timestamp, giving
+   them extra lifetime in the new repository until they gc'ed.
+
+ * "git commit --dry-run $args" is a new recommended way to ask "what would
+   happen if I try to commit with these arguments."
+
+ * "git commit --dry-run" and "git status" shows conflicted paths in a
+   separate section to make them easier to spot during a merge.
+
+ * "git cvsimport" now supports password-protected pserver access even
+   when the password is not taken from ~/.cvspass file.
+
+ * "git fast-export" learned --no-data option that can be useful when
+   reordering commits and trees without touching the contents of
+   blobs.
+
+ * "git fast-import" has a pair of new front-end in contrib/ area.
+
+ * "git init" learned to mkdir/chdir into a directory when given an
+   extra argument (i.e. "git init this").
+
+ * "git instaweb" optionally can use mongoose as the web server.
+
+ * "git log --decorate" can optionally be told with --decorate=full to
+   give the reference name in full.
+
+ * "git merge" issued an unnecessarily scary message when it detected
+   that the merge may have to touch the path that the user has local
+   uncommitted changes to. The message has been reworded to make it
+   clear that the command aborted, without doing any harm.
+
+ * "git push" can be told to be --quiet.
+
+ * "git push" pays attention to url.$base.pushInsteadOf and uses a URL
+   that is derived from the URL used for fetching.
+
+ * informational output from "git reset" that lists the locally modified
+   paths is made consistent with that of "git checkout $another_branch".
+
+ * "git submodule" learned to give submodule name to scripts run with
+   "foreach" subcommand.
+
+ * various subcommands to "git submodule" learned --recursive option.
+
+ * "git submodule summary" learned --files option to compare the work
+   tree vs the commit bound at submodule path, instead of comparing
+   the index.
+
+ * "git upload-pack", which is the server side support for "git clone" and
+   "git fetch", can call a new post-upload-pack hook for statistics purposes.
+
+(developers)
+
+ * With GIT_TEST_OPTS="--root=/p/a/t/h", tests can be run outside the
+   source directory; using tmpfs may give faster turnaround.
+
+ * With NO_PERL_MAKEMAKER set, DESTDIR= is now honoured, so you can
+   build for one location, and install into another location to tar it
+   up.
+
+Fixes since v1.6.4
+------------------
+
+All of the fixes in v1.6.4.X maintenance series are included in this
+release, unless otherwise noted.
diff --git a/Documentation/RelNotes/1.6.6.1.txt b/Documentation/RelNotes/1.6.6.1.txt
new file mode 100644 (file)
index 0000000..f1d0a4a
--- /dev/null
@@ -0,0 +1,37 @@
+Git v1.6.6.1 Release Notes
+==========================
+
+Fixes since v1.6.6
+------------------
+
+ * "git blame" did not work well when commit lacked the author name.
+
+ * "git branch -a name" wasn't diagnosed as an error.
+
+ * "git count-objects" did not handle packfiles that are bigger than 4G on
+   platforms with 32-bit off_t.
+
+ * "git checkout -m other" while on a branch that does not have any commit
+   segfaulted, instead of failing.
+
+ * "git fast-import" choked when fed a tag that do not point at a
+   commit.
+
+ * "git grep" finding from work tree files could have fed garbage to
+   the underlying regexec(3).
+
+ * "git grep -L" didn't show empty files (they should never match, and
+   they should always appear in -L output as unmatching).
+
+ * "git rebase -i" did not abort cleanly if it failed to launch the editor.
+
+ * "git reset --hard" did not work correctly when GIT_WORK_TREE environment
+   variable is used to point at the root of the true work tree.
+
+ * http-backend was not listed in the command list in the documentation.
+
+ * Building on FreeBSD (both 7 and 8) needs OLD_ICONV set in the Makefile
+
+ * "git checkout -m some-branch" while on an unborn branch crashed.
+
+Other minor documentation updates are included.
diff --git a/Documentation/RelNotes/1.6.6.2.txt b/Documentation/RelNotes/1.6.6.2.txt
new file mode 100644 (file)
index 0000000..4eaddc0
--- /dev/null
@@ -0,0 +1,46 @@
+Git v1.6.6.2 Release Notes
+==========================
+
+Fixes since v1.6.6.1
+--------------------
+
+ * recursive merge didn't correctly diagnose its own programming errors,
+   and instead caused the caller to segfault.
+
+ * The new "smart http" aware clients probed the web servers to see if
+   they support smart http, but did not fall back to dumb http transport
+   correctly with some servers.
+
+ * Time based reflog syntax e.g. "@{yesterday}" didn't diagnose a misspelled
+   time specification and instead assumed "@{now}".
+
+ * "git archive HEAD -- no-such-directory" produced an empty archive
+   without complaining.
+
+ * "git blame -L start,end -- file" misbehaved when given a start that is
+   larger than the number of lines in the file.
+
+ * "git checkout -m" didn't correctly call custom merge backend supplied
+   by the end user.
+
+ * "git config -f <file>" misbehaved when run from a subdirectory.
+
+ * "git cvsserver" didn't like having regex metacharacters (e.g. '+') in
+   CVSROOT environment.
+
+ * "git fast-import" did not correctly handle large blobs that may
+   bust the pack size limit.
+
+ * "git gui" is supposed to work even when launched from inside a .git
+   directory.
+
+ * "git gui" misbehaved when applying a hunk that ends with deletion.
+
+ * "git imap-send" did not honor imap.preformattedHTML as documented.
+
+ * "git log" family incorrectly showed the commit notes unconditionally by
+   mistake, which was especially irritating when running "git log --oneline".
+
+ * "git status" shouldn't require an write access to the repository.
+
+Other minor documentation updates are included.
diff --git a/Documentation/RelNotes/1.6.6.txt b/Documentation/RelNotes/1.6.6.txt
new file mode 100644 (file)
index 0000000..c50b59c
--- /dev/null
@@ -0,0 +1,224 @@
+Git v1.6.6 Release Notes
+========================
+
+Notes on behaviour change
+-------------------------
+
+ * In this release, "git fsck" defaults to "git fsck --full" and
+   checks packfiles, and because of this it will take much longer to
+   complete than before.  If you prefer a quicker check only on loose
+   objects (the old default), you can say "git fsck --no-full".  This
+   has been supported by 1.5.4 and newer versions of git, so it is
+   safe to write it in your script even if you use slightly older git
+   on some of your machines.
+
+Preparing yourselves for compatibility issues in 1.7.0
+------------------------------------------------------
+
+In git 1.7.0, which is planned to be the release after 1.6.6, there will
+be a handful of behaviour changes that will break backward compatibility.
+
+These changes were discussed long time ago and existing behaviours have
+been identified as more problematic to the userbase than keeping them for
+the sake of backward compatibility.
+
+When necessary, a transition strategy for existing users has been designed
+not to force them running around setting configuration variables and
+updating their scripts in order to either keep the traditional behaviour
+or adjust to the new behaviour, on the day their sysadmin decides to install
+the new version of git.  When we switched from "git-foo" to "git foo" in
+1.6.0, even though the change had been advertised and the transition
+guide had been provided for a very long time, the users procrastinated
+during the entire transition period, and ended up panicking on the day
+their sysadmins updated their git installation.  We are trying to avoid
+repeating that unpleasantness in the 1.7.0 release.
+
+For changes decided to be in 1.7.0, commands that will be affected
+have been much louder to strongly discourage such procrastination, and
+they continue to be in this release.  If you have been using recent
+versions of git, you would have seen warnings issued when you used
+features whose behaviour will change, with a clear instruction on how
+to keep the existing behaviour if you want to.  You hopefully are
+already well prepared.
+
+Of course, we have also been giving "this and that will change in
+1.7.0; prepare yourselves" warnings in the release notes and
+announcement messages for the past few releases.  Let's see how well
+users will fare this time.
+
+ * "git push" into a branch that is currently checked out (i.e. pointed by
+   HEAD in a repository that is not bare) will be refused by default.
+
+   Similarly, "git push $there :$killed" to delete the branch $killed
+   in a remote repository $there, when $killed branch is the current
+   branch pointed at by its HEAD, will be refused by default.
+
+   Setting the configuration variables receive.denyCurrentBranch and
+   receive.denyDeleteCurrent to 'ignore' in the receiving repository
+   can be used to override these safety features.  Versions of git
+   since 1.6.2 have issued a loud warning when you tried to do these
+   operations without setting the configuration, so repositories of
+   people who still need to be able to perform such a push should
+   already have been future proofed.
+
+   Please refer to:
+
+   http://git.or.cz/gitwiki/GitFaq#non-bare
+   http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007
+
+   for more details on the reason why this change is needed and the
+   transition process that already took place so far.
+
+ * "git send-email" will not make deep threads by default when sending a
+   patch series with more than two messages.  All messages will be sent
+   as a reply to the first message, i.e. cover letter.  Git 1.6.6 (this
+   release) will issue a warning about the upcoming default change, when
+   it uses the traditional "deep threading" behaviour as the built-in
+   default.  To squelch the warning but still use the "deep threading"
+   behaviour, give --chain-reply-to option or set sendemail.chainreplyto
+   to true.
+
+   It has been possible to configure send-email to send "shallow thread"
+   by setting sendemail.chainreplyto configuration variable to false.
+   The only thing 1.7.0 release will do is to change the default when
+   you haven't configured that variable.
+
+ * "git status" will not be "git commit --dry-run".  This change does not
+   affect you if you run the command without pathspec.
+
+   Nobody sane found the current behaviour of "git status Makefile" useful
+   nor meaningful, and it confused users.  "git commit --dry-run" has been
+   provided as a way to get the current behaviour of this command since
+   1.6.5.
+
+ * "git diff" traditionally treated various "ignore whitespace" options
+   only as a way to filter the patch output.  "git diff --exit-code -b"
+   exited with non-zero status even if all changes were about changing the
+   amount of whitespace and nothing else.  and "git diff -b" showed the
+   "diff --git" header line for such a change without patch text.
+
+   In 1.7.0, the "ignore whitespaces" will affect the semantics of the
+   diff operation itself.  A change that does not affect anything but
+   whitespaces will be reported with zero exit status when run with
+   --exit-code, and there will not be "diff --git" header for such a
+   change.
+
+
+Updates since v1.6.5
+--------------------
+
+(subsystems)
+
+ * various gitk updates including use of themed widgets under Tk 8.5,
+   Japanese translation, a fix to a bug when running "gui blame" from
+   a subdirectory, etc.
+
+ * various git-gui updates including new translations, wm states fixes,
+   Tk bug workaround after quitting, improved heuristics to trigger gc,
+   etc.
+
+ * various git-svn updates.
+
+ * "git fetch" over http learned a new mode that is different from the
+   traditional "dumb commit walker".
+
+(portability)
+
+ * imap-send can be built on mingw port.
+
+(performance)
+
+ * "git diff -B" has smaller memory footprint.
+
+(usability, bells and whistles)
+
+ * The object replace mechanism can be bypassed with --no-replace-objects
+   global option given to the "git" program.
+
+ * In configuration files, a few variables that name paths can begin with ~/
+   and ~username/ and they are expanded as expected.
+
+ * "git subcmd -h" now shows short usage help for many more subcommands.
+
+ * "git bisect reset" can reset to an arbitrary commit.
+
+ * "git checkout frotz" when there is no local branch "frotz" but there
+   is only one remote tracking branch "frotz" is taken as a request to
+   start the named branch at the corresponding remote tracking branch.
+
+ * "git commit -c/-C/--amend" can be told with a new "--reset-author" option
+   to ignore authorship information in the commit it is taking the message
+   from.
+
+ * "git describe" can be told to add "-dirty" suffix with "--dirty" option.
+
+ * "git diff" learned --submodule option to show a list of one-line logs
+   instead of differences between the commit object names.
+
+ * "git diff" learned to honor diff.color.func configuration to paint
+   function name hint printed on the hunk header "@@ -j,k +l,m @@" line
+   in the specified color.
+
+ * "git fetch" learned --all and --multiple options, to run fetch from
+   many repositories, and --prune option to remove remote tracking
+   branches that went stale.  These make "git remote update" and "git
+   remote prune" less necessary (there is no plan to remove "remote
+   update" nor "remote prune", though).
+
+ * "git fsck" by default checks the packfiles (i.e. "--full" is the
+   default); you can turn it off with "git fsck --no-full".
+
+ * "git grep" can use -F (fixed strings) and -i (ignore case) together.
+
+ * import-tars contributed fast-import frontend learned more types of
+   compressed tarballs.
+
+ * "git instaweb" knows how to talk with mod_cgid to apache2.
+
+ * "git log --decorate" shows the location of HEAD as well.
+
+ * "git log" and "git rev-list" learned to take revs and pathspecs from
+   the standard input with the new "--stdin" option.
+
+ * "--pretty=format" option to "log" family of commands learned:
+
+   . to wrap text with the "%w()" specifier.
+   . to show reflog information with "%g[sdD]" specifier.
+
+ * "git notes" command to annotate existing commits.
+
+ * "git merge" (and "git pull") learned --ff-only option to make it fail
+   if the merge does not result in a fast-forward.
+
+ * "git mergetool" learned to use p4merge.
+
+ * "git rebase -i" learned "reword" that acts like "edit" but immediately
+   starts an editor to tweak the log message without returning control to
+   the shell, which is done by "edit" to give an opportunity to tweak the
+   contents.
+
+ * "git send-email" can be told with "--envelope-sender=auto" to use the
+   same address as "From:" address as the envelope sender address.
+
+ * "git send-email" will issue a warning when it defaults to the
+   --chain-reply-to behaviour without being told by the user and
+   instructs to prepare for the change of the default in 1.7.0 release.
+
+ * In "git submodule add <repository> <path>", <path> is now optional and
+   inferred from <repository> the same way "git clone <repository>" does.
+
+ * "git svn" learned to read SVN 1.5+ and SVK merge tickets.
+
+ * "git svn" learned to recreate empty directories tracked only by SVN.
+
+ * "gitweb" can optionally render its "blame" output incrementally (this
+   requires JavaScript on the client side).
+
+ * Author names shown in gitweb output are links to search commits by the
+   author.
+
+Fixes since v1.6.5
+------------------
+
+All of the fixes in v1.6.5.X maintenance series are included in this
+release, unless otherwise noted.
diff --git a/Documentation/RelNotes/1.7.0.1.txt b/Documentation/RelNotes/1.7.0.1.txt
new file mode 100644 (file)
index 0000000..8ff5bca
--- /dev/null
@@ -0,0 +1,35 @@
+Git v1.7.0.1 Release Notes
+==========================
+
+Fixes since v1.7.0
+------------------
+
+ * In a freshly created repository "rev-parse HEAD^0" complained that
+   it is dangling symref, even though "rev-parse HEAD" didn't.
+
+ * "git show :no-such-name" tried to access the index without bounds
+   check, leading to a potential segfault.
+
+ * Message from "git cherry-pick" was harder to read and use than necessary
+   when it stopped due to conflicting changes.
+
+ * We referred to ".git/refs/" throughout the documentation when we
+   meant to talk about abstract notion of "ref namespace".  Because
+   people's repositories often have packed refs these days, this was
+   confusing.
+
+ * "git diff --output=/path/that/cannot/be/written" did not correctly
+   error out.
+
+ * "git grep -e -pattern-that-begin-with-dash paths..." could not be
+   spelled as "git grep -- -pattern-that-begin-with-dash paths..." which
+   would be a GNU way to use "--" as "end of options".
+
+ * "git grep" compiled with threading support tried to access an
+   uninitialized mutex on boxes with a single CPU.
+
+ * "git stash pop -q --index" failed because the unnecessary --index
+   option was propagated to "git stash drop" that is internally run at the
+   end.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.0.2.txt b/Documentation/RelNotes/1.7.0.2.txt
new file mode 100644 (file)
index 0000000..fcb46ca
--- /dev/null
@@ -0,0 +1,40 @@
+Git v1.7.0.2 Release Notes
+==========================
+
+Fixes since v1.7.0.1
+--------------------
+
+ * GIT_PAGER was not honored consistently by some scripted Porcelains, most
+   notably "git am".
+
+ * updating working tree files after telling git to add them to the
+   index and while it is still working created garbage object files in
+   the repository without diagnosing it as an error.
+
+ * "git bisect -- pathspec..." did not diagnose an error condition properly when
+   the simplification with given pathspec made the history empty.
+
+ * "git rev-list --cherry-pick A...B" now has an obvious optimization when the
+   histories haven't diverged (i.e. when one end is an ancestor of the other).
+
+ * "git diff --quiet -w" did not work as expected.
+
+ * "git fast-import" didn't work with a large input, as it lacked support
+   for producing the pack index in v2 format.
+
+ * "git imap-send" didn't use CRLF line endings over the imap protocol
+   when storing its payload to the draft box, violating RFC 3501.
+
+ * "git log --format='%w(x,y,z)%b'" and friends that rewrap message
+   has been optimized for utf-8 payload.
+
+ * Error messages generated on the receiving end did not come back to "git
+   push".
+
+ * "git status" in 1.7.0 lacked the optimization we used to have in 1.6.X series
+   to speed up scanning of large working tree.
+
+ * "gitweb" did not diagnose parsing errors properly while reading tis configuration
+   file.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.0.3.txt b/Documentation/RelNotes/1.7.0.3.txt
new file mode 100644 (file)
index 0000000..3b35573
--- /dev/null
@@ -0,0 +1,34 @@
+Git v1.7.0.3 Release Notes
+==========================
+
+Fixes since v1.7.0.2
+--------------------
+
+ * Object files are created in a more ACL friendly way in repositories
+   where group permission is ACL controlled.
+
+ * "git add -i" didn't handle a deleted path very well.
+
+ * "git blame" padded line numbers with one extra SP when the total number
+   of lines was one less than multiple of ten due to an off-by-one error.
+
+ * "git fetch --all/--multi" used to discard information for remotes that
+   are fetched earlier.
+
+ * "git log --author=me --grep=it" tried to find commits that have "it"
+   or are written by "me", instead of the ones that have "it" _and_ are
+   written by "me".
+
+ * "git log -g branch" misbehaved when there was no entries in the reflog
+   for the named branch.
+
+ * "git mailinfo" (hence "git am") incorrectly removed initial indent from
+   paragraphs.
+
+ * "git prune" and "git reflog" (hence "git gc" as well) didn't honor
+   an instruction never to expire by setting gc.reflogexpire to never.
+
+ * "git push" misbehaved when branch.<name>.merge was configured without
+   matching branch.<name>.remote.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.0.4.txt b/Documentation/RelNotes/1.7.0.4.txt
new file mode 100644 (file)
index 0000000..cf7f60e
--- /dev/null
@@ -0,0 +1,27 @@
+Git v1.7.0.4 Release Notes
+==========================
+
+Fixes since v1.7.0.3
+--------------------
+
+ * Optimized ntohl/htonl on big-endian machines were broken.
+
+ * Color values given to "color.<cmd>.<slot>" configuration can now have
+   more than one attributes (e.g. "bold ul").
+
+ * "git add -u nonexistent-path" did not complain.
+
+ * "git apply --whitespace=fix" didn't work well when an early patch in
+   a patch series adds trailing blank lines and a later one depended on
+   such a block of blank lines at the end.
+
+ * "git fast-export" didn't check error status and stop when marks file
+   cannot be opened.
+
+ * "git format-patch --ignore-if-in-upstream" gave unwarranted errors
+   when the range was empty, instead of silently finishing.
+
+ * "git remote prune" did not detect remote tracking refs that became
+   dangling correctly.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.0.5.txt b/Documentation/RelNotes/1.7.0.5.txt
new file mode 100644 (file)
index 0000000..3149c91
--- /dev/null
@@ -0,0 +1,26 @@
+Git v1.7.0.5 Release Notes
+==========================
+
+Fixes since v1.7.0.4
+--------------------
+
+ * "git daemon" failed to compile on platforms without sockaddr_storage type.
+
+ * Output from "git rev-list --pretty=oneline" was unparsable when a
+   commit did not have any message, which is abnormal but possible in a
+   repository converted from foreign scm.
+
+ * "git stash show <commit-that-is-not-a-stash>" gave an error message
+   that was not so useful.  Reworded the message to "<it> is not a
+   stash".
+
+ * Python scripts in contrib/ area now start with "#!/usr/bin/env python"
+   to honor user's PATH.
+
+ * "git imap-send" used to mistake any line that begins with "From " as a
+   message separator in format-patch output.
+
+ * Smart http server backend failed to report an internal server error and
+   infinitely looped instead after output pipe was closed.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.0.6.txt b/Documentation/RelNotes/1.7.0.6.txt
new file mode 100644 (file)
index 0000000..b2852b6
--- /dev/null
@@ -0,0 +1,13 @@
+Git v1.7.0.6 Release Notes
+==========================
+
+Fixes since v1.7.0.5
+--------------------
+
+ * "git diff --stat" used "int" to count the size of differences,
+   which could result in overflowing.
+
+ * "git rev-list --abbrev-commit" defaulted to 40-byte abbreviations, unlike
+   newer tools in the git toolset.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.0.7.txt b/Documentation/RelNotes/1.7.0.7.txt
new file mode 100644 (file)
index 0000000..d0cb7ca
--- /dev/null
@@ -0,0 +1,16 @@
+Git v1.7.0.7 Release Notes
+==========================
+
+Fixes since v1.7.0.6
+--------------------
+
+ * "make NO_CURL=NoThanks install" was broken.
+
+ * An overlong line after ".gitdir: " in a git file caused out of bounds
+   access to an array on the stack.
+
+ * "git config --path conf.var" to attempt to expand a variable conf.var
+   that uses "~/" short-hand segfaulted when $HOME environment variable
+   was not set.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.0.txt b/Documentation/RelNotes/1.7.0.txt
new file mode 100644 (file)
index 0000000..0bb8c0b
--- /dev/null
@@ -0,0 +1,214 @@
+Git v1.7.0 Release Notes
+========================
+
+Notes on behaviour change
+-------------------------
+
+ * "git push" into a branch that is currently checked out (i.e. pointed at by
+   HEAD in a repository that is not bare) is refused by default.
+
+   Similarly, "git push $there :$killed" to delete the branch $killed
+   in a remote repository $there, when $killed branch is the current
+   branch pointed at by its HEAD, will be refused by default.
+
+   Setting the configuration variables receive.denyCurrentBranch and
+   receive.denyDeleteCurrent to 'ignore' in the receiving repository
+   can be used to override these safety features.
+
+ * "git send-email" does not make deep threads by default when sending a
+   patch series with more than two messages.  All messages will be sent
+   as a reply to the first message, i.e. cover letter.
+
+   It has been possible already to configure send-email to send "shallow thread"
+   by setting sendemail.chainreplyto configuration variable to false.  The
+   only thing this release does is to change the default when you haven't
+   configured that variable.
+
+ * "git status" is not "git commit --dry-run" anymore.  This change does
+   not affect you if you run the command without argument.
+
+ * "git diff" traditionally treated various "ignore whitespace" options
+   only as a way to filter the patch output.  "git diff --exit-code -b"
+   exited with non-zero status even if all changes were about changing the
+   amount of whitespace and nothing else;  and "git diff -b" showed the
+   "diff --git" header line for such a change without patch text.
+
+   In this release, the "ignore whitespaces" options affect the semantics
+   of the diff operation.  A change that does not affect anything but
+   whitespaces is reported with zero exit status when run with
+   --exit-code, and there is no "diff --git" header for such a change.
+
+ * External diff and textconv helpers are now executed using the shell.
+   This makes them consistent with other programs executed by git, and
+   allows you to pass command-line parameters to the helpers. Any helper
+   paths containing spaces or other metacharacters now need to be
+   shell-quoted.  The affected helpers are GIT_EXTERNAL_DIFF in the
+   environment, and diff.*.command and diff.*.textconv in the config
+   file.
+
+ * The --max-pack-size argument to 'git repack', 'git pack-objects', and
+   'git fast-import' was assuming the provided size to be expressed in MiB,
+   unlike the corresponding config variable and other similar options accepting
+   a size value.  It is now expecting a size expressed in bytes, with a possible
+   unit suffix of 'k', 'm', or 'g'.
+
+Updates since v1.6.6
+--------------------
+
+(subsystems)
+
+ * "git fast-import" updates; adds "option" and "feature" to detect the
+   mismatch between fast-import and the frontends that produce the input
+   stream.
+
+ * "git svn" support of subversion "merge tickets" and miscellaneous fixes.
+
+ * "gitk" and "git gui" translation updates.
+
+ * "gitweb" updates (code clean-up, load checking etc.)
+
+(portability)
+
+ * Some more MSVC portability patches for msysgit port.
+
+ * Minimum Pthreads emulation for msysgit port.
+
+(performance)
+
+ * More performance improvement patches for msysgit port.
+
+(usability, bells and whistles)
+
+ * More commands learned "--quiet" and "--[no-]progress" options.
+
+ * Various commands given by the end user (e.g. diff.type.textconv,
+   and GIT_EDITOR) can be specified with command line arguments.  E.g. it
+   is now possible to say "[diff "utf8doc"] textconv = nkf -w".
+
+ * "sparse checkout" feature allows only part of the work tree to be
+   checked out.
+
+ * HTTP transfer can use authentication scheme other than basic
+   (i.e./e.g. digest).
+
+ * Switching from a version of superproject that used to have a submodule
+   to another version of superproject that no longer has it did not remove
+   the submodule directory when it should (namely, when you are not
+   interested in the submodule at all and didn't clone/checkout).
+
+ * A new attribute conflict-marker-size can be used to change the size of
+   the conflict markers from the default 7; this is useful when tracked
+   contents (e.g. git-merge documentation) have strings that resemble the
+   conflict markers.
+
+ * A new syntax "<branch>@{upstream}" can be used on the command line to
+   substitute the name of the "upstream" of the branch.  Missing branch
+   defaults to the current branch, so "git fetch && git merge @{upstream}"
+   will be equivalent to "git pull".
+
+ * "git am --resolved" has a synonym "git am --continue".
+
+ * "git branch --set-upstream" can be used to update the (surprise!) upstream,
+   i.e. where the branch is supposed to pull and merge from (or rebase onto).
+
+ * "git checkout A...B" is a way to detach HEAD at the merge base between
+   A and B.
+
+ * "git checkout -m path" to reset the work tree file back into the
+   conflicted state works even when you already ran "git add path" and
+   resolved the conflicts.
+
+ * "git commit --date='<date>'" can be used to override the author date
+   just like "git commit --author='<name> <email>'" can be used to
+   override the author identity.
+
+ * "git commit --no-status" can be used to omit the listing of the index
+   and the work tree status in the editor used to prepare the log message.
+
+ * "git commit" warns a bit more aggressively until you configure user.email,
+   whose default value almost always is not (and fundamentally cannot be)
+   what you want.
+
+ * "git difftool" has been extended to make it easier to integrate it
+   with gitk.
+
+ * "git fetch --all" can now be used in place of "git remote update".
+
+ * "git grep" does not rely on external grep anymore.  It can use more than
+   one thread to accelerate the operation.
+
+ * "git grep" learned "--quiet" option.
+
+ * "git log" and friends learned "--glob=heads/*" syntax that is a more
+   flexible way to complement "--branches/--tags/--remotes".
+
+ * "git merge" learned to pass options specific to strategy-backends.  E.g.
+
+    - "git merge -Xsubtree=path/to/directory" can be used to tell the subtree
+      strategy how much to shift the trees explicitly.
+
+    - "git merge -Xtheirs" can be used to auto-merge as much as possible,
+      while discarding your own changes and taking merged version in
+      conflicted regions.
+
+ * "git push" learned "git push origin --delete branch", a syntactic sugar
+   for "git push origin :branch".
+
+ * "git push" learned "git push --set-upstream origin forker:forkee" that
+   lets you configure your "forker" branch to later pull from "forkee"
+   branch at "origin".
+
+ * "git rebase --onto A...B" means the history is replayed on top of the
+   merge base between A and B.
+
+ * "git rebase -i" learned new action "fixup" that squashes the change
+   but does not affect existing log message.
+
+ * "git rebase -i" also learned --autosquash option that is useful
+   together with the new "fixup" action.
+
+ * "git remote" learned set-url subcommand that updates (surprise!) url
+   for an existing remote nickname.
+
+ * "git rerere" learned "forget path" subcommand.  Together with "git
+   checkout -m path" it will be useful when you recorded a wrong
+   resolution.
+
+ * Use of "git reset --merge" has become easier when resetting away a
+   conflicted mess left in the work tree.
+
+ * "git rerere" had rerere.autoupdate configuration but there was no way
+   to countermand it from the command line; --no-rerere-autoupdate option
+   given to "merge", "revert", etc. fixes this.
+
+ * "git status" learned "-s(hort)" output format.
+
+(developers)
+
+ * The infrastructure to build foreign SCM interface has been updated.
+
+ * Many more commands are now built-in.
+
+ * THREADED_DELTA_SEARCH is no more.  If you build with threads, delta
+   compression will always take advantage of it.
+
+Fixes since v1.6.6
+------------------
+
+All of the fixes in v1.6.6.X maintenance series are included in this
+release, unless otherwise noted.
+
+ * "git branch -d branch" used to refuse deleting the branch even when
+   the branch is fully merged to its upstream branch if it is not merged
+   to the current branch.  It now deletes it in such a case.
+
+ * "filter-branch" command incorrectly said --prune-empty and --filter-commit
+   were incompatible; the latter should be read as --commit-filter.
+
+ * When using "git status" or asking "git diff" to compare the work tree
+   with something, they used to consider that a checked-out submodule with
+   uncommitted changes is not modified; this could cause people to forget
+   committing these changes in the submodule before committing in the
+   superproject. They now consider such a change as a modification and
+   "git diff" will append a "-dirty" to the work tree side when generating
+   patch output or when used with the --submodule option.
diff --git a/Documentation/RelNotes/1.7.1.1.txt b/Documentation/RelNotes/1.7.1.1.txt
new file mode 100644 (file)
index 0000000..3f6b314
--- /dev/null
@@ -0,0 +1,96 @@
+Git v1.7.1.1 Release Notes
+==========================
+
+Fixes since v1.7.1
+------------------
+
+ * Authentication over http transport can now be made lazily, in that the
+   request can first go to a URL without username, get a 401 response and
+   then the client will ask for the username to use.
+
+ * We used to mistakenly think "../work" is a subdirectory of the current
+   directory when we are in "../work-xyz".
+
+ * The attribute mechanism now allows an entry that uses an attribute
+   macro that set/unset one attribute, immediately followed by an
+   overriding setting; this makes attribute macros much easier to use.
+
+ * We didn't recognize timezone "Z" as a synonym for "UTC" (75b37e70).
+
+ * In 1.7.0, read-tree and user commands that use the mechanism such as
+   checkout and merge were fixed to handle switching between branches one
+   of which has a file while the other has a directory at the same path
+   correctly even when there are some "confusing" pathnames in them.  But
+   the algorithm used for this fix was suboptimal and had a terrible
+   performance degradation especially in larger trees.
+
+ * "git am -3" did not show diagnosis when the patch in the message was corrupt.
+
+ * After "git apply --whitespace=fix" removed trailing blank lines in an
+   patch in a patch series, it failed to apply later patches that depend
+   on the presence of such blank lines.
+
+ * "git bundle --stdin" segfaulted.
+
+ * "git checkout" and "git rebase" overwrote paths that are marked "assume
+   unchanged".
+
+ * "git commit --amend" on a commit with an invalid author-name line that
+   lacks the display name didn't work.
+
+ * "git describe" did not tie-break tags that point at the same commit
+   correctly; newer ones are preferred by paying attention to the
+   tagger date now.
+
+ * "git diff" used to tell underlying xdiff machinery to work very hard to
+   minimize the output, but this often was spending too many extra cycles
+   for very little gain.
+
+ * "git diff --color" did not paint extended diff headers per line
+   (i.e. the coloring escape sequence didn't end at the end of line),
+   which confused "less -R".
+
+ * "git fetch" over HTTP verifies the downloaded packfiles more robustly.
+
+ * The memory usage by "git index-pack" (run during "git fetch" and "git
+   push") got leaner.
+
+ * "GIT_DIR=foo.git git init --bare bar.git" created foo.git instead of bar.git.
+
+ * "git log --abbrev=$num --format='%h' ignored --abbrev=$num.
+
+ * "git ls-files ../out/side/cwd" refused to work.
+
+ * "git merge --log" used to replace the custom message given by "-m" with
+   the shortlog, instead of appending to it.
+
+ * "git notes copy" without any other argument segfaulted.
+
+ * "git pull" accepted "--dry-run", gave it to underlying "git fetch" but
+   ignored the option itself, resulting in a bogus attempt to merge
+   unrelated commit.
+
+ * "git rebase" did not faithfully reproduce a malformed author ident, that
+   is often seen in a repository converted from foreign SCMs.
+
+ * "git reset --hard" started from a wrong directory and a working tree in
+   a nonstandard location is in use got confused.
+
+ * "git send-email" lacked a way to specify the domainname used in the
+   EHLO/HELO exchange, causing rejected connection from picky servers.
+   It learned --smtp-domain option to solve this issue.
+
+ * "git send-email" did not declare a content-transfer-encoding and
+   content-type even when its payload needs to be sent in 8-bit.
+
+ * "git show -C -C" and other corner cases lost diff metainfo output
+   in 1.7.0.
+
+ * "git stash" incorrectly lost paths in the working tree that were
+   previously removed from the index.
+
+ * "git status" stopped refreshing the index by mistake in 1.7.1.
+
+ * "git status" showed excess "hints" even when advice.statusHints is set to false.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.1.2.txt b/Documentation/RelNotes/1.7.1.2.txt
new file mode 100644 (file)
index 0000000..61ba14e
--- /dev/null
@@ -0,0 +1,28 @@
+Git v1.7.1.2 Release Notes
+==========================
+
+Fixes since v1.7.1.1
+--------------------
+
+ * "git commit" did not honor GIT_REFLOG_ACTION environment variable, resulting
+   reflog messages for cherry-pick and revert actions to be recorded as "commit".
+
+ * "git clone/fetch/pull" issued an incorrect error message when a ref and
+   a symref that points to the ref were updated at the same time.  This
+   obviously would update them to the same value, and should not result in
+   an error condition.
+
+ * "git diff" inside a tree with many pathnames that have certain
+   characters has become very slow in 1.7.0 by mistake.
+
+ * "git rev-parse --parseopt --stop-at-non-option" did not stop at non option
+   when --keep-dashdash was in effect.
+
+ * An overlong line after ".gitdir: " in a git file caused out of bounds
+   access to an array on the stack.
+
+ * "git config --path conf.var" to attempt to expand a variable conf.var
+   that uses "~/" short-hand segfaulted when $HOME environment variable
+   was not set.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.1.txt b/Documentation/RelNotes/1.7.1.txt
new file mode 100644 (file)
index 0000000..9d89fed
--- /dev/null
@@ -0,0 +1,89 @@
+Git v1.7.1 Release Notes
+========================
+
+Updates since v1.7.0
+--------------------
+
+ * Eric Raymond is the maintainer of updated CIAbot scripts, in contrib/.
+
+ * gitk updates.
+
+ * Some commands (e.g. svn and http interfaces) that interactively ask
+   for a password can be told to use an external program given via
+   GIT_ASKPASS.
+
+ * Conflict markers that lead the common ancestor in diff3-style output
+   now have a label, which hopefully would help third-party tools that
+   expect one.
+
+ * Comes with an updated bash-completion script.
+
+ * "git am" learned "--keep-cr" option to handle inputs that are
+   a mixture of changes to files with and without CRLF line endings.
+
+ * "git cvsimport" learned -R option to leave revision mapping between
+   CVS revisions and resulting git commits.
+
+ * "git diff --submodule" notices and describes dirty submodules.
+
+ * "git for-each-ref" learned %(symref), %(symref:short) and %(flag)
+   tokens.
+
+ * "git hash-object --stdin-paths" can take "--no-filters" option now.
+
+ * "git init" can be told to look at init.templatedir configuration
+   variable (obviously that has to come from either /etc/gitconfig or
+   $HOME/.gitconfig).
+
+ * "git grep" learned "--no-index" option, to search inside contents that
+   are not managed by git.
+
+ * "git grep" learned --color=auto/always/never.
+
+ * "git grep" learned to paint filename and line-number in colors.
+
+ * "git log -p --first-parent -m" shows one-parent diff for merge
+   commits, instead of showing combined diff.
+
+ * "git merge-file" learned to use custom conflict marker size and also
+   to use the "union merge" behaviour.
+
+ * "git notes" command has been rewritten in C and learned many commands
+   and features to help you carry notes forward across rebases and amends.
+
+ * "git request-pull" identifies the commit the request is relative to in
+   a more readable way.
+
+ * "git reset" learned "--keep" option that lets you discard commits
+   near the tip while preserving your local changes in a way similar
+   to how "git checkout branch" does.
+
+ * "git status" notices and describes dirty submodules.
+
+ * "git svn" should work better when interacting with repositories
+   with CRLF line endings.
+
+ * "git imap-send" learned to support CRAM-MD5 authentication.
+
+ * "gitweb" installation procedure can use "minified" js/css files
+   better.
+
+ * Various documentation updates.
+
+Fixes since v1.7.0
+------------------
+
+All of the fixes in v1.7.0.X maintenance series are included in this
+release, unless otherwise noted.
+
+ * "git add frotz/nitfol" did not complain when the entire frotz/ directory
+   was ignored.
+
+ * "git diff --stat" used "int" to count the size of differences,
+   which could result in overflowing.
+
+ * "git rev-list --pretty=oneline" didn't terminate a record with LF for
+   commits without any message.
+
+ * "git rev-list --abbrev-commit" defaulted to 40-byte abbreviations, unlike
+   newer tools in the git toolset.
diff --git a/Documentation/RelNotes/1.7.2.1.txt b/Documentation/RelNotes/1.7.2.1.txt
new file mode 100644 (file)
index 0000000..1103c47
--- /dev/null
@@ -0,0 +1,25 @@
+Git v1.7.2.1 Release Notes
+==========================
+
+Fixes since v1.7.2
+------------------
+
+ * "git instaweb" wasn't useful when your Apache was installed under a
+   name other than apache2 (e.g. "httpd").
+
+ * Similarly, "git web--browse" (invoked by "git help -w") learned that
+   chrome browser is sometimes called google-chrome.
+
+ * An overlong line after ".gitdir: " in a git file caused out of bounds
+   access to an array on the stack.
+
+ * "git config --path conf.var" to attempt to expand a variable conf.var
+   that uses "~/" short-hand segfaulted when $HOME environment variable
+   was not set.
+
+ * Documentation on Cygwin failed to build.
+
+ * The error message from "git pull blarg" when 'blarg' is an unknown
+   remote name has been improved.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.2.2.txt b/Documentation/RelNotes/1.7.2.2.txt
new file mode 100644 (file)
index 0000000..71eb6a8
--- /dev/null
@@ -0,0 +1,22 @@
+Git v1.7.2.2 Release Notes
+==========================
+
+Fixes since v1.7.2.1
+--------------------
+
+ * Object transfer over smart http transport deadlocked the client when
+   the remote HTTP server returned a failure, instead of erroring it out.
+
+ * git-gui honors custom textconv filters when showing diff and blame;
+
+ * git diff --relative=subdir (without the necessary trailing /) did not
+   work well;
+
+ * "git diff-files -p --submodule" was recently broken;
+
+ * "git checkout -b n ':/token'" did not work;
+
+ * "git index-pack" (hence "git fetch/clone/pull/push") enabled the object
+   replacement machinery by mistake (it never should have);
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.2.3.txt b/Documentation/RelNotes/1.7.2.3.txt
new file mode 100644 (file)
index 0000000..610960c
--- /dev/null
@@ -0,0 +1,39 @@
+Git v1.7.2.3 Release Notes
+==========================
+
+Fixes since v1.7.2.2
+--------------------
+
+ * When people try insane things such as delta-compressing 4GiB files, we
+   threw an assertion failure.
+
+ * "git archive" gave the full commit ID for "$Format:%h$".
+
+ * "git fetch --tags" did not fetch tags when remote.<nick>.tagopt was set
+   to --no-tags.  The command line option now overrides the configuration
+   setting.
+
+ * "git for-each-ref --format='%(objectname:short)'" has been completely
+   broken for a long time.
+
+ * "git gc" incorrectly pruned a rerere record that was created long
+   time ago but still is actively and repeatedly used.
+
+ * "git log --follow -M -p" was seriously broken in 1.7.2, reporting
+   assertion failure.
+
+ * Running "git log" with an incorrect option started pager nevertheless,
+   forcing the user to dismiss it.
+
+ * "git rebase" did not work well when the user has diff.renames
+   configuration variable set.
+
+ * An earlier (and rather old) fix to "git rebase" against a rebased
+   upstream broke a more normal, non rebased upstream case rather badly,
+   attempting to re-apply patches that are already accepted upstream.
+
+ * "git submodule sync" forgot to update the superproject's config file
+   when submodule URL changed.
+
+ * "git pack-refs --all --prune" did not remove a directory that has
+   become empty.
diff --git a/Documentation/RelNotes/1.7.2.txt b/Documentation/RelNotes/1.7.2.txt
new file mode 100644 (file)
index 0000000..15cf011
--- /dev/null
@@ -0,0 +1,151 @@
+Git v1.7.2 Release Notes
+========================
+
+Updates since v1.7.1
+--------------------
+
+ * core.eol configuration and text/eol attributes are the new way to control
+   the end of line conventions for files in the working tree.
+
+ * core.autocrlf has been made safer - it will now only handle line
+   endings for new files and files that are LF-only in the
+   repository. To normalize content that has been checked in with
+   CRLF, use the new eol/text attributes.
+
+ * The whitespace rules used in "git apply --whitespace" and "git diff"
+   gained a new member in the family (tab-in-indent) to help projects with
+   policy to indent only with spaces.
+
+ * When working from a subdirectory, by default, git does not look for its
+   metadirectory ".git" across filesystems, primarily to help people who
+   have invocations of git in their custom PS1 prompts, as being outside
+   of a git repository would look for ".git" all the way up to the root
+   directory, and NFS mounts are often slow.  DISCOVERY_ACROSS_FILESYSTEM
+   environment variable can be used to tell git not to stop at a
+   filesystem boundary.
+
+ * Usage help messages generated by parse-options library (i.e. most
+   of the Porcelain commands) are sent to the standard output now.
+
+ * ':/<string>' notation to look for a commit now takes regular expression
+   and it is not anchored at the beginning of the commit log message
+   anymore (this is a backward incompatible change).
+
+ * "git" wrapper learned "-c name=value" option to override configuration
+   variable from the command line.
+
+ * Improved portability for various platforms including older SunOS,
+   HP-UX 10/11, AIX, Tru64, etc. and platforms with Python 2.4.
+
+ * The message from "git am -3" has been improved when conflict
+   resolution ended up making the patch a no-op.
+
+ * "git blame" applies the textconv filter to the contents it works
+   on, when available.
+
+ * "git checkout --orphan newbranch" is similar to "-b newbranch" but
+   prepares to create a root commit that is not connected to any existing
+   commit.
+
+ * "git cherry-pick" learned to pick a range of commits
+   (e.g. "cherry-pick A..B" and "cherry-pick --stdin"), so did "git
+   revert"; these do not support the nicer sequencing control "rebase
+   [-i]" has, though.
+
+ * "git cherry-pick" and "git revert" learned --strategy option to specify
+   the merge strategy to be used when performing three-way merges.
+
+ * "git cvsserver" can be told to use pserver; its password file can be
+   stored outside the repository.
+
+ * The output from the textconv filter used by "git diff" can be cached to
+   speed up their reuse.
+
+ * "git diff --word-diff=<mode>" extends the existing "--color-words"
+   option, making it more useful in color-challenged environments.
+
+ * The regexp to detect function headers used by "git diff" for PHP has
+   been enhanced for visibility modifiers (public, protected, etc.) to
+   better support PHP5.
+
+ * "diff.noprefix" configuration variable can be used to implicitly
+   ask for "diff --no-prefix" behaviour.
+
+ * "git for-each-ref" learned "%(objectname:short)" that gives the object
+   name abbreviated.
+
+ * "git format-patch" learned --signature option and format.signature
+   configuration variable to customize the e-mail signature used in the
+   output.
+
+ * Various options to "git grep" (e.g. --count, --name-only) work better
+   with binary files.
+
+ * "git grep" learned "-Ovi" to open the files with hits in your editor.
+
+ * "git help -w" learned "chrome" and "chromium" browsers.
+
+ * "git log --decorate" shows commit decorations in various colours.
+
+ * "git log --follow <path>" follows across copies (it used to only follow
+   renames).  This may make the processing more expensive.
+
+ * "git log --pretty=format:<template>" specifier learned "% <something>"
+   magic that inserts a space only when %<something> expands to a
+   non-empty string; this is similar to "%+<something>" magic, but is
+   useful in a context to generate a single line output.
+
+ * "git notes prune" learned "-n" (dry-run) and "-v" options, similar to
+   what "git prune" has.
+
+ * "git patch-id" can be fed a mbox without getting confused by the
+   signature line in the format-patch output.
+
+ * "git remote" learned "set-branches" subcommand.
+
+ * "git rev-list A..B" learned --ancestry-path option to further limit
+   the result to the commits that are on the ancestry chain between A and
+   B (i.e. commits that are not descendants of A are excluded).
+
+ * "git show -5" is equivalent to "git show --do-walk 5"; this is similar
+   to the update to make "git show master..next" walk the history,
+   introduced in 1.6.4.
+
+ * "git status [-s] --ignored" can be used to list ignored paths.
+
+ * "git status -s -b" shows the current branch in the output.
+
+ * "git status" learned "--ignore-submodules" option.
+
+ * Various "gitweb" enhancements and clean-ups, including syntax
+   highlighting, "plackup" support for instaweb, .fcgi suffix to run
+   it as FastCGI script, etc.
+
+ * The test harness has been updated to produce TAP-friendly output.
+
+ * Many documentation improvement patches are also included.
+
+
+Fixes since v1.7.1
+------------------
+
+All of the fixes in v1.7.1.X maintenance series are included in this
+release, unless otherwise noted.
+
+ * We didn't URL decode "file:///path/to/repo" correctly when path/to/repo
+   had percent-encoded characters (638794c, 9d2e942, ce83eda, 3c73a1d).
+
+ * "git clone" did not configure remote.origin.url correctly for bare
+   clones (df61c889).
+
+ * "git diff --graph" works better with "--color-words" and other options
+   (81fa024..4297c0a).
+
+ * "git diff" could show ambiguous abbreviation of blob object names on
+   its "index" line (3e5a188).
+
+ * "git reset --hard" started from a wrong directory and a working tree in
+   a nonstandard location is in use got confused (560fb6a1).
+
+ * "git read-tree -m A B" used to switch to branch B while retaining
+   local changes added an incorrect cache-tree information (b1f47514).
diff --git a/Documentation/RelNotes/1.7.3.txt b/Documentation/RelNotes/1.7.3.txt
new file mode 100644 (file)
index 0000000..b55731f
--- /dev/null
@@ -0,0 +1,87 @@
+Git v1.7.3 Release Notes (draft)
+================================
+
+Updates since v1.7.2
+--------------------
+
+ * git-gui, now at version 0.13.0, got various updates and a new
+   maintainer, Pat Thoyts.
+
+ * Gitweb allows its configuration to change per each request; it used to
+   read the configuration once upon startup.
+
+ * When git finds a corrupt object, it now reports the file that contains
+   it.
+
+ * "git checkout -B <it>" is a shorter way to say "git branch -f <it>"
+   followed by "git checkout <it>".
+
+ * When "git checkout" or "git merge" refuse to proceed in order to
+   protect local modification to your working tree, they used to stop
+   after showing just one path that might be lost.  They now show all,
+   in a format that is easier to read.
+
+ * "git clean" learned "-e" ("--exclude") option.
+
+ * Hunk headers produced for C# files by "git diff" and friends show more
+   relevant context than before.
+
+ * diff.ignoresubmodules configuration variable can be used to squelch the
+   differences in submodules reported when running commands (e.g. "diff",
+   "status", etc.) at the superproject level.
+
+ * http.useragent configuration can be used to lie who you are to your
+   restrictive firewall.
+
+ * "git rebase --strategy <s>" learned "-X" option to pass extra options
+   that are understood by the chosen merge strategy.
+
+ * "git rebase -i" learned "exec" that you can insert into the insn sheet
+   to run a command between its steps.
+
+ * "git rebase" between branches that have many binary changes that do
+   not conflict should be faster.
+
+ * "git rebase -i" peeks into rebase.autosquash configuration and acts as
+   if you gave --autosquash from the command line.
+
+
+Also contains various documentation updates.
+
+
+Fixes since v1.7.2
+------------------
+
+All of the fixes in v1.7.2.X maintenance series are included in this
+release, unless otherwise noted.
+
+ * "git merge -s recursive" (which is the default) did not handle cases
+   where a directory becomes a file (or vice versa) very well.
+
+ * "git fetch" and friends were accidentally broken for url with "+" in
+   its path, e.g. "git://git.gnome.org/gtk+".
+
+ * "git fetch $url" (i.e. without refspecs) was broken for quite some
+   time, if the current branch happen to be tracking some remote.
+
+ * "git ls-tree dir dirgarbage", when "dir" was a directory,
+   incorrectly recursed into "dir".
+
+ * "git note remove" created unnecessary extra commit when named object
+   did not have any note to begin with.
+
+ * "git rebase" did not work well if you had diff.noprefix configured.
+
+ * "git -c foo=bar subcmd" did not work well for subcmd that is not
+   implemented as a built-in command.
+
+---
+exec >/var/tmp/1
+echo O=$(git describe master)
+O=v1.7.3-rc2
+git shortlog --no-merges $O..master ^maint
+exit 0
+
+What did we want to do with...
+
+1e3d411 (Enable custom schemes for column colors in the graph API, 2010-07-13)
index 76fc84d8780762e083cd4ca584b9d783b8c0cd81..ece3c77482b3ff006b973f1ed90b708e26556862 100644 (file)
@@ -7,17 +7,16 @@ Checklist (and a short version for the impatient):
          before committing
        - do not check in commented out code or unneeded files
        - the first line of the commit message should be a short
-         description and should skip the full stop
+         description (50 characters is the soft limit, see DISCUSSION
+         in git-commit(1)), and should skip the full stop
        - the body should provide a meaningful commit message, which:
                - uses the imperative, present tense: "change",
                  not "changed" or "changes".
                - includes motivation for the change, and contrasts
                  its implementation with previous behaviour
-       - if you want your work included in git.git, add a
-         "Signed-off-by: Your Name <you@example.com>" line to the
-         commit message (or just use the option "-s" when
-         committing) to confirm that you agree to the Developer's
-         Certificate of Origin
+       - add a "Signed-off-by: Your Name <you@example.com>" line to the
+         commit message (or just use the option "-s" when committing)
+         to confirm that you agree to the Developer's Certificate of Origin
        - make sure that you have tests for the bug you are fixing
        - make sure that the test suite passes after your commit
 
@@ -41,6 +40,7 @@ Checklist (and a short version for the impatient):
          maintainer (gitster@pobox.com) if (and only if) the patch
          is ready for inclusion. If you use git-send-email(1),
          please test it first by sending email to yourself.
+       - see below for instructions specific to your mailer
 
 Long version:
 
@@ -53,6 +53,34 @@ 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.
 
+(0) Decide what to base your work on.
+
+In general, always base your work on the oldest branch that your
+change is relevant to.
+
+ - A bugfix should be based on 'maint' in general. If the bug is not
+   present in 'maint', base it on 'master'. For a bug that's not yet
+   in 'master', find the topic that introduces the regression, and
+   base your work on the tip of the topic.
+
+ - A new feature should be based on 'master' in general. If the new
+   feature depends on a topic that is in 'pu', but not in 'master',
+   base your work on the tip of that topic.
+
+ - Corrections and enhancements to a topic not yet in 'master' should
+   be based on the tip of that topic. If the topic has not been merged
+   to 'next', it's alright to add a note to squash minor corrections
+   into the series.
+
+ - In the exceptional case that a new feature depends on several topics
+   not in 'master', start working on 'next' or 'pu' privately and send
+   out patches for discussion. Before the final merge, you may have to
+   wait until some of the dependent topics graduate to 'master', and
+   rebase your work.
+
+To find the tip of a topic branch, run "git log --first-parent
+master..pu" and look for the merge commit. The second parent of this
+commit is the tip of the topic branch.
 
 (1) Make separate commits for logically separate changes.
 
@@ -170,17 +198,16 @@ 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 for
-inclusion.
-
-Also note that your maintainer does not actively involve himself in
-maintaining what are in contrib/ hierarchy.  When you send fixes and
-enhancements to them, do not forget to "cc: " the person who primarily
-worked on that hierarchy in contrib/.
+Unless your patch is a very trivial and an obviously correct one,
+first send it with "To:" set to the mailing list, with "cc:" listing
+people who are involved in the area you are touching (the output from
+"git blame $path" and "git shortlog --no-merges $path" would help to
+identify them), to solicit comments and reviews.  After the list
+reached a consensus that it is a good idea to apply the patch, re-send
+it with "To:" set to the maintainer and optionally "cc:" the list for
+inclusion.  Do not forget to add trailers such as "Acked-by:",
+"Reviewed-by:" and "Tested-by:" after your "Signed-off-by:" line as
+necessary.
 
 
 (4) Sign your work
@@ -279,6 +306,20 @@ from the list and queue it to 'pu', in order to make it easier for
 people play with it without having to pick up and apply the patch to
 their trees themselves.
 
+------------------------------------------------
+Know the status of your patch after submission
+
+* You can use Git itself to find out when your patch is merged in
+  master. 'git pull --rebase' will automatically skip already-applied
+  patches, and will let you know. This works only if you rebase on top
+  of the branch in which your patch has been merged (i.e. it will not
+  tell you if your patch is merged in pu if you rebase on top of
+  master).
+
+* Read the git mailing list, the maintainer regularly posts messages
+  entitled "What's cooking in git.git" and "What's in git.git" giving
+  the status of various proposed changes.
+
 ------------------------------------------------
 MUA specific hints
 
@@ -505,12 +546,28 @@ Gmail
 
 GMail does not appear to have any way to turn off line wrapping in the web
 interface, so this will mangle any emails that you send.  You can however
-use any IMAP email client to connect to the google imap server, and forward
-the emails through that.  Just make sure to disable line wrapping in that
-email client.  Alternatively, use "git send-email" instead.
+use "git send-email" and send your patches through the GMail SMTP server, or
+use any IMAP email client to connect to the google IMAP server and forward
+the emails through that.
+
+To use "git send-email" and send your patches through the GMail SMTP server,
+edit ~/.gitconfig to specify your account settings:
 
-Submitting properly formatted patches via Gmail is simple now that
-IMAP support is available. First, edit your ~/.gitconfig to specify your
+[sendemail]
+       smtpencryption = tls
+       smtpserver = smtp.gmail.com
+       smtpuser = user@gmail.com
+       smtppass = p4ssw0rd
+       smtpserverport = 587
+
+Once your commits are ready to be sent to the mailing list, run the
+following commands:
+
+  $ git format-patch --cover-letter -M origin/master -o outgoing/
+  $ edit outgoing/0000-*
+  $ git send-email outgoing/*
+
+To submit using the IMAP interface, first, edit your ~/.gitconfig to specify your
 account settings:
 
 [imap]
@@ -524,14 +581,12 @@ account settings:
 You might need to instead use: folder = "[Google Mail]/Drafts" if you get an error
 that the "Folder doesn't exist".
 
-Next, ensure that your Gmail settings are correct. In "Settings" the
-"Use Unicode (UTF-8) encoding for outgoing messages" should be checked.
-
-Once your commits are ready to send to the mailing list, run the following
-command to send the patch emails to your Gmail Drafts folder.
+Once your commits are ready to be sent to the mailing list, run the
+following commands:
 
-       $ git format-patch -M --stdout origin/master | git imap-send
+  $ git format-patch --cover-letter -M --stdout origin/master | git imap-send
 
-Go to your Gmail account, open the Drafts folder, find the patch email, fill
-in the To: and CC: fields and send away!
+Just make sure to disable line wrapping in the email client (GMail web
+interface will line wrap no matter what, so you need to use a real
+IMAP client).
 
index dc76e7f073f23cd911da0b0d0d49b72726576f1a..aea8627be088c58ef9ad7fd07f5498da88d9b1fc 100644 (file)
@@ -16,7 +16,11 @@ plus=&#43;
 caret=&#94;
 startsb=&#91;
 endsb=&#93;
+backslash=&#92;
 tilde=&#126;
+apostrophe=&#39;
+backtick=&#96;
+litdd=&#45;&#45;
 
 ifdef::backend-docbook[]
 [linkgit-inlinemacro]
index 1625ffce6a1910c879447705fea4e3301039debd..16e3c685762a311eda1bbc39adb8ab19e000ec97 100644 (file)
@@ -79,32 +79,37 @@ of lines before or after the line given by <start>.
        of the --date option at linkgit:git-log[1].
 
 -M|<num>|::
-       Detect moving lines in the file as well.  When a commit
-       moves a block of lines in a file (e.g. the original file
-       has A and then B, and the commit changes it to B and
-       then A), the traditional 'blame' algorithm typically blames
-       the lines that were moved up (i.e. B) to the parent and
-       assigns blame to the lines that were moved down (i.e. A)
-       to the child commit.  With this option, both groups of lines
-       are blamed on the parent.
+       Detect moved or copied lines within a file. When a commit
+       moves or copies a block of lines (e.g. the original file
+       has A and then B, and the commit changes it to B and then
+       A), the traditional 'blame' algorithm notices only half of
+       the movement and typically blames the lines that were moved
+       up (i.e. B) to the parent and assigns blame to the lines that
+       were moved down (i.e. A) to the child commit.  With this
+       option, both groups of lines are blamed on the parent by
+       running extra passes of inspection.
 +
 <num> is optional but it is the lower bound on the number of
-alphanumeric characters that git must detect as moving
+alphanumeric characters that git must detect as moving/copying
 within a file for it to associate those lines with the parent
-commit.
+commit. The default value is 20.
 
 -C|<num>|::
-       In addition to `-M`, detect lines copied from other
+       In addition to `-M`, detect lines moved or copied from other
        files that were modified in the same commit.  This is
        useful when you reorganize your program and move code
        around across files.  When this option is given twice,
-       the command additionally looks for copies from all other
-       files in the parent for the commit that creates the file.
+       the command additionally looks for copies from other
+       files in the commit that creates the file. When this
+       option is given three times, the command additionally
+       looks for copies from other files in any commit.
 +
 <num> is optional but it is the lower bound on the number of
-alphanumeric characters that git must detect as moving
+alphanumeric characters that git must detect as moving/copying
 between files for it to associate those lines with the parent
-commit.
+commit. And the default value is 40. If there are more than one
+`-C` options given, the <num> argument of the last `-C` will
+take effect.
 
 -h::
 --help::
index 5dcad94f841c395beb21961ebdacd341d76b25c9..d82c0da2cf470dbfe7936762d4645b7509647177 100644 (file)
@@ -49,7 +49,8 @@ There is also a case insensitive alternative `[section.subsection]` syntax.
 In this syntax, subsection names follow the same restrictions as for section
 names.
 
-All the other lines are recognized as setting variables, in the form
+All the other lines (and the remainder of the line after the section
+header) are recognized as setting variables, in the form
 'name = value'.  If there is no equal sign on the line, the entire line
 is taken as 'name' and the variable is recognized as boolean "true".
 The variable names are case-insensitive and only alphanumeric
@@ -63,7 +64,7 @@ The values following the equals sign in variable assign are all either
 a string, an integer, or a boolean.  Boolean values may be given as yes/no,
 0/1, true/false or on/off.  Case is not significant in boolean values, when
 converting value to the canonical form using '--bool' type specifier;
-'git-config' will ensure that the output is "true" or "false".
+'git config' will ensure that the output is "true" or "false".
 
 String values may be entirely or partially enclosed in double quotes.
 You need to enclose variable values in double quotes if you want to
@@ -112,10 +113,46 @@ For command-specific variables, you will find a more detailed description
 in the appropriate manual page. You will find a description of non-core
 porcelain configuration variables in the respective porcelain documentation.
 
+advice.*::
+       When set to 'true', display the given optional help message.
+       When set to 'false', do not display. The configuration variables
+       are:
++
+--
+       pushNonFastForward::
+               Advice shown when linkgit:git-push[1] refuses
+               non-fast-forward refs. Default: true.
+       statusHints::
+               Directions on how to stage/unstage/add shown in the
+               output of linkgit:git-status[1] and the template shown
+               when writing commit messages. Default: true.
+       commitBeforeMerge::
+               Advice shown when linkgit:git-merge[1] refuses to
+               merge to avoid overwriting local changes.
+               Default: true.
+       resolveConflict::
+               Advices shown by various commands when conflicts
+               prevent the operation from being performed.
+               Default: true.
+       implicitIdentity::
+               Advice on how to set your identity configuration when
+               your information is guessed from the system username and
+               domain name. Default: true.
+
+       detachedHead::
+               Advice shown when you used linkgit::git-checkout[1] to
+               move to the detach HEAD state, to instruct how to create
+               a local branch after the fact.  Default: true.
+--
+
 core.fileMode::
        If false, the executable bit differences between the index and
        the working copy are ignored; useful on broken filesystems like FAT.
-       See linkgit:git-update-index[1]. True by default.
+       See linkgit:git-update-index[1].
++
+The default is true, except linkgit:git-clone[1] or linkgit:git-init[1]
+will probe and set core.fileMode false if appropriate when the
+repository is created.
 
 core.ignoreCygwinFSTricks::
        This option is only used by Cygwin implementation of Git. If false,
@@ -128,6 +165,18 @@ core.ignoreCygwinFSTricks::
        is true, in which case ignoreCygwinFSTricks is ignored as Cygwin's
        POSIX emulation is required to support core.filemode.
 
+core.ignorecase::
+       If true, this option enables various workarounds to enable
+       git to work better on filesystems that are not case sensitive,
+       like FAT. For example, if a directory listing finds
+       "makefile" when git expects "Makefile", git will assume
+       it is really the same file, and continue to remember it as
+       "Makefile".
++
+The default is false, except linkgit:git-clone[1] or linkgit:git-init[1]
+will probe and set core.ignorecase true if appropriate when the repository
+is created.
+
 core.trustctime::
        If false, the ctime differences between the index and the
        working copy are ignored; useful when the inode change time
@@ -147,19 +196,17 @@ core.quotepath::
        quoted without `-z` regardless of the setting of this
        variable.
 
-core.autocrlf::
-       If true, makes git convert `CRLF` at the end of lines in text files to
-       `LF` when reading from the filesystem, and convert in reverse when
-       writing to the filesystem.  The variable can be set to
-       'input', in which case the conversion happens only while
-       reading from the filesystem but files are written out with
-       `LF` at the end of lines.  Currently, which paths to consider
-       "text" (i.e. be subjected to the autocrlf mechanism) is
-       decided purely based on the contents.
+core.eol::
+       Sets the line ending type to use in the working directory for
+       files that have the `text` property set.  Alternatives are
+       'lf', 'crlf' and 'native', which uses the platform's native
+       line ending.  The default value is `native`.  See
+       linkgit:gitattributes[5] for more information on end-of-line
+       conversion.
 
 core.safecrlf::
-       If true, makes git check if converting `CRLF` as controlled by
-       `core.autocrlf` is reversible.  Git will verify if a command
+       If true, makes git check if converting `CRLF` is reversible when
+       end-of-line conversion is active.  Git will verify if a command
        modifies a file in the work tree either directly or indirectly.
        For example, committing a file followed by checking out the
        same file should yield the original file in the work tree.  If
@@ -169,7 +216,7 @@ core.safecrlf::
        irreversible conversion but continue the operation.
 +
 CRLF conversion bears a slight chance of corrupting data.
-autocrlf=true will convert CRLF to LF during commit and LF to
+When it is enabled, git will convert CRLF to LF during commit and LF to
 CRLF during checkout.  A file that contains a mixture of LF and
 CRLF before the commit cannot be recreated by git.  For text
 files this is the right thing to do: it corrects line endings
@@ -193,21 +240,35 @@ converting CRLFs corrupts data.
 +
 Note, this safety check does not mean that a checkout will generate a
 file identical to the original file for a different setting of
-`core.autocrlf`, but only for the current one.  For example, a text
-file with `LF` would be accepted with `core.autocrlf=input` and could
-later be checked out with `core.autocrlf=true`, in which case the
+`core.eol` and `core.autocrlf`, but only for the current one.  For
+example, a text file with `LF` would be accepted with `core.eol=lf`
+and could later be checked out with `core.eol=crlf`, in which case the
 resulting file would contain `CRLF`, although the original file
 contained `LF`.  However, in both work trees the line endings would be
 consistent, that is either all `LF` or all `CRLF`, but never mixed.  A
 file with mixed line endings would be reported by the `core.safecrlf`
 mechanism.
 
+core.autocrlf::
+       Setting this variable to "true" is almost the same as setting
+       the `text` attribute to "auto" on all files except that text
+       files are not guaranteed to be normalized: files that contain
+       `CRLF` in the repository will not be touched.  Use this
+       setting if you want to have `CRLF` line endings in your
+       working directory even though the repository does not have
+       normalized line endings.  This variable can be set to 'input',
+       in which case no output conversion is performed.
+
 core.symlinks::
        If false, symbolic links are checked out as small plain files that
        contain the link text. linkgit:git-update-index[1] and
        linkgit:git-add[1] will not change the recorded type to regular
        file. Useful on filesystems like FAT that do not support
-       symbolic links. True by default.
+       symbolic links.
++
+The default is true, except linkgit:git-clone[1] or linkgit:git-init[1]
+will probe and set core.symlinks false if appropriate when the repository
+is created.
 
 core.gitProxy::
        A "proxy command" to execute (as 'command host port') instead
@@ -256,17 +317,24 @@ false), while all other repositories are assumed to be bare (bare
 = true).
 
 core.worktree::
-       Set the path to the working tree.  The value will not be
-       used in combination with repositories found automatically in
-       a .git directory (i.e. $GIT_DIR is not set).
+       Set the path to the root of the work tree.
        This can be overridden by the GIT_WORK_TREE environment
        variable and the '--work-tree' command line option. It can be
-       a absolute path or relative path to the directory specified by
-       --git-dir or GIT_DIR.
-       Note: If --git-dir or GIT_DIR are specified but none of
+       an absolute path or a relative path to the .git directory,
+       either specified by --git-dir or GIT_DIR, or automatically
+       discovered.
+       If --git-dir or GIT_DIR are specified but none of
        --work-tree, GIT_WORK_TREE and core.worktree is specified,
-       the current working directory is regarded as the top directory
-       of your working tree.
+       the current working directory is regarded as the root of the
+       work tree.
++
+Note that this variable is honored even when set in a configuration
+file in a ".git" subdirectory of a directory, and its value differs
+from the latter directory (e.g. "/path/to/.git/config" has
+core.worktree set to "/different/path"), which is most likely a
+misconfiguration.  Running git commands in "/path/to" directory will
+still use "/different/path" as the root of the work tree and can cause
+great confusion to the users.
 
 core.logAllRefUpdates::
        Enable the reflog. Updates to a ref <ref> is logged to the file
@@ -350,7 +418,7 @@ Common unit suffixes of 'k', 'm', or 'g' are supported.
 
 core.deltaBaseCacheLimit::
        Maximum number of bytes to reserve for caching base objects
-       that multiple deltafied objects reference.  By storing the
+       that may be referenced by multiple deltified objects.  By storing the
        entire decompressed base objects in a cache Git is able
        to avoid unpacking and decompressing frequently used base
        objects multiple times.
@@ -361,19 +429,41 @@ You probably do not need to adjust this value.
 +
 Common unit suffixes of 'k', 'm', or 'g' are supported.
 
+core.bigFileThreshold::
+       Files larger than this size are stored deflated, without
+       attempting delta compression.  Storing large files without
+       delta compression avoids excessive memory usage, at the
+       slight expense of increased disk usage.
++
+Default is 512 MiB on all platforms.  This should be reasonable
+for most projects as source code and other text files can still
+be delta compressed, but larger binary media files won't be.
++
+Common unit suffixes of 'k', 'm', or 'g' are supported.
++
+Currently only linkgit:git-fast-import[1] honors this setting.
+
 core.excludesfile::
        In addition to '.gitignore' (per-directory) and
        '.git/info/exclude', git looks into this file for patterns
-       of files which are not meant to be tracked.  See
-       linkgit:gitignore[5].
+       of files which are not meant to be tracked.  "{tilde}/" is expanded
+       to the value of `$HOME` and "{tilde}user/" to the specified user's
+       home directory.  See linkgit:gitignore[5].
+
+core.askpass::
+       Some commands (e.g. svn and http interfaces) that interactively
+       ask for a password can be told to use an external program given
+       via the value of this variable. Can be overridden by the 'GIT_ASKPASS'
+       environment variable. If not set, fall back to the value of the
+       'SSH_ASKPASS' environment variable or, failing that, a simple password
+       prompt. The external program shall be given a suitable prompt as
+       command line argument and write the password on its STDOUT.
 
 core.editor::
        Commands such as `commit` and `tag` that lets you edit
        messages by launching an editor uses the value of this
        variable when it is set, and the environment variable
-       `GIT_EDITOR` is not set.  The order of preference is
-       `GIT_EDITOR` environment, `core.editor`, `VISUAL` and
-       `EDITOR` environment variables and then finally `vi`.
+       `GIT_EDITOR` is not set.  See linkgit:git-var[1].
 
 core.pager::
        The command that git will use to paginate output.  Can
@@ -395,18 +485,24 @@ core.pager::
 
 core.whitespace::
        A comma separated list of common whitespace problems to
-       notice.  'git-diff' will use `color.diff.whitespace` to
-       highlight them, and 'git-apply --whitespace=error' will
+       notice.  'git diff' will use `color.diff.whitespace` to
+       highlight them, and 'git apply --whitespace=error' will
        consider them as errors.  You can prefix `-` to disable
        any of them (e.g. `-trailing-space`):
 +
-* `trailing-space` treats trailing whitespaces at the end of the line
+* `blank-at-eol` treats trailing whitespaces at the end of the line
   as an error (enabled by default).
 * `space-before-tab` treats a space character that appears immediately
   before a tab character in the initial indent part of the line as an
   error (enabled by default).
 * `indent-with-non-tab` treats a line that is indented with 8 or more
   space characters as an error (not enabled by default).
+* `tab-in-indent` treats a tab character in the initial indent part of
+  the line as an error (not enabled by default).
+* `blank-at-eof` treats blank lines added at the end of file as an error
+  (enabled by default).
+* `trailing-space` is a short-hand to cover both `blank-at-eol` and
+  `blank-at-eof`.
 * `cr-at-eol` treats a carriage-return at the end of line as
   part of the line terminator, i.e. with it, `trailing-space`
   does not trigger if the character before such a carriage-return
@@ -438,6 +534,24 @@ On some file system/operating system combinations, this is unreliable.
 Set this config setting to 'rename' there; However, This will remove the
 check that makes sure that existing object files will not get overwritten.
 
+core.notesRef::
+       When showing commit messages, also show notes which are stored in
+       the given ref.  The ref must be fully qualified.  If the given
+       ref does not exist, it is not an error but means that no
+       notes should be printed.
++
+This setting defaults to "refs/notes/commits", and it can be overridden by
+the 'GIT_NOTES_REF' environment variable.  See linkgit:git-notes[1].
+
+core.sparseCheckout::
+       Enable "sparse checkout" feature. See section "Sparse checkout" in
+       linkgit:git-read-tree[1] for more information.
+
+add.ignore-errors::
+       Tells 'git add' to continue adding files when some files cannot be
+       added due to indexing errors. Equivalent to the '--ignore-errors'
+       option of linkgit:git-add[1].
+
 alias.*::
        Command aliases for the linkgit:git[1] command wrapper - e.g.
        after defining "alias.last = cat-file commit HEAD", the invocation
@@ -451,14 +565,31 @@ If the alias expansion is prefixed with an exclamation point,
 it will be treated as a shell command.  For example, defining
 "alias.new = !gitk --all --not ORIG_HEAD", the invocation
 "git new" is equivalent to running the shell command
-"gitk --all --not ORIG_HEAD".
+"gitk --all --not ORIG_HEAD".  Note that shell commands will be
+executed from the top-level directory of a repository, which may
+not necessarily be the current directory.
+
+am.keepcr::
+       If true, git-am will call git-mailsplit for patches in mbox format
+       with parameter '--keep-cr'. In this case git-mailsplit will
+       not remove `\r` from lines ending with `\r\n`. Can be overridden
+       by giving '--no-keep-cr' from the command line.
+       See linkgit:git-am[1], linkgit:git-mailsplit[1].
+
+apply.ignorewhitespace::
+       When set to 'change', tells 'git apply' to ignore changes in
+       whitespace, in the same way as the '--ignore-space-change'
+       option.
+       When set to one of: no, none, never, false tells 'git apply' to
+       respect all whitespace differences.
+       See linkgit:git-apply[1].
 
 apply.whitespace::
-       Tells 'git-apply' how to handle whitespaces, in the same way
+       Tells 'git apply' how to handle whitespaces, in the same way
        as the '--whitespace' option. See linkgit:git-apply[1].
 
 branch.autosetupmerge::
-       Tells 'git-branch' and 'git-checkout' to setup new branches
+       Tells 'git branch' and 'git checkout' to set up new branches
        so that linkgit:git-pull[1] will appropriately merge from the
        starting point branch. Note that even if this option is not set,
        this behavior can be chosen per-branch using the `--track`
@@ -469,7 +600,7 @@ branch.autosetupmerge::
        branch. This option defaults to true.
 
 branch.autosetuprebase::
-       When a new branch is created with 'git-branch' or 'git-checkout'
+       When a new branch is created with 'git branch' or 'git checkout'
        that tracks another branch, this variable tells git to set
        up pull to rebase instead of merge (see "branch.<name>.rebase").
        When `never`, rebase is never automatically set to true.
@@ -484,31 +615,31 @@ branch.autosetuprebase::
        This option defaults to never.
 
 branch.<name>.remote::
-       When in branch <name>, it tells 'git-fetch' and 'git-push' which
+       When in branch <name>, it tells 'git fetch' and 'git push' which
        remote to fetch from/push to.  It defaults to `origin` if no remote is
        configured. `origin` is also used if you are not on any branch.
 
 branch.<name>.merge::
        Defines, together with branch.<name>.remote, the upstream branch
-       for the given branch. It tells 'git-fetch'/'git-pull' which
-       branch to merge and can also affect 'git-push' (see push.default).
-       When in branch <name>, it tells 'git-fetch' the default
+       for the given branch. It tells 'git fetch'/'git pull' which
+       branch to merge and can also affect 'git push' (see push.default).
+       When in branch <name>, it tells 'git fetch' the default
        refspec to be marked for merging in FETCH_HEAD. The value is
        handled like the remote part of a refspec, and must match a
        ref which is fetched from the remote given by
        "branch.<name>.remote".
-       The merge information is used by 'git-pull' (which at first calls
-       'git-fetch') to lookup the default branch for merging. Without
-       this option, 'git-pull' defaults to merge the first refspec fetched.
+       The merge information is used by 'git pull' (which at first calls
+       'git fetch') to lookup the default branch for merging. Without
+       this option, 'git pull' defaults to merge the first refspec fetched.
        Specify multiple values to get an octopus merge.
-       If you wish to setup 'git-pull' so that it merges into <name> from
+       If you wish to setup 'git pull' so that it merges into <name> from
        another branch in the local repository, you can point
        branch.<name>.merge to the desired branch, and use the special setting
        `.` (a period) for branch.<name>.remote.
 
 branch.<name>.mergeoptions::
        Sets default options for merging into branch <name>. The syntax and
-       supported options are equal to that of linkgit:git-merge[1], but
+       supported options are the same as those of linkgit:git-merge[1], but
        option values containing whitespace characters are currently not
        supported.
 
@@ -563,29 +694,44 @@ color.diff.<slot>::
        Use customized color for diff colorization.  `<slot>` specifies
        which part of the patch to use the specified color, and is one
        of `plain` (context text), `meta` (metainformation), `frag`
-       (hunk header), `old` (removed lines), `new` (added lines),
-       `commit` (commit headers), or `whitespace` (highlighting
-       whitespace errors). The values of these variables may be specified as
-       in color.branch.<slot>.
+       (hunk header), 'func' (function in hunk header), `old` (removed lines),
+       `new` (added lines), `commit` (commit headers), or `whitespace`
+       (highlighting whitespace errors). The values of these variables may be
+       specified as in color.branch.<slot>.
+
+color.decorate.<slot>::
+       Use customized color for 'git log --decorate' output.  `<slot>` is one
+       of `branch`, `remoteBranch`, `tag`, `stash` or `HEAD` for local
+       branches, remote tracking branches, tags, stash and HEAD, respectively.
 
 color.grep::
        When set to `always`, always highlight matches.  When `false` (or
        `never`), never.  When set to `true` or `auto`, use color only
        when the output is written to the terminal.  Defaults to `false`.
 
-color.grep.external::
-       The string value of this variable is passed to an external 'grep'
-       command as a command line option if match highlighting is turned
-       on.  If set to an empty string, no option is passed at all,
-       turning off coloring for external 'grep' calls; this is the default.
-       For GNU grep, set it to `--color=always` to highlight matches even
-       when a pager is used.
-
-color.grep.match::
-       Use customized color for matches.  The value of this variable
-       may be specified as in color.branch.<slot>.  It is passed using
-       the environment variables 'GREP_COLOR' and 'GREP_COLORS' when
-       calling an external 'grep'.
+color.grep.<slot>::
+       Use customized color for grep colorization.  `<slot>` specifies which
+       part of the line to use the specified color, and is one of
++
+--
+`context`;;
+       non-matching text in context lines (when using `-A`, `-B`, or `-C`)
+`filename`;;
+       filename prefix (when not using `-h`)
+`function`;;
+       function name lines (when using `-p`)
+`linenumber`;;
+       line number prefix (when using `-n`)
+`match`;;
+       matching text
+`selected`;;
+       non-matching text in selected lines
+`separator`;;
+       separators between fields on a line (`:`, `-`, and `=`)
+       and between hunks (`--`)
+--
++
+The values of these variables may be specified as in color.branch.<slot>.
 
 color.interactive::
        When set to `always`, always use colors for interactive prompts
@@ -594,16 +740,22 @@ color.interactive::
        colors only when the output is to the terminal. Defaults to false.
 
 color.interactive.<slot>::
-       Use customized color for 'git-add --interactive'
+       Use customized color for 'git add --interactive'
        output. `<slot>` may be `prompt`, `header`, `help` or `error`, for
        four distinct types of normal output from interactive
-       programs.  The values of these variables may be specified as
+       commands.  The values of these variables may be specified as
        in color.branch.<slot>.
 
 color.pager::
        A boolean to enable/disable colored output when the pager is in
        use (default is true).
 
+color.showbranch::
+       A boolean to enable/disable color in the output of
+       linkgit:git-show-branch[1]. May be set to `always`,
+       `false` (or `never`) or `auto` (or `true`), in which case colors are used
+       only when the output is to a terminal. Defaults to false.
+
 color.status::
        A boolean to enable/disable color in the output of
        linkgit:git-status[1]. May be set to `always`,
@@ -627,18 +779,25 @@ color.ui::
        terminal. When more specific variables of color.* are set, they always
        take precedence over this setting. Defaults to false.
 
+commit.status::
+       A boolean to enable/disable inclusion of status information in the
+       commit message template when using an editor to prepare the commit
+       message.  Defaults to true.
+
 commit.template::
        Specify a file to use as the template for new commit messages.
+       "{tilde}/" is expanded to the value of `$HOME` and "{tilde}user/" to the
+       specified user's home directory.
 
 diff.autorefreshindex::
-       When using 'git-diff' to compare with work tree
+       When using 'git diff' to compare with work tree
        files, do not consider stat-only change as changed.
        Instead, silently run `git update-index --refresh` to
        update the cached stat information for paths whose
        contents in the work tree match the contents in the
        index.  This option defaults to true.  Note that this
-       affects only 'git-diff' Porcelain, and not lower level
-       'diff' commands, such as 'git-diff-files'.
+       affects only 'git diff' Porcelain, and not lower level
+       'diff' commands such as 'git diff-files'.
 
 diff.external::
        If this config variable is set, diff generation is not
@@ -650,30 +809,39 @@ diff.external::
        your files, you might want to use linkgit:gitattributes[5] instead.
 
 diff.mnemonicprefix::
-       If set, 'git-diff' uses a prefix pair that is different from the
+       If set, 'git diff' uses a prefix pair that is different from the
        standard "a/" and "b/" depending on what is being compared.  When
        this configuration is in effect, reverse diff output also swaps
        the order of the prefixes:
-'git-diff';;
+`git diff`;;
        compares the (i)ndex and the (w)ork tree;
-'git-diff HEAD';;
+`git diff HEAD`;;
         compares a (c)ommit and the (w)ork tree;
-'git diff --cached';;
+`git diff --cached`;;
        compares a (c)ommit and the (i)ndex;
-'git-diff HEAD:file1 file2';;
+`git diff HEAD:file1 file2`;;
        compares an (o)bject and a (w)ork tree entity;
-'git diff --no-index a b';;
+`git diff --no-index a b`;;
        compares two non-git things (1) and (2).
 
+diff.noprefix::
+       If set, 'git diff' does not show any source or destination prefix.
+
 diff.renameLimit::
        The number of files to consider when performing the copy/rename
-       detection; equivalent to the 'git-diff' option '-l'.
+       detection; equivalent to the 'git diff' option '-l'.
 
 diff.renames::
        Tells git to detect renames.  If set to any boolean value, it
        will enable basic rename detection.  If set to "copies" or
        "copy", it will detect copies, as well.
 
+diff.ignoreSubmodules::
+       Sets the default value of --ignore-submodules. Note that this
+       affects only 'git diff' Porcelain, and not lower level 'diff'
+       commands such as 'git diff-files'. 'git checkout' also honors
+       this setting when reporting uncommitted changes.
+
 diff.suppressBlankEmpty::
        A boolean to inhibit the standard behavior of printing a space
        before each empty output line. Defaults to false.
@@ -734,14 +902,22 @@ format.headers::
        Additional email headers to include in a patch to be submitted
        by mail.  See linkgit:git-format-patch[1].
 
+format.to::
 format.cc::
-       Additional "Cc:" headers to include in a patch to be submitted
-       by mail.  See the --cc option in linkgit:git-format-patch[1].
+       Additional recipients to include in a patch to be submitted
+       by mail.  See the --to and --cc options in
+       linkgit:git-format-patch[1].
 
 format.subjectprefix::
        The default for format-patch is to output files with the '[PATCH]'
        subject prefix. Use this variable to change that prefix.
 
+format.signature::
+       The default for format-patch is to output a signature containing
+       the git version number. Use this variable to change that default.
+       Set this variable to the empty string ("") to suppress
+       signature generation.
+
 format.suffix::
        The default for format-patch is to output files with the suffix
        `.patch`. Use this variable to change that suffix (make sure to
@@ -753,9 +929,9 @@ format.pretty::
        linkgit:git-whatchanged[1].
 
 format.thread::
-       The default threading style for 'git-format-patch'.  Can be
-       either a boolean value, `shallow` or `deep`.  `shallow`
-       threading makes every mail a reply to the head of the series,
+       The default threading style for 'git format-patch'.  Can be
+       a boolean value, or `shallow` or `deep`.  `shallow` threading
+       makes every mail a reply to the head of the series,
        where the head is chosen from the cover letter, the
        `\--in-reply-to`, and the first patch mail, in this order.
        `deep` threading makes every mail a reply to the previous one.
@@ -771,8 +947,8 @@ format.signoff::
 
 gc.aggressiveWindow::
        The window size parameter used in the delta compression
-       algorithm used by 'git-gc --aggressive'.  This defaults
-       to 10.
+       algorithm used by 'git gc --aggressive'.  This defaults
+       to 250.
 
 gc.auto::
        When there are approximately more than this many loose
@@ -788,39 +964,42 @@ gc.autopacklimit::
        default value is 50.  Setting this to 0 disables it.
 
 gc.packrefs::
-       'git-gc' does not run `git pack-refs` in a bare repository by
-       default so that older dumb-transport clients can still fetch
-       from the repository.  Setting this to `true` lets 'git-gc'
-       to run `git pack-refs`.  Setting this to `false` tells
-       'git-gc' never to run `git pack-refs`. The default setting is
-       `notbare`. Enable it only when you know you do not have to
-       support such clients.  The default setting will change to `true`
-       at some stage, and setting this to `false` will continue to
-       prevent `git pack-refs` from being run from 'git-gc'.
+       Running `git pack-refs` in a repository renders it
+       unclonable by Git versions prior to 1.5.1.2 over dumb
+       transports such as HTTP.  This variable determines whether
+       'git gc' runs `git pack-refs`. This can be set to `nobare`
+       to enable it within all non-bare repos or it can be set to a
+       boolean value.  The default is `true`.
 
 gc.pruneexpire::
-       When 'git-gc' is run, it will call 'prune --expire 2.weeks.ago'.
+       When 'git gc' is run, it will call 'prune --expire 2.weeks.ago'.
        Override the grace period with this config variable.  The value
        "now" may be used to disable this  grace period and always prune
        unreachable objects immediately.
 
 gc.reflogexpire::
-       'git-reflog expire' removes reflog entries older than
-       this time; defaults to 90 days.
+gc.<pattern>.reflogexpire::
+       'git reflog expire' removes reflog entries older than
+       this time; defaults to 90 days.  With "<pattern>" (e.g.
+       "refs/stash") in the middle the setting applies only to
+       the refs that match the <pattern>.
 
 gc.reflogexpireunreachable::
-       'git-reflog expire' removes reflog entries older than
+gc.<ref>.reflogexpireunreachable::
+       'git reflog expire' removes reflog entries older than
        this time and are not reachable from the current tip;
-       defaults to 30 days.
+       defaults to 30 days.  With "<pattern>" (e.g. "refs/stash")
+       in the middle, the setting applies only to the refs that
+       match the <pattern>.
 
 gc.rerereresolved::
        Records of conflicted merge you resolved earlier are
-       kept for this many days when 'git-rerere gc' is run.
+       kept for this many days when 'git rerere gc' is run.
        The default is 60 days.  See linkgit:git-rerere[1].
 
 gc.rerereunresolved::
        Records of conflicted merge you have not resolved are
-       kept for this many days when 'git-rerere gc' is run.
+       kept for this many days when 'git rerere gc' is run.
        The default is 15 days.  See linkgit:git-rerere[1].
 
 gitcvs.commitmsgannotation::
@@ -836,13 +1015,15 @@ gitcvs.logfile::
        various stuff. See linkgit:git-cvsserver[1].
 
 gitcvs.usecrlfattr::
-       If true, the server will look up the `crlf` attribute for
-       files to determine the '-k' modes to use. If `crlf` is set,
-       the '-k' mode will be left blank, so cvs clients will
-       treat it as text. If `crlf` is explicitly unset, the file
+       If true, the server will look up the end-of-line conversion
+       attributes for files to determine the '-k' modes to use. If
+       the attributes force git to treat a file as text,
+       the '-k' mode will be left blank so CVS clients will
+       treat it as text. If they suppress text conversion, the file
        will be set with '-kb' mode, which suppresses any newline munging
-       the client might otherwise do. If `crlf` is not specified,
-       then 'gitcvs.allbinary' is used. See linkgit:gitattributes[5].
+       the client might otherwise do. If the attributes do not allow
+       the file type to be determined, then 'gitcvs.allbinary' is
+       used. See linkgit:gitattributes[5].
 
 gitcvs.allbinary::
        This is used if 'gitcvs.usecrlfattr' does not resolve
@@ -928,7 +1109,7 @@ gui.spellingdictionary::
        off.
 
 gui.fastcopyblame::
-       If true, 'git gui blame' uses '-C' instead of '-C -C' for original
+       If true, 'git gui blame' uses `-C` instead of `-C -C` for original
        location detection. It makes blame significantly faster on huge
        repositories at the expense of less thorough copy detection.
 
@@ -1032,6 +1213,12 @@ http.sslKey::
        over HTTPS. Can be overridden by the 'GIT_SSL_KEY' environment
        variable.
 
+http.sslCertPasswordProtected::
+       Enable git's password prompt for the SSL certificate.  Otherwise
+       OpenSSL will prompt the user, possibly many times, if the
+       certificate or private key is encrypted.  Can be overridden by the
+       'GIT_SSL_CERT_PASSWORD_PROTECTED' environment variable.
+
 http.sslCAInfo::
        File containing the certificates to verify the peer with when
        fetching or pushing over HTTPS. Can be overridden by the
@@ -1046,6 +1233,20 @@ 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.minSessions::
+       The number of curl sessions (counted across slots) to be kept across
+       requests. They will not be ended with curl_easy_cleanup() until
+       http_cleanup() is invoked. If USE_CURL_MULTI is not defined, this
+       value will be capped at 1. Defaults to 1.
+
+http.postBuffer::
+       Maximum size in bytes of the buffer used by smart HTTP
+       transports when POSTing data to the remote system.
+       For requests larger than this buffer size, HTTP/1.1 and
+       Transfer-Encoding: chunked is used to avoid creating a
+       massive pack file locally.  Default is 1 MiB, which is
+       sufficient for most requests.
+
 http.lowSpeedLimit, http.lowSpeedTime::
        If the HTTP transfer speed is less than 'http.lowSpeedLimit'
        for longer than 'http.lowSpeedTime' seconds, the transfer is aborted.
@@ -1058,6 +1259,15 @@ http.noEPSV::
        support EPSV mode. Can be overridden by the 'GIT_CURL_FTP_NO_EPSV'
        environment variable. Default is false (curl will use EPSV).
 
+http.useragent::
+       The HTTP USER_AGENT string presented to an HTTP server.  The default
+       value represents the version of the client git such as git/1.7.1.
+       This option allows you to override this value to a more common value
+       such as Mozilla/4.0.  This may be necessary, for instance, if
+       connecting through a firewall that restricts HTTP connections to a set
+       of common USER_AGENT strings (but not including those like git/1.7.1).
+       Can be overridden by the 'GIT_HTTP_USER_AGENT' environment variable.
+
 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
@@ -1067,12 +1277,16 @@ i18n.commitEncoding::
 
 i18n.logOutputEncoding::
        Character encoding the commit messages are converted to when
-       running 'git-log' and friends.
+       running 'git log' and friends.
 
 imap::
        The configuration variables in the 'imap' section are described
        in linkgit:git-imap-send[1].
 
+init.templatedir::
+       Specify the directory from which templates will be copied.
+       (See the "TEMPLATE DIRECTORY" section of linkgit:git-init[1].)
+
 instaweb.browser::
        Specify the program that will be used to browse your working
        repository in gitweb. See linkgit:git-instaweb[1].
@@ -1086,24 +1300,34 @@ instaweb.local::
        be bound to the local IP (127.0.0.1).
 
 instaweb.modulepath::
-       The module path for an apache httpd used by linkgit:git-instaweb[1].
+       The default module path for linkgit:git-instaweb[1] to use
+       instead of /usr/lib/apache2/modules.  Only used if httpd
+       is Apache.
 
 instaweb.port::
        The port number to bind the gitweb httpd to. See
        linkgit:git-instaweb[1].
 
 interactive.singlekey::
-       In interactive programs, allow the user to provide one-letter
+       In interactive commands, allow the user to provide one-letter
        input with a single key (i.e., without hitting enter).
        Currently this is used only by the `\--patch` mode of
        linkgit:git-add[1].  Note that this setting is silently
        ignored if portable keystroke input is not available.
 
 log.date::
-       Set default date-time mode for the log command. Setting log.date
-       value is similar to using 'git-log'\'s --date option. The value is one of the
-       following alternatives: {relative,local,default,iso,rfc,short}.
-       See linkgit:git-log[1].
+       Set the default date-time mode for the 'log' command.
+       Setting a value for log.date is similar to using 'git log''s
+       `\--date` option.  Possible values are `relative`, `local`,
+       `default`, `iso`, `rfc`, and `short`; see linkgit:git-log[1]
+       for details.
+
+log.decorate::
+       Print out the ref names of any commits that are shown by the log
+       command. If 'short' is specified, the ref name prefixes 'refs/heads/',
+       'refs/tags/' and 'refs/remotes/' will not be printed. If 'full' is
+       specified, the full ref name (including prefix) will be printed.
+       This is the same as the log commands '--decorate' option.
 
 log.showroot::
        If true, the initial commit will be shown as a big creation event.
@@ -1173,6 +1397,53 @@ mergetool.keepTemporaries::
 mergetool.prompt::
        Prompt before each invocation of the merge resolution program.
 
+notes.displayRef::
+       The (fully qualified) refname from which to show notes when
+       showing commit messages.  The value of this variable can be set
+       to a glob, in which case notes from all matching refs will be
+       shown.  You may also specify this configuration variable
+       several times.  A warning will be issued for refs that do not
+       exist, but a glob that does not match any refs is silently
+       ignored.
++
+This setting can be overridden with the `GIT_NOTES_DISPLAY_REF`
+environment variable, which must be a colon separated list of refs or
+globs.
++
+The effective value of "core.notesRef" (possibly overridden by
+GIT_NOTES_REF) is also implicitly added to the list of refs to be
+displayed.
+
+notes.rewrite.<command>::
+       When rewriting commits with <command> (currently `amend` or
+       `rebase`) and this variable is set to `true`, git
+       automatically copies your notes from the original to the
+       rewritten commit.  Defaults to `true`, but see
+       "notes.rewriteRef" below.
+
+notes.rewriteMode::
+       When copying notes during a rewrite (see the
+       "notes.rewrite.<command>" option), determines what to do if
+       the target commit already has a note.  Must be one of
+       `overwrite`, `concatenate`, or `ignore`.  Defaults to
+       `concatenate`.
++
+This setting can be overridden with the `GIT_NOTES_REWRITE_MODE`
+environment variable.
+
+notes.rewriteRef::
+       When copying notes during a rewrite, specifies the (fully
+       qualified) ref whose notes should be copied.  The ref may be a
+       glob, in which case notes in all matching refs will be copied.
+       You may also specify this configuration several times.
++
+Does not have a default value; you must configure this variable to
+enable note rewriting.
++
+This setting can be overridden with the `GIT_NOTES_REWRITE_REF`
+environment variable, which must be a colon separated list of refs or
+globs.
+
 pack.window::
        The size of the window used by linkgit:git-pack-objects[1] when no
        window size is given on the command line. Defaults to 10.
@@ -1198,12 +1469,20 @@ pack.compression::
 
 pack.deltaCacheSize::
        The maximum memory in bytes used for caching deltas in
-       linkgit:git-pack-objects[1].
-       A value of 0 means no limit. Defaults to 0.
+       linkgit:git-pack-objects[1] before writing them out to a pack.
+       This cache is used to speed up the writing object phase by not
+       having to recompute the final delta result once the best match
+       for all objects is found.  Repacking large repositories on machines
+       which are tight with memory might be badly impacted by this though,
+       especially if this cache pushes the system into swapping.
+       A value of 0 means no limit. The smallest size of 1 byte may be
+       used to virtually disable this cache. Defaults to 256 MiB.
 
 pack.deltaCacheLimit::
        The maximum size of a delta, that is cached in
-       linkgit:git-pack-objects[1]. Defaults to 1000.
+       linkgit:git-pack-objects[1]. This cache is used to speed up the
+       writing object phase by not having to recompute the final delta
+       result once the best match for all objects is found. Defaults to 1000.
 
 pack.threads::
        Specifies the number of threads to spawn when searching for best
@@ -1233,10 +1512,13 @@ you can use linkgit:git-index-pack[1] on the *.pack file to regenerate
 the `{asterisk}.idx` file.
 
 pack.packSizeLimit::
-       The default maximum size of a pack.  This setting only affects
-       packing to a file, i.e. the git:// protocol is unaffected.  It
-       can be overridden by the `\--max-pack-size` option of
-       linkgit:git-repack[1].
+       The maximum size of a pack.  This setting only affects
+       packing to a file when repacking, i.e. the git:// protocol
+       is unaffected.  It can be overridden by the `\--max-pack-size`
+       option of linkgit:git-repack[1]. The minimum size allowed is
+       limited to 1 MiB. The default is unlimited.
+       Common unit suffixes of 'k', 'm', or 'g' are
+       supported.
 
 pager.<cmd>::
        Allows turning on or off pagination of the output of a
@@ -1245,6 +1527,16 @@ pager.<cmd>::
        it takes precedence over this option.  To disable pagination for
        all commands, set `core.pager` or `GIT_PAGER` to `cat`.
 
+pretty.<name>::
+       Alias for a --pretty= format string, as specified in
+       linkgit:git-log[1]. Any aliases defined here can be used just
+       as the built-in pretty formats could. For example,
+       running `git config pretty.changelog "format:{asterisk} %H %s"`
+       would cause the invocation `git log --pretty=changelog`
+       to be equivalent to running `git log "--pretty=format:{asterisk} %H %s"`.
+       Note that an alias with the same name as a built-in format
+       will be silently ignored.
+
 pull.octopus::
        The default merge strategy to use when pulling multiple branches
        at once.
@@ -1269,6 +1561,14 @@ rebase.stat::
        Whether to show a diffstat of what changed upstream since the last
        rebase. False by default.
 
+rebase.autosquash::
+       If set to true enable '--autosquash' option by default.
+
+receive.autogc::
+       By default, git-receive-pack will run "git-gc --auto" after
+       receiving data from git-push and updating refs.  You can stop
+       it by setting this variable to false.
+
 receive.fsckObjects::
        If it is set to true, git-receive-pack will check all received
        objects. It will abort in the case of a malformed object or a
@@ -1289,25 +1589,36 @@ receive.denyDeletes::
        If set to true, git-receive-pack will deny a ref update that deletes
        the ref. Use this to prevent such a ref deletion via a push.
 
+receive.denyDeleteCurrent::
+       If set to true, git-receive-pack will deny a ref update that
+       deletes the currently checked out branch of a non-bare repository.
+
 receive.denyCurrentBranch::
-       If set to true or "refuse", receive-pack will deny a ref update
+       If set to true or "refuse", git-receive-pack will deny a ref update
        to the currently checked out branch of a non-bare repository.
        Such a push is potentially dangerous because it brings the HEAD
        out of sync with the index and working tree. If set to "warn",
        print a warning of such a push to stderr, but allow the push to
        proceed. If set to false or "ignore", allow such pushes with no
-       message. Defaults to "warn".
+       message. Defaults to "refuse".
 
 receive.denyNonFastForwards::
        If set to true, git-receive-pack will deny a ref update which is
-       not a fast forward. Use this to prevent such an update via a push,
+       not a fast-forward. Use this to prevent such an update via a push,
        even if that push is forced. This configuration variable is
        set when initializing a shared repository.
 
+receive.updateserverinfo::
+       If set to true, git-receive-pack will run git-update-server-info
+       after receiving data from git-push and updating refs.
+
 remote.<name>.url::
        The URL of a remote repository.  See linkgit:git-fetch[1] or
        linkgit:git-push[1].
 
+remote.<name>.pushurl::
+       The push URL of a remote repository.  See linkgit:git-push[1].
+
 remote.<name>.proxy::
        For remotes that require curl (http, https and ftp), the URL to
        the proxy to use for that remote.  Set to the empty string to
@@ -1327,7 +1638,13 @@ remote.<name>.mirror::
 
 remote.<name>.skipDefaultUpdate::
        If true, this remote will be skipped by default when updating
-       using the update subcommand of linkgit:git-remote[1].
+       using linkgit:git-fetch[1] or the `update` subcommand of
+       linkgit:git-remote[1].
+
+remote.<name>.skipFetchAll::
+       If true, this remote will be skipped by default when updating
+       using linkgit:git-fetch[1] or the `update` subcommand of
+       linkgit:git-remote[1].
 
 remote.<name>.receivepack::
        The default program to execute on the remote side when pushing.  See
@@ -1339,7 +1656,15 @@ remote.<name>.uploadpack::
 
 remote.<name>.tagopt::
        Setting this value to \--no-tags disables automatic tag following when
-       fetching from remote <name>
+       fetching from remote <name>. Setting it to \--tags will fetch every
+       tag from remote <name>, even if they are not reachable from remote
+       branch heads. Passing these flags directly to linkgit:git-fetch[1] can
+       override this setting. See options \--tags and \--no-tags of
+       linkgit:git-fetch[1].
+
+remote.<name>.vcs::
+       Setting this to a value <vcs> will cause git to interact with
+       the remote with the git-remote-<vcs> helper.
 
 remotes.<group>::
        The list of remotes which are fetched by "git remote update
@@ -1365,6 +1690,51 @@ rerere.enabled::
        default enabled if you create `rr-cache` directory under
        `$GIT_DIR`, but can be disabled by setting this option to false.
 
+sendemail.identity::
+       A configuration identity. When given, causes values in the
+       'sendemail.<identity>' subsection to take precedence over
+       values in the 'sendemail' section. The default identity is
+       the value of 'sendemail.identity'.
+
+sendemail.smtpencryption::
+       See linkgit:git-send-email[1] for description.  Note that this
+       setting is not subject to the 'identity' mechanism.
+
+sendemail.smtpssl::
+       Deprecated alias for 'sendemail.smtpencryption = ssl'.
+
+sendemail.<identity>.*::
+       Identity-specific versions of the 'sendemail.*' parameters
+       found below, taking precedence over those when the this
+       identity is selected, through command-line or
+       'sendemail.identity'.
+
+sendemail.aliasesfile::
+sendemail.aliasfiletype::
+sendemail.bcc::
+sendemail.cc::
+sendemail.cccmd::
+sendemail.chainreplyto::
+sendemail.confirm::
+sendemail.envelopesender::
+sendemail.from::
+sendemail.multiedit::
+sendemail.signedoffbycc::
+sendemail.smtppass::
+sendemail.suppresscc::
+sendemail.suppressfrom::
+sendemail.to::
+sendemail.smtpdomain::
+sendemail.smtpserver::
+sendemail.smtpserverport::
+sendemail.smtpuser::
+sendemail.thread::
+sendemail.validate::
+       See linkgit:git-send-email[1] for description.
+
+sendemail.signedoffcc::
+       Deprecated alias for 'sendemail.signedoffbycc'.
+
 showbranch.default::
        The default set of branches for linkgit:git-show-branch[1].
        See linkgit:git-show-branch[1].
@@ -1394,6 +1764,35 @@ If this variable is not specified, it defaults to 'normal'.
 This variable can be overridden with the -u|--untracked-files option
 of linkgit:git-status[1] and linkgit:git-commit[1].
 
+status.submodulesummary::
+       Defaults to false.
+       If this is set to a non zero number or true (identical to -1 or an
+       unlimited number), the submodule summary will be enabled and a
+       summary of commits for modified submodules will be shown (see
+       --summary-limit option of linkgit:git-submodule[1]).
+
+submodule.<name>.path::
+submodule.<name>.url::
+submodule.<name>.update::
+       The path within this project, URL, and the updating strategy
+       for a submodule.  These variables are initially populated
+       by 'git submodule init'; edit them to override the
+       URL and other values found in the `.gitmodules` file.  See
+       linkgit:git-submodule[1] and linkgit:gitmodules[5] for details.
+
+submodule.<name>.ignore::
+       Defines under what circumstances "git status" and the diff family show
+       a submodule as modified. When set to "all", it will never be considered
+       modified, "dirty" will ignore all changes to the submodules work tree and
+       takes only differences between the HEAD of the submodule and the commit
+       recorded in the superproject into account. "untracked" will additionally
+       let submodules with modified tracked files in their work tree show up.
+       Using "none" (the default when this option is not set) also shows
+       submodules that have untracked files in their work tree as changed.
+       This setting overrides any setting made in .gitmodules for this submodule,
+       both settings can be overridden on the command line by using the
+       "--ignore-submodules" option.
+
 tar.umask::
        This variable can be used to restrict the permission bits of
        tar archive entries.  The default is 0002, which turns off the
@@ -1417,6 +1816,19 @@ url.<base>.insteadOf::
        never-before-seen repository on the site.  When more than one
        insteadOf strings match a given URL, the longest match is used.
 
+url.<base>.pushInsteadOf::
+       Any URL that starts with this value will not be pushed to;
+       instead, it will be rewritten to start with <base>, and the
+       resulting URL will be pushed to. In cases where some site serves
+       a large number of repositories, and serves them with multiple
+       access methods, some of which do not allow push, this feature
+       allows people to specify a pull-only URL and have git
+       automatically use an appropriate URL to push, even for a
+       never-before-seen repository on the site.  When more than one
+       pushInsteadOf strings match a given URL, the longest match is
+       used.  If a remote has an explicit pushurl, git will ignore this
+       setting for that remote.
+
 user.email::
        Your email address to be recorded in any newly created commits.
        Can be overridden by the 'GIT_AUTHOR_EMAIL', 'GIT_COMMITTER_EMAIL', and
diff --git a/Documentation/date-formats.txt b/Documentation/date-formats.txt
new file mode 100644 (file)
index 0000000..c000f08
--- /dev/null
@@ -0,0 +1,26 @@
+DATE FORMATS
+------------
+
+The GIT_AUTHOR_DATE, GIT_COMMITTER_DATE environment variables
+ifdef::git-commit[]
+and the `--date` option
+endif::git-commit[]
+support the following date formats:
+
+Git internal format::
+       It is `<unix timestamp> <timezone offset>`, where `<unix
+       timestamp>` is the number of seconds since the UNIX epoch.
+       `<timezone offset>` is a positive or negative offset from UTC.
+       For example CET (which is 2 hours ahead UTC) is `+0200`.
+
+RFC 2822::
+       The standard email format as described by RFC 2822, for example
+       `Thu, 07 Apr 2005 22:13:13 +0200`.
+
+ISO 8601::
+       Time and date specified by the ISO 8601 standard, for example
+       `2005-04-07T22:13:13`. The parser accepts a space instead of the
+       `T` character as well.
++
+NOTE: In addition, the date part is accepted in the following formats:
+`YYYY.MM.DD`, `MM/DD/YYYY` and `DD.MM.YYYY`.
index 1eeb1c76838c1911fc4d57b36a16dece0538809a..15c7e794f4adaccb8885683946a9f4e7e4dc92c2 100644 (file)
@@ -1,4 +1,7 @@
-The output format from "git-diff-index", "git-diff-tree",
+Raw output format
+-----------------
+
+The raw output format from "git-diff-index", "git-diff-tree",
 "git-diff-files" and "git diff --raw" are very similar.
 
 These commands all compare two sets of things; what is
@@ -16,6 +19,9 @@ git-diff-tree [-r] <tree-ish-1> <tree-ish-2> [<pattern>...]::
 git-diff-files [<pattern>...]::
         compares the index and the files on the filesystem.
 
+The "git-diff-tree" command begins its output by printing the hash of
+what is being compared. After that, all the commands print one output
+line per changed file.
 
 An output line is formatted this way:
 
index 0f25ba7e3857e6c4f18c3589b31f082b602df6dc..8f9a2412fd44c80f2bf8d63271e2897ebeb3ddeb 100644 (file)
@@ -56,7 +56,8 @@ combined diff format
 
 "git-diff-tree", "git-diff-files" and "git-diff" can take '-c' or
 '--cc' option to produce 'combined diff'.  For showing a merge commit
-with "git log -p", this is the default format.
+with "git log -p", this is the default format; you can force showing
+full diff with the '-m' option.
 A 'combined diff' format looks like this:
 
 ------------
index 9276faeb11aec2650393c56fe5edf2b571692dbd..4656a97e6012796f182a725e14c13dc03f71bba2 100644 (file)
@@ -14,12 +14,14 @@ endif::git-format-patch[]
 
 ifdef::git-format-patch[]
 -p::
-       Generate patches without diffstat.
+--no-stat::
+       Generate plain patches without any diffstats.
 endif::git-format-patch[]
 
 ifndef::git-format-patch[]
 -p::
 -u::
+--patch::
        Generate patch (see section on generating patches).
        {git-diff? This is the default.}
 endif::git-format-patch[]
@@ -27,33 +29,40 @@ endif::git-format-patch[]
 -U<n>::
 --unified=<n>::
        Generate diffs with <n> lines of context instead of
-       the usual three. Implies "-p".
+       the usual three.
+ifndef::git-format-patch[]
+       Implies `-p`.
+endif::git-format-patch[]
 
+ifndef::git-format-patch[]
 --raw::
        Generate the raw format.
        {git-diff-core? This is the default.}
+endif::git-format-patch[]
 
+ifndef::git-format-patch[]
 --patch-with-raw::
-       Synonym for "-p --raw".
+       Synonym for `-p --raw`.
+endif::git-format-patch[]
 
 --patience::
        Generate a diff using the "patience diff" algorithm.
 
 --stat[=width[,name-width]]::
        Generate a diffstat.  You can override the default
-       output width for 80-column terminal by "--stat=width".
+       output width for 80-column terminal by `--stat=width`.
        The width of the filename part can be controlled by
        giving another width to it separated by a comma.
 
 --numstat::
-       Similar to \--stat, but shows number of added and
+       Similar to `\--stat`, but shows number of added and
        deleted lines in decimal notation and pathname without
        abbreviation, to make it more machine friendly.  For
        binary files, outputs two `-` instead of saying
        `0 0`.
 
 --shortstat::
-       Output only the last line of the --stat format containing total
+       Output only the last line of the `--stat` format containing total
        number of modified files, as well as number of added and deleted
        lines.
 
@@ -61,24 +70,39 @@ endif::git-format-patch[]
        Output the distribution of relative amount of changes (number of lines added or
        removed) for each sub-directory. Directories with changes below
        a cut-off percent (3% by default) are not shown. The cut-off percent
-       can be set with "--dirstat=limit". Changes in a child directory is not
-       counted for the parent directory, unless "--cumulative" is used.
+       can be set with `--dirstat=limit`. Changes in a child directory is not
+       counted for the parent directory, unless `--cumulative` is used.
 
 --dirstat-by-file[=limit]::
-       Same as --dirstat, but counts changed files instead of lines.
+       Same as `--dirstat`, but counts changed files instead of lines.
 
 --summary::
        Output a condensed summary of extended header information
        such as creations, renames and mode changes.
 
+ifndef::git-format-patch[]
 --patch-with-stat::
-       Synonym for "-p --stat".
-       {git-format-patch? This is the default.}
+       Synonym for `-p --stat`.
+endif::git-format-patch[]
+
+ifndef::git-format-patch[]
 
 -z::
-       NUL-line termination on output.  This affects the --raw
-       output field terminator.  Also output from commands such
-       as "git-log" will be delimited with NUL between commits.
+ifdef::git-log[]
+       Separate the commits with NULs instead of with new newlines.
++
+Also, when `--raw` or `--numstat` has been given, do not munge
+pathnames and use NULs as output field terminators.
+endif::git-log[]
+ifndef::git-log[]
+       When `--raw`, `--numstat`, `--name-only` or `--name-status` has been
+       given, do not munge pathnames and use NULs as output field terminators.
+endif::git-log[]
++
+Without this option, each pathname output will have TAB, LF, double quotes,
+and backslash characters replaced with `\t`, `\n`, `\"`, and `\\`,
+respectively, and the pathname will be enclosed in double quotes if
+any of those replacements occurred.
 
 --name-only::
        Show only names of changed files.
@@ -87,18 +111,55 @@ endif::git-format-patch[]
        Show only names and status of changed files. See the description
        of the `--diff-filter` option on what the status letters mean.
 
---color::
+--submodule[=<format>]::
+       Chose the output format for submodule differences. <format> can be one of
+       'short' and 'log'. 'short' just shows pairs of commit names, this format
+       is used when this option is not given. 'log' is the default value for this
+       option and lists the commits in that commit range like the 'summary'
+       option of linkgit:git-submodule[1] does.
+
+--color[=<when>]::
        Show colored diff.
+       The value must be always (the default), never, or auto.
 
 --no-color::
        Turn off colored diff, even when the configuration file
        gives the default to color output.
+       Same as `--color=never`.
 
---color-words[=<regex>]::
-       Show colored word diff, i.e., color words which have changed.
-       By default, words are separated by whitespace.
+--word-diff[=<mode>]::
+       Show a word diff, using the <mode> to delimit changed words.
+       By default, words are delimited by whitespace; see
+       `--word-diff-regex` below.  The <mode> defaults to 'plain', and
+       must be one of:
++
+--
+color::
+       Highlight changed words using only colors.  Implies `--color`.
+plain::
+       Show words as `[-removed-]` and `{+added+}`.  Makes no
+       attempts to escape the delimiters if they appear in the input,
+       so the output may be ambiguous.
+porcelain::
+       Use a special line-based format intended for script
+       consumption.  Added/removed/unchanged runs are printed in the
+       usual unified diff format, starting with a `+`/`-`/` `
+       character at the beginning of the line and extending to the
+       end of the line.  Newlines in the input are represented by a
+       tilde `~` on a line of its own.
+none::
+       Disable word diff again.
+--
++
+Note that despite the name of the first mode, color is used to
+highlight the changed parts in all modes if enabled.
+
+--word-diff-regex=<regex>::
+       Use <regex> to decide what a word is, instead of considering
+       runs of non-whitespace to be a word.  Also implies
+       `--word-diff` unless it was already enabled.
 +
-When a <regex> is specified, every non-overlapping match of the
+Every non-overlapping match of the
 <regex> is considered a word.  Anything between these matches is
 considered whitespace and ignored(!) for the purposes of finding
 differences.  You may want to append `|[^[:space:]]` to your regular
@@ -111,15 +172,22 @@ linkgit:gitattributes[1] or linkgit:git-config[1].  Giving it explicitly
 overrides any diff driver or configuration setting.  Diff drivers
 override configuration settings.
 
+--color-words[=<regex>]::
+       Equivalent to `--word-diff=color` plus (if a regex was
+       specified) `--word-diff-regex=<regex>`.
+endif::git-format-patch[]
+
 --no-renames::
        Turn off rename detection, even when the configuration
        file gives the default to do so.
 
+ifndef::git-format-patch[]
 --check::
        Warn if changes introduce trailing whitespace
        or an indent that uses a space before a tab. Exits with
        non-zero status if problems are found. Not compatible with
        --exit-code.
+endif::git-format-patch[]
 
 --full-index::
        Instead of the first handful of characters, show the full
@@ -127,26 +195,59 @@ override configuration settings.
        line when generating patch format output.
 
 --binary::
-       In addition to --full-index, output "binary diff" that
-       can be applied with "git apply".
+       In addition to `--full-index`, output a binary diff that
+       can be applied with `git-apply`.
 
 --abbrev[=<n>]::
        Instead of showing the full 40-byte hexadecimal object
        name in diff-raw format output and diff-tree header
        lines, show only a partial prefix.  This is
-       independent of --full-index option above, which controls
+       independent of the `--full-index` option above, which controls
        the diff-patch output format.  Non default number of
-       digits can be specified with --abbrev=<n>.
+       digits can be specified with `--abbrev=<n>`.
 
--B::
-       Break complete rewrite changes into pairs of delete and create.
-
--M::
+-B[<n>][/<m>]::
+       Break complete rewrite changes into pairs of delete and
+       create. This serves two purposes:
++
+It affects the way a change that amounts to a total rewrite of a file
+not as a series of deletion and insertion mixed together with a very
+few lines that happen to match textually as the context, but as a
+single deletion of everything old followed by a single insertion of
+everything new, and the number `m` controls this aspect of the -B
+option (defaults to 60%). `-B/70%` specifies that less than 30% of the
+original should remain in the result for git to consider it a total
+rewrite (i.e. otherwise the resulting patch will be a series of
+deletion and insertion mixed together with context lines).
++
+When used with -M, a totally-rewritten file is also considered as the
+source of a rename (usually -M only considers a file that disappeared
+as the source of a rename), and the number `n` controls this aspect of
+the -B option (defaults to 50%). `-B20%` specifies that a change with
+addition and deletion compared to 20% or more of the file's size are
+eligible for being picked up as a possible source of a rename to
+another file.
+
+-M[<n>]::
+ifndef::git-log[]
        Detect renames.
+endif::git-log[]
+ifdef::git-log[]
+       If generating diffs, detect and report renames for each commit.
+       For following files across renames while traversing history, see
+       `--follow`.
+endif::git-log[]
+       If `n` is specified, it is a is a threshold on the similarity
+       index (i.e. amount of addition/deletions compared to the
+       file's size). For example, `-M90%` means git should consider a
+       delete/add pair to be a rename if more than 90% of the file
+       hasn't changed.
 
--C::
+-C[<n>]::
        Detect copies as well as renames.  See also `--find-copies-harder`.
+       If `n` is specified, it has the same meaning as for `-M<n>`.
 
+ifndef::git-format-patch[]
 --diff-filter=[ACDMRTUXB*]::
        Select only files that are Added (`A`), Copied (`C`),
        Deleted (`D`), Modified (`M`), Renamed (`R`), have their
@@ -158,6 +259,7 @@ override configuration settings.
        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.
+endif::git-format-patch[]
 
 --find-copies-harder::
        For performance reasons, by default, `-C` option finds copies only
@@ -169,12 +271,13 @@ override configuration settings.
        `-C` option has the same effect.
 
 -l<num>::
-       -M and -C options require O(n^2) processing time where n
+       The `-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.
 
+ifndef::git-format-patch[]
 -S<string>::
        Look for differences that introduce or remove an instance of
        <string>. Note that this is different than the string simply
@@ -182,18 +285,20 @@ override configuration settings.
        linkgit:gitdiffcore[7] for more details.
 
 --pickaxe-all::
-       When -S finds a change, show all the changes in that
+       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.
+endif::git-format-patch[]
 
 -O<orderfile>::
        Output the patch in the order specified in the
        <orderfile>, which has one shell glob pattern per line.
 
+ifndef::git-format-patch[]
 -R::
        Swap two inputs; that is, show differences from index or
        on-disk file to tree contents.
@@ -205,6 +310,7 @@ override configuration settings.
        not in a subdirectory (e.g. in a bare repository), you
        can name which subdirectory to make the output relative
        to by giving a <path> as an argument.
+endif::git-format-patch[]
 
 -a::
 --text::
@@ -229,13 +335,15 @@ override configuration settings.
        Show the context between diff hunks, up to the specified number
        of lines, thereby fusing hunks that are close to each other.
 
+ifndef::git-format-patch[]
 --exit-code::
        Make the program exit with codes similar to diff(1).
        That is, it exits with 1 if there were differences and
        0 means no differences.
 
 --quiet::
-       Disable all output of the program. Implies --exit-code.
+       Disable all output of the program. Implies `--exit-code`.
+endif::git-format-patch[]
 
 --ext-diff::
        Allow an external diff helper to be executed. If you set an
@@ -245,8 +353,18 @@ override configuration settings.
 --no-ext-diff::
        Disallow external diff drivers.
 
---ignore-submodules::
-       Ignore changes to submodules in the diff generation.
+--ignore-submodules[=<when>]::
+       Ignore changes to submodules in the diff generation. <when> can be
+       either "none", "untracked", "dirty" or "all", which is the default
+       Using "none" will consider the submodule modified when it either contains
+       untracked or modified files or its HEAD differs from the commit recorded
+       in the superproject and can be used to override any settings of the
+       'ignore' option in linkgit:git-config[1] or linkgit:gitmodules[5]. When
+       "untracked" is used submodules are not considered dirty when they only
+       contain untracked content (but they are still scanned for modified
+       content). Using "dirty" ignores all changes to the work tree of submodules,
+       only changes to the commits stored in the superproject are shown (this was
+       the behavior until 1.7.0). Using "all" hides all changes to submodules.
 
 --src-prefix=<prefix>::
        Show the given source prefix instead of "a/".
index 9a6912c641edf52083b8996fdce3a0be2f4dba45..da8b05b92283a37b9552f322bbe31df884a3cae7 100644 (file)
@@ -1,5 +1,8 @@
 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version='1.0'>
  <xsl:import href="http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl"/>
- <xsl:output method="html" encoding="UTF-8" indent="no" />
+ <xsl:output method="html"
+     encoding="UTF-8" indent="no"
+     doctype-public="-//W3C//DTD HTML 4.01//EN"
+     doctype-system="http://www.w3.org/TR/html4/strict.dtd" />
 </xsl:stylesheet>
index 9310b650d3ca6b6cb7d69814eb9b800e8c2c85cd..e0ba8cc07549af375c89496c57c016a41b8cc699 100644 (file)
@@ -1,13 +1,8 @@
 Everyday GIT With 20 Commands Or So
 ===================================
 
-<<Basic Repository>> commands are needed by people who have 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.
+<<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
 the <<Individual Developer (Participant)>> section as well.
@@ -20,46 +15,6 @@ administrators who are responsible for the care and feeding
 of git repositories.
 
 
-Basic Repository[[Basic Repository]]
-------------------------------------
-
-Everybody uses these commands to maintain git repositories.
-
-  * linkgit:git-init[1] or linkgit:git-clone[1] to create a
-    new repository.
-
-  * linkgit:git-fsck[1] to check the repository for errors.
-
-  * linkgit:git-gc[1] to do common housekeeping tasks such as
-    repack and prune.
-
-Examples
-~~~~~~~~
-
-Check health and remove cruft.::
-+
-------------
-$ git fsck <1>
-$ git count-objects <2>
-$ git gc <3>
-------------
-+
-<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> repacks the local repository and performs other housekeeping tasks.
-
-Repack a small project into single pack.::
-+
-------------
-$ git gc <1>
-------------
-+
-<1> pack all the objects reachable from the refs into one pack,
-then remove the other packs.
-
-
 Individual Developer (Standalone)[[Individual Developer (Standalone)]]
 ----------------------------------------------------------------------
 
@@ -67,6 +22,8 @@ A standalone individual developer does not exchange patches with
 other people, and works alone in a single repository, using the
 following commands.
 
+  * linkgit:git-init[1] to create a new repository.
+
   * linkgit:git-show-branch[1] to see where you are.
 
   * linkgit:git-log[1] to see what happened.
index d313795fdbc420e3395adc42aebe82fabda037d4..470ac31396606a18298f69329b050a86943c580c 100644 (file)
@@ -1,11 +1,5 @@
--q::
---quiet::
-       Pass --quiet to git-fetch-pack and silence any other internally
-       used programs.
-
--v::
---verbose::
-       Be verbose.
+--all::
+       Fetch all remotes.
 
 -a::
 --append::
        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.
+--depth=<depth>::
+       Deepen the history of a 'shallow' repository created by
+       `git clone` with `--depth=<depth>` option (see linkgit:git-clone[1])
+       by the specified number of commits.
+
+ifndef::git-pull[]
+--dry-run::
+       Show what would be done, without making any changes.
+endif::git-pull[]
 
 -f::
 --force::
-       When 'git-fetch' is used with `<rbranch>:<lbranch>`
+       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.
 
+-k::
+--keep::
+       Keep downloaded pack.
+
+ifndef::git-pull[]
+--multiple::
+       Allow several <repository> and <group> arguments to be
+       specified. No <refspec>s may be specified.
+
+-p::
+--prune::
+       After fetching, remove any remote tracking branches which
+       no longer exist on the remote.
+endif::git-pull[]
+
 ifdef::git-pull[]
 --no-tags::
 endif::git-pull[]
@@ -36,7 +49,9 @@ ifndef::git-pull[]
 endif::git-pull[]
        By default, tags that point at objects that are downloaded
        from the remote repository are fetched and stored locally.
-       This option disables this automatic tag following.
+       This option disables this automatic tag following. The default
+       behavior for a remote may be specified with the remote.<name>.tagopt
+       setting. See linkgit:git-config[1].
 
 -t::
 --tags::
@@ -45,22 +60,39 @@ endif::git-pull[]
        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.
+       downloaded. The default behavior for a remote may be
+       specified with the remote.<name>.tagopt setting. See
+       linkgit:git-config[1].
 
 -u::
 --update-head-ok::
-       By default 'git-fetch' refuses to update the head which
+       By default 'git fetch' refuses to update the head which
        corresponds to the current branch.  This flag disables the
-       check.  This is purely for the internal use for 'git-pull'
-       to communicate with 'git-fetch', and unless you are
+       check.  This is purely for the internal use for 'git pull'
+       to communicate with 'git fetch', and unless you are
        implementing your own Porcelain you are not supposed to
        use it.
 
---depth=<depth>::
-       Deepen the history of a 'shallow' repository created by
-       `git clone` with `--depth=<depth>` option (see linkgit:git-clone[1])
-       by the specified number of commits.
+--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.
+
+ifndef::git-pull[]
+-q::
+--quiet::
+       Pass --quiet to git-fetch-pack and silence any other internally
+       used git commands. Progress is not reported to the standard error
+       stream.
+
+-v::
+--verbose::
+       Be verbose.
+endif::git-pull[]
+
+--progress::
+       Progress status is reported on the standard error stream
+       by default when it is attached to a terminal, unless -q
+       is specified. This flag forces progress status even if the
+       standard error stream is not directed to a terminal.
index d938b422893067feb1a430edb34fb51ef7db6d85..73378b2bef8405f54c661e8c9745057b5b243f4d 100644 (file)
@@ -9,33 +9,38 @@ SYNOPSIS
 --------
 [verse]
 'git add' [-n] [-v] [--force | -f] [--interactive | -i] [--patch | -p]
-         [--all | [--update | -u]] [--intent-to-add | -N]
-         [--refresh] [--ignore-errors] [--] <filepattern>...
+         [--edit | -e] [--all | [--update | -u]] [--intent-to-add | -N]
+         [--refresh] [--ignore-errors] [--ignore-missing] [--]
+         [<filepattern>...]
 
 DESCRIPTION
 -----------
-This command adds the current content of new or modified files to the
-index, thus staging that content for inclusion in the next commit.
+This command updates the index using the current content found in
+the working tree, to prepare the content staged for the next commit.
+It typically adds the current content of existing paths as a whole,
+but with some options it can also be used to add content with
+only part of the changes made to the working tree files applied, or
+remove paths that do not exist in the working tree anymore.
 
 The "index" holds a snapshot of the content of the working tree, and it
 is this snapshot that is taken as the contents of the next commit.  Thus
 after making any changes to the working directory, and before running
-the commit command, you must use the 'add' command to add any new or
+the commit command, you must use the `add` command to add any new or
 modified files to the index.
 
 This command can be performed multiple times before a commit.  It only
 adds the content of the specified file(s) at the time the add command is
 run; if you want subsequent changes included in the next commit, then
-you must run 'git add' again to add the new content to the index.
+you must run `git add` again to add the new content to the index.
 
-The 'git status' command can be used to obtain a summary of which
+The `git status` command can be used to obtain a summary of which
 files have changes that are staged for the next commit.
 
-The 'git add' command will not add ignored files by default.  If any
-ignored files were explicitly specified on the command line, 'git add'
+The `git add` command will not add ignored files by default.  If any
+ignored files were explicitly specified on the command line, `git add`
 will fail with a list of ignored files.  Ignored files reached by
 directory recursion or filename globbing performed by Git (quote your
-globs before the shell) will be silently ignored.  The 'add' command can
+globs before the shell) will be silently ignored.  The 'git add' command can
 be used to add ignored files with the `-f` (force) option.
 
 Please see linkgit:git-commit[1] for alternative ways to add content to a
@@ -53,7 +58,8 @@ OPTIONS
 
 -n::
 --dry-run::
-        Don't actually add the file(s), just show if they exist.
+       Don't actually add the file(s), just show if they exist and/or will
+       be ignored.
 
 -v::
 --verbose::
@@ -72,34 +78,51 @@ OPTIONS
 
 -p::
 --patch::
-       Similar to Interactive mode but the initial command loop is
-       bypassed and the 'patch' subcommand is invoked using each of
-       the specified filepatterns before exiting.
+       Interactively choose hunks of patch between the index and the
+       work tree and add them to the index. This gives the user a chance
+       to review the difference before adding modified contents to the
+       index.
++
+This effectively runs `add --interactive`, but bypasses the
+initial command menu and directly jumps to the `patch` subcommand.
+See ``Interactive mode'' for details.
+
+-e, \--edit::
+       Open the diff vs. the index in an editor and let the user
+       edit it.  After the editor was closed, adjust the hunk headers
+       and apply the patch to the index.
++
+*NOTE*: Obviously, if you change anything else than the first character
+on lines beginning with a space or a minus, the patch will no longer
+apply.
 
 -u::
 --update::
-       Update only files that git already knows about, staging modified
-       content for commit and marking deleted files for removal. This
-       is similar
-       to what "git commit -a" does in preparation for making a commit,
-       except that the update is limited to paths specified on the
-       command line. If no paths are specified, all tracked files in the
-       current directory and its subdirectories are updated.
+       Only match <filepattern> against already tracked files in
+       the index rather than the working tree. That means that it
+       will never stage new files, but that it will stage modified
+       new contents of tracked files and that it will remove files
+       from the index if the corresponding files in the working tree
+       have been removed.
++
+If no <filepattern> is given, default to "."; in other words,
+update all tracked files in the current directory and its
+subdirectories.
 
 -A::
 --all::
-       Update files that git already knows about (same as '\--update')
-       and add all untracked files that are not ignored by '.gitignore'
-       mechanism.
-
+       Like `-u`, but match <filepattern> against files in the
+       working tree in addition to the index. That means that it
+       will find new files as well as staging modified content and
+       removing files that are no longer in the working tree.
 
 -N::
 --intent-to-add::
        Record only the fact that the path will be added later. An entry
        for the path is placed in the index with no content. This is
        useful for, among other things, showing the unstaged content of
-       such files with 'git diff' and committing them with 'git commit
-       -a'.
+       such files with `git diff` and committing them with `git commit
+       -a`.
 
 --refresh::
        Don't add the file(s), but only refresh their stat()
@@ -110,6 +133,12 @@ OPTIONS
        them, do not abort the operation, but continue adding the
        others. The command shall still exit with non-zero status.
 
+--ignore-missing::
+       This option can only be used together with --dry-run. By using
+       this option the user can check if any of the given files would
+       be ignored, no matter if they are already present in the work
+       tree or not.
+
 \--::
        This option can be used to separate command-line options from
        the list of files, (useful when filenames might be mistaken
@@ -119,7 +148,7 @@ OPTIONS
 Configuration
 -------------
 
-The optional configuration variable 'core.excludesfile' indicates a path to a
+The optional configuration variable `core.excludesfile` indicates a path to a
 file containing patterns of file names to exclude from git-add, similar to
 $GIT_DIR/info/exclude.  Patterns in the exclude file are used in addition to
 those in info/exclude.  See linkgit:gitrepository-layout[5].
@@ -128,14 +157,14 @@ those in info/exclude.  See linkgit:gitrepository-layout[5].
 EXAMPLES
 --------
 
-* Adds content from all `\*.txt` files under `Documentation` directory
+* Adds content from all `*.txt` files under `Documentation` directory
 and its subdirectories:
 +
 ------------
-$ git add Documentation/\\*.txt
+$ git add Documentation/\*.txt
 ------------
 +
-Note that the asterisk `\*` is quoted from the shell in this
+Note that the asterisk `*` is quoted from the shell in this
 example; this lets the command include the files from
 subdirectories of `Documentation/` directory.
 
@@ -167,7 +196,7 @@ and type return, like this:
     What now> 1
 ------------
 
-You also could say "s" or "sta" or "status" above as long as the
+You also could say `s` or `sta` or `status` above as long as the
 choice is unique.
 
 The main command loop has 6 subcommands (plus help and quit).
@@ -175,9 +204,9 @@ The main command loop has 6 subcommands (plus help and quit).
 status::
 
    This shows the change between HEAD and index (i.e. what will be
-   committed if you say "git commit"), and between index and
+   committed if you say `git commit`), and between index and
    working tree files (i.e. what you could stage further before
-   "git commit" using "git-add") for each path.  A sample output
+   `git commit` using `git add`) for each path.  A sample output
    looks like this:
 +
 ------------
@@ -191,7 +220,7 @@ binary so line count cannot be shown) and there is no
 difference between indexed copy and the working tree
 version (if the working tree version were also different,
 'binary' would have been shown in place of 'nothing').  The
-other file, git-add--interactive.perl, has 403 lines added
+other file, git-add{litdd}interactive.perl, has 403 lines added
 and 35 lines deleted if you commit what is in the index, but
 working tree file has further modifications (one addition and
 one deletion).
@@ -245,9 +274,9 @@ patch::
 
        y - stage this hunk
        n - do not stage this hunk
-       q - quit, do not stage this hunk nor any of the remaining ones
-       a - stage this and all the remaining hunks in the file
-       d - do not stage this hunk nor any of the remaining hunks in the file
+       q - quit; do not stage this hunk nor any of the remaining ones
+       a - stage this hunk and all later hunks in the file
+       d - do not stage this hunk nor any of the later hunks in the file
        g - select a hunk to go to
        / - search for a hunk matching the given regex
        j - leave this hunk undecided, see next undecided hunk
index 6d92cbee6475e29660d91a97683bf5843aacab38..9e62f8778f6590328de4d9c0c7c08044e8019eac 100644 (file)
@@ -9,13 +9,13 @@ git-am - Apply a series of patches from a mailbox
 SYNOPSIS
 --------
 [verse]
-'git am' [--signoff] [--keep] [--utf8 | --no-utf8]
+'git am' [--signoff] [--keep] [--keep-cr | --no-keep-cr] [--utf8 | --no-utf8]
         [--3way] [--interactive] [--committer-date-is-author-date]
-        [--ignore-date]
+        [--ignore-date] [--ignore-space-change | --ignore-whitespace]
         [--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>]
-        [--reject]
+        [--reject] [-q | --quiet] [--scissors | --no-scissors]
         [<mbox> | <Maildir>...]
-'git am' (--skip | --resolved | --abort)
+'git am' (--continue | --skip | --abort)
 
 DESCRIPTION
 -----------
@@ -37,11 +37,30 @@ OPTIONS
 
 -k::
 --keep::
-       Pass `-k` flag to 'git-mailinfo' (see linkgit:git-mailinfo[1]).
+       Pass `-k` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]).
+
+--keep-cr::
+--no-keep-cr::
+       With `--keep-cr`, call 'git mailsplit' (see linkgit:git-mailsplit[1])
+       with the same option, to prevent it from stripping CR at the end of
+       lines. `am.keepcr` configuration variable can be used to specify the
+       default behaviour.  `--no-keep-cr` is useful to override `am.keepcr`.
+
+-c::
+--scissors::
+       Remove everything in body before a scissors line (see
+       linkgit:git-mailinfo[1]).
+
+--no-scissors::
+       Ignore scissors lines (see linkgit:git-mailinfo[1]).
+
+-q::
+--quiet::
+       Be quiet. Only print error messages.
 
 -u::
 --utf8::
-       Pass `-u` flag to 'git-mailinfo' (see linkgit:git-mailinfo[1]).
+       Pass `-u` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]).
        The proposed commit log message taken from the e-mail
        is re-coded into UTF-8 encoding (configuration variable
        `i18n.commitencoding` can be used to specify project's
@@ -51,7 +70,7 @@ This was optional in prior versions of git, but now it is the
 default.   You can use `--no-utf8` to override this.
 
 --no-utf8::
-       Pass `-n` flag to 'git-mailinfo' (see
+       Pass `-n` flag to 'git mailinfo' (see
        linkgit:git-mailinfo[1]).
 
 -3::
@@ -61,12 +80,15 @@ default.   You can use `--no-utf8` to override this.
        it is supposed to apply to and we have those blobs
        available locally.
 
+--ignore-date::
+--ignore-space-change::
+--ignore-whitespace::
 --whitespace=<option>::
 -C<n>::
 -p<n>::
 --directory=<dir>::
 --reject::
-       These flags are passed to the 'git-apply' (see linkgit:git-apply[1])
+       These flags are passed to the 'git apply' (see linkgit:git-apply[1])
        program that applies
        the patch.
 
@@ -92,6 +114,7 @@ default.   You can use `--no-utf8` to override this.
        Skip the current patch.  This is only meaningful when
        restarting an aborted patch.
 
+--continue::
 -r::
 --resolved::
        After a patch failure (e.g. attempting to apply
@@ -106,7 +129,7 @@ default.   You can use `--no-utf8` to override this.
        to the screen before exiting.  This overrides the
        standard message informing you to use `--resolved`
        or `--skip` to handle the failure.  This is solely
-       for internal use between 'git-rebase' and 'git-am'.
+       for internal use between 'git rebase' and 'git am'.
 
 --abort::
        Restore the original branch and abort the patching operation.
@@ -121,10 +144,8 @@ the commit, after stripping common prefix "[PATCH <anything>]".
 The "Subject: " line is supposed to concisely describe what the
 commit is about in one line of text.
 
-"From: " and "Subject: " lines starting the body (the rest of the
-message after the blank line terminating the RFC2822 headers)
-override the respective commit author name and title values taken
-from the headers.
+"From: " and "Subject: " lines starting the body override the respective
+commit author name and title values taken from the headers.
 
 The commit message is formed by the title taken from the
 "Subject: ", a blank line and the body of the message up to
index 9e5baa277731adf14e47fda8c6b13a076e0873db..4a74b23d403d970643fcb8e0438ab6649f54e54c 100644 (file)
@@ -3,7 +3,7 @@ git-apply(1)
 
 NAME
 ----
-git-apply - Apply a patch on a git index file and a working tree
+git-apply - Apply a patch to files and/or to the index
 
 
 SYNOPSIS
@@ -13,14 +13,22 @@ SYNOPSIS
          [--apply] [--no-add] [--build-fake-ancestor=<file>] [-R | --reverse]
          [--allow-binary-replacement | --binary] [--reject] [-z]
          [-pNUM] [-CNUM] [--inaccurate-eof] [--recount] [--cached]
+         [--ignore-space-change | --ignore-whitespace ]
          [--whitespace=<nowarn|warn|fix|error|error-all>]
          [--exclude=PATH] [--include=PATH] [--directory=<root>]
          [--verbose] [<patch>...]
 
 DESCRIPTION
 -----------
-Reads supplied 'diff' output and applies it on a git index file
-and a work tree.
+Reads the supplied diff output (i.e. "a patch") and applies it to files.
+With the `--index` option the patch is also applied to the index, and
+with the `--cache` option the patch is only applied to the index.
+Without these options, the command applies the patch only to files,
+and does not require them to be in a git repository.
+
+This command applies the patch but does not create a commit.  Use
+linkgit:git-am[1] to create commits from patches generated by
+linkgit:git-format-patch[1] and/or received by email.
 
 OPTIONS
 -------
@@ -33,7 +41,7 @@ OPTIONS
        input.  Turns off "apply".
 
 --numstat::
-       Similar to \--stat, but shows the number of added and
+       Similar to `--stat`, but shows the number of added and
        deleted lines in decimal notation and the pathname without
        abbreviation, to make it more machine friendly.  For
        binary files, outputs two `-` instead of saying
@@ -47,25 +55,25 @@ OPTIONS
 
 --check::
        Instead of applying the patch, see if the patch is
-       applicable to the current work tree and/or the index
+       applicable to the current working tree and/or the index
        file and detects errors.  Turns off "apply".
 
 --index::
-       When --check is in effect, or when applying the patch
+       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
+       the file to be patched in the working tree is not
        up-to-date, it is flagged as an error.  This flag also
        causes the index file to be updated.
 
 --cached::
        Apply a patch without touching the working tree. Instead take the
        cached data, apply the patch, and store the result in the index
-       without using the working tree. This implies '--index'.
+       without using the working tree. This implies `--index`.
 
 --build-fake-ancestor=<file>::
-       Newer 'git-diff' output has embedded 'index information'
+       Newer 'git diff' output has embedded 'index information'
        for each blob to help identify the original version that
        the patch applies to.  When this flag is given, and if
        the original versions of the blobs are available locally,
@@ -79,18 +87,20 @@ the information is read from the current index instead.
        Apply the patch in reverse.
 
 --reject::
-       For atomicity, 'git-apply' by default fails the whole patch and
+       For atomicity, 'git apply' by default fails the whole patch and
        does not touch the working tree when some of the hunks
        do not apply.  This option makes it apply
        the parts of the patch that are applicable, and leave the
        rejected hunks in corresponding *.rej files.
 
 -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.
+       When `--numstat` has been given, do not munge pathnames,
+       but use a NUL-terminated machine-readable format.
++
+Without this option, each pathname output will have TAB, LF, double quotes,
+and backslash characters replaced with `\t`, `\n`, `\"`, and `\\`,
+respectively, and the pathname will be enclosed in double quotes if
+any of those replacements occurred.
 
 -p<n>::
        Remove <n> leading slashes from traditional diff paths. The
@@ -103,18 +113,18 @@ the information is read from the current index instead.
        ever ignored.
 
 --unidiff-zero::
-       By default, 'git-apply' expects that the patch being
+       By default, 'git apply' expects that the patch being
        applied is a unified diff with at least one line of context.
        This provides good safety measures, but breaks down when
-       applying a diff generated with --unified=0. To bypass these
-       checks use '--unidiff-zero'.
+       applying a diff generated with `--unified=0`. To bypass these
+       checks use `--unidiff-zero`.
 +
 Note, for the reasons stated above usage of context-free patches is
 discouraged.
 
 --apply::
        If you use any of the options marked "Turns off
-       'apply'" above, 'git-apply' reads and outputs the
+       'apply'" above, 'git apply' reads and outputs the
        requested information without actually applying the
        patch.  Give this flag after those flags to also apply
        the patch.
@@ -143,12 +153,20 @@ discouraged.
        be useful when importing patchsets, where you want to include certain
        files or directories.
 +
-When --exclude and --include patterns are used, they are examined in the
+When `--exclude` and `--include` patterns are used, they are examined in the
 order they appear on the command line, and the first match determines if a
 patch to each path is used.  A patch to a path that does not match any
 include/exclude pattern is used by default if there is no include pattern
 on the command line, and ignored if there is any include pattern.
 
+--ignore-space-change::
+--ignore-whitespace::
+       When applying a patch, ignore changes in whitespace in context
+       lines if necessary.
+       Context lines will preserve their whitespace, and they will not
+       undergo whitespace fixing regardless of the value of the
+       `--whitespace` option. New lines will still be fixed, though.
+
 --whitespace=<action>::
        When applying a patch, detect a new or modified line that has
        whitespace errors.  What are considered whitespace errors is
@@ -205,25 +223,35 @@ running `git apply --directory=modules/git-gui`.
 Configuration
 -------------
 
+apply.ignorewhitespace::
+       Set to 'change' if you want changes in whitespace to be ignored by default.
+       Set to one of: no, none, never, false if you want changes in
+       whitespace to be significant.
 apply.whitespace::
        When no `--whitespace` flag is given from the command
        line, this configuration item is used as the default.
 
 Submodules
 ----------
-If the patch contains any changes to submodules then 'git-apply'
+If the patch contains any changes to submodules then 'git apply'
 treats these changes as follows.
 
-If --index is specified (explicitly or implicitly), then the submodule
+If `--index` is specified (explicitly or implicitly), then the submodule
 commits must match the index exactly for the patch to apply.  If any
 of the submodules are checked-out, then these check-outs are completely
 ignored, i.e., they are not required to be up-to-date or clean and they
 are not updated.
 
-If --index is not specified, then the submodule commits in the patch
+If `--index` is not specified, then the submodule commits in the patch
 are ignored and only the absence or presence of the corresponding
 subdirectory is checked and (if possible) updated.
 
+
+SEE ALSO
+--------
+linkgit:git-am[1].
+
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
index c7a6e3ec050b7ceeec79d468b5ffa123314c8f5d..4f358c8d6c3320c5ef0a78d6c4d155535433f3d0 100644 (file)
@@ -29,22 +29,22 @@ branches that have different roots, it will refuse to run. In that case,
 edit your <archive/branch> parameters to define clearly the scope of the
 import.
 
-'git-archimport' uses `tla` extensively in the background to access the
+'git archimport' uses `tla` extensively in the background to access the
 Arch repository.
 Make sure you have a recent version of `tla` available in the path. `tla` must
-know about the repositories you pass to 'git-archimport'.
+know about the repositories you pass to 'git archimport'.
 
-For the initial import, 'git-archimport' expects to find itself in an empty
+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
+'git archimport' with the same parameters as the initial import to perform
 incremental imports.
 
-While 'git-archimport' will try to create sensible branch names for the
+While 'git archimport' will try to create sensible branch names for the
 archives that it imports, it is also possible to specify git branch names
 manually.  To do so, write a git branch name after each <archive/branch>
 parameter, separated by a colon.  This way, you can shorten the Arch
 branch names and convert Arch jargon to git jargon, for example mapping a
-"PROJECT--devo--VERSION" branch to "master".
+"PROJECT{litdd}devo{litdd}VERSION" branch to "master".
 
 Associating multiple Arch branches to one git branch is possible; the
 result will make the most sense only if no commits are made to the first
@@ -84,9 +84,9 @@ OPTIONS
 
 -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.  In both cases, names given
+       earlier versions of 'git archimport'.  Old-style branch names
+       were category{litdd}branch, whereas new-style branch names are
+       archive,category{litdd}branch{litdd}version.  In both cases, names given
        on the command-line will override the automatically-generated
        ones.
 
index bc132c87e1a5b9f42ea1f2bbd4e3c46f67fc3852..8d3e66626f7561b0ba7f8140288cfc15167bdc14 100644 (file)
@@ -9,8 +9,8 @@ git-archive - Create an archive of files from a named tree
 SYNOPSIS
 --------
 [verse]
-'git archive' --format=<fmt> [--list] [--prefix=<prefix>/] [<extra>]
-             [--output=<file>] [--worktree-attributes]
+'git archive' [--format=<fmt>] [--list] [--prefix=<prefix>/] [<extra>]
+             [-o | --output=<file>] [--worktree-attributes]
              [--remote=<repo> [--exec=<git-upload-archive>]] <tree-ish>
              [path...]
 
@@ -21,21 +21,24 @@ structure for the named tree, and writes it out to the standard
 output.  If <prefix> is specified it is
 prepended to the filenames in the archive.
 
-'git-archive' behaves differently when given a tree ID versus when
+'git archive' behaves differently when given a tree ID versus when
 given a commit ID or tag ID.  In the first case the current time is
 used as the modification time of each file in the archive.  In the latter
 case the commit time as recorded in the referenced commit object is
 used instead.  Additionally the commit ID is stored in a global
 extended pax header if the tar format is used; it can be extracted
-using 'git-get-tar-commit-id'. In ZIP files it is stored as a file
+using 'git get-tar-commit-id'. In ZIP files it is stored as a file
 comment.
 
 OPTIONS
 -------
 
 --format=<fmt>::
-       Format of the resulting archive: 'tar' or 'zip'.  The default
-       is 'tar'.
+       Format of the resulting archive: 'tar' or 'zip'. If this option
+       is not given, and the output file is specified, the format is
+       inferred from the filename if possible (e.g. writing to "foo.zip"
+       makes the output to be in the zip format). Otherwise the output
+       format is `tar`.
 
 -l::
 --list::
@@ -48,6 +51,7 @@ OPTIONS
 --prefix=<prefix>/::
        Prepend <prefix>/ to each filename in the archive.
 
+-o <file>::
 --output=<file>::
        Write the archive to <file> instead of stdout.
 
@@ -70,8 +74,9 @@ OPTIONS
        The tree or commit to produce an archive for.
 
 path::
-       If one or more paths are specified, include only these in the
-       archive, otherwise include all files and subdirectories.
+       Without an optional path parameter, all files and subdirectories
+       of the current working directory are included in the archive.
+       If one or more paths are specified, only these are included.
 
 BACKEND EXTRA OPTIONS
 ---------------------
@@ -107,6 +112,14 @@ export-subst::
        expand several placeholders when adding this file to an archive.
        See linkgit:gitattributes[5] for details.
 
+Note that attributes are by default taken from the `.gitattributes` files
+in the tree that is being archived.  If you want to tweak the way the
+output is generated after the fact (e.g. you committed without adding an
+appropriate export-ignore in its `.gitattributes`), adjust the checked out
+`.gitattributes` file as necessary and use `--work-tree-attributes`
+option.  Alternatively you can keep necessary attributes that should apply
+while archiving any tree in your `$GIT_DIR/info/attributes` file.
+
 EXAMPLES
 --------
 git archive --format=tar --prefix=junk/ HEAD | (cd /var/tmp/ && tar xf -)::
@@ -129,6 +142,12 @@ git archive --format=zip --prefix=git-docs/ HEAD:Documentation/ > git-1.4.0-docs
        Put everything in the current head's Documentation/ directory
        into 'git-1.4.0-docs.zip', with the prefix 'git-docs/'.
 
+git archive -o latest.zip HEAD::
+
+       Create a Zip archive that contains the contents of the latest
+       commit on the current branch. Note that the output format is
+       inferred by the extension of the output file.
+
 
 SEE ALSO
 --------
diff --git a/Documentation/git-bisect-lk2009.txt b/Documentation/git-bisect-lk2009.txt
new file mode 100644 (file)
index 0000000..8a2ba37
--- /dev/null
@@ -0,0 +1,1358 @@
+Fighting regressions with git bisect
+====================================
+:Author: Christian Couder
+:Email: chriscool@tuxfamily.org
+:Date: 2009/11/08
+
+Abstract
+--------
+
+"git bisect" enables software users and developers to easily find the
+commit that introduced a regression. We show why it is important to
+have good tools to fight regressions. We describe how "git bisect"
+works from the outside and the algorithms it uses inside. Then we
+explain how to take advantage of "git bisect" to improve current
+practices. And we discuss how "git bisect" could improve in the
+future.
+
+
+Introduction to "git bisect"
+----------------------------
+
+Git is a Distributed Version Control system (DVCS) created by Linus
+Torvalds and maintained by Junio Hamano.
+
+In Git like in many other Version Control Systems (VCS), the different
+states of the data that is managed by the system are called
+commits. And, as VCS are mostly used to manage software source code,
+sometimes "interesting" changes of behavior in the software are
+introduced in some commits.
+
+In fact people are specially interested in commits that introduce a
+"bad" behavior, called a bug or a regression. They are interested in
+these commits because a commit (hopefully) contains a very small set
+of source code changes. And it's much easier to understand and
+properly fix a problem when you only need to check a very small set of
+changes, than when you don't know where look in the first place.
+
+So to help people find commits that introduce a "bad" behavior, the
+"git bisect" set of commands was invented. And it follows of course
+that in "git bisect" parlance, commits where the "interesting
+behavior" is present are called "bad" commits, while other commits are
+called "good" commits. And a commit that introduce the behavior we are
+interested in is called a "first bad commit". Note that there could be
+more than one "first bad commit" in the commit space we are searching.
+
+So "git bisect" is designed to help find a "first bad commit". And to
+be as efficient as possible, it tries to perform a binary search.
+
+
+Fighting regressions overview
+-----------------------------
+
+Regressions: a big problem
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Regressions are a big problem in the software industry. But it's
+difficult to put some real numbers behind that claim.
+
+There are some numbers about bugs in general, like a NIST study in
+2002 <<1>> that said:
+
+_____________
+Software bugs, or errors, are so prevalent and so detrimental that
+they cost the U.S. economy an estimated $59.5 billion annually, or
+about 0.6 percent of the gross domestic product, according to a newly
+released study commissioned by the Department of Commerce's National
+Institute of Standards and Technology (NIST). At the national level,
+over half of the costs are borne by software users and the remainder
+by software developers/vendors.  The study also found that, although
+all errors cannot be removed, more than a third of these costs, or an
+estimated $22.2 billion, could be eliminated by an improved testing
+infrastructure that enables earlier and more effective identification
+and removal of software defects. These are the savings associated with
+finding an increased percentage (but not 100 percent) of errors closer
+to the development stages in which they are introduced. Currently,
+over half of all errors are not found until "downstream" in the
+development process or during post-sale software use.
+_____________
+
+And then:
+
+_____________
+Software developers already spend approximately 80 percent of
+development costs on identifying and correcting defects, and yet few
+products of any type other than software are shipped with such high
+levels of errors.
+_____________
+
+Eventually the conclusion started with:
+
+_____________
+The path to higher software quality is significantly improved software
+testing.
+_____________
+
+There are other estimates saying that 80% of the cost related to
+software is about maintenance <<2>>.
+
+Though, according to Wikipedia <<3>>:
+
+_____________
+A common perception of maintenance is that it is merely fixing
+bugs. However, studies and surveys over the years have indicated that
+the majority, over 80%, of the maintenance effort is used for
+non-corrective actions (Pigosky 1997). This perception is perpetuated
+by users submitting problem reports that in reality are functionality
+enhancements to the system.
+_____________
+
+But we can guess that improving on existing software is very costly
+because you have to watch out for regressions. At least this would
+make the above studies consistent among themselves.
+
+Of course some kind of software is developed, then used during some
+time without being improved on much, and then finally thrown away. In
+this case, of course, regressions may not be a big problem. But on the
+other hand, there is a lot of big software that is continually
+developed and maintained during years or even tens of years by a lot
+of people. And as there are often many people who depend (sometimes
+critically) on such software, regressions are a really big problem.
+
+One such software is the linux kernel. And if we look at the linux
+kernel, we can see that a lot of time and effort is spent to fight
+regressions. The release cycle start with a 2 weeks long merge
+window. Then the first release candidate (rc) version is tagged. And
+after that about 7 or 8 more rc versions will appear with around one
+week between each of them, before the final release.
+
+The time between the first rc release and the final release is
+supposed to be used to test rc versions and fight bugs and especially
+regressions. And this time is more than 80% of the release cycle
+time. But this is not the end of the fight yet, as of course it
+continues after the release.
+
+And then this is what Ingo Molnar (a well known linux kernel
+developer) says about his use of git bisect:
+
+_____________
+I most actively use it during the merge window (when a lot of trees
+get merged upstream and when the influx of bugs is the highest) - and
+yes, there have been cases that i used it multiple times a day. My
+average is roughly once a day.
+_____________
+
+So regressions are fought all the time by developers, and indeed it is
+well known that bugs should be fixed as soon as possible, so as soon
+as they are found. That's why it is interesting to have good tools for
+this purpose.
+
+Other tools to fight regressions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+So what are the tools used to fight regressions? They are nearly the
+same as those used to fight regular bugs. The only specific tools are
+test suites and tools similar as "git bisect".
+
+Test suites are very nice. But when they are used alone, they are
+supposed to be used so that all the tests are checked after each
+commit. This means that they are not very efficient, because many
+tests are run for no interesting result, and they suffer from
+combinational explosion.
+
+In fact the problem is that big software often has many different
+configuration options and that each test case should pass for each
+configuration after each commit. So if you have for each release: N
+configurations, M commits and T test cases, you should perform:
+
+-------------
+N * M * T tests
+-------------
+
+where N, M and T are all growing with the size your software.
+
+So very soon it will not be possible to completely test everything.
+
+And if some bugs slip through your test suite, then you can add a test
+to your test suite. But if you want to use your new improved test
+suite to find where the bug slipped in, then you will either have to
+emulate a bisection process or you will perhaps bluntly test each
+commit backward starting from the "bad" commit you have which may be
+very wasteful.
+
+"git bisect" overview
+---------------------
+
+Starting a bisection
+~~~~~~~~~~~~~~~~~~~~
+
+The first "git bisect" subcommand to use is "git bisect start" to
+start the search. Then bounds must be set to limit the commit
+space. This is done usually by giving one "bad" and at least one
+"good" commit. They can be passed in the initial call to "git bisect
+start" like this:
+
+-------------
+$ git bisect start [BAD [GOOD...]]
+-------------
+
+or they can be set using:
+
+-------------
+$ git bisect bad [COMMIT]
+-------------
+
+and:
+
+-------------
+$ git bisect good [COMMIT...]
+-------------
+
+where BAD, GOOD and COMMIT are all names that can be resolved to a
+commit.
+
+Then "git bisect" will checkout a commit of its choosing and ask the
+user to test it, like this:
+
+-------------
+$ git bisect start v2.6.27 v2.6.25
+Bisecting: 10928 revisions left to test after this (roughly 14 steps)
+[2ec65f8b89ea003c27ff7723525a2ee335a2b393] x86: clean up using max_low_pfn on 32-bit
+-------------
+
+Note that the example that we will use is really a toy example, we
+will be looking for the first commit that has a version like
+"2.6.26-something", that is the commit that has a "SUBLEVEL = 26" line
+in the top level Makefile. This is a toy example because there are
+better ways to find this commit with git than using "git bisect" (for
+example "git blame" or "git log -S<string>").
+
+Driving a bisection manually
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+At this point there are basically 2 ways to drive the search. It can
+be driven manually by the user or it can be driven automatically by a
+script or a command.
+
+If the user is driving it, then at each step of the search, the user
+will have to test the current commit and say if it is "good" or "bad"
+using the "git bisect good" or "git bisect bad" commands respectively
+that have been described above. For example:
+
+-------------
+$ git bisect bad
+Bisecting: 5480 revisions left to test after this (roughly 13 steps)
+[66c0b394f08fd89236515c1c84485ea712a157be] KVM: kill file->f_count abuse in kvm
+-------------
+
+And after a few more steps like that, "git bisect" will eventually
+find a first bad commit:
+
+-------------
+$ git bisect bad
+2ddcca36c8bcfa251724fe342c8327451988be0d is the first bad commit
+commit 2ddcca36c8bcfa251724fe342c8327451988be0d
+Author: Linus Torvalds <torvalds@linux-foundation.org>
+Date:   Sat May 3 11:59:44 2008 -0700
+
+    Linux 2.6.26-rc1
+
+:100644 100644 5cf8258195331a4dbdddff08b8d68642638eea57 4492984efc09ab72ff6219a7bc21fb6a957c4cd5 M      Makefile
+-------------
+
+At this point we can see what the commit does, check it out (if it's
+not already checked out) or tinker with it, for example:
+
+-------------
+$ git show HEAD
+commit 2ddcca36c8bcfa251724fe342c8327451988be0d
+Author: Linus Torvalds <torvalds@linux-foundation.org>
+Date:   Sat May 3 11:59:44 2008 -0700
+
+    Linux 2.6.26-rc1
+
+diff --git a/Makefile b/Makefile
+index 5cf8258..4492984 100644
+--- a/Makefile
++++ b/Makefile
+@@ -1,7 +1,7 @@
+ VERSION = 2
+ PATCHLEVEL = 6
+-SUBLEVEL = 25
+-EXTRAVERSION =
++SUBLEVEL = 26
++EXTRAVERSION = -rc1
+ NAME = Funky Weasel is Jiggy wit it
+
+ # *DOCUMENTATION*
+-------------
+
+And when we are finished we can use "git bisect reset" to go back to
+the branch we were in before we started bisecting:
+
+-------------
+$ git bisect reset
+Checking out files: 100% (21549/21549), done.
+Previous HEAD position was 2ddcca3... Linux 2.6.26-rc1
+Switched to branch 'master'
+-------------
+
+Driving a bisection automatically
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The other way to drive the bisection process is to tell "git bisect"
+to launch a script or command at each bisection step to know if the
+current commit is "good" or "bad". To do that, we use the "git bisect
+run" command. For example:
+
+-------------
+$ git bisect start v2.6.27 v2.6.25
+Bisecting: 10928 revisions left to test after this (roughly 14 steps)
+[2ec65f8b89ea003c27ff7723525a2ee335a2b393] x86: clean up using max_low_pfn on 32-bit
+$
+$ git bisect run grep '^SUBLEVEL = 25' Makefile
+running grep ^SUBLEVEL = 25 Makefile
+Bisecting: 5480 revisions left to test after this (roughly 13 steps)
+[66c0b394f08fd89236515c1c84485ea712a157be] KVM: kill file->f_count abuse in kvm
+running grep ^SUBLEVEL = 25 Makefile
+SUBLEVEL = 25
+Bisecting: 2740 revisions left to test after this (roughly 12 steps)
+[671294719628f1671faefd4882764886f8ad08cb] V4L/DVB(7879): Adding cx18 Support for mxl5005s
+...
+...
+running grep ^SUBLEVEL = 25 Makefile
+Bisecting: 0 revisions left to test after this (roughly 0 steps)
+[2ddcca36c8bcfa251724fe342c8327451988be0d] Linux 2.6.26-rc1
+running grep ^SUBLEVEL = 25 Makefile
+2ddcca36c8bcfa251724fe342c8327451988be0d is the first bad commit
+commit 2ddcca36c8bcfa251724fe342c8327451988be0d
+Author: Linus Torvalds <torvalds@linux-foundation.org>
+Date:   Sat May 3 11:59:44 2008 -0700
+
+    Linux 2.6.26-rc1
+
+:100644 100644 5cf8258195331a4dbdddff08b8d68642638eea57 4492984efc09ab72ff6219a7bc21fb6a957c4cd5 M      Makefile
+bisect run success
+-------------
+
+In this example, we passed "grep '^SUBLEVEL = 25' Makefile" as
+parameter to "git bisect run". This means that at each step, the grep
+command we passed will be launched. And if it exits with code 0 (that
+means success) then git bisect will mark the current state as
+"good". If it exits with code 1 (or any code between 1 and 127
+included, except the special code 125), then the current state will be
+marked as "bad".
+
+Exit code between 128 and 255 are special to "git bisect run". They
+make it stop immediately the bisection process. This is useful for
+example if the command passed takes too long to complete, because you
+can kill it with a signal and it will stop the bisection process.
+
+It can also be useful in scripts passed to "git bisect run" to "exit
+255" if some very abnormal situation is detected.
+
+Avoiding untestable commits
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sometimes it happens that the current state cannot be tested, for
+example if it does not compile because there was a bug preventing it
+at that time. This is what the special exit code 125 is for. It tells
+"git bisect run" that the current commit should be marked as
+untestable and that another one should be chosen and checked out.
+
+If the bisection process is driven manually, you can use "git bisect
+skip" to do the same thing. (In fact the special exit code 125 makes
+"git bisect run" use "git bisect skip" in the background.)
+
+Or if you want more control, you can inspect the current state using
+for example "git bisect visualize". It will launch gitk (or "git log"
+if the DISPLAY environment variable is not set) to help you find a
+better bisection point.
+
+Either way, if you have a string of untestable commits, it might
+happen that the regression you are looking for has been introduced by
+one of these untestable commits. In this case it's not possible to
+tell for sure which commit introduced the regression.
+
+So if you used "git bisect skip" (or the run script exited with
+special code 125) you could get a result like this:
+
+-------------
+There are only 'skip'ped commits left to test.
+The first bad commit could be any of:
+15722f2fa328eaba97022898a305ffc8172db6b1
+78e86cf3e850bd755bb71831f42e200626fbd1e0
+e15b73ad3db9b48d7d1ade32f8cd23a751fe0ace
+070eab2303024706f2924822bfec8b9847e4ac1b
+We cannot bisect more!
+-------------
+
+Saving a log and replaying it
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you want to show other people your bisection process, you can get a
+log using for example:
+
+-------------
+$ git bisect log > bisect_log.txt
+-------------
+
+And it is possible to replay it using:
+
+-------------
+$ git bisect replay bisect_log.txt
+-------------
+
+
+"git bisect" details
+--------------------
+
+Bisection algorithm
+~~~~~~~~~~~~~~~~~~~
+
+As the Git commits form a directed acyclic graph (DAG), finding the
+best bisection commit to test at each step is not so simple. Anyway
+Linus found and implemented a "truly stupid" algorithm, later improved
+by Junio Hamano, that works quite well.
+
+So the algorithm used by "git bisect" to find the best bisection
+commit when there are no skipped commits is the following:
+
+1) keep only the commits that:
+
+a) are ancestor of the "bad" commit (including the "bad" commit itself),
+b) are not ancestor of a "good" commit (excluding the "good" commits).
+
+This means that we get rid of the uninteresting commits in the DAG.
+
+For example if we start with a graph like this:
+
+-------------
+G-Y-G-W-W-W-X-X-X-X
+          \ /
+           W-W-B
+          /
+Y---G-W---W
+ \ /   \
+Y-Y     X-X-X-X
+
+-> time goes this way ->
+-------------
+
+where B is the "bad" commit, "G" are "good" commits and W, X, and Y
+are other commits, we will get the following graph after this first
+step:
+
+-------------
+W-W-W
+     \
+      W-W-B
+     /
+W---W
+-------------
+
+So only the W and B commits will be kept. Because commits X and Y will
+have been removed by rules a) and b) respectively, and because commits
+G are removed by rule b) too.
+
+Note for git users, that it is equivalent as keeping only the commit
+given by:
+
+-------------
+git rev-list BAD --not GOOD1 GOOD2...
+-------------
+
+Also note that we don't require the commits that are kept to be
+descendants of a "good" commit. So in the following example, commits W
+and Z will be kept:
+
+-------------
+G-W-W-W-B
+   /
+Z-Z
+-------------
+
+2) starting from the "good" ends of the graph, associate to each
+commit the number of ancestors it has plus one
+
+For example with the following graph where H is the "bad" commit and A
+and D are some parents of some "good" commits:
+
+-------------
+A-B-C
+     \
+      F-G-H
+     /
+D---E
+-------------
+
+this will give:
+
+-------------
+1 2 3
+A-B-C
+     \6 7 8
+      F-G-H
+1   2/
+D---E
+-------------
+
+3) associate to each commit: min(X, N - X)
+
+where X is the value associated to the commit in step 2) and N is the
+total number of commits in the graph.
+
+In the above example we have N = 8, so this will give:
+
+-------------
+1 2 3
+A-B-C
+     \2 1 0
+      F-G-H
+1   2/
+D---E
+-------------
+
+4) the best bisection point is the commit with the highest associated
+number
+
+So in the above example the best bisection point is commit C.
+
+5) note that some shortcuts are implemented to speed up the algorithm
+
+As we know N from the beginning, we know that min(X, N - X) can't be
+greater than N/2. So during steps 2) and 3), if we would associate N/2
+to a commit, then we know this is the best bisection point. So in this
+case we can just stop processing any other commit and return the
+current commit.
+
+Bisection algorithm debugging
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For any commit graph, you can see the number associated with each
+commit using "git rev-list --bisect-all".
+
+For example, for the above graph, a command like:
+
+-------------
+$ git rev-list --bisect-all BAD --not GOOD1 GOOD2
+-------------
+
+would output something like:
+
+-------------
+e15b73ad3db9b48d7d1ade32f8cd23a751fe0ace (dist=3)
+15722f2fa328eaba97022898a305ffc8172db6b1 (dist=2)
+78e86cf3e850bd755bb71831f42e200626fbd1e0 (dist=2)
+a1939d9a142de972094af4dde9a544e577ddef0e (dist=2)
+070eab2303024706f2924822bfec8b9847e4ac1b (dist=1)
+a3864d4f32a3bf5ed177ddef598490a08760b70d (dist=1)
+a41baa717dd74f1180abf55e9341bc7a0bb9d556 (dist=1)
+9e622a6dad403b71c40979743bb9d5be17b16bd6 (dist=0)
+-------------
+
+Bisection algorithm discussed
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+First let's define "best bisection point". We will say that a commit X
+is a best bisection point or a best bisection commit if knowing its
+state ("good" or "bad") gives as much information as possible whether
+the state of the commit happens to be "good" or "bad".
+
+This means that the best bisection commits are the commits where the
+following function is maximum:
+
+-------------
+f(X) = min(information_if_good(X), information_if_bad(X))
+-------------
+
+where information_if_good(X) is the information we get if X is good
+and information_if_bad(X) is the information we get if X is bad.
+
+Now we will suppose that there is only one "first bad commit". This
+means that all its descendants are "bad" and all the other commits are
+"good". And we will suppose that all commits have an equal probability
+of being good or bad, or of being the first bad commit, so knowing the
+state of c commits gives always the same amount of information
+wherever these c commits are on the graph and whatever c is. (So we
+suppose that these commits being for example on a branch or near a
+good or a bad commit does not give more or less information).
+
+Let's also suppose that we have a cleaned up graph like one after step
+1) in the bisection algorithm above. This means that we can measure
+the information we get in terms of number of commit we can remove from
+the graph..
+
+And let's take a commit X in the graph.
+
+If X is found to be "good", then we know that its ancestors are all
+"good", so we want to say that:
+
+-------------
+information_if_good(X) = number_of_ancestors(X)  (TRUE)
+-------------
+
+And this is true because at step 1) b) we remove the ancestors of the
+"good" commits.
+
+If X is found to be "bad", then we know that its descendants are all
+"bad", so we want to say that:
+
+-------------
+information_if_bad(X) = number_of_descendants(X)  (WRONG)
+-------------
+
+But this is wrong because at step 1) a) we keep only the ancestors of
+the bad commit. So we get more information when a commit is marked as
+"bad", because we also know that the ancestors of the previous "bad"
+commit that are not ancestors of the new "bad" commit are not the
+first bad commit. We don't know if they are good or bad, but we know
+that they are not the first bad commit because they are not ancestor
+of the new "bad" commit.
+
+So when a commit is marked as "bad" we know we can remove all the
+commits in the graph except those that are ancestors of the new "bad"
+commit. This means that:
+
+-------------
+information_if_bad(X) = N - number_of_ancestors(X)  (TRUE)
+-------------
+
+where N is the number of commits in the (cleaned up) graph.
+
+So in the end this means that to find the best bisection commits we
+should maximize the function:
+
+-------------
+f(X) = min(number_of_ancestors(X), N - number_of_ancestors(X))
+-------------
+
+And this is nice because at step 2) we compute number_of_ancestors(X)
+and so at step 3) we compute f(X).
+
+Let's take the following graph as an example:
+
+-------------
+           G-H-I-J
+          /       \
+A-B-C-D-E-F         O
+          \       /
+           K-L-M-N
+-------------
+
+If we compute the following non optimal function on it:
+
+-------------
+g(X) = min(number_of_ancestors(X), number_of_descendants(X))
+-------------
+
+we get:
+
+-------------
+           4 3 2 1
+           G-H-I-J
+1 2 3 4 5 6/       \0
+A-B-C-D-E-F         O
+          \       /
+           K-L-M-N
+           4 3 2 1
+-------------
+
+but with the algorithm used by git bisect we get:
+
+-------------
+           7 7 6 5
+           G-H-I-J
+1 2 3 4 5 6/       \0
+A-B-C-D-E-F         O
+          \       /
+           K-L-M-N
+           7 7 6 5
+-------------
+
+So we chose G, H, K or L as the best bisection point, which is better
+than F. Because if for example L is bad, then we will know not only
+that L, M and N are bad but also that G, H, I and J are not the first
+bad commit (since we suppose that there is only one first bad commit
+and it must be an ancestor of L).
+
+So the current algorithm seems to be the best possible given what we
+initially supposed.
+
+Skip algorithm
+~~~~~~~~~~~~~~
+
+When some commits have been skipped (using "git bisect skip"), then
+the bisection algorithm is the same for step 1) to 3). But then we use
+roughly the following steps:
+
+6) sort the commit by decreasing associated value
+
+7) if the first commit has not been skipped, we can return it and stop
+here
+
+8) otherwise filter out all the skipped commits in the sorted list
+
+9) use a pseudo random number generator (PRNG) to generate a random
+number between 0 and 1
+
+10) multiply this random number with its square root to bias it toward
+0
+
+11) multiply the result by the number of commits in the filtered list
+to get an index into this list
+
+12) return the commit at the computed index
+
+Skip algorithm discussed
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+After step 7) (in the skip algorithm), we could check if the second
+commit has been skipped and return it if it is not the case. And in
+fact that was the algorithm we used from when "git bisect skip" was
+developed in git version 1.5.4 (released on February 1st 2008) until
+git version 1.6.4 (released July 29th 2009).
+
+But Ingo Molnar and H. Peter Anvin (another well known linux kernel
+developer) both complained that sometimes the best bisection points
+all happened to be in an area where all the commits are
+untestable. And in this case the user was asked to test many
+untestable commits, which could be very inefficient.
+
+Indeed untestable commits are often untestable because a breakage was
+introduced at one time, and that breakage was fixed only after many
+other commits were introduced.
+
+This breakage is of course most of the time unrelated to the breakage
+we are trying to locate in the commit graph. But it prevents us to
+know if the interesting "bad behavior" is present or not.
+
+So it is a fact that commits near an untestable commit have a high
+probability of being untestable themselves. And the best bisection
+commits are often found together too (due to the bisection algorithm).
+
+This is why it is a bad idea to just chose the next best unskipped
+bisection commit when the first one has been skipped.
+
+We found that most commits on the graph may give quite a lot of
+information when they are tested. And the commits that will not on
+average give a lot of information are the one near the good and bad
+commits.
+
+So using a PRNG with a bias to favor commits away from the good and
+bad commits looked like a good choice.
+
+One obvious improvement to this algorithm would be to look for a
+commit that has an associated value near the one of the best bisection
+commit, and that is on another branch, before using the PRNG. Because
+if such a commit exists, then it is not very likely to be untestable
+too, so it will probably give more information than a nearly randomly
+chosen one.
+
+Checking merge bases
+~~~~~~~~~~~~~~~~~~~~
+
+There is another tweak in the bisection algorithm that has not been
+described in the "bisection algorithm" above.
+
+We supposed in the previous examples that the "good" commits were
+ancestors of the "bad" commit. But this is not a requirement of "git
+bisect".
+
+Of course the "bad" commit cannot be an ancestor of a "good" commit,
+because the ancestors of the good commits are supposed to be
+"good". And all the "good" commits must be related to the bad commit.
+They cannot be on a branch that has no link with the branch of the
+"bad" commit. But it is possible for a good commit to be related to a
+bad commit and yet not be neither one of its ancestor nor one of its
+descendants.
+
+For example, there can be a "main" branch, and a "dev" branch that was
+forked of the main branch at a commit named "D" like this:
+
+-------------
+A-B-C-D-E-F-G  <--main
+       \
+       H-I-J  <--dev
+-------------
+
+The commit "D" is called a "merge base" for branch "main" and "dev"
+because it's the best common ancestor for these branches for a merge.
+
+Now let's suppose that commit J is bad and commit G is good and that
+we apply the bisection algorithm like it has been previously
+described.
+
+As described in step 1) b) of the bisection algorithm, we remove all
+the ancestors of the good commits because they are supposed to be good
+too.
+
+So we would be left with only:
+
+-------------
+H-I-J
+-------------
+
+But what happens if the first bad commit is "B" and if it has been
+fixed in the "main" branch by commit "F"?
+
+The result of such a bisection would be that we would find that H is
+the first bad commit, when in fact it's B. So that would be wrong!
+
+And yes it can happen in practice that people working on one branch
+are not aware that people working on another branch fixed a bug! It
+could also happen that F fixed more than one bug or that it is a
+revert of some big development effort that was not ready to be
+released.
+
+In fact development teams often maintain both a development branch and
+a maintenance branch, and it would be quite easy for them if "git
+bisect" just worked when they want to bisect a regression on the
+development branch that is not on the maintenance branch. They should
+be able to start bisecting using:
+
+-------------
+$ git bisect start dev main
+-------------
+
+To enable that additional nice feature, when a bisection is started
+and when some good commits are not ancestors of the bad commit, we
+first compute the merge bases between the bad and the good commits and
+we chose these merge bases as the first commits that will be checked
+out and tested.
+
+If it happens that one merge base is bad, then the bisection process
+is stopped with a message like:
+
+-------------
+The merge base BBBBBB is bad.
+This means the bug has been fixed between BBBBBB and [GGGGGG,...].
+-------------
+
+where BBBBBB is the sha1 hash of the bad merge base and [GGGGGG,...]
+is a comma separated list of the sha1 of the good commits.
+
+If some of the merge bases are skipped, then the bisection process
+continues, but the following message is printed for each skipped merge
+base:
+
+-------------
+Warning: the merge base between BBBBBB and [GGGGGG,...] must be skipped.
+So we cannot be sure the first bad commit is between MMMMMM and BBBBBB.
+We continue anyway.
+-------------
+
+where BBBBBB is the sha1 hash of the bad commit, MMMMMM is the sha1
+hash of the merge base that is skipped and [GGGGGG,...]  is a comma
+separated list of the sha1 of the good commits.
+
+So if there is no bad merge base, the bisection process continues as
+usual after this step.
+
+Best bisecting practices
+------------------------
+
+Using test suites and git bisect together
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you both have a test suite and use git bisect, then it becomes less
+important to check that all tests pass after each commit. Though of
+course it is probably a good idea to have some checks to avoid
+breaking too many things because it could make bisecting other bugs
+more difficult.
+
+You can focus your efforts to check at a few points (for example rc
+and beta releases) that all the T test cases pass for all the N
+configurations. And when some tests don't pass you can use "git
+bisect" (or better "git bisect run"). So you should perform roughly:
+
+-------------
+c * N * T + b * M * log2(M) tests
+-------------
+
+where c is the number of rounds of test (so a small constant) and b is
+the ratio of bug per commit (hopefully a small constant too).
+
+So of course it's much better as it's O(N * T) vs O(N * T * M) if
+you would test everything after each commit.
+
+This means that test suites are good to prevent some bugs from being
+committed and they are also quite good to tell you that you have some
+bugs. But they are not so good to tell you where some bugs have been
+introduced. To tell you that efficiently, git bisect is needed.
+
+The other nice thing with test suites, is that when you have one, you
+already know how to test for bad behavior. So you can use this
+knowledge to create a new test case for "git bisect" when it appears
+that there is a regression. So it will be easier to bisect the bug and
+fix it. And then you can add the test case you just created to your
+test suite.
+
+So if you know how to create test cases and how to bisect, you will be
+subject to a virtuous circle:
+
+more tests => easier to create tests => easier to bisect => more tests
+
+So test suites and "git bisect" are complementary tools that are very
+powerful and efficient when used together.
+
+Bisecting build failures
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can very easily automatically bisect broken builds using something
+like:
+
+-------------
+$ git bisect start BAD GOOD
+$ git bisect run make
+-------------
+
+Passing sh -c "some commands" to "git bisect run"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For example:
+
+-------------
+$ git bisect run sh -c "make || exit 125; ./my_app | grep 'good output'"
+-------------
+
+On the other hand if you do this often, then it can be worth having
+scripts to avoid too much typing.
+
+Finding performance regressions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Here is an example script that comes slightly modified from a real
+world script used by Junio Hamano <<4>>.
+
+This script can be passed to "git bisect run" to find the commit that
+introduced a performance regression:
+
+-------------
+#!/bin/sh
+
+# Build errors are not what I am interested in.
+make my_app || exit 255
+
+# We are checking if it stops in a reasonable amount of time, so
+# let it run in the background...
+
+./my_app >log 2>&1 &
+
+# ... and grab its process ID.
+pid=$!
+
+# ... and then wait for sufficiently long.
+sleep $NORMAL_TIME
+
+# ... and then see if the process is still there.
+if kill -0 $pid
+then
+       # It is still running -- that is bad.
+       kill $pid; sleep 1; kill $pid;
+       exit 1
+else
+       # It has already finished (the $pid process was no more),
+       # and we are happy.
+       exit 0
+fi
+-------------
+
+Following general best practices
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It is obviously a good idea not to have commits with changes that
+knowingly break things, even if some other commits later fix the
+breakage.
+
+It is also a good idea when using any VCS to have only one small
+logical change in each commit.
+
+The smaller the changes in your commit, the most effective "git
+bisect" will be. And you will probably need "git bisect" less in the
+first place, as small changes are easier to review even if they are
+only reviewed by the committer.
+
+Another good idea is to have good commit messages. They can be very
+helpful to understand why some changes were made.
+
+These general best practices are very helpful if you bisect often.
+
+Avoiding bug prone merges
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+First merges by themselves can introduce some regressions even when
+the merge needs no source code conflict resolution. This is because a
+semantic change can happen in one branch while the other branch is not
+aware of it.
+
+For example one branch can change the semantic of a function while the
+other branch add more calls to the same function.
+
+This is made much worse if many files have to be fixed to resolve
+conflicts. That's why such merges are called "evil merges". They can
+make regressions very difficult to track down. It can even be
+misleading to know the first bad commit if it happens to be such a
+merge, because people might think that the bug comes from bad conflict
+resolution when it comes from a semantic change in one branch.
+
+Anyway "git rebase" can be used to linearize history. This can be used
+either to avoid merging in the first place. Or it can be used to
+bisect on a linear history instead of the non linear one, as this
+should give more information in case of a semantic change in one
+branch.
+
+Merges can be also made simpler by using smaller branches or by using
+many topic branches instead of only long version related branches.
+
+And testing can be done more often in special integration branches
+like linux-next for the linux kernel.
+
+Adapting your work-flow
+~~~~~~~~~~~~~~~~~~~~~~~
+
+A special work-flow to process regressions can give great results.
+
+Here is an example of a work-flow used by Andreas Ericsson:
+
+* write, in the test suite, a test script that exposes the regression
+* use "git bisect run" to find the commit that introduced it
+* fix the bug that is often made obvious by the previous step
+* commit both the fix and the test script (and if needed more tests)
+
+And here is what Andreas said about this work-flow <<5>>:
+
+_____________
+To give some hard figures, we used to have an average report-to-fix
+cycle of 142.6 hours (according to our somewhat weird bug-tracker
+which just measures wall-clock time). Since we moved to git, we've
+lowered that to 16.2 hours. Primarily because we can stay on top of
+the bug fixing now, and because everyone's jockeying to get to fix
+bugs (we're quite proud of how lazy we are to let git find the bugs
+for us). Each new release results in ~40% fewer bugs (almost certainly
+due to how we now feel about writing tests).
+_____________
+
+Clearly this work-flow uses the virtuous circle between test suites
+and "git bisect". In fact it makes it the standard procedure to deal
+with regression.
+
+In other messages Andreas says that they also use the "best practices"
+described above: small logical commits, topic branches, no evil
+merge,... These practices all improve the bisectability of the commit
+graph, by making it easier and more useful to bisect.
+
+So a good work-flow should be designed around the above points. That
+is making bisecting easier, more useful and standard.
+
+Involving QA people and if possible end users
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+One nice about "git bisect" is that it is not only a developer
+tool. It can effectively be used by QA people or even end users (if
+they have access to the source code or if they can get access to all
+the builds).
+
+There was a discussion at one point on the linux kernel mailing list
+of whether it was ok to always ask end user to bisect, and very good
+points were made to support the point of view that it is ok.
+
+For example David Miller wrote <<6>>:
+
+_____________
+What people don't get is that this is a situation where the "end node
+principle" applies. When you have limited resources (here: developers)
+you don't push the bulk of the burden upon them. Instead you push
+things out to the resource you have a lot of, the end nodes (here:
+users), so that the situation actually scales.
+_____________
+
+This means that it is often "cheaper" if QA people or end users can do
+it.
+
+What is interesting too is that end users that are reporting bugs (or
+QA people that reproduced a bug) have access to the environment where
+the bug happens. So they can often more easily reproduce a
+regression. And if they can bisect, then more information will be
+extracted from the environment where the bug happens, which means that
+it will be easier to understand and then fix the bug.
+
+For open source projects it can be a good way to get more useful
+contributions from end users, and to introduce them to QA and
+development activities.
+
+Using complex scripts
+~~~~~~~~~~~~~~~~~~~~~
+
+In some cases like for kernel development it can be worth developing
+complex scripts to be able to fully automate bisecting.
+
+Here is what Ingo Molnar says about that <<7>>:
+
+_____________
+i have a fully automated bootup-hang bisection script. It is based on
+"git-bisect run". I run the script, it builds and boots kernels fully
+automatically, and when the bootup fails (the script notices that via
+the serial log, which it continuously watches - or via a timeout, if
+the system does not come up within 10 minutes it's a "bad" kernel),
+the script raises my attention via a beep and i power cycle the test
+box. (yeah, i should make use of a managed power outlet to 100%
+automate it)
+_____________
+
+Combining test suites, git bisect and other systems together
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We have seen that test suites an git bisect are very powerful when
+used together. It can be even more powerful if you can combine them
+with other systems.
+
+For example some test suites could be run automatically at night with
+some unusual (or even random) configurations. And if a regression is
+found by a test suite, then "git bisect" can be automatically
+launched, and its result can be emailed to the author of the first bad
+commit found by "git bisect", and perhaps other people too. And a new
+entry in the bug tracking system could be automatically created too.
+
+
+The future of bisecting
+-----------------------
+
+"git replace"
+~~~~~~~~~~~~~
+
+We saw earlier that "git bisect skip" is now using a PRNG to try to
+avoid areas in the commit graph where commits are untestable. The
+problem is that sometimes the first bad commit will be in an
+untestable area.
+
+To simplify the discussion we will suppose that the untestable area is
+a simple string of commits and that it was created by a breakage
+introduced by one commit (let's call it BBC for bisect breaking
+commit) and later fixed by another one (let's call it BFC for bisect
+fixing commit).
+
+For example:
+
+-------------
+...-Y-BBC-X1-X2-X3-X4-X5-X6-BFC-Z-...
+-------------
+
+where we know that Y is good and BFC is bad, and where BBC and X1 to
+X6 are untestable.
+
+In this case if you are bisecting manually, what you can do is create
+a special branch that starts just before the BBC. The first commit in
+this branch should be the BBC with the BFC squashed into it. And the
+other commits in the branch should be the commits between BBC and BFC
+rebased on the first commit of the branch and then the commit after
+BFC also rebased on.
+
+For example:
+
+-------------
+      (BBC+BFC)-X1'-X2'-X3'-X4'-X5'-X6'-Z'
+     /
+...-Y-BBC-X1-X2-X3-X4-X5-X6-BFC-Z-...
+-------------
+
+where commits quoted with ' have been rebased.
+
+You can easily create such a branch with Git using interactive rebase.
+
+For example using:
+
+-------------
+$ git rebase -i Y Z
+-------------
+
+and then moving BFC after BBC and squashing it.
+
+After that you can start bisecting as usual in the new branch and you
+should eventually find the first bad commit.
+
+For example:
+
+-------------
+$ git bisect start Z' Y
+-------------
+
+If you are using "git bisect run", you can use the same manual fix up
+as above, and then start another "git bisect run" in the special
+branch. Or as the "git bisect" man page says, the script passed to
+"git bisect run" can apply a patch before it compiles and test the
+software <<8>>. The patch should turn a current untestable commits
+into a testable one. So the testing will result in "good" or "bad" and
+"git bisect" will be able to find the first bad commit. And the script
+should not forget to remove the patch once the testing is done before
+exiting from the script.
+
+(Note that instead of a patch you can use "git cherry-pick BFC" to
+apply the fix, and in this case you should use "git reset --hard
+HEAD^" to revert the cherry-pick after testing and before returning
+from the script.)
+
+But the above ways to work around untestable areas are a little bit
+clunky. Using special branches is nice because these branches can be
+shared by developers like usual branches, but the risk is that people
+will get many such branches. And it disrupts the normal "git bisect"
+work-flow. So, if you want to use "git bisect run" completely
+automatically, you have to add special code in your script to restart
+bisection in the special branches.
+
+Anyway one can notice in the above special branch example that the Z'
+and Z commits should point to the same source code state (the same
+"tree" in git parlance). That's because Z' result from applying the
+same changes as Z just in a slightly different order.
+
+So if we could just "replace" Z by Z' when we bisect, then we would
+not need to add anything to a script. It would just work for anyone in
+the project sharing the special branches and the replacements.
+
+With the example above that would give:
+
+-------------
+      (BBC+BFC)-X1'-X2'-X3'-X4'-X5'-X6'-Z'-...
+     /
+...-Y-BBC-X1-X2-X3-X4-X5-X6-BFC-Z
+-------------
+
+That's why the "git replace" command was created. Technically it
+stores replacements "refs" in the "refs/replace/" hierarchy. These
+"refs" are like branches (that are stored in "refs/heads/") or tags
+(that are stored in "refs/tags"), and that means that they can
+automatically be shared like branches or tags among developers.
+
+"git replace" is a very powerful mechanism. It can be used to fix
+commits in already released history, for example to change the commit
+message or the author. And it can also be used instead of git "grafts"
+to link a repository with another old repository.
+
+In fact it's this last feature that "sold" it to the git community, so
+it is now in the "master" branch of git's git repository and it should
+be released in git 1.6.5 in October or November 2009.
+
+One problem with "git replace" is that currently it stores all the
+replacements refs in "refs/replace/", but it would be perhaps better
+if the replacement refs that are useful only for bisecting would be in
+"refs/replace/bisect/". This way the replacement refs could be used
+only for bisecting, while other refs directly in "refs/replace/" would
+be used nearly all the time.
+
+Bisecting sporadic bugs
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Another possible improvement to "git bisect" would be to optionally
+add some redundancy to the tests performed so that it would be more
+reliable when tracking sporadic bugs.
+
+This has been requested by some kernel developers because some bugs
+called sporadic bugs do not appear in all the kernel builds because
+they are very dependent on the compiler output.
+
+The idea is that every 3 test for example, "git bisect" could ask the
+user to test a commit that has already been found to be "good" or
+"bad" (because one of its descendants or one of its ancestors has been
+found to be "good" or "bad" respectively). If it happens that a commit
+has been previously incorrectly classified then the bisection can be
+aborted early, hopefully before too many mistakes have been made. Then
+the user will have to look at what happened and then restart the
+bisection using a fixed bisect log.
+
+There is already a project called BBChop created by Ealdwulf Wuffinga
+on Github that does something like that using Bayesian Search Theory
+<<9>>:
+
+_____________
+BBChop is like 'git bisect' (or equivalent), but works when your bug
+is intermittent. That is, it works in the presence of false negatives
+(when a version happens to work this time even though it contains the
+bug). It assumes that there are no false positives (in principle, the
+same approach would work, but adding it may be non-trivial).
+_____________
+
+But BBChop is independent of any VCS and it would be easier for Git
+users to have something integrated in Git.
+
+Conclusion
+----------
+
+We have seen that regressions are an important problem, and that "git
+bisect" has nice features that complement very well practices and
+other tools, especially test suites, that are generally used to fight
+regressions. But it might be needed to change some work-flows and
+(bad) habits to get the most out of it.
+
+Some improvements to the algorithms inside "git bisect" are possible
+and some new features could help in some cases, but overall "git
+bisect" works already very well, is used a lot, and is already very
+useful. To back up that last claim, let's give the final word to Ingo
+Molnar when he was asked by the author how much time does he think
+"git bisect" saves him when he uses it:
+
+_____________
+a _lot_.
+
+About ten years ago did i do my first 'bisection' of a Linux patch
+queue. That was prior the Git (and even prior the BitKeeper) days. I
+literally days spent sorting out patches, creating what in essence
+were standalone commits that i guessed to be related to that bug.
+
+It was a tool of absolute last resort. I'd rather spend days looking
+at printk output than do a manual 'patch bisection'.
+
+With Git bisect it's a breeze: in the best case i can get a ~15 step
+kernel bisection done in 20-30 minutes, in an automated way. Even with
+manual help or when bisecting multiple, overlapping bugs, it's rarely
+more than an hour.
+
+In fact it's invaluable because there are bugs i would never even
+_try_ to debug if it wasn't for git bisect. In the past there were bug
+patterns that were immediately hopeless for me to debug - at best i
+could send the crash/bug signature to lkml and hope that someone else
+can think of something.
+
+And even if a bisection fails today it tells us something valuable
+about the bug: that it's non-deterministic - timing or kernel image
+layout dependent.
+
+So git bisect is unconditional goodness - and feel free to quote that
+;-)
+_____________
+
+Acknowledgements
+----------------
+
+Many thanks to Junio Hamano for his help in reviewing this paper, for
+reviewing the patches I sent to the git mailing list, for discussing
+some ideas and helping me improve them, for improving "git bisect" a
+lot and for his awesome work in maintaining and developing Git.
+
+Many thanks to Ingo Molnar for giving me very useful information that
+appears in this paper, for commenting on this paper, for his
+suggestions to improve "git bisect" and for evangelizing "git bisect"
+on the linux kernel mailing lists.
+
+Many thanks to Linus Torvalds for inventing, developing and
+evangelizing "git bisect", Git and Linux.
+
+Many thanks to the many other great people who helped one way or
+another when I worked on git, especially to Andreas Ericsson, Johannes
+Schindelin, H. Peter Anvin, Daniel Barkalow, Bill Lear, John Hawley,
+Shawn O. Pierce, Jeff King, Sam Vilain, Jon Seymour.
+
+Many thanks to the Linux-Kongress program committee for choosing the
+author to given a talk and for publishing this paper.
+
+References
+----------
+
+- [[[1]]] http://www.nist.gov/public_affairs/releases/n02-10.htm['Software Errors Cost U.S. Economy $59.5 Billion Annually'. Nist News Release.]
+- [[[2]]] http://java.sun.com/docs/codeconv/html/CodeConventions.doc.html#16712['Code Conventions for the Java Programming Language'. Sun Microsystems.]
+- [[[3]]] http://en.wikipedia.org/wiki/Software_maintenance['Software maintenance'. Wikipedia.]
+- [[[4]]] http://article.gmane.org/gmane.comp.version-control.git/45195/[Junio C Hamano. 'Automated bisect success story'. Gmane.]
+- [[[5]]] http://lwn.net/Articles/317154/[Christian Couder. 'Fully automated bisecting with "git bisect run"'. LWN.net.]
+- [[[6]]] http://lwn.net/Articles/277872/[Jonathan Corbet. 'Bisection divides users and developers'. LWN.net.]
+- [[[7]]] http://article.gmane.org/gmane.linux.scsi/36652/[Ingo Molnar. 'Re: BUG 2.6.23-rc3 can't see sd partitions on Alpha'. Gmane.]
+- [[[8]]] http://www.kernel.org/pub/software/scm/git/docs/git-bisect.html[Junio C Hamano and the git-list. 'git-bisect(1) Manual Page'. Linux Kernel Archives.]
+- [[[9]]] http://github.com/Ealdwulf/bbchop[Ealdwulf. 'bbchop'. GitHub.]
index ffc02c737cf0a8d2bdb3cd812e0a23d43ee27fd7..c39d957c3a3a432f5e685d44066c145f03b96365 100644 (file)
@@ -20,7 +20,7 @@ on the subcommand:
  git bisect bad [<rev>]
  git bisect good [<rev>...]
  git bisect skip [(<rev>|<range>)...]
- git bisect reset [<branch>]
+ git bisect reset [<commit>]
  git bisect visualize
  git bisect replay <logfile>
  git bisect log
@@ -81,16 +81,27 @@ will have been left with the first bad kernel revision in "refs/bisect/bad".
 Bisect reset
 ~~~~~~~~~~~~
 
-To return to the original head after a bisect session, issue the
-following command:
+After a bisect session, to clean up the bisection state and return to
+the original HEAD, issue the following command:
 
 ------------------------------------------------
 $ git bisect reset
 ------------------------------------------------
 
-This resets the tree to the original branch instead of being on the
-bisection commit ("git bisect start" will also do that, as it resets
-the bisection state).
+By default, this will return your tree to the commit that was checked
+out before `git bisect start`.  (A new `git bisect start` will also do
+that, as it cleans up the old bisection state.)
+
+With an optional argument, you can return to a different commit
+instead:
+
+------------------------------------------------
+$ git bisect reset <commit>
+------------------------------------------------
+
+For example, `git bisect reset HEAD` will leave you on the current
+bisection commit and avoid switching commits at all, while `git bisect
+reset bisect/bad` will check out the first bad revision.
 
 Bisect visualize
 ~~~~~~~~~~~~~~~~
@@ -164,9 +175,8 @@ to do it for you by issuing the command:
 $ git bisect skip                 # Current version cannot be tested
 ------------
 
-But computing the commit to test may be slower afterwards and git may
-eventually not be able to tell the first bad commit among a bad commit
-and one or more skipped commits.
+But git may eventually be unable to tell the first bad commit among
+a bad commit and one or more skipped commits.
 
 You can even skip a range of commits, instead of just one commit,
 using the "'<commit1>'..'<commit2>'" notation. For example:
@@ -320,6 +330,11 @@ Documentation
 -------------
 Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
+SEE ALSO
+--------
+link:git-bisect-lk2009.html[Fighting regressions with git bisect],
+linkgit:git-blame[1].
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index 8c7b7b08386f69aaa96df2915566208d6abf3bf8..a27f43950fdfe8f6a0e8860d79ee31f0bc60750d 100644 (file)
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-p] [-w] [--incremental] [-L n,m]
-            [-S <revs-file>] [-M] [-C] [-C] [--since=<date>]
+           [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
            [<rev> | --contents <file> | --reverse <rev>] [--] <file>
 
 DESCRIPTION
@@ -21,7 +21,7 @@ last modified the line. Optionally, start annotating from the given revision.
 The command can also limit the range of lines annotated.
 
 The report does not tell you anything about lines which have been deleted or
-replaced; you need to use a tool such as 'git-diff' or the "pickaxe"
+replaced; you need to use a tool such as 'git diff' or the "pickaxe"
 interface briefly mentioned in the following paragraph.
 
 Apart from supporting file annotation, git also supports searching the
@@ -49,7 +49,7 @@ include::blame-options.txt[]
        file (see `-M`).  The first number listed is the score.
        This is the number of alphanumeric characters detected
        as having been moved between or within files.  This must be above
-       a certain threshold for 'git-blame' to consider those lines
+       a certain threshold for 'git blame' to consider those lines
        of code to have been moved.
 
 -f::
@@ -100,7 +100,7 @@ header elements later.
 SPECIFYING RANGES
 -----------------
 
-Unlike 'git-blame' and 'git-annotate' in older versions of git, the extent
+Unlike 'git blame' and 'git annotate' in older versions of git, the extent
 of the annotation can be limited to both line ranges and revision
 ranges.  When you are interested in finding the origin for
 lines 40-60 for file `foo`, you can use the `-L` option like so
@@ -118,7 +118,7 @@ which limits the annotation to the body of the `hello` subroutine.
 
 When you are not interested in changes older than version
 v2.6.18, or changes older than 3 weeks, you can use revision
-range specifiers  similar to 'git-rev-list':
+range specifiers  similar to 'git rev-list':
 
        git blame v2.6.18.. -- foo
        git blame --since=3.weeks -- foo
index cbd427587188d81726445183f772f02982736e6a..1940256930d92c0679b95bb0cd41f24847828bd4 100644 (file)
@@ -8,10 +8,10 @@ git-branch - List, create, or delete branches
 SYNOPSIS
 --------
 [verse]
-'git branch' [--color | --no-color] [-r | -a]
+'git branch' [--color[=<when>] | --no-color] [-r | -a]
        [-v [--abbrev=<length> | --no-abbrev]]
        [(--merged | --no-merged | --contains) [<commit>]]
-'git branch' [--track | --no-track] [-l] [-f] <branchname> [<start-point>]
+'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]
 'git branch' (-m | -M) [<oldbranch>] <newbranch>
 'git branch' (-d | -D) [-r] <branchname>...
 
@@ -30,17 +30,15 @@ commit) will be listed.  With `--no-merged` only branches not merged into
 the named commit will be listed.  If the <commit> argument is missing it
 defaults to 'HEAD' (i.e. the tip of the current branch).
 
-In the command's second form, a new branch named <branchname> will be created.
-It will start out with a head equal to the one given as <start-point>.
-If no <start-point> is given, the branch will be created with a head
-equal to that of the currently checked out branch.
+The command's second form creates a new branch head named <branchname>
+which points to the current 'HEAD', or <start-point> if given.
 
 Note that this will create the new branch, but it will not switch the
 working tree to it; use "git checkout <newbranch>" to switch to the
 new branch.
 
 When a local branch is started off a remote branch, git sets up the
-branch so that 'git-pull' will appropriately merge from
+branch so that 'git pull' will appropriately merge from
 the remote branch. This behavior may be changed via the global
 `branch.autosetupmerge` configuration flag. That setting can be
 overridden by using the `--track` and `--no-track` options.
@@ -57,7 +55,7 @@ has a reflog then the reflog will also be deleted.
 
 Use -r together with -d to delete remote-tracking branches. Note, that it
 only makes sense to delete remote-tracking branches if they no longer exist
-in the remote repository or if 'git-fetch' was configured not to fetch
+in the remote repository or if 'git fetch' was configured not to fetch
 them again. See also the 'prune' subcommand of linkgit:git-remote[1] for a
 way to clean up all obsolete remote-tracking branches.
 
@@ -65,7 +63,9 @@ way to clean up all obsolete remote-tracking branches.
 OPTIONS
 -------
 -d::
-       Delete a branch. The branch must be fully merged in HEAD.
+       Delete a branch. The branch must be fully merged in its
+       upstream branch, or in `HEAD` if no upstream was set with
+       `--track` or `--set-upstream`.
 
 -D::
        Delete a branch irrespective of its merged status.
@@ -74,10 +74,13 @@ OPTIONS
        Create the branch's reflog.  This activates recording of
        all changes made to the branch ref, enabling use of date
        based sha1 expressions such as "<branchname>@\{yesterday}".
+       Note that in non-bare repositories, reflogs are usually
+       enabled by default by the `core.logallrefupdates` config option.
 
 -f::
+--force::
        Reset <branchname> to <startpoint> if <branchname> exists
-       already. Without `-f` 'git-branch' refuses to change an existing branch.
+       already. Without `-f` 'git branch' refuses to change an existing branch.
 
 -m::
        Move/rename a branch and the corresponding reflog.
@@ -85,12 +88,14 @@ OPTIONS
 -M::
        Move/rename a branch even if the new branch name already exists.
 
---color::
+--color[=<when>]::
        Color branches to highlight current, local, and remote branches.
+       The value must be always (the default), never, or auto.
 
 --no-color::
        Turn off branch colors, even when the configuration file gives the
        default to color output.
+       Same as `--color=never`.
 
 -r::
        List or delete (if used with -d) the remote-tracking branches.
@@ -111,6 +116,7 @@ OPTIONS
 --no-abbrev::
        Display the full sha1s in the output listing rather than abbreviating them.
 
+-t::
 --track::
        When creating a new branch, set up configuration to mark the
        start-point branch as "upstream" from the new branch. This
@@ -129,14 +135,22 @@ start-point is either a local or remote branch.
        Do not set up "upstream" configuration, even if the
        branch.autosetupmerge configuration variable is true.
 
+--set-upstream::
+       If specified branch does not exist yet or if '--force' has been
+       given, acts exactly like '--track'. Otherwise sets up configuration
+       like '--track' would when creating the branch, except that where
+       branch points to is not changed.
+
 --contains <commit>::
        Only list branches which contain the specified commit.
 
---merged::
-       Only list branches which are fully contained by HEAD.
+--merged [<commit>]::
+       Only list branches whose tips are reachable from the
+       specified commit (HEAD if not specified).
 
---no-merged::
-       Do not list branches which are fully contained by HEAD.
+--no-merged [<commit>]::
+       Only list branches whose tips are not reachable from the
+       specified commit (HEAD if not specified).
 
 <branchname>::
        The name of the branch to create or delete.
@@ -145,9 +159,9 @@ start-point is either a local or remote branch.
        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.
+       The new branch head will point to this commit.  It may be
+       given as a branch name, a commit-id, or a tag.  If this
+       option is omitted, the current HEAD will be used instead.
 
 <oldbranch>::
        The name of an existing branch to rename.
@@ -208,6 +222,14 @@ but different purposes:
 - `--no-merged` is used to find branches which are candidates for merging
   into HEAD, since those branches are not fully contained by HEAD.
 
+SEE ALSO
+--------
+linkgit:git-check-ref-format[1],
+linkgit:git-fetch[1],
+linkgit:git-remote[1],
+link:user-manual.html#what-is-a-branch[``Understanding history: What is
+a branch?''] in the Git User's Manual.
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org> and Junio C Hamano <gitster@pobox.com>
index aee7e4a8c9396225e6d82d6a85618128c4170ce0..38e59afb34effd84da6d8a79da67e5ad3ae45c5a 100644 (file)
@@ -9,7 +9,7 @@ git-bundle - Move objects and refs by archive
 SYNOPSIS
 --------
 [verse]
-'git bundle' create <file> <git-rev-list args>
+'git bundle' create <file> <git-rev-list-args>
 'git bundle' verify <file>
 'git bundle' list-heads <file> [refname...]
 'git bundle' unbundle <file> [refname...]
@@ -21,10 +21,10 @@ Some workflows require that one or more branches of development on one
 machine be replicated on another machine, but the two machines cannot
 be directly connected, and therefore the interactive git protocols (git,
 ssh, rsync, http) cannot be used.  This command provides support for
-'git-fetch' and 'git-pull' to operate by packaging objects and references
+'git fetch' and 'git pull' to operate by packaging objects and references
 in an archive at the originating machine, then importing those into
-another repository using 'git-fetch' and 'git-pull'
-after moving the archive by some means (i.e., by sneakernet).  As no
+another repository using 'git fetch' and 'git pull'
+after moving the archive by some means (e.g., by sneakernet).  As no
 direct connection between the repositories exists, the user must specify a
 basis for the bundle that is held by the destination repository: the
 bundle assumes that all objects in the basis are already in the
@@ -34,57 +34,58 @@ OPTIONS
 -------
 
 create <file>::
-       Used to create a bundle named 'file'.  This requires the
-       'git-rev-list' arguments to define the bundle contents.
+       Used to create a bundle named 'file'.  This requires the
+       'git-rev-list-args' arguments to define the bundle contents.
 
 verify <file>::
-       Used to check that a bundle file is valid and will apply
-       cleanly to the current repository.  This includes checks on the
-       bundle format itself as well as checking that the prerequisite
-       commits exist and are fully linked in the current repository.
-       'git-bundle' prints a list of missing commits, if any, and exits
-       with a non-zero status.
+       Used to check that a bundle file is valid and will apply
+       cleanly to the current repository.  This includes checks on the
+       bundle format itself as well as checking that the prerequisite
+       commits exist and are fully linked in the current repository.
+       'git bundle' prints a list of missing commits, if any, and exits
+       with a non-zero status.
 
 list-heads <file>::
-       Lists the references defined in the bundle.  If followed by a
-       list of references, only references matching those given are
-       printed out.
+       Lists the references defined in the bundle.  If followed by a
+       list of references, only references matching those given are
+       printed out.
 
 unbundle <file>::
-       Passes the objects in the bundle to 'git-index-pack'
-       for storage in the repository, then prints the names of all
-       defined references. If a list of references is given, only
-       references matching those in the list are printed. This command is
-       really plumbing, intended to be called only by 'git-fetch'.
-
-[git-rev-list-args...]::
-       A list of arguments, acceptable to 'git-rev-parse' and
-       'git-rev-list', that specifies the specific objects and references
-       to transport.  For example, `master\~10..master` causes the
-       current master reference to be packaged along with all objects
-       added since its 10th ancestor commit.  There is no explicit
-       limit to the number of references and objects that may be
-       packaged.
+       Passes the objects in the bundle to 'git index-pack'
+       for storage in the repository, then prints the names of all
+       defined references. If a list of references is given, only
+       references matching those in the list are printed. This command is
+       really plumbing, intended to be called only by 'git fetch'.
+
+<git-rev-list-args>::
+       A list of arguments, acceptable to 'git rev-parse' and
+       'git rev-list' (and containg a named ref, see SPECIFYING REFERENCES
+       below), that specifies the specific objects and references
+       to transport.  For example, `master{tilde}10..master` causes the
+       current master reference to be packaged along with all objects
+       added since its 10th ancestor commit.  There is no explicit
+       limit to the number of references and objects that may be
+       packaged.
 
 
 [refname...]::
-       A list of references used to limit the references reported as
-       available. This is principally of use to 'git-fetch', which
-       expects to receive only those references asked for and not
-       necessarily everything in the pack (in this case, 'git-bundle' acts
-       like 'git-fetch-pack').
+       A list of references used to limit the references reported as
+       available. This is principally of use to 'git fetch', which
+       expects to receive only those references asked for and not
+       necessarily everything in the pack (in this case, 'git bundle' acts
+       like 'git fetch-pack').
 
 SPECIFYING REFERENCES
 ---------------------
 
-'git-bundle' will only package references that are shown by
-'git-show-ref': this includes heads, tags, and remote heads.  References
-such as `master\~1` cannot be packaged, but are perfectly suitable for
+'git bundle' will only package references that are shown by
+'git show-ref': this includes heads, tags, and remote heads.  References
+such as `master{tilde}1` cannot be packaged, but are perfectly suitable for
 defining the basis.  More than one reference may be packaged, and more
 than one basis can be specified.  The objects packaged are those not
 contained in the union of the given bases.  Each basis can be
-specified explicitly (e.g. `^master\~10`), or implicitly (e.g.
-`master\~10..master`, `--since=10.days.ago master`).
+specified explicitly (e.g. `^master{tilde}10`), or implicitly (e.g.
+`master{tilde}10..master`, `--since=10.days.ago master`).
 
 It is very important that the basis used be held by the destination.
 It is okay to err on the side of caution, causing the bundle file
@@ -154,7 +155,7 @@ machineB$ git pull
 If you know up to what commit the intended recipient repository should
 have the necessary objects, you can use that knowledge to specify the
 basis, giving a cut-off point to limit the revisions and objects that go
-in the resulting bundle. The previous example used lastR2bundle tag
+in the resulting bundle. The previous example used the lastR2bundle tag
 for this purpose, but you can use any other options that you would give to
 the linkgit:git-log[1] command. Here are more examples:
 
@@ -194,7 +195,7 @@ references when fetching:
 $ git fetch mybundle master:localRef
 ----------------
 
-You can also see what references it offers.
+You can also see what references it offers:
 
 ----------------
 $ git ls-remote mybundle
index b191276d7a44511bfce60febe6f5cb9a025ec9a3..a3f56b07fd251cbd121c28189d1f9018b892b48e 100644 (file)
@@ -9,14 +9,15 @@ git-cat-file - Provide content or type and size information for repository objec
 SYNOPSIS
 --------
 [verse]
-'git cat-file' [-t | -s | -e | -p | <type>] <object>
-'git cat-file' [--batch | --batch-check] < <list-of-objects>
+'git cat-file' (-t | -s | -e | -p | <type> | --textconv ) <object>
+'git cat-file' (--batch | --batch-check) < <list-of-objects>
 
 DESCRIPTION
 -----------
 In its first form, the command provides the content or the type of an object in
 the repository. The type is required unless '-t' or '-p' is used to find the
-object type, or '-s' is used to find the object size.
+object type, or '-s' is used to find the object size, or '--textconv' is used
+(which implies type "blob").
 
 In the second form, a list of objects (separated by linefeeds) is provided on
 stdin, and the SHA1, type, and size of each object is printed on stdout.
@@ -26,7 +27,7 @@ OPTIONS
 <object>::
        The name of the object to show.
        For a more complete list of ways to spell object names, see
-       the "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
+       the "SPECIFYING REVISIONS" section in linkgit:gitrevisions[1].
 
 -t::
        Instead of the content, show the object type identified by
@@ -51,6 +52,11 @@ OPTIONS
        or to ask for a "blob" with <object> being a tag object that
        points at it.
 
+--textconv::
+       Show the content as transformed by a textconv filter. In this case,
+       <object> has be of the form <treeish>:<path>, or :<path> in order
+       to apply the filter to the content recorded in the index at <path>.
+
 --batch::
        Print the SHA1, type, size, and contents of each object provided on
        stdin. May not be combined with any other options or arguments.
index c1ce26884e73f1599602472ba5032988486cc465..f5c2e0601db6782b2a8690c687611b93da935024 100644 (file)
@@ -9,7 +9,8 @@ SYNOPSIS
 --------
 [verse]
 'git check-ref-format' <refname>
-'git check-ref-format' [--branch] <branchname-shorthand>
+'git check-ref-format' --print <refname>
+'git check-ref-format' --branch <branchname-shorthand>
 
 DESCRIPTION
 -----------
@@ -18,13 +19,18 @@ status if it is not.
 
 A reference is used in git to specify branches and tags.  A
 branch head is stored under the `$GIT_DIR/refs/heads` directory, and
-a tag is stored under the `$GIT_DIR/refs/tags` directory.  git
-imposes the following rules on how references are named:
+a tag is stored under the `$GIT_DIR/refs/tags` directory (or, if refs
+are packed by `git gc`, as entries in the `$GIT_DIR/packed-refs` file).
+git imposes the following rules on how references are named:
 
 . They can include slash `/` for hierarchical (directory)
   grouping, but no slash-separated component can begin with a
   dot `.`.
 
+. They must contain at least one `/`. This enforces the presence of a
+  category like `heads/`, `tags/` etc. but the actual names are not
+  restricted.
+
 . They cannot have two consecutive dots `..` anywhere.
 
 . They cannot have ASCII control characters (i.e. bytes whose
@@ -38,10 +44,12 @@ imposes the following rules on how references are named:
 
 . They cannot contain a sequence `@{`.
 
+. They cannot contain a `\`.
+
 These rules make it easy for shell script based tools to parse
 reference names, pathname expansion by the shell when a reference name is used
 unquoted (by mistake), and also avoids ambiguities in certain
-reference name expressions (see linkgit:git-rev-parse[1]):
+reference name expressions (see linkgit:gitrevisions[1]):
 
 . A double-dot `..` is often used as in `ref1..ref2`, and in some
   contexts this notation means `{caret}ref1 ref2` (i.e. not in
@@ -53,20 +61,35 @@ reference name expressions (see linkgit:git-rev-parse[1]):
 . A colon `:` is used as in `srcref:dstref` to mean "use srcref\'s
   value and store it in dstref" in fetch and push operations.
   It may also be used to select a specific object such as with
-  'git-cat-file': "git cat-file blob v1.3.3:refs.c".
+  'git cat-file': "git cat-file blob v1.3.3:refs.c".
 
 . at-open-brace `@{` is used as a notation to access a reflog entry.
 
-With the `--branch` option, it expands a branch name shorthand and
-prints the name of the branch the shorthand refers to.
+With the `--print` option, if 'refname' is acceptable, it prints the
+canonicalized name of a hypothetical reference with that name.  That is,
+it prints 'refname' with any extra `/` characters removed.
 
-EXAMPLE
--------
+With the `--branch` option, it expands the ``previous branch syntax''
+`@{-n}`.  For example, `@{-1}` is a way to refer the last branch you
+were on.  This option should be used by porcelains to accept this
+syntax anywhere a branch name is expected, so they can act as if you
+typed the branch name.
 
-git check-ref-format --branch @{-1}::
-
-Print the name of the previous branch.
+EXAMPLES
+--------
 
+* Print the name of the previous branch:
++
+------------
+$ git check-ref-format --branch @{-1}
+------------
+
+* Determine the reference name to use for a new branch:
++
+------------
+$ ref=$(git check-ref-format --print "refs/heads/$newbranch") ||
+die "we do not like '$newbranch' as a branch name."
+------------
 
 GIT
 ---
index 62d84836b8a0d77c2a6ea534566ff8462b0eb37a..62f9ab24c97875df6f053aa11f8f5cde56377175 100644 (file)
@@ -13,7 +13,7 @@ SYNOPSIS
                   [--stage=<number>|all]
                   [--temp]
                   [-z] [--stdin]
-                  [--] [<file>]\*
+                  [--] [<file>]*
 
 DESCRIPTION
 -----------
@@ -88,7 +88,7 @@ $ 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:
+since 'git checkout-index' accepts --stdin it would be faster to use:
 
 ----------------
 $ find . -name '*.h' -print0 | git checkout-index -f -z --stdin
@@ -102,7 +102,7 @@ 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
+'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
@@ -147,9 +147,9 @@ 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"::
+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.
+       'git checkout-index' as an "export as tree" function.
        Just read the desired tree into the index, and do:
 +
 ----------------
index ad4b31e89218e857001fce69f32acbe85fc7539c..f88e9977d1137da0449736f5f8fe6746b42437e1 100644 (file)
@@ -9,45 +9,67 @@ SYNOPSIS
 --------
 [verse]
 'git checkout' [-q] [-f] [-m] [<branch>]
-'git checkout' [-q] [-f] [-m] [-b <new_branch>] [<start_point>]
+'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]
 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
+'git checkout' --patch [<tree-ish>] [--] [<paths>...]
 
 DESCRIPTION
 -----------
-
-When <paths> are not given, this command switches branches by
-updating the index, working tree, and HEAD to reflect the specified
+Updates files in the working tree to match the version in the index
+or the specified tree.  If no paths are given, 'git checkout' will
+also update `HEAD` to set the specified branch as the current
 branch.
 
-If `-b` is given, a new branch is created and checked out, as if
-linkgit:git-branch[1] were called; in this case you can
-use the --track or --no-track options, which will be passed to `git
-branch`.  As a convenience, --track without `-b` implies branch
-creation; see the description of --track below.
-
-When <paths> are given, this command does *not* switch
-branches.  It updates the named paths in the working tree from
-the index file, or from a named <tree-ish> (most often a commit).  In
-this case, the `-b` and `--track` options are meaningless and giving
-either of them results in an error. The <tree-ish> argument can be
-used to specify a specific tree-ish (i.e. commit, tag or tree)
-to update the index for the given paths before updating the
-working tree.
-
-The index may contain unmerged entries after a failed merge.  By
-default, if you try to check out such an entry from the index, the
+'git checkout' [<branch>]::
+'git checkout' -b|-B <new_branch> [<start point>]::
+
+       This form switches branches by updating the index, working
+       tree, and HEAD to reflect the specified branch.
++
+If `-b` is given, a new branch is created as if linkgit:git-branch[1]
+were called and then checked out; in this case you can
+use the `--track` or `--no-track` options, which will be passed to
+'git branch'.  As a convenience, `--track` without `-b` implies branch
+creation; see the description of `--track` below.
++
+If `-B` is given, <new_branch> is created if it doesn't exist; otherwise, it
+is reset. This is the transactional equivalent of
++
+------------
+$ git branch -f <branch> [<start point>]
+$ git checkout <branch>
+------------
++
+that is to say, the branch is not reset/created unless "git checkout" is
+successful.
+
+'git checkout' [--patch] [<tree-ish>] [--] <pathspec>...::
+
+       When <paths> or `--patch` are given, 'git checkout' *not* switch
+       branches.  It updates the named paths in the working tree from
+       the index file or from a named <tree-ish> (most often a commit).  In
+       this case, the `-b` and `--track` options are meaningless and giving
+       either of them results in an error. The <tree-ish> argument can be
+       used to specify a specific tree-ish (i.e. commit, tag or tree)
+       to update the index for the given paths before updating the
+       working tree.
++
+The index may contain unmerged entries because of a previous failed merge.
+By default, if you try to check out such an entry from the index, the
 checkout operation will fail and nothing will be checked out.
-Using -f will ignore these unmerged entries.  The contents from a
+Using `-f` will ignore these unmerged entries.  The contents from a
 specific side of the merge can be checked out of the index by
-using --ours or --theirs.  With -m, changes made to the working tree
-file can be discarded to recreate the original conflicted merge result.
+using `--ours` or `--theirs`.  With `-m`, changes made to the working tree
+file can be discarded to re-create the original conflicted merge result.
 
 OPTIONS
 -------
 -q::
+--quiet::
        Quiet, suppress feedback messages.
 
 -f::
+--force::
        When switching branches, proceed even if the index or the
        working tree differs from HEAD.  This is used to throw away
        local changes.
@@ -64,6 +86,12 @@ entries; instead, unmerged entries are ignored.
        Create a new branch named <new_branch> and start it at
        <start_point>; see linkgit:git-branch[1] for details.
 
+-B::
+       Creates the branch <new_branch> and start it at <start_point>;
+       if it already exists, then reset it to <start_point>. This is
+       equivalent to running "git branch" with "-f"; see
+       linkgit:git-branch[1] for details.
+
 -t::
 --track::
        When creating a new branch, set up "upstream" configuration. See
@@ -87,6 +115,31 @@ explicitly give a name with '-b' in such a case.
        Create the new branch's reflog; see linkgit:git-branch[1] for
        details.
 
+--orphan::
+       Create a new 'orphan' branch, named <new_branch>, started from
+       <start_point> and switch to it.  The first commit made on this
+       new branch will have no parents and it will be the root of a new
+       history totally disconnected from all the other branches and
+       commits.
++
+The index and the working tree are adjusted as if you had previously run
+"git checkout <start_point>".  This allows you to start a new history
+that records a set of paths similar to <start_point> by easily running
+"git commit -a" to make the root commit.
++
+This can be useful when you want to publish the tree from a commit
+without exposing its full history. You might want to do this to publish
+an open source branch of a project whose current tree is "clean", but
+whose full history contains proprietary or otherwise encumbered bits of
+code.
++
+If you want to start a disconnected history that records a set of paths
+that is totally different from the one of <start_point>, then you should
+clear the index and the working tree right after creating the orphan
+branch by running "git rm -rf ." from the top level of the working tree.
+Afterwards you will be ready to prepare your new files, repopulating the
+working tree, by copying them from elsewhere, extracting a tarball, etc.
+
 -m::
 --merge::
        When switching branches,
@@ -113,6 +166,16 @@ the conflicted merge in the specified paths.
        "merge" (default) and "diff3" (in addition to what is shown by
        "merge" style, shows the original contents).
 
+-p::
+--patch::
+       Interactively select hunks in the difference between the
+       <tree-ish> (or the index, if unspecified) and the working
+       tree.  The chosen hunks are then applied in reverse to the
+       working tree (and if a <tree-ish> was specified, the index).
++
+This means that you can use `git checkout -p` to selectively discard
+edits from your current working tree.
+
 <branch>::
        Branch to checkout; if it refers to a branch (i.e., a name that,
        when prepended with "refs/heads/", is a valid ref), then that
@@ -123,6 +186,10 @@ the conflicted merge in the specified paths.
 As a special case, the `"@\{-N\}"` syntax for the N-th last branch
 checks out the branch (instead of detaching).  You may also specify
 `-` which is synonymous with `"@\{-1\}"`.
++
+As a further special case, you may use `"A\...B"` as a shortcut for the
+merge base of `A` and `B` if there is exactly one merge base. You can
+leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
 
 <new_branch>::
        Name for the new branch.
@@ -213,7 +280,7 @@ the above checkout would fail like this:
 +
 ------------
 $ git checkout mytopic
-fatal: Entry 'frotz' not uptodate. Cannot merge.
+error: You have local changes to 'frotz'; not switching branches.
 ------------
 +
 You can give the `-m` flag to the command, which would try a
index b764130d26eb750d2b5173dcc98366828e413046..2cef579316ba11e68d65dc4d7309f2e56a465b3d 100644 (file)
@@ -3,28 +3,32 @@ git-cherry-pick(1)
 
 NAME
 ----
-git-cherry-pick - Apply the change introduced by an existing commit
+git-cherry-pick - Apply the changes introduced by some existing commits
 
 SYNOPSIS
 --------
-'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] <commit>
+'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] <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).
+
+Given one or more existing commits, apply the change each one
+introduces, recording a new commit for each.  This requires your
+working tree to be clean (no modifications from the HEAD commit).
 
 OPTIONS
 -------
-<commit>::
-       Commit to cherry-pick.
-       For a more complete list of ways to spell commits, see the
-       "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
+<commit>...::
+       Commits to cherry-pick.
+       For a more complete list of ways to spell commits, see
+       linkgit:gitrevisions[1].
+       Sets of commits can be passed but no traversal is done by
+       default, as if the '--no-walk' option was specified, see
+       linkgit:git-rev-list[1].
 
 -e::
 --edit::
-       With this option, 'git-cherry-pick' will let you edit the commit
+       With this option, 'git cherry-pick' will let you edit the commit
        message prior to committing.
 
 -x::
@@ -55,10 +59,10 @@ OPTIONS
 
 -n::
 --no-commit::
-       Usually the command automatically creates a commit.
-       This flag applies the change necessary to cherry-pick
-       the named commit to your working tree and the index,
-       but does not make the commit.  In addition, when this
+       Usually the command automatically creates a sequence of commits.
+       This flag applies the changes necessary to cherry-pick
+       each named commit to your working tree and the index,
+       without making any commit.  In addition, when this
        option is used, your index does not have to match the
        HEAD commit.  The cherry-pick is done against the
        beginning state of your index.
@@ -70,6 +74,51 @@ effect to your index in a row.
 --signoff::
        Add Signed-off-by line at the end of the commit message.
 
+--ff::
+       If the current HEAD is the same as the parent of the
+       cherry-pick'ed commit, then a fast forward to this commit will
+       be performed.
+
+EXAMPLES
+--------
+git cherry-pick master::
+
+       Apply the change introduced by the commit at the tip of the
+       master branch and create a new commit with this change.
+
+git cherry-pick ..master::
+git cherry-pick ^HEAD master::
+
+       Apply the changes introduced by all commits that are ancestors
+       of master but not of HEAD to produce new commits.
+
+git cherry-pick master\~4 master~2::
+
+       Apply the changes introduced by the fifth and third last
+       commits pointed to by master and create 2 new commits with
+       these changes.
+
+git cherry-pick -n master~1 next::
+
+       Apply to the working tree and the index the changes introduced
+       by the second last commit pointed to by master and by the last
+       commit pointed to by next, but do not create any commit with
+       these changes.
+
+git cherry-pick --ff ..next::
+
+       If history is linear and HEAD is an ancestor of next, update
+       the working tree and advance the HEAD pointer to match next.
+       Otherwise, apply the changes introduced by those commits that
+       are in next but not HEAD to the current branch, creating a new
+       commit for each new change.
+
+git rev-list --reverse master \-- README | git cherry-pick -n --stdin::
+
+       Apply the changes introduced by all commits on the master
+       branch that touched README to the working tree and index,
+       so the result can be inspected and made into a single new
+       commit if suitable.
 
 Author
 ------
@@ -79,6 +128,10 @@ Documentation
 --------------
 Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
+SEE ALSO
+--------
+linkgit:git-revert[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index 7deefdae8f995d843971f6beff24af8325b99f1f..fed115acd01a63801a647ae870357d4c2d59ea44 100644 (file)
@@ -14,7 +14,7 @@ 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>.
 The commits are compared with their 'patch id', obtained from
-the 'git-patch-id' program.
+the 'git patch-id' program.
 
 Every commit that doesn't exist in the <upstream> branch
 has its id (sha1) reported, prefixed by a symbol.  The ones that have
@@ -37,8 +37,8 @@ to and including <limit> are not reported:
               \__*__*__<limit>__-__+__> <head>
 
 
-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
+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.
index 670cb02b6cc035e4fbcf1a1016f66b7a85cd4ef7..fb2753c97eb186c63ba16f9b7714c2e3a5b82a64 100644 (file)
@@ -14,9 +14,9 @@ DESCRIPTION
 A Tcl/Tk based graphical interface to review modified files, stage
 them into the index, enter a commit message and record the new
 commit onto the current branch.  This interface is an alternative
-to the less interactive 'git-commit' program.
+to the less interactive 'git commit' program.
 
-'git-citool' is actually a standard alias for `git gui citool`.
+'git citool' is actually a standard alias for `git gui citool`.
 See linkgit:git-gui[1] for more details.
 
 Author
index be894af39ff6559affbf0617e4c5d8fe68c33fe8..60e38e6e275f5cf90a1ccba36dd3930cafa1b630 100644 (file)
@@ -8,7 +8,7 @@ git-clean - Remove untracked files from the working tree
 SYNOPSIS
 --------
 [verse]
-'git clean' [-d] [-f] [-n] [-q] [-x | -X] [--] <path>...
+'git clean' [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <path>...
 
 DESCRIPTION
 -----------
@@ -27,10 +27,14 @@ OPTIONS
 -------
 -d::
        Remove untracked directories in addition to untracked files.
+       If an untracked directory is managed by a different git
+       repository, it is not removed by default.  Use -f option twice
+       if you really want to remove such a directory.
 
 -f::
-       If the git configuration specifies clean.requireForce as true,
-       'git-clean' will refuse to run unless given -f or -n.
+--force::
+       If the git configuration variable clean.requireForce is not set
+       to false, 'git clean' will refuse to run unless given -f or -n.
 
 -n::
 --dry-run::
@@ -41,10 +45,16 @@ OPTIONS
        Be quiet, only report errors, but not the files that are
        successfully removed.
 
+-e <pattern>::
+--exclude=<pattern>::
+       Specify special exceptions to not be cleaned.  Each <pattern> is
+       the same form as in $GIT_DIR/info/excludes and this option can be
+       given multiple times.
+
 -x::
        Don't use the ignore rules.  This allows removing all untracked
        files, including build products.  This can be used (possibly in
-       conjunction with 'git-reset') to create a pristine
+       conjunction with 'git reset') to create a pristine
        working directory to test a clean build.
 
 -X::
index 4072f40d7ae5417c51d1cddcf587b674aa1dc0e5..dc7d3d17b151d05827925fdcd2806945e2e278cb 100644 (file)
@@ -11,16 +11,17 @@ SYNOPSIS
 [verse]
 'git clone' [--template=<template_directory>]
          [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
-         [-o <name>] [-u <upload-pack>] [--reference <repository>]
-         [--depth <depth>] [--] <repository> [<directory>]
+         [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
+         [--depth <depth>] [--recursive] [--] <repository> [<directory>]
 
 DESCRIPTION
 -----------
 
 Clones a repository into a newly created directory, creates
 remote-tracking branches for each branch in the cloned repository
-(visible using `git branch -r`), and creates and checks out an initial
-branch equal to the cloned repository's currently active branch.
+(visible using `git branch -r`), and creates and checks out an
+initial branch that is forked from the cloned repository's
+currently active branch.
 
 After the clone, a plain `git fetch` without arguments will update
 all the remote-tracking branches, and a `git pull` without
@@ -28,7 +29,7 @@ arguments will in addition merge the remote master branch into the
 current master branch, if any.
 
 This default configuration is achieved by creating references to
-the remote branch heads under `$GIT_DIR/refs/remotes/origin` and
+the remote branch heads under `refs/remotes/origin` and
 by initializing `remote.origin.url` and `remote.origin.fetch`
 configuration variables.
 
@@ -38,7 +39,7 @@ OPTIONS
 --local::
 -l::
        When the repository to clone from is on a local machine,
-       this flag bypasses normal "git aware" transport
+       this flag bypasses the 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
@@ -59,7 +60,7 @@ OPTIONS
 -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
+       `.git/objects/info/alternates` to share the objects
        with the source repository.  The resulting repository
        starts out without any object of its own.
 +
@@ -68,32 +69,47 @@ it unless you understand what it does. If you clone your
 repository using this option and then delete branches (or use any
 other git command that makes any existing commit unreferenced) in the
 source repository, some objects may become unreferenced (or dangling).
-These objects may be removed by normal git operations (such as 'git-commit')
+These objects may be removed by normal git operations (such as `git commit`)
 which automatically call `git gc --auto`. (See linkgit:git-gc[1].)
 If these objects are removed and were referenced by the cloned repository,
 then the cloned repository will become corrupt.
-
-
++
+Note that running `git repack` without the `-l` option in a repository
+cloned with `-s` will copy objects from the source repository into a pack
+in the cloned repository, removing the disk space savings of `clone -s`.
+It is safe, however, to run `git gc`, which uses the `-l` option by
+default.
++
+If you want to break the dependency of a repository cloned with `-s` on
+its source repository, you can simply run `git repack -a` to copy all
+objects from the source repository into a pack in the cloned repository.
 
 --reference <repository>::
-       If the reference repository is on the local machine
-       automatically setup .git/objects/info/alternates to
+       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 fewer objects to be copied from the repository
        being cloned, reducing network and local storage costs.
 +
-*NOTE*: see NOTE to --shared option.
+*NOTE*: see the NOTE for the `--shared` option.
 
 --quiet::
 -q::
-       Operate quietly.  This flag is also passed to the `rsync'
+       Operate quietly.  Progress is not reported to the standard
+       error stream. This flag is also passed to the `rsync'
        command when given.
 
 --verbose::
 -v::
-       Display the progressbar, even in case the standard output is not
-       a terminal.
+       Run verbosely. Does not affect the reporting of progress status
+       to the standard error stream.
+
+--progress::
+       Progress status is reported on the standard error stream
+       by default when it is attached to a terminal, unless -q
+       is specified. This flag forces progress status even if the
+       standard error stream is not directed to a terminal.
 
 --no-checkout::
 -n::
@@ -112,12 +128,19 @@ then the cloned repository will become corrupt.
        configuration variables are created.
 
 --mirror::
-       Set up a mirror of the remote repository.  This implies --bare.
+       Set up a mirror of the remote repository.  This implies `--bare`.
 
 --origin <name>::
 -o <name>::
-       Instead of using the remote name 'origin' to keep track
-       of the upstream repository, use <name>.
+       Instead of using the remote name `origin` to keep track
+       of the upstream repository, use `<name>`.
+
+--branch <name>::
+-b <name>::
+       Instead of pointing the newly created HEAD to the branch pointed
+       to by the cloned repository's HEAD, point to `<name>` branch
+       instead. In a non-bare repository, this is the branch that will
+       be checked out.
 
 --upload-pack <upload-pack>::
 -u <upload-pack>::
@@ -127,8 +150,7 @@ then the cloned repository will become corrupt.
 
 --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`.
+       (See the "TEMPLATE DIRECTORY" section of linkgit:git-init[1].)
 
 --depth <depth>::
        Create a 'shallow' clone with a history truncated to the
@@ -139,6 +161,14 @@ then the cloned repository will become corrupt.
        with a long history, and would want to send in fixes
        as patches.
 
+--recursive::
+       After the clone is created, initialize all submodules within,
+       using their default settings. This is equivalent to running
+       `git submodule update --init --recursive` immediately after
+       the clone is finished. This option is ignored if the cloned
+       repository does not have a worktree/checkout (i.e. if any of
+       `--no-checkout`/`-n`, `--bare`, or `--mirror` is given)
+
 <repository>::
        The (possibly remote) repository to clone from.  See the
        <<URLS,URLS>> section below for more information on specifying
@@ -147,9 +177,9 @@ then the cloned repository will become corrupt.
 <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.
+       explicitly given (`repo` for `/path/to/repo.git` and `foo`
+       for `host.xz:foo/.git`).  Cloning into an existing directory
+       is only allowed if the directory is empty.
 
 :git-clone: 1
 include::urls.txt[]
@@ -157,7 +187,7 @@ include::urls.txt[]
 Examples
 --------
 
-Clone from upstream::
+* Clone from upstream:
 +
 ------------
 $ git clone git://git.kernel.org/pub/scm/.../linux-2.6 my2.6
@@ -166,7 +196,7 @@ $ make
 ------------
 
 
-Make a local clone that borrows from the current directory, without checking things out::
+* Make a local clone that borrows from the current directory, without checking things out:
 +
 ------------
 $ git clone -l -s -n . ../copy
@@ -175,7 +205,7 @@ $ git show-branch
 ------------
 
 
-Clone from upstream while borrowing from an existing local directory::
+* Clone from upstream while borrowing from an existing local directory:
 +
 ------------
 $ git clone --reference my2.6 \
@@ -185,14 +215,14 @@ $ cd my2.7
 ------------
 
 
-Create a bare repository to publish your changes to the public::
+* 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::
+* Create a repository on the kernel.org machine that borrows from Linus:
 +
 ------------
 $ git clone --bare -l -s /pub/scm/.../torvalds/linux-2.6.git \
index b8834baced4b25d6df47eb578c4d745d23e8cc73..349366ee1e2c414aed87745d2c717ea9a2149367 100644 (file)
@@ -8,7 +8,7 @@ git-commit-tree - Create a new commit object
 
 SYNOPSIS
 --------
-'git commit-tree' <tree> [-p <parent commit>]\* < changelog
+'git commit-tree' <tree> [-p <parent commit>]* < changelog
 
 DESCRIPTION
 -----------
@@ -70,9 +70,10 @@ is taken from the configuration items user.name and user.email, or, if not
 present, system user name and fully qualified hostname.
 
 A commit comment is read from stdin. If a changelog
-entry is not provided via "<" redirection, 'git-commit-tree' will just wait
+entry is not provided via "<" redirection, 'git commit-tree' will just wait
 for one to be entered and terminated with ^D.
 
+include::date-formats.txt[]
 
 Diagnostics
 -----------
index b5d81be7ecd60daa1a1d476441ca58e6b732d9ef..42fb1f57b21d7c355d3df6be07329c478786a9a7 100644 (file)
@@ -8,10 +8,11 @@ git-commit - Record changes to the repository
 SYNOPSIS
 --------
 [verse]
-'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend]
-          [(-c | -C) <commit>] [-F <file> | -m <msg>]
-          [--allow-empty] [--no-verify] [-e] [--author=<author>]
-          [--cleanup=<mode>] [--] [[-i | -o ]<file>...]
+'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] [--dry-run]
+          [(-c | -C) <commit>] [-F <file> | -m <msg>] [--reset-author]
+          [--allow-empty] [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
+          [--date=<date>] [--cleanup=<mode>] [--status | --no-status] [--]
+          [[-i | -o ]<file>...]
 
 DESCRIPTION
 -----------
@@ -20,11 +21,11 @@ with a log message from the user describing the changes.
 
 The content to be added can be specified in several ways:
 
-1. by using 'git-add' to incrementally "add" changes to the
+1. by using 'git add' to incrementally "add" changes to the
    index before using the 'commit' command (Note: even modified
    files must be "added");
 
-2. by using 'git-rm' to remove files from the working tree
+2. by using 'git rm' to remove files from the working tree
    and the index, again before using the 'commit' command;
 
 3. by listing files as arguments to the 'commit' command, in which
@@ -40,15 +41,14 @@ The content to be added can be specified in several ways:
 
 5. by using the --interactive switch with the 'commit' command to decide one
    by one which files should be part of the commit, before finalizing the
-   operation.  Currently, this is done by invoking 'git-add --interactive'.
+   operation.  Currently, this is done by invoking 'git add --interactive'.
 
-The 'git-status' command can be used to obtain a
+The `--dry-run` option can be used to obtain a
 summary of what is included by any of the above for the next
-commit by giving the same set of parameters you would give to
-this command.
+commit by giving the same set of parameters (options and paths).
 
 If you make a commit and then find a mistake immediately after
-that, you can recover from it with 'git-reset'.
+that, you can recover from it with 'git reset'.
 
 
 OPTIONS
@@ -70,16 +70,39 @@ OPTIONS
        Like '-C', but with '-c' the editor is invoked, so that
        the user can further edit the commit message.
 
+--reset-author::
+       When used with -C/-c/--amend options, declare that the
+       authorship of the resulting commit now belongs of the committer.
+       This also renews the author timestamp.
+
+--short::
+       When doing a dry-run, give the output in the short-format. See
+       linkgit:git-status[1] for details. Implies `--dry-run`.
+
+--porcelain::
+       When doing a dry-run, give the output in a porcelain-ready
+       format. See linkgit:git-status[1] for details. Implies
+       `--dry-run`.
+
+-z::
+       When showing `short` or `porcelain` status output, terminate
+       entries in the status output with NUL, instead of LF. If no
+       format is given, implies the `--porcelain` output format.
+
 -F <file>::
 --file=<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.  You can use the
-       standard `A U Thor <author@example.com>` format.  Otherwise,
-       an existing commit that matches the given string and its author
-       name is used.
+       Override the commit author. Specify an explicit author using the
+       standard `A U Thor <author@example.com>` format. Otherwise <author>
+       is assumed to be a pattern and is used to search for an existing
+       commit by that author (i.e. rev-list --all -i --author=<author>);
+       the commit author is then copied from the first such commit found.
+
+--date=<date>::
+       Override the author date used in the commit.
 
 -m <msg>::
 --message=<msg>::
@@ -107,7 +130,13 @@ OPTIONS
        Usually recording a commit that has the exact same tree as its
        sole parent commit is a mistake, and the command prevents you
        from making such a commit.  This option bypasses the safety, and
-       is primarily for use by foreign scm interface scripts.
+       is primarily for use by foreign SCM interface scripts.
+
+--allow-empty-message::
+       Like --allow-empty this command is primarily for use by foreign
+       SCM interface scripts. It allows you to create a commit with an
+       empty commit message without using plumbing commands like
+       linkgit:git-commit-tree[1].
 
 --cleanup=<mode>::
        This option sets how the commit message is cleaned up.
@@ -163,7 +192,7 @@ FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].)
        Make a commit only from the paths specified on the
        command line, disregarding any contents that have been
        staged so far. This is the default mode of operation of
-       'git-commit' if any paths are given on the command line,
+       'git commit' if any paths are given on the command line,
        in which case this option can be omitted.
        If this option is specified together with '--amend', then
        no paths need to be specified, which can be used to amend
@@ -175,13 +204,13 @@ FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].)
        Show untracked files (Default: 'all').
 +
 The mode parameter is optional, and is used to specify
-the handling of untracked files. The possible options are:
+the handling of untracked files.
++
+The possible options are:
 +
---
        - 'no'     - Show no untracked files
        - 'normal' - Shows untracked files and directories
        - 'all'    - Also shows individual files in untracked directories.
---
 +
 See linkgit:git-config[1] for configuration variable
 used to change the default for when the option is not
@@ -198,6 +227,22 @@ specified.
 --quiet::
        Suppress commit summary message.
 
+--dry-run::
+       Do not create a commit, but show a list of paths that are
+       to be committed, paths with local changes that will be left
+       uncommitted and paths that are untracked.
+
+--status::
+       Include the output of linkgit:git-status[1] in the commit
+       message template when using an editor to prepare the commit
+       message.  Defaults to on, but can be used to override
+       configuration variable commit.status.
+
+--no-status::
+       Do not include the output of linkgit:git-status[1] in the
+       commit message template when using an editor to prepare the
+       default commit message.
+
 \--::
        Do not interpret any more arguments as options.
 
@@ -208,15 +253,17 @@ specified.
        these files are also staged for the next commit on top
        of what have been staged before.
 
+:git-commit: 1
+include::date-formats.txt[]
 
 EXAMPLES
 --------
 When recording your own work, the contents of modified files in
 your working tree are temporarily stored to a staging area
-called the "index" with 'git-add'.  A file can be
+called the "index" with 'git add'.  A file can be
 reverted back, only in the index but not in the working tree,
 to that of the last commit with `git reset HEAD -- <file>`,
-which effectively reverts 'git-add' and prevents the changes to
+which effectively reverts 'git add' and prevents the changes to
 this file from participating in the next commit.  After building
 the state to be committed incrementally with these commands,
 `git commit` (without any pathname parameter) is used to record what
@@ -272,13 +319,13 @@ $ git commit
 this second commit would record the changes to `hello.c` and
 `hello.h` as expected.
 
-After a merge (initiated by 'git-merge' or 'git-pull') stops
+After a merge (initiated by 'git merge' or 'git pull') stops
 because of conflicts, cleanly merged
 paths are already staged to be committed for you, and paths that
 conflicted are left in unmerged state.  You would have to first
-check which paths are conflicting with 'git-status'
+check which paths are conflicting with 'git status'
 and after fixing them manually in your working tree, you would
-stage the result as usual with 'git-add':
+stage the result as usual with 'git add':
 
 ------------
 $ git status | grep unmerged
@@ -319,7 +366,7 @@ ENVIRONMENT AND CONFIGURATION VARIABLES
 The editor used to edit the commit log message will be chosen from the
 GIT_EDITOR environment variable, the core.editor configuration variable, the
 VISUAL environment variable, or the EDITOR environment variable (in that
-order).
+order).  See linkgit:git-var[1] for details.
 
 HOOKS
 -----
index 7131ee3c66951de5981a82e304f4619de88064c5..543dd64a468a8e96daf7c97d9c52264ce426db65 100644 (file)
@@ -37,11 +37,12 @@ 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 also <<EXAMPLES>>).
 
-The type specifier can be either '--int' or '--bool', which will make
-'git-config' ensure that the variable(s) are of the given type and
+The type specifier can be either '--int' or '--bool', to make
+'git config' ensure that the variable(s) are of the given type and
 convert the value to the canonical form (simple decimal number for int,
-a "true" or "false" string for bool).  If no type specifier is passed,
-no checks or transformations are performed on the value.
+a "true" or "false" string for bool), or '--path', which does some
+path expansion (see '--path' below).  If no type specifier is passed, no
+checks or transformations are performed on the value.
 
 The file-option can be one of '--system', '--global' or '--file'
 which specify where the values will be read from or written to.
@@ -69,7 +70,8 @@ OPTIONS
 
 --add::
        Adds a new line to the option without altering any existing
-       values.  This is the same as providing '^$' as the value_regex.
+       values.  This is the same as providing '^$' as the value_regex
+       in `--replace-all`.
 
 --get::
        Get the value for a given key (optionally filtered by a regex
@@ -123,18 +125,25 @@ See also <<FILES>>.
        List all variables set in config file.
 
 --bool::
-       'git-config' will ensure that the output is "true" or "false"
+       'git config' will ensure that the output is "true" or "false"
 
 --int::
-       'git-config' will ensure that the output is a simple
+       'git config' will ensure that the output is a simple
        decimal number.  An optional value suffix of 'k', 'm', or 'g'
        in the config file will cause the value to be multiplied
        by 1024, 1048576, or 1073741824 prior to output.
 
 --bool-or-int::
-       'git-config' will ensure that the output matches the format of
+       'git config' will ensure that the output matches the format of
        either --bool or --int, as described above.
 
+--path::
+       'git-config' will expand leading '{tilde}' to the value of
+       '$HOME', and '{tilde}user' to the home directory for the
+       specified user.  This option has no effect when setting the
+       value (but you can use 'git config bla {tilde}/' from the
+       command line to let your shell do the expansion).
+
 -z::
 --null::
        For all options that output values and/or keys, always
@@ -155,7 +164,7 @@ See also <<FILES>>.
        When the color setting for `name` is undefined, the command uses
        `color.ui` as fallback.
 
---get-color name default::
+--get-color name [default]::
 
        Find the color configured for `name` (e.g. `color.diff.new`) and
        output it as the ANSI color escape sequence to the standard
@@ -172,7 +181,7 @@ FILES
 -----
 
 If not set explicitly with '--file', there are three files where
-'git-config' will search for configuration options:
+'git config' will search for configuration options:
 
 $GIT_DIR/config::
        Repository specific configuration file. (The filename is
@@ -189,12 +198,12 @@ $(prefix)/etc/gitconfig::
 If no further options are given, all reading options will read all of these
 files that are available. If the global or the system-wide configuration
 file are not available they will be ignored. If the repository configuration
-file is not available or readable, 'git-config' will exit with a non-zero
+file is not available or readable, 'git config' will exit with a non-zero
 error code. However, in neither case will an error message be issued.
 
 All writing options will per default write to the repository specific
 configuration file. Note that this also affects options like '--replace-all'
-and '--unset'. *'git-config' will only ever change one file at a time*.
+and '--unset'. *'git config' will only ever change one file at a time*.
 
 You can override these rules either by command line options or by environment
 variables. The '--global' and the '--system' options will limit the file used
index 2da8588f4fd6edb842a9824181165b3f043ec87b..b2696efae95b20b693251a9d9785405218285084 100644 (file)
@@ -27,7 +27,7 @@ by default.
 
 Supports file additions, removals, and commits that affect binary files.
 
-If the commit is a merge commit, you must tell 'git-cvsexportcommit' what
+If the commit is a merge commit, you must tell 'git cvsexportcommit' what
 parent the changeset should be done against.
 
 OPTIONS
@@ -63,6 +63,10 @@ OPTIONS
 -u::
        Update affected files from CVS repository before attempting export.
 
+-k::
+       Reverse CVS keyword expansion (e.g. $Revision: 1.2.3.4$
+       becomes $Revision$) in working CVS checkout before applying patch.
+
 -w::
        Specify the location of the CVS checkout to use for the export. This
        option does not require GIT_DIR to be set before execution if the
index 614e769f4e5351d2820ebb9bf0dd1ae519c965ba..608cd63fc359d591e2aafbb8bd2fbae9b868b68b 100644 (file)
@@ -13,7 +13,7 @@ SYNOPSIS
              [-A <author-conv-file>] [-p <options-for-cvsps>] [-P <file>]
              [-C <git_repository>] [-z <fuzz>] [-i] [-k] [-u] [-s <subst>]
              [-a] [-m] [-M <regex>] [-S <regex>] [-L <commitlimit>]
-             [-r <remote>] [<CVS_module>]
+             [-r <remote>] [-R] [<CVS_module>]
 
 
 DESCRIPTION
@@ -28,9 +28,9 @@ At least version 2.1 is required.
 Please see the section <<issues,ISSUES>> for further reference.
 
 You should *never* do any work of your own on the branches that are
-created by 'git-cvsimport'.  By default initial import will create and populate a
+created by 'git cvsimport'.  By default initial import will create and populate a
 "master" branch from the CVS repository's main branch which you're free
-to work with; after that, you need to 'git-merge' incremental imports, or
+to work with; after that, you need to 'git merge' incremental imports, or
 any CVS branches, yourself.  It is advisable to specify a named remote via
 -r to separate and protect the incoming branches.
 
@@ -49,13 +49,13 @@ 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. If not given, 'git-cvsimport' will try to read it
+       are supported. If not given, 'git cvsimport' will try to read it
        from `CVS/Root`. If no such file exists, it checks for the
        `CVSROOT` environment variable.
 
 <CVS_module>::
        The CVS module you want to import. Relative to <CVSROOT>.
-       If not given, 'git-cvsimport' tries to read it from
+       If not given, 'git cvsimport' tries to read it from
        `CVS/Repository`.
 
 -C <target-dir>::
@@ -65,14 +65,14 @@ OPTIONS
 -r <remote>::
        The git remote to import this CVS repository into.
        Moves all CVS branches into remotes/<remote>/<branch>
-       akin to the way 'git-clone' uses 'origin' by default.
+       akin to the way 'git clone' uses 'origin' by default.
 
 -o <branch-for-HEAD>::
        When no remote is specified (via -r) the 'HEAD' branch
        from CVS is imported to the 'origin' branch within the git
        repository, as 'HEAD' already has a special meaning for git.
        When a remote is specified the 'HEAD' branch is named
-       remotes/<remote>/master mirroring 'git-clone' behaviour.
+       remotes/<remote>/master mirroring 'git clone' behaviour.
        Use this option if you want to import into a different
        branch.
 +
@@ -145,17 +145,33 @@ This option can be used several times to provide several detection regexes.
 
 ---------
 +
-'git-cvsimport' will make it appear as those authors had
+'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.
+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-cvsexportcommit'.
+'git cvsexportcommit'.
+
+-R::
+       Generate a `$GIT_DIR/cvs-revisions` file containing a mapping from CVS
+       revision numbers to newly-created Git commit IDs.  The generated file
+       will contain one line for each (filename, revision) pair imported;
+       each line will look like
++
+---------
+src/widget.c 1.1 1d862f173cdc7325b6fa6d2ae1cfd61fd1b512b7
+---------
++
+The revision data is appended to the file if it already exists, for use when
+doing incremental imports.
++
+This option may be useful if you have CVS revision numbers stored in commit
+messages, bug-tracking systems, email archives, and the like.
 
 -h::
        Print a short usage message and exit.
@@ -172,7 +188,7 @@ ISSUES
 ------
 Problems related to timestamps:
 
- * If timestamps of commits in the cvs repository are not stable enough
+ * If timestamps of commits in the CVS repository are not stable enough
    to be used for ordering commits changes may show up in the wrong
    order.
  * If any files were ever "cvs import"ed more than once (e.g., import of
@@ -185,7 +201,7 @@ Problems related to branches:
 
  * Branches on which no commits have been made are not imported.
  * All files from the branching point are added to a branch even if
-   never added in cvs.
+   never added in CVS.
  * This applies to files added to the source branch *after* a daughter
    branch was created: if previously no commit was made on the daughter
    branch they will erroneously be added to the daughter branch in git.
index 785779e22122156bdc8c58a94d36edb66a8ee266..f4472c61dbb297d61053ef2e8503ad7c56d06148 100644 (file)
@@ -22,7 +22,7 @@ cvspserver stream tcp nowait nobody /usr/bin/git-cvsserver git-cvsserver pserver
 Usage:
 
 [verse]
-'git cvsserver' [options] [pserver|server] [<directory> ...]
+'git-cvsserver' [options] [pserver|server] [<directory> ...]
 
 OPTIONS
 -------
@@ -72,9 +72,6 @@ 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.
 
 'git-cvsserver' maps GIT branches to CVS modules. This is very different
@@ -84,7 +81,7 @@ one or more directories.
 INSTALLATION
 ------------
 
-1. If you are going to offer anonymous CVS access via pserver, add a line in
+1. If you are going to offer CVS access via pserver, add a line in
    /etc/inetd.conf like
 +
 --
@@ -101,6 +98,38 @@ looks like
    cvspserver stream tcp nowait nobody /usr/bin/git-cvsserver git-cvsserver pserver
 
 ------
+
+Only anonymous access is provided by pserve by default. To commit you
+will have to create pserver accounts, simply add a gitcvs.authdb
+setting in the config file of the repositories you want the cvsserver
+to allow writes to, for example:
+
+------
+
+   [gitcvs]
+       authdb = /etc/cvsserver/passwd
+
+------
+The format of these files is username followed by the crypted password,
+for example:
+
+------
+   myuser:$1Oyx5r9mdGZ2
+   myuser:$1$BA)@$vbnMJMDym7tA32AamXrm./
+------
+You can use the 'htpasswd' facility that comes with Apache to make these
+files, but Apache's MD5 crypt method differs from the one used by most C
+library's crypt() function, so don't use the -m option.
+
+Alternatively you can produce the password with perl's crypt() operator:
+-----
+   perl -e 'my ($user, $pass) = @ARGV; printf "%s:%s\n", $user, crypt($user, $pass)' $USER password
+-----
+
+Then provide your password via the pserver method, for example:
+------
+   cvs -d:pserver:someuser:somepassword <at> server/path/repo.git co <HEAD_name>
+------
 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
 environment variable, you can rename 'git-cvsserver' to `cvs`.
@@ -182,10 +211,9 @@ Database Backend
 ----------------
 
 'git-cvsserver' uses one database per git head (i.e. CVS module) to
-store information about the repository for faster access. The
-database doesn't contain any persistent data and can be completely
-regenerated from the git repository at any time. The database
-needs to be updated (i.e. written to) after every commit.
+store information about the repository to maintain consistent
+CVS revision numbers. The database needs to be
+updated (i.e. written to) after every commit.
 
 If the commit is done directly by using `git` (as opposed to
 using 'git-cvsserver') the update will need to happen on the
@@ -204,6 +232,18 @@ write so it might not be enough to grant the users using
 'git-cvsserver' write access to the database file without granting
 them write access to the directory, too.
 
+The database can not be reliably regenerated in a
+consistent form after the branch it is tracking has changed.
+Example: For merged branches, 'git-cvsserver' only tracks
+one branch of development, and after a 'git merge' an
+incrementally updated database may track a different branch
+than a database regenerated from scratch, causing inconsistent
+CVS revision numbers. `git-cvsserver` has no way of knowing which
+branch it would have picked if it had been run incrementally
+pre-merge. So if you have to fully or partially (from old
+backup) regenerate the database, you should be suspicious
+of pre-existing CVS sandboxes.
+
 You can configure the database backend with the following
 configuration variables:
 
@@ -266,6 +306,21 @@ In `dbdriver` and `dbuser` you can use the following variables:
        If no name can be determined, the
        numeric uid is used.
 
+ENVIRONMENT
+-----------
+
+These variables obviate the need for command-line options in some
+circumstances, allowing easier restricted usage through git-shell.
+
+GIT_CVSSERVER_BASE_PATH takes the place of the argument to --base-path.
+
+GIT_CVSSERVER_ROOT specifies a single-directory whitelist. The
+repository must still be configured to allow access through
+git-cvsserver, as described above.
+
+When these environment variables are set, the corresponding
+command-line arguments may not be used.
+
 Eclipse CVS Client Notes
 ------------------------
 
@@ -283,7 +338,7 @@ To get a checkout with the Eclipse CVS client:
 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'". Note that password support is not good when using 'ext',
+"`git cvsserver`". Note 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
@@ -311,19 +366,16 @@ CRLF Line Ending Conversions
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 By default the server leaves the '-k' mode blank for all files,
-which causes the cvs client to treat them as a text files, subject
-to crlf conversion on some platforms.
+which causes the CVS client to treat them as a text files, subject
+to end-of-line conversion on some platforms.
 
-You can make the server use `crlf` attributes to set the '-k' modes
-for files by setting the `gitcvs.usecrlfattr` config variable.
-In this case, if `crlf` is explicitly unset ('-crlf'), then the
-server will set '-kb' mode for binary files. If `crlf` is set,
-then the '-k' mode will explicitly be left blank.  See
-also linkgit:gitattributes[5] for more information about the `crlf`
-attribute.
+You can make the server use the end-of-line conversion attributes to
+set the '-k' modes for files by setting the `gitcvs.usecrlfattr`
+config variable.  See linkgit:gitattributes[5] for more information
+about end-of-line conversion.
 
 Alternatively, if `gitcvs.usecrlfattr` config is not enabled
-or if the `crlf` attribute is unspecified for a filename, then
+or the attributes do not allow automatic detection for a filename, then
 the server uses the `gitcvs.allbinary` config for the default setting.
 If `gitcvs.allbinary` is set, then file not otherwise
 specified will default to '-kb' mode. Otherwise the '-k' mode
index a85121c689e394e86f7c97025b92ffa03fca0df9..01c9f8eb9eff634fe8ab99908c62207ebecd5e31 100644 (file)
@@ -28,36 +28,36 @@ that service if it is enabled.
 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
+pass some directory paths as 'git daemon' arguments, you can further restrict
 the offers to a whitelist comprising of those.
 
 By default, only `upload-pack` service is enabled, which serves
-'git-fetch-pack' and 'git-ls-remote' clients, which are invoked
-from 'git-fetch', 'git-pull', and 'git-clone'.
+'git fetch-pack' and 'git ls-remote' clients, which are invoked
+from 'git fetch', 'git pull', and 'git clone'.
 
 This is ideally suited for read-only updates, i.e., pulling from
 git repositories.
 
-An `upload-archive` also exists to serve 'git-archive'.
+An `upload-archive` also exists to serve 'git archive'.
 
 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
+       'git daemon' will refuse to start when this option is enabled and no
        whitelist is specified.
 
 --base-path=path::
        Remap all the path requests as relative to the given path.
-       This is sort of "GIT root" - if you run 'git-daemon' with
+       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
+       'git://example.com/hello.git', 'git daemon' will interpret the path
        as '/srv/git/hello.git'.
 
 --base-path-relaxed::
        If --base-path is enabled and repo lookup fails, with this option
-       'git-daemon' will attempt to lookup without prefixing the base path.
+       'git daemon' will attempt to lookup without prefixing the base path.
        This is useful for switching to --base-path usage, while still
        allowing the old paths.
 
@@ -143,7 +143,7 @@ OPTIONS
 +
 Giving these options is an error when used with `--inetd`; use
 the facility of inet daemon to achieve the same before spawning
-'git-daemon' if needed.
+'git daemon' if needed.
 
 --enable=service::
 --disable=service::
@@ -169,24 +169,24 @@ SERVICES
 
 These services can be globally enabled/disabled using the
 command line options of this command.  If a finer-grained
-control is desired (e.g. to allow 'git-archive' to be run
+control is desired (e.g. to allow 'git archive' to be run
 against only in a few selected repositories the daemon serves),
 the per-repository configuration file can be used to enable or
 disable them.
 
 upload-pack::
-       This serves 'git-fetch-pack' and 'git-ls-remote'
+       This serves 'git fetch-pack' and 'git ls-remote'
        clients.  It is enabled by default, but a repository can
        disable it by setting `daemon.uploadpack` configuration
        item to `false`.
 
 upload-archive::
-       This serves 'git-archive --remote'.  It is disabled by
+       This serves 'git archive --remote'.  It is disabled by
        default, but a repository can enable it by setting
        `daemon.uploadarch` configuration item to `true`.
 
 receive-pack::
-       This serves 'git-send-pack' clients, allowing anonymous
+       This serves 'git send-pack' clients, allowing anonymous
        push.  It is disabled by default, as there is _no_
        authentication in the protocol (in other words, anybody
        can push anything into the repository, including removal
@@ -204,8 +204,8 @@ $ grep 9418 /etc/services
 git            9418/tcp                # Git Version Control System
 ------------
 
-'git-daemon' as inetd server::
-       To set up 'git-daemon' as an inetd service that handles any
+'git daemon' as inetd server::
+       To set up 'git daemon' as an inetd service that handles any
        repository under the whitelisted set of directories, /pub/foo
        and /pub/bar, place an entry like the following into
        /etc/inetd all on one line:
@@ -217,8 +217,8 @@ git         9418/tcp                # Git Version Control System
 ------------------------------------------------
 
 
-'git-daemon' as inetd server for virtual hosts::
-       To set up 'git-daemon' as an inetd service that handles
+'git daemon' as inetd server for virtual hosts::
+       To set up 'git daemon' as an inetd service that handles
        repositories for different virtual hosts, `www.example.com`
        and `www.example.org`, place an entry like the following into
        `/etc/inetd` all on one line:
@@ -240,8 +240,8 @@ clients, a symlink from `/software` into the appropriate
 default repository could be made as well.
 
 
-'git-daemon' as regular daemon for virtual hosts::
-       To set up 'git-daemon' as a regular, non-inetd service that
+'git daemon' as regular daemon for virtual hosts::
+       To set up 'git daemon' as a regular, non-inetd service that
        handles repositories for multiple virtual hosts based on
        their IP addresses, start the daemon like this:
 +
@@ -258,7 +258,7 @@ Repositories can still be accessed by hostname though, assuming
 they correspond to these IP addresses.
 
 selectively enable/disable services per repository::
-       To enable 'git-archive --remote' and disable 'git-fetch' against
+       To enable 'git archive --remote' and disable 'git fetch' against
        a repository, have the following in the configuration file in the
        repository (that is the file 'config' next to 'HEAD', 'refs' and
        'objects').
@@ -272,7 +272,7 @@ selectively enable/disable services per repository::
 
 ENVIRONMENT
 -----------
-'git-daemon' will set REMOTE_ADDR to the IP address of the client
+'git daemon' will set REMOTE_ADDR to the IP address of the client
 that connected to it, if the IP address is available. REMOTE_ADDR will
 be available in the environment of hooks called when
 services are performed.
index b231dbb947791bb4fc5cde552e8c736b3558ca0a..7ef9d51577594ae6a27f71bae251d6ee2f52befa 100644 (file)
@@ -8,7 +8,9 @@ git-describe - Show the most recent tag that is reachable from a commit
 
 SYNOPSIS
 --------
+[verse]
 'git describe' [--all] [--tags] [--contains] [--abbrev=<n>] <committish>...
+'git describe' [--all] [--tags] [--contains] [--abbrev=<n>] --dirty[=<mark>]
 
 DESCRIPTION
 -----------
@@ -27,6 +29,11 @@ OPTIONS
 <committish>...::
        Committish object names to describe.
 
+--dirty[=<mark>]::
+       Describe the working tree.
+       It means describe HEAD and appends <mark> (`-dirty` by
+       default) if the working tree is dirty.
+
 --all::
        Instead of using only the annotated tags, use any ref
        found in `.git/refs/`.  This option enables matching
@@ -44,7 +51,9 @@ OPTIONS
 
 --abbrev=<n>::
        Instead of using the default 7 hexadecimal digits as the
-       abbreviated object name, use <n> digits.
+       abbreviated object name, use <n> digits, or as many digits
+       as needed to form a unique object name.  An <n> of 0
+       will suppress long format, only showing the closest tag.
 
 --candidates=<n>::
        Instead of considering only the 10 most recent tags as
@@ -68,8 +77,8 @@ OPTIONS
        This is useful when you want to see parts of the commit object name
        in "describe" output, even when the commit in question happens to be
        a tagged version.  Instead of just emitting the tag name, it will
-       describe such a commit as v1.2-0-deadbeef (0th commit since tag v1.2
-       that points at object deadbeef....).
+       describe such a commit as v1.2-0-gdeadbee (0th commit since tag v1.2
+       that points at object deadbee....).
 
 --match <pattern>::
        Only consider tags matching the given pattern (can be used to avoid
@@ -96,8 +105,11 @@ The number of additional commits is the number
 of commits which would be displayed by "git log v1.0.4..parent".
 The hash suffix is "-g" + 7-char abbreviation for the tip commit
 of parent (which was `2414721b194453f058079d897d13c4e377f92dc6`).
+The "g" prefix stands for "git" and is used to allow describing the version of
+a software depending on the SCM the software is managed with. This is useful
+in an environment where people may use different SCMs.
 
-Doing a 'git-describe' on a tag-name will just show the tag name:
+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
@@ -108,7 +120,7 @@ the output shows the reference path as well:
        [torvalds@g5 git]$ git describe --all --abbrev=4 v1.0.5^2
        tags/v1.0.0-21-g975b
 
-       [torvalds@g5 git]$ git describe --all HEAD^
+       [torvalds@g5 git]$ git describe --all --abbrev=4 HEAD^
        heads/lt/describe-7-g975b
 
 With --abbrev set to 0, the command can be used to find the
@@ -117,16 +129,23 @@ closest tagname without any suffix:
        [torvalds@g5 git]$ git describe --abbrev=0 v1.0.5^2
        tags/v1.0.0
 
+Note that the suffix you get if you type these commands today may be
+longer than what Linus saw above when he ran these commands, as your
+git repository may have new commits whose object names begin with
+975b that did not exist back then, and "-g975b" suffix alone may not
+be sufficient to disambiguate these commits.
+
+
 SEARCH STRATEGY
 ---------------
 
-For each committish supplied, 'git-describe' will first look for
+For each committish supplied, 'git describe' will first look for
 a tag which tags exactly that commit.  Annotated tags will always
 be preferred over lightweight tags, and tags with newer dates will
 always be preferred over tags with older dates.  If an exact match
 is found, its name will be output and searching will stop.
 
-If an exact match was not found, 'git-describe' will walk back
+If an exact match was not found, 'git describe' will walk back
 through the commit history to locate an ancestor commit which
 has been tagged.  The ancestor's tag will be output along with an
 abbreviation of the input committish's SHA1.
index c5261415643d359648900e17f522ba7b96fed44a..9cd8ccef37f131d7913926ef9b5b7b49fe6d3463 100644 (file)
@@ -15,7 +15,7 @@ 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 for 'git-diff-index' and 'git-diff-tree'.
+same as for 'git diff-index' and 'git diff-tree'.
 
 OPTIONS
 -------
@@ -43,8 +43,7 @@ omit diff output for unmerged entries and just show "Unmerged".
 -q::
        Remain silent even on nonexistent files
 
-Output format
--------------
+
 include::diff-format.txt[]
 
 
index 26920d4f63cd213ff17ab28d8dd0dbea94482147..162cb741b3052560351d2dd3f53c56ece5e51e95 100644 (file)
@@ -31,11 +31,9 @@ include::diff-options.txt[]
 -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
+       'git diff-index' say that all non-checked-out files are up
        to date.
 
-Output format
--------------
 include::diff-format.txt[]
 
 Operating Modes
@@ -50,7 +48,7 @@ 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 using 'git-write-tree')
+       contents (the ones I'd write using '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
@@ -62,7 +60,7 @@ object and compare it that way, and to do that, you just do
 Example: let's say I had renamed `commit.c` to `git-commit.c`, and I had
 done an `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:
+matches my working directory. But doing a 'git diff-index' does:
 
   torvalds@ppc970:~/git> git diff-index --cached HEAD
   -100644 blob    4161aecc6700a2eb579e842af0b7f22b98443f74        commit.c
@@ -71,10 +69,10 @@ matches my working directory. But doing a 'git-diff-index' does:
 You can see easily 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
+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
+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".
 
@@ -82,20 +80,20 @@ 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.
+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'
+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
+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
@@ -106,11 +104,11 @@ 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
+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.
+'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
index 23b7abd3c6b0e02eb325983eaad66598c42fc8be..a7e37b875fdf4bd25ce9adcb3296e2f2066f7130 100644 (file)
@@ -20,7 +20,7 @@ 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.
+Note that 'git diff-tree' can use the tree encapsulated in a commit object.
 
 OPTIONS
 -------
@@ -67,25 +67,25 @@ The following flags further affect the behavior when comparing
 commits (but not trees).
 
 -m::
-       By default, 'git-diff-tree --stdin' does not show
+       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,
+       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
+       This flag causes 'git diff-tree --stdin' to also show
        the commit message before the differences.
 
 include::pretty-options.txt[]
 
 --no-commit-id::
-       'git-diff-tree' outputs a line with the commit ID when
+       'git diff-tree' outputs a line with the commit ID when
        applicable.  This flag suppressed the commit ID output.
 
 -c::
@@ -159,8 +159,7 @@ HEAD commits it finds, which is even more interesting.
 
 in case you care).
 
-Output format
--------------
+
 include::diff-format.txt[]
 
 
index a2f192fb7519117f8e47f3ec4608e3301c200dc3..08fd4099addac3959e5aedbd1a0668367f07bb21 100644 (file)
@@ -68,11 +68,11 @@ for the last two forms that use ".." notations, can be any
 <tree-ish>.
 
 For a more complete list of ways to spell <commit>, see
-"SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
+"SPECIFYING REVISIONS" section in linkgit:gitrevisions[1].
 However, "diff" is about comparing two _endpoints_, not ranges,
 and the range notations ("<commit>..<commit>" and
 "<commit>\...<commit>") do not mean a range as defined in the
-"SPECIFYING RANGES" section in linkgit:git-rev-parse[1].
+"SPECIFYING RANGES" section in linkgit:gitrevisions[1].
 
 OPTIONS
 -------
@@ -84,8 +84,7 @@ include::diff-options.txt[]
        the diff to the named paths (you can give directory
        names and get diff for all files under them).
 
-Output format
--------------
+
 include::diff-format.txt[]
 
 EXAMPLES
@@ -158,6 +157,10 @@ $ git diff -R                          <2>
 rewrites (very expensive).
 <2> Output diff in reverse.
 
+SEE ALSO
+--------
+linkgit:git-difftool[1]::
+       Show changes using common diff tools
 
 Author
 ------
index 15b247bab4195286e55447388308473af4e2d001..8250bad2ce95245ca8e2a41c0740d0029b92ae4b 100644 (file)
@@ -7,13 +7,13 @@ git-difftool - Show changes using common diff tools
 
 SYNOPSIS
 --------
-'git difftool' [--tool=<tool>] [-y|--no-prompt|--prompt] [<'git diff' options>]
+'git difftool' [<options>] <commit>{0,2} [--] [<path>...]
 
 DESCRIPTION
 -----------
-'git-difftool' is a git command that allows you to compare and edit files
+'git difftool' is a git command that allows you to compare and edit files
 between revisions using common diff tools.  'git difftool' is a frontend
-to 'git-diff' and accepts the same options and arguments.
+to 'git diff' and accepts the same options and arguments.
 
 OPTIONS
 -------
@@ -31,25 +31,25 @@ OPTIONS
        Use the diff tool specified by <tool>.
        Valid merge tools are:
        kdiff3, kompare, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff,
-       ecmerge, diffuse and opendiff
+       ecmerge, diffuse, opendiff, p4merge and araxis.
 +
-If a diff tool is not specified, 'git-difftool'
+If a diff tool is not specified, 'git difftool'
 will use the configuration variable `diff.tool`.  If the
-configuration variable `diff.tool` is not set, 'git-difftool'
+configuration variable `diff.tool` is not set, 'git difftool'
 will pick a suitable default.
 +
 You can explicitly provide a full path to the tool by setting the
 configuration variable `difftool.<tool>.path`. For example, you
 can configure the absolute path to kdiff3 by setting
-`difftool.kdiff3.path`. Otherwise, 'git-difftool' assumes the
+`difftool.kdiff3.path`. Otherwise, 'git difftool' assumes the
 tool is available in PATH.
 +
 Instead of running one of the known diff tools,
-'git-difftool' can be customized to run an alternative program
+'git difftool' can be customized to run an alternative program
 by specifying the command line to invoke in a configuration
 variable `difftool.<tool>.cmd`.
 +
-When 'git-difftool' is invoked with this tool (either through the
+When 'git difftool' is invoked with this tool (either through the
 `-t` or `--tool` option or the `diff.tool` configuration variable)
 the configured command line will be invoked with the following
 variables available: `$LOCAL` is set to the name of the temporary
@@ -58,16 +58,31 @@ is set to the name of the temporary file containing the contents
 of the diff post-image.  `$BASE` is provided for compatibility
 with custom merge tool commands and has the same value as `$LOCAL`.
 
+-x <command>::
+--extcmd=<command>::
+       Specify a custom command for viewing diffs.
+       'git-difftool' ignores the configured defaults and runs
+       `$command $LOCAL $REMOTE` when this option is specified.
+
+-g::
+--gui::
+       When 'git-difftool' is invoked with the `-g` or `--gui` option
+       the default diff tool will be read from the configured
+       `diff.guitool` variable instead of `diff.tool`.
+
 See linkgit:git-diff[1] for the full list of supported options.
 
 CONFIG VARIABLES
 ----------------
-'git-difftool' falls back to 'git-mergetool' config variables when the
+'git difftool' falls back to 'git mergetool' config variables when the
 difftool equivalents have not been defined.
 
 diff.tool::
        The default diff tool to use.
 
+diff.guitool::
+       The default diff tool to use when `--gui` is specified.
+
 difftool.<tool>.path::
        Override the path for the given tool.  This is useful in case
        your tool is not in the PATH.
index 0c9eb567cb3e3af89a2e2613d1e074941fa30d13..fcad1132767021a03a88cf084d5eaf4ae8780792 100644 (file)
@@ -13,18 +13,18 @@ SYNOPSIS
 DESCRIPTION
 -----------
 This program dumps the given revisions in a form suitable to be piped
-into 'git-fast-import'.
+into 'git fast-import'.
 
 You can use it as a human-readable bundle replacement (see
 linkgit:git-bundle[1]), or as a kind of an interactive
-'git-filter-branch'.
+'git filter-branch'.
 
 
 OPTIONS
 -------
 --progress=<n>::
        Insert 'progress' statements every <n> objects, to be shown by
-       'git-fast-import' during import.
+       'git fast-import' during import.
 
 --signed-tags=(verbatim|warn|strip|abort)::
        Specify how to handle signed tags.  Since any transformation
@@ -36,6 +36,17 @@ when encountering a signed tag.  With 'strip', the tags will be made
 unsigned, with 'verbatim', they will be silently exported
 and with 'warn', they will be exported, but you will see a warning.
 
+--tag-of-filtered-object=(abort|drop|rewrite)::
+       Specify how to handle tags whose tagged object is filtered out.
+       Since revisions and files to export can be limited by path,
+       tagged objects may be filtered completely.
++
+When asking to 'abort' (which is the default), this program will die
+when encountering such a tag.  With 'drop' it will omit such tags from
+the output.  With 'rewrite', if the tagged object is a commit, it will
+rewrite the tag to tag an ancestor commit (via parent rewriting; see
+linkgit:git-rev-list[1])
+
 -M::
 -C::
        Perform move and/or copy detection, as described in the
@@ -71,6 +82,26 @@ marks the same across runs.
        allow that.  So fake a tagger to be able to fast-import the
        output.
 
+--no-data::
+       Skip output of blob objects and instead refer to blobs via
+       their original SHA-1 hash.  This is useful when rewriting the
+       directory structure or history of a repository without
+       touching the contents of individual files.  Note that the
+       resulting stream can only be used by a repository which
+       already contains the necessary objects.
+
+--full-tree::
+       This option will cause fast-export to issue a "deleteall"
+       directive for each commit followed by a full list of all files
+       in the commit (as opposed to just listing the files which are
+       different from the commit's first parent).
+
+[git-rev-list-args...]::
+       A list of arguments, acceptable to 'git rev-parse' and
+       'git rev-list', that specifies the specific objects and references
+       to export.  For example, `master{tilde}10..master` causes the
+       current master reference to be exported along with all objects
+       added since its 10th ancestor commit.
 
 EXAMPLES
 --------
@@ -100,7 +131,7 @@ referenced by that revision range contains the string
 Limitations
 -----------
 
-Since 'git-fast-import' cannot tag trees, you will not be
+Since 'git fast-import' cannot tag trees, you will not be
 able to export the linux-2.6.git repository completely, as it contains
 a tag referencing a tree instead of a commit.
 
index c2f483a8d2aed8dc017f3172e2d5fff4bed2c450..966ba4f21337e72f7be0a5b852e76a8b12192f7f 100644 (file)
@@ -15,7 +15,7 @@ DESCRIPTION
 This program is usually not what the end user wants to run directly.
 Most end users want to use one of the existing frontend programs,
 which parses a specific type of foreign source and feeds the contents
-stored there to 'git-fast-import'.
+stored there to 'git fast-import'.
 
 fast-import reads a mixed command/data stream from standard input and
 writes one or more packfiles directly into the current repository.
@@ -24,7 +24,7 @@ updated branch and tag refs, fully updating the current repository
 with the newly imported data.
 
 The fast-import backend itself can import into an empty repository (one that
-has already been initialized by 'git-init') or incrementally
+has already been initialized by 'git init') or incrementally
 update an existing populated repository.  Whether or not incremental
 imports are supported from a particular foreign source depends on
 the frontend program in use.
@@ -44,11 +44,14 @@ OPTIONS
        not contain the old commit).
 
 --max-pack-size=<n>::
-       Maximum size of each output packfile, expressed in MiB.
-       The default is 4096 (4 GiB) as that is the maximum allowed
-       packfile size (due to file format limitations). Some
-       importers may wish to lower this, such as to ensure the
-       resulting packfiles fit on CDs.
+       Maximum size of each output packfile.
+       The default is unlimited.
+
+--big-file-threshold=<n>::
+       Maximum size of a blob that fast-import will attempt to
+       create a delta for, expressed in bytes.  The default is 512m
+       (512 MiB).  Some importers may wish to lower this on systems
+       with constrained memory.
 
 --depth=<n>::
        Maximum delta depth, for blob and tree deltification.
@@ -75,6 +78,20 @@ OPTIONS
        set of marks.  If a mark is defined to different values,
        the last file wins.
 
+--relative-marks::
+       After specifying --relative-marks= the paths specified
+       with --import-marks= and --export-marks= are relative
+       to an internal directory in the current repository.
+       In git-fast-import this means that the paths are relative
+       to the .git/info/fast-import directory. However, other
+       importers may use a different location.
+
+--no-relative-marks::
+       Negates a previous --relative-marks. Allows for combining
+       relative and non-relative marks by interweaving
+       --(no-)-relative-marks= with the --(import|export)-marks=
+       options.
+
 --export-pack-edges=<file>::
        After creating a packfile, print a line of data to
        <file> listing the filename of the packfile and the last
@@ -82,7 +99,7 @@ OPTIONS
        This information may be useful after importing projects
        whose total object set exceeds the 4 GiB packfile limit,
        as these commits can be used as edge points during calls
-       to 'git-pack-objects'.
+       to 'git pack-objects'.
 
 --quiet::
        Disable all non-fatal output, making fast-import silent when it
@@ -124,9 +141,9 @@ an ideal situation, given that most conversion tools are throw-away
 
 Parallel Operation
 ------------------
-Like 'git-push' or 'git-fetch', imports handled by fast-import are safe to
+Like 'git push' or 'git fetch', imports handled by fast-import are safe to
 run alongside parallel `git repack -a -d` or `git gc` invocations,
-or any other Git operation (including 'git-prune', as loose objects
+or any other Git operation (including 'git prune', as loose objects
 are never used by fast-import).
 
 fast-import does not lock the branch or tag refs it is actively importing.
@@ -138,7 +155,7 @@ fast-forward update, fast-import will skip updating that ref and instead
 prints a warning message.  fast-import will always attempt to update all
 branch refs, and does not stop on the first failure.
 
-Branch updates can be forced with \--force, but its recommended that
+Branch updates can be forced with \--force, but it's recommended that
 this only be used on an otherwise quiet repository.  Using \--force
 is not necessary for an initial import into an empty repository.
 
@@ -220,7 +237,7 @@ variation in formatting will cause fast-import to reject the value.
 +
 An example value is ``Tue Feb 6 11:22:18 2007 -0500''.  The Git
 parser is accurate, but a little on the lenient side.  It is the
-same parser used by 'git-am' when applying patches
+same parser used by 'git am' when applying patches
 received from email.
 +
 Some malformed strings may be accepted as valid dates.  In some of
@@ -253,10 +270,10 @@ is always copied into the identity string at the time it is being
 created by fast-import.  There is no way to specify a different time or
 timezone.
 +
-This particular format is supplied as its short to implement and
+This particular format is supplied as it's short to implement and
 may be useful to a process that wants to create a new commit
 right now, without needing to use a working directory or
-'git-update-index'.
+'git update-index'.
 +
 If separate `author` and `committer` commands are used in a `commit`
 the timestamps may not match, as the system clock will be polled
@@ -303,6 +320,15 @@ and control the current import process.  More detailed discussion
        standard output.  This command is optional and is not needed
        to perform an import.
 
+`feature`::
+       Require that fast-import supports the specified feature, or
+       abort if it does not.
+
+`option`::
+       Specify any of the options listed under OPTIONS that do not
+       change stream semantic to suit the frontend's needs. This
+       command is optional and is not needed to perform an import.
+
 `commit`
 ~~~~~~~~
 Create or update a branch with a new commit, recording one logical
@@ -311,12 +337,12 @@ change to the project.
 ....
        'commit' SP <ref> LF
        mark?
-       ('author' SP <name> SP LT <email> GT SP <when> LF)?
-       'committer' SP <name> SP LT <email> GT SP <when> LF
+       ('author' (SP <name>)? SP LT <email> GT SP <when> LF)?
+       'committer' (SP <name>)? SP LT <email> GT SP <when> LF
        data
        ('from' SP <committish> LF)?
        ('merge' SP <committish> LF)?
-       (filemodify | filedelete | filecopy | filerename | filedeleteall)*
+       (filemodify | filedelete | filecopy | filerename | filedeleteall | notemodify)*
        LF?
 ....
 
@@ -339,14 +365,13 @@ commit message use a 0 length data.  Commit messages are free-form
 and are not interpreted by Git.  Currently they must be encoded in
 UTF-8, as fast-import does not permit other encodings to be specified.
 
-Zero or more `filemodify`, `filedelete`, `filecopy`, `filerename`
-and `filedeleteall` commands
+Zero or more `filemodify`, `filedelete`, `filecopy`, `filerename`,
+`filedeleteall` and `notemodify` commands
 may be included to update the contents of the branch prior to
 creating the commit.  These commands may be supplied in any order.
 However it is recommended that a `filedeleteall` command precede
-all `filemodify`, `filecopy` and `filerename` commands in the same
-commit, as `filedeleteall`
-wipes the branch clean (see below).
+all `filemodify`, `filecopy`, `filerename` and `notemodify` commands in
+the same commit, as `filedeleteall` wipes the branch clean (see below).
 
 The `LF` after the command is optional (it used to be required).
 
@@ -398,7 +423,7 @@ quoting or escaping syntax is supported within `<committish>`.
 Here `<committish>` is any of the following:
 
 * The name of an existing branch already in fast-import's internal branch
-  table.  If fast-import doesn't know the name, its treated as a SHA-1
+  table.  If fast-import doesn't know the name, it's treated as a SHA-1
   expression.
 
 * A mark reference, `:<idnum>`, where `<idnum>` is the mark number.
@@ -414,7 +439,7 @@ Marks must be declared (via `mark`) before they can be used.
 * A complete 40 byte or abbreviated commit SHA-1 in hex.
 
 * Any valid Git SHA-1 expression that resolves to a commit.  See
-  ``SPECIFYING REVISIONS'' in linkgit:git-rev-parse[1] for details.
+  ``SPECIFYING REVISIONS'' in linkgit:gitrevisions[1] for details.
 
 The special case of restarting an incremental import from the
 current branch value should be written as:
@@ -457,9 +482,11 @@ External data format::
        'M' SP <mode> SP <dataref> SP <path> LF
 ....
 +
-Here `<dataref>` can be either a mark reference (`:<idnum>`)
+Here usually `<dataref>` must be either a mark reference (`:<idnum>`)
 set by a prior `blob` command, or a full 40-byte SHA-1 of an
-existing Git blob object.
+existing Git blob object.  If `<mode>` is `040000`` then
+`<dataref>` must be the full 40-byte SHA-1 of an existing
+Git tree object or a mark reference set with `--import-marks`.
 
 Inline data format::
        The data content for the file has not been supplied yet.
@@ -484,6 +511,8 @@ in octal.  Git only supports the following modes:
 * `160000`: A gitlink, SHA-1 of the object refers to a commit in
   another repository. Git links can only be specified by SHA or through
   a commit mark. They are used to implement submodules.
+* `040000`: A subdirectory.  Subdirectories can only be specified by
+  SHA or through a tree mark set with `--import-marks`.
 
 In both formats `<path>` is the complete path of the file to be added
 (if not already existing) or modified (if already existing).
@@ -595,6 +624,40 @@ more memory per active branch (less than 1 MiB for even most large
 projects); so frontends that can easily obtain only the affected
 paths for a commit are encouraged to do so.
 
+`notemodify`
+^^^^^^^^^^^^
+Included in a `commit` command to add a new note (annotating a given
+commit) or change the content of an existing note.  This command has
+two different means of specifying the content of the note.
+
+External data format::
+       The data content for the note was already supplied by a prior
+       `blob` command.  The frontend just needs to connect it to the
+       commit that is to be annotated.
++
+....
+       'N' SP <dataref> SP <committish> LF
+....
++
+Here `<dataref>` can be either a mark reference (`:<idnum>`)
+set by a prior `blob` command, or a full 40-byte SHA-1 of an
+existing Git blob object.
+
+Inline data format::
+       The data content for the note has not been supplied yet.
+       The frontend wants to supply it as part of this modify
+       command.
++
+....
+       'N' SP 'inline' SP <committish> LF
+       data
+....
++
+See below for a detailed description of the `data` command.
+
+In both formats `<committish>` is any of the commit specification
+expressions also accepted by `from` (see above).
+
 `mark`
 ~~~~~~
 Arranges for fast-import to save a reference to the current object, allowing
@@ -624,7 +687,7 @@ lightweight (non-annotated) tags see the `reset` command below.
 ....
        'tag' SP <name> LF
        'from' SP <committish> LF
-       'tagger' SP <name> SP LT <email> GT SP <when> LF
+       'tagger' (SP <name>)? SP LT <email> GT SP <when> LF
        data
 ....
 
@@ -657,7 +720,7 @@ recommended, as the frontend does not (easily) have access to the
 complete set of bytes which normally goes into such a signature.
 If signing is required, create lightweight tags from within fast-import with
 `reset`, then create the annotated versions of those tags offline
-with the standard 'git-tag' process.
+with the standard 'git tag' process.
 
 `reset`
 ~~~~~~~
@@ -703,7 +766,7 @@ assigned mark.
 
 The mark command is optional here as some frontends have chosen
 to generate the Git SHA-1 for the blob on their own, and feed that
-directly to `commit`.  This is typically more work than its worth
+directly to `commit`.  This is typically more work than it's worth
 however, as marks are inexpensive to store and easy to use.
 
 `data`
@@ -813,6 +876,62 @@ Placing a `progress` command immediately after a `checkpoint` will
 inform the reader when the `checkpoint` has been completed and it
 can safely access the refs that fast-import updated.
 
+`feature`
+~~~~~~~~~
+Require that fast-import supports the specified feature, or abort if
+it does not.
+
+....
+       'feature' SP <feature> LF
+....
+
+The <feature> part of the command may be any string matching
+^[a-zA-Z][a-zA-Z-]*$ and should be understood by fast-import.
+
+Feature work identical as their option counterparts with the
+exception of the import-marks feature, see below.
+
+The following features are currently supported:
+
+* date-format
+* import-marks
+* export-marks
+* relative-marks
+* no-relative-marks
+* force
+
+The import-marks behaves differently from when it is specified as
+commandline option in that only one "feature import-marks" is allowed
+per stream. Also, any --import-marks= specified on the commandline
+will override those from the stream (if any).
+
+`option`
+~~~~~~~~
+Processes the specified option so that git fast-import behaves in a
+way that suits the frontend's needs.
+Note that options specified by the frontend are overridden by any
+options the user may specify to git fast-import itself.
+
+....
+    'option' SP <option> LF
+....
+
+The `<option>` part of the command may contain any of the options
+listed in the OPTIONS section that do not change import semantics,
+without the leading '--' and is treated in the same way.
+
+Option commands must be the first commands on the input (not counting
+feature commands), to give an option command after any non-option
+command is an error.
+
+The following commandline options change import semantics and may therefore
+not be passed as option:
+
+* date-format
+* import-marks
+* export-marks
+* force
+
 Crash Reports
 -------------
 If fast-import is supplied invalid input it will terminate with a
@@ -958,7 +1077,7 @@ is not `refs/heads/TAG_FIXUP`).
 
 When committing fixups, consider using `merge` to connect the
 commit(s) which are supplying file revisions to the fixup branch.
-Doing so will allow tools such as 'git-blame' to track
+Doing so will allow tools such as 'git blame' to track
 through the real commit history and properly annotate the source
 files.
 
@@ -987,7 +1106,7 @@ Repacking Historical Data
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 If you are repacking very old imported data (e.g. older than the
 last year), consider expending some extra CPU time and supplying
-\--window=50 (or higher) when you run 'git-repack'.
+\--window=50 (or higher) when you run 'git repack'.
 This will take longer, but will also produce a smaller packfile.
 You only need to expend the effort once, and everyone using your
 project will benefit from the smaller repository.
index 47448da22eeebf51fe5829717df2dc7129a9b17e..4a8487c1547abc156c8fad9e7cafe4d3c5a535f6 100644 (file)
@@ -12,13 +12,13 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Usually you would want to use 'git-fetch', which is a
+Usually you would want to use 'git fetch', which is a
 higher level wrapper of this command, instead.
 
 Invokes 'git-upload-pack' on a possibly 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
+is found out by scanning the local refs/ hierarchy and sent to
 'git-upload-pack' running on the other end.
 
 This command degenerates to download everything to complete the
@@ -33,19 +33,19 @@ OPTIONS
 
 -q::
 --quiet::
-       Pass '-q' flag to 'git-unpack-objects'; this makes the
+       Pass '-q' flag to 'git unpack-objects'; this makes the
        cloning process less verbose.
 
 -k::
 --keep::
-       Do not invoke 'git-unpack-objects' on received data, but
+       Do not invoke 'git unpack-objects' on received data, but
        create a single packfile out of it instead, and store it
        in the object database. If provided twice then the pack is
        locked against repacking.
 
 --thin::
-       Spend extra cycles to minimize the number of objects to be sent.
-       Use it on slower connection.
+       Fetch a "thin" pack, which records objects in deltified form based
+       on objects not included in the pack to reduce network traffic.
 
 --include-tag::
        If the remote side supports it, annotated tags objects will
index d3164c5c88db6b9e02a4186c398e19c425bc204b..400fe7f956961ba0ddf09d2dcc6e539adec7ff74 100644 (file)
@@ -8,17 +8,23 @@ git-fetch - Download objects and refs from another repository
 
 SYNOPSIS
 --------
-'git fetch' <options> <repository> <refspec>...
+'git fetch' [<options>] [<repository> [<refspec>...]]
+
+'git fetch' [<options>] <group>
+
+'git fetch' --multiple [<options>] [<repository> | <group>]...
+
+'git fetch' --all [<options>]
 
 
 DESCRIPTION
 -----------
-Fetches named heads or tags from another repository, along with
-the objects necessary to complete them.
+Fetches named heads or tags from one or more other repositories,
+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'.
+operation done by 'git merge'.
 
 When <refspec> stores the fetched result in tracking branches,
 the tags that point at these branches are automatically
@@ -28,6 +34,10 @@ pointed by remote tags that it does not yet have, then fetch
 those missing tags.  If the other end has tags that point at
 branches you are not interested in, you will not get them.
 
+'git fetch' can fetch from either a single named repository, or
+or from several repositories at once if <group> is given and
+there is a remotes.<group> entry in the configuration file.
+(See linkgit:git-config[1]).
 
 OPTIONS
 -------
@@ -37,6 +47,35 @@ include::pull-fetch-param.txt[]
 
 include::urls-remotes.txt[]
 
+
+EXAMPLES
+--------
+
+* Update the remote-tracking branches:
++
+------------------------------------------------
+$ git fetch origin
+------------------------------------------------
++
+The above command copies all branches from the remote refs/heads/
+namespace and stores them to the local refs/remotes/origin/ namespace,
+unless the branch.<name>.fetch option is used to specify a non-default
+refspec.
+
+* Using refspecs explicitly:
++
+------------------------------------------------
+$ git fetch origin +pu:pu maint:tmp
+------------------------------------------------
++
+This updates (or creates, as necessary) branches `pu` and `tmp` in
+the local repository by fetching from the branches (respectively)
+`pu` and `maint` from the remote repository.
++
+The `pu` branch will be updated even if it is does not fast-forward,
+because it is prefixed with a plus sign; `tmp` will not be.
+
+
 SEE ALSO
 --------
 linkgit:git-pull[1]
index ab527b5b316a81eddd8a0eefb501448a3097182f..7357c8879a941537fa6aee084b572e8aacced89d 100644 (file)
@@ -12,6 +12,7 @@ SYNOPSIS
        [--index-filter <command>] [--parent-filter <command>]
        [--msg-filter <command>] [--commit-filter <command>]
        [--tag-name-filter <command>] [--subdirectory-filter <directory>]
+       [--prune-empty]
        [--original <namespace>] [-d <directory>] [-f | --force]
        [--] [<rev-list options>...]
 
@@ -115,7 +116,7 @@ OPTIONS
 --commit-filter <command>::
        This is the filter for performing the commit.
        If this filter is specified, it will be called instead of the
-       'git-commit-tree' command, with arguments of the form
+       'git commit-tree' command, with arguments of the form
        "<TREE_ID> [-p <PARENT_COMMIT_ID>]..." and the log message on
        stdin.  The commit id is expected on stdout.
 +
@@ -126,10 +127,10 @@ have all of them as parents.
 You can use the 'map' convenience function in this filter, and other
 convenience functions, too.  For example, calling 'skip_commit "$@"'
 will leave out the current commit (but not its changes! If you want
-that, use 'git-rebase' instead).
+that, use 'git rebase' instead).
 +
-You can also use the 'git_commit_non_empty_tree "$@"' instead of
-'git commit-tree "$@"' if you don't wish to keep commits with a single parent
+You can also use the `git_commit_non_empty_tree "$@"` instead of
+`git commit-tree "$@"` if you don't wish to keep commits with a single parent
 and that makes no change to the tree.
 
 --tag-name-filter <command>::
@@ -158,7 +159,7 @@ to other tags will be rewritten to point to the underlying commit.
 --subdirectory-filter <directory>::
        Only look at the history which touches the given subdirectory.
        The result will contain that directory (and only that) as its
-       project root.
+       project root. Implies <<Remap_to_ancestor>>.
 
 --prune-empty::
        Some kind of filters will generate empty commits, that left the tree
@@ -167,7 +168,7 @@ to other tags will be rewritten to point to the underlying commit.
        and only one parent, it will hence keep merges points. Also, this
        option is not compatible with the use of '--commit-filter'. Though you
        just need to use the function 'git_commit_non_empty_tree "$@"' instead
-       of the 'git commit-tree "$@"' idiom in your commit filter to make that
+       of the `git commit-tree "$@"` idiom in your commit filter to make that
        happen.
 
 --original <namespace>::
@@ -184,15 +185,26 @@ to other tags will be rewritten to point to the underlying commit.
 
 -f::
 --force::
-       'git-filter-branch' refuses to start with an existing temporary
+       'git filter-branch' refuses to start with an existing temporary
        directory or when there are already refs starting with
        'refs/original/', unless forced.
 
 <rev-list options>...::
-       Arguments for 'git-rev-list'.  All positive refs included by
+       Arguments for 'git rev-list'.  All positive refs included by
        these options are rewritten.  You may also specify options
        such as '--all', but you must use '--' to separate them from
-       the 'git-filter-branch' options.
+       the 'git filter-branch' options. Implies <<Remap_to_ancestor>>.
+
+
+[[Remap_to_ancestor]]
+Remap to ancestor
+~~~~~~~~~~~~~~~~~
+
+By using linkgit:rev-list[1] arguments, e.g., path limiters, you can limit the
+set of revisions which get rewritten. However, positive refs on the command
+line are distinguished: we don't let them be excluded by such limiters. For
+this purpose, they are instead rewritten to point at the nearest ancestor that
+was not excluded.
 
 
 Examples
@@ -209,7 +221,7 @@ However, if the file is absent from the tree of some commit,
 a simple `rm filename` will fail for that tree and commit.
 Thus you may instead want to use `rm -f filename` as the script.
 
-Using `\--index-filter` with 'git-rm' yields a significantly faster
+Using `\--index-filter` with 'git rm' yields a significantly faster
 version.  Like with using `rm filename`, `git rm --cached filename`
 will fail if the file is absent from the tree of a commit.  If you
 want to "completely forget" a file, it does not matter when it entered
@@ -291,7 +303,7 @@ and all children of the merge will become merge commits with P1,P2
 as their parents instead of the merge commit.
 
 You can rewrite the commit log messages using `--msg-filter`.  For
-example, 'git-svn-id' strings in a repository created by 'git-svn' can
+example, 'git svn-id' strings in a repository created by 'git svn' can
 be removed this way:
 
 -------------------------------------------------------
@@ -302,13 +314,23 @@ git filter-branch --msg-filter '
 
 To restrict rewriting to only part of the history, specify a revision
 range in addition to the new branch name.  The new branch name will
-point to the top-most revision that a 'git-rev-list' of this range
+point to the top-most revision that a 'git rev-list' of this range
 will print.
 
+If you need to add 'Acked-by' lines to, say, the last 10 commits (none
+of which is a merge), use this command:
+
+--------------------------------------------------------
+git filter-branch --msg-filter '
+       cat &&
+       echo "Acked-by: Bugs Bunny <bunny@bugzilla.org>"
+' HEAD~10..HEAD
+--------------------------------------------------------
+
 *NOTE* the changes introduced by the commits, and which are not reverted
 by subsequent commits, will still be in the rewritten branch. If you want
 to throw out _changes_ together with the commits, you should use the
-interactive mode of 'git-rebase'.
+interactive mode of 'git rebase'.
 
 
 Consider this history:
@@ -336,7 +358,7 @@ To move the whole tree into a subdirectory, or remove it from there:
 
 ---------------------------------------------------------------
 git filter-branch --index-filter \
-       'git ls-files -s | sed "s-\t-&newsubdir/-" |
+       'git ls-files -s | sed "s-\t\"*-&newsubdir/-" |
                GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
                        git update-index --index-info &&
         mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE' HEAD
index 1c24796d66d5aeaeeccfd152c69cddba1953fd6c..302f56b88941cfaa002a8b75c078335bf8d86276 100644 (file)
@@ -9,17 +9,17 @@ git-fmt-merge-msg - Produce a merge commit message
 SYNOPSIS
 --------
 [verse]
-'git fmt-merge-msg' [--log | --no-log] <$GIT_DIR/FETCH_HEAD
-'git fmt-merge-msg' [--log | --no-log] -F <file>
+'git fmt-merge-msg' [-m <message>] [--log | --no-log] <$GIT_DIR/FETCH_HEAD
+'git fmt-merge-msg' [-m <message>] [--log | --no-log] -F <file>
 
 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'.
+passed as the '<merge-message>' argument of 'git merge'.
 
-This script is intended mostly for internal use by scripts
-automatically invoking 'git-merge'.
+This command is intended mostly for internal use by scripts
+automatically invoking 'git merge'.
 
 OPTIONS
 -------
@@ -38,6 +38,11 @@ OPTIONS
        Synonyms to --log and --no-log; these are deprecated and will be
        removed in the future.
 
+-m <message>::
+--message <message>::
+       Use <message> instead of the branch names for the first line
+       of the log message.  For use with `--log`.
+
 -F <file>::
 --file <file>::
        Take the list of merged objects from <file> instead of
index 8dc873fd4465879ec3224732c690f1173a6e2dc2..d66fd9d2314774a0b31eadb23f8c00c199276d84 100644 (file)
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git for-each-ref' [--count=<count>] [--shell|--perl|--python|--tcl]
-                  [--sort=<key>]\* [--format=<format>] [<pattern>...]
+                  [--sort=<key>]* [--format=<format>] [<pattern>...]
 
 DESCRIPTION
 -----------
@@ -82,10 +82,11 @@ objecttype::
        The type of the object (`blob`, `tree`, `commit`, `tag`).
 
 objectsize::
-       The size of the object (the same as 'git-cat-file -s' reports).
+       The size of the object (the same as 'git cat-file -s' reports).
 
 objectname::
        The object name (aka SHA-1).
+       For a non-ambiguous abbreviation of the object name append `:short`.
 
 upstream::
        The name of a local ref which can be considered ``upstream''
index 6f1fc80119600c419d2e3b98bac0c44f9cb09026..4b3f5ba5358da469feff4e861e69717f16e81d21 100644 (file)
@@ -10,15 +10,16 @@ SYNOPSIS
 --------
 [verse]
 'git format-patch' [-k] [(-o|--output-directory) <dir> | --stdout]
-                  [--thread[=<style>]]
+                  [--no-thread | --thread[=<style>]]
                   [(--attach|--inline)[=<boundary>] | --no-attach]
                   [-s | --signoff]
+                  [--signature=<signature> | --no-signature]
                   [-n | --numbered | -N | --no-numbered]
                   [--start-number <n>] [--numbered-files]
                   [--in-reply-to=Message-Id] [--suffix=.<sfx>]
                   [--ignore-if-in-upstream]
                   [--subject-prefix=Subject-Prefix]
-                  [--cc=<email>]
+                  [--to=<email>] [--cc=<email>]
                   [--cover-letter]
                   [<common diff options>]
                   [ <since> | <revision range> ]
@@ -29,7 +30,7 @@ DESCRIPTION
 Prepare each commit with its patch in
 one file per commit, formatted to resemble UNIX mailbox format.
 The output of this command is convenient for e-mail submission or
-for use with 'git-am'.
+for use with 'git am'.
 
 There are two ways to specify which commits to operate on.
 
@@ -38,33 +39,33 @@ There are two ways to specify which commits to operate on.
    that leads to the <since> to be output.
 
 2. Generic <revision range> expression (see "SPECIFYING
-   REVISIONS" section in linkgit:git-rev-parse[1]) means the
+   REVISIONS" section in linkgit:gitrevisions[1]) means the
    commits in the specified range.
 
 The first rule takes precedence in the case of a single <commit>.  To
 apply the second rule, i.e., format everything since the beginning of
-history up until <commit>, use the '\--root' option: "git format-patch
-\--root <commit>".  If you want to format only <commit> itself, you
-can do this with "git format-patch -1 <commit>".
+history up until <commit>, use the '\--root' option: `git format-patch
+\--root <commit>`.  If you want to format only <commit> itself, you
+can do this with `git format-patch -1 <commit>`.
 
 By default, each output file is numbered sequentially from 1, and uses the
 first line of the commit message (massaged for pathname safety) as
-the filename. With the --numbered-files option, the output file names
+the filename. With the `--numbered-files` option, the output file names
 will only be numbers, without the first line of the commit appended.
 The names of the output files are printed to standard
-output, unless the --stdout option is specified.
+output, unless the `--stdout` option is specified.
 
-If -o is specified, output files are created in <dir>.  Otherwise
+If `-o` is specified, output files are created in <dir>.  Otherwise
 they are created in the current working directory.
 
 By default, the subject of a single patch is "[PATCH] First Line" and
 the subject when multiple patches are output is "[PATCH n/m] First
-Line". To force 1/1 to be added for a single patch, use -n.  To omit
-patch numbers from the subject, use -N
+Line". To force 1/1 to be added for a single patch, use `-n`.  To omit
+patch numbers from the subject, use `-N`.
 
-If given --thread, 'git-format-patch' will generate In-Reply-To and
-References headers to make the second and subsequent patch mails appear
-as replies to the first mail; this also generates a Message-Id header to
+If given `--thread`, `git-format-patch` will generate `In-Reply-To` and
+`References` headers to make the second and subsequent patch mails appear
+as replies to the first mail; this also generates a `Message-Id` header to
 reference.
 
 OPTIONS
@@ -112,7 +113,7 @@ include::diff-options.txt[]
 --attach[=<boundary>]::
        Create multipart/mixed attachment, the first part of
        which is the commit message and the patch itself in the
-       second part, with "Content-Disposition: attachment".
+       second part, with `Content-Disposition: attachment`.
 
 --no-attach::
        Disable the creation of an attachment, overriding the
@@ -121,23 +122,31 @@ include::diff-options.txt[]
 --inline[=<boundary>]::
        Create multipart/mixed attachment, the first part of
        which is the commit message and the patch itself in the
-       second part, with "Content-Disposition: inline".
+       second part, with `Content-Disposition: inline`.
 
 --thread[=<style>]::
-       Add In-Reply-To and References headers to make the second and
-       subsequent mails appear as replies to the first.  Also generates
-       the Message-Id header to reference.
+--no-thread::
+       Controls addition of `In-Reply-To` and `References` headers to
+       make the second and subsequent mails appear as replies to the
+       first.  Also controls generation of the `Message-Id` header to
+       reference.
 +
 The optional <style> argument can be either `shallow` or `deep`.
 'shallow' threading makes every mail a reply to the head of the
 series, where the head is chosen from the cover letter, the
 `\--in-reply-to`, and the first patch mail, in this order.  'deep'
-threading makes every mail a reply to the previous one.  If not
-specified, defaults to the 'format.thread' configuration, or `shallow`
-if that is not set.
+threading makes every mail a reply to the previous one.
++
+The default is `--no-thread`, unless the 'format.thread' configuration
+is set.  If `--thread` is specified without a style, it defaults to the
+style specified by 'format.thread' if any, or else `shallow`.
++
+Beware that the default for 'git send-email' is to thread emails
+itself.  If you want `git format-patch` to take care of threading, you
+will want to ensure that threading is disabled for `git send-email`.
 
 --in-reply-to=Message-Id::
-       Make the first mail (or all the mails with --no-thread) appear as a
+       Make the first mail (or all the mails with `--no-thread`) appear as a
        reply to the given Message-Id, which avoids breaking threads to
        provide a new patch series.
 
@@ -152,22 +161,32 @@ if that is not set.
        Instead of the standard '[PATCH]' prefix in the subject
        line, instead use '[<Subject-Prefix>]'. This
        allows for useful naming of a patch series, and can be
-       combined with the --numbered option.
+       combined with the `--numbered` option.
+
+--to=<email>::
+       Add a `To:` header to the email headers. This is in addition
+       to any configured headers, and may be used multiple times.
 
 --cc=<email>::
-       Add a "Cc:" header to the email headers. This is in addition
+       Add a `Cc:` header to the email headers. This is in addition
        to any configured headers, and may be used multiple times.
 
 --add-header=<header>::
        Add an arbitrary header to the email headers.  This is in addition
        to any configured headers, and may be used multiple times.
-       For example, --add-header="Organization: git-foo"
+       For example, `--add-header="Organization: git-foo"`
 
 --cover-letter::
        In addition to the patches, generate a cover letter file
        containing the shortlog and the overall diffstat.  You can
        fill in a description in the file before sending it out.
 
+--[no]-signature=<signature>::
+       Add a signature to each message produced. Per RFC 3676 the signature
+       is separated from the body by a line with '-- ' on it. If the
+       signature option is omitted the signature defaults to the git version
+       number.
+
 --suffix=.<sfx>::
        Instead of using `.patch` as the suffix for generated
        filenames, use specified suffix.  A common alternative is
@@ -194,8 +213,8 @@ CONFIGURATION
 -------------
 You can specify extra mail header lines to be added to each message,
 defaults for the subject prefix and file suffix, number patches when
-outputting more than one patch, add "Cc:" headers, configure attachments,
-and sign off patches with configuration variables.
+outputting more than one patch, add "To" or "Cc:" headers, configure
+attachments, and sign off patches with configuration variables.
 
 ------------
 [format]
@@ -203,6 +222,7 @@ and sign off patches with configuration variables.
        subjectprefix = CHANGE
        suffix = .txt
        numbered = auto
+       to = <email>
        cc = <email>
        attach [ = mime-boundary-string ]
        signoff = true
@@ -213,7 +233,7 @@ EXAMPLES
 --------
 
 * Extract commits between revisions R1 and R2, and apply them on top of
-the current branch using 'git-am' to cherry-pick them:
+the current branch using 'git am' to cherry-pick them:
 +
 ------------
 $ git format-patch -k --stdout R1..R2 | git am -3 -k
index 287c4fc5e07ea753c2a3d93bf6480f41aac8c9af..3ad48a633663ec22af16b4737c42fd7e7664e4d2 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]
-        [--full] [--strict] [--verbose] [--lost-found] [<object>*]
+        [--[no-]full] [--strict] [--verbose] [--lost-found] [<object>*]
 
 DESCRIPTION
 -----------
@@ -21,7 +21,7 @@ OPTIONS
 <object>::
        An object to treat as the head of an unreachability trace.
 +
-If no objects are given, 'git-fsck' defaults to using the
+If no objects are given, 'git fsck' defaults to using the
 index file, all SHA1 references in .git/refs/*, and all reflogs (unless
 --no-reflogs is given) as heads.
 
@@ -52,7 +52,8 @@ index file, all SHA1 references in .git/refs/*, and all reflogs (unless
        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.
+       object pools.  This is now default; you can turn it off
+       with --no-full.
 
 --strict::
        Enable more strict checking, namely to catch a file mode
@@ -84,7 +85,7 @@ So for example
 
 will do quite a _lot_ of verification on the tree. There are a few
 extra validity tests to be added (make sure that tree objects are
-sorted properly etc), but on the whole if 'git-fsck' is happy, you
+sorted properly etc), but on the whole if 'git fsck' is happy, you
 do have a valid tree.
 
 Any corrupt objects you will have to find in backups or other archives
index b292e9843aa9da86cd44bd07d3ce35053be32177..315f07ef1c6997c98c4e446af507c3e1e4566218 100644 (file)
@@ -15,13 +15,13 @@ DESCRIPTION
 Runs a number of housekeeping tasks within the current repository,
 such as compressing file revisions (to reduce disk space and increase
 performance) and removing unreachable objects which may have been
-created from prior invocations of 'git-add'.
+created from prior invocations of 'git add'.
 
 Users are encouraged to run this task on a regular basis within
 each repository to maintain good disk space utilization and good
 operating performance.
 
-Some git commands may automatically run 'git-gc'; see the `--auto` flag
+Some git commands may automatically run 'git gc'; see the `--auto` flag
 below for details. If you know what you're doing and all you want is to
 disable this behavior permanently without further considerations, just do:
 
@@ -33,15 +33,15 @@ OPTIONS
 -------
 
 --aggressive::
-       Usually 'git-gc' runs very quickly while providing good disk
+       Usually 'git gc' runs very quickly while providing good disk
        space utilization and performance.  This option will cause
-       'git-gc' to more aggressively optimize the repository at the expense
+       'git gc' to more aggressively optimize the repository at the expense
        of taking much more time.  The effects of this optimization are
        persistent, so this option only needs to be used occasionally; every
        few hundred changesets or so.
 
 --auto::
-       With this option, 'git-gc' checks whether any housekeeping is
+       With this option, 'git gc' checks whether any housekeeping is
        required; if not, it exits without performing any work.
        Some git commands run `git gc --auto` after performing
        operations that could create many loose objects.
@@ -50,18 +50,18 @@ Housekeeping is required if there are too many loose objects or
 too many packs in the repository. If the number of loose objects
 exceeds the value of the `gc.auto` configuration variable, then
 all loose objects are combined into a single pack using
-'git-repack -d -l'.  Setting the value of `gc.auto` to 0
+`git repack -d -l`.  Setting the value of `gc.auto` to 0
 disables automatic packing of loose objects.
 +
 If the number of packs exceeds the value of `gc.autopacklimit`,
 then existing packs (except those marked with a `.keep` file)
 are consolidated into a single pack by using the `-A` option of
-'git-repack'. Setting `gc.autopacklimit` to 0 disables
+'git repack'. Setting `gc.autopacklimit` to 0 disables
 automatic consolidation of packs.
 
 --prune=<date>::
        Prune loose objects older than date (default is 2 weeks ago,
-       overrideable by the config variable `gc.pruneExpire`).  This
+       overridable by the config variable `gc.pruneExpire`).  This
        option is on by default.
 
 --no-prune::
@@ -88,6 +88,16 @@ commits prior to the amend or rebase occurring.  Since these changes
 are not part of the current project most users will want to expire
 them sooner.  This option defaults to '30 days'.
 
+The above two configuration variables can be given to a pattern.  For
+example, this sets non-default expiry values only to remote tracking
+branches:
+
+------------
+[gc "refs/remotes/*"]
+       reflogExpire = never
+       reflogexpireUnreachable = 3 days
+------------
+
 The optional configuration variable 'gc.rerereresolved' indicates
 how long records of conflicted merge you resolved earlier are
 kept.  This defaults to 60 days.
@@ -97,7 +107,7 @@ how long records of conflicted merge you have not resolved are
 kept.  This defaults to 15 days.
 
 The optional configuration variable 'gc.packrefs' determines if
-'git-gc' runs 'git-pack-refs'. This can be set to "nobare" to enable
+'git gc' runs 'git pack-refs'. This can be set to "nobare" to enable
 it within all non-bare repos or it can be set to a boolean value.
 This defaults to true.
 
@@ -106,7 +116,7 @@ much time is spent optimizing the delta compression of the objects in
 the repository when the --aggressive option is specified.  The larger
 the value, the more time is spent optimizing the delta compression.  See
 the documentation for the --window' option in linkgit:git-repack[1] for
-more details.  This defaults to 10.
+more details.  This defaults to 250.
 
 The optional configuration variable 'gc.pruneExpire' controls how old
 the unreferenced loose objects have to be before they are pruned.  The
@@ -116,17 +126,24 @@ default is "2 weeks ago".
 Notes
 -----
 
-'git-gc' tries very hard to be safe about the garbage it collects. In
+'git gc' tries very hard to be safe about the garbage it collects. In
 particular, it will keep not only objects referenced by your current set
 of branches and tags, but also objects referenced by the index, remote
-tracking branches, refs saved by 'git-filter-branch' in
-refs/original/, or reflogs (which may references commits in branches
+tracking branches, refs saved by 'git filter-branch' in
+refs/original/, or reflogs (which may reference commits in branches
 that were later amended or rewound).
 
 If you are expecting some objects to be collected and they aren't, check
 all of those locations and decide whether it makes sense in your case to
 remove those references.
 
+HOOKS
+-----
+
+The 'git gc --auto' command will run the 'pre-auto-gc' hook.  See
+linkgit:githooks[5] for more information.
+
+
 SEE ALSO
 --------
 linkgit:git-prune[1]
index 84f23ee525336fc2bdd289991b97eafecddc14b2..790af9573b02d643aa6dae117f8b50f7cb565b9b 100644 (file)
@@ -14,12 +14,12 @@ SYNOPSIS
 DESCRIPTION
 -----------
 Acts as a filter, extracting the commit ID stored in archives created by
-'git-archive'.  It reads only the first 1024 bytes of input, thus its
+'git archive'.  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
+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-archive' or if the first parameter of 'git-archive' had been
+using 'git archive' or if the first parameter of 'git archive' had been
 a tree ID instead of a commit ID or tag.
 
 
index fccb82deb48ec75d25f948e7caa69a44e09f5832..dab0a78fa890d0c0cd193d89b7f4ac9ed19fb001 100644 (file)
@@ -9,31 +9,36 @@ git-grep - Print lines matching a pattern
 SYNOPSIS
 --------
 [verse]
-'git grep' [--cached]
-          [-a | --text] [-I] [-i | --ignore-case] [-w | --word-regexp]
+'git grep' [-a | --text] [-I] [-i | --ignore-case] [-w | --word-regexp]
           [-v | --invert-match] [-h|-H] [--full-name]
           [-E | --extended-regexp] [-G | --basic-regexp]
           [-F | --fixed-strings] [-n]
           [-l | --files-with-matches] [-L | --files-without-match]
+          [(-O | --open-files-in-pager) [<pager>]]
           [-z | --null]
-          [-c | --count] [--all-match]
-          [--color | --no-color]
+          [-c | --count] [--all-match] [-q | --quiet]
+          [--max-depth <depth>]
+          [--color[=<when>] | --no-color]
           [-A <post-context>] [-B <pre-context>] [-C <context>]
           [-f <file>] [-e] <pattern>
-          [--and|--or|--not|(|)|-e <pattern>...] [<tree>...]
-          [--] [<path>...]
+          [--and|--or|--not|(|)|-e <pattern>...]
+          [--cached | --no-index | <tree>...]
+          [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
-Look for specified patterns in the working tree files, blobs
-registered in the index file, or given tree objects.
+Look for specified patterns in the tracked files in the work tree, blobs
+registered in the index file, or blobs in given tree objects.
 
 
 OPTIONS
 -------
 --cached::
-       Instead of searching in the working tree files, check
-       the blobs registered in the index file.
+       Instead of searching tracked files in the working tree, search
+       blobs registered in the index file.
+
+--no-index::
+       Search files in the current directory, not just those tracked by git.
 
 -a::
 --text::
@@ -47,6 +52,10 @@ OPTIONS
 -I::
        Don't match the pattern in binary files.
 
+--max-depth <depth>::
+       For each <pathspec> given on command line, descend at most <depth>
+       levels of directories. A negative value means no limit.
+
 -w::
 --word-regexp::
        Match the pattern only at word boundary (either begin at the
@@ -93,8 +102,15 @@ OPTIONS
 --files-without-match::
        Instead of showing every matched line, show only the
        names of files that contain (or do not contain) matches.
-       For better compatibility with 'git-diff', --name-only is a
-       synonym for --files-with-matches.
+       For better compatibility with 'git diff', `--name-only` is a
+       synonym for `--files-with-matches`.
+
+-O [<pager>]::
+--open-files-in-pager [<pager>]::
+       Open the matching files in the pager (not the output of 'grep').
+       If the pager happens to be "less" or "vi", and the user
+       specified only one pattern, the first file is positioned at
+       the first match automatically.
 
 -z::
 --null::
@@ -106,12 +122,14 @@ OPTIONS
        Instead of showing every matched line, show the number of
        lines that match.
 
---color::
+--color[=<when>]::
        Show colored matches.
+       The value must be always (the default), never, or auto.
 
 --no-color::
        Turn off match highlighting, even when the configuration file
        gives the default to color output.
+       Same as `--color=never`.
 
 -[ABC] <context>::
        Show `context` trailing (`A` -- after), or leading (`B`
@@ -120,14 +138,22 @@ OPTIONS
        matches.
 
 -<num>::
-       A shortcut for specifying -C<num>.
+       A shortcut for specifying `-C<num>`.
+
+-p::
+--show-function::
+       Show the preceding line that contains the function name of
+       the match, unless the matching line is a function name itself.
+       The name is determined in the same way as 'git diff' works out
+       patch hunk headers (see 'Defining a custom hunk-header' in
+       linkgit:gitattributes[5]).
 
 -f <file>::
        Read patterns from <file>, one per line.
 
 -e::
        The next parameter is the pattern. This option has to be
-       used for patterns starting with - and should be used in
+       used for patterns starting with `-` and should be used in
        scripts passing user input to grep.  Multiple patterns are
        combined by 'or'.
 
@@ -145,18 +171,31 @@ OPTIONS
        this flag is specified to limit the match to files that
        have lines to match all of them.
 
-`<tree>...`::
-       Search blobs in the trees for specified patterns.
+-q::
+--quiet::
+       Do not output matched lines; instead, exit with status 0 when
+       there is a match and with non-zero status when there isn't.
+
+<tree>...::
+       Instead of searching tracked files in the working tree, search
+       blobs in the given trees.
 
 \--::
        Signals the end of options; the rest of the parameters
-       are <path> limiters.
+       are <pathspec> limiters.
 
+<pathspec>...::
+       If given, limit the search to paths matching at least one pattern.
+       Both leading paths match and glob(7) patterns are supported.
 
-Example
--------
+Examples
+--------
+
+git grep {apostrophe}time_t{apostrophe} \-- {apostrophe}*.[ch]{apostrophe}::
+       Looks for `time_t` in all tracked .c and .h files in the working
+       directory and its subdirectories.
 
-git grep -e \'#define\' --and \( -e MAX_PATH -e PATH_MAX \)::
+git grep -e {apostrophe}#define{apostrophe} --and \( -e MAX_PATH -e PATH_MAX \)::
        Looks for a line that has `#define` and either `MAX_PATH` or
        `PATH_MAX`.
 
index d0bc98b85289b42727afc903a50e9e83d37def35..2563710b56bf441125bda6766365eb2a2b5eb46a 100644 (file)
@@ -11,19 +11,19 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-A Tcl/Tk based graphical user interface to Git.  'git-gui' focuses
+A Tcl/Tk based graphical user interface to Git.  'git gui' focuses
 on allowing users to make changes to their repository by making
 new commits, amending existing ones, creating branches, performing
 local merges, and fetching/pushing to remote repositories.
 
-Unlike 'gitk', 'git-gui' focuses on commit generation
+Unlike 'gitk', 'git gui' focuses on commit generation
 and single file annotation and does not show project history.
 It does however supply menu actions to start a 'gitk' session from
-within 'git-gui'.
+within 'git gui'.
 
-'git-gui' is known to work on all popular UNIX systems, Mac OS X,
+'git gui' is known to work on all popular UNIX systems, Mac OS X,
 and Windows (under both Cygwin and MSYS).  To the extent possible
-OS specific user interface guidelines are followed, making 'git-gui'
+OS specific user interface guidelines are followed, making 'git gui'
 a fairly native interface for users.
 
 COMMANDS
@@ -38,13 +38,13 @@ browser::
        browser are opened in the blame viewer.
 
 citool::
-       Start 'git-gui' and arrange to make exactly one commit before
+       Start 'git gui' and arrange to make exactly one commit before
        exiting and returning to the shell.  The interface is limited
        to only commit actions, slightly reducing the application's
        startup time and simplifying the menubar.
 
 version::
-       Display the currently running version of 'git-gui'.
+       Display the currently running version of 'git gui'.
 
 
 Examples
@@ -103,15 +103,15 @@ SEE ALSO
 linkgit:gitk[1]::
        The git repository browser.  Shows branches, commit history
        and file differences.  gitk is the utility started by
-       'git-gui''s Repository Visualize actions.
+       'git gui''s Repository Visualize actions.
 
 Other
 -----
-'git-gui' is actually maintained as an independent project, but stable
+'git gui' is actually maintained as an independent project, but stable
 versions are distributed as part of the Git suite for the convenience
 of end users.
 
-A 'git-gui' development repository can be obtained from:
+A 'git gui' development repository can be obtained from:
 
   git clone git://repo.or.cz/git-gui.git
 
index 0af40cfb85ca6e0eb6e540f0beb47e449ef25afd..51edeecbe5b546c6fbbbe117f39b51f352cb6bc8 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git hash-object' [-t <type>] [-w] [--path=<file>|--no-filters] [--stdin] [--] <file>...
-'git hash-object' [-t <type>] [-w] --stdin-paths < <list-of-paths>
+'git hash-object' [-t <type>] [-w] --stdin-paths [--no-filters] < <list-of-paths>
 
 DESCRIPTION
 -----------
@@ -18,7 +18,7 @@ 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
+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".
 
@@ -49,7 +49,7 @@ OPTIONS
 
 --no-filters::
        Hash the contents as is, ignoring any input filter that would
-       have been chosen by the attributes mechanism, including crlf
+       have been chosen by the attributes mechanism, including the end-of-line
        conversion. If the file is read from standard input then this
        is always implied, unless the --path option is given.
 
index d9b9c34b3a60f09bea4f9e53b5127f33f356e042..eccd0ffd384bf90f1846bf4cfeb7670dbd64cb6e 100644 (file)
@@ -55,9 +55,9 @@ other display programs (see below).
 +
 The web browser can be specified using the configuration variable
 'help.browser', or 'web.browser' if the former is not set. If none of
-these config variables is set, the 'git-web--browse' helper script
-(called by 'git-help') will pick a suitable default. See
-linkgit:git-web--browse[1] for more information about this.
+these config variables is set, the 'git web{litdd}browse' helper script
+(called by 'git help') will pick a suitable default. See
+linkgit:git-web{litdd}browse[1] for more information about this.
 
 CONFIGURATION VARIABLES
 -----------------------
@@ -67,7 +67,7 @@ help.format
 
 If no command line option is passed, the 'help.format' configuration
 variable will be checked. The following values are supported for this
-variable; they make 'git-help' behave as their corresponding command
+variable; they make 'git help' behave as their corresponding command
 line option:
 
 * "man" corresponds to '-m|--man',
@@ -80,7 +80,7 @@ help.browser, web.browser and browser.<tool>.path
 The 'help.browser', 'web.browser' and 'browser.<tool>.path' will also
 be checked if the 'web' format is chosen (either by command line
 option or configuration variable). See '-w|--web' in the OPTIONS
-section above and linkgit:git-web--browse[1].
+section above and linkgit:git-web{litdd}browse[1].
 
 man.viewer
 ~~~~~~~~~~
@@ -122,7 +122,7 @@ man.<tool>.path
 You can explicitly provide a full path to your preferred man viewer by
 setting the configuration variable 'man.<tool>.path'. For example, you
 can configure the absolute path to konqueror by setting
-'man.konqueror.path'. Otherwise, 'git-help' assumes the tool is
+'man.konqueror.path'. Otherwise, 'git help' assumes the tool is
 available in PATH.
 
 man.<tool>.cmd
diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt
new file mode 100644 (file)
index 0000000..277d9e1
--- /dev/null
@@ -0,0 +1,188 @@
+git-http-backend(1)
+===================
+
+NAME
+----
+git-http-backend - Server side implementation of Git over HTTP
+
+SYNOPSIS
+--------
+[verse]
+'git http-backend'
+
+DESCRIPTION
+-----------
+A simple CGI program to serve the contents of a Git repository to Git
+clients accessing the repository over http:// and https:// protocols.
+The program supports clients fetching using both the smart HTTP protocol
+and the backwards-compatible dumb HTTP protocol, as well as clients
+pushing using the smart HTTP protocol.
+
+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
+GIT_HTTP_EXPORT_ALL environmental variable is set).
+
+By default, only the `upload-pack` service is enabled, which serves
+'git fetch-pack' and 'git ls-remote' clients, which are invoked from
+'git fetch', 'git pull', and 'git clone'.  If the client is authenticated,
+the `receive-pack` service is enabled, which serves 'git send-pack'
+clients, which is invoked from 'git push'.
+
+SERVICES
+--------
+These services can be enabled/disabled using the per-repository
+configuration file:
+
+http.getanyfile::
+       This serves Git clients older than version 1.6.6 that are unable to use the
+       upload pack service.  When enabled, clients are able to read
+       any file within the repository, including objects that are
+       no longer reachable from a branch but are still present.
+       It is enabled by default, but a repository can disable it
+       by setting this configuration item to `false`.
+
+http.uploadpack::
+       This serves 'git fetch-pack' and 'git ls-remote' clients.
+       It is enabled by default, but a repository can disable it
+       by setting this configuration item to `false`.
+
+http.receivepack::
+       This serves 'git send-pack' clients, allowing push.  It is
+       disabled by default for anonymous users, and enabled by
+       default for users authenticated by the web server.  It can be
+       disabled by setting this item to `false`, or enabled for all
+       users, including anonymous users, by setting it to `true`.
+
+URL TRANSLATION
+---------------
+To determine the location of the repository on disk, 'git http-backend'
+concatenates the environment variables PATH_INFO, which is set
+automatically by the web server, and GIT_PROJECT_ROOT, which must be set
+manually in the web server configuration.  If GIT_PROJECT_ROOT is not
+set, 'git http-backend' reads PATH_TRANSLATED, which is also set
+automatically by the web server.
+
+EXAMPLES
+--------
+All of the following examples map 'http://$hostname/git/foo/bar.git'
+to '/var/www/git/foo/bar.git'.
+
+Apache 2.x::
+       Ensure mod_cgi, mod_alias, and mod_env are enabled, set
+       GIT_PROJECT_ROOT (or DocumentRoot) appropriately, and
+       create a ScriptAlias to the CGI:
++
+----------------------------------------------------------------
+SetEnv GIT_PROJECT_ROOT /var/www/git
+SetEnv GIT_HTTP_EXPORT_ALL
+ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
+----------------------------------------------------------------
++
+To enable anonymous read access but authenticated write access,
+require authorization with a LocationMatch directive:
++
+----------------------------------------------------------------
+<LocationMatch "^/git/.*/git-receive-pack$">
+       AuthType Basic
+       AuthName "Git Access"
+       Require group committers
+       ...
+</LocationMatch>
+----------------------------------------------------------------
++
+To require authentication for both reads and writes, use a Location
+directive around the repository, or one of its parent directories:
++
+----------------------------------------------------------------
+<Location /git/private>
+       AuthType Basic
+       AuthName "Private Git Access"
+       Require group committers
+       ...
+</Location>
+----------------------------------------------------------------
++
+To serve gitweb at the same url, use a ScriptAliasMatch to only
+those URLs that 'git http-backend' can handle, and forward the
+rest to gitweb:
++
+----------------------------------------------------------------
+ScriptAliasMatch \
+       "(?x)^/git/(.*/(HEAD | \
+                       info/refs | \
+                       objects/(info/[^/]+ | \
+                                [0-9a-f]{2}/[0-9a-f]{38} | \
+                                pack/pack-[0-9a-f]{40}\.(pack|idx)) | \
+                       git-(upload|receive)-pack))$" \
+       /usr/libexec/git-core/git-http-backend/$1
+
+ScriptAlias /git/ /var/www/cgi-bin/gitweb.cgi/
+----------------------------------------------------------------
+
+Accelerated static Apache 2.x::
+       Similar to the above, but Apache can be used to return static
+       files that are stored on disk.  On many systems this may
+       be more efficient as Apache can ask the kernel to copy the
+       file contents from the file system directly to the network:
++
+----------------------------------------------------------------
+SetEnv GIT_PROJECT_ROOT /var/www/git
+
+AliasMatch ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$          /var/www/git/$1
+AliasMatch ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.(pack|idx))$ /var/www/git/$1
+ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
+----------------------------------------------------------------
++
+This can be combined with the gitweb configuration:
++
+----------------------------------------------------------------
+SetEnv GIT_PROJECT_ROOT /var/www/git
+
+AliasMatch ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$          /var/www/git/$1
+AliasMatch ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.(pack|idx))$ /var/www/git/$1
+ScriptAliasMatch \
+       "(?x)^/git/(.*/(HEAD | \
+                       info/refs | \
+                       objects/info/[^/]+ | \
+                       git-(upload|receive)-pack))$" \
+       /usr/libexec/git-core/git-http-backend/$1
+ScriptAlias /git/ /var/www/cgi-bin/gitweb.cgi/
+----------------------------------------------------------------
+
+
+ENVIRONMENT
+-----------
+'git http-backend' relies upon the CGI environment variables set
+by the invoking web server, including:
+
+* PATH_INFO (if GIT_PROJECT_ROOT is set, otherwise PATH_TRANSLATED)
+* REMOTE_USER
+* REMOTE_ADDR
+* CONTENT_TYPE
+* QUERY_STRING
+* REQUEST_METHOD
+
+The GIT_HTTP_EXPORT_ALL environmental variable may be passed to
+'git-http-backend' to bypass the check for the "git-daemon-export-ok"
+file in each repository before allowing export of that repository.
+
+The backend process sets GIT_COMMITTER_NAME to '$REMOTE_USER' and
+GIT_COMMITTER_EMAIL to '$\{REMOTE_USER}@http.$\{REMOTE_ADDR\}',
+ensuring that any reflogs created by 'git-receive-pack' contain some
+identifying information of the remote user who performed the push.
+
+All CGI environment variables are available to each of the hooks
+invoked by the 'git-receive-pack'.
+
+Author
+------
+Written by Shawn O. Pearce <spearce@spearce.org>.
+
+Documentation
+--------------
+Documentation by Shawn O. Pearce <spearce@spearce.org>.
+
+GIT
+---
+Part of the linkgit:git[1] suite
index e7c796155fdd0ad644decf5dc488c6d780a2d164..d91cb7ff85b8c05a39cd18757bc66a16662c5150 100644 (file)
@@ -35,7 +35,7 @@ commit-id::
 
 --stdin::
        Instead of a commit id on the command line (which is not expected in this
-       case), 'git-http-fetch' expects lines on stdin in the format
+       case), 'git http-fetch' expects lines on stdin in the format
 
                <commit-id>['\t'<filename-as-in--w>]
 
index aef383e0b142bd603b77620cad720c102d70c4b7..ddf7a18dc42ef4d5f8ad44aed89ee5b17bb0e9a6 100644 (file)
@@ -82,11 +82,11 @@ destination side.
 
 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",
+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.
+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.
index d016dafd491de5a4635e30b92607dadbaf7bf46f..57aba42e6654e3d32617c3c7b17d18e8dd85852b 100644 (file)
@@ -13,10 +13,12 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-This command uploads a mailbox generated with 'git-format-patch'
+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 when using mail clients that cannot read mailbox
-files directly.
+files directly. The command also works with any general mailbox
+in which emails have the fields "From", "Date", and "Subject" in
+that order.
 
 Typical usage is something like:
 
@@ -71,6 +73,10 @@ imap.preformattedHTML::
        option causes Thunderbird to send the patch as a plain/text,
        format=fixed email.  Default is `false`.
 
+imap.authMethod::
+       Specify authenticate method for authentication with IMAP server.
+       Current supported method is 'CRAM-MD5' only.
+
 Examples
 ~~~~~~~~
 
@@ -118,12 +124,6 @@ Thunderbird in particular is known to be problematic.  Thunderbird
 users may wish to visit this web page for more information:
   http://kb.mozillazine.org/Plain_text_e-mail_-_Thunderbird#Completely_plain_email
 
-
-BUGS
-----
-Doesn't handle lines starting with "From " in the message body.
-
-
 Author
 ------
 Derived from isync 1.0.1 by Mike McCormack.
index 4b5c743c1e5f11281e2b9df7508d57e9878ee5d2..f3ccc72f0d68f03cd7b3fb6a36aabc8d7c7772a1 100644 (file)
@@ -43,23 +43,19 @@ OPTIONS
        a default name determined from the pack content.  If
        <pack-file> is not specified consider using --keep to
        prevent a race condition between this process and
-       'git-repack'.
+       'git repack'.
 
 --fix-thin::
-       It is possible for 'git-pack-objects' to build
-       "thin" pack, which records objects in deltified form based on
-       objects not included in the pack to reduce network traffic.
-       Those objects are expected to be present on the receiving end
-       and they must be included in the pack for that pack to be self
-       contained and indexable. Without this option any attempt to
-       index a thin pack will fail. This option only makes sense in
-       conjunction with --stdin.
+       Fix a "thin" pack produced by `git pack-objects --thin` (see
+       linkgit:git-pack-objects[1] for details) by adding the
+       excluded objects the deltified objects are based on to the
+       pack. This option only makes sense in conjunction with --stdin.
 
 --keep::
        Before moving the index into its final destination
        create an empty .keep file for the associated pack file.
        This option is usually necessary with --stdin to prevent a
-       simultaneous 'git-repack' process from deleting
+       simultaneous 'git repack' process from deleting
        the newly constructed pack and index before refs can be
        updated to use objects contained in the pack.
 
@@ -86,7 +82,7 @@ Once the index has been created, the list of object names is sorted
 and the SHA1 hash of that list is printed to stdout. If --stdin was
 also used then this is prefixed by either "pack\t", or "keep\t" if a
 new .keep file was successfully created. This is useful to remove a
-.keep file used as a lock to prevent the race with 'git-repack'
+.keep file used as a lock to prevent the race with 'git repack'
 mentioned above.
 
 
index 1fd0ff2610a1375bcf0defe2a234b2dee1a7997a..eba3cb4998b2732a22551aaf962ae1a742d221de 100644 (file)
@@ -8,7 +8,7 @@ git-init-db - Creates an empty git repository
 
 SYNOPSIS
 --------
-'git init-db' [-q | --quiet] [--template=<template_directory>] [--shared[=<permissions>]]
+'git init-db' [-q | --quiet] [--bare] [--template=<template_directory>] [--shared[=<permissions>]]
 
 
 DESCRIPTION
index 7151d12f349b7c6e265d5a4631029d71028a2c7d..246b07ebf94394d2079c2acc3e8c1a2e6c40bba9 100644 (file)
@@ -8,7 +8,7 @@ git-init - Create an empty git repository or reinitialize an existing one
 
 SYNOPSIS
 --------
-'git init' [-q | --quiet] [--bare] [--template=<template_directory>] [--shared[=<permissions>]]
+'git init' [-q | --quiet] [--bare] [--template=<template_directory>] [--shared[=<permissions>]] [directory]
 
 
 OPTIONS
@@ -28,14 +28,8 @@ current working directory.
 
 --template=<template_directory>::
 
-Provide the directory from which templates will be used.  The default template
-directory is `/usr/share/git-core/templates`.
-
-When specified, `<template_directory>` is used as the source of the template
-files rather than the default.  The template files include some directory
-structure, some suggested "exclude patterns", and copies of non-executing
-"hook" files.  The suggested patterns and hook files are all modifiable and
-extensible.
+Specify the directory from which templates will be used.  (See the "TEMPLATE
+DIRECTORY" section below.)
 
 --shared[={false|true|umask|group|all|world|everybody|0xxx}]::
 
@@ -74,6 +68,9 @@ By default, the configuration flag receive.denyNonFastForwards is enabled
 in shared repositories, so that you cannot force a non fast-forwarding push
 into it.
 
+If you name a (possibly non-existent) directory at the end of the command
+line, the command is run inside the directory (possibly after creating it).
+
 --
 
 
@@ -92,17 +89,36 @@ If the object storage directory is specified via the `$GIT_OBJECT_DIRECTORY`
 environment variable then the sha1 directories are created underneath -
 otherwise the default `$GIT_DIR/objects` directory is used.
 
-Running 'git-init' in an existing repository is safe. It will not overwrite
-things that are already there. The primary reason for rerunning 'git-init'
+Running 'git init' in an existing repository is safe. It will not overwrite
+things that are already there. The primary reason for rerunning 'git init'
 is to pick up newly added templates.
 
-Note that 'git-init' is the same as 'git-init-db'.  The command
+Note that 'git init' is the same as 'git init-db'.  The command
 was primarily meant to initialize the object database, but over
 time it has become responsible for setting up the other aspects
 of the repository, such as installing the default hooks and
 setting the configuration variables.  The old name is retained
 for backward compatibility reasons.
 
+TEMPLATE DIRECTORY
+------------------
+
+The template directory contains files and directories that will be copied to
+the `$GIT_DIR` after it is created.
+
+The template directory used will (in order):
+
+ - The argument given with the `--template` option.
+
+ - The contents of the `$GIT_TEMPLATE_DIR` environment variable.
+
+ - The `init.templatedir` configuration variable.
+
+ - The default template directory: `/usr/share/git-core/templates`.
+
+The default template directory includes some directory structure, some
+suggested "exclude patterns", and copies of sample "hook" files.
+The suggested patterns and hook files are all modifiable and extensible.
 
 EXAMPLES
 --------
index 22da21a54f625c434216945889127ec283d3d09f..7477ce8fa8e5185cfbd74a31b8d5accd18216a1b 100644 (file)
@@ -29,7 +29,7 @@ OPTIONS
        The HTTP daemon command-line that will be executed.
        Command-line options may be specified here, and the
        configuration file will be added at the end of the command-line.
-       Currently lighttpd, apache2 and webrick are supported.
+       Currently apache2, lighttpd, mongoose, plackup and webrick are supported.
        (Default: lighttpd)
 
 -m::
@@ -44,20 +44,23 @@ OPTIONS
 -b::
 --browser::
        The web browser that should be used to view the gitweb
-       page. This will be passed to the 'git-web--browse' helper
+       page. This will be passed to the 'git web{litdd}browse' helper
        script along with the URL of the gitweb instance. See
-       linkgit:git-web--browse[1] for more information about this. If
+       linkgit:git-web{litdd}browse[1] for more information about this. If
        the script fails, the URL will be printed to stdout.
 
+start::
 --start::
        Start the httpd instance and exit.  This does not generate
        any of the configuration files for spawning a new instance.
 
+stop::
 --stop::
        Stop the httpd instance and exit.  This does not generate
        any of the configuration files for spawning a new instance,
        nor does it close the browser.
 
+restart::
 --restart::
        Restart the httpd instance and exit.  This does not generate
        any of the configuration files for spawning a new instance.
@@ -79,7 +82,7 @@ You may specify configuration in your .git/config
 
 If the configuration variable 'instaweb.browser' is not set,
 'web.browser' will be used instead if it is defined. See
-linkgit:git-web--browse[1] for more information about this.
+linkgit:git-web{litdd}browse[1] for more information about this.
 
 Author
 ------
index 34cf4e5811d1a6f46fcbd333a2ff48c200eadff8..c213bdbdc500b1117805bc84cb2a0f31eeff3bcd 100644 (file)
@@ -14,18 +14,15 @@ DESCRIPTION
 -----------
 Shows the commit logs.
 
-The command takes options applicable to the 'git-rev-list'
+The command takes options applicable to the 'git rev-list'
 command to control what is shown and how, and options applicable to
-the 'git-diff-*' commands to control how the changes
+the 'git diff-*' commands to control how the changes
 each commit introduces are shown.
 
 
 OPTIONS
 -------
 
-:git-log: 1
-include::diff-options.txt[]
-
 -<n>::
        Limits the number of commits to show.
 
@@ -34,11 +31,19 @@ include::diff-options.txt[]
        either <since> or <until> is omitted, it defaults to
        `HEAD`, i.e. the tip of the current branch.
        For a more complete list of ways to spell <since>
-       and <until>, see "SPECIFYING REVISIONS" section in
-       linkgit:git-rev-parse[1].
+       and <until>, see linkgit:gitrevisions[1].
+
+--follow::
+       Continue listing the history of a file beyond renames
+       (works only for a single file).
 
---decorate::
-       Print out the ref names of any commits that are shown.
+--no-decorate::
+--decorate[=short|full|no]::
+       Print out the ref names of any commits that are shown. If 'short' is
+       specified, the ref name prefixes 'refs/heads/', 'refs/tags/' and
+       'refs/remotes/' will not be printed. If 'full' is specified, the
+       full ref name (including prefix) will be printed. The default option
+       is 'short'.
 
 --source::
        Print out the ref name given on the command line by which each
@@ -50,9 +55,9 @@ include::diff-options.txt[]
        paths.  With this, the full diff is shown for commits that touch
        the specified paths; this means that "<path>..." limits only
        commits, and doesn't limit diff for those commits.
-
---follow::
-       Continue listing the history of a file beyond renames.
++
+Note that this affects all diff-based output types, e.g. those
+produced by --stat etc.
 
 --log-size::
        Before the log message print out its size in bytes. Intended
@@ -67,6 +72,11 @@ include::diff-options.txt[]
        to be prefixed with "\-- " to separate them from options or
        refnames.
 
+Common diff options
+~~~~~~~~~~~~~~~~~~~
+
+:git-log: 1
+include::diff-options.txt[]
 
 include::rev-list-options.txt[]
 
@@ -103,11 +113,73 @@ git log --follow builtin-rev-list.c::
        those commits that occurred before the file was given its
        present name.
 
+git log --branches --not --remotes=origin::
+
+       Shows all commits that are in any of local branches but not in
+       any of remote tracking branches for 'origin' (what you have that
+       origin doesn't).
+
+git log master --not --remotes=*/master::
+
+       Shows all commits that are in local master but not in any remote
+       repository master branches.
+
+git log -p -m --first-parent::
+
+       Shows the history including change diffs, but only from the
+       "main branch" perspective, skipping commits that come from merged
+       branches, and showing full diffs of changes introduced by the merges.
+       This makes sense only when following a strict policy of merging all
+       topic branches when staying on a single integration branch.
+
+
 Discussion
 ----------
 
 include::i18n.txt[]
 
+Configuration
+-------------
+
+See linkgit:git-config[1] for core variables and linkgit:git-diff[1]
+for settings related to diff generation.
+
+format.pretty::
+       Default for the `--format` option.  (See "PRETTY FORMATS" above.)
+       Defaults to "medium".
+
+i18n.logOutputEncoding::
+       Encoding to use when displaying logs.  (See "Discussion", above.)
+       Defaults to the value of `i18n.commitEncoding` if set, UTF-8
+       otherwise.
+
+log.date::
+       Default format for human-readable dates.  (Compare the
+       `--date` option.)  Defaults to "default", which means to write
+       dates like `Sat May 8 19:35:34 2010 -0500`.
+
+log.showroot::
+       If `false`, 'git log' and related commands will not treat the
+       initial commit as a big creation event.  Any root commits in
+       `git log -p` output would be shown without a diff attached.
+       The default is `true`.
+
+mailmap.file::
+       See linkgit:git-shortlog[1].
+
+notes.displayRef::
+       Which refs, in addition to the default set by `core.notesRef`
+       or 'GIT_NOTES_REF', to read notes from when showing commit
+       messages with the 'log' family of commands.  See
+       linkgit:git-notes[1].
++
+May be an unabbreviated ref name or a glob and may be specified
+multiple times.  A warning will be issued for refs that do not exist,
+but a glob that does not match any refs is silently ignored.
++
+This setting can be disabled by the `--no-standard-notes` option,
+overridden by the 'GIT_NOTES_DISPLAY_REF' environment variable,
+and supplemented by the `--show-notes` option.
 
 Author
 ------
index 44b6480c0f7f71fcbb5adc2ccc9c319ca440b533..347f4479865419d010b874bc8b26f5f1b40a6f61 100644 (file)
@@ -10,14 +10,14 @@ SYNOPSIS
 --------
 [verse]
 'git ls-files' [-z] [-t] [-v]
-               (--[cached|deleted|others|ignored|stage|unmerged|killed|modified])\*
-               (-[c|d|o|i|s|u|k|m])\*
+               (--[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>]
                [--exclude-standard]
                [--error-unmatch] [--with-tree=<tree-ish>]
-               [--full-name] [--abbrev] [--] [<file>]\*
+               [--full-name] [--abbrev] [--] [<file>]*
 
 DESCRIPTION
 -----------
@@ -44,7 +44,7 @@ OPTIONS
 
 -o::
 --others::
-       Show other files in the output
+       Show other (i.e. untracked) files in the output
 
 -i::
 --ignored::
@@ -107,9 +107,18 @@ OPTIONS
        with `-s` or `-u` options does not make any sense.
 
 -t::
-       Identify the file status with the following tags (followed by
-       a space) at the start of each line:
+       This feature is semi-deprecated. For scripting purpose,
+       linkgit:git-status[1] `--porcelain` and
+       linkgit:git-diff-files[1] `--name-status` are almost always
+       superior alternatives, and users should look at
+       linkgit:git-status[1] `--short` or linkgit:git-diff[1]
+       `--name-status` for more user-friendly alternatives.
++
+This option identifies the file status with the following tags (followed by
+a space) at the start of each line:
+
        H::     cached
+       S::     skip-worktree
        M::     unmerged
        R::     removed/deleted
        C::     modified/changed
@@ -132,6 +141,12 @@ OPTIONS
        lines, show only a partial prefix.
        Non default number of digits can be specified with --abbrev=<n>.
 
+--debug::
+       After each line that describes a file, add more data about its
+       cache entry.  This is intended to show as much information as
+       possible for manual inspection; the exact format may change at
+       any time.
+
 \--::
        Do not interpret any more arguments as options.
 
@@ -141,12 +156,12 @@ OPTIONS
 
 Output
 ------
-show files just outputs the filename unless '--stage' is specified in
+'git ls-files' just outputs the filenames 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
+'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,
@@ -163,7 +178,7 @@ respectively.
 Exclude Patterns
 ----------------
 
-'git-ls-files' can use a list of "exclude patterns" when
+'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.  linkgit:gitignore[5]
 specifies the format of exclude patterns.
@@ -179,7 +194,7 @@ These exclude patterns come from these places, in order:
      in the same order they appear in the file.
 
   3. The command line flag --exclude-per-directory=<name> specifies
-     a name of the file in each directory 'git-ls-files'
+     a name of the file in each directory 'git ls-files'
      examines, normally `.gitignore`.  Files in deeper
      directories take precedence.  Patterns are ordered in the
      same order they appear in the files.
index f68e5c5c1abd15350e679eebf976f0ac8516123b..1f89d3680043cc98ad39facb1f43ae770ff66e14 100644 (file)
@@ -28,7 +28,7 @@ in the current working directory.  Note that:
    in a directory 'sub' that has a directory 'dir', you can run 'git
    ls-tree -r HEAD dir' to list the contents of the tree (that is
    'sub/dir' in 'HEAD').  You don't want to give a tree that is not at the
-   root level (e.g. 'git ls-tree -r HEAD:sub dir') in this case, as that
+   root level (e.g. `git ls-tree -r HEAD:sub dir`) in this case, as that
    would result in asking for 'sub/sub/dir' in the 'HEAD' commit.
    However, the current working directory can be ignored by passing
    --full-tree option.
@@ -82,8 +82,10 @@ Output Format
 -------------
         <mode> SP <type> SP <object> TAB <file>
 
-When the `-z` option is not used, TAB, LF, and backslash characters
+Unless the `-z` option is used, TAB, LF, and backslash characters
 in pathnames are represented as `\t`, `\n`, and `\\`, respectively.
+This output format is compatible with what `--index-info --stdin` of
+'git update-index' expects.
 
 When the `-l` option is used, format changes to
 
index 8d95aaa30441c36a019e9d3d78ec451fbd40fdaf..3ea5aad56c5e1cec7fc2ca01ba9b5ea6badebe2f 100644 (file)
@@ -8,7 +8,7 @@ git-mailinfo - Extracts patch and authorship from a single e-mail message
 
 SYNOPSIS
 --------
-'git mailinfo' [-k] [-u | --encoding=<encoding> | -n] <msg> <patch>
+'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--scissors] <msg> <patch>
 
 
 DESCRIPTION
@@ -16,7 +16,7 @@ DESCRIPTION
 Reads 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-am'
+written out to the standard output to be used by 'git am'
 to create a commit.  It is usually not necessary to use this
 command directly.  See linkgit:git-am[1] instead.
 
@@ -30,25 +30,49 @@ OPTIONS
        whitespaces, (3) '[' up to ']', typically '[PATCH]', and
        then prepends "[PATCH] ".  This flag forbids this
        munging, and is most useful when used to read back
-       'git-format-patch -k' output.
+       'git format-patch -k' output.
+
+-b::
+       When -k is not in effect, all leading strings bracketed with '['
+       and ']' pairs are stripped.  This option limits the stripping to
+       only the pairs whose bracketed string contains the word "PATCH".
 
 -u::
        The commit log message, author name and author email are
        taken from the e-mail, and after minimally decoding MIME
-       transfer encoding, re-coded in UTF-8 by transliterating
+       transfer encoding, re-coded in the charset specified by
+       i18n.commitencoding (defaulting to UTF-8) by transliterating
        them.  This used to be optional but now it is the default.
 +
 Note that the patch is always used as-is without charset
 conversion, even with this flag.
 
 --encoding=<encoding>::
-       Similar to -u but if the local convention is different
-       from what is specified by i18n.commitencoding, this flag
-       can be used to override it.
+       Similar to -u.  But when re-coding, the charset specified here is
+       used instead of the one specified by i18n.commitencoding or UTF-8.
 
 -n::
        Disable all charset re-coding of the metadata.
 
+--scissors::
+       Remove everything in body before a scissors line.  A line that
+       mainly consists of scissors (either ">8" or "8<") and perforation
+       (dash "-") marks is called a scissors line, and is used to request
+       the reader to cut the message at that line.  If such a line
+       appears in the body of the message before the patch, everything
+       before it (including the scissors line itself) is ignored when
+       this option is used.
++
+This is useful if you want to begin your message in a discussion thread
+with comments and suggestions on the message you are responding to, and to
+conclude it with a patch submission, separating the discussion and the
+beginning of the proposed commit log message with a scissors line.
++
+This can enabled by default with the configuration option mailinfo.scissors.
+
+--no-scissors::
+       Ignore scissors lines. Useful for overriding mailinfo.scissors settings.
+
 <msg>::
        The commit log message extracted from e-mail, usually
        except the title line which comes from e-mail Subject.
index 5cc94ec53daf3057f57c993983d659543962abec..a634485281154cb665c8168cc3469dc0f5051697 100644 (file)
@@ -7,7 +7,7 @@ git-mailsplit - Simple UNIX mbox splitter program
 
 SYNOPSIS
 --------
-'git mailsplit' [-b] [-f<nn>] [-d<prec>] -o<directory> [--] [<mbox>|<Maildir>...]
+'git mailsplit' [-b] [-f<nn>] [-d<prec>] [--keep-cr] -o<directory> [--] [<mbox>|<Maildir>...]
 
 DESCRIPTION
 -----------
@@ -43,6 +43,9 @@ OPTIONS
        Skip the first <nn> numbers, for example if -f3 is specified,
        start the numbering with 0004.
 
+--keep-cr::
+       Do not remove `\r` from lines ending with `\r\n`.
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
index 767486c770afd385d118ee5f9a6f9cd3ad0a2d73..eedef1bb1a14591d23b1fe6e93da1e3c2be2786b 100644 (file)
@@ -8,30 +8,49 @@ git-merge-base - Find as good common ancestors as possible for a merge
 
 SYNOPSIS
 --------
-'git merge-base' [--all] <commit> <commit>...
+[verse]
+'git merge-base' [-a|--all] [--octopus] <commit> <commit>...
+'git merge-base' --independent <commit>...
 
 DESCRIPTION
 -----------
 
-'git-merge-base' finds best common ancestor(s) between two commits to use
+'git merge-base' finds best common ancestor(s) between two commits to use
 in a three-way merge.  One common ancestor is 'better' than another common
 ancestor if the latter is an ancestor of the former.  A common ancestor
 that does not have any better common ancestor is a 'best common
 ancestor', i.e. a 'merge base'.  Note that there can be more than one
 merge base for a pair of commits.
 
-Among the two commits to compute the merge base from, one is specified by
-the first commit argument on the command line; the other commit is a
-(possibly hypothetical) commit that is a merge across all the remaining
-commits on the command line.  As the most common special case, specifying only
-two commits on the command line means computing the merge base between
-the given two commits.
+Unless `--octopus` is given, among the two commits to compute the merge
+base from, one is specified by the first commit argument on the command
+line; the other commit is a (possibly hypothetical) commit that is a merge
+across all the remaining commits on the command line.  As the most common
+special case, specifying only two commits on the command line means
+computing the merge base between the given two commits.
+
+As a consequence, the 'merge base' is not necessarily contained in each of the
+commit arguments if more than two commits are specified. This is different
+from linkgit:git-show-branch[1] when used with the `--merge-base` option.
 
 OPTIONS
 -------
+-a::
 --all::
        Output all merge bases for the commits, instead of just one.
 
+--octopus::
+       Compute the best common ancestors of all supplied commits,
+       in preparation for an n-way merge.  This mimics the behavior
+       of 'git show-branch --merge-base'.
+
+--independent::
+       Instead of printing merge bases, print a minimal subset of
+       the supplied commits with the same ancestors.  In other words,
+       among the commits given, list those which cannot be reached
+       from any other.  This mimics the behavior of 'git show-branch
+       --independent'.
+
 DISCUSSION
 ----------
 
@@ -91,6 +110,12 @@ Documentation
 --------------
 Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
 
+See also
+--------
+linkgit:git-rev-list[1],
+linkgit:git-show-branch[1],
+linkgit:git-merge[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index 303537357b2fb5d1a0d7fa291d51803ddb97749c..f334d694e0160df91d197293e5b76cc0bfaac187 100644 (file)
@@ -10,20 +10,21 @@ SYNOPSIS
 --------
 [verse]
 'git merge-file' [-L <current-name> [-L <base-name> [-L <other-name>]]]
-       [-p|--stdout] [-q|--quiet] <current-file> <base-file> <other-file>
+       [--ours|--theirs|--union] [-p|--stdout] [-q|--quiet] [--marker-size=<n>]
+       <current-file> <base-file> <other-file>
 
 
 DESCRIPTION
 -----------
-'git-merge-file' incorporates all changes that lead from the `<base-file>`
+'git merge-file' incorporates all changes that lead from the `<base-file>`
 to `<other-file>` into `<current-file>`. The result ordinarily goes into
-`<current-file>`. 'git-merge-file' is useful for combining separate changes
+`<current-file>`. 'git merge-file' is useful for combining separate changes
 to an original. Suppose `<base-file>` is the original, and both
 `<current-file>` and `<other-file>` are modifications of `<base-file>`,
-then 'git-merge-file' combines both changes.
+then 'git merge-file' combines both changes.
 
 A conflict occurs if both `<current-file>` and `<other-file>` have changes
-in a common segment of lines. If a conflict is found, 'git-merge-file'
+in a common segment of lines. If a conflict is found, 'git merge-file'
 normally outputs a warning and brackets the conflict with lines containing
 <<<<<<< and >>>>>>> markers. A typical conflict will look like this:
 
@@ -34,12 +35,15 @@ normally outputs a warning and brackets the conflict with lines containing
        >>>>>>> B
 
 If there are conflicts, the user should edit the result and delete one of
-the alternatives.
+the alternatives.  When `--ours`, `--theirs`, or `--union` option is in effect,
+however, these conflicts are resolved favouring lines from `<current-file>`,
+lines from `<other-file>`, or lines from both respectively.  The length of the
+conflict markers can be given with the `--marker-size` option.
 
 The exit value of this program is negative on error, and the number of
 conflicts otherwise. If the merge was clean, the exit value is 0.
 
-'git-merge-file' is designed to be a minimal clone of RCS 'merge'; that is, it
+'git merge-file' is designed to be a minimal clone of RCS 'merge'; that is, it
 implements all of RCS 'merge''s functionality which is needed by
 linkgit:git[1].
 
@@ -62,6 +66,12 @@ OPTIONS
 -q::
        Quiet; do not warn about conflicts.
 
+--ours::
+--theirs::
+--union::
+       Instead of leaving conflicts in the file, resolve conflicts
+       favouring our (or their or both) side of the lines.
+
 
 EXAMPLES
 --------
index 123e6d024a47537981526d0c078249565e72d74f..921b38f18374c20d1f94c0907a629425acb38aa4 100644 (file)
@@ -8,7 +8,7 @@ git-merge-index - Run a merge for files needing merging
 
 SYNOPSIS
 --------
-'git merge-index' [-o] [-q] <merge-program> (-a | [--] <file>\*)
+'git merge-index' [-o] [-q] <merge-program> (-a | [--] <file>*)
 
 DESCRIPTION
 -----------
@@ -36,14 +36,14 @@ OPTIONS
        failure usually indicates conflicts during the 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
+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 a script calling git's imitation of
 the 'merge' command from the RCS package.
 
-A sample script called 'git-merge-one-file' is included in the
+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
@@ -68,10 +68,10 @@ or
   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
+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).
+'git merge-index' didn't even try to merge the MM thing).
 
 Author
 ------
index dc8a96adb00c0b674e12e071a4a56f89bfe8583d..a163cfca6993b61b3f3d198bd0980bd1fed37e52 100644 (file)
@@ -8,12 +8,12 @@ git-merge-one-file - The standard helper program to use with git-merge-index
 
 SYNOPSIS
 --------
-'git-merge-one-file'
+'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'.
+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
 ------
index c04ae739edd409307d286fe31e6f87e41f977eac..84043cc5b26db7d34c93a70ec005df913c345d85 100644 (file)
@@ -9,116 +9,141 @@ git-merge - Join two or more development histories together
 SYNOPSIS
 --------
 [verse]
-'git merge' [-n] [--stat] [--no-commit] [--squash] [-s <strategy>]...
-       [-m <msg>] <remote> <remote>...
-'git merge' <msg> HEAD <remote>...
+'git merge' [-n] [--stat] [--no-commit] [--squash]
+       [-s <strategy>] [-X <strategy-option>]
+       [--[no-]rerere-autoupdate] [-m <msg>] <commit>...
+'git merge' <msg> HEAD <commit>...
 
 DESCRIPTION
 -----------
-This is the top-level interface to the merge machinery
-which drives multiple merge strategy scripts.
-
-The second syntax (<msg> `HEAD` <remote>) is supported for
-historical reasons.  Do not use it from the command line or in
-new scripts.  It is the same as `git merge -m <msg> <remote>`.
+Incorporates changes from the named commits (since the time their
+histories diverged from the current branch) into the current
+branch.  This command is used by 'git pull' to incorporate changes
+from another repository and can be used by hand to merge changes
+from one branch into another.
 
+Assume the following history exists and the current branch is
+"`master`":
 
-OPTIONS
--------
-include::merge-options.txt[]
+------------
+         A---B---C topic
+        /
+    D---E---F---G master
+------------
 
--m <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.
+Then "`git merge topic`" will replay the changes made on the
+`topic` branch since it diverged from `master` (i.e., `E`) until
+its current commit (`C`) on top of `master`, and record the result
+in a new commit along with the names of the two parent commits and
+a log message from the user describing the changes.
 
-<remote>...::
-       Other branch heads to merge into our branch.  You need at
-       least one <remote>.  Specifying more than one <remote>
-       obviously means you are trying an Octopus.
+------------
+         A---B---C topic
+        /         \
+    D---E---F---G---H master
+------------
 
-include::merge-strategies.txt[]
+The second syntax (<msg> `HEAD` <commit>...) is supported for
+historical reasons.  Do not use it from the command line or in
+new scripts.  It is the same as `git merge -m <msg> <commit>...`.
 
+*Warning*: Running 'git merge' with uncommitted changes is
+discouraged: while possible, it leaves you in a state that is hard to
+back out of in the case of a conflict.
 
-If you tried a merge which resulted in complex conflicts and
-want to start over, you can recover with 'git-reset'.
 
-CONFIGURATION
--------------
-include::merge-config.txt[]
+OPTIONS
+-------
+include::merge-options.txt[]
 
-branch.<name>.mergeoptions::
-       Sets default options for merging into branch <name>. The syntax and
-       supported options are equal to that of 'git-merge', but option values
-       containing whitespace characters are currently not supported.
-
-HOW MERGE WORKS
----------------
-
-A merge is always between the current `HEAD` and one or more
-commits (usually, branch head or tag), and the index file must
-match the tree of `HEAD` commit (i.e. the contents of the last commit)
-when it starts out.  In other words, `git diff --cached HEAD` must
-report no changes.  (One exception is when the changed index
-entries are already in the same state that would result from
-the merge anyway.)
-
-Three kinds of merge can happen:
-
-* The merged commit is already contained in `HEAD`. This is the
-  simplest case, called "Already up-to-date."
-
-* `HEAD` is already contained in the merged commit. This is the
-  most common case especially when invoked from 'git pull':
-  you are tracking an upstream repository, have committed no local
-  changes and now you want to update to a newer upstream revision.
-  Your `HEAD` (and the index) is updated to point at the merged
-  commit, without creating an extra merge commit.  This is
-  called "Fast-forward".
-
-* Both the merged commit and `HEAD` are independent and must be
-  tied together by a merge commit that has both of them as its parents.
-  The rest of this section describes this "True merge" case.
-
-The chosen merge strategy merges the two commits into a single
-new source tree.
-When things merge cleanly, this is what happens:
-
-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 matches 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 those paths differing 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, the following happens:
-
-1. `HEAD` stays the same.
-
-2. Cleanly merged paths are updated both in the index file and
+-m <msg>::
+       Set the commit message to be used for the merge commit (in
+       case one is created).
+
+       If `--log` is specified, a shortlog of the commits being merged
+       will be appended to the specified message.
+
+       The 'git fmt-merge-msg' command can be
+       used to give a good default for automated 'git merge'
+       invocations.
+
+--rerere-autoupdate::
+--no-rerere-autoupdate::
+       Allow the rerere mechanism to update the index with the
+       result of auto-conflict resolution if possible.
+
+<commit>...::
+       Commits, usually other branch heads, to merge into our branch.
+       You need at least one <commit>.  Specifying more than one
+       <commit> obviously means you are trying an Octopus.
+
+
+PRE-MERGE CHECKS
+----------------
+
+Before applying outside changes, you should get your own work in
+good shape and committed locally, so it will not be clobbered if
+there are conflicts.  See also linkgit:git-stash[1].
+'git pull' and 'git merge' will stop without doing anything when
+local uncommitted changes overlap with files that 'git pull'/'git
+merge' may need to update.
+
+To avoid recording unrelated changes in the merge commit,
+'git pull' and 'git merge' will also abort if there are any changes
+registered in the index relative to the `HEAD` commit.  (One
+exception is when the changed index entries are in the state that
+would result from the merge already.)
+
+If all named commits are already ancestors of `HEAD`, 'git merge'
+will exit early with the message "Already up-to-date."
+
+FAST-FORWARD MERGE
+------------------
+
+Often the current branch head is an ancestor of the named commit.
+This is the most common case especially when invoked from 'git
+pull': you are tracking an upstream repository, you have committed
+no local changes, and now you want to update to a newer upstream
+revision.  In this case, a new commit is not needed to store the
+combined history; instead, the `HEAD` (along with the index) is
+updated to point at the named commit, without creating an extra
+merge commit.
+
+This behavior can be suppressed with the `--no-ff` option.
+
+TRUE MERGE
+----------
+
+Except in a fast-forward merge (see above), the branches to be
+merged must be tied together by a merge commit that has both of them
+as its parents.
+
+A merged version reconciling the changes from all branches to be
+merged is committed, and your `HEAD`, index, and working tree are
+updated to it.  It is possible to have modifications in the working
+tree as long as they do not overlap; the update will preserve them.
+
+When it is not obvious how to reconcile the changes, the following
+happens:
+
+1. The `HEAD` pointer stays the same.
+2. The `MERGE_HEAD` ref is set to point to the other branch head.
+3. Paths that merged cleanly 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
+4. For conflicting paths, the index file records up to three
+   versions: stage 1 stores the version from the common ancestor,
+   stage 2 from `HEAD`, and stage 3 from `MERGE_HEAD` (you
    can inspect the stages with `git ls-files -u`).  The working
    tree files contain the result of the "merge" program; i.e. 3-way
-   merge results with familiar conflict markers `<<< === >>>`.
-
-4. No other changes are done.  In particular, the local
+   merge results with familiar conflict markers `<<<` `===` `>>>`.
+5. No other changes are made.  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`.
 
+If you tried a merge which resulted in complex conflicts and
+want to start over, you can recover with `git reset --merge`.
+
 HOW CONFLICTS ARE PRESENTED
 ---------------------------
 
@@ -188,28 +213,74 @@ After seeing a conflict, you can do two things:
 
  * Decide not to merge.  The only clean-ups 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 --hard' can
+   up working tree changes made by 2. and 3.; `git-reset --hard` can
    be used for this.
 
  * Resolve the conflicts.  Git will mark the conflicts in
    the working tree.  Edit the files into shape and
-   'git-add' them to the index.  Use 'git-commit' to seal the deal.
+   'git add' them to the index.  Use 'git commit' to seal the deal.
 
 You can work through the conflict with a number of tools:
 
- * Use a mergetool.  'git mergetool' to launch a graphical
+ * Use a mergetool.  `git mergetool` to launch a graphical
    mergetool which will work you through the merge.
 
- * Look at the diffs.  'git diff' will show a three-way diff,
-   highlighting changes from both the HEAD and remote versions.
+ * Look at the diffs.  `git diff` will show a three-way diff,
+   highlighting changes from both the `HEAD` and `MERGE_HEAD`
+   versions.
+
+ * Look at the diffs from each branch. `git log --merge -p <path>`
+   will show diffs first for the `HEAD` version and then the
+   `MERGE_HEAD` version.
+
+ * Look at the originals.  `git show :1:filename` shows the
+   common ancestor, `git show :2:filename` shows the `HEAD`
+   version, and `git show :3:filename` shows the `MERGE_HEAD`
+   version.
+
+
+EXAMPLES
+--------
+
+* Merge branches `fixes` and `enhancements` on top of
+  the current branch, making an octopus merge:
++
+------------------------------------------------
+$ git merge fixes enhancements
+------------------------------------------------
+
+* Merge branch `obsolete` into the current branch, using `ours`
+  merge strategy:
++
+------------------------------------------------
+$ git merge -s ours obsolete
+------------------------------------------------
+
+* Merge branch `maint` into the current branch, but do not make
+  a new commit automatically:
++
+------------------------------------------------
+$ git merge --no-commit maint
+------------------------------------------------
++
+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.
+
+
+include::merge-strategies.txt[]
 
- * Look at the diffs on their own. 'git log --merge -p <path>'
-   will show diffs first for the HEAD version and then the
-   remote version.
+CONFIGURATION
+-------------
+include::merge-config.txt[]
 
- * Look at the originals.  'git show :1:filename' shows the
-   common ancestor, 'git show :2:filename' shows the HEAD
-   version and 'git show :3:filename' shows the remote version.
+branch.<name>.mergeoptions::
+       Sets default options for merging into branch <name>. The syntax and
+       supported options are the same as those of 'git merge', but option
+       values containing whitespace characters are currently not supported.
 
 SEE ALSO
 --------
index 78eb03f0ae864f67faac55f450bf0fd2be3416b3..d8df55362ce653516006b02df2950c325f1176b7 100644 (file)
@@ -1,5 +1,5 @@
-git-mergetool--lib(1)
-=====================
+git-mergetool{litdd}lib(1)
+==========================
 
 NAME
 ----
@@ -16,11 +16,11 @@ This is not a command the end user would want to run.  Ever.
 This documentation is meant for people who are studying the
 Porcelain-ish scripts and/or are writing new ones.
 
-The 'git-mergetool--lib' scriptlet is designed to be sourced (using
+The 'git-mergetool{litdd}lib' scriptlet is designed to be sourced (using
 `.`) by other shell scripts to set up functions for working
 with git merge tools.
 
-Before sourcing 'git-mergetool--lib', your script must set `TOOL_MODE`
+Before sourcing 'git-mergetool{litdd}lib', your script must set `TOOL_MODE`
 to define the operation mode for the functions listed below.
 'diff' and 'merge' are valid values.
 
index ff9700d17a333f50d6509bc14a0bd81fad876d3b..e4ed0161467866f731c89eb48e64453051c64281 100644 (file)
@@ -13,11 +13,11 @@ DESCRIPTION
 -----------
 
 Use `git mergetool` to run one of several merge utilities to resolve
-merge conflicts.  It is typically run after 'git-merge'.
+merge conflicts.  It is typically run after 'git merge'.
 
 If one or more <file> parameters are given, the merge tool program will
 be run to resolve differences on each file.  If no <file> names are
-specified, 'git-mergetool' will run the merge tool program on every file
+specified, 'git mergetool' will run the merge tool program on every file
 with merge conflicts.
 
 OPTIONS
@@ -27,25 +27,25 @@ OPTIONS
        Use the merge resolution program specified by <tool>.
        Valid merge tools are:
        kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge,
-       diffuse, tortoisemerge and opendiff
+       diffuse, tortoisemerge, opendiff, p4merge and araxis.
 +
-If a merge resolution program is not specified, 'git-mergetool'
+If a merge resolution program is not specified, 'git mergetool'
 will use the configuration variable `merge.tool`.  If the
-configuration variable `merge.tool` is not set, 'git-mergetool'
+configuration variable `merge.tool` is not set, 'git mergetool'
 will pick a suitable default.
 +
 You can explicitly provide a full path to the tool by setting the
 configuration variable `mergetool.<tool>.path`. For example, you
 can configure the absolute path to kdiff3 by setting
-`mergetool.kdiff3.path`. Otherwise, 'git-mergetool' assumes the
+`mergetool.kdiff3.path`. Otherwise, 'git mergetool' assumes the
 tool is available in PATH.
 +
 Instead of running one of the known merge tool programs,
-'git-mergetool' can be customized to run an alternative program
+'git mergetool' can be customized to run an alternative program
 by specifying the command line to invoke in a configuration
 variable `mergetool.<tool>.cmd`.
 +
-When 'git-mergetool' is invoked with this tool (either through the
+When 'git mergetool' is invoked with this tool (either through the
 `-t` or `--tool` option or the `merge.tool` configuration
 variable) the configured command line will be invoked with `$BASE`
 set to the name of a temporary file containing the common base for
@@ -59,7 +59,7 @@ merge resolution.
 If the custom merge tool correctly indicates the success of a
 merge resolution with its exit code, then the configuration
 variable `mergetool.<tool>.trustExitCode` can be set to `true`.
-Otherwise, 'git-mergetool' will prompt the user to indicate the
+Otherwise, 'git mergetool' will prompt the user to indicate the
 success of the resolution after the custom tool has exited.
 
 -y::
@@ -72,6 +72,16 @@ success of the resolution after the custom tool has exited.
        This is the default behaviour; the option is provided to
        override any configuration settings.
 
+TEMPORARY FILES
+---------------
+`git mergetool` creates `*.orig` backup files while resolving merges.
+These are safe to remove once a file has been merged and its
+`git mergetool` session has completed.
+
+Setting the `mergetool.keepBackup` configuration variable to `false`
+causes `git mergetool` to automatically remove the backup as files
+are successfully merged.
+
 Author
 ------
 Written by Theodore Y Ts'o <tytso@mit.edu>
index af19f06ed738bdecc7ab9a72a5c9a216b816f4c2..81e3326772d94464708cc2037715e1e62eae5f11 100644 (file)
@@ -8,12 +8,13 @@ git-mktree - Build a tree-object from ls-tree formatted text
 
 SYNOPSIS
 --------
-'git mktree' [-z]
+'git mktree' [-z] [--missing] [--batch]
 
 DESCRIPTION
 -----------
-Reads standard input in non-recursive `ls-tree` output format,
-and creates a tree object.  The object name of the tree object
+Reads standard input in non-recursive `ls-tree` output format, and creates
+a tree object.  The order of the tree entries is normalised by mktree so
+pre-sorting the input is not required.  The object name of the tree object
 built is written to the standard output.
 
 OPTIONS
@@ -21,6 +22,18 @@ OPTIONS
 -z::
        Read the NUL-terminated `ls-tree -z` output instead.
 
+--missing::
+       Allow missing objects.  The default behaviour (without this option)
+       is to verify that each tree entry's sha1 identifies an existing
+       object.  This option has no effect on the treatment of gitlink entries
+       (aka "submodules") which are always allowed to be missing.
+
+--batch::
+       Allow building of more than one tree object before exiting.  Each
+       tree is separated by as single blank line. The final new-line is
+       optional.  Note - if the '-z' option is used, lines are terminated
+       with NUL.
+
 Author
 ------
 Written by Junio C Hamano <gitster@pobox.com>
index 9c5660275b326661bf7dc9a5162e5177b8a62b0f..bdcb58526ec2e838949e079891a420802df477db 100644 (file)
@@ -28,6 +28,7 @@ committed.
 OPTIONS
 -------
 -f::
+--force::
        Force renaming or moving of a file even if the target exists
 -k::
         Skip move or rename actions which would lead to an error
index 7ca8a7b48cea191b58db93581f02fa4ff265acdc..2108237c36cead52b14cd9a593c1398ea717a231 100644 (file)
@@ -15,7 +15,7 @@ SYNOPSIS
 DESCRIPTION
 -----------
 Finds symbolic names suitable for human digestion for revisions given in any
-format parsable by 'git-rev-parse'.
+format parsable by 'git rev-parse'.
 
 
 OPTIONS
@@ -55,7 +55,7 @@ wrote you about that fantastic commit 33db5f4d9027a10e477ccf054b2c1ab94f74c85a.
 Of course, you look into the commit, but that only tells you what happened, but
 not the context.
 
-Enter 'git-name-rev':
+Enter 'git name-rev':
 
 ------------
 % git name-rev 33db5f4d9027a10e477ccf054b2c1ab94f74c85a
diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
new file mode 100644 (file)
index 0000000..2981d8c
--- /dev/null
@@ -0,0 +1,287 @@
+git-notes(1)
+============
+
+NAME
+----
+git-notes - Add or inspect object notes
+
+SYNOPSIS
+--------
+[verse]
+'git notes' [list [<object>]]
+'git notes' add [-f] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
+'git notes' copy [-f] ( --stdin | <from-object> <to-object> )
+'git notes' append [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
+'git notes' edit [<object>]
+'git notes' show [<object>]
+'git notes' remove [<object>]
+'git notes' prune [-n | -v]
+
+
+DESCRIPTION
+-----------
+Adds, removes, or reads notes attached to objects, without touching
+the objects themselves.
+
+By default, notes are saved to and read from `refs/notes/commits`, but
+this default can be overridden.  See the OPTIONS, CONFIGURATION, and
+ENVIRONMENT sections below.  If this ref does not exist, it will be
+quietly created when it is first needed to store a note.
+
+A typical use of notes is to supplement a commit message without
+changing the commit itself. Notes can be shown by 'git log' along with
+the original commit message. To distinguish these notes from the
+message stored in the commit object, the notes are indented like the
+message, after an unindented line saying "Notes (<refname>):" (or
+"Notes:" for `refs/notes/commits`).
+
+To change which notes are shown by 'git log', see the
+"notes.displayRef" configuration in linkgit:git-log[1].
+
+See the "notes.rewrite.<command>" configuration for a way to carry
+notes across commands that rewrite commits.
+
+
+SUBCOMMANDS
+-----------
+
+list::
+       List the notes object for a given object. If no object is
+       given, show a list of all note objects and the objects they
+       annotate (in the format "<note object> <annotated object>").
+       This is the default subcommand if no subcommand is given.
+
+add::
+       Add notes for a given object (defaults to HEAD). Abort if the
+       object already has notes (use `-f` to overwrite an
+       existing note).
+
+copy::
+       Copy the notes for the first object onto the second object.
+       Abort if the second object already has notes, or if the first
+       object has none (use -f to overwrite existing notes to the
+       second object). This subcommand is equivalent to:
+       `git notes add [-f] -C $(git notes list <from-object>) <to-object>`
++
+In `\--stdin` mode, take lines in the format
++
+----------
+<from-object> SP <to-object> [ SP <rest> ] LF
+----------
++
+on standard input, and copy the notes from each <from-object> to its
+corresponding <to-object>.  (The optional `<rest>` is ignored so that
+the command can read the input given to the `post-rewrite` hook.)
+
+append::
+       Append to the notes of an existing object (defaults to HEAD).
+       Creates a new notes object if needed.
+
+edit::
+       Edit the notes for a given object (defaults to HEAD).
+
+show::
+       Show the notes for a given object (defaults to HEAD).
+
+remove::
+       Remove the notes for a given object (defaults to HEAD).
+       This is equivalent to specifying an empty note message to
+       the `edit` subcommand.
+
+prune::
+       Remove all notes for non-existing/unreachable objects.
+
+OPTIONS
+-------
+-f::
+--force::
+       When adding notes to an object that already has notes,
+       overwrite the existing notes (instead of aborting).
+
+-m <msg>::
+--message=<msg>::
+       Use the given note message (instead of prompting).
+       If multiple `-m` options are given, their values
+       are concatenated as separate paragraphs.
+       Lines starting with `#` and empty lines other than a
+       single line between paragraphs will be stripped out.
+
+-F <file>::
+--file=<file>::
+       Take the note message from the given file.  Use '-' to
+       read the note message from the standard input.
+       Lines starting with `#` and empty lines other than a
+       single line between paragraphs will be stripped out.
+
+-C <object>::
+--reuse-message=<object>::
+       Take the note message from the given blob object (for
+       example, another note).
+
+-c <object>::
+--reedit-message=<object>::
+       Like '-C', but with '-c' the editor is invoked, so that
+       the user can further edit the note message.
+
+--ref <ref>::
+       Manipulate the notes tree in <ref>.  This overrides
+       'GIT_NOTES_REF' and the "core.notesRef" configuration.  The ref
+       is taken to be in `refs/notes/` if it is not qualified.
+
+-n::
+--dry-run::
+       Do not remove anything; just report the object names whose notes
+       would be removed.
+
+-v::
+--verbose::
+       Report all object names whose notes are removed.
+
+
+DISCUSSION
+----------
+
+Commit notes are blobs containing extra information about an object
+(usually information to supplement a commit's message).  These blobs
+are taken from notes refs.  A notes ref is usually a branch which
+contains "files" whose paths are the object names for the objects
+they describe, with some directory separators included for performance
+reasons footnote:[Permitted pathnames have the form
+'ab'`/`'cd'`/`'ef'`/`'...'`/`'abcdef...': a sequence of directory
+names of two hexadecimal digits each followed by a filename with the
+rest of the object ID.].
+
+Every notes change creates a new commit at the specified notes ref.
+You can therefore inspect the history of the notes by invoking, e.g.,
+`git log -p notes/commits`.  Currently the commit message only records
+which operation triggered the update, and the commit authorship is
+determined according to the usual rules (see linkgit:git-commit[1]).
+These details may change in the future.
+
+It is also permitted for a notes ref to point directly to a tree
+object, in which case the history of the notes can be read with
+`git log -p -g <refname>`.
+
+
+EXAMPLES
+--------
+
+You can use notes to add annotations with information that was not
+available at the time a commit was written.
+
+------------
+$ git notes add -m 'Tested-by: Johannes Sixt <j6t@kdbg.org>' 72a144e2
+$ git show -s 72a144e
+[...]
+    Signed-off-by: Junio C Hamano <gitster@pobox.com>
+
+Notes:
+    Tested-by: Johannes Sixt <j6t@kdbg.org>
+------------
+
+In principle, a note is a regular Git blob, and any kind of
+(non-)format is accepted.  You can binary-safely create notes from
+arbitrary files using 'git hash-object':
+
+------------
+$ cc *.c
+$ blob=$(git hash-object -w a.out)
+$ git notes --ref=built add -C "$blob" HEAD
+------------
+
+Of course, it doesn't make much sense to display non-text-format notes
+with 'git log', so if you use such notes, you'll probably need to write
+some special-purpose tools to do something useful with them.
+
+
+CONFIGURATION
+-------------
+
+core.notesRef::
+       Notes ref to read and manipulate instead of
+       `refs/notes/commits`.  Must be an unabbreviated ref name.
+       This setting can be overridden through the environment and
+       command line.
+
+notes.displayRef::
+       Which ref (or refs, if a glob or specified more than once), in
+       addition to the default set by `core.notesRef` or
+       'GIT_NOTES_REF', to read notes from when showing commit
+       messages with the 'git log' family of commands.
+       This setting can be overridden on the command line or by the
+       'GIT_NOTES_DISPLAY_REF' environment variable.
+       See linkgit:git-log[1].
+
+notes.rewrite.<command>::
+       When rewriting commits with <command> (currently `amend` or
+       `rebase`), if this variable is `false`, git will not copy
+       notes from the original to the rewritten commit.  Defaults to
+       `true`.  See also "`notes.rewriteRef`" below.
++
+This setting can be overridden by the 'GIT_NOTES_REWRITE_REF'
+environment variable.
+
+notes.rewriteMode::
+       When copying notes during a rewrite, what to do if the target
+       commit already has a note.  Must be one of `overwrite`,
+       `concatenate`, and `ignore`.  Defaults to `concatenate`.
++
+This setting can be overridden with the `GIT_NOTES_REWRITE_MODE`
+environment variable.
+
+notes.rewriteRef::
+       When copying notes during a rewrite, specifies the (fully
+       qualified) ref whose notes should be copied.  May be a glob,
+       in which case notes in all matching refs will be copied.  You
+       may also specify this configuration several times.
++
+Does not have a default value; you must configure this variable to
+enable note rewriting.
++
+Can be overridden with the 'GIT_NOTES_REWRITE_REF' environment variable.
+
+
+ENVIRONMENT
+-----------
+
+'GIT_NOTES_REF'::
+       Which ref to manipulate notes from, instead of `refs/notes/commits`.
+       This overrides the `core.notesRef` setting.
+
+'GIT_NOTES_DISPLAY_REF'::
+       Colon-delimited list of refs or globs indicating which refs,
+       in addition to the default from `core.notesRef` or
+       'GIT_NOTES_REF', to read notes from when showing commit
+       messages.
+       This overrides the `notes.displayRef` setting.
++
+A warning will be issued for refs that do not exist, but a glob that
+does not match any refs is silently ignored.
+
+'GIT_NOTES_REWRITE_MODE'::
+       When copying notes during a rewrite, what to do if the target
+       commit already has a note.
+       Must be one of `overwrite`, `concatenate`, and `ignore`.
+       This overrides the `core.rewriteMode` setting.
+
+'GIT_NOTES_REWRITE_REF'::
+       When rewriting commits, which notes to copy from the original
+       to the rewritten commit.  Must be a colon-delimited list of
+       refs or globs.
++
+If not set in the environment, the list of notes to copy depends
+on the `notes.rewrite.<command>` and `notes.rewriteRef` settings.
+
+
+Author
+------
+Written by Johannes Schindelin <johannes.schindelin@gmx.de> and
+Johan Herland <johan@herland.net>
+
+Documentation
+-------------
+Documentation by Johannes Schindelin and Johan Herland
+
+GIT
+---
+Part of the linkgit:git[7] suite
index 7d4c1a75562de80aa80dfbfa665330b14ec948c6..8ed09c0b3cfc0fff2dbb8907fdc00e027c2c6d33 100644 (file)
@@ -9,9 +9,11 @@ git-pack-objects - Create a packed archive of objects
 SYNOPSIS
 --------
 [verse]
-'git pack-objects' [-q] [--no-reuse-delta] [--delta-base-offset] [--non-empty]
-       [--local] [--incremental] [--window=N] [--depth=N] [--all-progress]
-       [--revs [--unpacked | --all]*] [--stdout | base-name] < object-list
+'git pack-objects' [-q | --progress | --all-progress] [--all-progress-implied]
+       [--no-reuse-delta] [--delta-base-offset] [--non-empty]
+       [--local] [--incremental] [--window=N] [--depth=N]
+       [--revs [--unpacked | --all]*] [--stdout | base-name]
+       [--keep-true-parents] < object-list
 
 
 DESCRIPTION
@@ -19,27 +21,28 @@ 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 self contained so that it can be unpacked without
-any further information, but for fast, random access to the objects
-in the pack, a pack index file (.idx) will be generated.
+A packed archive is an efficient way to transfer a set of objects
+between two repositories as well as an access efficient archival
+format.  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.
 
-Placing both in the pack/ subdirectory of $GIT_OBJECT_DIRECTORY (or
+The packed archive format (.pack) is designed to be self-contained
+so that it can be unpacked without any further information. Therefore,
+each object that a delta depends upon must be present within the pack.
+
+A pack index file (.idx) is generated for fast, random access to the
+objects in the pack. Placing both the index file (.idx) and the packed
+archive (.pack) 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.
+enables git to read from the pack archive.
 
-The 'git-unpack-objects' command can read the packed archive and
+The '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.
 
-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
 -------
@@ -59,7 +62,7 @@ base-name::
 --revs::
        Read the revision arguments from the standard input, instead of
        individual object names.  The revision arguments are processed
-       the same way as 'git-rev-list' with the `--objects` flag
+       the same way as 'git rev-list' with the `--objects` flag
        uses its `commit` arguments to build the list of objects it
        outputs.  The objects on the resulting list are packed.
 
@@ -71,7 +74,7 @@ base-name::
 --all::
        This implies `--revs`.  In addition to the list of
        revision arguments read from the standard input, pretend
-       as if all refs under `$GIT_DIR/refs` are specified to be
+       as if all refs under `refs/` are specified to be
        included.
 
 --include-tag::
@@ -103,26 +106,26 @@ base-name::
        `--window-memory=0` makes memory usage unlimited, which is the
        default.
 
---max-pack-size=<n>::
-       Maximum size of each output packfile, expressed in MiB.
+--max-pack-size=[N]::
+       Maximum size of each output pack file. The size can be suffixed with
+       "k", "m", or "g". The minimum size allowed is limited to 1 MiB.
        If specified,  multiple packfiles may be created.
        The default is unlimited, unless the config variable
        `pack.packSizeLimit` is set.
 
 --honor-pack-keep::
        This flag causes an object already in a local pack that
-       has a .keep file to be ignored, even if it appears in the
-       standard input.
+       has a .keep file to be ignored, even if it it would have
+       otherwise been packed.
 
 --incremental::
-       This flag causes an object already in a pack ignored
-       even if it appears in the standard input.
+       This flag causes an object already in a pack to be ignored
+       even if it would have otherwise been packed.
 
 --local::
-       This flag is similar to `--incremental`; instead of
-       ignoring all packed objects, it only ignores objects
-       that are packed and/or not in the local object store
-       (i.e. borrowed from an alternate).
+       This flag causes an object that is borrowed from an alternate
+       object store to be ignored even if it would have otherwise been
+       packed.
 
 --non-empty::
         Only create a packed archive if it would contain at
@@ -136,7 +139,7 @@ base-name::
 
 --all-progress::
        When --stdout is specified then progress report is
-       displayed during the object count and deltification phases
+       displayed during the object count and compression phases
        but inhibited during the write-out phase. The reason is
        that in some cases the output stream is directly linked
        to another command which may wish to display progress
@@ -145,6 +148,11 @@ base-name::
        report for the write-out phase as well even if --stdout is
        used.
 
+--all-progress-implied::
+       This is used to imply --all-progress whenever progress display
+       is activated.  Unlike --all-progress this flag doesn't actually
+       force any progress display by itself.
+
 -q::
        This flag makes the command not to report its progress
        on the standard error stream.
@@ -171,11 +179,21 @@ base-name::
        Add --no-reuse-object if you want to force a uniform compression
        level on all data no matter the source.
 
+--thin::
+       Create a "thin" pack by omitting the common objects between a
+       sender and a receiver in order to reduce network transfer. This
+       option only makes sense in conjunction with --stdout.
++
+Note: A thin pack violates the packed archive format by omitting
+required objects and is thus unusable by git without making it
+self-contained. Use `git index-pack --fix-thin`
+(see linkgit:git-index-pack[1]) to restore the self-contained property.
+
 --delta-base-offset::
        A packed archive can express base object of a delta as
        either 20-byte object name or as an offset in the
        stream, but older version of git does not understand the
-       latter.  By default, 'git-pack-objects' only uses the
+       latter.  By default, 'git pack-objects' only uses the
        former format for better compatibility.  This option
        allows the command to use the latter format for
        compactness.  Depending on the average delta chain
@@ -197,6 +215,10 @@ base-name::
        to force the version for the generated pack index, and to force
        64-bit index entries on objects located above the given offset.
 
+--keep-true-parents::
+       With this option, parents that are hidden by grafts are packed
+       nevertheless.
+
 
 Author
 ------
index 5f9435e59b49fec1e37c65f1bfdc38be3704c4e5..d0607879db7ffc125e3a3aad2046b5e77730dcd6 100644 (file)
@@ -16,7 +16,7 @@ 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
+'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.
index cd43069874d59504627211e011250a3554aeee5a..39d9daa7e00b97ddcca8f3d25d7376234c57c246 100644 (file)
@@ -17,26 +17,6 @@ routines to parse files under $GIT_DIR/remotes/ and
 $GIT_DIR/branches/ and configuration variables that are related
 to fetching, pulling and pushing.
 
-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/`, `$GIT_DIR/branches/`, or `remote.*.fetch`
-       configuration.
-
-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.
index 253fc0fc255a0f82778d120647031eaa66eb647a..4dae1390a5c663867b93e1dce6cd4d2bf59e84ab 100644 (file)
@@ -18,7 +18,7 @@ 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
+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 strings.  The first
 string is the patch ID, and the second string is the commit ID.
index 8282a5e82b6e897ac501ef05c982d5e69415363f..87dacd797f6e36d47024bc0d2ebc82bb0b5bb387 100644 (file)
@@ -12,7 +12,7 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-This command is deprecated; use 'git-ls-remote' instead.
+This command is deprecated; use 'git ls-remote' instead.
 
 OPTIONS
 -------
index b5f26cee132622185457d92522fb932302dec97d..abfc6b6ead534311d8a29696c497ecf94ce4fd1a 100644 (file)
@@ -8,7 +8,7 @@ git-prune-packed - Remove extra objects that are already in pack files
 
 SYNOPSIS
 --------
-'git prune-packed' [-n] [-q]
+'git prune-packed' [-n|--dry-run] [-q|--quiet]
 
 
 DESCRIPTION
@@ -28,10 +28,12 @@ disk storage, etc.
 OPTIONS
 -------
 -n::
+--dry-run::
         Don't actually remove any objects, only show those that would have been
         removed.
 
 -q::
+--quiet::
        Squelch the progress indicator.
 
 Author
index da6055d4b8cf78aff16fa553e684b0b3ed57138e..4d673a56864ca88de0e98f54a6de469e99ea0f44 100644 (file)
@@ -8,21 +8,21 @@ git-prune - Prune all unreachable objects from the object database
 
 SYNOPSIS
 --------
-'git-prune' [-n] [-v] [--expire <expire>] [--] [<head>...]
+'git prune' [-n] [-v] [--expire <expire>] [--] [<head>...]
 
 DESCRIPTION
 -----------
 
-NOTE: In most cases, users should run 'git-gc', which calls
-'git-prune'. See the section "NOTES", below.
+NOTE: In most cases, users should run 'git gc', which calls
+'git prune'. See the section "NOTES", below.
 
-This runs 'git-fsck --unreachable' using all the refs
-available in `$GIT_DIR/refs`, optionally with additional set of
+This runs 'git fsck --unreachable' using all the refs
+available in `refs/`, optionally with additional set of
 objects specified on the command line, and prunes all unpacked
 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'.
+running 'git prune-packed'.
 
 Note that unreachable, packed objects will remain.  If this is
 not desired, see linkgit:git-repack[1].
@@ -31,10 +31,12 @@ OPTIONS
 -------
 
 -n::
+--dry-run::
        Do not remove anything; just report what it would
        remove.
 
 -v::
+--verbose::
        Report all removed objects.
 
 \--::
@@ -62,12 +64,12 @@ $ git prune $(cd ../another && $(git rev-parse --all))
 Notes
 -----
 
-In most cases, users will not need to call 'git-prune' directly, but
-should instead call 'git-gc', which handles pruning along with
+In most cases, users will not need to call 'git prune' directly, but
+should instead call 'git gc', which handles pruning along with
 many other housekeeping tasks.
 
 For a description of which objects are considered for pruning, see
-'git-fsck''s --unreachable option.
+'git fsck''s --unreachable option.
 
 SEE ALSO
 --------
index 7578623edba9e2ddc5232f1a981bcb297182638d..c50f7dcb890139d829bae51c30b513d54fd7761e 100644 (file)
@@ -8,24 +8,85 @@ git-pull - Fetch from and merge with another repository or a local branch
 
 SYNOPSIS
 --------
-'git pull' <options> <repository> <refspec>...
+'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.
-With `--rebase`, calls 'git-rebase' instead of 'git-merge'.
 
-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.
+Incorporates changes from a remote repository into the current
+branch.  In its default mode, `git pull` is shorthand for
+`git fetch` followed by `git merge FETCH_HEAD`.
 
-Also note that options meant for 'git-pull' itself and underlying
-'git-merge' must be given before the options meant for 'git-fetch'.
+More precisely, 'git pull' runs 'git fetch' with the given
+parameters and calls 'git merge' to merge the retrieved branch
+heads into the current branch.
+With `--rebase`, it runs 'git rebase' instead of 'git merge'.
+
+<repository> should be the name of a remote repository as
+passed to linkgit:git-fetch[1].  <refspec> can name an
+arbitrary remote ref (for example, the name of a tag) or even
+a collection of refs with corresponding remote tracking branches
+(e.g., refs/heads/*:refs/remotes/origin/*), but usually it is
+the name of a branch in the remote repository.
+
+Default values for <repository> and <branch> are read from the
+"remote" and "merge" configuration for the current branch
+as set by linkgit:git-branch[1] `--track`.
+
+Assume the following history exists and the current branch is
+"`master`":
+
+------------
+         A---B---C master on origin
+        /
+    D---E---F---G master
+------------
+
+Then "`git pull`" will fetch and replay the changes from the remote
+`master` branch since it diverged from the local `master` (i.e., `E`)
+until its current commit (`C`) on top of `master` and record the
+result in a new commit along with the names of the two parent commits
+and a log message from the user describing the changes.
+
+------------
+         A---B---C remotes/origin/master
+        /         \
+    D---E---F---G---H master
+------------
+
+See linkgit:git-merge[1] for details, including how conflicts
+are presented and handled.
+
+In git 1.7.0 or later, to cancel a conflicting merge, use
+`git reset --merge`.  *Warning*: In older versions of git, running 'git pull'
+with uncommitted changes is discouraged: while possible, it leaves you
+in a state that may be hard to back out of in the case of a conflict.
+
+If any of the remote changes overlap with local uncommitted changes,
+the merge will be automatically cancelled and the work tree untouched.
+It is generally best to get any local changes in working order before
+pulling or stash them away with linkgit:git-stash[1].
 
 OPTIONS
 -------
+
+Options meant for 'git pull' itself and the underlying 'git merge'
+must be given before the options meant for 'git fetch'.
+
+-q::
+--quiet::
+       This is passed to both underlying git-fetch to squelch reporting of
+       during transfer, and underlying git-merge to squelch output during
+       merging.
+
+-v::
+--verbose::
+       Pass --verbose to git-fetch and git-merge.
+
+Options related to merging
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
 include::merge-options.txt[]
 
 :git-pull: 1
@@ -47,6 +108,9 @@ unless you have read linkgit:git-rebase[1] carefully.
 --no-rebase::
        Override earlier --rebase.
 
+Options related to fetching
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
 include::fetch-options.txt[]
 
 include::pull-fetch-param.txt[]
@@ -131,58 +195,17 @@ $ git pull origin next
 ------------------------------------------------
 +
 This leaves a copy of `next` temporarily in FETCH_HEAD, but
-does not update any remote-tracking branches.
-
-* Bundle local branch `fixes` and `enhancements` on top of
-  the current branch, making an Octopus merge:
-+
-------------------------------------------------
-$ git pull . fixes enhancements
-------------------------------------------------
-+
-This `git pull .` syntax is equivalent to `git merge`.
-
-* Merge local branch `obsolete` into the current branch, using `ours`
-  merge strategy:
-+
-------------------------------------------------
-$ git pull -s ours . obsolete
-------------------------------------------------
-
-* Merge local branch `maint` into the current branch, but do not make
-  a commit automatically:
+does not update any remote-tracking branches. Using remote-tracking
+branches, the same can be done by invoking fetch and merge:
 +
 ------------------------------------------------
-$ git pull --no-commit . maint
+$ git fetch origin
+$ git merge origin/next
 ------------------------------------------------
-+
-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:
-+
-------------------------------------------------
-$ git checkout master
-$ git fetch origin +pu:pu maint:tmp
-$ git pull . tmp
-------------------------------------------------
-+
-This updates (or creates, as necessary) branches `pu` and `tmp` in
-the local repository by fetching from the branches (respectively)
-`pu` and `maint` from the remote repository.
-+
-The `pu` branch will be updated even if it is does not fast-forward;
-the others will not be.
-+
-The final command then merges the newly fetched `tmp` into master.
 
 
 If you tried a pull which resulted in a complex conflicts and
-would want to start over, you can recover with 'git-reset'.
+would want to start over, you can recover with 'git reset'.
 
 
 SEE ALSO
index fd53c49fb886b196ba79e40c4c9c4efe0dae5432..020955ff5a8635e385208a6a89b1ad71125fb205 100644 (file)
@@ -9,9 +9,9 @@ git-push - Update remote refs along with associated objects
 SYNOPSIS
 --------
 [verse]
-'git push' [--all | --mirror | --tags] [--dry-run] [--receive-pack=<git-receive-pack>]
-          [--repo=<repository>] [-f | --force] [-v | --verbose]
-          [<repository> <refspec>...]
+'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
+          [--repo=<repository>] [-f | --force] [-v | --verbose] [-u | --set-upstream]
+          [<repository> [<refspec>...]]
 
 DESCRIPTION
 -----------
@@ -41,7 +41,7 @@ OPTIONS[[OPTIONS]]
 +
 The <src> is often the name of the branch you would want to push, but
 it can be any arbitrary "SHA-1 expression", such as `master~4` or
-`HEAD` (see linkgit:git-rev-parse[1]).
+`HEAD` (see linkgit:gitrevisions[1]).
 +
 The <dst> tells which ref on the remote side is updated with this
 push. Arbitrary expressions cannot be used here, an actual ref must
@@ -50,9 +50,9 @@ updated.
 +
 The object referenced by <src> is used to update the <dst> reference
 on the remote side, but by default this is only allowed if the
-update can fast forward <dst>.  By having the optional leading `{plus}`,
+update can fast-forward <dst>.  By having the optional leading `{plus}`,
 you can tell git to update the <dst> ref even when the update is not a
-fast forward.  This does *not* attempt to merge <src> into <dst>.  See
+fast-forward.  This does *not* attempt to merge <src> into <dst>.  See
 EXAMPLES below for details.
 +
 `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
@@ -60,7 +60,7 @@ EXAMPLES below for details.
 Pushing an empty <src> allows you to delete the <dst> ref from
 the remote repository.
 +
-The special refspec `:` (or `{plus}:` to allow non-fast forward updates)
+The special refspec `:` (or `{plus}:` to allow non-fast-forward updates)
 directs git to push "matching" branches: for every branch that exists on
 the local side, the remote side is updated if a branch of the same name
 already exists on the remote side.  This is the default operation mode
@@ -69,11 +69,11 @@ nor in any Push line of the corresponding remotes file---see below).
 
 --all::
        Instead of naming each ref to push, specifies that all
-       refs under `$GIT_DIR/refs/heads/` be pushed.
+       refs under `refs/heads/` be pushed.
 
 --mirror::
        Instead of naming each ref to push, specifies that all
-       refs under `$GIT_DIR/refs/` (which includes but is not
+       refs under `refs/` (which includes but is not
        limited to `refs/heads/`, `refs/remotes/`, and `refs/tags/`)
        be mirrored to the remote repository.  Newly created local
        refs will be pushed to the remote end, locally updated refs
@@ -82,11 +82,21 @@ nor in any Push line of the corresponding remotes file---see below).
        if the configuration option `remote.<remote>.mirror` is
        set.
 
+-n::
 --dry-run::
        Do everything except actually send the updates.
 
+--porcelain::
+       Produce machine-readable output.  The output status line for each ref
+       will be tab-separated and sent to stdout instead of stderr.  The full
+       symbolic names of the refs will be given.
+
+--delete::
+       All listed refs are deleted from the remote repository. This is
+       the same as prefixing all refs with a colon.
+
 --tags::
-       All refs under `$GIT_DIR/refs/tags` are pushed, in
+       All refs under `refs/tags` are pushed, in
        addition to refspecs explicitly listed on the command
        line.
 
@@ -106,7 +116,7 @@ nor in any Push line of the corresponding remotes file---see below).
 
 --repo=<repository>::
        This option is only relevant if no <repository> argument is
-       passed in the invocation. In this case, 'git-push' derives the
+       passed in the invocation. In this case, 'git push' derives the
        remote name from the current branch: If it tracks a remote
        branch, then that remote repository is pushed to. Otherwise,
        the name "origin" is used. For this latter case, this option
@@ -120,18 +130,38 @@ git push --repo=public  #2
 +
 is that #1 always pushes to "public" whereas #2 pushes to "public"
 only if the current branch does not track a remote branch. This is
-useful if you write an alias or script around 'git-push'.
+useful if you write an alias or script around 'git push'.
+
+-u::
+--set-upstream::
+       For every branch that is up to date or successfully pushed, add
+       upstream (tracking) reference, used by argument-less
+       linkgit:git-pull[1] and other commands. For more information,
+       see 'branch.<name>.merge' in linkgit:git-config[1].
 
 --thin::
 --no-thin::
-       These options are passed to 'git-send-pack'.  Thin
-       transfer spends extra cycles to minimize the number of
-       objects to be sent and meant to be used on slower connection.
+       These options are passed to linkgit:git-send-pack[1]. A thin transfer
+       significantly reduces the amount of sent data when the sender and
+       receiver share many of the same objects in common. The default is
+       \--thin.
+
+-q::
+--quiet::
+       Suppress all output, including the listing of updated refs,
+       unless an error occurs. Progress is not reported to the standard
+       error stream.
 
 -v::
 --verbose::
        Run verbosely.
 
+--progress::
+       Progress status is reported on the standard error stream
+       by default when it is attached to a terminal, unless -q
+       is specified. This flag forces progress status even if the
+       standard error stream is not directed to a terminal.
+
 include::urls-remotes.txt[]
 
 OUTPUT
@@ -148,27 +178,51 @@ representing the status of a single ref. Each line is of the form:
  <flag> <summary> <from> -> <to> (<reason>)
 -------------------------------
 
+If --porcelain is used, then each line of the output is of the form:
+
+-------------------------------
+ <flag> \t <from>:<to> \t <summary> (<reason>)
+-------------------------------
+
+The status of up-to-date refs is shown only if --porcelain or --verbose
+option is used.
+
 flag::
-       A single character indicating the status of the ref. This is
-       blank for a successfully pushed ref, `!` for a ref that was
-       rejected or failed to push, and '=' for a ref that was up to
-       date and did not need pushing (note that the status of up to
-       date refs is shown only when `git push` is running verbosely).
+       A single character indicating the status of the ref:
+(space);; for a successfully pushed fast-forward;
+`{plus}`;; for a successful forced update;
+`-`;; for a successfully deleted ref;
+`*`;; for a successfully pushed new ref;
+`!`;; for a ref that was rejected or failed to push; and
+`=`;; for a ref that was up to date and did not need pushing.
 
 summary::
        For a successfully pushed ref, the summary shows the old and new
        values of the ref in a form suitable for using as an argument to
        `git log` (this is `<old>..<new>` in most cases, and
-       `<old>...<new>` for forced non-fast forward updates). For a
-       failed update, more details are given for the failure.
-       The string `rejected` indicates that git did not try to send the
-       ref at all (typically because it is not a fast forward). The
-       string `remote rejected` indicates that the remote end refused
-       the update; this rejection is typically caused by a hook on the
-       remote side. The string `remote failure` indicates that the
-       remote end did not report the successful update of the ref
-       (perhaps because of a temporary error on the remote side, a
-       break in the network connection, or other transient error).
+       `<old>\...<new>` for forced non-fast-forward updates).
++
+For a failed update, more details are given:
++
+--
+rejected::
+       Git did not try to send the ref at all, typically because it
+       is not a fast-forward and you did not force the update.
+
+remote rejected::
+       The remote end refused the update.  Usually caused by a hook
+       on the remote side, or because the remote repository has one
+       of the following safety options in effect:
+       `receive.denyCurrentBranch` (for pushes to the checked out
+       branch), `receive.denyNonFastForwards` (for forced
+       non-fast-forward updates), `receive.denyDeletes` or
+       `receive.denyDeleteCurrent`.  See linkgit:git-config[1].
+
+remote failure::
+       The remote end did not report the successful update of the ref,
+       perhaps because of a temporary error on the remote side, a
+       break in the network connection, or other transient error.
+--
 
 from::
        The name of the local ref being pushed, minus its
@@ -184,6 +238,92 @@ reason::
        refs, no explanation is needed. For a failed ref, the reason for
        failure is described.
 
+Note about fast-forwards
+------------------------
+
+When an update changes a branch (or more in general, a ref) that used to
+point at commit A to point at another commit B, it is called a
+fast-forward update if and only if B is a descendant of A.
+
+In a fast-forward update from A to B, the set of commits that the original
+commit A built on top of is a subset of the commits the new commit B
+builds on top of.  Hence, it does not lose any history.
+
+In contrast, a non-fast-forward update will lose history.  For example,
+suppose you and somebody else started at the same commit X, and you built
+a history leading to commit B while the other person built a history
+leading to commit A.  The history looks like this:
+
+----------------
+
+      B
+     /
+ ---X---A
+
+----------------
+
+Further suppose that the other person already pushed changes leading to A
+back to the original repository you two obtained the original commit X.
+
+The push done by the other person updated the branch that used to point at
+commit X to point at commit A.  It is a fast-forward.
+
+But if you try to push, you will attempt to update the branch (that
+now points at A) with commit B.  This does _not_ fast-forward.  If you did
+so, the changes introduced by commit A will be lost, because everybody
+will now start building on top of B.
+
+The command by default does not allow an update that is not a fast-forward
+to prevent such loss of history.
+
+If you do not want to lose your work (history from X to B) nor the work by
+the other person (history from X to A), you would need to first fetch the
+history from the repository, create a history that contains changes done
+by both parties, and push the result back.
+
+You can perform "git pull", resolve potential conflicts, and "git push"
+the result.  A "git pull" will create a merge commit C between commits A
+and B.
+
+----------------
+
+      B---C
+     /   /
+ ---X---A
+
+----------------
+
+Updating A with the resulting merge commit will fast-forward and your
+push will be accepted.
+
+Alternatively, you can rebase your change between X and B on top of A,
+with "git pull --rebase", and push the result back.  The rebase will
+create a new commit D that builds the change between X and B on top of
+A.
+
+----------------
+
+      B   D
+     /   /
+ ---X---A
+
+----------------
+
+Again, updating A with this commit will fast-forward and your push will be
+accepted.
+
+There is another common situation where you may encounter non-fast-forward
+rejection when you try to push, and it is possible even when you are
+pushing into a repository nobody else pushes into. After you push commit
+A yourself (in the first picture in this section), replace it with "git
+commit --amend" to produce commit B, and you try to push it out, because
+forgot that you have pushed A out already. In such a case, and only if
+you are certain that nobody in the meantime fetched your earlier commit A
+(and started building on top of it), you can run "git push --force" to
+overwrite it. In other words, "git push --force" is a method reserved for
+a case where you do mean to lose history.
+
+
 Examples
 --------
 
@@ -244,9 +384,9 @@ git push origin :experimental::
 
 git push origin {plus}dev:master::
        Update the origin repository's master branch with the dev branch,
-       allowing non-fast forward updates.  *This can leave unreferenced
+       allowing non-fast-forward updates.  *This can leave unreferenced
        commits dangling in the origin repository.*  Consider the
-       following situation, where a fast forward is not possible:
+       following situation, where a fast-forward is not possible:
 +
 ----
            o---o---o---A---B  origin/master
index d4037de5124010e9c90dcc97e8b64e6011dbed21..579e8d2f3ba54e393a60a8b4d8a4ad6a95cb79f1 100644 (file)
@@ -9,7 +9,7 @@ git-quiltimport - Applies a quilt patchset onto the current branch
 SYNOPSIS
 --------
 [verse]
-'git quiltimport' [--dry-run] [--author <author>] [--patches <dir>]
+'git quiltimport' [--dry-run | -n] [--author <author>] [--patches <dir>]
 
 
 DESCRIPTION
index 7160fa1536e3af2edbdfb6d1049f2145bf2b4f50..2e78da448f30ad260cb7683a5610f424d0c5ffea 100644 (file)
@@ -8,7 +8,10 @@ git-read-tree - Reads tree information into the index
 
 SYNOPSIS
 --------
-'git read-tree' (<tree-ish> | [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
+'git read-tree' [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>]
+               [-u [--exclude-per-directory=<gitignore>] | -i]]
+               [--index-output=<file>] [--no-sparse-checkout]
+               <tree-ish1> [<tree-ish2> [<tree-ish3>]]
 
 
 DESCRIPTION
@@ -22,8 +25,8 @@ 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.
+Trivial merges are done by 'git read-tree' itself.  Only conflicting paths
+will be in unmerged state when 'git read-tree' returns.
 
 OPTIONS
 -------
@@ -54,13 +57,13 @@ OPTIONS
        Show the progress of checking files out.
 
 --trivial::
-       Restrict three-way merge by 'git-read-tree' to happen
+       Restrict three-way merge by 'git read-tree' to happen
        only if there is no file-level merging required, instead
        of resolving merge for trivial cases and leaving
        conflicting files unresolved in the index.
 
 --aggressive::
-       Usually a three-way merge by 'git-read-tree' resolves
+       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
@@ -107,13 +110,17 @@ OPTIONS
        directories the index file and index output file are
        located in.
 
+--no-sparse-checkout::
+       Disable sparse checkout support even if `core.sparseCheckout`
+       is true.
+
 <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
+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.
@@ -121,18 +128,18 @@ provided.
 
 Single Tree Merge
 ~~~~~~~~~~~~~~~~~
-If only 1 tree is specified, 'git-read-tree' operates as if the user did not
+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
+given pathname, and the contents of the path match 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
+`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'.
+This is used to avoid unnecessary false hits when 'git diff-files' is
+run after 'git read-tree'.
 
 
 Two Tree Merge
@@ -141,46 +148,48 @@ 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).
+fast-forward situation).
 
-When two trees are specified, the user is telling 'git-read-tree'
+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;
+       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:
+Here are the "carry forward" rules, where "I" denotes the index,
+"clean" means that index and work tree coincide, and "exists"/"nothing"
+refer to the presence of a path in the specified commit:
 
-        I (index)           H        M        Result
+       I                   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 if "initial checkout"
+      nothing             nothing  nothing  (does not happen)
+      nothing             nothing  exists   use M
+      nothing             exists   nothing  remove path from index
+     3  nothing             exists   exists,  use M if "initial checkout",
                                     H == M   keep index otherwise
-                                    exists   fail
+                                    exists,  fail
                                     H != 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
+      yes   N/A   N/A     nothing  nothing  keep index
+      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
+      yes   N/A   yes     nothing  exists   keep index
+      no    N/A   yes     nothing  exists   keep index
+      yes   N/A   no      nothing  exists   fail
+      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)
+       clean (H==M)
        ------
      14 yes                 exists   exists   keep index
      15 no                  exists   exists   keep index
@@ -195,26 +204,26 @@ Here are the "carry forward" rules:
      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
+original index file.  If the entry is 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
+When this form of 'git read-tree' returns successfully, you can
+see which of the "local changes" that you made were carried forward by running
 `git diff-index --cached $M`.  Note that this does not
-necessarily match `git diff-index --cached $H` would have
+necessarily match what `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.
+output after the two-tree merge.
 
-Case #3 is slightly tricky and needs explanation.  The result from this
+Case 3 is slightly tricky and needs explanation.  The result from this
 rule logically should be to remove the path if the user staged the removal
 of the path and then switching to a new branch.  That however will prevent
 the initial checkout from happening, so the rule is modified to use M (new
-tree) only when the contents of the index is empty.  Otherwise the removal
+tree) only when the content of the index is empty.  Otherwise the removal
 of the path is kept as long as $H and $M are the same.
 
 3-Way Merge
@@ -222,7 +231,7 @@ of the path is kept as long as $H and $M are the same.
 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"
+However, when you do 'git read-tree' with three trees, the "stage"
 starts out at 1.
 
 This means that you can do
@@ -238,7 +247,7 @@ 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
+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":
 
@@ -254,7 +263,7 @@ a file that matches in all respects in the following states, 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
+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.
 
@@ -270,7 +279,7 @@ 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'.
+  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
@@ -294,8 +303,8 @@ populated.  Here is an outline of how the algorithm works:
     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
+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.
 
@@ -317,7 +326,7 @@ $ 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 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:
 
@@ -343,20 +352,73 @@ 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
+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'
+*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.
 
 
+Sparse checkout
+---------------
+
+"Sparse checkout" allows to sparsely populate working directory.
+It uses skip-worktree bit (see linkgit:git-update-index[1]) to tell
+Git whether a file on working directory is worth looking at.
+
+"git read-tree" and other merge-based commands ("git merge", "git
+checkout"...) can help maintaining skip-worktree bitmap and working
+directory update. `$GIT_DIR/info/sparse-checkout` is used to
+define the skip-worktree reference bitmap. When "git read-tree" needs
+to update working directory, it will reset skip-worktree bit in index
+based on this file, which uses the same syntax as .gitignore files.
+If an entry matches a pattern in this file, skip-worktree will be
+set on that entry. Otherwise, skip-worktree will be unset.
+
+Then it compares the new skip-worktree value with the previous one. If
+skip-worktree turns from unset to set, it will add the corresponding
+file back. If it turns from set to unset, that file will be removed.
+
+While `$GIT_DIR/info/sparse-checkout` is usually used to specify what
+files are in. You can also specify what files are _not_ in, using
+negate patterns. For example, to remove file "unwanted":
+
+----------------
+*
+!unwanted
+----------------
+
+Another tricky thing is fully repopulating working directory when you
+no longer want sparse checkout. You cannot just disable "sparse
+checkout" because skip-worktree are still in the index and you working
+directory is still sparsely populated. You should re-populate working
+directory with the `$GIT_DIR/info/sparse-checkout` file content as
+follows:
+
+----------------
+*
+----------------
+
+Then you can disable sparse checkout. Sparse checkout support in "git
+read-tree" and similar commands is disabled by default. You need to
+turn `core.sparseCheckout` on in order to have sparse checkout
+support.
+
+
+BUGS
+----
+In order to match a directory with $GIT_DIR/info/sparse-checkout,
+trailing slash must be used. The form without trailing slash, while
+works with .gitignore, does not work with sparse checkout.
+
+
 SEE ALSO
 --------
 linkgit:git-write-tree[1]; linkgit:git-ls-files[1];
index 3d5a066c31675e502eb027dde824d1966c9c0f09..30e5c0eb14470a5f72174b1071eac79c55fb0c41 100644 (file)
@@ -17,7 +17,7 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-If <branch> is specified, 'git-rebase' will perform an automatic
+If <branch> is specified, 'git rebase' will perform an automatic
 `git checkout <branch>` before doing anything else.  Otherwise
 it remains on the current branch.
 
@@ -170,8 +170,8 @@ This is useful if F and G were flawed in some way, or should not be
 part of topicA.  Note that the argument to --onto and the <upstream>
 parameter can be any valid commit-ish.
 
-In case of conflict, 'git-rebase' will stop at the first problematic commit
-and leave conflict markers in the tree.  You can use 'git-diff' to locate
+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
@@ -187,7 +187,7 @@ desired resolution, you can continue the rebasing process with
     git rebase --continue
 
 
-Alternatively, you can undo the 'git-rebase' with
+Alternatively, you can undo the 'git rebase' with
 
 
     git rebase --abort
@@ -199,6 +199,9 @@ rebase.stat::
        Whether to show a diffstat of what changed upstream since the last
        rebase. False by default.
 
+rebase.autosquash::
+       If set to true enable '--autosquash' option by default.
+
 OPTIONS
 -------
 <newbase>::
@@ -206,6 +209,10 @@ OPTIONS
        --onto option is not specified, the starting point is
        <upstream>.  May be any valid commit, and not just an
        existing branch name.
++
+As a special case, you may use "A\...B" as a shortcut for the
+merge base of A and B if there is exactly one merge base. You can
+leave out at most one of A and B, in which case it defaults to HEAD.
 
 <upstream>::
        Upstream branch to compare against.  May be any valid commit,
@@ -228,14 +235,34 @@ OPTIONS
        Use merging strategies to rebase.  When the recursive (default) merge
        strategy is used, this allows rebase to be aware of renames on the
        upstream side.
++
+Note that a rebase merge works by replaying each commit from the working
+branch on top of the <upstream> branch.  Because of this, when a merge
+conflict happens, the side reported as 'ours' is the so-far rebased
+series, starting with <upstream>, and 'theirs' is the working branch.  In
+other words, the sides are swapped.
 
 -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).  This implies --merge.
+       Use the given merge strategy.
+       If there is no `-s` option 'git merge-recursive' is used
+       instead.  This implies --merge.
++
+Because 'git rebase' replays each commit from the working branch
+on top of the <upstream> branch using the given strategy, using
+the 'ours' strategy simply discards all patches from the <branch>,
+which makes little sense.
+
+-X <strategy-option>::
+--strategy-option=<strategy-option>::
+       Pass the <strategy-option> through to the merge strategy.
+       This implies `\--merge` and, if no strategy has been
+       specified, `-s recursive`.  Note the reversal of 'ours' and
+       'theirs' as noted in above for the `-m` option.
+
+-q::
+--quiet::
+       Be quiet. Implies --no-stat.
 
 -v::
 --verbose::
@@ -261,19 +288,28 @@ OPTIONS
 -f::
 --force-rebase::
        Force the rebase even if the current branch is a descendant
-       of the commit you are rebasing onto.  Normally the command will
+       of the commit you are rebasing onto.  Normally non-interactive rebase will
        exit with the message "Current branch is up to date" in such a
        situation.
-
+       Incompatible with the --interactive option.
++
+You may find this (or --no-ff with an interactive rebase) helpful after
+reverting a topic branch merge, as this option recreates the topic branch with
+fresh commits so it can be remerged successfully without needing to "revert
+the reversion" (see the
+link:howto/revert-a-faulty-merge.txt[revert-a-faulty-merge How-To] for details).
+
+--ignore-whitespace::
 --whitespace=<option>::
-       This flag is passed to the 'git-apply' program
+       These flag are passed to the 'git apply' program
        (see linkgit:git-apply[1]) that applies the patch.
        Incompatible with the --interactive option.
 
 --committer-date-is-author-date::
 --ignore-date::
-       These flags are passed to 'git-am' to easily change the dates
+       These flags are passed to 'git am' to easily change the dates
        of the rebased commits (see linkgit:git-am[1]).
+       Incompatible with the --interactive option.
 
 -i::
 --interactive::
@@ -284,6 +320,11 @@ OPTIONS
 -p::
 --preserve-merges::
        Instead of ignoring merges, try to recreate them.
++
+This uses the `--interactive` machinery internally, but combining it
+with the `--interactive` option explicitly is generally not a good
+idea unless you know what you are doing (see BUGS below).
+
 
 --root::
        Rebase all commits reachable from <branch>, instead of
@@ -294,12 +335,39 @@ OPTIONS
        root commits will be rewritten to have <newbase> as parent
        instead.
 
+--autosquash::
+--no-autosquash::
+       When the commit log message begins with "squash! ..." (or
+       "fixup! ..."), and there is a commit whose title begins with
+       the same ..., automatically modify the todo list of rebase -i
+       so that the commit marked for squashing comes right after the
+       commit to be modified, and change the action of the moved
+       commit from `pick` to `squash` (or `fixup`).
++
+This option is only valid when the '--interactive' option is used.
++
+If the '--autosquash' option is enabled by default using the
+configuration variable `rebase.autosquash`, this option can be
+used to override and disable this setting.
+
+--no-ff::
+       With --interactive, cherry-pick all rebased commits instead of
+       fast-forwarding over the unchanged ones.  This ensures that the
+       entire history of the rebased branch is composed of new commits.
++
+Without --interactive, this is a synonym for --force-rebase.
++
+You may find this helpful after reverting a topic branch merge, as this option
+recreates the topic branch with fresh commits so it can be remerged
+successfully without needing to "revert the reversion" (see the
+link:howto/revert-a-faulty-merge.txt[revert-a-faulty-merge How-To] for details).
+
 include::merge-strategies.txt[]
 
 NOTES
 -----
 
-You should understand the implications of using 'git-rebase' on a
+You should understand the implications of using 'git rebase' on a
 repository that you share.  See also RECOVERING FROM UPSTREAM REBASE
 below.
 
@@ -355,27 +423,33 @@ pick fa1afe1 The oneline of the next commit
 ...
 -------------------------------------------
 
-The oneline descriptions are purely for your pleasure; 'git-rebase' will
+The oneline descriptions are purely for your pleasure; 'git rebase' will
 not look at them but at the commit names ("deadbee" and "fa1afe1" in this
 example), so do not delete or edit the names.
 
 By replacing the command "pick" with the command "edit", you can tell
-'git-rebase' to stop after applying that commit, so that you can edit
+'git rebase' to stop after applying that commit, so that you can edit
 the files and/or the commit message, amend the commit, and continue
 rebasing.
 
+If you just want to edit the commit message for a commit, replace the
+command "pick" with the command "reword".
+
 If you want to fold two or more commits into one, replace the command
-"pick" with "squash" for the second and subsequent commit.  If the
-commits had different authors, it will attribute the squashed commit to
-the author of the first commit.
+"pick" for the second and subsequent commits with "squash" or "fixup".
+If the commits had different authors, the folded commit will be
+attributed to the author of the first commit.  The suggested commit
+message for the folded commit is the concatenation of the commit
+messages of the first commit and of those with the "squash" command,
+but omits the commit messages of commits with the "fixup" command.
 
-In both cases, or when a "pick" does not succeed (because of merge
-errors), the loop will stop to let you fix things, and you can continue
-the loop with `git rebase --continue`.
+'git rebase' will stop when "pick" has been replaced with "edit" or
+when a command fails due to merge errors. When you are done editing
+and/or resolving conflicts you can continue with `git rebase --continue`.
 
 For example, if you want to reorder the last 5 commits, such that what
 was HEAD~4 becomes the new HEAD. To achieve that, you would call
-'git-rebase' like this:
+'git rebase' like this:
 
 ----------------------
 $ git rebase -i HEAD~5
@@ -400,12 +474,36 @@ sure that the current HEAD is "B", and call
 $ git rebase -i -p --onto Q O
 -----------------------------
 
+Reordering and editing commits usually creates untested intermediate
+steps.  You may want to check that your history editing did not break
+anything by running a test, or at least recompiling at intermediate
+points in history by using the "exec" command (shortcut "x").  You may
+do so by creating a todo list like this one:
+
+-------------------------------------------
+pick deadbee Implement feature XXX
+fixup f1a5c00 Fix to feature XXX
+exec make
+pick c0ffeee The oneline of the next commit
+edit deadbab The oneline of the commit after
+exec cd subdir; make test
+...
+-------------------------------------------
+
+The interactive rebase will stop when a command fails (i.e. exits with
+non-0 status) to give you an opportunity to fix the problem. You can
+continue with `git rebase --continue`.
+
+The "exec" command launches the command in a shell (the one specified
+in `$SHELL`, or the default shell if `$SHELL` is not set), so you can
+use shell features (like "cd", ">", ";" ...). The command is run from
+the root of the working tree.
 
 SPLITTING COMMITS
 -----------------
 
 In interactive mode, you can mark commits with the action "edit".  However,
-this does not necessarily mean that 'git-rebase' expects the result of this
+this does not necessarily mean that 'git rebase' expects the result of this
 edit to be exactly one commit.  Indeed, you can undo the commit, or you can
 add other commits.  This can be used to split a commit into two:
 
@@ -421,7 +519,7 @@ add other commits.  This can be used to split a commit into two:
 
 - Now add the changes to the index that you want to have in the first
   commit.  You can use `git add` (possibly interactively) or
-  'git-gui' (or both) to do that.
+  'git gui' (or both) to do that.
 
 - Commit the now-current index with whatever commit message is appropriate
   now.
@@ -432,7 +530,7 @@ add other commits.  This can be used to split a commit into two:
 
 If you are not absolutely sure that the intermediate revisions are
 consistent (they compile, pass the testsuite, etc.) you should use
-'git-stash' to stash away the not-yet-committed changes
+'git stash' to stash away the not-yet-committed changes
 after each commit, test, and amend the commit if fixes are necessary.
 
 
@@ -495,8 +593,8 @@ Easy case: The changes are literally the same.::
 Hard case: The changes are not the same.::
 
        This happens if the 'subsystem' rebase had conflicts, or used
-       `\--interactive` to omit, edit, or squash commits; or if the
-       upstream used one of `commit \--amend`, `reset`, or
+       `\--interactive` to omit, edit, squash, or fixup commits; or
+       if the upstream used one of `commit \--amend`, `reset`, or
        `filter-branch`.
 
 
@@ -507,7 +605,7 @@ Only works if the changes (patch IDs based on the diff contents) on
 'subsystem' are literally the same before and after the rebase
 'subsystem' did.
 
-In that case, the fix is easy because 'git-rebase' knows to skip
+In that case, the fix is easy because 'git rebase' knows to skip
 changes that are already present in the new upstream.  So if you say
 (assuming you're on 'topic')
 ------------
@@ -534,12 +632,12 @@ NOTE: While an "easy case recovery" sometimes appears to be successful
       example, a commit that was removed via `git rebase
       \--interactive` will be **resurrected**!
 
-The idea is to manually tell 'git-rebase' "where the old 'subsystem'
+The idea is to manually tell 'git rebase' "where the old 'subsystem'
 ended and your 'topic' began", that is, what the old merge-base
 between them was.  You will have to find a way to name the last commit
 of the old 'subsystem', for example:
 
-* With the 'subsystem' reflog: after 'git-fetch', the old tip of
+* With the 'subsystem' reflog: after 'git fetch', the old tip of
   'subsystem' is at `subsystem@\{1}`.  Subsequent fetches will
   increase the number.  (See linkgit:git-reflog[1].)
 
@@ -557,6 +655,28 @@ The ripple effect of a "hard case" recovery is especially bad:
 case" recovery too!
 
 
+BUGS
+----
+The todo list presented by `--preserve-merges --interactive` does not
+represent the topology of the revision graph.  Editing commits and
+rewording their commit messages should work fine, but attempts to
+reorder commits tend to produce counterintuitive results.
+
+For example, an attempt to rearrange
+------------
+1 --- 2 --- 3 --- 4 --- 5
+------------
+to
+------------
+1 --- 2 --- 4 --- 3 --- 5
+------------
+by moving the "pick 4" line will result in the following history:
+------------
+       3
+       /
+1 --- 2 --- 4 --- 5
+------------
+
 Authors
 ------
 Written by Junio C Hamano <gitster@pobox.com> and
index 514f03c97903aa0be41a4a8f0df236ccb68280b0..2790eebaff3b9dd9a56358cc470396f36eb87085 100644 (file)
@@ -8,19 +8,19 @@ git-receive-pack - Receive what is pushed into the repository
 
 SYNOPSIS
 --------
-'git receive-pack' <directory>
+'git-receive-pack' <directory>
 
 DESCRIPTION
 -----------
-Invoked by 'git-send-pack' and updates the repository with the
+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
+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 linkgit:git-fetch-pack[1].
 
-The command allows for creation and fast forwarding of sha1 refs
+The command allows for creation and fast-forwarding of sha1 refs
 (heads/tags) on the remote end (strictly speaking, it is the
 local end 'git-receive-pack' runs, but to the user who is sitting at
 the send-pack end, it is updating the remote.  Confused?)
index 7f7a5445c7d043dc2f28cfbca63732a417d91a87..5a0451aaf377d13590d6112a9ee8cf51e217a21d 100644 (file)
@@ -18,9 +18,7 @@ depending on the subcommand:
 [verse]
 'git reflog expire' [--dry-run] [--stale-fix] [--verbose]
        [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...
-+
 'git reflog delete' ref@\{specifier\}...
-+
 'git reflog' ['show'] [log-options] [<ref>]
 
 Reflog is a mechanism to record when the tip of branches are
@@ -42,7 +40,7 @@ see linkgit:git-log[1].
 The reflog is useful in various git commands, to specify the old value
 of a reference. For example, `HEAD@\{2\}` means "where HEAD used to be
 two moves ago", `master@\{one.week.ago\}` means "where master used to
-point to one week ago", and so on. See linkgit:git-rev-parse[1] for
+point to one week ago", and so on. See linkgit:gitrevisions[1] for
 more details.
 
 To delete single entries from the reflog, use the subcommand "delete"
@@ -60,7 +58,7 @@ OPTIONS
        refs.
 +
 This computation involves traversing all the reachable objects, i.e. it
-has the same cost as 'git-prune'.  Fortunately, once this is run, we
+has the same cost as 'git prune'.  Fortunately, once this is run, we
 should not have to ever worry about missing objects, because the current
 prune and pack-objects know about reflogs and protect objects referred by
 them.
index 25ff8f9dcbe0db52675338f1429e9169052b9cf1..8a5842bb937a0f8f89a561566d1d7ce129edc4b0 100644 (file)
@@ -7,7 +7,7 @@ git-relink - Hardlink common objects in local repositories
 
 SYNOPSIS
 --------
-'git relink' [--safe] <dir> [<dir>]\* <master_dir>
+'git relink' [--safe] <dir> [<dir>]* <master_dir>
 
 DESCRIPTION
 -----------
diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt
new file mode 100644 (file)
index 0000000..3a23477
--- /dev/null
@@ -0,0 +1,248 @@
+git-remote-helpers(1)
+=====================
+
+NAME
+----
+git-remote-helpers - Helper programs to interact with remote repositories
+
+SYNOPSIS
+--------
+'git remote-<transport>' <repository> [<URL>]
+
+DESCRIPTION
+-----------
+
+Remote helper programs are normally not used directly by end users,
+but they are invoked by git when it needs to interact with remote
+repositories git does not support natively.  A given helper will
+implement a subset of the capabilities documented here. When git
+needs to interact with a repository using a remote helper, it spawns
+the helper as an independent process, sends commands to the helper's
+standard input, and expects results from the helper's standard
+output. Because a remote helper runs as an independent process from
+git, there is no need to re-link git to add a new helper, nor any
+need to link the helper with the implementation of git.
+
+Every helper must support the "capabilities" command, which git will
+use to determine what other commands the helper will accept.  Other
+commands generally concern facilities like discovering and updating
+remote refs, transporting objects between the object database and
+the remote repository, and updating the local object store.
+
+Helpers supporting the 'fetch' capability can discover refs from the
+remote repository and transfer objects reachable from those refs to
+the local object store. Helpers supporting the 'push' capability can
+transfer local objects to the remote repository and update remote refs.
+
+Git comes with a "curl" family of remote helpers, that handle various
+transport protocols, such as 'git-remote-http', 'git-remote-https',
+'git-remote-ftp' and 'git-remote-ftps'. They implement the capabilities
+'fetch', 'option', and 'push'.
+
+INVOCATION
+----------
+
+Remote helper programs are invoked with one or (optionally) two
+arguments. The first argument specifies a remote repository as in git;
+it is either the name of a configured remote or a URL. The second
+argument specifies a URL; it is usually of the form
+'<transport>://<address>', but any arbitrary string is possible.
+
+When git encounters a URL of the form '<transport>://<address>', where
+'<transport>' is a protocol that it cannot handle natively, it
+automatically invokes 'git remote-<transport>' with the full URL as
+the second argument. If such a URL is encountered directly on the
+command line, the first argument is the same as the second, and if it
+is encountered in a configured remote, the first argument is the name
+of that remote.
+
+A URL of the form '<transport>::<address>' explicitly instructs git to
+invoke 'git remote-<transport>' with '<address>' as the second
+argument. If such a URL is encountered directly on the command line,
+the first argument is '<address>', and if it is encountered in a
+configured remote, the first argument is the name of that remote.
+
+Additionally, when a configured remote has 'remote.<name>.vcs' set to
+'<transport>', git explicitly invokes 'git remote-<transport>' with
+'<name>' as the first argument. If set, the second argument is
+'remote.<name>.url'; otherwise, the second argument is omitted.
+
+COMMANDS
+--------
+
+Commands are given by the caller on the helper's standard input, one per line.
+
+'capabilities'::
+       Lists the capabilities of the helper, one per line, ending
+       with a blank line. Each capability may be preceded with '*',
+       which marks them mandatory for git version using the remote
+       helper to understand (unknown mandatory capability is fatal
+       error).
+
+'list'::
+       Lists the refs, one per line, in the format "<value> <name>
+       [<attr> ...]". The value may be a hex sha1 hash, "@<dest>" for
+       a symref, or "?" to indicate that the helper could not get the
+       value of the ref. A space-separated list of attributes follows
+       the name; unrecognized attributes are ignored. The list ends
+       with a blank line.
++
+If 'push' is supported this may be called as 'list for-push'
+to obtain the current refs prior to sending one or more 'push'
+commands to the helper.
+
+'option' <name> <value>::
+       Sets the transport helper option <name> to <value>.  Outputs a
+       single line containing one of 'ok' (option successfully set),
+       'unsupported' (option not recognized) or 'error <msg>'
+       (option <name> is supported but <value> is not valid
+       for it).  Options should be set before other commands,
+       and may influence the behavior of those commands.
++
+Supported if the helper has the "option" capability.
+
+'fetch' <sha1> <name>::
+       Fetches the given object, writing the necessary objects
+       to the database.  Fetch commands are sent in a batch, one
+       per line, terminated with a blank line.
+       Outputs a single blank line when all fetch commands in the
+       same batch are complete. Only objects which were reported
+       in the ref list with a sha1 may be fetched this way.
++
+Optionally may output a 'lock <file>' line indicating a file under
+GIT_DIR/objects/pack which is keeping a pack until refs can be
+suitably updated.
++
+Supported if the helper has the "fetch" capability.
+
+'push' +<src>:<dst>::
+       Pushes the given local <src> commit or branch to the
+       remote branch described by <dst>.  A batch sequence of
+       one or more push commands is terminated with a blank line.
++
+Zero or more protocol options may be entered after the last 'push'
+command, before the batch's terminating blank line.
++
+When the push is complete, outputs one or more 'ok <dst>' or
+'error <dst> <why>?' lines to indicate success or failure of
+each pushed ref.  The status report output is terminated by
+a blank line.  The option field <why> may be quoted in a C
+style string if it contains an LF.
++
+Supported if the helper has the "push" capability.
+
+'import' <name>::
+       Produces a fast-import stream which imports the current value
+       of the named ref. It may additionally import other refs as
+       needed to construct the history efficiently. The script writes
+       to a helper-specific private namespace. The value of the named
+       ref should be written to a location in this namespace derived
+       by applying the refspecs from the "refspec" capability to the
+       name of the ref.
++
+Especially useful for interoperability with a foreign versioning
+system.
++
+Supported if the helper has the "import" capability.
+
+'connect' <service>::
+       Connects to given service. Standard input and standard output
+       of helper are connected to specified service (git prefix is
+       included in service name so e.g. fetching uses 'git-upload-pack'
+       as service) on remote side. Valid replies to this command are
+       empty line (connection established), 'fallback' (no smart
+       transport support, fall back to dumb transports) and just
+       exiting with error message printed (can't connect, don't
+       bother trying to fall back). After line feed terminating the
+       positive (empty) response, the output of service starts. After
+       the connection ends, the remote helper exits.
++
+Supported if the helper has the "connect" capability.
+
+If a fatal error occurs, the program writes the error message to
+stderr and exits. The caller should expect that a suitable error
+message has been printed if the child closes the connection without
+completing a valid response for the current command.
+
+Additional commands may be supported, as may be determined from
+capabilities reported by the helper.
+
+CAPABILITIES
+------------
+
+'fetch'::
+'option'::
+'push'::
+'import'::
+'connect'::
+       This helper supports the corresponding command with the same name.
+
+'refspec' 'spec'::
+       When using the import command, expect the source ref to have
+       been written to the destination ref. The earliest applicable
+       refspec takes precedence. For example
+       "refs/heads/*:refs/svn/origin/branches/*" means that, after an
+       "import refs/heads/name", the script has written to
+       refs/svn/origin/branches/name. If this capability is used at
+       all, it must cover all refs reported by the list command; if
+       it is not used, it is effectively "*:*"
+
+REF LIST ATTRIBUTES
+-------------------
+
+'for-push'::
+       The caller wants to use the ref list to prepare push
+       commands.  A helper might chose to acquire the ref list by
+       opening a different type of connection to the destination.
+
+'unchanged'::
+       This ref is unchanged since the last import or fetch, although
+       the helper cannot necessarily determine what value that produced.
+
+OPTIONS
+-------
+'option verbosity' <N>::
+       Changes the verbosity of messages displayed by the helper.
+       A value of 0 for N means that processes operate
+       quietly, and the helper produces only error output.
+       1 is the default level of verbosity, and higher values
+       of N correspond to the number of -v flags passed on the
+       command line.
+
+'option progress' \{'true'|'false'\}::
+       Enables (or disables) progress messages displayed by the
+       transport helper during a command.
+
+'option depth' <depth>::
+       Deepens the history of a shallow repository.
+
+'option followtags' \{'true'|'false'\}::
+       If enabled the helper should automatically fetch annotated
+       tag objects if the object the tag points at was transferred
+       during the fetch command.  If the tag is not fetched by
+       the helper a second fetch command will usually be sent to
+       ask for the tag specifically.  Some helpers may be able to
+       use this option to avoid a second network connection.
+
+'option dry-run' \{'true'|'false'\}:
+       If true, pretend the operation completed successfully,
+       but don't actually change any repository data.  For most
+       helpers this only applies to the 'push', if supported.
+
+'option servpath <c-style-quoted-path>'::
+       Sets service path (--upload-pack, --receive-pack etc.) for
+       next connect. Remote helper may support this option, but
+       must not rely on this option being set before
+       connect request occurs.
+
+SEE ALSO
+--------
+linkgit:git-remote[1]
+
+Documentation
+-------------
+Documentation by Daniel Barkalow and Ilari Liusvaara
+
+GIT
+---
+Part of the linkgit:git[1] suite
index 9e2b4eaa385db66ffe0c547f0452d29e9e3dc484..aa021b0cb803a9cd9f08566f6a6fe407eb025e4b 100644 (file)
@@ -10,13 +10,17 @@ SYNOPSIS
 --------
 [verse]
 'git remote' [-v | --verbose]
-'git remote add' [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>
+'git remote add' [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror] <name> <url>
 'git remote rename' <old> <new>
 'git remote rm' <name>
-'git remote set-head' <name> [-a | -d | <branch>]
-'git remote show' [-n] <name>
+'git remote set-head' <name> (-a | -d | <branch>)
+'git remote set-branches' <name> [--add] <branch>...
+'git remote set-url' [--push] <name> <newurl> [<oldurl>]
+'git remote set-url --add' [--push] <name> <newurl>
+'git remote set-url --delete' [--push] <name> <url>
+'git remote' [-v | --verbose] 'show' [-n] <name>
 'git remote prune' [-n | --dry-run] <name>
-'git remote update' [-p | --prune] [group | remote]...
+'git remote' [-v | --verbose] 'update' [-p | --prune] [group | remote]...
 
 DESCRIPTION
 -----------
@@ -30,6 +34,7 @@ OPTIONS
 -v::
 --verbose::
        Be a little more verbose and show remote url after name.
+       NOTE: This must be placed between `remote` and `subcommand`.
 
 
 COMMANDS
@@ -47,6 +52,12 @@ update remote-tracking branches <name>/<branch>.
 With `-f` option, `git fetch <name>` is run immediately after
 the remote information is set up.
 +
+With `--tags` option, `git fetch <name>` imports every tag from the
+remote repository.
++
+With `--no-tags` option, `git fetch <name>` does not import tags from
+the remote repository.
++
 With `-t <branch>` option, instead of the default glob
 refspec for the remote to track all branches under
 `$GIT_DIR/remotes/<name>/`, a refspec to track only `<branch>`
@@ -100,6 +111,32 @@ remote set-head origin master" will set `$GIT_DIR/refs/remotes/origin/HEAD` to
 `refs/remotes/origin/master` already exists; if not it must be fetched first.
 +
 
+'set-branches'::
+
+Changes the list of branches tracked by the named remote.
+This can be used to track a subset of the available remote branches
+after the initial setup for a remote.
++
+The named branches will be interpreted as if specified with the
+`-t` option on the 'git remote add' command line.
++
+With `--add`, instead of replacing the list of currently tracked
+branches, adds to that list.
+
+'set-url'::
+
+Changes URL remote points to. Sets first URL remote points to matching
+regex <oldurl> (first URL if no <oldurl> is given) to <newurl>. If
+<oldurl> doesn't match any URL, error occurs and nothing is changed.
++
+With '--push', push URLs are manipulated instead of fetch URLs.
++
+With '--add', instead of changing some URL, new URL is added.
++
+With '--delete', instead of changing some URL, all URLs matching
+regex <url> are deleted. Trying to delete all non-push URLs is an
+error.
+
 'show'::
 
 Gives some information about the remote <name>.
@@ -114,14 +151,14 @@ These stale branches have already been removed from the remote repository
 referenced by <name>, but are still locally available in
 "remotes/<name>".
 +
-With `--dry-run` option, report what branches will be pruned, but do no
+With `--dry-run` option, report what branches will be pruned, but do not
 actually prune them.
 
 'update'::
 
 Fetch updates for a named set of remotes in the repository as defined by
 remotes.<group>.  If a named group is not specified on the command line,
-the configuration parameter remotes.default will get used; if
+the configuration parameter remotes.default will be used; if
 remotes.default is not defined, all remotes which do not have the
 configuration parameter remote.<name>.skipDefaultUpdate set to true will
 be updated.  (See linkgit:git-config[1]).
@@ -160,7 +197,7 @@ $ git checkout -b nfs linux-nfs/master
 ...
 ------------
 
-* Imitate 'git-clone' but track only selected branches
+* Imitate 'git clone' but track only selected branches
 +
 ------------
 $ mkdir project.git
index aaa88526291a26db55c7ebb0833faefda9c7e5a4..8c67d1724f705c94fb8faf6801ee2bc7cd459629 100644 (file)
@@ -31,11 +31,14 @@ OPTIONS
        Instead of incrementally packing the unpacked objects,
        pack everything referenced into a single pack.
        Especially useful when packing a repository that is used
-       for private development and there is no need to worry
-       about people fetching via dumb protocols from it.  Use
+       for private development. Use
        with '-d'.  This will clean up the objects that `git prune`
        leaves behind, but `git fsck --full` shows as
        dangling.
++
+Note that users fetching over dumb protocols will have to fetch the
+whole new pack in order to get any contained object, no matter how many
+other objects in that pack they already have locally.
 
 -A::
        Same as `-a`, unless '-d' is used.  Then any unreachable
@@ -46,16 +49,16 @@ OPTIONS
        deleted by way of being left in the old pack and then
        removed.  Instead, the loose unreachable objects
        will be pruned according to normal expiry rules
-       with the next 'git-gc' invocation. See linkgit:git-gc[1].
+       with the next 'git gc' invocation. See linkgit:git-gc[1].
 
 -d::
        After packing, if the newly created packs make some
        existing packs redundant, remove the redundant packs.
-       Also run  'git-prune-packed' to remove redundant
+       Also run  'git prune-packed' to remove redundant
        loose object files.
 
 -l::
-       Pass the `--local` option to 'git-pack-objects'. See
+       Pass the `--local` option to 'git pack-objects'. See
        linkgit:git-pack-objects[1].
 
 -f::
@@ -63,12 +66,12 @@ OPTIONS
        linkgit:git-pack-objects[1].
 
 -q::
-       Pass the `-q` option to 'git-pack-objects'. See
+       Pass the `-q` option to 'git pack-objects'. See
        linkgit:git-pack-objects[1].
 
 -n::
        Do not update the server information with
-       'git-update-server-info'.  This option skips
+       'git update-server-info'.  This option skips
        updating local catalog files needed to publish
        this repository (or a direct copy of it)
        over HTTP or FTP.  See linkgit:git-update-server-info[1].
@@ -95,24 +98,26 @@ OPTIONS
        `--window-memory=0` makes memory usage unlimited, which is the
        default.
 
---max-pack-size=<n>::
-       Maximum size of each output packfile, expressed in MiB.
+--max-pack-size=[N]::
+       Maximum size of each output pack file. The size can be suffixed with
+       "k", "m", or "g". The minimum size allowed is limited to 1 MiB.
        If specified,  multiple packfiles may be created.
-       The default is unlimited.
+       The default is unlimited, unless the config variable
+       `pack.packSizeLimit` is set.
 
 
 Configuration
 -------------
 
-When configuration variable `repack.UseDeltaBaseOffset` is set
-for the repository, the command passes `--delta-base-offset`
-option to 'git-pack-objects'; this typically results in slightly
-smaller packs, but the generated packs are incompatible with
-versions of git older than (and including) v1.4.3; do not set
-the variable in a repository that older version of git needs to
-be able to read (this includes repositories from which packs can
-be copied out over http or rsync, and people who obtained packs
-that way can try to use older git with it).
+By default, the command passes `--delta-base-offset` option to
+'git pack-objects'; this typically results in slightly smaller packs,
+but the generated packs are incompatible with versions of Git older than
+version 1.4.4. If you need to share your repository with such ancient Git
+versions, either directly or via the dumb http or rsync protocol, then you
+need to set the configuration variable `repack.UseDeltaBaseOffset` to
+"false" and repack. Access from old Git versions over the native protocol
+is unaffected by this option as the conversion is performed on the fly
+as needed in that case.
 
 
 Author
diff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt
new file mode 100644 (file)
index 0000000..fde2092
--- /dev/null
@@ -0,0 +1,96 @@
+git-replace(1)
+==============
+
+NAME
+----
+git-replace - Create, list, delete refs to replace objects
+
+SYNOPSIS
+--------
+[verse]
+'git replace' [-f] <object> <replacement>
+'git replace' -d <object>...
+'git replace' -l [<pattern>]
+
+DESCRIPTION
+-----------
+Adds a 'replace' reference in `.git/refs/replace/`
+
+The name of the 'replace' reference is the SHA1 of the object that is
+replaced. The content of the 'replace' reference is the SHA1 of the
+replacement object.
+
+Unless `-f` is given, the 'replace' reference must not yet exist in
+`.git/refs/replace/` directory.
+
+Replacement references will be used by default by all git commands
+except those doing reachability traversal (prune, pack transfer and
+fsck).
+
+It is possible to disable use of replacement references for any
+command using the `--no-replace-objects` option just after 'git'.
+
+For example if commit 'foo' has been replaced by commit 'bar':
+
+------------------------------------------------
+$ git --no-replace-objects cat-file commit foo
+------------------------------------------------
+
+shows information about commit 'foo', while:
+
+------------------------------------------------
+$ git cat-file commit foo
+------------------------------------------------
+
+shows information about commit 'bar'.
+
+The 'GIT_NO_REPLACE_OBJECTS' environment variable can be set to
+achieve the same effect as the `--no-replace-objects` option.
+
+OPTIONS
+-------
+-f::
+       If an existing replace ref for the same object exists, it will
+       be overwritten (instead of failing).
+
+-d::
+       Delete existing replace refs for the given objects.
+
+-l <pattern>::
+       List replace refs for objects that match the given pattern (or
+       all if no pattern is given).
+       Typing "git replace" without arguments, also lists all replace
+       refs.
+
+BUGS
+----
+Comparing blobs or trees that have been replaced with those that
+replace them will not work properly. And using `git reset --hard` to
+go back to a replaced commit will move the branch to the replacement
+commit instead of the replaced commit.
+
+There may be other problems when using 'git rev-list' related to
+pending objects. And of course things may break if an object of one
+type is replaced by an object of another type (for example a blob
+replaced by a commit).
+
+SEE ALSO
+--------
+linkgit:git-tag[1]
+linkgit:git-branch[1]
+linkgit:git[1]
+
+Author
+------
+Written by Christian Couder <chriscool@tuxfamily.org> and Junio C
+Hamano <gitster@pobox.com>, based on 'git tag' by Kristian Hogsberg
+<krh@redhat.com> and Carlos Rica <jasampler@gmail.com>.
+
+Documentation
+--------------
+Documentation by Christian Couder <chriscool@tuxfamily.org> and the
+git-list <git@vger.kernel.org>, based on 'git tag' documentation.
+
+GIT
+---
+Part of the linkgit:git[1] suite
index 19335fddae2b706cd785258a8c02a5595c525667..400f61f6e24947525e51b7e5d808378819945205 100644 (file)
@@ -7,7 +7,7 @@ git-request-pull - Generates a summary of pending changes
 
 SYNOPSIS
 --------
-'git request-pull' <start> <url> [<end>]
+'git request-pull' [-p] <start> <url> [<end>]
 
 DESCRIPTION
 -----------
@@ -17,6 +17,9 @@ the given URL in the generated summary.
 
 OPTIONS
 -------
+-p::
+       Show patch text
+
 <start>::
        Commit to start at.
 
index 64715c17da6c313a1cec4c353300eb0faee2313b..db99d4786e06df1cd2c9352c877c45efcbfb10cc 100644 (file)
@@ -7,39 +7,44 @@ git-rerere - Reuse recorded resolution of conflicted merges
 
 SYNOPSIS
 --------
-'git rerere' ['clear'|'diff'|'status'|'gc']
+'git rerere' ['clear'|'forget' [<pathspec>]|'diff'|'status'|'gc']
 
 DESCRIPTION
 -----------
 
-In a workflow that employs relatively long lived topic branches,
-the developer sometimes needs to resolve the same conflict over
+In a workflow employing relatively long lived topic branches,
+the developer sometimes needs to resolve the same conflicts 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.
+This command assists the developer in this process by recording
+conflicted automerge results and corresponding hand resolve results
+on the initial manual merge, and applying previously recorded
+hand resolutions to their corresponding automerge results.
 
 [NOTE]
-You need to set the configuration variable rerere.enabled to
+You need to set the configuration variable rerere.enabled in order to
 enable this command.
 
 
 COMMANDS
 --------
 
-Normally, 'git-rerere' is run without arguments or user-intervention.
+Normally, 'git rerere' is run without arguments or user-intervention.
 However, it has several commands that allow it to interact with
 its working state.
 
 'clear'::
 
 This resets the metadata used by rerere if a merge resolution is to be
-aborted.  Calling 'git-am [--skip|--abort]' or 'git-rebase [--skip|--abort]'
+aborted.  Calling 'git am [--skip|--abort]' or 'git rebase [--skip|--abort]'
 will automatically invoke this command.
 
+'forget' <pathspec>::
+
+This resets the conflict resolutions which rerere has recorded for the current
+conflict in <pathspec>.  The <pathspec> is optional.
+
 'diff'::
 
 This displays diffs for the current state of the resolution.  It is
@@ -54,18 +59,18 @@ for resolutions.
 
 'gc'::
 
-This command is used to prune records of conflicted merge that
-occurred long time ago.  By default, conflicts older than 15
-days that you have not recorded their resolution, and conflicts
-older than 60 days, are pruned.  These are controlled with
+This prunes records of conflicted merges that
+occurred a long time ago.  By default, unresolved conflicts older
+than 15 days and resolved conflicts older than 60
+days are pruned.  These defaults are controlled via the
 `gc.rerereunresolved` and `gc.rerereresolved` configuration
-variables.
+variables respectively.
 
 
 DISCUSSION
 ----------
 
-When your topic branch modifies overlapping area that your
+When your topic branch modifies an 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:
@@ -140,46 +145,45 @@ top of the tip before the test merge:
 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
+commits marked with `*`.  However, this conflict is often the
 same conflict you resolved when you created the test merge you
-blew away.  'git-rerere' command helps you to resolve this final
+blew away.  'git rerere' helps you resolve this final
 conflicted merge using the information from your earlier hand
 resolve.
 
-Running the 'git-rerere' command immediately after a conflicted
+Running the '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
+running 'git rerere' again will record 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
+Next time, after seeing the same conflicted automerge,
+running 'git rerere' will perform a three-way merge between the
 earlier conflicted automerge, the earlier manual resolution, and
-the current conflicted automerge is performed by the command.
+the current conflicted automerge.
 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,
+out to your working tree file, so you do 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-add' when you are satisfied.
+(or `git diff -c`) and 'git add' 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 set the config variable rerere.enabled to enable this command).
+As a convenience measure, 'git merge' automatically invokes
+'git rerere' upon exiting with a failed automerge and 'git rerere'
+records the hand resolve when it is a new conflict, or reuses the earlier hand
+resolve when it is not.  'git commit' also invokes 'git rerere'
+when committing a merge result.  What this means is that you do
+not have to do anything special yourself (besides enabling
+the rerere.enabled config variable).
 
-In our example, when you did the test merge, the manual
+In our example, when you do 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.
+actual merge later with the updated master and topic branch, as long
+as the recorded resolution is still applicable.
 
-The information 'git-rerere' records is also used when running
-'git-rebase'.  After blowing away the test merge and continuing
+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:
 
 ------------
@@ -194,11 +198,11 @@ development on the topic branch:
     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
+you could run `git rebase master topic`, to bring yourself
+up-to-date 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 as the test merge you resolved earlier.
+'git rerere' will be run by 'git rebase' to help you resolve this
 conflict.
 
 
index abb25d1c00c97144b1f3709e408fe9cad613e623..9cf31485feec4b03850edb1f2f3a4b765245f4cc 100644 (file)
@@ -8,38 +8,50 @@ git-reset - Reset current HEAD to the specified state
 SYNOPSIS
 --------
 [verse]
-'git reset' [--mixed | --soft | --hard | --merge] [-q] [<commit>]
 'git reset' [-q] [<commit>] [--] <paths>...
+'git reset' --patch [<commit>] [--] [<paths>...]
+'git reset' [--soft | --mixed | --hard | --merge | --keep] [-q] [<commit>]
 
 DESCRIPTION
 -----------
-Sets the current head to the specified commit and optionally resets the
-index and working tree to match.
+In the first and second form, copy entries from <commit> to the index.
+In the third form, set the current branch to <commit>, optionally
+modifying index and worktree to match.  The <commit> defaults to HEAD
+in all forms.
 
-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,
-linkgit:git-revert[1] is your friend.
+'git reset' [-q] [<commit>] [--] <paths>...::
+       This form resets the index entries for all <paths> to their
+       state at the <commit>.  (It does not affect the worktree, nor
+       the current branch.)
++
+This means that `git reset <paths>` is the opposite of `git add
+<paths>`.
 
-The second form with 'paths' is used to revert selected paths in
-the index from a given commit, without moving HEAD.
+'git reset' --patch|-p [<commit>] [--] [<paths>...]::
+       Interactively select hunks in the difference between the index
+       and <commit> (defaults to HEAD).  The chosen hunks are applied
+       in reverse to the index.
++
+This means that `git reset -p` is the opposite of `git add -p` (see
+linkgit:git-add[1]).
 
+'git reset' [--<mode>] [<commit>]::
+       This form points the current branch to <commit> and then
+       updates index and working tree according to <mode>, which must
+       be one of the following:
++
+--
+--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 "Changes to be committed", as 'git status' would
+       put it.
 
-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 "Changes to be committed", as 'git-status' 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
@@ -50,15 +62,53 @@ OPTIONS
        and updates the files that are different between the named commit
        and the current commit in the working tree.
 
+--keep::
+       Reset the index to the given commit, keeping local changes in
+       the working tree since the current commit, while updating
+       working tree files without local changes to what appears in
+       the given commit.  If a file that is different between the
+       current commit and the given commit has local changes, reset
+       is aborted.
+--
+
+If you want to undo a commit other than the latest on a branch,
+linkgit:git-revert[1] is your friend.
+
+
+OPTIONS
+-------
+
 -q::
+--quiet::
        Be quiet, only report errors.
 
-<commit>::
-       Commit to make the current HEAD. If not given defaults to HEAD.
 
-Examples
+EXAMPLES
 --------
 
+Undo add::
++
+------------
+$ edit                                     <1>
+$ git add 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 commit and redo::
 +
 ------------
@@ -78,19 +128,6 @@ edit the message further, you can give -C option instead.
 +
 See also the --amend option to linkgit:git-commit[1].
 
-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.  (See the
-"RECOVERING FROM UPSTREAM REBASE" section in linkgit:git-rebase[1] for
-the implications of doing so.)
-
 Undo a commit, making it a topic branch::
 +
 ------------
@@ -106,28 +143,18 @@ current HEAD.
 <2> Rewind the master branch to get rid of those three commits.
 <3> Switch to "topic/wip" branch and keep working.
 
-Undo add::
+Undo commits permanently::
 +
 ------------
-$ edit                                     <1>
-$ git add frotz.c filfre.c
-$ mailx                                    <2>
-$ git reset                                <3>
-$ git pull git://info.example.com/ nitfol  <4>
+$ git commit ...
+$ git reset --hard HEAD~3   <1>
 ------------
 +
-<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.
+<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.  (See the
+"RECOVERING FROM UPSTREAM REBASE" section in linkgit:git-rebase[1] for
+the implications of doing so.)
 
 Undo a merge or pull::
 +
@@ -139,7 +166,7 @@ Automatic merge failed; fix conflicts and then commit the result.
 $ git reset --hard                 <2>
 $ git pull . topic/branch          <3>
 Updating from 41223... to 13134...
-Fast forward
+Fast-forward
 $ git reset --hard ORIG_HEAD       <4>
 ------------
 +
@@ -150,7 +177,7 @@ right now, so you decide to do that later.
 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.
+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
@@ -224,6 +251,140 @@ $ git add frotz.c                           <3>
 <2> This commits all other changes in the index.
 <3> Adds the file to the index again.
 
+Keep changes in working tree while discarding some previous commits::
++
+Suppose you are working on something and you commit it, and then you
+continue working a bit more, but now you think that what you have in
+your working tree should be in another branch that has nothing to do
+with what you committed previously. You can start a new branch and
+reset it while keeping the changes in your work tree.
++
+------------
+$ git tag start
+$ git checkout -b branch1
+$ edit
+$ git commit ...                            <1>
+$ edit
+$ git checkout -b branch2                   <2>
+$ git reset --keep start                    <3>
+------------
++
+<1> This commits your first edits in branch1.
+<2> In the ideal world, you could have realized that the earlier
+    commit did not belong to the new topic when you created and switched
+    to branch2 (i.e. "git checkout -b branch2 start"), but nobody is
+    perfect.
+<3> But you can use "reset --keep" to remove the unwanted commit after
+    you switched to "branch2".
+
+
+DISCUSSION
+----------
+
+The tables below show what happens when running:
+
+----------
+git reset --option target
+----------
+
+to reset the HEAD to another commit (`target`) with the different
+reset options depending on the state of the files.
+
+In these tables, A, B, C and D are some different states of a
+file. For example, the first line of the first table means that if a
+file is in state A in the working tree, in state B in the index, in
+state C in HEAD and in state D in the target, then "git reset --soft
+target" will put the file in state A in the working tree, in state B
+in the index and in state D in HEAD.
+
+      working index HEAD target         working index HEAD
+      ----------------------------------------------------
+       A       B     C    D     --soft   A       B     D
+                               --mixed  A       D     D
+                               --hard   D       D     D
+                               --merge (disallowed)
+                               --keep  (disallowed)
+
+      working index HEAD target         working index HEAD
+      ----------------------------------------------------
+       A       B     C    C     --soft   A       B     C
+                               --mixed  A       C     C
+                               --hard   C       C     C
+                               --merge (disallowed)
+                               --keep   A       C     C
+
+      working index HEAD target         working index HEAD
+      ----------------------------------------------------
+       B       B     C    D     --soft   B       B     D
+                               --mixed  B       D     D
+                               --hard   D       D     D
+                               --merge  D       D     D
+                               --keep  (disallowed)
+
+      working index HEAD target         working index HEAD
+      ----------------------------------------------------
+       B       B     C    C     --soft   B       B     C
+                               --mixed  B       C     C
+                               --hard   C       C     C
+                               --merge  C       C     C
+                               --keep   B       C     C
+
+      working index HEAD target         working index HEAD
+      ----------------------------------------------------
+       B       C     C    D     --soft   B       C     D
+                               --mixed  B       D     D
+                               --hard   D       D     D
+                               --merge (disallowed)
+                               --keep  (disallowed)
+
+      working index HEAD target         working index HEAD
+      ----------------------------------------------------
+       B       C     C    C     --soft   B       C     C
+                               --mixed  B       C     C
+                               --hard   C       C     C
+                               --merge  B       C     C
+                               --keep   B       C     C
+
+"reset --merge" is meant to be used when resetting out of a conflicted
+merge. Any mergy operation guarantees that the work tree file that is
+involved in the merge does not have local change wrt the index before
+it starts, and that it writes the result out to the work tree. So if
+we see some difference between the index and the target and also
+between the index and the work tree, then it means that we are not
+resetting out from a state that a mergy operation left after failing
+with a conflict. That is why we disallow --merge option in this case.
+
+"reset --keep" is meant to be used when removing some of the last
+commits in the current branch while keeping changes in the working
+tree. If there could be conflicts between the changes in the commit we
+want to remove and the changes in the working tree we want to keep,
+the reset is disallowed. That's why it is disallowed if there are both
+changes between the working tree and HEAD, and between HEAD and the
+target. To be safe, it is also disallowed when there are unmerged
+entries.
+
+The following tables show what happens when there are unmerged
+entries:
+
+      working index HEAD target         working index HEAD
+      ----------------------------------------------------
+       X       U     A    B     --soft  (disallowed)
+                               --mixed  X       B     B
+                               --hard   B       B     B
+                               --merge  B       B     B
+                               --keep  (disallowed)
+
+      working index HEAD target         working index HEAD
+      ----------------------------------------------------
+       X       U     A    A     --soft  (disallowed)
+                               --mixed  X       A     A
+                               --hard   A       A     A
+                               --merge  A       A     A
+                               --keep  (disallowed)
+
+X means any state and U means an unmerged index.
+
+
 Author
 ------
 Written by Junio C Hamano <gitster@pobox.com> and Linus Torvalds <torvalds@osdl.org>
index 1c9cc28895a6ea3fcfd978f940e3fa327219de0a..173f3fc78599f268c1e483c4d1abe51e95ad0e59 100644 (file)
@@ -9,20 +9,22 @@ git-rev-list - Lists commit objects in reverse chronological order
 SYNOPSIS
 --------
 [verse]
-'git-rev-list' [ \--max-count=number ]
+'git rev-list' [ \--max-count=number ]
             [ \--skip=number ]
             [ \--max-age=timestamp ]
             [ \--min-age=timestamp ]
             [ \--sparse ]
+            [ \--merges ]
             [ \--no-merges ]
             [ \--first-parent ]
             [ \--remove-empty ]
             [ \--full-history ]
             [ \--not ]
             [ \--all ]
-            [ \--branches ]
-            [ \--tags ]
-            [ \--remotes ]
+            [ \--branches[=pattern] ]
+            [ \--tags[=pattern] ]
+            [ \--remotes[=pattern] ]
+            [ \--glob=glob-pattern ]
             [ \--stdin ]
             [ \--quiet ]
             [ \--topo-order ]
@@ -50,20 +52,26 @@ SYNOPSIS
 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.
+List commits that are reachable by following the `parent` links from the
+given commit(s), but exclude commits that are reachable from the one(s)
+given with a '{caret}' in front of them.  The output is given in reverse
+chronological order by default.
 
-Commits which are stated with a preceding '{caret}' cause listing to
-stop at that point. Their parents are implied. Thus the following
-command:
+You can think of this as a set operation.  Commits given on the command
+line form a set of commits that are reachable from any of them, and then
+commits reachable from any of the ones given with '{caret}' in front are
+subtracted from that set.  The remaining commits are what comes out in the
+command's output.  Various other options and paths parameters can be used
+to further limit the result.
+
+Thus, the following command:
 
 -----------------------------------------------------------------------
        $ git rev-list foo bar ^baz
 -----------------------------------------------------------------------
 
-means "list all the commits which are included in 'foo' and 'bar', but
-not in 'baz'".
+means "list all the commits which are reachable from 'foo' or 'bar', but
+not from 'baz'".
 
 A special notation "'<commit1>'..'<commit2>'" can be used as a
 short-hand for "{caret}'<commit1>' '<commit2>'". For example, either of
@@ -83,11 +91,11 @@ between the two operands.  The following two commands are equivalent:
        $ git rev-list A...B
 -----------------------------------------------------------------------
 
-'git-rev-list' is a very essential git program, since it
+'rev-list' is a very essential git command, since it
 provides the ability to build and traverse commit ancestry graphs. For
 this reason, it has a lot of different options that enables it to be
-used by commands as different as 'git-bisect' and
-'git-repack'.
+used by commands as different as 'git bisect' and
+'git repack'.
 
 OPTIONS
 -------
index 52c353e674761bf4897484a261c702e5cc02f18a..341ca90c6e2b40676d4be360118176a39955b11c 100644 (file)
@@ -15,28 +15,38 @@ DESCRIPTION
 
 Many git porcelainish commands take mixture of flags
 (i.e. parameters that begin with a dash '-') and parameters
-meant for the underlying 'git-rev-list' command they use internally
+meant for the underlying 'git rev-list' command they use internally
 and flags and parameters for the other commands they use
-downstream of 'git-rev-list'.  This command is used to
+downstream of 'git rev-list'.  This command is used to
 distinguish between them.
 
 
 OPTIONS
 -------
 --parseopt::
-       Use 'git-rev-parse' in option parsing mode (see PARSEOPT section below).
+       Use 'git rev-parse' in option parsing mode (see PARSEOPT section below).
 
 --keep-dashdash::
        Only meaningful in `--parseopt` mode. Tells the option parser to echo
        out the first `--` met instead of skipping it.
 
+--stop-at-non-option::
+       Only meaningful in `--parseopt` mode.  Lets the option parser stop at
+       the first non-option argument.  This can be used to parse sub-commands
+       that take options themselves.
+
+--sq-quote::
+       Use 'git rev-parse' in shell quoting mode (see SQ-QUOTE
+       section below). In contrast to the `--sq` option below, this
+       mode does only quoting. Nothing else is done to command input.
+
 --revs-only::
        Do not output flags and parameters not meant for
-       'git-rev-list' command.
+       'git rev-list' command.
 
 --no-revs::
        Do not output flags and parameters meant for
-       'git-rev-list' command.
+       'git rev-list' command.
 
 --flags::
        Do not output non-flag parameters.
@@ -64,7 +74,8 @@ OPTIONS
        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-\*').
+       'git diff-{asterisk}'). In contrast to the `--sq-quote` option,
+       the command input is still interpreted as usual.
 
 --not::
        When showing object names, prefix them with '{caret}' and
@@ -90,16 +101,29 @@ OPTIONS
        abbreviation mode.
 
 --all::
-       Show all refs found in `$GIT_DIR/refs`.
-
---branches::
-       Show branch refs found in `$GIT_DIR/refs/heads`.
+       Show all refs found in `refs/`.
+
+--branches[=pattern]::
+--tags[=pattern]::
+--remotes[=pattern]::
+       Show all branches, tags, or remote-tracking branches,
+       respectively (i.e., refs found in `refs/heads`,
+       `refs/tags`, or `refs/remotes`, respectively).
++
+If a `pattern` is given, only refs matching the given shell glob are
+shown.  If the pattern does not contain a globbing character (`?`,
+`{asterisk}`, or `[`), it is turned into a prefix match by
+appending `/{asterisk}`.
 
---tags::
-       Show tag refs found in `$GIT_DIR/refs/tags`.
+--glob=pattern::
+       Show all refs matching the shell glob pattern `pattern`. If
+       the pattern does not start with `refs/`, this is automatically
+       prepended.  If the pattern does not contain a globbing
+       character (`?`, `{asterisk}`, or `[`), it is turned into a prefix
+       match by appending `/{asterisk}`.
 
---remotes::
-       Show tag refs found in `$GIT_DIR/refs/remotes`.
+--show-toplevel::
+       Show the absolute path of the top-level directory.
 
 --show-prefix::
        When the command is invoked from a subdirectory, show the
@@ -125,6 +149,12 @@ OPTIONS
 --is-bare-repository::
        When the repository is bare print "true", otherwise "false".
 
+--local-env-vars::
+       List the GIT_* environment variables that are local to the
+       repository (e.g. GIT_DIR or GIT_WORK_TREE, but not GIT_EDITOR).
+       Only the names of the variables are listed, not their value,
+       even if they are set.
+
 --short::
 --short=number::
        Instead of outputting the full SHA1 values of object names try to
@@ -134,223 +164,38 @@ OPTIONS
 --since=datestring::
 --after=datestring::
        Parse the date string, and output the corresponding
-       --max-age= parameter for 'git-rev-list'.
+       --max-age= parameter for 'git rev-list'.
 
 --until=datestring::
 --before=datestring::
        Parse the date string, and output the corresponding
-       --min-age= parameter for 'git-rev-list'.
+       --min-age= parameter for 'git rev-list'.
 
 <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.  Here are various ways to spell object names.  The
-ones listed near the end of this list are to name trees and
-blobs contained in a commit.
-
-* 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.
-
-* An output from 'git-describe'; i.e. a closest tag, optionally
-  followed by a dash and a number of commits, followed by a dash, a
-  `g`, and an abbreviated object name.
-
-* 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.
-  When ambiguous, a `<name>` is disambiguated by taking the
-  first match in the following rules:
-
-  . if `$GIT_DIR/<name>` exists, that is what you mean (this is usually
-    useful only for `HEAD`, `FETCH_HEAD`, `ORIG_HEAD` and `MERGE_HEAD`);
-
-  . otherwise, `$GIT_DIR/refs/<name>` if exists;
-
-  . otherwise, `$GIT_DIR/refs/tags/<name>` if exists;
-
-  . otherwise, `$GIT_DIR/refs/heads/<name>` if exists;
-
-  . otherwise, `$GIT_DIR/refs/remotes/<name>` if exists;
-
-  . otherwise, `$GIT_DIR/refs/remotes/<name>/HEAD` if exists.
-+
-HEAD names the commit your changes in the working tree is based on.
-FETCH_HEAD records the branch you fetched from a remote repository
-with your last 'git-fetch' invocation.
-ORIG_HEAD is created by commands that moves your HEAD in a drastic
-way, to record the position of the HEAD before their operation, so that
-you can change the tip of the branch back to the state before you ran
-them easily.
-MERGE_HEAD records the commit(s) you are merging into your branch
-when you run 'git-merge'.
-
-* A ref followed by the suffix '@' with 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>). Note that this looks up the state
-  of your *local* ref at a given time; e.g., what was in your local
-  `master` branch last week. If you want to look at commits made during
-  certain times, see `--since` and `--until`.
-
-* A ref followed by the suffix '@' with an ordinal specification
-  enclosed in a brace pair (e.g. '\{1\}', '\{15\}') to specify
-  the n-th prior value of that ref.  For example 'master@\{1\}'
-  is the immediate prior value of 'master' while 'master@\{5\}'
-  is the 5th prior value of 'master'. This suffix may only be used
-  immediately following a ref name and the ref must have an existing
-  log ($GIT_DIR/logs/<ref>).
-
-* You can use the '@' construct with an empty ref part to get at a
-  reflog of the current branch. For example, if you are on the
-  branch 'blabla', then '@\{1\}' means the same as 'blabla@\{1\}'.
-
-* The special construct '@\{-<n>\}' means the <n>th branch checked out
-  before the current one.
-
-* A suffix '{caret}' to a revision parameter means the first parent of
-  that commit object.  '{caret}<n>' means the <n>th parent (i.e.
-  'rev{caret}'
-  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 '{tilde}<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.  See below for a illustration of
-  the usage of this form.
-
-* 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.
-
-* A colon, followed by a slash, followed by a text: this names
-  a commit whose commit message starts with the specified text.
-  This name returns the youngest matching commit which is
-  reachable from any ref.  If the commit message starts with a
-  '!', you have to repeat that;  the special sequence ':/!',
-  followed by something else than '!' is reserved for now.
-
-* A suffix ':' followed by a path; this names the blob or tree
-  at the given path in the tree-ish object named by the part
-  before the colon.
-
-* A colon, optionally followed by a stage number (0 to 3) and a
-  colon, followed by a path; this names a blob object in the
-  index at the given path.  Missing stage number (and the colon
-  that follows it) names a stage 0 entry. During a merge, stage
-  1 is the common ancestor, stage 2 is the target branch's version
-  (typically the current branch), and stage 3 is the version from
-  the branch being merged.
-
-Here is an illustration, by Jon Loeliger.  Both commit nodes B
-and C are 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
-
-
-SPECIFYING RANGES
------------------
-
-History traversing commands such as 'git-log' operate on a set
-of commits, not just a single commit.  To these commands,
-specifying a single revision with the notation described in the
-previous section means the set of commits reachable from that
-commit, following the commit ancestry chain.
-
-To exclude commits reachable from a commit, a prefix `{caret}`
-notation is used.  E.g. `{caret}r1 r2` means commits reachable
-from `r2` but exclude the ones reachable from `r1`.
-
-This set operation appears so often that there is a shorthand
-for it.  When you have two commits `r1` and `r2` (named according
-to the syntax explained in SPECIFYING REVISIONS above), you can ask
-for commits that are reachable from r2 excluding those that are reachable
-from r1 by `{caret}r1 r2` and it can be written as `r1..r2`.
-
-A similar notation `r1\...r2` is called symmetric difference
-of `r1` and `r2` and is defined as
-`r1 r2 --not $(git merge-base --all r1 r2)`.
-It is the set of commits that are reachable from either one of
-`r1` or `r2` but not from both.
-
-Two other shorthands for naming a set that is formed by a commit
-and its parent commits exist.  The `r1{caret}@` notation means all
-parents of `r1`.  `r1{caret}!` includes commit `r1` but excludes
-all of its parents.
-
-Here are a handful of examples:
-
-   D                G H D
-   D F              G H I J D F
-   ^G D             H D
-   ^D B             E I J F B
-   B...C            G H D E B C
-   ^D B C           E I J F B C
-   C^@              I J F
-   F^! D            G H D F
+include::revisions.txt[]
 
 PARSEOPT
 --------
 
-In `--parseopt` mode, 'git-rev-parse' helps massaging options to bring to shell
+In `--parseopt` mode, 'git rev-parse' helps massaging options to bring to shell
 scripts the same facilities C builtins have. It works as an option normalizer
 (e.g. splits single switches aggregate values), a bit like `getopt(1)` does.
 
 It takes on the standard input the specification of the options to parse and
-understand, and echoes on the standard output a line suitable for `sh(1)` `eval`
+understand, and echoes on the standard output a string suitable for `sh(1)` `eval`
 to replace the arguments with normalized ones.  In case of error, it outputs
 usage on the standard error stream, and exits with code 129.
 
+Note: Make sure you quote the result when passing it to `eval`.  See
+below for an example.
+
 Input Format
 ~~~~~~~~~~~~
 
-'git-rev-parse --parseopt' input format is fully text based. It has two parts,
+'git rev-parse --parseopt' input format is fully text based. It has two parts,
 separated by a line that contains only `--`. The lines before the separator
 (should be more than one) are used for the usage.
 The lines after the separator describe the options.
@@ -403,7 +248,34 @@ bar=      some cool option --bar with an argument
   An option group Header
 C?        option C with an optional argument"
 
-eval `echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?`
+eval "$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
+------------
+
+SQ-QUOTE
+--------
+
+In `--sq-quote` mode, 'git rev-parse' echoes on the standard output a
+single line suitable for `sh(1)` `eval`. This line is made by
+normalizing the arguments following `--sq-quote`. Nothing other than
+quoting the arguments is done.
+
+If you want command input to still be interpreted as usual by
+'git rev-parse' before the output is shell quoted, see the `--sq`
+option.
+
+Example
+~~~~~~~
+
+------------
+$ cat >your-git-script.sh <<\EOF
+#!/bin/sh
+args=$(git rev-parse --sq-quote "$@")   # quote user-supplied arguments
+command="git frotz -n24 $args"          # and use it inside a handcrafted
+                                       # command line
+eval "$command"
+EOF
+
+$ sh your-git-script.sh "a b'c"
 ------------
 
 EXAMPLES
index 5e1175800a11d9e6a63102063af782757464d72e..b7d9ef7e4726db40f3b0d1ec9474494bb6e07dcf 100644 (file)
@@ -3,37 +3,42 @@ git-revert(1)
 
 NAME
 ----
-git-revert - Revert an existing commit
+git-revert - Revert some existing commits
 
 SYNOPSIS
 --------
-'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit>
+'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <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).
 
-Note: 'git revert' is used to record a new commit to reverse the
-effect of an earlier commit (often a faulty one).  If you want to
+Given one or more existing commits, revert the changes that the
+related patches introduce, and record some new commits that record
+them.  This requires your working tree to be clean (no modifications
+from the HEAD commit).
+
+Note: 'git revert' is used to record some new commits to reverse the
+effect of some earlier commits (often only a faulty one).  If you want to
 throw away all uncommitted changes in your working directory, you
 should see linkgit:git-reset[1], particularly the '--hard' option.  If
 you want to extract specific files as they were in another commit, you
-should see linkgit:git-checkout[1], specifically the 'git checkout
-<commit> -- <filename>' syntax.  Take care with these alternatives as
+should see linkgit:git-checkout[1], specifically the `git checkout
+<commit> -- <filename>` syntax.  Take care with these alternatives as
 both will discard uncommitted changes in your working directory.
 
 OPTIONS
 -------
-<commit>::
-       Commit to revert.
+<commit>...::
+       Commits to revert.
        For a more complete list of ways to spell commit names, see
-       "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
+       linkgit:gitrevisions[1].
+       Sets of commits can also be given but no traversal is done by
+       default, see linkgit:git-rev-list[1] and its '--no-walk'
+       option.
 
 -e::
 --edit::
-       With this option, 'git-revert' will let you edit the commit
+       With this option, 'git revert' will let you edit the commit
        message prior to committing the revert. This is the default if
        you run the command from a terminal.
 
@@ -54,16 +59,16 @@ See the link:howto/revert-a-faulty-merge.txt[revert-a-faulty-merge How-To] for
 more details.
 
 --no-edit::
-       With this option, 'git-revert' will not start the commit
+       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
-       and the index, but does not make the commit.  In addition,
+       Usually the command automatically creates some commits with
+       commit log messages stating which commits were
+       reverted.  This flag applies the changes necessary
+       to revert the named commits to your working tree
+       and the index, but does not make the commits.  In addition,
        when this option is used, your index does not have to match
        the HEAD commit.  The revert is done against the
        beginning state of your index.
@@ -75,6 +80,20 @@ effect to your index in a row.
 --signoff::
        Add Signed-off-by line at the end of the commit message.
 
+EXAMPLES
+--------
+git revert HEAD~3::
+
+       Revert the changes specified by the fourth last commit in HEAD
+       and create a new commit with the reverted changes.
+
+git revert -n master\~5..master~2::
+
+       Revert the changes done by commits from the fifth last commit
+       in master (included) to the third last commit in master
+       (included), but do not create any commit with the reverted
+       changes. The revert only modifies the working tree and the
+       index.
 
 Author
 ------
@@ -84,6 +103,10 @@ Documentation
 --------------
 Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
+SEE ALSO
+--------
+linkgit:git-cherry-pick[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index 5afb1e7428126c79171cf7e7b1fb027e1de64c86..71e3d9fc23ab6f1c181c47ebb7c9483950a9e9a6 100644 (file)
@@ -12,13 +12,13 @@ SYNOPSIS
 DESCRIPTION
 -----------
 Remove files from the index, or from the working tree and the index.
-'git-rm' will not remove a file from just your working directory.
-(There is no option to remove a file only from the work tree
+`git rm` will not remove a file from just your working directory.
+(There is no option to remove a file only from the working tree
 and yet keep it in the index; use `/bin/rm` if you want to do that.)
 The files being removed have to be identical to the tip of the branch,
 and no updates to their contents can be staged in the index,
 though that default behavior can be overridden with the `-f` option.
-When '--cached' is given, the staged content has to
+When `--cached` is given, the staged content has to
 match either the tip of the branch or the file on disk,
 allowing the file to be removed from just the index.
 
@@ -64,7 +64,7 @@ OPTIONS
 
 -q::
 --quiet::
-       'git-rm' normally outputs one line (in the form of an "rm" command)
+       `git rm` normally outputs one line (in the form of an `rm` command)
        for each file removed. This option suppresses that output.
 
 
@@ -78,16 +78,69 @@ a file that you have not told git about does not remove that file.
 
 File globbing matches across directory boundaries.  Thus, given
 two directories `d` and `d2`, there is a difference between
-using `git rm \'d\*\'` and `git rm \'d/\*\'`, as the former will
+using `git rm {apostrophe}d{asterisk}{apostrophe}` and
+`git rm {apostrophe}d/{asterisk}{apostrophe}`, as the former will
 also remove all of directory `d2`.
 
+REMOVING FILES THAT HAVE DISAPPEARED FROM THE FILESYSTEM
+--------------------------------------------------------
+There is no option for `git rm` to remove from the index only
+the paths that have disappeared from the filesystem. However,
+depending on the use case, there are several ways that can be
+done.
+
+Using "git commit -a"
+~~~~~~~~~~~~~~~~~~~~~
+If you intend that your next commit should record all modifications
+of tracked files in the working tree and record all removals of
+files that have been removed from the working tree with `rm`
+(as opposed to `git rm`), use `git commit -a`, as it will
+automatically notice and record all removals.  You can also have a
+similar effect without committing by using `git add -u`.
+
+Using "git add -A"
+~~~~~~~~~~~~~~~~~~
+When accepting a new code drop for a vendor branch, you probably
+want to record both the removal of paths and additions of new paths
+as well as modifications of existing paths.
+
+Typically you would first remove all tracked files from the working
+tree using this command:
+
+----------------
+git ls-files -z | xargs -0 rm -f
+----------------
+
+and then "untar" the new code in the working tree. Alternately
+you could "rsync" the changes into the working tree.
+
+After that, the easiest way to record all removals, additions, and
+modifications in the working tree is:
+
+----------------
+git add -A
+----------------
+
+See linkgit:git-add[1].
+
+Other ways
+~~~~~~~~~~
+If all you really want to do is to remove from the index the files
+that are no longer present in the working tree (perhaps because
+your working tree is dirty so that you cannot use `git commit -a`),
+use the following command:
+
+----------------
+git diff --name-only --diff-filter=D -z | xargs -0 git rm --cached
+----------------
+
 EXAMPLES
 --------
-git rm Documentation/\\*.txt::
-       Removes all `\*.txt` files from the index that are under the
+git rm Documentation/\*.txt::
+       Removes all `*.txt` files from the index that are under the
        `Documentation` directory and any of its subdirectories.
 +
-Note that the asterisk `\*` is quoted from the shell in this
+Note that the asterisk `*` is quoted from the shell in this
 example; this lets git, and not the shell, expand the pathnames
 of files and subdirectories under the `Documentation/` directory.
 
index 794224b1b3431655aa9e9683c9d938e35f7a3ea4..c283084272090c28d5529d200a1147c9f57c639b 100644 (file)
@@ -14,6 +14,10 @@ SYNOPSIS
 DESCRIPTION
 -----------
 Takes the patches given on the command line and emails them out.
+Patches can be specified as files, directories (which will send all
+files in the directory), or directly as a revision list.  In the
+last case, any format accepted by linkgit:git-format-patch[1] can
+be passed to git send-email.
 
 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
@@ -39,6 +43,10 @@ OPTIONS
 Composing
 ~~~~~~~~~
 
+--annotate::
+       Review and edit each patch you're about to send. See the
+       CONFIGURATION section for 'sendemail.multiedit'.
+
 --bcc=<address>::
        Specify a "Bcc:" value for each email. Default is the value of
        'sendemail.bcc'.
@@ -51,14 +59,9 @@ The --bcc option must be repeated for each user you want on the bcc list.
 +
 The --cc option must be repeated for each user you want on the cc list.
 
---annotate::
-       Review each patch you're about to send in an editor. The setting
-       'sendemail.multiedit' defines if this will spawn one editor per patch
-       or one for all of them at once.
-
 --compose::
-       Use $GIT_EDITOR, core.editor, $VISUAL, or $EDITOR to edit an
-       introductory message for the patch series.
+       Invoke a text editor (see GIT_EDITOR in linkgit:git-var[1])
+       to edit an introductory message for the patch series.
 +
 When '--compose' is used, git send-email will use the From, Subject, and
 In-Reply-To headers specified in the message. If the body of the message
@@ -67,16 +70,21 @@ In-Reply-To headers specified in the message. If the body of the message
 and In-Reply-To headers will be used unless they are removed.
 +
 Missing From or In-Reply-To headers will be prompted for.
++
+See the CONFIGURATION section for 'sendemail.multiedit'.
 
 --from=<address>::
-       Specify the sender of the emails.  This will default to
-       the value GIT_COMMITTER_IDENT, as returned by "git var -l".
-       The user will still be prompted to confirm this entry.
+       Specify the sender of the emails.  If not specified on the command line,
+       the value of the 'sendemail.from' configuration option is used.  If
+       neither the command line option nor 'sendemail.from' are set, then the
+       user will be prompted for the value.  The default for the prompt will be
+       the value of GIT_AUTHOR_IDENT, or GIT_COMMITTER_IDENT if that is not
+       set, as returned by "git var -l".
 
 --in-reply-to=<identifier>::
        Specify the contents of the first In-Reply-To header.
        Subsequent emails will refer to the previous email
-       instead of this if --chain-reply-to is set (the default)
+       instead of this if --chain-reply-to is set.
        Only necessary if --compose is also set.  If --compose
        is not set, this will be prompted for.
 
@@ -93,6 +101,15 @@ Missing From or In-Reply-To headers will be prompted for.
 +
 The --to option must be repeated for each user you want on the to list.
 
+--8bit-encoding=<encoding>::
+       When encountering a non-ASCII message or subject that does not
+       declare its encoding, add headers/quoting to indicate it is
+       encoded in <encoding>.  Default is the value of the
+       'sendemail.assume8bitEncoding'; if that is unspecified, this
+       will be prompted for if any non-ASCII files are encountered.
++
+Note that no attempts whatsoever are made to validate the encoding.
+
 
 Sending
 ~~~~~~~
@@ -100,9 +117,10 @@ Sending
 --envelope-sender=<address>::
        Specify the envelope sender used to send the emails.
        This is useful if your default address is not the address that is
-       subscribed to a list. If you use the sendmail binary, you must have
-       suitable privileges for the -f parameter. Default is the value of
-       the 'sendemail.envelopesender' configuration variable; if that is
+       subscribed to a list. In order to use the 'From' address, set the
+       value to "auto". If you use the sendmail binary, you must have
+       suitable privileges for the -f parameter.  Default is the value of the
+       'sendemail.envelopesender' configuration variable; if that is
        unspecified, choosing the envelope sender is left to your MTA.
 
 --smtp-encryption=<encryption>::
@@ -110,6 +128,13 @@ Sending
        value reverts to plain SMTP.  Default is the value of
        'sendemail.smtpencryption'.
 
+--smtp-domain=<FQDN>::
+       Specifies the Fully Qualified Domain Name (FQDN) used in the
+       HELO/EHLO command to the SMTP server.  Some servers require the
+       FQDN to match your IP address.  If not set, git send-email attempts
+       to determine your FQDN automatically.  Default is the value of
+       'sendemail.smtpdomain'.
+
 --smtp-pass[=<password>]::
        Password for SMTP-AUTH. The argument is optional: If no
        argument is specified, then the empty string is used as
@@ -134,8 +159,11 @@ user is prompted for a password while the input is masked for privacy.
 
 --smtp-server-port=<port>::
        Specifies a port different from the default port (SMTP
-       servers typically listen to smtp port 25 and ssmtp port
-       465). This can be set with 'sendemail.smtpserverport'.
+       servers typically listen to smtp port 25, but may also listen to
+       submission port 587, or the common SSL smtp port 465);
+       symbolic port names (e.g. "submission" instead of 587)
+       are also accepted. The port can also be set with the
+       'sendemail.smtpserverport' configuration variable.
 
 --smtp-ssl::
        Legacy alias for '--smtp-encryption ssl'.
@@ -155,13 +183,13 @@ Automating
        Output of this command must be single email address per line.
        Default is the value of 'sendemail.cccmd' configuration value.
 
---[no-]chain-reply-to=<identifier>::
+--[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 the value of the 'sendemail.chainreplyto'
-       configuration value; if that is unspecified, default to --chain-reply-to.
+       entire patch series. Disabled by default, but the 'sendemail.chainreplyto'
+       configuration variable can be used to enable it.
 
 --identity=<identity>::
        A configuration identity. When given, causes values in the
@@ -183,12 +211,12 @@ Automating
 - 'self' will avoid including the sender
 - 'cc' will avoid including anyone mentioned in Cc lines in the patch header
   except for self (use 'self' for that).
-- 'ccbody' will avoid including anyone mentioned in Cc lines in the
+- 'bodycc' will avoid including anyone mentioned in Cc lines in the
   patch body (commit message) except for self (use 'self' for that).
 - 'sob' will avoid including anyone mentioned in Signed-off-by lines except
    for self (use 'self' for that).
 - 'cccmd' will avoid running the --cc-cmd.
-- 'body' is equivalent to 'sob' + 'ccbody'
+- 'body' is equivalent to 'sob' + 'bodycc'
 - 'all' will suppress all auto cc values.
 --
 +
@@ -202,10 +230,22 @@ specified, as well as 'body' if --no-signed-off-cc is specified.
        value; if that is unspecified, default to --no-suppress-from.
 
 --[no-]thread::
-       If this is set, the In-Reply-To header will be set on each email sent.
-       If disabled with "--no-thread", no emails will have the In-Reply-To
-       header set. Default is the value of the 'sendemail.thread' configuration
-       value; if that is unspecified, default to --thread.
+       If this is set, the In-Reply-To and References headers will be
+       added to each email sent.  Whether each mail refers to the
+       previous email (`deep` threading per 'git format-patch'
+       wording) or to the first email (`shallow` threading) is
+       governed by "--[no-]chain-reply-to".
++
+If disabled with "--no-thread", those headers will not be added
+(unless specified with --in-reply-to).  Default is the value of the
+'sendemail.thread' configuration value; if that is unspecified,
+default to --thread.
++
+It is up to the user to ensure that no In-Reply-To header already
+exists when 'git send-email' is asked to add it (especially note that
+'git format-patch' can be configured to do the threading itself).
+Failure to do so may not produce the expected result in the
+recipient's MUA.
 
 
 Administering
@@ -230,6 +270,12 @@ have been specified, in which case default to 'compose'.
 --dry-run::
        Do everything except actually send the emails.
 
+--[no-]format-patch::
+       When an argument may be understood either as a reference or as a file name,
+       choose to understand it as a format-patch argument ('--format-patch')
+       or as a file name ('--no-format-patch'). By default, when such a conflict
+       occurs, git send-email will fail.
+
 --quiet::
        Make git-send-email less verbose.  One line per email should be
        all that is output.
@@ -246,12 +292,6 @@ have been specified, in which case default to 'compose'.
 Default is the value of 'sendemail.validate'; if this is not set,
 default to '--validate'.
 
---[no-]format-patch::
-       When an argument may be understood either as a reference or as a file name,
-       choose to understand it as a format-patch argument ('--format-patch')
-       or as a file name ('--no-format-patch'). By default, when such a conflict
-       occurs, git send-email will fail.
-
 
 CONFIGURATION
 -------------
@@ -276,6 +316,21 @@ sendemail.confirm::
        in the previous section for the meaning of these values.
 
 
+Use gmail as the smtp server
+----------------------------
+
+Add the following section to the config file:
+
+       [sendemail]
+               smtpencryption = tls
+               smtpserver = smtp.gmail.com
+               smtpuser = yourname@gmail.com
+               smtpserverport = 587
+
+Note: the following perl modules are required
+      Net::SMTP::SSL, MIME::Base64 and Authen::SASL
+
+
 Author
 ------
 Written by Ryan Anderson <ryan@michonline.com>
index 399821832c2a5cd6a718a7dc37a87e6b5bc0b213..deaa7d9654f5938fabad3e1eae29ae7c7803d2ec 100644 (file)
@@ -12,7 +12,7 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Usually you would want to use 'git-push', which is a
+Usually you would want to use 'git push', which is a
 higher-level wrapper of this command, instead. See linkgit:git-push[1].
 
 Invokes 'git-receive-pack' on a possibly remote repository, and
@@ -48,8 +48,8 @@ OPTIONS
        Run verbosely.
 
 --thin::
-       Spend extra cycles to minimize the number of objects to be sent.
-       Use it on slower connection.
+       Send a "thin" pack, which records objects in deltified form based
+       on objects not included in the pack to reduce network traffic.
 
 <host>::
        A remote host to house the repository.  When this
@@ -86,7 +86,7 @@ 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. The rules used to match a ref are the same
-rules used by 'git-rev-parse' to resolve a symbolic ref
+rules used by 'git rev-parse' to resolve a symbolic ref
 name. See linkgit:git-rev-parse[1].
 
  - It is an error if <src> does not match exactly one of the
@@ -105,11 +105,11 @@ name. See linkgit:git-rev-parse[1].
 
 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",
+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.
+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.
index 18f14b5be89b4e0240f59b13313308f3c09d012c..3da241304b0d2fdaa376bca740beb024a36b2cb6 100644 (file)
@@ -16,7 +16,7 @@ This is not a command the end user would want to run.  Ever.
 This documentation is meant for people who are studying the
 Porcelain-ish scripts and/or are writing new ones.
 
-The 'git-sh-setup' scriptlet is designed to be sourced (using
+The 'git sh-setup' scriptlet is designed to be sourced (using
 `.`) by other shell scripts to set up some variables pointing at
 the normal git directories and a few helper shell functions.
 
index 42463a955dd3f5dad25f3a45f0eec62d8712f731..bc1ac77495347967c941298e2da38f76d5d124c6 100644 (file)
@@ -3,22 +3,27 @@ git-shortlog(1)
 
 NAME
 ----
-git-shortlog - Summarize 'git-log' output
+git-shortlog - Summarize 'git log' output
 
 SYNOPSIS
 --------
 [verse]
 git log --pretty=short | 'git shortlog' [-h] [-n] [-s] [-e] [-w]
-git shortlog [-n|--numbered] [-s|--summary] [-e|--email] [-w[<width>[,<indent1>[,<indent2>]]]] [<committish>...]
+'git shortlog' [-n|--numbered] [-s|--summary] [-e|--email] [-w[<width>[,<indent1>[,<indent2>]]]] <commit>...
 
 DESCRIPTION
 -----------
-Summarizes 'git-log' output in a format suitable for inclusion
+Summarizes 'git log' output in a format suitable for inclusion
 in release announcements. Each commit will be grouped by author and
 the first line of the commit message will be shown.
 
 Additionally, "[PATCH]" will be stripped from the commit description.
 
+If no revisions are passed on the command line and either standard input
+is not a terminal or there is no current branch, 'git shortlog' will
+output a summary of the log read from standard input, without
+reference to the current repository.
+
 OPTIONS
 -------
 
@@ -39,6 +44,14 @@ OPTIONS
 --email::
        Show the email address of each author.
 
+--format[='<format>']::
+       Instead of the commit subject, use some other information to
+       describe each commit.  '<format>' can be any string accepted
+       by the `--format` option of 'git log', such as '{asterisk} [%h] %s'.
+       (See the "PRETTY FORMATS" section of linkgit:git-log[1].)
+
+       Each pretty-printed commit will be rewrapped before it is shown.
+
 -w[<width>[,<indent1>[,<indent2>]]]::
        Linewrap the output by wrapping each line at `width`.  The first
        line of each entry is indented by `indent1` spaces, and the second
index 51a4e9d6d767f34205f418ec86a6281dbf4c7b2c..64532633406bedb431c4c6755101ce117e71388f 100644 (file)
@@ -8,17 +8,20 @@ git-show-branch - Show branches and their commits
 SYNOPSIS
 --------
 [verse]
-'git show-branch' [--all] [--remotes] [--topo-order] [--current]
+'git show-branch' [-a|--all] [-r|--remotes] [--topo-order | --date-order]
+               [--current] [--color[=<when>] | --no-color] [--sparse]
                [--more=<n> | --list | --independent | --merge-base]
-               [--no-name | --sha1-name] [--topics] [<rev> | <glob>]...
+               [--no-name | --sha1-name] [--topics]
+               [<rev> | <glob>]...
+
 'git show-branch' (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]
 
 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.
+with <rev>s or <globs>s (or all refs under refs/heads
+and/or refs/tags) semi-visually.
 
 It cannot show more than 29 branches and commits at a time.
 
@@ -29,13 +32,13 @@ no <rev> nor <glob> is given on the command line.
 OPTIONS
 -------
 <rev>::
-       Arbitrary extended SHA1 expression (see linkgit:git-rev-parse[1])
+       Arbitrary extended SHA1 expression (see linkgit:gitrevisions[1])
        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
+       refs/.  For example, if you have many topic
+       branches under refs/heads/topic, giving
        `topic/*` would show all of them.
 
 -r::
@@ -57,6 +60,11 @@ OPTIONS
         appear in topological order (i.e., descendant commits
         are shown before their parents).
 
+--date-order::
+       This option is similar to '--topo-order' in the sense that no
+       parent comes before all of its children, but otherwise commits
+       are ordered according to their commit date.
+
 --sparse::
        By default, the output omits merges that are reachable
        from only one tip being shown.  This option makes them
@@ -74,9 +82,11 @@ OPTIONS
        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.
+       Instead of showing the commit list, determine possible
+       merge bases for the specified commits. All merge bases
+       will be contained in all specified commits. This is
+       different from how linkgit:git-merge-base[1] handles
+       the case of three or more commits.
 
 --independent::
        Among the <reference>s given, display only the ones that
@@ -107,6 +117,16 @@ OPTIONS
        When no explicit <ref> parameter is given, it defaults to the
        current branch (or `HEAD` if it is detached).
 
+--color[=<when>]::
+       Color the status sign (one of these: `*` `!` `+` `-`) of each commit
+       corresponding to the branch it's in.
+       The value must be always (the default), never, or auto.
+
+--no-color::
+       Turn off colored output, even when the configuration file gives the
+       default to color output.
+       Same as `--color=never`.
+
 Note that --more, --list, --independent and --merge-base options
 are mutually exclusive.
 
@@ -148,17 +168,17 @@ $ git show-branch master fixes mhf
 ------------------------------------------------
 
 These three branches all forked from a common commit, [master],
-whose commit message is "Add \'git show-branch\'". The "fixes"
-branch adds one commit "Introduce "reset type" flag to "git reset"".
-The "mhf" branch adds many other commits. The current branch
-is "master".
+whose commit message is "Add {apostrophe}git show-branch{apostrophe}".
+The "fixes" branch adds one commit "Introduce "reset type" flag to
+"git reset"". The "mhf" branch adds many other commits.
+The current branch is "master".
 
 
 EXAMPLE
 -------
 
 If you keep your primary branches immediately under
-`$GIT_DIR/refs/heads`, and topic branches in subdirectories of
+`refs/heads`, and topic branches in subdirectories of
 it, having the following in the configuration file may help:
 
 ------------
index e3285aacfd310cc269cdb03aa9243c939c24def7..8382fbe0ec5e59545e265317bdff38bfa098e0ed 100644 (file)
@@ -14,10 +14,10 @@ SYNOPSIS
 DESCRIPTION
 -----------
 Reads given idx file for packed git archive created with
-'git-pack-objects' command, and dumps its contents.
+'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
+'git verify-pack -v'; this command only shows the packfile
 offset and SHA1 of each object.
 
 
index 2f173fff356282df7c906da6edeac1fcd4430025..4696af743321d78d9e1d17115d29222f17cb4d55 100644 (file)
@@ -8,9 +8,10 @@ git-show-ref - List references in a local repository
 SYNOPSIS
 --------
 [verse]
-'git show-ref' [-q|--quiet] [--verify] [-h|--head] [-d|--dereference]
-            [-s|--hash] [--abbrev] [--tags] [--heads] [--] <pattern>...
-'git show-ref' --exclude-existing[=pattern]
+'git show-ref' [-q|--quiet] [--verify] [--head] [-d|--dereference]
+            [-s|--hash[=<n>]] [--abbrev[=<n>]] [--tags]
+            [--heads] [--] [<pattern>...]
+'git show-ref' --exclude-existing[=<pattern>] < ref-list
 
 DESCRIPTION
 -----------
@@ -24,12 +25,11 @@ The --exclude-existing form is a filter that does the inverse, it shows the
 refs from stdin that don't exist in the local repository.
 
 Use of this utility is encouraged in favor of directly accessing files under
-in the `.git` directory.
+the `.git` directory.
 
 OPTIONS
 -------
 
--h::
 --head::
 
        Show the HEAD reference.
@@ -48,9 +48,9 @@ OPTIONS
        appended.
 
 -s::
---hash::
+--hash[=<n>]::
 
-       Only show the SHA1 hash, not the reference name. When also using
+       Only show the SHA1 hash, not the reference name. When combined with
        --dereference the dereferenced tag will still be shown after the SHA1.
 
 --verify::
@@ -59,11 +59,10 @@ OPTIONS
        Aside from returning an error code of 1, it will also print an error
        message if '--quiet' was not specified.
 
---abbrev::
---abbrev=len::
+--abbrev[=<n>]::
 
        Abbreviate the object name.  When using `--hash`, you do
-       not have to say `--hash --abbrev`; `--hash=len` would do.
+       not have to say `--hash --abbrev`; `--hash=n` would do.
 
 -q::
 --quiet::
@@ -71,12 +70,11 @@ OPTIONS
        Do not print any results to stdout. When combined with '--verify' this
        can be used to silently check if a reference exists.
 
---exclude-existing::
---exclude-existing=pattern::
+--exclude-existing[=<pattern>]::
 
-       Make 'git-show-ref' act as a filter that reads refs from stdin of the
-       form "^(?:<anything>\s)?<refname>(?:\^\{\})?$" and performs the
-       following actions on each:
+       Make 'git show-ref' act as a filter that reads refs from stdin of the
+       form "^(?:<anything>\s)?<refname>(?:{backslash}{caret}\{\})?$"
+       and performs the following actions on each:
        (1) strip "^{}" at the end of line if any;
        (2) ignore if pattern is provided and does not head-match refname;
        (3) warn if refname is not a well-formed refname and skip;
@@ -137,7 +135,7 @@ When using the '--verify' flag, the command requires an exact path:
 
 will only match the exact branch called "master".
 
-If nothing matches, 'git-show-ref' will return an error code of 1,
+If nothing matches, 'git show-ref' will return an error code of 1,
 and in the case of verification, it will show an error message.
 
 For scripting, you can ask it to be quiet with the "--quiet" flag, which
@@ -165,9 +163,15 @@ flag, so you can do
 
 to get a listing of all tags together with what they dereference.
 
+FILES
+-----
+`.git/refs/*`, `.git/packed-refs`
+
 SEE ALSO
 --------
-linkgit:git-ls-remote[1]
+linkgit:git-ls-remote[1],
+linkgit:git-update-ref[1],
+linkgit:gitrepository-layout[5]
 
 AUTHORS
 -------
index 48b612e2ae50c319bcef567c7619f60396dd8408..0002bfb0455b38eecf6b29da6a2464058ba42c57 100644 (file)
@@ -16,16 +16,16 @@ Shows one or more objects (blobs, trees, tags and commits).
 
 For commits it shows the log message and textual diff. It also
 presents the merge commit in a special format as produced by
-'git-diff-tree --cc'.
+'git diff-tree --cc'.
 
 For tags, it shows the tag message and the referenced objects.
 
-For trees, it shows the names (equivalent to 'git-ls-tree'
+For trees, it shows the names (equivalent to 'git ls-tree'
 with \--name-only).
 
 For plain blobs, it shows the plain contents.
 
-The command takes options applicable to the 'git-diff-tree' command to
+The command takes options applicable to the 'git diff-tree' command to
 control how the changes the commit introduces are shown.
 
 This manual page describes only the most frequently used options.
@@ -36,7 +36,7 @@ OPTIONS
 <object>...::
        The names of objects to show.
        For a more complete list of ways to spell object names, see
-       "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
+       "SPECIFYING REVISIONS" section in linkgit:gitrevisions[1].
 
 include::pretty-options.txt[]
 
index 051f94d26f9f057cbd4db0d6886ca1a4d33c912c..8728f7a5142284d036594628cbb99da677c16651 100644 (file)
@@ -9,17 +9,18 @@ SYNOPSIS
 --------
 [verse]
 'git stash' list [<options>]
-'git stash' (show | drop | pop ) [<stash>]
-'git stash' apply [--index] [<stash>]
+'git stash' show [<stash>]
+'git stash' drop [-q|--quiet] [<stash>]
+'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]
 'git stash' branch <branchname> [<stash>]
-'git stash' [save [--keep-index] [<message>]]
+'git stash' [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]]
 'git stash' clear
 'git stash' create
 
 DESCRIPTION
 -----------
 
-Use 'git stash' when you want to record the current state of the
+Use `git stash` when you want to record the current state of the
 working directory and the index, but want to go back to a clean
 working directory.  The command saves your local modifications away
 and reverts the working directory to match the `HEAD` commit.
@@ -32,7 +33,7 @@ A stash is by default listed as "WIP on 'branchname' ...", but
 you can give a more descriptive message on the command line when
 you create one.
 
-The latest stash you created is stored in `$GIT_DIR/refs/stash`; older
+The latest stash you created is stored in `refs/stash`; older
 stashes are found in the reflog of this reference and can be named using
 the usual reflog syntax (e.g. `stash@\{0}` is the most recently
 created stash, `stash@\{1}` is the one before it, `stash@\{2.hours.ago}`
@@ -41,15 +42,27 @@ is also possible).
 OPTIONS
 -------
 
-save [--keep-index] [<message>]::
+save [--patch] [--[no-]keep-index] [-q|--quiet] [<message>]::
 
        Save your local modifications to a new 'stash', and run `git reset
-       --hard` to revert them.  This is the default action when no
-       subcommand is given. The <message> part is optional and gives
-       the description along with the stashed state.
+       --hard` to revert them.  The <message> part is optional and gives
+       the description along with the stashed state.  For quickly making
+       a snapshot, you can omit _both_ "save" and <message>, but giving
+       only <message> does not trigger this action to prevent a misspelled
+       subcommand from making an unwanted stash.
 +
 If the `--keep-index` option is used, all changes already added to the
 index are left intact.
++
+With `--patch`, you can interactively select hunks from in the diff
+between HEAD and the working tree to be stashed.  The stash entry is
+constructed such that its index state is the same as the index state
+of your repository, and its worktree contains only the changes you
+selected interactively.  The selected changes are then rolled back
+from your worktree.
++
+The `--patch` option implies `--keep-index`.  You can use
+`--no-keep-index` to override this.
 
 list [<options>]::
 
@@ -64,7 +77,7 @@ stash@{0}: WIP on submit: 6ebd0e2... Update git-stash documentation
 stash@{1}: On master: 9cc0589... Add git-stash
 ----------------------------------------------------------------
 +
-The command takes options applicable to the 'git-log'
+The command takes options applicable to the 'git log'
 command to control what is shown and how. See linkgit:git-log[1].
 
 show [<stash>]::
@@ -72,29 +85,41 @@ show [<stash>]::
        Show the changes recorded in the stash as a diff between the
        stashed state and its original parent. When no `<stash>` is given,
        shows the latest one. By default, the command shows the diffstat, but
-       it will accept any format known to 'git-diff' (e.g., `git stash show
+       it will accept any format known to 'git diff' (e.g., `git stash show
        -p stash@\{1}` to view the second most recent stash in patch form).
 
-apply [--index] [<stash>]::
+pop [--index] [-q|--quiet] [<stash>]::
 
-       Restore the changes recorded in the stash on top of the current
-       working tree state.  When no `<stash>` is given, applies the latest
-       one.  The working directory must match the index.
+       Remove a single stashed state from the stash list and apply it
+       on top of the current working tree state, i.e., do the inverse
+       operation of `git stash save`. The working directory must
+       match the index.
 +
-This operation can fail with conflicts; you need to resolve them
-by hand in the working tree.
+Applying the state can fail with conflicts; in this case, it is not
+removed from the stash list. You need to resolve the conflicts by hand
+and call `git stash drop` manually afterwards.
 +
 If the `--index` option is used, then tries to reinstate not only the working
 tree's changes, but also the index's ones. However, this can fail, when you
 have conflicts (which are stored in the index, where you therefore can no
 longer apply the changes as they were originally).
++
+When no `<stash>` is given, `stash@\{0}` is assumed, otherwise `<stash>` must
+be a reference of the form `stash@\{<revision>}`.
+
+apply [--index] [-q|--quiet] [<stash>]::
+
+       Like `pop`, but do not remove the state from the stash list. Unlike `pop`,
+       `<stash>` may be any commit that looks like a commit created by
+       `stash save` or `stash create`.
 
 branch <branchname> [<stash>]::
 
        Creates and checks out a new branch named `<branchname>` starting from
        the commit at which the `<stash>` was originally created, applies the
-       changes recorded in `<stash>` to the new working tree and index, then
-       drops the `<stash>` if that completes successfully. When no `<stash>`
+       changes recorded in `<stash>` to the new working tree and index.
+       If that succeeds, and `<stash>` is a reference of the form
+       `stash@{<revision>}`, it then drops the `<stash>`. When no `<stash>`
        is given, applies the latest one.
 +
 This is useful if the branch on which you ran `git stash save` has
@@ -105,18 +130,15 @@ no conflicts.
 
 clear::
        Remove all the stashed states. Note that those states will then
-       be subject to pruning, and may be difficult or impossible to recover.
+       be subject to pruning, and may be impossible to recover (see
+       'Examples' below for a possible strategy).
 
-drop [<stash>]::
+drop [-q|--quiet] [<stash>]::
 
        Remove a single stashed state from the stash list. When no `<stash>`
-       is given, it removes the latest one. i.e. `stash@\{0}`
-
-pop [<stash>]::
-
-       Remove a single stashed state from the stash list and apply on top
-       of the current working tree state. When no `<stash>` is given,
-       `stash@\{0}` is assumed. See also `apply`.
+       is given, it removes the latest one. i.e. `stash@\{0}`, otherwise
+       `<stash>` must a valid stash log reference of the form
+       `stash@\{<revision>}`.
 
 create::
 
@@ -163,7 +185,7 @@ $ git pull
 file foobar not up to date, cannot merge.
 $ git stash
 $ git pull
-$ git stash apply
+$ git stash pop
 ----------------------------------------------------------------
 
 Interrupted workflow::
@@ -185,14 +207,14 @@ $ git reset --soft HEAD^
 # ... continue hacking ...
 ----------------------------------------------------------------
 +
-You can use 'git-stash' to simplify the above, like this:
+You can use 'git stash' to simplify the above, like this:
 +
 ----------------------------------------------------------------
 # ... hack hack hack ...
 $ git stash
 $ edit emergency fix
 $ git commit -a -m "Fix in a hurry"
-$ git stash apply
+$ git stash pop
 # ... continue hacking ...
 ----------------------------------------------------------------
 
@@ -214,6 +236,20 @@ $ edit/build/test remaining parts
 $ git commit foo -m 'Remaining parts'
 ----------------------------------------------------------------
 
+Recovering stashes that were cleared/dropped erroneously::
+
+If you mistakenly drop or clear stashes, they cannot be recovered
+through the normal safety mechanisms.  However, you can try the
+following incantation to get a list of stashes that are still in your
+repository, but not reachable any more:
++
+----------------------------------------------------------------
+git fsck --unreachable |
+grep commit | cut -d\  -f3 |
+xargs git log --merges --no-walk --grep=WIP
+----------------------------------------------------------------
+
+
 SEE ALSO
 --------
 linkgit:git-checkout[1],
index 84f60f3407499c40a8e0caadf9d40ed5e9b8386b..dae190a5f21d3835024ce263bf4bd9b08048cfa6 100644 (file)
@@ -8,7 +8,7 @@ git-status - Show the working tree status
 
 SYNOPSIS
 --------
-'git status' <options>...
+'git status' [<options>...] [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
@@ -17,28 +17,141 @@ current HEAD commit, paths that have differences between the working
 tree and the index file, and paths in the working tree that are not
 tracked by git (and are not ignored by linkgit:gitignore[5]). The first
 are what you _would_ commit by running `git commit`; the second and
-third are what you _could_ commit by running 'git-add' before running
+third are what you _could_ commit by running 'git add' before running
 `git commit`.
 
-The command takes the same set of options as 'git-commit'; it
-shows what would be committed if the same options are given to
-'git-commit'.
-
-If there is no path that is different between the index file and
-the current HEAD commit (i.e., there is nothing to commit by running
-`git commit`), the command exits with non-zero status.
+OPTIONS
+-------
+
+-s::
+--short::
+       Give the output in the short-format.
+
+-b::
+--branch::
+       Show the branch and tracking info even in short-format.
+
+--porcelain::
+       Give the output in a stable, easy-to-parse format for scripts.
+       Currently this is identical to --short output, but is guaranteed
+       not to change in the future, making it safe for scripts.
+
+-u[<mode>]::
+--untracked-files[=<mode>]::
+       Show untracked files (Default: 'all').
++
+The mode parameter is optional, and is used to specify
+the handling of untracked files. The possible options are:
++
+--
+       - 'no'     - Show no untracked files
+       - 'normal' - Shows untracked files and directories
+       - 'all'    - Also shows individual files in untracked directories.
+--
++
+See linkgit:git-config[1] for configuration variable
+used to change the default for when the option is not
+specified.
+
+--ignore-submodules[=<when>]::
+       Ignore changes to submodules when looking for changes. <when> can be
+       either "none", "untracked", "dirty" or "all", which is the default.
+       Using "none" will consider the submodule modified when it either contains
+       untracked or modified files or its HEAD differs from the commit recorded
+       in the superproject and can be used to override any settings of the
+       'ignore' option in linkgit:git-config[1] or linkgit:gitmodules[5]. When
+       "untracked" is used submodules are not considered dirty when they only
+       contain untracked content (but they are still scanned for modified
+       content). Using "dirty" ignores all changes to the work tree of submodules,
+       only changes to the commits stored in the superproject are shown (this was
+       the behavior before 1.7.0). Using "all" hides all changes to submodules
+       (and suppresses the output of submodule summaries when the config option
+       `status.submodulesummary` is set).
+
+-z::
+       Terminate entries with NUL, instead of LF.  This implies
+       the `--porcelain` output format if no other format is given.
 
 
 OUTPUT
 ------
 The output from this command is designed to be used as a commit
 template comment, and all the output lines are prefixed with '#'.
+The default, long format, is designed to be human readable,
+verbose and descriptive.  They are subject to change in any time.
 
 The paths mentioned in the output, unlike many other git commands, are
 made relative to the current directory if you are working in a
 subdirectory (this is on purpose, to help cutting and pasting). See
 the status.relativePaths config option below.
 
+In short-format, the status of each path is shown as
+
+       XY PATH1 -> PATH2
+
+where `PATH1` is the path in the `HEAD`, and ` -> PATH2` part is
+shown only when `PATH1` corresponds to a different path in the
+index/worktree (i.e. the file is renamed). The 'XY' is a two-letter
+status code.
+
+The fields (including the `->`) are separated from each other by a
+single space. If a filename contains whitespace or other nonprintable
+characters, that field will be quoted in the manner of a C string
+literal: surrounded by ASCII double quote (34) characters, and with
+interior special characters backslash-escaped.
+
+For paths with merge conflicts, `X` and 'Y' show the modification
+states of each side of the merge. For paths that do not have merge
+conflicts, `X` shows the status of the index, and `Y` shows the status
+of the work tree.  For untracked paths, `XY` are `??`.  Other status
+codes can be interpreted as follows:
+
+* ' ' = unmodified
+* 'M' = modified
+* 'A' = added
+* 'D' = deleted
+* 'R' = renamed
+* 'C' = copied
+* 'U' = updated but unmerged
+
+Ignored files are not listed.
+
+    X          Y     Meaning
+    -------------------------------------------------
+              [MD]   not updated
+    M        [ MD]   updated in index
+    A        [ MD]   added to index
+    D         [ M]   deleted from index
+    R        [ MD]   renamed in index
+    C        [ MD]   copied in index
+    [MARC]           index and work tree matches
+    [ MARC]     M    work tree changed since index
+    [ MARC]     D    deleted in work tree
+    -------------------------------------------------
+    D           D    unmerged, both deleted
+    A           U    unmerged, added by us
+    U           D    unmerged, deleted by them
+    U           A    unmerged, added by them
+    D           U    unmerged, deleted by us
+    A           A    unmerged, both added
+    U           U    unmerged, both modified
+    -------------------------------------------------
+    ?           ?    untracked
+    -------------------------------------------------
+
+If -b is used the short-format status is preceded by a line
+
+## branchname tracking info
+
+There is an alternate -z format recommended for machine parsing.  In
+that format, the status field is the same, but some other things
+change.  First, the '->' is omitted from rename entries and the field
+order is reversed (e.g 'from -> to' becomes 'to from'). Second, a NUL
+(ASCII 0) follows each filename, replacing space as a field separator
+and the terminating newline (but a space still separates the status
+field from the first filename).  Third, filenames containing special
+characters are not specially formatted; no quoting or
+backslash-escaping is performed. Fourth, there is no branch line.
 
 CONFIGURATION
 -------------
@@ -53,9 +166,9 @@ paths shown are relative to the repository root, not to the current
 directory.
 
 If `status.submodulesummary` is set to a non zero number or true (identical
-to -1 or an unlimited number), the submodule summary will be enabled and a
-summary of commits for modified submodules will be shown (see --summary-limit
-option of linkgit:git-submodule[1]).
+to -1 or an unlimited number), the submodule summary will be enabled for
+the long format and a summary of commits for modified submodules will be
+shown (see --summary-limit option of linkgit:git-submodule[1]).
 
 SEE ALSO
 --------
@@ -63,8 +176,7 @@ linkgit:gitignore[5]
 
 Author
 ------
-Written by Linus Torvalds <torvalds@osdl.org> and
-Junio C Hamano <gitster@pobox.com>.
+Written by Junio C Hamano <gitster@pobox.com>.
 
 Documentation
 --------------
index 3b8df4467377d73d613f76875c725cbf8544ee77..1ed331c599d4b492b0dec112332bb83db5506d74 100644 (file)
@@ -9,12 +9,14 @@ git-submodule - Initialize, update or inspect submodules
 SYNOPSIS
 --------
 [verse]
-'git submodule' [--quiet] add [-b branch] [--] <repository> <path>
-'git submodule' [--quiet] status [--cached] [--] [<path>...]
+'git submodule' [--quiet] add [-b branch] [-f|--force]
+             [--reference <repository>] [--] <repository> [<path>]
+'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...]
 'git submodule' [--quiet] init [--] [<path>...]
-'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--] [<path>...]
-'git submodule' [--quiet] summary [--summary-limit <n>] [commit] [--] [<path>...]
-'git submodule' [--quiet] foreach <command>
+'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--rebase]
+             [--reference <repository>] [--merge] [--recursive] [--] [<path>...]
+'git submodule' [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]
+'git submodule' [--quiet] foreach [--recursive] <command>
 'git submodule' [--quiet] sync [--] [<path>...]
 
 
@@ -67,7 +69,11 @@ add::
        to the changeset to be committed next to the current
        project: the current project is termed the "superproject".
 +
-This requires two arguments: <repository> and <path>.
+This requires at least one argument: <repository>. The optional
+argument <path> is the relative location for the cloned submodule
+to exist in the superproject. If <path> is not given, the
+"humanish" part of the source repository is used ("repo" for
+"/path/to/repo.git" and "foo" for "host.xz:foo/.git").
 +
 <repository> is the URL of the new submodule's origin repository.
 This may be either an absolute URL, or (if it begins with ./
@@ -93,11 +99,14 @@ locate the submodule using the relative URL in .gitmodules.
 status::
        Show the status of the submodules. This will print the SHA-1 of the
        currently checked out commit for each submodule, along with the
-       submodule path and the output of 'git-describe' for the
+       submodule path and the output of 'git describe' for the
        SHA-1. Each SHA-1 will be prefixed with `-` if the submodule is not
        initialized and `+` if the currently checked out submodule commit
        does not match the SHA-1 found in the index of the containing
-       repository. This command is the default command for 'git-submodule'.
+       repository. This command is the default command for 'git submodule'.
++
+If '--recursive' is specified, this command will recurse into nested
+submodules, and show their status as well.
 
 init::
        Initialize the submodules, i.e. register each submodule name
@@ -105,40 +114,55 @@ init::
        The key used in .git/config is `submodule.$name.url`.
        This command does not alter existing information in .git/config.
        You can then customize the submodule clone URLs in .git/config
-       for your local setup and proceed to 'git submodule update';
-       you can also just use 'git submodule update --init' without
+       for your local setup and proceed to `git submodule update`;
+       you can also just use `git submodule update --init` without
        the explicit 'init' step if you do not intend to customize
        any submodule locations.
 
 update::
        Update the registered submodules, i.e. clone missing submodules and
        checkout the commit specified in the index of the containing repository.
-       This will make the submodules HEAD be detached.
+       This will make the submodules HEAD be detached unless '--rebase' or
+       '--merge' is specified or the key `submodule.$name.update` is set to
+       `rebase` or `merge`.
 +
 If the submodule is not yet initialized, and you just want to use the
 setting as stored in .gitmodules, you can automatically initialize the
 submodule with the --init option.
++
+If '--recursive' is specified, this command will recurse into the
+registered submodules, and update any nested submodules within.
 
 summary::
        Show commit summary between the given commit (defaults to HEAD) and
        working tree/index. For a submodule in question, a series of commits
        in the submodule between the given super project commit and the
-       index or working tree (switched by --cached) are shown.
+       index or working tree (switched by --cached) are shown. If the option
+       --files is given, show the series of commits in the submodule between
+       the index of the super project and the working tree of the submodule
+       (this option doesn't allow to use the --cached option or to provide an
+       explicit commit).
 
 foreach::
        Evaluates an arbitrary shell command in each checked out submodule.
-       The command has access to the variables $path and $sha1:
+       The command has access to the variables $name, $path, $sha1 and
+       $toplevel:
+       $name is the name of the relevant submodule section in .gitmodules,
        $path is the name of the submodule directory relative to the
-       superproject, and $sha1 is the commit as recorded in the superproject.
+       superproject, $sha1 is the commit as recorded in the superproject,
+       and $toplevel is the absolute path to the top-level of the superproject.
        Any submodules defined in the superproject but not checked out are
        ignored by this command. Unless given --quiet, foreach prints the name
        of each submodule before evaluating the command.
+       If --recursive is given, submodules are traversed recursively (i.e.
+       the given shell command is evaluated in nested submodules as well).
        A non-zero return from the command in any submodule causes
        the processing to terminate. This can be overridden by adding '|| :'
        to the end of the command.
 +
-As an example, "git submodule foreach 'echo $path `git rev-parse HEAD`' will
-show the path and currently checked out commit for each submodule.
+As an example, +git submodule foreach \'echo $path {backtick}git
+rev-parse HEAD{backtick}'+ will show the path and currently checked out
+commit for each submodule.
 
 sync::
        Synchronizes submodules' remote URL configuration setting
@@ -159,11 +183,21 @@ OPTIONS
 --branch::
        Branch of repository to add as submodule.
 
+-f::
+--force::
+       This option is only valid for the add command.
+       Allow adding an otherwise ignored submodule path.
+
 --cached::
        This option is only valid for status and summary commands.  These
        commands typically use the commit found in the submodule HEAD, but
        with this option, the commit stored in the index is used instead.
 
+--files::
+       This option is only valid for the summary command. This command
+       compares the commit in the index with that in the submodule HEAD
+       when this option is used.
+
 -n::
 --summary-limit::
        This option is only valid for the summary command.
@@ -177,6 +211,39 @@ OPTIONS
        This option is only valid for the update command.
        Don't fetch new objects from the remote site.
 
+--merge::
+       This option is only valid for the update command.
+       Merge the commit recorded in the superproject into the current branch
+       of the submodule. If this option is given, the submodule's HEAD will
+       not be detached. If a merge failure prevents this process, you will
+       have to resolve the resulting conflicts within the submodule with the
+       usual conflict resolution tools.
+       If the key `submodule.$name.update` is set to `merge`, this option is
+       implicit.
+
+--rebase::
+       This option is only valid for the update command.
+       Rebase the current branch onto the commit recorded in the
+       superproject. If this option is given, the submodule's HEAD will not
+       be detached. If a merge failure prevents this process, you will have
+       to resolve these failures with linkgit:git-rebase[1].
+       If the key `submodule.$name.update` is set to `rebase`, this option is
+       implicit.
+
+--reference <repository>::
+       This option is only valid for add and update commands.  These
+       commands sometimes need to clone a remote repository. In this case,
+       this option will be passed to the linkgit:git-clone[1] command.
++
+*NOTE*: Do *not* use this option unless you have read the note
+for linkgit:git-clone[1]'s --reference and --shared options carefully.
+
+--recursive::
+       This option is only valid for foreach, update and status commands.
+       Traverse submodules recursively. The operation is performed not
+       only in the submodules of the current repo, but also
+       in any nested submodules inside those submodules (and so on).
+
 <path>...::
        Paths to submodule(s). When specified this will restrict the command
        to only operate on the submodules found at the specified paths.
index 1c40894669d6f86e7dbb97d86ef9ee6f2a76190d..4b84d08fc87c13f6db371ecc52710bd09865d784 100644 (file)
@@ -3,7 +3,7 @@ git-svn(1)
 
 NAME
 ----
-git-svn - Bidirectional operation between a single Subversion branch and git
+git-svn - Bidirectional operation between a Subversion repository and git
 
 SYNOPSIS
 --------
@@ -11,27 +11,25 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-'git-svn' is a simple conduit for changesets between Subversion and git.
+'git svn' is a simple conduit for changesets between Subversion and git.
 It provides a bidirectional flow of changes between a Subversion and a git
 repository.
 
-'git-svn' can track a single Subversion branch simply by using a
-URL to the branch, follow branches laid out in the Subversion recommended
-method (trunk, branches, tags directories) with the --stdlayout option, or
-follow branches in any layout with the -T/-t/-b options (see options to
-'init' below, and also the 'clone' command).
+'git svn' can track a standard Subversion repository,
+following the common "trunk/branches/tags" layout, with the --stdlayout option.
+It can also follow branches and tags in any layout with the -T/-t/-b options
+(see options to 'init' below, and also the 'clone' command).
 
-Once tracking a Subversion branch (with any of the above methods), the git
+Once tracking a Subversion repository (with any of the above methods), the git
 repository can be updated from Subversion by the 'fetch' command and
 Subversion updated from git by the 'dcommit' command.
 
 COMMANDS
 --------
---
 
 'init'::
        Initializes an empty git repository with additional
-       metadata directories for 'git-svn'.  The Subversion URL
+       metadata directories for 'git svn'.  The Subversion URL
        may be specified as a command-line argument, or as full
        URL arguments to -T/-t/-b.  Optionally, the target
        directory to operate on can be specified as a second
@@ -48,8 +46,11 @@ COMMANDS
 --stdlayout;;
        These are optional command-line options for init.  Each of
        these flags can point to a relative repository path
-       (--tags=project/tags') or a full url
-       (--tags=https://foo.org/project/tags). The option --stdlayout is
+       (--tags=project/tags) or a full url
+       (--tags=https://foo.org/project/tags).
+       You can specify more than one --tags and/or --branches options, in case
+       your Subversion repository places tags or branches under multiple paths.
+       The option --stdlayout is
        a shorthand way of setting trunk,tags,branches as the relative paths,
        which is the Subversion default. If any of the other options are given
        as well, they take precedence.
@@ -61,16 +62,8 @@ COMMANDS
        Set the 'useSvnsyncProps' option in the [svn-remote] config.
 --rewrite-root=<URL>;;
        Set the 'rewriteRoot' option in the [svn-remote] config.
---use-log-author;;
-       When retrieving svn commits into git (as part of fetch, rebase, or
-       dcommit operations), look for the first From: or Signed-off-by: line
-       in the log message and use that as the author string.
---add-author-from;;
-       When committing to svn from git (as part of commit or dcommit
-       operations), if the existing log message doesn't already have a
-       From: or Signed-off-by: line, append a From: line based on the
-       git commit's author string.  If you use this, then --use-log-author
-       will retrieve a valid author string for all commits.
+--rewrite-uuid=<UUID>;;
+       Set the 'rewriteUUID' option in the [svn-remote] config.
 --username=<USER>;;
        For transports that SVN handles authentication for (http,
        https, and plain svn), specify the username.  For other
@@ -89,6 +82,17 @@ COMMANDS
        When passed to 'init' or 'clone' this regular expression will
        be preserved as a config key.  See 'fetch' for a description
        of '--ignore-paths'.
+--no-minimize-url;;
+       When tracking multiple directories (using --stdlayout,
+       --branches, or --tags options), git svn will attempt to connect
+       to the root (or highest allowed level) of the Subversion
+       repository.  This default allows better tracking of history if
+       entire projects are moved within a repository, but may cause
+       issues on repositories where read access restrictions are in
+       place.  Passing '--no-minimize-url' will allow git svn to
+       accept URLs as-is without attempting to connect to a higher
+       level directory.  This option is off by default when only
+       one URL/branch is tracked (it would do little good).
 
 'fetch'::
        Fetch unfetched revisions from the Subversion remote we are
@@ -98,38 +102,57 @@ COMMANDS
 
 --localtime;;
        Store Git commit times in the local timezone instead of UTC.  This
-       makes 'git-log' (even without --date=local) show the same times
+       makes 'git log' (even without --date=local) show the same times
        that `svn log` would in the local timezone.
-
---parent;;
-       Fetch only from the SVN parent of the current HEAD.
-
++
 This doesn't interfere with interoperating with the Subversion
 repository you cloned from, but if you wish for your local Git
 repository to be able to interoperate with someone else's local Git
 repository, either don't use this option or you should both use it in
 the same local timezone.
 
+--parent;;
+       Fetch only from the SVN parent of the current HEAD.
+
 --ignore-paths=<regex>;;
        This allows one to specify a Perl regular expression that will
        cause skipping of all matching paths from checkout from SVN.
        The '--ignore-paths' option should match for every 'fetch'
        (including automatic fetches due to 'clone', 'dcommit',
        'rebase', etc) on a given repository.
-
++
+[verse]
 config key: svn-remote.<name>.ignore-paths
-
-       If the ignore-paths config key is set and the command
-       line option is also given, both regular expressions
-       will be used.
-
++
+If the ignore-paths config key is set and the command line option is
+also given, both regular expressions will be used.
++
 Examples:
++
+--
+Skip "doc*" directory for every fetch;;
++
+------------------------------------------------------------------------
+--ignore-paths="^doc"
+------------------------------------------------------------------------
 
-       --ignore-paths="^doc" - skip "doc*" directory for every
-           fetch.
+Skip "branches" and "tags" of first level directories;;
++
+------------------------------------------------------------------------
+--ignore-paths="^[^/]+/(?:branches|tags)"
+------------------------------------------------------------------------
+--
 
-       --ignore-paths="^[^/]+/(?:branches|tags)" - skip
-           "branches" and "tags" of first level directories.
+--use-log-author;;
+       When retrieving svn commits into git (as part of fetch, rebase, or
+       dcommit operations), look for the first From: or Signed-off-by: line
+       in the log message and use that as the author string.
+--add-author-from;;
+       When committing to svn from git (as part of commit or dcommit
+       operations), if the existing log message doesn't already have a
+       From: or Signed-off-by: line, append a From: line based on the
+       git commit's author string.  If you use this, then --use-log-author
+       will retrieve a valid author string for all commits.
 
 'clone'::
        Runs 'init' and 'fetch'.  It will automatically create a
@@ -137,29 +160,29 @@ Examples:
        or if a second argument is passed; it will create a directory
        and work within that.  It accepts all arguments that the
        'init' and 'fetch' commands accept; with the exception of
-       '--fetch-all'.   After a repository is cloned, the 'fetch'
-       command will be able to update revisions without affecting
-       the working tree; and the 'rebase' command will be able
-       to update the working tree with the latest changes.
+       '--fetch-all' and '--parent'.  After a repository is cloned,
+       the 'fetch' command will be able to update revisions without
+       affecting the working tree; and the 'rebase' command will be
+       able to update the working tree with the latest changes.
 
 'rebase'::
        This fetches revisions from the SVN parent of the current HEAD
        and rebases the current (uncommitted to SVN) work against it.
-
-This works similarly to `svn update` or 'git-pull' except that
-it preserves linear history with 'git-rebase' instead of
-'git-merge' for ease of dcommitting with 'git-svn'.
-
-This accepts all options that 'git-svn fetch' and 'git-rebase'
++
+This works similarly to `svn update` or 'git pull' except that
+it preserves linear history with 'git rebase' instead of
+'git merge' for ease of dcommitting with 'git svn'.
++
+This accepts all options that 'git svn fetch' and 'git rebase'
 accept.  However, '--fetch-all' only fetches from the current
 [svn-remote], and not all [svn-remote] definitions.
-
-Like 'git-rebase'; this requires that the working tree be clean
++
+Like 'git rebase'; this requires that the working tree be clean
 and have no uncommitted changes.
 
 -l;;
 --local;;
-       Do not fetch remotely; only run 'git-rebase' against the
+       Do not fetch remotely; only run 'git rebase' against the
        last fetched commit from the upstream SVN.
 
 'dcommit'::
@@ -167,11 +190,12 @@ and have no uncommitted changes.
        repository, and then rebase or reset (depending on whether or
        not there is a diff between SVN and head).  This will create
        a revision in SVN for each commit in git.
-       It is recommended that you run 'git-svn' fetch and rebase (not
+       It is recommended that you run 'git svn' fetch and rebase (not
        pull or merge) your commits against the latest changes in the
        SVN repository.
-       An optional command-line argument may be specified as an
-       alternative to HEAD.
+       An optional revision or branch argument may be specified, and
+       causes 'git svn' to do all work on that revision/branch
+       instead of HEAD.
        This is advantageous over 'set-tree' (below) because it produces
        cleaner, more linear history.
 +
@@ -179,18 +203,17 @@ and have no uncommitted changes.
        After committing, do not rebase or reset.
 --commit-url <URL>;;
        Commit to this SVN URL (the full path).  This is intended to
-       allow existing git-svn repositories created with one transport
+       allow existing 'git svn' repositories created with one transport
        method (e.g. `svn://` or `http://` for anonymous read) to be
        reused if a user is later given access to an alternate transport
        method (e.g. `svn+ssh://` or `https://`) for commit.
-
++
+[verse]
 config key: svn-remote.<name>.commiturl
-
 config key: svn.commiturl (overwrites all svn-remote.<name>.commiturl options)
-
-       Using this option for any other purpose (don't ask)
-       is very strongly discouraged.
---
++
+Using this option for any other purpose (don't ask) is very strongly
+discouraged.
 
 'branch'::
        Create a branch in the SVN repository.
@@ -204,6 +227,33 @@ config key: svn.commiturl (overwrites all svn-remote.<name>.commiturl options)
        Create a tag by using the tags_subdir instead of the branches_subdir
        specified during git svn init.
 
+-d;;
+--destination;;
+       If more than one --branches (or --tags) option was given to the 'init'
+       or 'clone' command, you must provide the location of the branch (or
+       tag) you wish to create in the SVN repository.  The value of this
+       option must match one of the paths specified by a --branches (or
+       --tags) option.  You can see these paths with the commands
++
+       git config --get-all svn-remote.<name>.branches
+       git config --get-all svn-remote.<name>.tags
++
+where <name> is the name of the SVN repository as specified by the -R option to
+'init' (or "svn" by default).
+
+--username;;
+       Specify the SVN username to perform the commit as.  This option overrides
+       the 'username' configuration property.
+
+--commit-url;;
+       Use the specified URL to connect to the destination Subversion
+       repository.  This is useful in cases where the source SVN
+       repository is read-only.  This option overrides configuration
+       property 'commiturl'.
++
+       git config --get-all svn-remote.<name>.commiturl
++
+
 'tag'::
        Create a tag in the SVN repository. This is a shorthand for
        'branch -t'.
@@ -215,10 +265,12 @@ config key: svn.commiturl (overwrites all svn-remote.<name>.commiturl options)
 The following features from `svn log' are supported:
 +
 --
+-r <n>[:<n>];;
 --revision=<n>[:<n>];;
        is supported, non-numeric args are not:
        HEAD, NEXT, BASE, PREV, etc ...
--v/--verbose;;
+-v;;
+--verbose;;
        it's not completely compatible with the --verbose
        output in svn log, but reasonably close.
 --limit=<n>;;
@@ -241,7 +293,7 @@ NOTE: SVN itself only stores times in UTC and nothing else. The regular svn
 client converts the UTC time to the local time (or based on the TZ=
 environment). This command has the same behaviour.
 +
-Any other arguments are passed directly to 'git-log'
+Any other arguments are passed directly to 'git log'
 
 'blame'::
        Show what revision and author last modified each line of a file. The
@@ -249,15 +301,14 @@ Any other arguments are passed directly to 'git-log'
        `svn blame' by default. Like the SVN blame command,
        local uncommitted changes in the working copy are ignored;
        the version of the file in the HEAD revision is annotated. Unknown
-       arguments are passed directly to 'git-blame'.
+       arguments are passed directly to 'git blame'.
 +
 --git-format;;
-       Produce output in the same format as 'git-blame', but with
+       Produce output in the same format as 'git blame', but with
        SVN revision numbers instead of git commit hashes. In this mode,
        changes that haven't been committed to SVN (including local
        working-copy edits) are shown as revision 0.
 
---
 'find-rev'::
        When given an SVN revision number of the form 'rN', returns the
        corresponding git commit hash (this can optionally be followed by a
@@ -271,7 +322,7 @@ Any other arguments are passed directly to 'git-log'
        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.
+       independently of 'git svn' functions.
 
 'create-ignore'::
        Recursively finds the svn:ignore property on directories and
@@ -284,14 +335,21 @@ Any other arguments are passed directly to 'git-log'
        directories.  The output is suitable for appending to
        the $GIT_DIR/info/exclude file.
 
+'mkdirs'::
+       Attempts to recreate empty directories that core git cannot track
+       based on information in $GIT_DIR/svn/<refname>/unhandled.log files.
+       Empty directories are automatically recreated when using
+       "git svn clone" and "git svn rebase", so "mkdirs" is intended
+       for use after commands like "git checkout" or "git reset".
+
 'commit-diff'::
        Commits the diff of two tree-ish arguments from the
-       command-line.  This command does not rely on being inside an `git-svn
+       command-line.  This command does not rely on being inside an `git svn
        init`-ed repository.  This command takes three arguments, (a) the
        original tree to diff against, (b) the new tree result, (c) the
        URL of the target Subversion repository.  The final argument
-       (URL) may be omitted if you are working from a 'git-svn'-aware
-       repository (that has been `init`-ed with 'git-svn').
+       (URL) may be omitted if you are working from a 'git svn'-aware
+       repository (that has been `init`-ed with 'git svn').
        The -r<revision> option is required for this.
 
 'info'::
@@ -313,108 +371,170 @@ Any other arguments are passed directly to 'git-log'
        Shows the Subversion externals.  Use -r/--revision to specify a
        specific revision.
 
---
+'gc'::
+       Compress $GIT_DIR/svn/<refname>/unhandled.log files in .git/svn
+       and remove $GIT_DIR/svn/<refname>index files in .git/svn.
+
+'reset'::
+       Undoes the effects of 'fetch' back to the specified revision.
+       This allows you to re-'fetch' an SVN revision.  Normally the
+       contents of an SVN revision should never change and 'reset'
+       should not be necessary.  However, if SVN permissions change,
+       or if you alter your --ignore-paths option, a 'fetch' may fail
+       with "not found in commit" (file not previously visible) or
+       "checksum mismatch" (missed a modification).  If the problem
+       file cannot be ignored forever (with --ignore-paths) the only
+       way to repair the repo is to use 'reset'.
++
+Only the rev_map and refs/remotes/git-svn are changed.  Follow 'reset'
+with a 'fetch' and then 'git reset' or 'git rebase' to move local
+branches onto the new tree.
+
+-r <n>;;
+--revision=<n>;;
+       Specify the most recent revision to keep.  All later revisions
+       are discarded.
+-p;;
+--parent;;
+       Discard the specified revision as well, keeping the nearest
+       parent instead.
+Example:;;
+Assume you have local changes in "master", but you need to refetch "r2".
++
+------------
+    r1---r2---r3 remotes/git-svn
+                \
+                 A---B master
+------------
++
+Fix the ignore-paths or SVN permissions problem that caused "r2" to
+be incomplete in the first place.  Then:
++
+[verse]
+git svn reset -r2 -p
+git svn fetch
++
+------------
+    r1---r2'--r3' remotes/git-svn
+      \
+       r2---r3---A---B master
+------------
++
+Then fixup "master" with 'git rebase'.
+Do NOT use 'git merge' or your history will not be compatible with a
+future 'dcommit'!
++
+[verse]
+git rebase --onto remotes/git-svn A^ master
++
+------------
+    r1---r2'--r3' remotes/git-svn
+                \
+                 A'--B' master
+------------
 
 OPTIONS
 -------
---
 
 --shared[={false|true|umask|group|all|world|everybody}]::
 --template=<template_directory>::
        Only used with the 'init' command.
-       These are passed directly to 'git-init'.
+       These are passed directly to 'git init'.
 
 -r <ARG>::
 --revision <ARG>::
-
-Used with the 'fetch' command.
-
+          Used with the 'fetch' command.
++
 This allows revision ranges for partial/cauterized history
 to be supported.  $NUMBER, $NUMBER1:$NUMBER2 (numeric ranges),
 $NUMBER:HEAD, and BASE:$NUMBER are all supported.
-
++
 This can allow you to make partial mirrors when running fetch;
 but is generally not recommended because history will be skipped
 and lost.
 
 -::
 --stdin::
-
-Only used with the 'set-tree' command.
-
+       Only used with the 'set-tree' command.
++
 Read a list of commits from stdin and commit them in reverse
 order.  Only the leading sha1 is read from each line, so
-'git-rev-list --pretty=oneline' output can be used.
+'git rev-list --pretty=oneline' output can be used.
 
 --rmdir::
-
-Only used with the 'dcommit', 'set-tree' and 'commit-diff' commands.
-
+       Only used with the 'dcommit', 'set-tree' and 'commit-diff' commands.
++
 Remove directories from the SVN tree if there are no files left
 behind.  SVN can version empty directories, and they are not
 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.
-
++
+[verse]
 config key: svn.rmdir
 
 -e::
 --edit::
-
-Only used with the 'dcommit', 'set-tree' and 'commit-diff' commands.
-
+       Only used with the 'dcommit', 'set-tree' and 'commit-diff' commands.
++
 Edit the commit message before committing to SVN.  This is off by
 default for objects that are commits, and forced on when committing
 tree objects.
-
++
+[verse]
 config key: svn.edit
 
 -l<num>::
 --find-copies-harder::
-
-Only used with the 'dcommit', 'set-tree' and 'commit-diff' commands.
-
-They are both passed directly to 'git-diff-tree'; see
+       Only used with the 'dcommit', 'set-tree' and 'commit-diff' commands.
++
+They are both passed directly to 'git diff-tree'; see
 linkgit:git-diff-tree[1] for more information.
-
++
 [verse]
 config key: svn.l
 config key: svn.findcopiesharder
 
 -A<filename>::
 --authors-file=<filename>::
-
-Syntax is compatible with the file used by 'git-cvsimport':
-
+       Syntax is compatible with the file used by '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'
++
+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
+appropriate entry.  Re-running the previous 'git svn' command
 after the authors-file is modified should continue operation.
-
++
+[verse]
 config key: svn.authorsfile
 
+--authors-prog=<filename>::
+       If this option is specified, for each SVN committer name that
+       does not exist in the authors file, the given file is executed
+       with the committer name as the first argument.  The program is
+       expected to return a single line of the form "Name <email>",
+       which will be treated as if included in the authors file.
+
 -q::
 --quiet::
-       Make 'git-svn' less verbose. Specify a second time to make it
+       Make 'git svn' less verbose. Specify a second time to make it
        even less verbose.
 
 --repack[=<n>]::
 --repack-flags=<flags>::
-
-These should help keep disk usage sane for large fetches
-with many revisions.
-
+       These should help keep disk usage sane for large fetches with
+       many revisions.
++
 --repack takes an optional argument for the number of revisions
 to fetch before repacking.  This defaults to repacking every
 1000 commits fetched if no argument is specified.
-
---repack-flags are passed directly to 'git-repack'.
-
++
+--repack-flags are passed directly to 'git repack'.
++
 [verse]
 config key: svn.repack
 config key: svn.repackflags
@@ -423,41 +543,36 @@ config key: svn.repackflags
 --merge::
 -s<strategy>::
 --strategy=<strategy>::
-
-These are only used with the 'dcommit' and 'rebase' commands.
-
-Passed directly to 'git-rebase' when using 'dcommit' if a
-'git-reset' cannot be used (see 'dcommit').
+       These are only used with the 'dcommit' and 'rebase' commands.
++
+Passed directly to 'git rebase' when using 'dcommit' if a
+'git reset' cannot be used (see 'dcommit').
 
 -n::
 --dry-run::
-
-This can be used with the 'dcommit', 'rebase', 'branch' and 'tag'
-commands.
-
+       This can be used with the 'dcommit', 'rebase', 'branch' and
+       'tag' commands.
++
 For 'dcommit', print out the series of git arguments that would show
 which diffs would be committed to SVN.
-
++
 For 'rebase', display the local branch associated with the upstream svn
 repository associated with the current branch and the URL of svn
 repository that will be fetched from.
-
++
 For 'branch' and 'tag', display the urls that will be used for copying when
 creating the branch or tag.
 
---
 
 ADVANCED OPTIONS
 ----------------
---
 
 -i<GIT_SVN_ID>::
 --id <GIT_SVN_ID>::
-
-This sets GIT_SVN_ID (instead of using the environment).  This
-allows the user to override the default refname to fetch from
-when tracking a single URL.  The 'log' and 'dcommit' commands
-no longer require this switch as an argument.
+       This sets GIT_SVN_ID (instead of using the environment).  This
+       allows the user to override the default refname to fetch from
+       when tracking a single URL.  The 'log' and 'dcommit' commands
+       no longer require this switch as an argument.
 
 -R<remote name>::
 --svn-remote <remote name>::
@@ -471,33 +586,30 @@ no longer require this switch as an argument.
        started tracking a branch and never tracked the trunk it was
        descended from. This feature is enabled by default, use
        --no-follow-parent to disable it.
-
++
+[verse]
 config key: svn.followparent
 
---
 CONFIG FILE-ONLY OPTIONS
 ------------------------
---
 
 svn.noMetadata::
 svn-remote.<name>.noMetadata::
-
-This gets rid of the 'git-svn-id:' lines at the end of every commit.
-
-If you lose your .git/svn/git-svn/.rev_db file, 'git-svn' will not
+       This gets rid of the 'git-svn-id:' lines at the end of every commit.
++
+If you lose your .git/svn/git-svn/.rev_db file, 'git svn' will not
 be able to rebuild it and you won't be able to fetch again,
 either.  This is fine for one-shot imports.
-
-The 'git-svn log' command will not work on repositories using
++
+The 'git svn log' command will not work on repositories using
 this, either.  Using this conflicts with the 'useSvmProps'
 option for (hopefully) obvious reasons.
 
 svn.useSvmProps::
 svn-remote.<name>.useSvmProps::
-
-This allows 'git-svn' to re-map repository URLs and UUIDs from
-mirrors created using SVN::Mirror (or svk) for metadata.
-
+       This allows 'git svn' to re-map repository URLs and UUIDs from
+       mirrors created using SVN::Mirror (or svk) for metadata.
++
 If an SVN revision has a property, "svm:headrev", it is likely
 that the revision was created by SVN::Mirror (also used by SVK).
 The property contains a repository UUID and a revision.  We want
@@ -514,28 +626,40 @@ svn-remote.<name>.useSvnsyncprops::
 
 svn-remote.<name>.rewriteRoot::
        This allows users to create repositories from alternate
-       URLs.  For example, an administrator could run 'git-svn' on the
+       URLs.  For example, an administrator could run 'git svn' on the
        server locally (accessing via file://) but wish to distribute
        the repository with a public http:// or svn:// URL in the
        metadata so users of it will see the public URL.
 
-svn.brokenSymlinkWorkaround::
-This disables potentially expensive checks to workaround broken symlinks
-checked into SVN by broken clients.  Set this option to "false" if you
-track a SVN repository with many empty blobs that are not symlinks.
-This option may be changed while "git-svn" is running and take effect on
-the next revision fetched.  If unset, git-svn assumes this option to be
-"true".
-
---
+svn-remote.<name>.rewriteUUID::
+       Similar to the useSvmProps option; this is for users who need
+       to remap the UUID manually. This may be useful in situations
+       where the original UUID is not available via either useSvmProps
+       or useSvnsyncProps.
 
-Since the noMetadata, rewriteRoot, useSvnsyncProps and useSvmProps
-options all affect the metadata generated and used by 'git-svn'; they
+svn.brokenSymlinkWorkaround::
+       This disables potentially expensive checks to workaround
+       broken symlinks checked into SVN by broken clients.  Set this
+       option to "false" if you track a SVN repository with many
+       empty blobs that are not symlinks.  This option may be changed
+       while 'git svn' is running and take effect on the next
+       revision fetched.  If unset, 'git svn' assumes this option to
+       be "true".
+
+svn.pathnameencoding::
+       This instructs git svn to recode pathnames to a given encoding.
+       It can be used by windows users and by those who work in non-utf8
+       locales to avoid corrupted file names with non-ASCII characters.
+       Valid encodings are the ones supported by Perl's Encode module.
+
+Since the noMetadata, rewriteRoot, rewriteUUID, useSvnsyncProps and useSvmProps
+options all affect the metadata generated and used by 'git svn'; they
 *must* be set in the configuration file before any history is imported
 and these settings should never be changed once they are set.
 
-Additionally, only one of these four options can be used per-svn-remote
-section because they affect the 'git-svn-id:' metadata line.
+Additionally, only one of these options can be used per svn-remote
+section because they affect the 'git-svn-id:' metadata line, except
+for rewriteRoot and rewriteUUID which can be used together.
 
 
 BASIC EXAMPLES
@@ -548,7 +672,7 @@ Tracking and contributing to the trunk of a Subversion-managed project:
        git svn clone http://svn.example.com/project/trunk
 # Enter the newly cloned directory:
        cd trunk
-# You should be on master branch, double-check with git-branch
+# You should be on master branch, double-check with 'git branch'
        git branch
 # Do some work and commit locally to git:
        git commit ...
@@ -579,12 +703,12 @@ Tracking and contributing to an entire Subversion-managed project
 # of dcommit/rebase/show-ignore should be the same as above.
 ------------------------------------------------------------------------
 
-The initial 'git-svn clone' can be quite time-consuming
+The initial 'git svn clone' can be quite time-consuming
 (especially for large Subversion repositories). If multiple
 people (or one person with multiple machines) want to use
-'git-svn' to interact with the same Subversion repository, you can
-do the initial 'git-svn clone' to a repository on a server and
-have each person clone that repository with 'git-clone':
+'git svn' to interact with the same Subversion repository, you can
+do the initial 'git svn clone' to a repository on a server and
+have each person clone that repository with 'git clone':
 
 ------------------------------------------------------------------------
 # Do the initial import on a server
@@ -598,7 +722,7 @@ have each person clone that repository with 'git-clone':
        git fetch
 # Create a local branch from one of the branches just fetched
        git checkout -b master FETCH_HEAD
-# Initialize git-svn locally (be sure to use the same URL and -T/-b/-t options as were used on server)
+# Initialize 'git svn' locally (be sure to use the same URL and -T/-b/-t options as were used on server)
        git svn init http://svn.example.com/project
 # Pull the latest changes from Subversion
        git svn rebase
@@ -607,7 +731,7 @@ have each person clone that repository with 'git-clone':
 REBASE VS. PULL/MERGE
 ---------------------
 
-Originally, 'git-svn' recommended that the 'remotes/git-svn' branch be
+Originally, 'git svn' recommended that the 'remotes/git-svn' branch be
 pulled or merged from.  This is because the author favored
 `git svn set-tree B` to commit a single head rather than the
 `git svn set-tree A..B` notation to commit multiple commits.
@@ -615,14 +739,14 @@ pulled or merged from.  This is because the author favored
 If you use `git svn set-tree A..B` to commit several diffs and you do
 not have the latest remotes/git-svn merged into my-branch, you should
 use `git svn rebase` to update your work branch instead of `git pull` or
-`git merge`.  `pull`/`merge' can cause non-linear history to be flattened
+`git merge`.  `pull`/`merge` can cause non-linear history to be flattened
 when committing into SVN, which can lead to merge commits reversing
 previous commits in SVN.
 
 DESIGN PHILOSOPHY
 -----------------
 Merge tracking in Subversion is lacking and doing branched development
-with Subversion can be cumbersome as a result.  While 'git-svn' can track
+with Subversion can be cumbersome as a result.  While 'git svn' can track
 copy history (including branches and tags) for repositories adopting a
 standard layout, it cannot yet represent merge history that happened
 inside git back upstream to SVN users.  Therefore it is advised that
@@ -633,25 +757,35 @@ CAVEATS
 -------
 
 For the sake of simplicity and interoperating with a less-capable system
-(SVN), it is recommended that all 'git-svn' users clone, fetch and dcommit
-directly from the SVN server, and avoid all 'git-clone'/'pull'/'merge'/'push'
+(SVN), it is recommended that all 'git svn' users clone, fetch and dcommit
+directly from the SVN server, and avoid all 'git clone'/'pull'/'merge'/'push'
 operations between git repositories and branches.  The recommended
 method of exchanging code between git branches and users is
-'git-format-patch' and 'git-am', or just 'dcommit'ing to the SVN repository.
+'git format-patch' and 'git am', or just 'dcommit'ing to the SVN repository.
 
-Running 'git-merge' or 'git-pull' is NOT recommended on a branch you
+Running 'git merge' or 'git pull' is NOT recommended on a branch you
 plan to 'dcommit' from.  Subversion does not represent merges in any
 reasonable or useful fashion; so users using Subversion cannot see any
 merges you've made.  Furthermore, if you merge or pull from a git branch
 that is a mirror of an SVN branch, 'dcommit' may commit to the wrong
 branch.
 
-'git-clone' does not clone branches under the refs/remotes/ hierarchy or
-any 'git-svn' metadata, or config.  So repositories created and managed with
-using 'git-svn' should use 'rsync' for cloning, if cloning is to be done
+If you do merge, note the following rule: 'git svn dcommit' will
+attempt to commit on top of the SVN commit named in
+------------------------------------------------------------------------
+git log --grep=^git-svn-id: --first-parent -1
+------------------------------------------------------------------------
+You 'must' therefore ensure that the most recent commit of the branch
+you want to dcommit to is the 'first' parent of the merge.  Chaos will
+ensue otherwise, especially if the first parent is an older commit on
+the same SVN branch.
+
+'git clone' does not clone branches under the refs/remotes/ hierarchy or
+any 'git svn' metadata, or config.  So repositories created and managed with
+using 'git svn' should use 'rsync' for cloning, if cloning is to be done
 at all.
 
-Since 'dcommit' uses rebase internally, any git branches you 'git-push' to
+Since 'dcommit' uses rebase internally, any git branches you 'git push' to
 before 'dcommit' on will require forcing an overwrite of the existing ref
 on the remote repository.  This is generally considered bad practice,
 see the linkgit:git-push[1] documentation for details.
@@ -661,6 +795,16 @@ already dcommitted.  It is considered bad practice to --amend commits
 you've already pushed to a remote repository for other users, and
 dcommit with SVN is analogous to that.
 
+When using multiple --branches or --tags, 'git svn' does not automatically
+handle name collisions (for example, if two branches from different paths have
+the same name, or if a branch and a tag have the same name).  In these cases,
+use 'init' to set up your git repository then, before your first 'fetch', edit
+the .git/config file so that the branches and tags are associated with
+different name spaces.  For example:
+
+       branches = stable/*:refs/remotes/svn/stable/*
+       branches = debug/*:refs/remotes/svn/debug/*
+
 BUGS
 ----
 
@@ -677,7 +821,7 @@ for git to detect them.
 CONFIGURATION
 -------------
 
-'git-svn' stores [svn-remote] configuration information in the
+'git svn' stores [svn-remote] configuration information in the
 repository .git/config file.  It is similar the core git
 [remote] sections except 'fetch' keys do not accept glob
 arguments; but they are instead handled by the 'branches'
@@ -698,7 +842,23 @@ Keep in mind that the '\*' (asterisk) wildcard of the local ref
 however the remote wildcard may be anywhere as long as it's an
 independent path component (surrounded by '/' or EOL).   This
 type of configuration is not automatically created by 'init' and
-should be manually entered with a text-editor or using 'git-config'.
+should be manually entered with a text-editor or using 'git config'.
+
+It is also possible to fetch a subset of branches or tags by using a
+comma-separated list of names within braces. For example:
+
+------------------------------------------------------------------------
+[svn-remote "huge-project"]
+       url = http://server.org/svn
+       fetch = trunk/src:refs/remotes/trunk
+       branches = branches/{red,green}/src:refs/remotes/branches/*
+       tags = tags/{1.0,2.0}/src:refs/remotes/tags/*
+------------------------------------------------------------------------
+
+Note that git-svn keeps track of the highest revision in which a branch
+or tag has appeared. If the subset of branches or tags is changed after
+fetching, then .git/svn/.metadata must be manually edited to remove (or
+reset) branches-maxRev and/or tags-maxRev as appropriate.
 
 SEE ALSO
 --------
index 210fde03a12cd757769f81754e789a2a5934f02c..33a15362949bed9e23debb7ac50db35585ed23ce 100644 (file)
@@ -14,9 +14,9 @@ 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.
+argument to see which branch your working tree is on.
 
-Give two arguments, create or update a symbolic ref <name> to
+Given two arguments, creates or updates a symbolic ref <name> to
 point at the given branch <ref>.
 
 A symbolic ref is a regular file that stores a string that
@@ -49,7 +49,7 @@ cumbersome.  On some platforms, `ln -sf` does not even work as
 advertised (horrors).  Therefore symbolic links are now deprecated
 and symbolic refs are used by default.
 
-'git-symbolic-ref' will exit with status 0 if the contents of the
+'git symbolic-ref' will exit with status 0 if the contents of the
 symbolic ref were printed correctly, with status 1 if the requested
 name is not a symbolic ref, or 128 if another error occurs.
 
index fa733214ab0259dec1c866e9cb629dd0e7b69f3a..31c78a81e094ffc8a66639119eadefbd1e760e35 100644 (file)
@@ -10,14 +10,15 @@ SYNOPSIS
 --------
 [verse]
 'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]
-       <name> [<commit> | <object>]
-'git tag' -d <name>...
+       <tagname> [<commit> | <object>]
+'git tag' -d <tagname>...
 'git tag' [-n[<num>]] -l [--contains <commit>] [<pattern>]
-'git tag' -v <name>...
+'git tag' -v <tagname>...
 
 DESCRIPTION
 -----------
-Adds a 'tag' reference in `.git/refs/tags/`
+
+Adds a tag reference in `.git/refs/tags/`.
 
 Unless `-f` is given, the tag must not yet exist in
 `.git/refs/tags/` directory.
@@ -50,6 +51,7 @@ OPTIONS
        Make a GPG-signed tag, using the given key
 
 -f::
+--force::
        Replace an existing tag with the given name (instead of failing)
 
 -d::
@@ -85,9 +87,15 @@ OPTIONS
        Implies `-a` if none of `-a`, `-s`, or `-u <key-id>`
        is given.
 
+<tagname>::
+       The name of the tag to create, delete, or describe.
+       The new tag name must pass all checks defined by
+       linkgit:git-check-ref-format[1].  Some of these checks
+       may restrict the characters allowed in a tag name.
+
 CONFIGURATION
 -------------
-By default, 'git-tag' in sign-with-default mode (-s) will use your
+By default, 'git tag' in sign-with-default mode (-s) will use your
 committer identity (of the form "Your Name <your@email.address>") to
 find a key.  If you want to use a different default key, you can specify
 it in the repository configuration as follows:
@@ -123,12 +131,12 @@ and be done with it.
 
 . The insane thing.
 You really want to call the new version "X" too, 'even though'
-others have already seen the old one. So just use 'git-tag -f'
+others have already seen the old one. So just use 'git tag -f'
 again, as if you hadn't already published the old one.
 
 However, Git does *not* (and it should not) change tags behind
 users back. So if somebody already got the old tag, doing a
-'git-pull' on your tree shouldn't just make them overwrite the old
+'git pull' on your tree shouldn't just make them overwrite the old
 one.
 
 If somebody got a release tag from you, you cannot just change
@@ -182,7 +190,7 @@ private anchor point tags from the other person.
 
 You would notice "please pull" messages on the mailing list says
 repo URL and branch name alone.  This is designed to be easily
-cut&pasted to a 'git-fetch' command line:
+cut&pasted to a 'git fetch' command line:
 
 ------------
 Linus, please pull from
@@ -249,6 +257,10 @@ $ GIT_COMMITTER_DATE="2006-10-02 10:31" git tag -s v1.0.1
 ------------
 
 
+SEE ALSO
+--------
+linkgit:git-check-ref-format[1].
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>,
index a5d9558dd1eabd71e838026721d707c5f1ecc369..3c786bd283ce839ca9339f0a8ac7412a1f985d3a 100644 (file)
@@ -12,19 +12,19 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-THIS COMMAND IS DEPRECATED.  Use 'git-archive' with `--format=tar`
+THIS COMMAND IS DEPRECATED.  Use 'git archive' with `--format=tar`
 option instead (and move the <base> argument to `--prefix=base/`).
 
 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
+'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'.
+It can be extracted using 'git get-tar-commit-id'.
 
 OPTIONS
 -------
index 25e0bbea86caf1234da1746d4a2082cfb80129bd..74d1d49dbffda60a00f6c195d77020b5ec7be979 100644 (file)
@@ -12,15 +12,16 @@ SYNOPSIS
 'git update-index'
             [--add] [--remove | --force-remove] [--replace]
             [--refresh] [-q] [--unmerged] [--ignore-missing]
-            [--cacheinfo <mode> <object> <file>]\*
+            [--cacheinfo <mode> <object> <file>]*
             [--chmod=(+|-)x]
             [--assume-unchanged | --no-assume-unchanged]
+            [--skip-worktree | --no-skip-worktree]
             [--ignore-submodules]
             [--really-refresh] [--unresolve] [--again | -g]
             [--info-only] [--index-info]
             [-z] [--stdin]
             [--verbose]
-            [--] [<file>]\*
+            [--] [<file>]*
 
 DESCRIPTION
 -----------
@@ -31,7 +32,7 @@ cleared.
 See also linkgit:git-add[1] for a more user-friendly way to do some of
 the most common operations on the index.
 
-The way 'git-update-index' handles files it is told about can be modified
+The way 'git update-index' handles files it is told about can be modified
 using the various options:
 
 OPTIONS
@@ -53,7 +54,7 @@ OPTIONS
 -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.
+       'git update-index' continue anyway.
 
 --ignore-submodules::
        Do not try to update submodules.  This option is only respected
@@ -61,7 +62,7 @@ OPTIONS
 
 --unmerged::
         If --refresh finds unmerged changes in the index, the default
-       behavior is to error out.  This option makes 'git-update-index'
+       behavior is to error out.  This option makes 'git update-index'
         continue anyway.
 
 --ignore-missing::
@@ -92,16 +93,25 @@ OPTIONS
 This option can be also used as a coarse file-level mechanism
 to ignore uncommitted changes in tracked files (akin to what
 `.gitignore` does for untracked files).
-You should remember that an explicit 'git add' operation will
-still cause the file to be refreshed from the working tree.
 Git will fail (gracefully) in case it needs to modify this file
 in the index e.g. when merging in a commit;
 thus, in case the assumed-untracked file is changed upstream,
 you will need to handle the situation manually.
 
+--really-refresh::
+       Like '--refresh', but checks stat information unconditionally,
+       without regard to the "assume unchanged" setting.
+
+--skip-worktree::
+--no-skip-worktree::
+       When one of these flags is specified, the object name recorded
+       for the paths are not updated. Instead, these options
+       set and unset the "skip-worktree" bit for the paths. See
+       section "Skip-worktree bit" below for more information.
+
 -g::
 --again::
-       Runs 'git-update-index' itself on the paths whose index
+       Runs 'git update-index' itself on the paths whose index
        entries are different from those from the `HEAD` commit.
 
 --unresolve::
@@ -119,7 +129,7 @@ you will need to handle the situation manually.
 
 --replace::
        By default, when a file `path` exists in the index,
-       'git-update-index' refuses an attempt to add `path/file`.
+       '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 conflict with the entry being added are
@@ -155,7 +165,7 @@ up-to-date for mode/content changes. But what it *does* do is to
 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
+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
@@ -196,13 +206,13 @@ back on 3-way merge.
 
     . mode SP type SP sha1          TAB path
 +
-The second format is to stuff 'git-ls-tree' output
+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.
+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
@@ -259,8 +269,8 @@ 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
+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
@@ -304,16 +314,37 @@ M foo.c
 <9> now it checks with lstat(2) and finds it has been changed.
 
 
+Skip-worktree bit
+-----------------
+
+Skip-worktree bit can be defined in one (long) sentence: When reading
+an entry, if it is marked as skip-worktree, then Git pretends its
+working directory version is up to date and read the index version
+instead.
+
+To elaborate, "reading" means checking for file existence, reading
+file attributes or file content. The working directory version may be
+present or absent. If present, its content may match against the index
+version or not. Writing is not affected by this bit, content safety
+is still first priority. Note that Git _can_ update working directory
+file, that is marked skip-worktree, if it is safe to do so (i.e.
+working directory version matches index version)
+
+Although this bit looks similar to assume-unchanged bit, its goal is
+different from assume-unchanged bit's. Skip-worktree also takes
+precedence over assume-unchanged bit when both are set.
+
+
 Configuration
 -------------
 
 The command honors `core.filemode` configuration variable.  If
-your repository is on an filesystem whose executable bits are
+your repository is on a filesystem whose executable bits are
 unreliable, this should be set to 'false' (see linkgit:git-config[1]).
 This causes the command to ignore differences in file modes recorded
 in the index and the file mode on the filesystem if they differ only on
 executable bit.   On such an unfortunate filesystem, you may
-need to use 'git-update-index --chmod='.
+need to use 'git update-index --chmod='.
 
 Quite similarly, if `core.symlinks` configuration variable is set
 to 'false' (see linkgit:git-config[1]), symbolic links are checked out
index bbd7617587084b0c66fd8e0b9f623cac50be2c03..f5f2b3908b2a1550f44ad6615d9335d40e264112 100644 (file)
@@ -12,11 +12,11 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Invoked by 'git-archive --remote' and sends a generated archive to the
+Invoked by 'git archive --remote' and sends a generated archive to the
 other end over the git protocol.
 
 This command is usually not invoked directly by the end user.  The UI
-for the protocol is on the 'git-archive' side, and the program pair
+for the protocol is on the 'git archive' side, and the program pair
 is meant to be used to get an archive from a remote repository.
 
 OPTIONS
index b8e49dce4a19a4d7083459468f27c273c1d91fea..71ca4ef442e08acad27b4d7873a7f153dc4c79dc 100644 (file)
@@ -8,17 +8,17 @@ git-upload-pack - Send objects packed back to git-fetch-pack
 
 SYNOPSIS
 --------
-'git upload-pack' [--strict] [--timeout=<n>] <directory>
+'git-upload-pack' [--strict] [--timeout=<n>] <directory>
 
 DESCRIPTION
 -----------
-Invoked by 'git-fetch-pack', learns what
+Invoked by '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
+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'.
+repository.  For push operations, see 'git send-pack'.
 
 
 OPTIONS
index e2f4c0901bcb4bcc5361e400ff40d70062c77ae6..458f3e2755fcc6431757af6178942ef756f6a5bd 100644 (file)
@@ -8,7 +8,7 @@ git-var - Show a git logical variable
 
 SYNOPSIS
 --------
-'git var' [ -l | <variable> ]
+'git var' ( -l | <variable> )
 
 DESCRIPTION
 -----------
@@ -20,7 +20,7 @@ OPTIONS
        Cause the logical variables to be listed. In addition, all the
        variables of the git configuration file .git/config are listed
        as well. (However, the configuration variables listing functionality
-       is deprecated in favor of 'git config -l'.)
+       is deprecated in favor of `git config -l`.)
 
 EXAMPLE
 --------
@@ -36,6 +36,20 @@ GIT_AUTHOR_IDENT::
 GIT_COMMITTER_IDENT::
     The person who put a piece of code into git.
 
+GIT_EDITOR::
+    Text editor for use by git commands.  The value is meant to be
+    interpreted by the shell when it is used.  Examples: `~/bin/vi`,
+    `$SOME_ENVIRONMENT_VARIABLE`, `"C:\Program Files\Vim\gvim.exe"
+    --nofork`.  The order of preference is the `$GIT_EDITOR`
+    environment variable, then `core.editor` configuration, then
+    `$VISUAL`, then `$EDITOR`, and then finally 'vi'.
+
+GIT_PAGER::
+    Text viewer for use by git commands (e.g., 'less').  The value
+    is meant to be interpreted by the shell.  The order of preference
+    is the `$GIT_PAGER` environment variable, then `core.pager`
+    configuration, then `$PAGER`, and then finally 'less'.
+
 Diagnostics
 -----------
 You don't exist. Go away!::
index c8611632d1d501d57eb7000de0ec3c3b36810b80..916a38aa99020efe281907670932517a66d075e1 100644 (file)
@@ -8,13 +8,13 @@ git-verify-pack - Validate packed git archive files
 
 SYNOPSIS
 --------
-'git verify-pack' [-v] [--] <pack>.idx ...
+'git verify-pack' [-v|--verbose] [--] <pack>.idx ...
 
 
 DESCRIPTION
 -----------
 Reads given idx file for packed git archive created with the
-'git-pack-objects' command and verifies idx file and the
+'git pack-objects' command and verifies idx file and the
 corresponding pack file.
 
 OPTIONS
@@ -23,8 +23,15 @@ OPTIONS
        The idx files to verify.
 
 -v::
+--verbose::
        After verifying the pack, show list of objects contained
-       in the pack.
+       in the pack and a histogram of delta chain length.
+
+-s::
+--stat-only::
+       Do not verify the pack contents; only show the histogram of delta
+       chain length.  With `--verbose`, list of objects is also shown.
+
 \--::
        Do not interpret any more arguments as options.
 
index 84e70a02348105c98a004c080875ab8e85fe099c..dada21242c915148543dda8485b3322894aeb4c5 100644 (file)
@@ -11,7 +11,7 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Validates the gpg signature created by 'git-tag'.
+Validates the gpg signature created by 'git tag'.
 
 OPTIONS
 -------
index 278cf735279c13ab129ee28f1ef8e638ccf61dee..e1586c78c3d271c78bcf26827cf10ff234fcba62 100644 (file)
@@ -1,5 +1,5 @@
-git-web--browse(1)
-==================
+git-web{litdd}browse(1)
+=======================
 
 NAME
 ----
@@ -7,7 +7,7 @@ git-web--browse - git helper script to launch a web browser
 
 SYNOPSIS
 --------
-'git web--browse' [OPTIONS] URL/FILE ...
+'git web{litdd}browse' [OPTIONS] URL/FILE ...
 
 DESCRIPTION
 -----------
@@ -62,7 +62,7 @@ browser.<tool>.path
 You can explicitly provide a full path to your preferred browser by
 setting the configuration variable 'browser.<tool>.path'. For example,
 you can configure the absolute path to firefox by setting
-'browser.firefox.path'. Otherwise, 'git-web--browse' assumes the tool
+'browser.firefox.path'. Otherwise, 'git web--browse' assumes the tool
 is available in PATH.
 
 browser.<tool>.cmd
@@ -71,7 +71,7 @@ browser.<tool>.cmd
 When the browser, specified by options or configuration variables, is
 not among the supported ones, then the corresponding
 'browser.<tool>.cmd' configuration variable will be looked up. If this
-variable exists then 'git-web--browse' will treat the specified tool
+variable exists then 'git web{litdd}browse' will treat the specified tool
 as a custom command and will use a shell eval to run the command with
 the URLs passed as arguments.
 
@@ -113,7 +113,7 @@ See linkgit:git-config[1] for more information about this.
 Author
 ------
 Written by Christian Couder <chriscool@tuxfamily.org> and the git-list
-<git@vger.kernel.org>, based on 'git-mergetool' by Theodore Y. Ts'o.
+<git@vger.kernel.org>, based on 'git mergetool' by Theodore Y. Ts'o.
 
 Documentation
 -------------
index cadfbd90403766d44598c8d96d89dc5a0e4e2ef8..ea753cdafce66c33a0a0e8d9a52d75c974194ad5 100644 (file)
@@ -13,8 +13,8 @@ SYNOPSIS
 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
+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.
index 26d3850e7317c22dcf0999e0c4a6afe9a5ea2e03..bfceacacb34262b73507a87fa23995d1183a3edc 100644 (file)
@@ -12,21 +12,22 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Creates a tree object using the current index.
+Creates a tree object using the current index. The name of the new
+tree object is printed to standard output.
 
 The index must be in a fully merged state.
 
-Conceptually, 'git-write-tree' sync()s the current index contents
+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'.
+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
+       Normally 'git write-tree' ensures that the objects referenced by the
        directory exist in the object database.  This option disables this
        check.
 
index 9d8f236fe84cd02550b8cf08e835b2926e6c45f0..69ef12e272ed2ee762c5f8e1319d2b690d89fbad 100644 (file)
@@ -10,8 +10,9 @@ SYNOPSIS
 --------
 [verse]
 'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]
-    [-p|--paginate|--no-pager]
+    [-p|--paginate|--no-pager] [--no-replace-objects]
     [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]
+    [-c name=value]
     [--help] COMMAND [ARGS]
 
 DESCRIPTION
@@ -43,113 +44,176 @@ unreleased) version of git, that is available from 'master'
 branch of the `git.git` repository.
 Documentation for older releases are available here:
 
-* link:v1.6.3/git.html[documentation for release 1.6.3]
+* link:v1.7.2.3/git.html[documentation for release 1.7.2.3]
 
 * release notes for
-  link:RelNotes-1.6.2.5.txt[1.6.2.5],
-  link:RelNotes-1.6.2.4.txt[1.6.2.4],
-  link:RelNotes-1.6.2.3.txt[1.6.2.3],
-  link:RelNotes-1.6.2.2.txt[1.6.2.2],
-  link:RelNotes-1.6.2.1.txt[1.6.2.1],
-  link:RelNotes-1.6.2.txt[1.6.2].
+  link:RelNotes/1.7.2.3.txt[1.7.2.3],
+  link:RelNotes/1.7.2.2.txt[1.7.2.2],
+  link:RelNotes/1.7.2.1.txt[1.7.2.1],
+  link:RelNotes/1.7.2.txt[1.7.2].
+
+* link:v1.7.1.2/git.html[documentation for release 1.7.1.2]
+
+* release notes for
+  link:RelNotes/1.7.1.2.txt[1.7.1.2],
+  link:RelNotes/1.7.1.1.txt[1.7.1.1],
+  link:RelNotes/1.7.1.txt[1.7.1].
+
+* link:v1.7.0.7/git.html[documentation for release 1.7.0.7]
+
+* release notes for
+  link:RelNotes/1.7.0.7.txt[1.7.0.7],
+  link:RelNotes/1.7.0.6.txt[1.7.0.6],
+  link:RelNotes/1.7.0.5.txt[1.7.0.5],
+  link:RelNotes/1.7.0.4.txt[1.7.0.4],
+  link:RelNotes/1.7.0.3.txt[1.7.0.3],
+  link:RelNotes/1.7.0.2.txt[1.7.0.2],
+  link:RelNotes/1.7.0.1.txt[1.7.0.1],
+  link:RelNotes/1.7.0.txt[1.7.0].
+
+* link:v1.6.6.2/git.html[documentation for release 1.6.6.2]
+
+* release notes for
+  link:RelNotes/1.6.6.2.txt[1.6.6.2],
+  link:RelNotes/1.6.6.1.txt[1.6.6.1],
+  link:RelNotes/1.6.6.txt[1.6.6].
+
+* link:v1.6.5.8/git.html[documentation for release 1.6.5.8]
+
+* release notes for
+  link:RelNotes/1.6.5.8.txt[1.6.5.8],
+  link:RelNotes/1.6.5.7.txt[1.6.5.7],
+  link:RelNotes/1.6.5.6.txt[1.6.5.6],
+  link:RelNotes/1.6.5.5.txt[1.6.5.5],
+  link:RelNotes/1.6.5.4.txt[1.6.5.4],
+  link:RelNotes/1.6.5.3.txt[1.6.5.3],
+  link:RelNotes/1.6.5.2.txt[1.6.5.2],
+  link:RelNotes/1.6.5.1.txt[1.6.5.1],
+  link:RelNotes/1.6.5.txt[1.6.5].
+
+* link:v1.6.4.4/git.html[documentation for release 1.6.4.4]
+
+* release notes for
+  link:RelNotes/1.6.4.4.txt[1.6.4.4],
+  link:RelNotes/1.6.4.3.txt[1.6.4.3],
+  link:RelNotes/1.6.4.2.txt[1.6.4.2],
+  link:RelNotes/1.6.4.1.txt[1.6.4.1],
+  link:RelNotes/1.6.4.txt[1.6.4].
+
+* link:v1.6.3.4/git.html[documentation for release 1.6.3.4]
+
+* release notes for
+  link:RelNotes/1.6.3.4.txt[1.6.3.4],
+  link:RelNotes/1.6.3.3.txt[1.6.3.3],
+  link:RelNotes/1.6.3.2.txt[1.6.3.2],
+  link:RelNotes/1.6.3.1.txt[1.6.3.1],
+  link:RelNotes/1.6.3.txt[1.6.3].
+
+* release notes for
+  link:RelNotes/1.6.2.5.txt[1.6.2.5],
+  link:RelNotes/1.6.2.4.txt[1.6.2.4],
+  link:RelNotes/1.6.2.3.txt[1.6.2.3],
+  link:RelNotes/1.6.2.2.txt[1.6.2.2],
+  link:RelNotes/1.6.2.1.txt[1.6.2.1],
+  link:RelNotes/1.6.2.txt[1.6.2].
 
 * link:v1.6.1.3/git.html[documentation for release 1.6.1.3]
 
 * release notes for
-  link:RelNotes-1.6.1.3.txt[1.6.1.3],
-  link:RelNotes-1.6.1.2.txt[1.6.1.2],
-  link:RelNotes-1.6.1.1.txt[1.6.1.1],
-  link:RelNotes-1.6.1.txt[1.6.1].
+  link:RelNotes/1.6.1.3.txt[1.6.1.3],
+  link:RelNotes/1.6.1.2.txt[1.6.1.2],
+  link:RelNotes/1.6.1.1.txt[1.6.1.1],
+  link:RelNotes/1.6.1.txt[1.6.1].
 
 * link:v1.6.0.6/git.html[documentation for release 1.6.0.6]
 
 * release notes for
-  link:RelNotes-1.6.0.6.txt[1.6.0.6],
-  link:RelNotes-1.6.0.5.txt[1.6.0.5],
-  link:RelNotes-1.6.0.4.txt[1.6.0.4],
-  link:RelNotes-1.6.0.3.txt[1.6.0.3],
-  link:RelNotes-1.6.0.2.txt[1.6.0.2],
-  link:RelNotes-1.6.0.1.txt[1.6.0.1],
-  link:RelNotes-1.6.0.txt[1.6.0].
+  link:RelNotes/1.6.0.6.txt[1.6.0.6],
+  link:RelNotes/1.6.0.5.txt[1.6.0.5],
+  link:RelNotes/1.6.0.4.txt[1.6.0.4],
+  link:RelNotes/1.6.0.3.txt[1.6.0.3],
+  link:RelNotes/1.6.0.2.txt[1.6.0.2],
+  link:RelNotes/1.6.0.1.txt[1.6.0.1],
+  link:RelNotes/1.6.0.txt[1.6.0].
 
 * link:v1.5.6.6/git.html[documentation for release 1.5.6.6]
 
 * release notes for
-  link:RelNotes-1.5.6.6.txt[1.5.6.6],
-  link:RelNotes-1.5.6.5.txt[1.5.6.5],
-  link:RelNotes-1.5.6.4.txt[1.5.6.4],
-  link:RelNotes-1.5.6.3.txt[1.5.6.3],
-  link:RelNotes-1.5.6.2.txt[1.5.6.2],
-  link:RelNotes-1.5.6.1.txt[1.5.6.1],
-  link:RelNotes-1.5.6.txt[1.5.6].
+  link:RelNotes/1.5.6.6.txt[1.5.6.6],
+  link:RelNotes/1.5.6.5.txt[1.5.6.5],
+  link:RelNotes/1.5.6.4.txt[1.5.6.4],
+  link:RelNotes/1.5.6.3.txt[1.5.6.3],
+  link:RelNotes/1.5.6.2.txt[1.5.6.2],
+  link:RelNotes/1.5.6.1.txt[1.5.6.1],
+  link:RelNotes/1.5.6.txt[1.5.6].
 
 * link:v1.5.5.6/git.html[documentation for release 1.5.5.6]
 
 * release notes for
-  link:RelNotes-1.5.5.6.txt[1.5.5.6],
-  link:RelNotes-1.5.5.5.txt[1.5.5.5],
-  link:RelNotes-1.5.5.4.txt[1.5.5.4],
-  link:RelNotes-1.5.5.3.txt[1.5.5.3],
-  link:RelNotes-1.5.5.2.txt[1.5.5.2],
-  link:RelNotes-1.5.5.1.txt[1.5.5.1],
-  link:RelNotes-1.5.5.txt[1.5.5].
+  link:RelNotes/1.5.5.6.txt[1.5.5.6],
+  link:RelNotes/1.5.5.5.txt[1.5.5.5],
+  link:RelNotes/1.5.5.4.txt[1.5.5.4],
+  link:RelNotes/1.5.5.3.txt[1.5.5.3],
+  link:RelNotes/1.5.5.2.txt[1.5.5.2],
+  link:RelNotes/1.5.5.1.txt[1.5.5.1],
+  link:RelNotes/1.5.5.txt[1.5.5].
 
 * link:v1.5.4.7/git.html[documentation for release 1.5.4.7]
 
 * release notes for
-  link:RelNotes-1.5.4.7.txt[1.5.4.7],
-  link:RelNotes-1.5.4.6.txt[1.5.4.6],
-  link:RelNotes-1.5.4.5.txt[1.5.4.5],
-  link:RelNotes-1.5.4.4.txt[1.5.4.4],
-  link:RelNotes-1.5.4.3.txt[1.5.4.3],
-  link:RelNotes-1.5.4.2.txt[1.5.4.2],
-  link:RelNotes-1.5.4.1.txt[1.5.4.1],
-  link:RelNotes-1.5.4.txt[1.5.4].
+  link:RelNotes/1.5.4.7.txt[1.5.4.7],
+  link:RelNotes/1.5.4.6.txt[1.5.4.6],
+  link:RelNotes/1.5.4.5.txt[1.5.4.5],
+  link:RelNotes/1.5.4.4.txt[1.5.4.4],
+  link:RelNotes/1.5.4.3.txt[1.5.4.3],
+  link:RelNotes/1.5.4.2.txt[1.5.4.2],
+  link:RelNotes/1.5.4.1.txt[1.5.4.1],
+  link:RelNotes/1.5.4.txt[1.5.4].
 
 * link:v1.5.3.8/git.html[documentation for release 1.5.3.8]
 
 * release notes for
-  link:RelNotes-1.5.3.8.txt[1.5.3.8],
-  link:RelNotes-1.5.3.7.txt[1.5.3.7],
-  link:RelNotes-1.5.3.6.txt[1.5.3.6],
-  link:RelNotes-1.5.3.5.txt[1.5.3.5],
-  link:RelNotes-1.5.3.4.txt[1.5.3.4],
-  link:RelNotes-1.5.3.3.txt[1.5.3.3],
-  link:RelNotes-1.5.3.2.txt[1.5.3.2],
-  link:RelNotes-1.5.3.1.txt[1.5.3.1],
-  link:RelNotes-1.5.3.txt[1.5.3].
+  link:RelNotes/1.5.3.8.txt[1.5.3.8],
+  link:RelNotes/1.5.3.7.txt[1.5.3.7],
+  link:RelNotes/1.5.3.6.txt[1.5.3.6],
+  link:RelNotes/1.5.3.5.txt[1.5.3.5],
+  link:RelNotes/1.5.3.4.txt[1.5.3.4],
+  link:RelNotes/1.5.3.3.txt[1.5.3.3],
+  link:RelNotes/1.5.3.2.txt[1.5.3.2],
+  link:RelNotes/1.5.3.1.txt[1.5.3.1],
+  link:RelNotes/1.5.3.txt[1.5.3].
 
 * link:v1.5.2.5/git.html[documentation for release 1.5.2.5]
 
 * release notes for
-  link:RelNotes-1.5.2.5.txt[1.5.2.5],
-  link:RelNotes-1.5.2.4.txt[1.5.2.4],
-  link:RelNotes-1.5.2.3.txt[1.5.2.3],
-  link:RelNotes-1.5.2.2.txt[1.5.2.2],
-  link:RelNotes-1.5.2.1.txt[1.5.2.1],
-  link:RelNotes-1.5.2.txt[1.5.2].
+  link:RelNotes/1.5.2.5.txt[1.5.2.5],
+  link:RelNotes/1.5.2.4.txt[1.5.2.4],
+  link:RelNotes/1.5.2.3.txt[1.5.2.3],
+  link:RelNotes/1.5.2.2.txt[1.5.2.2],
+  link:RelNotes/1.5.2.1.txt[1.5.2.1],
+  link:RelNotes/1.5.2.txt[1.5.2].
 
 * link:v1.5.1.6/git.html[documentation for release 1.5.1.6]
 
 * release notes for
-  link:RelNotes-1.5.1.6.txt[1.5.1.6],
-  link:RelNotes-1.5.1.5.txt[1.5.1.5],
-  link:RelNotes-1.5.1.4.txt[1.5.1.4],
-  link:RelNotes-1.5.1.3.txt[1.5.1.3],
-  link:RelNotes-1.5.1.2.txt[1.5.1.2],
-  link:RelNotes-1.5.1.1.txt[1.5.1.1],
-  link:RelNotes-1.5.1.txt[1.5.1].
+  link:RelNotes/1.5.1.6.txt[1.5.1.6],
+  link:RelNotes/1.5.1.5.txt[1.5.1.5],
+  link:RelNotes/1.5.1.4.txt[1.5.1.4],
+  link:RelNotes/1.5.1.3.txt[1.5.1.3],
+  link:RelNotes/1.5.1.2.txt[1.5.1.2],
+  link:RelNotes/1.5.1.1.txt[1.5.1.1],
+  link:RelNotes/1.5.1.txt[1.5.1].
 
 * link:v1.5.0.7/git.html[documentation for release 1.5.0.7]
 
 * release notes for
-  link:RelNotes-1.5.0.7.txt[1.5.0.7],
-  link:RelNotes-1.5.0.6.txt[1.5.0.6],
-  link:RelNotes-1.5.0.5.txt[1.5.0.5],
-  link:RelNotes-1.5.0.3.txt[1.5.0.3],
-  link:RelNotes-1.5.0.2.txt[1.5.0.2],
-  link:RelNotes-1.5.0.1.txt[1.5.0.1],
-  link:RelNotes-1.5.0.txt[1.5.0].
+  link:RelNotes/1.5.0.7.txt[1.5.0.7],
+  link:RelNotes/1.5.0.6.txt[1.5.0.6],
+  link:RelNotes/1.5.0.5.txt[1.5.0.5],
+  link:RelNotes/1.5.0.3.txt[1.5.0.3],
+  link:RelNotes/1.5.0.2.txt[1.5.0.2],
+  link:RelNotes/1.5.0.1.txt[1.5.0.1],
+  link:RelNotes/1.5.0.txt[1.5.0].
 
 * documentation for release link:v1.4.4.4/git.html[1.4.4.4],
   link:v1.3.3/git.html[1.3.3],
@@ -176,6 +240,12 @@ displayed. See linkgit:git-help[1] for more information,
 because `git --help ...` is converted internally into `git
 help ...`.
 
+-c <name>=<value>::
+       Pass a configuration parameter to the command. The value
+       given will override values from configuration files.
+       The <name> is expected in the same format as listed by
+       'git config' (subkeys separated by dots).
+
 --exec-path::
        Path to wherever your core git programs are installed.
        This can also be controlled by setting the GIT_EXEC_PATH
@@ -188,7 +258,10 @@ help ...`.
 
 -p::
 --paginate::
-       Pipe all output into 'less' (or if set, $PAGER).
+       Pipe all output into 'less' (or if set, $PAGER) if standard
+       output is a terminal.  This overrides the `pager.<cmd>`
+       configuration options (see the "Configuration Mechanism" section
+       below).
 
 --no-pager::
        Do not pipe git output into a pager.
@@ -216,6 +289,10 @@ help ...`.
        environment is not set, it is set to the current working
        directory.
 
+--no-replace-objects::
+       Do not use replacement refs to replace git objects. See
+       linkgit:git-replace[1] for more information.
+
 
 FURTHER DOCUMENTATION
 ---------------------
@@ -227,6 +304,8 @@ The link:user-manual.html#git-concepts[git concepts chapter of the
 user-manual] and linkgit:gitcore-tutorial[7] both provide
 introductions to the underlying git architecture.
 
+See linkgit:gitworkflows[7] for an overview of recommended workflows.
+
 See also the link:howto-index.html[howto] documents for some useful
 examples.
 
@@ -313,7 +392,7 @@ Synching repositories
 
 include::cmds-synchingrepositories.txt[]
 
-The following are helper programs used by the above; end users
+The following are helper commands used by the above; end users
 typically do not use them directly.
 
 include::cmds-synchelpers.txt[]
@@ -354,7 +433,8 @@ people.  Here is an example:
 ------------
 
 Various commands read from the configuration file and adjust
-their operation accordingly.
+their operation accordingly.  See linkgit:git-config[1] for a
+list.
 
 
 Identifier Terminology
@@ -409,7 +489,7 @@ HEAD::
        (i.e. the contents of `$GIT_DIR/refs/heads/<head>`).
 
 For a more complete list of ways to spell object names, see
-"SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
+"SPECIFYING REVISIONS" section in linkgit:gitrevisions[1].
 
 
 File/Directory Structure
@@ -476,6 +556,16 @@ git so take care if using Cogito etc.
        a GIT_DIR set on the command line or in the environment.
        (Useful for excluding slow-loading network directories.)
 
+'GIT_DISCOVERY_ACROSS_FILESYSTEM'::
+       When run in a directory that does not have ".git" repository
+       directory, git tries to find such a directory in the parent
+       directories to find the top of the working tree, but by default it
+       does not cross filesystem boundaries.  This environment variable
+       can be set to true to tell git not to stop at filesystem
+       boundaries.  Like 'GIT_CEILING_DIRECTORIES', this will not affect
+       an explicit repository directory set via 'GIT_DIR' or on the
+       command line.
+
 git Commits
 ~~~~~~~~~~~
 'GIT_AUTHOR_NAME'::
@@ -534,8 +624,8 @@ other
        linkgit:git-config[1].
 
 'GIT_SSH'::
-       If this environment variable is set then 'git-fetch'
-       and 'git-push' will use this command instead
+       If this environment variable is set then 'git fetch'
+       and 'git push' will use this command instead
        of 'ssh' when they need to connect to a remote system.
        The '$GIT_SSH' command will be given exactly two arguments:
        the 'username@host' (or just 'host') from the URL and the
@@ -549,10 +639,17 @@ Usually it is easier to configure any desired options through your
 personal `.ssh/config` file.  Please consult your ssh documentation
 for further details.
 
+'GIT_ASKPASS'::
+       If this environment variable is set, then git commands which need to
+       acquire passwords or passphrases (e.g. for HTTP or IMAP authentication)
+       will call this program with a suitable prompt as command line argument
+       and read the password from its STDOUT. See also the 'core.askpass'
+       option in linkgit:git-config[1].
+
 'GIT_FLUSH'::
        If this environment variable is set to "1", then commands such
-       as 'git-blame' (in incremental mode), 'git-rev-list', 'git-log',
-       and 'git-whatchanged' will force a flush of the output stream
+       as 'git blame' (in incremental mode), 'git rev-list', 'git log',
+       and 'git whatchanged' will force a flush of the output stream
        after each commit-oriented record have been flushed.   If this
        variable is set to "0", the output of these commands will be done
        using completely buffered I/O.   If this environment variable is
@@ -639,12 +736,20 @@ 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>.
 
+Reporting Bugs
+--------------
+
+Report bugs to the Git mailing list <git@vger.kernel.org> where the
+development and maintenance is primarily done.  You do not have to be
+subscribed to the list to send a message there.
+
 SEE ALSO
 --------
 linkgit:gittutorial[7], linkgit:gittutorial-2[7],
 link:everyday.html[Everyday Git], linkgit:gitcvs-migration[7],
 linkgit:gitglossary[7], linkgit:gitcore-tutorial[7],
-linkgit:gitcli[7], link:user-manual.html[The Git User's Manual]
+linkgit:gitcli[7], link:user-manual.html[The Git User's Manual],
+linkgit:gitworkflows[7]
 
 GIT
 ---
index aaa073efc80522a649f17d60127aae8cc85b0b3b..e5a27d875eb63dbfeccbf5357983f5c94668ffeb 100644 (file)
@@ -88,57 +88,158 @@ Checking-out and checking-in
 
 These attributes affect how the contents stored in the
 repository are copied to the working tree files when commands
-such as 'git-checkout' and 'git-merge' run.  They also affect how
+such as 'git checkout' and 'git merge' run.  They also affect how
 git stores the contents you prepare in the working tree in the
-repository upon 'git-add' and 'git-commit'.
+repository upon 'git add' and 'git commit'.
 
-`crlf`
+`text`
 ^^^^^^
 
-This attribute controls the line-ending convention.
+This attribute enables and controls end-of-line normalization.  When a
+text file is normalized, its line endings are converted to LF in the
+repository.  To control what line ending style is used in the working
+directory, use the `eol` attribute for a single file and the
+`core.eol` configuration variable for all text files.
 
 Set::
 
-       Setting the `crlf` attribute on a path is meant to mark
-       the path as a "text" file.  'core.autocrlf' conversion
-       takes place without guessing the content type by
-       inspection.
+       Setting the `text` attribute on a path enables end-of-line
+       normalization and marks the path as a text file.  End-of-line
+       conversion takes place without guessing the content type.
 
 Unset::
 
-       Unsetting the `crlf` attribute on a path tells git not to
+       Unsetting the `text` attribute on a path tells git not to
        attempt any end-of-line conversion upon checkin or checkout.
 
+Set to string value "auto"::
+
+       When `text` is set to "auto", the path is marked for automatic
+       end-of-line normalization.  If git decides that the content is
+       text, its line endings are normalized to LF on checkin.
+
 Unspecified::
 
-       Unspecified `crlf` attribute tells git to apply the
-       `core.autocrlf` conversion when the file content looks
-       like text.
+       If the `text` attribute is unspecified, git uses the
+       `core.autocrlf` configuration variable to determine if the
+       file should be converted.
 
-Set to string value "input"::
+Any other value causes git to act as if `text` has been left
+unspecified.
 
-       This is similar to setting the attribute to `true`, but
-       also forces git to act as if `core.autocrlf` is set to
-       `input` for the path.
+`eol`
+^^^^^
 
-Any other value set to `crlf` attribute is ignored and git acts
-as if the attribute is left unspecified.
+This attribute sets a specific line-ending style to be used in the
+working directory.  It enables end-of-line normalization without any
+content checks, effectively setting the `text` attribute.
 
+Set to string value "crlf"::
 
-The `core.autocrlf` conversion
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+       This setting forces git to normalize line endings for this
+       file on checkin and convert them to CRLF when the file is
+       checked out.
+
+Set to string value "lf"::
+
+       This setting forces git to normalize line endings to LF on
+       checkin and prevents conversion to CRLF when the file is
+       checked out.
+
+Backwards compatibility with `crlf` attribute
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+For backwards compatibility, the `crlf` attribute is interpreted as
+follows:
+
+------------------------
+crlf           text
+-crlf          -text
+crlf=input     eol=lf
+------------------------
+
+End-of-line conversion
+^^^^^^^^^^^^^^^^^^^^^^
+
+While git normally leaves file contents alone, it can be configured to
+normalize line endings to LF in the repository and, optionally, to
+convert them to CRLF when files are checked out.
+
+Here is an example that will make git normalize .txt, .vcproj and .sh
+files, ensure that .vcproj files have CRLF and .sh files have LF in
+the working directory, and prevent .jpg files from being normalized
+regardless of their content.
+
+------------------------
+*.txt          text
+*.vcproj       eol=crlf
+*.sh           eol=lf
+*.jpg          -text
+------------------------
 
-If the configuration variable `core.autocrlf` is false, no
-conversion is done.
+Other source code management systems normalize all text files in their
+repositories, and there are two ways to enable similar automatic
+normalization in git.
 
-When `core.autocrlf` is true, it means that the platform wants
-CRLF line endings for files in the working tree, and you want to
-convert them back to the normal LF line endings when checking
-in to the repository.
+If you simply want to have CRLF line endings in your working directory
+regardless of the repository you are working with, you can set the
+config variable "core.autocrlf" without changing any attributes.
 
-When `core.autocrlf` is set to "input", line endings are
-converted to LF upon checkin, but there is no conversion done
-upon checkout.
+------------------------
+[core]
+       autocrlf = true
+------------------------
+
+This does not force normalization of all text files, but does ensure
+that text files that you introduce to the repository have their line
+endings normalized to LF when they are added, and that files that are
+already normalized in the repository stay normalized.
+
+If you want to interoperate with a source code management system that
+enforces end-of-line normalization, or you simply want all text files
+in your repository to be normalized, you should instead set the `text`
+attribute to "auto" for _all_ files.
+
+------------------------
+*      text=auto
+------------------------
+
+This ensures that all files that git considers to be text will have
+normalized (LF) line endings in the repository.  The `core.eol`
+configuration variable controls which line endings git will use for
+normalized files in your working directory; the default is to use the
+native line ending for your platform, or CRLF if `core.autocrlf` is
+set.
+
+NOTE: When `text=auto` normalization is enabled in an existing
+repository, any text files containing CRLFs should be normalized.  If
+they are not they will be normalized the next time someone tries to
+change them, causing unfortunate misattribution.  From a clean working
+directory:
+
+-------------------------------------------------
+$ echo "* text=auto" >>.gitattributes
+$ rm .git/index     # Remove the index to force git to
+$ git reset         # re-scan the working directory
+$ git status        # Show files that will be normalized
+$ git add -u
+$ git add .gitattributes
+$ git commit -m "Introduce end-of-line normalization"
+-------------------------------------------------
+
+If any files that should not be normalized show up in 'git status',
+unset their `text` attribute before running 'git add -u'.
+
+------------------------
+manual.pdf     -text
+------------------------
+
+Conversely, text files that git does not detect can have normalization
+enabled manually.
+
+------------------------
+weirdchars.txt text
+------------------------
 
 If `core.safecrlf` is set to "true" or "warn", git verifies if
 the conversion is reversible for the current setting of
@@ -148,16 +249,16 @@ an irreversible conversion.  The safety triggers to prevent such
 a conversion done to the files in the work tree, but there are a
 few exceptions.  Even though...
 
-- 'git-add' itself does not touch the files in the work tree, the
+- 'git add' itself does not touch the files in the work tree, the
   next checkout would, so the safety triggers;
 
-- 'git-apply' to update a text file with a patch does touch the files
+- 'git apply' to update a text file with a patch does touch the files
   in the work tree, but the operation is about text files and CRLF
   conversion is about fixing the line ending inconsistencies, so the
   safety does not trigger;
 
-- 'git-diff' itself does not touch the files in the work tree, it is
-  often run to inspect the changes you intend to next 'git-add'.  To
+- 'git diff' itself does not touch the files in the work tree, it is
+  often run to inspect the changes you intend to next 'git add'.  To
   catch potential problems early, safety triggers.
 
 
@@ -197,6 +298,36 @@ intent is that if someone unsets the filter driver definition,
 or does not have the appropriate filter program, the project
 should still be usable.
 
+For example, in .gitattributes, you would assign the `filter`
+attribute for paths.
+
+------------------------
+*.c    filter=indent
+------------------------
+
+Then you would define a "filter.indent.clean" and "filter.indent.smudge"
+configuration in your .git/config to specify a pair of commands to
+modify the contents of C programs when the source files are checked
+in ("clean" is run) and checked out (no change is made because the
+command is "cat").
+
+------------------------
+[filter "indent"]
+       clean = indent
+       smudge = cat
+------------------------
+
+For best results, `clean` should not alter its output further if it is
+run twice ("clean->clean" should be equivalent to "clean"), and
+multiple `smudge` commands should not alter `clean`'s output
+("smudge->smudge->clean" should be equivalent to "clean").  See the
+section on merging below.
+
+The "indent" filter is well-behaved in this regard: it will not modify
+input that is already correctly indented.  In this case, the lack of a
+smudge filter means that the clean filter _must_ accept its own output
+without modifying it.
+
 
 Interaction between checkin/checkout attributes
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -204,11 +335,34 @@ Interaction between checkin/checkout attributes
 In the check-in codepath, the worktree file is first converted
 with `filter` driver (if specified and corresponding driver
 defined), then the result is processed with `ident` (if
-specified), and then finally with `crlf` (again, if specified
+specified), and then finally with `text` (again, if specified
 and applicable).
 
 In the check-out codepath, the blob content is first converted
-with `crlf`, and then `ident` and fed to `filter`.
+with `text`, and then `ident` and fed to `filter`.
+
+
+Merging branches with differing checkin/checkout attributes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If you have added attributes to a file that cause the canonical
+repository format for that file to change, such as adding a
+clean/smudge filter or text/eol/ident attributes, merging anything
+where the attribute is not in place would normally cause merge
+conflicts.
+
+To prevent these unnecessary merge conflicts, git can be told to run a
+virtual check-out and check-in of all three stages of a file when
+resolving a three-way merge by setting the `merge.renormalize`
+configuration variable.  This prevents changes caused by check-in
+conversion from causing spurious merge conflicts when a converted file
+is merged with an unconverted file.
+
+As long as a "smudge->clean" results in the same output as a "clean"
+even on files that are already smudged, this strategy will
+automatically resolve all filter-related conflicts.  Filters that do
+not act in this way may cause additional merge conflicts that must be
+resolved manually.
 
 
 Generating diff text
@@ -321,6 +475,8 @@ patterns are available:
 
 - `cpp` suitable for source code in the C and C++ languages.
 
+- `csharp` suitable for source code in the C# language.
+
 - `html` suitable for HTML/XHTML documents.
 
 - `java` suitable for source code in the Java language.
@@ -341,7 +497,7 @@ patterns are available:
 Customizing word diff
 ^^^^^^^^^^^^^^^^^^^^^
 
-You can customize the rules that `git diff --color-words` uses to
+You can customize the rules that `git diff --word-diff` uses to
 split words in a line, by specifying an appropriate regular expression
 in the "diff.*.wordRegex" configuration variable.  For example, in TeX
 a backslash followed by a sequence of letters forms a command, but
@@ -395,6 +551,26 @@ because it quickly conveys the changes you have made), you
 should generate it separately and send it as a comment _in
 addition to_ the usual binary diff that you might send.
 
+Because text conversion can be slow, especially when doing a
+large number of them with `git log -p`, git provides a mechanism
+to cache the output and use it in future diffs.  To enable
+caching, set the "cachetextconv" variable in your diff driver's
+config. For example:
+
+------------------------
+[diff "jpg"]
+       textconv = exif
+       cachetextconv = true
+------------------------
+
+This will cache the result of running "exif" on each blob
+indefinitely. If you change the textconv config variable for a
+diff driver, git will automatically invalidate the cache entries
+and re-run the textconv filter. If you want to invalidate the
+cache manually (e.g., because your version of "exif" was updated
+and now produces better output), you can remove the cache
+manually with `git update-ref -d refs/notes/textconv/jpg` (where
+"jpg" is the name of the diff driver, as in the example above).
 
 Performing a three-way merge
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -404,7 +580,7 @@ Performing a three-way merge
 
 The attribute `merge` affects how three versions of a file is
 merged when a file-level merge is necessary during `git merge`,
-and other programs such as `git revert` and `git cherry-pick`.
+and other commands such as `git revert` and `git cherry-pick`.
 
 Set::
 
@@ -492,7 +668,8 @@ command to run to merge ancestor's version (`%O`), current
 version (`%A`) and the other branches' version (`%B`).  These
 three tokens are replaced with the names of temporary files that
 hold the contents of these versions when the command line is
-built.
+built. Additionally, %L will be replaced with the conflict marker
+size (see below).
 
 The merge driver is expected to leave the result of the merge in
 the file named with `%A` by overwriting it, and exit with zero
@@ -506,6 +683,23 @@ When left unspecified, the driver itself is used for both
 internal merge and the final merge.
 
 
+`conflict-marker-size`
+^^^^^^^^^^^^^^^^^^^^^^
+
+This attribute controls the length of conflict markers left in
+the work tree file during a conflicted merge.  Only setting to
+the value to a positive integer has any meaningful effect.
+
+For example, this line in `.gitattributes` can be used to tell the merge
+machinery to leave much longer (instead of the usual 7-character-long)
+conflict markers when merging the file `Documentation/git-merge.txt`
+results in a conflict.
+
+------------------------
+Documentation/git-merge.txt    conflict-marker-size=32
+------------------------
+
+
 Checking whitespace errors
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -560,6 +754,16 @@ in the file.  E.g. the string `$Format:%H$` will be replaced by the
 commit hash.
 
 
+Packing objects
+~~~~~~~~~~~~~~~
+
+`delta`
+^^^^^^^
+
+Delta compression will not be attempted for blobs for paths with the
+attribute `delta` set to false.
+
+
 Viewing files in GUI tools
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -584,7 +788,7 @@ You do not want any end-of-line conversions applied to, nor textual diffs
 produced for, any binary file you track.  You would need to specify e.g.
 
 ------------
-*.jpg -crlf -diff
+*.jpg -text -diff
 ------------
 
 but that may become cumbersome, when you have many attributes.  Using
@@ -597,7 +801,7 @@ the same time.  The system knows a built-in attribute macro, `binary`:
 
 which is equivalent to the above.  Note that the attribute macros can only
 be "Set" (see the above example that sets "binary" macro as if it were an
-ordinary attribute --- setting it in turn unsets "crlf" and "diff").
+ordinary attribute --- setting it in turn unsets "text" and "diff").
 
 
 DEFINING ATTRIBUTE MACROS
@@ -608,7 +812,7 @@ at the toplevel (i.e. not in any subdirectory).  The built-in attribute
 macro "binary" is equivalent to:
 
 ------------
-[attr]binary -diff -crlf
+[attr]binary -diff -text
 ------------
 
 
index be39ed7c1509158867998ce8fb77341242ecdf79..6928724a05f304c430e8d7f60cc35d22e4873bca 100644 (file)
@@ -81,7 +81,7 @@ couple of magic command line options:
 +
 ---------------------------------------------
 $ git describe -h
-usage: git-describe [options] <committish>*
+usage: git describe [options] <committish>*
 
     --contains            find the tag that comes after the commit
     --debug               debug search strategy on stderr
index 7ba5e589d7e824c526482c9707a5c26ac730cc9e..5e9c5ebba3e3239f61195155448c960cfe476463 100644 (file)
@@ -12,7 +12,7 @@ git *
 DESCRIPTION
 -----------
 
-This tutorial explains how to use the "core" git programs to set up and
+This tutorial explains how to use the "core" git commands to set up and
 work with a git repository.
 
 If you just need to use git as a revision control system you may prefer
@@ -27,6 +27,14 @@ 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.
 
+Back when this document was originally written, many porcelain
+commands were shell scripts. For simplicity, it still uses them as
+examples to illustrate how plumbing is fit together to form the
+porcelain commands. The source tree includes some of these scripts in
+contrib/examples/ for reference. Although these are not implemented as
+shell scripts anymore, the description of what the plumbing layer
+commands do is still valid.
+
 [NOTE]
 Deeper technical details are often marked as Notes, which you can
 skip on your first reading.
@@ -44,7 +52,7 @@ 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':
+subdirectory, and initialize the git infrastructure with 'git init':
 
 ------------------------------------------------
 $ mkdir git-tutorial
@@ -102,7 +110,7 @@ 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
+(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.
@@ -139,7 +147,7 @@ but to actually check in your hard work, you will have to go through two steps:
  - 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
+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
@@ -173,19 +181,19 @@ and see two files:
 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
+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
+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
+$ git cat-file blob 557db03
 ----------------
 
 which will print out "Hello World". The object `557db03` is nothing
@@ -205,7 +213,7 @@ 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
+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.
 
@@ -228,7 +236,7 @@ $ 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' command:
 
 ------------
 $ git diff-files
@@ -239,7 +247,7 @@ 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
+To make it readable, we can tell 'git diff-files' to output the
 differences as a patch, using the `-p` flag:
 
 ------------
@@ -255,7 +263,7 @@ index 557db03..263414f 100644
 
 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
+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.
 
@@ -283,7 +291,7 @@ 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'.
+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
@@ -302,28 +310,28 @@ and this will just output the name of the resulting tree, in this case
 ----------------
 
 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
+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
+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' 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
+'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
+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
+that's exactly what 'git commit-tree' spits out, we can do this
 all with a sequence of simple shell commands:
 
 ------------------------------------------------
@@ -345,11 +353,11 @@ 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
+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
+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
@@ -360,12 +368,12 @@ 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'.
+'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
+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
+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.
 
@@ -375,7 +383,7 @@ 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
+(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
@@ -390,7 +398,7 @@ $ git diff HEAD
 
 which ends up doing the above for you.
 
-In other words, 'git-diff-index' normally compares a tree against the
+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
@@ -399,7 +407,7 @@ an empty set of differences, and that's exactly what it does.
 
 [NOTE]
 ================
-'git-diff-index' really always uses the index for its
+'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,
@@ -428,11 +436,11 @@ $ 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
+Note what happens to the different 'git diff-{asterisk}' 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`
+'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
@@ -460,7 +468,7 @@ 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').
+commit itself ('git commit').
 
 
 Inspecting Changes
@@ -468,9 +476,9 @@ 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'.
+'diff' family, namely 'git diff-tree'.
 
-'git-diff-tree' can be given two arbitrary trees, and it will tell you the
+'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
@@ -486,7 +494,7 @@ 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.
+various 'diff-{asterisk}' commands compare things.
 
                       diff-tree
                        +----+
@@ -518,15 +526,15 @@ various diff-\* commands compare things.
                     +-----------+
 ============
 
-More interestingly, you can also give 'git-diff-tree' the `--pretty` flag,
+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
+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.
 
@@ -553,14 +561,14 @@ When using the above two commands, the initial commit will be shown.
 If this is a problem because it is huge, you can hide it by setting
 the log.showroot configuration variable to false. Having this, you
 can still show it for each command just adding the `\--root` option,
-which is a flag for 'git-diff-tree' accepted by both commands.
+which is a flag for 'git diff-tree' accepted by both commands.
 
 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 such as 'git-add', `git-rm'
+git Plumbing commands, but using Porcelain such as 'git add', `git-rm'
 and `git-commit'.
 
 
@@ -595,14 +603,14 @@ 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':
+`-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
+argument that specifies the thing to tag, e.g., 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
@@ -642,7 +650,7 @@ 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
+   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`.
@@ -666,7 +674,7 @@ 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
+so usually you'll precede the 'git update-index' with a
 
 ----------------
 $ git read-tree --reset HEAD
@@ -674,7 +682,7 @@ $ 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'
+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
@@ -689,8 +697,8 @@ $ 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` used to be
-the above two lines implemented in 'git-reset', but some things like
-'git-status' and 'git-commit' are slightly more complex scripts around
+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
@@ -729,7 +737,7 @@ 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
+flag first, to tell 'git checkout-index' to *force* overwriting of any old
 files).
 
 Again, this can all be simplified with
@@ -776,7 +784,7 @@ to it.
 ================================================
 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.
+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
 
 ------------
@@ -819,7 +827,7 @@ $ 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'
+on that branch -- switch to that branch with a regular 'git checkout'
 with the branchname as the argument.
 
 
@@ -881,7 +889,7 @@ 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
+script called 'git merge', which wants to know which branches you want
 to resolve and what the merge is all about:
 
 ------------
@@ -925,7 +933,7 @@ $ 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.
+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
@@ -950,11 +958,11 @@ $ git show-branch --topo-order --more=1 master mybranch
 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
+(notice the asterisk `{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 (`*`
+All of them have non blank characters in the first column (`{asterisk}`
 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,
@@ -963,25 +971,25 @@ 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^' is the first parent of 'master'
-branch head.  Please see linkgit:git-rev-parse[1] if you want to
+branch head.  Please see linkgit:gitrevisions[1] if you want to
 see more complex cases.
 
 [NOTE]
-Without the '--more=1' option, 'git-show-branch' would not output the
+Without the '--more=1' option, 'git show-branch' would not output the
 '[master^]' commit, as '[mybranch]' commit is a common ancestor of
 both 'master' and 'mybranch' tips.  Please see linkgit:git-show-branch[1]
 for details.
 
 [NOTE]
 If there were more commits on the 'master' branch after the merge, the
-merge commit itself would not be shown by 'git-show-branch' by
+merge commit itself would not be shown by 'git show-branch' by
 default.  You would need to provide '--sparse' option to make the
 merge commit visible in this case.
 
 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
-'git-merge' to get the "upstream changes" back to your branch.
+'git merge' to get the "upstream changes" back to your branch.
 
 ------------
 $ git checkout mybranch
@@ -993,7 +1001,7 @@ would be different)
 
 ----------------
 Updating from ae3a2da... to a80b4aa....
-Fast forward (no commit created; -m option ignored)
+Fast-forward (no commit created; -m option ignored)
  example |    1 +
  hello   |    1 +
  2 files changed, 2 insertions(+), 0 deletions(-)
@@ -1003,7 +1011,7 @@ Because your branch did not contain anything more than what had
 already been merged into the `master` branch, the merge operation did
 not actually do a merge. Instead, it just updated the top of
 the tree of your branch to that of the `master` branch. This is
-often called 'fast forward' merge.
+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.
@@ -1023,12 +1031,12 @@ 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
+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'.
+followed by a 'git merge'.
 
 Fetching from a remote repository is done by, unsurprisingly,
-'git-fetch':
+'git fetch':
 
 ----------------
 $ git fetch <remote-repository>
@@ -1084,7 +1092,7 @@ 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\...`
+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
@@ -1095,7 +1103,7 @@ 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'
+you must prepare your repository with 'git update-server-info'
 to help dumb transport downloaders.
 
 Once you fetch from the remote repository, you `merge` that
@@ -1115,7 +1123,7 @@ 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
+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 a set of files for each `branch` checked
 out and you may find it easier to switch back and forth if you
@@ -1132,7 +1140,7 @@ like this:
 $ git config remote.linus.url http://www.kernel.org/pub/scm/git/git.git/
 ------------------------------------------------
 
-and use the "linus" keyword with 'git-pull' instead of the full URL.
+and use the "linus" keyword with 'git pull' instead of the full URL.
 
 Examples.
 
@@ -1168,7 +1176,7 @@ $ git show-branch --more=2 master mybranch
 +* [master^] Some fun.
 ------------
 
-Remember, before running 'git-merge', our `master` head was at
+Remember, before running 'git merge', our `master` head was at
 "Some fun." commit, while our `mybranch` head was at "Some
 work." commit.
 
@@ -1186,16 +1194,16 @@ $ git show-branch
 * [master] Some fun.
  ! [mybranch] Some work.
 --
- + [mybranch] Some work.
 *  [master] Some fun.
-*+ [mybranch^] New day.
+ + [mybranch] Some work.
+*+ [master^] Initial commit
 ------------
 
 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':
+The command it uses is 'git merge-base':
 
 ------------
 $ mb=$(git merge-base HEAD mybranch)
@@ -1204,11 +1212,11 @@ $ 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.  By the way, the common
-ancestor commit is the "New day." commit in this case.  You can
+ancestor commit is the "Initial commit" commit in this case.  You can
 tell it by:
 
 ------------
-$ git name-rev $mb
+$ git name-rev --name-only --tags $mb
 my-first-tag
 ------------
 
@@ -1219,7 +1227,7 @@ this:
 $ git read-tree -m -u $mb HEAD mybranch
 ------------
 
-This is the same 'git-read-tree' command we have already seen,
+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 to stage 2,
@@ -1237,8 +1245,8 @@ inspect the index file with this command:
 ------------
 $ git ls-files --stage
 100644 7f8b141b65fdcee47321e399a2598a235a032422 0      example
-100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1      hello
-100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2      hello
+100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1      hello
+100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2      hello
 100644 cc44c73eb783565da5831b4d820c962954019b69 3      hello
 ------------
 
@@ -1253,15 +1261,15 @@ To look at only non-zero stages, use `\--unmerged` flag:
 
 ------------
 $ git ls-files --unmerged
-100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1      hello
-100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2      hello
+100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1      hello
+100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 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-one-file' command as one of the arguments to
+'git merge-index' command:
 
 ------------
 $ git merge-index git-merge-one-file hello
@@ -1270,7 +1278,7 @@ ERROR: Merge conflict in hello
 fatal: merge program failed
 ------------
 
-'git-merge-one-file' script is called with parameters to
+'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
@@ -1283,15 +1291,15 @@ the working tree..  This can be seen if you run `ls-files
 ------------
 $ git ls-files --stage
 100644 7f8b141b65fdcee47321e399a2598a235a032422 0      example
-100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1      hello
-100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2      hello
+100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1      hello
+100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 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
+'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
+unmerged, and what you see with 'git diff' at this point is
 differences since stage 2 (i.e. your version).
 
 
@@ -1328,8 +1336,8 @@ 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'
+'git push' uses a pair of commands,
+'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.
 
@@ -1344,7 +1352,7 @@ $ mkdir my-git.git
 ------------
 
 Then, make that directory into a git repository by running
-'git-init', but this time, since its name is not the usual
+'git init', but this time, since its name is not the usual
 `.git`, we do things slightly differently:
 
 ------------
@@ -1407,12 +1415,12 @@ $ 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
+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`,
+You will see two files, `pack-{asterisk}.pack` and `pack-{asterisk}.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
@@ -1420,7 +1428,7 @@ 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
+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 ;-).
 
@@ -1487,17 +1495,17 @@ 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`,
 `$GIT_DIR/hooks/post-update.sample` copied from the standard templates
-would contain a call to 'git-update-server-info'
+would contain a call to 'git update-server-info'
 but you need to manually enable the hook with
 `mv post-update.sample post-update`.  This makes sure
-'git-update-server-info' keeps the necessary files up-to-date.
+'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
+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
+   baseline, and possibly 'git prune' if the transport
    used for pulling from your repository supports packed
    repositories.
 
@@ -1511,14 +1519,14 @@ 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.
+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
+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 the remote.origin.url
    configuration variable.
@@ -1533,7 +1541,7 @@ on that project and has an own "public repository" goes like this:
    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
+   repository. Run 'git repack', and possibly 'git prune' if the
    transport used for pulling from your repository supports
    packed repositories.
 
@@ -1550,7 +1558,7 @@ like.
    "project lead" and possibly your "sub-subsystem
    maintainers" to pull from it.
 
-7. Every once in a while, 'git-repack' the public repository.
+7. Every once in a while, 'git repack' the public repository.
    Go back to step 5. and continue working.
 
 
@@ -1558,7 +1566,7 @@ 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
+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 the remote.origin.url
@@ -1656,8 +1664,8 @@ $ 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 merge these two
+those two 'git merge' you just did.  Then, instead of running
+two 'git merge' commands in a row, you would merge these two
 branch heads (this is known as 'making an Octopus'):
 
 ------------
index 0e49c1c03776af30f0509679f720273061aaa7b3..d861ec452f8f115017830422b4b459861f8bd703 100644 (file)
@@ -47,25 +47,25 @@ them first before running git pull.
 [NOTE]
 ================================
 The 'pull' command knows where to get updates from because of certain
-configuration variables that were set by the first 'git-clone'
+configuration variables that were set by the first 'git clone'
 command; see `git config -l` and the linkgit:git-config[1] man
 page for details.
 ================================
 
 You can update the shared repository with your changes by first committing
-your changes, and then using the 'git-push' command:
+your changes, and then using the 'git push' command:
 
 ------------------------------------------------
 $ git push origin master
 ------------------------------------------------
 
 to "push" those commits to the shared repository.  If someone else has
-updated the repository more recently, 'git-push', like 'cvs commit', will
+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
+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:
 
index e8041bc08f403d2752c41aaaf482a9700038fbef..5d91a7e5b3a40cbf41f03a81799c162775c36607 100644 (file)
@@ -3,7 +3,7 @@ gitdiffcore(7)
 
 NAME
 ----
-gitdiffcore - Tweaking diff output (June 2005)
+gitdiffcore - Tweaking diff output
 
 SYNOPSIS
 --------
@@ -12,7 +12,7 @@ SYNOPSIS
 DESCRIPTION
 -----------
 
-The diff commands 'git-diff-index', 'git-diff-files', and 'git-diff-tree'
+The diff commands 'git diff-index', 'git diff-files', and 'git diff-tree'
 can be told to manipulate differences they find in
 unconventional ways before showing 'diff' output.  The manipulation
 is collectively called "diffcore transformation".  This short note
@@ -23,18 +23,18 @@ that is easier to understand than the conventional kind.
 The chain of operation
 ----------------------
 
-The 'git-diff-{asterisk}' family works by first comparing two sets of
+The 'git diff-{asterisk}' family works by first comparing two sets of
 files:
 
- - 'git-diff-index' compares contents of a "tree" object and the
+ - '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
+ - 'git diff-files' compares contents of the index file and the
    working directory;
 
- - 'git-diff-tree' compares contents of two "tree" objects;
+ - 'git diff-tree' compares contents of two "tree" objects;
 
 In all of these cases, the commands themselves first optionally limit
 the two sets of files by any pathspecs given on their command-lines,
@@ -74,12 +74,12 @@ into another list.  There are currently 5 such transformations:
 - diffcore-pickaxe
 - diffcore-order
 
-These are applied in sequence.  The set of filepairs 'git-diff-{asterisk}'
+These are applied in sequence.  The set of filepairs 'git diff-{asterisk}'
 commands find are used as the input to diffcore-break, and
 the output from diffcore-break 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-{asterisk}' commands) or
+format sections of the manual for 'git diff-{asterisk}' commands) or
 diff-patch format.
 
 
@@ -87,7 +87,7 @@ 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-{asterisk}' commands.  This is
+controlled by the -B option to the 'git diff-{asterisk}' 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:
@@ -123,7 +123,7 @@ 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-{asterisk}' commands.  If the
+(to detect copies as well) to the 'git diff-{asterisk}' commands.  If the
 input contained these filepairs:
 
 ------------------------------------------------
@@ -168,11 +168,11 @@ 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-{asterisk}' commands feed unmodified filepairs to
+option, 'git diff-{asterisk}' 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-{asterisk}' commands can detect copies only if the file that was
+'git diff-{asterisk}' commands can detect copies only if the file that was
 copied happened to have been modified in the same changeset.
 
 
@@ -223,12 +223,12 @@ 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-{asterisk}'
+-S option and the `\--pickaxe-all` option to the 'git diff-{asterisk}'
 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
+filepairs whose "result" side has the specified string and
+whose "origin" 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.
 
@@ -246,7 +246,7 @@ 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-{asterisk}' commands.
+'git diff-{asterisk}' 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
index 1c736738ccc91b6929226a27e02716221ec3ef05..7183aa9abbc35018dc50a7c0e5254cc72115af23 100644 (file)
@@ -15,7 +15,7 @@ DESCRIPTION
 
 Hooks are little scripts you can place in `$GIT_DIR/hooks`
 directory to trigger action at certain points.  When
-'git-init' is run, a handful of example hooks are copied into the
+'git init' is run, a handful of example hooks are copied into the
 `hooks` directory of the new repository, but by default they are
 all disabled.  To enable a hook, rename it by removing its `.sample`
 suffix.
@@ -26,13 +26,16 @@ executable by default.
 
 This document describes the currently defined hooks.
 
+HOOKS
+-----
+
 applypatch-msg
---------------
+~~~~~~~~~~~~~~
 
-This hook is invoked by 'git-am' script.  It takes a single
+This hook is invoked by 'git am' script.  It takes a single
 parameter, the name of the file that holds the proposed commit
 log message.  Exiting with non-zero status causes
-'git-am' to abort before applying the patch.
+'git am' to abort before applying the patch.
 
 The hook is allowed to edit the message file in place, and can
 be used to normalize the message into some project standard
@@ -43,9 +46,9 @@ 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-am'.  It takes no parameter, and is
+This hook is invoked by 'git am'.  It takes no parameter, and is
 invoked after the patch is applied, but before a commit is made.
 
 If it exits with non-zero status, then the working tree will not be
@@ -58,35 +61,35 @@ 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-am'.  It takes no parameter,
+This hook is invoked by 'git am'.  It takes no parameter,
 and is invoked after the patch is applied and a commit is made.
 
 This hook is meant primarily for notification, and cannot affect
-the outcome of 'git-am'.
+the outcome of 'git am'.
 
 pre-commit
-----------
+~~~~~~~~~~
 
-This hook is invoked by 'git-commit', and can be bypassed
+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.
+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
 such a line is found.
 
-All the 'git-commit' hooks are invoked with the environment
+All the 'git commit' hooks are invoked with the environment
 variable `GIT_EDITOR=:` if the command will not bring up an editor
 to modify the commit message.
 
 prepare-commit-msg
-------------------
+~~~~~~~~~~~~~~~~~~
 
-This hook is invoked by 'git-commit' right after preparing the
+This hook is invoked by 'git commit' right after preparing the
 default log message, and before the editor is started.
 
 It takes one to three parameters.  The first is the name of the file
@@ -98,7 +101,7 @@ commit is a merge or a `.git/MERGE_MSG` file exists); `squash`
 (if a `.git/SQUASH_MSG` file exists); or `commit`, followed by
 a commit SHA1 (if a `-c`, `-C` or `\--amend` option was given).
 
-If the exit status is non-zero, 'git-commit' will abort.
+If the exit status is non-zero, 'git commit' will abort.
 
 The purpose of the hook is to edit the message file in place, and
 it is not suppressed by the `\--no-verify` option.  A non-zero exit
@@ -109,12 +112,12 @@ The sample `prepare-commit-msg` hook that comes with git comments
 out the `Conflicts:` part of a merge's commit message.
 
 commit-msg
-----------
+~~~~~~~~~~
 
-This hook is invoked by 'git-commit', and can be bypassed
+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
+Exiting with non-zero status causes the 'git commit' to
 abort.
 
 The hook is allowed to edit the message file in place, and can
@@ -126,32 +129,32 @@ The default 'commit-msg' hook, when enabled, detects duplicate
 "Signed-off-by" lines, and aborts the commit if one is found.
 
 post-commit
------------
+~~~~~~~~~~~
 
-This hook is invoked by 'git-commit'.  It takes no
+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 outcome of 'git commit'.
 
 pre-rebase
-----------
+~~~~~~~~~~
 
-This hook is called by 'git-rebase' and can be used to prevent a branch
+This hook is called by 'git rebase' and can be used to prevent a branch
 from getting rebased.
 
 
 post-checkout
------------
+~~~~~~~~~~~~~
 
-This hook is invoked when a 'git-checkout' is run after having updated the
+This hook is invoked when a 'git checkout' is run after having updated the
 worktree.  The hook is given three parameters: the ref of the previous HEAD,
 the ref of the new HEAD (which may or may not have changed), and a flag
 indicating whether the checkout was a branch checkout (changing branches,
 flag=1) or a file checkout (retrieving a file from the index, flag=0).
-This hook cannot affect the outcome of 'git-checkout'.
+This hook cannot affect the outcome of 'git checkout'.
 
-It is also run after 'git-clone', unless the --no-checkout (-n) option is
+It is also run after 'git clone', unless the --no-checkout (-n) option is
 used. The first parameter given to the hook is the null-ref, the second the
 ref of the new HEAD and the flag is always 1.
 
@@ -160,12 +163,12 @@ differences from the previous HEAD if different, or set working dir metadata
 properties.
 
 post-merge
------------
+~~~~~~~~~~
 
-This hook is invoked by 'git-merge', which happens when a 'git-pull'
+This hook is invoked by 'git merge', which happens when a 'git pull'
 is done on a local repository.  The hook takes a single parameter, a status
 flag specifying whether or not the merge being done was a squash merge.
-This hook cannot affect the outcome of 'git-merge' and is not executed,
+This hook cannot affect the outcome of 'git merge' and is not executed,
 if the merge failed due to conflicts.
 
 This hook can be used in conjunction with a corresponding pre-commit hook to
@@ -175,10 +178,10 @@ for an example of how to do this.
 
 [[pre-receive]]
 pre-receive
------------
+~~~~~~~~~~~
 
 This hook is invoked by 'git-receive-pack' on the remote repository,
-which happens when a 'git-push' is done on a local repository.
+which happens when a 'git push' is done on a local repository.
 Just before starting to update refs on the remote repository, the
 pre-receive hook is invoked.  Its exit status determines the success
 or failure of the update.
@@ -199,15 +202,15 @@ updated. If the hook exits with zero, updating of individual refs can
 still be prevented by the <<update,'update'>> hook.
 
 Both standard output and standard error output are forwarded to
-'git-send-pack' on the other end, so you can simply `echo` messages
+'git send-pack' on the other end, so you can simply `echo` messages
 for the user.
 
 [[update]]
 update
-------
+~~~~~~
 
 This hook is invoked by 'git-receive-pack' on the remote repository,
-which happens when a 'git-push' is done on a local repository.
+which 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.  Its exit status determines the success or failure of
 the ref update.
@@ -226,7 +229,7 @@ from updating that 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.
+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
@@ -238,19 +241,19 @@ implement access control which is finer grained than the one
 based on filesystem group.
 
 Both standard output and standard error output are forwarded to
-'git-send-pack' on the other end, so you can simply `echo` messages
+'git send-pack' on the other end, so you can simply `echo` messages
 for the user.
 
 The default 'update' hook, when enabled--and with
-`hooks.allowunannotated` config option turned on--prevents
+`hooks.allowunannotated` config option unset or set to false--prevents
 unannotated tags to be pushed.
 
 [[post-receive]]
 post-receive
-------------
+~~~~~~~~~~~~
 
 This hook is invoked by 'git-receive-pack' on the remote repository,
-which happens when a 'git-push' is done on a local repository.
+which 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.
 
@@ -267,7 +270,7 @@ both old and new values of all the refs in addition to their
 names.
 
 Both standard output and standard error output are forwarded to
-'git-send-pack' on the other end, so you can simply `echo` messages
+'git send-pack' on the other end, so you can simply `echo` messages
 for the user.
 
 The default 'post-receive' hook is empty, but there is
@@ -277,10 +280,10 @@ emails.
 
 [[post-update]]
 post-update
------------
+~~~~~~~~~~~
 
 This hook is invoked by 'git-receive-pack' on the remote repository,
-which happens when a 'git-push' is done on a local repository.
+which 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.
 
@@ -298,22 +301,60 @@ updated values of the refs. You might consider it instead if you need
 them.
 
 When enabled, the default 'post-update' hook runs
-'git-update-server-info' to keep the information used by dumb
+'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.
 
 Both standard output and standard error output are forwarded to
-'git-send-pack' on the other end, so you can simply `echo` messages
+'git send-pack' on the other end, so you can simply `echo` messages
 for the user.
 
 pre-auto-gc
------------
+~~~~~~~~~~~
 
-This hook is invoked by 'git-gc --auto'. It takes no parameter, and
-exiting with non-zero status from this script causes the 'git-gc --auto'
+This hook is invoked by 'git gc --auto'. It takes no parameter, and
+exiting with non-zero status from this script causes the 'git gc --auto'
 to abort.
 
+post-rewrite
+~~~~~~~~~~~~
+
+This hook is invoked by commands that rewrite commits (`git commit
+--amend`, 'git-rebase'; currently 'git-filter-branch' does 'not' call
+it!).  Its first argument denotes the command it was invoked by:
+currently one of `amend` or `rebase`.  Further command-dependent
+arguments may be passed in the future.
+
+The hook receives a list of the rewritten commits on stdin, in the
+format
+
+  <old-sha1> SP <new-sha1> [ SP <extra-info> ] LF
+
+The 'extra-info' is again command-dependent.  If it is empty, the
+preceding SP is also omitted.  Currently, no commands pass any
+'extra-info'.
+
+The hook always runs after the automatic note copying (see
+"notes.rewrite.<command>" in linkgit:git-config.txt) has happened, and
+thus has access to these notes.
+
+The following command-specific comments apply:
+
+rebase::
+       For the 'squash' and 'fixup' operation, all commits that were
+       squashed are listed as being rewritten to the squashed commit.
+       This means that there will be several lines sharing the same
+       'new-sha1'.
++
+The commits are guaranteed to be listed in the order that they were
+processed by rebase.
+
+There is no default 'post-rewrite' hook, but see the
+`post-receive-copy-notes` script in `contrib/hooks` for an example
+that copies your git-notes to the rewritten commits.
+
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index 7df3cef46f3cc2a0cb0e0d7197b431b01b3af4fd..7dc2e8b0bcdfb189724ef9b2204f4e624e3be589 100644 (file)
@@ -56,10 +56,10 @@ the user's editor of choice) generally go into a file specified by
 `core.excludesfile` in the user's `~/.gitconfig`.
 
 The underlying git plumbing tools, such as
-'git-ls-files' and 'git-read-tree', read
+'git ls-files' and 'git read-tree', read
 `gitignore` patterns specified by command-line options, or from
 files specified by command-line options.  Higher-level git
-tools, such as 'git-status' and 'git-add',
+tools, such as 'git status' and 'git add',
 use patterns from the sources specified above.
 
 Patterns have the following format:
@@ -83,16 +83,20 @@ Patterns have the following format:
 
  - If the pattern does not contain a slash '/', git treats it as
    a shell glob pattern and checks for a match against the
-   pathname without leading directories.
+   pathname relative to the location of the `.gitignore` file
+   (relative to the toplevel of the work tree if not from a
+   `.gitignore` file).
 
  - Otherwise, git treats the pattern as a shell glob suitable
    for consumption by fnmatch(3) with the FNM_PATHNAME flag:
    wildcards in the pattern will not match a / in the pathname.
-   For example, "Documentation/\*.html" matches
-   "Documentation/git.html" but not
-   "Documentation/ppc/ppc.html".  A leading slash matches the
-   beginning of the pathname; for example, "/*.c" matches
-   "cat-file.c" but not "mozilla-sha1/sha1.c".
+   For example, "Documentation/{asterisk}.html" matches
+   "Documentation/git.html" but not "Documentation/ppc/ppc.html"
+   or "tools/perf/Documentation/perf.html".
+
+ - A leading slash matches the beginning of the pathname.
+   For example, "/{asterisk}.c" matches "cat-file.c" but not
+   "mozilla-sha1/sha1.c".
 
 An example:
 
index cf465cb47e1f3fbb7452568591874cf865a16c2c..05ac1c79f768352be8756eeb483e71f0d09131de 100644 (file)
@@ -22,7 +22,7 @@ git repository.
 OPTIONS
 -------
 To control which revisions to show, the command takes options applicable to
-the 'git-rev-list' command (see linkgit:git-rev-list[1]).
+the 'git rev-list' command (see linkgit:git-rev-list[1]).
 This manual page describes only the most
 frequently used options.
 
@@ -69,7 +69,7 @@ frequently used options.
        the form "'<from>'..'<to>'" to show all revisions between '<from>' and
        back to '<to>'. Note, more advanced revision selection can be applied.
        For a more complete list of ways to spell object names, see
-       "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
+       linkgit:gitrevisions[1].
 
 <path>...::
 
index d1a17e2625890245341a2099cc2b058e63564da2..bcffd95ada574a8fb3c6e62e539e736a17170414 100644 (file)
@@ -29,6 +29,35 @@ submodule.<name>.path::
 
 submodule.<name>.url::
        Defines an url from where the submodule repository can be cloned.
+       This may be either an absolute URL ready to be passed to
+       linkgit:git-clone[1] or (if it begins with ./ or ../) a location
+       relative to the superproject's origin repository.
+
+submodule.<name>.update::
+       Defines what to do when the submodule is updated by the superproject.
+       If 'checkout' (the default), the new commit specified in the
+       superproject will be checked out in the submodule on a detached HEAD.
+       If 'rebase', the current branch of the submodule will be rebased onto
+       the commit specified in the superproject. If 'merge', the commit
+       specified in the superproject will be merged into the current branch
+       in the submodule.
+       This config option is overridden if 'git submodule update' is given
+       the '--merge' or '--rebase' options.
+
+submodule.<name>.ignore::
+       Defines under what circumstances "git status" and the diff family show
+       a submodule as modified. When set to "all", it will never be considered
+       modified, "dirty" will ignore all changes to the submodules work tree and
+       takes only differences between the HEAD of the submodule and the commit
+       recorded in the superproject into account. "untracked" will additionally
+       let submodules with modified tracked files in their work tree show up.
+       Using "none" (the default when this option is not set) also shows
+       submodules that have untracked files in their work tree as changed.
+       If this option is also present in the submodules entry in .git/config of
+       the superproject, the setting there will override the one found in
+       .gitmodules.
+       Both settings can be overridden on the command line by using the
+       "--ignore-submodule" option.
 
 
 EXAMPLES
index 1befca98d49583b42e652b4ce21fccb89b5b5cdb..eb3d040783e8202f502d05e8b4dc4e3b39736779 100644 (file)
@@ -16,7 +16,7 @@ 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. It is
 also possible to have a working tree where `.git` is a plain
-ascii file containing `gitdir: <path>`, i.e. the path to the
+ASCII file containing `gitdir: <path>`, i.e. the path to the
 real git repository).
 
 objects::
@@ -64,7 +64,7 @@ objects/info/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
+       published for dumb transports.  'git repack' does this
        by default.
 
 objects/info/alternates::
@@ -85,7 +85,7 @@ objects/info/http-alternates::
 
 refs::
        References are stored in subdirectories of this
-       directory.  The 'git-prune' command knows to keep
+       directory.  The 'git prune' command knows to keep
        objects reachable from refs found in this directory and
        its subdirectories.
 
@@ -125,7 +125,7 @@ details.
 
 branches::
        A slightly deprecated way to store shorthands to be used
-       to specify URL to 'git-fetch', 'git-pull' and 'git-push'
+       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.
@@ -133,7 +133,7 @@ branches::
 hooks::
        Hooks are customization scripts used by various git
        commands.  A handful of sample hooks are installed when
-       'git-init' is run, but all of them are disabled by
+       'git init' is run, but all of them are disabled by
        default.  To enable, the `.sample` suffix has to be
        removed from the filename by renaming.
        Read linkgit:githooks[5] for more details about
@@ -151,10 +151,10 @@ info/refs::
        This file helps dumb transports discover what refs are
        available in this repository.  If the repository is
        published for dumb transports, this file should be
-       regenerated by 'git-update-server-info' every time a tag
+       regenerated by 'git update-server-info' every time a tag
        or branch is created or modified.  This is normally done
        from the `hooks/update` hook, which is run by the
-       'git-receive-pack' command when you 'git-push' into the
+       'git-receive-pack' command when you 'git push' into the
        repository.
 
 info/grafts::
@@ -168,14 +168,14 @@ info/grafts::
 info/exclude::
        This file, by convention among Porcelains, stores the
        exclude pattern list. `.gitignore` is the per-directory
-       ignore file.  'git-status', 'git-add', 'git-rm' and
-       'git-clean' look at it but the core git commands do not look
+       ignore file.  'git status', 'git add', 'git rm' and
+       'git clean' look at it but the core git commands do not look
        at it.  See also: linkgit:gitignore[5].
 
 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.
+       'git fetch', 'git pull' and 'git push' commands.
 
 logs::
        Records of changes made to refs are stored in this
diff --git a/Documentation/gitrevisions.txt b/Documentation/gitrevisions.txt
new file mode 100644 (file)
index 0000000..fc4789f
--- /dev/null
@@ -0,0 +1,35 @@
+gitrevisions(7)
+================
+
+NAME
+----
+gitrevisions - specifying revisions and ranges for git
+
+SYNOPSIS
+--------
+gitrevisions
+
+
+DESCRIPTION
+-----------
+
+Many Git commands take revision parameters as arguments. Depending on
+the command, they denote a specific commit or, for commands which
+walk the revision graph (such as linkgit:git-log[1]), all commits which can
+be reached from that commit. In the latter case one can also specify a
+range of revisions explicitly.
+
+In addition, some Git commands (such as linkgit:git-show[1]) also take
+revision parameters which denote other objects than commits, e.g. blobs
+("files") or trees ("directories of files").
+
+include::revisions.txt[]
+
+
+SEE ALSO
+--------
+linkgit:git-rev-parse[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
index dc8fc3a18a5e613da910c6bd5a1dda9c85c3f5b1..ecab0c09d01fadd423b8d6bc4ee4bf3dd9e669de 100644 (file)
@@ -260,7 +260,7 @@ index a042389..513feba 100644
 +hello world, again
 ------------------------------------------------
 
-So 'git-diff' is comparing against something other than the head.
+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:
@@ -275,9 +275,9 @@ hello world!
 hello world, again
 ------------------------------------------------
 
-So what our 'git-add' did was store a new blob and then put
+So what our 'git add' 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'
+we'll see that the new modifications are reflected in the 'git diff'
 output:
 
 ------------------------------------------------
@@ -292,7 +292,7 @@ index 513feba..ba3da7b 100644
 +again?
 ------------------------------------------------
 
-With the right arguments, 'git-diff' can also show us the difference
+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:
 
@@ -316,7 +316,7 @@ index a042389..513feba 100644
 +hello world, again
 ------------------------------------------------
 
-At any time, we can create a new commit using 'git-commit' (without
+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:
@@ -334,11 +334,11 @@ index 513feba..ba3da7b 100644
 +again?
 ------------------------------------------------
 
-So by default 'git-commit' uses the index to create the commit, not
+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
+Finally, it's worth looking at the effect of 'git add' on the index
 file:
 
 ------------------------------------------------
@@ -346,7 +346,7 @@ $ echo "goodbye, world" >closing.txt
 $ git add closing.txt
 ------------------------------------------------
 
-The effect of the 'git-add' was to add one entry to the index file:
+The effect of the 'git add' was to add one entry to the index file:
 
 ------------------------------------------------
 $ git ls-files --stage
index c5d5596d895755d69c8060bc38a800d7c70eda26..1c1606696e3771f1b881c18689b049b53a8a3539 100644 (file)
@@ -67,7 +67,7 @@ You've now initialized the working directory--you may notice a new
 directory created, named ".git".
 
 Next, tell git to take a snapshot of the contents of all files under the
-current directory (note the '.'), with 'git-add':
+current directory (note the '.'), with 'git add':
 
 ------------------------------------------------
 $ git add .
@@ -75,7 +75,7 @@ $ git add .
 
 This snapshot is now stored in a temporary staging area which git calls
 the "index".  You can permanently store the contents of the index in the
-repository with 'git-commit':
+repository with 'git commit':
 
 ------------------------------------------------
 $ git commit
@@ -94,15 +94,15 @@ $ git add file1 file2 file3
 ------------------------------------------------
 
 You are now ready to commit.  You can see what is about to be committed
-using 'git-diff' with the --cached option:
+using 'git diff' with the --cached option:
 
 ------------------------------------------------
 $ git diff --cached
 ------------------------------------------------
 
-(Without --cached, 'git-diff' will show you any changes that
+(Without --cached, 'git diff' will show you any changes that
 you've made but not yet added to the index.)  You can also get a brief
-summary of the situation with 'git-status':
+summary of the situation with 'git status':
 
 ------------------------------------------------
 $ git status
@@ -126,7 +126,7 @@ $ git commit
 This will again prompt you for a message describing the change, and then
 record a new version of the project.
 
-Alternatively, instead of running 'git-add' beforehand, you can use
+Alternatively, instead of running 'git add' beforehand, you can use
 
 ------------------------------------------------
 $ git commit -a
@@ -147,7 +147,7 @@ Git tracks content not files
 
 Many revision control systems provide an `add` command that tells the
 system to start tracking changes to a new file.  Git's `add` command
-does something simpler and more powerful: 'git-add' is used both for new
+does something simpler and more powerful: 'git add' is used both for new
 and newly modified files, and in both cases it takes a snapshot of the
 given files and stages that content in the index, ready for inclusion in
 the next commit.
@@ -332,11 +332,11 @@ alice$ git log -p HEAD..FETCH_HEAD
 ------------------------------------------------
 
 This operation is safe even if Alice has uncommitted local changes.
-The range notation HEAD..FETCH_HEAD" means "show everything that is reachable
-from the FETCH_HEAD but exclude anything that is reachable from HEAD.
+The range notation "HEAD..FETCH_HEAD" means "show everything that is reachable
+from the FETCH_HEAD but exclude anything that is reachable from HEAD".
 Alice already knows everything that leads to her current state (HEAD),
-and reviewing what Bob has in his state (FETCH_HEAD) that she has not
-seen with this command
+and reviews what Bob has in his state (FETCH_HEAD) that she has not
+seen with this command.
 
 If Alice wants to visualize what Bob did since their histories forked
 she can issue the following command:
@@ -375,16 +375,16 @@ it easier:
 alice$ git remote add bob /home/bob/myrepo
 ------------------------------------------------
 
-With this, Alice can perform the first part of the "pull" operation alone using the
-'git-fetch' command without merging them with her own branch,
-using:
+With this, Alice can perform the first part of the "pull" operation
+alone using the 'git fetch' command without merging them with her own
+branch, using:
 
 -------------------------------------
 alice$ git fetch bob
 -------------------------------------
 
 Unlike the longhand form, when Alice fetches from Bob using a
-remote repository shorthand set up with 'git-remote', what was
+remote repository shorthand set up with 'git remote', what was
 fetched is stored in a remote tracking branch, in this case
 `bob/master`.  So after this:
 
@@ -428,7 +428,7 @@ bob$ git config --get remote.origin.url
 /home/alice/project
 -------------------------------------
 
-(The complete configuration created by 'git-clone' is visible using
+(The complete configuration created by 'git clone' is visible using
 `git config -l`, and the linkgit:git-config[1] man page
 explains the meaning of each option.)
 
@@ -458,7 +458,7 @@ 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.
+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:
 
@@ -471,7 +471,7 @@ 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
+We can give this name to 'git show' to see the details about this
 commit.
 
 -------------------------------------
@@ -529,13 +529,13 @@ $ git reset --hard HEAD^ # reset your current branch and working
 Be careful with that last command: in addition to losing any changes
 in the working directory, it will also remove all later commits from
 this branch.  If this branch is the only branch containing those
-commits, they will be lost.  Also, don't use 'git-reset' on a
+commits, they will be lost.  Also, don't use 'git reset' on a
 publicly-visible branch that other developers pull from, as it will
 force needless merges on other developers to clean up the history.
-If you need to undo changes that you have pushed, use 'git-revert'
+If you need to undo changes that you have pushed, use 'git revert'
 instead.
 
-The 'git-grep' command can search for strings in any version of your
+The 'git grep' command can search for strings in any version of your
 project, so
 
 -------------------------------------
@@ -544,7 +544,7 @@ $ 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
+If you leave out the commit name, 'git grep' will search any of the
 files it manages in your current directory.  So
 
 -------------------------------------
@@ -554,7 +554,7 @@ $ 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':
+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
@@ -564,28 +564,28 @@ $ 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
+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
+the branches "stable" and "master" diverged from a common
 commit some time ago, then
 
 -------------------------------------
-$ git log stable..experimental
+$ git log stable..master
 -------------------------------------
 
-will list commits made in the experimental branch but not in the
+will list commits made in the master branch but not in the
 stable branch, while
 
 -------------------------------------
-$ git log experimental..stable
+$ git log master..stable
 -------------------------------------
 
 will show the list of commits made on the stable branch but not
-the experimental branch.
+the master branch.
 
-The 'git-log' command has a weakness: it must present commits in a
+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
+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,
@@ -609,7 +609,7 @@ of the file:
 $ git diff v2.5:Makefile HEAD:Makefile.in
 -------------------------------------
 
-You can also use 'git-show' to see any such file:
+You can also use 'git show' to see any such file:
 
 -------------------------------------
 $ git show v2.5:Makefile
@@ -650,6 +650,9 @@ digressions that may be interesting at this point are:
     smart enough to perform a close-to-optimal search even in the
     case of complex non-linear history with lots of merged branches.
 
+  * linkgit:gitworkflows[7]: Gives an overview of recommended
+    workflows.
+
   * link:everyday.html[Everyday GIT with 20 Commands Or So]
 
   * linkgit:gitcvs-migration[7]: Git for CVS users.
@@ -661,6 +664,7 @@ linkgit:gitcvs-migration[7],
 linkgit:gitcore-tutorial[7],
 linkgit:gitglossary[7],
 linkgit:git-help[1],
+linkgit:gitworkflows[7],
 link:everyday.html[Everyday git],
 link:user-manual.html[The Git User's Manual]
 
index 2b021e3c155013965a383a2188fd1c380d379b62..1ef55fffcf6815a12a1f9845809592104e208092 100644 (file)
@@ -209,6 +209,121 @@ chance to see if their in-progress work will be compatible.  `git.git`
 has such an official throw-away integration branch called 'pu'.
 
 
+Branch management for a release
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Assuming you are using the merge approach discussed above, when you
+are releasing your project you will need to do some additional branch
+management work.
+
+A feature release is created from the 'master' branch, since 'master'
+tracks the commits that should go into the next feature release.
+
+The 'master' branch is supposed to be a superset of 'maint'. If this
+condition does not hold, then 'maint' contains some commits that
+are not included on 'master'. The fixes represented by those commits
+will therefore not be included in your feature release.
+
+To verify that 'master' is indeed a superset of 'maint', use git log:
+
+.Verify 'master' is a superset of 'maint'
+[caption="Recipe: "]
+=====================================
+`git log master..maint`
+=====================================
+
+This command should not list any commits.  Otherwise, check out
+'master' and merge 'maint' into it.
+
+Now you can proceed with the creation of the feature release. Apply a
+tag to the tip of 'master' indicating the release version:
+
+.Release tagging
+[caption="Recipe: "]
+=====================================
+`git tag -s -m "GIT X.Y.Z" vX.Y.Z master`
+=====================================
+
+You need to push the new tag to a public git server (see
+"DISTRIBUTED WORKFLOWS" below). This makes the tag available to
+others tracking your project. The push could also trigger a
+post-update hook to perform release-related items such as building
+release tarballs and preformatted documentation pages.
+
+Similarly, for a maintenance release, 'maint' is tracking the commits
+to be released. Therefore, in the steps above simply tag and push
+'maint' rather than 'master'.
+
+
+Maintenance branch management after a feature release
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+After a feature release, you need to manage your maintenance branches.
+
+First, if you wish to continue to release maintenance fixes for the
+feature release made before the recent one, then you must create
+another branch to track commits for that previous release.
+
+To do this, the current maintenance branch is copied to another branch
+named with the previous release version number (e.g. maint-X.Y.(Z-1)
+where X.Y.Z is the current release).
+
+.Copy maint
+[caption="Recipe: "]
+=====================================
+`git branch maint-X.Y.(Z-1) maint`
+=====================================
+
+The 'maint' branch should now be fast-forwarded to the newly released
+code so that maintenance fixes can be tracked for the current release:
+
+.Update maint to new release
+[caption="Recipe: "]
+=====================================
+* `git checkout maint`
+* `git merge --ff-only master`
+=====================================
+
+If the merge fails because it is not a fast-forward, then it is
+possible some fixes on 'maint' were missed in the feature release.
+This will not happen if the content of the branches was verified as
+described in the previous section.
+
+
+Branch management for next and pu after a feature release
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+After a feature release, the integration branch 'next' may optionally be
+rewound and rebuilt from the tip of 'master' using the surviving
+topics on 'next':
+
+.Rewind and rebuild next
+[caption="Recipe: "]
+=====================================
+* `git checkout next`
+* `git reset --hard master`
+* `git merge ai/topic_in_next1`
+* `git merge ai/topic_in_next2`
+* ...
+=====================================
+
+The advantage of doing this is that the history of 'next' will be
+clean. For example, some topics merged into 'next' may have initially
+looked promising, but were later found to be undesirable or premature.
+In such a case, the topic is reverted out of 'next' but the fact
+remains in the history that it was once merged and reverted. By
+recreating 'next', you give another incarnation of such topics a clean
+slate to retry, and a feature release is a good point in history to do
+so.
+
+If you do this, then you should make a public announcement indicating
+that 'next' was rewound and rebuilt.
+
+The same rewind and rebuild process may be followed for 'pu'. A public
+announcement is not necessary since 'pu' is a throw-away branch, as
+described above.
+
+
 DISTRIBUTED WORKFLOWS
 ---------------------
 
@@ -245,7 +360,7 @@ There are three main tools that can be used for this:
 
 * linkgit:git-pull[1] that does fetch and merge in one go.
 
-Note the last point.  Do 'not' use 'git-pull' unless you actually want
+Note the last point.  Do 'not' use 'git pull' unless you actually want
 to merge the remote branch.
 
 Getting changes out is easy:
@@ -282,7 +397,7 @@ Please pull from
     <url> <branch>
 -------------------------------------
 
-In that case, 'git-pull' can do the fetch and merge in one go, as
+In that case, 'git pull' can do the fetch and merge in one go, as
 follows.
 
 .Push/pull: Merging remote topics
@@ -334,7 +449,7 @@ problem.
 
 If you receive such a patch series (as maintainer, or perhaps as a
 reader of the mailing list it was sent to), save the mails to files,
-create a new topic branch and use 'git-am' to import the commits:
+create a new topic branch and use 'git am' to import the commits:
 
 .format-patch/am: Importing patches
 [caption="Recipe: "]
index 572374f7a6ff17dd40ad687a2fda24d53f7d730b..1f029f8aa080c4de6323e8b4905a81fa7e8e2046 100644 (file)
@@ -124,7 +124,7 @@ to point at the new commit.
        An evil merge is a <<def_merge,merge>> that introduces changes that
        do not appear in any <<def_parent,parent>>.
 
-[[def_fast_forward]]fast forward::
+[[def_fast_forward]]fast-forward::
        A fast-forward is a special type of <<def_merge,merge>> where you have a
        <<def_revision,revision>> and you are "merging" another
        <<def_branch,branch>>'s changes that happen to be a descendant of what
@@ -220,7 +220,7 @@ to point at the new commit.
        conflict, manual intervention may be required to complete the
        merge.
 +
-As a noun: unless it is a <<def_fast_forward,fast forward>>, a
+As a noun: unless it is a <<def_fast_forward,fast-forward>>, a
 successful merge results in the creation of a new <<def_commit,commit>>
 representing the result of the merge, and having as
 <<def_parent,parents>> the tips of the merged <<def_branch,branches>>.
@@ -456,6 +456,6 @@ This commit is referred to as a "merge commit", or sometimes just a
        of 'A' is 'origin/B' sometimes we say "'A' is tracking 'origin/B'".
 
 [[def_working_tree]]working tree::
-       The tree of actual checked out files.  The working tree is
-       normally equal to the <<def_HEAD,HEAD>> plus any local changes
-       that you have made but not yet committed.
+       The tree of actual checked out files.  The working tree normally
+       contains the contents of the <<def_HEAD,HEAD>> commit's tree,
+       plus any local changes that you have made but not yet committed.
index 4357e269131fad960367534ae4161fe078fee30a..d527b307707c676e82a08f18cb9fdd7d3abcb228 100644 (file)
@@ -59,7 +59,7 @@ The policy.
    not yet pass the criteria set for 'next'.
 
  - The tips of 'master', 'maint' and 'next' branches will always
-   fast forward, to allow people to build their own
+   fast-forward, to allow people to build their own
    customization on top of them.
 
  - Usually 'master' contains all of 'maint', 'next' contains all
index 3b4a390005b07c86ee320ee8ca1cf57e46458cb6..6fd711996a775d1da8ab2051db98896a601c8971 100644 (file)
@@ -142,6 +142,8 @@ different resolution strategies:
    revert of a merge was rebuilt from scratch (i.e. rebasing and fixing,
    as you seem to have interpreted), then re-merging the result without
    doing anything else fancy would be the right thing to do.
+   (See the ADDENDUM below for how to rebuild a branch from scratch
+   without changing its original branching-off point.)
 
 However, there are things to keep in mind when reverting a merge (and
 reverting such a revert).
@@ -177,3 +179,91 @@ the answer is: "oops, I really shouldn't have merged it, because it wasn't
 ready yet, and I really need to undo _all_ of the merge"). So then you
 really should revert the merge, but when you want to re-do the merge, you
 now need to do it by reverting the revert.
+
+ADDENDUM
+
+Sometimes you have to rewrite one of a topic branch's commits *and* you can't
+change the topic's branching-off point.  Consider the following situation:
+
+ P---o---o---M---x---x---W---x
+  \         /
+   A---B---C
+
+where commit W reverted commit M because it turned out that commit B was wrong
+and needs to be rewritten, but you need the rewritten topic to still branch
+from commit P (perhaps P is a branching-off point for yet another branch, and
+you want be able to merge the topic into both branches).
+
+The natural thing to do in this case is to checkout the A-B-C branch and use
+"rebase -i P" to change commit B.  However this does not rewrite commit A,
+because "rebase -i" by default fast-forwards over any initial commits selected
+with the "pick" command.  So you end up with this:
+
+ P---o---o---M---x---x---W---x
+  \         /
+   A---B---C   <-- old branch
+    \
+     B'---C'   <-- naively rewritten branch
+
+To merge A-B'-C' into the mainline branch you would still have to first revert
+commit W in order to pick up the changes in A, but then it's likely that the
+changes in B' will conflict with the original B changes re-introduced by the
+reversion of W.
+
+However, you can avoid these problems if you recreate the entire branch,
+including commit A:
+
+   A'---B'---C'  <-- completely rewritten branch
+  /
+ P---o---o---M---x---x---W---x
+  \         /
+   A---B---C
+
+You can merge A'-B'-C' into the mainline branch without worrying about first
+reverting W.  Mainline's history would look like this:
+
+   A'---B'---C'------------------
+  /                              \
+ P---o---o---M---x---x---W---x---M2
+  \         /
+   A---B---C
+
+But if you don't actually need to change commit A, then you need some way to
+recreate it as a new commit with the same changes in it.  The rebase command's
+--no-ff option provides a way to do this:
+
+    $ git rebase [-i] --no-ff P
+
+The --no-ff option creates a new branch A'-B'-C' with all-new commits (all the
+SHA IDs will be different) even if in the interactive case you only actually
+modify commit B.  You can then merge this new branch directly into the mainline
+branch and be sure you'll get all of the branch's changes.
+
+You can also use --no-ff in cases where you just add extra commits to the topic
+to fix it up.  Let's revisit the situation discussed at the start of this howto:
+
+ P---o---o---M---x---x---W---x
+  \         /
+   A---B---C----------------D---E   <-- fixed-up topic branch
+
+At this point, you can use --no-ff to recreate the topic branch:
+
+    $ git checkout E
+    $ git rebase --no-ff P
+
+yielding
+
+   A'---B'---C'------------D'---E'  <-- recreated topic branch
+  /
+ P---o---o---M---x---x---W---x
+  \         /
+   A---B---C----------------D---E
+
+You can merge the recreated branch into the mainline without reverting commit W,
+and mainline's history will look like this:
+
+   A'---B'---C'------------D'---E'
+  /                              \
+ P---o---o---M---x---x---W---x---M2
+  \         /
+   A---B---C
index e70d8a31e7b05e8efc70c6a56f476324065d57a6..093c656048a81e6cdc46d1aecf80fbffd97c94ea 100644 (file)
@@ -85,7 +85,7 @@ Fortunately I did not have to; what I have in the current branch
 
 ------------------------------------------------
 $ git checkout master
-$ git merge revert-c99 ;# this should be a fast forward
+$ git merge revert-c99 ;# this should be a fast-forward
 Updating from 10d781b9caa4f71495c7b34963bef137216f86a8 to e3a693c...
  cache.h        |    8 ++++----
  commit.c       |    2 +-
@@ -95,7 +95,7 @@ Updating from 10d781b9caa4f71495c7b34963bef137216f86a8 to e3a693c...
  5 files changed, 8 insertions(+), 8 deletions(-)
 ------------------------------------------------
 
-There is no need to redo the test at this point.  We fast forwarded
+There is no need to redo the test at this point.  We fast-forwarded
 and we know 'master' matches 'revert-c99' exactly.  In fact:
 
 ------------------------------------------------
@@ -112,25 +112,19 @@ $ 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
index 697d9188850e9a685045da5bd37844b02978752d..b7f8d416d65ca1cf17d6348e858a13f6ed72c7fe 100644 (file)
@@ -76,7 +76,7 @@ case "$1" in
     if expr "$2" : '0*$' >/dev/null; then
       info "The branch '$1' is new..."
     else
-      # updating -- make sure it is a fast forward
+      # 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" ;;
index 708da6ca31c1bc0a714df9f437d6813b016dacb7..625d3154ea5100c67ecb354f47e83142dfe8c8af 100644 (file)
@@ -21,7 +21,7 @@ project find it more convenient to use legacy encodings, git
 does not forbid it.  However, there are a few things to keep in
 mind.
 
-. 'git-commit' and 'git-commit-tree' issues
+. 'git commit' and 'git commit-tree' issues
   a warning if the commit log message given to it does not look
   like a valid UTF-8 string, unless you explicitly say your
   project uses a legacy encoding.  The way to say this is to
@@ -37,7 +37,7 @@ of `i18n.commitencoding` in its `encoding` header.  This is to
 help other people who look at them later.  Lack of this header
 implies that the commit log message is encoded in UTF-8.
 
-. 'git-log', 'git-show', 'git-blame' and friends look at the
+. 'git log', 'git show', 'git blame' and friends look at the
   `encoding` header of a commit object, and try to re-code the
   log message into UTF-8 unless otherwise specified.  You can
   specify the desired output encoding with
index 2135a8ee1f4f56a8c799437949ba76d7526164c0..76d69a907b48a961906e0b85c59a75e9c0c8986d 100755 (executable)
@@ -6,13 +6,13 @@ for h in \
        *.txt *.html \
        howto/*.txt howto/*.html \
        technical/*.txt technical/*.html \
-       RelNotes-*.txt *.css
+       RelNotes/*.txt *.css
 do
        if test ! -f "$h"
        then
                : did not match
        elif test -f "$T/$h" &&
-          diff -u -I'Last updated [0-9][0-9]-[A-Z][a-z][a-z]-' "$T/$h" "$h"
+               $DIFF -u -I'^Last updated ' "$T/$h" "$h"
        then
                :; # up to date
        else
@@ -30,7 +30,7 @@ for th in \
 do
        h=`expr "$th" : "$strip_leading"'\(.*\)'`
        case "$h" in
-       index.html) continue ;;
+       RelNotes-*.txt | index.html) continue ;;
        esac
        test -f "$h" && continue
        echo >&2 "# rm -f $th"
diff --git a/Documentation/manpage-base-url.xsl.in b/Documentation/manpage-base-url.xsl.in
new file mode 100644 (file)
index 0000000..e800904
--- /dev/null
@@ -0,0 +1,10 @@
+<!-- manpage-base-url.xsl:
+     special settings for manpages rendered from newer docbook -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+               version="1.0">
+
+<!-- set a base URL for relative links -->
+<xsl:param name="man.base.url.for.relative.links"
+       >@@MAN_BASE_URL@@</xsl:param>
+
+</xsl:stylesheet>
diff --git a/Documentation/manpage-quote-apos.xsl b/Documentation/manpage-quote-apos.xsl
new file mode 100644 (file)
index 0000000..aeb8839
--- /dev/null
@@ -0,0 +1,16 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+               version="1.0">
+
+<!-- work around newer groff/man setups using a prettier apostrophe
+     that unfortunately does not quote anything when cut&pasting
+     examples to the shell -->
+<xsl:template name="escape.apostrophe">
+  <xsl:param name="content"/>
+  <xsl:call-template name="string.subst">
+    <xsl:with-param name="string" select="$content"/>
+    <xsl:with-param name="target">'</xsl:with-param>
+    <xsl:with-param name="replacement">\(aq</xsl:with-param>
+  </xsl:call-template>
+</xsl:template>
+
+</xsl:stylesheet>
index 4832bc75e2604c997e004018023e3b93cb394b87..b72f5339704e89087992b7d6060e88f43b9d661b 100644 (file)
@@ -15,6 +15,16 @@ merge.renameLimit::
        during a merge; if not specified, defaults to the value of
        diff.renameLimit.
 
+merge.renormalize::
+       Tell git that canonical representation of files in the
+       repository has changed over time (e.g. earlier commits record
+       text files with CRLF line endings, but recent ones use LF line
+       endings).  In such a repository, git can convert the data
+       recorded in commits to a canonical form before performing a
+       merge to reduce unnecessary conflicts.  For more information,
+       see section "Merging branches with differing checkin/checkout
+       attributes" in linkgit:gitattributes[5].
+
 merge.stat::
        Whether to print the diffstat between ORIG_HEAD and the merge result
        at the end of the merge.  True by default.
@@ -23,7 +33,7 @@ merge.tool::
        Controls which merge resolution program is used by
        linkgit:git-mergetool[1].  Valid built-in values are: "kdiff3",
        "tkdiff", "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff",
-       "diffuse", "ecmerge", "tortoisemerge", and
+       "diffuse", "ecmerge", "tortoisemerge", "p4merge", "araxis" and
        "opendiff".  Any other value is treated is custom merge tool
        and there must be a corresponding mergetool.<tool>.cmd option.
 
index 637b53f898829af98153d60953434fa30e3df179..722d704ff2de1abd3d77a18ca396ba96d5cab6bc 100644 (file)
@@ -1,68 +1,83 @@
--q::
---quiet::
-       Operate quietly.
-
--v::
---verbose::
-       Be verbose.
-
---stat::
-       Show a diffstat at the end of the merge. The diffstat is also
-       controlled by the configuration option merge.stat.
-
--n::
---no-stat::
-       Do not show a diffstat at the end of the merge.
+--commit::
+--no-commit::
+       Perform the merge and commit the result. This option can
+       be used to override --no-commit.
++
+With --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.
 
---summary::
---no-summary::
-       Synonyms to --stat and --no-stat; these are deprecated and will be
-       removed in the future.
+--ff::
+--no-ff::
+       Do not generate a merge commit if the merge resolved as
+       a fast-forward, only update the branch pointer. This is
+       the default behavior of git-merge.
++
+With --no-ff Generate a merge commit even if the merge
+resolved as a fast-forward.
 
 --log::
+--no-log::
        In addition to branch names, populate the log message with
        one-line descriptions from the actual commits that are being
        merged.
++
+With --no-log do not list one-line descriptions from the
+actual commits being merged.
 
---no-log::
-       Do not list one-line descriptions from the actual commits being
-       merged.
-
---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.
 
---commit::
-       Perform the merge and commit the result. This option can
-       be used to override --no-commit.
+--stat::
+-n::
+--no-stat::
+       Show a diffstat at the end of the merge. The diffstat is also
+       controlled by the configuration option merge.stat.
++
+With -n or --no-stat do not show a diffstat at the end of the
+merge.
 
 --squash::
+--no-squash::
        Produce the working tree and index state as if a real
-       merge happened, but do not actually make a commit or
+       merge happened (except for the merge information),
+       but do not actually make a commit or
        move the `HEAD`, nor record `$GIT_DIR/MERGE_HEAD` to
        cause the next `git commit` command to create a merge
        commit.  This allows you to create a single commit on
        top of the current branch whose effect is the same as
        merging another branch (or more in case of an octopus).
++
+With --no-squash perform the merge and commit the result. This
+option can be used to override --squash.
 
---no-squash::
-       Perform the merge and commit the result. This option can
-       be used to override --squash.
-
---no-ff::
-       Generate a merge commit even if the merge resolved as a
-       fast-forward.
-
---ff::
-       Do not generate a merge commit if the merge resolved as
-       a fast-forward, only update the branch pointer. This is
-       the default behavior of git-merge.
+--ff-only::
+       Refuse to merge and exit with a non-zero status unless the
+       current `HEAD` is already up-to-date or the merge can be
+       resolved as a fast-forward.
 
 -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).
+       is used instead ('git merge-recursive' when merging a single
+       head, 'git merge-octopus' otherwise).
+
+-X <option>::
+--strategy-option=<option>::
+       Pass merge strategy specific option through to the merge
+       strategy.
+
+--summary::
+--no-summary::
+       Synonyms to --stat and --no-stat; these are deprecated and will be
+       removed in the future.
+
+ifndef::git-pull[]
+-q::
+--quiet::
+       Operate quietly.
+
+-v::
+--verbose::
+       Be verbose.
+endif::git-pull[]
index 4365b7e8420fa96d6cbfa14a5aa49d956ba2de16..049313d6015758fb3e95c21fecc40e021e8aa4bd 100644 (file)
@@ -1,6 +1,11 @@
 MERGE STRATEGIES
 ----------------
 
+The merge mechanism ('git-merge' and 'git-pull' commands) allows the
+backend 'merge strategies' to be chosen with `-s` option.  Some strategies
+can also take their own options, which can be passed by giving `-X<option>`
+arguments to 'git-merge' and/or 'git-pull'.
+
 resolve::
        This can only resolve two heads (i.e. the current branch
        and another branch you pulled from) using a 3-way merge
@@ -20,6 +25,39 @@ recursive::
        Additionally this can detect and handle merges involving
        renames.  This is the default merge strategy when
        pulling or merging one branch.
++
+The 'recursive' strategy can take the following options:
+
+ours;;
+       This option forces conflicting hunks to be auto-resolved cleanly by
+       favoring 'our' version.  Changes from the other tree that do not
+       conflict with our side are reflected to the merge result.
++
+This should not be confused with the 'ours' merge strategy, which does not
+even look at what the other tree contains at all.  It discards everything
+the other tree did, declaring 'our' history contains all that happened in it.
+
+theirs;;
+       This is opposite of 'ours'.
+
+renormalize;;
+       This runs a virtual check-out and check-in of all three stages
+       of a file when resolving a three-way merge.  This option is
+       meant to be used when merging branches with different clean
+       filters or end-of-line normalization rules.  See "Merging
+       branches with differing checkin/checkout attributes" in
+       linkgit:gitattributes[5] for details.
+
+no-renormalize;;
+       Disables the `renormalize` option.  This overrides the
+       `merge.renormalize` configuration variable.
+
+subtree[=path];;
+       This option is a more advanced form of 'subtree' strategy, where
+       the strategy makes a guess on how two trees must be shifted to
+       match with each other when merging.  Instead, the specified path
+       is prefixed (or stripped from the beginning) to make the shape of
+       two trees to match.
 
 octopus::
        This resolves cases with more than two heads, but refuses to do
@@ -29,10 +67,12 @@ octopus::
        pulling or merging more than one branch.
 
 ours::
-       This resolves any number of heads, but the result of the
-       merge is always the current branch head.  It is meant to
+       This resolves any number of heads, but the resulting tree of the
+       merge is always that of the current branch head, effectively
+       ignoring all changes from all other branches.  It is meant to
        be used to supersede old development history of side
-       branches.
+       branches.  Note that this is different from the -Xours option to
+       the 'recursive' merge strategy.
 
 subtree::
        This is a modified recursive strategy. When merging trees A and
index 2a845b1e57590a6f18f05fd3efa858b736c1071a..561cc9f7d7ef1a2502d608e59e2eae345bffd685 100644 (file)
@@ -11,7 +11,12 @@ have limited your view of history: for example, if you are
 only interested in changes related to a certain directory or
 file.
 
-Here are some additional details for each format:
+There are several built-in formats, and you can define
+additional formats by setting a pretty.<name>
+config option to either another format name, or a
+'format:' string, as described below (see
+linkgit:git-config[1]). Here are the details of the
+built-in formats:
 
 * 'oneline'
 
@@ -76,9 +81,9 @@ displayed in full, regardless of whether --abbrev or
 true parent commits, without taking grafts nor history
 simplification into account.
 
-* 'format:'
+* 'format:<string>'
 +
-The 'format:' format allows you to specify which information
+The 'format:<string>' format allows you to specify which information
 you want to show. It works a little bit like printf format,
 with the notable exception that you get a newline with '%n'
 instead of '\n'.
@@ -123,6 +128,11 @@ The placeholders are:
 - '%s': subject
 - '%f': sanitized subject line, suitable for a filename
 - '%b': body
+- '%B': raw body (unwrapped subject and body)
+- '%N': commit notes
+- '%gD': reflog selector, e.g., `refs/stash@\{1\}`
+- '%gd': shortened reflog selector, e.g., `stash@\{1\}`
+- '%gs': reflog subject
 - '%Cred': switch color to red
 - '%Cgreen': switch color to green
 - '%Cblue': switch color to blue
@@ -130,7 +140,28 @@ The placeholders are:
 - '%C(...)': color specification, as described in color.branch.* config option
 - '%m': left, right or boundary mark
 - '%n': newline
+- '%%': a raw '%'
 - '%x00': print a byte from a hex code
+- '%w([<w>[,<i1>[,<i2>]]])': switch line wrapping, like the -w option of
+  linkgit:git-shortlog[1].
+
+NOTE: Some placeholders may depend on other options given to the
+revision traversal engine. For example, the `%g*` reflog options will
+insert an empty string unless we are traversing reflog entries (e.g., by
+`git log -g`). The `%d` placeholder will use the "short" decoration
+format if `--decorate` was not already provided on the command line.
+
+If you add a `{plus}` (plus sign) after '%' of a placeholder, a line-feed
+is inserted immediately before the expansion if and only if the
+placeholder expands to a non-empty string.
+
+If you add a `-` (minus sign) after '%' of a placeholder, line-feeds that
+immediately precede the expansion are deleted if and only if the
+placeholder expands to an empty string.
+
+If you add a ` ` (space) after '%' of a placeholder, a space
+is inserted immediately before the expansion if and only if the
+placeholder expands to a non-empty string.
 
 * 'tformat:'
 +
index bff94991b68aaca5a81eae4e6681f3431aa6b9ac..9b6f3899ec28fb431c1f3969bf5bfb717c12f346 100644 (file)
@@ -1,10 +1,11 @@
 --pretty[='<format>']::
---format[='<format>']::
+--format='<format>'::
 
        Pretty-print the contents of the commit logs in a given format,
        where '<format>' can be one of 'oneline', 'short', 'medium',
-       'full', 'fuller', 'email', 'raw' and 'format:<string>'.
-       When omitted, the format defaults to 'medium'.
+       'full', 'fuller', 'email', 'raw' and 'format:<string>'.  See
+       the "PRETTY FORMATS" section for some additional details for each
+       format.  When omitted, the format defaults to 'medium'.
 +
 Note: you can specify the default pretty format in the repository
 configuration (see linkgit:git-config[1]).
@@ -28,3 +29,20 @@ people using 80-column terminals.
        command to re-code the commit log message in the encoding
        preferred by the user.  For non plumbing commands this
        defaults to UTF-8.
+
+--no-notes::
+--show-notes[=<ref>]::
+       Show the notes (see linkgit:git-notes[1]) that annotate the
+       commit, when showing the commit log message.  This is the default
+       for `git log`, `git show` and `git whatchanged` commands when
+       there is no `--pretty`, `--format` nor `--oneline` option is
+       given on the command line.
++
+With an optional argument, add this ref to the list of notes.  The ref
+is taken to be in `refs/notes/` if it is not qualified.
+
+--[no-]standard-notes::
+       Enable or disable populating the notes ref list from the
+       'core.notesRef' and 'notes.displayRef' variables (or
+       corresponding environment overrides).  Enabled by default.
+       See linkgit:git-config[1].
diff --git a/Documentation/pt_BR/gittutorial.txt b/Documentation/pt_BR/gittutorial.txt
new file mode 100644 (file)
index 0000000..beba065
--- /dev/null
@@ -0,0 +1,675 @@
+gittutorial(7)
+==============
+
+NOME
+----
+gittutorial - Um tutorial de introdução ao git (para versão 1.5.1 ou mais nova)
+
+SINOPSE
+--------
+git *
+
+DESCRIÇÃO
+-----------
+
+Este tutorial explica como importar um novo projeto para o git,
+adicionar mudanças a ele, e compartilhar mudanças com outros
+desenvolvedores.
+
+Se, ao invés disso, você está interessado primariamente em usar git para
+obter um projeto, por exemplo, para testar a última versão, você pode
+preferir começar com os primeiros dois capítulos de
+link:user-manual.html[O Manual do Usuário Git].
+
+Primeiro, note que você pode obter documentação para um comando como
+`git log --graph` com:
+
+------------------------------------------------
+$ man git-log
+------------------------------------------------
+
+ou:
+
+------------------------------------------------
+$ git help log
+------------------------------------------------
+
+Com a última forma, você pode usar o visualizador de manual de sua
+escolha; veja linkgit:git-help[1] para maior informação.
+
+É uma boa idéia informar ao git seu nome e endereço público de email
+antes de fazer qualquer operação. A maneira mais fácil de fazê-lo é:
+
+------------------------------------------------
+$ git config --global user.name "Seu Nome Vem Aqui"
+$ git config --global user.email voce@seudominio.exemplo.com
+------------------------------------------------
+
+
+Importando um novo projeto
+-----------------------
+
+Assuma que você tem um tarball project.tar.gz com seu trabalho inicial.
+Você pode colocá-lo sob controle de revisão git da seguinte forma:
+
+------------------------------------------------
+$ tar xzf project.tar.gz
+$ cd project
+$ git init
+------------------------------------------------
+
+Git irá responder
+
+------------------------------------------------
+Initialized empty Git repository in .git/
+------------------------------------------------
+
+Agora que você iniciou seu diretório de trabalho, você deve ter notado que um
+novo diretório foi criado com o nome de ".git".
+
+A seguir, diga ao git para gravar um instantâneo do conteúdo de todos os
+arquivos sob o diretório atual (note o '.'), com 'git-add':
+
+------------------------------------------------
+$ git add .
+------------------------------------------------
+
+Este instantâneo está agora armazenado em uma área temporária que o git
+chama de "index" ou índice. Você pode armazenar permanentemente o
+conteúdo do índice no repositório com 'git-commit':
+
+------------------------------------------------
+$ git commit
+------------------------------------------------
+
+Isto vai te pedir por uma mensagem de commit. Você agora gravou sua
+primeira versão de seu projeto no git.
+
+Fazendo mudanças
+--------------
+
+Modifique alguns arquivos, e, então, adicione seu conteúdo atualizado ao
+índice:
+
+------------------------------------------------
+$ git add file1 file2 file3
+------------------------------------------------
+
+Você está agora pronto para fazer o commit. Você pode ver o que está
+para ser gravado usando 'git-diff' com a opção --cached:
+
+------------------------------------------------
+$ git diff --cached
+------------------------------------------------
+
+(Sem --cached, o comando 'git-diff' irá te mostrar quaisquer mudanças
+que você tenha feito mas ainda não adicionou ao índice.) Você também
+pode obter um breve sumário da situação com 'git-status':
+
+------------------------------------------------
+$ git status
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#      modified:   file1
+#      modified:   file2
+#      modified:   file3
+#
+------------------------------------------------
+
+Se você precisar fazer qualquer outro ajuste, faça-o agora, e, então,
+adicione qualquer conteúdo modificado ao índice. Finalmente, grave suas
+mudanças com:
+
+------------------------------------------------
+$ git commit
+------------------------------------------------
+
+Ao executar esse comando, ele irá te pedir uma mensagem descrevendo a mudança,
+e, então, irá gravar a nova versão do projeto.
+
+Alternativamente, ao invés de executar 'git-add' antes, você pode usar
+
+------------------------------------------------
+$ git commit -a
+------------------------------------------------
+
+o que irá automaticamente notar quaisquer arquivos modificados (mas não
+novos), adicioná-los ao índices, e gravar, tudo em um único passo.
+
+Uma nota em mensagens de commit: Apesar de não ser exigido, é uma boa
+idéia começar a mensagem com uma simples e curta (menos de 50
+caracteres) linha sumarizando a mudança, seguida de uma linha em branco
+e, então, uma descrição mais detalhada. Ferramentas que transformam
+commits em email, por exemplo, usam a primeira linha no campo de
+cabeçalho "Subject:" e o resto no corpo.
+
+Git rastreia conteúdo, não arquivos
+----------------------------
+
+Muitos sistemas de controle de revisão provêem um comando `add` que diz
+ao sistema para começar a rastrear mudanças em um novo arquivo. O
+comando `add` do git faz algo mais simples e mais poderoso: 'git-add' é
+usado tanto para arquivos novos e arquivos recentemente modificados, e
+em ambos os casos, ele tira o instantâneo dos arquivos dados e armazena
+o conteúdo no índice, pronto para inclusão do próximo commit.
+
+Visualizando a história do projeto
+-----------------------
+
+Em qualquer ponto você pode visualizar a história das suas mudanças
+usando
+
+------------------------------------------------
+$ git log
+------------------------------------------------
+
+Se você também quiser ver a diferença completa a cada passo, use
+
+------------------------------------------------
+$ git log -p
+------------------------------------------------
+
+Geralmente, uma visão geral da mudança é útil para ter a sensação de
+cada passo
+
+------------------------------------------------
+$ git log --stat --summary
+------------------------------------------------
+
+Gerenciando "branches"/ramos
+-----------------
+
+Um simples repositório git pode manter múltiplos ramos de
+desenvolvimento. Para criar um novo ramo chamado "experimental", use
+
+------------------------------------------------
+$ git branch experimental
+------------------------------------------------
+
+Se você executar agora
+
+------------------------------------------------
+$ git branch
+------------------------------------------------
+
+você vai obter uma lista de todos os ramos existentes:
+
+------------------------------------------------
+  experimental
+* master
+------------------------------------------------
+
+O ramo "experimental" é o que você acaba de criar, e o ramo "master" é o
+ramo padrão que foi criado pra você automaticamente. O asterisco marca
+o ramo em que você está atualmente; digite
+
+------------------------------------------------
+$ git checkout experimental
+------------------------------------------------
+
+para mudar para o ramo experimental. Agora edite um arquivo, grave a
+mudança, e mude de volta para o ramo master:
+
+------------------------------------------------
+(edita arquivo)
+$ git commit -a
+$ git checkout master
+------------------------------------------------
+
+Verifique que a mudança que você fez não está mais visível, já que ela
+foi feita no ramo experimental e você está de volta ao ramo master.
+
+Você pode fazer uma mudança diferente no ramo master:
+
+------------------------------------------------
+(edit file)
+$ git commit -a
+------------------------------------------------
+
+neste ponto, os dois ramos divergiram, com diferentes mudanças feitas em
+cada um. Para unificar as mudanças feitas no experimental para o
+master, execute
+
+------------------------------------------------
+$ git merge experimental
+------------------------------------------------
+
+Se as mudanças não conflitarem, estará pronto. Se existirem conflitos,
+marcadores serão deixados nos arquivos problemáticos exibindo o
+conflito;
+
+------------------------------------------------
+$ git diff
+------------------------------------------------
+
+vai exibir isto. Após você editar os arquivos para resolver os
+conflitos,
+
+------------------------------------------------
+$ git commit -a
+------------------------------------------------
+
+irá gravar o resultado da unificação. Finalmente,
+
+------------------------------------------------
+$ gitk
+------------------------------------------------
+
+vai mostrar uma bela representação gráfica da história resultante.
+
+Neste ponto você pode remover seu ramo experimental com
+
+------------------------------------------------
+$ git branch -d experimental
+------------------------------------------------
+
+Este comando garante que as mudanças no ramo experimental já estão no
+ramo atual.
+
+Se você desenvolve em um ramo ideia-louca, e se arrepende, você pode
+sempre remover o ramo com
+
+-------------------------------------
+$ git branch -D ideia-louca
+-------------------------------------
+
+Ramos são baratos e fáceis, então isto é uma boa maneira de experimentar
+alguma coisa.
+
+Usando git para colaboração
+---------------------------
+
+Suponha que Alice começou um novo projeto com um repositório git em
+/home/alice/project, e que Bob, que tem um diretório home na mesma
+máquina, quer contribuir.
+
+Bob começa com:
+
+------------------------------------------------
+bob$ git clone /home/alice/project myrepo
+------------------------------------------------
+
+Isso cria um novo diretório "myrepo" contendo um clone do repositório de
+Alice. O clone está no mesmo pé que o projeto original, possuindo sua
+própria cópia da história do projeto original.
+
+Bob então faz algumas mudanças e as grava:
+
+------------------------------------------------
+(editar arquivos)
+bob$ git commit -a
+(repetir conforme necessário)
+------------------------------------------------
+
+Quanto está pronto, ele diz a Alice para puxar as mudanças do
+repositório em /home/bob/myrepo. Ela o faz com:
+
+------------------------------------------------
+alice$ cd /home/alice/project
+alice$ git pull /home/bob/myrepo master
+------------------------------------------------
+
+Isto unifica as mudanças do ramo "master" do Bob ao ramo atual de Alice.
+Se Alice fez suas próprias mudanças no intervalo, ela, então, pode
+precisar corrigir manualmente quaisquer conflitos. (Note que o argumento
+"master" no comando acima é, de fato, desnecessário, já que é o padrão.)
+
+O comando "pull" executa, então, duas operações: ele obtém mudanças de
+um ramo remoto, e, então, as unifica no ramo atual.
+
+Note que, em geral, Alice gostaria que suas mudanças locais fossem
+gravadas antes de iniciar este "pull". Se o trabalho de Bob conflita
+com o que Alice fez desde que suas histórias se ramificaram, Alice irá
+usar seu diretório de trabalho e o índice para resolver conflitos, e
+mudanças locais existentes irão interferir com o processo de resolução
+de conflitos (git ainda irá realizar a obtenção mas irá se recusar a
+unificar --- Alice terá que se livrar de suas mudanças locais de alguma
+forma e puxar de novo quando isso acontecer).
+
+Alice pode espiar o que Bob fez sem unificar primeiro, usando o comando
+"fetch"; isto permite Alice inspecionar o que Bob fez, usando um símbolo
+especial "FETCH_HEAD", com o fim de determinar se ele tem alguma coisa
+que vale puxar, assim:
+
+------------------------------------------------
+alice$ git fetch /home/bob/myrepo master
+alice$ git log -p HEAD..FETCH_HEAD
+------------------------------------------------
+
+Esta operação é segura mesmo se Alice tem mudanças locais não gravadas.
+A notação de intervalo "HEAD..FETCH_HEAD" significa mostrar tudo que é
+alcançável de FETCH_HEAD mas exclua tudo o que é alcançável de HEAD.
+Alice já sabe tudo que leva a seu estado atual (HEAD), e revisa o que Bob
+tem em seu estado (FETCH_HEAD) que ela ainda não viu com esse comando.
+
+Se Alice quer visualizar o que Bob fez desde que suas histórias se
+ramificaram, ela pode disparar o seguinte comando:
+
+------------------------------------------------
+$ gitk HEAD..FETCH_HEAD
+------------------------------------------------
+
+Isto usa a mesma notação de intervalo que vimos antes com 'git log'.
+
+Alice pode querer ver o que ambos fizeram desde que ramificaram. Ela
+pode usar a forma com três pontos ao invés da forma com dois pontos:
+
+------------------------------------------------
+$ gitk HEAD...FETCH_HEAD
+------------------------------------------------
+
+Isto significa "mostre tudo que é alcançável de qualquer um deles, mas
+exclua tudo que é alcançável a partir de ambos".
+
+Por favor, note que essas notações de intervalo podem ser usadas tanto
+com gitk quanto com "git log".
+
+Após inspecionar o que Bob fez, se não há nada urgente, Alice pode
+decidir continuar trabalhando sem puxar de Bob. Se a história de Bob
+tem alguma coisa que Alice precisa imediatamente, Alice pode optar por
+separar seu trabalho em progresso primeiro, fazer um "pull", e, então,
+finalmente, retomar seu trabalho em progresso em cima da história
+resultante.
+
+Quando você está trabalhando em um pequeno grupo unido, não é incomum
+interagir com o mesmo repositório várias e várias vezes. Definindo um
+repositório remoto antes de tudo, você pode fazê-lo mais facilmente:
+
+------------------------------------------------
+alice$ git remote add bob /home/bob/myrepo
+------------------------------------------------
+
+Com isso, Alice pode executar a primeira parte da operação "pull" usando
+o comando 'git-fetch' sem unificar suas mudanças com seu próprio ramo,
+usando:
+
+-------------------------------------
+alice$ git fetch bob
+-------------------------------------
+
+Diferente da forma longa, quando Alice obteve de Bob usando um
+repositório remoto antes definido com 'git-remote', o que foi obtido é
+armazenado em um ramo remoto, neste caso `bob/master`. Então, após isso:
+
+-------------------------------------
+alice$ git log -p master..bob/master
+-------------------------------------
+
+mostra uma lista de todas as mudanças que Bob fez desde que ramificou do
+ramo master de Alice.
+
+Após examinar essas mudanças, Alice pode unificá-las em seu ramo master:
+
+-------------------------------------
+alice$ git merge bob/master
+-------------------------------------
+
+Esse `merge` pode também ser feito puxando de seu próprio ramo remoto,
+assim:
+
+-------------------------------------
+alice$ git pull . remotes/bob/master
+-------------------------------------
+
+Note que 'git pull' sempre unifica ao ramo atual, independente do que
+mais foi passado na linha de comando.
+
+Depois, Bob pode atualizar seu repositório com as últimas mudanças de
+Alice, usando
+
+-------------------------------------
+bob$ git pull
+-------------------------------------
+
+Note que ele não precisa dar o caminho do repositório de Alice; quando
+Bob clonou seu repositório, o git armazenou a localização de seu
+repositório na configuração do mesmo, e essa localização é usada
+para puxar:
+
+-------------------------------------
+bob$ git config --get remote.origin.url
+/home/alice/project
+-------------------------------------
+
+(A configuração completa criada por 'git-clone' é visível usando `git
+config -l`, e a página de manual linkgit:git-config[1] explica o
+significado de cada opção.)
+
+Git também mantém uma cópia limpa do ramo master de Alice sob o nome
+"origin/master":
+
+-------------------------------------
+bob$ git branch -r
+  origin/master
+-------------------------------------
+
+Se Bob decidir depois em trabalhar em um host diferente, ele ainda pode
+executar clones e puxar usando o protocolo ssh:
+
+-------------------------------------
+bob$ git clone alice.org:/home/alice/project myrepo
+-------------------------------------
+
+Alternativamente, o git tem um protocolo nativo, ou pode usar rsync ou
+http; veja linkgit:git-pull[1] para detalhes.
+
+Git pode também ser usado em um modo parecido com CVS, com um
+repositório central para o qual vários usuários empurram modificações;
+veja linkgit:git-push[1] e linkgit:gitcvs-migration[7].
+
+Explorando história
+-----------------
+
+A história no git é representada como uma série de commits
+interrelacionados. Nós já vimos que o comando 'git-log' pode listar
+esses commits. Note que a primeira linha de cada entrada no log também
+dá o nome para o 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.
+-------------------------------------
+
+Nós podemos dar este nome ao 'git-show' para ver os detalhes sobre este
+commit.
+
+-------------------------------------
+$ git show c82a22c39cbc32576f64f5c6b3f24b99ea8149c7
+-------------------------------------
+
+Mas há outras formas de se referir aos commits. Você pode usar qualquer
+parte inicial do nome que seja longo o bastante para identificar
+unicamente o commit:
+
+-------------------------------------
+$ git show c82a22c39c  # os primeiros caracteres do nome são o bastante
+                       # usualmente
+$ git show HEAD                # a ponta do ramo atual
+$ git show experimental        # a ponta do ramo "experimental"
+-------------------------------------
+
+Todo commit normalmente tem um commit "pai" que aponta para o estado
+anterior do projeto:
+
+-------------------------------------
+$ git show HEAD^  # para ver o pai de HEAD
+$ git show HEAD^^ # para ver o avô de HEAD
+$ git show HEAD~4 # para ver o trisavô de HEAD
+-------------------------------------
+
+Note que commits de unificação podem ter mais de um pai:
+
+-------------------------------------
+$ git show HEAD^1 # mostra o primeiro pai de HEAD (o mesmo que HEAD^)
+$ git show HEAD^2 # mostra o segundo pai de HEAD
+-------------------------------------
+
+Você também pode dar aos commits nomes à sua escolha; após executar
+
+-------------------------------------
+$ git tag v2.5 1b2e1d63ff
+-------------------------------------
+
+você pode se referir a 1b2e1d63ff pelo nome "v2.5". Se você pretende
+compartilhar esse nome com outras pessoas (por exemplo, para identificar
+uma versão de lançamento), você deveria criar um objeto "tag", e talvez
+assiná-lo; veja linkgit:git-tag[1] para detalhes.
+
+Qualquer comando git que precise conhecer um commit pode receber
+quaisquer desses nomes. Por exemplo:
+
+-------------------------------------
+$ git diff v2.5 HEAD    # compara o HEAD atual com v2.5
+$ git branch stable v2.5 # inicia um novo ramo chamado "stable" baseado
+                        # em v2.5
+$ git reset --hard HEAD^ # reseta seu ramo atual e seu diretório de
+                        # trabalho a seu estado em HEAD^
+-------------------------------------
+
+Seja cuidadoso com o último comando: além de perder quaisquer mudanças
+em seu diretório de trabalho, ele também remove todos os commits
+posteriores desse ramo. Se esse ramo é o único ramo contendo esses
+commits, eles serão perdidos. Também, não use 'git-reset' num ramo
+publicamente visível de onde outros desenvolvedores puxam, já que vai
+forçar unificações desnecessárias para que outros desenvolvedores limpem
+a história. Se você precisa desfazer mudanças que você empurrou, use
+'git-revert' no lugar.
+
+O comando 'git-grep' pode buscar strings em qualquer versão de seu
+projeto, então
+
+-------------------------------------
+$ git grep "hello" v2.5
+-------------------------------------
+
+procura por todas as ocorrências de "hello" em v2.5.
+
+Se você deixar de fora o nome do commit, 'git-grep' irá procurar
+quaisquer dos arquivos que ele gerencia no diretório corrente. Então
+
+-------------------------------------
+$ git grep "hello"
+-------------------------------------
+
+é uma forma rápida de buscar somente os arquivos que são rastreados pelo
+git.
+
+Muitos comandos git também recebem um conjunto de commits, o que pode
+ser especificado de várias formas. Aqui estão alguns exemplos com 'git-log':
+
+-------------------------------------
+$ git log v2.5..v2.6            # commits entre v2.5 e v2.6
+$ git log v2.5..                # commits desde v2.5
+$ git log --since="2 weeks ago" # commits das últimas 2 semanas
+$ git log v2.5.. Makefile       # commits desde v2.5 que modificam
+                               # Makefile
+-------------------------------------
+
+Você também pode dar ao 'git-log' um "intervalo" de commits onde o
+primeiro não é necessariamente um ancestral do segundo; por exemplo, se
+as pontas dos ramos "stable" e "master" divergiram de um commit
+comum algum tempo atrás, então
+
+-------------------------------------
+$ git log stable..master
+-------------------------------------
+
+irá listar os commits feitos no ramo "master" mas não no ramo
+"stable", enquanto
+
+-------------------------------------
+$ git log master..stable
+-------------------------------------
+
+irá listar a lista de commits feitos no ramo "stable" mas não no ramo
+"master".
+
+O comando 'git-log' tem uma fraqueza: ele precisa mostrar os commits em
+uma lista. Quando a história tem linhas de desenvolvimento que
+divergiram e então foram unificadas novamente, a ordem em que 'git-log'
+apresenta essas mudanças é irrelevante.
+
+A maioria dos projetos com múltiplos contribuidores (como o kernel
+Linux, ou o próprio git) tem unificações frequentes, e 'gitk' faz um
+trabalho melhor de visualizar sua história. Por exemplo,
+
+-------------------------------------
+$ gitk --since="2 weeks ago" drivers/
+-------------------------------------
+
+permite a você navegar em quaisquer commits desde as últimas duas semanas
+de commits que modificaram arquivos sob o diretório "drivers". (Nota:
+você pode ajustar as fontes do gitk segurando a tecla control enquanto
+pressiona "-" ou "+".)
+
+Finalmente, a maioria dos comandos que recebem nomes de arquivo permitirão
+também, opcionalmente, preceder qualquer nome de arquivo por um
+commit, para especificar uma versão particular do arquivo:
+
+-------------------------------------
+$ git diff v2.5:Makefile HEAD:Makefile.in
+-------------------------------------
+
+Você pode usar 'git-show' para ver tal arquivo:
+
+-------------------------------------
+$ git show v2.5:Makefile
+-------------------------------------
+
+Próximos passos
+----------
+
+Este tutorial deve ser o bastante para operar controle de revisão
+distribuído básico para seus projetos. No entanto, para entender
+plenamente a profundidade e o poder do git você precisa entender duas
+idéias simples nas quais ele se baseia:
+
+  * A base de objetos é um sistema bem elegante usado para armazenar a
+    história de seu projeto--arquivos, diretórios, e commits.
+
+  * O arquivo de índice é um cache do estado de uma árvore de diretório,
+    usado para criar commits, restaurar diretórios de trabalho, e
+    armazenar as várias árvores envolvidas em uma unificação.
+
+A parte dois deste tutorial explica a base de objetos, o arquivo de
+índice, e algumas outras coisinhas que você vai precisar pra usar o
+máximo do git. Você pode encontrá-la em linkgit:gittutorial-2[7].
+
+Se você não quiser continuar com o tutorial agora nesse momento, algumas
+outras digressões que podem ser interessantes neste ponto são:
+
+  * linkgit:git-format-patch[1], linkgit:git-am[1]: Estes convertem
+    séries de commits em patches para email, e vice-versa, úteis para
+    projetos como o kernel Linux que dependem fortemente de patches
+    enviados por email.
+
+  * linkgit:git-bisect[1]: Quando há uma regressão em seu projeto, uma
+    forma de rastrear um bug é procurando pela história para encontrar o
+    commit culpado. Git bisect pode ajudar a executar uma busca binária
+    por esse commit. Ele é inteligente o bastante para executar uma
+    busca próxima da ótima mesmo no caso de uma história complexa
+    não-linear com muitos ramos unificados.
+
+  * link:everyday.html[GIT diariamente com 20 e tantos comandos]
+
+  * linkgit:gitcvs-migration[7]: Git para usuários de CVS.
+
+VEJA TAMBÉM
+--------
+linkgit:gittutorial-2[7],
+linkgit:gitcvs-migration[7],
+linkgit:gitcore-tutorial[7],
+linkgit:gitglossary[7],
+linkgit:git-help[1],
+link:everyday.html[git diariamente],
+link:user-manual.html[O Manual do Usuário git]
+
+GIT
+---
+Parte da suite linkgit:git[1].
index f9811f24733bde97b76dc8e695bad82eace5586b..5dd6e5a0c7e7f215d4de783a2751e98e41c69948 100644 (file)
@@ -4,6 +4,13 @@
        (see the section <<URLS,GIT URLS>> below) or the name
        of a remote (see the section <<REMOTES,REMOTES>> below).
 
+ifndef::git-pull[]
+<group>::
+       A name referring to a list of repositories as the value
+       of remotes.<group> in the configuration file.
+       (See linkgit:git-config[1]).
+endif::git-pull[]
+
 <refspec>::
        The format of a <refspec> parameter is an optional plus
        `{plus}`, followed by the source ref <src>, followed
@@ -11,9 +18,9 @@
 +
 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>.
+ref that matches it is fast-forwarded using <src>.
 If the optional plus `+` is used, the local ref
-is updated even if it does not result in a fast forward
+is updated even if it does not result in a fast-forward
 update.
 +
 [NOTE]
@@ -31,7 +38,7 @@ 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
+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
@@ -43,13 +50,13 @@ on the remote branch, merge it into your development branch with
 +
 [NOTE]
 There is a difference between listing multiple <refspec>
-directly on 'git-pull' command line and having multiple
+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.
+'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>
+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
index 11eec941dff7302ffcd68eca3d6437d31c022ca9..e2237ae4a0a80b0e5aa22cbb13aa5a01ce4fb891 100644 (file)
@@ -98,6 +98,15 @@ you would get an output like this:
 This implies the '--topo-order' option by default, but the
 '--date-order' option may also be specified.
 
+ifdef::git-rev-list[]
+--count::
+       Print a number stating how many commits would have been
+       listed, and suppress all other output.  When used together
+       with '--left-right', instead print the counts for left and
+       right commits, separated by a tab.
+endif::git-rev-list[]
+
+
 ifndef::git-rev-list[]
 Diff Formatting
 ~~~~~~~~~~~~~~~
@@ -108,8 +117,8 @@ options may be given. See linkgit:git-diff-files[1] for more options.
 
 -c::
 
-       This flag changes the way a merge commit is displayed.  It shows
-       the differences from each of the parents to the merge result
+       With this option, diff output for a merge commit
+       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. Furthermore, it lists only files
        which were modified from all parents.
@@ -121,6 +130,15 @@ options may be given. See linkgit:git-diff-files[1] for more options.
        the parents have only two variants and the merge result picks
        one of them without modification.
 
+-m::
+
+       This flag makes the merge commits show the full diff like
+       regular commits; for each merge parent, a separate log entry
+       and diff is generated. An exception is that only diff against
+       the first parent is shown when '--first-parent' option is given;
+       in that case, the output represents the changes the merge
+       brought _into_ the then-current branch.
+
 -r::
 
        Show recursive diffs.
@@ -201,6 +219,10 @@ endif::git-rev-list[]
 
        Stop when a given path disappears from the tree.
 
+--merges::
+
+       Print only merge commits.
+
 --no-merges::
 
        Do not print commits with more than one parent.
@@ -221,30 +243,54 @@ endif::git-rev-list[]
 
 --all::
 
-       Pretend as if all the refs in `$GIT_DIR/refs/` are listed on the
+       Pretend as if all the refs in `refs/` are listed on the
        command line as '<commit>'.
 
---branches::
+--branches[=pattern]::
 
-       Pretend as if all the refs in `$GIT_DIR/refs/heads` are listed
-       on the command line as '<commit>'.
+       Pretend as if all the refs in `refs/heads` are listed
+       on the command line as '<commit>'. If `pattern` is given, limit
+       branches to ones matching given shell glob. If pattern lacks '?',
+       '*', or '[', '/*' at the end is implied.
 
---tags::
+--tags[=pattern]::
 
-       Pretend as if all the refs in `$GIT_DIR/refs/tags` are listed
-       on the command line as '<commit>'.
+       Pretend as if all the refs in `refs/tags` are listed
+       on the command line as '<commit>'. If `pattern` is given, limit
+       tags to ones matching given shell glob. If pattern lacks '?', '*',
+       or '[', '/*' at the end is implied.
 
---remotes::
+--remotes[=pattern]::
 
-       Pretend as if all the refs in `$GIT_DIR/refs/remotes` are listed
-       on the command line as '<commit>'.
+       Pretend as if all the refs in `refs/remotes` are listed
+       on the command line as '<commit>'. If `pattern`is given, limit
+       remote tracking branches to ones matching given shell glob.
+       If pattern lacks '?', '*', or '[', '/*' at the end is implied.
+
+--glob=glob-pattern::
+       Pretend as if all the refs matching shell glob `glob-pattern`
+       are listed on the command line as '<commit>'. Leading 'refs/',
+       is automatically prepended if missing. If pattern lacks '?', '*',
+       or '[', '/*' at the end is implied.
+
+
+ifndef::git-rev-list[]
+--bisect::
+
+       Pretend as if the bad bisection ref `refs/bisect/bad`
+       was listed and as if it was followed by `--not` and the good
+       bisection refs `refs/bisect/good-*` on the command
+       line.
+endif::git-rev-list[]
 
-ifdef::git-rev-list[]
 --stdin::
 
        In addition to the '<commit>' listed on the command
-       line, read them from the standard input.
+       line, read them from the standard input. If a '--' separator is
+       seen, stop reading commits and start reading paths to limit the
+       result.
 
+ifdef::git-rev-list[]
 --quiet::
 
        Don't print anything to standard output.  This form
@@ -275,7 +321,7 @@ excluded from the output.
        reflog entries from the most recent one to older ones.
        When this option is used you cannot specify commits to
        exclude (that is, '{caret}commit', 'commit1..commit2',
-       nor 'commit1...commit2' notations cannot be used).
+       nor 'commit1\...commit2' notations cannot be used).
 +
 With '\--pretty' format other than oneline (for obvious reasons),
 this causes the output to have two extra lines of information
@@ -347,6 +393,14 @@ Default mode::
        merges from the resulting history, as there are no selected
        commits contributing to this merge.
 
+--ancestry-path::
+
+       When given a range of commits to display (e.g. 'commit1..commit2'
+       or 'commit2 {caret}commit1'), only display commits that exist
+       directly on the ancestry chain between the 'commit1' and
+       'commit2', i.e. commits that are both descendants of 'commit1',
+       and ancestors of 'commit2'.
+
 A more detailed explanation follows.
 
 Suppose you specified `foo` as the <paths>.  We shall call commits
@@ -403,7 +457,7 @@ This results in:
 +
 -----------------------------------------------------------------------
          .-A---N---O
-        /         /
+        /     /   /
        I---------D
 -----------------------------------------------------------------------
 +
@@ -474,8 +528,6 @@ Note that without '\--full-history', this still simplifies merges: if
 one of the parents is TREESAME, we follow only that one, so the other
 sides of the merge are never walked.
 
-Finally, there is a fourth simplification mode available:
-
 --simplify-merges::
 
        First, build a history graph in the same way that
@@ -517,6 +569,46 @@ Note the major differences in `N` and `P` over '\--full-history':
   removed completely, because it had one parent and is TREESAME.
 --
 
+Finally, there is a fifth simplification mode available:
+
+--ancestry-path::
+
+       Limit the displayed commits to those directly on the ancestry
+       chain between the "from" and "to" commits in the given commit
+       range. I.e. only display commits that are ancestor of the "to"
+       commit, and descendants of the "from" commit.
++
+As an example use case, consider the following commit history:
++
+-----------------------------------------------------------------------
+           D---E-------F
+          /     \       \
+         B---C---G---H---I---J
+        /                     \
+       A-------K---------------L--M
+-----------------------------------------------------------------------
++
+A regular 'D..M' computes the set of commits that are ancestors of `M`,
+but excludes the ones that are ancestors of `D`. This is useful to see
+what happened to the history leading to `M` since `D`, in the sense
+that "what does `M` have that did not exist in `D`". The result in this
+example would be all the commits, except `A` and `B` (and `D` itself,
+of course).
++
+When we want to find out what commits in `M` are contaminated with the
+bug introduced by `D` and need fixing, however, we might want to view
+only the subset of 'D..M' that are actually descendants of `D`, i.e.
+excluding `C` and `K`. This is exactly what the '\--ancestry-path'
+option does. Applied to the 'D..M' range, it results in:
++
+-----------------------------------------------------------------------
+               E-------F
+                \       \
+                 G---H---I---J
+                              \
+                               L--M
+-----------------------------------------------------------------------
+
 The '\--simplify-by-decoration' option allows you to view only the
 big picture of the topology of the history, by omitting commits
 that are not referenced by tags.  Commits are marked as !TREESAME
@@ -532,7 +624,11 @@ Bisection Helpers
 --bisect::
 
 Limit output to the one commit object which is roughly halfway between
-the included and excluded commits. Thus, if
+included and excluded commits. Note that the bad bisection ref
+`refs/bisect/bad` is added to the included commits (if it
+exists) and the good bisection refs `refs/bisect/good-*` are
+added to the excluded commits (if they exist). Thus, supposing there
+are no refs in `refs/bisect/`, if
 
 -----------------------------------------------------------------------
        $ git rev-list --bisect foo ^bar ^baz
@@ -552,22 +648,24 @@ one.
 
 --bisect-vars::
 
-This calculates the same as `--bisect`, but outputs text ready
-to be eval'ed by the shell. These lines will assign the name of
-the midpoint revision to the variable `bisect_rev`, and the
-expected number of commits to be tested after `bisect_rev` is
-tested to `bisect_nr`, the expected number of commits to be
-tested if `bisect_rev` turns out to be good to `bisect_good`,
-the expected number of commits to be tested if `bisect_rev`
-turns out to be bad to `bisect_bad`, and the number of commits
-we are bisecting right now to `bisect_all`.
+This calculates the same as `--bisect`, except that refs in
+`refs/bisect/` are not used, and except that this outputs
+text ready to be eval'ed by the shell. These lines will assign the
+name of the midpoint revision to the variable `bisect_rev`, and the
+expected number of commits to be tested after `bisect_rev` is tested
+to `bisect_nr`, the expected number of commits to be tested if
+`bisect_rev` turns out to be good to `bisect_good`, the expected
+number of commits to be tested if `bisect_rev` turns out to be bad to
+`bisect_bad`, and the number of commits we are bisecting right now to
+`bisect_all`.
 
 --bisect-all::
 
 This outputs all the commit objects between the included and excluded
 commits, ordered by their distance to the included and excluded
-commits. The farthest from them is displayed first. (This is the only
-one displayed by `--bisect`.)
+commits. Refs in `refs/bisect/` are not used. The farthest
+from them is displayed first. (This is the only one displayed by
+`--bisect`.)
 +
 This is useful because it makes it easy to choose a good commit to
 test when you want to avoid to test some of them for some reason (they
diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt
new file mode 100644 (file)
index 0000000..fe846f0
--- /dev/null
@@ -0,0 +1,199 @@
+SPECIFYING REVISIONS
+--------------------
+
+A revision parameter typically, but not necessarily, names a
+commit object.  They use what is called an 'extended SHA1'
+syntax.  Here are various ways to spell object names.  The
+ones listed near the end of this list are to name trees and
+blobs contained in a commit.
+
+* 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.
+
+* An output from 'git describe'; i.e. a closest tag, optionally
+  followed by a dash and a number of commits, followed by a dash, a
+  `g`, and an abbreviated object name.
+
+* A symbolic ref name.  E.g. 'master' typically means the commit
+  object referenced by 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.
+  When ambiguous, a `<name>` is disambiguated by taking the
+  first match in the following rules:
+
+  . if `$GIT_DIR/<name>` exists, that is what you mean (this is usually
+    useful only for `HEAD`, `FETCH_HEAD`, `ORIG_HEAD` and `MERGE_HEAD`);
+
+  . otherwise, `refs/<name>` if exists;
+
+  . otherwise, `refs/tags/<name>` if exists;
+
+  . otherwise, `refs/heads/<name>` if exists;
+
+  . otherwise, `refs/remotes/<name>` if exists;
+
+  . otherwise, `refs/remotes/<name>/HEAD` if exists.
++
+HEAD names the commit your changes in the working tree is based on.
+FETCH_HEAD records the branch you fetched from a remote repository
+with your last 'git fetch' invocation.
+ORIG_HEAD is created by commands that moves your HEAD in a drastic
+way, to record the position of the HEAD before their operation, so that
+you can change the tip of the branch back to the state before you ran
+them easily.
+MERGE_HEAD records the commit(s) you are merging into your branch
+when you run 'git merge'.
++
+Note that any of the `refs/*` cases above may come either from
+the `$GIT_DIR/refs` directory or from the `$GIT_DIR/packed-refs` file.
+
+* A ref followed by the suffix '@' with 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>). Note that this looks up the state
+  of your *local* ref at a given time; e.g., what was in your local
+  `master` branch last week. If you want to look at commits made during
+  certain times, see `--since` and `--until`.
+
+* A ref followed by the suffix '@' with an ordinal specification
+  enclosed in a brace pair (e.g. '\{1\}', '\{15\}') to specify
+  the n-th prior value of that ref.  For example 'master@\{1\}'
+  is the immediate prior value of 'master' while 'master@\{5\}'
+  is the 5th prior value of 'master'. This suffix may only be used
+  immediately following a ref name and the ref must have an existing
+  log ($GIT_DIR/logs/<ref>).
+
+* You can use the '@' construct with an empty ref part to get at a
+  reflog of the current branch. For example, if you are on the
+  branch 'blabla', then '@\{1\}' means the same as 'blabla@\{1\}'.
+
+* The special construct '@\{-<n>\}' means the <n>th branch checked out
+  before the current one.
+
+* The suffix '@\{upstream\}' to a ref (short form 'ref@\{u\}') refers to
+  the branch the ref is set to build on top of.  Missing ref defaults
+  to the current branch.
+
+* A suffix '{caret}' to a revision parameter (e.g. 'HEAD{caret}') 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 '{tilde}<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.  See below for a illustration of
+  the usage of this form.
+
+* 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.
+
+* A colon, followed by a slash, followed by a text (e.g. `:/fix nasty bug`): this names
+  a commit whose commit message starts with the specified text.
+  This name returns the youngest matching commit which is
+  reachable from any ref.  If the commit message starts with a
+  '!', you have to repeat that;  the special sequence ':/!',
+  followed by something else than '!' is reserved for now.
+
+* A suffix ':' followed by a path (e.g. `HEAD:README`); this names the blob or tree
+  at the given path in the tree-ish object named by the part
+  before the colon.
+  ':path' (with an empty part before the colon, e.g. `:README`)
+  is a special case of the syntax described next: content
+  recorded in the index at the given path.
+
+* A colon, optionally followed by a stage number (0 to 3) and a
+  colon, followed by a path (e.g. `:0:README`); this names a blob object in the
+  index at the given path. Missing stage number (and the colon
+  that follows it, e.g. `:README`) names a stage 0 entry. During a merge, stage
+  1 is the common ancestor, stage 2 is the target branch's version
+  (typically the current branch), and stage 3 is the version from
+  the branch being merged.
+
+Here is an illustration, by Jon Loeliger.  Both commit nodes B
+and C are 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
+
+
+SPECIFYING RANGES
+-----------------
+
+History traversing commands such as 'git log' operate on a set
+of commits, not just a single commit.  To these commands,
+specifying a single revision with the notation described in the
+previous section means the set of commits reachable from that
+commit, following the commit ancestry chain.
+
+To exclude commits reachable from a commit, a prefix `{caret}`
+notation is used.  E.g. `{caret}r1 r2` means commits reachable
+from `r2` but exclude the ones reachable from `r1`.
+
+This set operation appears so often that there is a shorthand
+for it.  When you have two commits `r1` and `r2` (named according
+to the syntax explained in SPECIFYING REVISIONS above), you can ask
+for commits that are reachable from r2 excluding those that are reachable
+from r1 by `{caret}r1 r2` and it can be written as `r1..r2`.
+
+A similar notation `r1\...r2` is called symmetric difference
+of `r1` and `r2` and is defined as
+`r1 r2 --not $(git merge-base --all r1 r2)`.
+It is the set of commits that are reachable from either one of
+`r1` or `r2` but not from both.
+
+Two other shorthands for naming a set that is formed by a commit
+and its parent commits exist.  The `r1{caret}@` notation means all
+parents of `r1`.  `r1{caret}!` includes commit `r1` but excludes
+all of its parents.
+
+Here are a handful of examples:
+
+   D                G H D
+   D F              G H I J D F
+   ^G D             H D
+   ^D B             E I J F B
+   B...C            G H D E B C
+   ^D B C           E I J F B C
+   C^@              I J F
+   F^! D            G H D F
index 5bbd18f0206604416c3833b7541a5b55b7e63976..add6f435b59e3df095e507d8a5d4f911cee9d3b2 100644 (file)
@@ -58,6 +58,9 @@ The result of the enumeration is left in these fields::
 Calling sequence
 ----------------
 
+Note: index may be looked at for .gitignore files that are CE_SKIP_WORKTREE
+marked. If you to exclude files, make sure you have loaded index first.
+
 * Prepare `struct dir_struct dir` and clear it with `memset(&dir, 0,
   sizeof(dir))`.
 
index c784d3edcb2537b84bfb5db3da55faaf45995155..e5061e0677e05f8127b0808104269961ee21f830 100644 (file)
@@ -1,6 +1,52 @@
 hash API
 ========
 
-Talk about <hash.h>
+The hash API is a collection of simple hash table functions. Users are expected
+to implement their own hashing.
 
-(Linus)
+Data Structures
+---------------
+
+`struct hash_table`::
+
+       The hash table structure. The `array` member points to the hash table
+       entries. The `size` member counts the total number of valid and invalid
+       entries in the table. The `nr` member keeps track of the number of
+       valid entries.
+
+`struct hash_table_entry`::
+
+       An opaque structure representing an entry in the hash table. The `hash`
+       member is the entry's hash key and the `ptr` member is the entry's
+       value.
+
+Functions
+---------
+
+`init_hash`::
+
+       Initialize the hash table.
+
+`free_hash`::
+
+       Release memory associated with the hash table.
+
+`insert_hash`::
+
+       Insert a pointer into the hash table. If an entry with that hash
+       already exists, a pointer to the existing entry's value is returned.
+       Otherwise NULL is returned.  This allows callers to implement
+       chaining, etc.
+
+`lookup_hash`::
+
+       Lookup an entry in the hash table. If an entry with that hash exists
+       the entry's value is returned. Otherwise NULL is returned.
+
+`for_each_hash`::
+
+       Call a function for each entry in the hash table. The function is
+       expected to take the entry's value as its only argument and return an
+       int. If the function returns a negative int the loop is aborted
+       immediately.  Otherwise, the return value is accumulated and the sum
+       returned upon completion of the loop.
index d66e61b1eca3d87ffcf14ee0e0447b6ccb75a2bb..d6fc90ac7eb210bd492057422748817a59cfc2a3 100644 (file)
@@ -11,9 +11,6 @@ Core functions:
 
 * `graph_init()` creates a new `struct git_graph`
 
-* `graph_release()` destroys a `struct git_graph`, and frees the memory
-  associated with it.
-
 * `graph_update()` moves the graph to a new commit.
 
 * `graph_next_line()` outputs the next line of the graph into a strbuf.  It
@@ -134,8 +131,6 @@ while ((commit = get_revision(opts)) != NULL) {
                        putchar(opts->diffopt.line_termination);
        }
 }
-
-graph_release(graph);
 ------------
 
 Sample output
diff --git a/Documentation/technical/api-merge.txt b/Documentation/technical/api-merge.txt
new file mode 100644 (file)
index 0000000..a7e050b
--- /dev/null
@@ -0,0 +1,73 @@
+merge API
+=========
+
+The merge API helps a program to reconcile two competing sets of
+improvements to some files (e.g., unregistered changes from the work
+tree versus changes involved in switching to a new branch), reporting
+conflicts if found.  The library called through this API is
+responsible for a few things.
+
+ * determining which trees to merge (recursive ancestor consolidation);
+
+ * lining up corresponding files in the trees to be merged (rename
+   detection, subtree shifting), reporting edge cases like add/add
+   and rename/rename conflicts to the user;
+
+ * performing a three-way merge of corresponding files, taking
+   path-specific merge drivers (specified in `.gitattributes`)
+   into account.
+
+Low-level (single file) merge
+-----------------------------
+
+`ll_merge`::
+
+       Perform a three-way single-file merge in core.  This is
+       a thin wrapper around `xdl_merge` that takes the path and
+       any merge backend specified in `.gitattributes` or
+       `.git/info/attributes` into account.  Returns 0 for a
+       clean merge.
+
+The caller:
+
+1. allocates an mmbuffer_t variable for the result;
+2. allocates and fills variables with the file's original content
+   and two modified versions (using `read_mmfile`, for example);
+3. calls ll_merge();
+4. reads the output from result_buf.ptr and result_buf.size;
+5. releases buffers when finished (free(ancestor.ptr); free(ours.ptr);
+   free(theirs.ptr); free(result_buf.ptr);).
+
+If the modifications do not merge cleanly, `ll_merge` will return a
+nonzero value and `result_buf` will generally include a description of
+the conflict bracketed by markers such as the traditional `<<<<<<<`
+and `>>>>>>>`.
+
+The `ancestor_label`, `our_label`, and `their_label` parameters are
+used to label the different sides of a conflict if the merge driver
+supports this.
+
+The `flag` parameter is a bitfield:
+
+ - The `LL_OPT_VIRTUAL_ANCESTOR` bit indicates whether this is an
+   internal merge to consolidate ancestors for a recursive merge.
+
+ - The `LL_OPT_FAVOR_MASK` bits allow local conflicts to be automatically
+   resolved in favor of one side or the other (as in 'git merge-file'
+   `--ours`/`--theirs`/`--union`).
+   They can be populated by `create_ll_flag`, whose argument can be
+   `XDL_MERGE_FAVOR_OURS`, `XDL_MERGE_FAVOR_THEIRS`, or
+   `XDL_MERGE_FAVOR_UNION`.
+
+Everything else
+---------------
+
+Talk about <merge-recursive.h> and merge_file():
+
+ - merge_trees() to merge with rename detection
+ - merge_recursive() for ancestor consolidation
+ - try_merge_command() for other strategies
+ - conflict format
+ - merge options
+
+(Daniel, Miklos, Stephan, JC)
index e66ca9f70c705841c864196a3a651e04d8156e01..c5d141cd63d15e97dd4b4777c30dccb57d430782 100644 (file)
@@ -60,13 +60,13 @@ Steps to parse options
 . in `cmd_foo(int argc, const char **argv, const char *prefix)`
   call
 
-       argc = parse_options(argc, argv, builtin_foo_options, builtin_foo_usage, flags);
+       argc = parse_options(argc, argv, prefix, builtin_foo_options, builtin_foo_usage, flags);
 +
 `parse_options()` will filter out the processed options of `argv[]` and leave the
 non-option arguments in `argv[]`.
 `argc` is updated appropriately because of the assignment.
 +
-You can also pass NULL instead of a usage array as fourth parameter of
+You can also pass NULL instead of a usage array as the fifth parameter of
 parse_options(), to avoid displaying a help screen with usage info and
 option list.  This should only be done if necessary, e.g. to implement
 a limited parser for only a subset of the options that needs to be run
@@ -115,6 +115,9 @@ There are some macros to easily define options:
 `OPT__ABBREV(&int_var)`::
        Add `\--abbrev[=<n>]`.
 
+`OPT__COLOR(&int_var, description)`::
+       Add `\--color[=<when>]` and `--no-color`.
+
 `OPT__DRY_RUN(&int_var)`::
        Add `-n, \--dry-run`.
 
@@ -137,6 +140,10 @@ There are some macros to easily define options:
        Introduce a boolean option.
        If used, `int_var` is bitwise-ored with `mask`.
 
+`OPT_NEGBIT(short, long, &int_var, description, mask)`::
+       Introduce a boolean option.
+       If used, `int_var` is bitwise-anded with the inverted `mask`.
+
 `OPT_SET_INT(short, long, &int_var, description, integer)`::
        Introduce a boolean option.
        If used, set `int_var` to `integer`.
@@ -163,16 +170,38 @@ There are some macros to easily define options:
        and the result will be put into `var`.
        See 'Option Callbacks' below for a more elaborate description.
 
+`OPT_FILENAME(short, long, &var, description)`::
+       Introduce an option with a filename argument.
+       The filename will be prefixed by passing the filename along with
+       the prefix argument of `parse_options()` to `prefix_filename()`.
+
 `OPT_ARGUMENT(long, description)`::
        Introduce a long-option argument that will be kept in `argv[]`.
 
+`OPT_NUMBER_CALLBACK(&var, description, func_ptr)`::
+       Recognize numerical options like -123 and feed the integer as
+       if it was an argument to the function given by `func_ptr`.
+       The result will be put into `var`.  There can be only one such
+       option definition.  It cannot be negated and it takes no
+       arguments.  Short options that happen to be digits take
+       precedence over it.
+
+`OPT_COLOR_FLAG(short, long, &int_var, description)`::
+       Introduce an option that takes an optional argument that can
+       have one of three values: "always", "never", or "auto".  If the
+       argument is not given, it defaults to "always".  The `--no-` form
+       works like `--long=never`; it cannot take an argument.  If
+       "always", set `int_var` to 1; if "never", set `int_var` to 0; if
+       "auto", set `int_var` to 1 if stdout is a tty or a pager,
+       0 otherwise.
+
 
 The last element of the array must be `OPT_END()`.
 
 If not stated otherwise, interpret the arguments as follows:
 
 * `short` is a character for the short option
-  (e.g. `\'e\'` for `-e`, use `0` to omit),
+  (e.g. `{apostrophe}e{apostrophe}` for `-e`, use `0` to omit),
 
 * `long` is a string for the long option
   (e.g. `"example"` for `\--example`, use `NULL` to omit),
@@ -198,11 +227,11 @@ The function must be defined in this form:
 
 The callback mechanism is as follows:
 
-* Inside `funct`, the only interesting member of the structure
-  given by `opt` is the void pointer `opt->value`.
-  `\*opt->value` will be the value that is saved into `var`, if you
+* Inside `func`, the only interesting member of the structure
+  given by `opt` is the void pointer `opt\->value`.
+  `\*opt\->value` will be the value that is saved into `var`, if you
   use `OPT_CALLBACK()`.
-  For example, do `*(unsigned long *)opt->value = 42;` to get 42
+  For example, do `*(unsigned long *)opt\->value = 42;` to get 42
   into an `unsigned long` variable.
 
 * Return value `0` indicates success and non-zero return
index 073b22bd83badb5aada47e061bb29e48d5f95518..c54b17db69b8420bdec7c6ee0f424a5216957add 100644 (file)
@@ -18,6 +18,10 @@ struct remote
 
        An array of all of the url_nr URLs configured for the remote
 
+`pushurl`::
+
+       An array of all of the pushurl_nr push URLs configured for the remote
+
 `push`::
 
         An array of refspecs configured for pushing, with
index 2efe7a40be641bc2532c139637fe02e534ea1152..f18b4f4817448530a5adbe2c8835bb7791add42a 100644 (file)
@@ -35,17 +35,37 @@ Functions
        Convenience functions that encapsulate a sequence of
        start_command() followed by finish_command(). The argument argv
        specifies the program and its arguments. The argument opt is zero
-       or more of the flags `RUN_COMMAND_NO_STDIN`, `RUN_GIT_CMD`, or
-       `RUN_COMMAND_STDOUT_TO_STDERR` that correspond to the members
-       .no_stdin, .git_cmd, .stdout_to_stderr of `struct child_process`.
+       or more of the flags `RUN_COMMAND_NO_STDIN`, `RUN_GIT_CMD`,
+       `RUN_COMMAND_STDOUT_TO_STDERR`, or `RUN_SILENT_EXEC_FAILURE`
+       that correspond to the members .no_stdin, .git_cmd,
+       .stdout_to_stderr, .silent_exec_failure of `struct child_process`.
        The argument dir corresponds the member .dir. The argument env
        corresponds to the member .env.
 
+The functions above do the following:
+
+. If a system call failed, errno is set and -1 is returned. A diagnostic
+  is printed.
+
+. If the program was not found, then -1 is returned and errno is set to
+  ENOENT; a diagnostic is printed only if .silent_exec_failure is 0.
+
+. Otherwise, the program is run. If it terminates regularly, its exit
+  code is returned. No diagnostic is printed, even if the exit code is
+  non-zero.
+
+. If the program terminated due to a signal, then the return value is the
+  signal number - 128, ie. it is negative and so indicates an unusual
+  condition; a diagnostic is printed. This return value can be passed to
+  exit(2), which will report the same code to the parent process that a
+  POSIX shell's $? would report for a program that died from the signal.
+
+
 `start_async`::
 
        Run a function asynchronously. Takes a pointer to a `struct
-       async` that specifies the details and returns a pipe FD
-       from which the caller reads. See below for details.
+       async` that specifies the details and returns a set of pipe FDs
+       for communication with the function. See below for details.
 
 `finish_async`::
 
@@ -115,7 +135,7 @@ stderr as follows:
 
        .in: The FD must be readable; it becomes child's stdin.
        .out: The FD must be writable; it becomes child's stdout.
-       .err > 0 is not supported.
+       .err: The FD must be writable; it becomes child's stderr.
 
   The specified FD is closed by start_command(), even if it fails to
   run the sub-process!
@@ -143,6 +163,11 @@ string pointers (NULL terminated) in .env:
 To specify a new initial working directory for the sub-process,
 specify it in the .dir member.
 
+If the program cannot be found, the functions return -1 and set
+errno to ENOENT. Normally, an error message is printed, but if
+.silent_exec_failure is set to 1, no message is printed for this
+special error condition.
+
 
 * `struct async`
 
@@ -155,17 +180,47 @@ The caller:
    struct async variable;
 2. initializes .proc and .data;
 3. calls start_async();
-4. processes the data by reading from the fd in .out;
-5. closes .out;
+4. processes communicates with proc through .in and .out;
+5. closes .in and .out;
 6. calls finish_async().
 
+The members .in, .out are used to provide a set of fd's for
+communication between the caller and the callee as follows:
+
+. Specify 0 to have no file descriptor passed.  The callee will
+  receive -1 in the corresponding argument.
+
+. Specify < 0 to have a pipe allocated; start_async() replaces
+  with the pipe FD in the following way:
+
+       .in: Returns the writable pipe end into which the caller
+       writes; the readable end of the pipe becomes the function's
+       in argument.
+
+       .out: Returns the readable pipe end from which the caller
+       reads; the writable end of the pipe becomes the function's
+       out argument.
+
+  The caller of start_async() must close the returned FDs after it
+  has completed reading from/writing from them.
+
+. Specify a file descriptor > 0 to be used by the function:
+
+       .in: The FD must be readable; it becomes the function's in.
+       .out: The FD must be writable; it becomes the function's out.
+
+  The specified FD is closed by start_async(), even if it fails to
+  run the function.
+
 The function pointer in .proc has the following signature:
 
-       int proc(int fd, void *data);
+       int proc(int in, int out, void *data);
 
-. fd specifies a writable file descriptor to which the function must
-  write the data that it produces. The function *must* close this
-  descriptor before it returns.
+. in, out specifies a set of file descriptors to which the function
+  must read/write the data that it needs/produces.  The function
+  *must* close these descriptors before it returns.  A descriptor
+  may be -1 if the caller did not configure a descriptor for that
+  direction.
 
 . data is the value that the caller has specified in the .data member
   of struct async.
@@ -176,12 +231,13 @@ The function pointer in .proc has the following signature:
 
 
 There are serious restrictions on what the asynchronous function can do
-because this facility is implemented by a pipe to a forked process on
-UNIX, but by a thread in the same address space on Windows:
+because this facility is implemented by a thread in the same address
+space on most platforms (when pthreads is available), but by a pipe to
+a forked process otherwise:
 
 . It cannot change the program's state (global variables, environment,
-  etc.) in a way that the caller notices; in other words, .out is the
-  only communication channel to the caller.
+  etc.) in a way that the caller notices; in other words, .in and .out
+  are the only communication channels to the caller.
 
 . It must not change the program's state that the caller of the
   facility also uses.
index 7438149249364ca8837811771b072b20990b3a5d..afe27599511c5f6bab6e4f799fd18e7d83bdd454 100644 (file)
@@ -12,7 +12,7 @@ strbuf API actually relies on the string being free of NULs.
 
 strbufs has some invariants that are very important to keep in mind:
 
-. The `buf` member is never NULL, so you it can be used in any usual C
+. The `buf` member is never NULL, so it can be used in any usual C
 string operations safely. strbuf's _have_ to be initialized either by
 `strbuf_init()` or by `= STRBUF_INIT` before the invariants, though.
 +
@@ -55,7 +55,7 @@ Data structures
 
 * `struct strbuf`
 
-This is string buffer structure. The `len` member can be used to
+This is the string buffer structure. The `len` member can be used to
 determine the current length of the string, and `buf` member provides access to
 the string itself.
 
@@ -199,6 +199,10 @@ character if the letter `n` appears after a `%`.  The function returns
 the length of the placeholder recognized and `strbuf_expand()` skips
 over it.
 +
+The format `%%` is automatically expanded to a single `%` as a quoting
+mechanism; callers do not need to handle the `%` placeholder themselves,
+and the callback function will not be invoked for this placeholder.
++
 All other characters (non-percent and not skipped ones) are copied
 verbatim to the strbuf.  If the callback returned zero, meaning that the
 placeholder is unknown, then the percent sign is copied, too.
@@ -214,6 +218,13 @@ which can be used by the programmer of the callback as she sees fit.
        placeholder and replacement string.  The array needs to be
        terminated by an entry with placeholder set to NULL.
 
+`strbuf_addbuf_percentquote`::
+
+       Append the contents of one strbuf to another, quoting any
+       percent signs ("%") into double-percents ("%%") in the
+       destination. This is useful for literal data to be fed to either
+       strbuf_expand or to the *printf family of functions.
+
 `strbuf_addf`::
 
        Add a formatted string to the buffer.
@@ -253,3 +264,9 @@ same behaviour as well.
        comments are considered contents to be removed or not.
 
 `launch_editor`::
+
+       Launch the user preferred editor to edit a file and fill the buffer
+       with the file's contents upon the user completing their editing. The
+       third argument can be used to set the environment which the editor is
+       run in. If the buffer is NULL the editor is launched as usual but the
+       file's contents are not read into the buffer upon completion.
index 293bb15d206e71f57e906b33ca27ee05e3429521..3f575bdcff3a6b38304710b22371784c2e7443c9 100644 (file)
@@ -38,8 +38,8 @@ struct string_list list;
 int i;
 
 memset(&list, 0, sizeof(struct string_list));
-string_list_append("foo", &list);
-string_list_append("bar", &list);
+string_list_append(&list, "foo");
+string_list_append(&list, "bar");
 for (i = 0; i < list.nr; i++)
        printf("%s\n", list.items[i].string)
 ----
@@ -104,8 +104,12 @@ write `string_list_insert(...)->util = ...;`.
 `unsorted_string_list_has_string`::
 
        It's like `string_list_has_string()` but for unsorted lists.
+
+`unsorted_string_list_lookup`::
+
+       It's like `string_list_lookup()` but for unsorted lists.
 +
-This function needs to look through all items, as opposed to its
+The above two functions need to look through all items, as opposed to their
 counterpart for sorted lists, which performs a binary search.
 
 Data structures
index e3ddf912841298d6317a682a29cbaf628e86f156..14af37c3f14854e87d9d28f60b5ff72eb189c37f 100644 (file)
 tree walking API
 ================
 
-Talk about <tree-walk.h>, things like
+The tree walking API is used to traverse and inspect trees.
 
-* struct tree_desc
-* init_tree_desc
-* tree_entry_extract
-* update_tree_entry
-* get_tree_entry
+Data Structures
+---------------
 
-(JC, Linus)
+`struct name_entry`::
+
+       An entry in a tree. Each entry has a sha1 identifier, pathname, and
+       mode.
+
+`struct tree_desc`::
+
+       A semi-opaque data structure used to maintain the current state of the
+       walk.
++
+* `buffer` is a pointer into the memory representation of the tree. It always
+points at the current entry being visited.
+
+* `size` counts the number of bytes left in the `buffer`.
+
+* `entry` points to the current entry being visited.
+
+`struct traverse_info`::
+
+       A structure used to maintain the state of a traversal.
++
+* `prev` points to the traverse_info which was used to descend into the
+current tree. If this is the top-level tree `prev` will point to
+a dummy traverse_info.
+
+* `name` is the entry for the current tree (if the tree is a subtree).
+
+* `pathlen` is the length of the full path for the current tree.
+
+* `conflicts` can be used by callbacks to maintain directory-file conflicts.
+
+* `fn` is a callback called for each entry in the tree. See Traversing for more
+information.
+
+* `data` can be anything the `fn` callback would want to use.
+
+* `show_all_errors` tells whether to stop at the first error or not.
+
+Initializing
+------------
+
+`init_tree_desc`::
+
+       Initialize a `tree_desc` and decode its first entry. The buffer and
+       size parameters are assumed to be the same as the buffer and size
+       members of `struct tree`.
+
+`fill_tree_descriptor`::
+
+       Initialize a `tree_desc` and decode its first entry given the sha1 of
+       a tree. Returns the `buffer` member if the sha1 is a valid tree
+       identifier and NULL otherwise.
+
+`setup_traverse_info`::
+
+       Initialize a `traverse_info` given the pathname of the tree to start
+       traversing from. The `base` argument is assumed to be the `path`
+       member of the `name_entry` being recursed into unless the tree is a
+       top-level tree in which case the empty string ("") is used.
+
+Walking
+-------
+
+`tree_entry`::
+
+       Visit the next entry in a tree. Returns 1 when there are more entries
+       left to visit and 0 when all entries have been visited. This is
+       commonly used in the test of a while loop.
+
+`tree_entry_len`::
+
+       Calculate the length of a tree entry's pathname. This utilizes the
+       memory structure of a tree entry to avoid the overhead of using a
+       generic strlen().
+
+`update_tree_entry`::
+
+       Walk to the next entry in a tree. This is commonly used in conjunction
+       with `tree_entry_extract` to inspect the current entry.
+
+`tree_entry_extract`::
+
+       Decode the entry currently being visited (the one pointed to by
+       `tree_desc's` `entry` member) and return the sha1 of the entry. The
+       `pathp` and `modep` arguments are set to the entry's pathname and mode
+       respectively.
+
+`get_tree_entry`::
+
+       Find an entry in a tree given a pathname and the sha1 of a tree to
+       search. Returns 0 if the entry is found and -1 otherwise. The third
+       and fourth parameters are set to the entry's sha1 and mode
+       respectively.
+
+Traversing
+----------
+
+`traverse_trees`::
+
+       Traverse `n` number of trees in parallel. The `fn` callback member of
+       `traverse_info` is called once for each tree entry.
+
+`traverse_callback_t`::
+       The arguments passed to the traverse callback are as follows:
++
+* `n` counts the number of trees being traversed.
+
+* `mask` has its nth bit set if something exists in the nth entry.
+
+* `dirmask` has its nth bit set if the nth tree's entry is a directory.
+
+* `entry` is an array of size `n` where the nth entry is from the nth tree.
+
+* `info` maintains the state of the traversal.
+
++
+Returning a negative value will terminate the traversal. Otherwise the
+return value is treated as an update mask. If the nth bit is set the nth tree
+will be updated and if the bit is not set the nth tree entry will be the
+same in the next callback invocation.
+
+`make_traverse_path`::
+
+       Generate the full pathname of a tree entry based from the root of the
+       traversal. For example, if the traversal has recursed into another
+       tree named "bar" the pathname of an entry "baz" in the "bar"
+       tree would be "bar/baz".
+
+`traverse_path_len`::
+
+       Calculate the length of a pathname returned by `make_traverse_path`.
+       This utilizes the memory structure of a tree entry to avoid the
+       overhead of using a generic strlen().
+
+Authors
+-------
+
+Written by Junio C Hamano <gitster@pobox.com> and Linus Torvalds
+<torvalds@linux-foundation.org>
index 9cd48b48597f9b7e822fc3d81e0bc556d6631b02..369f91d3b949b23682c4deda8234f13513f15732 100644 (file)
-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.
+Packfile transfer protocols
+===========================
+
+Git supports transferring data in packfiles over the ssh://, git:// and
+file:// transports.  There exist two sets of protocols, one for pushing
+data from a client to a server and another for fetching data from a
+server to a client.  All three transports (ssh, git, file) use the same
+protocol to transfer data.
+
+The processes invoked in the canonical Git implementation are 'upload-pack'
+on the server side and 'fetch-pack' on the client side for fetching data;
+then 'receive-pack' on the server and 'send-pack' on the client for pushing
+data.  The protocol functions to have a server tell a client what is
+currently on the server, then for the two to negotiate the smallest amount
+of data to send in order to fully update one or the other.
+
+Transports
+----------
+There are three transports over which the packfile protocol is
+initiated.  The Git transport is a simple, unauthenticated server that
+takes the command (almost always 'upload-pack', though Git
+servers can be configured to be globally writable, in which 'receive-
+pack' initiation is also allowed) with which the client wishes to
+communicate and executes it and connects it to the requesting
+process.
+
+In the SSH transport, the client just runs the 'upload-pack'
+or 'receive-pack' process on the server over the SSH protocol and then
+communicates with that invoked process over the SSH connection.
+
+The file:// transport runs the 'upload-pack' or 'receive-pack'
+process locally and communicates with it over a pipe.
+
+Git Transport
+-------------
+
+The Git transport starts off by sending the command and repository
+on the wire using the pkt-line format, followed by a NUL byte and a
+hostname parameter, terminated by a NUL byte.
+
+   0032git-upload-pack /project.git\0host=myserver.com\0
+
+--
+   git-proto-request = request-command SP pathname NUL [ host-parameter NUL ]
+   request-command   = "git-upload-pack" / "git-receive-pack" /
+                      "git-upload-archive"   ; case sensitive
+   pathname          = *( %x01-ff ) ; exclude NUL
+   host-parameter    = "host=" hostname [ ":" port ]
+--
+
+Only host-parameter is allowed in the git-proto-request. Clients
+MUST NOT attempt to send additional parameters. It is used for the
+git-daemon name based virtual hosting.  See --interpolated-path
+option to git daemon, with the %H/%CH format characters.
+
+Basically what the Git client is doing to connect to an 'upload-pack'
+process on the server side over the Git protocol is this:
+
+   $ echo -e -n \
+     "0039git-upload-pack /schacon/gitbook.git\0host=example.com\0" |
+     nc -v example.com 9418
+
+
+SSH Transport
+-------------
+
+Initiating the upload-pack or receive-pack processes over SSH is
+executing the binary on the server via SSH remote execution.
+It is basically equivalent to running this:
+
+   $ ssh git.example.com "git-upload-pack '/project.git'"
+
+For a server to support Git pushing and pulling for a given user over
+SSH, that user needs to be able to execute one or both of those
+commands via the SSH shell that they are provided on login.  On some
+systems, that shell access is limited to only being able to run those
+two commands, or even just one of them.
+
+In an ssh:// format URI, it's absolute in the URI, so the '/' after
+the host name (or port number) is sent as an argument, which is then
+read by the remote git-upload-pack exactly as is, so it's effectively
+an absolute path in the remote filesystem.
+
+       git clone ssh://user@example.com/project.git
+                   |
+                   v
+    ssh user@example.com "git-upload-pack '/project.git'"
+
+In a "user@host:path" format URI, its relative to the user's home
+directory, because the Git client will run:
+
+     git clone user@example.com:project.git
+                   |
+                   v
+  ssh user@example.com "git-upload-pack 'project.git'"
+
+The exception is if a '~' is used, in which case
+we execute it without the leading '/'.
+
+      ssh://user@example.com/~alice/project.git,
+                    |
+                    v
+   ssh user@example.com "git-upload-pack '~alice/project.git'"
+
+A few things to remember here:
+
+- The "command name" is spelled with dash (e.g. git-upload-pack), but
+  this can be overridden by the client;
+
+- The repository path is always quoted with single quotes.
+
+Fetching Data From a Server
+===========================
+
+When one Git repository wants to get data that a second repository
+has, the first can 'fetch' from the second.  This operation determines
+what data the server has that the client does not then streams that
+data down to the client in packfile format.
+
+
+Reference Discovery
+-------------------
+
+When the client initially connects the server will immediately respond
+with a listing of each reference it has (all branches and tags) along
+with the object name that each reference currently points to.
+
+   $ echo -e -n "0039git-upload-pack /schacon/gitbook.git\0host=example.com\0" |
+      nc -v example.com 9418
+   00887217a7c7e582c46cec22a130adf4b9d7d950fba0 HEAD\0multi_ack thin-pack side-band side-band-64k ofs-delta shallow no-progress include-tag
+   00441d3fcd5ced445d1abc402225c0b8a1299641f497 refs/heads/integration
+   003f7217a7c7e582c46cec22a130adf4b9d7d950fba0 refs/heads/master
+   003cb88d2441cac0977faf98efc80305012112238d9d refs/tags/v0.9
+   003c525128480b96c89e6418b1e40909bf6c5b2d580f refs/tags/v1.0
+   003fe92df48743b7bc7d26bcaabfddde0a1e20cae47c refs/tags/v1.0^{}
+   0000
+
+Server SHOULD terminate each non-flush line using LF ("\n") terminator;
+client MUST NOT complain if there is no terminator.
+
+The returned response is a pkt-line stream describing each ref and
+its current value.  The stream MUST be sorted by name according to
+the C locale ordering.
+
+If HEAD is a valid ref, HEAD MUST appear as the first advertised
+ref.  If HEAD is not a valid ref, HEAD MUST NOT appear in the
+advertisement list at all, but other refs may still appear.
+
+The stream MUST include capability declarations behind a NUL on the
+first ref. The peeled value of a ref (that is "ref^{}") MUST be
+immediately after the ref itself, if presented. A conforming server
+MUST peel the ref if it's an annotated tag.
+
+----
+  advertised-refs  =  (no-refs / list-of-refs)
+                     flush-pkt
+
+  no-refs          =  PKT-LINE(zero-id SP "capabilities^{}"
+                     NUL capability-list LF)
+
+  list-of-refs     =  first-ref *other-ref
+  first-ref        =  PKT-LINE(obj-id SP refname
+                     NUL capability-list LF)
+
+  other-ref        =  PKT-LINE(other-tip / other-peeled)
+  other-tip        =  obj-id SP refname LF
+  other-peeled     =  obj-id SP refname "^{}" LF
+
+  capability-list  =  capability *(SP capability)
+  capability       =  1*(LC_ALPHA / DIGIT / "-" / "_")
+  LC_ALPHA         =  %x61-7A
+----
+
+Server and client MUST use lowercase for obj-id, both MUST treat obj-id
+as case-insensitive.
+
+See protocol-capabilities.txt for a list of allowed server capabilities
+and descriptions.
+
+Packfile Negotiation
+--------------------
+After reference and capabilities discovery, the client can decide
+to terminate the connection by sending a flush-pkt, telling the
+server it can now gracefully terminate (as happens with the ls-remote
+command) or it can enter the negotiation phase, where the client and
+server determine what the minimal packfile necessary for transport is.
+
+Once the client has the initial list of references that the server
+has, as well as the list of capabilities, it will begin telling the
+server what objects it wants and what objects it has, so the server
+can make a packfile that only contains the objects that the client needs.
+The client will also send a list of the capabilities it wants to be in
+effect, out of what the server said it could do with the first 'want' line.
+
+----
+  upload-request    =  want-list
+                      have-list
+                      compute-end
+
+  want-list         =  first-want
+                      *additional-want
+                      flush-pkt
+
+  first-want        =  PKT-LINE("want" SP obj-id SP capability-list LF)
+  additional-want   =  PKT-LINE("want" SP obj-id LF)
+
+  have-list         =  *have-line
+  have-line         =  PKT-LINE("have" SP obj-id LF)
+  compute-end       =  flush-pkt / PKT-LINE("done")
+----
+
+Clients MUST send all the obj-ids it wants from the reference
+discovery phase as 'want' lines. Clients MUST send at least one
+'want' command in the request body. Clients MUST NOT mention an
+obj-id in a 'want' command which did not appear in the response
+obtained through ref discovery.
+
+If client is requesting a shallow clone, it will now send a 'deepen'
+line with the depth it is requesting.
+
+Once all the "want"s (and optional 'deepen') are transferred,
+clients MUST send a flush-pkt. If the client has all the references
+on the server, client flushes and disconnects.
+
+TODO: shallow/unshallow response and document the deepen command in the ABNF.
+
+Now the client will send a list of the obj-ids it has using 'have'
+lines.  In multi_ack mode, the canonical implementation will send up
+to 32 of these at a time, then will send a flush-pkt.  The canonical
+implementation will skip ahead and send the next 32 immediately,
+so that there is always a block of 32 "in-flight on the wire" at a
+time.
+
+If the server reads 'have' lines, it then will respond by ACKing any
+of the obj-ids the client said it had that the server also has. The
+server will ACK obj-ids differently depending on which ack mode is
+chosen by the client.
+
+In multi_ack mode:
+
+  * the server will respond with 'ACK obj-id continue' for any common
+    commits.
+
+  * once the server has found an acceptable common base commit and is
+    ready to make a packfile, it will blindly ACK all 'have' obj-ids
+    back to the client.
+
+  * the server will then send a 'NACK' and then wait for another response
+    from the client - either a 'done' or another list of 'have' lines.
+
+In multi_ack_detailed mode:
+
+  * the server will differentiate the ACKs where it is signaling
+    that it is ready to send data with 'ACK obj-id ready' lines, and
+    signals the identified common commits with 'ACK obj-id common' lines.
+
+Without either multi_ack or multi_ack_detailed:
+
+ * upload-pack sends "ACK obj-id" on the first common object it finds.
+   After that it says nothing until the client gives it a "done".
+
+ * upload-pack sends "NAK" on a flush-pkt if no common object
+   has been found yet.  If one has been found, and thus an ACK
+   was already sent, it's silent on the flush-pkt.
+
+After the client has gotten enough ACK responses that it can determine
+that the server has enough information to send an efficient packfile
+(in the canonical implementation, this is determined when it has received
+enough ACKs that it can color everything left in the --date-order queue
+as common with the server, or the --date-order queue is empty), or the
+client determines that it wants to give up (in the canonical implementation,
+this is determined when the client sends 256 'have' lines without getting
+any of them ACKed by the server - meaning there is nothing in common and
+the server should just send all of its objects), then the client will send
+a 'done' command.  The 'done' command signals to the server that the client
+is ready to receive its packfile data.
+
+However, the 256 limit *only* turns on in the canonical client
+implementation if we have received at least one "ACK %s continue"
+during a prior round.  This helps to ensure that at least one common
+ancestor is found before we give up entirely.
+
+Once the 'done' line is read from the client, the server will either
+send a final 'ACK obj-id' or it will send a 'NAK'. The server only sends
+ACK after 'done' if there is at least one common base and multi_ack or
+multi_ack_detailed is enabled. The server always sends NAK after 'done'
+if there is no common base found.
+
+Then the server will start sending its packfile data.
+
+----
+  server-response = *ack_multi ack / nak
+  ack_multi       = PKT-LINE("ACK" SP obj-id ack_status LF)
+  ack_status      = "continue" / "common" / "ready"
+  ack             = PKT-LINE("ACK SP obj-id LF)
+  nak             = PKT-LINE("NAK" LF)
+----
+
+A simple clone may look like this (with no 'have' lines):
+
+----
+   C: 0054want 74730d410fcb6603ace96f1dc55ea6196122532d\0multi_ack \
+     side-band-64k ofs-delta\n
+   C: 0032want 7d1665144a3a975c05f1f43902ddaf084e784dbe\n
+   C: 0032want 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a\n
+   C: 0032want 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01\n
+   C: 0032want 74730d410fcb6603ace96f1dc55ea6196122532d\n
+   C: 0000
+   C: 0009done\n
+
+   S: 0008NAK\n
+   S: [PACKFILE]
+----
+
+An incremental update (fetch) response might look like this:
+
+----
+   C: 0054want 74730d410fcb6603ace96f1dc55ea6196122532d\0multi_ack \
+     side-band-64k ofs-delta\n
+   C: 0032want 7d1665144a3a975c05f1f43902ddaf084e784dbe\n
+   C: 0032want 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a\n
+   C: 0000
+   C: 0032have 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01\n
+   C: [30 more have lines]
+   C: 0032have 74730d410fcb6603ace96f1dc55ea6196122532d\n
+   C: 0000
+
+   S: 003aACK 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01 continue\n
+   S: 003aACK 74730d410fcb6603ace96f1dc55ea6196122532d continue\n
+   S: 0008NAK\n
+
+   C: 0009done\n
+
+   S: 0031ACK 74730d410fcb6603ace96f1dc55ea6196122532d\n
+   S: [PACKFILE]
+----
+
+
+Packfile Data
+-------------
+
+Now that the client and server have finished negotiation about what
+the minimal amount of data that needs to be sent to the client is, the server
+will construct and send the required data in packfile format.
+
+See pack-format.txt for what the packfile itself actually looks like.
+
+If 'side-band' or 'side-band-64k' capabilities have been specified by
+the client, the server will send the packfile data multiplexed.
+
+Each packet starting with the packet-line length of the amount of data
+that follows, followed by a single byte specifying the sideband the
+following data is coming in on.
+
+In 'side-band' mode, it will send up to 999 data bytes plus 1 control
+code, for a total of up to 1000 bytes in a pkt-line.  In 'side-band-64k'
+mode it will send up to 65519 data bytes plus 1 control code, for a
+total of up to 65520 bytes in a pkt-line.
+
+The sideband byte will be a '1', '2' or a '3'. Sideband '1' will contain
+packfile data, sideband '2' will be used for progress information that the
+client will generally print to stderr and sideband '3' is used for error
+information.
+
+If no 'side-band' capability was specified, the server will stream the
+entire packfile without multiplexing.
+
+
+Pushing Data To a Server
+========================
+
+Pushing data to a server will invoke the 'receive-pack' process on the
+server, which will allow the client to tell it which references it should
+update and then send all the data the server will need for those new
+references to be complete.  Once all the data is received and validated,
+the server will then update its references to what the client specified.
+
+Authentication
+--------------
+
+The protocol itself contains no authentication mechanisms.  That is to be
+handled by the transport, such as SSH, before the 'receive-pack' process is
+invoked.  If 'receive-pack' is configured over the Git transport, those
+repositories will be writable by anyone who can access that port (9418) as
+that transport is unauthenticated.
+
+Reference Discovery
+-------------------
+
+The reference discovery phase is done nearly the same way as it is in the
+fetching protocol. Each reference obj-id and name on the server is sent
+in packet-line format to the client, followed by a flush-pkt.  The only
+real difference is that the capability listing is different - the only
+possible values are 'report-status', 'delete-refs' and 'ofs-delta'.
+
+Reference Update Request and Packfile Transfer
+----------------------------------------------
+
+Once the client knows what references the server is at, it can send a
+list of reference update requests.  For each reference on the server
+that it wants to update, it sends a line listing the obj-id currently on
+the server, the obj-id the client would like to update it to and the name
+of the reference.
+
+This list is followed by a flush-pkt and then the packfile that should
+contain all the objects that the server will need to complete the new
+references.
+
+----
+  update-request    =  command-list [pack-file]
+
+  command-list      =  PKT-LINE(command NUL capability-list LF)
+                      *PKT-LINE(command LF)
+                      flush-pkt
+
+  command           =  create / delete / update
+  create            =  zero-id SP new-id  SP name
+  delete            =  old-id  SP zero-id SP name
+  update            =  old-id  SP new-id  SP name
+
+  old-id            =  obj-id
+  new-id            =  obj-id
+
+  pack-file         = "PACK" 28*(OCTET)
+----
+
+If the receiving end does not support delete-refs, the sending end MUST
+NOT ask for delete command.
+
+The pack-file MUST NOT be sent if the only command used is 'delete'.
+
+A pack-file MUST be sent if either create or update command is used,
+even if the server already has all the necessary objects.  In this
+case the client MUST send an empty pack-file.   The only time this
+is likely to happen is if the client is creating
+a new branch or a tag that points to an existing obj-id.
+
+The server will receive the packfile, unpack it, then validate each
+reference that is being updated that it hasn't changed while the request
+was being processed (the obj-id is still the same as the old-id), and
+it will run any update hooks to make sure that the update is acceptable.
+If all of that is fine, the server will then update the references.
+
+Report Status
+-------------
+
+After receiving the pack data from the sender, the receiver sends a
+report if 'report-status' capability is in effect.
+It is a short listing of what happened in that update.  It will first
+list the status of the packfile unpacking as either 'unpack ok' or
+'unpack [error]'.  Then it will list the status for each of the references
+that it tried to update.  Each line is either 'ok [refname]' if the
+update was successful, or 'ng [refname] [error]' if the update was not.
+
+----
+  report-status     = unpack-status
+                     1*(command-status)
+                     flush-pkt
+
+  unpack-status     = PKT-LINE("unpack" SP unpack-result LF)
+  unpack-result     = "ok" / error-msg
+
+  command-status    = command-ok / command-fail
+  command-ok        = PKT-LINE("ok" SP refname LF)
+  command-fail      = PKT-LINE("ng" SP refname SP error-msg LF)
+
+  error-msg         = 1*(OCTECT) ; where not "ok"
+----
+
+Updates can be unsuccessful for a number of reasons.  The reference can have
+changed since the reference discovery phase was originally sent, meaning
+someone pushed in the meantime.  The reference being pushed could be a
+non-fast-forward reference and the update hooks or configuration could be
+set to not allow that, etc.  Also, some references can be updated while others
+can be rejected.
+
+An example client/server communication might look like this:
+
+----
+   S: 007c74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/local\0report-status delete-refs ofs-delta\n
+   S: 003e7d1665144a3a975c05f1f43902ddaf084e784dbe refs/heads/debug\n
+   S: 003f74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/master\n
+   S: 003f74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/team\n
+   S: 0000
+
+   C: 003e7d1665144a3a975c05f1f43902ddaf084e784dbe 74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/debug\n
+   C: 003e74730d410fcb6603ace96f1dc55ea6196122532d 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a refs/heads/master\n
+   C: 0000
+   C: [PACKDATA]
+
+   S: 000eunpack ok\n
+   S: 0018ok refs/heads/debug\n
+   S: 002ang refs/heads/master non-fast-forward\n
+----
diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt
new file mode 100644 (file)
index 0000000..b15517f
--- /dev/null
@@ -0,0 +1,187 @@
+Git Protocol Capabilities
+=========================
+
+Servers SHOULD support all capabilities defined in this document.
+
+On the very first line of the initial server response of either
+receive-pack and upload-pack the first reference is followed by
+a NUL byte and then a list of space delimited server capabilities.
+These allow the server to declare what it can and cannot support
+to the client.
+
+Client will then send a space separated list of capabilities it wants
+to be in effect. The client MUST NOT ask for capabilities the server
+did not say it supports.
+
+Server MUST diagnose and abort if capabilities it does not understand
+was sent.  Server MUST NOT ignore capabilities that client requested
+and server advertised.  As a consequence of these rules, server MUST
+NOT advertise capabilities it does not understand.
+
+The 'report-status' and 'delete-refs' capabilities are sent and
+recognized by the receive-pack (push to server) process.
+
+The 'ofs-delta' capability is sent and recognized by both upload-pack
+and receive-pack protocols.
+
+All other capabilities are only recognized by the upload-pack (fetch
+from server) process.
+
+multi_ack
+---------
+
+The 'multi_ack' capability allows the server to return "ACK obj-id
+continue" as soon as it finds a commit that it can use as a common
+base, between the client's wants and the client's have set.
+
+By sending this early, the server can potentially head off the client
+from walking any further down that particular branch of the client's
+repository history.  The client may still need to walk down other
+branches, sending have lines for those, until the server has a
+complete cut across the DAG, or the client has said "done".
+
+Without multi_ack, a client sends have lines in --date-order until
+the server has found a common base.  That means the client will send
+have lines that are already known by the server to be common, because
+they overlap in time with another branch that the server hasn't found
+a common base on yet.
+
+For example suppose the client has commits in caps that the server
+doesn't and the server has commits in lower case that the client
+doesn't, as in the following diagram:
+
+       +---- u ---------------------- x
+      /              +----- y
+     /              /
+    a -- b -- c -- d -- E -- F
+       \
+       +--- Q -- R -- S
+
+If the client wants x,y and starts out by saying have F,S, the server
+doesn't know what F,S is.  Eventually the client says "have d" and
+the server sends "ACK d continue" to let the client know to stop
+walking down that line (so don't send c-b-a), but it's not done yet,
+it needs a base for x. The client keeps going with S-R-Q, until a
+gets reached, at which point the server has a clear base and it all
+ends.
+
+Without multi_ack the client would have sent that c-b-a chain anyway,
+interleaved with S-R-Q.
+
+thin-pack
+---------
+
+This capability means that the server can send a 'thin' pack, a pack
+which does not contain base objects; if those base objects are available
+on client side. Client requests 'thin-pack' capability when it
+understands how to "thicken" it by adding required delta bases making
+it self-contained.
+
+Client MUST NOT request 'thin-pack' capability if it cannot turn a thin
+pack into a self-contained pack.
+
+
+side-band, side-band-64k
+------------------------
+
+This capability means that server can send, and client understand multiplexed
+progress reports and error info interleaved with the packfile itself.
+
+These two options are mutually exclusive. A modern client always
+favors 'side-band-64k'.
+
+Either mode indicates that the packfile data will be streamed broken
+up into packets of up to either 1000 bytes in the case of 'side_band',
+or 65520 bytes in the case of 'side_band_64k'. Each packet is made up
+of a leading 4-byte pkt-line length of how much data is in the packet,
+followed by a 1-byte stream code, followed by the actual data.
+
+The stream code can be one of:
+
+ 1 - pack data
+ 2 - progress messages
+ 3 - fatal error message just before stream aborts
+
+The "side-band-64k" capability came about as a way for newer clients
+that can handle much larger packets to request packets that are
+actually crammed nearly full, while maintaining backward compatibility
+for the older clients.
+
+Further, with side-band and its up to 1000-byte messages, it's actually
+999 bytes of payload and 1 byte for the stream code. With side-band-64k,
+same deal, you have up to 65519 bytes of data and 1 byte for the stream
+code.
+
+The client MUST send only maximum of one of "side-band" and "side-
+band-64k".  Server MUST diagnose it as an error if client requests
+both.
+
+ofs-delta
+---------
+
+Server can send, and client understand PACKv2 with delta referring to
+its base by position in pack rather than by an obj-id.  That is, they can
+send/read OBJ_OFS_DELTA (aka type 6) in a packfile.
+
+shallow
+-------
+
+This capability adds "deepen", "shallow" and "unshallow" commands to
+the  fetch-pack/upload-pack protocol so clients can request shallow
+clones.
+
+no-progress
+-----------
+
+The client was started with "git clone -q" or something, and doesn't
+want that side band 2.  Basically the client just says "I do not
+wish to receive stream 2 on sideband, so do not send it to me, and if
+you did, I will drop it on the floor anyway".  However, the sideband
+channel 3 is still used for error responses.
+
+include-tag
+-----------
+
+The 'include-tag' capability is about sending annotated tags if we are
+sending objects they point to.  If we pack an object to the client, and
+a tag object points exactly at that object, we pack the tag object too.
+In general this allows a client to get all new annotated tags when it
+fetches a branch, in a single network connection.
+
+Clients MAY always send include-tag, hardcoding it into a request when
+the server advertises this capability. The decision for a client to
+request include-tag only has to do with the client's desires for tag
+data, whether or not a server had advertised objects in the
+refs/tags/* namespace.
+
+Servers MUST pack the tags if their referrant is packed and the client
+has requested include-tags.
+
+Clients MUST be prepared for the case where a server has ignored
+include-tag and has not actually sent tags in the pack.  In such
+cases the client SHOULD issue a subsequent fetch to acquire the tags
+that include-tag would have otherwise given the client.
+
+The server SHOULD send include-tag, if it supports it, regardless
+of whether or not there are tags available.
+
+report-status
+-------------
+
+The upload-pack process can receive a 'report-status' capability,
+which tells it that the client wants a report of what happened after
+a packfile upload and reference update.  If the pushing client requests
+this capability, after unpacking and updating references the server
+will respond with whether the packfile unpacked successfully and if
+each reference was updated successfully.  If any of those were not
+successful, it will send back an error message.  See pack-protocol.txt
+for example messages.
+
+delete-refs
+-----------
+
+If the server sends back the 'delete-refs' capability, it means that
+it is capable of accepting a zero-id value as the target
+value of a reference update.  It is not sent back by the client, it
+simply informs the client that it can be sent zero-id values
+to delete references.
diff --git a/Documentation/technical/protocol-common.txt b/Documentation/technical/protocol-common.txt
new file mode 100644 (file)
index 0000000..d30a1b9
--- /dev/null
@@ -0,0 +1,96 @@
+Documentation Common to Pack and Http Protocols
+===============================================
+
+ABNF Notation
+-------------
+
+ABNF notation as described by RFC 5234 is used within the protocol documents,
+except the following replacement core rules are used:
+----
+  HEXDIG    =  DIGIT / "a" / "b" / "c" / "d" / "e" / "f"
+----
+
+We also define the following common rules:
+----
+  NUL       =  %x00
+  zero-id   =  40*"0"
+  obj-id    =  40*(HEXDIGIT)
+
+  refname  =  "HEAD"
+  refname /=  "refs/" <see discussion below>
+----
+
+A refname is a hierarchical octet string beginning with "refs/" and
+not violating the 'git-check-ref-format' command's validation rules.
+More specifically, they:
+
+. They can include slash `/` for hierarchical (directory)
+  grouping, but no slash-separated component can begin with a
+  dot `.`.
+
+. They must contain at least one `/`. This enforces the presence of a
+  category like `heads/`, `tags/` etc. but the actual names are not
+  restricted.
+
+. They cannot have two consecutive dots `..` anywhere.
+
+. They cannot have ASCII control characters (i.e. bytes whose
+  values are lower than \040, or \177 `DEL`), space, tilde `~`,
+  caret `{caret}`, colon `:`, question-mark `?`, asterisk `*`,
+  or open bracket `[` anywhere.
+
+. They cannot end with a slash `/` nor a dot `.`.
+
+. They cannot end with the sequence `.lock`.
+
+. They cannot contain a sequence `@{`.
+
+. They cannot contain a `\\`.
+
+
+pkt-line Format
+---------------
+
+Much (but not all) of the payload is described around pkt-lines.
+
+A pkt-line is a variable length binary string.  The first four bytes
+of the line, the pkt-len, indicates the total length of the line,
+in hexadecimal.  The pkt-len includes the 4 bytes used to contain
+the length's hexadecimal representation.
+
+A pkt-line MAY contain binary data, so implementors MUST ensure
+pkt-line parsing/formatting routines are 8-bit clean.
+
+A non-binary line SHOULD BE terminated by an LF, which if present
+MUST be included in the total length.
+
+The maximum length of a pkt-line's data component is 65520 bytes.
+Implementations MUST NOT send pkt-line whose length exceeds 65524
+(65520 bytes of payload + 4 bytes of length data).
+
+Implementations SHOULD NOT send an empty pkt-line ("0004").
+
+A pkt-line with a length field of 0 ("0000"), called a flush-pkt,
+is a special case and MUST be handled differently than an empty
+pkt-line ("0004").
+
+----
+  pkt-line     =  data-pkt / flush-pkt
+
+  data-pkt     =  pkt-len pkt-payload
+  pkt-len      =  4*(HEXDIG)
+  pkt-payload  =  (pkt-len - 4)*(OCTET)
+
+  flush-pkt    = "0000"
+----
+
+Examples (as C-style strings):
+
+----
+  pkt-line          actual value
+  ---------------------------------
+  "0006a\n"         "a\n"
+  "0005a"           "a"
+  "000bfoobar\n"    "foobar\n"
+  "0004"            ""
+----
index 48bb97f0b11048f3773fe9fba234fe1160ca3906..53aa0c82c22c687db1e16041fe9f4a97b1fe064e 100644 (file)
@@ -42,10 +42,12 @@ compared, but this is not enabled by default because this member
 is not stable on network filesystems.  With `USE_NSEC`
 compile-time option, `st_mtim.tv_nsec` and `st_ctim.tv_nsec`
 members are also compared, but this is not enabled by default
-because the value of this member becomes meaningless once the
-inode is evicted from the inode cache on filesystems that do not
-store it on disk.
-
+because in-core timestamps can have finer granularity than
+on-disk timestamps, resulting in meaningless changes when an
+inode is evicted from the inode cache.  See commit 8ce13b0
+of git://git.kernel.org/pub/scm/linux/kernel/git/tglx/history.git
+([PATCH] Sync in core time granuality with filesystems,
+2005-01-04).
 
 Racy git
 --------
index 41ec7774f481fd2d70492be4ab2b5e0bf887fc0b..00f7e79c44eec844969467fac431c33071888983 100644 (file)
@@ -27,10 +27,13 @@ config file would appear like this:
 ------------
        [remote "<name>"]
                url = <url>
+               pushurl = <pushurl>
                push = <refspec>
                fetch = <refspec>
 ------------
 
+The `<pushurl>` is used for pushes only. It is optional and defaults
+to `<url>`.
 
 Named file in `$GIT_DIR/remotes`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -49,8 +52,8 @@ following format:
 
 ------------
 
-`Push:` lines are used by 'git-push' and
-`Pull:` lines are used by 'git-pull' and 'git-fetch'.
+`Push:` lines are used by 'git push' and
+`Pull:` lines are used by 'git pull' and 'git fetch'.
 Multiple `Push:` and `Pull:` lines may
 be specified for additional branch mappings.
 
index 5355ebc0f39114823f830e0651078a99f0ac2e70..289019478d16719e5d2c86b7a17017005bd9a80d 100644 (file)
@@ -1,50 +1,57 @@
 GIT URLS[[URLS]]
 ----------------
 
-One of the following notations can be used
-to name the remote repository:
+In general, URLs contain information about the transport protocol, the
+address of the remote server, and the path to the repository.
+Depending on the transport protocol, some of this information may be
+absent.
+
+Git natively supports ssh, git, http, https, ftp, ftps, and rsync
+protocols. The following syntaxes may be used with them:
 
-===============================================================
-- rsync://host.xz/path/to/repo.git/
-- http://host.xz{startsb}:port{endsb}/path/to/repo.git/
-- https://host.xz{startsb}:port{endsb}/path/to/repo.git/
-- git://host.xz{startsb}:port{endsb}/path/to/repo.git/
-- git://host.xz{startsb}:port{endsb}/~user/path/to/repo.git/
 - ssh://{startsb}user@{endsb}host.xz{startsb}:port{endsb}/path/to/repo.git/
-- ssh://{startsb}user@{endsb}host.xz/path/to/repo.git/
-- ssh://{startsb}user@{endsb}host.xz/~user/path/to/repo.git/
-- ssh://{startsb}user@{endsb}host.xz/~/path/to/repo.git
-===============================================================
-
-SSH is the default transport protocol over the network.  You can
-optionally specify which user to log-in as, and an alternate,
-scp-like syntax is also supported.  Both syntaxes support
-username expansion, as does the native git protocol, but
-only the former supports port specification. The following
-three are identical to the last three above, respectively:
-
-===============================================================
-- {startsb}user@{endsb}host.xz:/path/to/repo.git/
-- {startsb}user@{endsb}host.xz:~user/path/to/repo.git/
-- {startsb}user@{endsb}host.xz:path/to/repo.git
-===============================================================
-
-To sync with a local directory, you can use:
-
-===============================================================
+- git://host.xz{startsb}:port{endsb}/path/to/repo.git/
+- http{startsb}s{endsb}://host.xz{startsb}:port{endsb}/path/to/repo.git/
+- ftp{startsb}s{endsb}://host.xz{startsb}:port{endsb}/path/to/repo.git/
+- rsync://host.xz/path/to/repo.git/
+
+An alternative scp-like syntax may also be used with the ssh protocol:
+
+- {startsb}user@{endsb}host.xz:path/to/repo.git/
+
+The ssh and git protocols additionally support ~username expansion:
+
+- ssh://{startsb}user@{endsb}host.xz{startsb}:port{endsb}/~{startsb}user{endsb}/path/to/repo.git/
+- git://host.xz{startsb}:port{endsb}/~{startsb}user{endsb}/path/to/repo.git/
+- {startsb}user@{endsb}host.xz:/~{startsb}user{endsb}/path/to/repo.git/
+
+For local repositories, also supported by git natively, the following
+syntaxes may be used:
+
 - /path/to/repo.git/
 - file:///path/to/repo.git/
-===============================================================
 
 ifndef::git-clone[]
-They are mostly equivalent, except when cloning.  See
-linkgit:git-clone[1] for details.
+These two syntaxes are mostly equivalent, except when cloning, when
+the former implies --local option. See linkgit:git-clone[1] for
+details.
 endif::git-clone[]
 
 ifdef::git-clone[]
-They are equivalent, except the former implies --local option.
+These two syntaxes are mostly equivalent, except the former implies
+--local option.
 endif::git-clone[]
 
+When git doesn't know how to handle a certain transport protocol, it
+attempts to use the 'remote-<transport>' remote helper, if one
+exists. To explicitly request a remote helper, the following syntax
+may be used:
+
+- <transport>::<address>
+
+where <address> may be a path, a server and path, or an arbitrary
+URL-like string recognized by the specific remote helper being
+invoked. See linkgit:git-remote-helpers[1] for details.
 
 If there are a large number of similarly-named remote repositories and
 you want to use a different format for them (such that the URLs you
@@ -67,3 +74,21 @@ For example, with this:
 a URL like "work:repo.git" or like "host.xz:/path/to/repo.git" will be
 rewritten in any context that takes a URL to be "git://git.host.xz/repo.git".
 
+If you want to rewrite URLs for push only, you can create a
+configuration section of the form:
+
+------------
+       [url "<actual url base>"]
+               pushInsteadOf = <other url base>
+------------
+
+For example, with this:
+
+------------
+       [url "ssh://example.org/"]
+               pushInsteadOf = git://example.org/
+------------
+
+a URL like "git://example.org/path/to/repo.git" will be rewritten to
+"ssh://example.org/path/to/repo.git" for pushes, but pulls will still
+use the original URL.
index dbbeb7e7c7856776450bc64321ff5f1de26202bb..fecc4eb5b3b853f4781c769fff8337e8bb6c3500 100644 (file)
@@ -397,7 +397,7 @@ is usually a shortcut for the HEAD branch in the repository "origin".
 For the complete list of paths which git checks for references, and
 the order it uses to decide which to choose when there are multiple
 references with the same shorthand name, see the "SPECIFYING
-REVISIONS" section of linkgit:git-rev-parse[1].
+REVISIONS" section of linkgit:gitrevisions[1].
 
 [[Updating-a-repository-With-git-fetch]]
 Updating a repository with git fetch
@@ -568,7 +568,7 @@ We have seen several ways of naming commits already:
        - HEAD: refers to the head of the current branch
 
 There are many more; see the "SPECIFYING REVISIONS" section of the
-linkgit:git-rev-parse[1] man page for the complete list of ways to
+linkgit:gitrevisions[1] man page for the complete list of ways to
 name revisions.  Some examples:
 
 -------------------------------------------------
@@ -909,7 +909,7 @@ commits reachable from some head but not from any tag in the repository:
 $ gitk $( git show-ref --heads ) --not  $( git show-ref --tags )
 -------------------------------------------------
 
-(See linkgit:git-rev-parse[1] for explanations of commit-selecting
+(See linkgit:gitrevisions[1] for explanations of commit-selecting
 syntax such as `--not`.)
 
 [[making-a-release]]
@@ -1183,7 +1183,23 @@ $ git merge branchname
 -------------------------------------------------
 
 merges the development in the branch "branchname" into the current
-branch.  If there are conflicts--for example, if the same file is
+branch.
+
+A merge is made by combining the changes made in "branchname" and the
+changes made up to the latest commit in your current branch since
+their histories forked. The work tree is overwritten by the result of
+the merge when this combining is done cleanly, or overwritten by a
+half-merged results when this combining results in conflicts.
+Therefore, if you have uncommitted changes touching the same files as
+the ones impacted by the merge, Git will refuse to proceed. Most of
+the time, you will want to commit your changes before you can merge,
+and if you don't, then linkgit:git-stash[1] can take these changes
+away while you're doing the merge, and reapply them afterwards.
+
+If the changes are independent enough, Git will automatically complete
+the merge and commit the result (or reuse an existing commit in case
+of <<fast-forwards,fast-forward>>, see below). On the other hand,
+if there are conflicts--for example, if the same file is
 modified in two different ways in the remote branch and the local
 branch--then you are warned; the output may look something like this:
 
@@ -1384,7 +1400,7 @@ were merged.
 
 However, if the current branch is a descendant of the other--so every
 commit present in the one is already contained in the other--then git
-just performs a "fast forward"; the head of the current branch is moved
+just performs a "fast-forward"; the head of the current branch is moved
 forward to point at the head of the merged-in branch, without any new
 commits being created.
 
@@ -1520,10 +1536,10 @@ $ git commit -a -m "blorpl: typofix"
 ------------------------------------------------
 
 After that, you can go back to what you were working on with
-`git stash apply`:
+`git stash pop`:
 
 ------------------------------------------------
-$ git stash apply
+$ git stash pop
 ------------------------------------------------
 
 
@@ -1619,7 +1635,7 @@ you've checked out.
 The reflogs are kept by default for 30 days, after which they may be
 pruned.  See linkgit:git-reflog[1] and linkgit:git-gc[1] to learn
 how to control this pruning, and see the "SPECIFYING REVISIONS"
-section of linkgit:git-rev-parse[1] for details.
+section of linkgit:gitrevisions[1] for details.
 
 Note that the reflog history is very different from normal git history.
 While normal history is shared by every repository that works on the
@@ -1679,7 +1695,7 @@ Sharing development with others
 Getting updates with git pull
 -----------------------------
 
-After you clone a repository and make a few changes of your own, you
+After you clone a repository and commit a few changes of your own, you
 may wish to check the original repository for updates and merge them
 into your own work.
 
@@ -1719,7 +1735,7 @@ producing a default commit message documenting the branch and
 repository that you pulled from.
 
 (But note that no such commit will be created in the case of a
-<<fast-forwards,fast forward>>; instead, your branch will just be
+<<fast-forwards,fast-forward>>; instead, your branch will just be
 updated to point to the latest commit from the upstream branch.)
 
 The `git pull` command can also be given "." as the "remote" repository,
@@ -1943,7 +1959,7 @@ $ git push ssh://yourserver.com/~you/proj.git master
 -------------------------------------------------
 
 As with `git fetch`, `git push` will complain if this does not result in a
-<<fast-forwards,fast forward>>; see the following section for details on
+<<fast-forwards,fast-forward>>; see the following section for details on
 handling this case.
 
 Note that the target of a "push" is normally a
@@ -1976,7 +1992,7 @@ details.
 What to do when a push fails
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-If a push would not result in a <<fast-forwards,fast forward>> of the
+If a push would not result in a <<fast-forwards,fast-forward>> of the
 remote branch, then it will fail with an error like:
 
 -------------------------------------------------
@@ -2115,7 +2131,7 @@ $ git checkout release && git pull
 
 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
+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
@@ -2569,7 +2585,7 @@ them again with linkgit:git-am[1].
 Other tools
 -----------
 
-There are numerous other tools, such as StGIT, which exist for the
+There are numerous other tools, such as StGit, which exist for the
 purpose of maintaining a patch series.  These are outside of the scope of
 this manual.
 
@@ -2729,9 +2745,9 @@ In the previous example, when updating an existing branch, "git fetch"
 checks to make sure that the most recent commit on the remote
 branch is a descendant of the most recent commit on your copy of the
 branch before updating your copy of the branch to point at the new
-commit.  Git calls this process a <<fast-forwards,fast forward>>.
+commit.  Git calls this process a <<fast-forwards,fast-forward>>.
 
-A fast forward looks something like this:
+A fast-forward looks something like this:
 
 ................................................
  o--o--o--o <-- old head of the branch
@@ -3624,6 +3640,26 @@ Did you forget to 'git add'?
 Unable to checkout '261dfac35cb99d380eb966e102c1197139f7fa24' in submodule path 'a'
 -------------------------------------------------
 
+In older git versions it could be easily forgotten to commit new or modified
+files in a submodule, which silently leads to similar problems as not pushing
+the submodule changes. Starting with git 1.7.0 both "git status" and "git diff"
+in the superproject show submodules as modified when they contain new or
+modified files to protect against accidentally committing such a state. "git
+diff" will also add a "-dirty" to the work tree side when generating patch
+output or used with the --submodule option:
+
+-------------------------------------------------
+$ git diff
+diff --git a/sub b/sub
+--- a/sub
++++ b/sub
+@@ -1 +1 @@
+-Subproject commit 3f356705649b5d566d97ff843cf193359229a453
++Subproject commit 3f356705649b5d566d97ff843cf193359229a453-dirty
+$ git diff --submodule
+Submodule sub 3f35670..3f35670-dirty:
+-------------------------------------------------
+
 You also should not rewind branches in a submodule beyond commits that were
 ever recorded in any superproject.
 
@@ -4131,7 +4167,7 @@ What does this mean?
 
 `git rev-list` is the original version of the revision walker, which
 _always_ printed a list of revisions to stdout.  It is still functional,
-and needs to, since most new Git programs start out as scripts using
+and needs to, since most new Git commands start out as scripts using
 `git rev-list`.
 
 `git rev-parse` is not as important any more; it was only used to filter out
@@ -4215,9 +4251,9 @@ Two things are interesting here:
   negative numbers in case of different errors--and 0 on success.
 
 - the variable `sha1` in the function signature of `get_sha1()` is `unsigned
-  char \*`, but is actually expected to be a pointer to `unsigned
+  char {asterisk}`, but is actually expected to be a pointer to `unsigned
   char[20]`.  This variable will contain the 160-bit SHA-1 of the given
-  commit.  Note that whenever a SHA-1 is passed as `unsigned char \*`, it
+  commit.  Note that whenever a SHA-1 is passed as `unsigned char {asterisk}`, it
   is the binary representation, as opposed to the ASCII representation in
   hex characters, which is passed as `char *`.
 
@@ -4275,7 +4311,7 @@ You see, Git is actually the best tool to find out about the source of Git
 itself!
 
 [[glossary]]
-GIT Glossary
+Git Glossary
 ============
 
 include::glossary-content.txt[]
index 39cde784c947fae6a2f294caac1898b9199d769d..556cee1ca7e6c95fb27137af045464b94ebaaeb2 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.6.3.GIT
+DEF_VER=v1.7.3-rc2
 
 LF='
 '
@@ -12,7 +12,7 @@ if test -f version
 then
        VN=$(cat version) || VN="$DEF_VER"
 elif test -d .git -o -f .git &&
-       VN=$(git describe --abbrev=4 HEAD 2>/dev/null) &&
+       VN=$(git describe --match "v[0-9]*" --abbrev=4 HEAD 2>/dev/null) &&
        case "$VN" in
        *$LF*) (exit 1) ;;
        v[0-9]*)
diff --git a/INSTALL b/INSTALL
index ae7f7508f8e8cffeb930c820e068ba70dabff7bd..59200b730ec00a63f981691f7fd37f2eacb11653 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -13,6 +13,10 @@ 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.
 
+The beginning of the Makefile documents many variables that affect the way
+git is built.  You can override them either from the command line, or in a
+config.mak file.
+
 Alternatively you can use autoconf generated ./configure script to
 set up install paths (via config.mak.autogen), so you can write instead
 
@@ -34,13 +38,17 @@ Issues of note:
    Interactive Tools package still can install "git", but you can build it
    with --disable-transition option to avoid this.
 
- - You can use git after building but without installing if you
-   wanted to.  Various git commands need to find other git
-   commands and scripts to do their work, so you would need to
-   arrange a few environment variables to tell them that their
-   friends will be found in your built source area instead of at
-   their standard installation area.  Something like this works
-   for me:
+ - You can use git after building but without installing if you want
+   to test drive it.  Simply run git found in bin-wrappers directory
+   in the build directory, or prepend that directory to your $PATH.
+   This however is less efficient than running an installed git, as
+   you always need an extra fork+exec to run any git subcommand.
+
+   It is still possible to use git without installing by setting a few
+   environment variables, which was the way this was done
+   traditionally.  But using git found in bin-wrappers directory in
+   the build directory is far simpler.  As a historical reference, the
+   old way went like this:
 
        GIT_EXEC_PATH=`pwd`
        PATH=`pwd`:$PATH
@@ -48,32 +56,42 @@ Issues of note:
        export GIT_EXEC_PATH PATH GITPERLLIB
 
  - Git is reasonably self-sufficient, but does depend on a few external
-   programs and libraries:
+   programs and libraries.  Git can be used without most of them by adding
+   the approriate "NO_<LIBRARY>=YesPlease" to the make command line or
+   config.mak file.
 
        - "zlib", the compression library. Git won't build without it.
 
-       - "openssl".  Unless you specify otherwise, you'll get the SHA1
-         library from here.
+       - "ssh" is used to push and pull over the net.
 
-         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).
+       - A POSIX-compliant shell is required to run many scripts needed
+         for everyday use (e.g. "bisect", "pull").
 
-       - libcurl library; git-http-fetch and git-fetch use them.  You
-         might also want the "curl" executable for debugging purposes.
-         If you do not use http transfer, you are probably OK if you
-         do not have them.
+       - "Perl" is needed to use some of the features (e.g. preparing a
+         partial commit using "git add -i/-p", interacting with svn
+         repositories with "git svn").  If you can live without these, use
+         NO_PERL.
 
-       - expat library; git-http-push uses it for remote lock
-         management over DAV.  Similar to "curl" above, this is optional.
+       - "openssl" library is used by git-imap-send to use IMAP over SSL.
+         If you don't need it, use NO_OPENSSL.
 
-        - "wish", the Tcl/Tk windowing shell is used in gitk to show the
-          history graphically, and in git-gui.
+         By default, git uses OpenSSL for SHA1 but it will use it's own
+         library (inspired by Mozilla's) with either NO_OPENSSL or
+         BLK_SHA1.  Also included is a version optimized for PowerPC
+         (PPC_SHA1).
 
-       - "ssh" is used to push and pull over the net
+       - "libcurl" library is used by git-http-fetch and git-fetch.  You
+         might also want the "curl" executable for debugging purposes.
+         If you do not use http:// or https:// repositories, you do not
+         have to have them (use NO_CURL).
+
+       - "expat" library; git-http-push uses it for remote lock
+         management over DAV.  Similar to "curl" above, this is optional
+         (with NO_EXPAT).
 
-       - "perl" and POSIX-compliant shells are needed to use most of
-         the bare-bones Porcelainish scripts.
+       - "wish", the Tcl/Tk windowing shell is used in gitk to show the
+         history graphically, and in git-gui.  If you don't want gitk or
+         git-gui, you can use NO_TCLTK.
 
  - Some platform specific issues are dealt with Makefile rules,
    but depending on your specific installation, you may not
@@ -139,3 +157,36 @@ Issues of note:
    It has been reported that docbook-xsl version 1.72 and 1.73 are
    buggy; 1.72 misformats manual pages for callouts, and 1.73 needs
    the patch in contrib/patches/docbook-xsl-manpages-charmap.patch
+
+   Users attempting to build the documentation on Cygwin may need to ensure
+   that the /etc/xml/catalog file looks something like this:
+
+   <?xml version="1.0"?>
+   <!DOCTYPE catalog PUBLIC
+      "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN"
+      "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd"
+   >
+   <catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
+     <rewriteURI
+       uriStartString = "http://docbook.sourceforge.net/release/xsl/current"
+       rewritePrefix = "/usr/share/sgml/docbook/xsl-stylesheets"
+     />
+     <rewriteURI
+       uriStartString="http://www.oasis-open.org/docbook/xml/4.5"
+       rewritePrefix="/usr/share/sgml/docbook/xml-dtd-4.5"
+     />
+  </catalog>
+
+  This can be achieved with the following two xmlcatalog commands:
+
+  xmlcatalog --noout \
+     --add rewriteURI \
+        http://docbook.sourceforge.net/release/xsl/current \
+        /usr/share/sgml/docbook/xsl-stylesheets \
+     /etc/xml/catalog
+
+  xmlcatalog --noout \
+     --add rewriteURI \
+         http://www.oasis-open.org/docbook/xml/4.5/xsl/current \
+         /usr/share/sgml/docbook/xml-dtd-4.5 \
+     /etc/xml/catalog
index 6e216436c36f4dcbe1121d02a291e9f568ca6071..b7a62cfdf2ca4f05e47d69332976adabb73583e9 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -3,6 +3,17 @@ all::
 
 # Define V=1 to have a more verbose compile.
 #
+# Define SHELL_PATH to a POSIX shell if your /bin/sh is broken.
+#
+# Define SANE_TOOL_PATH to a colon-separated list of paths to prepend
+# to PATH if your tools in /usr/bin are broken.
+#
+# Define SOCKLEN_T to a suitable type (such as 'size_t') if your
+# system headers do not define a socklen_t type.
+#
+# Define INLINE to a suitable substitute (such as '__inline' or '') if git
+# fails to compile with errors about undefined inline functions or similar.
+#
 # Define SNPRINTF_RETURNS_BOGUS if your are on a system which snprintf()
 # or vsnprintf() return -1 instead of number of characters which would
 # have been written to the final string if enough space had been available.
@@ -11,7 +22,7 @@ all::
 # when attempting to read from an fopen'ed directory.
 #
 # Define NO_OPENSSL environment variable if you do not have OpenSSL.
-# This also implies MOZILLA_SHA1.
+# This also implies BLK_SHA1.
 #
 # Define NO_CURL if you do not have libcurl installed.  git-http-pull and
 # git-http-push are not built, and you cannot use http:// and https://
@@ -26,10 +37,13 @@ all::
 # Define EXPATDIR=/foo/bar if your expat header and library files are in
 # /foo/bar/include and /foo/bar/lib directories.
 #
+# Define HAVE_PATHS_H if you have paths.h and want to use the default PATH
+# it specifies.
+#
 # 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).
+# d_type in struct dirent (Cygwin 1.5, fixed in Cygwin 1.7).
 #
 # Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.)
 # do not support the 'size specifiers' introduced by C99, namely ll, hh,
@@ -52,6 +66,14 @@ all::
 #
 # Define NO_MKDTEMP if you don't have mkdtemp in the C library.
 #
+# Define NO_MKSTEMPS if you don't have mkstemps in the C library.
+#
+# Define NO_STRTOK_R if you don't have strtok_r in the C library.
+#
+# Define NO_LIBGEN_H if you don't have libgen.h.
+#
+# Define NEEDS_LIBGEN if your libgen needs -lgen when linking
+#
 # Define NO_SYS_SELECT_H if you don't have sys/select.h.
 #
 # Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link.
@@ -73,30 +95,32 @@ all::
 # specify your own (or DarwinPort's) include directories and
 # library directories by defining CFLAGS and LDFLAGS appropriately.
 #
+# Define BLK_SHA1 environment variable if you want the C version
+# of the SHA1 that assumes you can do unaligned 32-bit loads and
+# have a fast htonl() function.
+#
 # 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_CRYPTO_WITH_SSL if you need -lcrypto when using -lssl (Darwin).
 #
-# 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 NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin).
+# Define NEEDS_SSL_WITH_CRYPTO if you need -lssl when using -lcrypto (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 NEEDS_RESOLV if linking with -lnsl and/or -lsocket is not enough.
+# Notably on Solaris hstrerror resides in libresolv and on Solaris 7
+# inet_ntop and inet_pton additionally reside there.
+#
 # Define NO_MMAP if you want to avoid mmap.
 #
 # Define NO_PTHREADS if you do not have or do not want to use Pthreads.
 #
 # Define NO_PREAD if you have a problem with pread() system call (e.g.
-# cygwin.dll before v1.5.22).
+# cygwin1.dll before v1.5.22).
 #
 # Define NO_FAST_WORKING_DIRECTORY if accessing objects in pack files is
 # generally faster on your platform than accessing the working directory.
@@ -140,13 +164,23 @@ all::
 #
 # Define ASCIIDOC8 if you want to format documentation with AsciiDoc 8
 #
-# Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72.
+# Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72
+# (not v1.73 or v1.71).
+#
+# Define ASCIIDOC_NO_ROFF if your DocBook XSL escapes raw roff directives
+# (versions 1.72 and later and 1.68.1 and earlier).
+#
+# Define GNU_ROFF if your target system uses GNU groff.  This forces
+# apostrophes to be ASCII so that cut&pasting examples to the shell
+# will work.
 #
 # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's
 # MakeMaker (e.g. using ActiveState under Cygwin).
 #
 # Define NO_PERL if you do not want Perl scripts or libraries at all.
 #
+# Define NO_PYTHON if you do not want Python scripts or libraries at all.
+#
 # Define NO_TCLTK if you do not want Tcl/Tk GUI.
 #
 # The TCL_PATH variable governs the location of the Tcl interpreter
@@ -157,17 +191,10 @@ all::
 # If not set it defaults to the bare 'wish'. If it is set to the empty
 # string then NO_TCLTK will be forced (this is used by configure script).
 #
-# Define THREADED_DELTA_SEARCH if you have pthreads and wish to exploit
-# parallel delta searching when packing objects.
-#
 # Define INTERNAL_QSORT to use Git's implementation of qsort(), which
 # is a simplified version of the merge sort used in glibc. This is
 # recommended if Git triggers O(n^2) behavior in your platform's qsort().
 #
-# Define NO_EXTERNAL_GREP if you don't want "git grep" to ever call
-# your external grep (e.g., if your system lacks grep, if its grep is
-# broken, or spawning external process is slower than built-in grep git has).
-#
 # Define UNRELIABLE_FSTAT if your system's fstat does not return the same
 # information on a not yet closed file that lstat would return for the same
 # file after it was closed.
@@ -175,8 +202,43 @@ all::
 # Define OBJECT_CREATION_USES_RENAMES if your operating systems has problems
 # when hardlinking a file to another name and unlinking the original file right
 # away (some NTFS drivers seem to zero the contents in that scenario).
+#
+# Define NO_CROSS_DIRECTORY_HARDLINKS if you plan to distribute the installed
+# programs as a tar, where bin/ and libexec/ might be on different file systems.
+#
+# Define USE_NED_ALLOCATOR if you want to replace the platforms default
+# memory allocators with the nedmalloc allocator written by Niall Douglas.
+#
+# Define NO_REGEX if you have no or inferior regex support in your C library.
+#
+# Define JSMIN to point to JavaScript minifier that functions as
+# a filter to have gitweb.js minified.
+#
+# Define CSSMIN to point to a CSS minifier in order to generate a minified
+# version of gitweb.css
+#
+# Define DEFAULT_PAGER to a sensible pager command (defaults to "less") if
+# you want to use something different.  The value will be interpreted by the
+# shell at runtime when it is used.
+#
+# Define DEFAULT_EDITOR to a sensible editor command (defaults to "vi") if you
+# want to use something different.  The value will be interpreted by the shell
+# if necessary when it is used.  Examples:
+#
+#   DEFAULT_EDITOR='~/bin/vi',
+#   DEFAULT_EDITOR='$GIT_FALLBACK_EDITOR',
+#   DEFAULT_EDITOR='"C:\Program Files\Vim\gvim.exe" --nofork'
+#
+# Define COMPUTE_HEADER_DEPENDENCIES if your compiler supports the -MMD option
+# and you want to avoid rebuilding objects when an unrelated header file
+# changes.
+#
+# Define CHECK_HEADER_DEPENDENCIES to check for problems in the hard-coded
+# dependency rules.
+#
+# Define NATIVE_CRLF if your platform uses CRLF for line endings.
 
-GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
+GIT-VERSION-FILE: FORCE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
 -include GIT-VERSION-FILE
 
@@ -187,11 +249,17 @@ uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
 uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not')
 uname_V := $(shell sh -c 'uname -v 2>/dev/null || echo not')
 
+ifdef MSVC
+       # avoid the MingW and Cygwin configuration sections
+       uname_S := Windows
+       uname_O := Windows
+endif
+
 # CFLAGS and LDFLAGS are for the users to override from the command line.
 
 CFLAGS = -g -O2 -Wall
 LDFLAGS =
-ALL_CFLAGS = $(CFLAGS)
+ALL_CFLAGS = $(CPPFLAGS) $(CFLAGS)
 ALL_LDFLAGS = $(LDFLAGS)
 STRIP ?= strip
 
@@ -214,6 +282,7 @@ mandir = share/man
 infodir = share/info
 gitexecdir = libexec/git-core
 sharedir = $(prefix)/share
+gitwebdir = $(sharedir)/gitweb
 template_dir = share/git-core/templates
 htmldir = share/doc/git-doc
 ifeq ($(prefix),/usr)
@@ -225,30 +294,14 @@ ETC_GITCONFIG = etc/gitconfig
 endif
 lib = lib
 # DESTDIR=
+pathsep = :
 
-# default configuration for gitweb
-GITWEB_CONFIG = gitweb_config.perl
-GITWEB_CONFIG_SYSTEM = /etc/gitweb.conf
-GITWEB_HOME_LINK_STR = projects
-GITWEB_SITENAME =
-GITWEB_PROJECTROOT = /pub/git
-GITWEB_PROJECT_MAXDEPTH = 2007
-GITWEB_EXPORT_OK =
-GITWEB_STRICT_EXPORT =
-GITWEB_BASE_URL =
-GITWEB_LIST =
-GITWEB_HOMETEXT = indextext.html
-GITWEB_CSS = gitweb.css
-GITWEB_LOGO = git-logo.png
-GITWEB_FAVICON = git-favicon.png
-GITWEB_SITE_HEADER =
-GITWEB_SITE_FOOTER =
-
-export prefix bindir sharedir sysconfdir
+export prefix bindir sharedir sysconfdir gitwebdir
 
 CC = gcc
 AR = ar
 RM = rm -f
+DIFF = diff
 TAR = tar
 FIND = find
 INSTALL = install
@@ -256,6 +309,8 @@ RPMBUILD = rpmbuild
 TCL_PATH = tclsh
 TCLTK_PATH = wish
 PTHREAD_LIBS = -lpthread
+PTHREAD_CFLAGS =
+GCOV = gcov
 
 export TCL_PATH TCLTK_PATH
 
@@ -270,7 +325,7 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__
 # Those must not be GNU-specific; they are shared with perl/ which may
 # be built by a different compiler. (Note that this is an artifact now
 # but it still might be nice to keep that distinction.)
-BASIC_CFLAGS =
+BASIC_CFLAGS = -I.
 BASIC_LDFLAGS =
 
 # Guard against environment variables
@@ -278,12 +333,22 @@ BUILTIN_OBJS =
 BUILT_INS =
 COMPAT_CFLAGS =
 COMPAT_OBJS =
+EXTRA_CPPFLAGS =
 LIB_H =
 LIB_OBJS =
+PROGRAM_OBJS =
 PROGRAMS =
 SCRIPT_PERL =
+SCRIPT_PYTHON =
 SCRIPT_SH =
-TEST_PROGRAMS =
+SCRIPT_LIB =
+TEST_PROGRAMS_NEED_X =
+
+# Having this variable in your environment would break pipelines because
+# you cause "cd" to echo its destination to stdout.  It can also take
+# scripts to unexpected places.  If you like CDPATH, define it for your
+# interactive shell sessions without exporting it.
+unexport CDPATH
 
 SCRIPT_SH += git-am.sh
 SCRIPT_SH += git-bisect.sh
@@ -294,19 +359,20 @@ SCRIPT_SH += git-merge-octopus.sh
 SCRIPT_SH += git-merge-one-file.sh
 SCRIPT_SH += git-merge-resolve.sh
 SCRIPT_SH += git-mergetool.sh
-SCRIPT_SH += git-mergetool--lib.sh
-SCRIPT_SH += git-parse-remote.sh
 SCRIPT_SH += git-pull.sh
 SCRIPT_SH += git-quiltimport.sh
 SCRIPT_SH += git-rebase--interactive.sh
 SCRIPT_SH += git-rebase.sh
 SCRIPT_SH += git-repack.sh
 SCRIPT_SH += git-request-pull.sh
-SCRIPT_SH += git-sh-setup.sh
 SCRIPT_SH += git-stash.sh
 SCRIPT_SH += git-submodule.sh
 SCRIPT_SH += git-web--browse.sh
 
+SCRIPT_LIB += git-mergetool--lib
+SCRIPT_LIB += git-parse-remote
+SCRIPT_LIB += git-sh-setup
+
 SCRIPT_PERL += git-add--interactive.perl
 SCRIPT_PERL += git-difftool.perl
 SCRIPT_PERL += git-archimport.perl
@@ -317,8 +383,11 @@ SCRIPT_PERL += git-relink.perl
 SCRIPT_PERL += git-send-email.perl
 SCRIPT_PERL += git-svn.perl
 
+SCRIPT_PYTHON += git-remote-testgit.py
+
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
          $(patsubst %.perl,%,$(SCRIPT_PERL)) \
+         $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
          git-instaweb
 
 # Empty...
@@ -326,25 +395,40 @@ EXTRA_PROGRAMS =
 
 # ... and all the rest that could be moved out of bindir to gitexecdir
 PROGRAMS += $(EXTRA_PROGRAMS)
-PROGRAMS += git-fast-import$X
-PROGRAMS += git-hash-object$X
-PROGRAMS += git-index-pack$X
-PROGRAMS += git-merge-index$X
-PROGRAMS += git-merge-tree$X
-PROGRAMS += git-mktag$X
-PROGRAMS += git-mktree$X
-PROGRAMS += git-pack-redundant$X
-PROGRAMS += git-patch-id$X
-PROGRAMS += git-shell$X
-PROGRAMS += git-show-index$X
-PROGRAMS += git-unpack-file$X
-PROGRAMS += git-update-server-info$X
-PROGRAMS += git-upload-pack$X
-PROGRAMS += git-var$X
+
+PROGRAM_OBJS += fast-import.o
+PROGRAM_OBJS += imap-send.o
+PROGRAM_OBJS += shell.o
+PROGRAM_OBJS += show-index.o
+PROGRAM_OBJS += upload-pack.o
+PROGRAM_OBJS += http-backend.o
+
+PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))
+
+TEST_PROGRAMS_NEED_X += test-chmtime
+TEST_PROGRAMS_NEED_X += test-ctype
+TEST_PROGRAMS_NEED_X += test-date
+TEST_PROGRAMS_NEED_X += test-delta
+TEST_PROGRAMS_NEED_X += test-dump-cache-tree
+TEST_PROGRAMS_NEED_X += test-genrandom
+TEST_PROGRAMS_NEED_X += test-line-buffer
+TEST_PROGRAMS_NEED_X += test-match-trees
+TEST_PROGRAMS_NEED_X += test-obj-pool
+TEST_PROGRAMS_NEED_X += test-parse-options
+TEST_PROGRAMS_NEED_X += test-path-utils
+TEST_PROGRAMS_NEED_X += test-run-command
+TEST_PROGRAMS_NEED_X += test-sha1
+TEST_PROGRAMS_NEED_X += test-sigchain
+TEST_PROGRAMS_NEED_X += test-string-pool
+TEST_PROGRAMS_NEED_X += test-svn-fe
+TEST_PROGRAMS_NEED_X += test-treap
+TEST_PROGRAMS_NEED_X += test-index-version
+
+TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))
 
 # List built-in command $C whose implementation cmd_$C() is not in
-# builtin-$C.o but is linked in as part of some other command.
-BUILT_INS += $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
+# builtin/$C.o but is linked in as part of some other command.
+BUILT_INS += $(patsubst builtin/%.o,git-%$X,$(BUILTIN_OBJS))
 
 BUILT_INS += git-cherry$X
 BUILT_INS += git-cherry-pick$X
@@ -360,12 +444,22 @@ BUILT_INS += git-stage$X
 BUILT_INS += git-status$X
 BUILT_INS += git-whatchanged$X
 
-# what 'all' will build and 'install' will install, in gitexecdir
+# what 'all' will build and 'install' will install in gitexecdir,
+# excluding programs for built-in commands
 ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS)
 
 # what 'all' will build but not install in gitexecdir
 OTHER_PROGRAMS = git$X
 
+# what test wrappers are needed and 'install' will install, in bindir
+BINDIR_PROGRAMS_NEED_X += git
+BINDIR_PROGRAMS_NEED_X += git-upload-pack
+BINDIR_PROGRAMS_NEED_X += git-receive-pack
+BINDIR_PROGRAMS_NEED_X += git-upload-archive
+BINDIR_PROGRAMS_NEED_X += git-shell
+
+BINDIR_PROGRAMS_NO_X += git-cvsserver
+
 # Set paths to tools early so that they can be used for version tests.
 ifndef SHELL_PATH
        SHELL_PATH = /bin/sh
@@ -373,27 +467,37 @@ endif
 ifndef PERL_PATH
        PERL_PATH = /usr/bin/perl
 endif
+ifndef PYTHON_PATH
+       PYTHON_PATH = /usr/bin/python
+endif
 
 export PERL_PATH
+export PYTHON_PATH
 
 LIB_FILE=libgit.a
 XDIFF_LIB=xdiff/lib.a
+VCSSVN_LIB=vcs-svn/lib.a
 
+LIB_H += advice.h
 LIB_H += archive.h
 LIB_H += attr.h
 LIB_H += blob.h
 LIB_H += builtin.h
 LIB_H += cache.h
 LIB_H += cache-tree.h
+LIB_H += color.h
 LIB_H += commit.h
+LIB_H += compat/bswap.h
 LIB_H += compat/cygwin.h
 LIB_H += compat/mingw.h
+LIB_H += compat/win32/pthread.h
 LIB_H += csum-file.h
 LIB_H += decorate.h
 LIB_H += delta.h
 LIB_H += diffcore.h
 LIB_H += diff.h
 LIB_H += dir.h
+LIB_H += exec_cmd.h
 LIB_H += fsck.h
 LIB_H += git-compat-util.h
 LIB_H += graph.h
@@ -406,6 +510,8 @@ LIB_H += ll-merge.h
 LIB_H += log-tree.h
 LIB_H += mailmap.h
 LIB_H += merge-recursive.h
+LIB_H += notes.h
+LIB_H += notes-cache.h
 LIB_H += object.h
 LIB_H += pack.h
 LIB_H += pack-refs.h
@@ -419,6 +525,7 @@ LIB_H += reflog-walk.h
 LIB_H += refs.h
 LIB_H += remote.h
 LIB_H += rerere.h
+LIB_H += resolve-undo.h
 LIB_H += revision.h
 LIB_H += run-command.h
 LIB_H += sha1-lookup.h
@@ -426,6 +533,7 @@ LIB_H += sideband.h
 LIB_H += sigchain.h
 LIB_H += strbuf.h
 LIB_H += string-list.h
+LIB_H += submodule.h
 LIB_H += tag.h
 LIB_H += transport.h
 LIB_H += tree.h
@@ -433,9 +541,11 @@ LIB_H += tree-walk.h
 LIB_H += unpack-trees.h
 LIB_H += userdiff.h
 LIB_H += utf8.h
-LIB_H += wt-status.h
+LIB_H += xdiff-interface.h
+LIB_H += xdiff/xdiff.h
 
 LIB_OBJS += abspath.o
+LIB_OBJS += advice.o
 LIB_OBJS += alias.o
 LIB_OBJS += alloc.o
 LIB_OBJS += archive.o
@@ -478,6 +588,7 @@ LIB_OBJS += graph.o
 LIB_OBJS += grep.o
 LIB_OBJS += hash.o
 LIB_OBJS += help.o
+LIB_OBJS += hex.o
 LIB_OBJS += ident.o
 LIB_OBJS += levenshtein.o
 LIB_OBJS += list-objects.o
@@ -489,6 +600,8 @@ LIB_OBJS += match-trees.o
 LIB_OBJS += merge-file.o
 LIB_OBJS += merge-recursive.o
 LIB_OBJS += name-hash.o
+LIB_OBJS += notes.o
+LIB_OBJS += notes-cache.o
 LIB_OBJS += object.o
 LIB_OBJS += pack-check.o
 LIB_OBJS += pack-refs.o
@@ -509,7 +622,9 @@ LIB_OBJS += read-cache.o
 LIB_OBJS += reflog-walk.o
 LIB_OBJS += refs.o
 LIB_OBJS += remote.o
+LIB_OBJS += replace_object.o
 LIB_OBJS += rerere.o
+LIB_OBJS += resolve-undo.o
 LIB_OBJS += revision.o
 LIB_OBJS += run-command.o
 LIB_OBJS += server-info.o
@@ -522,14 +637,17 @@ LIB_OBJS += sideband.o
 LIB_OBJS += sigchain.o
 LIB_OBJS += strbuf.o
 LIB_OBJS += string-list.o
+LIB_OBJS += submodule.o
 LIB_OBJS += symlinks.o
 LIB_OBJS += tag.o
 LIB_OBJS += trace.o
 LIB_OBJS += transport.o
+LIB_OBJS += transport-helper.o
 LIB_OBJS += tree-diff.o
 LIB_OBJS += tree.o
 LIB_OBJS += tree-walk.o
 LIB_OBJS += unpack-trees.o
+LIB_OBJS += url.o
 LIB_OBJS += usage.o
 LIB_OBJS += userdiff.o
 LIB_OBJS += utf8.o
@@ -540,84 +658,96 @@ LIB_OBJS += ws.o
 LIB_OBJS += wt-status.o
 LIB_OBJS += xdiff-interface.o
 
-BUILTIN_OBJS += builtin-add.o
-BUILTIN_OBJS += builtin-annotate.o
-BUILTIN_OBJS += builtin-apply.o
-BUILTIN_OBJS += builtin-archive.o
-BUILTIN_OBJS += builtin-bisect--helper.o
-BUILTIN_OBJS += builtin-blame.o
-BUILTIN_OBJS += builtin-branch.o
-BUILTIN_OBJS += builtin-bundle.o
-BUILTIN_OBJS += builtin-cat-file.o
-BUILTIN_OBJS += builtin-check-attr.o
-BUILTIN_OBJS += builtin-check-ref-format.o
-BUILTIN_OBJS += builtin-checkout-index.o
-BUILTIN_OBJS += builtin-checkout.o
-BUILTIN_OBJS += builtin-clean.o
-BUILTIN_OBJS += builtin-clone.o
-BUILTIN_OBJS += builtin-commit-tree.o
-BUILTIN_OBJS += builtin-commit.o
-BUILTIN_OBJS += builtin-config.o
-BUILTIN_OBJS += builtin-count-objects.o
-BUILTIN_OBJS += builtin-describe.o
-BUILTIN_OBJS += builtin-diff-files.o
-BUILTIN_OBJS += builtin-diff-index.o
-BUILTIN_OBJS += builtin-diff-tree.o
-BUILTIN_OBJS += builtin-diff.o
-BUILTIN_OBJS += builtin-fast-export.o
-BUILTIN_OBJS += builtin-fetch--tool.o
-BUILTIN_OBJS += builtin-fetch-pack.o
-BUILTIN_OBJS += builtin-fetch.o
-BUILTIN_OBJS += builtin-fmt-merge-msg.o
-BUILTIN_OBJS += builtin-for-each-ref.o
-BUILTIN_OBJS += builtin-fsck.o
-BUILTIN_OBJS += builtin-gc.o
-BUILTIN_OBJS += builtin-grep.o
-BUILTIN_OBJS += builtin-help.o
-BUILTIN_OBJS += builtin-init-db.o
-BUILTIN_OBJS += builtin-log.o
-BUILTIN_OBJS += builtin-ls-files.o
-BUILTIN_OBJS += builtin-ls-remote.o
-BUILTIN_OBJS += builtin-ls-tree.o
-BUILTIN_OBJS += builtin-mailinfo.o
-BUILTIN_OBJS += builtin-mailsplit.o
-BUILTIN_OBJS += builtin-merge.o
-BUILTIN_OBJS += builtin-merge-base.o
-BUILTIN_OBJS += builtin-merge-file.o
-BUILTIN_OBJS += builtin-merge-ours.o
-BUILTIN_OBJS += builtin-merge-recursive.o
-BUILTIN_OBJS += builtin-mv.o
-BUILTIN_OBJS += builtin-name-rev.o
-BUILTIN_OBJS += builtin-pack-objects.o
-BUILTIN_OBJS += builtin-pack-refs.o
-BUILTIN_OBJS += builtin-prune-packed.o
-BUILTIN_OBJS += builtin-prune.o
-BUILTIN_OBJS += builtin-push.o
-BUILTIN_OBJS += builtin-read-tree.o
-BUILTIN_OBJS += builtin-receive-pack.o
-BUILTIN_OBJS += builtin-reflog.o
-BUILTIN_OBJS += builtin-remote.o
-BUILTIN_OBJS += builtin-rerere.o
-BUILTIN_OBJS += builtin-reset.o
-BUILTIN_OBJS += builtin-rev-list.o
-BUILTIN_OBJS += builtin-rev-parse.o
-BUILTIN_OBJS += builtin-revert.o
-BUILTIN_OBJS += builtin-rm.o
-BUILTIN_OBJS += builtin-send-pack.o
-BUILTIN_OBJS += builtin-shortlog.o
-BUILTIN_OBJS += builtin-show-branch.o
-BUILTIN_OBJS += builtin-show-ref.o
-BUILTIN_OBJS += builtin-stripspace.o
-BUILTIN_OBJS += builtin-symbolic-ref.o
-BUILTIN_OBJS += builtin-tag.o
-BUILTIN_OBJS += builtin-tar-tree.o
-BUILTIN_OBJS += builtin-unpack-objects.o
-BUILTIN_OBJS += builtin-update-index.o
-BUILTIN_OBJS += builtin-update-ref.o
-BUILTIN_OBJS += builtin-upload-archive.o
-BUILTIN_OBJS += builtin-verify-pack.o
-BUILTIN_OBJS += builtin-verify-tag.o
-BUILTIN_OBJS += builtin-write-tree.o
+BUILTIN_OBJS += builtin/add.o
+BUILTIN_OBJS += builtin/annotate.o
+BUILTIN_OBJS += builtin/apply.o
+BUILTIN_OBJS += builtin/archive.o
+BUILTIN_OBJS += builtin/bisect--helper.o
+BUILTIN_OBJS += builtin/blame.o
+BUILTIN_OBJS += builtin/branch.o
+BUILTIN_OBJS += builtin/bundle.o
+BUILTIN_OBJS += builtin/cat-file.o
+BUILTIN_OBJS += builtin/check-attr.o
+BUILTIN_OBJS += builtin/check-ref-format.o
+BUILTIN_OBJS += builtin/checkout-index.o
+BUILTIN_OBJS += builtin/checkout.o
+BUILTIN_OBJS += builtin/clean.o
+BUILTIN_OBJS += builtin/clone.o
+BUILTIN_OBJS += builtin/commit-tree.o
+BUILTIN_OBJS += builtin/commit.o
+BUILTIN_OBJS += builtin/config.o
+BUILTIN_OBJS += builtin/count-objects.o
+BUILTIN_OBJS += builtin/describe.o
+BUILTIN_OBJS += builtin/diff-files.o
+BUILTIN_OBJS += builtin/diff-index.o
+BUILTIN_OBJS += builtin/diff-tree.o
+BUILTIN_OBJS += builtin/diff.o
+BUILTIN_OBJS += builtin/fast-export.o
+BUILTIN_OBJS += builtin/fetch-pack.o
+BUILTIN_OBJS += builtin/fetch.o
+BUILTIN_OBJS += builtin/fmt-merge-msg.o
+BUILTIN_OBJS += builtin/for-each-ref.o
+BUILTIN_OBJS += builtin/fsck.o
+BUILTIN_OBJS += builtin/gc.o
+BUILTIN_OBJS += builtin/grep.o
+BUILTIN_OBJS += builtin/hash-object.o
+BUILTIN_OBJS += builtin/help.o
+BUILTIN_OBJS += builtin/index-pack.o
+BUILTIN_OBJS += builtin/init-db.o
+BUILTIN_OBJS += builtin/log.o
+BUILTIN_OBJS += builtin/ls-files.o
+BUILTIN_OBJS += builtin/ls-remote.o
+BUILTIN_OBJS += builtin/ls-tree.o
+BUILTIN_OBJS += builtin/mailinfo.o
+BUILTIN_OBJS += builtin/mailsplit.o
+BUILTIN_OBJS += builtin/merge.o
+BUILTIN_OBJS += builtin/merge-base.o
+BUILTIN_OBJS += builtin/merge-file.o
+BUILTIN_OBJS += builtin/merge-index.o
+BUILTIN_OBJS += builtin/merge-ours.o
+BUILTIN_OBJS += builtin/merge-recursive.o
+BUILTIN_OBJS += builtin/merge-tree.o
+BUILTIN_OBJS += builtin/mktag.o
+BUILTIN_OBJS += builtin/mktree.o
+BUILTIN_OBJS += builtin/mv.o
+BUILTIN_OBJS += builtin/name-rev.o
+BUILTIN_OBJS += builtin/notes.o
+BUILTIN_OBJS += builtin/pack-objects.o
+BUILTIN_OBJS += builtin/pack-redundant.o
+BUILTIN_OBJS += builtin/pack-refs.o
+BUILTIN_OBJS += builtin/patch-id.o
+BUILTIN_OBJS += builtin/prune-packed.o
+BUILTIN_OBJS += builtin/prune.o
+BUILTIN_OBJS += builtin/push.o
+BUILTIN_OBJS += builtin/read-tree.o
+BUILTIN_OBJS += builtin/receive-pack.o
+BUILTIN_OBJS += builtin/reflog.o
+BUILTIN_OBJS += builtin/remote.o
+BUILTIN_OBJS += builtin/replace.o
+BUILTIN_OBJS += builtin/rerere.o
+BUILTIN_OBJS += builtin/reset.o
+BUILTIN_OBJS += builtin/rev-list.o
+BUILTIN_OBJS += builtin/rev-parse.o
+BUILTIN_OBJS += builtin/revert.o
+BUILTIN_OBJS += builtin/rm.o
+BUILTIN_OBJS += builtin/send-pack.o
+BUILTIN_OBJS += builtin/shortlog.o
+BUILTIN_OBJS += builtin/show-branch.o
+BUILTIN_OBJS += builtin/show-ref.o
+BUILTIN_OBJS += builtin/stripspace.o
+BUILTIN_OBJS += builtin/symbolic-ref.o
+BUILTIN_OBJS += builtin/tag.o
+BUILTIN_OBJS += builtin/tar-tree.o
+BUILTIN_OBJS += builtin/unpack-file.o
+BUILTIN_OBJS += builtin/unpack-objects.o
+BUILTIN_OBJS += builtin/update-index.o
+BUILTIN_OBJS += builtin/update-ref.o
+BUILTIN_OBJS += builtin/update-server-info.o
+BUILTIN_OBJS += builtin/upload-archive.o
+BUILTIN_OBJS += builtin/var.o
+BUILTIN_OBJS += builtin/verify-pack.o
+BUILTIN_OBJS += builtin/verify-tag.o
+BUILTIN_OBJS += builtin/write-tree.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 EXTLIBS =
@@ -630,13 +760,22 @@ EXTLIBS =
 # because maintaining the nesting to match is a pain.  If
 # we had "elif" things would have been much nicer...
 
+ifeq ($(uname_S),OSF1)
+       # Need this for u_short definitions et al
+       BASIC_CFLAGS += -D_OSF_SOURCE
+       SOCKLEN_T = int
+       NO_STRTOULL = YesPlease
+       NO_NSEC = YesPlease
+endif
 ifeq ($(uname_S),Linux)
        NO_STRLCPY = YesPlease
-       THREADED_DELTA_SEARCH = YesPlease
+       NO_MKSTEMPS = YesPlease
+       HAVE_PATHS_H = YesPlease
 endif
 ifeq ($(uname_S),GNU/kFreeBSD)
        NO_STRLCPY = YesPlease
-       THREADED_DELTA_SEARCH = YesPlease
+       NO_MKSTEMPS = YesPlease
+       HAVE_PATHS_H = YesPlease
 endif
 ifeq ($(uname_S),UnixWare)
        CC = cc
@@ -647,6 +786,7 @@ ifeq ($(uname_S),UnixWare)
        SHELL_PATH = /usr/local/bin/bash
        NO_IPV6 = YesPlease
        NO_HSTRERROR = YesPlease
+       NO_MKSTEMPS = YesPlease
        BASIC_CFLAGS += -Kthread
        BASIC_CFLAGS += -I/usr/local/include
        BASIC_LDFLAGS += -L/usr/local/lib
@@ -670,6 +810,7 @@ ifeq ($(uname_S),SCO_SV)
        SHELL_PATH = /usr/bin/bash
        NO_IPV6 = YesPlease
        NO_HSTRERROR = YesPlease
+       NO_MKSTEMPS = YesPlease
        BASIC_CFLAGS += -I/usr/local/include
        BASIC_LDFLAGS += -L/usr/local/lib
        NO_STRCASESTR = YesPlease
@@ -678,6 +819,7 @@ ifeq ($(uname_S),SCO_SV)
        TAR = gtar
 endif
 ifeq ($(uname_S),Darwin)
+       NEEDS_CRYPTO_WITH_SSL = YesPlease
        NEEDS_SSL_WITH_CRYPTO = YesPlease
        NEEDS_LIBICONV = YesPlease
        ifeq ($(shell expr "$(uname_R)" : '[15678]\.'),2)
@@ -687,72 +829,106 @@ ifeq ($(uname_S),Darwin)
                NO_STRLCPY = YesPlease
        endif
        NO_MEMMEM = YesPlease
-       THREADED_DELTA_SEARCH = YesPlease
        USE_ST_TIMESPEC = YesPlease
 endif
 ifeq ($(uname_S),SunOS)
        NEEDS_SOCKET = YesPlease
        NEEDS_NSL = YesPlease
        SHELL_PATH = /bin/bash
+       SANE_TOOL_PATH = /usr/xpg6/bin:/usr/xpg4/bin
        NO_STRCASESTR = YesPlease
        NO_MEMMEM = YesPlease
-       NO_HSTRERROR = YesPlease
        NO_MKDTEMP = YesPlease
-       OLD_ICONV = UnfortunatelyYes
+       NO_MKSTEMPS = YesPlease
+       NO_REGEX = YesPlease
+       ifeq ($(uname_R),5.6)
+               SOCKLEN_T = int
+               NO_HSTRERROR = YesPlease
+               NO_IPV6 = YesPlease
+               NO_SOCKADDR_STORAGE = YesPlease
+               NO_UNSETENV = YesPlease
+               NO_SETENV = YesPlease
+               NO_STRLCPY = YesPlease
+               NO_C99_FORMAT = YesPlease
+               NO_STRTOUMAX = YesPlease
+               GIT_TEST_CMP = cmp
+       endif
+       ifeq ($(uname_R),5.7)
+               NEEDS_RESOLV = YesPlease
+               NO_IPV6 = YesPlease
+               NO_SOCKADDR_STORAGE = YesPlease
+               NO_UNSETENV = YesPlease
+               NO_SETENV = YesPlease
+               NO_STRLCPY = YesPlease
+               NO_C99_FORMAT = YesPlease
+               NO_STRTOUMAX = YesPlease
+               GIT_TEST_CMP = cmp
+       endif
        ifeq ($(uname_R),5.8)
                NO_UNSETENV = YesPlease
                NO_SETENV = YesPlease
                NO_C99_FORMAT = YesPlease
                NO_STRTOUMAX = YesPlease
+               GIT_TEST_CMP = cmp
        endif
        ifeq ($(uname_R),5.9)
                NO_UNSETENV = YesPlease
                NO_SETENV = YesPlease
                NO_C99_FORMAT = YesPlease
                NO_STRTOUMAX = YesPlease
+               GIT_TEST_CMP = cmp
        endif
-       INSTALL = ginstall
+       INSTALL = /usr/ucb/install
        TAR = gtar
-       BASIC_CFLAGS += -D__EXTENSIONS__
+       BASIC_CFLAGS += -D__EXTENSIONS__ -D__sun__ -DHAVE_ALLOCA_H
 endif
 ifeq ($(uname_O),Cygwin)
-       NO_D_TYPE_IN_DIRENT = YesPlease
-       NO_D_INO_IN_DIRENT = YesPlease
-       NO_STRCASESTR = YesPlease
-       NO_MEMMEM = YesPlease
-       NO_SYMLINK_HEAD = YesPlease
+       ifeq ($(shell expr "$(uname_R)" : '1\.[1-6]\.'),4)
+               NO_D_TYPE_IN_DIRENT = YesPlease
+               NO_D_INO_IN_DIRENT = YesPlease
+               NO_STRCASESTR = YesPlease
+               NO_MEMMEM = YesPlease
+               NO_MKSTEMPS = YesPlease
+               NO_SYMLINK_HEAD = YesPlease
+               NO_IPV6 = YesPlease
+               OLD_ICONV = UnfortunatelyYes
+       endif
        NEEDS_LIBICONV = YesPlease
        NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes
        NO_TRUSTABLE_FILEMODE = UnfortunatelyYes
-       OLD_ICONV = UnfortunatelyYes
+       NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
        # There are conflicting reports about this.
        # On some boxes NO_MMAP is needed, and not so elsewhere.
        # Try commenting this out if you suspect MMAP is more efficient
        NO_MMAP = YesPlease
-       NO_IPV6 = YesPlease
        X = .exe
+       COMPAT_OBJS += compat/cygwin.o
+       UNRELIABLE_FSTAT = UnfortunatelyYes
 endif
 ifeq ($(uname_S),FreeBSD)
        NEEDS_LIBICONV = YesPlease
+       OLD_ICONV = YesPlease
        NO_MEMMEM = YesPlease
        BASIC_CFLAGS += -I/usr/local/include
        BASIC_LDFLAGS += -L/usr/local/lib
        DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease
        USE_ST_TIMESPEC = YesPlease
-       THREADED_DELTA_SEARCH = YesPlease
        ifeq ($(shell expr "$(uname_R)" : '4\.'),2)
                PTHREAD_LIBS = -pthread
                NO_UINTMAX_T = YesPlease
                NO_STRTOUMAX = YesPlease
        endif
+       PYTHON_PATH = /usr/local/bin/python
+       HAVE_PATHS_H = YesPlease
 endif
 ifeq ($(uname_S),OpenBSD)
        NO_STRCASESTR = YesPlease
        NO_MEMMEM = YesPlease
+       USE_ST_TIMESPEC = YesPlease
        NEEDS_LIBICONV = YesPlease
        BASIC_CFLAGS += -I/usr/local/include
        BASIC_LDFLAGS += -L/usr/local/lib
-       THREADED_DELTA_SEARCH = YesPlease
+       HAVE_PATHS_H = YesPlease
 endif
 ifeq ($(uname_S),NetBSD)
        ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2)
@@ -760,75 +936,179 @@ ifeq ($(uname_S),NetBSD)
        endif
        BASIC_CFLAGS += -I/usr/pkg/include
        BASIC_LDFLAGS += -L/usr/pkg/lib $(CC_LD_DYNPATH)/usr/pkg/lib
-       THREADED_DELTA_SEARCH = YesPlease
        USE_ST_TIMESPEC = YesPlease
+       NO_MKSTEMPS = YesPlease
+       HAVE_PATHS_H = YesPlease
 endif
 ifeq ($(uname_S),AIX)
+       DEFAULT_PAGER = more
        NO_STRCASESTR=YesPlease
        NO_MEMMEM = YesPlease
        NO_MKDTEMP = YesPlease
+       NO_MKSTEMPS = YesPlease
        NO_STRLCPY = YesPlease
        NO_NSEC = YesPlease
        FREAD_READS_DIRECTORIES = UnfortunatelyYes
        INTERNAL_QSORT = UnfortunatelyYes
        NEEDS_LIBICONV=YesPlease
        BASIC_CFLAGS += -D_LARGE_FILES
-       ifneq ($(shell expr "$(uname_V)" : '[1234]'),1)
-               THREADED_DELTA_SEARCH = YesPlease
-       else
+       ifeq ($(shell expr "$(uname_V)" : '[1234]'),1)
                NO_PTHREADS = YesPlease
+       else
+               PTHREAD_LIBS = -lpthread
        endif
+       ifeq ($(shell expr "$(uname_V).$(uname_R)" : '5\.1'),3)
+               INLINE=''
+       endif
+       GIT_TEST_CMP = cmp
 endif
 ifeq ($(uname_S),GNU)
        # GNU/Hurd
        NO_STRLCPY=YesPlease
+       NO_MKSTEMPS = YesPlease
+       HAVE_PATHS_H = YesPlease
+endif
+ifeq ($(uname_S),IRIX)
+       NO_SETENV = YesPlease
+       NO_UNSETENV = YesPlease
+       NO_STRCASESTR = YesPlease
+       NO_MEMMEM = YesPlease
+       NO_MKSTEMPS = YesPlease
+       NO_MKDTEMP = YesPlease
+       # When compiled with the MIPSpro 7.4.4m compiler, and without pthreads
+       # (i.e. NO_PTHREADS is set), and _with_ MMAP (i.e. NO_MMAP is not set),
+       # git dies with a segmentation fault when trying to access the first
+       # entry of a reflog.  The conservative choice is made to always set
+       # NO_MMAP.  If you suspect that your compiler is not affected by this
+       # issue, comment out the NO_MMAP statement.
+       NO_MMAP = YesPlease
+       NO_REGEX = YesPlease
+       SNPRINTF_RETURNS_BOGUS = YesPlease
+       SHELL_PATH = /usr/gnu/bin/bash
+       NEEDS_LIBGEN = YesPlease
 endif
 ifeq ($(uname_S),IRIX64)
-       NO_IPV6=YesPlease
        NO_SETENV=YesPlease
+       NO_UNSETENV = YesPlease
        NO_STRCASESTR=YesPlease
        NO_MEMMEM = YesPlease
-       NO_STRLCPY = YesPlease
-       NO_SOCKADDR_STORAGE=YesPlease
+       NO_MKSTEMPS = YesPlease
+       NO_MKDTEMP = YesPlease
+       # When compiled with the MIPSpro 7.4.4m compiler, and without pthreads
+       # (i.e. NO_PTHREADS is set), and _with_ MMAP (i.e. NO_MMAP is not set),
+       # git dies with a segmentation fault when trying to access the first
+       # entry of a reflog.  The conservative choice is made to always set
+       # NO_MMAP.  If you suspect that your compiler is not affected by this
+       # issue, comment out the NO_MMAP statement.
+       NO_MMAP = YesPlease
+       NO_REGEX = YesPlease
+       SNPRINTF_RETURNS_BOGUS = YesPlease
        SHELL_PATH=/usr/gnu/bin/bash
-       BASIC_CFLAGS += -DPATH_MAX=1024
-       # for now, build 32-bit version
-       BASIC_LDFLAGS += -L/usr/lib32
+       NEEDS_LIBGEN = YesPlease
 endif
 ifeq ($(uname_S),HP-UX)
+       INLINE = __inline
        NO_IPV6=YesPlease
        NO_SETENV=YesPlease
        NO_STRCASESTR=YesPlease
        NO_MEMMEM = YesPlease
+       NO_MKSTEMPS = YesPlease
        NO_STRLCPY = YesPlease
        NO_MKDTEMP = YesPlease
        NO_UNSETENV = YesPlease
        NO_HSTRERROR = YesPlease
        NO_SYS_SELECT_H = YesPlease
        SNPRINTF_RETURNS_BOGUS = YesPlease
+       NO_NSEC = YesPlease
+       ifeq ($(uname_R),B.11.00)
+               NO_INET_NTOP = YesPlease
+               NO_INET_PTON = YesPlease
+       endif
+       ifeq ($(uname_R),B.10.20)
+               # Override HP-UX 11.x setting:
+               INLINE =
+               SOCKLEN_T = size_t
+               NO_PREAD = YesPlease
+               NO_INET_NTOP = YesPlease
+               NO_INET_PTON = YesPlease
+       endif
+       GIT_TEST_CMP = cmp
 endif
-ifneq (,$(findstring CYGWIN,$(uname_S)))
-       COMPAT_OBJS += compat/cygwin.o
+ifeq ($(uname_S),Windows)
+       GIT_VERSION := $(GIT_VERSION).MSVC
+       pathsep = ;
+       NO_PREAD = YesPlease
+       NEEDS_CRYPTO_WITH_SSL = YesPlease
+       NO_LIBGEN_H = YesPlease
+       NO_SYMLINK_HEAD = YesPlease
+       NO_IPV6 = YesPlease
+       NO_SETENV = YesPlease
+       NO_UNSETENV = YesPlease
+       NO_STRCASESTR = YesPlease
+       NO_STRLCPY = YesPlease
+       NO_STRTOK_R = YesPlease
+       NO_MEMMEM = YesPlease
+       # NEEDS_LIBICONV = YesPlease
+       NO_ICONV = YesPlease
+       NO_C99_FORMAT = YesPlease
+       NO_STRTOUMAX = YesPlease
+       NO_STRTOULL = YesPlease
+       NO_MKDTEMP = YesPlease
+       NO_MKSTEMPS = YesPlease
+       SNPRINTF_RETURNS_BOGUS = YesPlease
+       NO_SVN_TESTS = YesPlease
+       NO_PERL_MAKEMAKER = YesPlease
+       RUNTIME_PREFIX = YesPlease
+       NO_POSIX_ONLY_PROGRAMS = YesPlease
+       NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
+       NO_NSEC = YesPlease
+       USE_WIN32_MMAP = YesPlease
+       # USE_NED_ALLOCATOR = YesPlease
        UNRELIABLE_FSTAT = UnfortunatelyYes
+       OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo
+       NO_REGEX = YesPlease
+       NO_CURL = YesPlease
+       NO_PYTHON = YesPlease
+       BLK_SHA1 = YesPlease
+       NATIVE_CRLF = YesPlease
+
+       CC = compat/vcbuild/scripts/clink.pl
+       AR = compat/vcbuild/scripts/lib.pl
+       CFLAGS =
+       BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE
+       COMPAT_OBJS = compat/msvc.o compat/fnmatch/fnmatch.o compat/winansi.o compat/win32/pthread.o
+       COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/fnmatch -Icompat/regex -Icompat/fnmatch -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
+       BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib
+       EXTLIBS = advapi32.lib shell32.lib wininet.lib ws2_32.lib
+       PTHREAD_LIBS =
+       lib =
+ifndef DEBUG
+       BASIC_CFLAGS += -GL -Os -MT
+       BASIC_LDFLAGS += -LTCG
+       AR += -LTCG
+else
+       BASIC_CFLAGS += -Zi -MTd
+endif
+       X = .exe
 endif
 ifneq (,$(findstring MINGW,$(uname_S)))
+       pathsep = ;
        NO_PREAD = YesPlease
-       NO_OPENSSL = YesPlease
-       NO_CURL = YesPlease
+       NEEDS_CRYPTO_WITH_SSL = YesPlease
+       NO_LIBGEN_H = YesPlease
        NO_SYMLINK_HEAD = YesPlease
-       NO_IPV6 = YesPlease
        NO_SETENV = YesPlease
        NO_UNSETENV = YesPlease
        NO_STRCASESTR = YesPlease
        NO_STRLCPY = YesPlease
+       NO_STRTOK_R = YesPlease
        NO_MEMMEM = YesPlease
-       NO_PTHREADS = YesPlease
        NEEDS_LIBICONV = YesPlease
        OLD_ICONV = YesPlease
        NO_C99_FORMAT = YesPlease
        NO_STRTOUMAX = YesPlease
        NO_MKDTEMP = YesPlease
-       SNPRINTF_RETURNS_BOGUS = YesPlease
+       NO_MKSTEMPS = YesPlease
        NO_SVN_TESTS = YesPlease
        NO_PERL_MAKEMAKER = YesPlease
        RUNTIME_PREFIX = YesPlease
@@ -836,22 +1116,59 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
        NO_NSEC = YesPlease
        USE_WIN32_MMAP = YesPlease
+       USE_NED_ALLOCATOR = YesPlease
        UNRELIABLE_FSTAT = UnfortunatelyYes
        OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo
-       COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/regex -Icompat/fnmatch
-       COMPAT_CFLAGS += -DSNPRINTF_SIZE_CORR=1
+       NO_REGEX = YesPlease
+       NO_PYTHON = YesPlease
+       BLK_SHA1 = YesPlease
+       COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch -Icompat/win32
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
-       COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/regex/regex.o compat/winansi.o
+       COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o \
+               compat/win32/pthread.o
        EXTLIBS += -lws2_32
+       PTHREAD_LIBS =
        X = .exe
+ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
+       htmldir=doc/git/html/
+       prefix =
+       INSTALL = /bin/install
+       EXTLIBS += /mingw/lib/libz.a
+       NO_R_TO_GCC_LINKER = YesPlease
+       INTERNAL_QSORT = YesPlease
+else
+       NO_CURL = YesPlease
 endif
-ifneq (,$(findstring arm,$(uname_M)))
-       ARM_SHA1 = YesPlease
 endif
 
 -include config.mak.autogen
 -include config.mak
 
+ifdef CHECK_HEADER_DEPENDENCIES
+COMPUTE_HEADER_DEPENDENCIES =
+USE_COMPUTED_HEADER_DEPENDENCIES =
+endif
+
+ifdef COMPUTE_HEADER_DEPENDENCIES
+USE_COMPUTED_HEADER_DEPENDENCIES = YesPlease
+endif
+
+ifdef SANE_TOOL_PATH
+SANE_TOOL_PATH_SQ = $(subst ','\'',$(SANE_TOOL_PATH))
+BROKEN_PATH_FIX = 's|^\# @@BROKEN_PATH_FIX@@$$|git_broken_path_fix $(SANE_TOOL_PATH_SQ)|'
+PATH := $(SANE_TOOL_PATH):${PATH}
+else
+BROKEN_PATH_FIX = '/^\# @@BROKEN_PATH_FIX@@$$/d'
+endif
+
+ifneq (,$(INLINE))
+       BASIC_CFLAGS += -Dinline=$(INLINE)
+endif
+
+ifneq (,$(SOCKLEN_T))
+       BASIC_CFLAGS += -Dsocklen_t=$(SOCKLEN_T)
+endif
+
 ifeq ($(uname_S),Darwin)
        ifndef NO_FINK
                ifeq ($(shell test -d /sw/lib && echo y),y)
@@ -878,8 +1195,16 @@ ifndef CC_LD_DYNPATH
        endif
 endif
 
+ifdef NO_LIBGEN_H
+       COMPAT_CFLAGS += -DNO_LIBGEN_H
+       COMPAT_OBJS += compat/basename.o
+endif
+
 ifdef NO_CURL
        BASIC_CFLAGS += -DNO_CURL
+       REMOTE_CURL_PRIMARY =
+       REMOTE_CURL_ALIASES =
+       REMOTE_CURL_NAMES =
 else
        ifdef CURLDIR
                # Try "-Wl,-rpath=$(CURLDIR)/$(lib)" in such a case.
@@ -888,13 +1213,15 @@ else
        else
                CURL_LIBCURL = -lcurl
        endif
-       BUILTIN_OBJS += builtin-http-fetch.o
-       EXTLIBS += $(CURL_LIBCURL)
-       LIB_OBJS += http.o http-walker.o
+       REMOTE_CURL_PRIMARY = git-remote-http$X
+       REMOTE_CURL_ALIASES = git-remote-https$X git-remote-ftp$X git-remote-ftps$X
+       REMOTE_CURL_NAMES = $(REMOTE_CURL_PRIMARY) $(REMOTE_CURL_ALIASES)
+       PROGRAM_OBJS += http-fetch.o
+       PROGRAMS += $(REMOTE_CURL_NAMES)
        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
+                       PROGRAM_OBJS += http-push.o
                endif
        endif
        ifndef NO_EXPAT
@@ -914,8 +1241,7 @@ endif
 EXTLIBS += -lz
 
 ifndef NO_POSIX_ONLY_PROGRAMS
-       PROGRAMS += git-daemon$X
-       PROGRAMS += git-imap-send$X
+       PROGRAM_OBJS += daemon.o
 endif
 ifndef NO_OPENSSL
        OPENSSL_LIBSSL = -lssl
@@ -925,9 +1251,12 @@ ifndef NO_OPENSSL
        else
                OPENSSL_LINK =
        endif
+       ifdef NEEDS_CRYPTO_WITH_SSL
+               OPENSSL_LINK += -lcrypto
+       endif
 else
        BASIC_CFLAGS += -DNO_OPENSSL
-       MOZILLA_SHA1 = 1
+       BLK_SHA1 = 1
        OPENSSL_LIBSSL =
 endif
 ifdef NEEDS_SSL_WITH_CRYPTO
@@ -944,12 +1273,18 @@ ifdef NEEDS_LIBICONV
        endif
        EXTLIBS += $(ICONV_LINK) -liconv
 endif
+ifdef NEEDS_LIBGEN
+       EXTLIBS += -lgen
+endif
 ifdef NEEDS_SOCKET
        EXTLIBS += -lsocket
 endif
 ifdef NEEDS_NSL
        EXTLIBS += -lnsl
 endif
+ifdef NEEDS_RESOLV
+       EXTLIBS += -lresolv
+endif
 ifdef NO_D_TYPE_IN_DIRENT
        BASIC_CFLAGS += -DNO_D_TYPE_IN_DIRENT
 endif
@@ -997,6 +1332,10 @@ endif
 ifdef NO_STRTOULL
        COMPAT_CFLAGS += -DNO_STRTOULL
 endif
+ifdef NO_STRTOK_R
+       COMPAT_CFLAGS += -DNO_STRTOK_R
+       COMPAT_OBJS += compat/strtok_r.o
+endif
 ifdef NO_SETENV
        COMPAT_CFLAGS += -DNO_SETENV
        COMPAT_OBJS += compat/setenv.o
@@ -1005,6 +1344,9 @@ ifdef NO_MKDTEMP
        COMPAT_CFLAGS += -DNO_MKDTEMP
        COMPAT_OBJS += compat/mkdtemp.o
 endif
+ifdef NO_MKSTEMPS
+       COMPAT_CFLAGS += -DNO_MKSTEMPS
+endif
 ifdef NO_UNSETENV
        COMPAT_CFLAGS += -DNO_UNSETENV
        COMPAT_OBJS += compat/unsetenv.o
@@ -1066,23 +1408,20 @@ ifdef NO_DEFLATE_BOUND
        BASIC_CFLAGS += -DNO_DEFLATE_BOUND
 endif
 
+ifdef BLK_SHA1
+       SHA1_HEADER = "block-sha1/sha1.h"
+       LIB_OBJS += block-sha1/sha1.o
+       LIB_H += block-sha1/sha1.h
+else
 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
+       LIB_H += ppc/sha1.h
 else
        SHA1_HEADER = <openssl/sha.h>
        EXTLIBS += $(LIB_4_CRYPTO)
 endif
 endif
-endif
 ifdef NO_PERL_MAKEMAKER
        export NO_PERL_MAKEMAKER
 endif
@@ -1103,25 +1442,36 @@ ifdef RUNTIME_PREFIX
 endif
 
 ifdef NO_PTHREADS
-       THREADED_DELTA_SEARCH =
        BASIC_CFLAGS += -DNO_PTHREADS
 else
+       BASIC_CFLAGS += $(PTHREAD_CFLAGS)
        EXTLIBS += $(PTHREAD_LIBS)
+       LIB_OBJS += thread-utils.o
 endif
 
-ifdef THREADED_DELTA_SEARCH
-       BASIC_CFLAGS += -DTHREADED_DELTA_SEARCH
-       LIB_OBJS += thread-utils.o
+ifdef HAVE_PATHS_H
+       BASIC_CFLAGS += -DHAVE_PATHS_H
 endif
+
 ifdef DIR_HAS_BSD_GROUP_SEMANTICS
        COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS
 endif
-ifdef NO_EXTERNAL_GREP
-       BASIC_CFLAGS += -DNO_EXTERNAL_GREP
-endif
 ifdef UNRELIABLE_FSTAT
        BASIC_CFLAGS += -DUNRELIABLE_FSTAT
 endif
+ifdef NO_REGEX
+       COMPAT_CFLAGS += -Icompat/regex
+       COMPAT_OBJS += compat/regex/regex.o
+endif
+
+ifdef USE_NED_ALLOCATOR
+       COMPAT_CFLAGS += -Icompat/nedmalloc
+       COMPAT_OBJS += compat/nedmalloc/nedmalloc.o
+endif
+
+ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
+       export GIT_TEST_CMP_USE_COPIED_CONTEXT
+endif
 
 ifeq ($(TCLTK_PATH),)
 NO_TCLTK=NoThanks
@@ -1131,6 +1481,10 @@ ifeq ($(PERL_PATH),)
 NO_PERL=NoThanks
 endif
 
+ifeq ($(PYTHON_PATH),)
+NO_PYTHON=NoThanks
+endif
+
 QUIET_SUBDIR0  = +$(MAKE) -C # space to separate -C and subdir
 QUIET_SUBDIR1  =
 
@@ -1147,6 +1501,8 @@ ifndef V
        QUIET_LINK     = @echo '   ' LINK $@;
        QUIET_BUILT_IN = @echo '   ' BUILTIN $@;
        QUIET_GEN      = @echo '   ' GEN $@;
+       QUIET_LNCP     = @echo '   ' LN/CP $@;
+       QUIET_GCOV     = @echo '   ' GCOV $@;
        QUIET_SUBDIR0  = +@subdir=
        QUIET_SUBDIR1  = ;$(NO_SUBDIR) echo '   ' SUBDIR $$subdir; \
                         $(MAKE) $(PRINT_DIR) -C $$subdir
@@ -1174,10 +1530,13 @@ gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
 template_dir_SQ = $(subst ','\'',$(template_dir))
 htmldir_SQ = $(subst ','\'',$(htmldir))
 prefix_SQ = $(subst ','\'',$(prefix))
+gitwebdir_SQ = $(subst ','\'',$(gitwebdir))
 
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
+PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
 TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
+DIFF_SQ = $(subst ','\'',$(DIFF))
 
 LIBS = $(GITLIBS) $(EXTLIBS)
 
@@ -1185,19 +1544,35 @@ BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' \
        $(COMPAT_CFLAGS)
 LIB_OBJS += $(COMPAT_OBJS)
 
+# Quote for C
+
+ifdef DEFAULT_EDITOR
+DEFAULT_EDITOR_CQ = "$(subst ",\",$(subst \,\\,$(DEFAULT_EDITOR)))"
+DEFAULT_EDITOR_CQ_SQ = $(subst ','\'',$(DEFAULT_EDITOR_CQ))
+
+BASIC_CFLAGS += -DDEFAULT_EDITOR='$(DEFAULT_EDITOR_CQ_SQ)'
+endif
+
+ifdef DEFAULT_PAGER
+DEFAULT_PAGER_CQ = "$(subst ",\",$(subst \,\\,$(DEFAULT_PAGER)))"
+DEFAULT_PAGER_CQ_SQ = $(subst ','\'',$(DEFAULT_PAGER_CQ))
+
+BASIC_CFLAGS += -DDEFAULT_PAGER='$(DEFAULT_PAGER_CQ_SQ)'
+endif
+
 ALL_CFLAGS += $(BASIC_CFLAGS)
 ALL_LDFLAGS += $(BASIC_LDFLAGS)
 
-export TAR INSTALL DESTDIR SHELL_PATH
+export DIFF TAR INSTALL DESTDIR SHELL_PATH
 
 
 ### Build rules
 
 SHELL = $(SHELL_PATH)
 
-all:: shell_compatibility_test $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS
+all:: shell_compatibility_test $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS
 ifneq (,$X)
-       $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test '$p' -ef '$p$X' || $(RM) '$p';)
+       $(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test -d '$p' -o '$p' -ef '$p$X' || $(RM) '$p';)
 endif
 
 all::
@@ -1208,7 +1583,10 @@ endif
 ifndef NO_PERL
        $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
 endif
-       $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1)
+ifndef NO_PYTHON
+       $(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all
+endif
+       $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) SHELL_PATH='$(SHELL_PATH_SQ)' PERL_PATH='$(PERL_PATH_SQ)'
 
 please_set_SHELL_PATH_to_a_more_modern_shell:
        @$$(:)
@@ -1218,20 +1596,19 @@ shell_compatibility_test: please_set_SHELL_PATH_to_a_more_modern_shell
 strip: $(PROGRAMS) git$X
        $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X
 
-git.o: git.c common-cmds.h GIT-CFLAGS
-       $(QUIET_CC)$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
-               '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
-               $(ALL_CFLAGS) -c $(filter %.c,$^)
+git.o: common-cmds.h
+git.s git.o: EXTRA_CPPFLAGS = -DGIT_VERSION='"$(GIT_VERSION)"' \
+       '-DGIT_HTML_PATH="$(htmldir_SQ)"'
 
 git$X: git.o $(BUILTIN_OBJS) $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \
                $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS)
 
-builtin-help.o: builtin-help.c common-cmds.h GIT-CFLAGS
-       $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) \
-               '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
-               '-DGIT_MAN_PATH="$(mandir_SQ)"' \
-               '-DGIT_INFO_PATH="$(infodir_SQ)"' $<
+builtin/help.o: common-cmds.h
+builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \
+       '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
+       '-DGIT_MAN_PATH="$(mandir_SQ)"' \
+       '-DGIT_INFO_PATH="$(infodir_SQ)"'
 
 $(BUILT_INS): git$X
        $(QUIET_BUILT_IN)$(RM) $@ && \
@@ -1244,17 +1621,26 @@ common-cmds.h: ./generate-cmdlist.sh command-list.txt
 common-cmds.h: $(wildcard Documentation/git-*.txt)
        $(QUIET_GEN)./generate-cmdlist.sh > $@+ && mv $@+ $@
 
+define cmd_munge_script
+$(RM) $@ $@+ && \
+sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
+    -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \
+    -e 's|@@DIFF@@|$(DIFF_SQ)|' \
+    -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+    -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
+    -e $(BROKEN_PATH_FIX) \
+    $@.sh >$@+
+endef
+
 $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
-       $(QUIET_GEN)$(RM) $@ $@+ && \
-       sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
-           -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \
-           -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \
-           -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
-           -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
-           $@.sh >$@+ && \
+       $(QUIET_GEN)$(cmd_munge_script) && \
        chmod +x $@+ && \
        mv $@+ $@
 
+$(SCRIPT_LIB) : % : %.sh
+       $(QUIET_GEN)$(cmd_munge_script) && \
+       mv $@+ $@
+
 ifndef NO_PERL
 $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak
 
@@ -1267,51 +1653,52 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
        sed -e '1{' \
            -e '        s|#!.*perl|#!$(PERL_PATH_SQ)|' \
            -e '        h' \
-           -e '        s=.*=use lib (split(/:/, $$ENV{GITPERLLIB} || "@@INSTLIBDIR@@"));=' \
+           -e '        s=.*=use lib (split(/$(pathsep)/, $$ENV{GITPERLLIB} || "'"$$INSTLIBDIR"'"));=' \
            -e '        H' \
            -e '        x' \
            -e '}' \
-           -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
            $@.perl >$@+ && \
        chmod +x $@+ && \
        mv $@+ $@
 
-OTHER_PROGRAMS += gitweb/gitweb.cgi
-gitweb/gitweb.cgi: gitweb/gitweb.perl
-       $(QUIET_GEN)$(RM) $@ $@+ && \
-       sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
-           -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
-           -e 's|++GIT_BINDIR++|$(bindir)|g' \
-           -e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \
-           -e 's|++GITWEB_CONFIG_SYSTEM++|$(GITWEB_CONFIG_SYSTEM)|g' \
-           -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \
-           -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \
-           -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \
-           -e 's|"++GITWEB_PROJECT_MAXDEPTH++"|$(GITWEB_PROJECT_MAXDEPTH)|g' \
-           -e 's|++GITWEB_EXPORT_OK++|$(GITWEB_EXPORT_OK)|g' \
-           -e 's|++GITWEB_STRICT_EXPORT++|$(GITWEB_STRICT_EXPORT)|g' \
-           -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \
-           -e 's|++GITWEB_LIST++|$(GITWEB_LIST)|g' \
-           -e 's|++GITWEB_HOMETEXT++|$(GITWEB_HOMETEXT)|g' \
-           -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \
-           -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \
-           -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \
-           -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \
-           -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \
-           $< >$@+ && \
-       chmod +x $@+ && \
-       mv $@+ $@
 
-git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css
+.PHONY: gitweb
+gitweb:
+       $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) all
+
+ifdef JSMIN
+GITWEB_PROGRAMS += gitweb/static/gitweb.min.js
+GITWEB_JS = gitweb/static/gitweb.min.js
+else
+GITWEB_JS = gitweb/static/gitweb.js
+endif
+ifdef CSSMIN
+GITWEB_PROGRAMS += gitweb/static/gitweb.min.css
+GITWEB_CSS = gitweb/static/gitweb.min.css
+else
+GITWEB_CSS = gitweb/static/gitweb.css
+endif
+OTHER_PROGRAMS +=  gitweb/gitweb.cgi  $(GITWEB_PROGRAMS)
+gitweb/gitweb.cgi: gitweb/gitweb.perl $(GITWEB_PROGRAMS)
+       $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@)
+
+ifdef JSMIN
+gitweb/static/gitweb.min.js: gitweb/static/gitweb.js
+       $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@)
+endif # JSMIN
+ifdef CSSMIN
+gitweb/static/gitweb.min.css: gitweb/static/gitweb.css
+       $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@)
+endif # CSSMIN
+
+
+git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/static/gitweb.css gitweb/static/gitweb.js
        $(QUIET_GEN)$(RM) $@ $@+ && \
        sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
            -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
-           -e '/@@GITWEB_CGI@@/r gitweb/gitweb.cgi' \
-           -e '/@@GITWEB_CGI@@/d' \
-           -e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \
-           -e '/@@GITWEB_CSS@@/d' \
+           -e 's|@@GITWEBDIR@@|$(gitwebdir_SQ)|g' \
            -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \
            $@.sh > $@+ && \
        chmod +x $@+ && \
@@ -1326,6 +1713,29 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)) git-instaweb: % : unimplemented.sh
        mv $@+ $@
 endif # NO_PERL
 
+ifndef NO_PYTHON
+$(patsubst %.py,%,$(SCRIPT_PYTHON)): GIT-CFLAGS
+$(patsubst %.py,%,$(SCRIPT_PYTHON)): % : %.py
+       $(QUIET_GEN)$(RM) $@ $@+ && \
+       INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C git_remote_helpers -s \
+               --no-print-directory prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' \
+               instlibdir` && \
+       sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \
+           -e 's|\(os\.getenv("GITPYTHONLIB"\)[^)]*)|\1,"@@INSTLIBDIR@@")|' \
+           -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
+           $@.py >$@+ && \
+       chmod +x $@+ && \
+       mv $@+ $@
+else # NO_PYTHON
+$(patsubst %.py,%,$(SCRIPT_PYTHON)): % : unimplemented.sh
+       $(QUIET_GEN)$(RM) $@ $@+ && \
+       sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
+           -e 's|@@REASON@@|NO_PYTHON=$(NO_PYTHON)|g' \
+           unimplemented.sh >$@+ && \
+       chmod +x $@+ && \
+       mv $@+ $@
+endif # NO_PYTHON
+
 configure: configure.ac
        $(QUIET_GEN)$(RM) $@ $<+ && \
        sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
@@ -1339,32 +1749,168 @@ git.o git.spec \
        $(patsubst %.perl,%,$(SCRIPT_PERL)) \
        : GIT-VERSION-FILE
 
-%.o: %.c GIT-CFLAGS
-       $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
-%.s: %.c GIT-CFLAGS
-       $(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $<
-%.o: %.S
-       $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
+TEST_OBJS := $(patsubst test-%$X,test-%.o,$(TEST_PROGRAMS))
+GIT_OBJS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
+       git.o
+ifndef NO_CURL
+       GIT_OBJS += http.o http-walker.o remote-curl.o
+endif
+XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \
+       xdiff/xmerge.o xdiff/xpatience.o
+VCSSVN_OBJS = vcs-svn/string_pool.o vcs-svn/line_buffer.o \
+       vcs-svn/repo_tree.o vcs-svn/fast_export.o vcs-svn/svndump.o
+OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) $(VCSSVN_OBJS)
+
+dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d)
+dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS))))
+
+ifdef COMPUTE_HEADER_DEPENDENCIES
+$(dep_dirs):
+       mkdir -p $@
+
+missing_dep_dirs := $(filter-out $(wildcard $(dep_dirs)),$(dep_dirs))
+dep_file = $(dir $@).depend/$(notdir $@).d
+dep_args = -MF $(dep_file) -MMD -MP
+ifdef CHECK_HEADER_DEPENDENCIES
+$(error cannot compute header dependencies outside a normal build. \
+Please unset CHECK_HEADER_DEPENDENCIES and try again)
+endif
+endif
+
+ifndef COMPUTE_HEADER_DEPENDENCIES
+ifndef CHECK_HEADER_DEPENDENCIES
+dep_dirs =
+missing_dep_dirs =
+dep_args =
+endif
+endif
+
+ifdef CHECK_HEADER_DEPENDENCIES
+ifndef PRINT_HEADER_DEPENDENCIES
+missing_deps = $(filter-out $(notdir $^), \
+       $(notdir $(shell $(MAKE) -s $@ \
+               CHECK_HEADER_DEPENDENCIES=YesPlease \
+               USE_COMPUTED_HEADER_DEPENDENCIES=YesPlease \
+               PRINT_HEADER_DEPENDENCIES=YesPlease)))
+endif
+endif
+
+ASM_SRC := $(wildcard $(OBJECTS:o=S))
+ASM_OBJ := $(ASM_SRC:S=o)
+C_OBJ := $(filter-out $(ASM_OBJ),$(OBJECTS))
+
+.SUFFIXES:
+
+ifdef PRINT_HEADER_DEPENDENCIES
+$(C_OBJ): %.o: %.c FORCE
+       echo $^
+$(ASM_OBJ): %.o: %.S FORCE
+       echo $^
+
+ifndef CHECK_HEADER_DEPENDENCIES
+$(error cannot print header dependencies during a normal build. \
+Please set CHECK_HEADER_DEPENDENCIES and try again)
+endif
+endif
+
+ifndef PRINT_HEADER_DEPENDENCIES
+ifdef CHECK_HEADER_DEPENDENCIES
+$(C_OBJ): %.o: %.c $(dep_files) FORCE
+       @set -e; echo CHECK $@; \
+       missing_deps="$(missing_deps)"; \
+       if test "$$missing_deps"; \
+       then \
+               echo missing dependencies: $$missing_deps; \
+               false; \
+       fi
+$(ASM_OBJ): %.o: %.S $(dep_files) FORCE
+       @set -e; echo CHECK $@; \
+       missing_deps="$(missing_deps)"; \
+       if test "$$missing_deps"; \
+       then \
+               echo missing dependencies: $$missing_deps; \
+               false; \
+       fi
+endif
+endif
+
+ifndef CHECK_HEADER_DEPENDENCIES
+$(C_OBJ): %.o: %.c GIT-CFLAGS $(missing_dep_dirs)
+       $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $<
+$(ASM_OBJ): %.o: %.S GIT-CFLAGS $(missing_dep_dirs)
+       $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $<
+endif
+
+%.s: %.c GIT-CFLAGS FORCE
+       $(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $<
+
+ifdef USE_COMPUTED_HEADER_DEPENDENCIES
+# Take advantage of gcc's on-the-fly dependency generation
+# See <http://gcc.gnu.org/gcc-3.0/features.html>.
+dep_files_present := $(wildcard $(dep_files))
+ifneq ($(dep_files_present),)
+include $(dep_files_present)
+endif
+else
+# Dependencies on header files, for platforms that do not support
+# the gcc -MMD option.
+#
+# Dependencies on automatically generated headers such as common-cmds.h
+# should _not_ be included here, since they are necessary even when
+# building an object for the first time.
+#
+# XXX. Please check occasionally that these include all dependencies
+# gcc detects!
+
+$(GIT_OBJS): $(LIB_H)
+builtin/branch.o builtin/checkout.o builtin/clone.o builtin/reset.o branch.o transport.o: branch.h
+builtin/bundle.o bundle.o transport.o: bundle.h
+builtin/bisect--helper.o builtin/rev-list.o bisect.o: bisect.h
+builtin/clone.o builtin/fetch-pack.o transport.o: fetch-pack.h
+builtin/grep.o: thread-utils.h
+builtin/send-pack.o transport.o: send-pack.h
+builtin/log.o builtin/shortlog.o: shortlog.h
+builtin/prune.o builtin/reflog.o reachable.o: reachable.h
+builtin/commit.o builtin/revert.o wt-status.o: wt-status.h
+builtin/tar-tree.o archive-tar.o: tar.h
+builtin/pack-objects.o: thread-utils.h
+connect.o transport.o http-backend.o: url.h
+http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h
+http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h
+
+xdiff-interface.o $(XDIFF_OBJS): \
+       xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \
+       xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h
+
+$(VCSSVN_OBJS): \
+       vcs-svn/obj_pool.h vcs-svn/trp.h vcs-svn/string_pool.h \
+       vcs-svn/line_buffer.h vcs-svn/repo_tree.h vcs-svn/fast_export.h \
+       vcs-svn/svndump.h
+endif
 
-exec_cmd.o: exec_cmd.c GIT-CFLAGS
-       $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) \
-               '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \
-               '-DBINDIR="$(bindir_relative_SQ)"' \
-               '-DPREFIX="$(prefix_SQ)"' \
-               $<
+exec_cmd.s exec_cmd.o: EXTRA_CPPFLAGS = \
+       '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \
+       '-DBINDIR="$(bindir_relative_SQ)"' \
+       '-DPREFIX="$(prefix_SQ)"'
 
-builtin-init-db.o: builtin-init-db.c GIT-CFLAGS
-       $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $<
+builtin/init-db.s builtin/init-db.o: EXTRA_CPPFLAGS = \
+       -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"'
 
-config.o: config.c GIT-CFLAGS
-       $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' $<
+config.s config.o: EXTRA_CPPFLAGS = -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"'
 
-http.o: http.c GIT-CFLAGS
-       $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DGIT_USER_AGENT='"git/$(GIT_VERSION)"' $<
+http.s http.o: EXTRA_CPPFLAGS = -DGIT_HTTP_USER_AGENT='"git/$(GIT_VERSION)"'
 
 ifdef NO_EXPAT
-http-walker.o: http-walker.c http.h GIT-CFLAGS
-       $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DNO_EXPAT $<
+http-walker.s http-walker.o: EXTRA_CPPFLAGS = -DNO_EXPAT
+endif
+
+ifdef NO_REGEX
+compat/regex/regex.o: EXTRA_CPPFLAGS = -DGAWK -DNO_MBSUPPORT
+endif
+
+ifdef USE_NED_ALLOCATOR
+compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \
+       -DNDEBUG -DOVERRIDE_STRDUP -DREPLACE_SYSTEM_ALLOCATOR
 endif
 
 git-%$X: %.o $(GITLIBS)
@@ -1374,27 +1920,31 @@ git-imap-send$X: imap-send.o $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
                $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL)
 
-http.o http-walker.o http-push.o transport.o: http.h
-
+git-http-fetch$X: revision.o http.o http-walker.o http-fetch.o $(GITLIBS)
+       $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+               $(LIBS) $(CURL_LIBCURL)
 git-http-push$X: revision.o http.o http-push.o $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
                $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 
-$(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H)
-$(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h)
-builtin-revert.o wt-status.o: wt-status.h
+$(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY)
+       $(QUIET_LNCP)$(RM) $@ && \
+       ln $< $@ 2>/dev/null || \
+       ln -s $< $@ 2>/dev/null || \
+       cp $< $@
+
+$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o $(GITLIBS)
+       $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+               $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 
 $(LIB_FILE): $(LIB_OBJS)
        $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS)
 
-XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \
-       xdiff/xmerge.o xdiff/xpatience.o
-$(XDIFF_OBJS): xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \
-       xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h
-
 $(XDIFF_LIB): $(XDIFF_OBJS)
        $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(XDIFF_OBJS)
 
+$(VCSSVN_LIB): $(VCSSVN_OBJS)
+       $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(VCSSVN_OBJS)
 
 doc:
        $(MAKE) -C Documentation all
@@ -1427,7 +1977,7 @@ cscope:
 TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\
              $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)
 
-GIT-CFLAGS: .FORCE-GIT-CFLAGS
+GIT-CFLAGS: FORCE
        @FLAGS='$(TRACK_CFLAGS)'; \
            if test x"$$FLAGS" != x"`cat GIT-CFLAGS 2>/dev/null`" ; then \
                echo 1>&2 "    * new build flags or prefix"; \
@@ -1437,41 +1987,44 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS
 # We need to apply sq twice, once to protect from the shell
 # that runs GIT-BUILD-OPTIONS, and then again to protect it
 # and the first level quoting from the shell that runs "echo".
-GIT-BUILD-OPTIONS: .FORCE-GIT-BUILD-OPTIONS
+GIT-BUILD-OPTIONS: FORCE
        @echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@
+       @echo PERL_PATH=\''$(subst ','\'',$(PERL_PATH_SQ))'\' >>$@
+       @echo DIFF=\''$(subst ','\'',$(subst ','\'',$(DIFF)))'\' >>$@
+       @echo PYTHON_PATH=\''$(subst ','\'',$(PYTHON_PATH_SQ))'\' >>$@
        @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@
        @echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@
        @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
+       @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@
+ifdef GIT_TEST_CMP
+       @echo GIT_TEST_CMP=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_CMP)))'\' >>$@
+endif
+ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
+       @echo GIT_TEST_CMP_USE_COPIED_CONTEXT=YesPlease >>$@
+endif
 
 ### Detect Tck/Tk interpreter path changes
 ifndef NO_TCLTK
 TRACK_VARS = $(subst ','\'',-DTCLTK_PATH='$(TCLTK_PATH_SQ)')
 
-GIT-GUI-VARS: .FORCE-GIT-GUI-VARS
+GIT-GUI-VARS: FORCE
        @VARS='$(TRACK_VARS)'; \
            if test x"$$VARS" != x"`cat $@ 2>/dev/null`" ; then \
                echo 1>&2 "    * new Tcl/Tk interpreter location"; \
                echo "$$VARS" >$@; \
             fi
-
-.PHONY: .FORCE-GIT-GUI-VARS
 endif
 
-### Testing rules
+test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X))
 
-TEST_PROGRAMS += test-chmtime$X
-TEST_PROGRAMS += test-ctype$X
-TEST_PROGRAMS += test-date$X
-TEST_PROGRAMS += test-delta$X
-TEST_PROGRAMS += test-dump-cache-tree$X
-TEST_PROGRAMS += test-genrandom$X
-TEST_PROGRAMS += test-match-trees$X
-TEST_PROGRAMS += test-parse-options$X
-TEST_PROGRAMS += test-path-utils$X
-TEST_PROGRAMS += test-sha1$X
-TEST_PROGRAMS += test-sigchain$X
+all:: $(TEST_PROGRAMS) $(test_bindir_programs)
 
-all:: $(TEST_PROGRAMS)
+bin-wrappers/%: wrap-for-bin.sh
+       @mkdir -p bin-wrappers
+       $(QUIET_GEN)sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
+            -e 's|@@BUILD_DIR@@|$(shell pwd)|' \
+            -e 's|@@PROG@@|$(@F)|' < $< > $@ && \
+       chmod +x $@
 
 # GNU make supports exporting all variables by "export" without parameters.
 # However, the environment gets quite big, and some programs have problems
@@ -1479,6 +2032,8 @@ all:: $(TEST_PROGRAMS)
 
 export NO_SVN_TESTS
 
+### Testing rules
+
 test: all
        $(MAKE) -C t/ all
 
@@ -1488,12 +2043,18 @@ test-date$X: date.o ctype.o
 
 test-delta$X: diff-delta.o patch-delta.o
 
+test-line-buffer$X: vcs-svn/lib.a
+
 test-parse-options$X: parse-options.o
 
-.PRECIOUS: $(patsubst test-%$X,test-%.o,$(TEST_PROGRAMS))
+test-string-pool$X: vcs-svn/lib.a
+
+test-svn-fe$X: vcs-svn/lib.a
+
+.PRECIOUS: $(TEST_OBJS)
 
 test-%$X: test-%.o $(GITLIBS)
-       $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+       $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
 
 check-sha1:: test-sha1$X
        ./test-sha1.sh
@@ -1530,14 +2091,21 @@ endif
 gitexec_instdir_SQ = $(subst ','\'',$(gitexec_instdir))
 export gitexec_instdir
 
+install_bindir_programs := $(patsubst %,%$X,$(BINDIR_PROGRAMS_NEED_X)) $(BINDIR_PROGRAMS_NO_X)
+
 install: all
        $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)'
        $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
        $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
-       $(INSTALL) git$X git-upload-pack$X git-receive-pack$X git-upload-archive$X git-shell$X git-cvsserver '$(DESTDIR_SQ)$(bindir_SQ)'
+       $(INSTALL) -m 644 $(SCRIPT_LIB) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
+       $(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'
        $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
 ifndef NO_PERL
        $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
+       $(MAKE) -C gitweb install
+endif
+ifndef NO_PYTHON
+       $(MAKE) -C git_remote_helpers prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
 endif
 ifndef NO_TCLTK
        $(MAKE) -C gitk-git install
@@ -1546,19 +2114,41 @@ endif
 ifneq (,$X)
        $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p' -ef '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p$X' || $(RM) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p';)
 endif
+
        bindir=$$(cd '$(DESTDIR_SQ)$(bindir_SQ)' && pwd) && \
        execdir=$$(cd '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' && pwd) && \
-       { $(RM) "$$execdir/git-add$X" && \
-               ln "$$bindir/git$X" "$$execdir/git-add$X" 2>/dev/null || \
-               cp "$$bindir/git$X" "$$execdir/git-add$X"; } && \
-       { for p in $(filter-out git-add$X,$(BUILT_INS)); do \
+       { test "$$bindir/" = "$$execdir/" || \
+         for p in git$X $(filter $(install_bindir_programs),$(ALL_PROGRAMS)); do \
+               $(RM) "$$execdir/$$p" && \
+               test -z "$(NO_CROSS_DIRECTORY_HARDLINKS)" && \
+               ln "$$bindir/$$p" "$$execdir/$$p" 2>/dev/null || \
+               cp "$$bindir/$$p" "$$execdir/$$p" || exit; \
+         done; \
+       } && \
+       for p in $(filter $(install_bindir_programs),$(BUILT_INS)); do \
+               $(RM) "$$bindir/$$p" && \
+               ln "$$bindir/git$X" "$$bindir/$$p" 2>/dev/null || \
+               ln -s "git$X" "$$bindir/$$p" 2>/dev/null || \
+               cp "$$bindir/git$X" "$$bindir/$$p" || exit; \
+       done && \
+       for p in $(BUILT_INS); do \
                $(RM) "$$execdir/$$p" && \
-               ln "$$execdir/git-add$X" "$$execdir/$$p" 2>/dev/null || \
-               ln -s "git-add$X" "$$execdir/$$p" 2>/dev/null || \
-               cp "$$execdir/git-add$X" "$$execdir/$$p" || exit; \
-         done; } && \
+               ln "$$execdir/git$X" "$$execdir/$$p" 2>/dev/null || \
+               ln -s "git$X" "$$execdir/$$p" 2>/dev/null || \
+               cp "$$execdir/git$X" "$$execdir/$$p" || exit; \
+       done && \
+       remote_curl_aliases="$(REMOTE_CURL_ALIASES)" && \
+       for p in $$remote_curl_aliases; do \
+               $(RM) "$$execdir/$$p" && \
+               ln "$$execdir/git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
+               ln -s "git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
+               cp "$$execdir/git-remote-http$X" "$$execdir/$$p" || exit; \
+       done && \
        ./check_bindir "z$$bindir" "z$$execdir" "$$bindir/git-add$X"
 
+install-gitweb:
+       $(MAKE) -C gitweb install
+
 install-doc:
        $(MAKE) -C Documentation install
 
@@ -1608,7 +2198,10 @@ dist: git.spec git-archive$(X) configure
        gzip -f -9 $(GIT_TARNAME).tar
 
 rpm: dist
-       $(RPMBUILD) -ta $(GIT_TARNAME).tar.gz
+       $(RPMBUILD) \
+               --define "_source_filedigest_algorithm md5" \
+               --define "_binary_filedigest_algorithm md5" \
+               -ta $(GIT_TARNAME).tar.gz
 
 htmldocs = git-htmldocs-$(GIT_VERSION)
 manpages = git-manpages-$(GIT_VERSION)
@@ -1636,10 +2229,12 @@ distclean: clean
        $(RM) configure
 
 clean:
-       $(RM) *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o xdiff/*.o \
-               $(LIB_FILE) $(XDIFF_LIB)
-       $(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X
+       $(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o vcs-svn/*.o \
+               builtin/*.o $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB)
+       $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X
        $(RM) $(TEST_PROGRAMS)
+       $(RM) -r bin-wrappers
+       $(RM) -r $(dep_dirs)
        $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags cscope*
        $(RM) -r autom4te.cache
        $(RM) config.log config.mak.autogen config.mak.append config.status config.cache
@@ -1648,8 +2243,11 @@ clean:
        $(RM) $(htmldocs).tar.gz $(manpages).tar.gz
        $(MAKE) -C Documentation/ clean
 ifndef NO_PERL
-       $(RM) gitweb/gitweb.cgi
+       $(MAKE) -C gitweb clean
        $(MAKE) -C perl clean
+endif
+ifndef NO_PYTHON
+       $(MAKE) -C git_remote_helpers clean
 endif
        $(MAKE) -C templates/ clean
        $(MAKE) -C t/ clean
@@ -1661,18 +2259,18 @@ endif
 
 .PHONY: all install clean strip
 .PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell
-.PHONY: .FORCE-GIT-VERSION-FILE TAGS tags cscope .FORCE-GIT-CFLAGS
-.PHONY: .FORCE-GIT-BUILD-OPTIONS
+.PHONY: FORCE TAGS tags cscope
 
 ### Check documentation
 #
 check-docs::
-       @(for v in $(ALL_PROGRAMS) $(BUILT_INS) git gitk; \
+       @(for v in $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git gitk; \
        do \
                case "$$v" in \
                git-merge-octopus | git-merge-ours | git-merge-recursive | \
                git-merge-resolve | git-merge-subtree | \
                git-fsck-objects | git-init-db | \
+               git-remote-* | git-stage | \
                git-?*--?* ) continue ;; \
                esac ; \
                test -f "Documentation/$$v.txt" || \
@@ -1708,11 +2306,15 @@ check-docs::
                documented,gitglossary | \
                documented,githooks | \
                documented,gitrepository-layout | \
+               documented,gitrevisions | \
                documented,gittutorial | \
                documented,gittutorial-2 | \
+               documented,git-bisect-lk2009 | \
+               documented,git-remote-helpers | \
+               documented,gitworkflows | \
                sentinel,not,matching,is,ok ) continue ;; \
                esac; \
-               case " $(ALL_PROGRAMS) $(BUILT_INS) git gitk " in \
+               case " $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git gitk " in \
                *" $$cmd "*)    ;; \
                *) echo "removed but $$how: $$cmd" ;; \
                esac; \
@@ -1731,11 +2333,18 @@ coverage:
        $(MAKE) coverage-build
        $(MAKE) coverage-report
 
+object_dirs := $(sort $(dir $(OBJECTS)))
 coverage-clean:
-       rm -f *.gcda *.gcno
+       $(RM) $(addsuffix *.gcov,$(object_dirs))
+       $(RM) $(addsuffix *.gcda,$(object_dirs))
+       $(RM) $(addsuffix *.gcno,$(object_dirs))
+       $(RM) coverage-untested-functions
+       $(RM) -r cover_db/
+       $(RM) -r cover_db_html/
 
 COVERAGE_CFLAGS = $(CFLAGS) -O0 -ftest-coverage -fprofile-arcs
 COVERAGE_LDFLAGS = $(CFLAGS)  -O0 -lgcov
+GCOVFLAGS = --preserve-paths --branch-probabilities --all-blocks
 
 coverage-build: coverage-clean
        $(MAKE) CFLAGS="$(COVERAGE_CFLAGS)" LDFLAGS="$(COVERAGE_LDFLAGS)" all
@@ -1743,7 +2352,17 @@ coverage-build: coverage-clean
                -j1 test
 
 coverage-report:
-       gcov -b *.c
+       $(QUIET_GCOV)for dir in $(object_dirs); do \
+               $(GCOV) $(GCOVFLAGS) --object-directory=$$dir $$dir*.c || exit; \
+       done
+
+coverage-untested-functions: coverage-report
        grep '^function.*called 0 ' *.c.gcov \
                | sed -e 's/\([^:]*\)\.gcov: *function \([^ ]*\) called.*/\1: \2/' \
-               | tee coverage-untested-functions
+               > coverage-untested-functions
+
+cover_db: coverage-report
+       gcov2perl -db cover_db *.gcov
+
+cover_db_html: cover_db
+       cover -report html -outputdir cover_db_html cover_db
diff --git a/README b/README
index c932ab310510c84c3e69107b458a228db37cb1f6..67cfeb2016b24df1cb406c18145efd399f6a1792 100644 (file)
--- a/README
+++ b/README
@@ -37,7 +37,7 @@ CVS users may also want to read Documentation/gitcvs-migration.txt
 ("man gitcvs-migration" or "git help cvs-migration" if git is
 installed).
 
-Many Git online resources are accessible from http://git.or.cz/
+Many Git online resources are accessible from http://git-scm.com/
 including full documentation and Git related tools.
 
 The user discussion and development of Git take place on the Git
index dd8bc4bb4ab75e1148cb9b7aaf8ff7384ebb19ec..8c65931ff4d966ea7c76cdfa1fcb000c97ab1c19 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes-1.6.3.txt
\ No newline at end of file
+Documentation/RelNotes/1.7.3.txt
\ No newline at end of file
index 649f34f83365db3513c5166b897c4f033831444d..c91a29cb298a3ad792ff8745f3e8e0eb28d71678 100644 (file)
--- a/abspath.c
+++ b/abspath.c
@@ -18,7 +18,7 @@ const char *make_absolute_path(const char *path)
 {
        static char bufs[2][PATH_MAX + 1], *buf = bufs[0], *next_buf = bufs[1];
        char cwd[1024] = "";
-       int buf_index = 1, len;
+       int buf_index = 1;
 
        int depth = MAXDEPTH;
        char *last_elem = NULL;
@@ -41,29 +41,30 @@ const char *make_absolute_path(const char *path)
 
                if (*buf) {
                        if (!*cwd && !getcwd(cwd, sizeof(cwd)))
-                               die ("Could not get current working directory");
+                               die_errno ("Could not get current working directory");
 
                        if (chdir(buf))
-                               die ("Could not switch to '%s'", buf);
+                               die_errno ("Could not switch to '%s'", buf);
                }
                if (!getcwd(buf, PATH_MAX))
-                       die ("Could not get current working directory");
+                       die_errno ("Could not get current working directory");
 
                if (last_elem) {
-                       int len = strlen(buf);
+                       size_t len = strlen(buf);
                        if (len + strlen(last_elem) + 2 > PATH_MAX)
                                die ("Too long path name: '%s/%s'",
                                                buf, last_elem);
-                       buf[len] = '/';
-                       strcpy(buf + len + 1, last_elem);
+                       if (len && buf[len-1] != '/')
+                               buf[len++] = '/';
+                       strcpy(buf + len, last_elem);
                        free(last_elem);
                        last_elem = NULL;
                }
 
                if (!lstat(buf, &st) && S_ISLNK(st.st_mode)) {
-                       len = readlink(buf, next_buf, PATH_MAX);
+                       ssize_t len = readlink(buf, next_buf, PATH_MAX);
                        if (len < 0)
-                               die ("Invalid symlink: %s", buf);
+                               die_errno ("Invalid symlink '%s'", buf);
                        if (PATH_MAX <= len)
                                die("symbolic link too long: %s", buf);
                        next_buf[len] = '\0';
@@ -75,7 +76,7 @@ const char *make_absolute_path(const char *path)
        }
 
        if (*cwd && chdir(cwd))
-               die ("Could not change back to '%s'", cwd);
+               die_errno ("Could not change back to '%s'", cwd);
 
        return buf;
 }
@@ -109,7 +110,7 @@ const char *make_nonrelative_path(const char *path)
        } else {
                const char *cwd = get_pwd_cwd();
                if (!cwd)
-                       die("Cannot determine the current working directory");
+                       die_errno("Cannot determine the current working directory");
                if (snprintf(buf, PATH_MAX, "%s/%s", cwd, path) >= PATH_MAX)
                        die("Too long path: %.*s", 60, path);
        }
diff --git a/aclocal.m4 b/aclocal.m4
new file mode 100644 (file)
index 0000000..d399de2
--- /dev/null
@@ -0,0 +1,40 @@
+dnl Check for socklen_t: historically on BSD it is an int, and in
+dnl POSIX 1g it is a type of its own, but some platforms use different
+dnl types for the argument to getsockopt, getpeername, etc.  So we
+dnl have to test to find something that will work.
+AC_DEFUN([TYPE_SOCKLEN_T],
+[
+   AC_CHECK_TYPE([socklen_t], ,[
+      AC_MSG_CHECKING([for socklen_t equivalent])
+      AC_CACHE_VAL([git_cv_socklen_t_equiv],
+      [
+         # Systems have either "struct sockaddr *" or
+         # "void *" as the second argument to getpeername
+         git_cv_socklen_t_equiv=
+         for arg2 in "struct sockaddr" void; do
+            for t in int size_t unsigned long "unsigned long"; do
+               AC_TRY_COMPILE([
+                  #include <sys/types.h>
+                  #include <sys/socket.h>
+
+                  int getpeername (int, $arg2 *, $t *);
+               ],[
+                  $t len;
+                  getpeername(0,0,&len);
+               ],[
+                  git_cv_socklen_t_equiv="$t"
+                  break 2
+               ])
+            done
+         done
+
+         if test "x$git_cv_socklen_t_equiv" = x; then
+            AC_MSG_ERROR([Cannot find a type to use in place of socklen_t])
+         fi
+      ])
+      AC_MSG_RESULT($git_cv_socklen_t_equiv)
+      AC_DEFINE_UNQUOTED(socklen_t, $git_cv_socklen_t_equiv,
+                       [type to use in place of socklen_t if not defined])],
+      [#include <sys/types.h>
+#include <sys/socket.h>])
+])
diff --git a/advice.c b/advice.c
new file mode 100644 (file)
index 0000000..0be4b5f
--- /dev/null
+++ b/advice.c
@@ -0,0 +1,49 @@
+#include "cache.h"
+
+int advice_push_nonfastforward = 1;
+int advice_status_hints = 1;
+int advice_commit_before_merge = 1;
+int advice_resolve_conflict = 1;
+int advice_implicit_identity = 1;
+int advice_detached_head = 1;
+
+static struct {
+       const char *name;
+       int *preference;
+} advice_config[] = {
+       { "pushnonfastforward", &advice_push_nonfastforward },
+       { "statushints", &advice_status_hints },
+       { "commitbeforemerge", &advice_commit_before_merge },
+       { "resolveconflict", &advice_resolve_conflict },
+       { "implicitidentity", &advice_implicit_identity },
+       { "detachedhead", &advice_detached_head },
+};
+
+int git_default_advice_config(const char *var, const char *value)
+{
+       const char *k = skip_prefix(var, "advice.");
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(advice_config); i++) {
+               if (strcmp(k, advice_config[i].name))
+                       continue;
+               *advice_config[i].preference = git_config_bool(var, value);
+               return 0;
+       }
+
+       return 0;
+}
+
+void NORETURN die_resolve_conflict(const char *me)
+{
+       if (advice_resolve_conflict)
+               /*
+                * Message used both when 'git commit' fails and when
+                * other commands doing a merge do.
+                */
+               die("'%s' is not possible because you have unmerged files.\n"
+                   "Please, fix them up in the work tree, and then use 'git add/rm <file>' as\n"
+                   "appropriate to mark resolution and make a commit, or use 'git commit -a'.", me);
+       else
+               die("'%s' is not possible because you have unmerged files.", me);
+}
diff --git a/advice.h b/advice.h
new file mode 100644 (file)
index 0000000..3244ebb
--- /dev/null
+++ b/advice.h
@@ -0,0 +1,17 @@
+#ifndef ADVICE_H
+#define ADVICE_H
+
+#include "git-compat-util.h"
+
+extern int advice_push_nonfastforward;
+extern int advice_status_hints;
+extern int advice_commit_before_merge;
+extern int advice_resolve_conflict;
+extern int advice_implicit_identity;
+extern int advice_detached_head;
+
+int git_default_advice_config(const char *var, const char *value);
+
+extern void NORETURN die_resolve_conflict(const char *me);
+
+#endif /* ADVICE_H */
diff --git a/alias.c b/alias.c
index e687fe54c1ce2a435e8f0d7806db231c06f998eb..eb9f08b912b2089d434926fc6083ac5ab90c73bc 100644 (file)
--- a/alias.c
+++ b/alias.c
@@ -22,6 +22,13 @@ char *alias_lookup(const char *alias)
        return alias_val;
 }
 
+#define SPLIT_CMDLINE_BAD_ENDING 1
+#define SPLIT_CMDLINE_UNCLOSED_QUOTE 2
+static const char *split_cmdline_errors[] = {
+       "cmdline ends with \\",
+       "unclosed quote"
+};
+
 int split_cmdline(char *cmdline, const char ***argv)
 {
        int src, dst, count = 0, size = 16;
@@ -38,10 +45,7 @@ int split_cmdline(char *cmdline, const char ***argv)
                        while (cmdline[++src]
                                        && isspace(cmdline[src]))
                                ; /* skip */
-                       if (count >= size) {
-                               size += 16;
-                               *argv = xrealloc(*argv, sizeof(char *) * size);
-                       }
+                       ALLOC_GROW(*argv, count+1, size);
                        (*argv)[count++] = cmdline + dst;
                } else if (!quoted && (c == '\'' || c == '"')) {
                        quoted = c;
@@ -56,7 +60,7 @@ int split_cmdline(char *cmdline, const char ***argv)
                                if (!c) {
                                        free(*argv);
                                        *argv = NULL;
-                                       return error("cmdline ends with \\");
+                                       return -SPLIT_CMDLINE_BAD_ENDING;
                                }
                        }
                        cmdline[dst++] = c;
@@ -69,9 +73,15 @@ int split_cmdline(char *cmdline, const char ***argv)
        if (quoted) {
                free(*argv);
                *argv = NULL;
-               return error("unclosed quote");
+               return -SPLIT_CMDLINE_UNCLOSED_QUOTE;
        }
 
+       ALLOC_GROW(*argv, count+1, size);
+       (*argv)[count] = NULL;
+
        return count;
 }
 
+const char *split_cmdline_strerror(int split_cmdline_errno) {
+       return split_cmdline_errors[-split_cmdline_errno-1];
+}
index ba890ebdecd8672aeb32757605c8a2976fa21161..cee06ce3cbc1819008337633ed0753638c423ac5 100644 (file)
@@ -180,7 +180,7 @@ static int write_tar_entry(struct archiver_args *args,
 
        sprintf(header.mode, "%07o", mode & 07777);
        sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0);
-       sprintf(header.mtime, "%011lo", args->time);
+       sprintf(header.mtime, "%011lo", (unsigned long) args->time);
 
        sprintf(header.uid, "%07o", 0);
        sprintf(header.gid, "%07o", 0);
index b2b90d31700d3aa50a3ac0626c1a8561c37c49b7..edd68534fa847ad839fe8554e6864b39de4fcde0 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -31,6 +31,9 @@ static void format_subst(const struct commit *commit,
 {
        char *to_free = NULL;
        struct strbuf fmt = STRBUF_INIT;
+       struct pretty_print_context ctx = {0};
+       ctx.date_mode = DATE_NORMAL;
+       ctx.abbrev = DEFAULT_ABBREV;
 
        if (src == buf->buf)
                to_free = strbuf_detach(buf, NULL);
@@ -48,7 +51,7 @@ static void format_subst(const struct commit *commit,
                strbuf_add(&fmt, b + 8, c - b - 8);
 
                strbuf_add(buf, src, b - src);
-               format_commit_message(commit, fmt.buf, buf, DATE_NORMAL);
+               format_commit_message(commit, fmt.buf, buf, &ctx);
                len -= c + 1 - src;
                src  = c + 1;
        }
@@ -85,8 +88,8 @@ static void setup_archive_check(struct git_attr_check *check)
        static struct git_attr *attr_export_subst;
 
        if (!attr_export_ignore) {
-               attr_export_ignore = git_attr("export-ignore", 13);
-               attr_export_subst = git_attr("export-subst", 12);
+               attr_export_ignore = git_attr("export-ignore");
+               attr_export_subst = git_attr("export-subst");
        }
        check[0].attr = attr_export_ignore;
        check[1].attr = attr_export_subst;
@@ -115,6 +118,7 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
 
        strbuf_reset(&path);
        strbuf_grow(&path, PATH_MAX);
+       strbuf_add(&path, args->base, args->baselen);
        strbuf_add(&path, base, baselen);
        strbuf_addstr(&path, filename);
        path_without_prefix = path.buf + args->baselen;
@@ -187,8 +191,8 @@ int write_archive_entries(struct archiver_args *args,
                git_attr_set_direction(GIT_ATTR_INDEX, &the_index);
        }
 
-       err =  read_tree_recursive(args->tree, args->base, args->baselen, 0,
-                       args->pathspec, write_archive_entry, &context);
+       err = read_tree_recursive(args->tree, "", 0, 0, args->pathspec,
+                                 write_archive_entry, &context);
        if (err == READ_TREE_RECURSIVE)
                err = 0;
        return err;
@@ -208,10 +212,33 @@ static const struct archiver *lookup_archiver(const char *name)
        return NULL;
 }
 
+static int reject_entry(const unsigned char *sha1, const char *base,
+                       int baselen, const char *filename, unsigned mode,
+                       int stage, void *context)
+{
+       return -1;
+}
+
+static int path_exists(struct tree *tree, const char *path)
+{
+       const char *pathspec[] = { path, NULL };
+
+       if (read_tree_recursive(tree, "", 0, 0, pathspec, reject_entry, NULL))
+               return 1;
+       return 0;
+}
+
 static void parse_pathspec_arg(const char **pathspec,
                struct archiver_args *ar_args)
 {
-       ar_args->pathspec = get_pathspec(ar_args->base, pathspec);
+       ar_args->pathspec = pathspec = get_pathspec("", pathspec);
+       if (pathspec) {
+               while (*pathspec) {
+                       if (!path_exists(ar_args->tree, *pathspec))
+                               die("path not found: %s", *pathspec);
+                       pathspec++;
+               }
+       }
 }
 
 static void parse_treeish_arg(const char **argv,
@@ -283,7 +310,7 @@ static int parse_archive_args(int argc, const char **argv,
                OPT_STRING(0, "format", &format, "fmt", "archive format"),
                OPT_STRING(0, "prefix", &base, "prefix",
                        "prepend prefix to each pathname in the archive"),
-               OPT_STRING(0, "output", &output, "file",
+               OPT_STRING('o', "output", &output, "file",
                        "write the archive to this file"),
                OPT_BOOLEAN(0, "worktree-attributes", &worktree_attributes,
                        "read .gitattributes in working directory"),
@@ -309,7 +336,7 @@ static int parse_archive_args(int argc, const char **argv,
                OPT_END()
        };
 
-       argc = parse_options(argc, argv, opts, archive_usage, 0);
+       argc = parse_options(argc, argv, NULL, opts, archive_usage, 0);
 
        if (remote)
                die("Unexpected option --remote");
diff --git a/arm/sha1.c b/arm/sha1.c
deleted file mode 100644 (file)
index c61ad4a..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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 arm_sha_transform(uint32_t *hash, const unsigned char *data, uint32_t *W);
-
-void arm_SHA1_Init(arm_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 arm_SHA1_Update(arm_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);
-                       arm_sha_transform(c->hash, c->buffer, workspace);
-                       partial = 0;
-               } else
-                       done = 0;
-               while (n >= done + 64) {
-                       arm_sha_transform(c->hash, p + done, workspace);
-                       done += 64;
-               }
-       } else
-               done = 0;
-       if (n - done)
-               memcpy(c->buffer + partial, p + done, n - done);
-}
-
-void arm_SHA1_Final(unsigned char *hash, arm_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;
-       arm_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;
-       arm_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
deleted file mode 100644 (file)
index b61b618..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * SHA-1 implementation optimized for ARM
- *
- * Copyright:  (C) 2005 by Nicolas Pitre <nico@cam.org>
- * Created:    September 17, 2005
- */
-
-#include <stdint.h>
-
-typedef struct {
-       uint64_t len;
-       uint32_t hash[5];
-       unsigned char buffer[64];
-} arm_SHA_CTX;
-
-void arm_SHA1_Init(arm_SHA_CTX *c);
-void arm_SHA1_Update(arm_SHA_CTX *c, const void *p, unsigned long n);
-void arm_SHA1_Final(unsigned char *hash, arm_SHA_CTX *c);
-
-#define git_SHA_CTX    arm_SHA_CTX
-#define git_SHA1_Init  arm_SHA1_Init
-#define git_SHA1_Update        arm_SHA1_Update
-#define git_SHA1_Final arm_SHA1_Final
diff --git a/arm/sha1_arm.S b/arm/sha1_arm.S
deleted file mode 100644 (file)
index 41e9263..0000000
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- *  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  arm_sha_transform
-
-/*
- * void sha_transform(uint32_t *hash, const unsigned char *data, uint32_t *W);
- *
- * note: the "data" pointer may be unaligned.
- */
-
-arm_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/attr.c b/attr.c
index 98eb636f13d314c20e18e646c074e2511b3c891c..8ba606c933088e27ac08aabb546b764745f8187e 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -35,8 +35,7 @@ static struct git_attr *(git_attr_hash[HASHSIZE]);
 
 static unsigned hash_name(const char *name, int namelen)
 {
-       unsigned val = 0;
-       unsigned char c;
+       unsigned val = 0, c;
 
        while (namelen--) {
                c = *name++;
@@ -66,7 +65,7 @@ static int invalid_attr_name(const char *name, int namelen)
        return 0;
 }
 
-struct git_attr *git_attr(const char *name, int len)
+static struct git_attr *git_attr_internal(const char *name, int len)
 {
        unsigned hval = hash_name(name, len);
        unsigned pos = hval % HASHSIZE;
@@ -96,6 +95,11 @@ struct git_attr *git_attr(const char *name, int len)
        return a;
 }
 
+struct git_attr *git_attr(const char *name)
+{
+       return git_attr_internal(name, strlen(name));
+}
+
 /*
  * .gitattributes file is one line per record, each of which is
  *
@@ -163,7 +167,7 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
                else {
                        e->setto = xmemdupz(equals + 1, ep - equals - 1);
                }
-               e->attr = git_attr(cp, len);
+               e->attr = git_attr_internal(cp, len);
        }
        (*num_attr)++;
        return ep + strspn(ep, blank);
@@ -222,7 +226,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
                              sizeof(struct attr_state) * num_attr +
                              (is_macro ? 0 : namelen + 1));
                if (is_macro)
-                       res->u.attr = git_attr(name, namelen);
+                       res->u.attr = git_attr_internal(name, namelen);
                else {
                        res->u.pattern = (char *)&(res->state[num_attr]);
                        memcpy(res->u.pattern, name, namelen);
@@ -283,7 +287,7 @@ static void free_attr_elem(struct attr_stack *e)
 }
 
 static const char *builtin_attr[] = {
-       "[attr]binary -diff -crlf",
+       "[attr]binary -diff -text",
        NULL,
 };
 
@@ -556,6 +560,8 @@ static void prepare_attr_stack(const char *path, int dirlen)
                }
        }
 
+       strbuf_release(&pathbuf);
+
        /*
         * Finally push the "info" one at the top of the stack.
         */
@@ -588,20 +594,25 @@ static int path_matches(const char *pathname, int pathlen,
        return fnmatch(pattern, pathname + baselen, FNM_PATHNAME) == 0;
 }
 
+static int macroexpand_one(int attr_nr, int rem);
+
 static int fill_one(const char *what, struct match_attr *a, int rem)
 {
        struct git_attr_check *check = check_all_attr;
        int i;
 
-       for (i = 0; 0 < rem && i < a->num_attr; i++) {
+       for (i = a->num_attr - 1; 0 < rem && 0 <= i; i--) {
                struct git_attr *attr = a->state[i].attr;
                const char **n = &(check[attr->attr_nr].value);
                const char *v = a->state[i].setto;
 
                if (*n == ATTR__UNKNOWN) {
-                       debug_set(what, a->u.pattern, attr, v);
+                       debug_set(what,
+                                 a->is_macro ? a->u.attr->name : a->u.pattern,
+                                 attr, v);
                        *n = v;
                        rem--;
+                       rem = macroexpand_one(attr->attr_nr, rem);
                }
        }
        return rem;
@@ -623,19 +634,27 @@ static int fill(const char *path, int pathlen, struct attr_stack *stk, int rem)
        return rem;
 }
 
-static int macroexpand(struct attr_stack *stk, int rem)
+static int macroexpand_one(int attr_nr, int rem)
 {
+       struct attr_stack *stk;
+       struct match_attr *a = NULL;
        int i;
-       struct git_attr_check *check = check_all_attr;
 
-       for (i = stk->num_matches - 1; 0 < rem && 0 <= i; i--) {
-               struct match_attr *a = stk->attrs[i];
-               if (!a->is_macro)
-                       continue;
-               if (check[a->u.attr->attr_nr].value != ATTR__TRUE)
-                       continue;
+       if (check_all_attr[attr_nr].value != ATTR__TRUE)
+               return rem;
+
+       for (stk = attr_stack; !a && stk; stk = stk->prev)
+               for (i = stk->num_matches - 1; !a && 0 <= i; i--) {
+                       struct match_attr *ma = stk->attrs[i];
+                       if (!ma->is_macro)
+                               continue;
+                       if (ma->u.attr->attr_nr == attr_nr)
+                               a = ma;
+               }
+
+       if (a)
                rem = fill_one("expand", a, rem);
-       }
+
        return rem;
 }
 
@@ -660,9 +679,6 @@ int git_checkattr(const char *path, int num, struct git_attr_check *check)
        for (stk = attr_stack; 0 < rem && stk; stk = stk->prev)
                rem = fill(path, pathlen, stk, rem);
 
-       for (stk = attr_stack; 0 < rem && stk; stk = stk->prev)
-               rem = macroexpand(stk, rem);
-
        for (i = 0; i < num; i++) {
                const char *value = check_all_attr[check[i].attr->attr_nr].value;
                if (value == ATTR__UNKNOWN)
diff --git a/attr.h b/attr.h
index 69b5767ebc2189a8bf9d98ff88c1885ec8fcdb7d..8b3f19be67f17c1fbf6edb37ac3ea27002ca6415 100644 (file)
--- a/attr.h
+++ b/attr.h
@@ -8,7 +8,7 @@ struct git_attr;
  * Given a string, return the gitattribute object that
  * corresponds to it.
  */
-struct git_attr *git_attr(const char *, int);
+struct git_attr *git_attr(const char *);
 
 /* Internal use */
 extern const char git_attr__true[];
@@ -34,7 +34,7 @@ int git_checkattr(const char *path, int, struct git_attr_check *);
 enum git_attr_direction {
        GIT_ATTR_CHECKIN,
        GIT_ATTR_CHECKOUT,
-       GIT_ATTR_INDEX,
+       GIT_ATTR_INDEX
 };
 void git_attr_set_direction(enum git_attr_direction, struct index_state *);
 
index b88270f90844095b3d352cc4213cbebd95a7f420..781b5754f0e533008694e71ac7cfe2e52b2d0ac6 100644 (file)
--- a/base85.c
+++ b/base85.c
@@ -7,9 +7,9 @@
 #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)
+#define say(a) do { /* nothing */ } while (0)
+#define say1(a,b) do { /* nothing */ } while (0)
+#define say2(a,b,c) do { /* nothing */ } while (0)
 #endif
 
 static const char en85[] = {
@@ -57,14 +57,8 @@ int decode_85(char *dst, const char *buffer, int len)
                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 ||
+               /* Detect overflow. */
+               if (0xffffffff / 85 < acc ||
                    0xffffffff - de < (acc *= 85))
                        return error("invalid base85 sequence %.5s", buffer-5);
                acc += de;
@@ -84,14 +78,12 @@ int decode_85(char *dst, const char *buffer, int len)
 
 void encode_85(char *buf, const 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++;
+                       unsigned ch = *data++;
                        acc |= ch << cnt;
                        if (--bytes == 0)
                                break;
@@ -118,7 +110,7 @@ int main(int ac, char **av)
                int len = strlen(av[2]);
                encode_85(buf, av[2], len);
                if (len <= 26) len = len + 'A' - 1;
-               else len = len + 'a' - 26 + 1;
+               else len = len + 'a' - 26 - 1;
                printf("encoded: %c%s\n", len, buf);
                return 0;
        }
index 58f7e6f7738def184072247f934cce06d349c91d..060c042f8bcc2d402cb9908be3bdbd3bb180a862 100644 (file)
--- a/bisect.c
+++ b/bisect.c
@@ -6,15 +6,30 @@
 #include "list-objects.h"
 #include "quote.h"
 #include "sha1-lookup.h"
+#include "run-command.h"
+#include "log-tree.h"
 #include "bisect.h"
 
-static unsigned char (*skipped_sha1)[20];
-static int skipped_sha1_nr;
-static int skipped_sha1_alloc;
+struct sha1_array {
+       unsigned char (*sha1)[20];
+       int sha1_nr;
+       int sha1_alloc;
+       int sorted;
+};
+
+static struct sha1_array good_revs;
+static struct sha1_array skipped_revs;
+
+static const unsigned char *current_bad_sha1;
 
-static const char **rev_argv;
-static int rev_argv_nr;
-static int rev_argv_alloc;
+struct argv_array {
+       const char **argv;
+       int argv_nr;
+       int argv_alloc;
+};
+
+static const char *argv_checkout[] = {"checkout", "-q", NULL, "--", NULL};
+static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
 
 /* bits #0-15 in revision.h */
 
@@ -126,7 +141,8 @@ static void show_list(const char *debug, int counted, int nr,
                enum object_type type;
                unsigned long size;
                char *buf = read_sha1_file(commit->object.sha1, &type, &size);
-               char *ep, *sp;
+               const char *subject_start;
+               int subject_len;
 
                fprintf(stderr, "%c%c%c ",
                        (flags & TREESAME) ? ' ' : 'T',
@@ -141,13 +157,9 @@ static void show_list(const char *debug, int counted, int nr,
                        fprintf(stderr, " %.*s", 8,
                                sha1_to_hex(pp->item->object.sha1));
 
-               sp = strstr(buf, "\n\n");
-               if (sp) {
-                       sp += 2;
-                       for (ep = sp; *ep && *ep != '\n'; ep++)
-                               ;
-                       fprintf(stderr, " %.*s", (int)(ep - sp), sp);
-               }
+               subject_len = find_commit_subject(buf, &subject_start);
+               if (subject_len)
+                       fprintf(stderr, " %.*s", subject_len, subject_start);
                fprintf(stderr, "\n");
        }
 }
@@ -398,23 +410,37 @@ struct commit_list *find_bisection(struct commit_list *list,
        return best;
 }
 
+static void argv_array_push(struct argv_array *array, const char *string)
+{
+       ALLOC_GROW(array->argv, array->argv_nr + 1, array->argv_alloc);
+       array->argv[array->argv_nr++] = string;
+}
+
+static void argv_array_push_sha1(struct argv_array *array,
+                                const unsigned char *sha1,
+                                const char *format)
+{
+       struct strbuf buf = STRBUF_INIT;
+       strbuf_addf(&buf, format, sha1_to_hex(sha1));
+       argv_array_push(array, strbuf_detach(&buf, NULL));
+}
+
+static void sha1_array_push(struct sha1_array *array,
+                           const unsigned char *sha1)
+{
+       ALLOC_GROW(array->sha1, array->sha1_nr + 1, array->sha1_alloc);
+       hashcpy(array->sha1[array->sha1_nr++], sha1);
+}
+
 static int register_ref(const char *refname, const unsigned char *sha1,
                        int flags, void *cb_data)
 {
        if (!strcmp(refname, "bad")) {
-               ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc);
-               rev_argv[rev_argv_nr++] = xstrdup(sha1_to_hex(sha1));
+               current_bad_sha1 = sha1;
        } else if (!prefixcmp(refname, "good-")) {
-               const char *hex = sha1_to_hex(sha1);
-               char *good = xmalloc(strlen(hex) + 2);
-               *good = '^';
-               memcpy(good + 1, hex, strlen(hex) + 1);
-               ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc);
-               rev_argv[rev_argv_nr++] = good;
+               sha1_array_push(&good_revs, sha1);
        } else if (!prefixcmp(refname, "skip-")) {
-               ALLOC_GROW(skipped_sha1, skipped_sha1_nr + 1,
-                          skipped_sha1_alloc);
-               hashcpy(skipped_sha1[skipped_sha1_nr++], sha1);
+               sha1_array_push(&skipped_revs, sha1);
        }
 
        return 0;
@@ -425,14 +451,14 @@ static int read_bisect_refs(void)
        return for_each_ref_in("refs/bisect/", register_ref, NULL);
 }
 
-void read_bisect_paths(void)
+static void read_bisect_paths(struct argv_array *array)
 {
        struct strbuf str = STRBUF_INIT;
        const char *filename = git_path("BISECT_NAMES");
        FILE *fp = fopen(filename, "r");
 
        if (!fp)
-               die("Could not open file '%s': %s", filename, strerror(errno));
+               die_errno("Could not open file '%s'", filename);
 
        while (strbuf_getline(&str, fp, '\n') != EOF) {
                char *quoted;
@@ -440,8 +466,8 @@ void read_bisect_paths(void)
 
                strbuf_trim(&str);
                quoted = strbuf_detach(&str, NULL);
-               res = sq_dequote_to_argv(quoted, &rev_argv,
-                                        &rev_argv_nr, &rev_argv_alloc);
+               res = sq_dequote_to_argv(quoted, &array->argv,
+                                        &array->argv_nr, &array->argv_alloc);
                if (res)
                        die("Badly quoted content in file '%s': %s",
                            filename, quoted);
@@ -451,106 +477,535 @@ void read_bisect_paths(void)
        fclose(fp);
 }
 
-static int skipcmp(const void *a, const void *b)
+static int array_cmp(const void *a, const void *b)
 {
        return hashcmp(a, b);
 }
 
-static void prepare_skipped(void)
+static void sort_sha1_array(struct sha1_array *array)
 {
-       qsort(skipped_sha1, skipped_sha1_nr, sizeof(*skipped_sha1), skipcmp);
+       qsort(array->sha1, array->sha1_nr, sizeof(*array->sha1), array_cmp);
+
+       array->sorted = 1;
 }
 
-static const unsigned char *skipped_sha1_access(size_t index, void *table)
+static const unsigned char *sha1_access(size_t index, void *table)
 {
-       unsigned char (*skipped)[20] = table;
-       return skipped[index];
+       unsigned char (*array)[20] = table;
+       return array[index];
 }
 
-static int lookup_skipped(unsigned char *sha1)
+static int lookup_sha1_array(struct sha1_array *array,
+                            const unsigned char *sha1)
 {
-       return sha1_pos(sha1, skipped_sha1, skipped_sha1_nr,
-                       skipped_sha1_access);
+       if (!array->sorted)
+               sort_sha1_array(array);
+
+       return sha1_pos(sha1, array->sha1, array->sha1_nr, sha1_access);
 }
 
+static char *join_sha1_array_hex(struct sha1_array *array, char delim)
+{
+       struct strbuf joined_hexs = STRBUF_INIT;
+       int i;
+
+       for (i = 0; i < array->sha1_nr; i++) {
+               strbuf_addstr(&joined_hexs, sha1_to_hex(array->sha1[i]));
+               if (i + 1 < array->sha1_nr)
+                       strbuf_addch(&joined_hexs, delim);
+       }
+
+       return strbuf_detach(&joined_hexs, NULL);
+}
+
+/*
+ * In this function, passing a not NULL skipped_first is very special.
+ * It means that we want to know if the first commit in the list is
+ * skipped because we will want to test a commit away from it if it is
+ * indeed skipped.
+ * So if the first commit is skipped, we cannot take the shortcut to
+ * just "return list" when we find the first non skipped commit, we
+ * have to return a fully filtered list.
+ *
+ * We use (*skipped_first == -1) to mean "it has been found that the
+ * first commit is not skipped". In this case *skipped_first is set back
+ * to 0 just before the function returns.
+ */
 struct commit_list *filter_skipped(struct commit_list *list,
                                   struct commit_list **tried,
-                                  int show_all)
+                                  int show_all,
+                                  int *count,
+                                  int *skipped_first)
 {
        struct commit_list *filtered = NULL, **f = &filtered;
 
        *tried = NULL;
 
-       if (!skipped_sha1_nr)
-               return list;
+       if (skipped_first)
+               *skipped_first = 0;
+       if (count)
+               *count = 0;
 
-       prepare_skipped();
+       if (!skipped_revs.sha1_nr)
+               return list;
 
        while (list) {
                struct commit_list *next = list->next;
                list->next = NULL;
-               if (0 <= lookup_skipped(list->item->object.sha1)) {
+               if (0 <= lookup_sha1_array(&skipped_revs,
+                                          list->item->object.sha1)) {
+                       if (skipped_first && !*skipped_first)
+                               *skipped_first = 1;
                        /* Move current to tried list */
                        *tried = list;
                        tried = &list->next;
                } else {
-                       if (!show_all)
-                               return list;
+                       if (!show_all) {
+                               if (!skipped_first || !*skipped_first)
+                                       return list;
+                       } else if (skipped_first && !*skipped_first) {
+                               /* This means we know it's not skipped */
+                               *skipped_first = -1;
+                       }
                        /* Move current to filtered list */
                        *f = list;
                        f = &list->next;
+                       if (count)
+                               (*count)++;
                }
                list = next;
        }
 
+       if (skipped_first && *skipped_first == -1)
+               *skipped_first = 0;
+
        return filtered;
 }
 
-static void bisect_rev_setup(struct rev_info *revs, const char *prefix)
+#define PRN_MODULO 32768
+
+/*
+ * This is a pseudo random number generator based on "man 3 rand".
+ * It is not used properly because the seed is the argument and it
+ * is increased by one between each call, but that should not matter
+ * for this application.
+ */
+static int get_prn(int count) {
+       count = count * 1103515245 + 12345;
+       return ((unsigned)(count/65536) % PRN_MODULO);
+}
+
+/*
+ * Custom integer square root from
+ * http://en.wikipedia.org/wiki/Integer_square_root
+ */
+static int sqrti(int val)
+{
+       float d, x = val;
+
+       if (val == 0)
+               return 0;
+
+       do {
+               float y = (x + (float)val / x) / 2;
+               d = (y > x) ? y - x : x - y;
+               x = y;
+       } while (d >= 0.5);
+
+       return (int)x;
+}
+
+static struct commit_list *skip_away(struct commit_list *list, int count)
+{
+       struct commit_list *cur, *previous;
+       int prn, index, i;
+
+       prn = get_prn(count);
+       index = (count * prn / PRN_MODULO) * sqrti(prn) / sqrti(PRN_MODULO);
+
+       cur = list;
+       previous = NULL;
+
+       for (i = 0; cur; cur = cur->next, i++) {
+               if (i == index) {
+                       if (hashcmp(cur->item->object.sha1, current_bad_sha1))
+                               return cur;
+                       if (previous)
+                               return previous;
+                       return list;
+               }
+               previous = cur;
+       }
+
+       return list;
+}
+
+static struct commit_list *managed_skipped(struct commit_list *list,
+                                          struct commit_list **tried)
+{
+       int count, skipped_first;
+
+       *tried = NULL;
+
+       if (!skipped_revs.sha1_nr)
+               return list;
+
+       list = filter_skipped(list, tried, 0, &count, &skipped_first);
+
+       if (!skipped_first)
+               return list;
+
+       return skip_away(list, count);
+}
+
+static void bisect_rev_setup(struct rev_info *revs, const char *prefix,
+                            const char *bad_format, const char *good_format,
+                            int read_paths)
 {
+       struct argv_array rev_argv = { NULL, 0, 0 };
+       int i;
+
        init_revisions(revs, prefix);
        revs->abbrev = 0;
        revs->commit_format = CMIT_FMT_UNSPECIFIED;
 
-       /* argv[0] will be ignored by setup_revisions */
-       ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc);
-       rev_argv[rev_argv_nr++] = xstrdup("bisect_rev_setup");
+       /* rev_argv.argv[0] will be ignored by setup_revisions */
+       argv_array_push(&rev_argv, xstrdup("bisect_rev_setup"));
+       argv_array_push_sha1(&rev_argv, current_bad_sha1, bad_format);
+       for (i = 0; i < good_revs.sha1_nr; i++)
+               argv_array_push_sha1(&rev_argv, good_revs.sha1[i],
+                                    good_format);
+       argv_array_push(&rev_argv, xstrdup("--"));
+       if (read_paths)
+               read_bisect_paths(&rev_argv);
+       argv_array_push(&rev_argv, NULL);
+
+       setup_revisions(rev_argv.argv_nr, rev_argv.argv, revs, NULL);
+}
 
-       if (read_bisect_refs())
-               die("reading bisect refs failed");
+static void bisect_common(struct rev_info *revs)
+{
+       if (prepare_revision_walk(revs))
+               die("revision walk setup failed");
+       if (revs->tree_objects)
+               mark_edges_uninteresting(revs->commits, revs, NULL);
+}
+
+static void exit_if_skipped_commits(struct commit_list *tried,
+                                   const unsigned char *bad)
+{
+       if (!tried)
+               return;
+
+       printf("There are only 'skip'ped commits left to test.\n"
+              "The first bad commit could be any of:\n");
+       print_commit_list(tried, "%s\n", "%s\n");
+       if (bad)
+               printf("%s\n", sha1_to_hex(bad));
+       printf("We cannot bisect more!\n");
+       exit(2);
+}
 
-       ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc);
-       rev_argv[rev_argv_nr++] = xstrdup("--");
+static int is_expected_rev(const unsigned char *sha1)
+{
+       const char *filename = git_path("BISECT_EXPECTED_REV");
+       struct stat st;
+       struct strbuf str = STRBUF_INIT;
+       FILE *fp;
+       int res = 0;
 
-       read_bisect_paths();
+       if (stat(filename, &st) || !S_ISREG(st.st_mode))
+               return 0;
+
+       fp = fopen(filename, "r");
+       if (!fp)
+               return 0;
 
-       ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc);
-       rev_argv[rev_argv_nr++] = NULL;
+       if (strbuf_getline(&str, fp, '\n') != EOF)
+               res = !strcmp(str.buf, sha1_to_hex(sha1));
 
-       setup_revisions(rev_argv_nr, rev_argv, revs, NULL);
+       strbuf_release(&str);
+       fclose(fp);
 
-       revs->limited = 1;
+       return res;
 }
 
-int bisect_next_vars(const char *prefix)
+static void mark_expected_rev(char *bisect_rev_hex)
+{
+       int len = strlen(bisect_rev_hex);
+       const char *filename = git_path("BISECT_EXPECTED_REV");
+       int fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
+
+       if (fd < 0)
+               die_errno("could not create file '%s'", filename);
+
+       bisect_rev_hex[len] = '\n';
+       write_or_die(fd, bisect_rev_hex, len + 1);
+       bisect_rev_hex[len] = '\0';
+
+       if (close(fd) < 0)
+               die("closing file %s: %s", filename, strerror(errno));
+}
+
+static int bisect_checkout(char *bisect_rev_hex)
+{
+       int res;
+
+       mark_expected_rev(bisect_rev_hex);
+
+       argv_checkout[2] = bisect_rev_hex;
+       res = run_command_v_opt(argv_checkout, RUN_GIT_CMD);
+       if (res)
+               exit(res);
+
+       argv_show_branch[1] = bisect_rev_hex;
+       return run_command_v_opt(argv_show_branch, RUN_GIT_CMD);
+}
+
+static struct commit *get_commit_reference(const unsigned char *sha1)
+{
+       struct commit *r = lookup_commit_reference(sha1);
+       if (!r)
+               die("Not a valid commit name %s", sha1_to_hex(sha1));
+       return r;
+}
+
+static struct commit **get_bad_and_good_commits(int *rev_nr)
+{
+       int len = 1 + good_revs.sha1_nr;
+       struct commit **rev = xmalloc(len * sizeof(*rev));
+       int i, n = 0;
+
+       rev[n++] = get_commit_reference(current_bad_sha1);
+       for (i = 0; i < good_revs.sha1_nr; i++)
+               rev[n++] = get_commit_reference(good_revs.sha1[i]);
+       *rev_nr = n;
+
+       return rev;
+}
+
+static void handle_bad_merge_base(void)
+{
+       if (is_expected_rev(current_bad_sha1)) {
+               char *bad_hex = sha1_to_hex(current_bad_sha1);
+               char *good_hex = join_sha1_array_hex(&good_revs, ' ');
+
+               fprintf(stderr, "The merge base %s is bad.\n"
+                       "This means the bug has been fixed "
+                       "between %s and [%s].\n",
+                       bad_hex, bad_hex, good_hex);
+
+               exit(3);
+       }
+
+       fprintf(stderr, "Some good revs are not ancestor of the bad rev.\n"
+               "git bisect cannot work properly in this case.\n"
+               "Maybe you mistake good and bad revs?\n");
+       exit(1);
+}
+
+static void handle_skipped_merge_base(const unsigned char *mb)
+{
+       char *mb_hex = sha1_to_hex(mb);
+       char *bad_hex = sha1_to_hex(current_bad_sha1);
+       char *good_hex = join_sha1_array_hex(&good_revs, ' ');
+
+       warning("the merge base between %s and [%s] "
+               "must be skipped.\n"
+               "So we cannot be sure the first bad commit is "
+               "between %s and %s.\n"
+               "We continue anyway.",
+               bad_hex, good_hex, mb_hex, bad_hex);
+       free(good_hex);
+}
+
+/*
+ * "check_merge_bases" checks that merge bases are not "bad".
+ *
+ * - If one is "bad", it means the user assumed something wrong
+ * and we must exit with a non 0 error code.
+ * - If one is "good", that's good, we have nothing to do.
+ * - If one is "skipped", we can't know but we should warn.
+ * - If we don't know, we should check it out and ask the user to test.
+ */
+static void check_merge_bases(void)
+{
+       struct commit_list *result;
+       int rev_nr;
+       struct commit **rev = get_bad_and_good_commits(&rev_nr);
+
+       result = get_merge_bases_many(rev[0], rev_nr - 1, rev + 1, 0);
+
+       for (; result; result = result->next) {
+               const unsigned char *mb = result->item->object.sha1;
+               if (!hashcmp(mb, current_bad_sha1)) {
+                       handle_bad_merge_base();
+               } else if (0 <= lookup_sha1_array(&good_revs, mb)) {
+                       continue;
+               } else if (0 <= lookup_sha1_array(&skipped_revs, mb)) {
+                       handle_skipped_merge_base(mb);
+               } else {
+                       printf("Bisecting: a merge base must be tested\n");
+                       exit(bisect_checkout(sha1_to_hex(mb)));
+               }
+       }
+
+       free(rev);
+       free_commit_list(result);
+}
+
+static int check_ancestors(const char *prefix)
 {
        struct rev_info revs;
-       struct rev_list_info info;
-       int reaches = 0, all = 0;
+       struct object_array pending_copy;
+       int i, res;
 
-       memset(&info, 0, sizeof(info));
-       info.revs = &revs;
-       info.bisect_show_flags = BISECT_SHOW_TRIED | BISECT_SHOW_STRINGED;
+       bisect_rev_setup(&revs, prefix, "^%s", "%s", 0);
 
-       bisect_rev_setup(&revs, prefix);
+       /* Save pending objects, so they can be cleaned up later. */
+       memset(&pending_copy, 0, sizeof(pending_copy));
+       for (i = 0; i < revs.pending.nr; i++)
+               add_object_array(revs.pending.objects[i].item,
+                                revs.pending.objects[i].name,
+                                &pending_copy);
 
-       if (prepare_revision_walk(&revs))
-               die("revision walk setup failed");
-       if (revs.tree_objects)
-               mark_edges_uninteresting(revs.commits, &revs, NULL);
+       bisect_common(&revs);
+       res = (revs.commits != NULL);
+
+       /* Clean up objects used, as they will be reused. */
+       for (i = 0; i < pending_copy.nr; i++) {
+               struct object *o = pending_copy.objects[i].item;
+               clear_commit_marks((struct commit *)o, ALL_REV_FLAGS);
+       }
+
+       return res;
+}
+
+/*
+ * "check_good_are_ancestors_of_bad" checks that all "good" revs are
+ * ancestor of the "bad" rev.
+ *
+ * If that's not the case, we need to check the merge bases.
+ * If a merge base must be tested by the user, its source code will be
+ * checked out to be tested by the user and we will exit.
+ */
+static void check_good_are_ancestors_of_bad(const char *prefix)
+{
+       const char *filename = git_path("BISECT_ANCESTORS_OK");
+       struct stat st;
+       int fd;
+
+       if (!current_bad_sha1)
+               die("a bad revision is needed");
+
+       /* Check if file BISECT_ANCESTORS_OK exists. */
+       if (!stat(filename, &st) && S_ISREG(st.st_mode))
+               return;
+
+       /* Bisecting with no good rev is ok. */
+       if (good_revs.sha1_nr == 0)
+               return;
+
+       /* Check if all good revs are ancestor of the bad rev. */
+       if (check_ancestors(prefix))
+               check_merge_bases();
+
+       /* Create file BISECT_ANCESTORS_OK. */
+       fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
+       if (fd < 0)
+               warning("could not create file '%s': %s",
+                       filename, strerror(errno));
+       else
+               close(fd);
+}
+
+/*
+ * This does "git diff-tree --pretty COMMIT" without one fork+exec.
+ */
+static void show_diff_tree(const char *prefix, struct commit *commit)
+{
+       struct rev_info opt;
+
+       /* diff-tree init */
+       init_revisions(&opt, prefix);
+       git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
+       opt.abbrev = 0;
+       opt.diff = 1;
+
+       /* This is what "--pretty" does */
+       opt.verbose_header = 1;
+       opt.use_terminator = 0;
+       opt.commit_format = CMIT_FMT_DEFAULT;
+
+       /* diff-tree init */
+       if (!opt.diffopt.output_format)
+               opt.diffopt.output_format = DIFF_FORMAT_RAW;
+
+       log_tree_commit(&opt, commit);
+}
+
+/*
+ * We use the convention that exiting with an exit code 10 means that
+ * the bisection process finished successfully.
+ * In this case the calling shell script should exit 0.
+ */
+int bisect_next_all(const char *prefix)
+{
+       struct rev_info revs;
+       struct commit_list *tried;
+       int reaches = 0, all = 0, nr, steps;
+       const unsigned char *bisect_rev;
+       char bisect_rev_hex[41];
+
+       if (read_bisect_refs())
+               die("reading bisect refs failed");
+
+       check_good_are_ancestors_of_bad(prefix);
+
+       bisect_rev_setup(&revs, prefix, "%s", "^%s", 1);
+       revs.limited = 1;
+
+       bisect_common(&revs);
 
        revs.commits = find_bisection(revs.commits, &reaches, &all,
-                                     !!skipped_sha1_nr);
+                                      !!skipped_revs.sha1_nr);
+       revs.commits = managed_skipped(revs.commits, &tried);
+
+       if (!revs.commits) {
+               /*
+                * We should exit here only if the "bad"
+                * commit is also a "skip" commit.
+                */
+               exit_if_skipped_commits(tried, NULL);
+
+               printf("%s was both good and bad\n",
+                      sha1_to_hex(current_bad_sha1));
+               exit(1);
+       }
+
+       if (!all) {
+               fprintf(stderr, "No testable commit found.\n"
+                       "Maybe you started with bad path parameters?\n");
+               exit(4);
+       }
 
-       return show_bisect_vars(&info, reaches, all);
+       bisect_rev = revs.commits->item->object.sha1;
+       memcpy(bisect_rev_hex, sha1_to_hex(bisect_rev), 41);
+
+       if (!hashcmp(bisect_rev, current_bad_sha1)) {
+               exit_if_skipped_commits(tried, current_bad_sha1);
+               printf("%s is the first bad commit\n", bisect_rev_hex);
+               show_diff_tree(prefix, revs.commits->item);
+               /* This means the bisection process succeeded. */
+               exit(10);
+       }
+
+       nr = all - reaches - 1;
+       steps = estimate_bisect_steps(all);
+       printf("Bisecting: %d revision%s left to test after this "
+              "(roughly %d step%s)\n", nr, (nr == 1 ? "" : "s"),
+              steps, (steps == 1 ? "" : "s"));
+
+       return bisect_checkout(bisect_rev_hex);
 }
+
index fdba9138773f6f6a4f36655f81965e955e79b3bc..0862ce56d76e9b08ab913e6a472fac590974340e 100644 (file)
--- a/bisect.h
+++ b/bisect.h
@@ -7,12 +7,17 @@ extern struct commit_list *find_bisection(struct commit_list *list,
 
 extern struct commit_list *filter_skipped(struct commit_list *list,
                                          struct commit_list **tried,
-                                         int show_all);
+                                         int show_all,
+                                         int *count,
+                                         int *skipped_first);
+
+extern void print_commit_list(struct commit_list *list,
+                             const char *format_cur,
+                             const char *format_last);
 
 /* bisect_show_flags flags in struct rev_list_info */
 #define BISECT_SHOW_ALL                (1<<0)
 #define BISECT_SHOW_TRIED      (1<<1)
-#define BISECT_SHOW_STRINGED   (1<<2)
 
 struct rev_list_info {
        struct rev_info *revs;
@@ -22,8 +27,8 @@ struct rev_list_info {
        const char *header_prefix;
 };
 
-extern int show_bisect_vars(struct rev_list_info *info, int reaches, int all);
+extern int bisect_next_all(const char *prefix);
 
-extern int bisect_next_vars(const char *prefix);
+extern int estimate_bisect_steps(int all);
 
 #endif
diff --git a/blob.c b/blob.c
index bd7d078e1ae5fe4ce0a16fda62a2c1743237941b..ae320bd8fa22aaaef8144bd6bc35c20d1e85e4f4 100644 (file)
--- a/blob.c
+++ b/blob.c
@@ -23,24 +23,3 @@ int parse_blob_buffer(struct blob *item, void *buffer, unsigned long size)
        item->object.parsed = 1;
        return 0;
 }
-
-int parse_blob(struct blob *item)
-{
-        enum object_type type;
-        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 (type != OBJ_BLOB)
-                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
index ea5d9e9f8b63be2c7048d19ee53feb06b0795c80..59b394eea38494d5dfa525e28ca949e5a03efcf5 100644 (file)
--- a/blob.h
+++ b/blob.h
@@ -13,6 +13,13 @@ 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);
+/**
+ * Blobs do not contain references to other objects and do not have
+ * structured data that needs parsing. However, code may use the
+ * "parsed" bit in the struct object for a blob to determine whether
+ * its content has been found to actually be available, so
+ * parse_blob_buffer() is used (by object.c) to flag that the object
+ * has been read successfully from the database.
+ **/
 
 #endif /* BLOB_H */
diff --git a/block-sha1/sha1.c b/block-sha1/sha1.c
new file mode 100644 (file)
index 0000000..c0054a0
--- /dev/null
@@ -0,0 +1,283 @@
+/*
+ * SHA1 routine optimized to do word accesses rather than byte accesses,
+ * and to avoid unnecessary copies into the context array.
+ *
+ * This was initially based on the Mozilla SHA1 implementation, although
+ * none of the original Mozilla code remains.
+ */
+
+/* this is only to get definitions for memcpy(), ntohl() and htonl() */
+#include "../git-compat-util.h"
+
+#include "sha1.h"
+
+#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
+
+/*
+ * Force usage of rol or ror by selecting the one with the smaller constant.
+ * It _can_ generate slightly smaller code (a constant of 1 is special), but
+ * perhaps more importantly it's possibly faster on any uarch that does a
+ * rotate with a loop.
+ */
+
+#define SHA_ASM(op, x, n) ({ unsigned int __res; __asm__(op " %1,%0":"=r" (__res):"i" (n), "0" (x)); __res; })
+#define SHA_ROL(x,n)   SHA_ASM("rol", x, n)
+#define SHA_ROR(x,n)   SHA_ASM("ror", x, n)
+
+#else
+
+#define SHA_ROT(X,l,r) (((X) << (l)) | ((X) >> (r)))
+#define SHA_ROL(X,n)   SHA_ROT(X,n,32-(n))
+#define SHA_ROR(X,n)   SHA_ROT(X,32-(n),n)
+
+#endif
+
+/*
+ * If you have 32 registers or more, the compiler can (and should)
+ * try to change the array[] accesses into registers. However, on
+ * machines with less than ~25 registers, that won't really work,
+ * and at least gcc will make an unholy mess of it.
+ *
+ * So to avoid that mess which just slows things down, we force
+ * the stores to memory to actually happen (we might be better off
+ * with a 'W(t)=(val);asm("":"+m" (W(t))' there instead, as
+ * suggested by Artur Skawina - that will also make gcc unable to
+ * try to do the silly "optimize away loads" part because it won't
+ * see what the value will be).
+ *
+ * Ben Herrenschmidt reports that on PPC, the C version comes close
+ * to the optimized asm with this (ie on PPC you don't want that
+ * 'volatile', since there are lots of registers).
+ *
+ * On ARM we get the best code generation by forcing a full memory barrier
+ * between each SHA_ROUND, otherwise gcc happily get wild with spilling and
+ * the stack frame size simply explode and performance goes down the drain.
+ */
+
+#if defined(__i386__) || defined(__x86_64__)
+  #define setW(x, val) (*(volatile unsigned int *)&W(x) = (val))
+#elif defined(__GNUC__) && defined(__arm__)
+  #define setW(x, val) do { W(x) = (val); __asm__("":::"memory"); } while (0)
+#else
+  #define setW(x, val) (W(x) = (val))
+#endif
+
+/*
+ * Performance might be improved if the CPU architecture is OK with
+ * unaligned 32-bit loads and a fast ntohl() is available.
+ * Otherwise fall back to byte loads and shifts which is portable,
+ * and is faster on architectures with memory alignment issues.
+ */
+
+#if defined(__i386__) || defined(__x86_64__) || \
+    defined(_M_IX86) || defined(_M_X64) || \
+    defined(__ppc__) || defined(__ppc64__) || \
+    defined(__powerpc__) || defined(__powerpc64__) || \
+    defined(__s390__) || defined(__s390x__)
+
+#define get_be32(p)    ntohl(*(unsigned int *)(p))
+#define put_be32(p, v) do { *(unsigned int *)(p) = htonl(v); } while (0)
+
+#else
+
+#define get_be32(p)    ( \
+       (*((unsigned char *)(p) + 0) << 24) | \
+       (*((unsigned char *)(p) + 1) << 16) | \
+       (*((unsigned char *)(p) + 2) <<  8) | \
+       (*((unsigned char *)(p) + 3) <<  0) )
+#define put_be32(p, v) do { \
+       unsigned int __v = (v); \
+       *((unsigned char *)(p) + 0) = __v >> 24; \
+       *((unsigned char *)(p) + 1) = __v >> 16; \
+       *((unsigned char *)(p) + 2) = __v >>  8; \
+       *((unsigned char *)(p) + 3) = __v >>  0; } while (0)
+
+#endif
+
+/* This "rolls" over the 512-bit array */
+#define W(x) (array[(x)&15])
+
+/*
+ * Where do we get the source from? The first 16 iterations get it from
+ * the input data, the next mix it from the 512-bit array.
+ */
+#define SHA_SRC(t) get_be32(data + t)
+#define SHA_MIX(t) SHA_ROL(W(t+13) ^ W(t+8) ^ W(t+2) ^ W(t), 1)
+
+#define SHA_ROUND(t, input, fn, constant, A, B, C, D, E) do { \
+       unsigned int TEMP = input(t); setW(t, TEMP); \
+       E += TEMP + SHA_ROL(A,5) + (fn) + (constant); \
+       B = SHA_ROR(B, 2); } while (0)
+
+#define T_0_15(t, A, B, C, D, E)  SHA_ROUND(t, SHA_SRC, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E )
+#define T_16_19(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E )
+#define T_20_39(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0x6ed9eba1, A, B, C, D, E )
+#define T_40_59(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, ((B&C)+(D&(B^C))) , 0x8f1bbcdc, A, B, C, D, E )
+#define T_60_79(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) ,  0xca62c1d6, A, B, C, D, E )
+
+static void blk_SHA1_Block(blk_SHA_CTX *ctx, const unsigned int *data)
+{
+       unsigned int A,B,C,D,E;
+       unsigned int array[16];
+
+       A = ctx->H[0];
+       B = ctx->H[1];
+       C = ctx->H[2];
+       D = ctx->H[3];
+       E = ctx->H[4];
+
+       /* Round 1 - iterations 0-16 take their input from 'data' */
+       T_0_15( 0, A, B, C, D, E);
+       T_0_15( 1, E, A, B, C, D);
+       T_0_15( 2, D, E, A, B, C);
+       T_0_15( 3, C, D, E, A, B);
+       T_0_15( 4, B, C, D, E, A);
+       T_0_15( 5, A, B, C, D, E);
+       T_0_15( 6, E, A, B, C, D);
+       T_0_15( 7, D, E, A, B, C);
+       T_0_15( 8, C, D, E, A, B);
+       T_0_15( 9, B, C, D, E, A);
+       T_0_15(10, A, B, C, D, E);
+       T_0_15(11, E, A, B, C, D);
+       T_0_15(12, D, E, A, B, C);
+       T_0_15(13, C, D, E, A, B);
+       T_0_15(14, B, C, D, E, A);
+       T_0_15(15, A, B, C, D, E);
+
+       /* Round 1 - tail. Input from 512-bit mixing array */
+       T_16_19(16, E, A, B, C, D);
+       T_16_19(17, D, E, A, B, C);
+       T_16_19(18, C, D, E, A, B);
+       T_16_19(19, B, C, D, E, A);
+
+       /* Round 2 */
+       T_20_39(20, A, B, C, D, E);
+       T_20_39(21, E, A, B, C, D);
+       T_20_39(22, D, E, A, B, C);
+       T_20_39(23, C, D, E, A, B);
+       T_20_39(24, B, C, D, E, A);
+       T_20_39(25, A, B, C, D, E);
+       T_20_39(26, E, A, B, C, D);
+       T_20_39(27, D, E, A, B, C);
+       T_20_39(28, C, D, E, A, B);
+       T_20_39(29, B, C, D, E, A);
+       T_20_39(30, A, B, C, D, E);
+       T_20_39(31, E, A, B, C, D);
+       T_20_39(32, D, E, A, B, C);
+       T_20_39(33, C, D, E, A, B);
+       T_20_39(34, B, C, D, E, A);
+       T_20_39(35, A, B, C, D, E);
+       T_20_39(36, E, A, B, C, D);
+       T_20_39(37, D, E, A, B, C);
+       T_20_39(38, C, D, E, A, B);
+       T_20_39(39, B, C, D, E, A);
+
+       /* Round 3 */
+       T_40_59(40, A, B, C, D, E);
+       T_40_59(41, E, A, B, C, D);
+       T_40_59(42, D, E, A, B, C);
+       T_40_59(43, C, D, E, A, B);
+       T_40_59(44, B, C, D, E, A);
+       T_40_59(45, A, B, C, D, E);
+       T_40_59(46, E, A, B, C, D);
+       T_40_59(47, D, E, A, B, C);
+       T_40_59(48, C, D, E, A, B);
+       T_40_59(49, B, C, D, E, A);
+       T_40_59(50, A, B, C, D, E);
+       T_40_59(51, E, A, B, C, D);
+       T_40_59(52, D, E, A, B, C);
+       T_40_59(53, C, D, E, A, B);
+       T_40_59(54, B, C, D, E, A);
+       T_40_59(55, A, B, C, D, E);
+       T_40_59(56, E, A, B, C, D);
+       T_40_59(57, D, E, A, B, C);
+       T_40_59(58, C, D, E, A, B);
+       T_40_59(59, B, C, D, E, A);
+
+       /* Round 4 */
+       T_60_79(60, A, B, C, D, E);
+       T_60_79(61, E, A, B, C, D);
+       T_60_79(62, D, E, A, B, C);
+       T_60_79(63, C, D, E, A, B);
+       T_60_79(64, B, C, D, E, A);
+       T_60_79(65, A, B, C, D, E);
+       T_60_79(66, E, A, B, C, D);
+       T_60_79(67, D, E, A, B, C);
+       T_60_79(68, C, D, E, A, B);
+       T_60_79(69, B, C, D, E, A);
+       T_60_79(70, A, B, C, D, E);
+       T_60_79(71, E, A, B, C, D);
+       T_60_79(72, D, E, A, B, C);
+       T_60_79(73, C, D, E, A, B);
+       T_60_79(74, B, C, D, E, A);
+       T_60_79(75, A, B, C, D, E);
+       T_60_79(76, E, A, B, C, D);
+       T_60_79(77, D, E, A, B, C);
+       T_60_79(78, C, D, E, A, B);
+       T_60_79(79, B, C, D, E, A);
+
+       ctx->H[0] += A;
+       ctx->H[1] += B;
+       ctx->H[2] += C;
+       ctx->H[3] += D;
+       ctx->H[4] += E;
+}
+
+void blk_SHA1_Init(blk_SHA_CTX *ctx)
+{
+       ctx->size = 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;
+}
+
+void blk_SHA1_Update(blk_SHA_CTX *ctx, const void *data, unsigned long len)
+{
+       unsigned int lenW = ctx->size & 63;
+
+       ctx->size += len;
+
+       /* Read the data into W and process blocks as they get full */
+       if (lenW) {
+               unsigned int left = 64 - lenW;
+               if (len < left)
+                       left = len;
+               memcpy(lenW + (char *)ctx->W, data, left);
+               lenW = (lenW + left) & 63;
+               len -= left;
+               data = ((const char *)data + left);
+               if (lenW)
+                       return;
+               blk_SHA1_Block(ctx, ctx->W);
+       }
+       while (len >= 64) {
+               blk_SHA1_Block(ctx, data);
+               data = ((const char *)data + 64);
+               len -= 64;
+       }
+       if (len)
+               memcpy(ctx->W, data, len);
+}
+
+void blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx)
+{
+       static const unsigned char pad[64] = { 0x80 };
+       unsigned int padlen[2];
+       int i;
+
+       /* Pad with a binary 1 (ie 0x80), then zeroes, then length */
+       padlen[0] = htonl((uint32_t)(ctx->size >> 29));
+       padlen[1] = htonl((uint32_t)(ctx->size << 3));
+
+       i = ctx->size & 63;
+       blk_SHA1_Update(ctx, pad, 1+ (63 & (55 - i)));
+       blk_SHA1_Update(ctx, padlen, 8);
+
+       /* Output hash */
+       for (i = 0; i < 5; i++)
+               put_be32(hashout + i*4, ctx->H[i]);
+}
diff --git a/block-sha1/sha1.h b/block-sha1/sha1.h
new file mode 100644 (file)
index 0000000..b864df6
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * SHA1 routine optimized to do word accesses rather than byte accesses,
+ * and to avoid unnecessary copies into the context array.
+ *
+ * This was initially based on the Mozilla SHA1 implementation, although
+ * none of the original Mozilla code remains.
+ */
+
+typedef struct {
+       unsigned long long size;
+       unsigned int H[5];
+       unsigned int W[16];
+} blk_SHA_CTX;
+
+void blk_SHA1_Init(blk_SHA_CTX *ctx);
+void blk_SHA1_Update(blk_SHA_CTX *ctx, const void *dataIn, unsigned long len);
+void blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx);
+
+#define git_SHA_CTX    blk_SHA_CTX
+#define git_SHA1_Init  blk_SHA1_Init
+#define git_SHA1_Update        blk_SHA1_Update
+#define git_SHA1_Final blk_SHA1_Final
index 62030af4b51abbfb9c488dbf018770f3b3606789..93dc866f8c09a2da2308c4fd677178b043f2d4d5 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -49,9 +49,19 @@ static int should_setup_rebase(const char *origin)
 
 void install_branch_config(int flag, const char *local, const char *origin, const char *remote)
 {
+       const char *shortname = remote + 11;
+       int remote_is_branch = !prefixcmp(remote, "refs/heads/");
        struct strbuf key = STRBUF_INIT;
        int rebasing = should_setup_rebase(origin);
 
+       if (remote_is_branch
+           && !strcmp(local, shortname)
+           && !origin) {
+               warning("Not setting branch %s as its own upstream.",
+                       local);
+               return;
+       }
+
        strbuf_addf(&key, "branch.%s.remote", local);
        git_config_set(key.buf, origin ? origin : ".");
 
@@ -71,8 +81,8 @@ void install_branch_config(int flag, const char *local, const char *origin, cons
                strbuf_addstr(&key, origin ? "remote" : "local");
 
                /* Are we tracking a proper "branch"? */
-               if (!prefixcmp(remote, "refs/heads/")) {
-                       strbuf_addf(&key, " branch %s", remote + 11);
+               if (remote_is_branch) {
+                       strbuf_addf(&key, " branch %s", shortname);
                        if (origin)
                                strbuf_addf(&key, " from %s", origin);
                }
@@ -108,6 +118,7 @@ static int setup_tracking(const char *new_ref, const char *orig_ref,
                switch (track) {
                case BRANCH_TRACK_ALWAYS:
                case BRANCH_TRACK_EXPLICIT:
+               case BRANCH_TRACK_OVERRIDE:
                        break;
                default:
                        return 1;
@@ -128,20 +139,27 @@ void create_branch(const char *head,
                   const char *name, const char *start_name,
                   int force, int reflog, enum branch_track track)
 {
-       struct ref_lock *lock;
+       struct ref_lock *lock = NULL;
        struct commit *commit;
        unsigned char sha1[20];
        char *real_ref, msg[PATH_MAX + 20];
        struct strbuf ref = STRBUF_INIT;
        int forcing = 0;
+       int dont_change_ref = 0;
+       int explicit_tracking = 0;
+
+       if (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)
+               explicit_tracking = 1;
 
        if (strbuf_check_branch_ref(&ref, name))
                die("'%s' is not a valid branch name.", name);
 
        if (resolve_ref(ref.buf, sha1, 1, NULL)) {
-               if (!force)
+               if (!force && track == BRANCH_TRACK_OVERRIDE)
+                       dont_change_ref = 1;
+               else if (!force)
                        die("A branch named '%s' already exists.", name);
-               else if (!is_bare_repository() && !strcmp(head, name))
+               else if (!is_bare_repository() && head && !strcmp(head, name))
                        die("Cannot force update the current branch.");
                forcing = 1;
        }
@@ -153,12 +171,12 @@ void create_branch(const char *head,
        switch (dwim_ref(start_name, strlen(start_name), sha1, &real_ref)) {
        case 0:
                /* Not branching from any existing branch */
-               if (track == BRANCH_TRACK_EXPLICIT)
+               if (explicit_tracking)
                        die("Cannot setup tracking information; starting point is not a branch.");
                break;
        case 1:
                /* Unique completion -- good, only if it is a real ref */
-               if (track == BRANCH_TRACK_EXPLICIT && !strcmp(real_ref, "HEAD"))
+               if (explicit_tracking && !strcmp(real_ref, "HEAD"))
                        die("Cannot setup tracking information; starting point is not a branch.");
                break;
        default:
@@ -170,25 +188,28 @@ void create_branch(const char *head,
                die("Not a valid branch point: '%s'.", start_name);
        hashcpy(sha1, commit->object.sha1);
 
-       lock = lock_any_ref_for_update(ref.buf, NULL, 0);
-       if (!lock)
-               die("Failed to lock ref for update: %s.", strerror(errno));
+       if (!dont_change_ref) {
+               lock = lock_any_ref_for_update(ref.buf, NULL, 0);
+               if (!lock)
+                       die_errno("Failed to lock ref for update");
+       }
 
        if (reflog)
                log_all_ref_updates = 1;
 
        if (forcing)
-               snprintf(msg, sizeof msg, "branch: Reset from %s",
+               snprintf(msg, sizeof msg, "branch: Reset to %s",
                         start_name);
-       else
+       else if (!dont_change_ref)
                snprintf(msg, sizeof msg, "branch: Created from %s",
                         start_name);
 
        if (real_ref && track)
                setup_tracking(name, real_ref, track);
 
-       if (write_ref_sha1(lock, sha1, msg) < 0)
-               die("Failed to write ref: %s.", strerror(errno));
+       if (!dont_change_ref)
+               if (write_ref_sha1(lock, sha1, msg) < 0)
+                       die_errno("Failed to write ref");
 
        strbuf_release(&ref);
        free(real_ref);
diff --git a/builtin-add.c b/builtin-add.c
deleted file mode 100644 (file)
index cb67d2c..0000000
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * "git add" builtin command
- *
- * Copyright (C) 2006 Linus Torvalds
- */
-#include "cache.h"
-#include "builtin.h"
-#include "dir.h"
-#include "exec_cmd.h"
-#include "cache-tree.h"
-#include "run-command.h"
-#include "parse-options.h"
-
-static const char * const builtin_add_usage[] = {
-       "git add [options] [--] <filepattern>...",
-       NULL
-};
-static int patch_interactive, add_interactive;
-static int take_worktree_changes;
-
-static void fill_pathspec_matches(const char **pathspec, char *seen, int specs)
-{
-       int num_unmatched = 0, i;
-
-       /*
-        * Since we are walking the index as if we were walking the directory,
-        * we have to mark the matched pathspec as seen; otherwise we will
-        * mistakenly think that the user gave a pathspec that did not match
-        * anything.
-        */
-       for (i = 0; i < specs; i++)
-               if (!seen[i])
-                       num_unmatched++;
-       if (!num_unmatched)
-               return;
-       for (i = 0; i < active_nr; i++) {
-               struct cache_entry *ce = active_cache[i];
-               match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen);
-       }
-}
-
-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 = xcalloc(specs, 1);
-
-       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))
-                       *dst++ = entry;
-       }
-       dir->nr = dst - dir->entries;
-       fill_pathspec_matches(pathspec, seen, specs);
-
-       for (i = 0; i < specs; i++) {
-               if (!seen[i] && !file_exists(pathspec[i]))
-                       die("pathspec '%s' did not match any files",
-                                       pathspec[i]);
-       }
-        free(seen);
-}
-
-static void treat_gitlinks(const char **pathspec)
-{
-       int i;
-
-       if (!pathspec || !*pathspec)
-               return;
-
-       for (i = 0; i < active_nr; i++) {
-               struct cache_entry *ce = active_cache[i];
-               if (S_ISGITLINK(ce->ce_mode)) {
-                       int len = ce_namelen(ce), j;
-                       for (j = 0; pathspec[j]; j++) {
-                               int len2 = strlen(pathspec[j]);
-                               if (len2 <= len || pathspec[j][len] != '/' ||
-                                   memcmp(ce->name, pathspec[j], len))
-                                       continue;
-                               if (len2 == len + 1)
-                                       /* strip trailing slash */
-                                       pathspec[j] = xstrndup(ce->name, len);
-                               else
-                                       die ("Path '%s' is in submodule '%.*s'",
-                                               pathspec[j], len, ce->name);
-                       }
-               }
-       }
-}
-
-static void fill_directory(struct dir_struct *dir, const char **pathspec,
-               int ignored_too)
-{
-       const char *path, *base;
-       int baselen;
-
-       /* Set up the default git porcelain excludes */
-       memset(dir, 0, sizeof(*dir));
-       if (!ignored_too) {
-               dir->flags |= DIR_COLLECT_IGNORED;
-               setup_standard_excludes(dir);
-       }
-
-       /*
-        * Calculate common prefix for the pathspec, and
-        * use that to optimize the directory walk
-        */
-       baselen = common_prefix(pathspec);
-       path = ".";
-       base = "";
-       if (baselen)
-               path = base = xmemdupz(*pathspec, baselen);
-
-       /* Read the directory and prune it */
-       read_directory(dir, path, base, baselen, pathspec);
-       if (pathspec)
-               prune_directory(dir, pathspec, baselen);
-}
-
-static void refresh(int verbose, const char **pathspec)
-{
-       char *seen;
-       int i, specs;
-
-       for (specs = 0; pathspec[specs];  specs++)
-               /* nothing */;
-       seen = xcalloc(specs, 1);
-       refresh_index(&the_index, verbose ? REFRESH_SAY_CHANGED : REFRESH_QUIET,
-                     pathspec, seen);
-       for (i = 0; i < specs; i++) {
-               if (!seen[i])
-                       die("pathspec '%s' did not match any files", pathspec[i]);
-       }
-        free(seen);
-}
-
-static const char **validate_pathspec(int argc, const char **argv, const char *prefix)
-{
-       const char **pathspec = get_pathspec(prefix, argv);
-
-       if (pathspec) {
-               const char **p;
-               for (p = pathspec; *p; p++) {
-                       if (has_symlink_leading_path(*p, strlen(*p))) {
-                               int len = prefix ? strlen(prefix) : 0;
-                               die("'%s' is beyond a symbolic link", *p + len);
-                       }
-               }
-       }
-
-       return pathspec;
-}
-
-int interactive_add(int argc, const char **argv, const char *prefix)
-{
-       int status, ac;
-       const char **args;
-       const char **pathspec = NULL;
-
-       if (argc) {
-               pathspec = validate_pathspec(argc, argv, prefix);
-               if (!pathspec)
-                       return -1;
-       }
-
-       args = xcalloc(sizeof(const char *), (argc + 4));
-       ac = 0;
-       args[ac++] = "add--interactive";
-       if (patch_interactive)
-               args[ac++] = "--patch";
-       args[ac++] = "--";
-       if (argc) {
-               memcpy(&(args[ac]), pathspec, sizeof(const char *) * argc);
-               ac += argc;
-       }
-       args[ac] = NULL;
-
-       status = run_command_v_opt(args, RUN_GIT_CMD);
-       free(args);
-       return status;
-}
-
-static struct lock_file lock_file;
-
-static const char ignore_error[] =
-"The following paths are ignored by one of your .gitignore files:\n";
-
-static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0;
-static int ignore_add_errors, addremove, intent_to_add;
-
-static struct option builtin_add_options[] = {
-       OPT__DRY_RUN(&show_only),
-       OPT__VERBOSE(&verbose),
-       OPT_GROUP(""),
-       OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"),
-       OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"),
-       OPT_BOOLEAN('f', "force", &ignored_too, "allow adding otherwise ignored files"),
-       OPT_BOOLEAN('u', "update", &take_worktree_changes, "update tracked files"),
-       OPT_BOOLEAN('N', "intent-to-add", &intent_to_add, "record only the fact that the path will be added later"),
-       OPT_BOOLEAN('A', "all", &addremove, "add all, noticing removal of tracked files"),
-       OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"),
-       OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, "just skip files which cannot be added because of errors"),
-       OPT_END(),
-};
-
-static int add_config(const char *var, const char *value, void *cb)
-{
-       if (!strcasecmp(var, "add.ignore-errors")) {
-               ignore_add_errors = git_config_bool(var, value);
-               return 0;
-       }
-       return git_default_config(var, value, cb);
-}
-
-static int add_files(struct dir_struct *dir, int flags)
-{
-       int i, exit_status = 0;
-
-       if (dir->ignored_nr) {
-               fprintf(stderr, ignore_error);
-               for (i = 0; i < dir->ignored_nr; i++)
-                       fprintf(stderr, "%s\n", dir->ignored[i]->name);
-               fprintf(stderr, "Use -f if you really want to add them.\n");
-               die("no files added");
-       }
-
-       for (i = 0; i < dir->nr; i++)
-               if (add_file_to_cache(dir->entries[i]->name, flags)) {
-                       if (!ignore_add_errors)
-                               die("adding files failed");
-                       exit_status = 1;
-               }
-       return exit_status;
-}
-
-int cmd_add(int argc, const char **argv, const char *prefix)
-{
-       int exit_status = 0;
-       int newfd;
-       const char **pathspec;
-       struct dir_struct dir;
-       int flags;
-       int add_new_files;
-       int require_pathspec;
-
-       argc = parse_options(argc, argv, builtin_add_options,
-                         builtin_add_usage, 0);
-       if (patch_interactive)
-               add_interactive = 1;
-       if (add_interactive)
-               exit(interactive_add(argc, argv, prefix));
-
-       git_config(add_config, NULL);
-
-       if (addremove && take_worktree_changes)
-               die("-A and -u are mutually incompatible");
-       if ((addremove || take_worktree_changes) && !argc) {
-               static const char *here[2] = { ".", NULL };
-               argc = 1;
-               argv = here;
-       }
-
-       add_new_files = !take_worktree_changes && !refresh_only;
-       require_pathspec = !take_worktree_changes;
-
-       newfd = hold_locked_index(&lock_file, 1);
-
-       flags = ((verbose ? ADD_CACHE_VERBOSE : 0) |
-                (show_only ? ADD_CACHE_PRETEND : 0) |
-                (intent_to_add ? ADD_CACHE_INTENT : 0) |
-                (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) |
-                (!(addremove || take_worktree_changes)
-                 ? ADD_CACHE_IGNORE_REMOVAL : 0));
-
-       if (require_pathspec && argc == 0) {
-               fprintf(stderr, "Nothing specified, nothing added.\n");
-               fprintf(stderr, "Maybe you wanted to say 'git add .'?\n");
-               return 0;
-       }
-       pathspec = validate_pathspec(argc, argv, prefix);
-
-       if (read_cache() < 0)
-               die("index file corrupt");
-       treat_gitlinks(pathspec);
-
-       if (add_new_files)
-               /* This picks up the paths that are not tracked */
-               fill_directory(&dir, pathspec, ignored_too);
-
-       if (refresh_only) {
-               refresh(verbose, pathspec);
-               goto finish;
-       }
-
-       exit_status |= add_files_to_cache(prefix, pathspec, flags);
-
-       if (add_new_files)
-               exit_status |= add_files(&dir, flags);
-
- finish:
-       if (active_cache_changed) {
-               if (write_cache(newfd, active_cache, active_nr) ||
-                   commit_locked_index(&lock_file))
-                       die("Unable to write new index file");
-       }
-
-       return exit_status;
-}
diff --git a/builtin-annotate.c b/builtin-annotate.c
deleted file mode 100644 (file)
index fc43eed..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * "git annotate" builtin alias
- *
- * Copyright (C) 2006 Ryan Anderson
- */
-#include "git-compat-util.h"
-#include "builtin.h"
-
-int cmd_annotate(int argc, const char **argv, const char *prefix)
-{
-       const char **nargv;
-       int i;
-       nargv = xmalloc(sizeof(char *) * (argc + 2));
-
-       nargv[0] = "annotate";
-       nargv[1] = "-c";
-
-       for (i = 1; i < argc; i++) {
-               nargv[i+1] = argv[i];
-       }
-       nargv[argc + 1] = NULL;
-
-       return cmd_blame(argc + 1, nargv, prefix);
-}
diff --git a/builtin-apply.c b/builtin-apply.c
deleted file mode 100644 (file)
index 7b404ef..0000000
+++ /dev/null
@@ -1,3385 +0,0 @@
-/*
- * apply.c
- *
- * Copyright (C) Linus Torvalds, 2005
- *
- * This applies patches on top of some (arbitrary) version of the SCM.
- *
- */
-#include "cache.h"
-#include "cache-tree.h"
-#include "quote.h"
-#include "blob.h"
-#include "delta.h"
-#include "builtin.h"
-#include "string-list.h"
-#include "dir.h"
-#include "parse-options.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 unidiff_zero;
-static int p_value = 1;
-static int p_value_known;
-static int check_index;
-static int update_index;
-static int cached;
-static int diffstat;
-static int numstat;
-static int summary;
-static int check;
-static int apply = 1;
-static int apply_in_reverse;
-static int apply_with_reject;
-static int apply_verbosely;
-static int no_add;
-static const char *fake_ancestor;
-static int line_termination = '\n';
-static unsigned int p_context = UINT_MAX;
-static const char * const apply_usage[] = {
-       "git apply [options] [<patch>...]",
-       NULL
-};
-
-static enum ws_error_action {
-       nowarn_ws_error,
-       warn_on_ws_error,
-       die_on_ws_error,
-       correct_ws_error,
-} ws_error_action = warn_on_ws_error;
-static int whitespace_error;
-static int squelch_whitespace_errors = 5;
-static int applied_after_fixing_ws;
-static const char *patch_input_file;
-static const char *root;
-static int root_len;
-static int read_stdin = 1;
-static int options;
-
-static void parse_whitespace_option(const char *option)
-{
-       if (!option) {
-               ws_error_action = warn_on_ws_error;
-               return;
-       }
-       if (!strcmp(option, "warn")) {
-               ws_error_action = warn_on_ws_error;
-               return;
-       }
-       if (!strcmp(option, "nowarn")) {
-               ws_error_action = nowarn_ws_error;
-               return;
-       }
-       if (!strcmp(option, "error")) {
-               ws_error_action = die_on_ws_error;
-               return;
-       }
-       if (!strcmp(option, "error-all")) {
-               ws_error_action = die_on_ws_error;
-               squelch_whitespace_errors = 0;
-               return;
-       }
-       if (!strcmp(option, "strip") || !strcmp(option, "fix")) {
-               ws_error_action = correct_ws_error;
-               return;
-       }
-       die("unrecognized whitespace option '%s'", option);
-}
-
-static void set_default_whitespace_mode(const char *whitespace_option)
-{
-       if (!whitespace_option && !apply_default_whitespace)
-               ws_error_action = (apply ? warn_on_ws_error : nowarn_ws_error);
-}
-
-/*
- * 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;
-
-/*
- * This represents one "hunk" from a patch, starting with
- * "@@ -oldpos,oldlines +newpos,newlines @@" marker.  The
- * patch text is pointed at by patch, and its byte length
- * is stored in size.  leading and trailing are the number
- * of context lines.
- */
-struct fragment {
-       unsigned long leading, trailing;
-       unsigned long oldpos, oldlines;
-       unsigned long newpos, newlines;
-       const char *patch;
-       int size;
-       int rejected;
-       struct fragment *next;
-};
-
-/*
- * When dealing with a binary patch, we reuse "leading" field
- * to store the type of the binary hunk, either deflated "delta"
- * or deflated "literal".
- */
-#define binary_patch_method leading
-#define BINARY_DELTA_DEFLATED  1
-#define BINARY_LITERAL_DEFLATED 2
-
-/*
- * This represents a "patch" to a file, both metainfo changes
- * such as creation/deletion, filemode and content changes represented
- * as a series of fragments.
- */
-struct patch {
-       char *new_name, *old_name, *def_name;
-       unsigned int old_mode, new_mode;
-       int is_new, is_delete;  /* -1 = unknown, 0 = false, 1 = true */
-       int rejected;
-       unsigned ws_rule;
-       unsigned long deflate_origlen;
-       int lines_added, lines_deleted;
-       int score;
-       unsigned int is_toplevel_relative:1;
-       unsigned int inaccurate_eof:1;
-       unsigned int is_binary:1;
-       unsigned int is_copy:1;
-       unsigned int is_rename:1;
-       unsigned int recount:1;
-       struct fragment *fragments;
-       char *result;
-       size_t resultsize;
-       char old_sha1_prefix[41];
-       char new_sha1_prefix[41];
-       struct patch *next;
-};
-
-/*
- * A line in a file, len-bytes long (includes the terminating LF,
- * except for an incomplete line at the end if the file ends with
- * one), and its contents hashes to 'hash'.
- */
-struct line {
-       size_t len;
-       unsigned hash : 24;
-       unsigned flag : 8;
-#define LINE_COMMON     1
-};
-
-/*
- * This represents a "file", which is an array of "lines".
- */
-struct image {
-       char *buf;
-       size_t len;
-       size_t nr;
-       size_t alloc;
-       struct line *line_allocated;
-       struct line *line;
-};
-
-/*
- * Records filenames that have been touched, in order to handle
- * the case where more than one patches touch the same file.
- */
-
-static struct string_list fn_table;
-
-static uint32_t hash_line(const char *cp, size_t len)
-{
-       size_t i;
-       uint32_t h;
-       for (i = 0, h = 0; i < len; i++) {
-               if (!isspace(cp[i])) {
-                       h = h * 3 + (cp[i] & 0xff);
-               }
-       }
-       return h;
-}
-
-static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag)
-{
-       ALLOC_GROW(img->line_allocated, img->nr + 1, img->alloc);
-       img->line_allocated[img->nr].len = len;
-       img->line_allocated[img->nr].hash = hash_line(bol, len);
-       img->line_allocated[img->nr].flag = flag;
-       img->nr++;
-}
-
-static void prepare_image(struct image *image, char *buf, size_t len,
-                         int prepare_linetable)
-{
-       const char *cp, *ep;
-
-       memset(image, 0, sizeof(*image));
-       image->buf = buf;
-       image->len = len;
-
-       if (!prepare_linetable)
-               return;
-
-       ep = image->buf + image->len;
-       cp = image->buf;
-       while (cp < ep) {
-               const char *next;
-               for (next = cp; next < ep && *next != '\n'; next++)
-                       ;
-               if (next < ep)
-                       next++;
-               add_line_info(image, cp, next - cp, 0);
-               cp = next;
-       }
-       image->line = image->line_allocated;
-}
-
-static void clear_image(struct image *image)
-{
-       free(image->buf);
-       image->buf = NULL;
-       image->len = 0;
-}
-
-static void say_patch_name(FILE *output, const char *pre,
-                          struct patch *patch, const char *post)
-{
-       fputs(pre, output);
-       if (patch->old_name && patch->new_name &&
-           strcmp(patch->old_name, patch->new_name)) {
-               quote_c_style(patch->old_name, NULL, output, 0);
-               fputs(" => ", output);
-               quote_c_style(patch->new_name, NULL, output, 0);
-       } else {
-               const char *n = patch->new_name;
-               if (!n)
-                       n = patch->old_name;
-               quote_c_style(n, NULL, output, 0);
-       }
-       fputs(post, output);
-}
-
-#define CHUNKSIZE (8192)
-#define SLOP (16)
-
-static void read_patch_file(struct strbuf *sb, int fd)
-{
-       if (strbuf_read(sb, fd, 0) < 0)
-               die("git apply: read returned %s", strerror(errno));
-
-       /*
-        * 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.
-        */
-       strbuf_grow(sb, SLOP);
-       memset(sb->buf + sb->len, 0, SLOP);
-}
-
-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;
-
-       if (*line == '"') {
-               struct strbuf name = STRBUF_INIT;
-
-               /*
-                * Proposed "new-style" GNU patch/diff format; see
-                * http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2
-                */
-               if (!unquote_c_style(&name, line, NULL)) {
-                       char *cp;
-
-                       for (cp = name.buf; p_value; p_value--) {
-                               cp = strchr(cp, '/');
-                               if (!cp)
-                                       break;
-                               cp++;
-                       }
-                       if (cp) {
-                               /* name can later be freed, so we need
-                                * to memmove, not just return cp
-                                */
-                               strbuf_remove(&name, 0, cp - name.buf);
-                               free(def);
-                               if (root)
-                                       strbuf_insert(&name, 0, root, root_len);
-                               return strbuf_detach(&name, NULL);
-                       }
-               }
-               strbuf_release(&name);
-       }
-
-       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;
-               free(def);
-       }
-
-       if (root) {
-               char *ret = xmalloc(root_len + len + 1);
-               strcpy(ret, root);
-               memcpy(ret + root_len, start, len);
-               ret[root_len + len] = '\0';
-               return ret;
-       }
-
-       return xmemdupz(start, len);
-}
-
-static int count_slashes(const char *cp)
-{
-       int cnt = 0;
-       char ch;
-
-       while ((ch = *cp++))
-               if (ch == '/')
-                       cnt++;
-       return cnt;
-}
-
-/*
- * Given the string after "--- " or "+++ ", guess the appropriate
- * p_value for the given patch.
- */
-static int guess_p_value(const char *nameline)
-{
-       char *name, *cp;
-       int val = -1;
-
-       if (is_dev_null(nameline))
-               return -1;
-       name = find_name(nameline, NULL, 0, TERM_SPACE | TERM_TAB);
-       if (!name)
-               return -1;
-       cp = strchr(name, '/');
-       if (!cp)
-               val = 0;
-       else if (prefix) {
-               /*
-                * Does it begin with "a/$our-prefix" and such?  Then this is
-                * very likely to apply to our directory.
-                */
-               if (!strncmp(name, prefix, prefix_length))
-                       val = count_slashes(prefix);
-               else {
-                       cp++;
-                       if (!strncmp(cp, prefix, prefix_length))
-                               val = count_slashes(prefix) + 1;
-               }
-       }
-       free(name);
-       return val;
-}
-
-/*
- * Get the name etc info from the ---/+++ lines of a traditional patch header
- *
- * 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 (!p_value_known) {
-               int p, q;
-               p = guess_p_value(first);
-               q = guess_p_value(second);
-               if (p < 0) p = q;
-               if (0 <= p && p == q) {
-                       p_value = p;
-                       p_value_known = 1;
-               }
-       }
-       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, p_value, TERM_TAB);
-
-       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, p_value, TERM_TAB);
-               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->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)
-{
-       const char *name;
-       const char *second = NULL;
-       size_t len;
-
-       line += strlen("diff --git ");
-       llen -= strlen("diff --git ");
-
-       if (*line == '"') {
-               const char *cp;
-               struct strbuf first = STRBUF_INIT;
-               struct strbuf sp = STRBUF_INIT;
-
-               if (unquote_c_style(&first, line, &second))
-                       goto free_and_fail1;
-
-               /* advance to the first slash */
-               cp = stop_at_slash(first.buf, first.len);
-               /* we do not accept absolute paths */
-               if (!cp || cp == first.buf)
-                       goto free_and_fail1;
-               strbuf_remove(&first, 0, cp + 1 - first.buf);
-
-               /*
-                * 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_and_fail1;
-               if (*second == '"') {
-                       if (unquote_c_style(&sp, second, NULL))
-                               goto free_and_fail1;
-                       cp = stop_at_slash(sp.buf, sp.len);
-                       if (!cp || cp == sp.buf)
-                               goto free_and_fail1;
-                       /* They must match, otherwise ignore */
-                       if (strcmp(cp + 1, first.buf))
-                               goto free_and_fail1;
-                       strbuf_release(&sp);
-                       return strbuf_detach(&first, NULL);
-               }
-
-               /* unquoted second */
-               cp = stop_at_slash(second, line + llen - second);
-               if (!cp || cp == second)
-                       goto free_and_fail1;
-               cp++;
-               if (line + llen - cp != first.len + 1 ||
-                   memcmp(first.buf, cp, first.len))
-                       goto free_and_fail1;
-               return strbuf_detach(&first, NULL);
-
-       free_and_fail1:
-               strbuf_release(&first);
-               strbuf_release(&sp);
-               return NULL;
-       }
-
-       /* 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 == '"') {
-                       struct strbuf sp = STRBUF_INIT;
-                       const char *np;
-
-                       if (unquote_c_style(&sp, second, NULL))
-                               goto free_and_fail2;
-
-                       np = stop_at_slash(sp.buf, sp.len);
-                       if (!np || np == sp.buf)
-                               goto free_and_fail2;
-                       np++;
-
-                       len = sp.buf + sp.len - np;
-                       if (len < second - name &&
-                           !strncmp(np, name, len) &&
-                           isspace(name[len])) {
-                               /* Good */
-                               strbuf_remove(&sp, 0, np - sp.buf);
-                               return strbuf_detach(&sp, NULL);
-                       }
-
-               free_and_fail2:
-                       strbuf_release(&sp);
-                       return NULL;
-               }
-       }
-
-       /*
-        * Accept a name only if it shows up twice, exactly the same
-        * form.
-        */
-       for (len = 0 ; ; len++) {
-               switch (name[len]) {
-               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)) {
-                               return xmemdupz(name, len);
-                       }
-               }
-       }
-}
-
-/* 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);
-       if (patch->def_name && root) {
-               char *s = xmalloc(root_len + strlen(patch->def_name) + 1);
-               strcpy(s, root);
-               strcpy(s + root_len, patch->def_name);
-               free(patch->def_name);
-               patch->def_name = s;
-       }
-
-       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;
-}
-
-static void recount_diff(char *line, int size, struct fragment *fragment)
-{
-       int oldlines = 0, newlines = 0, ret = 0;
-
-       if (size < 1) {
-               warning("recount: ignore empty hunk");
-               return;
-       }
-
-       for (;;) {
-               int len = linelen(line, size);
-               size -= len;
-               line += len;
-
-               if (size < 1)
-                       break;
-
-               switch (*line) {
-               case ' ': case '\n':
-                       newlines++;
-                       /* fall through */
-               case '-':
-                       oldlines++;
-                       continue;
-               case '+':
-                       newlines++;
-                       continue;
-               case '\\':
-                       continue;
-               case '@':
-                       ret = size < 3 || prefixcmp(line, "@@ ");
-                       break;
-               case 'd':
-                       ret = size < 5 || prefixcmp(line, "diff ");
-                       break;
-               default:
-                       ret = -1;
-                       break;
-               }
-               if (ret) {
-                       warning("recount: unexpected line: %.*s",
-                               (int)linelen(line, size), line);
-                       return;
-               }
-               break;
-       }
-       fragment->oldlines = oldlines;
-       fragment->newlines = newlines;
-}
-
-/*
- * 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_toplevel_relative = 0;
-       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 fragments.
-                * 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;
-                       die("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;
-                       }
-                       patch->is_toplevel_relative = 1;
-                       *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 ("@@ -0,0 +1 @@\n" is the shortest).
-                */
-               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;
-}
-
-static void check_whitespace(const char *line, int len, unsigned ws_rule)
-{
-       char *err;
-       unsigned result = ws_check(line + 1, len - 1, ws_rule);
-       if (!result)
-               return;
-
-       whitespace_error++;
-       if (squelch_whitespace_errors &&
-           squelch_whitespace_errors < whitespace_error)
-               ;
-       else {
-               err = whitespace_error_string(result);
-               fprintf(stderr, "%s:%d: %s.\n%.*s\n",
-                       patch_input_file, linenr, err, len - 2, line + 1);
-               free(err);
-       }
-}
-
-/*
- * 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;
-       if (offset > 0 && patch->recount)
-               recount_diff(line + offset, size - offset, fragment);
-       oldlines = fragment->oldlines;
-       newlines = fragment->newlines;
-       leading = 0;
-       trailing = 0;
-
-       /* Parse the thing.. */
-       line += len;
-       size -= len;
-       linenr++;
-       added = deleted = 0;
-       for (offset = len;
-            0 < size;
-            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 '\n': /* newer GNU diff, an empty context line */
-               case ' ':
-                       oldlines--;
-                       newlines--;
-                       if (!deleted && !added)
-                               leading++;
-                       trailing++;
-                       break;
-               case '-':
-                       if (apply_in_reverse &&
-                           ws_error_action != nowarn_ws_error)
-                               check_whitespace(line, len, patch->ws_rule);
-                       deleted++;
-                       oldlines--;
-                       trailing = 0;
-                       break;
-               case '+':
-                       if (!apply_in_reverse &&
-                           ws_error_action != nowarn_ws_error)
-                               check_whitespace(line, len, patch->ws_rule);
-                       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;
-
-       if (0 < patch->is_new && oldlines)
-               return error("new file depends on old contents");
-       if (0 < patch->is_delete && newlines)
-               return error("deleted file still has contents");
-       return offset;
-}
-
-static int parse_single_patch(char *line, unsigned long size, struct patch *patch)
-{
-       unsigned long offset = 0;
-       unsigned long oldlines = 0, newlines = 0, context = 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;
-               oldlines += fragment->oldlines;
-               newlines += fragment->newlines;
-               context += fragment->leading + fragment->trailing;
-
-               *fragp = fragment;
-               fragp = &fragment->next;
-
-               offset += len;
-               line += len;
-               size -= len;
-       }
-
-       /*
-        * If something was removed (i.e. we have old-lines) it cannot
-        * be creation, and if something was added it cannot be
-        * deletion.  However, the reverse is not true; --unified=0
-        * patches that only add are not necessarily creation even
-        * though they do not have any old lines, and ones that only
-        * delete are not necessarily deletion.
-        *
-        * Unfortunately, a real creation/deletion patch do _not_ have
-        * any context line by definition, so we cannot safely tell it
-        * apart with --unified=0 insanity.  At least if the patch has
-        * more than one hunk it is not creation or deletion.
-        */
-       if (patch->is_new < 0 &&
-           (oldlines || (patch->fragments && patch->fragments->next)))
-               patch->is_new = 0;
-       if (patch->is_delete < 0 &&
-           (newlines || (patch->fragments && patch->fragments->next)))
-               patch->is_delete = 0;
-
-       if (0 < patch->is_new && oldlines)
-               die("new file %s depends on old contents", patch->new_name);
-       if (0 < patch->is_delete && newlines)
-               die("deleted file %s still has contents", patch->old_name);
-       if (!patch->is_delete && !newlines && context)
-               fprintf(stderr, "** warning: file %s becomes empty but "
-                       "is not deleted\n", patch->new_name);
-
-       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 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;
-       git_inflate_init(&stream);
-       st = git_inflate(&stream, Z_FINISH);
-       git_inflate_end(&stream);
-       if ((st != Z_STREAM_END) || stream.total_out != inflated_size) {
-               free(out);
-               return NULL;
-       }
-       return out;
-}
-
-static struct fragment *parse_binary_hunk(char **buf_p,
-                                         unsigned long *sz_p,
-                                         int *status_p,
-                                         int *used_p)
-{
-       /*
-        * Expect a line that begins with binary patch method ("literal"
-        * or "delta"), followed by the length of data before deflating.
-        * a sequence of 'length-byte' followed by base-85 encoded data
-        * should follow, terminated by a newline.
-        *
-        * 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.
-        */
-       int llen, used;
-       unsigned long size = *sz_p;
-       char *buffer = *buf_p;
-       int patch_method;
-       unsigned long origlen;
-       char *data = NULL;
-       int hunk_size = 0;
-       struct fragment *frag;
-
-       llen = linelen(buffer, size);
-       used = llen;
-
-       *status_p = 0;
-
-       if (!prefixcmp(buffer, "delta ")) {
-               patch_method = BINARY_DELTA_DEFLATED;
-               origlen = strtoul(buffer + 6, NULL, 10);
-       }
-       else if (!prefixcmp(buffer, "literal ")) {
-               patch_method = BINARY_LITERAL_DEFLATED;
-               origlen = strtoul(buffer + 8, NULL, 10);
-       }
-       else
-               return NULL;
-
-       linenr++;
-       buffer += llen;
-       while (1) {
-               int byte_length, max_byte_length, newsize;
-               llen = linelen(buffer, size);
-               used += llen;
-               linenr++;
-               if (llen == 1) {
-                       /* consume the blank line */
-                       buffer++;
-                       size--;
-                       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 = hunk_size + byte_length;
-               data = xrealloc(data, newsize);
-               if (decode_85(data + hunk_size, buffer + 1, byte_length))
-                       goto corrupt;
-               hunk_size = newsize;
-               buffer += llen;
-               size -= llen;
-       }
-
-       frag = xcalloc(1, sizeof(*frag));
-       frag->patch = inflate_it(data, hunk_size, origlen);
-       if (!frag->patch)
-               goto corrupt;
-       free(data);
-       frag->size = origlen;
-       *buf_p = buffer;
-       *sz_p = size;
-       *used_p = used;
-       frag->binary_patch_method = patch_method;
-       return frag;
-
- corrupt:
-       free(data);
-       *status_p = -1;
-       error("corrupt binary patch at line %d: %.*s",
-             linenr-1, llen-1, buffer);
-       return NULL;
-}
-
-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 "literal" or
-        * "delta") and the length of data before deflating; a
-        * sequence of 'length-byte' followed by base-85 encoded data
-        * follows.
-        *
-        * When a binary patch is reversible, there is another binary
-        * hunk in the same format, starting with patch method (either
-        * "literal" or "delta") with the length of data, and a sequence
-        * of length-byte + base-85 encoded data, terminated with another
-        * empty line.  This data, when applied to the postimage, produces
-        * the preimage.
-        */
-       struct fragment *forward;
-       struct fragment *reverse;
-       int status;
-       int used, used_1;
-
-       forward = parse_binary_hunk(&buffer, &size, &status, &used);
-       if (!forward && !status)
-               /* there has to be one hunk (forward hunk) */
-               return error("unrecognized binary patch at line %d", linenr-1);
-       if (status)
-               /* otherwise we already gave an error message */
-               return status;
-
-       reverse = parse_binary_hunk(&buffer, &size, &status, &used_1);
-       if (reverse)
-               used += used_1;
-       else if (status) {
-               /*
-                * Not having reverse hunk is not an error, but having
-                * a corrupt reverse hunk is.
-                */
-               free((void*) forward->patch);
-               free(forward);
-               return status;
-       }
-       forward->next = reverse;
-       patch->fragments = forward;
-       patch->is_binary = 1;
-       return used;
-}
-
-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;
-
-       patch->ws_rule = whitespace_rule(patch->new_name
-                                        ? patch->new_name
-                                        : patch->old_name);
-
-       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 text patch
-                * without metadata change.  A binary patch appears
-                * empty to us here.
-                */
-               if ((apply || check) &&
-                   (!patch->is_binary && !metadata_changes(patch)))
-                       die("patch with only garbage at line %d", linenr);
-       }
-
-       return offset + hdrsize + patchsize;
-}
-
-#define swap(a,b) myswap((a),(b),sizeof(a))
-
-#define myswap(a, b, size) do {                \
-       unsigned char mytmp[size];      \
-       memcpy(mytmp, &a, size);                \
-       memcpy(&a, &b, size);           \
-       memcpy(&b, mytmp, size);                \
-} while (0)
-
-static void reverse_patches(struct patch *p)
-{
-       for (; p; p = p->next) {
-               struct fragment *frag = p->fragments;
-
-               swap(p->new_name, p->old_name);
-               swap(p->new_mode, p->old_mode);
-               swap(p->is_new, p->is_delete);
-               swap(p->lines_added, p->lines_deleted);
-               swap(p->old_sha1_prefix, p->new_sha1_prefix);
-
-               for (; frag; frag = frag->next) {
-                       swap(frag->newpos, frag->oldpos);
-                       swap(frag->newlines, frag->oldlines);
-               }
-       }
-}
-
-static const char pluses[] =
-"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
-static const char minuses[]=
-"----------------------------------------------------------------------";
-
-static void show_stats(struct patch *patch)
-{
-       struct strbuf qname = STRBUF_INIT;
-       char *cp = patch->new_name ? patch->new_name : patch->old_name;
-       int max, add, del;
-
-       quote_c_style(cp, &qname, NULL, 0);
-
-       /*
-        * "scale" the filename
-        */
-       max = max_len;
-       if (max > 50)
-               max = 50;
-
-       if (qname.len > max) {
-               cp = strchr(qname.buf + qname.len + 3 - max, '/');
-               if (!cp)
-                       cp = qname.buf + qname.len + 3 - max;
-               strbuf_splice(&qname, 0, cp - qname.buf, "...", 3);
-       }
-
-       if (patch->is_binary) {
-               printf(" %-*s |  Bin\n", max, qname.buf);
-               strbuf_release(&qname);
-               return;
-       }
-
-       printf(" %-*s |", max, qname.buf);
-       strbuf_release(&qname);
-
-       /*
-        * scale the add/delete
-        */
-       max = max + max_change > 70 ? 70 - max : max_change;
-       add = patch->lines_added;
-       del = patch->lines_deleted;
-
-       if (max_change > 0) {
-               int total = ((add + del) * max + max_change / 2) / max_change;
-               add = (add * max + max_change / 2) / max_change;
-               del = total - add;
-       }
-       printf("%5d %.*s%.*s\n", patch->lines_added + patch->lines_deleted,
-               add, pluses, del, minuses);
-}
-
-static int read_old_data(struct stat *st, const char *path, struct strbuf *buf)
-{
-       switch (st->st_mode & S_IFMT) {
-       case S_IFLNK:
-               if (strbuf_readlink(buf, path, st->st_size) < 0)
-                       return error("unable to read symlink %s", path);
-               return 0;
-       case S_IFREG:
-               if (strbuf_read_file(buf, path, st->st_size) != st->st_size)
-                       return error("unable to open or read %s", path);
-               convert_to_git(path, buf->buf, buf->len, buf, 0);
-               return 0;
-       default:
-               return -1;
-       }
-}
-
-static void update_pre_post_images(struct image *preimage,
-                                  struct image *postimage,
-                                  char *buf,
-                                  size_t len)
-{
-       int i, ctx;
-       char *new, *old, *fixed;
-       struct image fixed_preimage;
-
-       /*
-        * Update the preimage with whitespace fixes.  Note that we
-        * are not losing preimage->buf -- apply_one_fragment() will
-        * free "oldlines".
-        */
-       prepare_image(&fixed_preimage, buf, len, 1);
-       assert(fixed_preimage.nr == preimage->nr);
-       for (i = 0; i < preimage->nr; i++)
-               fixed_preimage.line[i].flag = preimage->line[i].flag;
-       free(preimage->line_allocated);
-       *preimage = fixed_preimage;
-
-       /*
-        * Adjust the common context lines in postimage, in place.
-        * This is possible because whitespace fixing does not make
-        * the string grow.
-        */
-       new = old = postimage->buf;
-       fixed = preimage->buf;
-       for (i = ctx = 0; i < postimage->nr; i++) {
-               size_t len = postimage->line[i].len;
-               if (!(postimage->line[i].flag & LINE_COMMON)) {
-                       /* an added line -- no counterparts in preimage */
-                       memmove(new, old, len);
-                       old += len;
-                       new += len;
-                       continue;
-               }
-
-               /* a common context -- skip it in the original postimage */
-               old += len;
-
-               /* and find the corresponding one in the fixed preimage */
-               while (ctx < preimage->nr &&
-                      !(preimage->line[ctx].flag & LINE_COMMON)) {
-                       fixed += preimage->line[ctx].len;
-                       ctx++;
-               }
-               if (preimage->nr <= ctx)
-                       die("oops");
-
-               /* and copy it in, while fixing the line length */
-               len = preimage->line[ctx].len;
-               memcpy(new, fixed, len);
-               new += len;
-               fixed += len;
-               postimage->line[i].len = len;
-               ctx++;
-       }
-
-       /* Fix the length of the whole thing */
-       postimage->len = new - postimage->buf;
-}
-
-static int match_fragment(struct image *img,
-                         struct image *preimage,
-                         struct image *postimage,
-                         unsigned long try,
-                         int try_lno,
-                         unsigned ws_rule,
-                         int match_beginning, int match_end)
-{
-       int i;
-       char *fixed_buf, *buf, *orig, *target;
-
-       if (preimage->nr + try_lno > img->nr)
-               return 0;
-
-       if (match_beginning && try_lno)
-               return 0;
-
-       if (match_end && preimage->nr + try_lno != img->nr)
-               return 0;
-
-       /* Quick hash check */
-       for (i = 0; i < preimage->nr; i++)
-               if (preimage->line[i].hash != img->line[try_lno + i].hash)
-                       return 0;
-
-       /*
-        * Do we have an exact match?  If we were told to match
-        * at the end, size must be exactly at try+fragsize,
-        * otherwise try+fragsize must be still within the preimage,
-        * and either case, the old piece should match the preimage
-        * exactly.
-        */
-       if ((match_end
-            ? (try + preimage->len == img->len)
-            : (try + preimage->len <= img->len)) &&
-           !memcmp(img->buf + try, preimage->buf, preimage->len))
-               return 1;
-
-       if (ws_error_action != correct_ws_error)
-               return 0;
-
-       /*
-        * The hunk does not apply byte-by-byte, but the hash says
-        * it might with whitespace fuzz.
-        */
-       fixed_buf = xmalloc(preimage->len + 1);
-       buf = fixed_buf;
-       orig = preimage->buf;
-       target = img->buf + try;
-       for (i = 0; i < preimage->nr; i++) {
-               size_t fixlen; /* length after fixing the preimage */
-               size_t oldlen = preimage->line[i].len;
-               size_t tgtlen = img->line[try_lno + i].len;
-               size_t tgtfixlen; /* length after fixing the target line */
-               char tgtfixbuf[1024], *tgtfix;
-               int match;
-
-               /* Try fixing the line in the preimage */
-               fixlen = ws_fix_copy(buf, orig, oldlen, ws_rule, NULL);
-
-               /* Try fixing the line in the target */
-               if (sizeof(tgtfixbuf) > tgtlen)
-                       tgtfix = tgtfixbuf;
-               else
-                       tgtfix = xmalloc(tgtlen);
-               tgtfixlen = ws_fix_copy(tgtfix, target, tgtlen, ws_rule, NULL);
-
-               /*
-                * If they match, either the preimage was based on
-                * a version before our tree fixed whitespace breakage,
-                * or we are lacking a whitespace-fix patch the tree
-                * the preimage was based on already had (i.e. target
-                * has whitespace breakage, the preimage doesn't).
-                * In either case, we are fixing the whitespace breakages
-                * so we might as well take the fix together with their
-                * real change.
-                */
-               match = (tgtfixlen == fixlen && !memcmp(tgtfix, buf, fixlen));
-
-               if (tgtfix != tgtfixbuf)
-                       free(tgtfix);
-               if (!match)
-                       goto unmatch_exit;
-
-               orig += oldlen;
-               buf += fixlen;
-               target += tgtlen;
-       }
-
-       /*
-        * Yes, the preimage is based on an older version that still
-        * has whitespace breakages unfixed, and fixing them makes the
-        * hunk match.  Update the context lines in the postimage.
-        */
-       update_pre_post_images(preimage, postimage,
-                              fixed_buf, buf - fixed_buf);
-       return 1;
-
- unmatch_exit:
-       free(fixed_buf);
-       return 0;
-}
-
-static int find_pos(struct image *img,
-                   struct image *preimage,
-                   struct image *postimage,
-                   int line,
-                   unsigned ws_rule,
-                   int match_beginning, int match_end)
-{
-       int i;
-       unsigned long backwards, forwards, try;
-       int backwards_lno, forwards_lno, try_lno;
-
-       if (preimage->nr > img->nr)
-               return -1;
-
-       /*
-        * If match_begining or match_end is specified, there is no
-        * point starting from a wrong line that will never match and
-        * wander around and wait for a match at the specified end.
-        */
-       if (match_beginning)
-               line = 0;
-       else if (match_end)
-               line = img->nr - preimage->nr;
-
-       if (line > img->nr)
-               line = img->nr;
-
-       try = 0;
-       for (i = 0; i < line; i++)
-               try += img->line[i].len;
-
-       /*
-        * 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 = try;
-       backwards_lno = line;
-       forwards = try;
-       forwards_lno = line;
-       try_lno = line;
-
-       for (i = 0; ; i++) {
-               if (match_fragment(img, preimage, postimage,
-                                  try, try_lno, ws_rule,
-                                  match_beginning, match_end))
-                       return try_lno;
-
-       again:
-               if (backwards_lno == 0 && forwards_lno == img->nr)
-                       break;
-
-               if (i & 1) {
-                       if (backwards_lno == 0) {
-                               i++;
-                               goto again;
-                       }
-                       backwards_lno--;
-                       backwards -= img->line[backwards_lno].len;
-                       try = backwards;
-                       try_lno = backwards_lno;
-               } else {
-                       if (forwards_lno == img->nr) {
-                               i++;
-                               goto again;
-                       }
-                       forwards += img->line[forwards_lno].len;
-                       forwards_lno++;
-                       try = forwards;
-                       try_lno = forwards_lno;
-               }
-
-       }
-       return -1;
-}
-
-static void remove_first_line(struct image *img)
-{
-       img->buf += img->line[0].len;
-       img->len -= img->line[0].len;
-       img->line++;
-       img->nr--;
-}
-
-static void remove_last_line(struct image *img)
-{
-       img->len -= img->line[--img->nr].len;
-}
-
-static void update_image(struct image *img,
-                        int applied_pos,
-                        struct image *preimage,
-                        struct image *postimage)
-{
-       /*
-        * remove the copy of preimage at offset in img
-        * and replace it with postimage
-        */
-       int i, nr;
-       size_t remove_count, insert_count, applied_at = 0;
-       char *result;
-
-       for (i = 0; i < applied_pos; i++)
-               applied_at += img->line[i].len;
-
-       remove_count = 0;
-       for (i = 0; i < preimage->nr; i++)
-               remove_count += img->line[applied_pos + i].len;
-       insert_count = postimage->len;
-
-       /* Adjust the contents */
-       result = xmalloc(img->len + insert_count - remove_count + 1);
-       memcpy(result, img->buf, applied_at);
-       memcpy(result + applied_at, postimage->buf, postimage->len);
-       memcpy(result + applied_at + postimage->len,
-              img->buf + (applied_at + remove_count),
-              img->len - (applied_at + remove_count));
-       free(img->buf);
-       img->buf = result;
-       img->len += insert_count - remove_count;
-       result[img->len] = '\0';
-
-       /* Adjust the line table */
-       nr = img->nr + postimage->nr - preimage->nr;
-       if (preimage->nr < postimage->nr) {
-               /*
-                * NOTE: this knows that we never call remove_first_line()
-                * on anything other than pre/post image.
-                */
-               img->line = xrealloc(img->line, nr * sizeof(*img->line));
-               img->line_allocated = img->line;
-       }
-       if (preimage->nr != postimage->nr)
-               memmove(img->line + applied_pos + postimage->nr,
-                       img->line + applied_pos + preimage->nr,
-                       (img->nr - (applied_pos + preimage->nr)) *
-                       sizeof(*img->line));
-       memcpy(img->line + applied_pos,
-              postimage->line,
-              postimage->nr * sizeof(*img->line));
-       img->nr = nr;
-}
-
-static int apply_one_fragment(struct image *img, struct fragment *frag,
-                             int inaccurate_eof, unsigned ws_rule)
-{
-       int match_beginning, match_end;
-       const char *patch = frag->patch;
-       int size = frag->size;
-       char *old, *new, *oldlines, *newlines;
-       int new_blank_lines_at_end = 0;
-       unsigned long leading, trailing;
-       int pos, applied_pos;
-       struct image preimage;
-       struct image postimage;
-
-       memset(&preimage, 0, sizeof(preimage));
-       memset(&postimage, 0, sizeof(postimage));
-       oldlines = xmalloc(size);
-       newlines = xmalloc(size);
-
-       old = oldlines;
-       new = newlines;
-       while (size > 0) {
-               char first;
-               int len = linelen(patch, size);
-               int plen, added;
-               int added_blank_line = 0;
-
-               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--;
-               first = *patch;
-               if (apply_in_reverse) {
-                       if (first == '-')
-                               first = '+';
-                       else if (first == '+')
-                               first = '-';
-               }
-
-               switch (first) {
-               case '\n':
-                       /* Newer GNU diff, empty context line */
-                       if (plen < 0)
-                               /* ... followed by '\No newline'; nothing */
-                               break;
-                       *old++ = '\n';
-                       *new++ = '\n';
-                       add_line_info(&preimage, "\n", 1, LINE_COMMON);
-                       add_line_info(&postimage, "\n", 1, LINE_COMMON);
-                       break;
-               case ' ':
-               case '-':
-                       memcpy(old, patch + 1, plen);
-                       add_line_info(&preimage, old, plen,
-                                     (first == ' ' ? LINE_COMMON : 0));
-                       old += plen;
-                       if (first == '-')
-                               break;
-               /* Fall-through for ' ' */
-               case '+':
-                       /* --no-add does not add new lines */
-                       if (first == '+' && no_add)
-                               break;
-
-                       if (first != '+' ||
-                           !whitespace_error ||
-                           ws_error_action != correct_ws_error) {
-                               memcpy(new, patch + 1, plen);
-                               added = plen;
-                       }
-                       else {
-                               added = ws_fix_copy(new, patch + 1, plen, ws_rule, &applied_after_fixing_ws);
-                       }
-                       add_line_info(&postimage, new, added,
-                                     (first == '+' ? 0 : LINE_COMMON));
-                       new += added;
-                       if (first == '+' &&
-                           added == 1 && new[-1] == '\n')
-                               added_blank_line = 1;
-                       break;
-               case '@': case '\\':
-                       /* Ignore it, we already handled it */
-                       break;
-               default:
-                       if (apply_verbosely)
-                               error("invalid start of line: '%c'", first);
-                       return -1;
-               }
-               if (added_blank_line)
-                       new_blank_lines_at_end++;
-               else
-                       new_blank_lines_at_end = 0;
-               patch += len;
-               size -= len;
-       }
-       if (inaccurate_eof &&
-           old > oldlines && old[-1] == '\n' &&
-           new > newlines && new[-1] == '\n') {
-               old--;
-               new--;
-       }
-
-       leading = frag->leading;
-       trailing = frag->trailing;
-
-       /*
-        * A hunk to change lines at the beginning would begin with
-        * @@ -1,L +N,M @@
-        * but we need to be careful.  -U0 that inserts before the second
-        * line also has this pattern.
-        *
-        * And a hunk to add to an empty file would begin with
-        * @@ -0,0 +N,M @@
-        *
-        * In other words, a hunk that is (frag->oldpos <= 1) with or
-        * without leading context must match at the beginning.
-        */
-       match_beginning = (!frag->oldpos ||
-                          (frag->oldpos == 1 && !unidiff_zero));
-
-       /*
-        * A hunk without trailing lines must match at the end.
-        * However, we simply cannot tell if a hunk must match end
-        * from the lack of trailing lines if the patch was generated
-        * with unidiff without any context.
-        */
-       match_end = !unidiff_zero && !trailing;
-
-       pos = frag->newpos ? (frag->newpos - 1) : 0;
-       preimage.buf = oldlines;
-       preimage.len = old - oldlines;
-       postimage.buf = newlines;
-       postimage.len = new - newlines;
-       preimage.line = preimage.line_allocated;
-       postimage.line = postimage.line_allocated;
-
-       for (;;) {
-
-               applied_pos = find_pos(img, &preimage, &postimage, pos,
-                                      ws_rule, match_beginning, match_end);
-
-               if (applied_pos >= 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(&preimage);
-                       remove_first_line(&postimage);
-                       pos--;
-                       leading--;
-               }
-               if (trailing > leading) {
-                       remove_last_line(&preimage);
-                       remove_last_line(&postimage);
-                       trailing--;
-               }
-       }
-
-       if (applied_pos >= 0) {
-               if (ws_error_action == correct_ws_error &&
-                   new_blank_lines_at_end &&
-                   postimage.nr + applied_pos == img->nr) {
-                       /*
-                        * If the patch application adds blank lines
-                        * at the end, and if the patch applies at the
-                        * end of the image, remove those added blank
-                        * lines.
-                        */
-                       while (new_blank_lines_at_end--)
-                               remove_last_line(&postimage);
-               }
-
-               /*
-                * 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, applied_pos+1);
-               update_image(img, applied_pos, &preimage, &postimage);
-       } else {
-               if (apply_verbosely)
-                       error("while searching for:\n%.*s",
-                             (int)(old - oldlines), oldlines);
-       }
-
-       free(oldlines);
-       free(newlines);
-       free(preimage.line_allocated);
-       free(postimage.line_allocated);
-
-       return (applied_pos < 0);
-}
-
-static int apply_binary_fragment(struct image *img, struct patch *patch)
-{
-       struct fragment *fragment = patch->fragments;
-       unsigned long len;
-       void *dst;
-
-       /* Binary patch is irreversible without the optional second hunk */
-       if (apply_in_reverse) {
-               if (!fragment->next)
-                       return error("cannot reverse-apply a binary patch "
-                                    "without the reverse hunk to '%s'",
-                                    patch->new_name
-                                    ? patch->new_name : patch->old_name);
-               fragment = fragment->next;
-       }
-       switch (fragment->binary_patch_method) {
-       case BINARY_DELTA_DEFLATED:
-               dst = patch_delta(img->buf, img->len, fragment->patch,
-                                 fragment->size, &len);
-               if (!dst)
-                       return -1;
-               clear_image(img);
-               img->buf = dst;
-               img->len = len;
-               return 0;
-       case BINARY_LITERAL_DEFLATED:
-               clear_image(img);
-               img->len = fragment->size;
-               img->buf = xmalloc(img->len+1);
-               memcpy(img->buf, fragment->patch, img->len);
-               img->buf[img->len] = '\0';
-               return 0;
-       }
-       return -1;
-}
-
-static int apply_binary(struct image *img, struct patch *patch)
-{
-       const char *name = patch->old_name ? patch->old_name : patch->new_name;
-       unsigned char sha1[20];
-
-       /*
-        * 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.
-                */
-               hash_sha1_file(img->buf, img->len, blob_type, sha1);
-               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 (img->len)
-                       return error("the patch applies to an empty "
-                                    "'%s' but it is not empty", name);
-       }
-
-       get_sha1_hex(patch->new_sha1_prefix, sha1);
-       if (is_null_sha1(sha1)) {
-               clear_image(img);
-               return 0; /* deletion patch */
-       }
-
-       if (has_sha1_file(sha1)) {
-               /* We already have the postimage */
-               enum object_type type;
-               unsigned long size;
-               char *result;
-
-               result = read_sha1_file(sha1, &type, &size);
-               if (!result)
-                       return error("the necessary postimage %s for "
-                                    "'%s' cannot be read",
-                                    patch->new_sha1_prefix, name);
-               clear_image(img);
-               img->buf = result;
-               img->len = size;
-       } else {
-               /*
-                * We have verified buf matches the preimage;
-                * apply the patch data to it, which is stored
-                * in the patch->fragments->{patch,size}.
-                */
-               if (apply_binary_fragment(img, patch))
-                       return error("binary patch does not apply to '%s'",
-                                    name);
-
-               /* verify that the result matches */
-               hash_sha1_file(img->buf, img->len, blob_type, sha1);
-               if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix))
-                       return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)",
-                               name, patch->new_sha1_prefix, sha1_to_hex(sha1));
-       }
-
-       return 0;
-}
-
-static int apply_fragments(struct image *img, struct patch *patch)
-{
-       struct fragment *frag = patch->fragments;
-       const char *name = patch->old_name ? patch->old_name : patch->new_name;
-       unsigned ws_rule = patch->ws_rule;
-       unsigned inaccurate_eof = patch->inaccurate_eof;
-
-       if (patch->is_binary)
-               return apply_binary(img, patch);
-
-       while (frag) {
-               if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule)) {
-                       error("patch failed: %s:%ld", name, frag->oldpos);
-                       if (!apply_with_reject)
-                               return -1;
-                       frag->rejected = 1;
-               }
-               frag = frag->next;
-       }
-       return 0;
-}
-
-static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
-{
-       if (!ce)
-               return 0;
-
-       if (S_ISGITLINK(ce->ce_mode)) {
-               strbuf_grow(buf, 100);
-               strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(ce->sha1));
-       } else {
-               enum object_type type;
-               unsigned long sz;
-               char *result;
-
-               result = read_sha1_file(ce->sha1, &type, &sz);
-               if (!result)
-                       return -1;
-               /* XXX read_sha1_file NUL-terminates */
-               strbuf_attach(buf, result, sz, sz + 1);
-       }
-       return 0;
-}
-
-static struct patch *in_fn_table(const char *name)
-{
-       struct string_list_item *item;
-
-       if (name == NULL)
-               return NULL;
-
-       item = string_list_lookup(name, &fn_table);
-       if (item != NULL)
-               return (struct patch *)item->util;
-
-       return NULL;
-}
-
-/*
- * item->util in the filename table records the status of the path.
- * Usually it points at a patch (whose result records the contents
- * of it after applying it), but it could be PATH_WAS_DELETED for a
- * path that a previously applied patch has already removed.
- */
- #define PATH_TO_BE_DELETED ((struct patch *) -2)
-#define PATH_WAS_DELETED ((struct patch *) -1)
-
-static int to_be_deleted(struct patch *patch)
-{
-       return patch == PATH_TO_BE_DELETED;
-}
-
-static int was_deleted(struct patch *patch)
-{
-       return patch == PATH_WAS_DELETED;
-}
-
-static void add_to_fn_table(struct patch *patch)
-{
-       struct string_list_item *item;
-
-       /*
-        * Always add new_name unless patch is a deletion
-        * This should cover the cases for normal diffs,
-        * file creations and copies
-        */
-       if (patch->new_name != NULL) {
-               item = string_list_insert(patch->new_name, &fn_table);
-               item->util = patch;
-       }
-
-       /*
-        * store a failure on rename/deletion cases because
-        * later chunks shouldn't patch old names
-        */
-       if ((patch->new_name == NULL) || (patch->is_rename)) {
-               item = string_list_insert(patch->old_name, &fn_table);
-               item->util = PATH_WAS_DELETED;
-       }
-}
-
-static void prepare_fn_table(struct patch *patch)
-{
-       /*
-        * store information about incoming file deletion
-        */
-       while (patch) {
-               if ((patch->new_name == NULL) || (patch->is_rename)) {
-                       struct string_list_item *item;
-                       item = string_list_insert(patch->old_name, &fn_table);
-                       item->util = PATH_TO_BE_DELETED;
-               }
-               patch = patch->next;
-       }
-}
-
-static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce)
-{
-       struct strbuf buf = STRBUF_INIT;
-       struct image image;
-       size_t len;
-       char *img;
-       struct patch *tpatch;
-
-       if (!(patch->is_copy || patch->is_rename) &&
-           (tpatch = in_fn_table(patch->old_name)) != NULL && !to_be_deleted(tpatch)) {
-               if (was_deleted(tpatch)) {
-                       return error("patch %s has been renamed/deleted",
-                               patch->old_name);
-               }
-               /* We have a patched copy in memory use that */
-               strbuf_add(&buf, tpatch->result, tpatch->resultsize);
-       } else if (cached) {
-               if (read_file_or_gitlink(ce, &buf))
-                       return error("read of %s failed", patch->old_name);
-       } else if (patch->old_name) {
-               if (S_ISGITLINK(patch->old_mode)) {
-                       if (ce) {
-                               read_file_or_gitlink(ce, &buf);
-                       } else {
-                               /*
-                                * There is no way to apply subproject
-                                * patch without looking at the index.
-                                */
-                               patch->fragments = NULL;
-                       }
-               } else {
-                       if (read_old_data(st, patch->old_name, &buf))
-                               return error("read of %s failed", patch->old_name);
-               }
-       }
-
-       img = strbuf_detach(&buf, &len);
-       prepare_image(&image, img, len, !patch->is_binary);
-
-       if (apply_fragments(&image, patch) < 0)
-               return -1; /* note with --reject this succeeds. */
-       patch->result = image.buf;
-       patch->resultsize = image.len;
-       add_to_fn_table(patch);
-       free(image.line_allocated);
-
-       if (0 < patch->is_delete && patch->resultsize)
-               return error("removal patch leaves file contents");
-
-       return 0;
-}
-
-static int check_to_create_blob(const char *new_name, int ok_if_exists)
-{
-       struct stat nst;
-       if (!lstat(new_name, &nst)) {
-               if (S_ISDIR(nst.st_mode) || ok_if_exists)
-                       return 0;
-               /*
-                * A leading component of new_name might be a symlink
-                * that is going to be removed with this patch, but
-                * still pointing at somewhere that has the path.
-                * In such a case, path "new_name" does not exist as
-                * far as git is concerned.
-                */
-               if (has_symlink_leading_path(new_name, strlen(new_name)))
-                       return 0;
-
-               return error("%s: already exists in working directory", new_name);
-       }
-       else if ((errno != ENOENT) && (errno != ENOTDIR))
-               return error("%s: %s", new_name, strerror(errno));
-       return 0;
-}
-
-static int verify_index_match(struct cache_entry *ce, struct stat *st)
-{
-       if (S_ISGITLINK(ce->ce_mode)) {
-               if (!S_ISDIR(st->st_mode))
-                       return -1;
-               return 0;
-       }
-       return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID);
-}
-
-static int check_preimage(struct patch *patch, struct cache_entry **ce, struct stat *st)
-{
-       const char *old_name = patch->old_name;
-       struct patch *tpatch = NULL;
-       int stat_ret = 0;
-       unsigned st_mode = 0;
-
-       /*
-        * Make sure that we do not have local modifications from the
-        * index when we are looking at the index.  Also make sure
-        * we have the preimage file to be patched in the work tree,
-        * unless --cached, which tells git to apply only in the index.
-        */
-       if (!old_name)
-               return 0;
-
-       assert(patch->is_new <= 0);
-
-       if (!(patch->is_copy || patch->is_rename) &&
-           (tpatch = in_fn_table(old_name)) != NULL && !to_be_deleted(tpatch)) {
-               if (was_deleted(tpatch))
-                       return error("%s: has been deleted/renamed", old_name);
-               st_mode = tpatch->new_mode;
-       } else if (!cached) {
-               stat_ret = lstat(old_name, st);
-               if (stat_ret && errno != ENOENT)
-                       return error("%s: %s", old_name, strerror(errno));
-       }
-
-       if (to_be_deleted(tpatch))
-               tpatch = NULL;
-
-       if (check_index && !tpatch) {
-               int pos = cache_name_pos(old_name, strlen(old_name));
-               if (pos < 0) {
-                       if (patch->is_new < 0)
-                               goto is_new;
-                       return error("%s: does not exist in index", old_name);
-               }
-               *ce = active_cache[pos];
-               if (stat_ret < 0) {
-                       struct checkout costate;
-                       /* 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 && verify_index_match(*ce, st))
-                       return error("%s: does not match index", old_name);
-               if (cached)
-                       st_mode = (*ce)->ce_mode;
-       } else if (stat_ret < 0) {
-               if (patch->is_new < 0)
-                       goto is_new;
-               return error("%s: %s", old_name, strerror(errno));
-       }
-
-       if (!cached && !tpatch)
-               st_mode = ce_mode_from_stat(*ce, 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)
-               warning("%s has type %o, expected %o",
-                       old_name, st_mode, patch->old_mode);
-       if (!patch->new_mode && !patch->is_delete)
-               patch->new_mode = st_mode;
-       return 0;
-
- is_new:
-       patch->is_new = 1;
-       patch->is_delete = 0;
-       patch->old_name = NULL;
-       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;
-       struct patch *tpatch;
-       int ok_if_exists;
-       int status;
-
-       patch->rejected = 1; /* we will drop this after we succeed */
-
-       status = check_preimage(patch, &ce, &st);
-       if (status)
-               return status;
-       old_name = patch->old_name;
-
-       if ((tpatch = in_fn_table(new_name)) &&
-                       (was_deleted(tpatch) || to_be_deleted(tpatch)))
-               /*
-                * A type-change diff is always split into a patch to
-                * delete old, immediately followed by a patch to
-                * create new (see diff.c::run_diff()); in such a case
-                * it is Ok that the entry to be deleted by the
-                * previous patch is still in the working tree and in
-                * the index.
-                */
-               ok_if_exists = 1;
-       else
-               ok_if_exists = 0;
-
-       if (new_name &&
-           ((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) {
-               if (check_index &&
-                   cache_name_pos(new_name, strlen(new_name)) >= 0 &&
-                   !ok_if_exists)
-                       return error("%s: already exists in index", new_name);
-               if (!cached) {
-                       int err = check_to_create_blob(new_name, ok_if_exists);
-                       if (err)
-                               return err;
-               }
-               if (!patch->new_mode) {
-                       if (0 < 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);
-       patch->rejected = 0;
-       return 0;
-}
-
-static int check_patch_list(struct patch *patch)
-{
-       int err = 0;
-
-       prepare_fn_table(patch);
-       while (patch) {
-               if (apply_verbosely)
-                       say_patch_name(stderr,
-                                      "Checking patch ", patch, "...\n");
-               err |= check_patch(patch);
-               patch = patch->next;
-       }
-       return err;
-}
-
-/* This function tries to read the sha1 from the current index */
-static int get_current_sha1(const char *path, unsigned char *sha1)
-{
-       int pos;
-
-       if (read_cache() < 0)
-               return -1;
-       pos = cache_name_pos(path, strlen(path));
-       if (pos < 0)
-               return -1;
-       hashcpy(sha1, active_cache[pos]->sha1);
-       return 0;
-}
-
-/* Build an index that contains the just the files needed for a 3way merge */
-static void build_fake_ancestor(struct patch *list, const char *filename)
-{
-       struct patch *patch;
-       struct index_state result = { 0 };
-       int fd;
-
-       /* 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];
-               struct cache_entry *ce;
-               const char *name;
-
-               name = patch->old_name ? patch->old_name : patch->new_name;
-               if (0 < patch->is_new)
-                       continue;
-               else if (get_sha1(patch->old_sha1_prefix, sha1))
-                       /* git diff has no index line for mode/type changes */
-                       if (!patch->lines_added && !patch->lines_deleted) {
-                               if (get_current_sha1(patch->new_name, sha1) ||
-                                   get_current_sha1(patch->old_name, sha1))
-                                       die("mode change for %s, which is not "
-                                               "in current HEAD", name);
-                               sha1_ptr = sha1;
-                       } else
-                               die("sha1 information is lacking or useless "
-                                       "(%s).", name);
-               else
-                       sha1_ptr = sha1;
-
-               ce = make_cache_entry(patch->old_mode, sha1_ptr, name, 0, 0);
-               if (!ce)
-                       die("make_cache_entry failed for path '%s'", name);
-               if (add_index_entry(&result, ce, ADD_CACHE_OK_TO_ADD))
-                       die ("Could not add %s to temporary index", name);
-       }
-
-       fd = open(filename, O_WRONLY | O_CREAT, 0666);
-       if (fd < 0 || write_index(&result, fd) || close(fd))
-               die ("Could not write temporary index to %s", filename);
-
-       discard_index(&result);
-}
-
-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;
-               if (patch->is_binary)
-                       printf("-\t-\t");
-               else
-                       printf("%d\t%d\t", patch->lines_added, patch->lines_deleted);
-               write_name_quoted(name, stdout, line_termination);
-       }
-}
-
-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, int rmdir_empty)
-{
-       if (update_index) {
-               if (remove_file_from_cache(patch->old_name) < 0)
-                       die("unable to remove %s from index", patch->old_name);
-       }
-       if (!cached) {
-               if (S_ISGITLINK(patch->old_mode)) {
-                       if (rmdir(patch->old_name))
-                               warning("unable to remove submodule %s",
-                                       patch->old_name);
-               } else if (!unlink(patch->old_name) && rmdir_empty) {
-                       remove_path(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 (!update_index)
-               return;
-
-       ce = xcalloc(1, ce_size);
-       memcpy(ce->name, path, namelen);
-       ce->ce_mode = create_ce_mode(mode);
-       ce->ce_flags = namelen;
-       if (S_ISGITLINK(mode)) {
-               const char *s = buf;
-
-               if (get_sha1_hex(s + strlen("Subproject commit "), ce->sha1))
-                       die("corrupt patch for subproject %s", path);
-       } else {
-               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;
-       struct strbuf nbuf = STRBUF_INIT;
-
-       if (S_ISGITLINK(mode)) {
-               struct stat st;
-               if (!lstat(path, &st) && S_ISDIR(st.st_mode))
-                       return 0;
-               return mkdir(path, 0777);
-       }
-
-       if (has_symlinks && S_ISLNK(mode))
-               /* Although buf:size is counted string, it also is NUL
-                * terminated.
-                */
-               return symlink(buf, path);
-
-       fd = open(path, O_CREAT | O_EXCL | O_WRONLY, (mode & 0100) ? 0777 : 0666);
-       if (fd < 0)
-               return -1;
-
-       if (convert_to_working_tree(path, buf, size, &nbuf)) {
-               size = nbuf.len;
-               buf  = nbuf.buf;
-       }
-       write_or_die(fd, buf, size);
-       strbuf_release(&nbuf);
-
-       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 || errno == EACCES) {
-               /* We may be trying to create a file where a directory
-                * used to be.
-                */
-               struct stat st;
-               if (!lstat(path, &st) && (!S_ISDIR(st.st_mode) || !rmdir(path)))
-                       errno = EEXIST;
-       }
-
-       if (errno == EEXIST) {
-               unsigned int nr = getpid();
-
-               for (;;) {
-                       char newpath[PATH_MAX];
-                       mksnpath(newpath, sizeof(newpath), "%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);
-}
-
-/* phase zero is to remove, phase one is to create */
-static void write_out_one_result(struct patch *patch, int phase)
-{
-       if (patch->is_delete > 0) {
-               if (phase == 0)
-                       remove_file(patch, 1);
-               return;
-       }
-       if (patch->is_new > 0 || patch->is_copy) {
-               if (phase == 1)
-                       create_file(patch);
-               return;
-       }
-       /*
-        * Rename or modification boils down to the same
-        * thing: remove the old, write the new
-        */
-       if (phase == 0)
-               remove_file(patch, patch->is_rename);
-       if (phase == 1)
-               create_file(patch);
-}
-
-static int write_out_one_reject(struct patch *patch)
-{
-       FILE *rej;
-       char namebuf[PATH_MAX];
-       struct fragment *frag;
-       int cnt = 0;
-
-       for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) {
-               if (!frag->rejected)
-                       continue;
-               cnt++;
-       }
-
-       if (!cnt) {
-               if (apply_verbosely)
-                       say_patch_name(stderr,
-                                      "Applied patch ", patch, " cleanly.\n");
-               return 0;
-       }
-
-       /* This should not happen, because a removal patch that leaves
-        * contents are marked "rejected" at the patch level.
-        */
-       if (!patch->new_name)
-               die("internal error");
-
-       /* Say this even without --verbose */
-       say_patch_name(stderr, "Applying patch ", patch, " with");
-       fprintf(stderr, " %d rejects...\n", cnt);
-
-       cnt = strlen(patch->new_name);
-       if (ARRAY_SIZE(namebuf) <= cnt + 5) {
-               cnt = ARRAY_SIZE(namebuf) - 5;
-               warning("truncating .rej filename to %.*s.rej",
-                       cnt - 1, patch->new_name);
-       }
-       memcpy(namebuf, patch->new_name, cnt);
-       memcpy(namebuf + cnt, ".rej", 5);
-
-       rej = fopen(namebuf, "w");
-       if (!rej)
-               return error("cannot open %s: %s", namebuf, strerror(errno));
-
-       /* Normal git tools never deal with .rej, so do not pretend
-        * this is a git patch by saying --git nor give extended
-        * headers.  While at it, maybe please "kompare" that wants
-        * the trailing TAB and some garbage at the end of line ;-).
-        */
-       fprintf(rej, "diff a/%s b/%s\t(rejected hunks)\n",
-               patch->new_name, patch->new_name);
-       for (cnt = 1, frag = patch->fragments;
-            frag;
-            cnt++, frag = frag->next) {
-               if (!frag->rejected) {
-                       fprintf(stderr, "Hunk #%d applied cleanly.\n", cnt);
-                       continue;
-               }
-               fprintf(stderr, "Rejected hunk #%d.\n", cnt);
-               fprintf(rej, "%.*s", frag->size, frag->patch);
-               if (frag->patch[frag->size-1] != '\n')
-                       fputc('\n', rej);
-       }
-       fclose(rej);
-       return -1;
-}
-
-static int write_out_results(struct patch *list, int skipped_patch)
-{
-       int phase;
-       int errs = 0;
-       struct patch *l;
-
-       if (!list && !skipped_patch)
-               return error("No changes");
-
-       for (phase = 0; phase < 2; phase++) {
-               l = list;
-               while (l) {
-                       if (l->rejected)
-                               errs = 1;
-                       else {
-                               write_out_one_result(l, phase);
-                               if (phase == 1 && write_out_one_reject(l))
-                                       errs = 1;
-                       }
-                       l = l->next;
-               }
-       }
-       return errs;
-}
-
-static struct lock_file lock_file;
-
-static struct string_list limit_by_name;
-static int has_include;
-static void add_name_limit(const char *name, int exclude)
-{
-       struct string_list_item *it;
-
-       it = string_list_append(name, &limit_by_name);
-       it->util = exclude ? NULL : (void *) 1;
-}
-
-static int use_patch(struct patch *p)
-{
-       const char *pathname = p->new_name ? p->new_name : p->old_name;
-       int i;
-
-       /* Paths outside are not touched regardless of "--include" */
-       if (0 < prefix_length) {
-               int pathlen = strlen(pathname);
-               if (pathlen <= prefix_length ||
-                   memcmp(prefix, pathname, prefix_length))
-                       return 0;
-       }
-
-       /* See if it matches any of exclude/include rule */
-       for (i = 0; i < limit_by_name.nr; i++) {
-               struct string_list_item *it = &limit_by_name.items[i];
-               if (!fnmatch(it->string, pathname, 0))
-                       return (it->util != NULL);
-       }
-
-       /*
-        * If we had any include, a path that does not match any rule is
-        * not used.  Otherwise, we saw bunch of exclude rules (or none)
-        * and such a path is used.
-        */
-       return !has_include;
-}
-
-
-static void prefix_one(char **name)
-{
-       char *old_name = *name;
-       if (!old_name)
-               return;
-       *name = xstrdup(prefix_filename(prefix, prefix_length, *name));
-       free(old_name);
-}
-
-static void prefix_patches(struct patch *p)
-{
-       if (!prefix || p->is_toplevel_relative)
-               return;
-       for ( ; p; p = p->next) {
-               if (p->new_name == p->old_name) {
-                       char *prefixed = p->new_name;
-                       prefix_one(&prefixed);
-                       p->new_name = p->old_name = prefixed;
-               }
-               else {
-                       prefix_one(&p->new_name);
-                       prefix_one(&p->old_name);
-               }
-       }
-}
-
-#define INACCURATE_EOF (1<<0)
-#define RECOUNT                (1<<1)
-
-static int apply_patch(int fd, const char *filename, int options)
-{
-       size_t offset;
-       struct strbuf buf = STRBUF_INIT;
-       struct patch *list = NULL, **listp = &list;
-       int skipped_patch = 0;
-
-       /* FIXME - memory leak when using multiple patch files as inputs */
-       memset(&fn_table, 0, sizeof(struct string_list));
-       patch_input_file = filename;
-       read_patch_file(&buf, fd);
-       offset = 0;
-       while (offset < buf.len) {
-               struct patch *patch;
-               int nr;
-
-               patch = xcalloc(1, sizeof(*patch));
-               patch->inaccurate_eof = !!(options & INACCURATE_EOF);
-               patch->recount =  !!(options & RECOUNT);
-               nr = parse_chunk(buf.buf + offset, buf.len - offset, patch);
-               if (nr < 0)
-                       break;
-               if (apply_in_reverse)
-                       reverse_patches(patch);
-               if (prefix)
-                       prefix_patches(patch);
-               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;
-       }
-
-       if (whitespace_error && (ws_error_action == die_on_ws_error))
-               apply = 0;
-
-       update_index = check_index && apply;
-       if (update_index && newfd < 0)
-               newfd = hold_locked_index(&lock_file, 1);
-
-       if (check_index) {
-               if (read_cache() < 0)
-                       die("unable to read index file");
-       }
-
-       if ((check || apply) &&
-           check_patch_list(list) < 0 &&
-           !apply_with_reject)
-               exit(1);
-
-       if (apply && write_out_results(list, skipped_patch))
-               exit(1);
-
-       if (fake_ancestor)
-               build_fake_ancestor(list, fake_ancestor);
-
-       if (diffstat)
-               stat_patch_list(list);
-
-       if (numstat)
-               numstat_patch_list(list);
-
-       if (summary)
-               summary_patch_list(list);
-
-       strbuf_release(&buf);
-       return 0;
-}
-
-static int git_apply_config(const char *var, const char *value, void *cb)
-{
-       if (!strcmp(var, "apply.whitespace"))
-               return git_config_string(&apply_default_whitespace, var, value);
-       return git_default_config(var, value, cb);
-}
-
-static int option_parse_exclude(const struct option *opt,
-                               const char *arg, int unset)
-{
-       add_name_limit(arg, 1);
-       return 0;
-}
-
-static int option_parse_include(const struct option *opt,
-                               const char *arg, int unset)
-{
-       add_name_limit(arg, 0);
-       has_include = 1;
-       return 0;
-}
-
-static int option_parse_p(const struct option *opt,
-                         const char *arg, int unset)
-{
-       p_value = atoi(arg);
-       p_value_known = 1;
-       return 0;
-}
-
-static int option_parse_z(const struct option *opt,
-                         const char *arg, int unset)
-{
-       if (unset)
-               line_termination = '\n';
-       else
-               line_termination = 0;
-       return 0;
-}
-
-static int option_parse_whitespace(const struct option *opt,
-                                  const char *arg, int unset)
-{
-       const char **whitespace_option = opt->value;
-
-       *whitespace_option = arg;
-       parse_whitespace_option(arg);
-       return 0;
-}
-
-static int option_parse_directory(const struct option *opt,
-                                 const char *arg, int unset)
-{
-       root_len = strlen(arg);
-       if (root_len && arg[root_len - 1] != '/') {
-               char *new_root;
-               root = new_root = xmalloc(root_len + 2);
-               strcpy(new_root, arg);
-               strcpy(new_root + root_len++, "/");
-       } else
-               root = arg;
-       return 0;
-}
-
-int cmd_apply(int argc, const char **argv, const char *unused_prefix)
-{
-       int i;
-       int errs = 0;
-       int is_not_gitdir;
-       int binary;
-       int force_apply = 0;
-
-       const char *whitespace_option = NULL;
-
-       struct option builtin_apply_options[] = {
-               { OPTION_CALLBACK, 0, "exclude", NULL, "path",
-                       "don't apply changes matching the given path",
-                       0, option_parse_exclude },
-               { OPTION_CALLBACK, 0, "include", NULL, "path",
-                       "apply changes matching the given path",
-                       0, option_parse_include },
-               { OPTION_CALLBACK, 'p', NULL, NULL, "num",
-                       "remove <num> leading slashes from traditional diff paths",
-                       0, option_parse_p },
-               OPT_BOOLEAN(0, "no-add", &no_add,
-                       "ignore additions made by the patch"),
-               OPT_BOOLEAN(0, "stat", &diffstat,
-                       "instead of applying the patch, output diffstat for the input"),
-               { OPTION_BOOLEAN, 0, "allow-binary-replacement", &binary,
-                 NULL, "old option, now no-op", PARSE_OPT_HIDDEN },
-               { OPTION_BOOLEAN, 0, "binary", &binary,
-                 NULL, "old option, now no-op", PARSE_OPT_HIDDEN },
-               OPT_BOOLEAN(0, "numstat", &numstat,
-                       "shows number of added and deleted lines in decimal notation"),
-               OPT_BOOLEAN(0, "summary", &summary,
-                       "instead of applying the patch, output a summary for the input"),
-               OPT_BOOLEAN(0, "check", &check,
-                       "instead of applying the patch, see if the patch is applicable"),
-               OPT_BOOLEAN(0, "index", &check_index,
-                       "make sure the patch is applicable to the current index"),
-               OPT_BOOLEAN(0, "cached", &cached,
-                       "apply a patch without touching the working tree"),
-               OPT_BOOLEAN(0, "apply", &force_apply,
-                       "also apply the patch (use with --stat/--summary/--check)"),
-               OPT_STRING(0, "build-fake-ancestor", &fake_ancestor, "file",
-                       "build a temporary index based on embedded index information"),
-               { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
-                       "paths are separated with NUL character",
-                       PARSE_OPT_NOARG, option_parse_z },
-               OPT_INTEGER('C', NULL, &p_context,
-                               "ensure at least <n> lines of context match"),
-               { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, "action",
-                       "detect new or modified lines that have whitespace errors",
-                       0, option_parse_whitespace },
-               OPT_BOOLEAN('R', "reverse", &apply_in_reverse,
-                       "apply the patch in reverse"),
-               OPT_BOOLEAN(0, "unidiff-zero", &unidiff_zero,
-                       "don't expect at least one line of context"),
-               OPT_BOOLEAN(0, "reject", &apply_with_reject,
-                       "leave the rejected hunks in corresponding *.rej files"),
-               OPT__VERBOSE(&apply_verbosely),
-               OPT_BIT(0, "inaccurate-eof", &options,
-                       "tolerate incorrectly detected missing new-line at the end of file",
-                       INACCURATE_EOF),
-               OPT_BIT(0, "recount", &options,
-                       "do not trust the line counts in the hunk headers",
-                       RECOUNT),
-               { OPTION_CALLBACK, 0, "directory", NULL, "root",
-                       "prepend <root> to all filenames",
-                       0, option_parse_directory },
-               OPT_END()
-       };
-
-       prefix = setup_git_directory_gently(&is_not_gitdir);
-       prefix_length = prefix ? strlen(prefix) : 0;
-       git_config(git_apply_config, NULL);
-       if (apply_default_whitespace)
-               parse_whitespace_option(apply_default_whitespace);
-
-       argc = parse_options(argc, argv, builtin_apply_options,
-                       apply_usage, 0);
-       if (apply_with_reject)
-               apply = apply_verbosely = 1;
-       if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor))
-               apply = 0;
-       if (check_index && is_not_gitdir)
-               die("--index outside a repository");
-       if (cached) {
-               if (is_not_gitdir)
-                       die("--cached outside a repository");
-               check_index = 1;
-       }
-       for (i = 0; i < argc; i++) {
-               const char *arg = argv[i];
-               int fd;
-
-               if (!strcmp(arg, "-")) {
-                       errs |= apply_patch(0, "<stdin>", options);
-                       read_stdin = 0;
-                       continue;
-               } else if (0 < prefix_length)
-                       arg = prefix_filename(prefix, prefix_length, arg);
-
-               fd = open(arg, O_RDONLY);
-               if (fd < 0)
-                       die("can't open patch '%s': %s", arg, strerror(errno));
-               read_stdin = 0;
-               set_default_whitespace_mode(whitespace_option);
-               errs |= apply_patch(fd, arg, options);
-               close(fd);
-       }
-       set_default_whitespace_mode(whitespace_option);
-       if (read_stdin)
-               errs |= apply_patch(0, "<stdin>", options);
-       if (whitespace_error) {
-               if (squelch_whitespace_errors &&
-                   squelch_whitespace_errors < whitespace_error) {
-                       int squelched =
-                               whitespace_error - squelch_whitespace_errors;
-                       warning("squelched %d "
-                               "whitespace error%s",
-                               squelched,
-                               squelched == 1 ? "" : "s");
-               }
-               if (ws_error_action == die_on_ws_error)
-                       die("%d line%s add%s whitespace errors.",
-                           whitespace_error,
-                           whitespace_error == 1 ? "" : "s",
-                           whitespace_error == 1 ? "s" : "");
-               if (applied_after_fixing_ws && apply)
-                       warning("%d line%s applied after"
-                               " fixing whitespace errors.",
-                               applied_after_fixing_ws,
-                               applied_after_fixing_ws == 1 ? "" : "s");
-               else if (whitespace_error)
-                       warning("%d line%s add%s whitespace errors.",
-                               whitespace_error,
-                               whitespace_error == 1 ? "" : "s",
-                               whitespace_error == 1 ? "s" : "");
-       }
-
-       if (update_index) {
-               if (write_cache(newfd, active_cache, active_nr) ||
-                   commit_locked_index(&lock_file))
-                       die("Unable to write new index file");
-       }
-
-       return !!errs;
-}
diff --git a/builtin-archive.c b/builtin-archive.c
deleted file mode 100644 (file)
index ab50ceb..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (c) 2006 Franck Bui-Huu
- * Copyright (c) 2006 Rene Scharfe
- */
-#include "cache.h"
-#include "builtin.h"
-#include "archive.h"
-#include "parse-options.h"
-#include "pkt-line.h"
-#include "sideband.h"
-
-static void create_output_file(const char *output_file)
-{
-       int output_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
-       if (output_fd < 0)
-               die("could not create archive file: %s ", output_file);
-       if (output_fd != 1) {
-               if (dup2(output_fd, 1) < 0)
-                       die("could not redirect output");
-               else
-                       close(output_fd);
-       }
-}
-
-static int run_remote_archiver(int argc, const char **argv,
-                              const char *remote, const char *exec)
-{
-       char *url, buf[LARGE_PACKET_MAX];
-       int fd[2], i, len, rv;
-       struct child_process *conn;
-
-       url = xstrdup(remote);
-       conn = git_connect(fd, url, exec, 0);
-
-       for (i = 1; i < argc; i++)
-               packet_write(fd[1], "argument %s\n", argv[i]);
-       packet_flush(fd[1]);
-
-       len = packet_read_line(fd[0], buf, sizeof(buf));
-       if (!len)
-               die("git archive: expected ACK/NAK, got EOF");
-       if (buf[len-1] == '\n')
-               buf[--len] = 0;
-       if (strcmp(buf, "ACK")) {
-               if (len > 5 && !prefixcmp(buf, "NACK "))
-                       die("git archive: NACK %s", buf + 5);
-               die("git archive: protocol error");
-       }
-
-       len = packet_read_line(fd[0], buf, sizeof(buf));
-       if (len)
-               die("git archive: expected a flush");
-
-       /* Now, start reading from fd[0] and spit it out to stdout */
-       rv = recv_sideband("archive", fd[0], 1);
-       close(fd[0]);
-       close(fd[1]);
-       rv |= finish_connect(conn);
-
-       return !!rv;
-}
-
-#define PARSE_OPT_KEEP_ALL ( PARSE_OPT_KEEP_DASHDASH |         \
-                            PARSE_OPT_KEEP_ARGV0 |     \
-                            PARSE_OPT_KEEP_UNKNOWN |   \
-                            PARSE_OPT_NO_INTERNAL_HELP )
-
-int cmd_archive(int argc, const char **argv, const char *prefix)
-{
-       const char *exec = "git-upload-archive";
-       const char *output = NULL;
-       const char *remote = NULL;
-       struct option local_opts[] = {
-               OPT_STRING(0, "output", &output, "file",
-                       "write the archive to this file"),
-               OPT_STRING(0, "remote", &remote, "repo",
-                       "retrieve the archive from remote repository <repo>"),
-               OPT_STRING(0, "exec", &exec, "cmd",
-                       "path to the remote git-upload-archive command"),
-               OPT_END()
-       };
-
-       argc = parse_options(argc, argv, local_opts, NULL, PARSE_OPT_KEEP_ALL);
-
-       if (output)
-               create_output_file(output);
-
-       if (remote)
-               return run_remote_archiver(argc, argv, remote, exec);
-
-       setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
-
-       return write_archive(argc, argv, prefix, 1);
-}
diff --git a/builtin-bisect--helper.c b/builtin-bisect--helper.c
deleted file mode 100644 (file)
index 8fe7787..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-#include "builtin.h"
-#include "cache.h"
-#include "parse-options.h"
-#include "bisect.h"
-
-static const char * const git_bisect_helper_usage[] = {
-       "git bisect--helper --next-vars",
-       NULL
-};
-
-int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
-{
-       int next_vars = 0;
-       struct option options[] = {
-               OPT_BOOLEAN(0, "next-vars", &next_vars,
-                           "output next bisect step variables"),
-               OPT_END()
-       };
-
-       argc = parse_options(argc, argv, options, git_bisect_helper_usage, 0);
-
-       if (!next_vars)
-               usage_with_options(git_bisect_helper_usage, options);
-
-       /* next-vars */
-       return bisect_next_vars(prefix);
-}
diff --git a/builtin-blame.c b/builtin-blame.c
deleted file mode 100644 (file)
index cf74a92..0000000
+++ /dev/null
@@ -1,2454 +0,0 @@
-/*
- * Blame
- *
- * Copyright (c) 2006, Junio C Hamano
- */
-
-#include "cache.h"
-#include "builtin.h"
-#include "blob.h"
-#include "commit.h"
-#include "tag.h"
-#include "tree-walk.h"
-#include "diff.h"
-#include "diffcore.h"
-#include "revision.h"
-#include "quote.h"
-#include "xdiff-interface.h"
-#include "cache-tree.h"
-#include "string-list.h"
-#include "mailmap.h"
-#include "parse-options.h"
-#include "utf8.h"
-
-static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file";
-
-static const char *blame_opt_usage[] = {
-       blame_usage,
-       "",
-       "[rev-opts] are documented in git-rev-list(1)",
-       NULL
-};
-
-static int longest_file;
-static int longest_author;
-static int max_orig_digits;
-static int max_digits;
-static int max_score_digits;
-static int show_root;
-static int reverse;
-static int blank_boundary;
-static int incremental;
-static int xdl_opts = XDF_NEED_MINIMAL;
-
-static enum date_mode blame_date_mode = DATE_ISO8601;
-static size_t blame_date_width;
-
-static struct string_list mailmap;
-
-#ifndef DEBUG
-#define DEBUG 0
-#endif
-
-/* stats */
-static int num_read_blob;
-static int num_get_patch;
-static int num_commits;
-
-#define PICKAXE_BLAME_MOVE             01
-#define PICKAXE_BLAME_COPY             02
-#define PICKAXE_BLAME_COPY_HARDER      04
-#define PICKAXE_BLAME_COPY_HARDEST     010
-
-/*
- * blame for a blame_entry with score lower than these thresholds
- * is not passed to the parent using move/copy logic.
- */
-static unsigned blame_move_score;
-static unsigned blame_copy_score;
-#define BLAME_DEFAULT_MOVE_SCORE       20
-#define BLAME_DEFAULT_COPY_SCORE       40
-
-/* bits #0..7 in revision.h, #8..11 used for merge_bases() in commit.c */
-#define METAINFO_SHOWN         (1u<<12)
-#define MORE_THAN_ONE_PATH     (1u<<13)
-
-/*
- * One blob in a commit that is being suspected
- */
-struct origin {
-       int refcnt;
-       struct origin *previous;
-       struct commit *commit;
-       mmfile_t file;
-       unsigned char blob_sha1[20];
-       char path[FLEX_ARRAY];
-};
-
-/*
- * Given an origin, prepare mmfile_t structure to be used by the
- * diff machinery
- */
-static void fill_origin_blob(struct origin *o, mmfile_t *file)
-{
-       if (!o->file.ptr) {
-               enum object_type type;
-               num_read_blob++;
-               file->ptr = read_sha1_file(o->blob_sha1, &type,
-                                          (unsigned long *)(&(file->size)));
-               if (!file->ptr)
-                       die("Cannot read blob %s for path %s",
-                           sha1_to_hex(o->blob_sha1),
-                           o->path);
-               o->file = *file;
-       }
-       else
-               *file = o->file;
-}
-
-/*
- * Origin is refcounted and usually we keep the blob contents to be
- * reused.
- */
-static inline struct origin *origin_incref(struct origin *o)
-{
-       if (o)
-               o->refcnt++;
-       return o;
-}
-
-static void origin_decref(struct origin *o)
-{
-       if (o && --o->refcnt <= 0) {
-               if (o->previous)
-                       origin_decref(o->previous);
-               free(o->file.ptr);
-               free(o);
-       }
-}
-
-static void drop_origin_blob(struct origin *o)
-{
-       if (o->file.ptr) {
-               free(o->file.ptr);
-               o->file.ptr = NULL;
-       }
-}
-
-/*
- * Each group of lines is described by a blame_entry; it can be split
- * as we pass blame to the parents.  They form a linked list in the
- * scoreboard structure, sorted by the target line number.
- */
-struct blame_entry {
-       struct blame_entry *prev;
-       struct blame_entry *next;
-
-       /* the first line of this group in the final image;
-        * internally all line numbers are 0 based.
-        */
-       int lno;
-
-       /* how many lines this group has */
-       int num_lines;
-
-       /* the commit that introduced this group into the final image */
-       struct origin *suspect;
-
-       /* true if the suspect is truly guilty; false while we have not
-        * checked if the group came from one of its parents.
-        */
-       char guilty;
-
-       /* true if the entry has been scanned for copies in the current parent
-        */
-       char scanned;
-
-       /* the line number of the first line of this group in the
-        * suspect's file; internally all line numbers are 0 based.
-        */
-       int s_lno;
-
-       /* how significant this entry is -- cached to avoid
-        * scanning the lines over and over.
-        */
-       unsigned score;
-};
-
-/*
- * The current state of the blame assignment.
- */
-struct scoreboard {
-       /* the final commit (i.e. where we started digging from) */
-       struct commit *final;
-       struct rev_info *revs;
-       const char *path;
-
-       /*
-        * The contents in the final image.
-        * Used by many functions to obtain contents of the nth line,
-        * indexed with scoreboard.lineno[blame_entry.lno].
-        */
-       const char *final_buf;
-       unsigned long final_buf_size;
-
-       /* linked list of blames */
-       struct blame_entry *ent;
-
-       /* look-up a line in the final buffer */
-       int num_lines;
-       int *lineno;
-};
-
-static inline int same_suspect(struct origin *a, struct origin *b)
-{
-       if (a == b)
-               return 1;
-       if (a->commit != b->commit)
-               return 0;
-       return !strcmp(a->path, b->path);
-}
-
-static void sanity_check_refcnt(struct scoreboard *);
-
-/*
- * If two blame entries that are next to each other came from
- * contiguous lines in the same origin (i.e. <commit, path> pair),
- * merge them together.
- */
-static void coalesce(struct scoreboard *sb)
-{
-       struct blame_entry *ent, *next;
-
-       for (ent = sb->ent; ent && (next = ent->next); ent = next) {
-               if (same_suspect(ent->suspect, next->suspect) &&
-                   ent->guilty == next->guilty &&
-                   ent->s_lno + ent->num_lines == next->s_lno) {
-                       ent->num_lines += next->num_lines;
-                       ent->next = next->next;
-                       if (ent->next)
-                               ent->next->prev = ent;
-                       origin_decref(next->suspect);
-                       free(next);
-                       ent->score = 0;
-                       next = ent; /* again */
-               }
-       }
-
-       if (DEBUG) /* sanity */
-               sanity_check_refcnt(sb);
-}
-
-/*
- * Given a commit and a path in it, create a new origin structure.
- * The callers that add blame to the scoreboard should use
- * get_origin() to obtain shared, refcounted copy instead of calling
- * this function directly.
- */
-static struct origin *make_origin(struct commit *commit, const char *path)
-{
-       struct origin *o;
-       o = xcalloc(1, sizeof(*o) + strlen(path) + 1);
-       o->commit = commit;
-       o->refcnt = 1;
-       strcpy(o->path, path);
-       return o;
-}
-
-/*
- * Locate an existing origin or create a new one.
- */
-static struct origin *get_origin(struct scoreboard *sb,
-                                struct commit *commit,
-                                const char *path)
-{
-       struct blame_entry *e;
-
-       for (e = sb->ent; e; e = e->next) {
-               if (e->suspect->commit == commit &&
-                   !strcmp(e->suspect->path, path))
-                       return origin_incref(e->suspect);
-       }
-       return make_origin(commit, path);
-}
-
-/*
- * Fill the blob_sha1 field of an origin if it hasn't, so that later
- * call to fill_origin_blob() can use it to locate the data.  blob_sha1
- * for an origin is also used to pass the blame for the entire file to
- * the parent to detect the case where a child's blob is identical to
- * that of its parent's.
- */
-static int fill_blob_sha1(struct origin *origin)
-{
-       unsigned mode;
-
-       if (!is_null_sha1(origin->blob_sha1))
-               return 0;
-       if (get_tree_entry(origin->commit->object.sha1,
-                          origin->path,
-                          origin->blob_sha1, &mode))
-               goto error_out;
-       if (sha1_object_info(origin->blob_sha1, NULL) != OBJ_BLOB)
-               goto error_out;
-       return 0;
- error_out:
-       hashclr(origin->blob_sha1);
-       return -1;
-}
-
-/*
- * We have an origin -- check if the same path exists in the
- * parent and return an origin structure to represent it.
- */
-static struct origin *find_origin(struct scoreboard *sb,
-                                 struct commit *parent,
-                                 struct origin *origin)
-{
-       struct origin *porigin = NULL;
-       struct diff_options diff_opts;
-       const char *paths[2];
-
-       if (parent->util) {
-               /*
-                * Each commit object can cache one origin in that
-                * commit.  This is a freestanding copy of origin and
-                * not refcounted.
-                */
-               struct origin *cached = parent->util;
-               if (!strcmp(cached->path, origin->path)) {
-                       /*
-                        * The same path between origin and its parent
-                        * without renaming -- the most common case.
-                        */
-                       porigin = get_origin(sb, parent, cached->path);
-
-                       /*
-                        * If the origin was newly created (i.e. get_origin
-                        * would call make_origin if none is found in the
-                        * scoreboard), it does not know the blob_sha1,
-                        * so copy it.  Otherwise porigin was in the
-                        * scoreboard and already knows blob_sha1.
-                        */
-                       if (porigin->refcnt == 1)
-                               hashcpy(porigin->blob_sha1, cached->blob_sha1);
-                       return porigin;
-               }
-               /* otherwise it was not very useful; free it */
-               free(parent->util);
-               parent->util = NULL;
-       }
-
-       /* See if the origin->path is different between parent
-        * and origin first.  Most of the time they are the
-        * same and diff-tree is fairly efficient about this.
-        */
-       diff_setup(&diff_opts);
-       DIFF_OPT_SET(&diff_opts, RECURSIVE);
-       diff_opts.detect_rename = 0;
-       diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
-       paths[0] = origin->path;
-       paths[1] = NULL;
-
-       diff_tree_setup_paths(paths, &diff_opts);
-       if (diff_setup_done(&diff_opts) < 0)
-               die("diff-setup");
-
-       if (is_null_sha1(origin->commit->object.sha1))
-               do_diff_cache(parent->tree->object.sha1, &diff_opts);
-       else
-               diff_tree_sha1(parent->tree->object.sha1,
-                              origin->commit->tree->object.sha1,
-                              "", &diff_opts);
-       diffcore_std(&diff_opts);
-
-       /* It is either one entry that says "modified", or "created",
-        * or nothing.
-        */
-       if (!diff_queued_diff.nr) {
-               /* The path is the same as parent */
-               porigin = get_origin(sb, parent, origin->path);
-               hashcpy(porigin->blob_sha1, origin->blob_sha1);
-       }
-       else if (diff_queued_diff.nr != 1)
-               die("internal error in blame::find_origin");
-       else {
-               struct diff_filepair *p = diff_queued_diff.queue[0];
-               switch (p->status) {
-               default:
-                       die("internal error in blame::find_origin (%c)",
-                           p->status);
-               case 'M':
-                       porigin = get_origin(sb, parent, origin->path);
-                       hashcpy(porigin->blob_sha1, p->one->sha1);
-                       break;
-               case 'A':
-               case 'T':
-                       /* Did not exist in parent, or type changed */
-                       break;
-               }
-       }
-       diff_flush(&diff_opts);
-       diff_tree_release_paths(&diff_opts);
-       if (porigin) {
-               /*
-                * Create a freestanding copy that is not part of
-                * the refcounted origin found in the scoreboard, and
-                * cache it in the commit.
-                */
-               struct origin *cached;
-
-               cached = make_origin(porigin->commit, porigin->path);
-               hashcpy(cached->blob_sha1, porigin->blob_sha1);
-               parent->util = cached;
-       }
-       return porigin;
-}
-
-/*
- * We have an origin -- find the path that corresponds to it in its
- * parent and return an origin structure to represent it.
- */
-static struct origin *find_rename(struct scoreboard *sb,
-                                 struct commit *parent,
-                                 struct origin *origin)
-{
-       struct origin *porigin = NULL;
-       struct diff_options diff_opts;
-       int i;
-       const char *paths[2];
-
-       diff_setup(&diff_opts);
-       DIFF_OPT_SET(&diff_opts, RECURSIVE);
-       diff_opts.detect_rename = DIFF_DETECT_RENAME;
-       diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
-       diff_opts.single_follow = origin->path;
-       paths[0] = NULL;
-       diff_tree_setup_paths(paths, &diff_opts);
-       if (diff_setup_done(&diff_opts) < 0)
-               die("diff-setup");
-
-       if (is_null_sha1(origin->commit->object.sha1))
-               do_diff_cache(parent->tree->object.sha1, &diff_opts);
-       else
-               diff_tree_sha1(parent->tree->object.sha1,
-                              origin->commit->tree->object.sha1,
-                              "", &diff_opts);
-       diffcore_std(&diff_opts);
-
-       for (i = 0; i < diff_queued_diff.nr; i++) {
-               struct diff_filepair *p = diff_queued_diff.queue[i];
-               if ((p->status == 'R' || p->status == 'C') &&
-                   !strcmp(p->two->path, origin->path)) {
-                       porigin = get_origin(sb, parent, p->one->path);
-                       hashcpy(porigin->blob_sha1, p->one->sha1);
-                       break;
-               }
-       }
-       diff_flush(&diff_opts);
-       diff_tree_release_paths(&diff_opts);
-       return porigin;
-}
-
-/*
- * Link in a new blame entry to the scoreboard.  Entries that cover the
- * same line range have been removed from the scoreboard previously.
- */
-static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e)
-{
-       struct blame_entry *ent, *prev = NULL;
-
-       origin_incref(e->suspect);
-
-       for (ent = sb->ent; ent && ent->lno < e->lno; ent = ent->next)
-               prev = ent;
-
-       /* prev, if not NULL, is the last one that is below e */
-       e->prev = prev;
-       if (prev) {
-               e->next = prev->next;
-               prev->next = e;
-       }
-       else {
-               e->next = sb->ent;
-               sb->ent = e;
-       }
-       if (e->next)
-               e->next->prev = e;
-}
-
-/*
- * src typically is on-stack; we want to copy the information in it to
- * a malloced blame_entry that is already on the linked list of the
- * scoreboard.  The origin of dst loses a refcnt while the origin of src
- * gains one.
- */
-static void dup_entry(struct blame_entry *dst, struct blame_entry *src)
-{
-       struct blame_entry *p, *n;
-
-       p = dst->prev;
-       n = dst->next;
-       origin_incref(src->suspect);
-       origin_decref(dst->suspect);
-       memcpy(dst, src, sizeof(*src));
-       dst->prev = p;
-       dst->next = n;
-       dst->score = 0;
-}
-
-static const char *nth_line(struct scoreboard *sb, int lno)
-{
-       return sb->final_buf + sb->lineno[lno];
-}
-
-/*
- * It is known that lines between tlno to same came from parent, and e
- * has an overlap with that range.  it also is known that parent's
- * line plno corresponds to e's line tlno.
- *
- *                <---- e ----->
- *                   <------>
- *                   <------------>
- *             <------------>
- *             <------------------>
- *
- * Split e into potentially three parts; before this chunk, the chunk
- * to be blamed for the parent, and after that portion.
- */
-static void split_overlap(struct blame_entry *split,
-                         struct blame_entry *e,
-                         int tlno, int plno, int same,
-                         struct origin *parent)
-{
-       int chunk_end_lno;
-       memset(split, 0, sizeof(struct blame_entry [3]));
-
-       if (e->s_lno < tlno) {
-               /* there is a pre-chunk part not blamed on parent */
-               split[0].suspect = origin_incref(e->suspect);
-               split[0].lno = e->lno;
-               split[0].s_lno = e->s_lno;
-               split[0].num_lines = tlno - e->s_lno;
-               split[1].lno = e->lno + tlno - e->s_lno;
-               split[1].s_lno = plno;
-       }
-       else {
-               split[1].lno = e->lno;
-               split[1].s_lno = plno + (e->s_lno - tlno);
-       }
-
-       if (same < e->s_lno + e->num_lines) {
-               /* there is a post-chunk part not blamed on parent */
-               split[2].suspect = origin_incref(e->suspect);
-               split[2].lno = e->lno + (same - e->s_lno);
-               split[2].s_lno = e->s_lno + (same - e->s_lno);
-               split[2].num_lines = e->s_lno + e->num_lines - same;
-               chunk_end_lno = split[2].lno;
-       }
-       else
-               chunk_end_lno = e->lno + e->num_lines;
-       split[1].num_lines = chunk_end_lno - split[1].lno;
-
-       /*
-        * if it turns out there is nothing to blame the parent for,
-        * forget about the splitting.  !split[1].suspect signals this.
-        */
-       if (split[1].num_lines < 1)
-               return;
-       split[1].suspect = origin_incref(parent);
-}
-
-/*
- * split_overlap() divided an existing blame e into up to three parts
- * in split.  Adjust the linked list of blames in the scoreboard to
- * reflect the split.
- */
-static void split_blame(struct scoreboard *sb,
-                       struct blame_entry *split,
-                       struct blame_entry *e)
-{
-       struct blame_entry *new_entry;
-
-       if (split[0].suspect && split[2].suspect) {
-               /* The first part (reuse storage for the existing entry e) */
-               dup_entry(e, &split[0]);
-
-               /* The last part -- me */
-               new_entry = xmalloc(sizeof(*new_entry));
-               memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
-               add_blame_entry(sb, new_entry);
-
-               /* ... and the middle part -- parent */
-               new_entry = xmalloc(sizeof(*new_entry));
-               memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
-               add_blame_entry(sb, new_entry);
-       }
-       else if (!split[0].suspect && !split[2].suspect)
-               /*
-                * The parent covers the entire area; reuse storage for
-                * e and replace it with the parent.
-                */
-               dup_entry(e, &split[1]);
-       else if (split[0].suspect) {
-               /* me and then parent */
-               dup_entry(e, &split[0]);
-
-               new_entry = xmalloc(sizeof(*new_entry));
-               memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
-               add_blame_entry(sb, new_entry);
-       }
-       else {
-               /* parent and then me */
-               dup_entry(e, &split[1]);
-
-               new_entry = xmalloc(sizeof(*new_entry));
-               memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
-               add_blame_entry(sb, new_entry);
-       }
-
-       if (DEBUG) { /* sanity */
-               struct blame_entry *ent;
-               int lno = sb->ent->lno, corrupt = 0;
-
-               for (ent = sb->ent; ent; ent = ent->next) {
-                       if (lno != ent->lno)
-                               corrupt = 1;
-                       if (ent->s_lno < 0)
-                               corrupt = 1;
-                       lno += ent->num_lines;
-               }
-               if (corrupt) {
-                       lno = sb->ent->lno;
-                       for (ent = sb->ent; ent; ent = ent->next) {
-                               printf("L %8d l %8d n %8d\n",
-                                      lno, ent->lno, ent->num_lines);
-                               lno = ent->lno + ent->num_lines;
-                       }
-                       die("oops");
-               }
-       }
-}
-
-/*
- * After splitting the blame, the origins used by the
- * on-stack blame_entry should lose one refcnt each.
- */
-static void decref_split(struct blame_entry *split)
-{
-       int i;
-
-       for (i = 0; i < 3; i++)
-               origin_decref(split[i].suspect);
-}
-
-/*
- * Helper for blame_chunk().  blame_entry e is known to overlap with
- * the patch hunk; split it and pass blame to the parent.
- */
-static void blame_overlap(struct scoreboard *sb, struct blame_entry *e,
-                         int tlno, int plno, int same,
-                         struct origin *parent)
-{
-       struct blame_entry split[3];
-
-       split_overlap(split, e, tlno, plno, same, parent);
-       if (split[1].suspect)
-               split_blame(sb, split, e);
-       decref_split(split);
-}
-
-/*
- * Find the line number of the last line the target is suspected for.
- */
-static int find_last_in_target(struct scoreboard *sb, struct origin *target)
-{
-       struct blame_entry *e;
-       int last_in_target = -1;
-
-       for (e = sb->ent; e; e = e->next) {
-               if (e->guilty || !same_suspect(e->suspect, target))
-                       continue;
-               if (last_in_target < e->s_lno + e->num_lines)
-                       last_in_target = e->s_lno + e->num_lines;
-       }
-       return last_in_target;
-}
-
-/*
- * Process one hunk from the patch between the current suspect for
- * blame_entry e and its parent.  Find and split the overlap, and
- * pass blame to the overlapping part to the parent.
- */
-static void blame_chunk(struct scoreboard *sb,
-                       int tlno, int plno, int same,
-                       struct origin *target, struct origin *parent)
-{
-       struct blame_entry *e;
-
-       for (e = sb->ent; e; e = e->next) {
-               if (e->guilty || !same_suspect(e->suspect, target))
-                       continue;
-               if (same <= e->s_lno)
-                       continue;
-               if (tlno < e->s_lno + e->num_lines)
-                       blame_overlap(sb, e, tlno, plno, same, parent);
-       }
-}
-
-struct blame_chunk_cb_data {
-       struct scoreboard *sb;
-       struct origin *target;
-       struct origin *parent;
-       long plno;
-       long tlno;
-};
-
-static void blame_chunk_cb(void *data, long same, long p_next, long t_next)
-{
-       struct blame_chunk_cb_data *d = data;
-       blame_chunk(d->sb, d->tlno, d->plno, same, d->target, d->parent);
-       d->plno = p_next;
-       d->tlno = t_next;
-}
-
-/*
- * We are looking at the origin 'target' and aiming to pass blame
- * for the lines it is suspected to its parent.  Run diff to find
- * which lines came from parent and pass blame for them.
- */
-static int pass_blame_to_parent(struct scoreboard *sb,
-                               struct origin *target,
-                               struct origin *parent)
-{
-       int last_in_target;
-       mmfile_t file_p, file_o;
-       struct blame_chunk_cb_data d = { sb, target, parent, 0, 0 };
-       xpparam_t xpp;
-       xdemitconf_t xecfg;
-
-       last_in_target = find_last_in_target(sb, target);
-       if (last_in_target < 0)
-               return 1; /* nothing remains for this target */
-
-       fill_origin_blob(parent, &file_p);
-       fill_origin_blob(target, &file_o);
-       num_get_patch++;
-
-       memset(&xpp, 0, sizeof(xpp));
-       xpp.flags = xdl_opts;
-       memset(&xecfg, 0, sizeof(xecfg));
-       xecfg.ctxlen = 0;
-       xdi_diff_hunks(&file_p, &file_o, blame_chunk_cb, &d, &xpp, &xecfg);
-       /* The rest (i.e. anything after tlno) are the same as the parent */
-       blame_chunk(sb, d.tlno, d.plno, last_in_target, target, parent);
-
-       return 0;
-}
-
-/*
- * The lines in blame_entry after splitting blames many times can become
- * very small and trivial, and at some point it becomes pointless to
- * blame the parents.  E.g. "\t\t}\n\t}\n\n" appears everywhere in any
- * ordinary C program, and it is not worth to say it was copied from
- * totally unrelated file in the parent.
- *
- * Compute how trivial the lines in the blame_entry are.
- */
-static unsigned ent_score(struct scoreboard *sb, struct blame_entry *e)
-{
-       unsigned score;
-       const char *cp, *ep;
-
-       if (e->score)
-               return e->score;
-
-       score = 1;
-       cp = nth_line(sb, e->lno);
-       ep = nth_line(sb, e->lno + e->num_lines);
-       while (cp < ep) {
-               unsigned ch = *((unsigned char *)cp);
-               if (isalnum(ch))
-                       score++;
-               cp++;
-       }
-       e->score = score;
-       return score;
-}
-
-/*
- * best_so_far[] and this[] are both a split of an existing blame_entry
- * that passes blame to the parent.  Maintain best_so_far the best split
- * so far, by comparing this and best_so_far and copying this into
- * bst_so_far as needed.
- */
-static void copy_split_if_better(struct scoreboard *sb,
-                                struct blame_entry *best_so_far,
-                                struct blame_entry *this)
-{
-       int i;
-
-       if (!this[1].suspect)
-               return;
-       if (best_so_far[1].suspect) {
-               if (ent_score(sb, &this[1]) < ent_score(sb, &best_so_far[1]))
-                       return;
-       }
-
-       for (i = 0; i < 3; i++)
-               origin_incref(this[i].suspect);
-       decref_split(best_so_far);
-       memcpy(best_so_far, this, sizeof(struct blame_entry [3]));
-}
-
-/*
- * We are looking at a part of the final image represented by
- * ent (tlno and same are offset by ent->s_lno).
- * tlno is where we are looking at in the final image.
- * up to (but not including) same match preimage.
- * plno is where we are looking at in the preimage.
- *
- * <-------------- final image ---------------------->
- *       <------ent------>
- *         ^tlno ^same
- *    <---------preimage----->
- *         ^plno
- *
- * All line numbers are 0-based.
- */
-static void handle_split(struct scoreboard *sb,
-                        struct blame_entry *ent,
-                        int tlno, int plno, int same,
-                        struct origin *parent,
-                        struct blame_entry *split)
-{
-       if (ent->num_lines <= tlno)
-               return;
-       if (tlno < same) {
-               struct blame_entry this[3];
-               tlno += ent->s_lno;
-               same += ent->s_lno;
-               split_overlap(this, ent, tlno, plno, same, parent);
-               copy_split_if_better(sb, split, this);
-               decref_split(this);
-       }
-}
-
-struct handle_split_cb_data {
-       struct scoreboard *sb;
-       struct blame_entry *ent;
-       struct origin *parent;
-       struct blame_entry *split;
-       long plno;
-       long tlno;
-};
-
-static void handle_split_cb(void *data, long same, long p_next, long t_next)
-{
-       struct handle_split_cb_data *d = data;
-       handle_split(d->sb, d->ent, d->tlno, d->plno, same, d->parent, d->split);
-       d->plno = p_next;
-       d->tlno = t_next;
-}
-
-/*
- * Find the lines from parent that are the same as ent so that
- * we can pass blames to it.  file_p has the blob contents for
- * the parent.
- */
-static void find_copy_in_blob(struct scoreboard *sb,
-                             struct blame_entry *ent,
-                             struct origin *parent,
-                             struct blame_entry *split,
-                             mmfile_t *file_p)
-{
-       const char *cp;
-       int cnt;
-       mmfile_t file_o;
-       struct handle_split_cb_data d = { sb, ent, parent, split, 0, 0 };
-       xpparam_t xpp;
-       xdemitconf_t xecfg;
-
-       /*
-        * Prepare mmfile that contains only the lines in ent.
-        */
-       cp = nth_line(sb, ent->lno);
-       file_o.ptr = (char *) cp;
-       cnt = ent->num_lines;
-
-       while (cnt && cp < sb->final_buf + sb->final_buf_size) {
-               if (*cp++ == '\n')
-                       cnt--;
-       }
-       file_o.size = cp - file_o.ptr;
-
-       /*
-        * file_o is a part of final image we are annotating.
-        * file_p partially may match that image.
-        */
-       memset(&xpp, 0, sizeof(xpp));
-       xpp.flags = xdl_opts;
-       memset(&xecfg, 0, sizeof(xecfg));
-       xecfg.ctxlen = 1;
-       memset(split, 0, sizeof(struct blame_entry [3]));
-       xdi_diff_hunks(file_p, &file_o, handle_split_cb, &d, &xpp, &xecfg);
-       /* remainder, if any, all match the preimage */
-       handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split);
-}
-
-/*
- * See if lines currently target is suspected for can be attributed to
- * parent.
- */
-static int find_move_in_parent(struct scoreboard *sb,
-                              struct origin *target,
-                              struct origin *parent)
-{
-       int last_in_target, made_progress;
-       struct blame_entry *e, split[3];
-       mmfile_t file_p;
-
-       last_in_target = find_last_in_target(sb, target);
-       if (last_in_target < 0)
-               return 1; /* nothing remains for this target */
-
-       fill_origin_blob(parent, &file_p);
-       if (!file_p.ptr)
-               return 0;
-
-       made_progress = 1;
-       while (made_progress) {
-               made_progress = 0;
-               for (e = sb->ent; e; e = e->next) {
-                       if (e->guilty || !same_suspect(e->suspect, target) ||
-                           ent_score(sb, e) < blame_move_score)
-                               continue;
-                       find_copy_in_blob(sb, e, parent, split, &file_p);
-                       if (split[1].suspect &&
-                           blame_move_score < ent_score(sb, &split[1])) {
-                               split_blame(sb, split, e);
-                               made_progress = 1;
-                       }
-                       decref_split(split);
-               }
-       }
-       return 0;
-}
-
-struct blame_list {
-       struct blame_entry *ent;
-       struct blame_entry split[3];
-};
-
-/*
- * Count the number of entries the target is suspected for,
- * and prepare a list of entry and the best split.
- */
-static struct blame_list *setup_blame_list(struct scoreboard *sb,
-                                          struct origin *target,
-                                          int min_score,
-                                          int *num_ents_p)
-{
-       struct blame_entry *e;
-       int num_ents, i;
-       struct blame_list *blame_list = NULL;
-
-       for (e = sb->ent, num_ents = 0; e; e = e->next)
-               if (!e->scanned && !e->guilty &&
-                   same_suspect(e->suspect, target) &&
-                   min_score < ent_score(sb, e))
-                       num_ents++;
-       if (num_ents) {
-               blame_list = xcalloc(num_ents, sizeof(struct blame_list));
-               for (e = sb->ent, i = 0; e; e = e->next)
-                       if (!e->scanned && !e->guilty &&
-                           same_suspect(e->suspect, target) &&
-                           min_score < ent_score(sb, e))
-                               blame_list[i++].ent = e;
-       }
-       *num_ents_p = num_ents;
-       return blame_list;
-}
-
-/*
- * Reset the scanned status on all entries.
- */
-static void reset_scanned_flag(struct scoreboard *sb)
-{
-       struct blame_entry *e;
-       for (e = sb->ent; e; e = e->next)
-               e->scanned = 0;
-}
-
-/*
- * For lines target is suspected for, see if we can find code movement
- * across file boundary from the parent commit.  porigin is the path
- * in the parent we already tried.
- */
-static int find_copy_in_parent(struct scoreboard *sb,
-                              struct origin *target,
-                              struct commit *parent,
-                              struct origin *porigin,
-                              int opt)
-{
-       struct diff_options diff_opts;
-       const char *paths[1];
-       int i, j;
-       int retval;
-       struct blame_list *blame_list;
-       int num_ents;
-
-       blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents);
-       if (!blame_list)
-               return 1; /* nothing remains for this target */
-
-       diff_setup(&diff_opts);
-       DIFF_OPT_SET(&diff_opts, RECURSIVE);
-       diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
-
-       paths[0] = NULL;
-       diff_tree_setup_paths(paths, &diff_opts);
-       if (diff_setup_done(&diff_opts) < 0)
-               die("diff-setup");
-
-       /* Try "find copies harder" on new path if requested;
-        * we do not want to use diffcore_rename() actually to
-        * match things up; find_copies_harder is set only to
-        * force diff_tree_sha1() to feed all filepairs to diff_queue,
-        * and this code needs to be after diff_setup_done(), which
-        * usually makes find-copies-harder imply copy detection.
-        */
-       if ((opt & PICKAXE_BLAME_COPY_HARDEST)
-           || ((opt & PICKAXE_BLAME_COPY_HARDER)
-               && (!porigin || strcmp(target->path, porigin->path))))
-               DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
-
-       if (is_null_sha1(target->commit->object.sha1))
-               do_diff_cache(parent->tree->object.sha1, &diff_opts);
-       else
-               diff_tree_sha1(parent->tree->object.sha1,
-                              target->commit->tree->object.sha1,
-                              "", &diff_opts);
-
-       if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER))
-               diffcore_std(&diff_opts);
-
-       retval = 0;
-       while (1) {
-               int made_progress = 0;
-
-               for (i = 0; i < diff_queued_diff.nr; i++) {
-                       struct diff_filepair *p = diff_queued_diff.queue[i];
-                       struct origin *norigin;
-                       mmfile_t file_p;
-                       struct blame_entry this[3];
-
-                       if (!DIFF_FILE_VALID(p->one))
-                               continue; /* does not exist in parent */
-                       if (S_ISGITLINK(p->one->mode))
-                               continue; /* ignore git links */
-                       if (porigin && !strcmp(p->one->path, porigin->path))
-                               /* find_move already dealt with this path */
-                               continue;
-
-                       norigin = get_origin(sb, parent, p->one->path);
-                       hashcpy(norigin->blob_sha1, p->one->sha1);
-                       fill_origin_blob(norigin, &file_p);
-                       if (!file_p.ptr)
-                               continue;
-
-                       for (j = 0; j < num_ents; j++) {
-                               find_copy_in_blob(sb, blame_list[j].ent,
-                                                 norigin, this, &file_p);
-                               copy_split_if_better(sb, blame_list[j].split,
-                                                    this);
-                               decref_split(this);
-                       }
-                       origin_decref(norigin);
-               }
-
-               for (j = 0; j < num_ents; j++) {
-                       struct blame_entry *split = blame_list[j].split;
-                       if (split[1].suspect &&
-                           blame_copy_score < ent_score(sb, &split[1])) {
-                               split_blame(sb, split, blame_list[j].ent);
-                               made_progress = 1;
-                       }
-                       else
-                               blame_list[j].ent->scanned = 1;
-                       decref_split(split);
-               }
-               free(blame_list);
-
-               if (!made_progress)
-                       break;
-               blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents);
-               if (!blame_list) {
-                       retval = 1;
-                       break;
-               }
-       }
-       reset_scanned_flag(sb);
-       diff_flush(&diff_opts);
-       diff_tree_release_paths(&diff_opts);
-       return retval;
-}
-
-/*
- * The blobs of origin and porigin exactly match, so everything
- * origin is suspected for can be blamed on the parent.
- */
-static void pass_whole_blame(struct scoreboard *sb,
-                            struct origin *origin, struct origin *porigin)
-{
-       struct blame_entry *e;
-
-       if (!porigin->file.ptr && origin->file.ptr) {
-               /* Steal its file */
-               porigin->file = origin->file;
-               origin->file.ptr = NULL;
-       }
-       for (e = sb->ent; e; e = e->next) {
-               if (!same_suspect(e->suspect, origin))
-                       continue;
-               origin_incref(porigin);
-               origin_decref(e->suspect);
-               e->suspect = porigin;
-       }
-}
-
-/*
- * We pass blame from the current commit to its parents.  We keep saying
- * "parent" (and "porigin"), but what we mean is to find scapegoat to
- * exonerate ourselves.
- */
-static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit)
-{
-       if (!reverse)
-               return commit->parents;
-       return lookup_decoration(&revs->children, &commit->object);
-}
-
-static int num_scapegoats(struct rev_info *revs, struct commit *commit)
-{
-       int cnt;
-       struct commit_list *l = first_scapegoat(revs, commit);
-       for (cnt = 0; l; l = l->next)
-               cnt++;
-       return cnt;
-}
-
-#define MAXSG 16
-
-static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
-{
-       struct rev_info *revs = sb->revs;
-       int i, pass, num_sg;
-       struct commit *commit = origin->commit;
-       struct commit_list *sg;
-       struct origin *sg_buf[MAXSG];
-       struct origin *porigin, **sg_origin = sg_buf;
-
-       num_sg = num_scapegoats(revs, commit);
-       if (!num_sg)
-               goto finish;
-       else if (num_sg < ARRAY_SIZE(sg_buf))
-               memset(sg_buf, 0, sizeof(sg_buf));
-       else
-               sg_origin = xcalloc(num_sg, sizeof(*sg_origin));
-
-       /*
-        * The first pass looks for unrenamed path to optimize for
-        * common cases, then we look for renames in the second pass.
-        */
-       for (pass = 0; pass < 2; pass++) {
-               struct origin *(*find)(struct scoreboard *,
-                                      struct commit *, struct origin *);
-               find = pass ? find_rename : find_origin;
-
-               for (i = 0, sg = first_scapegoat(revs, commit);
-                    i < num_sg && sg;
-                    sg = sg->next, i++) {
-                       struct commit *p = sg->item;
-                       int j, same;
-
-                       if (sg_origin[i])
-                               continue;
-                       if (parse_commit(p))
-                               continue;
-                       porigin = find(sb, p, origin);
-                       if (!porigin)
-                               continue;
-                       if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) {
-                               pass_whole_blame(sb, origin, porigin);
-                               origin_decref(porigin);
-                               goto finish;
-                       }
-                       for (j = same = 0; j < i; j++)
-                               if (sg_origin[j] &&
-                                   !hashcmp(sg_origin[j]->blob_sha1,
-                                            porigin->blob_sha1)) {
-                                       same = 1;
-                                       break;
-                               }
-                       if (!same)
-                               sg_origin[i] = porigin;
-                       else
-                               origin_decref(porigin);
-               }
-       }
-
-       num_commits++;
-       for (i = 0, sg = first_scapegoat(revs, commit);
-            i < num_sg && sg;
-            sg = sg->next, i++) {
-               struct origin *porigin = sg_origin[i];
-               if (!porigin)
-                       continue;
-               if (!origin->previous) {
-                       origin_incref(porigin);
-                       origin->previous = porigin;
-               }
-               if (pass_blame_to_parent(sb, origin, porigin))
-                       goto finish;
-       }
-
-       /*
-        * Optionally find moves in parents' files.
-        */
-       if (opt & PICKAXE_BLAME_MOVE)
-               for (i = 0, sg = first_scapegoat(revs, commit);
-                    i < num_sg && sg;
-                    sg = sg->next, i++) {
-                       struct origin *porigin = sg_origin[i];
-                       if (!porigin)
-                               continue;
-                       if (find_move_in_parent(sb, origin, porigin))
-                               goto finish;
-               }
-
-       /*
-        * Optionally find copies from parents' files.
-        */
-       if (opt & PICKAXE_BLAME_COPY)
-               for (i = 0, sg = first_scapegoat(revs, commit);
-                    i < num_sg && sg;
-                    sg = sg->next, i++) {
-                       struct origin *porigin = sg_origin[i];
-                       if (find_copy_in_parent(sb, origin, sg->item,
-                                               porigin, opt))
-                               goto finish;
-               }
-
- finish:
-       for (i = 0; i < num_sg; i++) {
-               if (sg_origin[i]) {
-                       drop_origin_blob(sg_origin[i]);
-                       origin_decref(sg_origin[i]);
-               }
-       }
-       drop_origin_blob(origin);
-       if (sg_buf != sg_origin)
-               free(sg_origin);
-}
-
-/*
- * Information on commits, used for output.
- */
-struct commit_info
-{
-       const char *author;
-       const char *author_mail;
-       unsigned long author_time;
-       const char *author_tz;
-
-       /* filled only when asked for details */
-       const char *committer;
-       const char *committer_mail;
-       unsigned long committer_time;
-       const char *committer_tz;
-
-       const char *summary;
-};
-
-/*
- * Parse author/committer line in the commit object buffer
- */
-static void get_ac_line(const char *inbuf, const char *what,
-                       int person_len, char *person,
-                       int mail_len, char *mail,
-                       unsigned long *time, const char **tz)
-{
-       int len, tzlen, maillen;
-       char *tmp, *endp, *timepos, *mailpos;
-
-       tmp = strstr(inbuf, what);
-       if (!tmp)
-               goto error_out;
-       tmp += strlen(what);
-       endp = strchr(tmp, '\n');
-       if (!endp)
-               len = strlen(tmp);
-       else
-               len = endp - tmp;
-       if (person_len <= len) {
-       error_out:
-               /* Ugh */
-               *tz = "(unknown)";
-               strcpy(mail, *tz);
-               *time = 0;
-               return;
-       }
-       memcpy(person, tmp, len);
-
-       tmp = person;
-       tmp += len;
-       *tmp = 0;
-       while (*tmp != ' ')
-               tmp--;
-       *tz = tmp+1;
-       tzlen = (person+len)-(tmp+1);
-
-       *tmp = 0;
-       while (*tmp != ' ')
-               tmp--;
-       *time = strtoul(tmp, NULL, 10);
-       timepos = tmp;
-
-       *tmp = 0;
-       while (*tmp != ' ')
-               tmp--;
-       mailpos = tmp + 1;
-       *tmp = 0;
-       maillen = timepos - tmp;
-       memcpy(mail, mailpos, maillen);
-
-       if (!mailmap.nr)
-               return;
-
-       /*
-        * mailmap expansion may make the name longer.
-        * make room by pushing stuff down.
-        */
-       tmp = person + person_len - (tzlen + 1);
-       memmove(tmp, *tz, tzlen);
-       tmp[tzlen] = 0;
-       *tz = tmp;
-
-       /*
-        * Now, convert both name and e-mail using mailmap
-        */
-       if(map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) {
-               /* Add a trailing '>' to email, since map_user returns plain emails
-                  Note: It already has '<', since we replace from mail+1 */
-               mailpos = memchr(mail, '\0', mail_len);
-               if (mailpos && mailpos-mail < mail_len - 1) {
-                       *mailpos = '>';
-                       *(mailpos+1) = '\0';
-               }
-       }
-}
-
-static void get_commit_info(struct commit *commit,
-                           struct commit_info *ret,
-                           int detailed)
-{
-       int len;
-       char *tmp, *endp, *reencoded, *message;
-       static char author_name[1024];
-       static char author_mail[1024];
-       static char committer_name[1024];
-       static char committer_mail[1024];
-       static char summary_buf[1024];
-
-       /*
-        * We've operated without save_commit_buffer, so
-        * we now need to populate them for output.
-        */
-       if (!commit->buffer) {
-               enum object_type type;
-               unsigned long size;
-               commit->buffer =
-                       read_sha1_file(commit->object.sha1, &type, &size);
-               if (!commit->buffer)
-                       die("Cannot read commit %s",
-                           sha1_to_hex(commit->object.sha1));
-       }
-       reencoded = reencode_commit_message(commit, NULL);
-       message   = reencoded ? reencoded : commit->buffer;
-       ret->author = author_name;
-       ret->author_mail = author_mail;
-       get_ac_line(message, "\nauthor ",
-                   sizeof(author_name), author_name,
-                   sizeof(author_mail), author_mail,
-                   &ret->author_time, &ret->author_tz);
-
-       if (!detailed) {
-               free(reencoded);
-               return;
-       }
-
-       ret->committer = committer_name;
-       ret->committer_mail = committer_mail;
-       get_ac_line(message, "\ncommitter ",
-                   sizeof(committer_name), committer_name,
-                   sizeof(committer_mail), committer_mail,
-                   &ret->committer_time, &ret->committer_tz);
-
-       ret->summary = summary_buf;
-       tmp = strstr(message, "\n\n");
-       if (!tmp) {
-       error_out:
-               sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1));
-               free(reencoded);
-               return;
-       }
-       tmp += 2;
-       endp = strchr(tmp, '\n');
-       if (!endp)
-               endp = tmp + strlen(tmp);
-       len = endp - tmp;
-       if (len >= sizeof(summary_buf) || len == 0)
-               goto error_out;
-       memcpy(summary_buf, tmp, len);
-       summary_buf[len] = 0;
-       free(reencoded);
-}
-
-/*
- * To allow LF and other nonportable characters in pathnames,
- * they are c-style quoted as needed.
- */
-static void write_filename_info(const char *path)
-{
-       printf("filename ");
-       write_name_quoted(path, stdout, '\n');
-}
-
-/*
- * Porcelain/Incremental format wants to show a lot of details per
- * commit.  Instead of repeating this every line, emit it only once,
- * the first time each commit appears in the output.
- */
-static int emit_one_suspect_detail(struct origin *suspect)
-{
-       struct commit_info ci;
-
-       if (suspect->commit->object.flags & METAINFO_SHOWN)
-               return 0;
-
-       suspect->commit->object.flags |= METAINFO_SHOWN;
-       get_commit_info(suspect->commit, &ci, 1);
-       printf("author %s\n", ci.author);
-       printf("author-mail %s\n", ci.author_mail);
-       printf("author-time %lu\n", ci.author_time);
-       printf("author-tz %s\n", ci.author_tz);
-       printf("committer %s\n", ci.committer);
-       printf("committer-mail %s\n", ci.committer_mail);
-       printf("committer-time %lu\n", ci.committer_time);
-       printf("committer-tz %s\n", ci.committer_tz);
-       printf("summary %s\n", ci.summary);
-       if (suspect->commit->object.flags & UNINTERESTING)
-               printf("boundary\n");
-       if (suspect->previous) {
-               struct origin *prev = suspect->previous;
-               printf("previous %s ", sha1_to_hex(prev->commit->object.sha1));
-               write_name_quoted(prev->path, stdout, '\n');
-       }
-       return 1;
-}
-
-/*
- * The blame_entry is found to be guilty for the range.  Mark it
- * as such, and show it in incremental output.
- */
-static void found_guilty_entry(struct blame_entry *ent)
-{
-       if (ent->guilty)
-               return;
-       ent->guilty = 1;
-       if (incremental) {
-               struct origin *suspect = ent->suspect;
-
-               printf("%s %d %d %d\n",
-                      sha1_to_hex(suspect->commit->object.sha1),
-                      ent->s_lno + 1, ent->lno + 1, ent->num_lines);
-               emit_one_suspect_detail(suspect);
-               write_filename_info(suspect->path);
-               maybe_flush_or_die(stdout, "stdout");
-       }
-}
-
-/*
- * The main loop -- while the scoreboard has lines whose true origin
- * is still unknown, pick one blame_entry, and allow its current
- * suspect to pass blames to its parents.
- */
-static void assign_blame(struct scoreboard *sb, int opt)
-{
-       struct rev_info *revs = sb->revs;
-
-       while (1) {
-               struct blame_entry *ent;
-               struct commit *commit;
-               struct origin *suspect = NULL;
-
-               /* find one suspect to break down */
-               for (ent = sb->ent; !suspect && ent; ent = ent->next)
-                       if (!ent->guilty)
-                               suspect = ent->suspect;
-               if (!suspect)
-                       return; /* all done */
-
-               /*
-                * We will use this suspect later in the loop,
-                * so hold onto it in the meantime.
-                */
-               origin_incref(suspect);
-               commit = suspect->commit;
-               if (!commit->object.parsed)
-                       parse_commit(commit);
-               if (reverse ||
-                   (!(commit->object.flags & UNINTERESTING) &&
-                    !(revs->max_age != -1 && commit->date < revs->max_age)))
-                       pass_blame(sb, suspect, opt);
-               else {
-                       commit->object.flags |= UNINTERESTING;
-                       if (commit->object.parsed)
-                               mark_parents_uninteresting(commit);
-               }
-               /* treat root commit as boundary */
-               if (!commit->parents && !show_root)
-                       commit->object.flags |= UNINTERESTING;
-
-               /* Take responsibility for the remaining entries */
-               for (ent = sb->ent; ent; ent = ent->next)
-                       if (same_suspect(ent->suspect, suspect))
-                               found_guilty_entry(ent);
-               origin_decref(suspect);
-
-               if (DEBUG) /* sanity */
-                       sanity_check_refcnt(sb);
-       }
-}
-
-static const char *format_time(unsigned long time, const char *tz_str,
-                              int show_raw_time)
-{
-       static char time_buf[128];
-       const char *time_str;
-       int time_len;
-       int tz;
-
-       if (show_raw_time) {
-               sprintf(time_buf, "%lu %s", time, tz_str);
-       }
-       else {
-               tz = atoi(tz_str);
-               time_str = show_date(time, tz, blame_date_mode);
-               time_len = strlen(time_str);
-               memcpy(time_buf, time_str, time_len);
-               memset(time_buf + time_len, ' ', blame_date_width - time_len);
-       }
-       return time_buf;
-}
-
-#define OUTPUT_ANNOTATE_COMPAT 001
-#define OUTPUT_LONG_OBJECT_NAME        002
-#define OUTPUT_RAW_TIMESTAMP   004
-#define OUTPUT_PORCELAIN       010
-#define OUTPUT_SHOW_NAME       020
-#define OUTPUT_SHOW_NUMBER     040
-#define OUTPUT_SHOW_SCORE      0100
-#define OUTPUT_NO_AUTHOR       0200
-
-static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
-{
-       int cnt;
-       const char *cp;
-       struct origin *suspect = ent->suspect;
-       char hex[41];
-
-       strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
-       printf("%s%c%d %d %d\n",
-              hex,
-              ent->guilty ? ' ' : '*', // purely for debugging
-              ent->s_lno + 1,
-              ent->lno + 1,
-              ent->num_lines);
-       if (emit_one_suspect_detail(suspect) ||
-           (suspect->commit->object.flags & MORE_THAN_ONE_PATH))
-               write_filename_info(suspect->path);
-
-       cp = nth_line(sb, ent->lno);
-       for (cnt = 0; cnt < ent->num_lines; cnt++) {
-               char ch;
-               if (cnt)
-                       printf("%s %d %d\n", hex,
-                              ent->s_lno + 1 + cnt,
-                              ent->lno + 1 + cnt);
-               putchar('\t');
-               do {
-                       ch = *cp++;
-                       putchar(ch);
-               } while (ch != '\n' &&
-                        cp < sb->final_buf + sb->final_buf_size);
-       }
-}
-
-static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
-{
-       int cnt;
-       const char *cp;
-       struct origin *suspect = ent->suspect;
-       struct commit_info ci;
-       char hex[41];
-       int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP);
-
-       get_commit_info(suspect->commit, &ci, 1);
-       strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
-
-       cp = nth_line(sb, ent->lno);
-       for (cnt = 0; cnt < ent->num_lines; cnt++) {
-               char ch;
-               int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8;
-
-               if (suspect->commit->object.flags & UNINTERESTING) {
-                       if (blank_boundary)
-                               memset(hex, ' ', length);
-                       else if (!(opt & OUTPUT_ANNOTATE_COMPAT)) {
-                               length--;
-                               putchar('^');
-                       }
-               }
-
-               printf("%.*s", length, hex);
-               if (opt & OUTPUT_ANNOTATE_COMPAT)
-                       printf("\t(%10s\t%10s\t%d)", ci.author,
-                              format_time(ci.author_time, ci.author_tz,
-                                          show_raw_time),
-                              ent->lno + 1 + cnt);
-               else {
-                       if (opt & OUTPUT_SHOW_SCORE)
-                               printf(" %*d %02d",
-                                      max_score_digits, ent->score,
-                                      ent->suspect->refcnt);
-                       if (opt & OUTPUT_SHOW_NAME)
-                               printf(" %-*.*s", longest_file, longest_file,
-                                      suspect->path);
-                       if (opt & OUTPUT_SHOW_NUMBER)
-                               printf(" %*d", max_orig_digits,
-                                      ent->s_lno + 1 + cnt);
-
-                       if (!(opt & OUTPUT_NO_AUTHOR)) {
-                               int pad = longest_author - utf8_strwidth(ci.author);
-                               printf(" (%s%*s %10s",
-                                      ci.author, pad, "",
-                                      format_time(ci.author_time,
-                                                  ci.author_tz,
-                                                  show_raw_time));
-                       }
-                       printf(" %*d) ",
-                              max_digits, ent->lno + 1 + cnt);
-               }
-               do {
-                       ch = *cp++;
-                       putchar(ch);
-               } while (ch != '\n' &&
-                        cp < sb->final_buf + sb->final_buf_size);
-       }
-}
-
-static void output(struct scoreboard *sb, int option)
-{
-       struct blame_entry *ent;
-
-       if (option & OUTPUT_PORCELAIN) {
-               for (ent = sb->ent; ent; ent = ent->next) {
-                       struct blame_entry *oth;
-                       struct origin *suspect = ent->suspect;
-                       struct commit *commit = suspect->commit;
-                       if (commit->object.flags & MORE_THAN_ONE_PATH)
-                               continue;
-                       for (oth = ent->next; oth; oth = oth->next) {
-                               if ((oth->suspect->commit != commit) ||
-                                   !strcmp(oth->suspect->path, suspect->path))
-                                       continue;
-                               commit->object.flags |= MORE_THAN_ONE_PATH;
-                               break;
-                       }
-               }
-       }
-
-       for (ent = sb->ent; ent; ent = ent->next) {
-               if (option & OUTPUT_PORCELAIN)
-                       emit_porcelain(sb, ent);
-               else {
-                       emit_other(sb, ent, option);
-               }
-       }
-}
-
-/*
- * To allow quick access to the contents of nth line in the
- * final image, prepare an index in the scoreboard.
- */
-static int prepare_lines(struct scoreboard *sb)
-{
-       const char *buf = sb->final_buf;
-       unsigned long len = sb->final_buf_size;
-       int num = 0, incomplete = 0, bol = 1;
-
-       if (len && buf[len-1] != '\n')
-               incomplete++; /* incomplete line at the end */
-       while (len--) {
-               if (bol) {
-                       sb->lineno = xrealloc(sb->lineno,
-                                             sizeof(int *) * (num + 1));
-                       sb->lineno[num] = buf - sb->final_buf;
-                       bol = 0;
-               }
-               if (*buf++ == '\n') {
-                       num++;
-                       bol = 1;
-               }
-       }
-       sb->lineno = xrealloc(sb->lineno,
-                             sizeof(int *) * (num + incomplete + 1));
-       sb->lineno[num + incomplete] = buf - sb->final_buf;
-       sb->num_lines = num + incomplete;
-       return sb->num_lines;
-}
-
-/*
- * Add phony grafts for use with -S; this is primarily to
- * support git's cvsserver that wants to give a linear history
- * to its clients.
- */
-static int read_ancestry(const char *graft_file)
-{
-       FILE *fp = fopen(graft_file, "r");
-       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)
-                       register_commit_graft(graft, 0);
-       }
-       fclose(fp);
-       return 0;
-}
-
-/*
- * How many columns do we need to show line numbers in decimal?
- */
-static int lineno_width(int lines)
-{
-       int i, width;
-
-       for (width = 1, i = 10; i <= lines + 1; width++)
-               i *= 10;
-       return width;
-}
-
-/*
- * How many columns do we need to show line numbers, authors,
- * and filenames?
- */
-static void find_alignment(struct scoreboard *sb, int *option)
-{
-       int longest_src_lines = 0;
-       int longest_dst_lines = 0;
-       unsigned largest_score = 0;
-       struct blame_entry *e;
-
-       for (e = sb->ent; e; e = e->next) {
-               struct origin *suspect = e->suspect;
-               struct commit_info ci;
-               int num;
-
-               if (strcmp(suspect->path, sb->path))
-                       *option |= OUTPUT_SHOW_NAME;
-               num = strlen(suspect->path);
-               if (longest_file < num)
-                       longest_file = num;
-               if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
-                       suspect->commit->object.flags |= METAINFO_SHOWN;
-                       get_commit_info(suspect->commit, &ci, 1);
-                       num = utf8_strwidth(ci.author);
-                       if (longest_author < num)
-                               longest_author = num;
-               }
-               num = e->s_lno + e->num_lines;
-               if (longest_src_lines < num)
-                       longest_src_lines = num;
-               num = e->lno + e->num_lines;
-               if (longest_dst_lines < num)
-                       longest_dst_lines = num;
-               if (largest_score < ent_score(sb, e))
-                       largest_score = ent_score(sb, e);
-       }
-       max_orig_digits = lineno_width(longest_src_lines);
-       max_digits = lineno_width(longest_dst_lines);
-       max_score_digits = lineno_width(largest_score);
-}
-
-/*
- * For debugging -- origin is refcounted, and this asserts that
- * we do not underflow.
- */
-static void sanity_check_refcnt(struct scoreboard *sb)
-{
-       int baa = 0;
-       struct blame_entry *ent;
-
-       for (ent = sb->ent; ent; ent = ent->next) {
-               /* Nobody should have zero or negative refcnt */
-               if (ent->suspect->refcnt <= 0) {
-                       fprintf(stderr, "%s in %s has negative refcnt %d\n",
-                               ent->suspect->path,
-                               sha1_to_hex(ent->suspect->commit->object.sha1),
-                               ent->suspect->refcnt);
-                       baa = 1;
-               }
-       }
-       if (baa) {
-               int opt = 0160;
-               find_alignment(sb, &opt);
-               output(sb, opt);
-               die("Baa %d!", baa);
-       }
-}
-
-/*
- * Used for the command line parsing; check if the path exists
- * in the working tree.
- */
-static int has_string_in_work_tree(const char *path)
-{
-       struct stat st;
-       return !lstat(path, &st);
-}
-
-static unsigned parse_score(const char *arg)
-{
-       char *end;
-       unsigned long score = strtoul(arg, &end, 10);
-       if (*end)
-               return 0;
-       return score;
-}
-
-static const char *add_prefix(const char *prefix, const char *path)
-{
-       return prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
-}
-
-/*
- * Parsing of (comma separated) one item in the -L option
- */
-static const char *parse_loc(const char *spec,
-                            struct scoreboard *sb, long lno,
-                            long begin, long *ret)
-{
-       char *term;
-       const char *line;
-       long num;
-       int reg_error;
-       regex_t regexp;
-       regmatch_t match[1];
-
-       /* Allow "-L <something>,+20" to mean starting at <something>
-        * for 20 lines, or "-L <something>,-5" for 5 lines ending at
-        * <something>.
-        */
-       if (1 < begin && (spec[0] == '+' || spec[0] == '-')) {
-               num = strtol(spec + 1, &term, 10);
-               if (term != spec + 1) {
-                       if (spec[0] == '-')
-                               num = 0 - num;
-                       if (0 < num)
-                               *ret = begin + num - 2;
-                       else if (!num)
-                               *ret = begin;
-                       else
-                               *ret = begin + num;
-                       return term;
-               }
-               return spec;
-       }
-       num = strtol(spec, &term, 10);
-       if (term != spec) {
-               *ret = num;
-               return term;
-       }
-       if (spec[0] != '/')
-               return spec;
-
-       /* it could be a regexp of form /.../ */
-       for (term = (char *) spec + 1; *term && *term != '/'; term++) {
-               if (*term == '\\')
-                       term++;
-       }
-       if (*term != '/')
-               return spec;
-
-       /* try [spec+1 .. term-1] as regexp */
-       *term = 0;
-       begin--; /* input is in human terms */
-       line = nth_line(sb, begin);
-
-       if (!(reg_error = regcomp(&regexp, spec + 1, REG_NEWLINE)) &&
-           !(reg_error = regexec(&regexp, line, 1, match, 0))) {
-               const char *cp = line + match[0].rm_so;
-               const char *nline;
-
-               while (begin++ < lno) {
-                       nline = nth_line(sb, begin);
-                       if (line <= cp && cp < nline)
-                               break;
-                       line = nline;
-               }
-               *ret = begin;
-               regfree(&regexp);
-               *term++ = '/';
-               return term;
-       }
-       else {
-               char errbuf[1024];
-               regerror(reg_error, &regexp, errbuf, 1024);
-               die("-L parameter '%s': %s", spec + 1, errbuf);
-       }
-}
-
-/*
- * Parsing of -L option
- */
-static void prepare_blame_range(struct scoreboard *sb,
-                               const char *bottomtop,
-                               long lno,
-                               long *bottom, long *top)
-{
-       const char *term;
-
-       term = parse_loc(bottomtop, sb, lno, 1, bottom);
-       if (*term == ',') {
-               term = parse_loc(term + 1, sb, lno, *bottom + 1, top);
-               if (*term)
-                       usage(blame_usage);
-       }
-       if (*term)
-               usage(blame_usage);
-}
-
-static int git_blame_config(const char *var, const char *value, void *cb)
-{
-       if (!strcmp(var, "blame.showroot")) {
-               show_root = git_config_bool(var, value);
-               return 0;
-       }
-       if (!strcmp(var, "blame.blankboundary")) {
-               blank_boundary = git_config_bool(var, value);
-               return 0;
-       }
-       if (!strcmp(var, "blame.date")) {
-               if (!value)
-                       return config_error_nonbool(var);
-               blame_date_mode = parse_date_format(value);
-               return 0;
-       }
-       return git_default_config(var, value, cb);
-}
-
-/*
- * Prepare a dummy commit that represents the work tree (or staged) item.
- * Note that annotating work tree item never works in the reverse.
- */
-static struct commit *fake_working_tree_commit(const char *path, const char *contents_from)
-{
-       struct commit *commit;
-       struct origin *origin;
-       unsigned char head_sha1[20];
-       struct strbuf buf = STRBUF_INIT;
-       const char *ident;
-       time_t now;
-       int size, len;
-       struct cache_entry *ce;
-       unsigned mode;
-
-       if (get_sha1("HEAD", head_sha1))
-               die("No such ref: HEAD");
-
-       time(&now);
-       commit = xcalloc(1, sizeof(*commit));
-       commit->parents = xcalloc(1, sizeof(*commit->parents));
-       commit->parents->item = lookup_commit_reference(head_sha1);
-       commit->object.parsed = 1;
-       commit->date = now;
-       commit->object.type = OBJ_COMMIT;
-
-       origin = make_origin(commit, path);
-
-       if (!contents_from || strcmp("-", contents_from)) {
-               struct stat st;
-               const char *read_from;
-
-               if (contents_from) {
-                       if (stat(contents_from, &st) < 0)
-                               die("Cannot stat %s", contents_from);
-                       read_from = contents_from;
-               }
-               else {
-                       if (lstat(path, &st) < 0)
-                               die("Cannot lstat %s", path);
-                       read_from = path;
-               }
-               mode = canon_mode(st.st_mode);
-               switch (st.st_mode & S_IFMT) {
-               case S_IFREG:
-                       if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
-                               die("cannot open or read %s", read_from);
-                       break;
-               case S_IFLNK:
-                       if (strbuf_readlink(&buf, read_from, st.st_size) < 0)
-                               die("cannot readlink %s", read_from);
-                       break;
-               default:
-                       die("unsupported file type %s", read_from);
-               }
-       }
-       else {
-               /* Reading from stdin */
-               contents_from = "standard input";
-               mode = 0;
-               if (strbuf_read(&buf, 0, 0) < 0)
-                       die("read error %s from stdin", strerror(errno));
-       }
-       convert_to_git(path, buf.buf, buf.len, &buf, 0);
-       origin->file.ptr = buf.buf;
-       origin->file.size = buf.len;
-       pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_sha1);
-       commit->util = origin;
-
-       /*
-        * Read the current index, replace the path entry with
-        * origin->blob_sha1 without mucking with its mode or type
-        * bits; we are not going to write this index out -- we just
-        * want to run "diff-index --cached".
-        */
-       discard_cache();
-       read_cache();
-
-       len = strlen(path);
-       if (!mode) {
-               int pos = cache_name_pos(path, len);
-               if (0 <= pos)
-                       mode = active_cache[pos]->ce_mode;
-               else
-                       /* Let's not bother reading from HEAD tree */
-                       mode = S_IFREG | 0644;
-       }
-       size = cache_entry_size(len);
-       ce = xcalloc(1, size);
-       hashcpy(ce->sha1, origin->blob_sha1);
-       memcpy(ce->name, path, len);
-       ce->ce_flags = create_ce_flags(len, 0);
-       ce->ce_mode = create_ce_mode(mode);
-       add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
-
-       /*
-        * We are not going to write this out, so this does not matter
-        * right now, but someday we might optimize diff-index --cached
-        * with cache-tree information.
-        */
-       cache_tree_invalidate_path(active_cache_tree, path);
-
-       commit->buffer = xmalloc(400);
-       ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0);
-       snprintf(commit->buffer, 400,
-               "tree 0000000000000000000000000000000000000000\n"
-               "parent %s\n"
-               "author %s\n"
-               "committer %s\n\n"
-               "Version of %s from %s\n",
-               sha1_to_hex(head_sha1),
-               ident, ident, path, contents_from ? contents_from : path);
-       return commit;
-}
-
-static const char *prepare_final(struct scoreboard *sb)
-{
-       int i;
-       const char *final_commit_name = NULL;
-       struct rev_info *revs = sb->revs;
-
-       /*
-        * There must be one and only one positive commit in the
-        * revs->pending array.
-        */
-       for (i = 0; i < revs->pending.nr; i++) {
-               struct object *obj = revs->pending.objects[i].item;
-               if (obj->flags & UNINTERESTING)
-                       continue;
-               while (obj->type == OBJ_TAG)
-                       obj = deref_tag(obj, NULL, 0);
-               if (obj->type != OBJ_COMMIT)
-                       die("Non commit %s?", revs->pending.objects[i].name);
-               if (sb->final)
-                       die("More than one commit to dig from %s and %s?",
-                           revs->pending.objects[i].name,
-                           final_commit_name);
-               sb->final = (struct commit *) obj;
-               final_commit_name = revs->pending.objects[i].name;
-       }
-       return final_commit_name;
-}
-
-static const char *prepare_initial(struct scoreboard *sb)
-{
-       int i;
-       const char *final_commit_name = NULL;
-       struct rev_info *revs = sb->revs;
-
-       /*
-        * There must be one and only one negative commit, and it must be
-        * the boundary.
-        */
-       for (i = 0; i < revs->pending.nr; i++) {
-               struct object *obj = revs->pending.objects[i].item;
-               if (!(obj->flags & UNINTERESTING))
-                       continue;
-               while (obj->type == OBJ_TAG)
-                       obj = deref_tag(obj, NULL, 0);
-               if (obj->type != OBJ_COMMIT)
-                       die("Non commit %s?", revs->pending.objects[i].name);
-               if (sb->final)
-                       die("More than one commit to dig down to %s and %s?",
-                           revs->pending.objects[i].name,
-                           final_commit_name);
-               sb->final = (struct commit *) obj;
-               final_commit_name = revs->pending.objects[i].name;
-       }
-       if (!final_commit_name)
-               die("No commit to dig down to?");
-       return final_commit_name;
-}
-
-static int blame_copy_callback(const struct option *option, const char *arg, int unset)
-{
-       int *opt = option->value;
-
-       /*
-        * -C enables copy from removed files;
-        * -C -C enables copy from existing files, but only
-        *       when blaming a new file;
-        * -C -C -C enables copy from existing files for
-        *          everybody
-        */
-       if (*opt & PICKAXE_BLAME_COPY_HARDER)
-               *opt |= PICKAXE_BLAME_COPY_HARDEST;
-       if (*opt & PICKAXE_BLAME_COPY)
-               *opt |= PICKAXE_BLAME_COPY_HARDER;
-       *opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE;
-
-       if (arg)
-               blame_copy_score = parse_score(arg);
-       return 0;
-}
-
-static int blame_move_callback(const struct option *option, const char *arg, int unset)
-{
-       int *opt = option->value;
-
-       *opt |= PICKAXE_BLAME_MOVE;
-
-       if (arg)
-               blame_move_score = parse_score(arg);
-       return 0;
-}
-
-static int blame_bottomtop_callback(const struct option *option, const char *arg, int unset)
-{
-       const char **bottomtop = option->value;
-       if (!arg)
-               return -1;
-       if (*bottomtop)
-               die("More than one '-L n,m' option given");
-       *bottomtop = arg;
-       return 0;
-}
-
-int cmd_blame(int argc, const char **argv, const char *prefix)
-{
-       struct rev_info revs;
-       const char *path;
-       struct scoreboard sb;
-       struct origin *o;
-       struct blame_entry *ent;
-       long dashdash_pos, bottom, top, lno;
-       const char *final_commit_name = NULL;
-       enum object_type type;
-
-       static const char *bottomtop = NULL;
-       static int output_option = 0, opt = 0;
-       static int show_stats = 0;
-       static const char *revs_file = NULL;
-       static const char *contents_from = NULL;
-       static const struct option options[] = {
-               OPT_BOOLEAN(0, "incremental", &incremental, "Show blame entries as we find them, incrementally"),
-               OPT_BOOLEAN('b', NULL, &blank_boundary, "Show blank SHA-1 for boundary commits (Default: off)"),
-               OPT_BOOLEAN(0, "root", &show_root, "Do not treat root commits as boundaries (Default: off)"),
-               OPT_BOOLEAN(0, "show-stats", &show_stats, "Show work cost statistics"),
-               OPT_BIT(0, "score-debug", &output_option, "Show output score for blame entries", OUTPUT_SHOW_SCORE),
-               OPT_BIT('f', "show-name", &output_option, "Show original filename (Default: auto)", OUTPUT_SHOW_NAME),
-               OPT_BIT('n', "show-number", &output_option, "Show original linenumber (Default: off)", OUTPUT_SHOW_NUMBER),
-               OPT_BIT('p', "porcelain", &output_option, "Show in a format designed for machine consumption", OUTPUT_PORCELAIN),
-               OPT_BIT('c', NULL, &output_option, "Use the same output mode as git-annotate (Default: off)", OUTPUT_ANNOTATE_COMPAT),
-               OPT_BIT('t', NULL, &output_option, "Show raw timestamp (Default: off)", OUTPUT_RAW_TIMESTAMP),
-               OPT_BIT('l', NULL, &output_option, "Show long commit SHA1 (Default: off)", OUTPUT_LONG_OBJECT_NAME),
-               OPT_BIT('s', NULL, &output_option, "Suppress author name and timestamp (Default: off)", OUTPUT_NO_AUTHOR),
-               OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE),
-               OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from <file> instead of calling git-rev-list"),
-               OPT_STRING(0, "contents", &contents_from, "file", "Use <file>'s contents as the final image"),
-               { OPTION_CALLBACK, 'C', NULL, &opt, "score", "Find line copies within and across files", PARSE_OPT_OPTARG, blame_copy_callback },
-               { OPTION_CALLBACK, 'M', NULL, &opt, "score", "Find line movements within and across files", PARSE_OPT_OPTARG, blame_move_callback },
-               OPT_CALLBACK('L', NULL, &bottomtop, "n,m", "Process only line range n,m, counting from 1", blame_bottomtop_callback),
-               OPT_END()
-       };
-
-       struct parse_opt_ctx_t ctx;
-       int cmd_is_annotate = !strcmp(argv[0], "annotate");
-
-       git_config(git_blame_config, NULL);
-       init_revisions(&revs, NULL);
-       revs.date_mode = blame_date_mode;
-
-       save_commit_buffer = 0;
-       dashdash_pos = 0;
-
-       parse_options_start(&ctx, argc, argv, PARSE_OPT_KEEP_DASHDASH |
-                           PARSE_OPT_KEEP_ARGV0);
-       for (;;) {
-               switch (parse_options_step(&ctx, options, blame_opt_usage)) {
-               case PARSE_OPT_HELP:
-                       exit(129);
-               case PARSE_OPT_DONE:
-                       if (ctx.argv[0])
-                               dashdash_pos = ctx.cpidx;
-                       goto parse_done;
-               }
-
-               if (!strcmp(ctx.argv[0], "--reverse")) {
-                       ctx.argv[0] = "--children";
-                       reverse = 1;
-               }
-               parse_revision_opt(&revs, &ctx, options, blame_opt_usage);
-       }
-parse_done:
-       argc = parse_options_end(&ctx);
-
-       if (revs_file && read_ancestry(revs_file))
-               die("reading graft file %s failed: %s",
-                   revs_file, strerror(errno));
-
-       if (cmd_is_annotate) {
-               output_option |= OUTPUT_ANNOTATE_COMPAT;
-               blame_date_mode = DATE_ISO8601;
-       } else {
-               blame_date_mode = revs.date_mode;
-       }
-
-       /* The maximum width used to show the dates */
-       switch (blame_date_mode) {
-       case DATE_RFC2822:
-               blame_date_width = sizeof("Thu, 19 Oct 2006 16:00:04 -0700");
-               break;
-       case DATE_ISO8601:
-               blame_date_width = sizeof("2006-10-19 16:00:04 -0700");
-               break;
-       case DATE_RAW:
-               blame_date_width = sizeof("1161298804 -0700");
-               break;
-       case DATE_SHORT:
-               blame_date_width = sizeof("2006-10-19");
-               break;
-       case DATE_RELATIVE:
-               /* "normal" is used as the fallback for "relative" */
-       case DATE_LOCAL:
-       case DATE_NORMAL:
-               blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
-               break;
-       }
-       blame_date_width -= 1; /* strip the null */
-
-       if (DIFF_OPT_TST(&revs.diffopt, FIND_COPIES_HARDER))
-               opt |= (PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE |
-                       PICKAXE_BLAME_COPY_HARDER);
-
-       if (!blame_move_score)
-               blame_move_score = BLAME_DEFAULT_MOVE_SCORE;
-       if (!blame_copy_score)
-               blame_copy_score = BLAME_DEFAULT_COPY_SCORE;
-
-       /*
-        * We have collected options unknown to us in argv[1..unk]
-        * which are to be passed to revision machinery if we are
-        * going to do the "bottom" processing.
-        *
-        * The remaining are:
-        *
-        * (1) if dashdash_pos != 0, its either
-        *     "blame [revisions] -- <path>" or
-        *     "blame -- <path> <rev>"
-        *
-        * (2) otherwise, its one of the two:
-        *     "blame [revisions] <path>"
-        *     "blame <path> <rev>"
-        *
-        * Note that we must strip out <path> from the arguments: we do not
-        * want the path pruning but we may want "bottom" processing.
-        */
-       if (dashdash_pos) {
-               switch (argc - dashdash_pos - 1) {
-               case 2: /* (1b) */
-                       if (argc != 4)
-                               usage_with_options(blame_opt_usage, options);
-                       /* reorder for the new way: <rev> -- <path> */
-                       argv[1] = argv[3];
-                       argv[3] = argv[2];
-                       argv[2] = "--";
-                       /* FALLTHROUGH */
-               case 1: /* (1a) */
-                       path = add_prefix(prefix, argv[--argc]);
-                       argv[argc] = NULL;
-                       break;
-               default:
-                       usage_with_options(blame_opt_usage, options);
-               }
-       } else {
-               if (argc < 2)
-                       usage_with_options(blame_opt_usage, options);
-               path = add_prefix(prefix, argv[argc - 1]);
-               if (argc == 3 && !has_string_in_work_tree(path)) { /* (2b) */
-                       path = add_prefix(prefix, argv[1]);
-                       argv[1] = argv[2];
-               }
-               argv[argc - 1] = "--";
-
-               setup_work_tree();
-               if (!has_string_in_work_tree(path))
-                       die("cannot stat path %s: %s", path, strerror(errno));
-       }
-
-       setup_revisions(argc, argv, &revs, NULL);
-       memset(&sb, 0, sizeof(sb));
-
-       sb.revs = &revs;
-       if (!reverse)
-               final_commit_name = prepare_final(&sb);
-       else if (contents_from)
-               die("--contents and --children do not blend well.");
-       else
-               final_commit_name = prepare_initial(&sb);
-
-       if (!sb.final) {
-               /*
-                * "--not A B -- path" without anything positive;
-                * do not default to HEAD, but use the working tree
-                * or "--contents".
-                */
-               setup_work_tree();
-               sb.final = fake_working_tree_commit(path, contents_from);
-               add_pending_object(&revs, &(sb.final->object), ":");
-       }
-       else if (contents_from)
-               die("Cannot use --contents with final commit object name");
-
-       /*
-        * If we have bottom, this will mark the ancestors of the
-        * bottom commits we would reach while traversing as
-        * uninteresting.
-        */
-       if (prepare_revision_walk(&revs))
-               die("revision walk setup failed");
-
-       if (is_null_sha1(sb.final->object.sha1)) {
-               char *buf;
-               o = sb.final->util;
-               buf = xmalloc(o->file.size + 1);
-               memcpy(buf, o->file.ptr, o->file.size + 1);
-               sb.final_buf = buf;
-               sb.final_buf_size = o->file.size;
-       }
-       else {
-               o = get_origin(&sb, sb.final, path);
-               if (fill_blob_sha1(o))
-                       die("no such path %s in %s", path, final_commit_name);
-
-               sb.final_buf = read_sha1_file(o->blob_sha1, &type,
-                                             &sb.final_buf_size);
-               if (!sb.final_buf)
-                       die("Cannot read blob %s for path %s",
-                           sha1_to_hex(o->blob_sha1),
-                           path);
-       }
-       num_read_blob++;
-       lno = prepare_lines(&sb);
-
-       bottom = top = 0;
-       if (bottomtop)
-               prepare_blame_range(&sb, bottomtop, lno, &bottom, &top);
-       if (bottom && top && top < bottom) {
-               long tmp;
-               tmp = top; top = bottom; bottom = tmp;
-       }
-       if (bottom < 1)
-               bottom = 1;
-       if (top < 1)
-               top = lno;
-       bottom--;
-       if (lno < top)
-               die("file %s has only %lu lines", path, lno);
-
-       ent = xcalloc(1, sizeof(*ent));
-       ent->lno = bottom;
-       ent->num_lines = top - bottom;
-       ent->suspect = o;
-       ent->s_lno = bottom;
-
-       sb.ent = ent;
-       sb.path = path;
-
-       read_mailmap(&mailmap, NULL);
-
-       if (!incremental)
-               setup_pager();
-
-       assign_blame(&sb, opt);
-
-       if (incremental)
-               return 0;
-
-       coalesce(&sb);
-
-       if (!(output_option & OUTPUT_PORCELAIN))
-               find_alignment(&sb, &output_option);
-
-       output(&sb, output_option);
-       free((void *)sb.final_buf);
-       for (ent = sb.ent; ent; ) {
-               struct blame_entry *e = ent->next;
-               free(ent);
-               ent = e;
-       }
-
-       if (show_stats) {
-               printf("num read blob: %d\n", num_read_blob);
-               printf("num get patch: %d\n", num_get_patch);
-               printf("num commits: %d\n", num_commits);
-       }
-       return 0;
-}
diff --git a/builtin-branch.c b/builtin-branch.c
deleted file mode 100644 (file)
index 91098ca..0000000
+++ /dev/null
@@ -1,632 +0,0 @@
-/*
- * Builtin "git branch"
- *
- * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com>
- * Based on git-branch.sh by Junio C Hamano.
- */
-
-#include "cache.h"
-#include "color.h"
-#include "refs.h"
-#include "commit.h"
-#include "builtin.h"
-#include "remote.h"
-#include "parse-options.h"
-#include "branch.h"
-#include "diff.h"
-#include "revision.h"
-
-static const char * const builtin_branch_usage[] = {
-       "git branch [options] [-r | -a] [--merged | --no-merged]",
-       "git branch [options] [-l] [-f] <branchname> [<start-point>]",
-       "git branch [options] [-r] (-d | -D) <branchname>",
-       "git branch [options] (-m | -M) [<oldbranch>] <newbranch>",
-       NULL
-};
-
-#define REF_LOCAL_BRANCH    0x01
-#define REF_REMOTE_BRANCH   0x02
-
-static const char *head;
-static unsigned char head_sha1[20];
-
-static int branch_use_color = -1;
-static char branch_colors[][COLOR_MAXLEN] = {
-       GIT_COLOR_RESET,
-       GIT_COLOR_NORMAL,       /* PLAIN */
-       GIT_COLOR_RED,          /* REMOTE */
-       GIT_COLOR_NORMAL,       /* LOCAL */
-       GIT_COLOR_GREEN,        /* CURRENT */
-};
-enum color_branch {
-       BRANCH_COLOR_RESET = 0,
-       BRANCH_COLOR_PLAIN = 1,
-       BRANCH_COLOR_REMOTE = 2,
-       BRANCH_COLOR_LOCAL = 3,
-       BRANCH_COLOR_CURRENT = 4,
-};
-
-static enum merge_filter {
-       NO_FILTER = 0,
-       SHOW_NOT_MERGED,
-       SHOW_MERGED,
-} merge_filter;
-static unsigned char merge_filter_ref[20];
-
-static int parse_branch_color_slot(const char *var, int ofs)
-{
-       if (!strcasecmp(var+ofs, "plain"))
-               return BRANCH_COLOR_PLAIN;
-       if (!strcasecmp(var+ofs, "reset"))
-               return BRANCH_COLOR_RESET;
-       if (!strcasecmp(var+ofs, "remote"))
-               return BRANCH_COLOR_REMOTE;
-       if (!strcasecmp(var+ofs, "local"))
-               return BRANCH_COLOR_LOCAL;
-       if (!strcasecmp(var+ofs, "current"))
-               return BRANCH_COLOR_CURRENT;
-       die("bad config variable '%s'", var);
-}
-
-static int git_branch_config(const char *var, const char *value, void *cb)
-{
-       if (!strcmp(var, "color.branch")) {
-               branch_use_color = git_config_colorbool(var, value, -1);
-               return 0;
-       }
-       if (!prefixcmp(var, "color.branch.")) {
-               int slot = parse_branch_color_slot(var, 13);
-               if (!value)
-                       return config_error_nonbool(var);
-               color_parse(value, var, branch_colors[slot]);
-               return 0;
-       }
-       return git_color_default_config(var, value, cb);
-}
-
-static const char *branch_get_color(enum color_branch ix)
-{
-       if (branch_use_color > 0)
-               return branch_colors[ix];
-       return "";
-}
-
-static int delete_branches(int argc, const char **argv, int force, int kinds)
-{
-       struct commit *rev, *head_rev = head_rev;
-       unsigned char sha1[20];
-       char *name = NULL;
-       const char *fmt, *remote;
-       int i;
-       int ret = 0;
-       struct strbuf bname = STRBUF_INIT;
-
-       switch (kinds) {
-       case REF_REMOTE_BRANCH:
-               fmt = "refs/remotes/%s";
-               remote = "remote ";
-               force = 1;
-               break;
-       case REF_LOCAL_BRANCH:
-               fmt = "refs/heads/%s";
-               remote = "";
-               break;
-       default:
-               die("cannot use -a with -d");
-       }
-
-       if (!force) {
-               head_rev = lookup_commit_reference(head_sha1);
-               if (!head_rev)
-                       die("Couldn't look up commit object for HEAD");
-       }
-       for (i = 0; i < argc; i++, strbuf_release(&bname)) {
-               strbuf_branchname(&bname, argv[i]);
-               if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) {
-                       error("Cannot delete the branch '%s' "
-                             "which you are currently on.", bname.buf);
-                       ret = 1;
-                       continue;
-               }
-
-               free(name);
-
-               name = xstrdup(mkpath(fmt, bname.buf));
-               if (!resolve_ref(name, sha1, 1, NULL)) {
-                       error("%sbranch '%s' not found.",
-                                       remote, bname.buf);
-                       ret = 1;
-                       continue;
-               }
-
-               rev = lookup_commit_reference(sha1);
-               if (!rev) {
-                       error("Couldn't look up commit object for '%s'", name);
-                       ret = 1;
-                       continue;
-               }
-
-               /* This checks whether the merge bases of branch and
-                * HEAD contains branch -- which means that the HEAD
-                * contains everything in both.
-                */
-
-               if (!force &&
-                   !in_merge_bases(rev, &head_rev, 1)) {
-                       error("The branch '%s' is not an ancestor of "
-                             "your current HEAD.\n"
-                             "If you are sure you want to delete it, "
-                             "run 'git branch -D %s'.", bname.buf, bname.buf);
-                       ret = 1;
-                       continue;
-               }
-
-               if (delete_ref(name, sha1, 0)) {
-                       error("Error deleting %sbranch '%s'", remote,
-                             bname.buf);
-                       ret = 1;
-               } else {
-                       struct strbuf buf = STRBUF_INIT;
-                       printf("Deleted %sbranch %s (was %s).\n", remote,
-                              bname.buf,
-                              find_unique_abbrev(sha1, DEFAULT_ABBREV));
-                       strbuf_addf(&buf, "branch.%s", bname.buf);
-                       if (git_config_rename_section(buf.buf, NULL) < 0)
-                               warning("Update of config-file failed");
-                       strbuf_release(&buf);
-               }
-       }
-
-       free(name);
-
-       return(ret);
-}
-
-struct ref_item {
-       char *name;
-       char *dest;
-       unsigned int kind, len;
-       struct commit *commit;
-};
-
-struct ref_list {
-       struct rev_info revs;
-       int index, alloc, maxwidth;
-       struct ref_item *list;
-       struct commit_list *with_commit;
-       int kinds;
-};
-
-static char *resolve_symref(const char *src, const char *prefix)
-{
-       unsigned char sha1[20];
-       int flag;
-       const char *dst, *cp;
-
-       dst = resolve_ref(src, sha1, 0, &flag);
-       if (!(dst && (flag & REF_ISSYMREF)))
-               return NULL;
-       if (prefix && (cp = skip_prefix(dst, prefix)))
-               dst = cp;
-       return xstrdup(dst);
-}
-
-static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
-{
-       struct ref_list *ref_list = (struct ref_list*)(cb_data);
-       struct ref_item *newitem;
-       struct commit *commit;
-       int kind, i;
-       const char *prefix, *orig_refname = refname;
-
-       static struct {
-               int kind;
-               const char *prefix;
-               int pfxlen;
-       } ref_kind[] = {
-               { REF_LOCAL_BRANCH, "refs/heads/", 11 },
-               { REF_REMOTE_BRANCH, "refs/remotes/", 13 },
-       };
-
-       /* Detect kind */
-       for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
-               prefix = ref_kind[i].prefix;
-               if (strncmp(refname, prefix, ref_kind[i].pfxlen))
-                       continue;
-               kind = ref_kind[i].kind;
-               refname += ref_kind[i].pfxlen;
-               break;
-       }
-       if (ARRAY_SIZE(ref_kind) <= i)
-               return 0;
-
-       commit = lookup_commit_reference_gently(sha1, 1);
-       if (!commit)
-               return error("branch '%s' does not point at a commit", refname);
-
-       /* Filter with with_commit if specified */
-       if (!is_descendant_of(commit, ref_list->with_commit))
-               return 0;
-
-       /* Don't add types the caller doesn't want */
-       if ((kind & ref_list->kinds) == 0)
-               return 0;
-
-       if (merge_filter != NO_FILTER)
-               add_pending_object(&ref_list->revs,
-                                  (struct object *)commit, refname);
-
-       /* Resize buffer */
-       if (ref_list->index >= ref_list->alloc) {
-               ref_list->alloc = alloc_nr(ref_list->alloc);
-               ref_list->list = xrealloc(ref_list->list,
-                               ref_list->alloc * sizeof(struct ref_item));
-       }
-
-       /* Record the new item */
-       newitem = &(ref_list->list[ref_list->index++]);
-       newitem->name = xstrdup(refname);
-       newitem->kind = kind;
-       newitem->commit = commit;
-       newitem->len = strlen(refname);
-       newitem->dest = resolve_symref(orig_refname, prefix);
-       /* adjust for "remotes/" */
-       if (newitem->kind == REF_REMOTE_BRANCH &&
-           ref_list->kinds != REF_REMOTE_BRANCH)
-               newitem->len += 8;
-       if (newitem->len > ref_list->maxwidth)
-               ref_list->maxwidth = newitem->len;
-
-       return 0;
-}
-
-static void free_ref_list(struct ref_list *ref_list)
-{
-       int i;
-
-       for (i = 0; i < ref_list->index; i++) {
-               free(ref_list->list[i].name);
-               free(ref_list->list[i].dest);
-       }
-       free(ref_list->list);
-}
-
-static int ref_cmp(const void *r1, const void *r2)
-{
-       struct ref_item *c1 = (struct ref_item *)(r1);
-       struct ref_item *c2 = (struct ref_item *)(r2);
-
-       if (c1->kind != c2->kind)
-               return c1->kind - c2->kind;
-       return strcmp(c1->name, c2->name);
-}
-
-static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
-               int show_upstream_ref)
-{
-       int ours, theirs;
-       struct branch *branch = branch_get(branch_name);
-
-       if (!stat_tracking_info(branch, &ours, &theirs)) {
-               if (branch && branch->merge && branch->merge[0]->dst &&
-                   show_upstream_ref)
-                       strbuf_addf(stat, "[%s] ",
-                           shorten_unambiguous_ref(branch->merge[0]->dst, 0));
-               return;
-       }
-
-       strbuf_addch(stat, '[');
-       if (show_upstream_ref)
-               strbuf_addf(stat, "%s: ",
-                       shorten_unambiguous_ref(branch->merge[0]->dst, 0));
-       if (!ours)
-               strbuf_addf(stat, "behind %d] ", theirs);
-       else if (!theirs)
-               strbuf_addf(stat, "ahead %d] ", ours);
-       else
-               strbuf_addf(stat, "ahead %d, behind %d] ", ours, theirs);
-}
-
-static int matches_merge_filter(struct commit *commit)
-{
-       int is_merged;
-
-       if (merge_filter == NO_FILTER)
-               return 1;
-
-       is_merged = !!(commit->object.flags & UNINTERESTING);
-       return (is_merged == (merge_filter == SHOW_MERGED));
-}
-
-static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
-                          int abbrev, int current, char *prefix)
-{
-       char c;
-       int color;
-       struct commit *commit = item->commit;
-       struct strbuf out = STRBUF_INIT, name = STRBUF_INIT;
-
-       if (!matches_merge_filter(commit))
-               return;
-
-       switch (item->kind) {
-       case REF_LOCAL_BRANCH:
-               color = BRANCH_COLOR_LOCAL;
-               break;
-       case REF_REMOTE_BRANCH:
-               color = BRANCH_COLOR_REMOTE;
-               break;
-       default:
-               color = BRANCH_COLOR_PLAIN;
-               break;
-       }
-
-       c = ' ';
-       if (current) {
-               c = '*';
-               color = BRANCH_COLOR_CURRENT;
-       }
-
-       strbuf_addf(&name, "%s%s", prefix, item->name);
-       if (verbose)
-               strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color),
-                           maxwidth, name.buf,
-                           branch_get_color(BRANCH_COLOR_RESET));
-       else
-               strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color),
-                           name.buf, branch_get_color(BRANCH_COLOR_RESET));
-
-       if (item->dest)
-               strbuf_addf(&out, " -> %s", item->dest);
-       else if (verbose) {
-               struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT;
-               const char *sub = " **** invalid ref ****";
-
-               commit = item->commit;
-               if (commit && !parse_commit(commit)) {
-                       pretty_print_commit(CMIT_FMT_ONELINE, commit,
-                                           &subject, 0, NULL, NULL, 0, 0);
-                       sub = subject.buf;
-               }
-
-               if (item->kind == REF_LOCAL_BRANCH)
-                       fill_tracking_info(&stat, item->name, verbose > 1);
-
-               strbuf_addf(&out, " %s %s%s",
-                       find_unique_abbrev(item->commit->object.sha1, abbrev),
-                       stat.buf, sub);
-               strbuf_release(&stat);
-               strbuf_release(&subject);
-       }
-       printf("%s\n", out.buf);
-       strbuf_release(&name);
-       strbuf_release(&out);
-}
-
-static int calc_maxwidth(struct ref_list *refs)
-{
-       int i, w = 0;
-       for (i = 0; i < refs->index; i++) {
-               if (!matches_merge_filter(refs->list[i].commit))
-                       continue;
-               if (refs->list[i].len > w)
-                       w = refs->list[i].len;
-       }
-       return w;
-}
-
-static void print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit)
-{
-       int i;
-       struct ref_list ref_list;
-       struct commit *head_commit = lookup_commit_reference_gently(head_sha1, 1);
-
-       memset(&ref_list, 0, sizeof(ref_list));
-       ref_list.kinds = kinds;
-       ref_list.with_commit = with_commit;
-       if (merge_filter != NO_FILTER)
-               init_revisions(&ref_list.revs, NULL);
-       for_each_ref(append_ref, &ref_list);
-       if (merge_filter != NO_FILTER) {
-               struct commit *filter;
-               filter = lookup_commit_reference_gently(merge_filter_ref, 0);
-               filter->object.flags |= UNINTERESTING;
-               add_pending_object(&ref_list.revs,
-                                  (struct object *) filter, "");
-               ref_list.revs.limited = 1;
-               prepare_revision_walk(&ref_list.revs);
-               if (verbose)
-                       ref_list.maxwidth = calc_maxwidth(&ref_list);
-       }
-
-       qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
-
-       detached = (detached && (kinds & REF_LOCAL_BRANCH));
-       if (detached && head_commit &&
-           is_descendant_of(head_commit, with_commit)) {
-               struct ref_item item;
-               item.name = xstrdup("(no branch)");
-               item.len = strlen(item.name);
-               item.kind = REF_LOCAL_BRANCH;
-               item.dest = NULL;
-               item.commit = head_commit;
-               if (item.len > ref_list.maxwidth)
-                       ref_list.maxwidth = item.len;
-               print_ref_item(&item, ref_list.maxwidth, verbose, abbrev, 1, "");
-               free(item.name);
-       }
-
-       for (i = 0; i < ref_list.index; i++) {
-               int current = !detached &&
-                       (ref_list.list[i].kind == REF_LOCAL_BRANCH) &&
-                       !strcmp(ref_list.list[i].name, head);
-               char *prefix = (kinds != REF_REMOTE_BRANCH &&
-                               ref_list.list[i].kind == REF_REMOTE_BRANCH)
-                               ? "remotes/" : "";
-               print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose,
-                              abbrev, current, prefix);
-       }
-
-       free_ref_list(&ref_list);
-}
-
-static void rename_branch(const char *oldname, const char *newname, int force)
-{
-       struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
-       unsigned char sha1[20];
-       struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
-       int recovery = 0;
-
-       if (!oldname)
-               die("cannot rename the current branch while not on any.");
-
-       if (strbuf_check_branch_ref(&oldref, oldname)) {
-               /*
-                * Bad name --- this could be an attempt to rename a
-                * ref that we used to allow to be created by accident.
-                */
-               if (resolve_ref(oldref.buf, sha1, 1, NULL))
-                       recovery = 1;
-               else
-                       die("Invalid branch name: '%s'", oldname);
-       }
-
-       if (strbuf_check_branch_ref(&newref, newname))
-               die("Invalid branch name: '%s'", newname);
-
-       if (resolve_ref(newref.buf, sha1, 1, NULL) && !force)
-               die("A branch named '%s' already exists.", newref.buf + 11);
-
-       strbuf_addf(&logmsg, "Branch: renamed %s to %s",
-                oldref.buf, newref.buf);
-
-       if (rename_ref(oldref.buf, newref.buf, logmsg.buf))
-               die("Branch rename failed");
-       strbuf_release(&logmsg);
-
-       if (recovery)
-               warning("Renamed a misnamed branch '%s' away", oldref.buf + 11);
-
-       /* no need to pass logmsg here as HEAD didn't really move */
-       if (!strcmp(oldname, head) && create_symref("HEAD", newref.buf, NULL))
-               die("Branch renamed to %s, but HEAD is not updated!", newname);
-
-       strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11);
-       strbuf_release(&oldref);
-       strbuf_addf(&newsection, "branch.%s", newref.buf + 11);
-       strbuf_release(&newref);
-       if (git_config_rename_section(oldsection.buf, newsection.buf) < 0)
-               die("Branch is renamed, but update of config-file failed");
-       strbuf_release(&oldsection);
-       strbuf_release(&newsection);
-}
-
-static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset)
-{
-       merge_filter = ((opt->long_name[0] == 'n')
-                       ? SHOW_NOT_MERGED
-                       : SHOW_MERGED);
-       if (unset)
-               merge_filter = SHOW_NOT_MERGED; /* b/c for --no-merged */
-       if (!arg)
-               arg = "HEAD";
-       if (get_sha1(arg, merge_filter_ref))
-               die("malformed object name %s", arg);
-       return 0;
-}
-
-int cmd_branch(int argc, const char **argv, const char *prefix)
-{
-       int delete = 0, rename = 0, force_create = 0;
-       int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;
-       int reflog = 0;
-       enum branch_track track;
-       int kinds = REF_LOCAL_BRANCH;
-       struct commit_list *with_commit = NULL;
-
-       struct option options[] = {
-               OPT_GROUP("Generic options"),
-               OPT__VERBOSE(&verbose),
-               OPT_SET_INT( 0 , "track",  &track, "set up tracking mode (see git-pull(1))",
-                       BRANCH_TRACK_EXPLICIT),
-               OPT_BOOLEAN( 0 , "color",  &branch_use_color, "use colored output"),
-               OPT_SET_INT('r', NULL,     &kinds, "act on remote-tracking branches",
-                       REF_REMOTE_BRANCH),
-               {
-                       OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
-                       "print only branches that contain the commit",
-                       PARSE_OPT_LASTARG_DEFAULT,
-                       parse_opt_with_commit, (intptr_t)"HEAD",
-               },
-               {
-                       OPTION_CALLBACK, 0, "with", &with_commit, "commit",
-                       "print only branches that contain the commit",
-                       PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
-                       parse_opt_with_commit, (intptr_t) "HEAD",
-               },
-               OPT__ABBREV(&abbrev),
-
-               OPT_GROUP("Specific git-branch actions:"),
-               OPT_SET_INT('a', NULL, &kinds, "list both remote-tracking and local branches",
-                       REF_REMOTE_BRANCH | REF_LOCAL_BRANCH),
-               OPT_BIT('d', NULL, &delete, "delete fully merged branch", 1),
-               OPT_BIT('D', NULL, &delete, "delete branch (even if not merged)", 2),
-               OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1),
-               OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2),
-               OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"),
-               OPT_BOOLEAN('f', NULL, &force_create, "force creation (when already exists)"),
-               {
-                       OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
-                       "commit", "print only not merged branches",
-                       PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
-                       opt_parse_merge_filter, (intptr_t) "HEAD",
-               },
-               {
-                       OPTION_CALLBACK, 0, "merged", &merge_filter_ref,
-                       "commit", "print only merged branches",
-                       PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
-                       opt_parse_merge_filter, (intptr_t) "HEAD",
-               },
-               OPT_END(),
-       };
-
-       git_config(git_branch_config, NULL);
-
-       if (branch_use_color == -1)
-               branch_use_color = git_use_color_default;
-
-       track = git_branch_track;
-
-       head = resolve_ref("HEAD", head_sha1, 0, NULL);
-       if (!head)
-               die("Failed to resolve HEAD as a valid ref.");
-       head = xstrdup(head);
-       if (!strcmp(head, "HEAD")) {
-               detached = 1;
-       } else {
-               if (prefixcmp(head, "refs/heads/"))
-                       die("HEAD not found below refs/heads!");
-               head += 11;
-       }
-       hashcpy(merge_filter_ref, head_sha1);
-
-       argc = parse_options(argc, argv, options, builtin_branch_usage, 0);
-       if (!!delete + !!rename + !!force_create > 1)
-               usage_with_options(builtin_branch_usage, options);
-
-       if (delete)
-               return delete_branches(argc, argv, delete > 1, kinds);
-       else if (argc == 0)
-               print_ref_list(kinds, detached, verbose, abbrev, with_commit);
-       else if (rename && (argc == 1))
-               rename_branch(head, argv[0], rename > 1);
-       else if (rename && (argc == 2))
-               rename_branch(argv[0], argv[1], rename > 1);
-       else if (argc <= 2)
-               create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
-                             force_create, reflog, track);
-       else
-               usage_with_options(builtin_branch_usage, options);
-
-       return 0;
-}
diff --git a/builtin-bundle.c b/builtin-bundle.c
deleted file mode 100644 (file)
index 9b58152..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-#include "builtin.h"
-#include "cache.h"
-#include "bundle.h"
-
-/*
- * Basic handler for bundle files to connect repositories via sneakernet.
- * Invocation must include action.
- * This function can create a bundle or provide information on an existing
- * bundle supporting "fetch", "pull", and "ls-remote".
- */
-
-static const char *bundle_usage="git bundle (create <bundle> <git rev-list args> | verify <bundle> | list-heads <bundle> [refname]... | unbundle <bundle> [refname]... )";
-
-int cmd_bundle(int argc, const char **argv, const char *prefix)
-{
-       struct bundle_header header;
-       int nongit;
-       const char *cmd, *bundle_file;
-       int bundle_fd = -1;
-       char buffer[PATH_MAX];
-
-       if (argc < 3)
-               usage(bundle_usage);
-
-       cmd = argv[1];
-       bundle_file = argv[2];
-       argc -= 2;
-       argv += 2;
-
-       prefix = setup_git_directory_gently(&nongit);
-       if (prefix && bundle_file[0] != '/') {
-               snprintf(buffer, sizeof(buffer), "%s/%s", prefix, bundle_file);
-               bundle_file = buffer;
-       }
-
-       memset(&header, 0, sizeof(header));
-       if (strcmp(cmd, "create") && (bundle_fd =
-                               read_bundle_header(bundle_file, &header)) < 0)
-               return 1;
-
-       if (!strcmp(cmd, "verify")) {
-               close(bundle_fd);
-               if (verify_bundle(&header, 1))
-                       return 1;
-               fprintf(stderr, "%s is okay\n", bundle_file);
-               return 0;
-       }
-       if (!strcmp(cmd, "list-heads")) {
-               close(bundle_fd);
-               return !!list_bundle_refs(&header, argc, argv);
-       }
-       if (!strcmp(cmd, "create")) {
-               if (nongit)
-                       die("Need a repository to create a bundle.");
-               return !!create_bundle(&header, bundle_file, argc, argv);
-       } else if (!strcmp(cmd, "unbundle")) {
-               if (nongit)
-                       die("Need a repository to unbundle.");
-               return !!unbundle(&header, bundle_fd) ||
-                       list_bundle_refs(&header, argc, argv);
-       } else
-               usage(bundle_usage);
-}
diff --git a/builtin-cat-file.c b/builtin-cat-file.c
deleted file mode 100644 (file)
index 8fad19d..0000000
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * 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"
-#include "parse-options.h"
-
-#define BATCH 1
-#define BATCH_CHECK 2
-
-static void 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.
-                        */
-                       write_or_die(1, 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 */
-                                               write_or_die(1, tagger,
-                                                            cp - tagger);
-                                               break;
-                                       }
-                                       while (sp < cp &&
-                                              !('0' <= *sp && *sp <= '9'))
-                                               sp++;
-                                       write_or_die(1, tagger, sp - tagger);
-                                       date = strtoul(sp, &ep, 10);
-                                       tz = strtol(ep, NULL, 10);
-                                       sp = show_date(date, tz, 0);
-                                       write_or_die(1, 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)
-               write_or_die(1, cp, endp - cp);
-}
-
-static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
-{
-       unsigned char sha1[20];
-       enum object_type type;
-       void *buf;
-       unsigned long size;
-
-       if (get_sha1(obj_name, sha1))
-               die("Not a valid object name %s", obj_name);
-
-       buf = NULL;
-       switch (opt) {
-       case 't':
-               type = sha1_object_info(sha1, NULL);
-               if (type > 0) {
-                       printf("%s\n", typename(type));
-                       return 0;
-               }
-               break;
-
-       case 's':
-               type = sha1_object_info(sha1, &size);
-               if (type > 0) {
-                       printf("%lu\n", size);
-                       return 0;
-               }
-               break;
-
-       case 'e':
-               return !has_sha1_file(sha1);
-
-       case 'p':
-               type = sha1_object_info(sha1, NULL);
-               if (type < 0)
-                       die("Not a valid object name %s", obj_name);
-
-               /* custom pretty-print here */
-               if (type == OBJ_TREE) {
-                       const char *ls_args[3] = {"ls-tree", obj_name, NULL};
-                       return cmd_ls_tree(2, ls_args, NULL);
-               }
-
-               buf = read_sha1_file(sha1, &type, &size);
-               if (!buf)
-                       die("Cannot read object %s", obj_name);
-               if (type == OBJ_TAG) {
-                       pprint_tag(sha1, buf, size);
-                       return 0;
-               }
-
-               /* otherwise just spit out the data */
-               break;
-       case 0:
-               buf = read_object_with_reference(sha1, exp_type, &size, NULL);
-               break;
-
-       default:
-               die("git cat-file: unknown option: %s", exp_type);
-       }
-
-       if (!buf)
-               die("git cat-file %s: bad file", obj_name);
-
-       write_or_die(1, buf, size);
-       return 0;
-}
-
-static int batch_one_object(const char *obj_name, int print_contents)
-{
-       unsigned char sha1[20];
-       enum object_type type = 0;
-       unsigned long size;
-       void *contents = contents;
-
-       if (!obj_name)
-          return 1;
-
-       if (get_sha1(obj_name, sha1)) {
-               printf("%s missing\n", obj_name);
-               fflush(stdout);
-               return 0;
-       }
-
-       if (print_contents == BATCH)
-               contents = read_sha1_file(sha1, &type, &size);
-       else
-               type = sha1_object_info(sha1, &size);
-
-       if (type <= 0) {
-               printf("%s missing\n", obj_name);
-               fflush(stdout);
-               return 0;
-       }
-
-       printf("%s %s %lu\n", sha1_to_hex(sha1), typename(type), size);
-       fflush(stdout);
-
-       if (print_contents == BATCH) {
-               write_or_die(1, contents, size);
-               printf("\n");
-               fflush(stdout);
-               free(contents);
-       }
-
-       return 0;
-}
-
-static int batch_objects(int print_contents)
-{
-       struct strbuf buf = STRBUF_INIT;
-
-       while (strbuf_getline(&buf, stdin, '\n') != EOF) {
-               int error = batch_one_object(buf.buf, print_contents);
-               if (error)
-                       return error;
-       }
-
-       return 0;
-}
-
-static const char * const cat_file_usage[] = {
-       "git cat-file [-t|-s|-e|-p|<type>] <sha1>",
-       "git cat-file [--batch|--batch-check] < <list_of_sha1s>",
-       NULL
-};
-
-int cmd_cat_file(int argc, const char **argv, const char *prefix)
-{
-       int opt = 0, batch = 0;
-       const char *exp_type = NULL, *obj_name = NULL;
-
-       const struct option options[] = {
-               OPT_GROUP("<type> can be one of: blob, tree, commit, tag"),
-               OPT_SET_INT('t', NULL, &opt, "show object type", 't'),
-               OPT_SET_INT('s', NULL, &opt, "show object size", 's'),
-               OPT_SET_INT('e', NULL, &opt,
-                           "exit with zero when there's no error", 'e'),
-               OPT_SET_INT('p', NULL, &opt, "pretty-print object's content", 'p'),
-               OPT_SET_INT(0, "batch", &batch,
-                           "show info and content of objects feeded on stdin", BATCH),
-               OPT_SET_INT(0, "batch-check", &batch,
-                           "show info about objects feeded on stdin",
-                           BATCH_CHECK),
-               OPT_END()
-       };
-
-       git_config(git_default_config, NULL);
-
-       if (argc != 3 && argc != 2)
-               usage_with_options(cat_file_usage, options);
-
-       argc = parse_options(argc, argv, options, cat_file_usage, 0);
-
-       if (opt) {
-               if (argc == 1)
-                       obj_name = argv[0];
-               else
-                       usage_with_options(cat_file_usage, options);
-       }
-       if (!opt && !batch) {
-               if (argc == 2) {
-                       exp_type = argv[0];
-                       obj_name = argv[1];
-               } else
-                       usage_with_options(cat_file_usage, options);
-       }
-       if (batch && (opt || argc)) {
-               usage_with_options(cat_file_usage, options);
-       }
-
-       if (batch)
-               return batch_objects(batch);
-
-       return cat_one_file(opt, exp_type, obj_name);
-}
diff --git a/builtin-check-attr.c b/builtin-check-attr.c
deleted file mode 100644 (file)
index 15a04b7..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-#include "builtin.h"
-#include "cache.h"
-#include "attr.h"
-#include "quote.h"
-#include "parse-options.h"
-
-static int stdin_paths;
-static const char * const check_attr_usage[] = {
-"git check-attr attr... [--] pathname...",
-"git check-attr --stdin attr... < <list-of-paths>",
-NULL
-};
-
-static int null_term_line;
-
-static const struct option check_attr_options[] = {
-       OPT_BOOLEAN(0 , "stdin", &stdin_paths, "read file names from stdin"),
-       OPT_BOOLEAN('z', NULL, &null_term_line,
-               "input paths are terminated by a null character"),
-       OPT_END()
-};
-
-static void check_attr(int cnt, struct git_attr_check *check,
-       const char** name, const char *file)
-{
-       int j;
-       if (git_checkattr(file, cnt, check))
-               die("git_checkattr died");
-       for (j = 0; j < cnt; j++) {
-               const char *value = check[j].value;
-
-               if (ATTR_TRUE(value))
-                       value = "set";
-               else if (ATTR_FALSE(value))
-                       value = "unset";
-               else if (ATTR_UNSET(value))
-                       value = "unspecified";
-
-               quote_c_style(file, NULL, stdout, 0);
-               printf(": %s: %s\n", name[j], value);
-       }
-}
-
-static void check_attr_stdin_paths(int cnt, struct git_attr_check *check,
-       const char** name)
-{
-       struct strbuf buf, nbuf;
-       int line_termination = null_term_line ? 0 : '\n';
-
-       strbuf_init(&buf, 0);
-       strbuf_init(&nbuf, 0);
-       while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
-               if (line_termination && buf.buf[0] == '"') {
-                       strbuf_reset(&nbuf);
-                       if (unquote_c_style(&nbuf, buf.buf, NULL))
-                               die("line is badly quoted");
-                       strbuf_swap(&buf, &nbuf);
-               }
-               check_attr(cnt, check, name, buf.buf);
-               maybe_flush_or_die(stdout, "attribute to stdout");
-       }
-       strbuf_release(&buf);
-       strbuf_release(&nbuf);
-}
-
-int cmd_check_attr(int argc, const char **argv, const char *prefix)
-{
-       struct git_attr_check *check;
-       int cnt, i, doubledash;
-       const char *errstr = NULL;
-
-       argc = parse_options(argc, argv, check_attr_options, check_attr_usage,
-               PARSE_OPT_KEEP_DASHDASH);
-       if (!argc)
-               usage_with_options(check_attr_usage, check_attr_options);
-
-       if (read_cache() < 0) {
-               die("invalid cache");
-       }
-
-       doubledash = -1;
-       for (i = 0; doubledash < 0 && i < argc; i++) {
-               if (!strcmp(argv[i], "--"))
-                       doubledash = i;
-       }
-
-       /* If there is no double dash, we handle only one attribute */
-       if (doubledash < 0) {
-               cnt = 1;
-               doubledash = 0;
-       } else
-               cnt = doubledash;
-       doubledash++;
-
-       if (cnt <= 0)
-               errstr = "No attribute specified";
-       else if (stdin_paths && doubledash < argc)
-               errstr = "Can't specify files with --stdin";
-       if (errstr) {
-               error("%s", errstr);
-               usage_with_options(check_attr_usage, check_attr_options);
-       }
-
-       check = xcalloc(cnt, sizeof(*check));
-       for (i = 0; i < cnt; i++) {
-               const char *name;
-               struct git_attr *a;
-               name = argv[i];
-               a = git_attr(name, strlen(name));
-               if (!a)
-                       return error("%s: not a valid attribute name", name);
-               check[i].attr = a;
-       }
-
-       if (stdin_paths)
-               check_attr_stdin_paths(cnt, check, argv);
-       else {
-               for (i = doubledash; i < argc; i++)
-                       check_attr(cnt, check, argv, argv[i]);
-               maybe_flush_or_die(stdout, "attribute to stdout");
-       }
-       return 0;
-}
diff --git a/builtin-check-ref-format.c b/builtin-check-ref-format.c
deleted file mode 100644 (file)
index f9381e0..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * GIT - The information manager from hell
- */
-
-#include "cache.h"
-#include "refs.h"
-#include "builtin.h"
-#include "strbuf.h"
-
-int cmd_check_ref_format(int argc, const char **argv, const char *prefix)
-{
-       if (argc == 3 && !strcmp(argv[1], "--branch")) {
-               struct strbuf sb = STRBUF_INIT;
-
-               if (strbuf_check_branch_ref(&sb, argv[2]))
-                       die("'%s' is not a valid branch name", argv[2]);
-               printf("%s\n", sb.buf + 11);
-               exit(0);
-       }
-       if (argc != 2)
-               usage("git check-ref-format refname");
-       return !!check_ref_format(argv[1]);
-}
diff --git a/builtin-checkout-index.c b/builtin-checkout-index.c
deleted file mode 100644 (file)
index afe35e2..0000000
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * 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 "builtin.h"
-#include "cache.h"
-#include "quote.h"
-#include "cache-tree.h"
-#include "parse-options.h"
-
-#define CHECKOUT_ALL 4
-static int line_termination = '\n';
-static int checkout_stage; /* default to checkout stage0 */
-static int to_tempfile;
-static char topath[4][PATH_MAX + 1];
-
-static struct checkout state;
-
-static void write_tempfile_record(const char *name, int prefix_length)
-{
-       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(name + prefix_length, stdout, line_termination);
-
-       for (i = 0; i < 4; i++) {
-               topath[i][0] = 0;
-       }
-}
-
-static int checkout_file(const char *name, int prefix_length)
-{
-       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, prefix_length);
-               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 void checkout_all(const char *prefix, int prefix_length)
-{
-       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, prefix_length);
-               }
-               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, prefix_length);
-       if (errs)
-               /* we have already done our error reporting.
-                * exit with the same code as die().
-                */
-               exit(128);
-}
-
-static const char * const builtin_checkout_index_usage[] = {
-       "git checkout-index [options] [--] <file>...",
-       NULL
-};
-
-static struct lock_file lock_file;
-
-static int option_parse_u(const struct option *opt,
-                             const char *arg, int unset)
-{
-       int *newfd = opt->value;
-
-       state.refresh_cache = 1;
-       if (*newfd < 0)
-               *newfd = hold_locked_index(&lock_file, 1);
-       return 0;
-}
-
-static int option_parse_z(const struct option *opt,
-                         const char *arg, int unset)
-{
-       if (unset)
-               line_termination = '\n';
-       else
-               line_termination = 0;
-       return 0;
-}
-
-static int option_parse_prefix(const struct option *opt,
-                              const char *arg, int unset)
-{
-       state.base_dir = arg;
-       state.base_dir_len = strlen(arg);
-       return 0;
-}
-
-static int option_parse_stage(const struct option *opt,
-                             const char *arg, int unset)
-{
-       if (!strcmp(arg, "all")) {
-               to_tempfile = 1;
-               checkout_stage = CHECKOUT_ALL;
-       } else {
-               int ch = arg[0];
-               if ('1' <= ch && ch <= '3')
-                       checkout_stage = arg[0] - '0';
-               else
-                       die("stage should be between 1 and 3 or all");
-       }
-       return 0;
-}
-
-int cmd_checkout_index(int argc, const char **argv, const char *prefix)
-{
-       int i;
-       int newfd = -1;
-       int all = 0;
-       int read_from_stdin = 0;
-       int prefix_length;
-       int force = 0, quiet = 0, not_new = 0;
-       struct option builtin_checkout_index_options[] = {
-               OPT_BOOLEAN('a', "all", &all,
-                       "checks out all files in the index"),
-               OPT_BOOLEAN('f', "force", &force,
-                       "forces overwrite of existing files"),
-               OPT__QUIET(&quiet),
-               OPT_BOOLEAN('n', "no-create", &not_new,
-                       "don't checkout new files"),
-               { OPTION_CALLBACK, 'u', "index", &newfd, NULL,
-                       "update stat information in the index file",
-                       PARSE_OPT_NOARG, option_parse_u },
-               { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
-                       "paths are separated with NUL character",
-                       PARSE_OPT_NOARG, option_parse_z },
-               OPT_BOOLEAN(0, "stdin", &read_from_stdin,
-                       "read list of paths from the standard input"),
-               OPT_BOOLEAN(0, "temp", &to_tempfile,
-                       "write the content to temporary files"),
-               OPT_CALLBACK(0, "prefix", NULL, "string",
-                       "when creating files, prepend <string>",
-                       option_parse_prefix),
-               OPT_CALLBACK(0, "stage", NULL, NULL,
-                       "copy out the files from named stage",
-                       option_parse_stage),
-               OPT_END()
-       };
-
-       git_config(git_default_config, NULL);
-       state.base_dir = "";
-       prefix_length = prefix ? strlen(prefix) : 0;
-
-       if (read_cache() < 0) {
-               die("invalid cache");
-       }
-
-       argc = parse_options(argc, argv, builtin_checkout_index_options,
-                       builtin_checkout_index_usage, 0);
-       state.force = force;
-       state.quiet = quiet;
-       state.not_new = not_new;
-
-       if (state.base_dir_len || to_tempfile) {
-               /* when --prefix is specified we do not
-                * want to update cache.
-                */
-               if (state.refresh_cache) {
-                       rollback_lock_file(&lock_file);
-                       newfd = -1;
-               }
-               state.refresh_cache = 0;
-       }
-
-       /* Check out named files first */
-       for (i = 0; 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, prefix_length);
-               if (p < arg || p > arg + strlen(arg))
-                       free((char *)p);
-       }
-
-       if (read_from_stdin) {
-               struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
-
-               if (all)
-                       die("git checkout-index: don't mix '--all' and '--stdin'");
-
-               while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
-                       const char *p;
-                       if (line_termination && buf.buf[0] == '"') {
-                               strbuf_reset(&nbuf);
-                               if (unquote_c_style(&nbuf, buf.buf, NULL))
-                                       die("line is badly quoted");
-                               strbuf_swap(&buf, &nbuf);
-                       }
-                       p = prefix_path(prefix, prefix_length, buf.buf);
-                       checkout_file(p, prefix_length);
-                       if (p < buf.buf || p > buf.buf + buf.len)
-                               free((char *)p);
-               }
-               strbuf_release(&nbuf);
-               strbuf_release(&buf);
-       }
-
-       if (all)
-               checkout_all(prefix, prefix_length);
-
-       if (0 <= newfd &&
-           (write_cache(newfd, active_cache, active_nr) ||
-            commit_locked_index(&lock_file)))
-               die("Unable to write new index file");
-       return 0;
-}
diff --git a/builtin-checkout.c b/builtin-checkout.c
deleted file mode 100644 (file)
index 15f0c32..0000000
+++ /dev/null
@@ -1,756 +0,0 @@
-#include "cache.h"
-#include "builtin.h"
-#include "parse-options.h"
-#include "refs.h"
-#include "commit.h"
-#include "tree.h"
-#include "tree-walk.h"
-#include "cache-tree.h"
-#include "unpack-trees.h"
-#include "dir.h"
-#include "run-command.h"
-#include "merge-recursive.h"
-#include "branch.h"
-#include "diff.h"
-#include "revision.h"
-#include "remote.h"
-#include "blob.h"
-#include "xdiff-interface.h"
-#include "ll-merge.h"
-
-static const char * const checkout_usage[] = {
-       "git checkout [options] <branch>",
-       "git checkout [options] [<branch>] -- <file>...",
-       NULL,
-};
-
-struct checkout_opts {
-       int quiet;
-       int merge;
-       int force;
-       int writeout_stage;
-       int writeout_error;
-
-       const char *new_branch;
-       int new_branch_log;
-       enum branch_track track;
-};
-
-static int post_checkout_hook(struct commit *old, struct commit *new,
-                             int changed)
-{
-       return run_hook(NULL, "post-checkout",
-                       sha1_to_hex(old ? old->object.sha1 : null_sha1),
-                       sha1_to_hex(new ? new->object.sha1 : null_sha1),
-                       changed ? "1" : "0", NULL);
-       /* "new" can be NULL when checking out from the index before
-          a commit exists. */
-
-}
-
-static int update_some(const unsigned char *sha1, const char *base, int baselen,
-               const char *pathname, unsigned mode, int stage, void *context)
-{
-       int len;
-       struct cache_entry *ce;
-
-       if (S_ISDIR(mode))
-               return READ_TREE_RECURSIVE;
-
-       len = baselen + strlen(pathname);
-       ce = xcalloc(1, cache_entry_size(len));
-       hashcpy(ce->sha1, sha1);
-       memcpy(ce->name, base, baselen);
-       memcpy(ce->name + baselen, pathname, len - baselen);
-       ce->ce_flags = create_ce_flags(len, 0);
-       ce->ce_mode = create_ce_mode(mode);
-       add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
-       return 0;
-}
-
-static int read_tree_some(struct tree *tree, const char **pathspec)
-{
-       read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL);
-
-       /* update the index with the given tree's info
-        * for all args, expanding wildcards, and exit
-        * with any non-zero return code.
-        */
-       return 0;
-}
-
-static int skip_same_name(struct cache_entry *ce, int pos)
-{
-       while (++pos < active_nr &&
-              !strcmp(active_cache[pos]->name, ce->name))
-               ; /* skip */
-       return pos;
-}
-
-static int check_stage(int stage, struct cache_entry *ce, int pos)
-{
-       while (pos < active_nr &&
-              !strcmp(active_cache[pos]->name, ce->name)) {
-               if (ce_stage(active_cache[pos]) == stage)
-                       return 0;
-               pos++;
-       }
-       return error("path '%s' does not have %s version",
-                    ce->name,
-                    (stage == 2) ? "our" : "their");
-}
-
-static int check_all_stages(struct cache_entry *ce, int pos)
-{
-       if (ce_stage(ce) != 1 ||
-           active_nr <= pos + 2 ||
-           strcmp(active_cache[pos+1]->name, ce->name) ||
-           ce_stage(active_cache[pos+1]) != 2 ||
-           strcmp(active_cache[pos+2]->name, ce->name) ||
-           ce_stage(active_cache[pos+2]) != 3)
-               return error("path '%s' does not have all three versions",
-                            ce->name);
-       return 0;
-}
-
-static int checkout_stage(int stage, struct cache_entry *ce, int pos,
-                         struct checkout *state)
-{
-       while (pos < active_nr &&
-              !strcmp(active_cache[pos]->name, ce->name)) {
-               if (ce_stage(active_cache[pos]) == stage)
-                       return checkout_entry(active_cache[pos], state, NULL);
-               pos++;
-       }
-       return error("path '%s' does not have %s version",
-                    ce->name,
-                    (stage == 2) ? "our" : "their");
-}
-
-/* NEEDSWORK: share with merge-recursive */
-static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
-{
-       unsigned long size;
-       enum object_type type;
-
-       if (!hashcmp(sha1, null_sha1)) {
-               mm->ptr = xstrdup("");
-               mm->size = 0;
-               return;
-       }
-
-       mm->ptr = read_sha1_file(sha1, &type, &size);
-       if (!mm->ptr || type != OBJ_BLOB)
-               die("unable to read blob object %s", sha1_to_hex(sha1));
-       mm->size = size;
-}
-
-static int checkout_merged(int pos, struct checkout *state)
-{
-       struct cache_entry *ce = active_cache[pos];
-       const char *path = ce->name;
-       mmfile_t ancestor, ours, theirs;
-       int status;
-       unsigned char sha1[20];
-       mmbuffer_t result_buf;
-
-       if (ce_stage(ce) != 1 ||
-           active_nr <= pos + 2 ||
-           strcmp(active_cache[pos+1]->name, path) ||
-           ce_stage(active_cache[pos+1]) != 2 ||
-           strcmp(active_cache[pos+2]->name, path) ||
-           ce_stage(active_cache[pos+2]) != 3)
-               return error("path '%s' does not have all 3 versions", path);
-
-       fill_mm(active_cache[pos]->sha1, &ancestor);
-       fill_mm(active_cache[pos+1]->sha1, &ours);
-       fill_mm(active_cache[pos+2]->sha1, &theirs);
-
-       status = ll_merge(&result_buf, path, &ancestor,
-                         &ours, "ours", &theirs, "theirs", 1);
-       free(ancestor.ptr);
-       free(ours.ptr);
-       free(theirs.ptr);
-       if (status < 0 || !result_buf.ptr) {
-               free(result_buf.ptr);
-               return error("path '%s': cannot merge", path);
-       }
-
-       /*
-        * NEEDSWORK:
-        * There is absolutely no reason to write this as a blob object
-        * and create a phony cache entry just to leak.  This hack is
-        * primarily to get to the write_entry() machinery that massages
-        * the contents to work-tree format and writes out which only
-        * allows it for a cache entry.  The code in write_entry() needs
-        * to be refactored to allow us to feed a <buffer, size, mode>
-        * instead of a cache entry.  Such a refactoring would help
-        * merge_recursive as well (it also writes the merge result to the
-        * object database even when it may contain conflicts).
-        */
-       if (write_sha1_file(result_buf.ptr, result_buf.size,
-                           blob_type, sha1))
-               die("Unable to add merge result for '%s'", path);
-       ce = make_cache_entry(create_ce_mode(active_cache[pos+1]->ce_mode),
-                             sha1,
-                             path, 2, 0);
-       if (!ce)
-               die("make_cache_entry failed for path '%s'", path);
-       status = checkout_entry(ce, state, NULL);
-       return status;
-}
-
-static int checkout_paths(struct tree *source_tree, const char **pathspec,
-                         struct checkout_opts *opts)
-{
-       int pos;
-       struct checkout state;
-       static char *ps_matched;
-       unsigned char rev[20];
-       int flag;
-       struct commit *head;
-       int errs = 0;
-       int stage = opts->writeout_stage;
-       int merge = opts->merge;
-       int newfd;
-       struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
-
-       newfd = hold_locked_index(lock_file, 1);
-       if (read_cache() < 0)
-               return error("corrupt index file");
-
-       if (source_tree)
-               read_tree_some(source_tree, pathspec);
-
-       for (pos = 0; pathspec[pos]; pos++)
-               ;
-       ps_matched = xcalloc(1, pos);
-
-       for (pos = 0; pos < active_nr; pos++) {
-               struct cache_entry *ce = active_cache[pos];
-               match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched);
-       }
-
-       if (report_path_error(ps_matched, pathspec, 0))
-               return 1;
-
-       /* Any unmerged paths? */
-       for (pos = 0; pos < active_nr; pos++) {
-               struct cache_entry *ce = active_cache[pos];
-               if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
-                       if (!ce_stage(ce))
-                               continue;
-                       if (opts->force) {
-                               warning("path '%s' is unmerged", ce->name);
-                       } else if (stage) {
-                               errs |= check_stage(stage, ce, pos);
-                       } else if (opts->merge) {
-                               errs |= check_all_stages(ce, pos);
-                       } else {
-                               errs = 1;
-                               error("path '%s' is unmerged", ce->name);
-                       }
-                       pos = skip_same_name(ce, pos) - 1;
-               }
-       }
-       if (errs)
-               return 1;
-
-       /* Now we are committed to check them out */
-       memset(&state, 0, sizeof(state));
-       state.force = 1;
-       state.refresh_cache = 1;
-       for (pos = 0; pos < active_nr; pos++) {
-               struct cache_entry *ce = active_cache[pos];
-               if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
-                       if (!ce_stage(ce)) {
-                               errs |= checkout_entry(ce, &state, NULL);
-                               continue;
-                       }
-                       if (stage)
-                               errs |= checkout_stage(stage, ce, pos, &state);
-                       else if (merge)
-                               errs |= checkout_merged(pos, &state);
-                       pos = skip_same_name(ce, pos) - 1;
-               }
-       }
-
-       if (write_cache(newfd, active_cache, active_nr) ||
-           commit_locked_index(lock_file))
-               die("unable to write new index file");
-
-       resolve_ref("HEAD", rev, 0, &flag);
-       head = lookup_commit_reference_gently(rev, 1);
-
-       errs |= post_checkout_hook(head, head, 0);
-       return errs;
-}
-
-static void show_local_changes(struct object *head)
-{
-       struct rev_info rev;
-       /* I think we want full paths, even if we're in a subdirectory. */
-       init_revisions(&rev, NULL);
-       rev.abbrev = 0;
-       rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
-       if (diff_setup_done(&rev.diffopt) < 0)
-               die("diff_setup_done failed");
-       add_pending_object(&rev, head, NULL);
-       run_diff_index(&rev, 0);
-}
-
-static void describe_detached_head(char *msg, struct commit *commit)
-{
-       struct strbuf sb = STRBUF_INIT;
-       parse_commit(commit);
-       pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, 0, NULL, NULL, 0, 0);
-       fprintf(stderr, "%s %s... %s\n", msg,
-               find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf);
-       strbuf_release(&sb);
-}
-
-static int reset_tree(struct tree *tree, struct checkout_opts *o, int worktree)
-{
-       struct unpack_trees_options opts;
-       struct tree_desc tree_desc;
-
-       memset(&opts, 0, sizeof(opts));
-       opts.head_idx = -1;
-       opts.update = worktree;
-       opts.skip_unmerged = !worktree;
-       opts.reset = 1;
-       opts.merge = 1;
-       opts.fn = oneway_merge;
-       opts.verbose_update = !o->quiet;
-       opts.src_index = &the_index;
-       opts.dst_index = &the_index;
-       parse_tree(tree);
-       init_tree_desc(&tree_desc, tree->buffer, tree->size);
-       switch (unpack_trees(1, &tree_desc, &opts)) {
-       case -2:
-               o->writeout_error = 1;
-               /*
-                * We return 0 nevertheless, as the index is all right
-                * and more importantly we have made best efforts to
-                * update paths in the work tree, and we cannot revert
-                * them.
-                */
-       case 0:
-               return 0;
-       default:
-               return 128;
-       }
-}
-
-struct branch_info {
-       const char *name; /* The short name used */
-       const char *path; /* The full name of a real branch */
-       struct commit *commit; /* The named commit */
-};
-
-static void setup_branch_path(struct branch_info *branch)
-{
-       struct strbuf buf = STRBUF_INIT;
-
-       strbuf_branchname(&buf, branch->name);
-       if (strcmp(buf.buf, branch->name))
-               branch->name = xstrdup(buf.buf);
-       strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
-       branch->path = strbuf_detach(&buf, NULL);
-}
-
-static int merge_working_tree(struct checkout_opts *opts,
-                             struct branch_info *old, struct branch_info *new)
-{
-       int ret;
-       struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
-       int newfd = hold_locked_index(lock_file, 1);
-       int reprime_cache_tree = 0;
-
-       if (read_cache() < 0)
-               return error("corrupt index file");
-
-       cache_tree_free(&active_cache_tree);
-       if (opts->force) {
-               ret = reset_tree(new->commit->tree, opts, 1);
-               if (ret)
-                       return ret;
-               reprime_cache_tree = 1;
-       } else {
-               struct tree_desc trees[2];
-               struct tree *tree;
-               struct unpack_trees_options topts;
-
-               memset(&topts, 0, sizeof(topts));
-               topts.head_idx = -1;
-               topts.src_index = &the_index;
-               topts.dst_index = &the_index;
-
-               topts.msgs.not_uptodate_file = "You have local changes to '%s'; cannot switch branches.";
-
-               refresh_cache(REFRESH_QUIET);
-
-               if (unmerged_cache()) {
-                       error("you need to resolve your current index first");
-                       return 1;
-               }
-
-               /* 2-way merge to the new branch */
-               topts.initial_checkout = is_cache_unborn();
-               topts.update = 1;
-               topts.merge = 1;
-               topts.gently = opts->merge;
-               topts.verbose_update = !opts->quiet;
-               topts.fn = twoway_merge;
-               topts.dir = xcalloc(1, sizeof(*topts.dir));
-               topts.dir->flags |= DIR_SHOW_IGNORED;
-               topts.dir->exclude_per_dir = ".gitignore";
-               tree = parse_tree_indirect(old->commit->object.sha1);
-               init_tree_desc(&trees[0], tree->buffer, tree->size);
-               tree = parse_tree_indirect(new->commit->object.sha1);
-               init_tree_desc(&trees[1], tree->buffer, tree->size);
-
-               ret = unpack_trees(2, trees, &topts);
-               if (ret != -1) {
-                       reprime_cache_tree = 1;
-               } else {
-                       /*
-                        * Unpack couldn't do a trivial merge; either
-                        * give up or do a real merge, depending on
-                        * whether the merge flag was used.
-                        */
-                       struct tree *result;
-                       struct tree *work;
-                       struct merge_options o;
-                       if (!opts->merge)
-                               return 1;
-                       parse_commit(old->commit);
-
-                       /* Do more real merge */
-
-                       /*
-                        * We update the index fully, then write the
-                        * tree from the index, then merge the new
-                        * branch with the current tree, with the old
-                        * branch as the base. Then we reset the index
-                        * (but not the working tree) to the new
-                        * branch, leaving the working tree as the
-                        * merged version, but skipping unmerged
-                        * entries in the index.
-                        */
-
-                       add_files_to_cache(NULL, NULL, 0);
-                       init_merge_options(&o);
-                       o.verbosity = 0;
-                       work = write_tree_from_memory(&o);
-
-                       ret = reset_tree(new->commit->tree, opts, 1);
-                       if (ret)
-                               return ret;
-                       o.branch1 = new->name;
-                       o.branch2 = "local";
-                       merge_trees(&o, new->commit->tree, work,
-                               old->commit->tree, &result);
-                       ret = reset_tree(new->commit->tree, opts, 0);
-                       if (ret)
-                               return ret;
-               }
-       }
-
-       if (reprime_cache_tree)
-               prime_cache_tree(&active_cache_tree, new->commit->tree);
-       if (write_cache(newfd, active_cache, active_nr) ||
-           commit_locked_index(lock_file))
-               die("unable to write new index file");
-
-       if (!opts->force && !opts->quiet)
-               show_local_changes(&new->commit->object);
-
-       return 0;
-}
-
-static void report_tracking(struct branch_info *new)
-{
-       struct strbuf sb = STRBUF_INIT;
-       struct branch *branch = branch_get(new->name);
-
-       if (!format_tracking_info(branch, &sb))
-               return;
-       fputs(sb.buf, stdout);
-       strbuf_release(&sb);
-}
-
-static void update_refs_for_switch(struct checkout_opts *opts,
-                                  struct branch_info *old,
-                                  struct branch_info *new)
-{
-       struct strbuf msg = STRBUF_INIT;
-       const char *old_desc;
-       if (opts->new_branch) {
-               create_branch(old->name, opts->new_branch, new->name, 0,
-                             opts->new_branch_log, opts->track);
-               new->name = opts->new_branch;
-               setup_branch_path(new);
-       }
-
-       old_desc = old->name;
-       if (!old_desc && old->commit)
-               old_desc = sha1_to_hex(old->commit->object.sha1);
-       strbuf_addf(&msg, "checkout: moving from %s to %s",
-                   old_desc ? old_desc : "(invalid)", new->name);
-
-       if (new->path) {
-               create_symref("HEAD", new->path, msg.buf);
-               if (!opts->quiet) {
-                       if (old->path && !strcmp(new->path, old->path))
-                               fprintf(stderr, "Already on '%s'\n",
-                                       new->name);
-                       else
-                               fprintf(stderr, "Switched to%s branch '%s'\n",
-                                       opts->new_branch ? " a new" : "",
-                                       new->name);
-               }
-       } else if (strcmp(new->name, "HEAD")) {
-               update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
-                          REF_NODEREF, DIE_ON_ERR);
-               if (!opts->quiet) {
-                       if (old->path)
-                               fprintf(stderr, "Note: moving to '%s' which isn't a local branch\nIf you want to create a new branch from this checkout, you may do so\n(now or later) by using -b with the checkout command again. Example:\n  git checkout -b <new_branch_name>\n", new->name);
-                       describe_detached_head("HEAD is now at", new->commit);
-               }
-       }
-       remove_branch_state();
-       strbuf_release(&msg);
-       if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD")))
-               report_tracking(new);
-}
-
-static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
-{
-       int ret = 0;
-       struct branch_info old;
-       unsigned char rev[20];
-       int flag;
-       memset(&old, 0, sizeof(old));
-       old.path = resolve_ref("HEAD", rev, 0, &flag);
-       old.commit = lookup_commit_reference_gently(rev, 1);
-       if (!(flag & REF_ISSYMREF))
-               old.path = NULL;
-
-       if (old.path && !prefixcmp(old.path, "refs/heads/"))
-               old.name = old.path + strlen("refs/heads/");
-
-       if (!new->name) {
-               new->name = "HEAD";
-               new->commit = old.commit;
-               if (!new->commit)
-                       die("You are on a branch yet to be born");
-               parse_commit(new->commit);
-       }
-
-       /*
-        * If we were on a detached HEAD, but we are now moving to
-        * a new commit, we want to mention the old commit once more
-        * to remind the user that it might be lost.
-        */
-       if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
-               describe_detached_head("Previous HEAD position was", old.commit);
-
-       if (!old.commit && !opts->force) {
-               if (!opts->quiet) {
-                       warning("You appear to be on a branch yet to be born.");
-                       warning("Forcing checkout of %s.", new->name);
-               }
-               opts->force = 1;
-       }
-
-       ret = merge_working_tree(opts, &old, new);
-       if (ret)
-               return ret;
-
-       update_refs_for_switch(opts, &old, new);
-
-       ret = post_checkout_hook(old.commit, new->commit, 1);
-       return ret || opts->writeout_error;
-}
-
-static int git_checkout_config(const char *var, const char *value, void *cb)
-{
-       return git_xmerge_config(var, value, cb);
-}
-
-int cmd_checkout(int argc, const char **argv, const char *prefix)
-{
-       struct checkout_opts opts;
-       unsigned char rev[20];
-       const char *arg;
-       struct branch_info new;
-       struct tree *source_tree = NULL;
-       char *conflict_style = NULL;
-       struct option options[] = {
-               OPT__QUIET(&opts.quiet),
-               OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"),
-               OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"),
-               OPT_SET_INT('t', "track",  &opts.track, "track",
-                       BRANCH_TRACK_EXPLICIT),
-               OPT_SET_INT('2', "ours", &opts.writeout_stage, "stage",
-                           2),
-               OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage",
-                           3),
-               OPT_BOOLEAN('f', NULL, &opts.force, "force"),
-               OPT_BOOLEAN('m', "merge", &opts.merge, "merge"),
-               OPT_STRING(0, "conflict", &conflict_style, "style",
-                          "conflict style (merge or diff3)"),
-               OPT_END(),
-       };
-       int has_dash_dash;
-
-       memset(&opts, 0, sizeof(opts));
-       memset(&new, 0, sizeof(new));
-
-       git_config(git_checkout_config, NULL);
-
-       opts.track = BRANCH_TRACK_UNSPECIFIED;
-
-       argc = parse_options(argc, argv, options, checkout_usage,
-                            PARSE_OPT_KEEP_DASHDASH);
-
-       /* --track without -b should DWIM */
-       if (0 < opts.track && !opts.new_branch) {
-               const char *argv0 = argv[0];
-               if (!argc || !strcmp(argv0, "--"))
-                       die ("--track needs a branch name");
-               if (!prefixcmp(argv0, "refs/"))
-                       argv0 += 5;
-               if (!prefixcmp(argv0, "remotes/"))
-                       argv0 += 8;
-               argv0 = strchr(argv0, '/');
-               if (!argv0 || !argv0[1])
-                       die ("Missing branch name; try -b");
-               opts.new_branch = argv0 + 1;
-       }
-
-       if (opts.track == BRANCH_TRACK_UNSPECIFIED)
-               opts.track = git_branch_track;
-       if (conflict_style) {
-               opts.merge = 1; /* implied */
-               git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
-       }
-
-       if (opts.force && opts.merge)
-               die("git checkout: -f and -m are incompatible");
-
-       /*
-        * case 1: git checkout <ref> -- [<paths>]
-        *
-        *   <ref> must be a valid tree, everything after the '--' must be
-        *   a path.
-        *
-        * case 2: git checkout -- [<paths>]
-        *
-        *   everything after the '--' must be paths.
-        *
-        * case 3: git checkout <something> [<paths>]
-        *
-        *   With no paths, if <something> is a commit, that is to
-        *   switch to the branch or detach HEAD at it.
-        *
-        *   Otherwise <something> shall not be ambiguous.
-        *   - If it's *only* a reference, treat it like case (1).
-        *   - If it's only a path, treat it like case (2).
-        *   - else: fail.
-        *
-        */
-       if (argc) {
-               if (!strcmp(argv[0], "--")) {       /* case (2) */
-                       argv++;
-                       argc--;
-                       goto no_reference;
-               }
-
-               arg = argv[0];
-               has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
-
-               if (!strcmp(arg, "-"))
-                       arg = "@{-1}";
-
-               if (get_sha1(arg, rev)) {
-                       if (has_dash_dash)          /* case (1) */
-                               die("invalid reference: %s", arg);
-                       goto no_reference;          /* case (3 -> 2) */
-               }
-
-               /* we can't end up being in (2) anymore, eat the argument */
-               argv++;
-               argc--;
-
-               new.name = arg;
-               if ((new.commit = lookup_commit_reference_gently(rev, 1))) {
-                       setup_branch_path(&new);
-                       if (resolve_ref(new.path, rev, 1, NULL))
-                               new.commit = lookup_commit_reference(rev);
-                       else
-                               new.path = NULL;
-                       parse_commit(new.commit);
-                       source_tree = new.commit->tree;
-               } else
-                       source_tree = parse_tree_indirect(rev);
-
-               if (!source_tree)                   /* case (1): want a tree */
-                       die("reference is not a tree: %s", arg);
-               if (!has_dash_dash) {/* case (3 -> 1) */
-                       /*
-                        * Do not complain the most common case
-                        *      git checkout branch
-                        * even if there happen to be a file called 'branch';
-                        * it would be extremely annoying.
-                        */
-                       if (argc)
-                               verify_non_filename(NULL, arg);
-               }
-               else {
-                       argv++;
-                       argc--;
-               }
-       }
-
-no_reference:
-       if (argc) {
-               const char **pathspec = get_pathspec(prefix, argv);
-
-               if (!pathspec)
-                       die("invalid path specification");
-
-               /* Checkout paths */
-               if (opts.new_branch) {
-                       if (argc == 1) {
-                               die("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]);
-                       } else {
-                               die("git checkout: updating paths is incompatible with switching branches.");
-                       }
-               }
-
-               if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
-                       die("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index.");
-
-               return checkout_paths(source_tree, pathspec, &opts);
-       }
-
-       if (opts.new_branch) {
-               struct strbuf buf = STRBUF_INIT;
-               if (strbuf_check_branch_ref(&buf, opts.new_branch))
-                       die("git checkout: we do not like '%s' as a branch name.",
-                           opts.new_branch);
-               if (!get_sha1(buf.buf, rev))
-                       die("git checkout: branch %s already exists", opts.new_branch);
-               strbuf_release(&buf);
-       }
-
-       if (new.name && !new.commit) {
-               die("Cannot switch branch to a non-commit.");
-       }
-       if (opts.writeout_stage)
-               die("--ours/--theirs is incompatible with switching branches.");
-
-       return switch_branches(&opts, &new);
-}
diff --git a/builtin-clean.c b/builtin-clean.c
deleted file mode 100644 (file)
index c5ad33d..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * "git clean" builtin command
- *
- * Copyright (C) 2007 Shawn Bohrer
- *
- * Based on git-clean.sh by Pavel Roskin
- */
-
-#include "builtin.h"
-#include "cache.h"
-#include "dir.h"
-#include "parse-options.h"
-#include "quote.h"
-
-static int force = -1; /* unset */
-
-static const char *const builtin_clean_usage[] = {
-       "git clean [-d] [-f] [-n] [-q] [-x | -X] [--] <paths>...",
-       NULL
-};
-
-static int git_clean_config(const char *var, const char *value, void *cb)
-{
-       if (!strcmp(var, "clean.requireforce"))
-               force = !git_config_bool(var, value);
-       return git_default_config(var, value, cb);
-}
-
-int cmd_clean(int argc, const char **argv, const char *prefix)
-{
-       int i;
-       int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0;
-       int ignored_only = 0, baselen = 0, config_set = 0, errors = 0;
-       struct strbuf directory = STRBUF_INIT;
-       struct dir_struct dir;
-       const char *path, *base;
-       static const char **pathspec;
-       struct strbuf buf = STRBUF_INIT;
-       const char *qname;
-       char *seen = NULL;
-       struct option options[] = {
-               OPT__QUIET(&quiet),
-               OPT__DRY_RUN(&show_only),
-               OPT_BOOLEAN('f', NULL, &force, "force"),
-               OPT_BOOLEAN('d', NULL, &remove_directories,
-                               "remove whole directories"),
-               OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"),
-               OPT_BOOLEAN('X', NULL, &ignored_only,
-                               "remove only ignored files"),
-               OPT_END()
-       };
-
-       git_config(git_clean_config, NULL);
-       if (force < 0)
-               force = 0;
-       else
-               config_set = 1;
-
-       argc = parse_options(argc, argv, options, builtin_clean_usage, 0);
-
-       memset(&dir, 0, sizeof(dir));
-       if (ignored_only)
-               dir.flags |= DIR_SHOW_IGNORED;
-
-       if (ignored && ignored_only)
-               die("-x and -X cannot be used together");
-
-       if (!show_only && !force)
-               die("clean.requireForce%s set and -n or -f not given; "
-                   "refusing to clean", config_set ? "" : " not");
-
-       dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
-
-       if (!ignored)
-               setup_standard_excludes(&dir);
-
-       pathspec = get_pathspec(prefix, argv);
-       read_cache();
-
-       /*
-        * Calculate common prefix for the pathspec, and
-        * use that to optimize the directory walk
-        */
-       baselen = common_prefix(pathspec);
-       path = ".";
-       base = "";
-       if (baselen)
-               path = base = xmemdupz(*pathspec, baselen);
-       read_directory(&dir, path, base, baselen, pathspec);
-
-       if (pathspec)
-               seen = xmalloc(argc > 0 ? argc : 1);
-
-       for (i = 0; i < dir.nr; i++) {
-               struct dir_entry *ent = dir.entries[i];
-               int len, pos;
-               int matches = 0;
-               struct cache_entry *ce;
-               struct stat st;
-
-               /*
-                * Remove the '/' at the end that directory
-                * walking adds for directory entries.
-                */
-               len = ent->len;
-               if (len && ent->name[len-1] == '/')
-                       len--;
-               pos = cache_name_pos(ent->name, len);
-               if (0 <= pos)
-                       continue;       /* exact match */
-               pos = -pos - 1;
-               if (pos < active_nr) {
-                       ce = active_cache[pos];
-                       if (ce_namelen(ce) == len &&
-                           !memcmp(ce->name, ent->name, len))
-                               continue; /* Yup, this one exists unmerged */
-               }
-
-               /*
-                * we might have removed this as part of earlier
-                * recursive directory removal, so lstat() here could
-                * fail with ENOENT.
-                */
-               if (lstat(ent->name, &st))
-                       continue;
-
-               if (pathspec) {
-                       memset(seen, 0, argc > 0 ? argc : 1);
-                       matches = match_pathspec(pathspec, ent->name, len,
-                                                baselen, seen);
-               }
-
-               if (S_ISDIR(st.st_mode)) {
-                       strbuf_addstr(&directory, ent->name);
-                       qname = quote_path_relative(directory.buf, directory.len, &buf, prefix);
-                       if (show_only && (remove_directories ||
-                           (matches == MATCHED_EXACTLY))) {
-                               printf("Would remove %s\n", qname);
-                       } else if (remove_directories ||
-                                  (matches == MATCHED_EXACTLY)) {
-                               if (!quiet)
-                                       printf("Removing %s\n", qname);
-                               if (remove_dir_recursively(&directory, 0) != 0) {
-                                       warning("failed to remove '%s'", qname);
-                                       errors++;
-                               }
-                       } else if (show_only) {
-                               printf("Would not remove %s\n", qname);
-                       } else {
-                               printf("Not removing %s\n", qname);
-                       }
-                       strbuf_reset(&directory);
-               } else {
-                       if (pathspec && !matches)
-                               continue;
-                       qname = quote_path_relative(ent->name, -1, &buf, prefix);
-                       if (show_only) {
-                               printf("Would remove %s\n", qname);
-                               continue;
-                       } else if (!quiet) {
-                               printf("Removing %s\n", qname);
-                       }
-                       if (unlink(ent->name) != 0) {
-                               warning("failed to remove '%s'", qname);
-                               errors++;
-                       }
-               }
-       }
-       free(seen);
-
-       strbuf_release(&directory);
-       return (errors != 0);
-}
diff --git a/builtin-clone.c b/builtin-clone.c
deleted file mode 100644 (file)
index 880373f..0000000
+++ /dev/null
@@ -1,595 +0,0 @@
-/*
- * Builtin "git clone"
- *
- * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>,
- *              2008 Daniel Barkalow <barkalow@iabervon.org>
- * Based on git-commit.sh by Junio C Hamano and Linus Torvalds
- *
- * Clone a repository into a different directory that does not yet exist.
- */
-
-#include "cache.h"
-#include "parse-options.h"
-#include "fetch-pack.h"
-#include "refs.h"
-#include "tree.h"
-#include "tree-walk.h"
-#include "unpack-trees.h"
-#include "transport.h"
-#include "strbuf.h"
-#include "dir.h"
-#include "pack-refs.h"
-#include "sigchain.h"
-#include "branch.h"
-#include "remote.h"
-#include "run-command.h"
-
-/*
- * Overall FIXMEs:
- *  - respect DB_ENVIRONMENT for .git/objects.
- *
- * Implementation notes:
- *  - dropping use-separate-remote and no-separate-remote compatibility
- *
- */
-static const char * const builtin_clone_usage[] = {
-       "git clone [options] [--] <repo> [<dir>]",
-       NULL
-};
-
-static int option_quiet, option_no_checkout, option_bare, option_mirror;
-static int option_local, option_no_hardlinks, option_shared;
-static char *option_template, *option_reference, *option_depth;
-static char *option_origin = NULL;
-static char *option_upload_pack = "git-upload-pack";
-static int option_verbose;
-
-static struct option builtin_clone_options[] = {
-       OPT__QUIET(&option_quiet),
-       OPT__VERBOSE(&option_verbose),
-       OPT_BOOLEAN('n', "no-checkout", &option_no_checkout,
-                   "don't create a checkout"),
-       OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"),
-       OPT_BOOLEAN(0, "naked", &option_bare, "create a bare repository"),
-       OPT_BOOLEAN(0, "mirror", &option_mirror,
-                   "create a mirror repository (implies bare)"),
-       OPT_BOOLEAN('l', "local", &option_local,
-                   "to clone from a local repository"),
-       OPT_BOOLEAN(0, "no-hardlinks", &option_no_hardlinks,
-                   "don't use local hardlinks, always copy"),
-       OPT_BOOLEAN('s', "shared", &option_shared,
-                   "setup as shared repository"),
-       OPT_STRING(0, "template", &option_template, "path",
-                  "path the template repository"),
-       OPT_STRING(0, "reference", &option_reference, "repo",
-                  "reference repository"),
-       OPT_STRING('o', "origin", &option_origin, "branch",
-                  "use <branch> instead of 'origin' to track upstream"),
-       OPT_STRING('u', "upload-pack", &option_upload_pack, "path",
-                  "path to git-upload-pack on the remote"),
-       OPT_STRING(0, "depth", &option_depth, "depth",
-                   "create a shallow clone of that depth"),
-
-       OPT_END()
-};
-
-static char *get_repo_path(const char *repo, int *is_bundle)
-{
-       static char *suffix[] = { "/.git", ".git", "" };
-       static char *bundle_suffix[] = { ".bundle", "" };
-       struct stat st;
-       int i;
-
-       for (i = 0; i < ARRAY_SIZE(suffix); i++) {
-               const char *path;
-               path = mkpath("%s%s", repo, suffix[i]);
-               if (is_directory(path)) {
-                       *is_bundle = 0;
-                       return xstrdup(make_nonrelative_path(path));
-               }
-       }
-
-       for (i = 0; i < ARRAY_SIZE(bundle_suffix); i++) {
-               const char *path;
-               path = mkpath("%s%s", repo, bundle_suffix[i]);
-               if (!stat(path, &st) && S_ISREG(st.st_mode)) {
-                       *is_bundle = 1;
-                       return xstrdup(make_nonrelative_path(path));
-               }
-       }
-
-       return NULL;
-}
-
-static char *guess_dir_name(const char *repo, int is_bundle, int is_bare)
-{
-       const char *end = repo + strlen(repo), *start;
-
-       /*
-        * Strip trailing slashes and /.git
-        */
-       while (repo < end && is_dir_sep(end[-1]))
-               end--;
-       if (end - repo > 5 && is_dir_sep(end[-5]) &&
-           !strncmp(end - 4, ".git", 4)) {
-               end -= 5;
-               while (repo < end && is_dir_sep(end[-1]))
-                       end--;
-       }
-
-       /*
-        * Find last component, but be prepared that repo could have
-        * the form  "remote.example.com:foo.git", i.e. no slash
-        * in the directory part.
-        */
-       start = end;
-       while (repo < start && !is_dir_sep(start[-1]) && start[-1] != ':')
-               start--;
-
-       /*
-        * Strip .{bundle,git}.
-        */
-       if (is_bundle) {
-               if (end - start > 7 && !strncmp(end - 7, ".bundle", 7))
-                       end -= 7;
-       } else {
-               if (end - start > 4 && !strncmp(end - 4, ".git", 4))
-                       end -= 4;
-       }
-
-       if (is_bare) {
-               struct strbuf result = STRBUF_INIT;
-               strbuf_addf(&result, "%.*s.git", (int)(end - start), start);
-               return strbuf_detach(&result, 0);
-       }
-
-       return xstrndup(start, end - start);
-}
-
-static void strip_trailing_slashes(char *dir)
-{
-       char *end = dir + strlen(dir);
-
-       while (dir < end - 1 && is_dir_sep(end[-1]))
-               end--;
-       *end = '\0';
-}
-
-static void setup_reference(const char *repo)
-{
-       const char *ref_git;
-       char *ref_git_copy;
-
-       struct remote *remote;
-       struct transport *transport;
-       const struct ref *extra;
-
-       ref_git = make_absolute_path(option_reference);
-
-       if (is_directory(mkpath("%s/.git/objects", ref_git)))
-               ref_git = mkpath("%s/.git", ref_git);
-       else if (!is_directory(mkpath("%s/objects", ref_git)))
-               die("reference repository '%s' is not a local directory.",
-                   option_reference);
-
-       ref_git_copy = xstrdup(ref_git);
-
-       add_to_alternates_file(ref_git_copy);
-
-       remote = remote_get(ref_git_copy);
-       transport = transport_get(remote, ref_git_copy);
-       for (extra = transport_get_remote_refs(transport); extra;
-            extra = extra->next)
-               add_extra_ref(extra->name, extra->old_sha1, 0);
-
-       transport_disconnect(transport);
-
-       free(ref_git_copy);
-}
-
-static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest)
-{
-       struct dirent *de;
-       struct stat buf;
-       int src_len, dest_len;
-       DIR *dir;
-
-       dir = opendir(src->buf);
-       if (!dir)
-               die("failed to open %s", src->buf);
-
-       if (mkdir(dest->buf, 0777)) {
-               if (errno != EEXIST)
-                       die("failed to create directory %s", dest->buf);
-               else if (stat(dest->buf, &buf))
-                       die("failed to stat %s", dest->buf);
-               else if (!S_ISDIR(buf.st_mode))
-                       die("%s exists and is not a directory", dest->buf);
-       }
-
-       strbuf_addch(src, '/');
-       src_len = src->len;
-       strbuf_addch(dest, '/');
-       dest_len = dest->len;
-
-       while ((de = readdir(dir)) != NULL) {
-               strbuf_setlen(src, src_len);
-               strbuf_addstr(src, de->d_name);
-               strbuf_setlen(dest, dest_len);
-               strbuf_addstr(dest, de->d_name);
-               if (stat(src->buf, &buf)) {
-                       warning ("failed to stat %s\n", src->buf);
-                       continue;
-               }
-               if (S_ISDIR(buf.st_mode)) {
-                       if (de->d_name[0] != '.')
-                               copy_or_link_directory(src, dest);
-                       continue;
-               }
-
-               if (unlink(dest->buf) && errno != ENOENT)
-                       die("failed to unlink %s", dest->buf);
-               if (!option_no_hardlinks) {
-                       if (!link(src->buf, dest->buf))
-                               continue;
-                       if (option_local)
-                               die("failed to create link %s", dest->buf);
-                       option_no_hardlinks = 1;
-               }
-               if (copy_file(dest->buf, src->buf, 0666))
-                       die("failed to copy file to %s", dest->buf);
-       }
-       closedir(dir);
-}
-
-static const struct ref *clone_local(const char *src_repo,
-                                    const char *dest_repo)
-{
-       const struct ref *ret;
-       struct strbuf src = STRBUF_INIT;
-       struct strbuf dest = STRBUF_INIT;
-       struct remote *remote;
-       struct transport *transport;
-
-       if (option_shared)
-               add_to_alternates_file(src_repo);
-       else {
-               strbuf_addf(&src, "%s/objects", src_repo);
-               strbuf_addf(&dest, "%s/objects", dest_repo);
-               copy_or_link_directory(&src, &dest);
-               strbuf_release(&src);
-               strbuf_release(&dest);
-       }
-
-       remote = remote_get(src_repo);
-       transport = transport_get(remote, src_repo);
-       ret = transport_get_remote_refs(transport);
-       transport_disconnect(transport);
-       return ret;
-}
-
-static const char *junk_work_tree;
-static const char *junk_git_dir;
-static pid_t junk_pid;
-
-static void remove_junk(void)
-{
-       struct strbuf sb = STRBUF_INIT;
-       if (getpid() != junk_pid)
-               return;
-       if (junk_git_dir) {
-               strbuf_addstr(&sb, junk_git_dir);
-               remove_dir_recursively(&sb, 0);
-               strbuf_reset(&sb);
-       }
-       if (junk_work_tree) {
-               strbuf_addstr(&sb, junk_work_tree);
-               remove_dir_recursively(&sb, 0);
-               strbuf_reset(&sb);
-       }
-}
-
-static void remove_junk_on_signal(int signo)
-{
-       remove_junk();
-       sigchain_pop(signo);
-       raise(signo);
-}
-
-static struct ref *write_remote_refs(const struct ref *refs,
-               struct refspec *refspec, const char *reflog)
-{
-       struct ref *local_refs = NULL;
-       struct ref **tail = &local_refs;
-       struct ref *r;
-
-       get_fetch_map(refs, refspec, &tail, 0);
-       if (!option_mirror)
-               get_fetch_map(refs, tag_refspec, &tail, 0);
-
-       for (r = local_refs; r; r = r->next)
-               add_extra_ref(r->peer_ref->name, r->old_sha1, 0);
-
-       pack_refs(PACK_REFS_ALL);
-       clear_extra_refs();
-
-       return local_refs;
-}
-
-int cmd_clone(int argc, const char **argv, const char *prefix)
-{
-       int is_bundle = 0;
-       struct stat buf;
-       const char *repo_name, *repo, *work_tree, *git_dir;
-       char *path, *dir;
-       int dest_exists;
-       const struct ref *refs, *head_points_at, *remote_head, *mapped_refs;
-       struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
-       struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
-       struct transport *transport = NULL;
-       char *src_ref_prefix = "refs/heads/";
-       int err = 0;
-
-       struct refspec *refspec;
-       const char *fetch_pattern;
-
-       junk_pid = getpid();
-
-       argc = parse_options(argc, argv, builtin_clone_options,
-                            builtin_clone_usage, 0);
-
-       if (argc == 0)
-               die("You must specify a repository to clone.");
-
-       if (option_mirror)
-               option_bare = 1;
-
-       if (option_bare) {
-               if (option_origin)
-                       die("--bare and --origin %s options are incompatible.",
-                           option_origin);
-               option_no_checkout = 1;
-       }
-
-       if (!option_origin)
-               option_origin = "origin";
-
-       repo_name = argv[0];
-
-       path = get_repo_path(repo_name, &is_bundle);
-       if (path)
-               repo = xstrdup(make_nonrelative_path(repo_name));
-       else if (!strchr(repo_name, ':'))
-               repo = xstrdup(make_absolute_path(repo_name));
-       else
-               repo = repo_name;
-
-       if (argc == 2)
-               dir = xstrdup(argv[1]);
-       else
-               dir = guess_dir_name(repo_name, is_bundle, option_bare);
-       strip_trailing_slashes(dir);
-
-       dest_exists = !stat(dir, &buf);
-       if (dest_exists && !is_empty_dir(dir))
-               die("destination path '%s' already exists and is not "
-                       "an empty directory.", dir);
-
-       strbuf_addf(&reflog_msg, "clone: from %s", repo);
-
-       if (option_bare)
-               work_tree = NULL;
-       else {
-               work_tree = getenv("GIT_WORK_TREE");
-               if (work_tree && !stat(work_tree, &buf))
-                       die("working tree '%s' already exists.", work_tree);
-       }
-
-       if (option_bare || work_tree)
-               git_dir = xstrdup(dir);
-       else {
-               work_tree = dir;
-               git_dir = xstrdup(mkpath("%s/.git", dir));
-       }
-
-       if (!option_bare) {
-               junk_work_tree = work_tree;
-               if (safe_create_leading_directories_const(work_tree) < 0)
-                       die("could not create leading directories of '%s': %s",
-                                       work_tree, strerror(errno));
-               if (!dest_exists && mkdir(work_tree, 0755))
-                       die("could not create work tree dir '%s': %s.",
-                                       work_tree, strerror(errno));
-               set_git_work_tree(work_tree);
-       }
-       junk_git_dir = git_dir;
-       atexit(remove_junk);
-       sigchain_push_common(remove_junk_on_signal);
-
-       setenv(CONFIG_ENVIRONMENT, mkpath("%s/config", git_dir), 1);
-
-       if (safe_create_leading_directories_const(git_dir) < 0)
-               die("could not create leading directories of '%s'", git_dir);
-       set_git_dir(make_absolute_path(git_dir));
-
-       init_db(option_template, option_quiet ? INIT_DB_QUIET : 0);
-
-       /*
-        * At this point, the config exists, so we do not need the
-        * environment variable.  We actually need to unset it, too, to
-        * re-enable parsing of the global configs.
-        */
-       unsetenv(CONFIG_ENVIRONMENT);
-
-       if (option_reference)
-               setup_reference(git_dir);
-
-       git_config(git_default_config, NULL);
-
-       if (option_bare) {
-               if (option_mirror)
-                       src_ref_prefix = "refs/";
-               strbuf_addstr(&branch_top, src_ref_prefix);
-
-               git_config_set("core.bare", "true");
-       } else {
-               strbuf_addf(&branch_top, "refs/remotes/%s/", option_origin);
-       }
-
-       strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf);
-
-       if (option_mirror || !option_bare) {
-               /* Configure the remote */
-               strbuf_addf(&key, "remote.%s.fetch", option_origin);
-               git_config_set_multivar(key.buf, value.buf, "^$", 0);
-               strbuf_reset(&key);
-
-               if (option_mirror) {
-                       strbuf_addf(&key, "remote.%s.mirror", option_origin);
-                       git_config_set(key.buf, "true");
-                       strbuf_reset(&key);
-               }
-
-               strbuf_addf(&key, "remote.%s.url", option_origin);
-               git_config_set(key.buf, repo);
-               strbuf_reset(&key);
-       }
-
-       fetch_pattern = value.buf;
-       refspec = parse_fetch_refspec(1, &fetch_pattern);
-
-       strbuf_reset(&value);
-
-       if (path && !is_bundle)
-               refs = clone_local(path, git_dir);
-       else {
-               struct remote *remote = remote_get(argv[0]);
-               transport = transport_get(remote, remote->url[0]);
-
-               if (!transport->get_refs_list || !transport->fetch)
-                       die("Don't know how to clone %s", transport->url);
-
-               transport_set_option(transport, TRANS_OPT_KEEP, "yes");
-
-               if (option_depth)
-                       transport_set_option(transport, TRANS_OPT_DEPTH,
-                                            option_depth);
-
-               if (option_quiet)
-                       transport->verbose = -1;
-               else if (option_verbose)
-                       transport->progress = 1;
-
-               if (option_upload_pack)
-                       transport_set_option(transport, TRANS_OPT_UPLOADPACK,
-                                            option_upload_pack);
-
-               refs = transport_get_remote_refs(transport);
-               if(refs)
-                       transport_fetch_refs(transport, refs);
-       }
-
-       if (refs) {
-               clear_extra_refs();
-
-               mapped_refs = write_remote_refs(refs, refspec, reflog_msg.buf);
-
-               remote_head = find_ref_by_name(refs, "HEAD");
-               head_points_at = guess_remote_head(remote_head, mapped_refs, 0);
-       }
-       else {
-               warning("You appear to have cloned an empty repository.");
-               head_points_at = NULL;
-               remote_head = NULL;
-               option_no_checkout = 1;
-               if (!option_bare)
-                       install_branch_config(0, "master", option_origin,
-                                             "refs/heads/master");
-       }
-
-       if (head_points_at) {
-               /* Local default branch link */
-               create_symref("HEAD", head_points_at->name, NULL);
-
-               if (!option_bare) {
-                       struct strbuf head_ref = STRBUF_INIT;
-                       const char *head = head_points_at->name;
-
-                       if (!prefixcmp(head, "refs/heads/"))
-                               head += 11;
-
-                       /* Set up the initial local branch */
-
-                       /* Local branch initial value */
-                       update_ref(reflog_msg.buf, "HEAD",
-                                  head_points_at->old_sha1,
-                                  NULL, 0, DIE_ON_ERR);
-
-                       strbuf_addstr(&head_ref, branch_top.buf);
-                       strbuf_addstr(&head_ref, "HEAD");
-
-                       /* Remote branch link */
-                       create_symref(head_ref.buf,
-                                     head_points_at->peer_ref->name,
-                                     reflog_msg.buf);
-
-                       install_branch_config(0, head, option_origin,
-                                             head_points_at->name);
-               }
-       } else if (remote_head) {
-               /* Source had detached HEAD pointing somewhere. */
-               if (!option_bare)
-                       update_ref(reflog_msg.buf, "HEAD",
-                                  remote_head->old_sha1,
-                                  NULL, REF_NODEREF, DIE_ON_ERR);
-       } else {
-               /* Nothing to checkout out */
-               if (!option_no_checkout)
-                       warning("remote HEAD refers to nonexistent ref, "
-                               "unable to checkout.\n");
-               option_no_checkout = 1;
-       }
-
-       if (transport)
-               transport_unlock_pack(transport);
-
-       if (!option_no_checkout) {
-               struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
-               struct unpack_trees_options opts;
-               struct tree *tree;
-               struct tree_desc t;
-               int fd;
-
-               /* We need to be in the new work tree for the checkout */
-               setup_work_tree();
-
-               fd = hold_locked_index(lock_file, 1);
-
-               memset(&opts, 0, sizeof opts);
-               opts.update = 1;
-               opts.merge = 1;
-               opts.fn = oneway_merge;
-               opts.verbose_update = !option_quiet;
-               opts.src_index = &the_index;
-               opts.dst_index = &the_index;
-
-               tree = parse_tree_indirect(remote_head->old_sha1);
-               parse_tree(tree);
-               init_tree_desc(&t, tree->buffer, tree->size);
-               unpack_trees(1, &t, &opts);
-
-               if (write_cache(fd, active_cache, active_nr) ||
-                   commit_locked_index(lock_file))
-                       die("unable to write new index file");
-
-               err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
-                               sha1_to_hex(remote_head->old_sha1), "1", NULL);
-       }
-
-       strbuf_release(&reflog_msg);
-       strbuf_release(&branch_top);
-       strbuf_release(&key);
-       strbuf_release(&value);
-       junk_pid = 0;
-       return err;
-}
diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c
deleted file mode 100644 (file)
index 0453425..0000000
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Linus Torvalds, 2005
- */
-#include "cache.h"
-#include "commit.h"
-#include "tree.h"
-#include "builtin.h"
-#include "utf8.h"
-
-#define BLOCKING (1ul << 14)
-
-/*
- * FIXME! Share the code with "write-tree.c"
- */
-static void check_valid(unsigned char *sha1, enum object_type expect)
-{
-       enum object_type type = sha1_object_info(sha1, NULL);
-       if (type < 0)
-               die("%s is not a valid object", sha1_to_hex(sha1));
-       if (type != expect)
-               die("%s is not a valid '%s' object", sha1_to_hex(sha1),
-                   typename(expect));
-}
-
-static const char commit_tree_usage[] = "git commit-tree <sha1> [-p <sha1>]* < changelog";
-
-static void new_parent(struct commit *parent, struct commit_list **parents_p)
-{
-       unsigned char *sha1 = parent->object.sha1;
-       struct commit_list *parents;
-       for (parents = *parents_p; parents; parents = parents->next) {
-               if (parents->item == parent) {
-                       error("duplicate parent %s ignored", sha1_to_hex(sha1));
-                       return;
-               }
-               parents_p = &parents->next;
-       }
-       commit_list_insert(parent, parents_p);
-}
-
-static const char commit_utf8_warn[] =
-"Warning: commit message does not conform to UTF-8.\n"
-"You may want to amend it after fixing the message, or set the config\n"
-"variable i18n.commitencoding to the encoding your project uses.\n";
-
-int commit_tree(const char *msg, unsigned char *tree,
-               struct commit_list *parents, unsigned char *ret,
-               const char *author)
-{
-       int result;
-       int encoding_is_utf8;
-       struct strbuf buffer;
-
-       check_valid(tree, OBJ_TREE);
-
-       /* Not having i18n.commitencoding is the same as having utf-8 */
-       encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
-
-       strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */
-       strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree));
-
-       /*
-        * 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.
-        */
-       while (parents) {
-               struct commit_list *next = parents->next;
-               strbuf_addf(&buffer, "parent %s\n",
-                       sha1_to_hex(parents->item->object.sha1));
-               free(parents);
-               parents = next;
-       }
-
-       /* Person/date information */
-       if (!author)
-               author = git_author_info(IDENT_ERROR_ON_NO_NAME);
-       strbuf_addf(&buffer, "author %s\n", author);
-       strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME));
-       if (!encoding_is_utf8)
-               strbuf_addf(&buffer, "encoding %s\n", git_commit_encoding);
-       strbuf_addch(&buffer, '\n');
-
-       /* And add the comment */
-       strbuf_addstr(&buffer, msg);
-
-       /* And check the encoding */
-       if (encoding_is_utf8 && !is_utf8(buffer.buf))
-               fprintf(stderr, commit_utf8_warn);
-
-       result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
-       strbuf_release(&buffer);
-       return result;
-}
-
-int cmd_commit_tree(int argc, const char **argv, const char *prefix)
-{
-       int i;
-       struct commit_list *parents = NULL;
-       unsigned char tree_sha1[20];
-       unsigned char commit_sha1[20];
-       struct strbuf buffer = STRBUF_INIT;
-
-       git_config(git_default_config, NULL);
-
-       if (argc < 2)
-               usage(commit_tree_usage);
-       if (get_sha1(argv[1], tree_sha1))
-               die("Not a valid object name %s", argv[1]);
-
-       for (i = 2; i < argc; i += 2) {
-               unsigned char sha1[20];
-               const char *a, *b;
-               a = argv[i]; b = argv[i+1];
-               if (!b || strcmp(a, "-p"))
-                       usage(commit_tree_usage);
-
-               if (get_sha1(b, sha1))
-                       die("Not a valid object name %s", b);
-               check_valid(sha1, OBJ_COMMIT);
-               new_parent(lookup_commit(sha1), &parents);
-       }
-
-       if (strbuf_read(&buffer, 0, 0) < 0)
-               die("git commit-tree: read returned %s", strerror(errno));
-
-       if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {
-               printf("%s\n", sha1_to_hex(commit_sha1));
-               return 0;
-       }
-       else
-               return 1;
-}
diff --git a/builtin-commit.c b/builtin-commit.c
deleted file mode 100644 (file)
index 81371b1..0000000
+++ /dev/null
@@ -1,1034 +0,0 @@
-/*
- * Builtin "git commit"
- *
- * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>
- * Based on git-commit.sh by Junio C Hamano and Linus Torvalds
- */
-
-#include "cache.h"
-#include "cache-tree.h"
-#include "color.h"
-#include "dir.h"
-#include "builtin.h"
-#include "diff.h"
-#include "diffcore.h"
-#include "commit.h"
-#include "revision.h"
-#include "wt-status.h"
-#include "run-command.h"
-#include "refs.h"
-#include "log-tree.h"
-#include "strbuf.h"
-#include "utf8.h"
-#include "parse-options.h"
-#include "string-list.h"
-#include "rerere.h"
-#include "unpack-trees.h"
-
-static const char * const builtin_commit_usage[] = {
-       "git commit [options] [--] <filepattern>...",
-       NULL
-};
-
-static const char * const builtin_status_usage[] = {
-       "git status [options] [--] <filepattern>...",
-       NULL
-};
-
-static unsigned char head_sha1[20], merge_head_sha1[20];
-static char *use_message_buffer;
-static const char commit_editmsg[] = "COMMIT_EDITMSG";
-static struct lock_file index_lock; /* real index */
-static struct lock_file false_lock; /* used only for partial commits */
-static enum {
-       COMMIT_AS_IS = 1,
-       COMMIT_NORMAL,
-       COMMIT_PARTIAL,
-} commit_style;
-
-static const char *logfile, *force_author;
-static const char *template_file;
-static char *edit_message, *use_message;
-static char *author_name, *author_email, *author_date;
-static int all, edit_flag, also, interactive, only, amend, signoff;
-static int quiet, verbose, no_verify, allow_empty;
-static char *untracked_files_arg;
-/*
- * The default commit message cleanup mode will remove the lines
- * beginning with # (shell comments) and leading and trailing
- * whitespaces (empty lines or containing only whitespaces)
- * if editor is used, and only the whitespaces if the message
- * is specified explicitly.
- */
-static enum {
-       CLEANUP_SPACE,
-       CLEANUP_NONE,
-       CLEANUP_ALL,
-} cleanup_mode;
-static char *cleanup_arg;
-
-static int use_editor = 1, initial_commit, in_merge;
-static const char *only_include_assumed;
-static struct strbuf message;
-
-static int opt_parse_m(const struct option *opt, const char *arg, int unset)
-{
-       struct strbuf *buf = opt->value;
-       if (unset)
-               strbuf_setlen(buf, 0);
-       else {
-               strbuf_addstr(buf, arg);
-               strbuf_addstr(buf, "\n\n");
-       }
-       return 0;
-}
-
-static struct option builtin_commit_options[] = {
-       OPT__QUIET(&quiet),
-       OPT__VERBOSE(&verbose),
-       OPT_GROUP("Commit message options"),
-
-       OPT_STRING('F', "file", &logfile, "FILE", "read log from file"),
-       OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"),
-       OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m),
-       OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit "),
-       OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"),
-       OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
-       OPT_STRING('t', "template", &template_file, "FILE", "use specified template file"),
-       OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
-
-       OPT_GROUP("Commit contents options"),
-       OPT_BOOLEAN('a', "all", &all, "commit all changed files"),
-       OPT_BOOLEAN('i', "include", &also, "add specified files to index for commit"),
-       OPT_BOOLEAN(0, "interactive", &interactive, "interactively add files"),
-       OPT_BOOLEAN('o', "only", &only, "commit only specified files"),
-       OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"),
-       OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
-       { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
-       OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
-       OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
-
-       OPT_END()
-};
-
-static void rollback_index_files(void)
-{
-       switch (commit_style) {
-       case COMMIT_AS_IS:
-               break; /* nothing to do */
-       case COMMIT_NORMAL:
-               rollback_lock_file(&index_lock);
-               break;
-       case COMMIT_PARTIAL:
-               rollback_lock_file(&index_lock);
-               rollback_lock_file(&false_lock);
-               break;
-       }
-}
-
-static int commit_index_files(void)
-{
-       int err = 0;
-
-       switch (commit_style) {
-       case COMMIT_AS_IS:
-               break; /* nothing to do */
-       case COMMIT_NORMAL:
-               err = commit_lock_file(&index_lock);
-               break;
-       case COMMIT_PARTIAL:
-               err = commit_lock_file(&index_lock);
-               rollback_lock_file(&false_lock);
-               break;
-       }
-
-       return err;
-}
-
-/*
- * Take a union of paths in the index and the named tree (typically, "HEAD"),
- * and return the paths that match the given pattern in list.
- */
-static int list_paths(struct string_list *list, const char *with_tree,
-                     const char *prefix, const char **pattern)
-{
-       int i;
-       char *m;
-
-       for (i = 0; pattern[i]; i++)
-               ;
-       m = xcalloc(1, i);
-
-       if (with_tree)
-               overlay_tree_on_cache(with_tree, prefix);
-
-       for (i = 0; i < active_nr; i++) {
-               struct cache_entry *ce = active_cache[i];
-               if (ce->ce_flags & CE_UPDATE)
-                       continue;
-               if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m))
-                       continue;
-               string_list_insert(ce->name, list);
-       }
-
-       return report_path_error(m, pattern, prefix ? strlen(prefix) : 0);
-}
-
-static void add_remove_files(struct string_list *list)
-{
-       int i;
-       for (i = 0; i < list->nr; i++) {
-               struct stat st;
-               struct string_list_item *p = &(list->items[i]);
-
-               if (!lstat(p->string, &st)) {
-                       if (add_to_cache(p->string, &st, 0))
-                               die("updating files failed");
-               } else
-                       remove_file_from_cache(p->string);
-       }
-}
-
-static void create_base_index(void)
-{
-       struct tree *tree;
-       struct unpack_trees_options opts;
-       struct tree_desc t;
-
-       if (initial_commit) {
-               discard_cache();
-               return;
-       }
-
-       memset(&opts, 0, sizeof(opts));
-       opts.head_idx = 1;
-       opts.index_only = 1;
-       opts.merge = 1;
-       opts.src_index = &the_index;
-       opts.dst_index = &the_index;
-
-       opts.fn = oneway_merge;
-       tree = parse_tree_indirect(head_sha1);
-       if (!tree)
-               die("failed to unpack HEAD tree object");
-       parse_tree(tree);
-       init_tree_desc(&t, tree->buffer, tree->size);
-       if (unpack_trees(1, &t, &opts))
-               exit(128); /* We've already reported the error, finish dying */
-}
-
-static char *prepare_index(int argc, const char **argv, const char *prefix)
-{
-       int fd;
-       struct string_list partial;
-       const char **pathspec = NULL;
-
-       if (interactive) {
-               if (interactive_add(argc, argv, prefix) != 0)
-                       die("interactive add failed");
-               if (read_cache_preload(NULL) < 0)
-                       die("index file corrupt");
-               commit_style = COMMIT_AS_IS;
-               return get_index_file();
-       }
-
-       if (*argv)
-               pathspec = get_pathspec(prefix, argv);
-
-       if (read_cache_preload(pathspec) < 0)
-               die("index file corrupt");
-
-       /*
-        * Non partial, non as-is commit.
-        *
-        * (1) get the real index;
-        * (2) update the_index as necessary;
-        * (3) write the_index out to the real index (still locked);
-        * (4) return the name of the locked index file.
-        *
-        * The caller should run hooks on the locked real index, and
-        * (A) if all goes well, commit the real index;
-        * (B) on failure, rollback the real index.
-        */
-       if (all || (also && pathspec && *pathspec)) {
-               int fd = hold_locked_index(&index_lock, 1);
-               add_files_to_cache(also ? prefix : NULL, pathspec, 0);
-               refresh_cache(REFRESH_QUIET);
-               if (write_cache(fd, active_cache, active_nr) ||
-                   close_lock_file(&index_lock))
-                       die("unable to write new_index file");
-               commit_style = COMMIT_NORMAL;
-               return index_lock.filename;
-       }
-
-       /*
-        * As-is commit.
-        *
-        * (1) return the name of the real index file.
-        *
-        * The caller should run hooks on the real index, and run
-        * hooks on the real index, and create commit from the_index.
-        * We still need to refresh the index here.
-        */
-       if (!pathspec || !*pathspec) {
-               fd = hold_locked_index(&index_lock, 1);
-               refresh_cache(REFRESH_QUIET);
-               if (write_cache(fd, active_cache, active_nr) ||
-                   commit_locked_index(&index_lock))
-                       die("unable to write new_index file");
-               commit_style = COMMIT_AS_IS;
-               return get_index_file();
-       }
-
-       /*
-        * A partial commit.
-        *
-        * (0) find the set of affected paths;
-        * (1) get lock on the real index file;
-        * (2) update the_index with the given paths;
-        * (3) write the_index out to the real index (still locked);
-        * (4) get lock on the false index file;
-        * (5) reset the_index from HEAD;
-        * (6) update the_index the same way as (2);
-        * (7) write the_index out to the false index file;
-        * (8) return the name of the false index file (still locked);
-        *
-        * The caller should run hooks on the locked false index, and
-        * create commit from it.  Then
-        * (A) if all goes well, commit the real index;
-        * (B) on failure, rollback the real index;
-        * In either case, rollback the false index.
-        */
-       commit_style = COMMIT_PARTIAL;
-
-       if (file_exists(git_path("MERGE_HEAD")))
-               die("cannot do a partial commit during a merge.");
-
-       memset(&partial, 0, sizeof(partial));
-       partial.strdup_strings = 1;
-       if (list_paths(&partial, initial_commit ? NULL : "HEAD", prefix, pathspec))
-               exit(1);
-
-       discard_cache();
-       if (read_cache() < 0)
-               die("cannot read the index");
-
-       fd = hold_locked_index(&index_lock, 1);
-       add_remove_files(&partial);
-       refresh_cache(REFRESH_QUIET);
-       if (write_cache(fd, active_cache, active_nr) ||
-           close_lock_file(&index_lock))
-               die("unable to write new_index file");
-
-       fd = hold_lock_file_for_update(&false_lock,
-                                      git_path("next-index-%"PRIuMAX,
-                                               (uintmax_t) getpid()),
-                                      LOCK_DIE_ON_ERROR);
-
-       create_base_index();
-       add_remove_files(&partial);
-       refresh_cache(REFRESH_QUIET);
-
-       if (write_cache(fd, active_cache, active_nr) ||
-           close_lock_file(&false_lock))
-               die("unable to write temporary index file");
-
-       discard_cache();
-       read_cache_from(false_lock.filename);
-
-       return false_lock.filename;
-}
-
-static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn)
-{
-       struct wt_status s;
-
-       wt_status_prepare(&s);
-       if (wt_status_relative_paths)
-               s.prefix = prefix;
-
-       if (amend) {
-               s.amend = 1;
-               s.reference = "HEAD^1";
-       }
-       s.verbose = verbose;
-       s.untracked = (show_untracked_files == SHOW_ALL_UNTRACKED_FILES);
-       s.index_file = index_file;
-       s.fp = fp;
-       s.nowarn = nowarn;
-
-       wt_status_print(&s);
-
-       return s.commitable;
-}
-
-static int is_a_merge(const unsigned char *sha1)
-{
-       struct commit *commit = lookup_commit(sha1);
-       if (!commit || parse_commit(commit))
-               die("could not parse HEAD commit");
-       return !!(commit->parents && commit->parents->next);
-}
-
-static const char sign_off_header[] = "Signed-off-by: ";
-
-static void determine_author_info(void)
-{
-       char *name, *email, *date;
-
-       name = getenv("GIT_AUTHOR_NAME");
-       email = getenv("GIT_AUTHOR_EMAIL");
-       date = getenv("GIT_AUTHOR_DATE");
-
-       if (use_message) {
-               const char *a, *lb, *rb, *eol;
-
-               a = strstr(use_message_buffer, "\nauthor ");
-               if (!a)
-                       die("invalid commit: %s", use_message);
-
-               lb = strstr(a + 8, " <");
-               rb = strstr(a + 8, "> ");
-               eol = strchr(a + 8, '\n');
-               if (!lb || !rb || !eol)
-                       die("invalid commit: %s", use_message);
-
-               name = xstrndup(a + 8, lb - (a + 8));
-               email = xstrndup(lb + 2, rb - (lb + 2));
-               date = xstrndup(rb + 2, eol - (rb + 2));
-       }
-
-       if (force_author) {
-               const char *lb = strstr(force_author, " <");
-               const char *rb = strchr(force_author, '>');
-
-               if (!lb || !rb)
-                       die("malformed --author parameter");
-               name = xstrndup(force_author, lb - force_author);
-               email = xstrndup(lb + 2, rb - (lb + 2));
-       }
-
-       author_name = name;
-       author_email = email;
-       author_date = date;
-}
-
-static int prepare_to_commit(const char *index_file, const char *prefix)
-{
-       struct stat statbuf;
-       int commitable, saved_color_setting;
-       struct strbuf sb = STRBUF_INIT;
-       char *buffer;
-       FILE *fp;
-       const char *hook_arg1 = NULL;
-       const char *hook_arg2 = NULL;
-       int ident_shown = 0;
-
-       if (!no_verify && run_hook(index_file, "pre-commit", NULL))
-               return 0;
-
-       if (message.len) {
-               strbuf_addbuf(&sb, &message);
-               hook_arg1 = "message";
-       } else if (logfile && !strcmp(logfile, "-")) {
-               if (isatty(0))
-                       fprintf(stderr, "(reading log message from standard input)\n");
-               if (strbuf_read(&sb, 0, 0) < 0)
-                       die("could not read log from standard input");
-               hook_arg1 = "message";
-       } else if (logfile) {
-               if (strbuf_read_file(&sb, logfile, 0) < 0)
-                       die("could not read log file '%s': %s",
-                           logfile, strerror(errno));
-               hook_arg1 = "message";
-       } else if (use_message) {
-               buffer = strstr(use_message_buffer, "\n\n");
-               if (!buffer || buffer[2] == '\0')
-                       die("commit has empty message");
-               strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
-               hook_arg1 = "commit";
-               hook_arg2 = use_message;
-       } else if (!stat(git_path("MERGE_MSG"), &statbuf)) {
-               if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0)
-                       die("could not read MERGE_MSG: %s", strerror(errno));
-               hook_arg1 = "merge";
-       } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) {
-               if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0)
-                       die("could not read SQUASH_MSG: %s", strerror(errno));
-               hook_arg1 = "squash";
-       } else if (template_file && !stat(template_file, &statbuf)) {
-               if (strbuf_read_file(&sb, template_file, 0) < 0)
-                       die("could not read %s: %s",
-                           template_file, strerror(errno));
-               hook_arg1 = "template";
-       }
-
-       /*
-        * This final case does not modify the template message,
-        * it just sets the argument to the prepare-commit-msg hook.
-        */
-       else if (in_merge)
-               hook_arg1 = "merge";
-
-       fp = fopen(git_path(commit_editmsg), "w");
-       if (fp == NULL)
-               die("could not open %s: %s",
-                   git_path(commit_editmsg), strerror(errno));
-
-       if (cleanup_mode != CLEANUP_NONE)
-               stripspace(&sb, 0);
-
-       if (signoff) {
-               struct strbuf sob = STRBUF_INIT;
-               int i;
-
-               strbuf_addstr(&sob, sign_off_header);
-               strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"),
-                                            getenv("GIT_COMMITTER_EMAIL")));
-               strbuf_addch(&sob, '\n');
-               for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--)
-                       ; /* do nothing */
-               if (prefixcmp(sb.buf + i, sob.buf)) {
-                       if (prefixcmp(sb.buf + i, sign_off_header))
-                               strbuf_addch(&sb, '\n');
-                       strbuf_addbuf(&sb, &sob);
-               }
-               strbuf_release(&sob);
-       }
-
-       if (fwrite(sb.buf, 1, sb.len, fp) < sb.len)
-               die("could not write commit template: %s", strerror(errno));
-
-       strbuf_release(&sb);
-
-       determine_author_info();
-
-       /* This checks if committer ident is explicitly given */
-       git_committer_info(0);
-       if (use_editor) {
-               char *author_ident;
-               const char *committer_ident;
-
-               if (in_merge)
-                       fprintf(fp,
-                               "#\n"
-                               "# It looks like you may be committing a MERGE.\n"
-                               "# If this is not correct, please remove the file\n"
-                               "#      %s\n"
-                               "# and try again.\n"
-                               "#\n",
-                               git_path("MERGE_HEAD"));
-
-               fprintf(fp,
-                       "\n"
-                       "# Please enter the commit message for your changes.");
-               if (cleanup_mode == CLEANUP_ALL)
-                       fprintf(fp,
-                               " Lines starting\n"
-                               "# with '#' will be ignored, and an empty"
-                               " message aborts the commit.\n");
-               else /* CLEANUP_SPACE, that is. */
-                       fprintf(fp,
-                               " Lines starting\n"
-                               "# with '#' will be kept; you may remove them"
-                               " yourself if you want to.\n"
-                               "# An empty message aborts the commit.\n");
-               if (only_include_assumed)
-                       fprintf(fp, "# %s\n", only_include_assumed);
-
-               author_ident = xstrdup(fmt_name(author_name, author_email));
-               committer_ident = fmt_name(getenv("GIT_COMMITTER_NAME"),
-                                          getenv("GIT_COMMITTER_EMAIL"));
-               if (strcmp(author_ident, committer_ident))
-                       fprintf(fp,
-                               "%s"
-                               "# Author:    %s\n",
-                               ident_shown++ ? "" : "#\n",
-                               author_ident);
-               free(author_ident);
-
-               if (!user_ident_explicitly_given)
-                       fprintf(fp,
-                               "%s"
-                               "# Committer: %s\n",
-                               ident_shown++ ? "" : "#\n",
-                               committer_ident);
-
-               if (ident_shown)
-                       fprintf(fp, "#\n");
-
-               saved_color_setting = wt_status_use_color;
-               wt_status_use_color = 0;
-               commitable = run_status(fp, index_file, prefix, 1);
-               wt_status_use_color = saved_color_setting;
-       } else {
-               unsigned char sha1[20];
-               const char *parent = "HEAD";
-
-               if (!active_nr && read_cache() < 0)
-                       die("Cannot read index");
-
-               if (amend)
-                       parent = "HEAD^1";
-
-               if (get_sha1(parent, sha1))
-                       commitable = !!active_nr;
-               else
-                       commitable = index_differs_from(parent, 0);
-       }
-
-       fclose(fp);
-
-       if (!commitable && !in_merge && !allow_empty &&
-           !(amend && is_a_merge(head_sha1))) {
-               run_status(stdout, index_file, prefix, 0);
-               return 0;
-       }
-
-       /*
-        * Re-read the index as pre-commit hook could have updated it,
-        * and write it out as a tree.  We must do this before we invoke
-        * the editor and after we invoke run_status above.
-        */
-       discard_cache();
-       read_cache_from(index_file);
-       if (!active_cache_tree)
-               active_cache_tree = cache_tree();
-       if (cache_tree_update(active_cache_tree,
-                             active_cache, active_nr, 0, 0) < 0) {
-               error("Error building trees");
-               return 0;
-       }
-
-       if (run_hook(index_file, "prepare-commit-msg",
-                    git_path(commit_editmsg), hook_arg1, hook_arg2, NULL))
-               return 0;
-
-       if (use_editor) {
-               char index[PATH_MAX];
-               const char *env[2] = { index, NULL };
-               snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
-               if (launch_editor(git_path(commit_editmsg), NULL, env)) {
-                       fprintf(stderr,
-                       "Please supply the message using either -m or -F option.\n");
-                       exit(1);
-               }
-       }
-
-       if (!no_verify &&
-           run_hook(index_file, "commit-msg", git_path(commit_editmsg), NULL)) {
-               return 0;
-       }
-
-       return 1;
-}
-
-/*
- * Find out if the message in the strbuf contains only whitespace and
- * Signed-off-by lines.
- */
-static int message_is_empty(struct strbuf *sb)
-{
-       struct strbuf tmpl = STRBUF_INIT;
-       const char *nl;
-       int eol, i, start = 0;
-
-       if (cleanup_mode == CLEANUP_NONE && sb->len)
-               return 0;
-
-       /* See if the template is just a prefix of the message. */
-       if (template_file && strbuf_read_file(&tmpl, template_file, 0) > 0) {
-               stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
-               if (start + tmpl.len <= sb->len &&
-                   memcmp(tmpl.buf, sb->buf + start, tmpl.len) == 0)
-                       start += tmpl.len;
-       }
-       strbuf_release(&tmpl);
-
-       /* Check if the rest is just whitespace and Signed-of-by's. */
-       for (i = start; i < sb->len; i++) {
-               nl = memchr(sb->buf + i, '\n', sb->len - i);
-               if (nl)
-                       eol = nl - sb->buf;
-               else
-                       eol = sb->len;
-
-               if (strlen(sign_off_header) <= eol - i &&
-                   !prefixcmp(sb->buf + i, sign_off_header)) {
-                       i = eol;
-                       continue;
-               }
-               while (i < eol)
-                       if (!isspace(sb->buf[i++]))
-                               return 0;
-       }
-
-       return 1;
-}
-
-static const char *find_author_by_nickname(const char *name)
-{
-       struct rev_info revs;
-       struct commit *commit;
-       struct strbuf buf = STRBUF_INIT;
-       const char *av[20];
-       int ac = 0;
-
-       init_revisions(&revs, NULL);
-       strbuf_addf(&buf, "--author=%s", name);
-       av[++ac] = "--all";
-       av[++ac] = "-i";
-       av[++ac] = buf.buf;
-       av[++ac] = NULL;
-       setup_revisions(ac, av, &revs, NULL);
-       prepare_revision_walk(&revs);
-       commit = get_revision(&revs);
-       if (commit) {
-               strbuf_release(&buf);
-               format_commit_message(commit, "%an <%ae>", &buf, DATE_NORMAL);
-               return strbuf_detach(&buf, NULL);
-       }
-       die("No existing author found with '%s'", name);
-}
-
-static int parse_and_validate_options(int argc, const char *argv[],
-                                     const char * const usage[],
-                                     const char *prefix)
-{
-       int f = 0;
-
-       argc = parse_options(argc, argv, builtin_commit_options, usage, 0);
-       logfile = parse_options_fix_filename(prefix, logfile);
-       template_file = parse_options_fix_filename(prefix, template_file);
-
-       if (force_author && !strchr(force_author, '>'))
-               force_author = find_author_by_nickname(force_author);
-
-       if (logfile || message.len || use_message)
-               use_editor = 0;
-       if (edit_flag)
-               use_editor = 1;
-       if (!use_editor)
-               setenv("GIT_EDITOR", ":", 1);
-
-       if (get_sha1("HEAD", head_sha1))
-               initial_commit = 1;
-
-       if (!get_sha1("MERGE_HEAD", merge_head_sha1))
-               in_merge = 1;
-
-       /* Sanity check options */
-       if (amend && initial_commit)
-               die("You have nothing to amend.");
-       if (amend && in_merge)
-               die("You are in the middle of a merge -- cannot amend.");
-
-       if (use_message)
-               f++;
-       if (edit_message)
-               f++;
-       if (logfile)
-               f++;
-       if (f > 1)
-               die("Only one of -c/-C/-F can be used.");
-       if (message.len && f > 0)
-               die("Option -m cannot be combined with -c/-C/-F.");
-       if (edit_message)
-               use_message = edit_message;
-       if (amend && !use_message)
-               use_message = "HEAD";
-       if (use_message) {
-               unsigned char sha1[20];
-               static char utf8[] = "UTF-8";
-               const char *out_enc;
-               char *enc, *end;
-               struct commit *commit;
-
-               if (get_sha1(use_message, sha1))
-                       die("could not lookup commit %s", use_message);
-               commit = lookup_commit_reference(sha1);
-               if (!commit || parse_commit(commit))
-                       die("could not parse commit %s", use_message);
-
-               enc = strstr(commit->buffer, "\nencoding");
-               if (enc) {
-                       end = strchr(enc + 10, '\n');
-                       enc = xstrndup(enc + 10, end - (enc + 10));
-               } else {
-                       enc = utf8;
-               }
-               out_enc = git_commit_encoding ? git_commit_encoding : utf8;
-
-               if (strcmp(out_enc, enc))
-                       use_message_buffer =
-                               reencode_string(commit->buffer, out_enc, enc);
-
-               /*
-                * If we failed to reencode the buffer, just copy it
-                * byte for byte so the user can try to fix it up.
-                * This also handles the case where input and output
-                * encodings are identical.
-                */
-               if (use_message_buffer == NULL)
-                       use_message_buffer = xstrdup(commit->buffer);
-               if (enc != utf8)
-                       free(enc);
-       }
-
-       if (!!also + !!only + !!all + !!interactive > 1)
-               die("Only one of --include/--only/--all/--interactive can be used.");
-       if (argc == 0 && (also || (only && !amend)))
-               die("No paths with --include/--only does not make sense.");
-       if (argc == 0 && only && amend)
-               only_include_assumed = "Clever... amending the last one with dirty index.";
-       if (argc > 0 && !also && !only)
-               only_include_assumed = "Explicit paths specified without -i nor -o; assuming --only paths...";
-       if (!cleanup_arg || !strcmp(cleanup_arg, "default"))
-               cleanup_mode = use_editor ? CLEANUP_ALL : CLEANUP_SPACE;
-       else if (!strcmp(cleanup_arg, "verbatim"))
-               cleanup_mode = CLEANUP_NONE;
-       else if (!strcmp(cleanup_arg, "whitespace"))
-               cleanup_mode = CLEANUP_SPACE;
-       else if (!strcmp(cleanup_arg, "strip"))
-               cleanup_mode = CLEANUP_ALL;
-       else
-               die("Invalid cleanup mode %s", cleanup_arg);
-
-       if (!untracked_files_arg)
-               ; /* default already initialized */
-       else if (!strcmp(untracked_files_arg, "no"))
-               show_untracked_files = SHOW_NO_UNTRACKED_FILES;
-       else if (!strcmp(untracked_files_arg, "normal"))
-               show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
-       else if (!strcmp(untracked_files_arg, "all"))
-               show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
-       else
-               die("Invalid untracked files mode '%s'", untracked_files_arg);
-
-       if (all && argc > 0)
-               die("Paths with -a does not make sense.");
-       else if (interactive && argc > 0)
-               die("Paths with --interactive does not make sense.");
-
-       return argc;
-}
-
-int cmd_status(int argc, const char **argv, const char *prefix)
-{
-       const char *index_file;
-       int commitable;
-
-       git_config(git_status_config, NULL);
-
-       if (wt_status_use_color == -1)
-               wt_status_use_color = git_use_color_default;
-
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
-
-       argc = parse_and_validate_options(argc, argv, builtin_status_usage, prefix);
-
-       index_file = prepare_index(argc, argv, prefix);
-
-       commitable = run_status(stdout, index_file, prefix, 0);
-
-       rollback_index_files();
-
-       return commitable ? 0 : 1;
-}
-
-static void print_summary(const char *prefix, const unsigned char *sha1)
-{
-       struct rev_info rev;
-       struct commit *commit;
-       static const char *format = "format:%h] %s";
-       unsigned char junk_sha1[20];
-       const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL);
-
-       commit = lookup_commit(sha1);
-       if (!commit)
-               die("couldn't look up newly created commit");
-       if (!commit || parse_commit(commit))
-               die("could not parse newly created commit");
-
-       init_revisions(&rev, prefix);
-       setup_revisions(0, NULL, &rev, NULL);
-
-       rev.abbrev = 0;
-       rev.diff = 1;
-       rev.diffopt.output_format =
-               DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY;
-
-       rev.verbose_header = 1;
-       rev.show_root_diff = 1;
-       get_commit_format(format, &rev);
-       rev.always_show_header = 0;
-       rev.diffopt.detect_rename = 1;
-       rev.diffopt.rename_limit = 100;
-       rev.diffopt.break_opt = 0;
-       diff_setup_done(&rev.diffopt);
-
-       printf("[%s%s ",
-               !prefixcmp(head, "refs/heads/") ?
-                       head + 11 :
-                       !strcmp(head, "HEAD") ?
-                               "detached HEAD" :
-                               head,
-               initial_commit ? " (root-commit)" : "");
-
-       if (!log_tree_commit(&rev, commit)) {
-               struct strbuf buf = STRBUF_INIT;
-               format_commit_message(commit, format + 7, &buf, DATE_NORMAL);
-               printf("%s\n", buf.buf);
-               strbuf_release(&buf);
-       }
-}
-
-static int git_commit_config(const char *k, const char *v, void *cb)
-{
-       if (!strcmp(k, "commit.template"))
-               return git_config_string(&template_file, k, v);
-
-       return git_status_config(k, v, cb);
-}
-
-int cmd_commit(int argc, const char **argv, const char *prefix)
-{
-       struct strbuf sb = STRBUF_INIT;
-       const char *index_file, *reflog_msg;
-       char *nl, *p;
-       unsigned char commit_sha1[20];
-       struct ref_lock *ref_lock;
-       struct commit_list *parents = NULL, **pptr = &parents;
-       struct stat statbuf;
-       int allow_fast_forward = 1;
-
-       git_config(git_commit_config, NULL);
-
-       if (wt_status_use_color == -1)
-               wt_status_use_color = git_use_color_default;
-
-       argc = parse_and_validate_options(argc, argv, builtin_commit_usage, prefix);
-
-       index_file = prepare_index(argc, argv, prefix);
-
-       /* Set up everything for writing the commit object.  This includes
-          running hooks, writing the trees, and interacting with the user.  */
-       if (!prepare_to_commit(index_file, prefix)) {
-               rollback_index_files();
-               return 1;
-       }
-
-       /* Determine parents */
-       if (initial_commit) {
-               reflog_msg = "commit (initial)";
-       } else if (amend) {
-               struct commit_list *c;
-               struct commit *commit;
-
-               reflog_msg = "commit (amend)";
-               commit = lookup_commit(head_sha1);
-               if (!commit || parse_commit(commit))
-                       die("could not parse HEAD commit");
-
-               for (c = commit->parents; c; c = c->next)
-                       pptr = &commit_list_insert(c->item, pptr)->next;
-       } else if (in_merge) {
-               struct strbuf m = STRBUF_INIT;
-               FILE *fp;
-
-               reflog_msg = "commit (merge)";
-               pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
-               fp = fopen(git_path("MERGE_HEAD"), "r");
-               if (fp == NULL)
-                       die("could not open %s for reading: %s",
-                           git_path("MERGE_HEAD"), strerror(errno));
-               while (strbuf_getline(&m, fp, '\n') != EOF) {
-                       unsigned char sha1[20];
-                       if (get_sha1_hex(m.buf, sha1) < 0)
-                               die("Corrupt MERGE_HEAD file (%s)", m.buf);
-                       pptr = &commit_list_insert(lookup_commit(sha1), pptr)->next;
-               }
-               fclose(fp);
-               strbuf_release(&m);
-               if (!stat(git_path("MERGE_MODE"), &statbuf)) {
-                       if (strbuf_read_file(&sb, git_path("MERGE_MODE"), 0) < 0)
-                               die("could not read MERGE_MODE: %s",
-                                               strerror(errno));
-                       if (!strcmp(sb.buf, "no-ff"))
-                               allow_fast_forward = 0;
-               }
-               if (allow_fast_forward)
-                       parents = reduce_heads(parents);
-       } else {
-               reflog_msg = "commit";
-               pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
-       }
-
-       /* Finally, get the commit message */
-       strbuf_reset(&sb);
-       if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) {
-               rollback_index_files();
-               die("could not read commit message");
-       }
-
-       /* Truncate the message just before the diff, if any. */
-       if (verbose) {
-               p = strstr(sb.buf, "\ndiff --git ");
-               if (p != NULL)
-                       strbuf_setlen(&sb, p - sb.buf + 1);
-       }
-
-       if (cleanup_mode != CLEANUP_NONE)
-               stripspace(&sb, cleanup_mode == CLEANUP_ALL);
-       if (message_is_empty(&sb)) {
-               rollback_index_files();
-               fprintf(stderr, "Aborting commit due to empty commit message.\n");
-               exit(1);
-       }
-
-       if (commit_tree(sb.buf, active_cache_tree->sha1, parents, commit_sha1,
-                       fmt_ident(author_name, author_email, author_date,
-                               IDENT_ERROR_ON_NO_NAME))) {
-               rollback_index_files();
-               die("failed to write commit object");
-       }
-
-       ref_lock = lock_any_ref_for_update("HEAD",
-                                          initial_commit ? NULL : head_sha1,
-                                          0);
-
-       nl = strchr(sb.buf, '\n');
-       if (nl)
-               strbuf_setlen(&sb, nl + 1 - sb.buf);
-       else
-               strbuf_addch(&sb, '\n');
-       strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg));
-       strbuf_insert(&sb, strlen(reflog_msg), ": ", 2);
-
-       if (!ref_lock) {
-               rollback_index_files();
-               die("cannot lock HEAD ref");
-       }
-       if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0) {
-               rollback_index_files();
-               die("cannot update HEAD ref");
-       }
-
-       unlink(git_path("MERGE_HEAD"));
-       unlink(git_path("MERGE_MSG"));
-       unlink(git_path("MERGE_MODE"));
-       unlink(git_path("SQUASH_MSG"));
-
-       if (commit_index_files())
-               die ("Repository has been updated, but unable to write\n"
-                    "new_index file. Check that disk is not full or quota is\n"
-                    "not exceeded, and then \"git reset HEAD\" to recover.");
-
-       rerere();
-       run_hook(get_index_file(), "post-commit", NULL);
-       if (!quiet)
-               print_summary(prefix, commit_sha1);
-
-       return 0;
-}
diff --git a/builtin-config.c b/builtin-config.c
deleted file mode 100644 (file)
index a81bc8b..0000000
+++ /dev/null
@@ -1,477 +0,0 @@
-#include "builtin.h"
-#include "cache.h"
-#include "color.h"
-#include "parse-options.h"
-
-static const char *const builtin_config_usage[] = {
-       "git config [options]",
-       NULL
-};
-
-static char *key;
-static regex_t *key_regexp;
-static regex_t *regexp;
-static int show_keys;
-static int use_key_regexp;
-static int do_all;
-static int do_not_match;
-static int seen;
-static char delim = '=';
-static char key_delim = ' ';
-static char term = '\n';
-
-static int use_global_config, use_system_config;
-static const char *given_config_file;
-static int actions, types;
-static const char *get_color_slot, *get_colorbool_slot;
-static int end_null;
-
-#define ACTION_GET (1<<0)
-#define ACTION_GET_ALL (1<<1)
-#define ACTION_GET_REGEXP (1<<2)
-#define ACTION_REPLACE_ALL (1<<3)
-#define ACTION_ADD (1<<4)
-#define ACTION_UNSET (1<<5)
-#define ACTION_UNSET_ALL (1<<6)
-#define ACTION_RENAME_SECTION (1<<7)
-#define ACTION_REMOVE_SECTION (1<<8)
-#define ACTION_LIST (1<<9)
-#define ACTION_EDIT (1<<10)
-#define ACTION_SET (1<<11)
-#define ACTION_SET_ALL (1<<12)
-#define ACTION_GET_COLOR (1<<13)
-#define ACTION_GET_COLORBOOL (1<<14)
-
-#define TYPE_BOOL (1<<0)
-#define TYPE_INT (1<<1)
-#define TYPE_BOOL_OR_INT (1<<2)
-
-static struct option builtin_config_options[] = {
-       OPT_GROUP("Config file location"),
-       OPT_BOOLEAN(0, "global", &use_global_config, "use global config file"),
-       OPT_BOOLEAN(0, "system", &use_system_config, "use system config file"),
-       OPT_STRING('f', "file", &given_config_file, "FILE", "use given config file"),
-       OPT_GROUP("Action"),
-       OPT_BIT(0, "get", &actions, "get value: name [value-regex]", ACTION_GET),
-       OPT_BIT(0, "get-all", &actions, "get all values: key [value-regex]", ACTION_GET_ALL),
-       OPT_BIT(0, "get-regexp", &actions, "get values for regexp: name-regex [value-regex]", ACTION_GET_REGEXP),
-       OPT_BIT(0, "replace-all", &actions, "replace all matching variables: name value [value_regex]", ACTION_REPLACE_ALL),
-       OPT_BIT(0, "add", &actions, "adds a new variable: name value", ACTION_ADD),
-       OPT_BIT(0, "unset", &actions, "removes a variable: name [value-regex]", ACTION_UNSET),
-       OPT_BIT(0, "unset-all", &actions, "removes all matches: name [value-regex]", ACTION_UNSET_ALL),
-       OPT_BIT(0, "rename-section", &actions, "rename section: old-name new-name", ACTION_RENAME_SECTION),
-       OPT_BIT(0, "remove-section", &actions, "remove a section: name", ACTION_REMOVE_SECTION),
-       OPT_BIT('l', "list", &actions, "list all", ACTION_LIST),
-       OPT_BIT('e', "edit", &actions, "opens an editor", ACTION_EDIT),
-       OPT_STRING(0, "get-color", &get_color_slot, "slot", "find the color configured: [default]"),
-       OPT_STRING(0, "get-colorbool", &get_colorbool_slot, "slot", "find the color setting: [stdout-is-tty]"),
-       OPT_GROUP("Type"),
-       OPT_BIT(0, "bool", &types, "value is \"true\" or \"false\"", TYPE_BOOL),
-       OPT_BIT(0, "int", &types, "value is decimal number", TYPE_INT),
-       OPT_BIT(0, "bool-or-int", &types, "value is --bool or --int", TYPE_BOOL_OR_INT),
-       OPT_GROUP("Other"),
-       OPT_BOOLEAN('z', "null", &end_null, "terminate values with NUL byte"),
-       OPT_END(),
-};
-
-static void check_argc(int argc, int min, int max) {
-       if (argc >= min && argc <= max)
-               return;
-       error("wrong number of arguments");
-       usage_with_options(builtin_config_usage, builtin_config_options);
-}
-
-static int show_all_config(const char *key_, const char *value_, void *cb)
-{
-       if (value_)
-               printf("%s%c%s%c", key_, delim, value_, term);
-       else
-               printf("%s%c", key_, term);
-       return 0;
-}
-
-static int show_config(const char *key_, const char *value_, void *cb)
-{
-       char value[256];
-       const char *vptr = value;
-       int dup_error = 0;
-
-       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_?value_:""), 0, NULL, 0)))
-               return 0;
-
-       if (show_keys) {
-               if (value_)
-                       printf("%s%c", key_, key_delim);
-               else
-                       printf("%s", key_);
-       }
-       if (seen && !do_all)
-               dup_error = 1;
-       if (types == TYPE_INT)
-               sprintf(value, "%d", git_config_int(key_, value_?value_:""));
-       else if (types == TYPE_BOOL)
-               vptr = git_config_bool(key_, value_) ? "true" : "false";
-       else if (types == TYPE_BOOL_OR_INT) {
-               int is_bool, v;
-               v = git_config_bool_or_int(key_, value_, &is_bool);
-               if (is_bool)
-                       vptr = v ? "true" : "false";
-               else
-                       sprintf(value, "%d", v);
-       }
-       else
-               vptr = value_?value_:"";
-       seen++;
-       if (dup_error) {
-               error("More than one value for the key %s: %s",
-                               key_, vptr);
-       }
-       else
-               printf("%s%c", vptr, term);
-
-       return 0;
-}
-
-static int get_value(const char *key_, const char *regex_)
-{
-       int ret = -1;
-       char *tl;
-       char *global = NULL, *repo_config = NULL;
-       const char *system_wide = NULL, *local;
-
-       local = config_exclusive_filename;
-       if (!local) {
-               const char *home = getenv("HOME");
-               local = repo_config = git_pathdup("config");
-               if (git_config_global() && home)
-                       global = xstrdup(mkpath("%s/.gitconfig", home));
-               if (git_config_system())
-                       system_wide = git_etc_gitconfig();
-       }
-
-       key = xstrdup(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*)xmalloc(sizeof(regex_t));
-               if (regcomp(key_regexp, key, REG_EXTENDED)) {
-                       fprintf(stderr, "Invalid key pattern: %s\n", key_);
-                       goto free_strings;
-               }
-       }
-
-       if (regex_) {
-               if (regex_[0] == '!') {
-                       do_not_match = 1;
-                       regex_++;
-               }
-
-               regexp = (regex_t*)xmalloc(sizeof(regex_t));
-               if (regcomp(regexp, regex_, REG_EXTENDED)) {
-                       fprintf(stderr, "Invalid pattern: %s\n", regex_);
-                       goto free_strings;
-               }
-       }
-
-       if (do_all && system_wide)
-               git_config_from_file(show_config, system_wide, NULL);
-       if (do_all && global)
-               git_config_from_file(show_config, global, NULL);
-       git_config_from_file(show_config, local, NULL);
-       if (!do_all && !seen && global)
-               git_config_from_file(show_config, global, NULL);
-       if (!do_all && !seen && system_wide)
-               git_config_from_file(show_config, system_wide, NULL);
-
-       free(key);
-       if (regexp) {
-               regfree(regexp);
-               free(regexp);
-       }
-
-       if (do_all)
-               ret = !seen;
-       else
-               ret = (seen == 1) ? 0 : seen > 1 ? 2 : 1;
-
-free_strings:
-       free(repo_config);
-       free(global);
-       return ret;
-}
-
-static char *normalize_value(const char *key, const char *value)
-{
-       char *normalized;
-
-       if (!value)
-               return NULL;
-
-       if (types == 0)
-               normalized = xstrdup(value);
-       else {
-               normalized = xmalloc(64);
-               if (types == TYPE_INT) {
-                       int v = git_config_int(key, value);
-                       sprintf(normalized, "%d", v);
-               }
-               else if (types == TYPE_BOOL)
-                       sprintf(normalized, "%s",
-                               git_config_bool(key, value) ? "true" : "false");
-               else if (types == TYPE_BOOL_OR_INT) {
-                       int is_bool, v;
-                       v = git_config_bool_or_int(key, value, &is_bool);
-                       if (!is_bool)
-                               sprintf(normalized, "%d", v);
-                       else
-                               sprintf(normalized, "%s", v ? "true" : "false");
-               }
-       }
-
-       return normalized;
-}
-
-static int get_color_found;
-static const char *get_color_slot;
-static const char *get_colorbool_slot;
-static char parsed_color[COLOR_MAXLEN];
-
-static int git_get_color_config(const char *var, const char *value, void *cb)
-{
-       if (!strcmp(var, get_color_slot)) {
-               if (!value)
-                       config_error_nonbool(var);
-               color_parse(value, var, parsed_color);
-               get_color_found = 1;
-       }
-       return 0;
-}
-
-static void get_color(const char *def_color)
-{
-       get_color_found = 0;
-       parsed_color[0] = '\0';
-       git_config(git_get_color_config, NULL);
-
-       if (!get_color_found && def_color)
-               color_parse(def_color, "command line", parsed_color);
-
-       fputs(parsed_color, stdout);
-}
-
-static int stdout_is_tty;
-static int get_colorbool_found;
-static int get_diff_color_found;
-static int git_get_colorbool_config(const char *var, const char *value,
-               void *cb)
-{
-       if (!strcmp(var, get_colorbool_slot)) {
-               get_colorbool_found =
-                       git_config_colorbool(var, value, stdout_is_tty);
-       }
-       if (!strcmp(var, "diff.color")) {
-               get_diff_color_found =
-                       git_config_colorbool(var, value, stdout_is_tty);
-       }
-       if (!strcmp(var, "color.ui")) {
-               git_use_color_default = git_config_colorbool(var, value, stdout_is_tty);
-               return 0;
-       }
-       return 0;
-}
-
-static int get_colorbool(int print)
-{
-       get_colorbool_found = -1;
-       get_diff_color_found = -1;
-       git_config(git_get_colorbool_config, NULL);
-
-       if (get_colorbool_found < 0) {
-               if (!strcmp(get_colorbool_slot, "color.diff"))
-                       get_colorbool_found = get_diff_color_found;
-               if (get_colorbool_found < 0)
-                       get_colorbool_found = git_use_color_default;
-       }
-
-       if (print) {
-               printf("%s\n", get_colorbool_found ? "true" : "false");
-               return 0;
-       } else
-               return get_colorbool_found ? 0 : 1;
-}
-
-int cmd_config(int argc, const char **argv, const char *unused_prefix)
-{
-       int nongit;
-       char *value;
-       const char *prefix = setup_git_directory_gently(&nongit);
-
-       config_exclusive_filename = getenv(CONFIG_ENVIRONMENT);
-
-       argc = parse_options(argc, argv, builtin_config_options, builtin_config_usage,
-                            PARSE_OPT_STOP_AT_NON_OPTION);
-
-       if (use_global_config + use_system_config + !!given_config_file > 1) {
-               error("only one config file at a time.");
-               usage_with_options(builtin_config_usage, builtin_config_options);
-       }
-
-       if (use_global_config) {
-               char *home = getenv("HOME");
-               if (home) {
-                       char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
-                       config_exclusive_filename = user_config;
-               } else {
-                       die("$HOME not set");
-               }
-       }
-       else if (use_system_config)
-               config_exclusive_filename = git_etc_gitconfig();
-       else if (given_config_file) {
-               if (!is_absolute_path(given_config_file) && prefix)
-                       config_exclusive_filename = prefix_filename(prefix,
-                                                                   strlen(prefix),
-                                                                   argv[2]);
-               else
-                       config_exclusive_filename = given_config_file;
-       }
-
-       if (end_null) {
-               term = '\0';
-               delim = '\n';
-               key_delim = '\n';
-       }
-
-       if (HAS_MULTI_BITS(types)) {
-               error("only one type at a time.");
-               usage_with_options(builtin_config_usage, builtin_config_options);
-       }
-
-       if (get_color_slot)
-           actions |= ACTION_GET_COLOR;
-       if (get_colorbool_slot)
-           actions |= ACTION_GET_COLORBOOL;
-
-       if ((get_color_slot || get_colorbool_slot) && types) {
-               error("--get-color and variable type are incoherent");
-               usage_with_options(builtin_config_usage, builtin_config_options);
-       }
-
-       if (HAS_MULTI_BITS(actions)) {
-               error("only one action at a time.");
-               usage_with_options(builtin_config_usage, builtin_config_options);
-       }
-       if (actions == 0)
-               switch (argc) {
-               case 1: actions = ACTION_GET; break;
-               case 2: actions = ACTION_SET; break;
-               case 3: actions = ACTION_SET_ALL; break;
-               default:
-                       usage_with_options(builtin_config_usage, builtin_config_options);
-               }
-
-       if (actions == ACTION_LIST) {
-               check_argc(argc, 0, 0);
-               if (git_config(show_all_config, NULL) < 0) {
-                       if (config_exclusive_filename)
-                               die("unable to read config file %s: %s",
-                                   config_exclusive_filename, strerror(errno));
-                       else
-                               die("error processing config file(s)");
-               }
-       }
-       else if (actions == ACTION_EDIT) {
-               check_argc(argc, 0, 0);
-               if (!config_exclusive_filename && nongit)
-                       die("not in a git directory");
-               git_config(git_default_config, NULL);
-               launch_editor(config_exclusive_filename ?
-                             config_exclusive_filename : git_path("config"),
-                             NULL, NULL);
-       }
-       else if (actions == ACTION_SET) {
-               check_argc(argc, 2, 2);
-               value = normalize_value(argv[0], argv[1]);
-               return git_config_set(argv[0], value);
-       }
-       else if (actions == ACTION_SET_ALL) {
-               check_argc(argc, 2, 3);
-               value = normalize_value(argv[0], argv[1]);
-               return git_config_set_multivar(argv[0], value, argv[2], 0);
-       }
-       else if (actions == ACTION_ADD) {
-               check_argc(argc, 2, 2);
-               value = normalize_value(argv[0], argv[1]);
-               return git_config_set_multivar(argv[0], value, "^$", 0);
-       }
-       else if (actions == ACTION_REPLACE_ALL) {
-               check_argc(argc, 2, 3);
-               value = normalize_value(argv[0], argv[1]);
-               return git_config_set_multivar(argv[0], value, argv[2], 1);
-       }
-       else if (actions == ACTION_GET) {
-               check_argc(argc, 1, 2);
-               return get_value(argv[0], argv[1]);
-       }
-       else if (actions == ACTION_GET_ALL) {
-               do_all = 1;
-               check_argc(argc, 1, 2);
-               return get_value(argv[0], argv[1]);
-       }
-       else if (actions == ACTION_GET_REGEXP) {
-               show_keys = 1;
-               use_key_regexp = 1;
-               do_all = 1;
-               check_argc(argc, 1, 2);
-               return get_value(argv[0], argv[1]);
-       }
-       else if (actions == ACTION_UNSET) {
-               check_argc(argc, 1, 2);
-               if (argc == 2)
-                       return git_config_set_multivar(argv[0], NULL, argv[1], 0);
-               else
-                       return git_config_set(argv[0], NULL);
-       }
-       else if (actions == ACTION_UNSET_ALL) {
-               check_argc(argc, 1, 2);
-               return git_config_set_multivar(argv[0], NULL, argv[1], 1);
-       }
-       else if (actions == ACTION_RENAME_SECTION) {
-               int ret;
-               check_argc(argc, 2, 2);
-               ret = git_config_rename_section(argv[0], argv[1]);
-               if (ret < 0)
-                       return ret;
-               if (ret == 0)
-                       die("No such section!");
-       }
-       else if (actions == ACTION_REMOVE_SECTION) {
-               int ret;
-               check_argc(argc, 1, 1);
-               ret = git_config_rename_section(argv[0], NULL);
-               if (ret < 0)
-                       return ret;
-               if (ret == 0)
-                       die("No such section!");
-       }
-       else if (actions == ACTION_GET_COLOR) {
-               get_color(argv[0]);
-       }
-       else if (actions == ACTION_GET_COLORBOOL) {
-               if (argc == 1)
-                       stdout_is_tty = git_config_bool("command line", argv[0]);
-               else if (argc == 0)
-                       stdout_is_tty = isatty(1);
-               return get_colorbool(argc != 0);
-       }
-
-       return 0;
-}
diff --git a/builtin-count-objects.c b/builtin-count-objects.c
deleted file mode 100644 (file)
index b814fe5..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Builtin "git count-objects".
- *
- * Copyright (c) 2006 Junio C Hamano
- */
-
-#include "cache.h"
-#include "dir.h"
-#include "builtin.h"
-#include "parse-options.h"
-
-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 (is_dot_or_dotdot(ent->d_name))
-                       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) += xsize_t(on_disk_bytes(st));
-               }
-               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)++;
-       }
-}
-
-static char const * const count_objects_usage[] = {
-       "git count-objects [-v]",
-       NULL
-};
-
-int cmd_count_objects(int argc, const char **argv, const char *prefix)
-{
-       int i, 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;
-       struct option opts[] = {
-               OPT__VERBOSE(&verbose),
-               OPT_END(),
-       };
-
-       argc = parse_options(argc, argv, opts, count_objects_usage, 0);
-       /* we do not take arguments other than flags for now */
-       if (argc)
-               usage_with_options(count_objects_usage, opts);
-       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;
-               unsigned long num_pack = 0;
-               unsigned long size_pack = 0;
-               if (!packed_git)
-                       prepare_packed_git();
-               for (p = packed_git; p; p = p->next) {
-                       if (!p->pack_local)
-                               continue;
-                       if (open_pack_index(p))
-                               continue;
-                       packed += p->num_objects;
-                       size_pack += p->pack_size + p->index_size;
-                       num_pack++;
-               }
-               printf("count: %lu\n", loose);
-               printf("size: %lu\n", loose_size / 1024);
-               printf("in-pack: %lu\n", packed);
-               printf("packs: %lu\n", num_pack);
-               printf("size-pack: %lu\n", size_pack / 1024);
-               printf("prune-packable: %lu\n", packed_loose);
-               printf("garbage: %lu\n", garbage);
-       }
-       else
-               printf("%lu objects, %lu kilobytes\n",
-                      loose, loose_size / 1024);
-       return 0;
-}
diff --git a/builtin-describe.c b/builtin-describe.c
deleted file mode 100644 (file)
index 63c6a19..0000000
+++ /dev/null
@@ -1,365 +0,0 @@
-#include "cache.h"
-#include "commit.h"
-#include "tag.h"
-#include "refs.h"
-#include "builtin.h"
-#include "exec_cmd.h"
-#include "parse-options.h"
-
-#define SEEN           (1u<<0)
-#define MAX_TAGS       (FLAG_BITS - 1)
-
-static const char * const describe_usage[] = {
-       "git describe [options] <committish>*",
-       NULL
-};
-
-static int debug;      /* Display lots of verbose info */
-static int all;        /* Any valid ref can be used */
-static int tags;       /* Allow lightweight tags */
-static int longformat;
-static int abbrev = DEFAULT_ABBREV;
-static int max_candidates = 10;
-static const char *pattern;
-static int always;
-
-struct commit_name {
-       struct tag *tag;
-       int prio; /* annotated tag = 2, tag = 1, head = 0 */
-       unsigned char sha1[20];
-       char path[FLEX_ARRAY]; /* more */
-};
-static const char *prio_names[] = {
-       "head", "lightweight", "annotated",
-};
-
-static void add_to_known_names(const char *path,
-                              struct commit *commit,
-                              int prio,
-                              const unsigned char *sha1)
-{
-       struct commit_name *e = commit->util;
-       if (!e || e->prio < prio) {
-               size_t len = strlen(path)+1;
-               free(e);
-               e = xmalloc(sizeof(struct commit_name) + len);
-               e->tag = NULL;
-               e->prio = prio;
-               hashcpy(e->sha1, sha1);
-               memcpy(e->path, path, len);
-               commit->util = e;
-       }
-}
-
-static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
-{
-       int might_be_tag = !prefixcmp(path, "refs/tags/");
-       struct commit *commit;
-       struct object *object;
-       unsigned char peeled[20];
-       int is_tag, prio;
-
-       if (!all && !might_be_tag)
-               return 0;
-
-       if (!peel_ref(path, peeled) && !is_null_sha1(peeled)) {
-               commit = lookup_commit_reference_gently(peeled, 1);
-               if (!commit)
-                       return 0;
-               is_tag = !!hashcmp(sha1, commit->object.sha1);
-       } else {
-               commit = lookup_commit_reference_gently(sha1, 1);
-               object = parse_object(sha1);
-               if (!commit || !object)
-                       return 0;
-               is_tag = object->type == OBJ_TAG;
-       }
-
-       /* If --all, then any refs are used.
-        * If --tags, then any tags are used.
-        * Otherwise only annotated tags are used.
-        */
-       if (might_be_tag) {
-               if (is_tag)
-                       prio = 2;
-               else
-                       prio = 1;
-
-               if (pattern && fnmatch(pattern, path + 10, 0))
-                       prio = 0;
-       }
-       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, sha1);
-       return 0;
-}
-
-struct possible_tag {
-       struct commit_name *name;
-       int depth;
-       int found_order;
-       unsigned flag_within;
-};
-
-static int compare_pt(const void *a_, const void *b_)
-{
-       struct possible_tag *a = (struct possible_tag *)a_;
-       struct possible_tag *b = (struct possible_tag *)b_;
-       if (a->depth != b->depth)
-               return a->depth - b->depth;
-       if (a->found_order != b->found_order)
-               return a->found_order - b->found_order;
-       return 0;
-}
-
-static unsigned long finish_depth_computation(
-       struct commit_list **list,
-       struct possible_tag *best)
-{
-       unsigned long seen_commits = 0;
-       while (*list) {
-               struct commit *c = pop_commit(list);
-               struct commit_list *parents = c->parents;
-               seen_commits++;
-               if (c->object.flags & best->flag_within) {
-                       struct commit_list *a = *list;
-                       while (a) {
-                               struct commit *i = a->item;
-                               if (!(i->object.flags & best->flag_within))
-                                       break;
-                               a = a->next;
-                       }
-                       if (!a)
-                               break;
-               } else
-                       best->depth++;
-               while (parents) {
-                       struct commit *p = parents->item;
-                       parse_commit(p);
-                       if (!(p->object.flags & SEEN))
-                               insert_by_date(p, list);
-                       p->object.flags |= c->object.flags;
-                       parents = parents->next;
-               }
-       }
-       return seen_commits;
-}
-
-static void display_name(struct commit_name *n)
-{
-       if (n->prio == 2 && !n->tag) {
-               n->tag = lookup_tag(n->sha1);
-               if (!n->tag || parse_tag(n->tag) || !n->tag->tag)
-                       die("annotated tag %s not available", n->path);
-               if (strcmp(n->tag->tag, all ? n->path + 5 : n->path))
-                       warning("tag '%s' is really '%s' here", n->tag->tag, n->path);
-       }
-
-       if (n->tag)
-               printf("%s", n->tag->tag);
-       else
-               printf("%s", n->path);
-}
-
-static void show_suffix(int depth, const unsigned char *sha1)
-{
-       printf("-%d-g%s", depth, find_unique_abbrev(sha1, abbrev));
-}
-
-static void describe(const char *arg, int last_one)
-{
-       unsigned char sha1[20];
-       struct commit *cmit, *gave_up_on = NULL;
-       struct commit_list *list;
-       static int initialized = 0;
-       struct commit_name *n;
-       struct possible_tag all_matches[MAX_TAGS];
-       unsigned int match_cnt = 0, annotated_cnt = 0, cur_match;
-       unsigned long seen_commits = 0;
-
-       if (get_sha1(arg, sha1))
-               die("Not a valid object name %s", arg);
-       cmit = lookup_commit_reference(sha1);
-       if (!cmit)
-               die("%s is not a valid '%s' object", arg, commit_type);
-
-       if (!initialized) {
-               initialized = 1;
-               for_each_ref(get_name, NULL);
-       }
-
-       n = cmit->util;
-       if (n) {
-               /*
-                * Exact match to an existing ref.
-                */
-               display_name(n);
-               if (longformat)
-                       show_suffix(0, n->tag ? n->tag->tagged->sha1 : sha1);
-               printf("\n");
-               return;
-       }
-
-       if (!max_candidates)
-               die("no tag exactly matches '%s'", sha1_to_hex(cmit->object.sha1));
-       if (debug)
-               fprintf(stderr, "searching to describe %s\n", arg);
-
-       list = NULL;
-       cmit->object.flags = SEEN;
-       commit_list_insert(cmit, &list);
-       while (list) {
-               struct commit *c = pop_commit(&list);
-               struct commit_list *parents = c->parents;
-               seen_commits++;
-               n = c->util;
-               if (n) {
-                       if (match_cnt < max_candidates) {
-                               struct possible_tag *t = &all_matches[match_cnt++];
-                               t->name = n;
-                               t->depth = seen_commits - 1;
-                               t->flag_within = 1u << match_cnt;
-                               t->found_order = match_cnt;
-                               c->object.flags |= t->flag_within;
-                               if (n->prio == 2)
-                                       annotated_cnt++;
-                       }
-                       else {
-                               gave_up_on = c;
-                               break;
-                       }
-               }
-               for (cur_match = 0; cur_match < match_cnt; cur_match++) {
-                       struct possible_tag *t = &all_matches[cur_match];
-                       if (!(c->object.flags & t->flag_within))
-                               t->depth++;
-               }
-               if (annotated_cnt && !list) {
-                       if (debug)
-                               fprintf(stderr, "finished search at %s\n",
-                                       sha1_to_hex(c->object.sha1));
-                       break;
-               }
-               while (parents) {
-                       struct commit *p = parents->item;
-                       parse_commit(p);
-                       if (!(p->object.flags & SEEN))
-                               insert_by_date(p, &list);
-                       p->object.flags |= c->object.flags;
-                       parents = parents->next;
-               }
-       }
-
-       if (!match_cnt) {
-               const unsigned char *sha1 = cmit->object.sha1;
-               if (always) {
-                       printf("%s\n", find_unique_abbrev(sha1, abbrev));
-                       return;
-               }
-               die("cannot describe '%s'", sha1_to_hex(sha1));
-       }
-
-       qsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt);
-
-       if (gave_up_on) {
-               insert_by_date(gave_up_on, &list);
-               seen_commits--;
-       }
-       seen_commits += finish_depth_computation(&list, &all_matches[0]);
-       free_commit_list(list);
-
-       if (debug) {
-               for (cur_match = 0; cur_match < match_cnt; cur_match++) {
-                       struct possible_tag *t = &all_matches[cur_match];
-                       fprintf(stderr, " %-11s %8d %s\n",
-                               prio_names[t->name->prio],
-                               t->depth, t->name->path);
-               }
-               fprintf(stderr, "traversed %lu commits\n", seen_commits);
-               if (gave_up_on) {
-                       fprintf(stderr,
-                               "more than %i tags found; listed %i most recent\n"
-                               "gave up search at %s\n",
-                               max_candidates, max_candidates,
-                               sha1_to_hex(gave_up_on->object.sha1));
-               }
-       }
-
-       display_name(all_matches[0].name);
-       if (abbrev)
-               show_suffix(all_matches[0].depth, cmit->object.sha1);
-       printf("\n");
-
-       if (!last_one)
-               clear_commit_marks(cmit, -1);
-}
-
-int cmd_describe(int argc, const char **argv, const char *prefix)
-{
-       int contains = 0;
-       struct option options[] = {
-               OPT_BOOLEAN(0, "contains",   &contains, "find the tag that comes after the commit"),
-               OPT_BOOLEAN(0, "debug",      &debug, "debug search strategy on stderr"),
-               OPT_BOOLEAN(0, "all",        &all, "use any ref in .git/refs"),
-               OPT_BOOLEAN(0, "tags",       &tags, "use any tag in .git/refs/tags"),
-               OPT_BOOLEAN(0, "long",       &longformat, "always use long format"),
-               OPT__ABBREV(&abbrev),
-               OPT_SET_INT(0, "exact-match", &max_candidates,
-                           "only output exact matches", 0),
-               OPT_INTEGER(0, "candidates", &max_candidates,
-                           "consider <n> most recent tags (default: 10)"),
-               OPT_STRING(0, "match",       &pattern, "pattern",
-                          "only consider tags matching <pattern>"),
-               OPT_BOOLEAN(0, "always",     &always,
-                          "show abbreviated commit object as fallback"),
-               OPT_END(),
-       };
-
-       argc = parse_options(argc, argv, options, describe_usage, 0);
-       if (max_candidates < 0)
-               max_candidates = 0;
-       else if (max_candidates > MAX_TAGS)
-               max_candidates = MAX_TAGS;
-
-       save_commit_buffer = 0;
-
-       if (longformat && abbrev == 0)
-               die("--long is incompatible with --abbrev=0");
-
-       if (contains) {
-               const char **args = xmalloc((7 + argc) * sizeof(char *));
-               int i = 0;
-               args[i++] = "name-rev";
-               args[i++] = "--name-only";
-               args[i++] = "--no-undefined";
-               if (always)
-                       args[i++] = "--always";
-               if (!all) {
-                       args[i++] = "--tags";
-                       if (pattern) {
-                               char *s = xmalloc(strlen("--refs=refs/tags/") + strlen(pattern) + 1);
-                               sprintf(s, "--refs=refs/tags/%s", pattern);
-                               args[i++] = s;
-                       }
-               }
-               memcpy(args + i, argv, argc * sizeof(char *));
-               args[i + argc] = NULL;
-               return cmd_name_rev(i + argc, args, prefix);
-       }
-
-       if (argc == 0) {
-               describe("HEAD", 1);
-       } else {
-               while (argc-- > 0) {
-                       describe(*argv++, argc == 0);
-               }
-       }
-       return 0;
-}
diff --git a/builtin-diff-files.c b/builtin-diff-files.c
deleted file mode 100644 (file)
index 5b64011..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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, const char *prefix)
-{
-       struct rev_info rev;
-       int result;
-       unsigned options = 0;
-
-       init_revisions(&rev, prefix);
-       git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
-       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"))
-                       options |= DIFF_SILENT_ON_REMOVED;
-               else
-                       usage(diff_files_usage);
-               argv++; argc--;
-       }
-       if (!rev.diffopt.output_format)
-               rev.diffopt.output_format = DIFF_FORMAT_RAW;
-
-       /*
-        * Make sure there are NO revision (i.e. pending object) parameter,
-        * rev.max_count is reasonable (0 <= n <= 3), and
-        * there is no other revision filtering parameters.
-        */
-       if (rev.pending.nr ||
-           rev.min_age != -1 || rev.max_age != -1 ||
-           3 < rev.max_count)
-               usage(diff_files_usage);
-
-       /*
-        * "diff-files --base -p" should not combine merges because it
-        * was not asked to.  "diff-files -c -p" should not densify
-        * (the user should ask with "diff-files --cc" explicitly).
-        */
-       if (rev.max_count == -1 && !rev.combine_merges &&
-           (rev.diffopt.output_format & DIFF_FORMAT_PATCH))
-               rev.combine_merges = rev.dense_combined_merges = 1;
-
-       if (read_cache_preload(rev.diffopt.paths) < 0) {
-               perror("read_cache_preload");
-               return -1;
-       }
-       result = run_diff_files(&rev, options);
-       return diff_result_code(&rev.diffopt, result);
-}
diff --git a/builtin-diff-index.c b/builtin-diff-index.c
deleted file mode 100644 (file)
index 0483749..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-#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, const char *prefix)
-{
-       struct rev_info rev;
-       int cached = 0;
-       int i;
-       int result;
-
-       init_revisions(&rev, prefix);
-       git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
-       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);
-       }
-       if (!rev.diffopt.output_format)
-               rev.diffopt.output_format = DIFF_FORMAT_RAW;
-
-       /*
-        * Make sure there is one revision (i.e. pending object),
-        * and there is no revision filtering parameters.
-        */
-       if (rev.pending.nr != 1 ||
-           rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1)
-               usage(diff_cache_usage);
-       if (!cached)
-               setup_work_tree();
-       if (read_cache() < 0) {
-               perror("read_cache");
-               return -1;
-       }
-       result = run_diff_index(&rev, cached);
-       return diff_result_code(&rev.diffopt, result);
-}
diff --git a/builtin-diff-tree.c b/builtin-diff-tree.c
deleted file mode 100644 (file)
index 79cedb7..0000000
+++ /dev/null
@@ -1,169 +0,0 @@
-#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);
-}
-
-/* Diff one or more commits. */
-static int stdin_diff_commit(struct commit *commit, char *line, int len)
-{
-       unsigned char sha1[20];
-       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);
-}
-
-/* Diff two trees. */
-static int stdin_diff_trees(struct tree *tree1, char *line, int len)
-{
-       unsigned char sha1[20];
-       struct tree *tree2;
-       if (len != 82 || !isspace(line[40]) || get_sha1_hex(line + 41, sha1))
-               return error("Need exactly two trees, separated by a space");
-       tree2 = lookup_tree(sha1);
-       if (!tree2 || parse_tree(tree2))
-               return -1;
-       printf("%s %s\n", sha1_to_hex(tree1->object.sha1),
-                         sha1_to_hex(tree2->object.sha1));
-       diff_tree_sha1(tree1->object.sha1, tree2->object.sha1,
-                      "", &log_tree_opt.diffopt);
-       log_tree_diff_flush(&log_tree_opt);
-       return 0;
-}
-
-static int diff_tree_stdin(char *line)
-{
-       int len = strlen(line);
-       unsigned char sha1[20];
-       struct object *obj;
-
-       if (!len || line[len-1] != '\n')
-               return -1;
-       line[len-1] = 0;
-       if (get_sha1_hex(line, sha1))
-               return -1;
-       obj = lookup_unknown_object(sha1);
-       if (!obj || !obj->parsed)
-               obj = parse_object(sha1);
-       if (!obj)
-               return -1;
-       if (obj->type == OBJ_COMMIT)
-               return stdin_diff_commit((struct commit *)obj, line, len);
-       if (obj->type == OBJ_TREE)
-               return stdin_diff_trees((struct tree *)obj, line, len);
-       error("Object %s is a %s, not a commit or tree",
-             sha1_to_hex(sha1), typename(obj->type));
-       return -1;
-}
-
-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, const char *prefix)
-{
-       int nr_sha1;
-       char line[1000];
-       struct object *tree1, *tree2;
-       static struct rev_info *opt = &log_tree_opt;
-       int read_stdin = 0;
-
-       init_revisions(opt, prefix);
-       git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
-       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);
-       }
-
-       if (!opt->diffopt.output_format)
-               opt->diffopt.output_format = DIFF_FORMAT_RAW;
-
-       /*
-        * NOTE! We expect "a ^b" to be equal to "a..b", so we
-        * reverse the order of the objects if the second one
-        * is marked UNINTERESTING.
-        */
-       nr_sha1 = opt->pending.nr;
-       switch (nr_sha1) {
-       case 0:
-               if (!read_stdin)
-                       usage(diff_tree_usage);
-               break;
-       case 1:
-               tree1 = opt->pending.objects[0].item;
-               diff_tree_commit_sha1(tree1->sha1);
-               break;
-       case 2:
-               tree1 = opt->pending.objects[0].item;
-               tree2 = opt->pending.objects[1].item;
-               if (tree2->flags & UNINTERESTING) {
-                       struct object *tmp = tree2;
-                       tree2 = tree1;
-                       tree1 = tmp;
-               }
-               diff_tree_sha1(tree1->sha1,
-                              tree2->sha1,
-                              "", &opt->diffopt);
-               log_tree_diff_flush(opt);
-               break;
-       }
-
-       if (read_stdin) {
-               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 diff_result_code(&opt->diffopt, 0);
-}
diff --git a/builtin-diff.c b/builtin-diff.c
deleted file mode 100644 (file)
index d75d69b..0000000
+++ /dev/null
@@ -1,423 +0,0 @@
-/*
- * Builtin "git diff"
- *
- * Copyright (c) 2006 Junio C Hamano
- */
-#include "cache.h"
-#include "color.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"
-
-struct blobinfo {
-       unsigned char sha1[20];
-       const char *name;
-       unsigned mode;
-};
-
-static const char builtin_diff_usage[] =
-"git diff <options> <rev>{0,2} -- <path>*";
-
-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 (!is_null_sha1(old_sha1) && !is_null_sha1(new_sha1) &&
-           !hashcmp(old_sha1, new_sha1) && (old_mode == new_mode))
-               return;
-
-       if (DIFF_OPT_TST(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;
-       }
-
-       if (opt->prefix &&
-           (strncmp(old_name, opt->prefix, opt->prefix_length) ||
-            strncmp(new_name, opt->prefix, opt->prefix_length)))
-               return;
-
-       one = alloc_filespec(old_name);
-       two = alloc_filespec(new_name);
-       fill_filespec(one, old_sha1, old_mode);
-       fill_filespec(two, new_sha1, new_mode);
-
-       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;
-
-       if (argc > 1)
-               usage(builtin_diff_usage);
-
-       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);
-
-       diff_set_mnemonic_prefix(&revs->diffopt, "o/", "w/");
-
-       if (blob[0].mode == S_IFINVALID)
-               blob[0].mode = canon_mode(st.st_mode);
-
-       stuff_change(&revs->diffopt,
-                    blob[0].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)
-{
-       unsigned mode = canon_mode(S_IFREG | 0644);
-
-       if (argc > 1)
-               usage(builtin_diff_usage);
-
-       if (blob[0].mode == S_IFINVALID)
-               blob[0].mode = mode;
-
-       if (blob[1].mode == S_IFINVALID)
-               blob[1].mode = mode;
-
-       stuff_change(&revs->diffopt,
-                    blob[0].mode, blob[1].mode,
-                    blob[0].sha1, blob[1].sha1,
-                    blob[0].name, blob[1].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") || !strcmp(arg, "--staged"))
-                       cached = 1;
-               else
-                       usage(builtin_diff_usage);
-               argv++; argc--;
-       }
-       if (!cached)
-               setup_work_tree();
-       /*
-        * Make sure there is one revision (i.e. pending object),
-        * and there is no revision filtering parameters.
-        */
-       if (revs->pending.nr != 1 ||
-           revs->max_count != -1 || revs->min_age != -1 ||
-           revs->max_age != -1)
-               usage(builtin_diff_usage);
-       if (read_cache_preload(revs->diffopt.paths) < 0) {
-               perror("read_cache_preload");
-               return -1;
-       }
-       return run_diff_index(revs, cached);
-}
-
-static int builtin_diff_tree(struct rev_info *revs,
-                            int argc, const char **argv,
-                            struct object_array_entry *ent)
-{
-       const unsigned char *(sha1[2]);
-       int swap = 0;
-
-       if (argc > 1)
-               usage(builtin_diff_usage);
-
-       /* We saw two trees, ent[0] and ent[1].
-        * if ent[1] is uninteresting, they are swapped
-        */
-       if (ent[1].item->flags & UNINTERESTING)
-               swap = 1;
-       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_array_entry *ent,
-                                int ents)
-{
-       const unsigned char (*parent)[20];
-       int i;
-
-       if (argc > 1)
-               usage(builtin_diff_usage);
-
-       if (!revs->dense_combined_merges && !revs->combine_merges)
-               revs->dense_combined_merges = revs->combine_merges = 1;
-       parent = xmalloc(ents * sizeof(*parent));
-       for (i = 0; i < ents; i++)
-               hashcpy((unsigned char *)(parent + i), ent[i].item->sha1);
-       diff_tree_combined(parent[0], parent + 1, ents - 1,
-                          revs->dense_combined_merges, revs);
-       return 0;
-}
-
-static void refresh_index_quietly(void)
-{
-       struct lock_file *lock_file;
-       int fd;
-
-       lock_file = xcalloc(1, sizeof(struct lock_file));
-       fd = hold_locked_index(lock_file, 0);
-       if (fd < 0)
-               return;
-       discard_cache();
-       read_cache();
-       refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED);
-
-       if (active_cache_changed &&
-           !write_cache(fd, active_cache, active_nr))
-               commit_locked_index(lock_file);
-
-       rollback_lock_file(lock_file);
-}
-
-static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv)
-{
-       int result;
-       unsigned int options = 0;
-
-       while (1 < argc && argv[1][0] == '-') {
-               if (!strcmp(argv[1], "--base"))
-                       revs->max_count = 1;
-               else if (!strcmp(argv[1], "--ours"))
-                       revs->max_count = 2;
-               else if (!strcmp(argv[1], "--theirs"))
-                       revs->max_count = 3;
-               else if (!strcmp(argv[1], "-q"))
-                       options |= DIFF_SILENT_ON_REMOVED;
-               else
-                       return error("invalid option: %s", argv[1]);
-               argv++; argc--;
-       }
-
-       /*
-        * "diff --base" should not combine merges because it was not
-        * asked to.  "diff -c" should not densify (if the user wants
-        * dense one, --cc can be explicitly asked for, or just rely
-        * on the default).
-        */
-       if (revs->max_count == -1 && !revs->combine_merges &&
-           (revs->diffopt.output_format & DIFF_FORMAT_PATCH))
-               revs->combine_merges = revs->dense_combined_merges = 1;
-
-       setup_work_tree();
-       if (read_cache_preload(revs->diffopt.paths) < 0) {
-               perror("read_cache_preload");
-               return -1;
-       }
-       result = run_diff_files(revs, options);
-       return diff_result_code(&revs->diffopt, result);
-}
-
-int cmd_diff(int argc, const char **argv, const char *prefix)
-{
-       int i;
-       struct rev_info rev;
-       struct object_array_entry ent[100];
-       int ents = 0, blobs = 0, paths = 0;
-       const char *path = NULL;
-       struct blobinfo blob[2];
-       int nongit;
-       int result = 0;
-
-       /*
-        * 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)
-        *
-        * N=0, M=0, P=2:
-        *      compare two filesystem entities (aka --no-index).
-        *
-        * Other cases are errors.
-        */
-
-       prefix = setup_git_directory_gently(&nongit);
-       git_config(git_diff_ui_config, NULL);
-
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
-
-       init_revisions(&rev, prefix);
-
-       /* If this is a no-index diff, just run it and exit there. */
-       diff_no_index(&rev, argc, argv, nongit, prefix);
-
-       /* Otherwise, we are doing the usual "git" diff */
-       rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index;
-
-       /* Default to let external and textconv be used */
-       DIFF_OPT_SET(&rev.diffopt, ALLOW_EXTERNAL);
-       DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
-
-       if (nongit)
-               die("Not a git repository");
-       argc = setup_revisions(argc, argv, &rev, NULL);
-       if (!rev.diffopt.output_format) {
-               rev.diffopt.output_format = DIFF_FORMAT_PATCH;
-               if (diff_setup_done(&rev.diffopt) < 0)
-                       die("diff_setup_done failed");
-       }
-
-       DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
-
-       /*
-        * If the user asked for our exit code then don't start a
-        * pager or we would end up reporting its exit code instead.
-        */
-       if (!DIFF_OPT_TST(&rev.diffopt, EXIT_WITH_STATUS) &&
-           check_pager_config("diff") != 0)
-               setup_pager();
-
-       /*
-        * Do we have --cached and not have a pending object, then
-        * default to HEAD by hand.  Eek.
-        */
-       if (!rev.pending.nr) {
-               int i;
-               for (i = 1; i < argc; i++) {
-                       const char *arg = argv[i];
-                       if (!strcmp(arg, "--"))
-                               break;
-                       else if (!strcmp(arg, "--cached") ||
-                                !strcmp(arg, "--staged")) {
-                               add_head_to_pending(&rev);
-                               if (!rev.pending.nr)
-                                       die("No HEAD commit to compare with (yet)");
-                               break;
-                       }
-               }
-       }
-
-       for (i = 0; i < rev.pending.nr; i++) {
-               struct object_array_entry *list = rev.pending.objects+i;
-               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 (obj->type == OBJ_COMMIT)
-                       obj = &((struct commit *)obj)->tree->object;
-               if (obj->type == OBJ_TREE) {
-                       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 (obj->type == OBJ_BLOB) {
-                       if (2 <= blobs)
-                               die("more than two blobs given: '%s'", name);
-                       hashcpy(blob[blobs].sha1, obj->sha1);
-                       blob[blobs].name = name;
-                       blob[blobs].mode = list->mode;
-                       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:
-                       result = builtin_diff_files(&rev, argc, argv);
-                       break;
-               case 1:
-                       if (paths != 1)
-                               usage(builtin_diff_usage);
-                       result = builtin_diff_b_f(&rev, argc, argv, blob, path);
-                       break;
-               case 2:
-                       if (paths)
-                               usage(builtin_diff_usage);
-                       result = builtin_diff_blobs(&rev, argc, argv, blob);
-                       break;
-               default:
-                       usage(builtin_diff_usage);
-               }
-       }
-       else if (blobs)
-               usage(builtin_diff_usage);
-       else if (ents == 1)
-               result = builtin_diff_index(&rev, argc, argv);
-       else if (ents == 2)
-               result = builtin_diff_tree(&rev, argc, argv, ent);
-       else if ((ents == 3) && (ent[0].item->flags & UNINTERESTING)) {
-               /* diff A...B where there is one sane merge base between
-                * A and B.  We have ent[0] == merge-base, ent[1] == A,
-                * and ent[2] == B.  Show diff between the base and B.
-                */
-               ent[1] = ent[2];
-               result = builtin_diff_tree(&rev, argc, argv, ent);
-       }
-       else
-               result = builtin_diff_combined(&rev, argc, argv,
-                                            ent, ents);
-       result = diff_result_code(&rev.diffopt, result);
-       if (1 < rev.diffopt.skip_stat_unmatch)
-               refresh_index_quietly();
-       return result;
-}
diff --git a/builtin-fast-export.c b/builtin-fast-export.c
deleted file mode 100644 (file)
index 6731713..0000000
+++ /dev/null
@@ -1,552 +0,0 @@
-/*
- * "git fast-export" builtin command
- *
- * Copyright (C) 2007 Johannes E. Schindelin
- */
-#include "builtin.h"
-#include "cache.h"
-#include "commit.h"
-#include "object.h"
-#include "tag.h"
-#include "diff.h"
-#include "diffcore.h"
-#include "log-tree.h"
-#include "revision.h"
-#include "decorate.h"
-#include "string-list.h"
-#include "utf8.h"
-#include "parse-options.h"
-
-static const char *fast_export_usage[] = {
-       "git fast-export [rev-list-opts]",
-       NULL
-};
-
-static int progress;
-static enum { VERBATIM, WARN, STRIP, ABORT } signed_tag_mode = ABORT;
-static int fake_missing_tagger;
-
-static int parse_opt_signed_tag_mode(const struct option *opt,
-                                    const char *arg, int unset)
-{
-       if (unset || !strcmp(arg, "abort"))
-               signed_tag_mode = ABORT;
-       else if (!strcmp(arg, "verbatim") || !strcmp(arg, "ignore"))
-               signed_tag_mode = VERBATIM;
-       else if (!strcmp(arg, "warn"))
-               signed_tag_mode = WARN;
-       else if (!strcmp(arg, "strip"))
-               signed_tag_mode = STRIP;
-       else
-               return error("Unknown signed-tag mode: %s", arg);
-       return 0;
-}
-
-static struct decoration idnums;
-static uint32_t last_idnum;
-
-static int has_unshown_parent(struct commit *commit)
-{
-       struct commit_list *parent;
-
-       for (parent = commit->parents; parent; parent = parent->next)
-               if (!(parent->item->object.flags & SHOWN) &&
-                   !(parent->item->object.flags & UNINTERESTING))
-                       return 1;
-       return 0;
-}
-
-/* Since intptr_t is C99, we do not use it here */
-static inline uint32_t *mark_to_ptr(uint32_t mark)
-{
-       return ((uint32_t *)NULL) + mark;
-}
-
-static inline uint32_t ptr_to_mark(void * mark)
-{
-       return (uint32_t *)mark - (uint32_t *)NULL;
-}
-
-static inline void mark_object(struct object *object, uint32_t mark)
-{
-       add_decoration(&idnums, object, mark_to_ptr(mark));
-}
-
-static inline void mark_next_object(struct object *object)
-{
-       mark_object(object, ++last_idnum);
-}
-
-static int get_object_mark(struct object *object)
-{
-       void *decoration = lookup_decoration(&idnums, object);
-       if (!decoration)
-               return 0;
-       return ptr_to_mark(decoration);
-}
-
-static void show_progress(void)
-{
-       static int counter = 0;
-       if (!progress)
-               return;
-       if ((++counter % progress) == 0)
-               printf("progress %d objects\n", counter);
-}
-
-static void handle_object(const unsigned char *sha1)
-{
-       unsigned long size;
-       enum object_type type;
-       char *buf;
-       struct object *object;
-
-       if (is_null_sha1(sha1))
-               return;
-
-       object = parse_object(sha1);
-       if (!object)
-               die ("Could not read blob %s", sha1_to_hex(sha1));
-
-       if (object->flags & SHOWN)
-               return;
-
-       buf = read_sha1_file(sha1, &type, &size);
-       if (!buf)
-               die ("Could not read blob %s", sha1_to_hex(sha1));
-
-       mark_next_object(object);
-
-       printf("blob\nmark :%"PRIu32"\ndata %lu\n", last_idnum, size);
-       if (size && fwrite(buf, size, 1, stdout) != 1)
-               die ("Could not write blob %s", sha1_to_hex(sha1));
-       printf("\n");
-
-       show_progress();
-
-       object->flags |= SHOWN;
-       free(buf);
-}
-
-static void show_filemodify(struct diff_queue_struct *q,
-                           struct diff_options *options, void *data)
-{
-       int i;
-       for (i = 0; i < q->nr; i++) {
-               struct diff_filespec *ospec = q->queue[i]->one;
-               struct diff_filespec *spec = q->queue[i]->two;
-
-               switch (q->queue[i]->status) {
-               case DIFF_STATUS_DELETED:
-                       printf("D %s\n", spec->path);
-                       break;
-
-               case DIFF_STATUS_COPIED:
-               case DIFF_STATUS_RENAMED:
-                       printf("%c \"%s\" \"%s\"\n", q->queue[i]->status,
-                              ospec->path, spec->path);
-
-                       if (!hashcmp(ospec->sha1, spec->sha1) &&
-                           ospec->mode == spec->mode)
-                               break;
-                       /* fallthrough */
-
-               case DIFF_STATUS_TYPE_CHANGED:
-               case DIFF_STATUS_MODIFIED:
-               case DIFF_STATUS_ADDED:
-                       /*
-                        * Links refer to objects in another repositories;
-                        * output the SHA-1 verbatim.
-                        */
-                       if (S_ISGITLINK(spec->mode))
-                               printf("M %06o %s %s\n", spec->mode,
-                                      sha1_to_hex(spec->sha1), spec->path);
-                       else {
-                               struct object *object = lookup_object(spec->sha1);
-                               printf("M %06o :%d %s\n", spec->mode,
-                                      get_object_mark(object), spec->path);
-                       }
-                       break;
-
-               default:
-                       die("Unexpected comparison status '%c' for %s, %s",
-                               q->queue[i]->status,
-                               ospec->path ? ospec->path : "none",
-                               spec->path ? spec->path : "none");
-               }
-       }
-}
-
-static const char *find_encoding(const char *begin, const char *end)
-{
-       const char *needle = "\nencoding ";
-       char *bol, *eol;
-
-       bol = memmem(begin, end ? end - begin : strlen(begin),
-                    needle, strlen(needle));
-       if (!bol)
-               return git_commit_encoding;
-       bol += strlen(needle);
-       eol = strchrnul(bol, '\n');
-       *eol = '\0';
-       return bol;
-}
-
-static void handle_commit(struct commit *commit, struct rev_info *rev)
-{
-       int saved_output_format = rev->diffopt.output_format;
-       const char *author, *author_end, *committer, *committer_end;
-       const char *encoding, *message;
-       char *reencoded = NULL;
-       struct commit_list *p;
-       int i;
-
-       rev->diffopt.output_format = DIFF_FORMAT_CALLBACK;
-
-       parse_commit(commit);
-       author = strstr(commit->buffer, "\nauthor ");
-       if (!author)
-               die ("Could not find author in commit %s",
-                    sha1_to_hex(commit->object.sha1));
-       author++;
-       author_end = strchrnul(author, '\n');
-       committer = strstr(author_end, "\ncommitter ");
-       if (!committer)
-               die ("Could not find committer in commit %s",
-                    sha1_to_hex(commit->object.sha1));
-       committer++;
-       committer_end = strchrnul(committer, '\n');
-       message = strstr(committer_end, "\n\n");
-       encoding = find_encoding(committer_end, message);
-       if (message)
-               message += 2;
-
-       if (commit->parents &&
-           get_object_mark(&commit->parents->item->object) != 0) {
-               parse_commit(commit->parents->item);
-               diff_tree_sha1(commit->parents->item->tree->object.sha1,
-                              commit->tree->object.sha1, "", &rev->diffopt);
-       }
-       else
-               diff_root_tree_sha1(commit->tree->object.sha1,
-                                   "", &rev->diffopt);
-
-       /* Export the referenced blobs, and remember the marks. */
-       for (i = 0; i < diff_queued_diff.nr; i++)
-               if (!S_ISGITLINK(diff_queued_diff.queue[i]->two->mode))
-                       handle_object(diff_queued_diff.queue[i]->two->sha1);
-
-       mark_next_object(&commit->object);
-       if (!is_encoding_utf8(encoding))
-               reencoded = reencode_string(message, "UTF-8", encoding);
-       if (!commit->parents)
-               printf("reset %s\n", (const char*)commit->util);
-       printf("commit %s\nmark :%"PRIu32"\n%.*s\n%.*s\ndata %u\n%s",
-              (const char *)commit->util, last_idnum,
-              (int)(author_end - author), author,
-              (int)(committer_end - committer), committer,
-              (unsigned)(reencoded
-                         ? strlen(reencoded) : message
-                         ? strlen(message) : 0),
-              reencoded ? reencoded : message ? message : "");
-       free(reencoded);
-
-       for (i = 0, p = commit->parents; p; p = p->next) {
-               int mark = get_object_mark(&p->item->object);
-               if (!mark)
-                       continue;
-               if (i == 0)
-                       printf("from :%d\n", mark);
-               else
-                       printf("merge :%d\n", mark);
-               i++;
-       }
-
-       log_tree_diff_flush(rev);
-       rev->diffopt.output_format = saved_output_format;
-
-       printf("\n");
-
-       show_progress();
-}
-
-static void handle_tail(struct object_array *commits, struct rev_info *revs)
-{
-       struct commit *commit;
-       while (commits->nr) {
-               commit = (struct commit *)commits->objects[commits->nr - 1].item;
-               if (has_unshown_parent(commit))
-                       return;
-               handle_commit(commit, revs);
-               commits->nr--;
-       }
-}
-
-static void handle_tag(const char *name, struct tag *tag)
-{
-       unsigned long size;
-       enum object_type type;
-       char *buf;
-       const char *tagger, *tagger_end, *message;
-       size_t message_size = 0;
-
-       buf = read_sha1_file(tag->object.sha1, &type, &size);
-       if (!buf)
-               die ("Could not read tag %s", sha1_to_hex(tag->object.sha1));
-       message = memmem(buf, size, "\n\n", 2);
-       if (message) {
-               message += 2;
-               message_size = strlen(message);
-       }
-       tagger = memmem(buf, message ? message - buf : size, "\ntagger ", 8);
-       if (!tagger) {
-               if (fake_missing_tagger)
-                       tagger = "tagger Unspecified Tagger "
-                               "<unspecified-tagger> 0 +0000";
-               else
-                       tagger = "";
-               tagger_end = tagger + strlen(tagger);
-       } else {
-               tagger++;
-               tagger_end = strchrnul(tagger, '\n');
-       }
-
-       /* handle signed tags */
-       if (message) {
-               const char *signature = strstr(message,
-                                              "\n-----BEGIN PGP SIGNATURE-----\n");
-               if (signature)
-                       switch(signed_tag_mode) {
-                       case ABORT:
-                               die ("Encountered signed tag %s; use "
-                                    "--signed-tag=<mode> to handle it.",
-                                    sha1_to_hex(tag->object.sha1));
-                       case WARN:
-                               warning ("Exporting signed tag %s",
-                                        sha1_to_hex(tag->object.sha1));
-                               /* fallthru */
-                       case VERBATIM:
-                               break;
-                       case STRIP:
-                               message_size = signature + 1 - message;
-                               break;
-                       }
-       }
-
-       if (!prefixcmp(name, "refs/tags/"))
-               name += 10;
-       printf("tag %s\nfrom :%d\n%.*s%sdata %d\n%.*s\n",
-              name, get_object_mark(tag->tagged),
-              (int)(tagger_end - tagger), tagger,
-              tagger == tagger_end ? "" : "\n",
-              (int)message_size, (int)message_size, message ? message : "");
-}
-
-static void get_tags_and_duplicates(struct object_array *pending,
-                                   struct string_list *extra_refs)
-{
-       struct tag *tag;
-       int i;
-
-       for (i = 0; i < pending->nr; i++) {
-               struct object_array_entry *e = pending->objects + i;
-               unsigned char sha1[20];
-               struct commit *commit = commit;
-               char *full_name;
-
-               if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1)
-                       continue;
-
-               switch (e->item->type) {
-               case OBJ_COMMIT:
-                       commit = (struct commit *)e->item;
-                       break;
-               case OBJ_TAG:
-                       tag = (struct tag *)e->item;
-
-                       /* handle nested tags */
-                       while (tag && tag->object.type == OBJ_TAG) {
-                               parse_object(tag->object.sha1);
-                               string_list_append(full_name, extra_refs)->util = tag;
-                               tag = (struct tag *)tag->tagged;
-                       }
-                       if (!tag)
-                               die ("Tag %s points nowhere?", e->name);
-                       switch(tag->object.type) {
-                       case OBJ_COMMIT:
-                               commit = (struct commit *)tag;
-                               break;
-                       case OBJ_BLOB:
-                               handle_object(tag->object.sha1);
-                               continue;
-                       default: /* OBJ_TAG (nested tags) is already handled */
-                               warning("Tag points to object of unexpected type %s, skipping.",
-                                       typename(tag->object.type));
-                               continue;
-                       }
-                       break;
-               default:
-                       warning("%s: Unexpected object of type %s, skipping.",
-                               e->name,
-                               typename(e->item->type));
-                       continue;
-               }
-               if (commit->util)
-                       /* more than one name for the same object */
-                       string_list_append(full_name, extra_refs)->util = commit;
-               else
-                       commit->util = full_name;
-       }
-}
-
-static void handle_tags_and_duplicates(struct string_list *extra_refs)
-{
-       struct commit *commit;
-       int i;
-
-       for (i = extra_refs->nr - 1; i >= 0; i--) {
-               const char *name = extra_refs->items[i].string;
-               struct object *object = extra_refs->items[i].util;
-               switch (object->type) {
-               case OBJ_TAG:
-                       handle_tag(name, (struct tag *)object);
-                       break;
-               case OBJ_COMMIT:
-                       /* create refs pointing to already seen commits */
-                       commit = (struct commit *)object;
-                       printf("reset %s\nfrom :%d\n\n", name,
-                              get_object_mark(&commit->object));
-                       show_progress();
-                       break;
-               }
-       }
-}
-
-static void export_marks(char *file)
-{
-       unsigned int i;
-       uint32_t mark;
-       struct object_decoration *deco = idnums.hash;
-       FILE *f;
-
-       f = fopen(file, "w");
-       if (!f)
-               error("Unable to open marks file %s for writing", file);
-
-       for (i = 0; i < idnums.size; i++) {
-               if (deco->base && deco->base->type == 1) {
-                       mark = ptr_to_mark(deco->decoration);
-                       fprintf(f, ":%"PRIu32" %s\n", mark,
-                               sha1_to_hex(deco->base->sha1));
-               }
-               deco++;
-       }
-
-       if (ferror(f) || fclose(f))
-               error("Unable to write marks file %s.", file);
-}
-
-static void import_marks(char *input_file)
-{
-       char line[512];
-       FILE *f = fopen(input_file, "r");
-       if (!f)
-               die("cannot read %s: %s", input_file, strerror(errno));
-
-       while (fgets(line, sizeof(line), f)) {
-               uint32_t mark;
-               char *line_end, *mark_end;
-               unsigned char sha1[20];
-               struct object *object;
-
-               line_end = strchr(line, '\n');
-               if (line[0] != ':' || !line_end)
-                       die("corrupt mark line: %s", line);
-               *line_end = '\0';
-
-               mark = strtoumax(line + 1, &mark_end, 10);
-               if (!mark || mark_end == line + 1
-                       || *mark_end != ' ' || get_sha1(mark_end + 1, sha1))
-                       die("corrupt mark line: %s", line);
-
-               object = parse_object(sha1);
-               if (!object)
-                       die ("Could not read blob %s", sha1_to_hex(sha1));
-
-               if (object->flags & SHOWN)
-                       error("Object %s already has a mark", sha1);
-
-               mark_object(object, mark);
-               if (last_idnum < mark)
-                       last_idnum = mark;
-
-               object->flags |= SHOWN;
-       }
-       fclose(f);
-}
-
-int cmd_fast_export(int argc, const char **argv, const char *prefix)
-{
-       struct rev_info revs;
-       struct object_array commits = { 0, 0, NULL };
-       struct string_list extra_refs = { NULL, 0, 0, 0 };
-       struct commit *commit;
-       char *export_filename = NULL, *import_filename = NULL;
-       struct option options[] = {
-               OPT_INTEGER(0, "progress", &progress,
-                           "show progress after <n> objects"),
-               OPT_CALLBACK(0, "signed-tags", &signed_tag_mode, "mode",
-                            "select handling of signed tags",
-                            parse_opt_signed_tag_mode),
-               OPT_STRING(0, "export-marks", &export_filename, "FILE",
-                            "Dump marks to this file"),
-               OPT_STRING(0, "import-marks", &import_filename, "FILE",
-                            "Import marks from this file"),
-               OPT_BOOLEAN(0, "fake-missing-tagger", &fake_missing_tagger,
-                            "Fake a tagger when tags lack one"),
-               OPT_END()
-       };
-
-       if (argc == 1)
-               usage_with_options (fast_export_usage, options);
-
-       /* we handle encodings */
-       git_config(git_default_config, NULL);
-
-       init_revisions(&revs, prefix);
-       argc = setup_revisions(argc, argv, &revs, NULL);
-       argc = parse_options(argc, argv, options, fast_export_usage, 0);
-       if (argc > 1)
-               usage_with_options (fast_export_usage, options);
-
-       if (import_filename)
-               import_marks(import_filename);
-
-       get_tags_and_duplicates(&revs.pending, &extra_refs);
-
-       revs.topo_order = 1;
-       if (prepare_revision_walk(&revs))
-               die("revision walk setup failed");
-       revs.diffopt.format_callback = show_filemodify;
-       DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
-       while ((commit = get_revision(&revs))) {
-               if (has_unshown_parent(commit)) {
-                       struct commit_list *parent = commit->parents;
-                       add_object_array(&commit->object, NULL, &commits);
-                       for (; parent; parent = parent->next)
-                               if (!parent->item->util)
-                                       parent->item->util = commit->util;
-               }
-               else {
-                       handle_commit(commit, &revs);
-                       handle_tail(&commits, &revs);
-               }
-       }
-
-       handle_tags_and_duplicates(&extra_refs);
-
-       if (export_filename)
-               export_marks(export_filename);
-
-       return 0;
-}
diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c
deleted file mode 100644 (file)
index 29356d2..0000000
+++ /dev/null
@@ -1,574 +0,0 @@
-#include "builtin.h"
-#include "cache.h"
-#include "refs.h"
-#include "commit.h"
-#include "sigchain.h"
-
-static char *get_stdin(void)
-{
-       struct strbuf buf = STRBUF_INIT;
-       if (strbuf_read(&buf, 0, 1024) < 0) {
-               die("error reading standard input: %s", strerror(errno));
-       }
-       return strbuf_detach(&buf, NULL);
-}
-
-static void show_new(enum object_type type, unsigned char *sha1_new)
-{
-       fprintf(stderr, "  %s: %s\n", typename(type),
-               find_unique_abbrev(sha1_new, DEFAULT_ABBREV));
-}
-
-static int update_ref_env(const char *action,
-                     const char *refname,
-                     unsigned char *sha1,
-                     unsigned char *oldval)
-{
-       char msg[1024];
-       const char *rla = getenv("GIT_REFLOG_ACTION");
-
-       if (!rla)
-               rla = "(reflog update)";
-       if (snprintf(msg, sizeof(msg), "%s: %s", rla, action) >= sizeof(msg))
-               warning("reflog message too long: %.*s...", 50, msg);
-       return update_ref(msg, refname, sha1, oldval, 0, QUIET_ON_ERR);
-}
-
-static int update_local_ref(const char *name,
-                           const char *new_head,
-                           const char *note,
-                           int verbose, int force)
-{
-       unsigned char sha1_old[20], sha1_new[20];
-       char oldh[41], newh[41];
-       struct commit *current, *updated;
-       enum object_type type;
-
-       if (get_sha1_hex(new_head, sha1_new))
-               die("malformed object name %s", new_head);
-
-       type = sha1_object_info(sha1_new, NULL);
-       if (type < 0)
-               die("object %s not found", new_head);
-
-       if (!*name) {
-               /* Not storing */
-               if (verbose) {
-                       fprintf(stderr, "* fetched %s\n", note);
-                       show_new(type, sha1_new);
-               }
-               return 0;
-       }
-
-       if (get_sha1(name, sha1_old)) {
-               const char *msg;
-       just_store:
-               /* new ref */
-               if (!strncmp(name, "refs/tags/", 10))
-                       msg = "storing tag";
-               else
-                       msg = "storing head";
-               fprintf(stderr, "* %s: storing %s\n",
-                       name, note);
-               show_new(type, sha1_new);
-               return update_ref_env(msg, name, sha1_new, NULL);
-       }
-
-       if (!hashcmp(sha1_old, sha1_new)) {
-               if (verbose) {
-                       fprintf(stderr, "* %s: same as %s\n", name, note);
-                       show_new(type, sha1_new);
-               }
-               return 0;
-       }
-
-       if (!strncmp(name, "refs/tags/", 10)) {
-               fprintf(stderr, "* %s: updating with %s\n", name, note);
-               show_new(type, sha1_new);
-               return update_ref_env("updating tag", name, sha1_new, NULL);
-       }
-
-       current = lookup_commit_reference(sha1_old);
-       updated = lookup_commit_reference(sha1_new);
-       if (!current || !updated)
-               goto just_store;
-
-       strcpy(oldh, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
-       strcpy(newh, find_unique_abbrev(sha1_new, DEFAULT_ABBREV));
-
-       if (in_merge_bases(current, &updated, 1)) {
-               fprintf(stderr, "* %s: fast forward to %s\n",
-                       name, note);
-               fprintf(stderr, "  old..new: %s..%s\n", oldh, newh);
-               return update_ref_env("fast forward", name, sha1_new, sha1_old);
-       }
-       if (!force) {
-               fprintf(stderr,
-                       "* %s: not updating to non-fast forward %s\n",
-                       name, note);
-               fprintf(stderr,
-                       "  old...new: %s...%s\n", oldh, newh);
-               return 1;
-       }
-       fprintf(stderr,
-               "* %s: forcing update to non-fast forward %s\n",
-               name, note);
-       fprintf(stderr, "  old...new: %s...%s\n", oldh, newh);
-       return update_ref_env("forced-update", name, sha1_new, sha1_old);
-}
-
-static int append_fetch_head(FILE *fp,
-                            const char *head, const char *remote,
-                            const char *remote_name, const char *remote_nick,
-                            const char *local_name, int not_for_merge,
-                            int verbose, int force)
-{
-       struct commit *commit;
-       int remote_len, i, note_len;
-       unsigned char sha1[20];
-       char note[1024];
-       const char *what, *kind;
-
-       if (get_sha1(head, sha1))
-               return error("Not a valid object name: %s", head);
-       commit = lookup_commit_reference_gently(sha1, 1);
-       if (!commit)
-               not_for_merge = 1;
-
-       if (!strcmp(remote_name, "HEAD")) {
-               kind = "";
-               what = "";
-       }
-       else if (!strncmp(remote_name, "refs/heads/", 11)) {
-               kind = "branch";
-               what = remote_name + 11;
-       }
-       else if (!strncmp(remote_name, "refs/tags/", 10)) {
-               kind = "tag";
-               what = remote_name + 10;
-       }
-       else if (!strncmp(remote_name, "refs/remotes/", 13)) {
-               kind = "remote branch";
-               what = remote_name + 13;
-       }
-       else {
-               kind = "";
-               what = remote_name;
-       }
-
-       remote_len = strlen(remote);
-       for (i = remote_len - 1; remote[i] == '/' && 0 <= i; i--)
-               ;
-       remote_len = i + 1;
-       if (4 < i && !strncmp(".git", remote + i - 3, 4))
-               remote_len = i - 3;
-
-       note_len = 0;
-       if (*what) {
-               if (*kind)
-                       note_len += sprintf(note + note_len, "%s ", kind);
-               note_len += sprintf(note + note_len, "'%s' of ", what);
-       }
-       note_len += sprintf(note + note_len, "%.*s", remote_len, remote);
-       fprintf(fp, "%s\t%s\t%s\n",
-               sha1_to_hex(commit ? commit->object.sha1 : sha1),
-               not_for_merge ? "not-for-merge" : "",
-               note);
-       return update_local_ref(local_name, head, note, verbose, force);
-}
-
-static char *keep;
-static void remove_keep(void)
-{
-       if (keep && *keep)
-               unlink(keep);
-}
-
-static void remove_keep_on_signal(int signo)
-{
-       remove_keep();
-       sigchain_pop(signo);
-       raise(signo);
-}
-
-static char *find_local_name(const char *remote_name, const char *refs,
-                            int *force_p, int *not_for_merge_p)
-{
-       const char *ref = refs;
-       int len = strlen(remote_name);
-
-       while (ref) {
-               const char *next;
-               int single_force, not_for_merge;
-
-               while (*ref == '\n')
-                       ref++;
-               if (!*ref)
-                       break;
-               next = strchr(ref, '\n');
-
-               single_force = not_for_merge = 0;
-               if (*ref == '+') {
-                       single_force = 1;
-                       ref++;
-               }
-               if (*ref == '.') {
-                       not_for_merge = 1;
-                       ref++;
-                       if (*ref == '+') {
-                               single_force = 1;
-                               ref++;
-                       }
-               }
-               if (!strncmp(remote_name, ref, len) && ref[len] == ':') {
-                       const char *local_part = ref + len + 1;
-                       int retlen;
-
-                       if (!next)
-                               retlen = strlen(local_part);
-                       else
-                               retlen = next - local_part;
-                       *force_p = single_force;
-                       *not_for_merge_p = not_for_merge;
-                       return xmemdupz(local_part, retlen);
-               }
-               ref = next;
-       }
-       return NULL;
-}
-
-static int fetch_native_store(FILE *fp,
-                             const char *remote,
-                             const char *remote_nick,
-                             const char *refs,
-                             int verbose, int force)
-{
-       char buffer[1024];
-       int err = 0;
-
-       sigchain_push_common(remove_keep_on_signal);
-       atexit(remove_keep);
-
-       while (fgets(buffer, sizeof(buffer), stdin)) {
-               int len;
-               char *cp;
-               char *local_name;
-               int single_force, not_for_merge;
-
-               for (cp = buffer; *cp && !isspace(*cp); cp++)
-                       ;
-               if (*cp)
-                       *cp++ = 0;
-               len = strlen(cp);
-               if (len && cp[len-1] == '\n')
-                       cp[--len] = 0;
-               if (!strcmp(buffer, "failed"))
-                       die("Fetch failure: %s", remote);
-               if (!strcmp(buffer, "pack"))
-                       continue;
-               if (!strcmp(buffer, "keep")) {
-                       char *od = get_object_directory();
-                       int len = strlen(od) + strlen(cp) + 50;
-                       keep = xmalloc(len);
-                       sprintf(keep, "%s/pack/pack-%s.keep", od, cp);
-                       continue;
-               }
-
-               local_name = find_local_name(cp, refs,
-                                            &single_force, &not_for_merge);
-               if (!local_name)
-                       continue;
-               err |= append_fetch_head(fp,
-                                        buffer, remote, cp, remote_nick,
-                                        local_name, not_for_merge,
-                                        verbose, force || single_force);
-       }
-       return err;
-}
-
-static int parse_reflist(const char *reflist)
-{
-       const char *ref;
-
-       printf("refs='");
-       for (ref = reflist; ref; ) {
-               const char *next;
-               while (*ref && isspace(*ref))
-                       ref++;
-               if (!*ref)
-                       break;
-               for (next = ref; *next && !isspace(*next); next++)
-                       ;
-               printf("\n%.*s", (int)(next - ref), ref);
-               ref = next;
-       }
-       printf("'\n");
-
-       printf("rref='");
-       for (ref = reflist; ref; ) {
-               const char *next, *colon;
-               while (*ref && isspace(*ref))
-                       ref++;
-               if (!*ref)
-                       break;
-               for (next = ref; *next && !isspace(*next); next++)
-                       ;
-               if (*ref == '.')
-                       ref++;
-               if (*ref == '+')
-                       ref++;
-               colon = strchr(ref, ':');
-               putchar('\n');
-               printf("%.*s", (int)((colon ? colon : next) - ref), ref);
-               ref = next;
-       }
-       printf("'\n");
-       return 0;
-}
-
-static int expand_refs_wildcard(const char *ls_remote_result, int numrefs,
-                               const char **refs)
-{
-       int i, matchlen, replacelen;
-       int found_one = 0;
-       const char *remote = *refs++;
-       numrefs--;
-
-       if (numrefs == 0) {
-               fprintf(stderr, "Nothing specified for fetching with remote.%s.fetch\n",
-                       remote);
-               printf("empty\n");
-       }
-
-       for (i = 0; i < numrefs; i++) {
-               const char *ref = refs[i];
-               const char *lref = ref;
-               const char *colon;
-               const char *tail;
-               const char *ls;
-               const char *next;
-
-               if (*lref == '+')
-                       lref++;
-               colon = strchr(lref, ':');
-               tail = lref + strlen(lref);
-               if (!(colon &&
-                     2 < colon - lref &&
-                     colon[-1] == '*' &&
-                     colon[-2] == '/' &&
-                     2 < tail - (colon + 1) &&
-                     tail[-1] == '*' &&
-                     tail[-2] == '/')) {
-                       /* not a glob */
-                       if (!found_one++)
-                               printf("explicit\n");
-                       printf("%s\n", ref);
-                       continue;
-               }
-
-               /* glob */
-               if (!found_one++)
-                       printf("glob\n");
-
-               /* lref to colon-2 is remote hierarchy name;
-                * colon+1 to tail-2 is local.
-                */
-               matchlen = (colon-1) - lref;
-               replacelen = (tail-1) - (colon+1);
-               for (ls = ls_remote_result; ls; ls = next) {
-                       const char *eol;
-                       unsigned char sha1[20];
-                       int namelen;
-
-                       while (*ls && isspace(*ls))
-                               ls++;
-                       next = strchr(ls, '\n');
-                       eol = !next ? (ls + strlen(ls)) : next;
-                       if (!memcmp("^{}", eol-3, 3))
-                               continue;
-                       if (eol - ls < 40)
-                               continue;
-                       if (get_sha1_hex(ls, sha1))
-                               continue;
-                       ls += 40;
-                       while (ls < eol && isspace(*ls))
-                               ls++;
-                       /* ls to next (or eol) is the name.
-                        * is it identical to lref to colon-2?
-                        */
-                       if ((eol - ls) <= matchlen ||
-                           strncmp(ls, lref, matchlen))
-                               continue;
-
-                       /* Yes, it is a match */
-                       namelen = eol - ls;
-                       if (lref != ref)
-                               putchar('+');
-                       printf("%.*s:%.*s%.*s\n",
-                              namelen, ls,
-                              replacelen, colon + 1,
-                              namelen - matchlen, ls + matchlen);
-               }
-       }
-       return 0;
-}
-
-static int pick_rref(int sha1_only, const char *rref, const char *ls_remote_result)
-{
-       int err = 0;
-       int lrr_count = lrr_count, i, pass;
-       const char *cp;
-       struct lrr {
-               const char *line;
-               const char *name;
-               int namelen;
-               int shown;
-       } *lrr_list = lrr_list;
-
-       for (pass = 0; pass < 2; pass++) {
-               /* pass 0 counts and allocates, pass 1 fills... */
-               cp = ls_remote_result;
-               i = 0;
-               while (1) {
-                       const char *np;
-                       while (*cp && isspace(*cp))
-                               cp++;
-                       if (!*cp)
-                               break;
-                       np = strchrnul(cp, '\n');
-                       if (pass) {
-                               lrr_list[i].line = cp;
-                               lrr_list[i].name = cp + 41;
-                               lrr_list[i].namelen = np - (cp + 41);
-                       }
-                       i++;
-                       cp = np;
-               }
-               if (!pass) {
-                       lrr_count = i;
-                       lrr_list = xcalloc(lrr_count, sizeof(*lrr_list));
-               }
-       }
-
-       while (1) {
-               const char *next;
-               int rreflen;
-               int i;
-
-               while (*rref && isspace(*rref))
-                       rref++;
-               if (!*rref)
-                       break;
-               next = strchrnul(rref, '\n');
-               rreflen = next - rref;
-
-               for (i = 0; i < lrr_count; i++) {
-                       struct lrr *lrr = &(lrr_list[i]);
-
-                       if (rreflen == lrr->namelen &&
-                           !memcmp(lrr->name, rref, rreflen)) {
-                               if (!lrr->shown)
-                                       printf("%.*s\n",
-                                              sha1_only ? 40 : lrr->namelen + 41,
-                                              lrr->line);
-                               lrr->shown = 1;
-                               break;
-                       }
-               }
-               if (lrr_count <= i) {
-                       error("pick-rref: %.*s not found", rreflen, rref);
-                       err = 1;
-               }
-               rref = next;
-       }
-       free(lrr_list);
-       return err;
-}
-
-int cmd_fetch__tool(int argc, const char **argv, const char *prefix)
-{
-       int verbose = 0;
-       int force = 0;
-       int sopt = 0;
-
-       while (1 < argc) {
-               const char *arg = argv[1];
-               if (!strcmp("-v", arg))
-                       verbose = 1;
-               else if (!strcmp("-f", arg))
-                       force = 1;
-               else if (!strcmp("-s", arg))
-                       sopt = 1;
-               else
-                       break;
-               argc--;
-               argv++;
-       }
-
-       if (argc <= 1)
-               return error("Missing subcommand");
-
-       if (!strcmp("append-fetch-head", argv[1])) {
-               int result;
-               FILE *fp;
-               char *filename;
-
-               if (argc != 8)
-                       return error("append-fetch-head takes 6 args");
-               filename = git_path("FETCH_HEAD");
-               fp = fopen(filename, "a");
-               if (!fp)
-                       return error("cannot open %s: %s\n", filename, strerror(errno));
-               result = append_fetch_head(fp, argv[2], argv[3],
-                                          argv[4], argv[5],
-                                          argv[6], !!argv[7][0],
-                                          verbose, force);
-               fclose(fp);
-               return result;
-       }
-       if (!strcmp("native-store", argv[1])) {
-               int result;
-               FILE *fp;
-               char *filename;
-
-               if (argc != 5)
-                       return error("fetch-native-store takes 3 args");
-               filename = git_path("FETCH_HEAD");
-               fp = fopen(filename, "a");
-               if (!fp)
-                       return error("cannot open %s: %s\n", filename, strerror(errno));
-               result = fetch_native_store(fp, argv[2], argv[3], argv[4],
-                                           verbose, force);
-               fclose(fp);
-               return result;
-       }
-       if (!strcmp("parse-reflist", argv[1])) {
-               const char *reflist;
-               if (argc != 3)
-                       return error("parse-reflist takes 1 arg");
-               reflist = argv[2];
-               if (!strcmp(reflist, "-"))
-                       reflist = get_stdin();
-               return parse_reflist(reflist);
-       }
-       if (!strcmp("pick-rref", argv[1])) {
-               const char *ls_remote_result;
-               if (argc != 4)
-                       return error("pick-rref takes 2 args");
-               ls_remote_result = argv[3];
-               if (!strcmp(ls_remote_result, "-"))
-                       ls_remote_result = get_stdin();
-               return pick_rref(sopt, argv[2], ls_remote_result);
-       }
-       if (!strcmp("expand-refs-wildcard", argv[1])) {
-               const char *reflist;
-               if (argc < 4)
-                       return error("expand-refs-wildcard takes at least 2 args");
-               reflist = argv[2];
-               if (!strcmp(reflist, "-"))
-                       reflist = get_stdin();
-               return expand_refs_wildcard(reflist, argc - 3, argv + 3);
-       }
-
-       return error("Unknown subcommand: %s", argv[1]);
-}
diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c
deleted file mode 100644 (file)
index 87f46c6..0000000
+++ /dev/null
@@ -1,837 +0,0 @@
-#include "cache.h"
-#include "refs.h"
-#include "pkt-line.h"
-#include "commit.h"
-#include "tag.h"
-#include "exec_cmd.h"
-#include "pack.h"
-#include "sideband.h"
-#include "fetch-pack.h"
-#include "remote.h"
-#include "run-command.h"
-
-static int transfer_unpack_limit = -1;
-static int fetch_unpack_limit = -1;
-static int unpack_limit = 100;
-static int prefer_ofs_delta = 1;
-static struct fetch_pack_args args = {
-       /* .uploadpack = */ "git-upload-pack",
-};
-
-static const char fetch_pack_usage[] =
-"git fetch-pack [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]";
-
-#define COMPLETE       (1U << 0)
-#define COMMON         (1U << 1)
-#define COMMON_REF     (1U << 2)
-#define SEEN           (1U << 3)
-#define POPPED         (1U << 4)
-
-static int marked;
-
-/*
- * After sending this many "have"s if we do not get any new ACK , we
- * give up traversing our history.
- */
-#define MAX_IN_VAIN 256
-
-static struct commit_list *rev_list;
-static int non_common_revs, multi_ack, use_sideband;
-
-static void rev_list_push(struct commit *commit, int mark)
-{
-       if (!(commit->object.flags & mark)) {
-               commit->object.flags |= mark;
-
-               if (!(commit->object.parsed))
-                       if (parse_commit(commit))
-                               return;
-
-               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, int flag, void *cb_data)
-{
-       struct object *o = deref_tag(parse_object(sha1), path, 0);
-
-       if (o && o->type == OBJ_COMMIT)
-               rev_list_push((struct commit *)o, SEEN);
-
-       return 0;
-}
-
-static int clear_marks(const char *path, const unsigned char *sha1, int flag, void *cb_data)
-{
-       struct object *o = deref_tag(parse_object(sha1), path, 0);
-
-       if (o && o->type == OBJ_COMMIT)
-               clear_commit_marks((struct commit *)o,
-                                  COMMON | COMMON_REF | SEEN | POPPED);
-       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)
-                               if (parse_commit(commit))
-                                       return;
-
-                       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);
-               parents = commit->parents;
-
-               commit->object.flags |= POPPED;
-               if (!(commit->object.flags & COMMON))
-                       non_common_revs--;
-
-               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;
-       unsigned in_vain = 0;
-       int got_continue = 0;
-
-       if (marked)
-               for_each_ref(clear_marks, NULL);
-       marked = 1;
-
-       for_each_ref(rev_list_insert_ref, NULL);
-
-       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;
-               }
-
-               if (!fetching)
-                       packet_write(fd[1], "want %s%s%s%s%s%s%s%s\n",
-                                    sha1_to_hex(remote),
-                                    (multi_ack ? " multi_ack" : ""),
-                                    (use_sideband == 2 ? " side-band-64k" : ""),
-                                    (use_sideband == 1 ? " side-band" : ""),
-                                    (args.use_thin_pack ? " thin-pack" : ""),
-                                    (args.no_progress ? " no-progress" : ""),
-                                    (args.include_tag ? " include-tag" : ""),
-                                    (prefer_ofs_delta ? " ofs-delta" : ""));
-               else
-                       packet_write(fd[1], "want %s\n", sha1_to_hex(remote));
-               fetching++;
-       }
-       if (is_repository_shallow())
-               write_shallow_commits(fd[1], 1);
-       if (args.depth > 0)
-               packet_write(fd[1], "deepen %d", args.depth);
-       packet_flush(fd[1]);
-       if (!fetching)
-               return 1;
-
-       if (args.depth > 0) {
-               char line[1024];
-               unsigned char sha1[20];
-
-               while (packet_read_line(fd[0], line, sizeof(line))) {
-                       if (!prefixcmp(line, "shallow ")) {
-                               if (get_sha1_hex(line + 8, sha1))
-                                       die("invalid shallow line: %s", line);
-                               register_shallow(sha1);
-                               continue;
-                       }
-                       if (!prefixcmp(line, "unshallow ")) {
-                               if (get_sha1_hex(line + 10, sha1))
-                                       die("invalid unshallow line: %s", line);
-                               if (!lookup_object(sha1))
-                                       die("object not found: %s", line);
-                               /* make sure that it is parsed as shallow */
-                               if (!parse_object(sha1))
-                                       die("error in object: %s", line);
-                               if (unregister_shallow(sha1))
-                                       die("no shallow found: %s", line);
-                               continue;
-                       }
-                       die("expected shallow/unshallow, got %s", line);
-               }
-       }
-
-       flushes = 0;
-       retval = -1;
-       while ((sha1 = get_rev())) {
-               packet_write(fd[1], "have %s\n", sha1_to_hex(sha1));
-               if (args.verbose)
-                       fprintf(stderr, "have %s\n", sha1_to_hex(sha1));
-               in_vain++;
-               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 (args.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;
-                                       in_vain = 0;
-                                       got_continue = 1;
-                               }
-                       } while (ack);
-                       flushes--;
-                       if (got_continue && MAX_IN_VAIN < in_vain) {
-                               if (args.verbose)
-                                       fprintf(stderr, "giving up\n");
-                               break; /* give up */
-                       }
-               }
-       }
-done:
-       packet_write(fd[1], "done\n");
-       if (args.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 (args.verbose)
-                               fprintf(stderr, "got ack (%d) %s\n", ack,
-                                       sha1_to_hex(result_sha1));
-                       if (ack == 1)
-                               return 0;
-                       multi_ack = 1;
-                       continue;
-               }
-               flushes--;
-       }
-       /* it is no error to fetch into a completely empty repo */
-       return count ? retval : 0;
-}
-
-static struct commit_list *complete;
-
-static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data)
-{
-       struct object *o = parse_object(sha1);
-
-       while (o && o->type == OBJ_TAG) {
-               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 == OBJ_COMMIT) {
-               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 (args.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 && !args.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 (args.fetch_all &&
-                        (!args.depth || prefixcmp(ref->name, "refs/tags/") )) {
-                       *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 (!args.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;
-
-       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 == OBJ_COMMIT) {
-                       struct commit *commit = (struct commit *)o;
-                       if (!cutoff || cutoff < commit->date)
-                               cutoff = commit->date;
-               }
-       }
-
-       if (!args.depth) {
-               for_each_ref(mark_complete, NULL);
-               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 != OBJ_COMMIT || !(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 (!args.verbose)
-                               continue;
-                       fprintf(stderr,
-                               "want %s (%s)\n", sha1_to_hex(remote),
-                               ref->name);
-                       continue;
-               }
-
-               hashcpy(ref->new_sha1, local);
-               if (!args.verbose)
-                       continue;
-               fprintf(stderr,
-                       "already have %s (%s)\n", sha1_to_hex(remote),
-                       ref->name);
-       }
-       return retval;
-}
-
-static int sideband_demux(int fd, void *data)
-{
-       int *xd = data;
-
-       return recv_sideband("fetch-pack", xd[0], fd);
-}
-
-static int get_pack(int xd[2], char **pack_lockfile)
-{
-       struct async demux;
-       const char *argv[20];
-       char keep_arg[256];
-       char hdr_arg[256];
-       const char **av;
-       int do_keep = args.keep_pack;
-       struct child_process cmd;
-
-       memset(&demux, 0, sizeof(demux));
-       if (use_sideband) {
-               /* xd[] is talking with upload-pack; subprocess reads from
-                * xd[0], spits out band#2 to stderr, and feeds us band#1
-                * through demux->out.
-                */
-               demux.proc = sideband_demux;
-               demux.data = xd;
-               if (start_async(&demux))
-                       die("fetch-pack: unable to fork off sideband"
-                           " demultiplexer");
-       }
-       else
-               demux.out = xd[0];
-
-       memset(&cmd, 0, sizeof(cmd));
-       cmd.argv = argv;
-       av = argv;
-       *hdr_arg = 0;
-       if (!args.keep_pack && unpack_limit) {
-               struct pack_header header;
-
-               if (read_pack_header(demux.out, &header))
-                       die("protocol error: bad pack header");
-               snprintf(hdr_arg, sizeof(hdr_arg),
-                        "--pack_header=%"PRIu32",%"PRIu32,
-                        ntohl(header.hdr_version), ntohl(header.hdr_entries));
-               if (ntohl(header.hdr_entries) < unpack_limit)
-                       do_keep = 0;
-               else
-                       do_keep = 1;
-       }
-
-       if (do_keep) {
-               if (pack_lockfile)
-                       cmd.out = -1;
-               *av++ = "index-pack";
-               *av++ = "--stdin";
-               if (!args.quiet && !args.no_progress)
-                       *av++ = "-v";
-               if (args.use_thin_pack)
-                       *av++ = "--fix-thin";
-               if (args.lock_pack || unpack_limit) {
-                       int s = sprintf(keep_arg,
-                                       "--keep=fetch-pack %"PRIuMAX " on ", (uintmax_t) getpid());
-                       if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
-                               strcpy(keep_arg + s, "localhost");
-                       *av++ = keep_arg;
-               }
-       }
-       else {
-               *av++ = "unpack-objects";
-               if (args.quiet)
-                       *av++ = "-q";
-       }
-       if (*hdr_arg)
-               *av++ = hdr_arg;
-       *av++ = NULL;
-
-       cmd.in = demux.out;
-       cmd.git_cmd = 1;
-       if (start_command(&cmd))
-               die("fetch-pack: unable to fork off %s", argv[0]);
-       if (do_keep && pack_lockfile) {
-               *pack_lockfile = index_pack_lockfile(cmd.out);
-               close(cmd.out);
-       }
-
-       if (finish_command(&cmd))
-               die("%s failed", argv[0]);
-       if (use_sideband && finish_async(&demux))
-               die("error in sideband demultiplexer");
-       return 0;
-}
-
-static struct ref *do_fetch_pack(int fd[2],
-               const struct ref *orig_ref,
-               int nr_match,
-               char **match,
-               char **pack_lockfile)
-{
-       struct ref *ref = copy_ref_list(orig_ref);
-       unsigned char sha1[20];
-
-       if (is_repository_shallow() && !server_supports("shallow"))
-               die("Server does not support shallow clients");
-       if (server_supports("multi_ack")) {
-               if (args.verbose)
-                       fprintf(stderr, "Server supports multi_ack\n");
-               multi_ack = 1;
-       }
-       if (server_supports("side-band-64k")) {
-               if (args.verbose)
-                       fprintf(stderr, "Server supports side-band-64k\n");
-               use_sideband = 2;
-       }
-       else if (server_supports("side-band")) {
-               if (args.verbose)
-                       fprintf(stderr, "Server supports side-band\n");
-               use_sideband = 1;
-       }
-       if (server_supports("ofs-delta")) {
-               if (args.verbose)
-                       fprintf(stderr, "Server supports ofs-delta\n");
-       } else
-               prefer_ofs_delta = 0;
-       if (everything_local(&ref, nr_match, match)) {
-               packet_flush(fd[1]);
-               goto all_done;
-       }
-       if (find_common(fd, sha1, ref) < 0)
-               if (!args.keep_pack)
-                       /* When cloning, it is not unusual to have
-                        * no common commit.
-                        */
-                       warning("no common commits");
-
-       if (get_pack(fd, pack_lockfile))
-               die("git fetch-pack: fetch failed.");
-
- all_done:
-       return ref;
-}
-
-static int remove_duplicates(int nr_heads, char **heads)
-{
-       int src, dst;
-
-       for (src = dst = 0; src < nr_heads; src++) {
-               /* If heads[src] is different from any of
-                * heads[0..dst], push it in.
-                */
-               int i;
-               for (i = 0; i < dst; i++) {
-                       if (!strcmp(heads[i], heads[src]))
-                               break;
-               }
-               if (i < dst)
-                       continue;
-               if (src != dst)
-                       heads[dst] = heads[src];
-               dst++;
-       }
-       return dst;
-}
-
-static int fetch_pack_config(const char *var, const char *value, void *cb)
-{
-       if (strcmp(var, "fetch.unpacklimit") == 0) {
-               fetch_unpack_limit = git_config_int(var, value);
-               return 0;
-       }
-
-       if (strcmp(var, "transfer.unpacklimit") == 0) {
-               transfer_unpack_limit = git_config_int(var, value);
-               return 0;
-       }
-
-       if (strcmp(var, "repack.usedeltabaseoffset") == 0) {
-               prefer_ofs_delta = git_config_bool(var, value);
-               return 0;
-       }
-
-       return git_default_config(var, value, cb);
-}
-
-static struct lock_file lock;
-
-static void fetch_pack_setup(void)
-{
-       static int did_setup;
-       if (did_setup)
-               return;
-       git_config(fetch_pack_config, NULL);
-       if (0 <= transfer_unpack_limit)
-               unpack_limit = transfer_unpack_limit;
-       else if (0 <= fetch_unpack_limit)
-               unpack_limit = fetch_unpack_limit;
-       did_setup = 1;
-}
-
-int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
-{
-       int i, ret, nr_heads;
-       struct ref *ref = NULL;
-       char *dest = NULL, **heads;
-       int fd[2];
-       struct child_process *conn;
-
-       nr_heads = 0;
-       heads = NULL;
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-
-               if (*arg == '-') {
-                       if (!prefixcmp(arg, "--upload-pack=")) {
-                               args.uploadpack = arg + 14;
-                               continue;
-                       }
-                       if (!prefixcmp(arg, "--exec=")) {
-                               args.uploadpack = arg + 7;
-                               continue;
-                       }
-                       if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) {
-                               args.quiet = 1;
-                               continue;
-                       }
-                       if (!strcmp("--keep", arg) || !strcmp("-k", arg)) {
-                               args.lock_pack = args.keep_pack;
-                               args.keep_pack = 1;
-                               continue;
-                       }
-                       if (!strcmp("--thin", arg)) {
-                               args.use_thin_pack = 1;
-                               continue;
-                       }
-                       if (!strcmp("--include-tag", arg)) {
-                               args.include_tag = 1;
-                               continue;
-                       }
-                       if (!strcmp("--all", arg)) {
-                               args.fetch_all = 1;
-                               continue;
-                       }
-                       if (!strcmp("-v", arg)) {
-                               args.verbose = 1;
-                               continue;
-                       }
-                       if (!prefixcmp(arg, "--depth=")) {
-                               args.depth = strtol(arg + 8, NULL, 0);
-                               continue;
-                       }
-                       if (!strcmp("--no-progress", arg)) {
-                               args.no_progress = 1;
-                               continue;
-                       }
-                       usage(fetch_pack_usage);
-               }
-               dest = (char *)arg;
-               heads = (char **)(argv + i + 1);
-               nr_heads = argc - i - 1;
-               break;
-       }
-       if (!dest)
-               usage(fetch_pack_usage);
-
-       conn = git_connect(fd, (char *)dest, args.uploadpack,
-                          args.verbose ? CONNECT_VERBOSE : 0);
-       if (conn) {
-               get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL);
-
-               ref = fetch_pack(&args, fd, conn, ref, dest, nr_heads, heads, NULL);
-               close(fd[0]);
-               close(fd[1]);
-               if (finish_connect(conn))
-                       ref = NULL;
-       } else {
-               ref = NULL;
-       }
-       ret = !ref;
-
-       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;
-                       }
-       }
-       while (ref) {
-               printf("%s %s\n",
-                      sha1_to_hex(ref->old_sha1), ref->name);
-               ref = ref->next;
-       }
-
-       return ret;
-}
-
-struct ref *fetch_pack(struct fetch_pack_args *my_args,
-                      int fd[], struct child_process *conn,
-                      const struct ref *ref,
-               const char *dest,
-               int nr_heads,
-               char **heads,
-               char **pack_lockfile)
-{
-       struct stat st;
-       struct ref *ref_cpy;
-
-       fetch_pack_setup();
-       if (&args != my_args)
-               memcpy(&args, my_args, sizeof(args));
-       if (args.depth > 0) {
-               if (stat(git_path("shallow"), &st))
-                       st.st_mtime = 0;
-       }
-
-       if (heads && nr_heads)
-               nr_heads = remove_duplicates(nr_heads, heads);
-       if (!ref) {
-               packet_flush(fd[1]);
-               die("no matching remote head");
-       }
-       ref_cpy = do_fetch_pack(fd, ref, nr_heads, heads, pack_lockfile);
-
-       if (args.depth > 0) {
-               struct cache_time mtime;
-               char *shallow = git_path("shallow");
-               int fd;
-
-               mtime.sec = st.st_mtime;
-               mtime.nsec = ST_MTIME_NSEC(st);
-               if (stat(shallow, &st)) {
-                       if (mtime.sec)
-                               die("shallow file was removed during fetch");
-               } else if (st.st_mtime != mtime.sec
-#ifdef USE_NSEC
-                               || ST_MTIME_NSEC(st) != mtime.nsec
-#endif
-                         )
-                       die("shallow file was changed during fetch");
-
-               fd = hold_lock_file_for_update(&lock, shallow,
-                                              LOCK_DIE_ON_ERROR);
-               if (!write_shallow_commits(fd, 0)) {
-                       unlink(shallow);
-                       rollback_lock_file(&lock);
-               } else {
-                       commit_lock_file(&lock);
-               }
-       }
-
-       reprepare_packed_git();
-       return ref_cpy;
-}
diff --git a/builtin-fetch.c b/builtin-fetch.c
deleted file mode 100644 (file)
index 3c998ea..0000000
+++ /dev/null
@@ -1,680 +0,0 @@
-/*
- * "git fetch"
- */
-#include "cache.h"
-#include "refs.h"
-#include "commit.h"
-#include "builtin.h"
-#include "string-list.h"
-#include "remote.h"
-#include "transport.h"
-#include "run-command.h"
-#include "parse-options.h"
-#include "sigchain.h"
-
-static const char * const builtin_fetch_usage[] = {
-       "git fetch [options] [<repository> <refspec>...]",
-       NULL
-};
-
-enum {
-       TAGS_UNSET = 0,
-       TAGS_DEFAULT = 1,
-       TAGS_SET = 2
-};
-
-static int append, force, keep, update_head_ok, verbosity;
-static int tags = TAGS_DEFAULT;
-static const char *depth;
-static const char *upload_pack;
-static struct strbuf default_rla = STRBUF_INIT;
-static struct transport *transport;
-
-static struct option builtin_fetch_options[] = {
-       OPT__VERBOSITY(&verbosity),
-       OPT_BOOLEAN('a', "append", &append,
-                   "append to .git/FETCH_HEAD instead of overwriting"),
-       OPT_STRING(0, "upload-pack", &upload_pack, "PATH",
-                  "path to upload pack on remote end"),
-       OPT_BOOLEAN('f', "force", &force,
-                   "force overwrite of local branch"),
-       OPT_SET_INT('t', "tags", &tags,
-                   "fetch all tags and associated objects", TAGS_SET),
-       OPT_SET_INT('n', NULL, &tags,
-                   "do not fetch all tags (--no-tags)", TAGS_UNSET),
-       OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
-       OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
-                   "allow updating of HEAD ref"),
-       OPT_STRING(0, "depth", &depth, "DEPTH",
-                  "deepen history of shallow clone"),
-       OPT_END()
-};
-
-static void unlock_pack(void)
-{
-       if (transport)
-               transport_unlock_pack(transport);
-}
-
-static void unlock_pack_on_signal(int signo)
-{
-       unlock_pack();
-       sigchain_pop(signo);
-       raise(signo);
-}
-
-static void add_merge_config(struct ref **head,
-                          const struct ref *remote_refs,
-                          struct branch *branch,
-                          struct ref ***tail)
-{
-       int i;
-
-       for (i = 0; i < branch->merge_nr; i++) {
-               struct ref *rm, **old_tail = *tail;
-               struct refspec refspec;
-
-               for (rm = *head; rm; rm = rm->next) {
-                       if (branch_merge_matches(branch, i, rm->name)) {
-                               rm->merge = 1;
-                               break;
-                       }
-               }
-               if (rm)
-                       continue;
-
-               /*
-                * Not fetched to a tracking branch?  We need to fetch
-                * it anyway to allow this branch's "branch.$name.merge"
-                * to be honored by 'git pull', but we do not have to
-                * fail if branch.$name.merge is misconfigured to point
-                * at a nonexisting branch.  If we were indeed called by
-                * 'git pull', it will notice the misconfiguration because
-                * there is no entry in the resulting FETCH_HEAD marked
-                * for merging.
-                */
-               refspec.src = branch->merge[i]->src;
-               refspec.dst = NULL;
-               refspec.pattern = 0;
-               refspec.force = 0;
-               get_fetch_map(remote_refs, &refspec, tail, 1);
-               for (rm = *old_tail; rm; rm = rm->next)
-                       rm->merge = 1;
-       }
-}
-
-static void find_non_local_tags(struct transport *transport,
-                       struct ref **head,
-                       struct ref ***tail);
-
-static struct ref *get_ref_map(struct transport *transport,
-                              struct refspec *refs, int ref_count, int tags,
-                              int *autotags)
-{
-       int i;
-       struct ref *rm;
-       struct ref *ref_map = NULL;
-       struct ref **tail = &ref_map;
-
-       const struct ref *remote_refs = transport_get_remote_refs(transport);
-
-       if (ref_count || tags == TAGS_SET) {
-               for (i = 0; i < ref_count; i++) {
-                       get_fetch_map(remote_refs, &refs[i], &tail, 0);
-                       if (refs[i].dst && refs[i].dst[0])
-                               *autotags = 1;
-               }
-               /* Merge everything on the command line, but not --tags */
-               for (rm = ref_map; rm; rm = rm->next)
-                       rm->merge = 1;
-               if (tags == TAGS_SET)
-                       get_fetch_map(remote_refs, tag_refspec, &tail, 0);
-       } else {
-               /* Use the defaults */
-               struct remote *remote = transport->remote;
-               struct branch *branch = branch_get(NULL);
-               int has_merge = branch_has_merge_config(branch);
-               if (remote && (remote->fetch_refspec_nr || has_merge)) {
-                       for (i = 0; i < remote->fetch_refspec_nr; i++) {
-                               get_fetch_map(remote_refs, &remote->fetch[i], &tail, 0);
-                               if (remote->fetch[i].dst &&
-                                   remote->fetch[i].dst[0])
-                                       *autotags = 1;
-                               if (!i && !has_merge && ref_map &&
-                                   !remote->fetch[0].pattern)
-                                       ref_map->merge = 1;
-                       }
-                       /*
-                        * if the remote we're fetching from is the same
-                        * as given in branch.<name>.remote, we add the
-                        * ref given in branch.<name>.merge, too.
-                        */
-                       if (has_merge &&
-                           !strcmp(branch->remote_name, remote->name))
-                               add_merge_config(&ref_map, remote_refs, branch, &tail);
-               } else {
-                       ref_map = get_remote_ref(remote_refs, "HEAD");
-                       if (!ref_map)
-                               die("Couldn't find remote ref HEAD");
-                       ref_map->merge = 1;
-                       tail = &ref_map->next;
-               }
-       }
-       if (tags == TAGS_DEFAULT && *autotags)
-               find_non_local_tags(transport, &ref_map, &tail);
-       ref_remove_duplicates(ref_map);
-
-       return ref_map;
-}
-
-static int s_update_ref(const char *action,
-                       struct ref *ref,
-                       int check_old)
-{
-       char msg[1024];
-       char *rla = getenv("GIT_REFLOG_ACTION");
-       static struct ref_lock *lock;
-
-       if (!rla)
-               rla = default_rla.buf;
-       snprintf(msg, sizeof(msg), "%s: %s", rla, action);
-       lock = lock_any_ref_for_update(ref->name,
-                                      check_old ? ref->old_sha1 : NULL, 0);
-       if (!lock)
-               return 2;
-       if (write_ref_sha1(lock, ref->new_sha1, msg) < 0)
-               return 2;
-       return 0;
-}
-
-#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
-#define REFCOL_WIDTH  10
-
-static int update_local_ref(struct ref *ref,
-                           const char *remote,
-                           char *display)
-{
-       struct commit *current = NULL, *updated;
-       enum object_type type;
-       struct branch *current_branch = branch_get(NULL);
-       const char *pretty_ref = prettify_ref(ref);
-
-       *display = 0;
-       type = sha1_object_info(ref->new_sha1, NULL);
-       if (type < 0)
-               die("object %s not found", sha1_to_hex(ref->new_sha1));
-
-       if (!hashcmp(ref->old_sha1, ref->new_sha1)) {
-               if (verbosity > 0)
-                       sprintf(display, "= %-*s %-*s -> %s", SUMMARY_WIDTH,
-                               "[up to date]", REFCOL_WIDTH, remote,
-                               pretty_ref);
-               return 0;
-       }
-
-       if (current_branch &&
-           !strcmp(ref->name, current_branch->name) &&
-           !(update_head_ok || is_bare_repository()) &&
-           !is_null_sha1(ref->old_sha1)) {
-               /*
-                * If this is the head, and it's not okay to update
-                * the head, and the old value of the head isn't empty...
-                */
-               sprintf(display, "! %-*s %-*s -> %s  (can't fetch in current branch)",
-                       SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
-                       pretty_ref);
-               return 1;
-       }
-
-       if (!is_null_sha1(ref->old_sha1) &&
-           !prefixcmp(ref->name, "refs/tags/")) {
-               int r;
-               r = s_update_ref("updating tag", ref, 0);
-               sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '-',
-                       SUMMARY_WIDTH, "[tag update]", REFCOL_WIDTH, remote,
-                       pretty_ref, r ? "  (unable to update local ref)" : "");
-               return r;
-       }
-
-       current = lookup_commit_reference_gently(ref->old_sha1, 1);
-       updated = lookup_commit_reference_gently(ref->new_sha1, 1);
-       if (!current || !updated) {
-               const char *msg;
-               const char *what;
-               int r;
-               if (!strncmp(ref->name, "refs/tags/", 10)) {
-                       msg = "storing tag";
-                       what = "[new tag]";
-               }
-               else {
-                       msg = "storing head";
-                       what = "[new branch]";
-               }
-
-               r = s_update_ref(msg, ref, 0);
-               sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '*',
-                       SUMMARY_WIDTH, what, REFCOL_WIDTH, remote, pretty_ref,
-                       r ? "  (unable to update local ref)" : "");
-               return r;
-       }
-
-       if (in_merge_bases(current, &updated, 1)) {
-               char quickref[83];
-               int r;
-               strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
-               strcat(quickref, "..");
-               strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
-               r = s_update_ref("fast forward", ref, 1);
-               sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ',
-                       SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
-                       pretty_ref, r ? "  (unable to update local ref)" : "");
-               return r;
-       } else if (force || ref->force) {
-               char quickref[84];
-               int r;
-               strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
-               strcat(quickref, "...");
-               strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
-               r = s_update_ref("forced-update", ref, 1);
-               sprintf(display, "%c %-*s %-*s -> %s  (%s)", r ? '!' : '+',
-                       SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
-                       pretty_ref,
-                       r ? "unable to update local ref" : "forced update");
-               return r;
-       } else {
-               sprintf(display, "! %-*s %-*s -> %s  (non fast forward)",
-                       SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
-                       pretty_ref);
-               return 1;
-       }
-}
-
-static int store_updated_refs(const char *url, const char *remote_name,
-               struct ref *ref_map)
-{
-       FILE *fp;
-       struct commit *commit;
-       int url_len, i, note_len, shown_url = 0, rc = 0;
-       char note[1024];
-       const char *what, *kind;
-       struct ref *rm;
-       char *filename = git_path("FETCH_HEAD");
-
-       fp = fopen(filename, "a");
-       if (!fp)
-               return error("cannot open %s: %s\n", filename, strerror(errno));
-       for (rm = ref_map; rm; rm = rm->next) {
-               struct ref *ref = NULL;
-
-               if (rm->peer_ref) {
-                       ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1);
-                       strcpy(ref->name, rm->peer_ref->name);
-                       hashcpy(ref->old_sha1, rm->peer_ref->old_sha1);
-                       hashcpy(ref->new_sha1, rm->old_sha1);
-                       ref->force = rm->peer_ref->force;
-               }
-
-               commit = lookup_commit_reference_gently(rm->old_sha1, 1);
-               if (!commit)
-                       rm->merge = 0;
-
-               if (!strcmp(rm->name, "HEAD")) {
-                       kind = "";
-                       what = "";
-               }
-               else if (!prefixcmp(rm->name, "refs/heads/")) {
-                       kind = "branch";
-                       what = rm->name + 11;
-               }
-               else if (!prefixcmp(rm->name, "refs/tags/")) {
-                       kind = "tag";
-                       what = rm->name + 10;
-               }
-               else if (!prefixcmp(rm->name, "refs/remotes/")) {
-                       kind = "remote branch";
-                       what = rm->name + 13;
-               }
-               else {
-                       kind = "";
-                       what = rm->name;
-               }
-
-               url_len = strlen(url);
-               for (i = url_len - 1; url[i] == '/' && 0 <= i; i--)
-                       ;
-               url_len = i + 1;
-               if (4 < i && !strncmp(".git", url + i - 3, 4))
-                       url_len = i - 3;
-
-               note_len = 0;
-               if (*what) {
-                       if (*kind)
-                               note_len += sprintf(note + note_len, "%s ",
-                                                   kind);
-                       note_len += sprintf(note + note_len, "'%s' of ", what);
-               }
-               note_len += sprintf(note + note_len, "%.*s", url_len, url);
-               fprintf(fp, "%s\t%s\t%s\n",
-                       sha1_to_hex(commit ? commit->object.sha1 :
-                                   rm->old_sha1),
-                       rm->merge ? "" : "not-for-merge",
-                       note);
-
-               if (ref)
-                       rc |= update_local_ref(ref, what, note);
-               else
-                       sprintf(note, "* %-*s %-*s -> FETCH_HEAD",
-                               SUMMARY_WIDTH, *kind ? kind : "branch",
-                                REFCOL_WIDTH, *what ? what : "HEAD");
-               if (*note) {
-                       if (verbosity >= 0 && !shown_url) {
-                               fprintf(stderr, "From %.*s\n",
-                                               url_len, url);
-                               shown_url = 1;
-                       }
-                       if (verbosity >= 0)
-                               fprintf(stderr, " %s\n", note);
-               }
-       }
-       fclose(fp);
-       if (rc & 2)
-               error("some local refs could not be updated; try running\n"
-                     " 'git remote prune %s' to remove any old, conflicting "
-                     "branches", remote_name);
-       return rc;
-}
-
-/*
- * We would want to bypass the object transfer altogether if
- * everything we are going to fetch already exists and connected
- * locally.
- *
- * The refs we are going to fetch are in to_fetch (nr_heads in
- * total).  If running
- *
- *  $ git rev-list --objects to_fetch[0] to_fetch[1] ... --not --all
- *
- * does not error out, that means everything reachable from the
- * refs we are going to fetch exists and is connected to some of
- * our existing refs.
- */
-static int quickfetch(struct ref *ref_map)
-{
-       struct child_process revlist;
-       struct ref *ref;
-       char **argv;
-       int i, err;
-
-       /*
-        * If we are deepening a shallow clone we already have these
-        * objects reachable.  Running rev-list here will return with
-        * a good (0) exit status and we'll bypass the fetch that we
-        * really need to perform.  Claiming failure now will ensure
-        * we perform the network exchange to deepen our history.
-        */
-       if (depth)
-               return -1;
-
-       for (i = 0, ref = ref_map; ref; ref = ref->next)
-               i++;
-       if (!i)
-               return 0;
-
-       argv = xmalloc(sizeof(*argv) * (i + 6));
-       i = 0;
-       argv[i++] = xstrdup("rev-list");
-       argv[i++] = xstrdup("--quiet");
-       argv[i++] = xstrdup("--objects");
-       for (ref = ref_map; ref; ref = ref->next)
-               argv[i++] = xstrdup(sha1_to_hex(ref->old_sha1));
-       argv[i++] = xstrdup("--not");
-       argv[i++] = xstrdup("--all");
-       argv[i++] = NULL;
-
-       memset(&revlist, 0, sizeof(revlist));
-       revlist.argv = (const char**)argv;
-       revlist.git_cmd = 1;
-       revlist.no_stdin = 1;
-       revlist.no_stdout = 1;
-       revlist.no_stderr = 1;
-       err = run_command(&revlist);
-
-       for (i = 0; argv[i]; i++)
-               free(argv[i]);
-       free(argv);
-       return err;
-}
-
-static int fetch_refs(struct transport *transport, struct ref *ref_map)
-{
-       int ret = quickfetch(ref_map);
-       if (ret)
-               ret = transport_fetch_refs(transport, ref_map);
-       if (!ret)
-               ret |= store_updated_refs(transport->url,
-                               transport->remote->name,
-                               ref_map);
-       transport_unlock_pack(transport);
-       return ret;
-}
-
-static int add_existing(const char *refname, const unsigned char *sha1,
-                       int flag, void *cbdata)
-{
-       struct string_list *list = (struct string_list *)cbdata;
-       string_list_insert(refname, list);
-       return 0;
-}
-
-static int will_fetch(struct ref **head, const unsigned char *sha1)
-{
-       struct ref *rm = *head;
-       while (rm) {
-               if (!hashcmp(rm->old_sha1, sha1))
-                       return 1;
-               rm = rm->next;
-       }
-       return 0;
-}
-
-static void find_non_local_tags(struct transport *transport,
-                       struct ref **head,
-                       struct ref ***tail)
-{
-       struct string_list existing_refs = { NULL, 0, 0, 0 };
-       struct string_list new_refs = { NULL, 0, 0, 1 };
-       char *ref_name;
-       int ref_name_len;
-       const unsigned char *ref_sha1;
-       const struct ref *tag_ref;
-       struct ref *rm = NULL;
-       const struct ref *ref;
-
-       for_each_ref(add_existing, &existing_refs);
-       for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
-               if (prefixcmp(ref->name, "refs/tags"))
-                       continue;
-
-               ref_name = xstrdup(ref->name);
-               ref_name_len = strlen(ref_name);
-               ref_sha1 = ref->old_sha1;
-
-               if (!strcmp(ref_name + ref_name_len - 3, "^{}")) {
-                       ref_name[ref_name_len - 3] = 0;
-                       tag_ref = transport_get_remote_refs(transport);
-                       while (tag_ref) {
-                               if (!strcmp(tag_ref->name, ref_name)) {
-                                       ref_sha1 = tag_ref->old_sha1;
-                                       break;
-                               }
-                               tag_ref = tag_ref->next;
-                       }
-               }
-
-               if (!string_list_has_string(&existing_refs, ref_name) &&
-                   !string_list_has_string(&new_refs, ref_name) &&
-                   (has_sha1_file(ref->old_sha1) ||
-                    will_fetch(head, ref->old_sha1))) {
-                       string_list_insert(ref_name, &new_refs);
-
-                       rm = alloc_ref(ref_name);
-                       rm->peer_ref = alloc_ref(ref_name);
-                       hashcpy(rm->old_sha1, ref_sha1);
-
-                       **tail = rm;
-                       *tail = &rm->next;
-               }
-               free(ref_name);
-       }
-       string_list_clear(&existing_refs, 0);
-       string_list_clear(&new_refs, 0);
-}
-
-static void check_not_current_branch(struct ref *ref_map)
-{
-       struct branch *current_branch = branch_get(NULL);
-
-       if (is_bare_repository() || !current_branch)
-               return;
-
-       for (; ref_map; ref_map = ref_map->next)
-               if (ref_map->peer_ref && !strcmp(current_branch->refname,
-                                       ref_map->peer_ref->name))
-                       die("Refusing to fetch into current branch %s "
-                           "of non-bare repository", current_branch->refname);
-}
-
-static int do_fetch(struct transport *transport,
-                   struct refspec *refs, int ref_count)
-{
-       struct ref *ref_map;
-       struct ref *rm;
-       int autotags = (transport->remote->fetch_tags == 1);
-       if (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET)
-               tags = TAGS_SET;
-       if (transport->remote->fetch_tags == -1)
-               tags = TAGS_UNSET;
-
-       if (!transport->get_refs_list || !transport->fetch)
-               die("Don't know how to fetch from %s", transport->url);
-
-       /* if not appending, truncate FETCH_HEAD */
-       if (!append) {
-               char *filename = git_path("FETCH_HEAD");
-               FILE *fp = fopen(filename, "w");
-               if (!fp)
-                       return error("cannot open %s: %s\n", filename, strerror(errno));
-               fclose(fp);
-       }
-
-       ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags);
-       if (!update_head_ok)
-               check_not_current_branch(ref_map);
-
-       for (rm = ref_map; rm; rm = rm->next) {
-               if (rm->peer_ref)
-                       read_ref(rm->peer_ref->name, rm->peer_ref->old_sha1);
-       }
-
-       if (tags == TAGS_DEFAULT && autotags)
-               transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
-       if (fetch_refs(transport, ref_map)) {
-               free_refs(ref_map);
-               return 1;
-       }
-       free_refs(ref_map);
-
-       /* if neither --no-tags nor --tags was specified, do automated tag
-        * following ... */
-       if (tags == TAGS_DEFAULT && autotags) {
-               struct ref **tail = &ref_map;
-               ref_map = NULL;
-               find_non_local_tags(transport, &ref_map, &tail);
-               if (ref_map) {
-                       transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
-                       transport_set_option(transport, TRANS_OPT_DEPTH, "0");
-                       fetch_refs(transport, ref_map);
-               }
-               free_refs(ref_map);
-       }
-
-       return 0;
-}
-
-static void set_option(const char *name, const char *value)
-{
-       int r = transport_set_option(transport, name, value);
-       if (r < 0)
-               die("Option \"%s\" value \"%s\" is not valid for %s",
-                       name, value, transport->url);
-       if (r > 0)
-               warning("Option \"%s\" is ignored for %s\n",
-                       name, transport->url);
-}
-
-int cmd_fetch(int argc, const char **argv, const char *prefix)
-{
-       struct remote *remote;
-       int i;
-       static const char **refs = NULL;
-       int ref_nr = 0;
-       int exit_code;
-
-       /* Record the command line for the reflog */
-       strbuf_addstr(&default_rla, "fetch");
-       for (i = 1; i < argc; i++)
-               strbuf_addf(&default_rla, " %s", argv[i]);
-
-       argc = parse_options(argc, argv,
-                            builtin_fetch_options, builtin_fetch_usage, 0);
-
-       if (argc == 0)
-               remote = remote_get(NULL);
-       else
-               remote = remote_get(argv[0]);
-
-       if (!remote)
-               die("Where do you want to fetch from today?");
-
-       transport = transport_get(remote, remote->url[0]);
-       if (verbosity >= 2)
-               transport->verbose = 1;
-       if (verbosity < 0)
-               transport->verbose = -1;
-       if (upload_pack)
-               set_option(TRANS_OPT_UPLOADPACK, upload_pack);
-       if (keep)
-               set_option(TRANS_OPT_KEEP, "yes");
-       if (depth)
-               set_option(TRANS_OPT_DEPTH, depth);
-
-       if (argc > 1) {
-               int j = 0;
-               refs = xcalloc(argc + 1, sizeof(const char *));
-               for (i = 1; i < argc; i++) {
-                       if (!strcmp(argv[i], "tag")) {
-                               char *ref;
-                               i++;
-                               if (i >= argc)
-                                       die("You need to specify a tag name.");
-                               ref = xmalloc(strlen(argv[i]) * 2 + 22);
-                               strcpy(ref, "refs/tags/");
-                               strcat(ref, argv[i]);
-                               strcat(ref, ":refs/tags/");
-                               strcat(ref, argv[i]);
-                               refs[j++] = ref;
-                       } else
-                               refs[j++] = argv[i];
-               }
-               refs[j] = NULL;
-               ref_nr = j;
-       }
-
-       sigchain_push_common(unlock_pack_on_signal);
-       atexit(unlock_pack);
-       exit_code = do_fetch(transport,
-                       parse_fetch_refspec(ref_nr, refs), ref_nr);
-       transport_disconnect(transport);
-       transport = NULL;
-       return exit_code;
-}
diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c
deleted file mode 100644 (file)
index a788369..0000000
+++ /dev/null
@@ -1,381 +0,0 @@
-#include "builtin.h"
-#include "cache.h"
-#include "commit.h"
-#include "diff.h"
-#include "revision.h"
-#include "tag.h"
-
-static const char * const fmt_merge_msg_usage[] = {
-       "git fmt-merge-msg [--log|--no-log] [--file <file>]",
-       NULL
-};
-
-static int merge_summary;
-
-static int fmt_merge_msg_config(const char *key, const char *value, void *cb)
-{
-       static int found_merge_log = 0;
-       if (!strcmp("merge.log", key)) {
-               found_merge_log = 1;
-               merge_summary = git_config_bool(key, value);
-       }
-       if (!found_merge_log && !strcmp("merge.summary", key))
-               merge_summary = git_config_bool(key, value);
-       return 0;
-}
-
-struct list {
-       char **list;
-       void **payload;
-       unsigned nr, alloc;
-};
-
-static void append_to_list(struct list *list, char *value, void *payload)
-{
-       if (list->nr == list->alloc) {
-               list->alloc += 32;
-               list->list = xrealloc(list->list, sizeof(char *) * list->alloc);
-               list->payload = xrealloc(list->payload,
-                               sizeof(char *) * list->alloc);
-       }
-       list->payload[list->nr] = payload;
-       list->list[list->nr++] = value;
-}
-
-static int find_in_list(struct list *list, char *value)
-{
-       int i;
-
-       for (i = 0; i < list->nr; i++)
-               if (!strcmp(list->list[i], value))
-                       return i;
-
-       return -1;
-}
-
-static void free_list(struct list *list)
-{
-       int i;
-
-       if (list->alloc == 0)
-               return;
-
-       for (i = 0; i < list->nr; i++) {
-               free(list->list[i]);
-               free(list->payload[i]);
-       }
-       free(list->list);
-       free(list->payload);
-       list->nr = list->alloc = 0;
-}
-
-struct src_data {
-       struct list branch, tag, r_branch, generic;
-       int head_status;
-};
-
-static struct list srcs = { NULL, NULL, 0, 0};
-static struct list origins = { NULL, NULL, 0, 0};
-
-static int handle_line(char *line)
-{
-       int i, len = strlen(line);
-       unsigned char *sha1;
-       char *src, *origin;
-       struct src_data *src_data;
-       int pulling_head = 0;
-
-       if (len < 43 || line[40] != '\t')
-               return 1;
-
-       if (!prefixcmp(line + 41, "not-for-merge"))
-               return 0;
-
-       if (line[41] != '\t')
-               return 2;
-
-       line[40] = 0;
-       sha1 = xmalloc(20);
-       i = get_sha1(line, sha1);
-       line[40] = '\t';
-       if (i)
-               return 3;
-
-       if (line[len - 1] == '\n')
-               line[len - 1] = 0;
-       line += 42;
-
-       src = strstr(line, " of ");
-       if (src) {
-               *src = 0;
-               src += 4;
-               pulling_head = 0;
-       } else {
-               src = line;
-               pulling_head = 1;
-       }
-
-       i = find_in_list(&srcs, src);
-       if (i < 0) {
-               i = srcs.nr;
-               append_to_list(&srcs, xstrdup(src),
-                               xcalloc(1, sizeof(struct src_data)));
-       }
-       src_data = srcs.payload[i];
-
-       if (pulling_head) {
-               origin = xstrdup(src);
-               src_data->head_status |= 1;
-       } else if (!prefixcmp(line, "branch ")) {
-               origin = xstrdup(line + 7);
-               append_to_list(&src_data->branch, origin, NULL);
-               src_data->head_status |= 2;
-       } else if (!prefixcmp(line, "tag ")) {
-               origin = line;
-               append_to_list(&src_data->tag, xstrdup(origin + 4), NULL);
-               src_data->head_status |= 2;
-       } else if (!prefixcmp(line, "remote branch ")) {
-               origin = xstrdup(line + 14);
-               append_to_list(&src_data->r_branch, origin, NULL);
-               src_data->head_status |= 2;
-       } else {
-               origin = xstrdup(src);
-               append_to_list(&src_data->generic, xstrdup(line), NULL);
-               src_data->head_status |= 2;
-       }
-
-       if (!strcmp(".", src) || !strcmp(src, origin)) {
-               int len = strlen(origin);
-               if (origin[0] == '\'' && origin[len - 1] == '\'') {
-                       origin = xmemdupz(origin + 1, len - 2);
-               } else {
-                       origin = xstrdup(origin);
-               }
-       } else {
-               char *new_origin = xmalloc(strlen(origin) + strlen(src) + 5);
-               sprintf(new_origin, "%s of %s", origin, src);
-               origin = new_origin;
-       }
-       append_to_list(&origins, origin, sha1);
-       return 0;
-}
-
-static void print_joined(const char *singular, const char *plural,
-               struct list *list, struct strbuf *out)
-{
-       if (list->nr == 0)
-               return;
-       if (list->nr == 1) {
-               strbuf_addf(out, "%s%s", singular, list->list[0]);
-       } else {
-               int i;
-               strbuf_addstr(out, plural);
-               for (i = 0; i < list->nr - 1; i++)
-                       strbuf_addf(out, "%s%s", i > 0 ? ", " : "", list->list[i]);
-               strbuf_addf(out, " and %s", list->list[list->nr - 1]);
-       }
-}
-
-static void shortlog(const char *name, unsigned char *sha1,
-               struct commit *head, struct rev_info *rev, int limit,
-               struct strbuf *out)
-{
-       int i, count = 0;
-       struct commit *commit;
-       struct object *branch;
-       struct list subjects = { NULL, NULL, 0, 0 };
-       int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
-
-       branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40);
-       if (!branch || branch->type != OBJ_COMMIT)
-               return;
-
-       setup_revisions(0, NULL, rev, NULL);
-       rev->ignore_merges = 1;
-       add_pending_object(rev, branch, name);
-       add_pending_object(rev, &head->object, "^HEAD");
-       head->object.flags |= UNINTERESTING;
-       if (prepare_revision_walk(rev))
-               die("revision walk setup failed");
-       while ((commit = get_revision(rev)) != NULL) {
-               char *oneline, *bol, *eol;
-
-               /* ignore merges */
-               if (commit->parents && commit->parents->next)
-                       continue;
-
-               count++;
-               if (subjects.nr > limit)
-                       continue;
-
-               bol = strstr(commit->buffer, "\n\n");
-               if (bol) {
-                       unsigned char c;
-                       do {
-                               c = *++bol;
-                       } while (isspace(c));
-                       if (!c)
-                               bol = NULL;
-               }
-
-               if (!bol) {
-                       append_to_list(&subjects, xstrdup(sha1_to_hex(
-                                                       commit->object.sha1)),
-                                       NULL);
-                       continue;
-               }
-
-               eol = strchr(bol, '\n');
-               if (eol) {
-                       oneline = xmemdupz(bol, eol - bol);
-               } else {
-                       oneline = xstrdup(bol);
-               }
-               append_to_list(&subjects, oneline, NULL);
-       }
-
-       if (count > limit)
-               strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
-       else
-               strbuf_addf(out, "\n* %s:\n", name);
-
-       for (i = 0; i < subjects.nr; i++)
-               if (i >= limit)
-                       strbuf_addf(out, "  ...\n");
-               else
-                       strbuf_addf(out, "  %s\n", subjects.list[i]);
-
-       clear_commit_marks((struct commit *)branch, flags);
-       clear_commit_marks(head, flags);
-       free_commit_list(rev->commits);
-       rev->commits = NULL;
-       rev->pending.nr = 0;
-
-       free_list(&subjects);
-}
-
-int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) {
-       int limit = 20, i = 0, pos = 0;
-       char *sep = "";
-       unsigned char head_sha1[20];
-       const char *current_branch;
-
-       /* get current branch */
-       current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);
-       if (!current_branch)
-               die("No current branch");
-       if (!prefixcmp(current_branch, "refs/heads/"))
-               current_branch += 11;
-
-       /* get a line */
-       while (pos < in->len) {
-               int len;
-               char *newline, *p = in->buf + pos;
-
-               newline = strchr(p, '\n');
-               len = newline ? newline - p : strlen(p);
-               pos += len + !!newline;
-               i++;
-               p[len] = 0;
-               if (handle_line(p))
-                       die ("Error in line %d: %.*s", i, len, p);
-       }
-
-       strbuf_addstr(out, "Merge ");
-       for (i = 0; i < srcs.nr; i++) {
-               struct src_data *src_data = srcs.payload[i];
-               const char *subsep = "";
-
-               strbuf_addstr(out, sep);
-               sep = "; ";
-
-               if (src_data->head_status == 1) {
-                       strbuf_addstr(out, srcs.list[i]);
-                       continue;
-               }
-               if (src_data->head_status == 3) {
-                       subsep = ", ";
-                       strbuf_addstr(out, "HEAD");
-               }
-               if (src_data->branch.nr) {
-                       strbuf_addstr(out, subsep);
-                       subsep = ", ";
-                       print_joined("branch ", "branches ", &src_data->branch,
-                                       out);
-               }
-               if (src_data->r_branch.nr) {
-                       strbuf_addstr(out, subsep);
-                       subsep = ", ";
-                       print_joined("remote branch ", "remote branches ",
-                                       &src_data->r_branch, out);
-               }
-               if (src_data->tag.nr) {
-                       strbuf_addstr(out, subsep);
-                       subsep = ", ";
-                       print_joined("tag ", "tags ", &src_data->tag, out);
-               }
-               if (src_data->generic.nr) {
-                       strbuf_addstr(out, subsep);
-                       print_joined("commit ", "commits ", &src_data->generic,
-                                       out);
-               }
-               if (strcmp(".", srcs.list[i]))
-                       strbuf_addf(out, " of %s", srcs.list[i]);
-       }
-
-       if (!strcmp("master", current_branch))
-               strbuf_addch(out, '\n');
-       else
-               strbuf_addf(out, " into %s\n", current_branch);
-
-       if (merge_summary) {
-               struct commit *head;
-               struct rev_info rev;
-
-               head = lookup_commit(head_sha1);
-               init_revisions(&rev, NULL);
-               rev.commit_format = CMIT_FMT_ONELINE;
-               rev.ignore_merges = 1;
-               rev.limited = 1;
-
-               for (i = 0; i < origins.nr; i++)
-                       shortlog(origins.list[i], origins.payload[i],
-                                       head, &rev, limit, out);
-       }
-       return 0;
-}
-
-int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
-{
-       const char *inpath = NULL;
-       struct option options[] = {
-               OPT_BOOLEAN(0, "log",     &merge_summary, "populate log with the shortlog"),
-               OPT_BOOLEAN(0, "summary", &merge_summary, "alias for --log"),
-               OPT_STRING('F', "file",   &inpath, "file", "file to read from"),
-               OPT_END()
-       };
-
-       FILE *in = stdin;
-       struct strbuf input = STRBUF_INIT, output = STRBUF_INIT;
-       int ret;
-
-       git_config(fmt_merge_msg_config, NULL);
-       argc = parse_options(argc, argv, options, fmt_merge_msg_usage, 0);
-       if (argc > 0)
-               usage_with_options(fmt_merge_msg_usage, options);
-
-       if (inpath && strcmp(inpath, "-")) {
-               in = fopen(inpath, "r");
-               if (!in)
-                       die("cannot open %s", inpath);
-       }
-
-       if (strbuf_read(&input, fileno(in), 0) < 0)
-               die("could not read input file %s", strerror(errno));
-
-       ret = fmt_merge_msg(merge_summary, &input, &output);
-       if (ret)
-               return ret;
-       write_in_full(STDOUT_FILENO, output.buf, output.len);
-       return 0;
-}
diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c
deleted file mode 100644 (file)
index 91e8f95..0000000
+++ /dev/null
@@ -1,944 +0,0 @@
-#include "builtin.h"
-#include "cache.h"
-#include "refs.h"
-#include "object.h"
-#include "tag.h"
-#include "commit.h"
-#include "tree.h"
-#include "blob.h"
-#include "quote.h"
-#include "parse-options.h"
-#include "remote.h"
-
-/* Quoting styles */
-#define QUOTE_NONE 0
-#define QUOTE_SHELL 1
-#define QUOTE_PERL 2
-#define QUOTE_PYTHON 4
-#define QUOTE_TCL 8
-
-typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
-
-struct atom_value {
-       const char *s;
-       unsigned long ul; /* used for sorting when not FIELD_STR */
-};
-
-struct ref_sort {
-       struct ref_sort *next;
-       int atom; /* index into used_atom array */
-       unsigned reverse : 1;
-};
-
-struct refinfo {
-       char *refname;
-       unsigned char objectname[20];
-       struct atom_value *value;
-};
-
-static struct {
-       const char *name;
-       cmp_type cmp_type;
-} valid_atom[] = {
-       { "refname" },
-       { "objecttype" },
-       { "objectsize", FIELD_ULONG },
-       { "objectname" },
-       { "tree" },
-       { "parent" },
-       { "numparent", FIELD_ULONG },
-       { "object" },
-       { "type" },
-       { "tag" },
-       { "author" },
-       { "authorname" },
-       { "authoremail" },
-       { "authordate", FIELD_TIME },
-       { "committer" },
-       { "committername" },
-       { "committeremail" },
-       { "committerdate", FIELD_TIME },
-       { "tagger" },
-       { "taggername" },
-       { "taggeremail" },
-       { "taggerdate", FIELD_TIME },
-       { "creator" },
-       { "creatordate", FIELD_TIME },
-       { "subject" },
-       { "body" },
-       { "contents" },
-       { "upstream" },
-};
-
-/*
- * An atom is a valid field atom listed above, possibly prefixed with
- * a "*" to denote deref_tag().
- *
- * We parse given format string and sort specifiers, and make a list
- * of properties that we need to extract out of objects.  refinfo
- * structure will hold an array of values extracted that can be
- * indexed with the "atom number", which is an index into this
- * array.
- */
-static const char **used_atom;
-static cmp_type *used_atom_type;
-static int used_atom_cnt, sort_atom_limit, need_tagged;
-
-/*
- * Used to parse format string and sort specifiers
- */
-static int parse_atom(const char *atom, const char *ep)
-{
-       const char *sp;
-       int i, at;
-
-       sp = atom;
-       if (*sp == '*' && sp < ep)
-               sp++; /* deref */
-       if (ep <= sp)
-               die("malformed field name: %.*s", (int)(ep-atom), atom);
-
-       /* Do we have the atom already used elsewhere? */
-       for (i = 0; i < used_atom_cnt; i++) {
-               int len = strlen(used_atom[i]);
-               if (len == ep - atom && !memcmp(used_atom[i], atom, len))
-                       return i;
-       }
-
-       /* Is the atom a valid one? */
-       for (i = 0; i < ARRAY_SIZE(valid_atom); i++) {
-               int len = strlen(valid_atom[i].name);
-               /*
-                * If the atom name has a colon, strip it and everything after
-                * it off - it specifies the format for this entry, and
-                * shouldn't be used for checking against the valid_atom
-                * table.
-                */
-               const char *formatp = strchr(sp, ':');
-               if (!formatp || ep < formatp)
-                       formatp = ep;
-               if (len == formatp - sp && !memcmp(valid_atom[i].name, sp, len))
-                       break;
-       }
-
-       if (ARRAY_SIZE(valid_atom) <= i)
-               die("unknown field name: %.*s", (int)(ep-atom), atom);
-
-       /* Add it in, including the deref prefix */
-       at = used_atom_cnt;
-       used_atom_cnt++;
-       used_atom = xrealloc(used_atom,
-                            (sizeof *used_atom) * used_atom_cnt);
-       used_atom_type = xrealloc(used_atom_type,
-                                 (sizeof(*used_atom_type) * used_atom_cnt));
-       used_atom[at] = xmemdupz(atom, ep - atom);
-       used_atom_type[at] = valid_atom[i].cmp_type;
-       return at;
-}
-
-/*
- * In a format string, find the next occurrence of %(atom).
- */
-static const char *find_next(const char *cp)
-{
-       while (*cp) {
-               if (*cp == '%') {
-                       /* %( is the start of an atom;
-                        * %% is a quoted per-cent.
-                        */
-                       if (cp[1] == '(')
-                               return cp;
-                       else if (cp[1] == '%')
-                               cp++; /* skip over two % */
-                       /* otherwise this is a singleton, literal % */
-               }
-               cp++;
-       }
-       return NULL;
-}
-
-/*
- * Make sure the format string is well formed, and parse out
- * the used atoms.
- */
-static int verify_format(const char *format)
-{
-       const char *cp, *sp;
-       for (cp = format; *cp && (sp = find_next(cp)); ) {
-               const char *ep = strchr(sp, ')');
-               if (!ep)
-                       return error("malformed format string %s", sp);
-               /* sp points at "%(" and ep points at the closing ")" */
-               parse_atom(sp + 2, ep);
-               cp = ep + 1;
-       }
-       return 0;
-}
-
-/*
- * Given an object name, read the object data and size, and return a
- * "struct object".  If the object data we are returning is also borrowed
- * by the "struct object" representation, set *eaten as well---it is a
- * signal from parse_object_buffer to us not to free the buffer.
- */
-static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned long *sz, int *eaten)
-{
-       enum object_type type;
-       void *buf = read_sha1_file(sha1, &type, sz);
-
-       if (buf)
-               *obj = parse_object_buffer(sha1, type, *sz, buf, eaten);
-       else
-               *obj = NULL;
-       return buf;
-}
-
-/* See grab_values */
-static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
-{
-       int i;
-
-       for (i = 0; i < used_atom_cnt; i++) {
-               const char *name = used_atom[i];
-               struct atom_value *v = &val[i];
-               if (!!deref != (*name == '*'))
-                       continue;
-               if (deref)
-                       name++;
-               if (!strcmp(name, "objecttype"))
-                       v->s = typename(obj->type);
-               else if (!strcmp(name, "objectsize")) {
-                       char *s = xmalloc(40);
-                       sprintf(s, "%lu", sz);
-                       v->ul = sz;
-                       v->s = s;
-               }
-               else if (!strcmp(name, "objectname")) {
-                       char *s = xmalloc(41);
-                       strcpy(s, sha1_to_hex(obj->sha1));
-                       v->s = s;
-               }
-       }
-}
-
-/* See grab_values */
-static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
-{
-       int i;
-       struct tag *tag = (struct tag *) obj;
-
-       for (i = 0; i < used_atom_cnt; i++) {
-               const char *name = used_atom[i];
-               struct atom_value *v = &val[i];
-               if (!!deref != (*name == '*'))
-                       continue;
-               if (deref)
-                       name++;
-               if (!strcmp(name, "tag"))
-                       v->s = tag->tag;
-               else if (!strcmp(name, "type") && tag->tagged)
-                       v->s = typename(tag->tagged->type);
-               else if (!strcmp(name, "object") && tag->tagged) {
-                       char *s = xmalloc(41);
-                       strcpy(s, sha1_to_hex(tag->tagged->sha1));
-                       v->s = s;
-               }
-       }
-}
-
-static int num_parents(struct commit *commit)
-{
-       struct commit_list *parents;
-       int i;
-
-       for (i = 0, parents = commit->parents;
-            parents;
-            parents = parents->next)
-               i++;
-       return i;
-}
-
-/* See grab_values */
-static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
-{
-       int i;
-       struct commit *commit = (struct commit *) obj;
-
-       for (i = 0; i < used_atom_cnt; i++) {
-               const char *name = used_atom[i];
-               struct atom_value *v = &val[i];
-               if (!!deref != (*name == '*'))
-                       continue;
-               if (deref)
-                       name++;
-               if (!strcmp(name, "tree")) {
-                       char *s = xmalloc(41);
-                       strcpy(s, sha1_to_hex(commit->tree->object.sha1));
-                       v->s = s;
-               }
-               if (!strcmp(name, "numparent")) {
-                       char *s = xmalloc(40);
-                       v->ul = num_parents(commit);
-                       sprintf(s, "%lu", v->ul);
-                       v->s = s;
-               }
-               else if (!strcmp(name, "parent")) {
-                       int num = num_parents(commit);
-                       int i;
-                       struct commit_list *parents;
-                       char *s = xmalloc(41 * num + 1);
-                       v->s = s;
-                       for (i = 0, parents = commit->parents;
-                            parents;
-                            parents = parents->next, i = i + 41) {
-                               struct commit *parent = parents->item;
-                               strcpy(s+i, sha1_to_hex(parent->object.sha1));
-                               if (parents->next)
-                                       s[i+40] = ' ';
-                       }
-                       if (!i)
-                               *s = '\0';
-               }
-       }
-}
-
-static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz)
-{
-       const char *eol;
-       while (*buf) {
-               if (!strncmp(buf, who, wholen) &&
-                   buf[wholen] == ' ')
-                       return buf + wholen + 1;
-               eol = strchr(buf, '\n');
-               if (!eol)
-                       return "";
-               eol++;
-               if (*eol == '\n')
-                       return ""; /* end of header */
-               buf = eol;
-       }
-       return "";
-}
-
-static const char *copy_line(const char *buf)
-{
-       const char *eol = strchrnul(buf, '\n');
-       return xmemdupz(buf, eol - buf);
-}
-
-static const char *copy_name(const char *buf)
-{
-       const char *cp;
-       for (cp = buf; *cp && *cp != '\n'; cp++) {
-               if (!strncmp(cp, " <", 2))
-                       return xmemdupz(buf, cp - buf);
-       }
-       return "";
-}
-
-static const char *copy_email(const char *buf)
-{
-       const char *email = strchr(buf, '<');
-       const char *eoemail = strchr(email, '>');
-       if (!email || !eoemail)
-               return "";
-       return xmemdupz(email, eoemail + 1 - email);
-}
-
-static void grab_date(const char *buf, struct atom_value *v, const char *atomname)
-{
-       const char *eoemail = strstr(buf, "> ");
-       char *zone;
-       unsigned long timestamp;
-       long tz;
-       enum date_mode date_mode = DATE_NORMAL;
-       const char *formatp;
-
-       /*
-        * We got here because atomname ends in "date" or "date<something>";
-        * it's not possible that <something> is not ":<format>" because
-        * parse_atom() wouldn't have allowed it, so we can assume that no
-        * ":" means no format is specified, and use the default.
-        */
-       formatp = strchr(atomname, ':');
-       if (formatp != NULL) {
-               formatp++;
-               date_mode = parse_date_format(formatp);
-       }
-
-       if (!eoemail)
-               goto bad;
-       timestamp = strtoul(eoemail + 2, &zone, 10);
-       if (timestamp == ULONG_MAX)
-               goto bad;
-       tz = strtol(zone, NULL, 10);
-       if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE)
-               goto bad;
-       v->s = xstrdup(show_date(timestamp, tz, date_mode));
-       v->ul = timestamp;
-       return;
- bad:
-       v->s = "";
-       v->ul = 0;
-}
-
-/* See grab_values */
-static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
-{
-       int i;
-       int wholen = strlen(who);
-       const char *wholine = NULL;
-
-       for (i = 0; i < used_atom_cnt; i++) {
-               const char *name = used_atom[i];
-               struct atom_value *v = &val[i];
-               if (!!deref != (*name == '*'))
-                       continue;
-               if (deref)
-                       name++;
-               if (strncmp(who, name, wholen))
-                       continue;
-               if (name[wholen] != 0 &&
-                   strcmp(name + wholen, "name") &&
-                   strcmp(name + wholen, "email") &&
-                   prefixcmp(name + wholen, "date"))
-                       continue;
-               if (!wholine)
-                       wholine = find_wholine(who, wholen, buf, sz);
-               if (!wholine)
-                       return; /* no point looking for it */
-               if (name[wholen] == 0)
-                       v->s = copy_line(wholine);
-               else if (!strcmp(name + wholen, "name"))
-                       v->s = copy_name(wholine);
-               else if (!strcmp(name + wholen, "email"))
-                       v->s = copy_email(wholine);
-               else if (!prefixcmp(name + wholen, "date"))
-                       grab_date(wholine, v, name);
-       }
-
-       /* For a tag or a commit object, if "creator" or "creatordate" is
-        * requested, do something special.
-        */
-       if (strcmp(who, "tagger") && strcmp(who, "committer"))
-               return; /* "author" for commit object is not wanted */
-       if (!wholine)
-               wholine = find_wholine(who, wholen, buf, sz);
-       if (!wholine)
-               return;
-       for (i = 0; i < used_atom_cnt; i++) {
-               const char *name = used_atom[i];
-               struct atom_value *v = &val[i];
-               if (!!deref != (*name == '*'))
-                       continue;
-               if (deref)
-                       name++;
-
-               if (!prefixcmp(name, "creatordate"))
-                       grab_date(wholine, v, name);
-               else if (!strcmp(name, "creator"))
-                       v->s = copy_line(wholine);
-       }
-}
-
-static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body)
-{
-       while (*buf) {
-               const char *eol = strchr(buf, '\n');
-               if (!eol)
-                       return;
-               if (eol[1] == '\n') {
-                       buf = eol + 1;
-                       break; /* found end of header */
-               }
-               buf = eol + 1;
-       }
-       while (*buf == '\n')
-               buf++;
-       if (!*buf)
-               return;
-       *sub = buf; /* first non-empty line */
-       buf = strchr(buf, '\n');
-       if (!buf) {
-               *body = "";
-               return; /* no body */
-       }
-       while (*buf == '\n')
-               buf++; /* skip blank between subject and body */
-       *body = buf;
-}
-
-/* See grab_values */
-static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
-{
-       int i;
-       const char *subpos = NULL, *bodypos = NULL;
-
-       for (i = 0; i < used_atom_cnt; i++) {
-               const char *name = used_atom[i];
-               struct atom_value *v = &val[i];
-               if (!!deref != (*name == '*'))
-                       continue;
-               if (deref)
-                       name++;
-               if (strcmp(name, "subject") &&
-                   strcmp(name, "body") &&
-                   strcmp(name, "contents"))
-                       continue;
-               if (!subpos)
-                       find_subpos(buf, sz, &subpos, &bodypos);
-               if (!subpos)
-                       return;
-
-               if (!strcmp(name, "subject"))
-                       v->s = copy_line(subpos);
-               else if (!strcmp(name, "body"))
-                       v->s = xstrdup(bodypos);
-               else if (!strcmp(name, "contents"))
-                       v->s = xstrdup(subpos);
-       }
-}
-
-/* We want to have empty print-string for field requests
- * that do not apply (e.g. "authordate" for a tag object)
- */
-static void fill_missing_values(struct atom_value *val)
-{
-       int i;
-       for (i = 0; i < used_atom_cnt; i++) {
-               struct atom_value *v = &val[i];
-               if (v->s == NULL)
-                       v->s = "";
-       }
-}
-
-/*
- * val is a list of atom_value to hold returned values.  Extract
- * the values for atoms in used_atom array out of (obj, buf, sz).
- * when deref is false, (obj, buf, sz) is the object that is
- * pointed at by the ref itself; otherwise it is the object the
- * ref (which is a tag) refers to.
- */
-static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
-{
-       grab_common_values(val, deref, obj, buf, sz);
-       switch (obj->type) {
-       case OBJ_TAG:
-               grab_tag_values(val, deref, obj, buf, sz);
-               grab_sub_body_contents(val, deref, obj, buf, sz);
-               grab_person("tagger", val, deref, obj, buf, sz);
-               break;
-       case OBJ_COMMIT:
-               grab_commit_values(val, deref, obj, buf, sz);
-               grab_sub_body_contents(val, deref, obj, buf, sz);
-               grab_person("author", val, deref, obj, buf, sz);
-               grab_person("committer", val, deref, obj, buf, sz);
-               break;
-       case OBJ_TREE:
-               // grab_tree_values(val, deref, obj, buf, sz);
-               break;
-       case OBJ_BLOB:
-               // grab_blob_values(val, deref, obj, buf, sz);
-               break;
-       default:
-               die("Eh?  Object of type %d?", obj->type);
-       }
-}
-
-/*
- * Parse the object referred by ref, and grab needed value.
- */
-static void populate_value(struct refinfo *ref)
-{
-       void *buf;
-       struct object *obj;
-       int eaten, i;
-       unsigned long size;
-       const unsigned char *tagged;
-
-       ref->value = xcalloc(sizeof(struct atom_value), used_atom_cnt);
-
-       buf = get_obj(ref->objectname, &obj, &size, &eaten);
-       if (!buf)
-               die("missing object %s for %s",
-                   sha1_to_hex(ref->objectname), ref->refname);
-       if (!obj)
-               die("parse_object_buffer failed on %s for %s",
-                   sha1_to_hex(ref->objectname), ref->refname);
-
-       /* Fill in specials first */
-       for (i = 0; i < used_atom_cnt; i++) {
-               const char *name = used_atom[i];
-               struct atom_value *v = &ref->value[i];
-               int deref = 0;
-               const char *refname;
-               const char *formatp;
-
-               if (*name == '*') {
-                       deref = 1;
-                       name++;
-               }
-
-               if (!prefixcmp(name, "refname"))
-                       refname = ref->refname;
-               else if(!prefixcmp(name, "upstream")) {
-                       struct branch *branch;
-                       /* only local branches may have an upstream */
-                       if (prefixcmp(ref->refname, "refs/heads/"))
-                               continue;
-                       branch = branch_get(ref->refname + 11);
-
-                       if (!branch || !branch->merge || !branch->merge[0] ||
-                           !branch->merge[0]->dst)
-                               continue;
-                       refname = branch->merge[0]->dst;
-               }
-               else
-                       continue;
-
-               formatp = strchr(name, ':');
-               /* look for "short" refname format */
-               if (formatp) {
-                       formatp++;
-                       if (!strcmp(formatp, "short"))
-                               refname = shorten_unambiguous_ref(refname,
-                                                     warn_ambiguous_refs);
-                       else
-                               die("unknown %.*s format %s",
-                                   (int)(formatp - name), name, formatp);
-               }
-
-               if (!deref)
-                       v->s = refname;
-               else {
-                       int len = strlen(refname);
-                       char *s = xmalloc(len + 4);
-                       sprintf(s, "%s^{}", refname);
-                       v->s = s;
-               }
-       }
-
-       grab_values(ref->value, 0, obj, buf, size);
-       if (!eaten)
-               free(buf);
-
-       /* If there is no atom that wants to know about tagged
-        * object, we are done.
-        */
-       if (!need_tagged || (obj->type != OBJ_TAG))
-               return;
-
-       /* If it is a tag object, see if we use a value that derefs
-        * the object, and if we do grab the object it refers to.
-        */
-       tagged = ((struct tag *)obj)->tagged->sha1;
-
-       /* NEEDSWORK: This derefs tag only once, which
-        * is good to deal with chains of trust, but
-        * is not consistent with what deref_tag() does
-        * which peels the onion to the core.
-        */
-       buf = get_obj(tagged, &obj, &size, &eaten);
-       if (!buf)
-               die("missing object %s for %s",
-                   sha1_to_hex(tagged), ref->refname);
-       if (!obj)
-               die("parse_object_buffer failed on %s for %s",
-                   sha1_to_hex(tagged), ref->refname);
-       grab_values(ref->value, 1, obj, buf, size);
-       if (!eaten)
-               free(buf);
-}
-
-/*
- * Given a ref, return the value for the atom.  This lazily gets value
- * out of the object by calling populate value.
- */
-static void get_value(struct refinfo *ref, int atom, struct atom_value **v)
-{
-       if (!ref->value) {
-               populate_value(ref);
-               fill_missing_values(ref->value);
-       }
-       *v = &ref->value[atom];
-}
-
-struct grab_ref_cbdata {
-       struct refinfo **grab_array;
-       const char **grab_pattern;
-       int grab_cnt;
-};
-
-/*
- * A call-back given to for_each_ref().  It is unfortunate that we
- * need to use global variables to pass extra information to this
- * function.
- */
-static int grab_single_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
-{
-       struct grab_ref_cbdata *cb = cb_data;
-       struct refinfo *ref;
-       int cnt;
-
-       if (*cb->grab_pattern) {
-               const char **pattern;
-               int namelen = strlen(refname);
-               for (pattern = cb->grab_pattern; *pattern; pattern++) {
-                       const char *p = *pattern;
-                       int plen = strlen(p);
-
-                       if ((plen <= namelen) &&
-                           !strncmp(refname, p, plen) &&
-                           (refname[plen] == '\0' ||
-                            refname[plen] == '/' ||
-                            p[plen-1] == '/'))
-                               break;
-                       if (!fnmatch(p, refname, FNM_PATHNAME))
-                               break;
-               }
-               if (!*pattern)
-                       return 0;
-       }
-
-       /* We do not open the object yet; sort may only need refname
-        * to do its job and the resulting list may yet to be pruned
-        * by maxcount logic.
-        */
-       ref = xcalloc(1, sizeof(*ref));
-       ref->refname = xstrdup(refname);
-       hashcpy(ref->objectname, sha1);
-
-       cnt = cb->grab_cnt;
-       cb->grab_array = xrealloc(cb->grab_array,
-                                 sizeof(*cb->grab_array) * (cnt + 1));
-       cb->grab_array[cnt++] = ref;
-       cb->grab_cnt = cnt;
-       return 0;
-}
-
-static int cmp_ref_sort(struct ref_sort *s, struct refinfo *a, struct refinfo *b)
-{
-       struct atom_value *va, *vb;
-       int cmp;
-       cmp_type cmp_type = used_atom_type[s->atom];
-
-       get_value(a, s->atom, &va);
-       get_value(b, s->atom, &vb);
-       switch (cmp_type) {
-       case FIELD_STR:
-               cmp = strcmp(va->s, vb->s);
-               break;
-       default:
-               if (va->ul < vb->ul)
-                       cmp = -1;
-               else if (va->ul == vb->ul)
-                       cmp = 0;
-               else
-                       cmp = 1;
-               break;
-       }
-       return (s->reverse) ? -cmp : cmp;
-}
-
-static struct ref_sort *ref_sort;
-static int compare_refs(const void *a_, const void *b_)
-{
-       struct refinfo *a = *((struct refinfo **)a_);
-       struct refinfo *b = *((struct refinfo **)b_);
-       struct ref_sort *s;
-
-       for (s = ref_sort; s; s = s->next) {
-               int cmp = cmp_ref_sort(s, a, b);
-               if (cmp)
-                       return cmp;
-       }
-       return 0;
-}
-
-static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs)
-{
-       ref_sort = sort;
-       qsort(refs, num_refs, sizeof(struct refinfo *), compare_refs);
-}
-
-static void print_value(struct refinfo *ref, int atom, int quote_style)
-{
-       struct atom_value *v;
-       get_value(ref, atom, &v);
-       switch (quote_style) {
-       case QUOTE_NONE:
-               fputs(v->s, stdout);
-               break;
-       case QUOTE_SHELL:
-               sq_quote_print(stdout, v->s);
-               break;
-       case QUOTE_PERL:
-               perl_quote_print(stdout, v->s);
-               break;
-       case QUOTE_PYTHON:
-               python_quote_print(stdout, v->s);
-               break;
-       case QUOTE_TCL:
-               tcl_quote_print(stdout, v->s);
-               break;
-       }
-}
-
-static int hex1(char ch)
-{
-       if ('0' <= ch && ch <= '9')
-               return ch - '0';
-       else if ('a' <= ch && ch <= 'f')
-               return ch - 'a' + 10;
-       else if ('A' <= ch && ch <= 'F')
-               return ch - 'A' + 10;
-       return -1;
-}
-static int hex2(const char *cp)
-{
-       if (cp[0] && cp[1])
-               return (hex1(cp[0]) << 4) | hex1(cp[1]);
-       else
-               return -1;
-}
-
-static void emit(const char *cp, const char *ep)
-{
-       while (*cp && (!ep || cp < ep)) {
-               if (*cp == '%') {
-                       if (cp[1] == '%')
-                               cp++;
-                       else {
-                               int ch = hex2(cp + 1);
-                               if (0 <= ch) {
-                                       putchar(ch);
-                                       cp += 3;
-                                       continue;
-                               }
-                       }
-               }
-               putchar(*cp);
-               cp++;
-       }
-}
-
-static void show_ref(struct refinfo *info, const char *format, int quote_style)
-{
-       const char *cp, *sp, *ep;
-
-       for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
-               ep = strchr(sp, ')');
-               if (cp < sp)
-                       emit(cp, sp);
-               print_value(info, parse_atom(sp + 2, ep), quote_style);
-       }
-       if (*cp) {
-               sp = cp + strlen(cp);
-               emit(cp, sp);
-       }
-       putchar('\n');
-}
-
-static struct ref_sort *default_sort(void)
-{
-       static const char cstr_name[] = "refname";
-
-       struct ref_sort *sort = xcalloc(1, sizeof(*sort));
-
-       sort->next = NULL;
-       sort->atom = parse_atom(cstr_name, cstr_name + strlen(cstr_name));
-       return sort;
-}
-
-static int opt_parse_sort(const struct option *opt, const char *arg, int unset)
-{
-       struct ref_sort **sort_tail = opt->value;
-       struct ref_sort *s;
-       int len;
-
-       if (!arg) /* should --no-sort void the list ? */
-               return -1;
-
-       *sort_tail = s = xcalloc(1, sizeof(*s));
-
-       if (*arg == '-') {
-               s->reverse = 1;
-               arg++;
-       }
-       len = strlen(arg);
-       s->atom = parse_atom(arg, arg+len);
-       return 0;
-}
-
-static char const * const for_each_ref_usage[] = {
-       "git for-each-ref [options] [<pattern>]",
-       NULL
-};
-
-int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
-{
-       int i, num_refs;
-       const char *format = "%(objectname) %(objecttype)\t%(refname)";
-       struct ref_sort *sort = NULL, **sort_tail = &sort;
-       int maxcount = 0, quote_style = 0;
-       struct refinfo **refs;
-       struct grab_ref_cbdata cbdata;
-
-       struct option opts[] = {
-               OPT_BIT('s', "shell", &quote_style,
-                       "quote placeholders suitably for shells", QUOTE_SHELL),
-               OPT_BIT('p', "perl",  &quote_style,
-                       "quote placeholders suitably for perl", QUOTE_PERL),
-               OPT_BIT(0 , "python", &quote_style,
-                       "quote placeholders suitably for python", QUOTE_PYTHON),
-               OPT_BIT(0 , "tcl",  &quote_style,
-                       "quote placeholders suitably for tcl", QUOTE_TCL),
-
-               OPT_GROUP(""),
-               OPT_INTEGER( 0 , "count", &maxcount, "show only <n> matched refs"),
-               OPT_STRING(  0 , "format", &format, "format", "format to use for the output"),
-               OPT_CALLBACK(0 , "sort", sort_tail, "key",
-                           "field name to sort on", &opt_parse_sort),
-               OPT_END(),
-       };
-
-       parse_options(argc, argv, opts, for_each_ref_usage, 0);
-       if (maxcount < 0) {
-               error("invalid --count argument: `%d'", maxcount);
-               usage_with_options(for_each_ref_usage, opts);
-       }
-       if (HAS_MULTI_BITS(quote_style)) {
-               error("more than one quoting style?");
-               usage_with_options(for_each_ref_usage, opts);
-       }
-       if (verify_format(format))
-               usage_with_options(for_each_ref_usage, opts);
-
-       if (!sort)
-               sort = default_sort();
-       sort_atom_limit = used_atom_cnt;
-
-       /* for warn_ambiguous_refs */
-       git_config(git_default_config, NULL);
-
-       memset(&cbdata, 0, sizeof(cbdata));
-       cbdata.grab_pattern = argv;
-       for_each_ref(grab_single_ref, &cbdata);
-       refs = cbdata.grab_array;
-       num_refs = cbdata.grab_cnt;
-
-       for (i = 0; i < used_atom_cnt; i++) {
-               if (used_atom[i][0] == '*') {
-                       need_tagged = 1;
-                       break;
-               }
-       }
-
-       sort_refs(sort, refs, num_refs);
-
-       if (!maxcount || num_refs < maxcount)
-               maxcount = num_refs;
-       for (i = 0; i < maxcount; i++)
-               show_ref(refs[i], format, quote_style);
-       return 0;
-}
diff --git a/builtin-fsck.c b/builtin-fsck.c
deleted file mode 100644 (file)
index 6436bc2..0000000
+++ /dev/null
@@ -1,681 +0,0 @@
-#include "builtin.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"
-#include "fsck.h"
-#include "parse-options.h"
-#include "dir.h"
-
-#define REACHABLE 0x0001
-#define SEEN      0x0002
-
-static int show_root;
-static int show_tags;
-static int show_unreachable;
-static int include_reflogs = 1;
-static int check_full;
-static int check_strict;
-static int keep_cache_objects;
-static unsigned char head_sha1[20];
-static const char *head_points_at;
-static int errors_found;
-static int write_lost_and_found;
-static int verbose;
-#define ERROR_OBJECT 01
-#define ERROR_REACHABLE 02
-
-#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, typename(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);
-       errors_found |= ERROR_OBJECT;
-       objreport(obj, "error", err, params);
-       va_end(params);
-       return -1;
-}
-
-static int fsck_error_func(struct object *obj, int type, const char *err, ...)
-{
-       va_list params;
-       va_start(params, err);
-       objreport(obj, (type == FSCK_WARN) ? "warning" : "error", err, params);
-       va_end(params);
-       return (type == FSCK_WARN) ? 0 : 1;
-}
-
-static struct object_array pending;
-
-static int mark_object(struct object *obj, int type, void *data)
-{
-       struct object *parent = data;
-
-       if (!obj) {
-               printf("broken link from %7s %s\n",
-                          typename(parent->type), sha1_to_hex(parent->sha1));
-               printf("broken link from %7s %s\n",
-                          (type == OBJ_ANY ? "unknown" : typename(type)), "unknown");
-               errors_found |= ERROR_REACHABLE;
-               return 1;
-       }
-
-       if (type != OBJ_ANY && obj->type != type)
-               objerror(parent, "wrong object type in link");
-
-       if (obj->flags & REACHABLE)
-               return 0;
-       obj->flags |= REACHABLE;
-       if (!obj->parsed) {
-               if (parent && !has_sha1_file(obj->sha1)) {
-                       printf("broken link from %7s %s\n",
-                                typename(parent->type), sha1_to_hex(parent->sha1));
-                       printf("              to %7s %s\n",
-                                typename(obj->type), sha1_to_hex(obj->sha1));
-                       errors_found |= ERROR_REACHABLE;
-               }
-               return 1;
-       }
-
-       add_object_array(obj, (void *) parent, &pending);
-       return 0;
-}
-
-static void mark_object_reachable(struct object *obj)
-{
-       mark_object(obj, OBJ_ANY, 0);
-}
-
-static int traverse_one_object(struct object *obj, struct object *parent)
-{
-       int result;
-       struct tree *tree = NULL;
-
-       if (obj->type == OBJ_TREE) {
-               obj->parsed = 0;
-               tree = (struct tree *)obj;
-               if (parse_tree(tree) < 0)
-                       return 1; /* error already displayed */
-       }
-       result = fsck_walk(obj, mark_object, obj);
-       if (tree) {
-               free(tree->buffer);
-               tree->buffer = NULL;
-       }
-       return result;
-}
-
-static int traverse_reachable(void)
-{
-       int result = 0;
-       while (pending.nr) {
-               struct object_array_entry *entry;
-               struct object *obj, *parent;
-
-               entry = pending.objects + --pending.nr;
-               obj = entry->item;
-               parent = (struct object *) entry->name;
-               result |= traverse_one_object(obj, parent);
-       }
-       return !!result;
-}
-
-static int mark_used(struct object *obj, int type, void *data)
-{
-       if (!obj)
-               return 1;
-       obj->used = 1;
-       return 0;
-}
-
-/*
- * Check a single reachable object
- */
-static void check_reachable_object(struct object *obj)
-{
-       /*
-        * We obviously want the object to be parsed,
-        * except if it was in a pack-file and we didn't
-        * do a full fsck
-        */
-       if (!obj->parsed) {
-               if (has_sha1_pack(obj->sha1))
-                       return; /* it is in pack - forget about it */
-               printf("missing %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1));
-               errors_found |= ERROR_REACHABLE;
-               return;
-       }
-}
-
-/*
- * Check a single unreachable object
- */
-static void check_unreachable_object(struct object *obj)
-{
-       /*
-        * Missing unreachable object? Ignore it. It's not like
-        * we miss it (since it can't be reached), nor do we want
-        * to complain about it being unreachable (since it does
-        * not exist).
-        */
-       if (!obj->parsed)
-               return;
-
-       /*
-        * Unreachable object that exists? Show it if asked to,
-        * since this is something that is prunable.
-        */
-       if (show_unreachable) {
-               printf("unreachable %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1));
-               return;
-       }
-
-       /*
-        * "!used" means that nothing at all points to it, including
-        * other unreachable objects. In other words, it's the "tip"
-        * of some set of unreachable objects, usually a commit that
-        * got dropped.
-        *
-        * Such starting points are more interesting than some random
-        * set of unreachable objects, so we show them even if the user
-        * hasn't asked for _all_ unreachable objects. If you have
-        * deleted a branch by mistake, this is a prime candidate to
-        * start looking at, for example.
-        */
-       if (!obj->used) {
-               printf("dangling %s %s\n", typename(obj->type),
-                      sha1_to_hex(obj->sha1));
-               if (write_lost_and_found) {
-                       char *filename = git_path("lost-found/%s/%s",
-                               obj->type == OBJ_COMMIT ? "commit" : "other",
-                               sha1_to_hex(obj->sha1));
-                       FILE *f;
-
-                       if (safe_create_leading_directories(filename)) {
-                               error("Could not create lost-found");
-                               return;
-                       }
-                       if (!(f = fopen(filename, "w")))
-                               die("Could not open %s", filename);
-                       if (obj->type == OBJ_BLOB) {
-                               enum object_type type;
-                               unsigned long size;
-                               char *buf = read_sha1_file(obj->sha1,
-                                               &type, &size);
-                               if (buf) {
-                                       if (fwrite(buf, size, 1, f) != 1)
-                                               die("Could not write %s: %s",
-                                                   filename, strerror(errno));
-                                       free(buf);
-                               }
-                       } else
-                               fprintf(f, "%s\n", sha1_to_hex(obj->sha1));
-                       if (fclose(f))
-                               die("Could not finish %s: %s",
-                                   filename, strerror(errno));
-               }
-               return;
-       }
-
-       /*
-        * Otherwise? It's there, it's unreachable, and some other unreachable
-        * object points to it. Ignore it - it's not interesting, and we showed
-        * all the interesting cases above.
-        */
-}
-
-static void check_object(struct object *obj)
-{
-       if (verbose)
-               fprintf(stderr, "Checking %s\n", sha1_to_hex(obj->sha1));
-
-       if (obj->flags & REACHABLE)
-               check_reachable_object(obj);
-       else
-               check_unreachable_object(obj);
-}
-
-static void check_connectivity(void)
-{
-       int i, max;
-
-       /* Traverse the pending reachable objects */
-       traverse_reachable();
-
-       /* Look up all the requirements, warn about missing objects.. */
-       max = get_max_object_index();
-       if (verbose)
-               fprintf(stderr, "Checking connectivity (%d objects)\n", max);
-
-       for (i = 0; i < max; i++) {
-               struct object *obj = get_indexed_object(i);
-
-               if (obj)
-                       check_object(obj);
-       }
-}
-
-static int fsck_sha1(const unsigned char *sha1)
-{
-       struct object *obj = parse_object(sha1);
-       if (!obj) {
-               errors_found |= ERROR_OBJECT;
-               return error("%s: object corrupt or missing",
-                            sha1_to_hex(sha1));
-       }
-       if (obj->flags & SEEN)
-               return 0;
-       obj->flags |= SEEN;
-
-       if (verbose)
-               fprintf(stderr, "Checking %s %s\n",
-                       typename(obj->type), sha1_to_hex(obj->sha1));
-
-       if (fsck_walk(obj, mark_used, 0))
-               objerror(obj, "broken links");
-       if (fsck_object(obj, check_strict, fsck_error_func))
-               return -1;
-
-       if (obj->type == OBJ_TREE) {
-               struct tree *item = (struct tree *) obj;
-
-               free(item->buffer);
-               item->buffer = NULL;
-       }
-
-       if (obj->type == OBJ_COMMIT) {
-               struct commit *commit = (struct commit *) obj;
-
-               free(commit->buffer);
-               commit->buffer = NULL;
-
-               if (!commit->parents && show_root)
-                       printf("root %s\n", sha1_to_hex(commit->object.sha1));
-       }
-
-       if (obj->type == OBJ_TAG) {
-               struct tag *tag = (struct tag *) obj;
-
-               if (show_tags && tag->tagged) {
-                       printf("tagged %s %s", typename(tag->tagged->type), sha1_to_hex(tag->tagged->sha1));
-                       printf(" (%s) in %s\n", tag->tag, sha1_to_hex(tag->object.sha1));
-               }
-       }
-
-       return 0;
-}
-
-/*
- * 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;
-       hashcpy(entry->sha1, sha1);
-       nr = sha1_list.nr;
-       if (nr == MAX_SHA1_ENTRIES) {
-               fsck_sha1_list();
-               nr = 0;
-       }
-       sha1_list.entry[nr] = entry;
-       sha1_list.nr = ++nr;
-}
-
-static void fsck_dir(int i, char *path)
-{
-       DIR *dir = opendir(path);
-       struct dirent *de;
-
-       if (!dir)
-               return;
-
-       if (verbose)
-               fprintf(stderr, "Checking directory %s\n", path);
-
-       while ((de = readdir(dir)) != NULL) {
-               char name[100];
-               unsigned char sha1[20];
-
-               if (is_dot_or_dotdot(de->d_name))
-                       continue;
-               if (strlen(de->d_name) == 38) {
-                       sprintf(name, "%02x", i);
-                       memcpy(name+2, de->d_name, 39);
-                       if (get_sha1_hex(name, sha1) < 0)
-                               break;
-                       add_sha1_list(sha1, DIRENT_SORT_HINT(de));
-                       continue;
-               }
-               if (!prefixcmp(de->d_name, "tmp_obj_"))
-                       continue;
-               fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
-       }
-       closedir(dir);
-}
-
-static int default_refs;
-
-static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
-               const char *email, unsigned long timestamp, int tz,
-               const char *message, void *cb_data)
-{
-       struct object *obj;
-
-       if (verbose)
-               fprintf(stderr, "Checking reflog %s->%s\n",
-                       sha1_to_hex(osha1), sha1_to_hex(nsha1));
-
-       if (!is_null_sha1(osha1)) {
-               obj = lookup_object(osha1);
-               if (obj) {
-                       obj->used = 1;
-                       mark_object_reachable(obj);
-               }
-       }
-       obj = lookup_object(nsha1);
-       if (obj) {
-               obj->used = 1;
-               mark_object_reachable(obj);
-       }
-       return 0;
-}
-
-static int fsck_handle_reflog(const char *logname, const unsigned char *sha1, int flag, void *cb_data)
-{
-       for_each_reflog_ent(logname, fsck_handle_reflog_ent, NULL);
-       return 0;
-}
-
-static int is_branch(const char *refname)
-{
-       return !strcmp(refname, "HEAD") || !prefixcmp(refname, "refs/heads/");
-}
-
-static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
-{
-       struct object *obj;
-
-       obj = parse_object(sha1);
-       if (!obj) {
-               error("%s: invalid sha1 pointer %s", refname, sha1_to_hex(sha1));
-               /* We'll continue with the rest despite the error.. */
-               return 0;
-       }
-       if (obj->type != OBJ_COMMIT && is_branch(refname))
-               error("%s: not a commit", refname);
-       default_refs++;
-       obj->used = 1;
-       mark_object_reachable(obj);
-
-       return 0;
-}
-
-static void get_default_heads(void)
-{
-       if (head_points_at && !is_null_sha1(head_sha1))
-               fsck_handle_ref("HEAD", head_sha1, 0, NULL);
-       for_each_ref(fsck_handle_ref, NULL);
-       if (include_reflogs)
-               for_each_reflog(fsck_handle_reflog, NULL);
-
-       /*
-        * Not having any default heads isn't really fatal, but
-        * it does mean that "--unreachable" no longer makes any
-        * sense (since in this case everything will obviously
-        * be unreachable by definition.
-        *
-        * Showing dangling objects is valid, though (as those
-        * dangling objects are likely lost heads).
-        *
-        * So we just print a warning about it, and clear the
-        * "show_unreachable" flag.
-        */
-       if (!default_refs) {
-               fprintf(stderr, "notice: No default references\n");
-               show_unreachable = 0;
-       }
-}
-
-static void fsck_object_dir(const char *path)
-{
-       int i;
-
-       if (verbose)
-               fprintf(stderr, "Checking object directory\n");
-
-       for (i = 0; i < 256; i++) {
-               static char dir[4096];
-               sprintf(dir, "%s/%02x", path, i);
-               fsck_dir(i, dir);
-       }
-       fsck_sha1_list();
-}
-
-static int fsck_head_link(void)
-{
-       int flag;
-       int null_is_error = 0;
-
-       if (verbose)
-               fprintf(stderr, "Checking HEAD link\n");
-
-       head_points_at = resolve_ref("HEAD", head_sha1, 0, &flag);
-       if (!head_points_at)
-               return error("Invalid HEAD");
-       if (!strcmp(head_points_at, "HEAD"))
-               /* detached HEAD */
-               null_is_error = 1;
-       else if (prefixcmp(head_points_at, "refs/heads/"))
-               return error("HEAD points to something strange (%s)",
-                            head_points_at);
-       if (is_null_sha1(head_sha1)) {
-               if (null_is_error)
-                       return error("HEAD: detached HEAD points at nothing");
-               fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n",
-                       head_points_at + 11);
-       }
-       return 0;
-}
-
-static int fsck_cache_tree(struct cache_tree *it)
-{
-       int i;
-       int err = 0;
-
-       if (verbose)
-               fprintf(stderr, "Checking cache tree\n");
-
-       if (0 <= it->entry_count) {
-               struct object *obj = parse_object(it->sha1);
-               if (!obj) {
-                       error("%s: invalid sha1 pointer in cache-tree",
-                             sha1_to_hex(it->sha1));
-                       return 1;
-               }
-               mark_object_reachable(obj);
-               obj->used = 1;
-               if (obj->type != OBJ_TREE)
-                       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;
-}
-
-static char const * const fsck_usage[] = {
-       "git fsck [options] [<object>...]",
-       NULL
-};
-
-static struct option fsck_opts[] = {
-       OPT__VERBOSE(&verbose),
-       OPT_BOOLEAN(0, "unreachable", &show_unreachable, "show unreachable objects"),
-       OPT_BOOLEAN(0, "tags", &show_tags, "report tags"),
-       OPT_BOOLEAN(0, "root", &show_root, "report root nodes"),
-       OPT_BOOLEAN(0, "cache", &keep_cache_objects, "make index objects head nodes"),
-       OPT_BOOLEAN(0, "reflogs", &include_reflogs, "make reflogs head nodes (default)"),
-       OPT_BOOLEAN(0, "full", &check_full, "also consider alternate objects"),
-       OPT_BOOLEAN(0, "strict", &check_strict, "enable more strict checking"),
-       OPT_BOOLEAN(0, "lost-found", &write_lost_and_found,
-                               "write dangling objects in .git/lost-found"),
-       OPT_END(),
-};
-
-int cmd_fsck(int argc, const char **argv, const char *prefix)
-{
-       int i, heads;
-       struct alternate_object_database *alt;
-
-       errors_found = 0;
-
-       argc = parse_options(argc, argv, fsck_opts, fsck_usage, 0);
-       if (write_lost_and_found) {
-               check_full = 1;
-               include_reflogs = 0;
-       }
-
-       fsck_head_link();
-       fsck_object_dir(get_object_directory());
-
-       prepare_alt_odb();
-       for (alt = alt_odb_list; alt; alt = alt->next) {
-               char namebuf[PATH_MAX];
-               int namelen = alt->name - alt->base;
-               memcpy(namebuf, alt->base, namelen);
-               namebuf[namelen - 1] = 0;
-               fsck_object_dir(namebuf);
-       }
-
-       if (check_full) {
-               struct packed_git *p;
-
-               prepare_packed_git();
-               for (p = packed_git; p; p = p->next)
-                       /* verify gives error messages itself */
-                       verify_pack(p);
-
-               for (p = packed_git; p; p = p->next) {
-                       uint32_t j, num;
-                       if (open_pack_index(p))
-                               continue;
-                       num = p->num_objects;
-                       for (j = 0; j < num; j++)
-                               fsck_sha1(nth_packed_object_sha1(p, j));
-               }
-       }
-
-       heads = 0;
-       for (i = 0; i < argc; i++) {
-               const char *arg = argv[i];
-               unsigned char sha1[20];
-               if (!get_sha1(arg, sha1)) {
-                       struct object *obj = lookup_object(sha1);
-
-                       /* Error is printed by lookup_object(). */
-                       if (!obj)
-                               continue;
-
-                       obj->used = 1;
-                       mark_object_reachable(obj);
-                       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) {
-               read_cache();
-               for (i = 0; i < active_nr; i++) {
-                       unsigned int mode;
-                       struct blob *blob;
-                       struct object *obj;
-
-                       mode = active_cache[i]->ce_mode;
-                       if (S_ISGITLINK(mode))
-                               continue;
-                       blob = lookup_blob(active_cache[i]->sha1);
-                       if (!blob)
-                               continue;
-                       obj = &blob->object;
-                       obj->used = 1;
-                       mark_object_reachable(obj);
-               }
-               if (active_cache_tree)
-                       fsck_cache_tree(active_cache_tree);
-       }
-
-       check_connectivity();
-       return errors_found;
-}
diff --git a/builtin-gc.c b/builtin-gc.c
deleted file mode 100644 (file)
index fc556ed..0000000
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * git gc builtin command
- *
- * Cleanup unreachable files and optimize the repository.
- *
- * Copyright (c) 2007 James Bowes
- *
- * Based on git-gc.sh, which is
- *
- * Copyright (c) 2006 Shawn O. Pearce
- */
-
-#include "builtin.h"
-#include "cache.h"
-#include "parse-options.h"
-#include "run-command.h"
-
-#define FAILED_RUN "failed to run %s"
-
-static const char * const builtin_gc_usage[] = {
-       "git gc [options]",
-       NULL
-};
-
-static int pack_refs = 1;
-static int aggressive_window = 250;
-static int gc_auto_threshold = 6700;
-static int gc_auto_pack_limit = 50;
-static const char *prune_expire = "2.weeks.ago";
-
-#define MAX_ADD 10
-static const char *argv_pack_refs[] = {"pack-refs", "--all", "--prune", NULL};
-static const char *argv_reflog[] = {"reflog", "expire", "--all", NULL};
-static const char *argv_repack[MAX_ADD] = {"repack", "-d", "-l", NULL};
-static const char *argv_prune[] = {"prune", "--expire", NULL, NULL};
-static const char *argv_rerere[] = {"rerere", "gc", NULL};
-
-static int gc_config(const char *var, const char *value, void *cb)
-{
-       if (!strcmp(var, "gc.packrefs")) {
-               if (value && !strcmp(value, "notbare"))
-                       pack_refs = -1;
-               else
-                       pack_refs = git_config_bool(var, value);
-               return 0;
-       }
-       if (!strcmp(var, "gc.aggressivewindow")) {
-               aggressive_window = git_config_int(var, value);
-               return 0;
-       }
-       if (!strcmp(var, "gc.auto")) {
-               gc_auto_threshold = git_config_int(var, value);
-               return 0;
-       }
-       if (!strcmp(var, "gc.autopacklimit")) {
-               gc_auto_pack_limit = git_config_int(var, value);
-               return 0;
-       }
-       if (!strcmp(var, "gc.pruneexpire")) {
-               if (value && strcmp(value, "now")) {
-                       unsigned long now = approxidate("now");
-                       if (approxidate(value) >= now)
-                               return error("Invalid %s: '%s'", var, value);
-               }
-               return git_config_string(&prune_expire, var, value);
-       }
-       return git_default_config(var, value, cb);
-}
-
-static void append_option(const char **cmd, const char *opt, int max_length)
-{
-       int i;
-
-       for (i = 0; cmd[i]; i++)
-               ;
-
-       if (i + 2 >= max_length)
-               die("Too many options specified");
-       cmd[i++] = opt;
-       cmd[i] = NULL;
-}
-
-static int too_many_loose_objects(void)
-{
-       /*
-        * Quickly check if a "gc" is needed, by estimating how
-        * many loose objects there are.  Because SHA-1 is evenly
-        * distributed, we can check only one and get a reasonable
-        * estimate.
-        */
-       char path[PATH_MAX];
-       const char *objdir = get_object_directory();
-       DIR *dir;
-       struct dirent *ent;
-       int auto_threshold;
-       int num_loose = 0;
-       int needed = 0;
-
-       if (gc_auto_threshold <= 0)
-               return 0;
-
-       if (sizeof(path) <= snprintf(path, sizeof(path), "%s/17", objdir)) {
-               warning("insanely long object directory %.*s", 50, objdir);
-               return 0;
-       }
-       dir = opendir(path);
-       if (!dir)
-               return 0;
-
-       auto_threshold = (gc_auto_threshold + 255) / 256;
-       while ((ent = readdir(dir)) != NULL) {
-               if (strspn(ent->d_name, "0123456789abcdef") != 38 ||
-                   ent->d_name[38] != '\0')
-                       continue;
-               if (++num_loose > auto_threshold) {
-                       needed = 1;
-                       break;
-               }
-       }
-       closedir(dir);
-       return needed;
-}
-
-static int too_many_packs(void)
-{
-       struct packed_git *p;
-       int cnt;
-
-       if (gc_auto_pack_limit <= 0)
-               return 0;
-
-       prepare_packed_git();
-       for (cnt = 0, p = packed_git; p; p = p->next) {
-               if (!p->pack_local)
-                       continue;
-               if (p->pack_keep)
-                       continue;
-               /*
-                * Perhaps check the size of the pack and count only
-                * very small ones here?
-                */
-               cnt++;
-       }
-       return gc_auto_pack_limit <= cnt;
-}
-
-static int need_to_gc(void)
-{
-       /*
-        * Setting gc.auto to 0 or negative can disable the
-        * automatic gc.
-        */
-       if (gc_auto_threshold <= 0)
-               return 0;
-
-       /*
-        * If there are too many loose objects, but not too many
-        * packs, we run "repack -d -l".  If there are too many packs,
-        * we run "repack -A -d -l".  Otherwise we tell the caller
-        * there is no need.
-        */
-       if (too_many_packs())
-               append_option(argv_repack,
-                             prune_expire && !strcmp(prune_expire, "now") ?
-                             "-a" : "-A",
-                             MAX_ADD);
-       else if (!too_many_loose_objects())
-               return 0;
-
-       if (run_hook(NULL, "pre-auto-gc", NULL))
-               return 0;
-       return 1;
-}
-
-int cmd_gc(int argc, const char **argv, const char *prefix)
-{
-       int aggressive = 0;
-       int auto_gc = 0;
-       int quiet = 0;
-       char buf[80];
-
-       struct option builtin_gc_options[] = {
-               { OPTION_STRING, 0, "prune", &prune_expire, "date",
-                       "prune unreferenced objects",
-                       PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire },
-               OPT_BOOLEAN(0, "aggressive", &aggressive, "be more thorough (increased runtime)"),
-               OPT_BOOLEAN(0, "auto", &auto_gc, "enable auto-gc mode"),
-               OPT_BOOLEAN('q', "quiet", &quiet, "suppress progress reports"),
-               OPT_END()
-       };
-
-       git_config(gc_config, NULL);
-
-       if (pack_refs < 0)
-               pack_refs = !is_bare_repository();
-
-       argc = parse_options(argc, argv, builtin_gc_options, builtin_gc_usage, 0);
-       if (argc > 0)
-               usage_with_options(builtin_gc_usage, builtin_gc_options);
-
-       if (aggressive) {
-               append_option(argv_repack, "-f", MAX_ADD);
-               append_option(argv_repack, "--depth=250", MAX_ADD);
-               if (aggressive_window > 0) {
-                       sprintf(buf, "--window=%d", aggressive_window);
-                       append_option(argv_repack, buf, MAX_ADD);
-               }
-       }
-       if (quiet)
-               append_option(argv_repack, "-q", MAX_ADD);
-
-       if (auto_gc) {
-               /*
-                * Auto-gc should be least intrusive as possible.
-                */
-               if (!need_to_gc())
-                       return 0;
-               fprintf(stderr, "Auto packing your repository for optimum "
-                       "performance. You may also\n"
-                       "run \"git gc\" manually. See "
-                       "\"git help gc\" for more information.\n");
-       } else
-               append_option(argv_repack,
-                             prune_expire && !strcmp(prune_expire, "now")
-                             ? "-a" : "-A",
-                             MAX_ADD);
-
-       if (pack_refs && run_command_v_opt(argv_pack_refs, RUN_GIT_CMD))
-               return error(FAILED_RUN, argv_pack_refs[0]);
-
-       if (run_command_v_opt(argv_reflog, RUN_GIT_CMD))
-               return error(FAILED_RUN, argv_reflog[0]);
-
-       if (run_command_v_opt(argv_repack, RUN_GIT_CMD))
-               return error(FAILED_RUN, argv_repack[0]);
-
-       if (prune_expire) {
-               argv_prune[2] = prune_expire;
-               if (run_command_v_opt(argv_prune, RUN_GIT_CMD))
-                       return error(FAILED_RUN, argv_prune[0]);
-       }
-
-       if (run_command_v_opt(argv_rerere, RUN_GIT_CMD))
-               return error(FAILED_RUN, argv_rerere[0]);
-
-       if (auto_gc && too_many_loose_objects())
-               warning("There are too many unreachable loose objects; "
-                       "run 'git prune' to remove them.");
-
-       return 0;
-}
diff --git a/builtin-grep.c b/builtin-grep.c
deleted file mode 100644 (file)
index f88a912..0000000
+++ /dev/null
@@ -1,891 +0,0 @@
-/*
- * 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 "grep.h"
-
-#ifndef NO_EXTERNAL_GREP
-#ifdef __unix__
-#define NO_EXTERNAL_GREP 0
-#else
-#define NO_EXTERNAL_GREP 1
-#endif
-#endif
-
-static int builtin_grep;
-
-static int grep_config(const char *var, const char *value, void *cb)
-{
-       struct grep_opt *opt = cb;
-
-       if (!strcmp(var, "color.grep")) {
-               opt->color = git_config_colorbool(var, value, -1);
-               return 0;
-       }
-       if (!strcmp(var, "color.grep.external"))
-               return git_config_string(&(opt->color_external), var, value);
-       if (!strcmp(var, "color.grep.match")) {
-               if (!value)
-                       return config_error_nonbool(var);
-               color_parse(value, var, opt->color_match);
-               return 0;
-       }
-       return git_color_default_config(var, value, cb);
-}
-
-/*
- * git grep pathspecs are somewhat different from diff-tree pathspecs;
- * pathname wildcards are allowed.
- */
-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 ||
-                   ((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;
-}
-
-static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char *name, int tree_name_len)
-{
-       unsigned long size;
-       char *data;
-       enum object_type type;
-       char *to_free = NULL;
-       int hit;
-
-       data = read_sha1_file(sha1, &type, &size);
-       if (!data) {
-               error("'%s': unable to read %s", name, sha1_to_hex(sha1));
-               return 0;
-       }
-       if (opt->relative && opt->prefix_length) {
-               static char name_buf[PATH_MAX];
-               char *cp;
-               int name_len = strlen(name) - opt->prefix_length + 1;
-
-               if (!tree_name_len)
-                       name += opt->prefix_length;
-               else {
-                       if (ARRAY_SIZE(name_buf) <= name_len)
-                               cp = to_free = xmalloc(name_len);
-                       else
-                               cp = name_buf;
-                       memcpy(cp, name, tree_name_len);
-                       strcpy(cp + tree_name_len,
-                              name + tree_name_len + opt->prefix_length);
-                       name = cp;
-               }
-       }
-       hit = grep_buffer(opt, name, data, size);
-       free(data);
-       free(to_free);
-       return hit;
-}
-
-static int grep_file(struct grep_opt *opt, const char *filename)
-{
-       struct stat st;
-       int i;
-       char *data;
-       size_t sz;
-
-       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;
-       sz = xsize_t(st.st_size);
-       i = open(filename, O_RDONLY);
-       if (i < 0)
-               goto err_ret;
-       data = xmalloc(sz + 1);
-       if (st.st_size != read_in_full(i, data, sz)) {
-               error("'%s': short read %s", filename, strerror(errno));
-               close(i);
-               free(data);
-               return 0;
-       }
-       close(i);
-       if (opt->relative && opt->prefix_length)
-               filename += opt->prefix_length;
-       i = grep_buffer(opt, filename, data, sz);
-       free(data);
-       return i;
-}
-
-#if !NO_EXTERNAL_GREP
-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)
-
-/*
- * If you send a singleton filename to grep, it does not give
- * the name of the file.  GNU grep has "-H" but we would want
- * that behaviour in a portable way.
- *
- * So we keep two pathnames in argv buffer unsent to grep in
- * the main loop if we need to do more than one grep.
- */
-static int flush_grep(struct grep_opt *opt,
-                     int argc, int arg0, const char **argv, int *kept)
-{
-       int status;
-       int count = argc - arg0;
-       const char *kept_0 = NULL;
-
-       if (count <= 2) {
-               /*
-                * Because we keep at least 2 paths in the call from
-                * the main loop (i.e. kept != NULL), and MAXARGS is
-                * far greater than 2, this usually is a call to
-                * conclude the grep.  However, the user could attempt
-                * to overflow the argv buffer by giving too many
-                * options to leave very small number of real
-                * arguments even for the call in the main loop.
-                */
-               if (kept)
-                       die("insanely many options to grep");
-
-               /*
-                * If we have two or more paths, we do not have to do
-                * anything special, but we need to push /dev/null to
-                * get "-H" behaviour of GNU grep portably but when we
-                * are not doing "-l" nor "-L" nor "-c".
-                */
-               if (count == 1 &&
-                   !opt->name_only &&
-                   !opt->unmatch_name_only &&
-                   !opt->count) {
-                       argv[argc++] = "/dev/null";
-                       argv[argc] = NULL;
-               }
-       }
-
-       else if (kept) {
-               /*
-                * Called because we found many paths and haven't finished
-                * iterating over the cache yet.  We keep two paths
-                * for the concluding call.  argv[argc-2] and argv[argc-1]
-                * has the last two paths, so save the first one away,
-                * replace it with NULL while sending the list to grep,
-                * and recover them after we are done.
-                */
-               *kept = 2;
-               kept_0 = argv[argc-2];
-               argv[argc-2] = NULL;
-               argc -= 2;
-       }
-
-       status = exec_grep(argc, argv);
-
-       if (kept_0) {
-               /*
-                * Then recover them.  Now the last arg is beyond the
-                * terminating NULL which is at argc, and the second
-                * from the last is what we saved away in kept_0
-                */
-               argv[arg0++] = kept_0;
-               argv[arg0] = argv[argc+1];
-       }
-       return status;
-}
-
-static void grep_add_color(struct strbuf *sb, const char *escape_seq)
-{
-       size_t orig_len = sb->len;
-
-       while (*escape_seq) {
-               if (*escape_seq == 'm')
-                       strbuf_addch(sb, ';');
-               else if (*escape_seq != '\033' && *escape_seq  != '[')
-                       strbuf_addch(sb, *escape_seq);
-               escape_seq++;
-       }
-       if (sb->len > orig_len && sb->buf[sb->len - 1] == ';')
-               strbuf_setlen(sb, sb->len - 1);
-}
-
-static int external_grep(struct grep_opt *opt, const char **paths, int cached)
-{
-       int i, nr, argc, hit, len, status;
-       const char *argv[MAXARGS+1];
-       char randarg[ARGBUF];
-       char *argptr = randarg;
-       struct grep_pat *p;
-
-       if (opt->extended || (opt->relative && opt->prefix_length))
-               return -1;
-       len = nr = 0;
-       push_arg("grep");
-       if (opt->fixed)
-               push_arg("-F");
-       if (opt->linenum)
-               push_arg("-n");
-       if (!opt->pathname)
-               push_arg("-h");
-       if (opt->regflags & REG_EXTENDED)
-               push_arg("-E");
-       if (opt->regflags & REG_ICASE)
-               push_arg("-i");
-       if (opt->binary == GREP_BINARY_NOMATCH)
-               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->null_following_name)
-               /* in GNU grep git's "-z" translates to "-Z" */
-               push_arg("-Z");
-       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) + 1;
-                               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) + 1;
-                               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) + 1;
-                       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);
-       }
-       if (opt->color) {
-               struct strbuf sb = STRBUF_INIT;
-
-               grep_add_color(&sb, opt->color_match);
-               setenv("GREP_COLOR", sb.buf, 1);
-
-               strbuf_reset(&sb);
-               strbuf_addstr(&sb, "mt=");
-               grep_add_color(&sb, opt->color_match);
-               strbuf_addstr(&sb, ":sl=:cx=:fn=:ln=:bn=:se=");
-               setenv("GREP_COLORS", sb.buf, 1);
-
-               strbuf_release(&sb);
-
-               if (opt->color_external && strlen(opt->color_external) > 0)
-                       push_arg(opt->color_external);
-       }
-
-       hit = 0;
-       argc = nr;
-       for (i = 0; i < active_nr; i++) {
-               struct cache_entry *ce = active_cache[i];
-               char *name;
-               int kept;
-               if (!S_ISREG(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 (MAXARGS <= argc) {
-                       status = flush_grep(opt, argc, nr, argv, &kept);
-                       if (0 < status)
-                               hit = 1;
-                       argc = nr + kept;
-               }
-               if (ce_stage(ce)) {
-                       do {
-                               i++;
-                       } while (i < active_nr &&
-                                !strcmp(ce->name, active_cache[i]->name));
-                       i--; /* compensate for loop control */
-               }
-       }
-       if (argc > nr) {
-               status = flush_grep(opt, argc, nr, argv, NULL);
-               if (0 < status)
-                       hit = 1;
-       }
-       return hit;
-}
-#endif
-
-static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
-{
-       int hit = 0;
-       int nr;
-       read_cache();
-
-#if !NO_EXTERNAL_GREP
-       /*
-        * 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 && !builtin_grep) {
-               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 (!S_ISREG(ce->ce_mode))
-                       continue;
-               if (!pathspec_matches(paths, ce->name))
-                       continue;
-               /*
-                * If CE_VALID is on, we assume worktree file and its cache entry
-                * are identical, even if worktree file has been modified, so use
-                * cache version instead
-                */
-               if (cached || (ce->ce_flags & CE_VALID)) {
-                       if (ce_stage(ce))
-                               continue;
-                       hit |= grep_sha1(opt, ce->sha1, ce->name, 0);
-               }
-               else
-                       hit |= grep_file(opt, ce->name);
-               if (ce_stage(ce)) {
-                       do {
-                               nr++;
-                       } while (nr < active_nr &&
-                                !strcmp(ce->name, active_cache[nr]->name));
-                       nr--; /* compensate for loop control */
-               }
-       }
-       free_grep_patterns(opt);
-       return hit;
-}
-
-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;
-       int tn_len = strlen(tree_name);
-       struct strbuf pathbuf;
-
-       strbuf_init(&pathbuf, PATH_MAX + tn_len);
-
-       if (tn_len) {
-               strbuf_add(&pathbuf, tree_name, tn_len);
-               strbuf_addch(&pathbuf, ':');
-               tn_len = pathbuf.len;
-       }
-       strbuf_addstr(&pathbuf, base);
-       len = pathbuf.len;
-
-       while (tree_entry(tree, &entry)) {
-               int te_len = tree_entry_len(entry.path, entry.sha1);
-               pathbuf.len = len;
-               strbuf_add(&pathbuf, entry.path, te_len);
-
-               if (S_ISDIR(entry.mode))
-                       /* Match "abc/" against pathspec to
-                        * decide if we want to descend into "abc"
-                        * directory.
-                        */
-                       strbuf_addch(&pathbuf, '/');
-
-               down = pathbuf.buf + tn_len;
-               if (!pathspec_matches(paths, down))
-                       ;
-               else if (S_ISREG(entry.mode))
-                       hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len);
-               else if (S_ISDIR(entry.mode)) {
-                       enum object_type type;
-                       struct tree_desc sub;
-                       void *data;
-                       unsigned long size;
-
-                       data = read_sha1_file(entry.sha1, &type, &size);
-                       if (!data)
-                               die("unable to read tree (%s)",
-                                   sha1_to_hex(entry.sha1));
-                       init_tree_desc(&sub, data, size);
-                       hit |= grep_tree(opt, paths, &sub, tree_name, down);
-                       free(data);
-               }
-       }
-       strbuf_release(&pathbuf);
-       return hit;
-}
-
-static int grep_object(struct grep_opt *opt, const char **paths,
-                      struct object *obj, const char *name)
-{
-       if (obj->type == OBJ_BLOB)
-               return grep_sha1(opt, obj->sha1, name, 0);
-       if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) {
-               struct tree_desc tree;
-               void *data;
-               unsigned long size;
-               int hit;
-               data = read_object_with_reference(obj->sha1, tree_type,
-                                                 &size, NULL);
-               if (!data)
-                       die("unable to read tree (%s)", sha1_to_hex(obj->sha1));
-               init_tree_desc(&tree, data, size);
-               hit = grep_tree(opt, paths, &tree, name, "");
-               free(data);
-               return hit;
-       }
-       die("unable to grep from object of type %s", typename(obj->type));
-}
-
-static const char builtin_grep_usage[] =
-"git grep <option>* [-e] <pattern> <rev>* [[--] <path>...]";
-
-static const char emsg_invalid_context_len[] =
-"%s: invalid context length argument";
-static const char emsg_missing_context_len[] =
-"missing context length argument";
-static const char emsg_missing_argument[] =
-"option requires an argument -%s";
-
-int cmd_grep(int argc, const char **argv, const char *prefix)
-{
-       int hit = 0;
-       int cached = 0;
-       int seen_dashdash = 0;
-       struct grep_opt opt;
-       struct object_array list = { 0, 0, NULL };
-       const char **paths = NULL;
-       int i;
-
-       memset(&opt, 0, sizeof(opt));
-       opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0;
-       opt.relative = 1;
-       opt.pathname = 1;
-       opt.pattern_tail = &opt.pattern_list;
-       opt.regflags = REG_NEWLINE;
-
-       strcpy(opt.color_match, GIT_COLOR_RED GIT_COLOR_BOLD);
-       opt.color = -1;
-       git_config(grep_config, &opt);
-       if (opt.color == -1)
-               opt.color = git_use_color_default;
-
-       /*
-        * If there is no -- then the paths must exist in the working
-        * tree.  If there is no explicit pattern specified with -e or
-        * -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
-        * unrecognized non option is the beginning of the refs list
-        * that continues up to the -- (if exists), and then paths.
-        */
-
-       while (1 < argc) {
-               const char *arg = argv[1];
-               argc--; argv++;
-               if (!strcmp("--cached", arg)) {
-                       cached = 1;
-                       continue;
-               }
-               if (!strcmp("--no-ext-grep", arg)) {
-                       builtin_grep = 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)) {
-                       opt.pathname = 0;
-                       continue;
-               }
-               if (!strcmp("-H", arg)) {
-                       opt.pathname = 1;
-                       continue;
-               }
-               if (!strcmp("-l", arg) ||
-                   !strcmp("--name-only", 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("-z", arg) ||
-                   !strcmp("--null", arg)) {
-                       opt.null_following_name = 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 (!prefixcmp(arg, "-A") ||
-                   !prefixcmp(arg, "-B") ||
-                   !prefixcmp(arg, "-C") ||
-                   (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)
-                                               die(emsg_missing_context_len);
-                                       scan = *++argv;
-                                       argc--;
-                               }
-                               else
-                                       scan = arg + 2;
-                               break;
-                       default:
-                               scan = arg + 1;
-                               break;
-                       }
-                       if (strtoul_ui(scan, 10, &num))
-                               die(emsg_invalid_context_len, scan);
-                       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)
-                               die(emsg_missing_argument, arg);
-                       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 (len && buf[len-1] == '\n')
-                                       buf[len-1] = 0;
-                               /* ignore empty line like grep does */
-                               if (!buf[0])
-                                       continue;
-                               append_grep_pattern(&opt, xstrdup(buf),
-                                                   argv[1], ++lno,
-                                                   GREP_PATTERN);
-                       }
-                       fclose(patterns);
-                       argv++;
-                       argc--;
-                       continue;
-               }
-               if (!strcmp("--not", arg)) {
-                       append_grep_pattern(&opt, arg, "command line", 0,
-                                           GREP_NOT);
-                       continue;
-               }
-               if (!strcmp("--and", arg)) {
-                       append_grep_pattern(&opt, arg, "command line", 0,
-                                           GREP_AND);
-                       continue;
-               }
-               if (!strcmp("--or", arg))
-                       continue; /* no-op */
-               if (!strcmp("(", arg)) {
-                       append_grep_pattern(&opt, arg, "command line", 0,
-                                           GREP_OPEN_PAREN);
-                       continue;
-               }
-               if (!strcmp(")", arg)) {
-                       append_grep_pattern(&opt, arg, "command line", 0,
-                                           GREP_CLOSE_PAREN);
-                       continue;
-               }
-               if (!strcmp("--all-match", arg)) {
-                       opt.all_match = 1;
-                       continue;
-               }
-               if (!strcmp("-e", arg)) {
-                       if (1 < argc) {
-                               append_grep_pattern(&opt, argv[1],
-                                                   "-e option", 0,
-                                                   GREP_PATTERN);
-                               argv++;
-                               argc--;
-                               continue;
-                       }
-                       die(emsg_missing_argument, arg);
-               }
-               if (!strcmp("--full-name", arg)) {
-                       opt.relative = 0;
-                       continue;
-               }
-               if (!strcmp("--color", arg)) {
-                       opt.color = 1;
-                       continue;
-               }
-               if (!strcmp("--no-color", arg)) {
-                       opt.color = 0;
-                       continue;
-               }
-               if (!strcmp("--", arg)) {
-                       /* later processing wants to have this at argv[1] */
-                       argv--;
-                       argc++;
-                       break;
-               }
-               if (*arg == '-')
-                       usage(builtin_grep_usage);
-
-               /* First unrecognized non-option token */
-               if (!opt.pattern_list) {
-                       append_grep_pattern(&opt, arg, "command line", 0,
-                                           GREP_PATTERN);
-                       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.color && !opt.color_external)
-               builtin_grep = 1;
-       if (!opt.pattern_list)
-               die("no pattern given.");
-       if ((opt.regflags != REG_NEWLINE) && opt.fixed)
-               die("cannot mix --fixed-strings and regexp");
-       compile_grep_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);
-                       if (!object)
-                               die("bad object %s", arg);
-                       add_object_array(object, arg, &list);
-                       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);
-               if (opt.prefix_length && opt.relative) {
-                       /* Make sure we do not get outside of paths */
-                       for (i = 0; paths[i]; i++)
-                               if (strncmp(prefix, paths[i], opt.prefix_length))
-                                       die("git grep: cannot generate relative filenames containing '..'");
-               }
-       }
-       else if (prefix) {
-               paths = xcalloc(2, sizeof(const char *));
-               paths[0] = prefix;
-               paths[1] = NULL;
-       }
-
-       if (!list.nr) {
-               if (!cached)
-                       setup_work_tree();
-               return !grep_cache(&opt, paths, cached);
-       }
-
-       if (cached)
-               die("both --cached and trees are given.");
-
-       for (i = 0; i < list.nr; i++) {
-               struct object *real_obj;
-               real_obj = deref_tag(list.objects[i].item, NULL, 0);
-               if (grep_object(&opt, paths, real_obj, list.objects[i].name))
-                       hit = 1;
-       }
-       free_grep_patterns(&opt);
-       return !hit;
-}
diff --git a/builtin-help.c b/builtin-help.c
deleted file mode 100644 (file)
index 67dda3e..0000000
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- * builtin-help.c
- *
- * Builtin help command
- */
-#include "cache.h"
-#include "builtin.h"
-#include "exec_cmd.h"
-#include "common-cmds.h"
-#include "parse-options.h"
-#include "run-command.h"
-#include "help.h"
-
-static struct man_viewer_list {
-       struct man_viewer_list *next;
-       char name[FLEX_ARRAY];
-} *man_viewer_list;
-
-static struct man_viewer_info_list {
-       struct man_viewer_info_list *next;
-       const char *info;
-       char name[FLEX_ARRAY];
-} *man_viewer_info_list;
-
-enum help_format {
-       HELP_FORMAT_MAN,
-       HELP_FORMAT_INFO,
-       HELP_FORMAT_WEB,
-};
-
-static int show_all = 0;
-static enum help_format help_format = HELP_FORMAT_MAN;
-static struct option builtin_help_options[] = {
-       OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
-       OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
-       OPT_SET_INT('w', "web", &help_format, "show manual in web browser",
-                       HELP_FORMAT_WEB),
-       OPT_SET_INT('i', "info", &help_format, "show info page",
-                       HELP_FORMAT_INFO),
-       OPT_END(),
-};
-
-static const char * const builtin_help_usage[] = {
-       "git help [--all] [--man|--web|--info] [command]",
-       NULL
-};
-
-static enum help_format parse_help_format(const char *format)
-{
-       if (!strcmp(format, "man"))
-               return HELP_FORMAT_MAN;
-       if (!strcmp(format, "info"))
-               return HELP_FORMAT_INFO;
-       if (!strcmp(format, "web") || !strcmp(format, "html"))
-               return HELP_FORMAT_WEB;
-       die("unrecognized help format '%s'", format);
-}
-
-static const char *get_man_viewer_info(const char *name)
-{
-       struct man_viewer_info_list *viewer;
-
-       for (viewer = man_viewer_info_list; viewer; viewer = viewer->next)
-       {
-               if (!strcasecmp(name, viewer->name))
-                       return viewer->info;
-       }
-       return NULL;
-}
-
-static int check_emacsclient_version(void)
-{
-       struct strbuf buffer = STRBUF_INIT;
-       struct child_process ec_process;
-       const char *argv_ec[] = { "emacsclient", "--version", NULL };
-       int version;
-
-       /* emacsclient prints its version number on stderr */
-       memset(&ec_process, 0, sizeof(ec_process));
-       ec_process.argv = argv_ec;
-       ec_process.err = -1;
-       ec_process.stdout_to_stderr = 1;
-       if (start_command(&ec_process)) {
-               fprintf(stderr, "Failed to start emacsclient.\n");
-               return -1;
-       }
-       strbuf_read(&buffer, ec_process.err, 20);
-       close(ec_process.err);
-
-       /*
-        * Don't bother checking return value, because "emacsclient --version"
-        * seems to always exits with code 1.
-        */
-       finish_command(&ec_process);
-
-       if (prefixcmp(buffer.buf, "emacsclient")) {
-               fprintf(stderr, "Failed to parse emacsclient version.\n");
-               strbuf_release(&buffer);
-               return -1;
-       }
-
-       strbuf_remove(&buffer, 0, strlen("emacsclient"));
-       version = atoi(buffer.buf);
-
-       if (version < 22) {
-               fprintf(stderr,
-                       "emacsclient version '%d' too old (< 22).\n",
-                       version);
-               strbuf_release(&buffer);
-               return -1;
-       }
-
-       strbuf_release(&buffer);
-       return 0;
-}
-
-static void exec_woman_emacs(const char *path, const char *page)
-{
-       if (!check_emacsclient_version()) {
-               /* This works only with emacsclient version >= 22. */
-               struct strbuf man_page = STRBUF_INIT;
-
-               if (!path)
-                       path = "emacsclient";
-               strbuf_addf(&man_page, "(woman \"%s\")", page);
-               execlp(path, "emacsclient", "-e", man_page.buf, NULL);
-               warning("failed to exec '%s': %s", path, strerror(errno));
-       }
-}
-
-static void exec_man_konqueror(const char *path, const char *page)
-{
-       const char *display = getenv("DISPLAY");
-       if (display && *display) {
-               struct strbuf man_page = STRBUF_INIT;
-               const char *filename = "kfmclient";
-
-               /* It's simpler to launch konqueror using kfmclient. */
-               if (path) {
-                       const char *file = strrchr(path, '/');
-                       if (file && !strcmp(file + 1, "konqueror")) {
-                               char *new = xstrdup(path);
-                               char *dest = strrchr(new, '/');
-
-                               /* strlen("konqueror") == strlen("kfmclient") */
-                               strcpy(dest + 1, "kfmclient");
-                               path = new;
-                       }
-                       if (file)
-                               filename = file;
-               } else
-                       path = "kfmclient";
-               strbuf_addf(&man_page, "man:%s(1)", page);
-               execlp(path, filename, "newTab", man_page.buf, NULL);
-               warning("failed to exec '%s': %s", path, strerror(errno));
-       }
-}
-
-static void exec_man_man(const char *path, const char *page)
-{
-       if (!path)
-               path = "man";
-       execlp(path, "man", page, NULL);
-       warning("failed to exec '%s': %s", path, strerror(errno));
-}
-
-static void exec_man_cmd(const char *cmd, const char *page)
-{
-       struct strbuf shell_cmd = STRBUF_INIT;
-       strbuf_addf(&shell_cmd, "%s %s", cmd, page);
-       execl("/bin/sh", "sh", "-c", shell_cmd.buf, NULL);
-       warning("failed to exec '%s': %s", cmd, strerror(errno));
-}
-
-static void add_man_viewer(const char *name)
-{
-       struct man_viewer_list **p = &man_viewer_list;
-       size_t len = strlen(name);
-
-       while (*p)
-               p = &((*p)->next);
-       *p = xcalloc(1, (sizeof(**p) + len + 1));
-       strncpy((*p)->name, name, len);
-}
-
-static int supported_man_viewer(const char *name, size_t len)
-{
-       return (!strncasecmp("man", name, len) ||
-               !strncasecmp("woman", name, len) ||
-               !strncasecmp("konqueror", name, len));
-}
-
-static void do_add_man_viewer_info(const char *name,
-                                  size_t len,
-                                  const char *value)
-{
-       struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1);
-
-       strncpy(new->name, name, len);
-       new->info = xstrdup(value);
-       new->next = man_viewer_info_list;
-       man_viewer_info_list = new;
-}
-
-static int add_man_viewer_path(const char *name,
-                              size_t len,
-                              const char *value)
-{
-       if (supported_man_viewer(name, len))
-               do_add_man_viewer_info(name, len, value);
-       else
-               warning("'%s': path for unsupported man viewer.\n"
-                       "Please consider using 'man.<tool>.cmd' instead.",
-                       name);
-
-       return 0;
-}
-
-static int add_man_viewer_cmd(const char *name,
-                             size_t len,
-                             const char *value)
-{
-       if (supported_man_viewer(name, len))
-               warning("'%s': cmd for supported man viewer.\n"
-                       "Please consider using 'man.<tool>.path' instead.",
-                       name);
-       else
-               do_add_man_viewer_info(name, len, value);
-
-       return 0;
-}
-
-static int add_man_viewer_info(const char *var, const char *value)
-{
-       const char *name = var + 4;
-       const char *subkey = strrchr(name, '.');
-
-       if (!subkey)
-               return 0;
-
-       if (!strcmp(subkey, ".path")) {
-               if (!value)
-                       return config_error_nonbool(var);
-               return add_man_viewer_path(name, subkey - name, value);
-       }
-       if (!strcmp(subkey, ".cmd")) {
-               if (!value)
-                       return config_error_nonbool(var);
-               return add_man_viewer_cmd(name, subkey - name, value);
-       }
-
-       return 0;
-}
-
-static int git_help_config(const char *var, const char *value, void *cb)
-{
-       if (!strcmp(var, "help.format")) {
-               if (!value)
-                       return config_error_nonbool(var);
-               help_format = parse_help_format(value);
-               return 0;
-       }
-       if (!strcmp(var, "man.viewer")) {
-               if (!value)
-                       return config_error_nonbool(var);
-               add_man_viewer(value);
-               return 0;
-       }
-       if (!prefixcmp(var, "man."))
-               return add_man_viewer_info(var, value);
-
-       return git_default_config(var, value, cb);
-}
-
-static struct cmdnames main_cmds, other_cmds;
-
-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));
-               puts(common_cmds[i].help);
-       }
-}
-
-static int is_git_command(const char *s)
-{
-       return is_in_cmdlist(&main_cmds, s) ||
-               is_in_cmdlist(&other_cmds, s);
-}
-
-static const char *prepend(const char *prefix, const char *cmd)
-{
-       size_t pre_len = strlen(prefix);
-       size_t cmd_len = strlen(cmd);
-       char *p = xmalloc(pre_len + cmd_len + 1);
-       memcpy(p, prefix, pre_len);
-       strcpy(p + pre_len, cmd);
-       return p;
-}
-
-static const char *cmd_to_page(const char *git_cmd)
-{
-       if (!git_cmd)
-               return "git";
-       else if (!prefixcmp(git_cmd, "git"))
-               return git_cmd;
-       else if (is_git_command(git_cmd))
-               return prepend("git-", git_cmd);
-       else
-               return prepend("git", git_cmd);
-}
-
-static void setup_man_path(void)
-{
-       struct strbuf new_path = STRBUF_INIT;
-       const char *old_path = getenv("MANPATH");
-
-       /* We should always put ':' after our path. If there is no
-        * old_path, the ':' at the end will let 'man' to try
-        * system-wide paths after ours to find the manual page. If
-        * there is old_path, we need ':' as delimiter. */
-       strbuf_addstr(&new_path, system_path(GIT_MAN_PATH));
-       strbuf_addch(&new_path, ':');
-       if (old_path)
-               strbuf_addstr(&new_path, old_path);
-
-       setenv("MANPATH", new_path.buf, 1);
-
-       strbuf_release(&new_path);
-}
-
-static void exec_viewer(const char *name, const char *page)
-{
-       const char *info = get_man_viewer_info(name);
-
-       if (!strcasecmp(name, "man"))
-               exec_man_man(info, page);
-       else if (!strcasecmp(name, "woman"))
-               exec_woman_emacs(info, page);
-       else if (!strcasecmp(name, "konqueror"))
-               exec_man_konqueror(info, page);
-       else if (info)
-               exec_man_cmd(info, page);
-       else
-               warning("'%s': unknown man viewer.", name);
-}
-
-static void show_man_page(const char *git_cmd)
-{
-       struct man_viewer_list *viewer;
-       const char *page = cmd_to_page(git_cmd);
-       const char *fallback = getenv("GIT_MAN_VIEWER");
-
-       setup_man_path();
-       for (viewer = man_viewer_list; viewer; viewer = viewer->next)
-       {
-               exec_viewer(viewer->name, page); /* will return when unable */
-       }
-       if (fallback)
-               exec_viewer(fallback, page);
-       exec_viewer("man", page);
-       die("no man viewer handled the request");
-}
-
-static void show_info_page(const char *git_cmd)
-{
-       const char *page = cmd_to_page(git_cmd);
-       setenv("INFOPATH", system_path(GIT_INFO_PATH), 1);
-       execlp("info", "info", "gitman", page, NULL);
-}
-
-static void get_html_page_path(struct strbuf *page_path, const char *page)
-{
-       struct stat st;
-       const char *html_path = system_path(GIT_HTML_PATH);
-
-       /* Check that we have a git documentation directory. */
-       if (stat(mkpath("%s/git.html", html_path), &st)
-           || !S_ISREG(st.st_mode))
-               die("'%s': not a documentation directory.", html_path);
-
-       strbuf_init(page_path, 0);
-       strbuf_addf(page_path, "%s/%s.html", html_path, page);
-}
-
-/*
- * If open_html is not defined in a platform-specific way (see for
- * example compat/mingw.h), we use the script web--browse to display
- * HTML.
- */
-#ifndef open_html
-void open_html(const char *path)
-{
-       execl_git_cmd("web--browse", "-c", "help.browser", path, NULL);
-}
-#endif
-
-static void show_html_page(const char *git_cmd)
-{
-       const char *page = cmd_to_page(git_cmd);
-       struct strbuf page_path; /* it leaks but we exec bellow */
-
-       get_html_page_path(&page_path, page);
-
-       open_html(page_path.buf);
-}
-
-int cmd_help(int argc, const char **argv, const char *prefix)
-{
-       int nongit;
-       const char *alias;
-       load_command_list("git-", &main_cmds, &other_cmds);
-
-       setup_git_directory_gently(&nongit);
-       git_config(git_help_config, NULL);
-
-       argc = parse_options(argc, argv, builtin_help_options,
-                       builtin_help_usage, 0);
-
-       if (show_all) {
-               printf("usage: %s\n\n", git_usage_string);
-               list_commands("git commands", &main_cmds, &other_cmds);
-               printf("%s\n", git_more_info_string);
-               return 0;
-       }
-
-       if (!argv[0]) {
-               printf("usage: %s\n\n", git_usage_string);
-               list_common_cmds_help();
-               printf("\n%s\n", git_more_info_string);
-               return 0;
-       }
-
-       alias = alias_lookup(argv[0]);
-       if (alias && !is_git_command(argv[0])) {
-               printf("`git %s' is aliased to `%s'\n", argv[0], alias);
-               return 0;
-       }
-
-       switch (help_format) {
-       case HELP_FORMAT_MAN:
-               show_man_page(argv[0]);
-               break;
-       case HELP_FORMAT_INFO:
-               show_info_page(argv[0]);
-               break;
-       case HELP_FORMAT_WEB:
-               show_html_page(argv[0]);
-               break;
-       }
-
-       return 0;
-}
diff --git a/builtin-http-fetch.c b/builtin-http-fetch.c
deleted file mode 100644 (file)
index f3e63d7..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-#include "cache.h"
-#include "walker.h"
-
-int cmd_http_fetch(int argc, const char **argv, const char *prefix)
-{
-       struct walker *walker;
-       int commits_on_stdin = 0;
-       int commits;
-       const char **write_ref = NULL;
-       char **commit_id;
-       const char *url;
-       char *rewritten_url = NULL;
-       int arg = 1;
-       int rc = 0;
-       int get_tree = 0;
-       int get_history = 0;
-       int get_all = 0;
-       int get_verbosely = 0;
-       int get_recover = 0;
-
-       git_config(git_default_config, NULL);
-
-       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;
-               } else if (!strcmp(argv[arg], "--stdin")) {
-                       commits_on_stdin = 1;
-               }
-               arg++;
-       }
-       if (argc < arg + 2 - commits_on_stdin) {
-               usage("git http-fetch [-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url");
-               return 1;
-       }
-       if (commits_on_stdin) {
-               commits = walker_targets_stdin(&commit_id, &write_ref);
-       } else {
-               commit_id = (char **) &argv[arg++];
-               commits = 1;
-       }
-       url = argv[arg];
-       if (url && url[strlen(url)-1] != '/') {
-               rewritten_url = xmalloc(strlen(url)+2);
-               strcpy(rewritten_url, url);
-               strcat(rewritten_url, "/");
-               url = rewritten_url;
-       }
-
-       walker = get_http_walker(url, NULL);
-       walker->get_tree = get_tree;
-       walker->get_history = get_history;
-       walker->get_all = get_all;
-       walker->get_verbosely = get_verbosely;
-       walker->get_recover = get_recover;
-
-       rc = walker_fetch(walker, commits, commit_id, write_ref, url);
-
-       if (commits_on_stdin)
-               walker_targets_free(commits, commit_id, write_ref);
-
-       if (walker->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'.\n");
-       }
-
-       walker_free(walker);
-
-       free(rewritten_url);
-
-       return rc;
-}
diff --git a/builtin-init-db.c b/builtin-init-db.c
deleted file mode 100644 (file)
index d1fa12a..0000000
+++ /dev/null
@@ -1,453 +0,0 @@
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Linus Torvalds, 2005
- */
-#include "cache.h"
-#include "builtin.h"
-#include "exec_cmd.h"
-
-#ifndef DEFAULT_GIT_TEMPLATE_DIR
-#define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates"
-#endif
-
-#ifdef NO_TRUSTABLE_FILEMODE
-#define TEST_FILEMODE 0
-#else
-#define TEST_FILEMODE 1
-#endif
-
-static int init_is_bare_repository = 0;
-static int init_shared_repository = -1;
-
-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", dir);
-}
-
-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" 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 *template_dir)
-{
-       char path[PATH_MAX];
-       char template_path[PATH_MAX];
-       int template_len;
-       DIR *dir;
-       const char *git_dir = get_git_dir();
-       int len = strlen(git_dir);
-
-       if (!template_dir)
-               template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
-       if (!template_dir)
-               template_dir = system_path(DEFAULT_GIT_TEMPLATE_DIR);
-       if (!template_dir[0])
-               return;
-       template_len = strlen(template_dir);
-       if (PATH_MAX <= (template_len+strlen("/config")))
-               die("insanely long template path %s", template_dir);
-       strcpy(template_path, template_dir);
-       if (template_path[template_len-1] != '/') {
-               template_path[template_len++] = '/';
-               template_path[template_len] = 0;
-       }
-       dir = opendir(template_path);
-       if (!dir) {
-               warning("templates not found %s", 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, NULL);
-       template_path[template_len] = 0;
-
-       if (repository_format_version &&
-           repository_format_version != GIT_REPO_VERSION) {
-               warning("not copying templates of "
-                       "a wrong format version %d from '%s'",
-                       repository_format_version,
-                       template_dir);
-               closedir(dir);
-               return;
-       }
-
-       memcpy(path, git_dir, len);
-       if (len && path[len - 1] != '/')
-               path[len++] = '/';
-       path[len] = 0;
-       copy_templates_1(path, len,
-                        template_path, template_len,
-                        dir);
-       closedir(dir);
-}
-
-static int create_default_files(const char *template_path)
-{
-       const char *git_dir = get_git_dir();
-       unsigned len = strlen(git_dir);
-       static char path[PATH_MAX];
-       struct stat st1;
-       char repo_version_string[10];
-       char junk[2];
-       int reinit;
-       int filemode;
-
-       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}
-        */
-       safe_create_dir(git_path("refs"), 1);
-       safe_create_dir(git_path("refs/heads"), 1);
-       safe_create_dir(git_path("refs/tags"), 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.
-        */
-       copy_templates(template_path);
-
-       git_config(git_default_config, NULL);
-       is_bare_repository_cfg = init_is_bare_repository;
-
-       /* reading existing config may have overwrote it */
-       if (init_shared_repository != -1)
-               shared_repository = init_shared_repository;
-
-       /*
-        * We would have created the above under user's umask -- under
-        * shared-repository settings, we would need to fix them up.
-        */
-       if (shared_repository) {
-               adjust_shared_perm(get_git_dir());
-               adjust_shared_perm(git_path("refs"));
-               adjust_shared_perm(git_path("refs/heads"));
-               adjust_shared_perm(git_path("refs/tags"));
-       }
-
-       /*
-        * Create the default symlink from ".git/HEAD" to the "master"
-        * branch, if it does not exist yet.
-        */
-       strcpy(path + len, "HEAD");
-       reinit = (!access(path, R_OK)
-                 || readlink(path, junk, sizeof(junk)-1) != -1);
-       if (!reinit) {
-               if (create_symref("HEAD", "refs/heads/master", NULL) < 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 */
-       filemode = TEST_FILEMODE;
-       if (TEST_FILEMODE && !lstat(path, &st1)) {
-               struct stat st2;
-               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");
-
-       if (is_bare_repository())
-               git_config_set("core.bare", "true");
-       else {
-               const char *work_tree = get_git_work_tree();
-               git_config_set("core.bare", "false");
-               /* allow template config file to override the default */
-               if (log_all_ref_updates == -1)
-                   git_config_set("core.logallrefupdates", "true");
-               if (prefixcmp(git_dir, work_tree) ||
-                   strcmp(git_dir + strlen(work_tree), "/.git")) {
-                       git_config_set("core.worktree", work_tree);
-               }
-       }
-
-       if (!reinit) {
-               /* Check if symlink is supported in the work tree */
-               path[len] = 0;
-               strcpy(path + len, "tXXXXXX");
-               if (!close(xmkstemp(path)) &&
-                   !unlink(path) &&
-                   !symlink("testing", path) &&
-                   !lstat(path, &st1) &&
-                   S_ISLNK(st1.st_mode))
-                       unlink(path); /* good */
-               else
-                       git_config_set("core.symlinks", "false");
-
-               /* Check if the filesystem is case-insensitive */
-               path[len] = 0;
-               strcpy(path + len, "CoNfIg");
-               if (!access(path, F_OK))
-                       git_config_set("core.ignorecase", "true");
-       }
-
-       return reinit;
-}
-
-int init_db(const char *template_dir, unsigned int flags)
-{
-       const char *sha1_dir;
-       char *path;
-       int len, reinit;
-
-       safe_create_dir(get_git_dir(), 0);
-
-       init_is_bare_repository = is_bare_repository();
-
-       /* 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();
-
-       reinit = create_default_files(template_dir);
-
-       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) {
-               char buf[10];
-               /* We do not spell "group" and such, so that
-                * the configuration can be read by older version
-                * of git. Note, we use octal numbers for new share modes,
-                * and compatibility values for PERM_GROUP and
-                * PERM_EVERYBODY.
-                */
-               if (shared_repository < 0)
-                       /* force to the mode value */
-                       sprintf(buf, "0%o", -shared_repository);
-               else if (shared_repository == PERM_GROUP)
-                       sprintf(buf, "%d", OLD_PERM_GROUP);
-               else if (shared_repository == PERM_EVERYBODY)
-                       sprintf(buf, "%d", OLD_PERM_EVERYBODY);
-               else
-                       die("oops");
-               git_config_set("core.sharedrepository", buf);
-               git_config_set("receive.denyNonFastforwards", "true");
-       }
-
-       if (!(flags & INIT_DB_QUIET))
-               printf("%s%s Git repository in %s/\n",
-                      reinit ? "Reinitialized existing" : "Initialized empty",
-                      shared_repository ? " shared" : "",
-                      get_git_dir());
-
-       return 0;
-}
-
-static int guess_repository_type(const char *git_dir)
-{
-       char cwd[PATH_MAX];
-       const char *slash;
-
-       /*
-        * "GIT_DIR=. git init" is always bare.
-        * "GIT_DIR=`pwd` git init" too.
-        */
-       if (!strcmp(".", git_dir))
-               return 1;
-       if (!getcwd(cwd, sizeof(cwd)))
-               die("cannot tell cwd");
-       if (!strcmp(git_dir, cwd))
-               return 1;
-       /*
-        * "GIT_DIR=.git or GIT_DIR=something/.git is usually not.
-        */
-       if (!strcmp(git_dir, ".git"))
-               return 0;
-       slash = strrchr(git_dir, '/');
-       if (slash && !strcmp(slash, "/.git"))
-               return 0;
-
-       /*
-        * Otherwise it is often bare.  At this point
-        * we are just guessing.
-        */
-       return 1;
-}
-
-static const char init_db_usage[] =
-"git init [-q | --quiet] [--bare] [--template=<template-directory>] [--shared[=<permissions>]]";
-
-/*
- * 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, const char *prefix)
-{
-       const char *git_dir;
-       const char *template_dir = NULL;
-       unsigned int flags = 0;
-       int i;
-
-       for (i = 1; i < argc; i++, argv++) {
-               const char *arg = argv[1];
-               if (!prefixcmp(arg, "--template="))
-                       template_dir = arg+11;
-               else if (!strcmp(arg, "--bare")) {
-                       static char git_dir[PATH_MAX+1];
-                       is_bare_repository_cfg = 1;
-                       setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir,
-                                               sizeof(git_dir)), 0);
-               } else if (!strcmp(arg, "--shared"))
-                       init_shared_repository = PERM_GROUP;
-               else if (!prefixcmp(arg, "--shared="))
-                       init_shared_repository = git_config_perm("arg", arg+9);
-               else if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet"))
-                       flags |= INIT_DB_QUIET;
-               else
-                       usage(init_db_usage);
-       }
-
-       if (init_shared_repository != -1)
-               shared_repository = init_shared_repository;
-
-       /*
-        * GIT_WORK_TREE makes sense only in conjunction with GIT_DIR
-        * without --bare.  Catch the error early.
-        */
-       git_dir = getenv(GIT_DIR_ENVIRONMENT);
-       if ((!git_dir || is_bare_repository_cfg == 1)
-           && getenv(GIT_WORK_TREE_ENVIRONMENT))
-               die("%s (or --work-tree=<directory>) not allowed without "
-                   "specifying %s (or --git-dir=<directory>)",
-                   GIT_WORK_TREE_ENVIRONMENT,
-                   GIT_DIR_ENVIRONMENT);
-
-       /*
-        * Set up the default .git directory contents
-        */
-       if (!git_dir)
-               git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
-
-       if (is_bare_repository_cfg < 0)
-               is_bare_repository_cfg = guess_repository_type(git_dir);
-
-       if (!is_bare_repository_cfg) {
-               if (git_dir) {
-                       const char *git_dir_parent = strrchr(git_dir, '/');
-                       if (git_dir_parent) {
-                               char *rel = xstrndup(git_dir, git_dir_parent - git_dir);
-                               git_work_tree_cfg = xstrdup(make_absolute_path(rel));
-                               free(rel);
-                       }
-               }
-               if (!git_work_tree_cfg) {
-                       git_work_tree_cfg = xcalloc(PATH_MAX, 1);
-                       if (!getcwd(git_work_tree_cfg, PATH_MAX))
-                               die ("Cannot access current working directory.");
-               }
-               if (access(get_git_work_tree(), X_OK))
-                       die ("Cannot access work tree '%s'",
-                            get_git_work_tree());
-       }
-
-       set_git_dir(make_absolute_path(git_dir));
-
-       return init_db(template_dir, flags);
-}
diff --git a/builtin-log.c b/builtin-log.c
deleted file mode 100644 (file)
index 5eaec5d..0000000
+++ /dev/null
@@ -1,1223 +0,0 @@
-/*
- * Builtin "git log" and related commands (show, whatchanged)
- *
- * (C) Copyright 2006 Linus Torvalds
- *              2006 Junio Hamano
- */
-#include "cache.h"
-#include "color.h"
-#include "commit.h"
-#include "diff.h"
-#include "revision.h"
-#include "log-tree.h"
-#include "builtin.h"
-#include "tag.h"
-#include "reflog-walk.h"
-#include "patch-ids.h"
-#include "run-command.h"
-#include "shortlog.h"
-#include "remote.h"
-#include "string-list.h"
-
-/* Set a default date-time format for git log ("log.date" config variable) */
-static const char *default_date_mode = NULL;
-
-static int default_show_root = 1;
-static const char *fmt_patch_subject_prefix = "PATCH";
-static const char *fmt_pretty;
-
-static void cmd_log_init(int argc, const char **argv, const char *prefix,
-                     struct rev_info *rev)
-{
-       int i;
-
-       rev->abbrev = DEFAULT_ABBREV;
-       rev->commit_format = CMIT_FMT_DEFAULT;
-       if (fmt_pretty)
-               get_commit_format(fmt_pretty, rev);
-       rev->verbose_header = 1;
-       DIFF_OPT_SET(&rev->diffopt, RECURSIVE);
-       rev->show_root_diff = default_show_root;
-       rev->subject_prefix = fmt_patch_subject_prefix;
-       DIFF_OPT_SET(&rev->diffopt, ALLOW_TEXTCONV);
-
-       if (default_date_mode)
-               rev->date_mode = parse_date_format(default_date_mode);
-
-       argc = setup_revisions(argc, argv, rev, "HEAD");
-
-       if (rev->diffopt.pickaxe || rev->diffopt.filter)
-               rev->always_show_header = 0;
-       if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) {
-               rev->always_show_header = 0;
-               if (rev->diffopt.nr_paths != 1)
-                       usage("git logs can only follow renames on one pathname at a time");
-       }
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-               if (!strcmp(arg, "--decorate")) {
-                       load_ref_decorations();
-                       rev->show_decorations = 1;
-               } else if (!strcmp(arg, "--source")) {
-                       rev->show_source = 1;
-               } else
-                       die("unrecognized argument: %s", arg);
-       }
-}
-
-/*
- * This gives a rough estimate for how many commits we
- * will print out in the list.
- */
-static int estimate_commit_count(struct rev_info *rev, struct commit_list *list)
-{
-       int n = 0;
-
-       while (list) {
-               struct commit *commit = list->item;
-               unsigned int flags = commit->object.flags;
-               list = list->next;
-               if (!(flags & (TREESAME | UNINTERESTING)))
-                       n++;
-       }
-       return n;
-}
-
-static void show_early_header(struct rev_info *rev, const char *stage, int nr)
-{
-       if (rev->shown_one) {
-               rev->shown_one = 0;
-               if (rev->commit_format != CMIT_FMT_ONELINE)
-                       putchar(rev->diffopt.line_termination);
-       }
-       printf("Final output: %d %s\n", nr, stage);
-}
-
-struct itimerval early_output_timer;
-
-static void log_show_early(struct rev_info *revs, struct commit_list *list)
-{
-       int i = revs->early_output;
-       int show_header = 1;
-
-       sort_in_topological_order(&list, revs->lifo);
-       while (list && i) {
-               struct commit *commit = list->item;
-               switch (simplify_commit(revs, commit)) {
-               case commit_show:
-                       if (show_header) {
-                               int n = estimate_commit_count(revs, list);
-                               show_early_header(revs, "incomplete", n);
-                               show_header = 0;
-                       }
-                       log_tree_commit(revs, commit);
-                       i--;
-                       break;
-               case commit_ignore:
-                       break;
-               case commit_error:
-                       return;
-               }
-               list = list->next;
-       }
-
-       /* Did we already get enough commits for the early output? */
-       if (!i)
-               return;
-
-       /*
-        * ..if no, then repeat it twice a second until we
-        * do.
-        *
-        * NOTE! We don't use "it_interval", because if the
-        * reader isn't listening, we want our output to be
-        * throttled by the writing, and not have the timer
-        * trigger every second even if we're blocked on a
-        * reader!
-        */
-       early_output_timer.it_value.tv_sec = 0;
-       early_output_timer.it_value.tv_usec = 500000;
-       setitimer(ITIMER_REAL, &early_output_timer, NULL);
-}
-
-static void early_output(int signal)
-{
-       show_early_output = log_show_early;
-}
-
-static void setup_early_output(struct rev_info *rev)
-{
-       struct sigaction sa;
-
-       /*
-        * Set up the signal handler, minimally intrusively:
-        * we only set a single volatile integer word (not
-        * using sigatomic_t - trying to avoid unnecessary
-        * system dependencies and headers), and using
-        * SA_RESTART.
-        */
-       memset(&sa, 0, sizeof(sa));
-       sa.sa_handler = early_output;
-       sigemptyset(&sa.sa_mask);
-       sa.sa_flags = SA_RESTART;
-       sigaction(SIGALRM, &sa, NULL);
-
-       /*
-        * If we can get the whole output in less than a
-        * tenth of a second, don't even bother doing the
-        * early-output thing..
-        *
-        * This is a one-time-only trigger.
-        */
-       early_output_timer.it_value.tv_sec = 0;
-       early_output_timer.it_value.tv_usec = 100000;
-       setitimer(ITIMER_REAL, &early_output_timer, NULL);
-}
-
-static void finish_early_output(struct rev_info *rev)
-{
-       int n = estimate_commit_count(rev, rev->commits);
-       signal(SIGALRM, SIG_IGN);
-       show_early_header(rev, "done", n);
-}
-
-static int cmd_log_walk(struct rev_info *rev)
-{
-       struct commit *commit;
-
-       if (rev->early_output)
-               setup_early_output(rev);
-
-       if (prepare_revision_walk(rev))
-               die("revision walk setup failed");
-
-       if (rev->early_output)
-               finish_early_output(rev);
-
-       /*
-        * For --check and --exit-code, the exit code is based on CHECK_FAILED
-        * and HAS_CHANGES being accumulated in rev->diffopt, so be careful to
-        * retain that state information if replacing rev->diffopt in this loop
-        */
-       while ((commit = get_revision(rev)) != NULL) {
-               log_tree_commit(rev, commit);
-               if (!rev->reflog_info) {
-                       /* we allow cycles in reflog ancestry */
-                       free(commit->buffer);
-                       commit->buffer = NULL;
-               }
-               free_commit_list(commit->parents);
-               commit->parents = NULL;
-       }
-       if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF &&
-           DIFF_OPT_TST(&rev->diffopt, CHECK_FAILED)) {
-               return 02;
-       }
-       return diff_result_code(&rev->diffopt, 0);
-}
-
-static int git_log_config(const char *var, const char *value, void *cb)
-{
-       if (!strcmp(var, "format.pretty"))
-               return git_config_string(&fmt_pretty, var, value);
-       if (!strcmp(var, "format.subjectprefix"))
-               return git_config_string(&fmt_patch_subject_prefix, var, value);
-       if (!strcmp(var, "log.date"))
-               return git_config_string(&default_date_mode, var, value);
-       if (!strcmp(var, "log.showroot")) {
-               default_show_root = git_config_bool(var, value);
-               return 0;
-       }
-       return git_diff_ui_config(var, value, cb);
-}
-
-int cmd_whatchanged(int argc, const char **argv, const char *prefix)
-{
-       struct rev_info rev;
-
-       git_config(git_log_config, NULL);
-
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
-
-       init_revisions(&rev, prefix);
-       rev.diff = 1;
-       rev.simplify_history = 0;
-       cmd_log_init(argc, argv, prefix, &rev);
-       if (!rev.diffopt.output_format)
-               rev.diffopt.output_format = DIFF_FORMAT_RAW;
-       return cmd_log_walk(&rev);
-}
-
-static void show_tagger(char *buf, int len, struct rev_info *rev)
-{
-       struct strbuf out = STRBUF_INIT;
-
-       pp_user_info("Tagger", rev->commit_format, &out, buf, rev->date_mode,
-               git_log_output_encoding ?
-               git_log_output_encoding: git_commit_encoding);
-       printf("%s\n", out.buf);
-       strbuf_release(&out);
-}
-
-static int show_object(const unsigned char *sha1, int show_tag_object,
-       struct rev_info *rev)
-{
-       unsigned long size;
-       enum object_type type;
-       char *buf = read_sha1_file(sha1, &type, &size);
-       int offset = 0;
-
-       if (!buf)
-               return error("Could not read object %s", sha1_to_hex(sha1));
-
-       if (show_tag_object)
-               while (offset < size && buf[offset] != '\n') {
-                       int new_offset = offset + 1;
-                       while (new_offset < size && buf[new_offset++] != '\n')
-                               ; /* do nothing */
-                       if (!prefixcmp(buf + offset, "tagger "))
-                               show_tagger(buf + offset + 7,
-                                           new_offset - offset - 7, rev);
-                       offset = new_offset;
-               }
-
-       if (offset < size)
-               fwrite(buf + offset, size - offset, 1, stdout);
-       free(buf);
-       return 0;
-}
-
-static int show_tree_object(const unsigned char *sha1,
-               const char *base, int baselen,
-               const char *pathname, unsigned mode, int stage, void *context)
-{
-       printf("%s%s\n", pathname, S_ISDIR(mode) ? "/" : "");
-       return 0;
-}
-
-int cmd_show(int argc, const char **argv, const char *prefix)
-{
-       struct rev_info rev;
-       struct object_array_entry *objects;
-       int i, count, ret = 0;
-
-       git_config(git_log_config, NULL);
-
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
-
-       init_revisions(&rev, prefix);
-       rev.diff = 1;
-       rev.combine_merges = 1;
-       rev.dense_combined_merges = 1;
-       rev.always_show_header = 1;
-       rev.ignore_merges = 0;
-       rev.no_walk = 1;
-       cmd_log_init(argc, argv, prefix, &rev);
-
-       count = rev.pending.nr;
-       objects = rev.pending.objects;
-       for (i = 0; i < count && !ret; i++) {
-               struct object *o = objects[i].item;
-               const char *name = objects[i].name;
-               switch (o->type) {
-               case OBJ_BLOB:
-                       ret = show_object(o->sha1, 0, NULL);
-                       break;
-               case OBJ_TAG: {
-                       struct tag *t = (struct tag *)o;
-
-                       printf("%stag %s%s\n",
-                                       diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
-                                       t->tag,
-                                       diff_get_color_opt(&rev.diffopt, DIFF_RESET));
-                       ret = show_object(o->sha1, 1, &rev);
-                       if (ret)
-                               break;
-                       o = parse_object(t->tagged->sha1);
-                       if (!o)
-                               ret = error("Could not read object %s",
-                                           sha1_to_hex(t->tagged->sha1));
-                       objects[i].item = o;
-                       i--;
-                       break;
-               }
-               case OBJ_TREE:
-                       printf("%stree %s%s\n\n",
-                                       diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
-                                       name,
-                                       diff_get_color_opt(&rev.diffopt, DIFF_RESET));
-                       read_tree_recursive((struct tree *)o, "", 0, 0, NULL,
-                                       show_tree_object, NULL);
-                       break;
-               case OBJ_COMMIT:
-                       rev.pending.nr = rev.pending.alloc = 0;
-                       rev.pending.objects = NULL;
-                       add_object_array(o, name, &rev.pending);
-                       ret = cmd_log_walk(&rev);
-                       break;
-               default:
-                       ret = error("Unknown type: %d", o->type);
-               }
-       }
-       free(objects);
-       return ret;
-}
-
-/*
- * This is equivalent to "git log -g --abbrev-commit --pretty=oneline"
- */
-int cmd_log_reflog(int argc, const char **argv, const char *prefix)
-{
-       struct rev_info rev;
-
-       git_config(git_log_config, NULL);
-
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
-
-       init_revisions(&rev, prefix);
-       init_reflog_walk(&rev.reflog_info);
-       rev.abbrev_commit = 1;
-       rev.verbose_header = 1;
-       cmd_log_init(argc, argv, prefix, &rev);
-
-       /*
-        * This means that we override whatever commit format the user gave
-        * on the cmd line.  Sad, but cmd_log_init() currently doesn't
-        * allow us to set a different default.
-        */
-       rev.commit_format = CMIT_FMT_ONELINE;
-       rev.use_terminator = 1;
-       rev.always_show_header = 1;
-
-       /*
-        * We get called through "git reflog", so unlike the other log
-        * routines, we need to set up our pager manually..
-        */
-       setup_pager();
-
-       return cmd_log_walk(&rev);
-}
-
-int cmd_log(int argc, const char **argv, const char *prefix)
-{
-       struct rev_info rev;
-
-       git_config(git_log_config, NULL);
-
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
-
-       init_revisions(&rev, prefix);
-       rev.always_show_header = 1;
-       cmd_log_init(argc, argv, prefix, &rev);
-       return cmd_log_walk(&rev);
-}
-
-/* format-patch */
-
-static const char *fmt_patch_suffix = ".patch";
-static int numbered = 0;
-static int auto_number = 1;
-
-static char *default_attach = NULL;
-
-static char **extra_hdr;
-static int extra_hdr_nr;
-static int extra_hdr_alloc;
-
-static char **extra_to;
-static int extra_to_nr;
-static int extra_to_alloc;
-
-static char **extra_cc;
-static int extra_cc_nr;
-static int extra_cc_alloc;
-
-static void add_header(const char *value)
-{
-       int len = strlen(value);
-       while (len && value[len - 1] == '\n')
-               len--;
-       if (!strncasecmp(value, "to: ", 4)) {
-               ALLOC_GROW(extra_to, extra_to_nr + 1, extra_to_alloc);
-               extra_to[extra_to_nr++] = xstrndup(value + 4, len - 4);
-               return;
-       }
-       if (!strncasecmp(value, "cc: ", 4)) {
-               ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc);
-               extra_cc[extra_cc_nr++] = xstrndup(value + 4, len - 4);
-               return;
-       }
-       ALLOC_GROW(extra_hdr, extra_hdr_nr + 1, extra_hdr_alloc);
-       extra_hdr[extra_hdr_nr++] = xstrndup(value, len);
-}
-
-#define THREAD_SHALLOW 1
-#define THREAD_DEEP 2
-static int thread = 0;
-static int do_signoff = 0;
-
-static int git_format_config(const char *var, const char *value, void *cb)
-{
-       if (!strcmp(var, "format.headers")) {
-               if (!value)
-                       die("format.headers without value");
-               add_header(value);
-               return 0;
-       }
-       if (!strcmp(var, "format.suffix"))
-               return git_config_string(&fmt_patch_suffix, var, value);
-       if (!strcmp(var, "format.cc")) {
-               if (!value)
-                       return config_error_nonbool(var);
-               ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc);
-               extra_cc[extra_cc_nr++] = xstrdup(value);
-               return 0;
-       }
-       if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
-               return 0;
-       }
-       if (!strcmp(var, "format.numbered")) {
-               if (value && !strcasecmp(value, "auto")) {
-                       auto_number = 1;
-                       return 0;
-               }
-               numbered = git_config_bool(var, value);
-               auto_number = auto_number && numbered;
-               return 0;
-       }
-       if (!strcmp(var, "format.attach")) {
-               if (value && *value)
-                       default_attach = xstrdup(value);
-               else
-                       default_attach = xstrdup(git_version_string);
-               return 0;
-       }
-       if (!strcmp(var, "format.thread")) {
-               if (value && !strcasecmp(value, "deep")) {
-                       thread = THREAD_DEEP;
-                       return 0;
-               }
-               if (value && !strcasecmp(value, "shallow")) {
-                       thread = THREAD_SHALLOW;
-                       return 0;
-               }
-               thread = git_config_bool(var, value) && THREAD_SHALLOW;
-               return 0;
-       }
-       if (!strcmp(var, "format.signoff")) {
-               do_signoff = git_config_bool(var, value);
-               return 0;
-       }
-
-       return git_log_config(var, value, cb);
-}
-
-static FILE *realstdout = NULL;
-static const char *output_directory = NULL;
-static int outdir_offset;
-
-static int reopen_stdout(struct commit *commit, struct rev_info *rev)
-{
-       struct strbuf filename = STRBUF_INIT;
-       int suffix_len = strlen(fmt_patch_suffix) + 1;
-
-       if (output_directory) {
-               strbuf_addstr(&filename, output_directory);
-               if (filename.len >=
-                   PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len)
-                       return error("name of output directory is too long");
-               if (filename.buf[filename.len - 1] != '/')
-                       strbuf_addch(&filename, '/');
-       }
-
-       get_patch_filename(commit, rev->nr, fmt_patch_suffix, &filename);
-
-       if (!DIFF_OPT_TST(&rev->diffopt, QUIET))
-               fprintf(realstdout, "%s\n", filename.buf + outdir_offset);
-
-       if (freopen(filename.buf, "w", stdout) == NULL)
-               return error("Cannot open patch file %s", filename.buf);
-
-       strbuf_release(&filename);
-       return 0;
-}
-
-static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const char *prefix)
-{
-       struct rev_info check_rev;
-       struct commit *commit;
-       struct object *o1, *o2;
-       unsigned flags1, flags2;
-
-       if (rev->pending.nr != 2)
-               die("Need exactly one range.");
-
-       o1 = rev->pending.objects[0].item;
-       flags1 = o1->flags;
-       o2 = rev->pending.objects[1].item;
-       flags2 = o2->flags;
-
-       if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING))
-               die("Not a range.");
-
-       init_patch_ids(ids);
-
-       /* given a range a..b get all patch ids for b..a */
-       init_revisions(&check_rev, prefix);
-       o1->flags ^= UNINTERESTING;
-       o2->flags ^= UNINTERESTING;
-       add_pending_object(&check_rev, o1, "o1");
-       add_pending_object(&check_rev, o2, "o2");
-       if (prepare_revision_walk(&check_rev))
-               die("revision walk setup failed");
-
-       while ((commit = get_revision(&check_rev)) != NULL) {
-               /* ignore merges */
-               if (commit->parents && commit->parents->next)
-                       continue;
-
-               add_commit_patch_id(commit, ids);
-       }
-
-       /* reset for next revision walk */
-       clear_commit_marks((struct commit *)o1,
-                       SEEN | UNINTERESTING | SHOWN | ADDED);
-       clear_commit_marks((struct commit *)o2,
-                       SEEN | UNINTERESTING | SHOWN | ADDED);
-       o1->flags = flags1;
-       o2->flags = flags2;
-}
-
-static void gen_message_id(struct rev_info *info, char *base)
-{
-       const char *committer = git_committer_info(IDENT_WARN_ON_NO_NAME);
-       const char *email_start = strrchr(committer, '<');
-       const char *email_end = strrchr(committer, '>');
-       struct strbuf buf = STRBUF_INIT;
-       if (!email_start || !email_end || email_start > email_end - 1)
-               die("Could not extract email from committer identity.");
-       strbuf_addf(&buf, "%s.%lu.git.%.*s", base,
-                   (unsigned long) time(NULL),
-                   (int)(email_end - email_start - 1), email_start + 1);
-       info->message_id = strbuf_detach(&buf, NULL);
-}
-
-static void make_cover_letter(struct rev_info *rev, int use_stdout,
-                             int numbered, int numbered_files,
-                             struct commit *origin,
-                             int nr, struct commit **list, struct commit *head)
-{
-       const char *committer;
-       const char *subject_start = NULL;
-       const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n";
-       const char *msg;
-       const char *extra_headers = rev->extra_headers;
-       struct shortlog log;
-       struct strbuf sb = STRBUF_INIT;
-       int i;
-       const char *encoding = "utf-8";
-       struct diff_options opts;
-       int need_8bit_cte = 0;
-       struct commit *commit = NULL;
-
-       if (rev->commit_format != CMIT_FMT_EMAIL)
-               die("Cover letter needs email format");
-
-       committer = git_committer_info(0);
-
-       if (!numbered_files) {
-               /*
-                * We fake a commit for the cover letter so we get the filename
-                * desired.
-                */
-               commit = xcalloc(1, sizeof(*commit));
-               commit->buffer = xmalloc(400);
-               snprintf(commit->buffer, 400,
-                       "tree 0000000000000000000000000000000000000000\n"
-                       "parent %s\n"
-                       "author %s\n"
-                       "committer %s\n\n"
-                       "cover letter\n",
-                       sha1_to_hex(head->object.sha1), committer, committer);
-       }
-
-       if (!use_stdout && reopen_stdout(commit, rev))
-               return;
-
-       if (commit) {
-
-               free(commit->buffer);
-               free(commit);
-       }
-
-       log_write_email_headers(rev, head, &subject_start, &extra_headers,
-                               &need_8bit_cte);
-
-       msg = body;
-       pp_user_info(NULL, CMIT_FMT_EMAIL, &sb, committer, DATE_RFC2822,
-                    encoding);
-       pp_title_line(CMIT_FMT_EMAIL, &msg, &sb, subject_start, extra_headers,
-                     encoding, need_8bit_cte);
-       pp_remainder(CMIT_FMT_EMAIL, &msg, &sb, 0);
-       printf("%s\n", sb.buf);
-
-       strbuf_release(&sb);
-
-       shortlog_init(&log);
-       log.wrap_lines = 1;
-       log.wrap = 72;
-       log.in1 = 2;
-       log.in2 = 4;
-       for (i = 0; i < nr; i++)
-               shortlog_add_commit(&log, list[i]);
-
-       shortlog_output(&log);
-
-       /*
-        * We can only do diffstat with a unique reference point
-        */
-       if (!origin)
-               return;
-
-       memcpy(&opts, &rev->diffopt, sizeof(opts));
-       opts.output_format = DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
-
-       diff_setup_done(&opts);
-
-       diff_tree_sha1(origin->tree->object.sha1,
-                      head->tree->object.sha1,
-                      "", &opts);
-       diffcore_std(&opts);
-       diff_flush(&opts);
-
-       printf("\n");
-}
-
-static const char *clean_message_id(const char *msg_id)
-{
-       char ch;
-       const char *a, *z, *m;
-
-       m = msg_id;
-       while ((ch = *m) && (isspace(ch) || (ch == '<')))
-               m++;
-       a = m;
-       z = NULL;
-       while ((ch = *m)) {
-               if (!isspace(ch) && (ch != '>'))
-                       z = m;
-               m++;
-       }
-       if (!z)
-               die("insane in-reply-to: %s", msg_id);
-       if (++z == m)
-               return a;
-       return xmemdupz(a, z - a);
-}
-
-static const char *set_outdir(const char *prefix, const char *output_directory)
-{
-       if (output_directory && is_absolute_path(output_directory))
-               return output_directory;
-
-       if (!prefix || !*prefix) {
-               if (output_directory)
-                       return output_directory;
-               /* The user did not explicitly ask for "./" */
-               outdir_offset = 2;
-               return "./";
-       }
-
-       outdir_offset = strlen(prefix);
-       if (!output_directory)
-               return prefix;
-
-       return xstrdup(prefix_filename(prefix, outdir_offset,
-                                      output_directory));
-}
-
-int cmd_format_patch(int argc, const char **argv, const char *prefix)
-{
-       struct commit *commit;
-       struct commit **list = NULL;
-       struct rev_info rev;
-       int nr = 0, total, i, j;
-       int use_stdout = 0;
-       int start_number = -1;
-       int keep_subject = 0;
-       int numbered_files = 0;         /* _just_ numbers */
-       int subject_prefix = 0;
-       int ignore_if_in_upstream = 0;
-       int cover_letter = 0;
-       int boundary_count = 0;
-       int no_binary_diff = 0;
-       struct commit *origin = NULL, *head = NULL;
-       const char *in_reply_to = NULL;
-       struct patch_ids ids;
-       char *add_signoff = NULL;
-       struct strbuf buf = STRBUF_INIT;
-
-       git_config(git_format_config, NULL);
-       init_revisions(&rev, prefix);
-       rev.commit_format = CMIT_FMT_EMAIL;
-       rev.verbose_header = 1;
-       rev.diff = 1;
-       rev.combine_merges = 0;
-       rev.ignore_merges = 1;
-       DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
-
-       rev.subject_prefix = fmt_patch_subject_prefix;
-
-       if (default_attach) {
-               rev.mime_boundary = default_attach;
-               rev.no_inline = 1;
-       }
-
-       /*
-        * Parse the arguments before setup_revisions(), or something
-        * like "git format-patch -o a123 HEAD^.." may fail; a123 is
-        * 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 (!strcmp(argv[i], "-N") ||
-                               !strcmp(argv[i], "--no-numbered")) {
-                       numbered = 0;
-                       auto_number = 0;
-               }
-               else if (!prefixcmp(argv[i], "--start-number="))
-                       start_number = strtol(argv[i] + 15, NULL, 10);
-               else if (!strcmp(argv[i], "--numbered-files"))
-                       numbered_files = 1;
-               else if (!strcmp(argv[i], "--start-number")) {
-                       i++;
-                       if (i == argc)
-                               die("Need a number for --start-number");
-                       start_number = strtol(argv[i], NULL, 10);
-               }
-               else if (!prefixcmp(argv[i], "--cc=")) {
-                       ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc);
-                       extra_cc[extra_cc_nr++] = xstrdup(argv[i] + 5);
-               }
-               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")) {
-                       do_signoff = 1;
-               }
-               else if (!strcmp(argv[i], "--attach")) {
-                       rev.mime_boundary = git_version_string;
-                       rev.no_inline = 1;
-               }
-               else if (!prefixcmp(argv[i], "--attach=")) {
-                       rev.mime_boundary = argv[i] + 9;
-                       rev.no_inline = 1;
-               }
-               else if (!strcmp(argv[i], "--no-attach")) {
-                       rev.mime_boundary = NULL;
-                       rev.no_inline = 0;
-               }
-               else if (!strcmp(argv[i], "--inline")) {
-                       rev.mime_boundary = git_version_string;
-                       rev.no_inline = 0;
-               }
-               else if (!prefixcmp(argv[i], "--inline=")) {
-                       rev.mime_boundary = argv[i] + 9;
-                       rev.no_inline = 0;
-               }
-               else if (!strcmp(argv[i], "--ignore-if-in-upstream"))
-                       ignore_if_in_upstream = 1;
-               else if (!strcmp(argv[i], "--thread")
-                       || !strcmp(argv[i], "--thread=shallow"))
-                       thread = THREAD_SHALLOW;
-               else if (!strcmp(argv[i], "--thread=deep"))
-                       thread = THREAD_DEEP;
-               else if (!strcmp(argv[i], "--no-thread"))
-                       thread = 0;
-               else if (!prefixcmp(argv[i], "--in-reply-to="))
-                       in_reply_to = argv[i] + 14;
-               else if (!strcmp(argv[i], "--in-reply-to")) {
-                       i++;
-                       if (i == argc)
-                               die("Need a Message-Id for --in-reply-to");
-                       in_reply_to = argv[i];
-               } else if (!prefixcmp(argv[i], "--subject-prefix=")) {
-                       subject_prefix = 1;
-                       rev.subject_prefix = argv[i] + 17;
-               } else if (!prefixcmp(argv[i], "--suffix="))
-                       fmt_patch_suffix = argv[i] + 9;
-               else if (!strcmp(argv[i], "--cover-letter"))
-                       cover_letter = 1;
-               else if (!strcmp(argv[i], "--no-binary"))
-                       no_binary_diff = 1;
-               else if (!prefixcmp(argv[i], "--add-header="))
-                       add_header(argv[i] + 13);
-               else
-                       argv[j++] = argv[i];
-       }
-       argc = j;
-
-       if (do_signoff) {
-               const char *committer;
-               const char *endpos;
-               committer = git_committer_info(IDENT_ERROR_ON_NO_NAME);
-               endpos = strchr(committer, '>');
-               if (!endpos)
-                       die("bogus committer info %s", committer);
-               add_signoff = xmemdupz(committer, endpos - committer + 1);
-       }
-
-       for (i = 0; i < extra_hdr_nr; i++) {
-               strbuf_addstr(&buf, extra_hdr[i]);
-               strbuf_addch(&buf, '\n');
-       }
-
-       if (extra_to_nr)
-               strbuf_addstr(&buf, "To: ");
-       for (i = 0; i < extra_to_nr; i++) {
-               if (i)
-                       strbuf_addstr(&buf, "    ");
-               strbuf_addstr(&buf, extra_to[i]);
-               if (i + 1 < extra_to_nr)
-                       strbuf_addch(&buf, ',');
-               strbuf_addch(&buf, '\n');
-       }
-
-       if (extra_cc_nr)
-               strbuf_addstr(&buf, "Cc: ");
-       for (i = 0; i < extra_cc_nr; i++) {
-               if (i)
-                       strbuf_addstr(&buf, "    ");
-               strbuf_addstr(&buf, extra_cc[i]);
-               if (i + 1 < extra_cc_nr)
-                       strbuf_addch(&buf, ',');
-               strbuf_addch(&buf, '\n');
-       }
-
-       rev.extra_headers = strbuf_detach(&buf, 0);
-
-       if (start_number < 0)
-               start_number = 1;
-       if (numbered && keep_subject)
-               die ("-n and -k are mutually exclusive.");
-       if (keep_subject && subject_prefix)
-               die ("--subject-prefix and -k are mutually exclusive.");
-
-       argc = setup_revisions(argc, argv, &rev, "HEAD");
-       if (argc > 1)
-               die ("unrecognized argument: %s", argv[1]);
-
-       if (!rev.diffopt.output_format
-               || rev.diffopt.output_format == DIFF_FORMAT_PATCH)
-               rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_PATCH;
-
-       if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff)
-               DIFF_OPT_SET(&rev.diffopt, BINARY);
-
-       if (!use_stdout)
-               output_directory = set_outdir(prefix, output_directory);
-
-       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.nr == 1) {
-               if (rev.max_count < 0 && !rev.show_root_diff) {
-                       /*
-                        * This is traditional behaviour of "git format-patch
-                        * origin" that prepares what the origin side still
-                        * does not have.
-                        */
-                       rev.pending.objects[0].item->flags |= UNINTERESTING;
-                       add_head_to_pending(&rev);
-               }
-               /*
-                * Otherwise, it is "format-patch -22 HEAD", and/or
-                * "format-patch --root HEAD".  The user wants
-                * get_revision() to do the usual traversal.
-                */
-       }
-
-       /*
-        * We cannot move this anywhere earlier because we do want to
-        * know if --root was given explicitly from the comand line.
-        */
-       rev.show_root_diff = 1;
-
-       if (cover_letter) {
-               /* remember the range */
-               int i;
-               for (i = 0; i < rev.pending.nr; i++) {
-                       struct object *o = rev.pending.objects[i].item;
-                       if (!(o->flags & UNINTERESTING))
-                               head = (struct commit *)o;
-               }
-               /* We can't generate a cover letter without any patches */
-               if (!head)
-                       return 0;
-       }
-
-       if (ignore_if_in_upstream)
-               get_patch_ids(&rev, &ids, prefix);
-
-       if (!use_stdout)
-               realstdout = xfdopen(xdup(1), "w");
-
-       if (prepare_revision_walk(&rev))
-               die("revision walk setup failed");
-       rev.boundary = 1;
-       while ((commit = get_revision(&rev)) != NULL) {
-               if (commit->object.flags & BOUNDARY) {
-                       boundary_count++;
-                       origin = (boundary_count == 1) ? commit : NULL;
-                       continue;
-               }
-
-               /* ignore merges */
-               if (commit->parents && commit->parents->next)
-                       continue;
-
-               if (ignore_if_in_upstream &&
-                               has_commit_patch_id(commit, &ids))
-                       continue;
-
-               nr++;
-               list = xrealloc(list, nr * sizeof(list[0]));
-               list[nr - 1] = commit;
-       }
-       total = nr;
-       if (!keep_subject && auto_number && total > 1)
-               numbered = 1;
-       if (numbered)
-               rev.total = total + start_number - 1;
-       if (in_reply_to || thread || cover_letter)
-               rev.ref_message_ids = xcalloc(1, sizeof(struct string_list));
-       if (in_reply_to) {
-               const char *msgid = clean_message_id(in_reply_to);
-               string_list_append(msgid, rev.ref_message_ids);
-       }
-       rev.numbered_files = numbered_files;
-       rev.patch_suffix = fmt_patch_suffix;
-       if (cover_letter) {
-               if (thread)
-                       gen_message_id(&rev, "cover");
-               make_cover_letter(&rev, use_stdout, numbered, numbered_files,
-                                 origin, nr, list, head);
-               total++;
-               start_number--;
-       }
-       rev.add_signoff = add_signoff;
-       while (0 <= --nr) {
-               int shown;
-               commit = list[nr];
-               rev.nr = total - nr + (start_number - 1);
-               /* Make the second and subsequent mails replies to the first */
-               if (thread) {
-                       /* Have we already had a message ID? */
-                       if (rev.message_id) {
-                               /*
-                                * For deep threading: make every mail
-                                * a reply to the previous one, no
-                                * matter what other options are set.
-                                *
-                                * For shallow threading:
-                                *
-                                * Without --cover-letter and
-                                * --in-reply-to, make every mail a
-                                * reply to the one before.
-                                *
-                                * With --in-reply-to but no
-                                * --cover-letter, make every mail a
-                                * reply to the <reply-to>.
-                                *
-                                * With --cover-letter, make every
-                                * mail but the cover letter a reply
-                                * to the cover letter.  The cover
-                                * letter is a reply to the
-                                * --in-reply-to, if specified.
-                                */
-                               if (thread == THREAD_SHALLOW
-                                   && rev.ref_message_ids->nr > 0
-                                   && (!cover_letter || rev.nr > 1))
-                                       free(rev.message_id);
-                               else
-                                       string_list_append(rev.message_id,
-                                                          rev.ref_message_ids);
-                       }
-                       gen_message_id(&rev, sha1_to_hex(commit->object.sha1));
-               }
-
-               if (!use_stdout && reopen_stdout(numbered_files ? NULL : commit,
-                                                &rev))
-                       die("Failed to create output files");
-               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);
-       if (ignore_if_in_upstream)
-               free_patch_ids(&ids);
-       return 0;
-}
-
-static int add_pending_commit(const char *arg, struct rev_info *revs, int flags)
-{
-       unsigned char sha1[20];
-       if (get_sha1(arg, sha1) == 0) {
-               struct commit *commit = lookup_commit_reference(sha1);
-               if (commit) {
-                       commit->object.flags |= flags;
-                       add_pending_object(revs, &commit->object, arg);
-                       return 0;
-               }
-       }
-       return -1;
-}
-
-static const char cherry_usage[] =
-"git cherry [-v] [<upstream> [<head> [<limit>]]]";
-int cmd_cherry(int argc, const char **argv, const char *prefix)
-{
-       struct rev_info revs;
-       struct patch_ids ids;
-       struct commit *commit;
-       struct commit_list *list = NULL;
-       struct branch *current_branch;
-       const char *upstream;
-       const char *head = "HEAD";
-       const char *limit = NULL;
-       int verbose = 0;
-
-       if (argc > 1 && !strcmp(argv[1], "-v")) {
-               verbose = 1;
-               argc--;
-               argv++;
-       }
-
-       switch (argc) {
-       case 4:
-               limit = argv[3];
-               /* FALLTHROUGH */
-       case 3:
-               head = argv[2];
-               /* FALLTHROUGH */
-       case 2:
-               upstream = argv[1];
-               break;
-       default:
-               current_branch = branch_get(NULL);
-               if (!current_branch || !current_branch->merge
-                                       || !current_branch->merge[0]
-                                       || !current_branch->merge[0]->dst) {
-                       fprintf(stderr, "Could not find a tracked"
-                                       " remote branch, please"
-                                       " specify <upstream> manually.\n");
-                       usage(cherry_usage);
-               }
-
-               upstream = current_branch->merge[0]->dst;
-       }
-
-       init_revisions(&revs, prefix);
-       revs.diff = 1;
-       revs.combine_merges = 0;
-       revs.ignore_merges = 1;
-       DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
-
-       if (add_pending_commit(head, &revs, 0))
-               die("Unknown commit %s", head);
-       if (add_pending_commit(upstream, &revs, UNINTERESTING))
-               die("Unknown commit %s", upstream);
-
-       /* Don't say anything if head and upstream are the same. */
-       if (revs.pending.nr == 2) {
-               struct object_array_entry *o = revs.pending.objects;
-               if (hashcmp(o[0].item->sha1, o[1].item->sha1) == 0)
-                       return 0;
-       }
-
-       get_patch_ids(&revs, &ids, prefix);
-
-       if (limit && add_pending_commit(limit, &revs, UNINTERESTING))
-               die("Unknown commit %s", limit);
-
-       /* reverse the list of commits */
-       if (prepare_revision_walk(&revs))
-               die("revision walk setup failed");
-       while ((commit = get_revision(&revs)) != NULL) {
-               /* ignore merges */
-               if (commit->parents && commit->parents->next)
-                       continue;
-
-               commit_list_insert(commit, &list);
-       }
-
-       while (list) {
-               char sign = '+';
-
-               commit = list->item;
-               if (has_commit_patch_id(commit, &ids))
-                       sign = '-';
-
-               if (verbose) {
-                       struct strbuf buf = STRBUF_INIT;
-                       pretty_print_commit(CMIT_FMT_ONELINE, commit,
-                                           &buf, 0, NULL, NULL, 0, 0);
-                       printf("%c %s %s\n", sign,
-                              sha1_to_hex(commit->object.sha1), buf.buf);
-                       strbuf_release(&buf);
-               }
-               else {
-                       printf("%c %s\n", sign,
-                              sha1_to_hex(commit->object.sha1));
-               }
-
-               list = list->next;
-       }
-
-       free_patch_ids(&ids);
-       return 0;
-}
diff --git a/builtin-ls-files.c b/builtin-ls-files.c
deleted file mode 100644 (file)
index e458a49..0000000
+++ /dev/null
@@ -1,566 +0,0 @@
-/*
- * 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 "cache.h"
-#include "quote.h"
-#include "dir.h"
-#include "builtin.h"
-#include "tree.h"
-#include "parse-options.h"
-
-static int abbrev;
-static int show_deleted;
-static int show_cached;
-static int show_others;
-static int show_stage;
-static int show_unmerged;
-static int show_modified;
-static int show_killed;
-static int show_valid_bit;
-static int line_terminator = '\n';
-
-static int prefix_len;
-static int prefix_offset;
-static const char **pathspec;
-static int error_unmatch;
-static char *ps_matched;
-static const char *with_tree;
-static int exc_given;
-
-static const char *tag_cached = "";
-static const char *tag_unmerged = "";
-static const char *tag_removed = "";
-static const char *tag_other = "";
-static const char *tag_killed = "";
-static const char *tag_modified = "";
-
-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 (!match_pathspec(pathspec, ent->name, ent->len, len, ps_matched))
-               return;
-
-       fputs(tag, stdout);
-       write_name_quoted(ent->name + offset, stdout, line_terminator);
-}
-
-static void show_other_files(struct dir_struct *dir)
-{
-       int i;
-
-       for (i = 0; i < dir->nr; i++) {
-               struct dir_entry *ent = dir->entries[i];
-               if (!cache_name_is_other(ent->name, ent->len))
-                       continue;
-               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 (!match_pathspec(pathspec, ce->name, ce_namelen(ce), len, ps_matched))
-               return;
-
-       if (tag && *tag && show_valid_bit &&
-           (ce->ce_flags & 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);
-       } else {
-               printf("%s%06o %s %d\t",
-                      tag,
-                      ce->ce_mode,
-                      abbrev ? find_unique_abbrev(ce->sha1,abbrev)
-                               : sha1_to_hex(ce->sha1),
-                      ce_stage(ce));
-       }
-       write_name_quoted(ce->name + offset, stdout, line_terminator);
-}
-
-static void show_files(struct dir_struct *dir, const char *prefix)
-{
-       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, pathspec);
-               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];
-                       int dtype = ce_to_dtype(ce);
-                       if (dir->flags & DIR_SHOW_IGNORED &&
-                           !excluded(dir, ce->name, &dtype))
-                               continue;
-                       if (show_unmerged && !ce_stage(ce))
-                               continue;
-                       if (ce->ce_flags & CE_UPDATE)
-                               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;
-                       int dtype = ce_to_dtype(ce);
-                       if (dir->flags & DIR_SHOW_IGNORED &&
-                           !excluded(dir, ce->name, &dtype))
-                               continue;
-                       if (ce->ce_flags & CE_UPDATE)
-                               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(const char *prefix)
-{
-       int pos = cache_name_pos(prefix, prefix_len);
-       unsigned int first, last;
-
-       if (pos < 0)
-               pos = -pos-1;
-       memmove(active_cache, active_cache + pos,
-               (active_nr - pos) * sizeof(struct cache_entry *));
-       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 const char *verify_pathspec(const char *prefix)
-{
-       const char **p, *n, *prev;
-       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 '..'");
-
-       prefix_len = max;
-       return max ? xmemdupz(prev, max) : NULL;
-}
-
-static void strip_trailing_slash_from_submodules(void)
-{
-       const char **p;
-
-       for (p = pathspec; *p != NULL; p++) {
-               int len = strlen(*p), pos;
-
-               if (len < 1 || (*p)[len - 1] != '/')
-                       continue;
-               pos = cache_name_pos(*p, len - 1);
-               if (pos >= 0 && S_ISGITLINK(active_cache[pos]->ce_mode))
-                       *p = xstrndup(*p, len - 1);
-       }
-}
-
-/*
- * Read the tree specified with --with-tree option
- * (typically, HEAD) into stage #1 and then
- * squash them down to stage #0.  This is used for
- * --error-unmatch to list and check the path patterns
- * that were given from the command line.  We are not
- * going to write this index out.
- */
-void overlay_tree_on_cache(const char *tree_name, const char *prefix)
-{
-       struct tree *tree;
-       unsigned char sha1[20];
-       const char **match;
-       struct cache_entry *last_stage0 = NULL;
-       int i;
-
-       if (get_sha1(tree_name, sha1))
-               die("tree-ish %s not found.", tree_name);
-       tree = parse_tree_indirect(sha1);
-       if (!tree)
-               die("bad tree-ish %s", tree_name);
-
-       /* Hoist the unmerged entries up to stage #3 to make room */
-       for (i = 0; i < active_nr; i++) {
-               struct cache_entry *ce = active_cache[i];
-               if (!ce_stage(ce))
-                       continue;
-               ce->ce_flags |= CE_STAGEMASK;
-       }
-
-       if (prefix) {
-               static const char *(matchbuf[2]);
-               matchbuf[0] = prefix;
-               matchbuf[1] = NULL;
-               match = matchbuf;
-       } else
-               match = NULL;
-       if (read_tree(tree, 1, match))
-               die("unable to read tree entries %s", tree_name);
-
-       for (i = 0; i < active_nr; i++) {
-               struct cache_entry *ce = active_cache[i];
-               switch (ce_stage(ce)) {
-               case 0:
-                       last_stage0 = ce;
-                       /* fallthru */
-               default:
-                       continue;
-               case 1:
-                       /*
-                        * If there is stage #0 entry for this, we do not
-                        * need to show it.  We use CE_UPDATE bit to mark
-                        * such an entry.
-                        */
-                       if (last_stage0 &&
-                           !strcmp(last_stage0->name, ce->name))
-                               ce->ce_flags |= CE_UPDATE;
-               }
-       }
-}
-
-int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset)
-{
-       /*
-        * Make sure all pathspec matched; otherwise it is an error.
-        */
-       int num, errors = 0;
-       for (num = 0; pathspec[num]; num++) {
-               int other, found_dup;
-
-               if (ps_matched[num])
-                       continue;
-               /*
-                * The caller might have fed identical pathspec
-                * twice.  Do not barf on such a mistake.
-                */
-               for (found_dup = other = 0;
-                    !found_dup && pathspec[other];
-                    other++) {
-                       if (other == num || !ps_matched[other])
-                               continue;
-                       if (!strcmp(pathspec[other], pathspec[num]))
-                               /*
-                                * Ok, we have a match already.
-                                */
-                               found_dup = 1;
-               }
-               if (found_dup)
-                       continue;
-
-               error("pathspec '%s' did not match any file(s) known to git.",
-                     pathspec[num] + prefix_offset);
-               errors++;
-       }
-       return errors;
-}
-
-static const char * const ls_files_usage[] = {
-       "git ls-files [options] [<file>]*",
-       NULL
-};
-
-static int option_parse_z(const struct option *opt,
-                         const char *arg, int unset)
-{
-       line_terminator = unset ? '\n' : '\0';
-
-       return 0;
-}
-
-static int option_parse_exclude(const struct option *opt,
-                               const char *arg, int unset)
-{
-       struct exclude_list *list = opt->value;
-
-       exc_given = 1;
-       add_exclude(arg, "", 0, list);
-
-       return 0;
-}
-
-static int option_parse_exclude_from(const struct option *opt,
-                                    const char *arg, int unset)
-{
-       struct dir_struct *dir = opt->value;
-
-       exc_given = 1;
-       add_excludes_from_file(dir, arg);
-
-       return 0;
-}
-
-static int option_parse_exclude_standard(const struct option *opt,
-                                        const char *arg, int unset)
-{
-       struct dir_struct *dir = opt->value;
-
-       exc_given = 1;
-       setup_standard_excludes(dir);
-
-       return 0;
-}
-
-int cmd_ls_files(int argc, const char **argv, const char *prefix)
-{
-       int require_work_tree = 0, show_tag = 0;
-       struct dir_struct dir;
-       struct option builtin_ls_files_options[] = {
-               { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
-                       "paths are separated with NUL character",
-                       PARSE_OPT_NOARG, option_parse_z },
-               OPT_BOOLEAN('t', NULL, &show_tag,
-                       "identify the file status with tags"),
-               OPT_BOOLEAN('v', NULL, &show_valid_bit,
-                       "use lowercase letters for 'assume unchanged' files"),
-               OPT_BOOLEAN('c', "cached", &show_cached,
-                       "show cached files in the output (default)"),
-               OPT_BOOLEAN('d', "deleted", &show_deleted,
-                       "show deleted files in the output"),
-               OPT_BOOLEAN('m', "modified", &show_modified,
-                       "show modified files in the output"),
-               OPT_BOOLEAN('o', "others", &show_others,
-                       "show other files in the output"),
-               OPT_BIT('i', "ignored", &dir.flags,
-                       "show ignored files in the output",
-                       DIR_SHOW_IGNORED),
-               OPT_BOOLEAN('s', "stage", &show_stage,
-                       "show staged contents' object name in the output"),
-               OPT_BOOLEAN('k', "killed", &show_killed,
-                       "show files on the filesystem that need to be removed"),
-               OPT_BIT(0, "directory", &dir.flags,
-                       "show 'other' directories' name only",
-                       DIR_SHOW_OTHER_DIRECTORIES),
-               OPT_BIT(0, "no-empty-directory", &dir.flags,
-                       "don't show empty directories",
-                       DIR_HIDE_EMPTY_DIRECTORIES),
-               OPT_BOOLEAN('u', "unmerged", &show_unmerged,
-                       "show unmerged files in the output"),
-               { OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], "pattern",
-                       "skip files matching pattern",
-                       0, option_parse_exclude },
-               { OPTION_CALLBACK, 'X', "exclude-from", &dir, "file",
-                       "exclude patterns are read from <file>",
-                       0, option_parse_exclude_from },
-               OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, "file",
-                       "read additional per-directory exclude patterns in <file>"),
-               { OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL,
-                       "add the standard git exclusions",
-                       PARSE_OPT_NOARG, option_parse_exclude_standard },
-               { OPTION_SET_INT, 0, "full-name", &prefix_offset, NULL,
-                       "make the output relative to the project top directory",
-                       PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL },
-               OPT_BOOLEAN(0, "error-unmatch", &error_unmatch,
-                       "if any <file> is not in the index, treat this as an error"),
-               OPT_STRING(0, "with-tree", &with_tree, "tree-ish",
-                       "pretend that paths removed since <tree-ish> are still present"),
-               OPT__ABBREV(&abbrev),
-               OPT_END()
-       };
-
-       memset(&dir, 0, sizeof(dir));
-       if (prefix)
-               prefix_offset = strlen(prefix);
-       git_config(git_default_config, NULL);
-
-       argc = parse_options(argc, argv, builtin_ls_files_options,
-                       ls_files_usage, 0);
-       if (show_tag || show_valid_bit) {
-               tag_cached = "H ";
-               tag_unmerged = "M ";
-               tag_removed = "R ";
-               tag_modified = "C ";
-               tag_other = "? ";
-               tag_killed = "K ";
-       }
-       if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed)
-               require_work_tree = 1;
-       if (show_unmerged)
-               /*
-                * There's no point in showing unmerged unless
-                * you also show the stage information.
-                */
-               show_stage = 1;
-       if (dir.exclude_per_dir)
-               exc_given = 1;
-
-       if (require_work_tree && !is_inside_work_tree())
-               setup_work_tree();
-
-       pathspec = get_pathspec(prefix, argv);
-
-       /* be nice with submodule paths ending in a slash */
-       read_cache();
-       if (pathspec)
-               strip_trailing_slash_from_submodules();
-
-       /* Verify that the pathspec matches the prefix */
-       if (pathspec)
-               prefix = verify_pathspec(prefix);
-
-       /* 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.flags & 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;
-
-       if (prefix)
-               prune_cache(prefix);
-       if (with_tree) {
-               /*
-                * Basic sanity check; show-stages and show-unmerged
-                * would not make any sense with this option.
-                */
-               if (show_stage || show_unmerged)
-                       die("ls-files --with-tree is incompatible with -s or -u");
-               overlay_tree_on_cache(with_tree, prefix);
-       }
-       show_files(&dir, prefix);
-
-       if (ps_matched) {
-               int bad;
-               bad = report_path_error(ps_matched, pathspec, prefix_offset);
-               if (bad)
-                       fprintf(stderr, "Did you forget to 'git add'?\n");
-
-               return bad ? 1 : 0;
-       }
-
-       return 0;
-}
diff --git a/builtin-ls-remote.c b/builtin-ls-remote.c
deleted file mode 100644 (file)
index 78a88f7..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-#include "builtin.h"
-#include "cache.h"
-#include "transport.h"
-#include "remote.h"
-
-static const char ls_remote_usage[] =
-"git ls-remote [--heads] [--tags]  [-u <exec> | --upload-pack <exec>] <repository> <refs>...";
-
-/*
- * Is there one among the list of patterns that match the tail part
- * of the path?
- */
-static int tail_match(const char **pattern, const char *path)
-{
-       const char *p;
-       char pathbuf[PATH_MAX];
-
-       if (!pattern)
-               return 1; /* no restriction */
-
-       if (snprintf(pathbuf, sizeof(pathbuf), "/%s", path) > sizeof(pathbuf))
-               return error("insanely long ref %.*s...", 20, path);
-       while ((p = *(pattern++)) != NULL) {
-               if (!fnmatch(p, pathbuf, 0))
-                       return 1;
-       }
-       return 0;
-}
-
-int cmd_ls_remote(int argc, const char **argv, const char *prefix)
-{
-       int i;
-       const char *dest = NULL;
-       int nongit;
-       unsigned flags = 0;
-       const char *uploadpack = NULL;
-       const char **pattern = NULL;
-
-       struct remote *remote;
-       struct transport *transport;
-       const struct ref *ref;
-
-       setup_git_directory_gently(&nongit);
-
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-
-               if (*arg == '-') {
-                       if (!prefixcmp(arg, "--upload-pack=")) {
-                               uploadpack = arg + 14;
-                               continue;
-                       }
-                       if (!prefixcmp(arg, "--exec=")) {
-                               uploadpack = arg + 7;
-                               continue;
-                       }
-                       if (!strcmp("--tags", arg) || !strcmp("-t", arg)) {
-                               flags |= REF_TAGS;
-                               continue;
-                       }
-                       if (!strcmp("--heads", arg) || !strcmp("-h", arg)) {
-                               flags |= REF_HEADS;
-                               continue;
-                       }
-                       if (!strcmp("--refs", arg)) {
-                               flags |= REF_NORMAL;
-                               continue;
-                       }
-                       usage(ls_remote_usage);
-               }
-               dest = arg;
-               i++;
-               break;
-       }
-
-       if (!dest)
-               usage(ls_remote_usage);
-
-       if (argv[i]) {
-               int j;
-               pattern = xcalloc(sizeof(const char *), argc - i + 1);
-               for (j = i; j < argc; j++) {
-                       int len = strlen(argv[j]);
-                       char *p = xmalloc(len + 3);
-                       sprintf(p, "*/%s", argv[j]);
-                       pattern[j - i] = p;
-               }
-       }
-       remote = nongit ? NULL : remote_get(dest);
-       if (remote && !remote->url_nr)
-               die("remote %s has no configured URL", dest);
-       transport = transport_get(remote, remote ? remote->url[0] : dest);
-       if (uploadpack != NULL)
-               transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack);
-
-       ref = transport_get_remote_refs(transport);
-       if (transport_disconnect(transport))
-               return 1;
-       for ( ; ref; ref = ref->next) {
-               if (!check_ref_type(ref, flags))
-                       continue;
-               if (!tail_match(pattern, ref->name))
-                       continue;
-               printf("%s      %s\n", sha1_to_hex(ref->old_sha1), ref->name);
-       }
-       return 0;
-}
diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c
deleted file mode 100644 (file)
index 22008df..0000000
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Linus Torvalds, 2005
- */
-#include "cache.h"
-#include "blob.h"
-#include "tree.h"
-#include "commit.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
-#define LS_SHOW_SIZE 16
-static int abbrev;
-static int ls_options;
-static const char **pathspec;
-static int chomp_prefix;
-static const char *ls_tree_prefix;
-
-static const char ls_tree_usage[] =
-       "git ls-tree [-d] [-r] [-t] [-l] [-z] [--name-only] [--name-status] [--full-name] [--full-tree] [--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, void *context)
-{
-       int retval = 0;
-       const char *type = blob_type;
-
-       if (S_ISGITLINK(mode)) {
-               /*
-                * Maybe we want to have some recursive version here?
-                *
-                * Something similar to this incomplete example:
-                *
-               if (show_subprojects(base, baselen, pathname))
-                       retval = READ_TREE_RECURSIVE;
-                *
-                */
-               type = commit_type;
-       } else 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(ls_tree_prefix, base, chomp_prefix)))
-               return 0;
-
-       if (!(ls_options & LS_NAME_ONLY)) {
-               if (ls_options & LS_SHOW_SIZE) {
-                       char size_text[24];
-                       if (!strcmp(type, blob_type)) {
-                               unsigned long size;
-                               if (sha1_object_info(sha1, &size) == OBJ_BAD)
-                                       strcpy(size_text, "BAD");
-                               else
-                                       snprintf(size_text, sizeof(size_text),
-                                                "%lu", size);
-                       } else
-                               strcpy(size_text, "-");
-                       printf("%06o %s %s %7s\t", mode, type,
-                              abbrev ? find_unique_abbrev(sha1, abbrev)
-                                     : sha1_to_hex(sha1),
-                              size_text);
-               } else
-                       printf("%06o %s %s\t", mode, type,
-                              abbrev ? find_unique_abbrev(sha1, abbrev)
-                                     : sha1_to_hex(sha1));
-       }
-       write_name_quotedpfx(base + chomp_prefix, baselen - chomp_prefix,
-                         pathname, stdout, line_termination);
-       return retval;
-}
-
-int cmd_ls_tree(int argc, const char **argv, const char *prefix)
-{
-       unsigned char sha1[20];
-       struct tree *tree;
-
-       git_config(git_default_config, NULL);
-       ls_tree_prefix = prefix;
-       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 'l':
-                       ls_options |= LS_SHOW_SIZE;
-                       break;
-               case '-':
-                       if (!strcmp(argv[1]+2, "name-only") ||
-                           !strcmp(argv[1]+2, "name-status")) {
-                               ls_options |= LS_NAME_ONLY;
-                               break;
-                       }
-                       if (!strcmp(argv[1]+2, "long")) {
-                               ls_options |= LS_SHOW_SIZE;
-                               break;
-                       }
-                       if (!strcmp(argv[1]+2, "full-name")) {
-                               chomp_prefix = 0;
-                               break;
-                       }
-                       if (!strcmp(argv[1]+2, "full-tree")) {
-                               ls_tree_prefix = prefix = NULL;
-                               chomp_prefix = 0;
-                               break;
-                       }
-                       if (!prefixcmp(argv[1]+2, "abbrev=")) {
-                               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, NULL);
-
-       return 0;
-}
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
deleted file mode 100644 (file)
index 1eeeb4d..0000000
+++ /dev/null
@@ -1,968 +0,0 @@
-/*
- * Another stupid program, this one parsing the headers of an
- * email to figure out authorship and subject
- */
-#include "cache.h"
-#include "builtin.h"
-#include "utf8.h"
-#include "strbuf.h"
-
-static FILE *cmitmsg, *patchfile, *fin, *fout;
-
-static int keep_subject;
-static const char *metainfo_charset;
-static struct strbuf line = STRBUF_INIT;
-static struct strbuf name = STRBUF_INIT;
-static struct strbuf email = STRBUF_INIT;
-
-static enum  {
-       TE_DONTCARE, TE_QP, TE_BASE64,
-} transfer_encoding;
-static enum  {
-       TYPE_TEXT, TYPE_OTHER,
-} message_type;
-
-static struct strbuf charset = STRBUF_INIT;
-static int patch_lines;
-static struct strbuf **p_hdr_data, **s_hdr_data;
-
-#define MAX_HDR_PARSED 10
-#define MAX_BOUNDARIES 5
-
-static void cleanup_space(struct strbuf *sb);
-
-
-static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email)
-{
-       struct strbuf *src = name;
-       if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') ||
-               strchr(name->buf, '<') || strchr(name->buf, '>'))
-               src = email;
-       else if (name == out)
-               return;
-       strbuf_reset(out);
-       strbuf_addbuf(out, src);
-}
-
-static void parse_bogus_from(const struct strbuf *line)
-{
-       /* John Doe <johndoe> */
-
-       char *bra, *ket;
-       /* This is fallback, so do not bother if we already have an
-        * e-mail address.
-        */
-       if (email.len)
-               return;
-
-       bra = strchr(line->buf, '<');
-       if (!bra)
-               return;
-       ket = strchr(bra, '>');
-       if (!ket)
-               return;
-
-       strbuf_reset(&email);
-       strbuf_add(&email, bra + 1, ket - bra - 1);
-
-       strbuf_reset(&name);
-       strbuf_add(&name, line->buf, bra - line->buf);
-       strbuf_trim(&name);
-       get_sane_name(&name, &name, &email);
-}
-
-static void handle_from(const struct strbuf *from)
-{
-       char *at;
-       size_t el;
-       struct strbuf f;
-
-       strbuf_init(&f, from->len);
-       strbuf_addbuf(&f, from);
-
-       at = strchr(f.buf, '@');
-       if (!at) {
-               parse_bogus_from(from);
-               return;
-       }
-
-       /*
-        * If we already have one email, don't take any confusing lines
-        */
-       if (email.len && strchr(at + 1, '@')) {
-               strbuf_release(&f);
-               return;
-       }
-
-       /* Pick up the string around '@', possibly delimited with <>
-        * pair; that is the email part.
-        */
-       while (at > f.buf) {
-               char c = at[-1];
-               if (isspace(c))
-                       break;
-               if (c == '<') {
-                       at[-1] = ' ';
-                       break;
-               }
-               at--;
-       }
-       el = strcspn(at, " \n\t\r\v\f>");
-       strbuf_reset(&email);
-       strbuf_add(&email, at, el);
-       strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0));
-
-       /* The remainder is name.  It could be
-        *
-        * - "John Doe <john.doe@xz>"                   (a), or
-        * - "john.doe@xz (John Doe)"                   (b), or
-        * - "John (zzz) Doe <john.doe@xz> (Comment)"   (c)
-        *
-        * but we have removed the email part, so
-        *
-        * - remove extra spaces which could stay after email (case 'c'), and
-        * - trim from both ends, possibly removing the () pair at the end
-        *   (cases 'a' and 'b').
-        */
-       cleanup_space(&f);
-       strbuf_trim(&f);
-       if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') {
-               strbuf_remove(&f, 0, 1);
-               strbuf_setlen(&f, f.len - 1);
-       }
-
-       get_sane_name(&name, &f, &email);
-       strbuf_release(&f);
-}
-
-static void handle_header(struct strbuf **out, const struct strbuf *line)
-{
-       if (!*out) {
-               *out = xmalloc(sizeof(struct strbuf));
-               strbuf_init(*out, line->len);
-       } else
-               strbuf_reset(*out);
-
-       strbuf_addbuf(*out, line);
-}
-
-/* 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, struct strbuf *attr)
-{
-       const char *ends, *ap = strcasestr(line, name);
-       size_t sz;
-
-       if (!ap) {
-               strbuf_setlen(attr, 0);
-               return 0;
-       }
-       ap += strlen(name);
-       if (*ap == '"') {
-               ap++;
-               ends = "\"";
-       }
-       else
-               ends = "; \t";
-       sz = strcspn(ap, ends);
-       strbuf_add(attr, ap, sz);
-       return 1;
-}
-
-static struct strbuf *content[MAX_BOUNDARIES];
-
-static struct strbuf **content_top = content;
-
-static void handle_content_type(struct strbuf *line)
-{
-       struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
-       strbuf_init(boundary, line->len);
-
-       if (!strcasestr(line->buf, "text/"))
-                message_type = TYPE_OTHER;
-       if (slurp_attr(line->buf, "boundary=", boundary)) {
-               strbuf_insert(boundary, 0, "--", 2);
-               if (++content_top > &content[MAX_BOUNDARIES]) {
-                       fprintf(stderr, "Too many boundaries to handle\n");
-                       exit(1);
-               }
-               *content_top = boundary;
-               boundary = NULL;
-       }
-       if (slurp_attr(line->buf, "charset=", &charset))
-               strbuf_tolower(&charset);
-
-       if (boundary) {
-               strbuf_release(boundary);
-               free(boundary);
-       }
-}
-
-static void handle_content_transfer_encoding(const struct strbuf *line)
-{
-       if (strcasestr(line->buf, "base64"))
-               transfer_encoding = TE_BASE64;
-       else if (strcasestr(line->buf, "quoted-printable"))
-               transfer_encoding = TE_QP;
-       else
-               transfer_encoding = TE_DONTCARE;
-}
-
-static int is_multipart_boundary(const struct strbuf *line)
-{
-       return (((*content_top)->len <= line->len) &&
-               !memcmp(line->buf, (*content_top)->buf, (*content_top)->len));
-}
-
-static void cleanup_subject(struct strbuf *subject)
-{
-       char *pos;
-       size_t remove;
-       while (subject->len) {
-               switch (*subject->buf) {
-               case 'r': case 'R':
-                       if (subject->len <= 3)
-                               break;
-                       if (!memcmp(subject->buf + 1, "e:", 2)) {
-                               strbuf_remove(subject, 0, 3);
-                               continue;
-                       }
-                       break;
-               case ' ': case '\t': case ':':
-                       strbuf_remove(subject, 0, 1);
-                       continue;
-               case '[':
-                       if ((pos = strchr(subject->buf, ']'))) {
-                               remove = pos - subject->buf;
-                               if (remove <= (subject->len - remove) * 2) {
-                                       strbuf_remove(subject, 0, remove + 1);
-                                       continue;
-                               }
-                       } else
-                               strbuf_remove(subject, 0, 1);
-                       break;
-               }
-               strbuf_trim(subject);
-               return;
-       }
-}
-
-static void cleanup_space(struct strbuf *sb)
-{
-       size_t pos, cnt;
-       for (pos = 0; pos < sb->len; pos++) {
-               if (isspace(sb->buf[pos])) {
-                       sb->buf[pos] = ' ';
-                       for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++);
-                       strbuf_remove(sb, pos + 1, cnt);
-               }
-       }
-}
-
-static void decode_header(struct strbuf *line);
-static const char *header[MAX_HDR_PARSED] = {
-       "From","Subject","Date",
-};
-
-static inline int cmp_header(const struct strbuf *line, const char *hdr)
-{
-       int len = strlen(hdr);
-       return !strncasecmp(line->buf, hdr, len) && line->len > len &&
-                       line->buf[len] == ':' && isspace(line->buf[len + 1]);
-}
-
-static int check_header(const struct strbuf *line,
-                               struct strbuf *hdr_data[], int overwrite)
-{
-       int i, ret = 0, len;
-       struct strbuf sb = STRBUF_INIT;
-       /* search for the interesting parts */
-       for (i = 0; header[i]; i++) {
-               int len = strlen(header[i]);
-               if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) {
-                       /* Unwrap inline B and Q encoding, and optionally
-                        * normalize the meta information to utf8.
-                        */
-                       strbuf_add(&sb, line->buf + len + 2, line->len - len - 2);
-                       decode_header(&sb);
-                       handle_header(&hdr_data[i], &sb);
-                       ret = 1;
-                       goto check_header_out;
-               }
-       }
-
-       /* Content stuff */
-       if (cmp_header(line, "Content-Type")) {
-               len = strlen("Content-Type: ");
-               strbuf_add(&sb, line->buf + len, line->len - len);
-               decode_header(&sb);
-               strbuf_insert(&sb, 0, "Content-Type: ", len);
-               handle_content_type(&sb);
-               ret = 1;
-               goto check_header_out;
-       }
-       if (cmp_header(line, "Content-Transfer-Encoding")) {
-               len = strlen("Content-Transfer-Encoding: ");
-               strbuf_add(&sb, line->buf + len, line->len - len);
-               decode_header(&sb);
-               handle_content_transfer_encoding(&sb);
-               ret = 1;
-               goto check_header_out;
-       }
-
-       /* for inbody stuff */
-       if (!prefixcmp(line->buf, ">From") && isspace(line->buf[5])) {
-               ret = 1; /* Should this return 0? */
-               goto check_header_out;
-       }
-       if (!prefixcmp(line->buf, "[PATCH]") && isspace(line->buf[7])) {
-               for (i = 0; header[i]; i++) {
-                       if (!memcmp("Subject", header[i], 7)) {
-                               handle_header(&hdr_data[i], line);
-                               ret = 1;
-                               goto check_header_out;
-                       }
-               }
-       }
-
-check_header_out:
-       strbuf_release(&sb);
-       return ret;
-}
-
-static int is_rfc2822_header(const struct strbuf *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->buf;
-
-       /* Count mbox From headers as headers */
-       if (!prefixcmp(cp, "From ") || !prefixcmp(cp, ">From "))
-               return 1;
-
-       while ((ch = *cp++)) {
-               if (ch == ':')
-                       return 1;
-               if ((33 <= ch && ch <= 57) ||
-                   (59 <= ch && ch <= 126))
-                       continue;
-               break;
-       }
-       return 0;
-}
-
-static int read_one_header_line(struct strbuf *line, FILE *in)
-{
-       /* Get the first part of the line. */
-       if (strbuf_getline(line, in, '\n'))
-               return 0;
-
-       /*
-        * Is it an empty line or not a valid rfc2822 header?
-        * If so, stop here, and return false ("not a header")
-        */
-       strbuf_rtrim(line);
-       if (!line->len || !is_rfc2822_header(line)) {
-               /* Re-add the newline */
-               strbuf_addch(line, '\n');
-               return 0;
-       }
-
-       /*
-        * Now we need to eat all the continuation lines..
-        * Yuck, 2822 header "folding"
-        */
-       for (;;) {
-               int peek;
-               struct strbuf continuation = STRBUF_INIT;
-
-               peek = fgetc(in); ungetc(peek, in);
-               if (peek != ' ' && peek != '\t')
-                       break;
-               if (strbuf_getline(&continuation, in, '\n'))
-                       break;
-               continuation.buf[0] = '\n';
-               strbuf_rtrim(&continuation);
-               strbuf_addbuf(line, &continuation);
-       }
-
-       return 1;
-}
-
-static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047)
-{
-       const char *in = q_seg->buf;
-       int c;
-       struct strbuf *out = xmalloc(sizeof(struct strbuf));
-       strbuf_init(out, q_seg->len);
-
-       while ((c = *in++) != 0) {
-               if (c == '=') {
-                       int d = *in++;
-                       if (d == '\n' || !d)
-                               break; /* drop trailing newline */
-                       strbuf_addch(out, (hexval(d) << 4) | hexval(*in++));
-                       continue;
-               }
-               if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */
-                       c = 0x20;
-               strbuf_addch(out, c);
-       }
-       return out;
-}
-
-static struct strbuf *decode_b_segment(const struct strbuf *b_seg)
-{
-       /* Decode in..ep, possibly in-place to ot */
-       int c, pos = 0, acc = 0;
-       const char *in = b_seg->buf;
-       struct strbuf *out = xmalloc(sizeof(struct strbuf));
-       strbuf_init(out, b_seg->len);
-
-       while ((c = *in++) != 0) {
-               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
-                       continue; /* garbage */
-               switch (pos++) {
-               case 0:
-                       acc = (c << 2);
-                       break;
-               case 1:
-                       strbuf_addch(out, (acc | (c >> 4)));
-                       acc = (c & 15) << 4;
-                       break;
-               case 2:
-                       strbuf_addch(out, (acc | (c >> 2)));
-                       acc = (c & 3) << 6;
-                       break;
-               case 3:
-                       strbuf_addch(out, (acc | c));
-                       acc = pos = 0;
-                       break;
-               }
-       }
-       return out;
-}
-
-/*
- * When there is no known charset, guess.
- *
- * Right now we assume that if the target is UTF-8 (the default),
- * and it already looks like UTF-8 (which includes US-ASCII as its
- * subset, of course) then that is what it is and there is nothing
- * to do.
- *
- * Otherwise, we default to assuming it is Latin1 for historical
- * reasons.
- */
-static const char *guess_charset(const struct strbuf *line, const char *target_charset)
-{
-       if (is_encoding_utf8(target_charset)) {
-               if (is_utf8(line->buf))
-                       return NULL;
-       }
-       return "latin1";
-}
-
-static void convert_to_utf8(struct strbuf *line, const char *charset)
-{
-       char *out;
-
-       if (!charset || !*charset) {
-               charset = guess_charset(line, metainfo_charset);
-               if (!charset)
-                       return;
-       }
-
-       if (!strcmp(metainfo_charset, charset))
-               return;
-       out = reencode_string(line->buf, metainfo_charset, charset);
-       if (!out)
-               die("cannot convert from %s to %s",
-                   charset, metainfo_charset);
-       strbuf_attach(line, out, strlen(out), strlen(out));
-}
-
-static int decode_header_bq(struct strbuf *it)
-{
-       char *in, *ep, *cp;
-       struct strbuf outbuf = STRBUF_INIT, *dec;
-       struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT;
-       int rfc2047 = 0;
-
-       in = it->buf;
-       while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) {
-               int encoding;
-               strbuf_reset(&charset_q);
-               strbuf_reset(&piecebuf);
-               rfc2047 = 1;
-
-               if (in != ep) {
-                       /*
-                        * We are about to process an encoded-word
-                        * that begins at ep, but there is something
-                        * before the encoded word.
-                        */
-                       char *scan;
-                       for (scan = in; scan < ep; scan++)
-                               if (!isspace(*scan))
-                                       break;
-
-                       if (scan != ep || in == it->buf) {
-                               /*
-                                * We should not lose that "something",
-                                * unless we have just processed an
-                                * encoded-word, and there is only LWS
-                                * before the one we are about to process.
-                                */
-                               strbuf_add(&outbuf, in, ep - in);
-                       }
-               }
-               /* E.g.
-                * ep : "=?iso-2022-jp?B?GyR...?= foo"
-                * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
-                */
-               ep += 2;
-
-               if (ep - it->buf >= it->len || !(cp = strchr(ep, '?')))
-                       goto decode_header_bq_out;
-
-               if (cp + 3 - it->buf > it->len)
-                       goto decode_header_bq_out;
-               strbuf_add(&charset_q, ep, cp - ep);
-               strbuf_tolower(&charset_q);
-
-               encoding = cp[1];
-               if (!encoding || cp[2] != '?')
-                       goto decode_header_bq_out;
-               ep = strstr(cp + 3, "?=");
-               if (!ep)
-                       goto decode_header_bq_out;
-               strbuf_add(&piecebuf, cp + 3, ep - cp - 3);
-               switch (tolower(encoding)) {
-               default:
-                       goto decode_header_bq_out;
-               case 'b':
-                       dec = decode_b_segment(&piecebuf);
-                       break;
-               case 'q':
-                       dec = decode_q_segment(&piecebuf, 1);
-                       break;
-               }
-               if (metainfo_charset)
-                       convert_to_utf8(dec, charset_q.buf);
-
-               strbuf_addbuf(&outbuf, dec);
-               strbuf_release(dec);
-               free(dec);
-               in = ep + 2;
-       }
-       strbuf_addstr(&outbuf, in);
-       strbuf_reset(it);
-       strbuf_addbuf(it, &outbuf);
-decode_header_bq_out:
-       strbuf_release(&outbuf);
-       strbuf_release(&charset_q);
-       strbuf_release(&piecebuf);
-       return rfc2047;
-}
-
-static void decode_header(struct strbuf *it)
-{
-       if (decode_header_bq(it))
-               return;
-       /* otherwise "it" is a straight copy of the input.
-        * This can be binary guck but there is no charset specified.
-        */
-       if (metainfo_charset)
-               convert_to_utf8(it, "");
-}
-
-static void decode_transfer_encoding(struct strbuf *line)
-{
-       struct strbuf *ret;
-
-       switch (transfer_encoding) {
-       case TE_QP:
-               ret = decode_q_segment(line, 0);
-               break;
-       case TE_BASE64:
-               ret = decode_b_segment(line);
-               break;
-       case TE_DONTCARE:
-       default:
-               return;
-       }
-       strbuf_reset(line);
-       strbuf_addbuf(line, ret);
-       strbuf_release(ret);
-       free(ret);
-}
-
-static void handle_filter(struct strbuf *line);
-
-static int find_boundary(void)
-{
-       while (!strbuf_getline(&line, fin, '\n')) {
-               if (*content_top && is_multipart_boundary(&line))
-                       return 1;
-       }
-       return 0;
-}
-
-static int handle_boundary(void)
-{
-       struct strbuf newline = STRBUF_INIT;
-
-       strbuf_addch(&newline, '\n');
-again:
-       if (line.len >= (*content_top)->len + 2 &&
-           !memcmp(line.buf + (*content_top)->len, "--", 2)) {
-               /* we hit an end boundary */
-               /* pop the current boundary off the stack */
-               strbuf_release(*content_top);
-               free(*content_top);
-               *content_top = NULL;
-
-               /* technically won't happen as is_multipart_boundary()
-                  will fail first.  But just in case..
-                */
-               if (--content_top < content) {
-                       fprintf(stderr, "Detected mismatched boundaries, "
-                                       "can't recover\n");
-                       exit(1);
-               }
-               handle_filter(&newline);
-               strbuf_release(&newline);
-
-               /* skip to the next boundary */
-               if (!find_boundary())
-                       return 0;
-               goto again;
-       }
-
-       /* set some defaults */
-       transfer_encoding = TE_DONTCARE;
-       strbuf_reset(&charset);
-       message_type = TYPE_TEXT;
-
-       /* slurp in this section's info */
-       while (read_one_header_line(&line, fin))
-               check_header(&line, p_hdr_data, 0);
-
-       strbuf_release(&newline);
-       /* replenish line */
-       if (strbuf_getline(&line, fin, '\n'))
-               return 0;
-       strbuf_addch(&line, '\n');
-       return 1;
-}
-
-static inline int patchbreak(const struct strbuf *line)
-{
-       size_t i;
-
-       /* Beginning of a "diff -" header? */
-       if (!prefixcmp(line->buf, "diff -"))
-               return 1;
-
-       /* CVS "Index: " line? */
-       if (!prefixcmp(line->buf, "Index: "))
-               return 1;
-
-       /*
-        * "--- <filename>" starts patches without headers
-        * "---<sp>*" is a manual separator
-        */
-       if (line->len < 4)
-               return 0;
-
-       if (!prefixcmp(line->buf, "---")) {
-               /* space followed by a filename? */
-               if (line->buf[3] == ' ' && !isspace(line->buf[4]))
-                       return 1;
-               /* Just whitespace? */
-               for (i = 3; i < line->len; i++) {
-                       unsigned char c = line->buf[i];
-                       if (c == '\n')
-                               return 1;
-                       if (!isspace(c))
-                               break;
-               }
-               return 0;
-       }
-       return 0;
-}
-
-static int handle_commit_msg(struct strbuf *line)
-{
-       static int still_looking = 1;
-
-       if (!cmitmsg)
-               return 0;
-
-       if (still_looking) {
-               strbuf_ltrim(line);
-               if (!line->len)
-                       return 0;
-               if ((still_looking = check_header(line, s_hdr_data, 0)) != 0)
-                       return 0;
-       }
-
-       /* normalize the log message to UTF-8. */
-       if (metainfo_charset)
-               convert_to_utf8(line, charset.buf);
-
-       if (patchbreak(line)) {
-               fclose(cmitmsg);
-               cmitmsg = NULL;
-               return 1;
-       }
-
-       fputs(line->buf, cmitmsg);
-       return 0;
-}
-
-static void handle_patch(const struct strbuf *line)
-{
-       fwrite(line->buf, 1, line->len, patchfile);
-       patch_lines++;
-}
-
-static void handle_filter(struct strbuf *line)
-{
-       static int filter = 0;
-
-       /* filter tells us which part we left off on */
-       switch (filter) {
-       case 0:
-               if (!handle_commit_msg(line))
-                       break;
-               filter++;
-       case 1:
-               handle_patch(line);
-               break;
-       }
-}
-
-static void handle_body(void)
-{
-       int len = 0;
-       struct strbuf prev = STRBUF_INIT;
-
-       /* Skip up to the first boundary */
-       if (*content_top) {
-               if (!find_boundary())
-                       goto handle_body_out;
-       }
-
-       do {
-               strbuf_setlen(&line, line.len + len);
-
-               /* process any boundary lines */
-               if (*content_top && is_multipart_boundary(&line)) {
-                       /* flush any leftover */
-                       if (prev.len) {
-                               handle_filter(&prev);
-                               strbuf_reset(&prev);
-                       }
-                       if (!handle_boundary())
-                               goto handle_body_out;
-               }
-
-               /* Unwrap transfer encoding */
-               decode_transfer_encoding(&line);
-
-               switch (transfer_encoding) {
-               case TE_BASE64:
-               case TE_QP:
-               {
-                       struct strbuf **lines, **it, *sb;
-
-                       /* Prepend any previous partial lines */
-                       strbuf_insert(&line, 0, prev.buf, prev.len);
-                       strbuf_reset(&prev);
-
-                       /* binary data most likely doesn't have newlines */
-                       if (message_type != TYPE_TEXT) {
-                               handle_filter(&line);
-                               break;
-                       }
-                       /*
-                        * This is a decoded line that may contain
-                        * multiple new lines.  Pass only one chunk
-                        * at a time to handle_filter()
-                        */
-                       lines = strbuf_split(&line, '\n');
-                       for (it = lines; (sb = *it); it++) {
-                               if (*(it + 1) == NULL) /* The last line */
-                                       if (sb->buf[sb->len - 1] != '\n') {
-                                               /* Partial line, save it for later. */
-                                               strbuf_addbuf(&prev, sb);
-                                               break;
-                                       }
-                               handle_filter(sb);
-                       }
-                       /*
-                        * The partial chunk is saved in "prev" and will be
-                        * appended by the next iteration of read_line_with_nul().
-                        */
-                       strbuf_list_free(lines);
-                       break;
-               }
-               default:
-                       handle_filter(&line);
-               }
-
-               strbuf_reset(&line);
-               if (strbuf_avail(&line) < 100)
-                       strbuf_grow(&line, 100);
-       } while ((len = read_line_with_nul(line.buf, strbuf_avail(&line), fin)));
-
-handle_body_out:
-       strbuf_release(&prev);
-}
-
-static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data)
-{
-       const char *sp = data->buf;
-       while (1) {
-               char *ep = strchr(sp, '\n');
-               int len;
-               if (!ep)
-                       len = strlen(sp);
-               else
-                       len = ep - sp;
-               fprintf(fout, "%s: %.*s\n", hdr, len, sp);
-               if (!ep)
-                       break;
-               sp = ep + 1;
-       }
-}
-
-static void handle_info(void)
-{
-       struct strbuf *hdr;
-       int i;
-
-       for (i = 0; header[i]; i++) {
-               /* only print inbody headers if we output a patch file */
-               if (patch_lines && s_hdr_data[i])
-                       hdr = s_hdr_data[i];
-               else if (p_hdr_data[i])
-                       hdr = p_hdr_data[i];
-               else
-                       continue;
-
-               if (!memcmp(header[i], "Subject", 7)) {
-                       if (!keep_subject) {
-                               cleanup_subject(hdr);
-                               cleanup_space(hdr);
-                       }
-                       output_header_lines(fout, "Subject", hdr);
-               } else if (!memcmp(header[i], "From", 4)) {
-                       cleanup_space(hdr);
-                       handle_from(hdr);
-                       fprintf(fout, "Author: %s\n", name.buf);
-                       fprintf(fout, "Email: %s\n", email.buf);
-               } else {
-                       cleanup_space(hdr);
-                       fprintf(fout, "%s: %s\n", header[i], hdr->buf);
-               }
-       }
-       fprintf(fout, "\n");
-}
-
-static int mailinfo(FILE *in, FILE *out, int ks, const char *encoding,
-                   const char *msg, const char *patch)
-{
-       int peek;
-       keep_subject = ks;
-       metainfo_charset = encoding;
-       fin = in;
-       fout = out;
-
-       cmitmsg = fopen(msg, "w");
-       if (!cmitmsg) {
-               perror(msg);
-               return -1;
-       }
-       patchfile = fopen(patch, "w");
-       if (!patchfile) {
-               perror(patch);
-               fclose(cmitmsg);
-               return -1;
-       }
-
-       p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*p_hdr_data));
-       s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*s_hdr_data));
-
-       do {
-               peek = fgetc(in);
-       } while (isspace(peek));
-       ungetc(peek, in);
-
-       /* process the email header */
-       while (read_one_header_line(&line, fin))
-               check_header(&line, p_hdr_data, 1);
-
-       handle_body();
-       handle_info();
-
-       return 0;
-}
-
-static const char mailinfo_usage[] =
-       "git mailinfo [-k] [-u | --encoding=<encoding> | -n] msg patch <mail >info";
-
-int cmd_mailinfo(int argc, const char **argv, const char *prefix)
-{
-       const char *def_charset;
-
-       /* NEEDSWORK: might want to do the optional .git/ directory
-        * discovery
-        */
-       git_config(git_default_config, NULL);
-
-       def_charset = (git_commit_encoding ? git_commit_encoding : "utf-8");
-       metainfo_charset = def_charset;
-
-       while (1 < argc && argv[1][0] == '-') {
-               if (!strcmp(argv[1], "-k"))
-                       keep_subject = 1;
-               else if (!strcmp(argv[1], "-u"))
-                       metainfo_charset = def_charset;
-               else if (!strcmp(argv[1], "-n"))
-                       metainfo_charset = NULL;
-               else if (!prefixcmp(argv[1], "--encoding="))
-                       metainfo_charset = argv[1] + 11;
-               else
-                       usage(mailinfo_usage);
-               argc--; argv++;
-       }
-
-       if (argc != 3)
-               usage(mailinfo_usage);
-
-       return !!mailinfo(stdin, stdout, keep_subject, metainfo_charset, argv[1], argv[2]);
-}
diff --git a/builtin-mailsplit.c b/builtin-mailsplit.c
deleted file mode 100644 (file)
index 71f3b3b..0000000
+++ /dev/null
@@ -1,320 +0,0 @@
-/*
- * 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 "cache.h"
-#include "builtin.h"
-#include "string-list.h"
-
-static const char git_mailsplit_usage[] =
-"git mailsplit [-d<prec>] [-f<n>] [-b] -o<directory> [<mbox>|<Maildir>...]";
-
-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];
-
-/* We cannot use fgets() because our lines can contain NULs */
-int read_line_with_nul(char *buf, int size, FILE *in)
-{
-       int len = 0, c;
-
-       for (;;) {
-               c = getc(in);
-               if (c == EOF)
-                       break;
-               buf[len++] = c;
-               if (c == '\n' || len + 1 >= size)
-                       break;
-       }
-       buf[len] = '\0';
-
-       return len;
-}
-
-/* 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 = len && buf[len-1] != '\n';
-
-               if (fwrite(buf, 1, len, output) != len)
-                       die("cannot write output");
-
-               len = read_line_with_nul(buf, sizeof(buf), mbox);
-               if (len == 0) {
-                       if (feof(mbox)) {
-                               status = 1;
-                               break;
-                       }
-                       die("cannot read mbox");
-               }
-               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);
-}
-
-static int populate_maildir_list(struct string_list *list, const char *path)
-{
-       DIR *dir;
-       struct dirent *dent;
-       char name[PATH_MAX];
-       char *subs[] = { "cur", "new", NULL };
-       char **sub;
-
-       for (sub = subs; *sub; ++sub) {
-               snprintf(name, sizeof(name), "%s/%s", path, *sub);
-               if ((dir = opendir(name)) == NULL) {
-                       if (errno == ENOENT)
-                               continue;
-                       error("cannot opendir %s (%s)", name, strerror(errno));
-                       return -1;
-               }
-
-               while ((dent = readdir(dir)) != NULL) {
-                       if (dent->d_name[0] == '.')
-                               continue;
-                       snprintf(name, sizeof(name), "%s/%s", *sub, dent->d_name);
-                       string_list_insert(name, list);
-               }
-
-               closedir(dir);
-       }
-
-       return 0;
-}
-
-static int split_maildir(const char *maildir, const char *dir,
-       int nr_prec, int skip)
-{
-       char file[PATH_MAX];
-       char name[PATH_MAX];
-       int ret = -1;
-       int i;
-       struct string_list list = {NULL, 0, 0, 1};
-
-       if (populate_maildir_list(&list, maildir) < 0)
-               goto out;
-
-       for (i = 0; i < list.nr; i++) {
-               FILE *f;
-               snprintf(file, sizeof(file), "%s/%s", maildir, list.items[i].string);
-               f = fopen(file, "r");
-               if (!f) {
-                       error("cannot open mail %s (%s)", file, strerror(errno));
-                       goto out;
-               }
-
-               if (fgets(buf, sizeof(buf), f) == NULL) {
-                       error("cannot read mail %s (%s)", file, strerror(errno));
-                       goto out;
-               }
-
-               sprintf(name, "%s/%0*d", dir, nr_prec, ++skip);
-               split_one(f, name, 1);
-
-               fclose(f);
-       }
-
-       ret = skip;
-out:
-       string_list_clear(&list, 1);
-       return ret;
-}
-
-static int split_mbox(const char *file, const char *dir, int allow_bare,
-                     int nr_prec, int skip)
-{
-       char name[PATH_MAX];
-       int ret = -1;
-       int peek;
-
-       FILE *f = !strcmp(file, "-") ? stdin : fopen(file, "r");
-       int file_done = 0;
-
-       if (!f) {
-               error("cannot open mbox %s", file);
-               goto out;
-       }
-
-       do {
-               peek = fgetc(f);
-       } while (isspace(peek));
-       ungetc(peek, f);
-
-       if (fgets(buf, sizeof(buf), f) == NULL) {
-               /* empty stdin is OK */
-               if (f != stdin) {
-                       error("cannot read mbox %s", file);
-                       goto out;
-               }
-               file_done = 1;
-       }
-
-       while (!file_done) {
-               sprintf(name, "%s/%0*d", dir, nr_prec, ++skip);
-               file_done = split_one(f, name, allow_bare);
-       }
-
-       if (f != stdin)
-               fclose(f);
-
-       ret = skip;
-out:
-       return ret;
-}
-
-int cmd_mailsplit(int argc, const char **argv, const char *prefix)
-{
-       int nr = 0, nr_prec = 4, num = 0;
-       int allow_bare = 0;
-       const char *dir = NULL;
-       const char **argp;
-       static const char *stdin_only[] = { "-", NULL };
-
-       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;
-       }
-
-       while (*argp) {
-               const char *arg = *argp++;
-               struct stat argstat;
-               int ret = 0;
-
-               if (arg[0] == '-' && arg[1] == 0) {
-                       ret = split_mbox(arg, dir, allow_bare, nr_prec, nr);
-                       if (ret < 0) {
-                               error("cannot split patches from stdin");
-                               return 1;
-                       }
-                       num += (ret - nr);
-                       nr = ret;
-                       continue;
-               }
-
-               if (stat(arg, &argstat) == -1) {
-                       error("cannot stat %s (%s)", arg, strerror(errno));
-                       return 1;
-               }
-
-               if (S_ISDIR(argstat.st_mode))
-                       ret = split_maildir(arg, dir, nr_prec, nr);
-               else
-                       ret = split_mbox(arg, dir, allow_bare, nr_prec, nr);
-
-               if (ret < 0) {
-                       error("cannot split patches from %s", arg);
-                       return 1;
-               }
-               num += (ret - nr);
-               nr = ret;
-       }
-
-       printf("%d\n", num);
-
-       return 0;
-}
diff --git a/builtin-merge-base.c b/builtin-merge-base.c
deleted file mode 100644 (file)
index 03fc1c2..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-#include "builtin.h"
-#include "cache.h"
-#include "commit.h"
-#include "parse-options.h"
-
-static int show_merge_base(struct commit **rev, int rev_nr, int show_all)
-{
-       struct commit_list *result;
-
-       result = get_merge_bases_many(rev[0], rev_nr - 1, rev + 1, 0);
-
-       if (!result)
-               return 1;
-
-       while (result) {
-               printf("%s\n", sha1_to_hex(result->item->object.sha1));
-               if (!show_all)
-                       return 0;
-               result = result->next;
-       }
-
-       return 0;
-}
-
-static const char * const merge_base_usage[] = {
-       "git merge-base [--all] <commit-id> <commit-id>...",
-       NULL
-};
-
-static struct commit *get_commit_reference(const char *arg)
-{
-       unsigned char revkey[20];
-       struct commit *r;
-
-       if (get_sha1(arg, revkey))
-               die("Not a valid object name %s", arg);
-       r = lookup_commit_reference(revkey);
-       if (!r)
-               die("Not a valid commit name %s", arg);
-
-       return r;
-}
-
-int cmd_merge_base(int argc, const char **argv, const char *prefix)
-{
-       struct commit **rev;
-       int rev_nr = 0;
-       int show_all = 0;
-
-       struct option options[] = {
-               OPT_BOOLEAN('a', "all", &show_all, "outputs all common ancestors"),
-               OPT_END()
-       };
-
-       git_config(git_default_config, NULL);
-       argc = parse_options(argc, argv, options, merge_base_usage, 0);
-       if (argc < 2)
-               usage_with_options(merge_base_usage, options);
-       rev = xmalloc(argc * sizeof(*rev));
-       while (argc-- > 0)
-               rev[rev_nr++] = get_commit_reference(*argv++);
-       return show_merge_base(rev, rev_nr, show_all);
-}
diff --git a/builtin-merge-file.c b/builtin-merge-file.c
deleted file mode 100644 (file)
index 96edb97..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-#include "builtin.h"
-#include "cache.h"
-#include "xdiff/xdiff.h"
-#include "xdiff-interface.h"
-#include "parse-options.h"
-
-static const char *const merge_file_usage[] = {
-       "git merge-file [options] [-L name1 [-L orig [-L name2]]] file1 orig_file file2",
-       NULL
-};
-
-static int label_cb(const struct option *opt, const char *arg, int unset)
-{
-       static int label_count = 0;
-       const char **names = (const char **)opt->value;
-
-       if (label_count >= 3)
-               return error("too many labels on the command line");
-       names[label_count++] = arg;
-       return 0;
-}
-
-int cmd_merge_file(int argc, const char **argv, const char *prefix)
-{
-       const char *names[3] = { NULL, NULL, NULL };
-       mmfile_t mmfs[3];
-       mmbuffer_t result = {NULL, 0};
-       xpparam_t xpp = {XDF_NEED_MINIMAL};
-       int ret = 0, i = 0, to_stdout = 0;
-       int merge_level = XDL_MERGE_ZEALOUS_ALNUM;
-       int merge_style = 0, quiet = 0;
-       int nongit;
-
-       struct option options[] = {
-               OPT_BOOLEAN('p', "stdout", &to_stdout, "send results to standard output"),
-               OPT_SET_INT(0, "diff3", &merge_style, "use a diff3 based merge", XDL_MERGE_DIFF3),
-               OPT__QUIET(&quiet),
-               OPT_CALLBACK('L', NULL, names, "name",
-                            "set labels for file1/orig_file/file2", &label_cb),
-               OPT_END(),
-       };
-
-       prefix = setup_git_directory_gently(&nongit);
-       if (!nongit) {
-               /* Read the configuration file */
-               git_config(git_xmerge_config, NULL);
-               if (0 <= git_xmerge_style)
-                       merge_style = git_xmerge_style;
-       }
-
-       argc = parse_options(argc, argv, options, merge_file_usage, 0);
-       if (argc != 3)
-               usage_with_options(merge_file_usage, options);
-       if (quiet) {
-               if (!freopen("/dev/null", "w", stderr))
-                       return error("failed to redirect stderr to /dev/null: "
-                                    "%s\n", strerror(errno));
-       }
-
-       for (i = 0; i < 3; i++) {
-               if (!names[i])
-                       names[i] = argv[i];
-               if (read_mmfile(mmfs + i, argv[i]))
-                       return -1;
-               if (buffer_is_binary(mmfs[i].ptr, mmfs[i].size))
-                       return error("Cannot merge binary files: %s\n",
-                                       argv[i]);
-       }
-
-       ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2],
-                       &xpp, merge_level | merge_style, &result);
-
-       for (i = 0; i < 3; i++)
-               free(mmfs[i].ptr);
-
-       if (ret >= 0) {
-               const char *filename = argv[0];
-               FILE *f = to_stdout ? stdout : fopen(filename, "wb");
-
-               if (!f)
-                       ret = error("Could not open %s for writing", filename);
-               else if (result.size &&
-                        fwrite(result.ptr, result.size, 1, f) != 1)
-                       ret = error("Could not write to %s", filename);
-               else if (fclose(f))
-                       ret = error("Could not close %s", filename);
-               free(result.ptr);
-       }
-
-       return ret;
-}
diff --git a/builtin-merge-ours.c b/builtin-merge-ours.c
deleted file mode 100644 (file)
index 8f5bbaf..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Implementation of git-merge-ours.sh as builtin
- *
- * Copyright (c) 2007 Thomas Harning Jr
- * Original:
- * Original Copyright (c) 2005 Junio C Hamano
- *
- * Pretend we resolved the heads, but declare our tree trumps everybody else.
- */
-#include "git-compat-util.h"
-#include "builtin.h"
-
-static const char *diff_index_args[] = {
-       "diff-index", "--quiet", "--cached", "HEAD", "--", NULL
-};
-#define NARGS (ARRAY_SIZE(diff_index_args) - 1)
-
-int cmd_merge_ours(int argc, const char **argv, const char *prefix)
-{
-       /*
-        * 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.
-        */
-       if (cmd_diff_index(NARGS, diff_index_args, prefix))
-               exit(2);
-       exit(0);
-}
diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c
deleted file mode 100644 (file)
index 703045b..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-#include "cache.h"
-#include "commit.h"
-#include "tag.h"
-#include "merge-recursive.h"
-
-static const char *better_branch_name(const char *branch)
-{
-       static char githead_env[8 + 40 + 1];
-       char *name;
-
-       if (strlen(branch) != 40)
-               return branch;
-       sprintf(githead_env, "GITHEAD_%s", branch);
-       name = getenv(githead_env);
-       return name ? name : branch;
-}
-
-int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
-{
-       const unsigned char *bases[21];
-       unsigned bases_count = 0;
-       int i, failed;
-       unsigned char h1[20], h2[20];
-       struct merge_options o;
-       struct commit *result;
-
-       init_merge_options(&o);
-       if (argv[0]) {
-               int namelen = strlen(argv[0]);
-               if (8 < namelen &&
-                   !strcmp(argv[0] + namelen - 8, "-subtree"))
-                       o.subtree_merge = 1;
-       }
-
-       if (argc < 4)
-               die("Usage: %s <base>... -- <head> <remote> ...", argv[0]);
-
-       for (i = 1; i < argc; ++i) {
-               if (!strcmp(argv[i], "--"))
-                       break;
-               if (bases_count < ARRAY_SIZE(bases)-1) {
-                       unsigned char *sha = xmalloc(20);
-                       if (get_sha1(argv[i], sha))
-                               die("Could not parse object '%s'", argv[i]);
-                       bases[bases_count++] = sha;
-               }
-               else
-                       warning("Cannot handle more than %zu bases. "
-                               "Ignoring %s.", ARRAY_SIZE(bases)-1, argv[i]);
-       }
-       if (argc - i != 3) /* "--" "<head>" "<remote>" */
-               die("Not handling anything other than two heads merge.");
-
-       o.branch1 = argv[++i];
-       o.branch2 = argv[++i];
-
-       if (get_sha1(o.branch1, h1))
-               die("Could not resolve ref '%s'", o.branch1);
-       if (get_sha1(o.branch2, h2))
-               die("Could not resolve ref '%s'", o.branch2);
-
-       o.branch1 = better_branch_name(o.branch1);
-       o.branch2 = better_branch_name(o.branch2);
-
-       if (o.verbosity >= 3)
-               printf("Merging %s with %s\n", o.branch1, o.branch2);
-
-       failed = merge_recursive_generic(&o, h1, h2, bases_count, bases, &result);
-       if (failed < 0)
-               return 128; /* die() error code */
-       return failed;
-}
diff --git a/builtin-merge.c b/builtin-merge.c
deleted file mode 100644 (file)
index 0b58e5e..0000000
+++ /dev/null
@@ -1,1219 +0,0 @@
-/*
- * Builtin "git merge"
- *
- * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
- *
- * Based on git-merge.sh by Junio C Hamano.
- */
-
-#include "cache.h"
-#include "parse-options.h"
-#include "builtin.h"
-#include "run-command.h"
-#include "diff.h"
-#include "refs.h"
-#include "commit.h"
-#include "diffcore.h"
-#include "revision.h"
-#include "unpack-trees.h"
-#include "cache-tree.h"
-#include "dir.h"
-#include "utf8.h"
-#include "log-tree.h"
-#include "color.h"
-#include "rerere.h"
-#include "help.h"
-#include "merge-recursive.h"
-
-#define DEFAULT_TWOHEAD (1<<0)
-#define DEFAULT_OCTOPUS (1<<1)
-#define NO_FAST_FORWARD (1<<2)
-#define NO_TRIVIAL      (1<<3)
-
-struct strategy {
-       const char *name;
-       unsigned attr;
-};
-
-static const char * const builtin_merge_usage[] = {
-       "git merge [options] <remote>...",
-       "git merge [options] <msg> HEAD <remote>",
-       NULL
-};
-
-static int show_diffstat = 1, option_log, squash;
-static int option_commit = 1, allow_fast_forward = 1;
-static int allow_trivial = 1, have_message;
-static struct strbuf merge_msg;
-static struct commit_list *remoteheads;
-static unsigned char head[20], stash[20];
-static struct strategy **use_strategies;
-static size_t use_strategies_nr, use_strategies_alloc;
-static const char *branch;
-static int verbosity;
-
-static struct strategy all_strategy[] = {
-       { "recursive",  DEFAULT_TWOHEAD | NO_TRIVIAL },
-       { "octopus",    DEFAULT_OCTOPUS },
-       { "resolve",    0 },
-       { "ours",       NO_FAST_FORWARD | NO_TRIVIAL },
-       { "subtree",    NO_FAST_FORWARD | NO_TRIVIAL },
-};
-
-static const char *pull_twohead, *pull_octopus;
-
-static int option_parse_message(const struct option *opt,
-                               const char *arg, int unset)
-{
-       struct strbuf *buf = opt->value;
-
-       if (unset)
-               strbuf_setlen(buf, 0);
-       else if (arg) {
-               strbuf_addf(buf, "%s\n\n", arg);
-               have_message = 1;
-       } else
-               return error("switch `m' requires a value");
-       return 0;
-}
-
-static struct strategy *get_strategy(const char *name)
-{
-       int i;
-       struct strategy *ret;
-       static struct cmdnames main_cmds, other_cmds;
-       static int loaded;
-
-       if (!name)
-               return NULL;
-
-       for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
-               if (!strcmp(name, all_strategy[i].name))
-                       return &all_strategy[i];
-
-       if (!loaded) {
-               struct cmdnames not_strategies;
-               loaded = 1;
-
-               memset(&not_strategies, 0, sizeof(struct cmdnames));
-               load_command_list("git-merge-", &main_cmds, &other_cmds);
-               for (i = 0; i < main_cmds.cnt; i++) {
-                       int j, found = 0;
-                       struct cmdname *ent = main_cmds.names[i];
-                       for (j = 0; j < ARRAY_SIZE(all_strategy); j++)
-                               if (!strncmp(ent->name, all_strategy[j].name, ent->len)
-                                               && !all_strategy[j].name[ent->len])
-                                       found = 1;
-                       if (!found)
-                               add_cmdname(&not_strategies, ent->name, ent->len);
-                       exclude_cmds(&main_cmds, &not_strategies);
-               }
-       }
-       if (!is_in_cmdlist(&main_cmds, name) && !is_in_cmdlist(&other_cmds, name)) {
-               fprintf(stderr, "Could not find merge strategy '%s'.\n", name);
-               fprintf(stderr, "Available strategies are:");
-               for (i = 0; i < main_cmds.cnt; i++)
-                       fprintf(stderr, " %s", main_cmds.names[i]->name);
-               fprintf(stderr, ".\n");
-               if (other_cmds.cnt) {
-                       fprintf(stderr, "Available custom strategies are:");
-                       for (i = 0; i < other_cmds.cnt; i++)
-                               fprintf(stderr, " %s", other_cmds.names[i]->name);
-                       fprintf(stderr, ".\n");
-               }
-               exit(1);
-       }
-
-       ret = xcalloc(1, sizeof(struct strategy));
-       ret->name = xstrdup(name);
-       return ret;
-}
-
-static void append_strategy(struct strategy *s)
-{
-       ALLOC_GROW(use_strategies, use_strategies_nr + 1, use_strategies_alloc);
-       use_strategies[use_strategies_nr++] = s;
-}
-
-static int option_parse_strategy(const struct option *opt,
-                                const char *name, int unset)
-{
-       if (unset)
-               return 0;
-
-       append_strategy(get_strategy(name));
-       return 0;
-}
-
-static int option_parse_n(const struct option *opt,
-                         const char *arg, int unset)
-{
-       show_diffstat = unset;
-       return 0;
-}
-
-static struct option builtin_merge_options[] = {
-       { OPTION_CALLBACK, 'n', NULL, NULL, NULL,
-               "do not show a diffstat at the end of the merge",
-               PARSE_OPT_NOARG, option_parse_n },
-       OPT_BOOLEAN(0, "stat", &show_diffstat,
-               "show a diffstat at the end of the merge"),
-       OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
-       OPT_BOOLEAN(0, "log", &option_log,
-               "add list of one-line log to merge commit message"),
-       OPT_BOOLEAN(0, "squash", &squash,
-               "create a single commit instead of doing a merge"),
-       OPT_BOOLEAN(0, "commit", &option_commit,
-               "perform a commit if the merge succeeds (default)"),
-       OPT_BOOLEAN(0, "ff", &allow_fast_forward,
-               "allow fast forward (default)"),
-       OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
-               "merge strategy to use", option_parse_strategy),
-       OPT_CALLBACK('m', "message", &merge_msg, "message",
-               "message to be used for the merge commit (if any)",
-               option_parse_message),
-       OPT__VERBOSITY(&verbosity),
-       OPT_END()
-};
-
-/* Cleans up metadata that is uninteresting after a succeeded merge. */
-static void drop_save(void)
-{
-       unlink(git_path("MERGE_HEAD"));
-       unlink(git_path("MERGE_MSG"));
-       unlink(git_path("MERGE_MODE"));
-}
-
-static void save_state(void)
-{
-       int len;
-       struct child_process cp;
-       struct strbuf buffer = STRBUF_INIT;
-       const char *argv[] = {"stash", "create", NULL};
-
-       memset(&cp, 0, sizeof(cp));
-       cp.argv = argv;
-       cp.out = -1;
-       cp.git_cmd = 1;
-
-       if (start_command(&cp))
-               die("could not run stash.");
-       len = strbuf_read(&buffer, cp.out, 1024);
-       close(cp.out);
-
-       if (finish_command(&cp) || len < 0)
-               die("stash failed");
-       else if (!len)
-               return;
-       strbuf_setlen(&buffer, buffer.len-1);
-       if (get_sha1(buffer.buf, stash))
-               die("not a valid object: %s", buffer.buf);
-}
-
-static void reset_hard(unsigned const char *sha1, int verbose)
-{
-       int i = 0;
-       const char *args[6];
-
-       args[i++] = "read-tree";
-       if (verbose)
-               args[i++] = "-v";
-       args[i++] = "--reset";
-       args[i++] = "-u";
-       args[i++] = sha1_to_hex(sha1);
-       args[i] = NULL;
-
-       if (run_command_v_opt(args, RUN_GIT_CMD))
-               die("read-tree failed");
-}
-
-static void restore_state(void)
-{
-       struct strbuf sb = STRBUF_INIT;
-       const char *args[] = { "stash", "apply", NULL, NULL };
-
-       if (is_null_sha1(stash))
-               return;
-
-       reset_hard(head, 1);
-
-       args[2] = sha1_to_hex(stash);
-
-       /*
-        * It is OK to ignore error here, for example when there was
-        * nothing to restore.
-        */
-       run_command_v_opt(args, RUN_GIT_CMD);
-
-       strbuf_release(&sb);
-       refresh_cache(REFRESH_QUIET);
-}
-
-/* This is called when no merge was necessary. */
-static void finish_up_to_date(const char *msg)
-{
-       if (verbosity >= 0)
-               printf("%s%s\n", squash ? " (nothing to squash)" : "", msg);
-       drop_save();
-}
-
-static void squash_message(void)
-{
-       struct rev_info rev;
-       struct commit *commit;
-       struct strbuf out = STRBUF_INIT;
-       struct commit_list *j;
-       int fd;
-
-       printf("Squash commit -- not updating HEAD\n");
-       fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
-       if (fd < 0)
-               die("Could not write to %s", git_path("SQUASH_MSG"));
-
-       init_revisions(&rev, NULL);
-       rev.ignore_merges = 1;
-       rev.commit_format = CMIT_FMT_MEDIUM;
-
-       commit = lookup_commit(head);
-       commit->object.flags |= UNINTERESTING;
-       add_pending_object(&rev, &commit->object, NULL);
-
-       for (j = remoteheads; j; j = j->next)
-               add_pending_object(&rev, &j->item->object, NULL);
-
-       setup_revisions(0, NULL, &rev, NULL);
-       if (prepare_revision_walk(&rev))
-               die("revision walk setup failed");
-
-       strbuf_addstr(&out, "Squashed commit of the following:\n");
-       while ((commit = get_revision(&rev)) != NULL) {
-               strbuf_addch(&out, '\n');
-               strbuf_addf(&out, "commit %s\n",
-                       sha1_to_hex(commit->object.sha1));
-               pretty_print_commit(rev.commit_format, commit, &out, rev.abbrev,
-                       NULL, NULL, rev.date_mode, 0);
-       }
-       if (write(fd, out.buf, out.len) < 0)
-               die("Writing SQUASH_MSG: %s", strerror(errno));
-       if (close(fd))
-               die("Finishing SQUASH_MSG: %s", strerror(errno));
-       strbuf_release(&out);
-}
-
-static void finish(const unsigned char *new_head, const char *msg)
-{
-       struct strbuf reflog_message = STRBUF_INIT;
-
-       if (!msg)
-               strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
-       else {
-               if (verbosity >= 0)
-                       printf("%s\n", msg);
-               strbuf_addf(&reflog_message, "%s: %s",
-                       getenv("GIT_REFLOG_ACTION"), msg);
-       }
-       if (squash) {
-               squash_message();
-       } else {
-               if (verbosity >= 0 && !merge_msg.len)
-                       printf("No merge message -- not updating HEAD\n");
-               else {
-                       const char *argv_gc_auto[] = { "gc", "--auto", NULL };
-                       update_ref(reflog_message.buf, "HEAD",
-                               new_head, head, 0,
-                               DIE_ON_ERR);
-                       /*
-                        * We ignore errors in 'gc --auto', since the
-                        * user should see them.
-                        */
-                       run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
-               }
-       }
-       if (new_head && show_diffstat) {
-               struct diff_options opts;
-               diff_setup(&opts);
-               opts.output_format |=
-                       DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
-               opts.detect_rename = DIFF_DETECT_RENAME;
-               if (diff_use_color_default > 0)
-                       DIFF_OPT_SET(&opts, COLOR_DIFF);
-               if (diff_setup_done(&opts) < 0)
-                       die("diff_setup_done failed");
-               diff_tree_sha1(head, new_head, "", &opts);
-               diffcore_std(&opts);
-               diff_flush(&opts);
-       }
-
-       /* Run a post-merge hook */
-       run_hook(NULL, "post-merge", squash ? "1" : "0", NULL);
-
-       strbuf_release(&reflog_message);
-}
-
-/* Get the name for the merge commit's message. */
-static void merge_name(const char *remote, struct strbuf *msg)
-{
-       struct object *remote_head;
-       unsigned char branch_head[20], buf_sha[20];
-       struct strbuf buf = STRBUF_INIT;
-       struct strbuf bname = STRBUF_INIT;
-       const char *ptr;
-       int len, early;
-
-       strbuf_branchname(&bname, remote);
-       remote = bname.buf;
-
-       memset(branch_head, 0, sizeof(branch_head));
-       remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
-       if (!remote_head)
-               die("'%s' does not point to a commit", remote);
-
-       strbuf_addstr(&buf, "refs/heads/");
-       strbuf_addstr(&buf, remote);
-       resolve_ref(buf.buf, branch_head, 0, 0);
-
-       if (!hashcmp(remote_head->sha1, branch_head)) {
-               strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
-                       sha1_to_hex(branch_head), remote);
-               goto cleanup;
-       }
-
-       /* See if remote matches <name>^^^.. or <name>~<number> */
-       for (len = 0, ptr = remote + strlen(remote);
-            remote < ptr && ptr[-1] == '^';
-            ptr--)
-               len++;
-       if (len)
-               early = 1;
-       else {
-               early = 0;
-               ptr = strrchr(remote, '~');
-               if (ptr) {
-                       int seen_nonzero = 0;
-
-                       len++; /* count ~ */
-                       while (*++ptr && isdigit(*ptr)) {
-                               seen_nonzero |= (*ptr != '0');
-                               len++;
-                       }
-                       if (*ptr)
-                               len = 0; /* not ...~<number> */
-                       else if (seen_nonzero)
-                               early = 1;
-                       else if (len == 1)
-                               early = 1; /* "name~" is "name~1"! */
-               }
-       }
-       if (len) {
-               struct strbuf truname = STRBUF_INIT;
-               strbuf_addstr(&truname, "refs/heads/");
-               strbuf_addstr(&truname, remote);
-               strbuf_setlen(&truname, truname.len - len);
-               if (resolve_ref(truname.buf, buf_sha, 0, 0)) {
-                       strbuf_addf(msg,
-                                   "%s\t\tbranch '%s'%s of .\n",
-                                   sha1_to_hex(remote_head->sha1),
-                                   truname.buf + 11,
-                                   (early ? " (early part)" : ""));
-                       strbuf_release(&truname);
-                       goto cleanup;
-               }
-       }
-
-       if (!strcmp(remote, "FETCH_HEAD") &&
-                       !access(git_path("FETCH_HEAD"), R_OK)) {
-               FILE *fp;
-               struct strbuf line = STRBUF_INIT;
-               char *ptr;
-
-               fp = fopen(git_path("FETCH_HEAD"), "r");
-               if (!fp)
-                       die("could not open %s for reading: %s",
-                               git_path("FETCH_HEAD"), strerror(errno));
-               strbuf_getline(&line, fp, '\n');
-               fclose(fp);
-               ptr = strstr(line.buf, "\tnot-for-merge\t");
-               if (ptr)
-                       strbuf_remove(&line, ptr-line.buf+1, 13);
-               strbuf_addbuf(msg, &line);
-               strbuf_release(&line);
-               goto cleanup;
-       }
-       strbuf_addf(msg, "%s\t\tcommit '%s'\n",
-               sha1_to_hex(remote_head->sha1), remote);
-cleanup:
-       strbuf_release(&buf);
-       strbuf_release(&bname);
-}
-
-static int git_merge_config(const char *k, const char *v, void *cb)
-{
-       if (branch && !prefixcmp(k, "branch.") &&
-               !prefixcmp(k + 7, branch) &&
-               !strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
-               const char **argv;
-               int argc;
-               char *buf;
-
-               buf = xstrdup(v);
-               argc = split_cmdline(buf, &argv);
-               if (argc < 0)
-                       die("Bad branch.%s.mergeoptions string", branch);
-               argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
-               memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
-               argc++;
-               parse_options(argc, argv, builtin_merge_options,
-                             builtin_merge_usage, 0);
-               free(buf);
-       }
-
-       if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
-               show_diffstat = git_config_bool(k, v);
-       else if (!strcmp(k, "pull.twohead"))
-               return git_config_string(&pull_twohead, k, v);
-       else if (!strcmp(k, "pull.octopus"))
-               return git_config_string(&pull_octopus, k, v);
-       else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary"))
-               option_log = git_config_bool(k, v);
-       return git_diff_ui_config(k, v, cb);
-}
-
-static int read_tree_trivial(unsigned char *common, unsigned char *head,
-                            unsigned char *one)
-{
-       int i, nr_trees = 0;
-       struct tree *trees[MAX_UNPACK_TREES];
-       struct tree_desc t[MAX_UNPACK_TREES];
-       struct unpack_trees_options opts;
-
-       memset(&opts, 0, sizeof(opts));
-       opts.head_idx = 2;
-       opts.src_index = &the_index;
-       opts.dst_index = &the_index;
-       opts.update = 1;
-       opts.verbose_update = 1;
-       opts.trivial_merges_only = 1;
-       opts.merge = 1;
-       trees[nr_trees] = parse_tree_indirect(common);
-       if (!trees[nr_trees++])
-               return -1;
-       trees[nr_trees] = parse_tree_indirect(head);
-       if (!trees[nr_trees++])
-               return -1;
-       trees[nr_trees] = parse_tree_indirect(one);
-       if (!trees[nr_trees++])
-               return -1;
-       opts.fn = threeway_merge;
-       cache_tree_free(&active_cache_tree);
-       for (i = 0; i < nr_trees; i++) {
-               parse_tree(trees[i]);
-               init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
-       }
-       if (unpack_trees(nr_trees, t, &opts))
-               return -1;
-       return 0;
-}
-
-static void write_tree_trivial(unsigned char *sha1)
-{
-       if (write_cache_as_tree(sha1, 0, NULL))
-               die("git write-tree failed to write a tree");
-}
-
-static int try_merge_strategy(const char *strategy, struct commit_list *common,
-                             const char *head_arg)
-{
-       const char **args;
-       int i = 0, ret;
-       struct commit_list *j;
-       struct strbuf buf = STRBUF_INIT;
-       int index_fd;
-       struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
-
-       index_fd = hold_locked_index(lock, 1);
-       refresh_cache(REFRESH_QUIET);
-       if (active_cache_changed &&
-                       (write_cache(index_fd, active_cache, active_nr) ||
-                        commit_locked_index(lock)))
-               return error("Unable to write index.");
-       rollback_lock_file(lock);
-
-       if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) {
-               int clean;
-               struct commit *result;
-               struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
-               int index_fd;
-               struct commit_list *reversed = NULL;
-               struct merge_options o;
-
-               if (remoteheads->next) {
-                       error("Not handling anything other than two heads merge.");
-                       return 2;
-               }
-
-               init_merge_options(&o);
-               if (!strcmp(strategy, "subtree"))
-                       o.subtree_merge = 1;
-
-               o.branch1 = head_arg;
-               o.branch2 = remoteheads->item->util;
-
-               for (j = common; j; j = j->next)
-                       commit_list_insert(j->item, &reversed);
-
-               index_fd = hold_locked_index(lock, 1);
-               clean = merge_recursive(&o, lookup_commit(head),
-                               remoteheads->item, reversed, &result);
-               if (active_cache_changed &&
-                               (write_cache(index_fd, active_cache, active_nr) ||
-                                commit_locked_index(lock)))
-                       die ("unable to write %s", get_index_file());
-               rollback_lock_file(lock);
-               return clean ? 0 : 1;
-       } else {
-               args = xmalloc((4 + commit_list_count(common) +
-                                       commit_list_count(remoteheads)) * sizeof(char *));
-               strbuf_addf(&buf, "merge-%s", strategy);
-               args[i++] = buf.buf;
-               for (j = common; j; j = j->next)
-                       args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
-               args[i++] = "--";
-               args[i++] = head_arg;
-               for (j = remoteheads; j; j = j->next)
-                       args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
-               args[i] = NULL;
-               ret = run_command_v_opt(args, RUN_GIT_CMD);
-               strbuf_release(&buf);
-               i = 1;
-               for (j = common; j; j = j->next)
-                       free((void *)args[i++]);
-               i += 2;
-               for (j = remoteheads; j; j = j->next)
-                       free((void *)args[i++]);
-               free(args);
-               discard_cache();
-               if (read_cache() < 0)
-                       die("failed to read the cache");
-               return -ret;
-       }
-}
-
-static void count_diff_files(struct diff_queue_struct *q,
-                            struct diff_options *opt, void *data)
-{
-       int *count = data;
-
-       (*count) += q->nr;
-}
-
-static int count_unmerged_entries(void)
-{
-       const struct index_state *state = &the_index;
-       int i, ret = 0;
-
-       for (i = 0; i < state->cache_nr; i++)
-               if (ce_stage(state->cache[i]))
-                       ret++;
-
-       return ret;
-}
-
-static int checkout_fast_forward(unsigned char *head, unsigned char *remote)
-{
-       struct tree *trees[MAX_UNPACK_TREES];
-       struct unpack_trees_options opts;
-       struct tree_desc t[MAX_UNPACK_TREES];
-       int i, fd, nr_trees = 0;
-       struct dir_struct dir;
-       struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
-
-       refresh_cache(REFRESH_QUIET);
-
-       fd = hold_locked_index(lock_file, 1);
-
-       memset(&trees, 0, sizeof(trees));
-       memset(&opts, 0, sizeof(opts));
-       memset(&t, 0, sizeof(t));
-       memset(&dir, 0, sizeof(dir));
-       dir.flags |= DIR_SHOW_IGNORED;
-       dir.exclude_per_dir = ".gitignore";
-       opts.dir = &dir;
-
-       opts.head_idx = 1;
-       opts.src_index = &the_index;
-       opts.dst_index = &the_index;
-       opts.update = 1;
-       opts.verbose_update = 1;
-       opts.merge = 1;
-       opts.fn = twoway_merge;
-
-       trees[nr_trees] = parse_tree_indirect(head);
-       if (!trees[nr_trees++])
-               return -1;
-       trees[nr_trees] = parse_tree_indirect(remote);
-       if (!trees[nr_trees++])
-               return -1;
-       for (i = 0; i < nr_trees; i++) {
-               parse_tree(trees[i]);
-               init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
-       }
-       if (unpack_trees(nr_trees, t, &opts))
-               return -1;
-       if (write_cache(fd, active_cache, active_nr) ||
-               commit_locked_index(lock_file))
-               die("unable to write new index file");
-       return 0;
-}
-
-static void split_merge_strategies(const char *string, struct strategy **list,
-                                  int *nr, int *alloc)
-{
-       char *p, *q, *buf;
-
-       if (!string)
-               return;
-
-       buf = xstrdup(string);
-       q = buf;
-       for (;;) {
-               p = strchr(q, ' ');
-               if (!p) {
-                       ALLOC_GROW(*list, *nr + 1, *alloc);
-                       (*list)[(*nr)++].name = xstrdup(q);
-                       free(buf);
-                       return;
-               } else {
-                       *p = '\0';
-                       ALLOC_GROW(*list, *nr + 1, *alloc);
-                       (*list)[(*nr)++].name = xstrdup(q);
-                       q = ++p;
-               }
-       }
-}
-
-static void add_strategies(const char *string, unsigned attr)
-{
-       struct strategy *list = NULL;
-       int list_alloc = 0, list_nr = 0, i;
-
-       memset(&list, 0, sizeof(list));
-       split_merge_strategies(string, &list, &list_nr, &list_alloc);
-       if (list) {
-               for (i = 0; i < list_nr; i++)
-                       append_strategy(get_strategy(list[i].name));
-               return;
-       }
-       for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
-               if (all_strategy[i].attr & attr)
-                       append_strategy(&all_strategy[i]);
-
-}
-
-static int merge_trivial(void)
-{
-       unsigned char result_tree[20], result_commit[20];
-       struct commit_list *parent = xmalloc(sizeof(*parent));
-
-       write_tree_trivial(result_tree);
-       printf("Wonderful.\n");
-       parent->item = lookup_commit(head);
-       parent->next = xmalloc(sizeof(*parent->next));
-       parent->next->item = remoteheads->item;
-       parent->next->next = NULL;
-       commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
-       finish(result_commit, "In-index merge");
-       drop_save();
-       return 0;
-}
-
-static int finish_automerge(struct commit_list *common,
-                           unsigned char *result_tree,
-                           const char *wt_strategy)
-{
-       struct commit_list *parents = NULL, *j;
-       struct strbuf buf = STRBUF_INIT;
-       unsigned char result_commit[20];
-
-       free_commit_list(common);
-       if (allow_fast_forward) {
-               parents = remoteheads;
-               commit_list_insert(lookup_commit(head), &parents);
-               parents = reduce_heads(parents);
-       } else {
-               struct commit_list **pptr = &parents;
-
-               pptr = &commit_list_insert(lookup_commit(head),
-                               pptr)->next;
-               for (j = remoteheads; j; j = j->next)
-                       pptr = &commit_list_insert(j->item, pptr)->next;
-       }
-       free_commit_list(remoteheads);
-       strbuf_addch(&merge_msg, '\n');
-       commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
-       strbuf_addf(&buf, "Merge made by %s.", wt_strategy);
-       finish(result_commit, buf.buf);
-       strbuf_release(&buf);
-       drop_save();
-       return 0;
-}
-
-static int suggest_conflicts(void)
-{
-       FILE *fp;
-       int pos;
-
-       fp = fopen(git_path("MERGE_MSG"), "a");
-       if (!fp)
-               die("Could not open %s for writing", git_path("MERGE_MSG"));
-       fprintf(fp, "\nConflicts:\n");
-       for (pos = 0; pos < active_nr; pos++) {
-               struct cache_entry *ce = active_cache[pos];
-
-               if (ce_stage(ce)) {
-                       fprintf(fp, "\t%s\n", ce->name);
-                       while (pos + 1 < active_nr &&
-                                       !strcmp(ce->name,
-                                               active_cache[pos + 1]->name))
-                               pos++;
-               }
-       }
-       fclose(fp);
-       rerere();
-       printf("Automatic merge failed; "
-                       "fix conflicts and then commit the result.\n");
-       return 1;
-}
-
-static struct commit *is_old_style_invocation(int argc, const char **argv)
-{
-       struct commit *second_token = NULL;
-       if (argc > 1) {
-               unsigned char second_sha1[20];
-
-               if (get_sha1(argv[1], second_sha1))
-                       return NULL;
-               second_token = lookup_commit_reference_gently(second_sha1, 0);
-               if (!second_token)
-                       die("'%s' is not a commit", argv[1]);
-               if (hashcmp(second_token->object.sha1, head))
-                       return NULL;
-       }
-       return second_token;
-}
-
-static int evaluate_result(void)
-{
-       int cnt = 0;
-       struct rev_info rev;
-
-       /* Check how many files differ. */
-       init_revisions(&rev, "");
-       setup_revisions(0, NULL, &rev, NULL);
-       rev.diffopt.output_format |=
-               DIFF_FORMAT_CALLBACK;
-       rev.diffopt.format_callback = count_diff_files;
-       rev.diffopt.format_callback_data = &cnt;
-       run_diff_files(&rev, 0);
-
-       /*
-        * Check how many unmerged entries are
-        * there.
-        */
-       cnt += count_unmerged_entries();
-
-       return cnt;
-}
-
-int cmd_merge(int argc, const char **argv, const char *prefix)
-{
-       unsigned char result_tree[20];
-       struct strbuf buf = STRBUF_INIT;
-       const char *head_arg;
-       int flag, head_invalid = 0, i;
-       int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
-       struct commit_list *common = NULL;
-       const char *best_strategy = NULL, *wt_strategy = NULL;
-       struct commit_list **remotes = &remoteheads;
-
-       setup_work_tree();
-       if (read_cache_unmerged())
-               die("You are in the middle of a conflicted merge.");
-
-       /*
-        * Check if we are _not_ on a detached HEAD, i.e. if there is a
-        * current branch.
-        */
-       branch = resolve_ref("HEAD", head, 0, &flag);
-       if (branch && !prefixcmp(branch, "refs/heads/"))
-               branch += 11;
-       if (is_null_sha1(head))
-               head_invalid = 1;
-
-       git_config(git_merge_config, NULL);
-
-       /* for color.ui */
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
-
-       argc = parse_options(argc, argv, builtin_merge_options,
-                       builtin_merge_usage, 0);
-       if (verbosity < 0)
-               show_diffstat = 0;
-
-       if (squash) {
-               if (!allow_fast_forward)
-                       die("You cannot combine --squash with --no-ff.");
-               option_commit = 0;
-       }
-
-       if (!argc)
-               usage_with_options(builtin_merge_usage,
-                       builtin_merge_options);
-
-       /*
-        * This could be traditional "merge <msg> HEAD <commit>..."  and
-        * the way we can tell it is to see if the second token is HEAD,
-        * but some people might have misused the interface and used a
-        * committish that is the same as HEAD there instead.
-        * Traditional format never would have "-m" so it is an
-        * additional safety measure to check for it.
-        */
-
-       if (!have_message && is_old_style_invocation(argc, argv)) {
-               strbuf_addstr(&merge_msg, argv[0]);
-               head_arg = argv[1];
-               argv += 2;
-               argc -= 2;
-       } else if (head_invalid) {
-               struct object *remote_head;
-               /*
-                * If the merged head is a valid one there is no reason
-                * to forbid "git merge" into a branch yet to be born.
-                * We do the same for "git pull".
-                */
-               if (argc != 1)
-                       die("Can merge only exactly one commit into "
-                               "empty head");
-               if (squash)
-                       die("Squash commit into empty head not supported yet");
-               if (!allow_fast_forward)
-                       die("Non-fast-forward commit does not make sense into "
-                           "an empty head");
-               remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
-               if (!remote_head)
-                       die("%s - not something we can merge", argv[0]);
-               update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
-                               DIE_ON_ERR);
-               reset_hard(remote_head->sha1, 0);
-               return 0;
-       } else {
-               struct strbuf msg = STRBUF_INIT;
-
-               /* We are invoked directly as the first-class UI. */
-               head_arg = "HEAD";
-
-               /*
-                * All the rest are the commits being merged;
-                * prepare the standard merge summary message to
-                * be appended to the given message.  If remote
-                * is invalid we will die later in the common
-                * codepath so we discard the error in this
-                * loop.
-                */
-               for (i = 0; i < argc; i++)
-                       merge_name(argv[i], &msg);
-               fmt_merge_msg(option_log, &msg, &merge_msg);
-               if (merge_msg.len)
-                       strbuf_setlen(&merge_msg, merge_msg.len-1);
-       }
-
-       if (head_invalid || !argc)
-               usage_with_options(builtin_merge_usage,
-                       builtin_merge_options);
-
-       strbuf_addstr(&buf, "merge");
-       for (i = 0; i < argc; i++)
-               strbuf_addf(&buf, " %s", argv[i]);
-       setenv("GIT_REFLOG_ACTION", buf.buf, 0);
-       strbuf_reset(&buf);
-
-       for (i = 0; i < argc; i++) {
-               struct object *o;
-               struct commit *commit;
-
-               o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
-               if (!o)
-                       die("%s - not something we can merge", argv[i]);
-               commit = lookup_commit(o->sha1);
-               commit->util = (void *)argv[i];
-               remotes = &commit_list_insert(commit, remotes)->next;
-
-               strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
-               setenv(buf.buf, argv[i], 1);
-               strbuf_reset(&buf);
-       }
-
-       if (!use_strategies) {
-               if (!remoteheads->next)
-                       add_strategies(pull_twohead, DEFAULT_TWOHEAD);
-               else
-                       add_strategies(pull_octopus, DEFAULT_OCTOPUS);
-       }
-
-       for (i = 0; i < use_strategies_nr; i++) {
-               if (use_strategies[i]->attr & NO_FAST_FORWARD)
-                       allow_fast_forward = 0;
-               if (use_strategies[i]->attr & NO_TRIVIAL)
-                       allow_trivial = 0;
-       }
-
-       if (!remoteheads->next)
-               common = get_merge_bases(lookup_commit(head),
-                               remoteheads->item, 1);
-       else {
-               struct commit_list *list = remoteheads;
-               commit_list_insert(lookup_commit(head), &list);
-               common = get_octopus_merge_bases(list);
-               free(list);
-       }
-
-       update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
-               DIE_ON_ERR);
-
-       if (!common)
-               ; /* No common ancestors found. We need a real merge. */
-       else if (!remoteheads->next && !common->next &&
-                       common->item == remoteheads->item) {
-               /*
-                * If head can reach all the merge then we are up to date.
-                * but first the most common case of merging one remote.
-                */
-               finish_up_to_date("Already up-to-date.");
-               return 0;
-       } else if (allow_fast_forward && !remoteheads->next &&
-                       !common->next &&
-                       !hashcmp(common->item->object.sha1, head)) {
-               /* Again the most common case of merging one remote. */
-               struct strbuf msg = STRBUF_INIT;
-               struct object *o;
-               char hex[41];
-
-               strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
-
-               if (verbosity >= 0)
-                       printf("Updating %s..%s\n",
-                               hex,
-                               find_unique_abbrev(remoteheads->item->object.sha1,
-                               DEFAULT_ABBREV));
-               strbuf_addstr(&msg, "Fast forward");
-               if (have_message)
-                       strbuf_addstr(&msg,
-                               " (no commit created; -m option ignored)");
-               o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
-                       0, NULL, OBJ_COMMIT);
-               if (!o)
-                       return 1;
-
-               if (checkout_fast_forward(head, remoteheads->item->object.sha1))
-                       return 1;
-
-               finish(o->sha1, msg.buf);
-               drop_save();
-               return 0;
-       } else if (!remoteheads->next && common->next)
-               ;
-               /*
-                * We are not doing octopus and not fast forward.  Need
-                * a real merge.
-                */
-       else if (!remoteheads->next && !common->next && option_commit) {
-               /*
-                * We are not doing octopus, not fast forward, and have
-                * only one common.
-                */
-               refresh_cache(REFRESH_QUIET);
-               if (allow_trivial) {
-                       /* See if it is really trivial. */
-                       git_committer_info(IDENT_ERROR_ON_NO_NAME);
-                       printf("Trying really trivial in-index merge...\n");
-                       if (!read_tree_trivial(common->item->object.sha1,
-                                       head, remoteheads->item->object.sha1))
-                               return merge_trivial();
-                       printf("Nope.\n");
-               }
-       } else {
-               /*
-                * An octopus.  If we can reach all the remote we are up
-                * to date.
-                */
-               int up_to_date = 1;
-               struct commit_list *j;
-
-               for (j = remoteheads; j; j = j->next) {
-                       struct commit_list *common_one;
-
-                       /*
-                        * Here we *have* to calculate the individual
-                        * merge_bases again, otherwise "git merge HEAD^
-                        * HEAD^^" would be missed.
-                        */
-                       common_one = get_merge_bases(lookup_commit(head),
-                               j->item, 1);
-                       if (hashcmp(common_one->item->object.sha1,
-                               j->item->object.sha1)) {
-                               up_to_date = 0;
-                               break;
-                       }
-               }
-               if (up_to_date) {
-                       finish_up_to_date("Already up-to-date. Yeeah!");
-                       return 0;
-               }
-       }
-
-       /* We are going to make a new commit. */
-       git_committer_info(IDENT_ERROR_ON_NO_NAME);
-
-       /*
-        * 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.
-        */
-       if (use_strategies_nr != 1) {
-               /*
-                * Stash away the local changes so that we can try more
-                * than one.
-                */
-               save_state();
-       } else {
-               memcpy(stash, null_sha1, 20);
-       }
-
-       for (i = 0; i < use_strategies_nr; i++) {
-               int ret;
-               if (i) {
-                       printf("Rewinding the tree to pristine...\n");
-                       restore_state();
-               }
-               if (use_strategies_nr != 1)
-                       printf("Trying merge strategy %s...\n",
-                               use_strategies[i]->name);
-               /*
-                * Remember which strategy left the state in the working
-                * tree.
-                */
-               wt_strategy = use_strategies[i]->name;
-
-               ret = try_merge_strategy(use_strategies[i]->name,
-                       common, head_arg);
-               if (!option_commit && !ret) {
-                       merge_was_ok = 1;
-                       /*
-                        * This is necessary here just to avoid writing
-                        * the tree, but later we will *not* exit with
-                        * status code 1 because merge_was_ok is set.
-                        */
-                       ret = 1;
-               }
-
-               if (ret) {
-                       /*
-                        * 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 (ret == 1) {
-                               int cnt = evaluate_result();
-
-                               if (best_cnt <= 0 || cnt <= best_cnt) {
-                                       best_strategy = use_strategies[i]->name;
-                                       best_cnt = cnt;
-                               }
-                       }
-                       if (merge_was_ok)
-                               break;
-                       else
-                               continue;
-               }
-
-               /* Automerge succeeded. */
-               write_tree_trivial(result_tree);
-               automerge_was_ok = 1;
-               break;
-       }
-
-       /*
-        * If we have a resulting tree, that means the strategy module
-        * auto resolved the merge cleanly.
-        */
-       if (automerge_was_ok)
-               return finish_automerge(common, result_tree, wt_strategy);
-
-       /*
-        * Pick the result from the best strategy and have the user fix
-        * it up.
-        */
-       if (!best_strategy) {
-               restore_state();
-               if (use_strategies_nr > 1)
-                       fprintf(stderr,
-                               "No merge strategy handled the merge.\n");
-               else
-                       fprintf(stderr, "Merge with strategy %s failed.\n",
-                               use_strategies[0]->name);
-               return 2;
-       } else if (best_strategy == wt_strategy)
-               ; /* We already have its result in the working tree. */
-       else {
-               printf("Rewinding the tree to pristine...\n");
-               restore_state();
-               printf("Using the %s to prepare resolving by hand.\n",
-                       best_strategy);
-               try_merge_strategy(best_strategy, common, head_arg);
-       }
-
-       if (squash)
-               finish(NULL, NULL);
-       else {
-               int fd;
-               struct commit_list *j;
-
-               for (j = remoteheads; j; j = j->next)
-                       strbuf_addf(&buf, "%s\n",
-                               sha1_to_hex(j->item->object.sha1));
-               fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
-               if (fd < 0)
-                       die("Could open %s for writing",
-                               git_path("MERGE_HEAD"));
-               if (write_in_full(fd, buf.buf, buf.len) != buf.len)
-                       die("Could not write to %s", git_path("MERGE_HEAD"));
-               close(fd);
-               strbuf_addch(&merge_msg, '\n');
-               fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
-               if (fd < 0)
-                       die("Could open %s for writing", git_path("MERGE_MSG"));
-               if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
-                       merge_msg.len)
-                       die("Could not write to %s", git_path("MERGE_MSG"));
-               close(fd);
-               fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
-               if (fd < 0)
-                       die("Could open %s for writing", git_path("MERGE_MODE"));
-               strbuf_reset(&buf);
-               if (!allow_fast_forward)
-                       strbuf_addf(&buf, "no-ff");
-               if (write_in_full(fd, buf.buf, buf.len) != buf.len)
-                       die("Could not write to %s", git_path("MERGE_MODE"));
-               close(fd);
-       }
-
-       if (merge_was_ok) {
-               fprintf(stderr, "Automatic merge went well; "
-                       "stopped before committing as requested\n");
-               return 0;
-       } else
-               return suggest_conflicts();
-}
diff --git a/builtin-mv.c b/builtin-mv.c
deleted file mode 100644 (file)
index 01270fe..0000000
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * "git mv" builtin command
- *
- * Copyright (C) 2006 Johannes Schindelin
- */
-#include "cache.h"
-#include "builtin.h"
-#include "dir.h"
-#include "cache-tree.h"
-#include "string-list.h"
-#include "parse-options.h"
-
-static const char * const builtin_mv_usage[] = {
-       "git mv [options] <source>... <destination>",
-       NULL
-};
-
-static const char **copy_pathspec(const char *prefix, const char **pathspec,
-                                 int count, int base_name)
-{
-       int i;
-       const char **result = xmalloc((count + 1) * sizeof(const char *));
-       memcpy(result, pathspec, count * sizeof(const char *));
-       result[count] = NULL;
-       for (i = 0; i < count; i++) {
-               int length = strlen(result[i]);
-               if (length > 0 && result[i][length - 1] == '/') {
-                       result[i] = xmemdupz(result[i], length - 1);
-               }
-               if (base_name) {
-                       const char *last_slash = strrchr(result[i], '/');
-                       if (last_slash)
-                               result[i] = last_slash + 1;
-               }
-       }
-       return get_pathspec(prefix, result);
-}
-
-static const char *add_slash(const char *path)
-{
-       int len = strlen(path);
-       if (path[len - 1] != '/') {
-               char *with_slash = xmalloc(len + 2);
-               memcpy(with_slash, path, len);
-               with_slash[len++] = '/';
-               with_slash[len] = 0;
-               return with_slash;
-       }
-       return path;
-}
-
-static struct lock_file lock_file;
-
-int cmd_mv(int argc, const char **argv, const char *prefix)
-{
-       int i, newfd;
-       int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;
-       struct option builtin_mv_options[] = {
-               OPT__DRY_RUN(&show_only),
-               OPT_BOOLEAN('f', NULL, &force, "force move/rename even if target exists"),
-               OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"),
-               OPT_END(),
-       };
-       const char **source, **destination, **dest_path;
-       enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;
-       struct stat st;
-       struct string_list src_for_dst = {NULL, 0, 0, 0};
-
-       git_config(git_default_config, NULL);
-
-       newfd = hold_locked_index(&lock_file, 1);
-       if (read_cache() < 0)
-               die("index file corrupt");
-
-       argc = parse_options(argc, argv, builtin_mv_options, builtin_mv_usage, 0);
-       if (--argc < 1)
-               usage_with_options(builtin_mv_usage, builtin_mv_options);
-
-       source = copy_pathspec(prefix, argv, argc, 0);
-       modes = xcalloc(argc, sizeof(enum update_mode));
-       dest_path = copy_pathspec(prefix, argv + argc, 1, 0);
-
-       if (dest_path[0][0] == '\0')
-               /* special case: "." was normalized to "" */
-               destination = copy_pathspec(dest_path[0], argv, argc, 1);
-       else if (!lstat(dest_path[0], &st) &&
-                       S_ISDIR(st.st_mode)) {
-               dest_path[0] = add_slash(dest_path[0]);
-               destination = copy_pathspec(dest_path[0], argv, argc, 1);
-       } else {
-               if (argc != 1)
-                       usage_with_options(builtin_mv_usage, builtin_mv_options);
-               destination = dest_path;
-       }
-
-       /* Checking */
-       for (i = 0; i < argc; i++) {
-               const char *src = source[i], *dst = destination[i];
-               int length, src_is_dir;
-               const char *bad = NULL;
-
-               if (show_only)
-                       printf("Checking rename of '%s' to '%s'\n", src, dst);
-
-               length = strlen(src);
-               if (lstat(src, &st) < 0)
-                       bad = "bad source";
-               else if (!strncmp(src, dst, length) &&
-                               (dst[length] == 0 || dst[length] == '/')) {
-                       bad = "can not move directory into itself";
-               } else if ((src_is_dir = S_ISDIR(st.st_mode))
-                               && lstat(dst, &st) == 0)
-                       bad = "cannot move directory over file";
-               else if (src_is_dir) {
-                       const char *src_w_slash = add_slash(src);
-                       int len_w_slash = length + 1;
-                       int first, last;
-
-                       modes[i] = WORKING_DIRECTORY;
-
-                       first = cache_name_pos(src_w_slash, len_w_slash);
-                       if (first >= 0)
-                               die ("Huh? %.*s is in index?",
-                                               len_w_slash, src_w_slash);
-
-                       first = -1 - first;
-                       for (last = first; last < active_nr; last++) {
-                               const char *path = active_cache[last]->name;
-                               if (strncmp(path, src_w_slash, len_w_slash))
-                                       break;
-                       }
-                       free((char *)src_w_slash);
-
-                       if (last - first < 1)
-                               bad = "source directory is empty";
-                       else {
-                               int j, dst_len;
-
-                               if (last - first > 0) {
-                                       source = xrealloc(source,
-                                                       (argc + last - first)
-                                                       * sizeof(char *));
-                                       destination = xrealloc(destination,
-                                                       (argc + last - first)
-                                                       * sizeof(char *));
-                                       modes = xrealloc(modes,
-                                                       (argc + last - first)
-                                                       * sizeof(enum update_mode));
-                               }
-
-                               dst = add_slash(dst);
-                               dst_len = strlen(dst);
-
-                               for (j = 0; j < last - first; j++) {
-                                       const char *path =
-                                               active_cache[first + j]->name;
-                                       source[argc + j] = path;
-                                       destination[argc + j] =
-                                               prefix_path(dst, dst_len,
-                                                       path + length + 1);
-                                       modes[argc + j] = INDEX;
-                               }
-                               argc += last - first;
-                       }
-               } else if (cache_name_pos(src, length) < 0)
-                       bad = "not under version control";
-               else if (lstat(dst, &st) == 0) {
-                       bad = "destination exists";
-                       if (force) {
-                               /*
-                                * only files can overwrite each other:
-                                * check both source and destination
-                                */
-                               if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
-                                       fprintf(stderr, "Warning: %s;"
-                                                       " will overwrite!\n",
-                                                       bad);
-                                       bad = NULL;
-                               } else
-                                       bad = "Cannot overwrite";
-                       }
-               } else if (string_list_has_string(&src_for_dst, dst))
-                       bad = "multiple sources for the same target";
-               else
-                       string_list_insert(dst, &src_for_dst);
-
-               if (bad) {
-                       if (ignore_errors) {
-                               if (--argc > 0) {
-                                       memmove(source + i, source + i + 1,
-                                               (argc - i) * sizeof(char *));
-                                       memmove(destination + i,
-                                               destination + i + 1,
-                                               (argc - i) * sizeof(char *));
-                                       i--;
-                               }
-                       } else
-                               die ("%s, source=%s, destination=%s",
-                                    bad, src, dst);
-               }
-       }
-
-       for (i = 0; i < argc; i++) {
-               const char *src = source[i], *dst = destination[i];
-               enum update_mode mode = modes[i];
-               int pos;
-               if (show_only || verbose)
-                       printf("Renaming %s to %s\n", src, dst);
-               if (!show_only && mode != INDEX &&
-                               rename(src, dst) < 0 && !ignore_errors)
-                       die ("renaming %s failed: %s", src, strerror(errno));
-
-               if (mode == WORKING_DIRECTORY)
-                       continue;
-
-               pos = cache_name_pos(src, strlen(src));
-               assert(pos >= 0);
-               if (!show_only)
-                       rename_cache_entry_at(pos, dst);
-       }
-
-       if (active_cache_changed) {
-               if (write_cache(newfd, active_cache, active_nr) ||
-                   commit_locked_index(&lock_file))
-                       die("Unable to write new index file");
-       }
-
-       return 0;
-}
diff --git a/builtin-name-rev.c b/builtin-name-rev.c
deleted file mode 100644 (file)
index 08c8aab..0000000
+++ /dev/null
@@ -1,305 +0,0 @@
-#include "builtin.h"
-#include "cache.h"
-#include "commit.h"
-#include "tag.h"
-#include "refs.h"
-#include "parse-options.h"
-
-#define CUTOFF_DATE_SLOP 86400 /* one day */
-
-typedef struct rev_name {
-       const char *tip_name;
-       int generation;
-       int distance;
-} rev_name;
-
-static long cutoff = LONG_MAX;
-
-/* How many generations are maximally preferred over _one_ merge traversal? */
-#define MERGE_TRAVERSAL_WEIGHT 65535
-
-static void name_rev(struct commit *commit,
-               const char *tip_name, int generation, int distance,
-               int deref)
-{
-       struct rev_name *name = (struct rev_name *)commit->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->util = name;
-               goto copy_data;
-       } else if (name->distance > distance) {
-copy_data:
-               name->tip_name = tip_name;
-               name->generation = generation;
-               name->distance = distance;
-       } else
-               return;
-
-       for (parents = commit->parents;
-                       parents;
-                       parents = parents->next, parent_number++) {
-               if (parent_number > 1) {
-                       int len = strlen(tip_name);
-                       char *new_name = xmalloc(len +
-                               1 + decimal_length(generation) +  /* ~<n> */
-                               1 + 2 +                           /* ^NN */
-                               1);
-
-                       if (len > 2 && !strcmp(tip_name + len - 2, "^0"))
-                               len -= 2;
-                       if (generation > 0)
-                               sprintf(new_name, "%.*s~%d^%d", len, tip_name,
-                                               generation, parent_number);
-                       else
-                               sprintf(new_name, "%.*s^%d", len, tip_name,
-                                               parent_number);
-
-                       name_rev(parents->item, new_name, 0,
-                               distance + MERGE_TRAVERSAL_WEIGHT, 0);
-               } else {
-                       name_rev(parents->item, tip_name, generation + 1,
-                               distance + 1, 0);
-               }
-       }
-}
-
-struct name_ref_data {
-       int tags_only;
-       int name_only;
-       const char *ref_filter;
-};
-
-static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data)
-{
-       struct object *o = parse_object(sha1);
-       struct name_ref_data *data = cb_data;
-       int deref = 0;
-
-       if (data->tags_only && prefixcmp(path, "refs/tags/"))
-               return 0;
-
-       if (data->ref_filter && fnmatch(data->ref_filter, path, 0))
-               return 0;
-
-       while (o && o->type == OBJ_TAG) {
-               struct tag *t = (struct tag *) o;
-               if (!t->tagged)
-                       break; /* broken repository */
-               o = parse_object(t->tagged->sha1);
-               deref = 1;
-       }
-       if (o && o->type == OBJ_COMMIT) {
-               struct commit *commit = (struct commit *)o;
-
-               if (!prefixcmp(path, "refs/heads/"))
-                       path = path + 11;
-               else if (data->tags_only
-                   && data->name_only
-                   && !prefixcmp(path, "refs/tags/"))
-                       path = path + 10;
-               else if (!prefixcmp(path, "refs/"))
-                       path = path + 5;
-
-               name_rev(commit, xstrdup(path), 0, 0, deref);
-       }
-       return 0;
-}
-
-/* returns a static buffer */
-static const char *get_rev_name(const struct object *o)
-{
-       static char buffer[1024];
-       struct rev_name *n;
-       struct commit *c;
-
-       if (o->type != OBJ_COMMIT)
-               return NULL;
-       c = (struct commit *) o;
-       n = c->util;
-       if (!n)
-               return NULL;
-
-       if (!n->generation)
-               return n->tip_name;
-       else {
-               int len = strlen(n->tip_name);
-               if (len > 2 && !strcmp(n->tip_name + len - 2, "^0"))
-                       len -= 2;
-               snprintf(buffer, sizeof(buffer), "%.*s~%d", len, n->tip_name,
-                               n->generation);
-
-               return buffer;
-       }
-}
-
-static void show_name(const struct object *obj,
-                     const char *caller_name,
-                     int always, int allow_undefined, int name_only)
-{
-       const char *name;
-       const unsigned char *sha1 = obj->sha1;
-
-       if (!name_only)
-               printf("%s ", caller_name ? caller_name : sha1_to_hex(sha1));
-       name = get_rev_name(obj);
-       if (name)
-               printf("%s\n", name);
-       else if (allow_undefined)
-               printf("undefined\n");
-       else if (always)
-               printf("%s\n", find_unique_abbrev(sha1, DEFAULT_ABBREV));
-       else
-               die("cannot describe '%s'", sha1_to_hex(sha1));
-}
-
-static char const * const name_rev_usage[] = {
-       "git name-rev [options] ( --all | --stdin | <commit>... )",
-       NULL
-};
-
-static void name_rev_line(char *p, struct name_ref_data *data)
-{
-       int forty = 0;
-       char *p_start;
-       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 = NULL;
-                       char c = *(p+1);
-                       int p_len = p - p_start + 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 (!name)
-                               continue;
-
-                       if (data->name_only)
-                               printf("%.*s%s", p_len - 40, p_start, name);
-                       else
-                               printf("%.*s (%s)", p_len, p_start, name);
-                       p_start = p + 1;
-               }
-       }
-
-       /* flush */
-       if (p_start != p)
-               fwrite(p_start, p - p_start, 1, stdout);
-}
-
-int cmd_name_rev(int argc, const char **argv, const char *prefix)
-{
-       struct object_array revs = { 0, 0, NULL };
-       int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0;
-       struct name_ref_data data = { 0, 0, NULL };
-       struct option opts[] = {
-               OPT_BOOLEAN(0, "name-only", &data.name_only, "print only names (no SHA-1)"),
-               OPT_BOOLEAN(0, "tags", &data.tags_only, "only use tags to name the commits"),
-               OPT_STRING(0, "refs", &data.ref_filter, "pattern",
-                                  "only use refs matching <pattern>"),
-               OPT_GROUP(""),
-               OPT_BOOLEAN(0, "all", &all, "list all commits reachable from all refs"),
-               OPT_BOOLEAN(0, "stdin", &transform_stdin, "read from stdin"),
-               OPT_BOOLEAN(0, "undefined", &allow_undefined, "allow to print `undefined` names"),
-               OPT_BOOLEAN(0, "always",     &always,
-                          "show abbreviated commit object as fallback"),
-               OPT_END(),
-       };
-
-       git_config(git_default_config, NULL);
-       argc = parse_options(argc, argv, opts, name_rev_usage, 0);
-       if (!!all + !!transform_stdin + !!argc > 1) {
-               error("Specify either a list, or --all, not both!");
-               usage_with_options(name_rev_usage, opts);
-       }
-       if (all || transform_stdin)
-               cutoff = 0;
-
-       for (; argc; argc--, argv++) {
-               unsigned char sha1[20];
-               struct object *o;
-               struct commit *commit;
-
-               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 != OBJ_COMMIT) {
-                       fprintf(stderr, "Could not get commit for %s. Skipping.\n",
-                                       *argv);
-                       continue;
-               }
-
-               commit = (struct commit *)o;
-               if (cutoff > commit->date)
-                       cutoff = commit->date;
-               add_object_array((struct object *)commit, *argv, &revs);
-       }
-
-       if (cutoff)
-               cutoff = cutoff - CUTOFF_DATE_SLOP;
-       for_each_ref(name_ref, &data);
-
-       if (transform_stdin) {
-               char buffer[2048];
-
-               while (!feof(stdin)) {
-                       char *p = fgets(buffer, sizeof(buffer), stdin);
-                       if (!p)
-                               break;
-                       name_rev_line(p, &data);
-               }
-       } else if (all) {
-               int i, max;
-
-               max = get_max_object_index();
-               for (i = 0; i < max; i++) {
-                       struct object *obj = get_indexed_object(i);
-                       if (!obj)
-                               continue;
-                       show_name(obj, NULL,
-                                 always, allow_undefined, data.name_only);
-               }
-       } else {
-               int i;
-               for (i = 0; i < revs.nr; i++)
-                       show_name(revs.objects[i].item, revs.objects[i].name,
-                                 always, allow_undefined, data.name_only);
-       }
-
-       return 0;
-}
diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c
deleted file mode 100644 (file)
index 9742b45..0000000
+++ /dev/null
@@ -1,2326 +0,0 @@
-#include "builtin.h"
-#include "cache.h"
-#include "attr.h"
-#include "object.h"
-#include "blob.h"
-#include "commit.h"
-#include "tag.h"
-#include "tree.h"
-#include "delta.h"
-#include "pack.h"
-#include "pack-revindex.h"
-#include "csum-file.h"
-#include "tree-walk.h"
-#include "diff.h"
-#include "revision.h"
-#include "list-objects.h"
-#include "progress.h"
-#include "refs.h"
-
-#ifdef THREADED_DELTA_SEARCH
-#include "thread-utils.h"
-#include <pthread.h>
-#endif
-
-static const char pack_usage[] = "\
-git pack-objects [{ -q | --progress | --all-progress }] \n\
-       [--max-pack-size=N] [--local] [--incremental] \n\
-       [--window=N] [--window-memory=N] [--depth=N] \n\
-       [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset] \n\
-       [--threads=N] [--non-empty] [--revs [--unpacked | --all]*] [--reflog] \n\
-       [--stdout | base-name] [--include-tag] \n\
-       [--keep-unreachable | --unpack-unreachable] \n\
-       [<ref-list | <object-list]";
-
-struct object_entry {
-       struct pack_idx_entry idx;
-       unsigned long size;     /* uncompressed size */
-       struct packed_git *in_pack;     /* already in pack */
-       off_t in_pack_offset;
-       struct object_entry *delta;     /* delta base object */
-       struct object_entry *delta_child; /* deltified objects who bases me */
-       struct object_entry *delta_sibling; /* other deltified objects who
-                                            * uses the same base as me
-                                            */
-       void *delta_data;       /* cached delta (uncompressed) */
-       unsigned long delta_size;       /* delta data size (uncompressed) */
-       unsigned long z_delta_size;     /* delta data size (compressed) */
-       unsigned int hash;      /* name hint hash */
-       enum object_type type;
-       enum object_type in_pack_type;  /* could be delta */
-       unsigned char in_pack_header_size;
-       unsigned char preferred_base; /* we do not pack this, but is available
-                                      * to be used as the base object to delta
-                                      * objects against.
-                                      */
-       unsigned char no_try_delta;
-};
-
-/*
- * Objects we are going to pack are collected 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.
- */
-static struct object_entry *objects;
-static struct pack_idx_entry **written_list;
-static uint32_t nr_objects, nr_alloc, nr_result, nr_written;
-
-static int non_empty;
-static int reuse_delta = 1, reuse_object = 1;
-static int keep_unreachable, unpack_unreachable, include_tag;
-static int local;
-static int incremental;
-static int ignore_packed_keep;
-static int allow_ofs_delta;
-static const char *base_name;
-static int progress = 1;
-static int window = 10;
-static uint32_t pack_size_limit, pack_size_limit_cfg;
-static int depth = 50;
-static int delta_search_threads;
-static int pack_to_stdout;
-static int num_preferred_base;
-static struct progress *progress_state;
-static int pack_compression_level = Z_DEFAULT_COMPRESSION;
-static int pack_compression_seen;
-
-static unsigned long delta_cache_size = 0;
-static unsigned long max_delta_cache_size = 0;
-static unsigned long cache_max_small_delta_size = 1000;
-
-static unsigned long window_memory_limit = 0;
-
-/*
- * The object names in objects array are hashed with this hashtable,
- * to help looking up the entry by object name.
- * This hashtable is built after all the objects are seen.
- */
-static int *object_ix;
-static int object_ix_hashsz;
-
-/*
- * stats
- */
-static uint32_t written, written_delta;
-static uint32_t reused, reused_delta;
-
-
-static void *get_delta(struct object_entry *entry)
-{
-       unsigned long size, base_size, delta_size;
-       void *buf, *base_buf, *delta_buf;
-       enum object_type type;
-
-       buf = read_sha1_file(entry->idx.sha1, &type, &size);
-       if (!buf)
-               die("unable to read %s", sha1_to_hex(entry->idx.sha1));
-       base_buf = read_sha1_file(entry->delta->idx.sha1, &type, &base_size);
-       if (!base_buf)
-               die("unable to read %s", sha1_to_hex(entry->delta->idx.sha1));
-       delta_buf = diff_delta(base_buf, base_size,
-                              buf, size, &delta_size, 0);
-       if (!delta_buf || delta_size != entry->delta_size)
-               die("delta size changed");
-       free(buf);
-       free(base_buf);
-       return delta_buf;
-}
-
-static unsigned long do_compress(void **pptr, unsigned long size)
-{
-       z_stream stream;
-       void *in, *out;
-       unsigned long maxsize;
-
-       memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, pack_compression_level);
-       maxsize = deflateBound(&stream, size);
-
-       in = *pptr;
-       out = xmalloc(maxsize);
-       *pptr = out;
-
-       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);
-
-       free(in);
-       return stream.total_out;
-}
-
-/*
- * 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_REF_DELTA)
-               die("bad type %d", type);
-
-       c = (type << 4) | (size & 15);
-       size >>= 4;
-       while (size) {
-               *hdr++ = c | 0x80;
-               c = size & 0x7f;
-               size >>= 7;
-               n++;
-       }
-       *hdr = c;
-       return n;
-}
-
-/*
- * we are going to reuse the existing object data as is.  make
- * sure it is not corrupt.
- */
-static int check_pack_inflate(struct packed_git *p,
-               struct pack_window **w_curs,
-               off_t offset,
-               off_t len,
-               unsigned long expect)
-{
-       z_stream stream;
-       unsigned char fakebuf[4096], *in;
-       int st;
-
-       memset(&stream, 0, sizeof(stream));
-       git_inflate_init(&stream);
-       do {
-               in = use_pack(p, w_curs, offset, &stream.avail_in);
-               stream.next_in = in;
-               stream.next_out = fakebuf;
-               stream.avail_out = sizeof(fakebuf);
-               st = git_inflate(&stream, Z_FINISH);
-               offset += stream.next_in - in;
-       } while (st == Z_OK || st == Z_BUF_ERROR);
-       git_inflate_end(&stream);
-       return (st == Z_STREAM_END &&
-               stream.total_out == expect &&
-               stream.total_in == len) ? 0 : -1;
-}
-
-static void copy_pack_data(struct sha1file *f,
-               struct packed_git *p,
-               struct pack_window **w_curs,
-               off_t offset,
-               off_t len)
-{
-       unsigned char *in;
-       unsigned int avail;
-
-       while (len) {
-               in = use_pack(p, w_curs, offset, &avail);
-               if (avail > len)
-                       avail = (unsigned int)len;
-               sha1write(f, in, avail);
-               offset += avail;
-               len -= avail;
-       }
-}
-
-static unsigned long write_object(struct sha1file *f,
-                                 struct object_entry *entry,
-                                 off_t write_offset)
-{
-       unsigned long size, limit, datalen;
-       void *buf;
-       unsigned char header[10], dheader[10];
-       unsigned hdrlen;
-       enum object_type type;
-       int usable_delta, to_reuse;
-
-       if (!pack_to_stdout)
-               crc32_begin(f);
-
-       type = entry->type;
-
-       /* write limit if limited packsize and not first object */
-       if (!pack_size_limit || !nr_written)
-               limit = 0;
-       else if (pack_size_limit <= write_offset)
-               /*
-                * the earlier object did not fit the limit; avoid
-                * mistaking this with unlimited (i.e. limit = 0).
-                */
-               limit = 1;
-       else
-               limit = pack_size_limit - write_offset;
-
-       if (!entry->delta)
-               usable_delta = 0;       /* no delta */
-       else if (!pack_size_limit)
-              usable_delta = 1;        /* unlimited packfile */
-       else if (entry->delta->idx.offset == (off_t)-1)
-               usable_delta = 0;       /* base was written to another pack */
-       else if (entry->delta->idx.offset)
-               usable_delta = 1;       /* base already exists in this pack */
-       else
-               usable_delta = 0;       /* base could end up in another pack */
-
-       if (!reuse_object)
-               to_reuse = 0;   /* explicit */
-       else if (!entry->in_pack)
-               to_reuse = 0;   /* can't reuse what we don't have */
-       else if (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA)
-                               /* check_object() decided it for us ... */
-               to_reuse = usable_delta;
-                               /* ... but pack split may override that */
-       else if (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) {
-               no_reuse:
-               if (!usable_delta) {
-                       buf = read_sha1_file(entry->idx.sha1, &type, &size);
-                       if (!buf)
-                               die("unable to read %s", sha1_to_hex(entry->idx.sha1));
-                       /*
-                        * make sure no cached delta data remains from a
-                        * previous attempt before a pack split occurred.
-                        */
-                       free(entry->delta_data);
-                       entry->delta_data = NULL;
-                       entry->z_delta_size = 0;
-               } else if (entry->delta_data) {
-                       size = entry->delta_size;
-                       buf = entry->delta_data;
-                       entry->delta_data = NULL;
-                       type = (allow_ofs_delta && entry->delta->idx.offset) ?
-                               OBJ_OFS_DELTA : OBJ_REF_DELTA;
-               } else {
-                       buf = get_delta(entry);
-                       size = entry->delta_size;
-                       type = (allow_ofs_delta && entry->delta->idx.offset) ?
-                               OBJ_OFS_DELTA : OBJ_REF_DELTA;
-               }
-
-               if (entry->z_delta_size)
-                       datalen = entry->z_delta_size;
-               else
-                       datalen = do_compress(&buf, size);
-
-               /*
-                * The object header is a byte of 'type' followed by zero or
-                * more bytes of length.
-                */
-               hdrlen = encode_header(type, size, header);
-
-               if (type == OBJ_OFS_DELTA) {
-                       /*
-                        * Deltas with relative base contain an additional
-                        * encoding of the relative offset for the delta
-                        * base from this object's position in the pack.
-                        */
-                       off_t ofs = entry->idx.offset - entry->delta->idx.offset;
-                       unsigned pos = sizeof(dheader) - 1;
-                       dheader[pos] = ofs & 127;
-                       while (ofs >>= 7)
-                               dheader[--pos] = 128 | (--ofs & 127);
-                       if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) {
-                               free(buf);
-                               return 0;
-                       }
-                       sha1write(f, header, hdrlen);
-                       sha1write(f, dheader + pos, sizeof(dheader) - pos);
-                       hdrlen += sizeof(dheader) - pos;
-               } else if (type == OBJ_REF_DELTA) {
-                       /*
-                        * Deltas with a base reference contain
-                        * an additional 20 bytes for the base sha1.
-                        */
-                       if (limit && hdrlen + 20 + datalen + 20 >= limit) {
-                               free(buf);
-                               return 0;
-                       }
-                       sha1write(f, header, hdrlen);
-                       sha1write(f, entry->delta->idx.sha1, 20);
-                       hdrlen += 20;
-               } else {
-                       if (limit && hdrlen + datalen + 20 >= limit) {
-                               free(buf);
-                               return 0;
-                       }
-                       sha1write(f, header, hdrlen);
-               }
-               sha1write(f, buf, datalen);
-               free(buf);
-       }
-       else {
-               struct packed_git *p = entry->in_pack;
-               struct pack_window *w_curs = NULL;
-               struct revindex_entry *revidx;
-               off_t offset;
-
-               if (entry->delta)
-                       type = (allow_ofs_delta && entry->delta->idx.offset) ?
-                               OBJ_OFS_DELTA : OBJ_REF_DELTA;
-               hdrlen = encode_header(type, entry->size, header);
-
-               offset = entry->in_pack_offset;
-               revidx = find_pack_revindex(p, offset);
-               datalen = revidx[1].offset - offset;
-               if (!pack_to_stdout && p->index_version > 1 &&
-                   check_pack_crc(p, &w_curs, offset, datalen, revidx->nr)) {
-                       error("bad packed object CRC for %s", sha1_to_hex(entry->idx.sha1));
-                       unuse_pack(&w_curs);
-                       goto no_reuse;
-               }
-
-               offset += entry->in_pack_header_size;
-               datalen -= entry->in_pack_header_size;
-               if (!pack_to_stdout && p->index_version == 1 &&
-                   check_pack_inflate(p, &w_curs, offset, datalen, entry->size)) {
-                       error("corrupt packed object for %s", sha1_to_hex(entry->idx.sha1));
-                       unuse_pack(&w_curs);
-                       goto no_reuse;
-               }
-
-               if (type == OBJ_OFS_DELTA) {
-                       off_t ofs = entry->idx.offset - entry->delta->idx.offset;
-                       unsigned pos = sizeof(dheader) - 1;
-                       dheader[pos] = ofs & 127;
-                       while (ofs >>= 7)
-                               dheader[--pos] = 128 | (--ofs & 127);
-                       if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) {
-                               unuse_pack(&w_curs);
-                               return 0;
-                       }
-                       sha1write(f, header, hdrlen);
-                       sha1write(f, dheader + pos, sizeof(dheader) - pos);
-                       hdrlen += sizeof(dheader) - pos;
-                       reused_delta++;
-               } else if (type == OBJ_REF_DELTA) {
-                       if (limit && hdrlen + 20 + datalen + 20 >= limit) {
-                               unuse_pack(&w_curs);
-                               return 0;
-                       }
-                       sha1write(f, header, hdrlen);
-                       sha1write(f, entry->delta->idx.sha1, 20);
-                       hdrlen += 20;
-                       reused_delta++;
-               } else {
-                       if (limit && hdrlen + datalen + 20 >= limit) {
-                               unuse_pack(&w_curs);
-                               return 0;
-                       }
-                       sha1write(f, header, hdrlen);
-               }
-               copy_pack_data(f, p, &w_curs, offset, datalen);
-               unuse_pack(&w_curs);
-               reused++;
-       }
-       if (usable_delta)
-               written_delta++;
-       written++;
-       if (!pack_to_stdout)
-               entry->idx.crc32 = crc32_end(f);
-       return hdrlen + datalen;
-}
-
-static int write_one(struct sha1file *f,
-                              struct object_entry *e,
-                              off_t *offset)
-{
-       unsigned long size;
-
-       /* offset is non zero if object is written already. */
-       if (e->idx.offset || e->preferred_base)
-               return 1;
-
-       /* if we are deltified, write out base object first. */
-       if (e->delta && !write_one(f, e->delta, offset))
-               return 0;
-
-       e->idx.offset = *offset;
-       size = write_object(f, e, *offset);
-       if (!size) {
-               e->idx.offset = 0;
-               return 0;
-       }
-       written_list[nr_written++] = &e->idx;
-
-       /* make sure off_t is sufficiently large not to wrap */
-       if (*offset > *offset + size)
-               die("pack too large for current definition of off_t");
-       *offset += size;
-       return 1;
-}
-
-/* forward declaration for write_pack_file */
-static int adjust_perm(const char *path, mode_t mode);
-
-static void write_pack_file(void)
-{
-       uint32_t i = 0, j;
-       struct sha1file *f;
-       off_t offset;
-       struct pack_header hdr;
-       uint32_t nr_remaining = nr_result;
-       time_t last_mtime = 0;
-
-       if (progress > pack_to_stdout)
-               progress_state = start_progress("Writing objects", nr_result);
-       written_list = xmalloc(nr_objects * sizeof(*written_list));
-
-       do {
-               unsigned char sha1[20];
-               char *pack_tmp_name = NULL;
-
-               if (pack_to_stdout) {
-                       f = sha1fd_throughput(1, "<stdout>", progress_state);
-               } else {
-                       char tmpname[PATH_MAX];
-                       int fd;
-                       fd = odb_mkstemp(tmpname, sizeof(tmpname),
-                                        "pack/tmp_pack_XXXXXX");
-                       pack_tmp_name = xstrdup(tmpname);
-                       f = sha1fd(fd, pack_tmp_name);
-               }
-
-               hdr.hdr_signature = htonl(PACK_SIGNATURE);
-               hdr.hdr_version = htonl(PACK_VERSION);
-               hdr.hdr_entries = htonl(nr_remaining);
-               sha1write(f, &hdr, sizeof(hdr));
-               offset = sizeof(hdr);
-               nr_written = 0;
-               for (; i < nr_objects; i++) {
-                       if (!write_one(f, objects + i, &offset))
-                               break;
-                       display_progress(progress_state, written);
-               }
-
-               /*
-                * Did we write the wrong # entries in the header?
-                * If so, rewrite it like in fast-import
-                */
-               if (pack_to_stdout) {
-                       sha1close(f, sha1, CSUM_CLOSE);
-               } else if (nr_written == nr_remaining) {
-                       sha1close(f, sha1, CSUM_FSYNC);
-               } else {
-                       int fd = sha1close(f, sha1, 0);
-                       fixup_pack_header_footer(fd, sha1, pack_tmp_name,
-                                                nr_written, sha1, offset);
-                       close(fd);
-               }
-
-               if (!pack_to_stdout) {
-                       mode_t mode = umask(0);
-                       struct stat st;
-                       char *idx_tmp_name, tmpname[PATH_MAX];
-
-                       umask(mode);
-                       mode = 0444 & ~mode;
-
-                       idx_tmp_name = write_idx_file(NULL, written_list,
-                                                     nr_written, sha1);
-
-                       snprintf(tmpname, sizeof(tmpname), "%s-%s.pack",
-                                base_name, sha1_to_hex(sha1));
-                       free_pack_by_name(tmpname);
-                       if (adjust_perm(pack_tmp_name, mode))
-                               die("unable to make temporary pack file readable: %s",
-                                   strerror(errno));
-                       if (rename(pack_tmp_name, tmpname))
-                               die("unable to rename temporary pack file: %s",
-                                   strerror(errno));
-
-                       /*
-                        * Packs are runtime accessed in their mtime
-                        * order since newer packs are more likely to contain
-                        * younger objects.  So if we are creating multiple
-                        * packs then we should modify the mtime of later ones
-                        * to preserve this property.
-                        */
-                       if (stat(tmpname, &st) < 0) {
-                               warning("failed to stat %s: %s",
-                                       tmpname, strerror(errno));
-                       } else if (!last_mtime) {
-                               last_mtime = st.st_mtime;
-                       } else {
-                               struct utimbuf utb;
-                               utb.actime = st.st_atime;
-                               utb.modtime = --last_mtime;
-                               if (utime(tmpname, &utb) < 0)
-                                       warning("failed utime() on %s: %s",
-                                               tmpname, strerror(errno));
-                       }
-
-                       snprintf(tmpname, sizeof(tmpname), "%s-%s.idx",
-                                base_name, sha1_to_hex(sha1));
-                       if (adjust_perm(idx_tmp_name, mode))
-                               die("unable to make temporary index file readable: %s",
-                                   strerror(errno));
-                       if (rename(idx_tmp_name, tmpname))
-                               die("unable to rename temporary index file: %s",
-                                   strerror(errno));
-
-                       free(idx_tmp_name);
-                       free(pack_tmp_name);
-                       puts(sha1_to_hex(sha1));
-               }
-
-               /* mark written objects as written to previous pack */
-               for (j = 0; j < nr_written; j++) {
-                       written_list[j]->offset = (off_t)-1;
-               }
-               nr_remaining -= nr_written;
-       } while (nr_remaining && i < nr_objects);
-
-       free(written_list);
-       stop_progress(&progress_state);
-       if (written != nr_result)
-               die("wrote %"PRIu32" objects while expecting %"PRIu32,
-                       written, nr_result);
-       /*
-        * We have scanned through [0 ... i).  Since we have written
-        * the correct number of objects,  the remaining [i ... nr_objects)
-        * items must be either already written (due to out-of-order delta base)
-        * or a preferred base.  Count those which are neither and complain if any.
-        */
-       for (j = 0; i < nr_objects; i++) {
-               struct object_entry *e = objects + i;
-               j += !e->idx.offset && !e->preferred_base;
-       }
-       if (j)
-               die("wrote %"PRIu32" objects as expected but %"PRIu32
-                       " unwritten", written, j);
-}
-
-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 (!hashcmp(sha1, objects[object_ix[i] - 1].idx.sha1))
-                       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)
-{
-       uint32_t 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->idx.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;
-
-       if (!name)
-               return 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 void setup_delta_attr_check(struct git_attr_check *check)
-{
-       static struct git_attr *attr_delta;
-
-       if (!attr_delta)
-               attr_delta = git_attr("delta", 5);
-
-       check[0].attr = attr_delta;
-}
-
-static int no_try_delta(const char *path)
-{
-       struct git_attr_check check[1];
-
-       setup_delta_attr_check(check);
-       if (git_checkattr(path, ARRAY_SIZE(check), check))
-               return 0;
-       if (ATTR_FALSE(check->value))
-               return 1;
-       return 0;
-}
-
-static int add_object_entry(const unsigned char *sha1, enum object_type type,
-                           const char *name, int exclude)
-{
-       struct object_entry *entry;
-       struct packed_git *p, *found_pack = NULL;
-       off_t found_offset = 0;
-       int ix;
-       unsigned hash = name_hash(name);
-
-       ix = nr_objects ? locate_object_entry_hash(sha1) : -1;
-       if (ix >= 0) {
-               if (exclude) {
-                       entry = objects + object_ix[ix] - 1;
-                       if (!entry->preferred_base)
-                               nr_result--;
-                       entry->preferred_base = 1;
-               }
-               return 0;
-       }
-
-       if (!exclude && local && has_loose_object_nonlocal(sha1))
-               return 0;
-
-       for (p = packed_git; p; p = p->next) {
-               off_t offset = find_pack_entry_one(sha1, p);
-               if (offset) {
-                       if (!found_pack) {
-                               found_offset = offset;
-                               found_pack = p;
-                       }
-                       if (exclude)
-                               break;
-                       if (incremental)
-                               return 0;
-                       if (local && !p->pack_local)
-                               return 0;
-                       if (ignore_packed_keep && p->pack_local && p->pack_keep)
-                               return 0;
-               }
-       }
-
-       if (nr_objects >= nr_alloc) {
-               nr_alloc = (nr_alloc  + 1024) * 3 / 2;
-               objects = xrealloc(objects, nr_alloc * sizeof(*entry));
-       }
-
-       entry = objects + nr_objects++;
-       memset(entry, 0, sizeof(*entry));
-       hashcpy(entry->idx.sha1, sha1);
-       entry->hash = hash;
-       if (type)
-               entry->type = type;
-       if (exclude)
-               entry->preferred_base = 1;
-       else
-               nr_result++;
-       if (found_pack) {
-               entry->in_pack = found_pack;
-               entry->in_pack_offset = found_offset;
-       }
-
-       if (object_ix_hashsz * 3 <= nr_objects * 4)
-               rehash_objects();
-       else
-               object_ix[-1 - ix] = nr_objects;
-
-       display_progress(progress_state, nr_objects);
-
-       if (name && no_try_delta(name))
-               entry->no_try_delta = 1;
-
-       return 1;
-}
-
-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;
-       enum object_type type;
-       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 && !hashcmp(ent->sha1, sha1)) {
-                       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 (type != OBJ_TREE) {
-               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;
-       }
-       hashcpy(nent->sha1, sha1);
-       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;
-       int cmp;
-
-       while (tree_entry(tree,&entry)) {
-               if (S_ISGITLINK(entry.mode))
-                       continue;
-               cmp = tree_entry_len(entry.path, entry.sha1) != cmplen ? 1 :
-                     memcmp(name, entry.path, cmplen);
-               if (cmp > 0)
-                       continue;
-               if (cmp < 0)
-                       return;
-               if (name[cmplen] != '/') {
-                       add_object_entry(entry.sha1,
-                                        object_type(entry.mode),
-                                        fullname, 1);
-                       return;
-               }
-               if (S_ISDIR(entry.mode)) {
-                       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;
-                       init_tree_desc(&sub, tree->tree_data, 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(const char *name)
-{
-       struct pbase_tree *it;
-       int cmplen;
-       unsigned hash = name_hash(name);
-
-       if (!num_preferred_base || check_pbase_path(hash))
-               return;
-
-       cmplen = name_cmp_len(name);
-       for (it = pbase_tree; it; it = it->next) {
-               if (cmplen == 0) {
-                       add_object_entry(it->pcache.sha1, OBJ_TREE, NULL, 1);
-               }
-               else {
-                       struct tree_desc tree;
-                       init_tree_desc(&tree, it->pcache.tree_data, 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];
-
-       if (window <= num_preferred_base++)
-               return;
-
-       data = read_object_with_reference(sha1, tree_type, &size, tree_sha1);
-       if (!data)
-               return;
-
-       for (it = pbase_tree; it; it = it->next) {
-               if (!hashcmp(it->pcache.sha1, tree_sha1)) {
-                       free(data);
-                       return;
-               }
-       }
-
-       it = xcalloc(1, sizeof(*it));
-       it->next = pbase_tree;
-       pbase_tree = it;
-
-       hashcpy(it->pcache.sha1, tree_sha1);
-       it->pcache.tree_data = data;
-       it->pcache.tree_size = size;
-}
-
-static void check_object(struct object_entry *entry)
-{
-       if (entry->in_pack) {
-               struct packed_git *p = entry->in_pack;
-               struct pack_window *w_curs = NULL;
-               const unsigned char *base_ref = NULL;
-               struct object_entry *base_entry;
-               unsigned long used, used_0;
-               unsigned int avail;
-               off_t ofs;
-               unsigned char *buf, c;
-
-               buf = use_pack(p, &w_curs, entry->in_pack_offset, &avail);
-
-               /*
-                * We want in_pack_type even if we do not reuse delta
-                * since non-delta representations could still be reused.
-                */
-               used = unpack_object_header_buffer(buf, avail,
-                                                  &entry->in_pack_type,
-                                                  &entry->size);
-               if (used == 0)
-                       goto give_up;
-
-               /*
-                * Determine if this is a delta and if so whether we can
-                * reuse it or not.  Otherwise let's find out as cheaply as
-                * possible what the actual type and size for this object is.
-                */
-               switch (entry->in_pack_type) {
-               default:
-                       /* Not a delta hence we've already got all we need. */
-                       entry->type = entry->in_pack_type;
-                       entry->in_pack_header_size = used;
-                       if (entry->type < OBJ_COMMIT || entry->type > OBJ_BLOB)
-                               goto give_up;
-                       unuse_pack(&w_curs);
-                       return;
-               case OBJ_REF_DELTA:
-                       if (reuse_delta && !entry->preferred_base)
-                               base_ref = use_pack(p, &w_curs,
-                                               entry->in_pack_offset + used, NULL);
-                       entry->in_pack_header_size = used + 20;
-                       break;
-               case OBJ_OFS_DELTA:
-                       buf = use_pack(p, &w_curs,
-                                      entry->in_pack_offset + used, NULL);
-                       used_0 = 0;
-                       c = buf[used_0++];
-                       ofs = c & 127;
-                       while (c & 128) {
-                               ofs += 1;
-                               if (!ofs || MSB(ofs, 7)) {
-                                       error("delta base offset overflow in pack for %s",
-                                             sha1_to_hex(entry->idx.sha1));
-                                       goto give_up;
-                               }
-                               c = buf[used_0++];
-                               ofs = (ofs << 7) + (c & 127);
-                       }
-                       ofs = entry->in_pack_offset - ofs;
-                       if (ofs <= 0 || ofs >= entry->in_pack_offset) {
-                               error("delta base offset out of bound for %s",
-                                     sha1_to_hex(entry->idx.sha1));
-                               goto give_up;
-                       }
-                       if (reuse_delta && !entry->preferred_base) {
-                               struct revindex_entry *revidx;
-                               revidx = find_pack_revindex(p, ofs);
-                               if (!revidx)
-                                       goto give_up;
-                               base_ref = nth_packed_object_sha1(p, revidx->nr);
-                       }
-                       entry->in_pack_header_size = used + used_0;
-                       break;
-               }
-
-               if (base_ref && (base_entry = locate_object_entry(base_ref))) {
-                       /*
-                        * If base_ref was set above that means we wish to
-                        * reuse delta data, and we even found that base
-                        * in the list of objects we want to pack. Goodie!
-                        *
-                        * 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.
-                        */
-                       entry->type = entry->in_pack_type;
-                       entry->delta = base_entry;
-                       entry->delta_size = entry->size;
-                       entry->delta_sibling = base_entry->delta_child;
-                       base_entry->delta_child = entry;
-                       unuse_pack(&w_curs);
-                       return;
-               }
-
-               if (entry->type) {
-                       /*
-                        * This must be a delta and we already know what the
-                        * final object type is.  Let's extract the actual
-                        * object size from the delta header.
-                        */
-                       entry->size = get_size_from_delta(p, &w_curs,
-                                       entry->in_pack_offset + entry->in_pack_header_size);
-                       if (entry->size == 0)
-                               goto give_up;
-                       unuse_pack(&w_curs);
-                       return;
-               }
-
-               /*
-                * No choice but to fall back to the recursive delta walk
-                * with sha1_object_info() to find about the object type
-                * at this point...
-                */
-               give_up:
-               unuse_pack(&w_curs);
-       }
-
-       entry->type = sha1_object_info(entry->idx.sha1, &entry->size);
-       /*
-        * The error condition is checked in prepare_pack().  This is
-        * to permit a missing preferred base object to be ignored
-        * as a preferred base.  Doing so can result in a larger
-        * pack file, but the transfer will still take place.
-        */
-}
-
-static int pack_offset_sort(const void *_a, const void *_b)
-{
-       const struct object_entry *a = *(struct object_entry **)_a;
-       const struct object_entry *b = *(struct object_entry **)_b;
-
-       /* avoid filesystem trashing with loose objects */
-       if (!a->in_pack && !b->in_pack)
-               return hashcmp(a->idx.sha1, b->idx.sha1);
-
-       if (a->in_pack < b->in_pack)
-               return -1;
-       if (a->in_pack > b->in_pack)
-               return 1;
-       return a->in_pack_offset < b->in_pack_offset ? -1 :
-                       (a->in_pack_offset > b->in_pack_offset);
-}
-
-static void get_object_details(void)
-{
-       uint32_t i;
-       struct object_entry **sorted_by_offset;
-
-       sorted_by_offset = xcalloc(nr_objects, sizeof(struct object_entry *));
-       for (i = 0; i < nr_objects; i++)
-               sorted_by_offset[i] = objects + i;
-       qsort(sorted_by_offset, nr_objects, sizeof(*sorted_by_offset), pack_offset_sort);
-
-       for (i = 0; i < nr_objects; i++)
-               check_object(sorted_by_offset[i]);
-
-       free(sorted_by_offset);
-}
-
-/*
- * We search for deltas in a list sorted by type, by filename hash, and then
- * 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.  The deepest deltas are therefore the oldest objects which are
- * less susceptible to be accessed often.
- */
-static int type_size_sort(const void *_a, const void *_b)
-{
-       const struct object_entry *a = *(struct object_entry **)_a;
-       const struct object_entry *b = *(struct object_entry **)_b;
-
-       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);  /* newest first */
-}
-
-struct unpacked {
-       struct object_entry *entry;
-       void *data;
-       struct delta_index *index;
-       unsigned depth;
-};
-
-static int delta_cacheable(unsigned long src_size, unsigned long trg_size,
-                          unsigned long delta_size)
-{
-       if (max_delta_cache_size && delta_cache_size + delta_size > max_delta_cache_size)
-               return 0;
-
-       if (delta_size < cache_max_small_delta_size)
-               return 1;
-
-       /* cache delta, if objects are large enough compared to delta size */
-       if ((src_size >> 20) + (trg_size >> 21) > (delta_size >> 10))
-               return 1;
-
-       return 0;
-}
-
-#ifdef THREADED_DELTA_SEARCH
-
-static pthread_mutex_t read_mutex = PTHREAD_MUTEX_INITIALIZER;
-#define read_lock()            pthread_mutex_lock(&read_mutex)
-#define read_unlock()          pthread_mutex_unlock(&read_mutex)
-
-static pthread_mutex_t cache_mutex = PTHREAD_MUTEX_INITIALIZER;
-#define cache_lock()           pthread_mutex_lock(&cache_mutex)
-#define cache_unlock()         pthread_mutex_unlock(&cache_mutex)
-
-static pthread_mutex_t progress_mutex = PTHREAD_MUTEX_INITIALIZER;
-#define progress_lock()                pthread_mutex_lock(&progress_mutex)
-#define progress_unlock()      pthread_mutex_unlock(&progress_mutex)
-
-#else
-
-#define read_lock()            (void)0
-#define read_unlock()          (void)0
-#define cache_lock()           (void)0
-#define cache_unlock()         (void)0
-#define progress_lock()                (void)0
-#define progress_unlock()      (void)0
-
-#endif
-
-static int try_delta(struct unpacked *trg, struct unpacked *src,
-                    unsigned max_depth, unsigned long *mem_usage)
-{
-       struct object_entry *trg_entry = trg->entry;
-       struct object_entry *src_entry = src->entry;
-       unsigned long trg_size, src_size, delta_size, sizediff, max_size, sz;
-       unsigned ref_depth;
-       enum object_type type;
-       void *delta_buf;
-
-       /* Don't bother doing diffs between different types */
-       if (trg_entry->type != src_entry->type)
-               return -1;
-
-       /*
-        * We do not bother to try a delta that we discarded
-        * on an earlier try, but only when reusing delta data.
-        */
-       if (reuse_delta && trg_entry->in_pack &&
-           trg_entry->in_pack == src_entry->in_pack &&
-           trg_entry->in_pack_type != OBJ_REF_DELTA &&
-           trg_entry->in_pack_type != OBJ_OFS_DELTA)
-               return 0;
-
-       /* Let's not bust the allowed depth. */
-       if (src->depth >= max_depth)
-               return 0;
-
-       /* Now some size filtering heuristics. */
-       trg_size = trg_entry->size;
-       if (!trg_entry->delta) {
-               max_size = trg_size/2 - 20;
-               ref_depth = 1;
-       } else {
-               max_size = trg_entry->delta_size;
-               ref_depth = trg->depth;
-       }
-       max_size = (uint64_t)max_size * (max_depth - src->depth) /
-                                               (max_depth - ref_depth + 1);
-       if (max_size == 0)
-               return 0;
-       src_size = src_entry->size;
-       sizediff = src_size < trg_size ? trg_size - src_size : 0;
-       if (sizediff >= max_size)
-               return 0;
-       if (trg_size < src_size / 32)
-               return 0;
-
-       /* Load data if not already done */
-       if (!trg->data) {
-               read_lock();
-               trg->data = read_sha1_file(trg_entry->idx.sha1, &type, &sz);
-               read_unlock();
-               if (!trg->data)
-                       die("object %s cannot be read",
-                           sha1_to_hex(trg_entry->idx.sha1));
-               if (sz != trg_size)
-                       die("object %s inconsistent object length (%lu vs %lu)",
-                           sha1_to_hex(trg_entry->idx.sha1), sz, trg_size);
-               *mem_usage += sz;
-       }
-       if (!src->data) {
-               read_lock();
-               src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz);
-               read_unlock();
-               if (!src->data)
-                       die("object %s cannot be read",
-                           sha1_to_hex(src_entry->idx.sha1));
-               if (sz != src_size)
-                       die("object %s inconsistent object length (%lu vs %lu)",
-                           sha1_to_hex(src_entry->idx.sha1), sz, src_size);
-               *mem_usage += sz;
-       }
-       if (!src->index) {
-               src->index = create_delta_index(src->data, src_size);
-               if (!src->index) {
-                       static int warned = 0;
-                       if (!warned++)
-                               warning("suboptimal pack - out of memory");
-                       return 0;
-               }
-               *mem_usage += sizeof_delta_index(src->index);
-       }
-
-       delta_buf = create_delta(src->index, trg->data, trg_size, &delta_size, max_size);
-       if (!delta_buf)
-               return 0;
-
-       if (trg_entry->delta) {
-               /* Prefer only shallower same-sized deltas. */
-               if (delta_size == trg_entry->delta_size &&
-                   src->depth + 1 >= trg->depth) {
-                       free(delta_buf);
-                       return 0;
-               }
-       }
-
-       /*
-        * Handle memory allocation outside of the cache
-        * accounting lock.  Compiler will optimize the strangeness
-        * away when THREADED_DELTA_SEARCH is not defined.
-        */
-       free(trg_entry->delta_data);
-       cache_lock();
-       if (trg_entry->delta_data) {
-               delta_cache_size -= trg_entry->delta_size;
-               trg_entry->delta_data = NULL;
-       }
-       if (delta_cacheable(src_size, trg_size, delta_size)) {
-               delta_cache_size += delta_size;
-               cache_unlock();
-               trg_entry->delta_data = xrealloc(delta_buf, delta_size);
-       } else {
-               cache_unlock();
-               free(delta_buf);
-       }
-
-       trg_entry->delta = src_entry;
-       trg_entry->delta_size = delta_size;
-       trg->depth = src->depth + 1;
-
-       return 1;
-}
-
-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 unsigned long free_unpacked(struct unpacked *n)
-{
-       unsigned long freed_mem = sizeof_delta_index(n->index);
-       free_delta_index(n->index);
-       n->index = NULL;
-       if (n->data) {
-               freed_mem += n->entry->size;
-               free(n->data);
-               n->data = NULL;
-       }
-       n->entry = NULL;
-       n->depth = 0;
-       return freed_mem;
-}
-
-static void find_deltas(struct object_entry **list, unsigned *list_size,
-                       int window, int depth, unsigned *processed)
-{
-       uint32_t i, idx = 0, count = 0;
-       struct unpacked *array;
-       unsigned long mem_usage = 0;
-
-       array = xcalloc(window, sizeof(struct unpacked));
-
-       for (;;) {
-               struct object_entry *entry;
-               struct unpacked *n = array + idx;
-               int j, max_depth, best_base = -1;
-
-               progress_lock();
-               if (!*list_size) {
-                       progress_unlock();
-                       break;
-               }
-               entry = *list++;
-               (*list_size)--;
-               if (!entry->preferred_base) {
-                       (*processed)++;
-                       display_progress(progress_state, *processed);
-               }
-               progress_unlock();
-
-               mem_usage -= free_unpacked(n);
-               n->entry = entry;
-
-               while (window_memory_limit &&
-                      mem_usage > window_memory_limit &&
-                      count > 1) {
-                       uint32_t tail = (idx + window - count) % window;
-                       mem_usage -= free_unpacked(array + tail);
-                       count--;
-               }
-
-               /* We do not compute delta to *create* objects we are not
-                * going to pack.
-                */
-               if (entry->preferred_base)
-                       goto next;
-
-               /*
-                * 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.
-                */
-               max_depth = depth;
-               if (entry->delta_child) {
-                       max_depth -= check_delta_limit(entry, 0);
-                       if (max_depth <= 0)
-                               goto next;
-               }
-
-               j = window;
-               while (--j > 0) {
-                       int ret;
-                       uint32_t other_idx = idx + j;
-                       struct unpacked *m;
-                       if (other_idx >= window)
-                               other_idx -= window;
-                       m = array + other_idx;
-                       if (!m->entry)
-                               break;
-                       ret = try_delta(n, m, max_depth, &mem_usage);
-                       if (ret < 0)
-                               break;
-                       else if (ret > 0)
-                               best_base = other_idx;
-               }
-
-               /*
-                * If we decided to cache the delta data, then it is best
-                * to compress it right away.  First because we have to do
-                * it anyway, and doing it here while we're threaded will
-                * save a lot of time in the non threaded write phase,
-                * as well as allow for caching more deltas within
-                * the same cache size limit.
-                * ...
-                * But only if not writing to stdout, since in that case
-                * the network is most likely throttling writes anyway,
-                * and therefore it is best to go to the write phase ASAP
-                * instead, as we can afford spending more time compressing
-                * between writes at that moment.
-                */
-               if (entry->delta_data && !pack_to_stdout) {
-                       entry->z_delta_size = do_compress(&entry->delta_data,
-                                                         entry->delta_size);
-                       cache_lock();
-                       delta_cache_size -= entry->delta_size;
-                       delta_cache_size += entry->z_delta_size;
-                       cache_unlock();
-               }
-
-               /* 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 && max_depth <= n->depth)
-                       continue;
-
-               /*
-                * Move the best delta base up in the window, after the
-                * currently deltified object, to keep it longer.  It will
-                * be the first base object to be attempted next.
-                */
-               if (entry->delta) {
-                       struct unpacked swap = array[best_base];
-                       int dist = (window + idx - best_base) % window;
-                       int dst = best_base;
-                       while (dist--) {
-                               int src = (dst + 1) % window;
-                               array[dst] = array[src];
-                               dst = src;
-                       }
-                       array[dst] = swap;
-               }
-
-               next:
-               idx++;
-               if (count + 1 < window)
-                       count++;
-               if (idx >= window)
-                       idx = 0;
-       }
-
-       for (i = 0; i < window; ++i) {
-               free_delta_index(array[i].index);
-               free(array[i].data);
-       }
-       free(array);
-}
-
-#ifdef THREADED_DELTA_SEARCH
-
-/*
- * The main thread waits on the condition that (at least) one of the workers
- * has stopped working (which is indicated in the .working member of
- * struct thread_params).
- * When a work thread has completed its work, it sets .working to 0 and
- * signals the main thread and waits on the condition that .data_ready
- * becomes 1.
- */
-
-struct thread_params {
-       pthread_t thread;
-       struct object_entry **list;
-       unsigned list_size;
-       unsigned remaining;
-       int window;
-       int depth;
-       int working;
-       int data_ready;
-       pthread_mutex_t mutex;
-       pthread_cond_t cond;
-       unsigned *processed;
-};
-
-static pthread_cond_t progress_cond = PTHREAD_COND_INITIALIZER;
-
-static void *threaded_find_deltas(void *arg)
-{
-       struct thread_params *me = arg;
-
-       while (me->remaining) {
-               find_deltas(me->list, &me->remaining,
-                           me->window, me->depth, me->processed);
-
-               progress_lock();
-               me->working = 0;
-               pthread_cond_signal(&progress_cond);
-               progress_unlock();
-
-               /*
-                * We must not set ->data_ready before we wait on the
-                * condition because the main thread may have set it to 1
-                * before we get here. In order to be sure that new
-                * work is available if we see 1 in ->data_ready, it
-                * was initialized to 0 before this thread was spawned
-                * and we reset it to 0 right away.
-                */
-               pthread_mutex_lock(&me->mutex);
-               while (!me->data_ready)
-                       pthread_cond_wait(&me->cond, &me->mutex);
-               me->data_ready = 0;
-               pthread_mutex_unlock(&me->mutex);
-       }
-       /* leave ->working 1 so that this doesn't get more work assigned */
-       return NULL;
-}
-
-static void ll_find_deltas(struct object_entry **list, unsigned list_size,
-                          int window, int depth, unsigned *processed)
-{
-       struct thread_params p[delta_search_threads];
-       int i, ret, active_threads = 0;
-
-       if (delta_search_threads <= 1) {
-               find_deltas(list, &list_size, window, depth, processed);
-               return;
-       }
-       if (progress > pack_to_stdout)
-               fprintf(stderr, "Delta compression using up to %d threads.\n",
-                               delta_search_threads);
-
-       /* Partition the work amongst work threads. */
-       for (i = 0; i < delta_search_threads; i++) {
-               unsigned sub_size = list_size / (delta_search_threads - i);
-
-               /* don't use too small segments or no deltas will be found */
-               if (sub_size < 2*window && i+1 < delta_search_threads)
-                       sub_size = 0;
-
-               p[i].window = window;
-               p[i].depth = depth;
-               p[i].processed = processed;
-               p[i].working = 1;
-               p[i].data_ready = 0;
-
-               /* try to split chunks on "path" boundaries */
-               while (sub_size && sub_size < list_size &&
-                      list[sub_size]->hash &&
-                      list[sub_size]->hash == list[sub_size-1]->hash)
-                       sub_size++;
-
-               p[i].list = list;
-               p[i].list_size = sub_size;
-               p[i].remaining = sub_size;
-
-               list += sub_size;
-               list_size -= sub_size;
-       }
-
-       /* Start work threads. */
-       for (i = 0; i < delta_search_threads; i++) {
-               if (!p[i].list_size)
-                       continue;
-               pthread_mutex_init(&p[i].mutex, NULL);
-               pthread_cond_init(&p[i].cond, NULL);
-               ret = pthread_create(&p[i].thread, NULL,
-                                    threaded_find_deltas, &p[i]);
-               if (ret)
-                       die("unable to create thread: %s", strerror(ret));
-               active_threads++;
-       }
-
-       /*
-        * Now let's wait for work completion.  Each time a thread is done
-        * with its work, we steal half of the remaining work from the
-        * thread with the largest number of unprocessed objects and give
-        * it to that newly idle thread.  This ensure good load balancing
-        * until the remaining object list segments are simply too short
-        * to be worth splitting anymore.
-        */
-       while (active_threads) {
-               struct thread_params *target = NULL;
-               struct thread_params *victim = NULL;
-               unsigned sub_size = 0;
-
-               progress_lock();
-               for (;;) {
-                       for (i = 0; !target && i < delta_search_threads; i++)
-                               if (!p[i].working)
-                                       target = &p[i];
-                       if (target)
-                               break;
-                       pthread_cond_wait(&progress_cond, &progress_mutex);
-               }
-
-               for (i = 0; i < delta_search_threads; i++)
-                       if (p[i].remaining > 2*window &&
-                           (!victim || victim->remaining < p[i].remaining))
-                               victim = &p[i];
-               if (victim) {
-                       sub_size = victim->remaining / 2;
-                       list = victim->list + victim->list_size - sub_size;
-                       while (sub_size && list[0]->hash &&
-                              list[0]->hash == list[-1]->hash) {
-                               list++;
-                               sub_size--;
-                       }
-                       if (!sub_size) {
-                               /*
-                                * It is possible for some "paths" to have
-                                * so many objects that no hash boundary
-                                * might be found.  Let's just steal the
-                                * exact half in that case.
-                                */
-                               sub_size = victim->remaining / 2;
-                               list -= sub_size;
-                       }
-                       target->list = list;
-                       victim->list_size -= sub_size;
-                       victim->remaining -= sub_size;
-               }
-               target->list_size = sub_size;
-               target->remaining = sub_size;
-               target->working = 1;
-               progress_unlock();
-
-               pthread_mutex_lock(&target->mutex);
-               target->data_ready = 1;
-               pthread_cond_signal(&target->cond);
-               pthread_mutex_unlock(&target->mutex);
-
-               if (!sub_size) {
-                       pthread_join(target->thread, NULL);
-                       pthread_cond_destroy(&target->cond);
-                       pthread_mutex_destroy(&target->mutex);
-                       active_threads--;
-               }
-       }
-}
-
-#else
-#define ll_find_deltas(l, s, w, d, p)  find_deltas(l, &s, w, d, p)
-#endif
-
-static int add_ref_tag(const char *path, const unsigned char *sha1, int flag, void *cb_data)
-{
-       unsigned char peeled[20];
-
-       if (!prefixcmp(path, "refs/tags/") && /* is a tag? */
-           !peel_ref(path, peeled)        && /* peelable? */
-           !is_null_sha1(peeled)          && /* annotated tag? */
-           locate_object_entry(peeled))      /* object packed? */
-               add_object_entry(sha1, OBJ_TAG, NULL, 0);
-       return 0;
-}
-
-static void prepare_pack(int window, int depth)
-{
-       struct object_entry **delta_list;
-       uint32_t i, nr_deltas;
-       unsigned n;
-
-       get_object_details();
-
-       /*
-        * If we're locally repacking then we need to be doubly careful
-        * from now on in order to make sure no stealth corruption gets
-        * propagated to the new pack.  Clients receiving streamed packs
-        * should validate everything they get anyway so no need to incur
-        * the additional cost here in that case.
-        */
-       if (!pack_to_stdout)
-               do_check_packed_object_crc = 1;
-
-       if (!nr_objects || !window || !depth)
-               return;
-
-       delta_list = xmalloc(nr_objects * sizeof(*delta_list));
-       nr_deltas = n = 0;
-
-       for (i = 0; i < nr_objects; i++) {
-               struct object_entry *entry = objects + i;
-
-               if (entry->delta)
-                       /* This happens if we decided to reuse existing
-                        * delta from a pack.  "reuse_delta &&" is implied.
-                        */
-                       continue;
-
-               if (entry->size < 50)
-                       continue;
-
-               if (entry->no_try_delta)
-                       continue;
-
-               if (!entry->preferred_base) {
-                       nr_deltas++;
-                       if (entry->type < 0)
-                               die("unable to get type of object %s",
-                                   sha1_to_hex(entry->idx.sha1));
-               } else {
-                       if (entry->type < 0) {
-                               /*
-                                * This object is not found, but we
-                                * don't have to include it anyway.
-                                */
-                               continue;
-                       }
-               }
-
-               delta_list[n++] = entry;
-       }
-
-       if (nr_deltas && n > 1) {
-               unsigned nr_done = 0;
-               if (progress)
-                       progress_state = start_progress("Compressing objects",
-                                                       nr_deltas);
-               qsort(delta_list, n, sizeof(*delta_list), type_size_sort);
-               ll_find_deltas(delta_list, n, window+1, depth, &nr_done);
-               stop_progress(&progress_state);
-               if (nr_done != nr_deltas)
-                       die("inconsistency with delta count");
-       }
-       free(delta_list);
-}
-
-static int git_pack_config(const char *k, const char *v, void *cb)
-{
-       if(!strcmp(k, "pack.window")) {
-               window = git_config_int(k, v);
-               return 0;
-       }
-       if (!strcmp(k, "pack.windowmemory")) {
-               window_memory_limit = git_config_ulong(k, v);
-               return 0;
-       }
-       if (!strcmp(k, "pack.depth")) {
-               depth = git_config_int(k, v);
-               return 0;
-       }
-       if (!strcmp(k, "pack.compression")) {
-               int level = git_config_int(k, v);
-               if (level == -1)
-                       level = Z_DEFAULT_COMPRESSION;
-               else if (level < 0 || level > Z_BEST_COMPRESSION)
-                       die("bad pack compression level %d", level);
-               pack_compression_level = level;
-               pack_compression_seen = 1;
-               return 0;
-       }
-       if (!strcmp(k, "pack.deltacachesize")) {
-               max_delta_cache_size = git_config_int(k, v);
-               return 0;
-       }
-       if (!strcmp(k, "pack.deltacachelimit")) {
-               cache_max_small_delta_size = git_config_int(k, v);
-               return 0;
-       }
-       if (!strcmp(k, "pack.threads")) {
-               delta_search_threads = git_config_int(k, v);
-               if (delta_search_threads < 0)
-                       die("invalid number of threads specified (%d)",
-                           delta_search_threads);
-#ifndef THREADED_DELTA_SEARCH
-               if (delta_search_threads != 1)
-                       warning("no threads support, ignoring %s", k);
-#endif
-               return 0;
-       }
-       if (!strcmp(k, "pack.indexversion")) {
-               pack_idx_default_version = git_config_int(k, v);
-               if (pack_idx_default_version > 2)
-                       die("bad pack.indexversion=%"PRIu32,
-                               pack_idx_default_version);
-               return 0;
-       }
-       if (!strcmp(k, "pack.packsizelimit")) {
-               pack_size_limit_cfg = git_config_ulong(k, v);
-               return 0;
-       }
-       return git_default_config(k, v, cb);
-}
-
-static void read_object_list_from_stdin(void)
-{
-       char line[40 + 1 + PATH_MAX + 2];
-       unsigned char sha1[20];
-
-       for (;;) {
-               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);
-                       add_preferred_base(sha1);
-                       continue;
-               }
-               if (get_sha1_hex(line, sha1))
-                       die("expected sha1, got garbage:\n %s", line);
-
-               add_preferred_base_object(line+41);
-               add_object_entry(sha1, 0, line+41, 0);
-       }
-}
-
-#define OBJECT_ADDED (1u<<20)
-
-static void show_commit(struct commit *commit, void *data)
-{
-       add_object_entry(commit->object.sha1, OBJ_COMMIT, NULL, 0);
-       commit->object.flags |= OBJECT_ADDED;
-}
-
-static void show_object(struct object *obj, const struct name_path *path, const char *last)
-{
-       char *name = path_name(path, last);
-
-       add_preferred_base_object(name);
-       add_object_entry(obj->sha1, obj->type, name, 0);
-       obj->flags |= OBJECT_ADDED;
-
-       /*
-        * We will have generated the hash from the name,
-        * but not saved a pointer to it - we can free it
-        */
-       free((char *)name);
-}
-
-static void show_edge(struct commit *commit)
-{
-       add_preferred_base(commit->object.sha1);
-}
-
-struct in_pack_object {
-       off_t offset;
-       struct object *object;
-};
-
-struct in_pack {
-       int alloc;
-       int nr;
-       struct in_pack_object *array;
-};
-
-static void mark_in_pack_object(struct object *object, struct packed_git *p, struct in_pack *in_pack)
-{
-       in_pack->array[in_pack->nr].offset = find_pack_entry_one(object->sha1, p);
-       in_pack->array[in_pack->nr].object = object;
-       in_pack->nr++;
-}
-
-/*
- * Compare the objects in the offset order, in order to emulate the
- * "git rev-list --objects" output that produced the pack originally.
- */
-static int ofscmp(const void *a_, const void *b_)
-{
-       struct in_pack_object *a = (struct in_pack_object *)a_;
-       struct in_pack_object *b = (struct in_pack_object *)b_;
-
-       if (a->offset < b->offset)
-               return -1;
-       else if (a->offset > b->offset)
-               return 1;
-       else
-               return hashcmp(a->object->sha1, b->object->sha1);
-}
-
-static void add_objects_in_unpacked_packs(struct rev_info *revs)
-{
-       struct packed_git *p;
-       struct in_pack in_pack;
-       uint32_t i;
-
-       memset(&in_pack, 0, sizeof(in_pack));
-
-       for (p = packed_git; p; p = p->next) {
-               const unsigned char *sha1;
-               struct object *o;
-
-               if (!p->pack_local || p->pack_keep)
-                       continue;
-               if (open_pack_index(p))
-                       die("cannot open pack index");
-
-               ALLOC_GROW(in_pack.array,
-                          in_pack.nr + p->num_objects,
-                          in_pack.alloc);
-
-               for (i = 0; i < p->num_objects; i++) {
-                       sha1 = nth_packed_object_sha1(p, i);
-                       o = lookup_unknown_object(sha1);
-                       if (!(o->flags & OBJECT_ADDED))
-                               mark_in_pack_object(o, p, &in_pack);
-                       o->flags |= OBJECT_ADDED;
-               }
-       }
-
-       if (in_pack.nr) {
-               qsort(in_pack.array, in_pack.nr, sizeof(in_pack.array[0]),
-                     ofscmp);
-               for (i = 0; i < in_pack.nr; i++) {
-                       struct object *o = in_pack.array[i].object;
-                       add_object_entry(o->sha1, o->type, "", 0);
-               }
-       }
-       free(in_pack.array);
-}
-
-static int has_sha1_pack_kept_or_nonlocal(const unsigned char *sha1)
-{
-       static struct packed_git *last_found = (void *)1;
-       struct packed_git *p;
-
-       p = (last_found != (void *)1) ? last_found : packed_git;
-
-       while (p) {
-               if ((!p->pack_local || p->pack_keep) &&
-                       find_pack_entry_one(sha1, p)) {
-                       last_found = p;
-                       return 1;
-               }
-               if (p == last_found)
-                       p = packed_git;
-               else
-                       p = p->next;
-               if (p == last_found)
-                       p = p->next;
-       }
-       return 0;
-}
-
-static void loosen_unused_packed_objects(struct rev_info *revs)
-{
-       struct packed_git *p;
-       uint32_t i;
-       const unsigned char *sha1;
-
-       for (p = packed_git; p; p = p->next) {
-               if (!p->pack_local || p->pack_keep)
-                       continue;
-
-               if (open_pack_index(p))
-                       die("cannot open pack index");
-
-               for (i = 0; i < p->num_objects; i++) {
-                       sha1 = nth_packed_object_sha1(p, i);
-                       if (!locate_object_entry(sha1) &&
-                               !has_sha1_pack_kept_or_nonlocal(sha1))
-                               if (force_object_loose(sha1, p->mtime))
-                                       die("unable to force loose object");
-               }
-       }
-}
-
-static void get_object_list(int ac, const char **av)
-{
-       struct rev_info revs;
-       char line[1000];
-       int flags = 0;
-
-       init_revisions(&revs, NULL);
-       save_commit_buffer = 0;
-       setup_revisions(ac, av, &revs, NULL);
-
-       while (fgets(line, sizeof(line), stdin) != NULL) {
-               int len = strlen(line);
-               if (len && line[len - 1] == '\n')
-                       line[--len] = 0;
-               if (!len)
-                       break;
-               if (*line == '-') {
-                       if (!strcmp(line, "--not")) {
-                               flags ^= UNINTERESTING;
-                               continue;
-                       }
-                       die("not a rev '%s'", line);
-               }
-               if (handle_revision_arg(line, &revs, flags, 1))
-                       die("bad revision '%s'", line);
-       }
-
-       if (prepare_revision_walk(&revs))
-               die("revision walk setup failed");
-       mark_edges_uninteresting(revs.commits, &revs, show_edge);
-       traverse_commit_list(&revs, show_commit, show_object, NULL);
-
-       if (keep_unreachable)
-               add_objects_in_unpacked_packs(&revs);
-       if (unpack_unreachable)
-               loosen_unused_packed_objects(&revs);
-}
-
-static int adjust_perm(const char *path, mode_t mode)
-{
-       if (chmod(path, mode))
-               return -1;
-       return adjust_shared_perm(path);
-}
-
-int cmd_pack_objects(int argc, const char **argv, const char *prefix)
-{
-       int use_internal_rev_list = 0;
-       int thin = 0;
-       uint32_t i;
-       const char **rp_av;
-       int rp_ac_alloc = 64;
-       int rp_ac;
-
-       rp_av = xcalloc(rp_ac_alloc, sizeof(*rp_av));
-
-       rp_av[0] = "pack-objects";
-       rp_av[1] = "--objects"; /* --thin will make it --objects-edge */
-       rp_ac = 2;
-
-       git_config(git_pack_config, NULL);
-       if (!pack_compression_seen && core_compression_seen)
-               pack_compression_level = core_compression_level;
-
-       progress = isatty(2);
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-
-               if (*arg != '-')
-                       break;
-
-               if (!strcmp("--non-empty", arg)) {
-                       non_empty = 1;
-                       continue;
-               }
-               if (!strcmp("--local", arg)) {
-                       local = 1;
-                       continue;
-               }
-               if (!strcmp("--incremental", arg)) {
-                       incremental = 1;
-                       continue;
-               }
-               if (!strcmp("--honor-pack-keep", arg)) {
-                       ignore_packed_keep = 1;
-                       continue;
-               }
-               if (!prefixcmp(arg, "--compression=")) {
-                       char *end;
-                       int level = strtoul(arg+14, &end, 0);
-                       if (!arg[14] || *end)
-                               usage(pack_usage);
-                       if (level == -1)
-                               level = Z_DEFAULT_COMPRESSION;
-                       else if (level < 0 || level > Z_BEST_COMPRESSION)
-                               die("bad pack compression level %d", level);
-                       pack_compression_level = level;
-                       continue;
-               }
-               if (!prefixcmp(arg, "--max-pack-size=")) {
-                       char *end;
-                       pack_size_limit_cfg = 0;
-                       pack_size_limit = strtoul(arg+16, &end, 0) * 1024 * 1024;
-                       if (!arg[16] || *end)
-                               usage(pack_usage);
-                       continue;
-               }
-               if (!prefixcmp(arg, "--window=")) {
-                       char *end;
-                       window = strtoul(arg+9, &end, 0);
-                       if (!arg[9] || *end)
-                               usage(pack_usage);
-                       continue;
-               }
-               if (!prefixcmp(arg, "--window-memory=")) {
-                       if (!git_parse_ulong(arg+16, &window_memory_limit))
-                               usage(pack_usage);
-                       continue;
-               }
-               if (!prefixcmp(arg, "--threads=")) {
-                       char *end;
-                       delta_search_threads = strtoul(arg+10, &end, 0);
-                       if (!arg[10] || *end || delta_search_threads < 0)
-                               usage(pack_usage);
-#ifndef THREADED_DELTA_SEARCH
-                       if (delta_search_threads != 1)
-                               warning("no threads support, "
-                                       "ignoring %s", arg);
-#endif
-                       continue;
-               }
-               if (!prefixcmp(arg, "--depth=")) {
-                       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("--all-progress", arg)) {
-                       progress = 2;
-                       continue;
-               }
-               if (!strcmp("-q", arg)) {
-                       progress = 0;
-                       continue;
-               }
-               if (!strcmp("--no-reuse-delta", arg)) {
-                       reuse_delta = 0;
-                       continue;
-               }
-               if (!strcmp("--no-reuse-object", arg)) {
-                       reuse_object = reuse_delta = 0;
-                       continue;
-               }
-               if (!strcmp("--delta-base-offset", arg)) {
-                       allow_ofs_delta = 1;
-                       continue;
-               }
-               if (!strcmp("--stdout", arg)) {
-                       pack_to_stdout = 1;
-                       continue;
-               }
-               if (!strcmp("--revs", arg)) {
-                       use_internal_rev_list = 1;
-                       continue;
-               }
-               if (!strcmp("--keep-unreachable", arg)) {
-                       keep_unreachable = 1;
-                       continue;
-               }
-               if (!strcmp("--unpack-unreachable", arg)) {
-                       unpack_unreachable = 1;
-                       continue;
-               }
-               if (!strcmp("--include-tag", arg)) {
-                       include_tag = 1;
-                       continue;
-               }
-               if (!strcmp("--unpacked", arg) ||
-                   !strcmp("--reflog", arg) ||
-                   !strcmp("--all", arg)) {
-                       use_internal_rev_list = 1;
-                       if (rp_ac >= rp_ac_alloc - 1) {
-                               rp_ac_alloc = alloc_nr(rp_ac_alloc);
-                               rp_av = xrealloc(rp_av,
-                                                rp_ac_alloc * sizeof(*rp_av));
-                       }
-                       rp_av[rp_ac++] = arg;
-                       continue;
-               }
-               if (!strcmp("--thin", arg)) {
-                       use_internal_rev_list = 1;
-                       thin = 1;
-                       rp_av[1] = "--objects-edge";
-                       continue;
-               }
-               if (!prefixcmp(arg, "--index-version=")) {
-                       char *c;
-                       pack_idx_default_version = strtoul(arg + 16, &c, 10);
-                       if (pack_idx_default_version > 2)
-                               die("bad %s", arg);
-                       if (*c == ',')
-                               pack_idx_off32_limit = strtoul(c+1, &c, 0);
-                       if (*c || pack_idx_off32_limit & 0x80000000)
-                               die("bad %s", arg);
-                       continue;
-               }
-               usage(pack_usage);
-       }
-
-       /* Traditionally "pack-objects [options] base extra" failed;
-        * we would however want to take refs parameter that would
-        * have been given to upstream rev-list ourselves, which means
-        * we somehow want to say what the base name is.  So the
-        * syntax would be:
-        *
-        * pack-objects [options] base <refs...>
-        *
-        * in other words, we would treat the first non-option as the
-        * base_name and send everything else to the internal revision
-        * walker.
-        */
-
-       if (!pack_to_stdout)
-               base_name = argv[i++];
-
-       if (pack_to_stdout != !base_name)
-               usage(pack_usage);
-
-       if (!pack_to_stdout && !pack_size_limit)
-               pack_size_limit = pack_size_limit_cfg;
-
-       if (pack_to_stdout && pack_size_limit)
-               die("--max-pack-size cannot be used to build a pack for transfer.");
-
-       if (!pack_to_stdout && thin)
-               die("--thin cannot be used to build an indexable pack.");
-
-       if (keep_unreachable && unpack_unreachable)
-               die("--keep-unreachable and --unpack-unreachable are incompatible.");
-
-#ifdef THREADED_DELTA_SEARCH
-       if (!delta_search_threads)      /* --threads=0 means autodetect */
-               delta_search_threads = online_cpus();
-#endif
-
-       prepare_packed_git();
-
-       if (progress)
-               progress_state = start_progress("Counting objects", 0);
-       if (!use_internal_rev_list)
-               read_object_list_from_stdin();
-       else {
-               rp_av[rp_ac] = NULL;
-               get_object_list(rp_ac, rp_av);
-       }
-       if (include_tag && nr_result)
-               for_each_ref(add_ref_tag, NULL);
-       stop_progress(&progress_state);
-
-       if (non_empty && !nr_result)
-               return 0;
-       if (nr_result)
-               prepare_pack(window, depth);
-       write_pack_file();
-       if (progress)
-               fprintf(stderr, "Total %"PRIu32" (delta %"PRIu32"),"
-                       " reused %"PRIu32" (delta %"PRIu32")\n",
-                       written, written_delta, reused, reused_delta);
-       return 0;
-}
diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c
deleted file mode 100644 (file)
index 34246df..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-#include "cache.h"
-#include "parse-options.h"
-#include "pack-refs.h"
-
-static char const * const pack_refs_usage[] = {
-       "git pack-refs [options]",
-       NULL
-};
-
-int cmd_pack_refs(int argc, const char **argv, const char *prefix)
-{
-       unsigned int flags = PACK_REFS_PRUNE;
-       struct option opts[] = {
-               OPT_BIT(0, "all",   &flags, "pack everything", PACK_REFS_ALL),
-               OPT_BIT(0, "prune", &flags, "prune loose refs (default)", PACK_REFS_PRUNE),
-               OPT_END(),
-       };
-       if (parse_options(argc, argv, opts, pack_refs_usage, 0))
-               usage_with_options(pack_refs_usage, opts);
-       return pack_refs(flags);
-}
diff --git a/builtin-prune-packed.c b/builtin-prune-packed.c
deleted file mode 100644 (file)
index 4942892..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-#include "builtin.h"
-#include "cache.h"
-#include "progress.h"
-
-static const char prune_packed_usage[] =
-"git prune-packed [-n] [-q]";
-
-#define DRY_RUN 01
-#define VERBOSE 02
-
-static struct progress *progress;
-
-static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts)
-{
-       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 (opts & DRY_RUN)
-                       printf("rm -f %s\n", pathname);
-               else if (unlink(pathname) < 0)
-                       error("unable to unlink %s", pathname);
-               display_progress(progress, i + 1);
-       }
-       pathname[len] = 0;
-       rmdir(pathname);
-}
-
-void prune_packed_objects(int opts)
-{
-       int i;
-       static char pathname[PATH_MAX];
-       const char *dir = get_object_directory();
-       int len = strlen(dir);
-
-       if (opts == VERBOSE)
-               progress = start_progress_delay("Removing duplicate objects",
-                       256, 95, 2);
-
-       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;
-
-               display_progress(progress, i + 1);
-               sprintf(pathname + len, "%02x/", i);
-               d = opendir(pathname);
-               if (!d)
-                       continue;
-               prune_dir(i, d, pathname, len + 3, opts);
-               closedir(d);
-       }
-       stop_progress(&progress);
-}
-
-int cmd_prune_packed(int argc, const char **argv, const char *prefix)
-{
-       int i;
-       int opts = VERBOSE;
-
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-
-               if (*arg == '-') {
-                       if (!strcmp(arg, "-n"))
-                               opts |= DRY_RUN;
-                       else if (!strcmp(arg, "-q"))
-                               opts &= ~VERBOSE;
-                       else
-                               usage(prune_packed_usage);
-                       continue;
-               }
-               /* Handle arguments here .. */
-               usage(prune_packed_usage);
-       }
-       prune_packed_objects(opts);
-       return 0;
-}
diff --git a/builtin-prune.c b/builtin-prune.c
deleted file mode 100644 (file)
index 545e9c1..0000000
+++ /dev/null
@@ -1,168 +0,0 @@
-#include "cache.h"
-#include "commit.h"
-#include "diff.h"
-#include "revision.h"
-#include "builtin.h"
-#include "reachable.h"
-#include "parse-options.h"
-#include "dir.h"
-
-static const char * const prune_usage[] = {
-       "git prune [-n] [-v] [--expire <time>] [--] [<head>...]",
-       NULL
-};
-static int show_only;
-static int verbose;
-static unsigned long expire;
-
-static int prune_tmp_object(const char *path, const char *filename)
-{
-       const char *fullpath = mkpath("%s/%s", path, filename);
-       if (expire) {
-               struct stat st;
-               if (lstat(fullpath, &st))
-                       return error("Could not stat '%s'", fullpath);
-               if (st.st_mtime > expire)
-                       return 0;
-       }
-       printf("Removing stale temporary file %s\n", fullpath);
-       if (!show_only)
-               unlink(fullpath);
-       return 0;
-}
-
-static int prune_object(char *path, const char *filename, const unsigned char *sha1)
-{
-       const char *fullpath = mkpath("%s/%s", path, filename);
-       if (expire) {
-               struct stat st;
-               if (lstat(fullpath, &st))
-                       return error("Could not stat '%s'", fullpath);
-               if (st.st_mtime > expire)
-                       return 0;
-       }
-       if (show_only || verbose) {
-               enum object_type type = sha1_object_info(sha1, NULL);
-               printf("%s %s\n", sha1_to_hex(sha1),
-                      (type > 0) ? typename(type) : "unknown");
-       }
-       if (!show_only)
-               unlink(fullpath);
-       return 0;
-}
-
-static int prune_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];
-
-               if (is_dot_or_dotdot(de->d_name))
-                       continue;
-               if (strlen(de->d_name) == 38) {
-                       sprintf(name, "%02x", i);
-                       memcpy(name+2, de->d_name, 39);
-                       if (get_sha1_hex(name, sha1) < 0)
-                               break;
-
-                       /*
-                        * Do we know about this object?
-                        * It must have been reachable
-                        */
-                       if (lookup_object(sha1))
-                               continue;
-
-                       prune_object(path, de->d_name, sha1);
-                       continue;
-               }
-               if (!prefixcmp(de->d_name, "tmp_obj_")) {
-                       prune_tmp_object(path, de->d_name);
-                       continue;
-               }
-               fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
-       }
-       if (!show_only)
-               rmdir(path);
-       closedir(dir);
-       return 0;
-}
-
-static void prune_object_dir(const char *path)
-{
-       int i;
-       for (i = 0; i < 256; i++) {
-               static char dir[4096];
-               sprintf(dir, "%s/%02x", path, i);
-               prune_dir(i, dir);
-       }
-}
-
-/*
- * Write errors (particularly out of space) can result in
- * failed temporary packs (and more rarely indexes and other
- * files begining with "tmp_") accumulating in the object
- * and the pack directories.
- */
-static void remove_temporary_files(const char *path)
-{
-       DIR *dir;
-       struct dirent *de;
-
-       dir = opendir(path);
-       if (!dir) {
-               fprintf(stderr, "Unable to open directory %s\n", path);
-               return;
-       }
-       while ((de = readdir(dir)) != NULL)
-               if (!prefixcmp(de->d_name, "tmp_"))
-                       prune_tmp_object(path, de->d_name);
-       closedir(dir);
-}
-
-int cmd_prune(int argc, const char **argv, const char *prefix)
-{
-       struct rev_info revs;
-       const struct option options[] = {
-               OPT_BOOLEAN('n', NULL, &show_only,
-                           "do not remove, show only"),
-               OPT_BOOLEAN('v', NULL, &verbose,
-                       "report pruned objects"),
-               OPT_DATE(0, "expire", &expire,
-                        "expire objects older than <time>"),
-               OPT_END()
-       };
-       char *s;
-
-       save_commit_buffer = 0;
-       init_revisions(&revs, prefix);
-
-       argc = parse_options(argc, argv, options, prune_usage, 0);
-       while (argc--) {
-               unsigned char sha1[20];
-               const char *name = *argv++;
-
-               if (!get_sha1(name, sha1)) {
-                       struct object *object = parse_object(sha1);
-                       if (!object)
-                               die("bad object: %s", name);
-                       add_pending_object(&revs, object, "");
-               }
-               else
-                       die("unrecognized argument: %s", name);
-       }
-       mark_reachable_objects(&revs, 1);
-       prune_object_dir(get_object_directory());
-
-       prune_packed_objects(show_only);
-       remove_temporary_files(get_object_directory());
-       s = xstrdup(mkpath("%s/pack", get_object_directory()));
-       remove_temporary_files(s);
-       free(s);
-       return 0;
-}
diff --git a/builtin-push.c b/builtin-push.c
deleted file mode 100644 (file)
index 2eabcd3..0000000
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * "git push"
- */
-#include "cache.h"
-#include "refs.h"
-#include "run-command.h"
-#include "builtin.h"
-#include "remote.h"
-#include "transport.h"
-#include "parse-options.h"
-
-static const char * const push_usage[] = {
-       "git push [--all | --mirror] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>] [--repo=<repository>] [-f | --force] [-v] [<repository> <refspec>...]",
-       NULL,
-};
-
-static int thin;
-static const char *receivepack;
-
-static const char **refspec;
-static int refspec_nr;
-
-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 void set_refspecs(const char **refs, int nr)
-{
-       int i;
-       for (i = 0; i < nr; i++) {
-               const char *ref = refs[i];
-               if (!strcmp("tag", ref)) {
-                       char *tag;
-                       int len;
-                       if (nr <= ++i)
-                               die("tag shorthand without <tag>");
-                       len = strlen(refs[i]) + 11;
-                       tag = xmalloc(len);
-                       strcpy(tag, "refs/tags/");
-                       strcat(tag, refs[i]);
-                       ref = tag;
-               }
-               add_refspec(ref);
-       }
-}
-
-static void setup_push_tracking(void)
-{
-       struct strbuf refspec = STRBUF_INIT;
-       struct branch *branch = branch_get(NULL);
-       if (!branch)
-               die("You are not currently on a branch.");
-       if (!branch->merge_nr)
-               die("The current branch %s is not tracking anything.",
-                   branch->name);
-       if (branch->merge_nr != 1)
-               die("The current branch %s is tracking multiple branches, "
-                   "refusing to push.", branch->name);
-       strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src);
-       add_refspec(refspec.buf);
-}
-
-static const char *warn_unconfigured_push_msg[] = {
-       "You did not specify any refspecs to push, and the current remote",
-       "has not configured any push refspecs. The default action in this",
-       "case is to push all matching refspecs, that is, all branches",
-       "that exist both locally and remotely will be updated.  This may",
-       "not necessarily be what you want to happen.",
-       "",
-       "You can specify what action you want to take in this case, and",
-       "avoid seeing this message again, by configuring 'push.default' to:",
-       "  'nothing'  : Do not push anything",
-       "  'matching' : Push all matching branches (default)",
-       "  'tracking' : Push the current branch to whatever it is tracking",
-       "  'current'  : Push the current branch"
-};
-
-static void warn_unconfigured_push(void)
-{
-       int i;
-       for (i = 0; i < ARRAY_SIZE(warn_unconfigured_push_msg); i++)
-               warning("%s", warn_unconfigured_push_msg[i]);
-}
-
-static void setup_default_push_refspecs(void)
-{
-       git_config(git_default_config, NULL);
-       switch (push_default) {
-       case PUSH_DEFAULT_UNSPECIFIED:
-               warn_unconfigured_push();
-               /* fallthrough */
-
-       case PUSH_DEFAULT_MATCHING:
-               add_refspec(":");
-               break;
-
-       case PUSH_DEFAULT_TRACKING:
-               setup_push_tracking();
-               break;
-
-       case PUSH_DEFAULT_CURRENT:
-               add_refspec("HEAD");
-               break;
-
-       case PUSH_DEFAULT_NOTHING:
-               die("You didn't specify any refspecs to push, and "
-                   "push.default is \"nothing\".");
-               break;
-       }
-}
-
-static int do_push(const char *repo, int flags)
-{
-       int i, errs;
-       struct remote *remote = remote_get(repo);
-
-       if (!remote) {
-               if (repo)
-                       die("bad repository '%s'", repo);
-               die("No destination configured to push to.");
-       }
-
-       if (remote->mirror)
-               flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
-
-       if ((flags & TRANSPORT_PUSH_ALL) && refspec) {
-               if (!strcmp(*refspec, "refs/tags/*"))
-                       return error("--all and --tags are incompatible");
-               return error("--all can't be combined with refspecs");
-       }
-
-       if ((flags & TRANSPORT_PUSH_MIRROR) && refspec) {
-               if (!strcmp(*refspec, "refs/tags/*"))
-                       return error("--mirror and --tags are incompatible");
-               return error("--mirror can't be combined with refspecs");
-       }
-
-       if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) ==
-                               (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) {
-               return error("--all and --mirror are incompatible");
-       }
-
-       if (!refspec && !(flags & TRANSPORT_PUSH_ALL)) {
-               if (remote->push_refspec_nr) {
-                       refspec = remote->push_refspec;
-                       refspec_nr = remote->push_refspec_nr;
-               } else if (!(flags & TRANSPORT_PUSH_MIRROR))
-                       setup_default_push_refspecs();
-       }
-       errs = 0;
-       for (i = 0; i < remote->url_nr; i++) {
-               struct transport *transport =
-                       transport_get(remote, remote->url[i]);
-               int err;
-               if (receivepack)
-                       transport_set_option(transport,
-                                            TRANS_OPT_RECEIVEPACK, receivepack);
-               if (thin)
-                       transport_set_option(transport, TRANS_OPT_THIN, "yes");
-
-               if (flags & TRANSPORT_PUSH_VERBOSE)
-                       fprintf(stderr, "Pushing to %s\n", remote->url[i]);
-               err = transport_push(transport, refspec_nr, refspec, flags);
-               err |= transport_disconnect(transport);
-
-               if (!err)
-                       continue;
-
-               error("failed to push some refs to '%s'", remote->url[i]);
-               errs++;
-       }
-       return !!errs;
-}
-
-int cmd_push(int argc, const char **argv, const char *prefix)
-{
-       int flags = 0;
-       int tags = 0;
-       int rc;
-       const char *repo = NULL;        /* default repository */
-
-       struct option options[] = {
-               OPT_BIT('v', "verbose", &flags, "be verbose", TRANSPORT_PUSH_VERBOSE),
-               OPT_STRING( 0 , "repo", &repo, "repository", "repository"),
-               OPT_BIT( 0 , "all", &flags, "push all refs", TRANSPORT_PUSH_ALL),
-               OPT_BIT( 0 , "mirror", &flags, "mirror all refs",
-                           (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)),
-               OPT_BOOLEAN( 0 , "tags", &tags, "push tags"),
-               OPT_BIT( 0 , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN),
-               OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE),
-               OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"),
-               OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"),
-               OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"),
-               OPT_END()
-       };
-
-       argc = parse_options(argc, argv, options, push_usage, 0);
-
-       if (tags)
-               add_refspec("refs/tags/*");
-
-       if (argc > 0) {
-               repo = argv[0];
-               set_refspecs(argv + 1, argc - 1);
-       }
-
-       rc = do_push(repo, flags);
-       if (rc == -1)
-               usage_with_options(push_usage, options);
-       else
-               return rc;
-}
diff --git a/builtin-read-tree.c b/builtin-read-tree.c
deleted file mode 100644 (file)
index 82e25ea..0000000
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Linus Torvalds, 2005
- */
-
-#include "cache.h"
-#include "object.h"
-#include "tree.h"
-#include "tree-walk.h"
-#include "cache-tree.h"
-#include "unpack-trees.h"
-#include "dir.h"
-#include "builtin.h"
-
-static int nr_trees;
-static struct tree *trees[MAX_UNPACK_TREES];
-
-static int list_tree(unsigned char *sha1)
-{
-       struct tree *tree;
-
-       if (nr_trees >= MAX_UNPACK_TREES)
-               die("I cannot read more than %d trees", MAX_UNPACK_TREES);
-       tree = parse_tree_indirect(sha1);
-       if (!tree)
-               return -1;
-       trees[nr_trees++] = tree;
-       return 0;
-}
-
-static const char read_tree_usage[] = "git read-tree (<sha> | [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <sha1> [<sha2> [<sha3>]])";
-
-static struct lock_file lock_file;
-
-int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
-{
-       int i, newfd, stage = 0;
-       unsigned char sha1[20];
-       struct tree_desc t[MAX_UNPACK_TREES];
-       struct unpack_trees_options opts;
-
-       memset(&opts, 0, sizeof(opts));
-       opts.head_idx = -1;
-       opts.src_index = &the_index;
-       opts.dst_index = &the_index;
-
-       git_config(git_default_config, NULL);
-
-       newfd = hold_locked_index(&lock_file, 1);
-
-       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")) {
-                       opts.update = 1;
-                       continue;
-               }
-
-               if (!strcmp(arg, "-v")) {
-                       opts.verbose_update = 1;
-                       continue;
-               }
-
-               /* "-i" means "index only", meaning that a merge will
-                * not even look at the working tree.
-                */
-               if (!strcmp(arg, "-i")) {
-                       opts.index_only = 1;
-                       continue;
-               }
-
-               if (!prefixcmp(arg, "--index-output=")) {
-                       set_alternate_index_output(arg + 15);
-                       continue;
-               }
-
-               /* "--prefix=<subdirectory>/" means keep the current index
-                *  entries and put the entries from the tree under the
-                * given subdirectory.
-                */
-               if (!prefixcmp(arg, "--prefix=")) {
-                       if (stage || opts.merge || opts.prefix)
-                               usage(read_tree_usage);
-                       opts.prefix = arg + 9;
-                       opts.merge = 1;
-                       stage = 1;
-                       if (read_cache_unmerged())
-                               die("you need to resolve your current index first");
-                       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 || opts.merge || opts.prefix)
-                               usage(read_tree_usage);
-                       opts.reset = 1;
-                       opts.merge = 1;
-                       stage = 1;
-                       read_cache_unmerged();
-                       continue;
-               }
-
-               if (!strcmp(arg, "--trivial")) {
-                       opts.trivial_merges_only = 1;
-                       continue;
-               }
-
-               if (!strcmp(arg, "--aggressive")) {
-                       opts.aggressive = 1;
-                       continue;
-               }
-
-               /* "-m" stands for "merge", meaning we start in stage 1 */
-               if (!strcmp(arg, "-m")) {
-                       if (stage || opts.merge || opts.prefix)
-                               usage(read_tree_usage);
-                       if (read_cache_unmerged())
-                               die("you need to resolve your current index first");
-                       stage = 1;
-                       opts.merge = 1;
-                       continue;
-               }
-
-               if (!prefixcmp(arg, "--exclude-per-directory=")) {
-                       struct dir_struct *dir;
-
-                       if (opts.dir)
-                               die("more than one --exclude-per-directory are given.");
-
-                       dir = xcalloc(1, sizeof(*opts.dir));
-                       dir->flags |= DIR_SHOW_IGNORED;
-                       dir->exclude_per_dir = arg + 24;
-                       opts.dir = dir;
-                       /* We do not need to nor want to do read-directory
-                        * here; we are merely interested in reusing the
-                        * per directory ignore stack mechanism.
-                        */
-                       continue;
-               }
-
-               /* using -u and -i at the same time makes no sense */
-               if (1 < opts.index_only + opts.update)
-                       usage(read_tree_usage);
-
-               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 ((opts.update||opts.index_only) && !opts.merge)
-               usage(read_tree_usage);
-       if ((opts.dir && !opts.update))
-               die("--exclude-per-directory is meaningless unless -u");
-       if (opts.merge && !opts.index_only)
-               setup_work_tree();
-
-       if (opts.merge) {
-               if (stage < 2)
-                       die("just how do you expect me to merge %d trees?", stage-1);
-               switch (stage - 1) {
-               case 1:
-                       opts.fn = opts.prefix ? bind_merge : oneway_merge;
-                       break;
-               case 2:
-                       opts.fn = twoway_merge;
-                       opts.initial_checkout = is_cache_unborn();
-                       break;
-               case 3:
-               default:
-                       opts.fn = threeway_merge;
-                       break;
-               }
-
-               if (stage - 1 >= 3)
-                       opts.head_idx = stage - 2;
-               else
-                       opts.head_idx = 1;
-       }
-
-       cache_tree_free(&active_cache_tree);
-       for (i = 0; i < nr_trees; i++) {
-               struct tree *tree = trees[i];
-               parse_tree(tree);
-               init_tree_desc(t+i, tree->buffer, tree->size);
-       }
-       if (unpack_trees(nr_trees, t, &opts))
-               return 128;
-
-       /*
-        * 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.
-        *
-        * The same holds true if we are switching between two trees
-        * using read-tree -m A B.  The index must match B after that.
-        */
-       if (nr_trees == 1 && !opts.prefix)
-               prime_cache_tree(&active_cache_tree, trees[0]);
-       else if (nr_trees == 2 && opts.merge)
-               prime_cache_tree(&active_cache_tree, trees[1]);
-
-       if (write_cache(newfd, active_cache, active_nr) ||
-           commit_locked_index(&lock_file))
-               die("unable to write new index file");
-       return 0;
-}
diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c
deleted file mode 100644 (file)
index a970b39..0000000
+++ /dev/null
@@ -1,712 +0,0 @@
-#include "cache.h"
-#include "pack.h"
-#include "refs.h"
-#include "pkt-line.h"
-#include "run-command.h"
-#include "exec_cmd.h"
-#include "commit.h"
-#include "object.h"
-#include "remote.h"
-#include "transport.h"
-
-static const char receive_pack_usage[] = "git receive-pack <git-dir>";
-
-enum deny_action {
-       DENY_UNCONFIGURED,
-       DENY_IGNORE,
-       DENY_WARN,
-       DENY_REFUSE,
-};
-
-static int deny_deletes;
-static int deny_non_fast_forwards;
-static enum deny_action deny_current_branch = DENY_UNCONFIGURED;
-static enum deny_action deny_delete_current = DENY_UNCONFIGURED;
-static int receive_fsck_objects;
-static int receive_unpack_limit = -1;
-static int transfer_unpack_limit = -1;
-static int unpack_limit = 100;
-static int report_status;
-static const char *head_name;
-
-static char capabilities[] = " report-status delete-refs ";
-static int capabilities_sent;
-
-static enum deny_action parse_deny_action(const char *var, const char *value)
-{
-       if (value) {
-               if (!strcasecmp(value, "ignore"))
-                       return DENY_IGNORE;
-               if (!strcasecmp(value, "warn"))
-                       return DENY_WARN;
-               if (!strcasecmp(value, "refuse"))
-                       return DENY_REFUSE;
-       }
-       if (git_config_bool(var, value))
-               return DENY_REFUSE;
-       return DENY_IGNORE;
-}
-
-static int receive_pack_config(const char *var, const char *value, void *cb)
-{
-       if (strcmp(var, "receive.denydeletes") == 0) {
-               deny_deletes = git_config_bool(var, value);
-               return 0;
-       }
-
-       if (strcmp(var, "receive.denynonfastforwards") == 0) {
-               deny_non_fast_forwards = git_config_bool(var, value);
-               return 0;
-       }
-
-       if (strcmp(var, "receive.unpacklimit") == 0) {
-               receive_unpack_limit = git_config_int(var, value);
-               return 0;
-       }
-
-       if (strcmp(var, "transfer.unpacklimit") == 0) {
-               transfer_unpack_limit = git_config_int(var, value);
-               return 0;
-       }
-
-       if (strcmp(var, "receive.fsckobjects") == 0) {
-               receive_fsck_objects = git_config_bool(var, value);
-               return 0;
-       }
-
-       if (!strcmp(var, "receive.denycurrentbranch")) {
-               deny_current_branch = parse_deny_action(var, value);
-               return 0;
-       }
-
-       if (strcmp(var, "receive.denydeletecurrent") == 0) {
-               deny_delete_current = parse_deny_action(var, value);
-               return 0;
-       }
-
-       return git_default_config(var, value, cb);
-}
-
-static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
-{
-       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, NULL);
-       if (!capabilities_sent)
-               show_ref("capabilities^{}", null_sha1, 0, NULL);
-
-}
-
-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;
-
-static const char pre_receive_hook[] = "hooks/pre-receive";
-static const char post_receive_hook[] = "hooks/post-receive";
-
-static int hook_status(int code, const char *hook_name)
-{
-       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_PIPE:
-               return error("hook pipe 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", hook_name);
-       case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
-               return error("%s died strangely", hook_name);
-       default:
-               error("%s exited with error code %d", hook_name, -code);
-               return -code;
-       }
-}
-
-static int run_receive_hook(const char *hook_name)
-{
-       static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4];
-       struct command *cmd;
-       struct child_process proc;
-       const char *argv[2];
-       int have_input = 0, code;
-
-       for (cmd = commands; !have_input && cmd; cmd = cmd->next) {
-               if (!cmd->error_string)
-                       have_input = 1;
-       }
-
-       if (!have_input || access(hook_name, X_OK) < 0)
-               return 0;
-
-       argv[0] = hook_name;
-       argv[1] = NULL;
-
-       memset(&proc, 0, sizeof(proc));
-       proc.argv = argv;
-       proc.in = -1;
-       proc.stdout_to_stderr = 1;
-
-       code = start_command(&proc);
-       if (code)
-               return hook_status(code, hook_name);
-       for (cmd = commands; cmd; cmd = cmd->next) {
-               if (!cmd->error_string) {
-                       size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n",
-                               sha1_to_hex(cmd->old_sha1),
-                               sha1_to_hex(cmd->new_sha1),
-                               cmd->ref_name);
-                       if (write_in_full(proc.in, buf, n) != n)
-                               break;
-               }
-       }
-       close(proc.in);
-       return hook_status(finish_command(&proc), hook_name);
-}
-
-static int run_update_hook(struct command *cmd)
-{
-       static const char update_hook[] = "hooks/update";
-       struct child_process proc;
-       const char *argv[5];
-
-       if (access(update_hook, X_OK) < 0)
-               return 0;
-
-       argv[0] = update_hook;
-       argv[1] = cmd->ref_name;
-       argv[2] = sha1_to_hex(cmd->old_sha1);
-       argv[3] = sha1_to_hex(cmd->new_sha1);
-       argv[4] = NULL;
-
-       memset(&proc, 0, sizeof(proc));
-       proc.argv = argv;
-       proc.no_stdin = 1;
-       proc.stdout_to_stderr = 1;
-
-       return hook_status(run_command(&proc), update_hook);
-}
-
-static int is_ref_checked_out(const char *ref)
-{
-       if (is_bare_repository())
-               return 0;
-
-       if (!head_name)
-               return 0;
-       return !strcmp(head_name, ref);
-}
-
-static char *warn_unconfigured_deny_msg[] = {
-       "Updating the currently checked out branch may cause confusion,",
-       "as the index and work tree do not reflect changes that are in HEAD.",
-       "As a result, you may see the changes you just pushed into it",
-       "reverted when you run 'git diff' over there, and you may want",
-       "to run 'git reset --hard' before starting to work to recover.",
-       "",
-       "You can set 'receive.denyCurrentBranch' configuration variable to",
-       "'refuse' in the remote repository to forbid pushing into its",
-       "current branch."
-       "",
-       "To allow pushing into the current branch, you can set it to 'ignore';",
-       "but this is not recommended unless you arranged to update its work",
-       "tree to match what you pushed in some other way.",
-       "",
-       "To squelch this message, you can set it to 'warn'.",
-       "",
-       "Note that the default will change in a future version of git",
-       "to refuse updating the current branch unless you have the",
-       "configuration variable set to either 'ignore' or 'warn'."
-};
-
-static void warn_unconfigured_deny(void)
-{
-       int i;
-       for (i = 0; i < ARRAY_SIZE(warn_unconfigured_deny_msg); i++)
-               warning("%s", warn_unconfigured_deny_msg[i]);
-}
-
-static char *warn_unconfigured_deny_delete_current_msg[] = {
-       "Deleting the current branch can cause confusion by making the next",
-       "'git clone' not check out any file.",
-       "",
-       "You can set 'receive.denyDeleteCurrent' configuration variable to",
-       "'refuse' in the remote repository to disallow deleting the current",
-       "branch.",
-       "",
-       "You can set it to 'ignore' to allow such a delete without a warning.",
-       "",
-       "To make this warning message less loud, you can set it to 'warn'.",
-       "",
-       "Note that the default will change in a future version of git",
-       "to refuse deleting the current branch unless you have the",
-       "configuration variable set to either 'ignore' or 'warn'."
-};
-
-static void warn_unconfigured_deny_delete_current(void)
-{
-       int i;
-       for (i = 0;
-            i < ARRAY_SIZE(warn_unconfigured_deny_delete_current_msg);
-            i++)
-               warning("%s", warn_unconfigured_deny_delete_current_msg[i]);
-}
-
-static const char *update(struct command *cmd)
-{
-       const char *name = cmd->ref_name;
-       unsigned char *old_sha1 = cmd->old_sha1;
-       unsigned char *new_sha1 = cmd->new_sha1;
-       struct ref_lock *lock;
-
-       /* only refs/... are allowed */
-       if (prefixcmp(name, "refs/") || check_ref_format(name + 5)) {
-               error("refusing to create funny ref '%s' remotely", name);
-               return "funny refname";
-       }
-
-       if (is_ref_checked_out(name)) {
-               switch (deny_current_branch) {
-               case DENY_IGNORE:
-                       break;
-               case DENY_UNCONFIGURED:
-               case DENY_WARN:
-                       warning("updating the current branch");
-                       if (deny_current_branch == DENY_UNCONFIGURED)
-                               warn_unconfigured_deny();
-                       break;
-               case DENY_REFUSE:
-                       error("refusing to update checked out branch: %s", name);
-                       return "branch is currently checked out";
-               }
-       }
-
-       if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) {
-               error("unpack should have generated %s, "
-                     "but I can't find it!", sha1_to_hex(new_sha1));
-               return "bad pack";
-       }
-
-       if (!is_null_sha1(old_sha1) && is_null_sha1(new_sha1)) {
-               if (deny_deletes && !prefixcmp(name, "refs/heads/")) {
-                       error("denying ref deletion for %s", name);
-                       return "deletion prohibited";
-               }
-
-               if (!strcmp(name, head_name)) {
-                       switch (deny_delete_current) {
-                       case DENY_IGNORE:
-                               break;
-                       case DENY_WARN:
-                       case DENY_UNCONFIGURED:
-                               if (deny_delete_current == DENY_UNCONFIGURED)
-                                       warn_unconfigured_deny_delete_current();
-                               warning("deleting the current branch");
-                               break;
-                       case DENY_REFUSE:
-                               error("refusing to delete the current branch: %s", name);
-                               return "deletion of the current branch prohibited";
-                       }
-               }
-       }
-
-       if (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
-           !is_null_sha1(old_sha1) &&
-           !prefixcmp(name, "refs/heads/")) {
-               struct object *old_object, *new_object;
-               struct commit *old_commit, *new_commit;
-               struct commit_list *bases, *ent;
-
-               old_object = parse_object(old_sha1);
-               new_object = parse_object(new_sha1);
-
-               if (!old_object || !new_object ||
-                   old_object->type != OBJ_COMMIT ||
-                   new_object->type != OBJ_COMMIT) {
-                       error("bad sha1 objects for %s", name);
-                       return "bad ref";
-               }
-               old_commit = (struct commit *)old_object;
-               new_commit = (struct commit *)new_object;
-               bases = get_merge_bases(old_commit, new_commit, 1);
-               for (ent = bases; ent; ent = ent->next)
-                       if (!hashcmp(old_sha1, ent->item->object.sha1))
-                               break;
-               free_commit_list(bases);
-               if (!ent) {
-                       error("denying non-fast forward %s"
-                             " (you should pull first)", name);
-                       return "non-fast forward";
-               }
-       }
-       if (run_update_hook(cmd)) {
-               error("hook declined to update %s", name);
-               return "hook declined";
-       }
-
-       if (is_null_sha1(new_sha1)) {
-               if (!parse_object(old_sha1)) {
-                       warning ("Allowing deletion of corrupt ref.");
-                       old_sha1 = NULL;
-               }
-               if (delete_ref(name, old_sha1, 0)) {
-                       error("failed to delete %s", name);
-                       return "failed to delete";
-               }
-               return NULL; /* good */
-       }
-       else {
-               lock = lock_any_ref_for_update(name, old_sha1, 0);
-               if (!lock) {
-                       error("failed to lock %s", name);
-                       return "failed to lock";
-               }
-               if (write_ref_sha1(lock, new_sha1, "push")) {
-                       return "failed to write"; /* error() already called */
-               }
-               return NULL; /* good */
-       }
-}
-
-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;
-
-       for (argc = 0, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
-               if (cmd_p->error_string)
-                       continue;
-               argc++;
-       }
-       if (!argc || access(update_post_hook, X_OK) < 0)
-               return;
-       argv = xmalloc(sizeof(*argv) * (2 + 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(argv, RUN_COMMAND_NO_STDIN
-               | RUN_COMMAND_STDOUT_TO_STDERR);
-}
-
-static void execute_commands(const char *unpacker_error)
-{
-       struct command *cmd = commands;
-       unsigned char sha1[20];
-
-       if (unpacker_error) {
-               while (cmd) {
-                       cmd->error_string = "n/a (unpacker error)";
-                       cmd = cmd->next;
-               }
-               return;
-       }
-
-       if (run_receive_hook(pre_receive_hook)) {
-               while (cmd) {
-                       cmd->error_string = "pre-receive hook declined";
-                       cmd = cmd->next;
-               }
-               return;
-       }
-
-       head_name = resolve_ref("HEAD", sha1, 0, NULL);
-
-       while (cmd) {
-               cmd->error_string = update(cmd);
-               cmd = cmd->next;
-       }
-}
-
-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);
-               hashcpy(cmd->old_sha1, old_sha1);
-               hashcpy(cmd->new_sha1, new_sha1);
-               memcpy(cmd->ref_name, line + 82, len - 81);
-               cmd->error_string = NULL;
-               cmd->next = NULL;
-               *p = cmd;
-               p = &cmd->next;
-       }
-}
-
-static const char *parse_pack_header(struct pack_header *hdr)
-{
-       switch (read_pack_header(0, hdr)) {
-       case PH_ERROR_EOF:
-               return "eof before pack header was fully read";
-
-       case PH_ERROR_PACK_SIGNATURE:
-               return "protocol error (pack signature mismatch detected)";
-
-       case PH_ERROR_PROTOCOL:
-               return "protocol error (pack version unsupported)";
-
-       default:
-               return "unknown error in parse_pack_header";
-
-       case 0:
-               return NULL;
-       }
-}
-
-static const char *pack_lockfile;
-
-static const char *unpack(void)
-{
-       struct pack_header hdr;
-       const char *hdr_err;
-       char hdr_arg[38];
-
-       hdr_err = parse_pack_header(&hdr);
-       if (hdr_err)
-               return hdr_err;
-       snprintf(hdr_arg, sizeof(hdr_arg),
-                       "--pack_header=%"PRIu32",%"PRIu32,
-                       ntohl(hdr.hdr_version), ntohl(hdr.hdr_entries));
-
-       if (ntohl(hdr.hdr_entries) < unpack_limit) {
-               int code, i = 0;
-               const char *unpacker[4];
-               unpacker[i++] = "unpack-objects";
-               if (receive_fsck_objects)
-                       unpacker[i++] = "--strict";
-               unpacker[i++] = hdr_arg;
-               unpacker[i++] = NULL;
-               code = run_command_v_opt(unpacker, RUN_GIT_CMD);
-               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:
-                       return "unpacker exited with error code";
-               }
-       } else {
-               const char *keeper[7];
-               int s, status, i = 0;
-               char keep_arg[256];
-               struct child_process ip;
-
-               s = sprintf(keep_arg, "--keep=receive-pack %"PRIuMAX" on ", (uintmax_t) getpid());
-               if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
-                       strcpy(keep_arg + s, "localhost");
-
-               keeper[i++] = "index-pack";
-               keeper[i++] = "--stdin";
-               if (receive_fsck_objects)
-                       keeper[i++] = "--strict";
-               keeper[i++] = "--fix-thin";
-               keeper[i++] = hdr_arg;
-               keeper[i++] = keep_arg;
-               keeper[i++] = NULL;
-               memset(&ip, 0, sizeof(ip));
-               ip.argv = keeper;
-               ip.out = -1;
-               ip.git_cmd = 1;
-               if (start_command(&ip))
-                       return "index-pack fork failed";
-               pack_lockfile = index_pack_lockfile(ip.out);
-               close(ip.out);
-               status = finish_command(&ip);
-               if (!status) {
-                       reprepare_packed_git();
-                       return NULL;
-               }
-               return "index-pack abnormal exit";
-       }
-}
-
-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);
-}
-
-static int delete_only(struct command *cmd)
-{
-       while (cmd) {
-               if (!is_null_sha1(cmd->new_sha1))
-                       return 0;
-               cmd = cmd->next;
-       }
-       return 1;
-}
-
-static int add_refs_from_alternate(struct alternate_object_database *e, void *unused)
-{
-       char *other;
-       size_t len;
-       struct remote *remote;
-       struct transport *transport;
-       const struct ref *extra;
-
-       e->name[-1] = '\0';
-       other = xstrdup(make_absolute_path(e->base));
-       e->name[-1] = '/';
-       len = strlen(other);
-
-       while (other[len-1] == '/')
-               other[--len] = '\0';
-       if (len < 8 || memcmp(other + len - 8, "/objects", 8))
-               return 0;
-       /* Is this a git repository with refs? */
-       memcpy(other + len - 8, "/refs", 6);
-       if (!is_directory(other))
-               return 0;
-       other[len - 8] = '\0';
-       remote = remote_get(other);
-       transport = transport_get(remote, other);
-       for (extra = transport_get_remote_refs(transport);
-            extra;
-            extra = extra->next) {
-               add_extra_ref(".have", extra->old_sha1, 0);
-       }
-       transport_disconnect(transport);
-       free(other);
-       return 0;
-}
-
-static void add_alternate_refs(void)
-{
-       foreach_alt_odb(add_refs_from_alternate, NULL);
-}
-
-int cmd_receive_pack(int argc, const char **argv, const char *prefix)
-{
-       int i;
-       char *dir = NULL;
-
-       argv++;
-       for (i = 1; i < argc; i++) {
-               const char *arg = *argv++;
-
-               if (*arg == '-') {
-                       /* Do flag handling here */
-                       usage(receive_pack_usage);
-               }
-               if (dir)
-                       usage(receive_pack_usage);
-               dir = xstrdup(arg);
-       }
-       if (!dir)
-               usage(receive_pack_usage);
-
-       setup_path();
-
-       if (!enter_repo(dir, 0))
-               die("'%s' does not appear to be a git repository", dir);
-
-       if (is_repository_shallow())
-               die("attempt to push into a shallow repository");
-
-       git_config(receive_pack_config, NULL);
-
-       if (0 <= transfer_unpack_limit)
-               unpack_limit = transfer_unpack_limit;
-       else if (0 <= receive_unpack_limit)
-               unpack_limit = receive_unpack_limit;
-
-       add_alternate_refs();
-       write_head_info();
-       clear_extra_refs();
-
-       /* EOF */
-       packet_flush(1);
-
-       read_head_info();
-       if (commands) {
-               const char *unpack_status = NULL;
-
-               if (!delete_only(commands))
-                       unpack_status = unpack();
-               execute_commands(unpack_status);
-               if (pack_lockfile)
-                       unlink(pack_lockfile);
-               if (report_status)
-                       report(unpack_status);
-               run_receive_hook(post_receive_hook);
-               run_update_post_hook(commands);
-       }
-       return 0;
-}
diff --git a/builtin-reflog.c b/builtin-reflog.c
deleted file mode 100644 (file)
index ddfdf5a..0000000
+++ /dev/null
@@ -1,716 +0,0 @@
-#include "cache.h"
-#include "builtin.h"
-#include "commit.h"
-#include "refs.h"
-#include "dir.h"
-#include "tree-walk.h"
-#include "diff.h"
-#include "revision.h"
-#include "reachable.h"
-
-/*
- * reflog expire
- */
-
-static const char reflog_expire_usage[] =
-"git reflog (show|expire) [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
-static const char reflog_delete_usage[] =
-"git reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>...";
-
-static unsigned long default_reflog_expire;
-static unsigned long default_reflog_expire_unreachable;
-
-struct cmd_reflog_expire_cb {
-       struct rev_info revs;
-       int dry_run;
-       int stalefix;
-       int rewrite;
-       int updateref;
-       int verbose;
-       unsigned long expire_total;
-       unsigned long expire_unreachable;
-       int recno;
-};
-
-struct expire_reflog_cb {
-       FILE *newlog;
-       const char *ref;
-       struct commit *ref_commit;
-       struct cmd_reflog_expire_cb *cmd;
-       unsigned char last_kept_sha1[20];
-};
-
-struct collected_reflog {
-       unsigned char sha1[20];
-       char reflog[FLEX_ARRAY];
-};
-struct collect_reflog_cb {
-       struct collected_reflog **e;
-       int alloc;
-       int nr;
-};
-
-#define INCOMPLETE     (1u<<10)
-#define STUDYING       (1u<<11)
-#define REACHABLE      (1u<<12)
-
-static int tree_is_complete(const unsigned char *sha1)
-{
-       struct tree_desc desc;
-       struct name_entry entry;
-       int complete;
-       struct tree *tree;
-
-       tree = lookup_tree(sha1);
-       if (!tree)
-               return 0;
-       if (tree->object.flags & SEEN)
-               return 1;
-       if (tree->object.flags & INCOMPLETE)
-               return 0;
-
-       if (!tree->buffer) {
-               enum object_type type;
-               unsigned long size;
-               void *data = read_sha1_file(sha1, &type, &size);
-               if (!data) {
-                       tree->object.flags |= INCOMPLETE;
-                       return 0;
-               }
-               tree->buffer = data;
-               tree->size = size;
-       }
-       init_tree_desc(&desc, tree->buffer, tree->size);
-       complete = 1;
-       while (tree_entry(&desc, &entry)) {
-               if (!has_sha1_file(entry.sha1) ||
-                   (S_ISDIR(entry.mode) && !tree_is_complete(entry.sha1))) {
-                       tree->object.flags |= INCOMPLETE;
-                       complete = 0;
-               }
-       }
-       free(tree->buffer);
-       tree->buffer = NULL;
-
-       if (complete)
-               tree->object.flags |= SEEN;
-       return complete;
-}
-
-static int commit_is_complete(struct commit *commit)
-{
-       struct object_array study;
-       struct object_array found;
-       int is_incomplete = 0;
-       int i;
-
-       /* early return */
-       if (commit->object.flags & SEEN)
-               return 1;
-       if (commit->object.flags & INCOMPLETE)
-               return 0;
-       /*
-        * Find all commits that are reachable and are not marked as
-        * SEEN.  Then make sure the trees and blobs contained are
-        * complete.  After that, mark these commits also as SEEN.
-        * If some of the objects that are needed to complete this
-        * commit are missing, mark this commit as INCOMPLETE.
-        */
-       memset(&study, 0, sizeof(study));
-       memset(&found, 0, sizeof(found));
-       add_object_array(&commit->object, NULL, &study);
-       add_object_array(&commit->object, NULL, &found);
-       commit->object.flags |= STUDYING;
-       while (study.nr) {
-               struct commit *c;
-               struct commit_list *parent;
-
-               c = (struct commit *)study.objects[--study.nr].item;
-               if (!c->object.parsed && !parse_object(c->object.sha1))
-                       c->object.flags |= INCOMPLETE;
-
-               if (c->object.flags & INCOMPLETE) {
-                       is_incomplete = 1;
-                       break;
-               }
-               else if (c->object.flags & SEEN)
-                       continue;
-               for (parent = c->parents; parent; parent = parent->next) {
-                       struct commit *p = parent->item;
-                       if (p->object.flags & STUDYING)
-                               continue;
-                       p->object.flags |= STUDYING;
-                       add_object_array(&p->object, NULL, &study);
-                       add_object_array(&p->object, NULL, &found);
-               }
-       }
-       if (!is_incomplete) {
-               /*
-                * make sure all commits in "found" array have all the
-                * necessary objects.
-                */
-               for (i = 0; i < found.nr; i++) {
-                       struct commit *c =
-                               (struct commit *)found.objects[i].item;
-                       if (!tree_is_complete(c->tree->object.sha1)) {
-                               is_incomplete = 1;
-                               c->object.flags |= INCOMPLETE;
-                       }
-               }
-               if (!is_incomplete) {
-                       /* mark all found commits as complete, iow SEEN */
-                       for (i = 0; i < found.nr; i++)
-                               found.objects[i].item->flags |= SEEN;
-               }
-       }
-       /* clear flags from the objects we traversed */
-       for (i = 0; i < found.nr; i++)
-               found.objects[i].item->flags &= ~STUDYING;
-       if (is_incomplete)
-               commit->object.flags |= INCOMPLETE;
-       else {
-               /*
-                * If we come here, we have (1) traversed the ancestry chain
-                * from the "commit" until we reach SEEN commits (which are
-                * known to be complete), and (2) made sure that the commits
-                * encountered during the above traversal refer to trees that
-                * are complete.  Which means that we know *all* the commits
-                * we have seen during this process are complete.
-                */
-               for (i = 0; i < found.nr; i++)
-                       found.objects[i].item->flags |= SEEN;
-       }
-       /* free object arrays */
-       free(study.objects);
-       free(found.objects);
-       return !is_incomplete;
-}
-
-static int keep_entry(struct commit **it, unsigned char *sha1)
-{
-       struct commit *commit;
-
-       if (is_null_sha1(sha1))
-               return 1;
-       commit = lookup_commit_reference_gently(sha1, 1);
-       if (!commit)
-               return 0;
-
-       /*
-        * Make sure everything in this commit exists.
-        *
-        * We have walked all the objects reachable from the refs
-        * and cache earlier.  The commits reachable by this commit
-        * must meet SEEN commits -- and then we should mark them as
-        * SEEN as well.
-        */
-       if (!commit_is_complete(commit))
-               return 0;
-       *it = commit;
-       return 1;
-}
-
-static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1)
-{
-       /*
-        * We may or may not have the commit yet - if not, look it
-        * up using the supplied sha1.
-        */
-       if (!commit) {
-               if (is_null_sha1(sha1))
-                       return 0;
-
-               commit = lookup_commit_reference_gently(sha1, 1);
-
-               /* Not a commit -- keep it */
-               if (!commit)
-                       return 0;
-       }
-
-       /* Reachable from the current ref?  Don't prune. */
-       if (commit->object.flags & REACHABLE)
-               return 0;
-       if (in_merge_bases(commit, &cb->ref_commit, 1))
-               return 0;
-
-       /* We can't reach it - prune it. */
-       return 1;
-}
-
-static void mark_reachable(struct commit *commit, unsigned long expire_limit)
-{
-       /*
-        * We need to compute whether the commit on either side of a reflog
-        * entry is reachable from the tip of the ref for all entries.
-        * Mark commits that are reachable from the tip down to the
-        * time threshold first; we know a commit marked thusly is
-        * reachable from the tip without running in_merge_bases()
-        * at all.
-        */
-       struct commit_list *pending = NULL;
-
-       commit_list_insert(commit, &pending);
-       while (pending) {
-               struct commit_list *entry = pending;
-               struct commit_list *parent;
-               pending = entry->next;
-               commit = entry->item;
-               free(entry);
-               if (commit->object.flags & REACHABLE)
-                       continue;
-               if (parse_commit(commit))
-                       continue;
-               commit->object.flags |= REACHABLE;
-               if (commit->date < expire_limit)
-                       continue;
-               parent = commit->parents;
-               while (parent) {
-                       commit = parent->item;
-                       parent = parent->next;
-                       if (commit->object.flags & REACHABLE)
-                               continue;
-                       commit_list_insert(commit, &pending);
-               }
-       }
-}
-
-static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
-               const char *email, unsigned long timestamp, int tz,
-               const char *message, void *cb_data)
-{
-       struct expire_reflog_cb *cb = cb_data;
-       struct commit *old, *new;
-
-       if (timestamp < cb->cmd->expire_total)
-               goto prune;
-
-       if (cb->cmd->rewrite)
-               osha1 = cb->last_kept_sha1;
-
-       old = new = NULL;
-       if (cb->cmd->stalefix &&
-           (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1)))
-               goto prune;
-
-       if (timestamp < cb->cmd->expire_unreachable) {
-               if (!cb->ref_commit)
-                       goto prune;
-               if (unreachable(cb, old, osha1) || unreachable(cb, new, nsha1))
-                       goto prune;
-       }
-
-       if (cb->cmd->recno && --(cb->cmd->recno) == 0)
-               goto prune;
-
-       if (cb->newlog) {
-               char sign = (tz < 0) ? '-' : '+';
-               int zone = (tz < 0) ? (-tz) : tz;
-               fprintf(cb->newlog, "%s %s %s %lu %c%04d\t%s",
-                       sha1_to_hex(osha1), sha1_to_hex(nsha1),
-                       email, timestamp, sign, zone,
-                       message);
-               hashcpy(cb->last_kept_sha1, nsha1);
-       }
-       if (cb->cmd->verbose)
-               printf("keep %s", message);
-       return 0;
- prune:
-       if (!cb->newlog || cb->cmd->verbose)
-               printf("%sprune %s", cb->newlog ? "" : "would ", message);
-       return 0;
-}
-
-static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
-{
-       struct cmd_reflog_expire_cb *cmd = cb_data;
-       struct expire_reflog_cb cb;
-       struct ref_lock *lock;
-       char *log_file, *newlog_path = NULL;
-       int status = 0;
-
-       memset(&cb, 0, sizeof(cb));
-
-       /*
-        * we take the lock for the ref itself to prevent it from
-        * getting updated.
-        */
-       lock = lock_any_ref_for_update(ref, sha1, 0);
-       if (!lock)
-               return error("cannot lock ref '%s'", ref);
-       log_file = git_pathdup("logs/%s", ref);
-       if (!file_exists(log_file))
-               goto finish;
-       if (!cmd->dry_run) {
-               newlog_path = git_pathdup("logs/%s.lock", ref);
-               cb.newlog = fopen(newlog_path, "w");
-       }
-
-       cb.ref_commit = lookup_commit_reference_gently(sha1, 1);
-       cb.ref = ref;
-       cb.cmd = cmd;
-       if (cb.ref_commit)
-               mark_reachable(cb.ref_commit, cmd->expire_total);
-       for_each_reflog_ent(ref, expire_reflog_ent, &cb);
-       if (cb.ref_commit)
-               clear_commit_marks(cb.ref_commit, REACHABLE);
- finish:
-       if (cb.newlog) {
-               if (fclose(cb.newlog)) {
-                       status |= error("%s: %s", strerror(errno),
-                                       newlog_path);
-                       unlink(newlog_path);
-               } else if (cmd->updateref &&
-                       (write_in_full(lock->lock_fd,
-                               sha1_to_hex(cb.last_kept_sha1), 40) != 40 ||
-                        write_in_full(lock->lock_fd, "\n", 1) != 1 ||
-                        close_ref(lock) < 0)) {
-                       status |= error("Couldn't write %s",
-                               lock->lk->filename);
-                       unlink(newlog_path);
-               } else if (rename(newlog_path, log_file)) {
-                       status |= error("cannot rename %s to %s",
-                                       newlog_path, log_file);
-                       unlink(newlog_path);
-               } else if (cmd->updateref && commit_ref(lock)) {
-                       status |= error("Couldn't set %s", lock->ref_name);
-               } else {
-                       adjust_shared_perm(log_file);
-               }
-       }
-       free(newlog_path);
-       free(log_file);
-       unlock_ref(lock);
-       return status;
-}
-
-static int collect_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
-{
-       struct collected_reflog *e;
-       struct collect_reflog_cb *cb = cb_data;
-       size_t namelen = strlen(ref);
-
-       e = xmalloc(sizeof(*e) + namelen + 1);
-       hashcpy(e->sha1, sha1);
-       memcpy(e->reflog, ref, namelen + 1);
-       ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc);
-       cb->e[cb->nr++] = e;
-       return 0;
-}
-
-static struct reflog_expire_cfg {
-       struct reflog_expire_cfg *next;
-       unsigned long expire_total;
-       unsigned long expire_unreachable;
-       size_t len;
-       char pattern[FLEX_ARRAY];
-} *reflog_expire_cfg, **reflog_expire_cfg_tail;
-
-static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)
-{
-       struct reflog_expire_cfg *ent;
-
-       if (!reflog_expire_cfg_tail)
-               reflog_expire_cfg_tail = &reflog_expire_cfg;
-
-       for (ent = reflog_expire_cfg; ent; ent = ent->next)
-               if (ent->len == len &&
-                   !memcmp(ent->pattern, pattern, len))
-                       return ent;
-
-       ent = xcalloc(1, (sizeof(*ent) + len));
-       memcpy(ent->pattern, pattern, len);
-       ent->len = len;
-       *reflog_expire_cfg_tail = ent;
-       reflog_expire_cfg_tail = &(ent->next);
-       return ent;
-}
-
-static int parse_expire_cfg_value(const char *var, const char *value, unsigned long *expire)
-{
-       if (!value)
-               return config_error_nonbool(var);
-       if (!strcmp(value, "never") || !strcmp(value, "false")) {
-               *expire = 0;
-               return 0;
-       }
-       *expire = approxidate(value);
-       return 0;
-}
-
-/* expiry timer slot */
-#define EXPIRE_TOTAL   01
-#define EXPIRE_UNREACH 02
-
-static int reflog_expire_config(const char *var, const char *value, void *cb)
-{
-       const char *lastdot = strrchr(var, '.');
-       unsigned long expire;
-       int slot;
-       struct reflog_expire_cfg *ent;
-
-       if (!lastdot || prefixcmp(var, "gc."))
-               return git_default_config(var, value, cb);
-
-       if (!strcmp(lastdot, ".reflogexpire")) {
-               slot = EXPIRE_TOTAL;
-               if (parse_expire_cfg_value(var, value, &expire))
-                       return -1;
-       } else if (!strcmp(lastdot, ".reflogexpireunreachable")) {
-               slot = EXPIRE_UNREACH;
-               if (parse_expire_cfg_value(var, value, &expire))
-                       return -1;
-       } else
-               return git_default_config(var, value, cb);
-
-       if (lastdot == var + 2) {
-               switch (slot) {
-               case EXPIRE_TOTAL:
-                       default_reflog_expire = expire;
-                       break;
-               case EXPIRE_UNREACH:
-                       default_reflog_expire_unreachable = expire;
-                       break;
-               }
-               return 0;
-       }
-
-       ent = find_cfg_ent(var + 3, lastdot - (var+3));
-       if (!ent)
-               return -1;
-       switch (slot) {
-       case EXPIRE_TOTAL:
-               ent->expire_total = expire;
-               break;
-       case EXPIRE_UNREACH:
-               ent->expire_unreachable = expire;
-               break;
-       }
-       return 0;
-}
-
-static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, const char *ref)
-{
-       struct reflog_expire_cfg *ent;
-
-       if (slot == (EXPIRE_TOTAL|EXPIRE_UNREACH))
-               return; /* both given explicitly -- nothing to tweak */
-
-       for (ent = reflog_expire_cfg; ent; ent = ent->next) {
-               if (!fnmatch(ent->pattern, ref, 0)) {
-                       if (!(slot & EXPIRE_TOTAL))
-                               cb->expire_total = ent->expire_total;
-                       if (!(slot & EXPIRE_UNREACH))
-                               cb->expire_unreachable = ent->expire_unreachable;
-                       return;
-               }
-       }
-
-       /*
-        * If unconfigured, make stash never expire
-        */
-       if (!strcmp(ref, "refs/stash")) {
-               if (!(slot & EXPIRE_TOTAL))
-                       cb->expire_total = 0;
-               if (!(slot & EXPIRE_UNREACH))
-                       cb->expire_unreachable = 0;
-               return;
-       }
-
-       /* Nothing matched -- use the default value */
-       if (!(slot & EXPIRE_TOTAL))
-               cb->expire_total = default_reflog_expire;
-       if (!(slot & EXPIRE_UNREACH))
-               cb->expire_unreachable = default_reflog_expire_unreachable;
-}
-
-static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
-{
-       struct cmd_reflog_expire_cb cb;
-       unsigned long now = time(NULL);
-       int i, status, do_all;
-       int explicit_expiry = 0;
-
-       git_config(reflog_expire_config, NULL);
-
-       save_commit_buffer = 0;
-       do_all = status = 0;
-       memset(&cb, 0, sizeof(cb));
-
-       if (!default_reflog_expire_unreachable)
-               default_reflog_expire_unreachable = now - 30 * 24 * 3600;
-       if (!default_reflog_expire)
-               default_reflog_expire = now - 90 * 24 * 3600;
-       cb.expire_total = default_reflog_expire;
-       cb.expire_unreachable = default_reflog_expire_unreachable;
-
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-               if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
-                       cb.dry_run = 1;
-               else if (!prefixcmp(arg, "--expire=")) {
-                       cb.expire_total = approxidate(arg + 9);
-                       explicit_expiry |= EXPIRE_TOTAL;
-               }
-               else if (!prefixcmp(arg, "--expire-unreachable=")) {
-                       cb.expire_unreachable = approxidate(arg + 21);
-                       explicit_expiry |= EXPIRE_UNREACH;
-               }
-               else if (!strcmp(arg, "--stale-fix"))
-                       cb.stalefix = 1;
-               else if (!strcmp(arg, "--rewrite"))
-                       cb.rewrite = 1;
-               else if (!strcmp(arg, "--updateref"))
-                       cb.updateref = 1;
-               else if (!strcmp(arg, "--all"))
-                       do_all = 1;
-               else if (!strcmp(arg, "--verbose"))
-                       cb.verbose = 1;
-               else if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               else if (arg[0] == '-')
-                       usage(reflog_expire_usage);
-               else
-                       break;
-       }
-
-       /*
-        * We can trust the commits and objects reachable from refs
-        * even in older repository.  We cannot trust what's reachable
-        * from reflog if the repository was pruned with older git.
-        */
-       if (cb.stalefix) {
-               init_revisions(&cb.revs, prefix);
-               if (cb.verbose)
-                       printf("Marking reachable objects...");
-               mark_reachable_objects(&cb.revs, 0);
-               if (cb.verbose)
-                       putchar('\n');
-       }
-
-       if (do_all) {
-               struct collect_reflog_cb collected;
-               int i;
-
-               memset(&collected, 0, sizeof(collected));
-               for_each_reflog(collect_reflog, &collected);
-               for (i = 0; i < collected.nr; i++) {
-                       struct collected_reflog *e = collected.e[i];
-                       set_reflog_expiry_param(&cb, explicit_expiry, e->reflog);
-                       status |= expire_reflog(e->reflog, e->sha1, 0, &cb);
-                       free(e);
-               }
-               free(collected.e);
-       }
-
-       for (; i < argc; i++) {
-               char *ref;
-               unsigned char sha1[20];
-               if (!dwim_log(argv[i], strlen(argv[i]), sha1, &ref)) {
-                       status |= error("%s points nowhere!", argv[i]);
-                       continue;
-               }
-               set_reflog_expiry_param(&cb, explicit_expiry, ref);
-               status |= expire_reflog(ref, sha1, 0, &cb);
-       }
-       return status;
-}
-
-static int count_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
-               const char *email, unsigned long timestamp, int tz,
-               const char *message, void *cb_data)
-{
-       struct cmd_reflog_expire_cb *cb = cb_data;
-       if (!cb->expire_total || timestamp < cb->expire_total)
-               cb->recno++;
-       return 0;
-}
-
-static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
-{
-       struct cmd_reflog_expire_cb cb;
-       int i, status = 0;
-
-       memset(&cb, 0, sizeof(cb));
-
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-               if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
-                       cb.dry_run = 1;
-               else if (!strcmp(arg, "--rewrite"))
-                       cb.rewrite = 1;
-               else if (!strcmp(arg, "--updateref"))
-                       cb.updateref = 1;
-               else if (!strcmp(arg, "--verbose"))
-                       cb.verbose = 1;
-               else if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               else if (arg[0] == '-')
-                       usage(reflog_delete_usage);
-               else
-                       break;
-       }
-
-       if (argc - i < 1)
-               return error("Nothing to delete?");
-
-       for ( ; i < argc; i++) {
-               const char *spec = strstr(argv[i], "@{");
-               unsigned char sha1[20];
-               char *ep, *ref;
-               int recno;
-
-               if (!spec) {
-                       status |= error("Not a reflog: %s", argv[i]);
-                       continue;
-               }
-
-               if (!dwim_log(argv[i], spec - argv[i], sha1, &ref)) {
-                       status |= error("no reflog for '%s'", argv[i]);
-                       continue;
-               }
-
-               recno = strtoul(spec + 2, &ep, 10);
-               if (*ep == '}') {
-                       cb.recno = -recno;
-                       for_each_reflog_ent(ref, count_reflog_ent, &cb);
-               } else {
-                       cb.expire_total = approxidate(spec + 2);
-                       for_each_reflog_ent(ref, count_reflog_ent, &cb);
-                       cb.expire_total = 0;
-               }
-
-               status |= expire_reflog(ref, sha1, 0, &cb);
-               free(ref);
-       }
-       return status;
-}
-
-/*
- * main "reflog"
- */
-
-static const char reflog_usage[] =
-"git reflog (expire | ...)";
-
-int cmd_reflog(int argc, const char **argv, const char *prefix)
-{
-       /* With no command, we default to showing it. */
-       if (argc < 2 || *argv[1] == '-')
-               return cmd_log_reflog(argc, argv, prefix);
-
-       if (!strcmp(argv[1], "show"))
-               return cmd_log_reflog(argc - 1, argv + 1, prefix);
-
-       if (!strcmp(argv[1], "expire"))
-               return cmd_reflog_expire(argc - 1, argv + 1, prefix);
-
-       if (!strcmp(argv[1], "delete"))
-               return cmd_reflog_delete(argc - 1, argv + 1, prefix);
-
-       /* Not a recognized reflog command..*/
-       usage(reflog_usage);
-}
diff --git a/builtin-remote.c b/builtin-remote.c
deleted file mode 100644 (file)
index 2ed752c..0000000
+++ /dev/null
@@ -1,1334 +0,0 @@
-#include "cache.h"
-#include "parse-options.h"
-#include "transport.h"
-#include "remote.h"
-#include "string-list.h"
-#include "strbuf.h"
-#include "run-command.h"
-#include "refs.h"
-
-static const char * const builtin_remote_usage[] = {
-       "git remote [-v | --verbose]",
-       "git remote add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>",
-       "git remote rename <old> <new>",
-       "git remote rm <name>",
-       "git remote set-head <name> [-a | -d | <branch>]",
-       "git remote show [-n] <name>",
-       "git remote prune [-n | --dry-run] <name>",
-       "git remote [-v | --verbose] update [-p | --prune] [group]",
-       NULL
-};
-
-#define GET_REF_STATES (1<<0)
-#define GET_HEAD_NAMES (1<<1)
-#define GET_PUSH_REF_STATES (1<<2)
-
-static int verbose;
-
-static int show_all(void);
-static int prune_remote(const char *remote, int dry_run);
-
-static inline int postfixcmp(const char *string, const char *postfix)
-{
-       int len1 = strlen(string), len2 = strlen(postfix);
-       if (len1 < len2)
-               return 1;
-       return strcmp(string + len1 - len2, postfix);
-}
-
-static int opt_parse_track(const struct option *opt, const char *arg, int not)
-{
-       struct string_list *list = opt->value;
-       if (not)
-               string_list_clear(list, 0);
-       else
-               string_list_append(arg, list);
-       return 0;
-}
-
-static int fetch_remote(const char *name)
-{
-       const char *argv[] = { "fetch", name, NULL, NULL };
-       if (verbose) {
-               argv[1] = "-v";
-               argv[2] = name;
-       }
-       printf("Updating %s\n", name);
-       if (run_command_v_opt(argv, RUN_GIT_CMD))
-               return error("Could not fetch %s", name);
-       return 0;
-}
-
-static int add(int argc, const char **argv)
-{
-       int fetch = 0, mirror = 0;
-       struct string_list track = { NULL, 0, 0 };
-       const char *master = NULL;
-       struct remote *remote;
-       struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
-       const char *name, *url;
-       int i;
-
-       struct option options[] = {
-               OPT_GROUP("add specific options"),
-               OPT_BOOLEAN('f', "fetch", &fetch, "fetch the remote branches"),
-               OPT_CALLBACK('t', "track", &track, "branch",
-                       "branch(es) to track", opt_parse_track),
-               OPT_STRING('m', "master", &master, "branch", "master branch"),
-               OPT_BOOLEAN(0, "mirror", &mirror, "no separate remotes"),
-               OPT_END()
-       };
-
-       argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
-
-       if (argc < 2)
-               usage_with_options(builtin_remote_usage, options);
-
-       name = argv[0];
-       url = argv[1];
-
-       remote = remote_get(name);
-       if (remote && (remote->url_nr > 1 || strcmp(name, remote->url[0]) ||
-                       remote->fetch_refspec_nr))
-               die("remote %s already exists.", name);
-
-       strbuf_addf(&buf2, "refs/heads/test:refs/remotes/%s/test", name);
-       if (!valid_fetch_refspec(buf2.buf))
-               die("'%s' is not a valid remote name", name);
-
-       strbuf_addf(&buf, "remote.%s.url", name);
-       if (git_config_set(buf.buf, url))
-               return 1;
-
-       strbuf_reset(&buf);
-       strbuf_addf(&buf, "remote.%s.fetch", name);
-
-       if (track.nr == 0)
-               string_list_append("*", &track);
-       for (i = 0; i < track.nr; i++) {
-               struct string_list_item *item = track.items + i;
-
-               strbuf_reset(&buf2);
-               strbuf_addch(&buf2, '+');
-               if (mirror)
-                       strbuf_addf(&buf2, "refs/%s:refs/%s",
-                                       item->string, item->string);
-               else
-                       strbuf_addf(&buf2, "refs/heads/%s:refs/remotes/%s/%s",
-                                       item->string, name, item->string);
-               if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0))
-                       return 1;
-       }
-
-       if (mirror) {
-               strbuf_reset(&buf);
-               strbuf_addf(&buf, "remote.%s.mirror", name);
-               if (git_config_set(buf.buf, "true"))
-                       return 1;
-       }
-
-       if (fetch && fetch_remote(name))
-               return 1;
-
-       if (master) {
-               strbuf_reset(&buf);
-               strbuf_addf(&buf, "refs/remotes/%s/HEAD", name);
-
-               strbuf_reset(&buf2);
-               strbuf_addf(&buf2, "refs/remotes/%s/%s", name, master);
-
-               if (create_symref(buf.buf, buf2.buf, "remote add"))
-                       return error("Could not setup master '%s'", master);
-       }
-
-       strbuf_release(&buf);
-       strbuf_release(&buf2);
-       string_list_clear(&track, 0);
-
-       return 0;
-}
-
-struct branch_info {
-       char *remote_name;
-       struct string_list merge;
-       int rebase;
-};
-
-static struct string_list branch_list;
-
-static const char *abbrev_ref(const char *name, const char *prefix)
-{
-       const char *abbrev = skip_prefix(name, prefix);
-       if (abbrev)
-               return abbrev;
-       return name;
-}
-#define abbrev_branch(name) abbrev_ref((name), "refs/heads/")
-
-static int config_read_branches(const char *key, const char *value, void *cb)
-{
-       if (!prefixcmp(key, "branch.")) {
-               const char *orig_key = key;
-               char *name;
-               struct string_list_item *item;
-               struct branch_info *info;
-               enum { REMOTE, MERGE, REBASE } type;
-
-               key += 7;
-               if (!postfixcmp(key, ".remote")) {
-                       name = xstrndup(key, strlen(key) - 7);
-                       type = REMOTE;
-               } else if (!postfixcmp(key, ".merge")) {
-                       name = xstrndup(key, strlen(key) - 6);
-                       type = MERGE;
-               } else if (!postfixcmp(key, ".rebase")) {
-                       name = xstrndup(key, strlen(key) - 7);
-                       type = REBASE;
-               } else
-                       return 0;
-
-               item = string_list_insert(name, &branch_list);
-
-               if (!item->util)
-                       item->util = xcalloc(sizeof(struct branch_info), 1);
-               info = item->util;
-               if (type == REMOTE) {
-                       if (info->remote_name)
-                               warning("more than one %s", orig_key);
-                       info->remote_name = xstrdup(value);
-               } else if (type == MERGE) {
-                       char *space = strchr(value, ' ');
-                       value = abbrev_branch(value);
-                       while (space) {
-                               char *merge;
-                               merge = xstrndup(value, space - value);
-                               string_list_append(merge, &info->merge);
-                               value = abbrev_branch(space + 1);
-                               space = strchr(value, ' ');
-                       }
-                       string_list_append(xstrdup(value), &info->merge);
-               } else
-                       info->rebase = git_config_bool(orig_key, value);
-       }
-       return 0;
-}
-
-static void read_branches(void)
-{
-       if (branch_list.nr)
-               return;
-       git_config(config_read_branches, NULL);
-}
-
-struct ref_states {
-       struct remote *remote;
-       struct string_list new, stale, tracked, heads, push;
-       int queried;
-};
-
-static int handle_one_branch(const char *refname,
-       const unsigned char *sha1, int flags, void *cb_data)
-{
-       struct ref_states *states = cb_data;
-       struct refspec refspec;
-
-       memset(&refspec, 0, sizeof(refspec));
-       refspec.dst = (char *)refname;
-       if (!remote_find_tracking(states->remote, &refspec)) {
-               struct string_list_item *item;
-               const char *name = abbrev_branch(refspec.src);
-               /* symbolic refs pointing nowhere were handled already */
-               if ((flags & REF_ISSYMREF) ||
-                   string_list_has_string(&states->tracked, name) ||
-                   string_list_has_string(&states->new, name))
-                       return 0;
-               item = string_list_append(name, &states->stale);
-               item->util = xstrdup(refname);
-       }
-       return 0;
-}
-
-static int get_ref_states(const struct ref *remote_refs, struct ref_states *states)
-{
-       struct ref *fetch_map = NULL, **tail = &fetch_map;
-       struct ref *ref;
-       int i;
-
-       for (i = 0; i < states->remote->fetch_refspec_nr; i++)
-               if (get_fetch_map(remote_refs, states->remote->fetch + i, &tail, 1))
-                       die("Could not get fetch map for refspec %s",
-                               states->remote->fetch_refspec[i]);
-
-       states->new.strdup_strings = states->tracked.strdup_strings = 1;
-       for (ref = fetch_map; ref; ref = ref->next) {
-               unsigned char sha1[20];
-               if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1))
-                       string_list_append(abbrev_branch(ref->name), &states->new);
-               else
-                       string_list_append(abbrev_branch(ref->name), &states->tracked);
-       }
-       free_refs(fetch_map);
-
-       sort_string_list(&states->new);
-       sort_string_list(&states->tracked);
-       for_each_ref(handle_one_branch, states);
-       sort_string_list(&states->stale);
-
-       return 0;
-}
-
-struct push_info {
-       char *dest;
-       int forced;
-       enum {
-               PUSH_STATUS_CREATE = 0,
-               PUSH_STATUS_DELETE,
-               PUSH_STATUS_UPTODATE,
-               PUSH_STATUS_FASTFORWARD,
-               PUSH_STATUS_OUTOFDATE,
-               PUSH_STATUS_NOTQUERIED,
-       } status;
-};
-
-static int get_push_ref_states(const struct ref *remote_refs,
-       struct ref_states *states)
-{
-       struct remote *remote = states->remote;
-       struct ref *ref, *local_refs, *push_map, **push_tail;
-       if (remote->mirror)
-               return 0;
-
-       local_refs = get_local_heads();
-       ref = push_map = copy_ref_list(remote_refs);
-       while (ref->next)
-               ref = ref->next;
-       push_tail = &ref->next;
-
-       match_refs(local_refs, push_map, &push_tail, remote->push_refspec_nr,
-                  remote->push_refspec, MATCH_REFS_NONE);
-
-       states->push.strdup_strings = 1;
-       for (ref = push_map; ref; ref = ref->next) {
-               struct string_list_item *item;
-               struct push_info *info;
-
-               if (!ref->peer_ref)
-                       continue;
-               hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
-
-               item = string_list_append(abbrev_branch(ref->peer_ref->name),
-                                         &states->push);
-               item->util = xcalloc(sizeof(struct push_info), 1);
-               info = item->util;
-               info->forced = ref->force;
-               info->dest = xstrdup(abbrev_branch(ref->name));
-
-               if (is_null_sha1(ref->new_sha1)) {
-                       info->status = PUSH_STATUS_DELETE;
-               } else if (!hashcmp(ref->old_sha1, ref->new_sha1))
-                       info->status = PUSH_STATUS_UPTODATE;
-               else if (is_null_sha1(ref->old_sha1))
-                       info->status = PUSH_STATUS_CREATE;
-               else if (has_sha1_file(ref->old_sha1) &&
-                        ref_newer(ref->new_sha1, ref->old_sha1))
-                       info->status = PUSH_STATUS_FASTFORWARD;
-               else
-                       info->status = PUSH_STATUS_OUTOFDATE;
-       }
-       free_refs(local_refs);
-       free_refs(push_map);
-       return 0;
-}
-
-static int get_push_ref_states_noquery(struct ref_states *states)
-{
-       int i;
-       struct remote *remote = states->remote;
-       struct string_list_item *item;
-       struct push_info *info;
-
-       if (remote->mirror)
-               return 0;
-
-       states->push.strdup_strings = 1;
-       if (!remote->push_refspec_nr) {
-               item = string_list_append("(matching)", &states->push);
-               info = item->util = xcalloc(sizeof(struct push_info), 1);
-               info->status = PUSH_STATUS_NOTQUERIED;
-               info->dest = xstrdup(item->string);
-       }
-       for (i = 0; i < remote->push_refspec_nr; i++) {
-               struct refspec *spec = remote->push + i;
-               if (spec->matching)
-                       item = string_list_append("(matching)", &states->push);
-               else if (strlen(spec->src))
-                       item = string_list_append(spec->src, &states->push);
-               else
-                       item = string_list_append("(delete)", &states->push);
-
-               info = item->util = xcalloc(sizeof(struct push_info), 1);
-               info->forced = spec->force;
-               info->status = PUSH_STATUS_NOTQUERIED;
-               info->dest = xstrdup(spec->dst ? spec->dst : item->string);
-       }
-       return 0;
-}
-
-static int get_head_names(const struct ref *remote_refs, struct ref_states *states)
-{
-       struct ref *ref, *matches;
-       struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
-       struct refspec refspec;
-
-       refspec.force = 0;
-       refspec.pattern = 1;
-       refspec.src = refspec.dst = "refs/heads/*";
-       states->heads.strdup_strings = 1;
-       get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
-       matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
-                                   fetch_map, 1);
-       for(ref = matches; ref; ref = ref->next)
-               string_list_append(abbrev_branch(ref->name), &states->heads);
-
-       free_refs(fetch_map);
-       free_refs(matches);
-
-       return 0;
-}
-
-struct known_remote {
-       struct known_remote *next;
-       struct remote *remote;
-};
-
-struct known_remotes {
-       struct remote *to_delete;
-       struct known_remote *list;
-};
-
-static int add_known_remote(struct remote *remote, void *cb_data)
-{
-       struct known_remotes *all = cb_data;
-       struct known_remote *r;
-
-       if (!strcmp(all->to_delete->name, remote->name))
-               return 0;
-
-       r = xmalloc(sizeof(*r));
-       r->remote = remote;
-       r->next = all->list;
-       all->list = r;
-       return 0;
-}
-
-struct branches_for_remote {
-       struct remote *remote;
-       struct string_list *branches, *skipped;
-       struct known_remotes *keep;
-};
-
-static int add_branch_for_removal(const char *refname,
-       const unsigned char *sha1, int flags, void *cb_data)
-{
-       struct branches_for_remote *branches = cb_data;
-       struct refspec refspec;
-       struct string_list_item *item;
-       struct known_remote *kr;
-
-       memset(&refspec, 0, sizeof(refspec));
-       refspec.dst = (char *)refname;
-       if (remote_find_tracking(branches->remote, &refspec))
-               return 0;
-
-       /* don't delete a branch if another remote also uses it */
-       for (kr = branches->keep->list; kr; kr = kr->next) {
-               memset(&refspec, 0, sizeof(refspec));
-               refspec.dst = (char *)refname;
-               if (!remote_find_tracking(kr->remote, &refspec))
-                       return 0;
-       }
-
-       /* don't delete non-remote refs */
-       if (prefixcmp(refname, "refs/remotes")) {
-               /* advise user how to delete local branches */
-               if (!prefixcmp(refname, "refs/heads/"))
-                       string_list_append(abbrev_branch(refname),
-                                          branches->skipped);
-               /* silently skip over other non-remote refs */
-               return 0;
-       }
-
-       /* make sure that symrefs are deleted */
-       if (flags & REF_ISSYMREF)
-               return unlink(git_path("%s", refname));
-
-       item = string_list_append(refname, branches->branches);
-       item->util = xmalloc(20);
-       hashcpy(item->util, sha1);
-
-       return 0;
-}
-
-struct rename_info {
-       const char *old;
-       const char *new;
-       struct string_list *remote_branches;
-};
-
-static int read_remote_branches(const char *refname,
-       const unsigned char *sha1, int flags, void *cb_data)
-{
-       struct rename_info *rename = cb_data;
-       struct strbuf buf = STRBUF_INIT;
-       struct string_list_item *item;
-       int flag;
-       unsigned char orig_sha1[20];
-       const char *symref;
-
-       strbuf_addf(&buf, "refs/remotes/%s", rename->old);
-       if(!prefixcmp(refname, buf.buf)) {
-               item = string_list_append(xstrdup(refname), rename->remote_branches);
-               symref = resolve_ref(refname, orig_sha1, 1, &flag);
-               if (flag & REF_ISSYMREF)
-                       item->util = xstrdup(symref);
-               else
-                       item->util = NULL;
-       }
-
-       return 0;
-}
-
-static int migrate_file(struct remote *remote)
-{
-       struct strbuf buf = STRBUF_INIT;
-       int i;
-       char *path = NULL;
-
-       strbuf_addf(&buf, "remote.%s.url", remote->name);
-       for (i = 0; i < remote->url_nr; i++)
-               if (git_config_set_multivar(buf.buf, remote->url[i], "^$", 0))
-                       return error("Could not append '%s' to '%s'",
-                                       remote->url[i], buf.buf);
-       strbuf_reset(&buf);
-       strbuf_addf(&buf, "remote.%s.push", remote->name);
-       for (i = 0; i < remote->push_refspec_nr; i++)
-               if (git_config_set_multivar(buf.buf, remote->push_refspec[i], "^$", 0))
-                       return error("Could not append '%s' to '%s'",
-                                       remote->push_refspec[i], buf.buf);
-       strbuf_reset(&buf);
-       strbuf_addf(&buf, "remote.%s.fetch", remote->name);
-       for (i = 0; i < remote->fetch_refspec_nr; i++)
-               if (git_config_set_multivar(buf.buf, remote->fetch_refspec[i], "^$", 0))
-                       return error("Could not append '%s' to '%s'",
-                                       remote->fetch_refspec[i], buf.buf);
-       if (remote->origin == REMOTE_REMOTES)
-               path = git_path("remotes/%s", remote->name);
-       else if (remote->origin == REMOTE_BRANCHES)
-               path = git_path("branches/%s", remote->name);
-       if (path && unlink(path))
-               warning("failed to remove '%s'", path);
-       return 0;
-}
-
-static int mv(int argc, const char **argv)
-{
-       struct option options[] = {
-               OPT_END()
-       };
-       struct remote *oldremote, *newremote;
-       struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT;
-       struct string_list remote_branches = { NULL, 0, 0, 0 };
-       struct rename_info rename;
-       int i;
-
-       if (argc != 3)
-               usage_with_options(builtin_remote_usage, options);
-
-       rename.old = argv[1];
-       rename.new = argv[2];
-       rename.remote_branches = &remote_branches;
-
-       oldremote = remote_get(rename.old);
-       if (!oldremote)
-               die("No such remote: %s", rename.old);
-
-       if (!strcmp(rename.old, rename.new) && oldremote->origin != REMOTE_CONFIG)
-               return migrate_file(oldremote);
-
-       newremote = remote_get(rename.new);
-       if (newremote && (newremote->url_nr > 1 || newremote->fetch_refspec_nr))
-               die("remote %s already exists.", rename.new);
-
-       strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new);
-       if (!valid_fetch_refspec(buf.buf))
-               die("'%s' is not a valid remote name", rename.new);
-
-       strbuf_reset(&buf);
-       strbuf_addf(&buf, "remote.%s", rename.old);
-       strbuf_addf(&buf2, "remote.%s", rename.new);
-       if (git_config_rename_section(buf.buf, buf2.buf) < 1)
-               return error("Could not rename config section '%s' to '%s'",
-                               buf.buf, buf2.buf);
-
-       strbuf_reset(&buf);
-       strbuf_addf(&buf, "remote.%s.fetch", rename.new);
-       if (git_config_set_multivar(buf.buf, NULL, NULL, 1))
-               return error("Could not remove config section '%s'", buf.buf);
-       for (i = 0; i < oldremote->fetch_refspec_nr; i++) {
-               char *ptr;
-
-               strbuf_reset(&buf2);
-               strbuf_addstr(&buf2, oldremote->fetch_refspec[i]);
-               ptr = strstr(buf2.buf, rename.old);
-               if (ptr)
-                       strbuf_splice(&buf2, ptr-buf2.buf, strlen(rename.old),
-                                       rename.new, strlen(rename.new));
-               if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0))
-                       return error("Could not append '%s'", buf.buf);
-       }
-
-       read_branches();
-       for (i = 0; i < branch_list.nr; i++) {
-               struct string_list_item *item = branch_list.items + i;
-               struct branch_info *info = item->util;
-               if (info->remote_name && !strcmp(info->remote_name, rename.old)) {
-                       strbuf_reset(&buf);
-                       strbuf_addf(&buf, "branch.%s.remote", item->string);
-                       if (git_config_set(buf.buf, rename.new)) {
-                               return error("Could not set '%s'", buf.buf);
-                       }
-               }
-       }
-
-       /*
-        * First remove symrefs, then rename the rest, finally create
-        * the new symrefs.
-        */
-       for_each_ref(read_remote_branches, &rename);
-       for (i = 0; i < remote_branches.nr; i++) {
-               struct string_list_item *item = remote_branches.items + i;
-               int flag = 0;
-               unsigned char sha1[20];
-
-               resolve_ref(item->string, sha1, 1, &flag);
-               if (!(flag & REF_ISSYMREF))
-                       continue;
-               if (delete_ref(item->string, NULL, REF_NODEREF))
-                       die("deleting '%s' failed", item->string);
-       }
-       for (i = 0; i < remote_branches.nr; i++) {
-               struct string_list_item *item = remote_branches.items + i;
-
-               if (item->util)
-                       continue;
-               strbuf_reset(&buf);
-               strbuf_addstr(&buf, item->string);
-               strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old),
-                               rename.new, strlen(rename.new));
-               strbuf_reset(&buf2);
-               strbuf_addf(&buf2, "remote: renamed %s to %s",
-                               item->string, buf.buf);
-               if (rename_ref(item->string, buf.buf, buf2.buf))
-                       die("renaming '%s' failed", item->string);
-       }
-       for (i = 0; i < remote_branches.nr; i++) {
-               struct string_list_item *item = remote_branches.items + i;
-
-               if (!item->util)
-                       continue;
-               strbuf_reset(&buf);
-               strbuf_addstr(&buf, item->string);
-               strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old),
-                               rename.new, strlen(rename.new));
-               strbuf_reset(&buf2);
-               strbuf_addstr(&buf2, item->util);
-               strbuf_splice(&buf2, strlen("refs/remotes/"), strlen(rename.old),
-                               rename.new, strlen(rename.new));
-               strbuf_reset(&buf3);
-               strbuf_addf(&buf3, "remote: renamed %s to %s",
-                               item->string, buf.buf);
-               if (create_symref(buf.buf, buf2.buf, buf3.buf))
-                       die("creating '%s' failed", buf.buf);
-       }
-       return 0;
-}
-
-static int remove_branches(struct string_list *branches)
-{
-       int i, result = 0;
-       for (i = 0; i < branches->nr; i++) {
-               struct string_list_item *item = branches->items + i;
-               const char *refname = item->string;
-               unsigned char *sha1 = item->util;
-
-               if (delete_ref(refname, sha1, 0))
-                       result |= error("Could not remove branch %s", refname);
-       }
-       return result;
-}
-
-static int rm(int argc, const char **argv)
-{
-       struct option options[] = {
-               OPT_END()
-       };
-       struct remote *remote;
-       struct strbuf buf = STRBUF_INIT;
-       struct known_remotes known_remotes = { NULL, NULL };
-       struct string_list branches = { NULL, 0, 0, 1 };
-       struct string_list skipped = { NULL, 0, 0, 1 };
-       struct branches_for_remote cb_data = {
-               NULL, &branches, &skipped, &known_remotes
-       };
-       int i, result;
-
-       if (argc != 2)
-               usage_with_options(builtin_remote_usage, options);
-
-       remote = remote_get(argv[1]);
-       if (!remote)
-               die("No such remote: %s", argv[1]);
-
-       known_remotes.to_delete = remote;
-       for_each_remote(add_known_remote, &known_remotes);
-
-       strbuf_addf(&buf, "remote.%s", remote->name);
-       if (git_config_rename_section(buf.buf, NULL) < 1)
-               return error("Could not remove config section '%s'", buf.buf);
-
-       read_branches();
-       for (i = 0; i < branch_list.nr; i++) {
-               struct string_list_item *item = branch_list.items + i;
-               struct branch_info *info = item->util;
-               if (info->remote_name && !strcmp(info->remote_name, remote->name)) {
-                       const char *keys[] = { "remote", "merge", NULL }, **k;
-                       for (k = keys; *k; k++) {
-                               strbuf_reset(&buf);
-                               strbuf_addf(&buf, "branch.%s.%s",
-                                               item->string, *k);
-                               if (git_config_set(buf.buf, NULL)) {
-                                       strbuf_release(&buf);
-                                       return -1;
-                               }
-                       }
-               }
-       }
-
-       /*
-        * We cannot just pass a function to for_each_ref() which deletes
-        * the branches one by one, since for_each_ref() relies on cached
-        * refs, which are invalidated when deleting a branch.
-        */
-       cb_data.remote = remote;
-       result = for_each_ref(add_branch_for_removal, &cb_data);
-       strbuf_release(&buf);
-
-       if (!result)
-               result = remove_branches(&branches);
-       string_list_clear(&branches, 1);
-
-       if (skipped.nr) {
-               fprintf(stderr, skipped.nr == 1 ?
-                       "Note: A non-remote branch was not removed; "
-                       "to delete it, use:\n" :
-                       "Note: Non-remote branches were not removed; "
-                       "to delete them, use:\n");
-               for (i = 0; i < skipped.nr; i++)
-                       fprintf(stderr, "  git branch -d %s\n",
-                               skipped.items[i].string);
-       }
-       string_list_clear(&skipped, 0);
-
-       return result;
-}
-
-void clear_push_info(void *util, const char *string)
-{
-       struct push_info *info = util;
-       free(info->dest);
-       free(info);
-}
-
-static void free_remote_ref_states(struct ref_states *states)
-{
-       string_list_clear(&states->new, 0);
-       string_list_clear(&states->stale, 0);
-       string_list_clear(&states->tracked, 0);
-       string_list_clear(&states->heads, 0);
-       string_list_clear_func(&states->push, clear_push_info);
-}
-
-static int append_ref_to_tracked_list(const char *refname,
-       const unsigned char *sha1, int flags, void *cb_data)
-{
-       struct ref_states *states = cb_data;
-       struct refspec refspec;
-
-       if (flags & REF_ISSYMREF)
-               return 0;
-
-       memset(&refspec, 0, sizeof(refspec));
-       refspec.dst = (char *)refname;
-       if (!remote_find_tracking(states->remote, &refspec))
-               string_list_append(abbrev_branch(refspec.src), &states->tracked);
-
-       return 0;
-}
-
-static int get_remote_ref_states(const char *name,
-                                struct ref_states *states,
-                                int query)
-{
-       struct transport *transport;
-       const struct ref *remote_refs;
-
-       states->remote = remote_get(name);
-       if (!states->remote)
-               return error("No such remote: %s", name);
-
-       read_branches();
-
-       if (query) {
-               transport = transport_get(NULL, states->remote->url_nr > 0 ?
-                       states->remote->url[0] : NULL);
-               remote_refs = transport_get_remote_refs(transport);
-               transport_disconnect(transport);
-
-               states->queried = 1;
-               if (query & GET_REF_STATES)
-                       get_ref_states(remote_refs, states);
-               if (query & GET_HEAD_NAMES)
-                       get_head_names(remote_refs, states);
-               if (query & GET_PUSH_REF_STATES)
-                       get_push_ref_states(remote_refs, states);
-       } else {
-               for_each_ref(append_ref_to_tracked_list, states);
-               sort_string_list(&states->tracked);
-               get_push_ref_states_noquery(states);
-       }
-
-       return 0;
-}
-
-struct show_info {
-       struct string_list *list;
-       struct ref_states *states;
-       int width, width2;
-       int any_rebase;
-};
-
-int add_remote_to_show_info(struct string_list_item *item, void *cb_data)
-{
-       struct show_info *info = cb_data;
-       int n = strlen(item->string);
-       if (n > info->width)
-               info->width = n;
-       string_list_insert(item->string, info->list);
-       return 0;
-}
-
-int show_remote_info_item(struct string_list_item *item, void *cb_data)
-{
-       struct show_info *info = cb_data;
-       struct ref_states *states = info->states;
-       const char *name = item->string;
-
-       if (states->queried) {
-               const char *fmt = "%s";
-               const char *arg = "";
-               if (string_list_has_string(&states->new, name)) {
-                       fmt = " new (next fetch will store in remotes/%s)";
-                       arg = states->remote->name;
-               } else if (string_list_has_string(&states->tracked, name))
-                       arg = " tracked";
-               else if (string_list_has_string(&states->stale, name))
-                       arg = " stale (use 'git remote prune' to remove)";
-               else
-                       arg = " ???";
-               printf("    %-*s", info->width, name);
-               printf(fmt, arg);
-               printf("\n");
-       } else
-               printf("    %s\n", name);
-
-       return 0;
-}
-
-int add_local_to_show_info(struct string_list_item *branch_item, void *cb_data)
-{
-       struct show_info *show_info = cb_data;
-       struct ref_states *states = show_info->states;
-       struct branch_info *branch_info = branch_item->util;
-       struct string_list_item *item;
-       int n;
-
-       if (!branch_info->merge.nr || !branch_info->remote_name ||
-           strcmp(states->remote->name, branch_info->remote_name))
-               return 0;
-       if ((n = strlen(branch_item->string)) > show_info->width)
-               show_info->width = n;
-       if (branch_info->rebase)
-               show_info->any_rebase = 1;
-
-       item = string_list_insert(branch_item->string, show_info->list);
-       item->util = branch_info;
-
-       return 0;
-}
-
-int show_local_info_item(struct string_list_item *item, void *cb_data)
-{
-       struct show_info *show_info = cb_data;
-       struct branch_info *branch_info = item->util;
-       struct string_list *merge = &branch_info->merge;
-       const char *also;
-       int i;
-
-       if (branch_info->rebase && branch_info->merge.nr > 1) {
-               error("invalid branch.%s.merge; cannot rebase onto > 1 branch",
-                       item->string);
-               return 0;
-       }
-
-       printf("    %-*s ", show_info->width, item->string);
-       if (branch_info->rebase) {
-               printf("rebases onto remote %s\n", merge->items[0].string);
-               return 0;
-       } else if (show_info->any_rebase) {
-               printf(" merges with remote %s\n", merge->items[0].string);
-               also = "    and with remote";
-       } else {
-               printf("merges with remote %s\n", merge->items[0].string);
-               also = "   and with remote";
-       }
-       for (i = 1; i < merge->nr; i++)
-               printf("    %-*s %s %s\n", show_info->width, "", also,
-                      merge->items[i].string);
-
-       return 0;
-}
-
-int add_push_to_show_info(struct string_list_item *push_item, void *cb_data)
-{
-       struct show_info *show_info = cb_data;
-       struct push_info *push_info = push_item->util;
-       struct string_list_item *item;
-       int n;
-       if ((n = strlen(push_item->string)) > show_info->width)
-               show_info->width = n;
-       if ((n = strlen(push_info->dest)) > show_info->width2)
-               show_info->width2 = n;
-       item = string_list_append(push_item->string, show_info->list);
-       item->util = push_item->util;
-       return 0;
-}
-
-/*
- * Sorting comparison for a string list that has push_info
- * structs in its util field
- */
-static int cmp_string_with_push(const void *va, const void *vb)
-{
-       const struct string_list_item *a = va;
-       const struct string_list_item *b = vb;
-       const struct push_info *a_push = a->util;
-       const struct push_info *b_push = b->util;
-       int cmp = strcmp(a->string, b->string);
-       return cmp ? cmp : strcmp(a_push->dest, b_push->dest);
-}
-
-int show_push_info_item(struct string_list_item *item, void *cb_data)
-{
-       struct show_info *show_info = cb_data;
-       struct push_info *push_info = item->util;
-       char *src = item->string, *status = NULL;
-
-       switch (push_info->status) {
-       case PUSH_STATUS_CREATE:
-               status = "create";
-               break;
-       case PUSH_STATUS_DELETE:
-               status = "delete";
-               src = "(none)";
-               break;
-       case PUSH_STATUS_UPTODATE:
-               status = "up to date";
-               break;
-       case PUSH_STATUS_FASTFORWARD:
-               status = "fast forwardable";
-               break;
-       case PUSH_STATUS_OUTOFDATE:
-               status = "local out of date";
-               break;
-       case PUSH_STATUS_NOTQUERIED:
-               break;
-       }
-       if (status)
-               printf("    %-*s %s to %-*s (%s)\n", show_info->width, src,
-                       push_info->forced ? "forces" : "pushes",
-                       show_info->width2, push_info->dest, status);
-       else
-               printf("    %-*s %s to %s\n", show_info->width, src,
-                       push_info->forced ? "forces" : "pushes",
-                       push_info->dest);
-       return 0;
-}
-
-static int show(int argc, const char **argv)
-{
-       int no_query = 0, result = 0, query_flag = 0;
-       struct option options[] = {
-               OPT_GROUP("show specific options"),
-               OPT_BOOLEAN('n', NULL, &no_query, "do not query remotes"),
-               OPT_END()
-       };
-       struct ref_states states;
-       struct string_list info_list = { NULL, 0, 0, 0 };
-       struct show_info info;
-
-       argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
-
-       if (argc < 1)
-               return show_all();
-
-       if (!no_query)
-               query_flag = (GET_REF_STATES | GET_HEAD_NAMES | GET_PUSH_REF_STATES);
-
-       memset(&states, 0, sizeof(states));
-       memset(&info, 0, sizeof(info));
-       info.states = &states;
-       info.list = &info_list;
-       for (; argc; argc--, argv++) {
-               int i;
-
-               get_remote_ref_states(*argv, &states, query_flag);
-
-               printf("* remote %s\n  URL: %s\n", *argv,
-                       states.remote->url_nr > 0 ?
-                               states.remote->url[0] : "(no URL)");
-               if (no_query)
-                       printf("  HEAD branch: (not queried)\n");
-               else if (!states.heads.nr)
-                       printf("  HEAD branch: (unknown)\n");
-               else if (states.heads.nr == 1)
-                       printf("  HEAD branch: %s\n", states.heads.items[0].string);
-               else {
-                       printf("  HEAD branch (remote HEAD is ambiguous,"
-                              " may be one of the following):\n");
-                       for (i = 0; i < states.heads.nr; i++)
-                               printf("    %s\n", states.heads.items[i].string);
-               }
-
-               /* remote branch info */
-               info.width = 0;
-               for_each_string_list(add_remote_to_show_info, &states.new, &info);
-               for_each_string_list(add_remote_to_show_info, &states.tracked, &info);
-               for_each_string_list(add_remote_to_show_info, &states.stale, &info);
-               if (info.list->nr)
-                       printf("  Remote branch%s:%s\n",
-                              info.list->nr > 1 ? "es" : "",
-                               no_query ? " (status not queried)" : "");
-               for_each_string_list(show_remote_info_item, info.list, &info);
-               string_list_clear(info.list, 0);
-
-               /* git pull info */
-               info.width = 0;
-               info.any_rebase = 0;
-               for_each_string_list(add_local_to_show_info, &branch_list, &info);
-               if (info.list->nr)
-                       printf("  Local branch%s configured for 'git pull':\n",
-                              info.list->nr > 1 ? "es" : "");
-               for_each_string_list(show_local_info_item, info.list, &info);
-               string_list_clear(info.list, 0);
-
-               /* git push info */
-               if (states.remote->mirror)
-                       printf("  Local refs will be mirrored by 'git push'\n");
-
-               info.width = info.width2 = 0;
-               for_each_string_list(add_push_to_show_info, &states.push, &info);
-               qsort(info.list->items, info.list->nr,
-                       sizeof(*info.list->items), cmp_string_with_push);
-               if (info.list->nr)
-                       printf("  Local ref%s configured for 'git push'%s:\n",
-                               info.list->nr > 1 ? "s" : "",
-                               no_query ? " (status not queried)" : "");
-               for_each_string_list(show_push_info_item, info.list, &info);
-               string_list_clear(info.list, 0);
-
-               free_remote_ref_states(&states);
-       }
-
-       return result;
-}
-
-static int set_head(int argc, const char **argv)
-{
-       int i, opt_a = 0, opt_d = 0, result = 0;
-       struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
-       char *head_name = NULL;
-
-       struct option options[] = {
-               OPT_GROUP("set-head specific options"),
-               OPT_BOOLEAN('a', "auto", &opt_a,
-                           "set refs/remotes/<name>/HEAD according to remote"),
-               OPT_BOOLEAN('d', "delete", &opt_d,
-                           "delete refs/remotes/<name>/HEAD"),
-               OPT_END()
-       };
-       argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
-       if (argc)
-               strbuf_addf(&buf, "refs/remotes/%s/HEAD", argv[0]);
-
-       if (!opt_a && !opt_d && argc == 2) {
-               head_name = xstrdup(argv[1]);
-       } else if (opt_a && !opt_d && argc == 1) {
-               struct ref_states states;
-               memset(&states, 0, sizeof(states));
-               get_remote_ref_states(argv[0], &states, GET_HEAD_NAMES);
-               if (!states.heads.nr)
-                       result |= error("Cannot determine remote HEAD");
-               else if (states.heads.nr > 1) {
-                       result |= error("Multiple remote HEAD branches. "
-                                       "Please choose one explicitly with:");
-                       for (i = 0; i < states.heads.nr; i++)
-                               fprintf(stderr, "  git remote set-head %s %s\n",
-                                       argv[0], states.heads.items[i].string);
-               } else
-                       head_name = xstrdup(states.heads.items[0].string);
-               free_remote_ref_states(&states);
-       } else if (opt_d && !opt_a && argc == 1) {
-               if (delete_ref(buf.buf, NULL, REF_NODEREF))
-                       result |= error("Could not delete %s", buf.buf);
-       } else
-               usage_with_options(builtin_remote_usage, options);
-
-       if (head_name) {
-               unsigned char sha1[20];
-               strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name);
-               /* make sure it's valid */
-               if (!resolve_ref(buf2.buf, sha1, 1, NULL))
-                       result |= error("Not a valid ref: %s", buf2.buf);
-               else if (create_symref(buf.buf, buf2.buf, "remote set-head"))
-                       result |= error("Could not setup %s", buf.buf);
-               if (opt_a)
-                       printf("%s/HEAD set to %s\n", argv[0], head_name);
-               free(head_name);
-       }
-
-       strbuf_release(&buf);
-       strbuf_release(&buf2);
-       return result;
-}
-
-static int prune(int argc, const char **argv)
-{
-       int dry_run = 0, result = 0;
-       struct option options[] = {
-               OPT_GROUP("prune specific options"),
-               OPT__DRY_RUN(&dry_run),
-               OPT_END()
-       };
-
-       argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
-
-       if (argc < 1)
-               usage_with_options(builtin_remote_usage, options);
-
-       for (; argc; argc--, argv++)
-               result |= prune_remote(*argv, dry_run);
-
-       return result;
-}
-
-static int prune_remote(const char *remote, int dry_run)
-{
-       int result = 0, i;
-       struct ref_states states;
-       const char *dangling_msg = dry_run
-               ? " %s will become dangling!\n"
-               : " %s has become dangling!\n";
-
-       memset(&states, 0, sizeof(states));
-       get_remote_ref_states(remote, &states, GET_REF_STATES);
-
-       if (states.stale.nr) {
-               printf("Pruning %s\n", remote);
-               printf("URL: %s\n",
-                      states.remote->url_nr
-                      ? states.remote->url[0]
-                      : "(no URL)");
-       }
-
-       for (i = 0; i < states.stale.nr; i++) {
-               const char *refname = states.stale.items[i].util;
-
-               if (!dry_run)
-                       result |= delete_ref(refname, NULL, 0);
-
-               printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
-                      abbrev_ref(refname, "refs/remotes/"));
-               warn_dangling_symref(dangling_msg, refname);
-       }
-
-       free_remote_ref_states(&states);
-       return result;
-}
-
-static int get_one_remote_for_update(struct remote *remote, void *priv)
-{
-       struct string_list *list = priv;
-       if (!remote->skip_default_update)
-               string_list_append(remote->name, list);
-       return 0;
-}
-
-struct remote_group {
-       const char *name;
-       struct string_list *list;
-} remote_group;
-
-static int get_remote_group(const char *key, const char *value, void *num_hits)
-{
-       if (!prefixcmp(key, "remotes.") &&
-                       !strcmp(key + 8, remote_group.name)) {
-               /* split list by white space */
-               int space = strcspn(value, " \t\n");
-               while (*value) {
-                       if (space > 1) {
-                               string_list_append(xstrndup(value, space),
-                                               remote_group.list);
-                               ++*((int *)num_hits);
-                       }
-                       value += space + (value[space] != '\0');
-                       space = strcspn(value, " \t\n");
-               }
-       }
-
-       return 0;
-}
-
-static int update(int argc, const char **argv)
-{
-       int i, result = 0, prune = 0;
-       struct string_list list = { NULL, 0, 0, 0 };
-       static const char *default_argv[] = { NULL, "default", NULL };
-       struct option options[] = {
-               OPT_GROUP("update specific options"),
-               OPT_BOOLEAN('p', "prune", &prune,
-                           "prune remotes after fetching"),
-               OPT_END()
-       };
-
-       argc = parse_options(argc, argv, options, builtin_remote_usage,
-                            PARSE_OPT_KEEP_ARGV0);
-       if (argc < 2) {
-               argc = 2;
-               argv = default_argv;
-       }
-
-       remote_group.list = &list;
-       for (i = 1; i < argc; i++) {
-               int groups_found = 0;
-               remote_group.name = argv[i];
-               result = git_config(get_remote_group, &groups_found);
-               if (!groups_found && (i != 1 || strcmp(argv[1], "default"))) {
-                       struct remote *remote;
-                       if (!remote_is_configured(argv[i]))
-                               die("No such remote or remote group: %s",
-                                   argv[i]);
-                       remote = remote_get(argv[i]);
-                       string_list_append(remote->name, remote_group.list);
-               }
-       }
-
-       if (!result && !list.nr  && argc == 2 && !strcmp(argv[1], "default"))
-               result = for_each_remote(get_one_remote_for_update, &list);
-
-       for (i = 0; i < list.nr; i++) {
-               int err = fetch_remote(list.items[i].string);
-               result |= err;
-               if (!err && prune)
-                       result |= prune_remote(list.items[i].string, 0);
-       }
-
-       /* all names were strdup()ed or strndup()ed */
-       list.strdup_strings = 1;
-       string_list_clear(&list, 0);
-
-       return result;
-}
-
-static int get_one_entry(struct remote *remote, void *priv)
-{
-       struct string_list *list = priv;
-
-       if (remote->url_nr > 0) {
-               int i;
-
-               for (i = 0; i < remote->url_nr; i++)
-                       string_list_append(remote->name, list)->util = (void *)remote->url[i];
-       } else
-               string_list_append(remote->name, list)->util = NULL;
-
-       return 0;
-}
-
-static int show_all(void)
-{
-       struct string_list list = { NULL, 0, 0 };
-       int result = for_each_remote(get_one_entry, &list);
-
-       if (!result) {
-               int i;
-
-               sort_string_list(&list);
-               for (i = 0; i < list.nr; i++) {
-                       struct string_list_item *item = list.items + i;
-                       if (verbose)
-                               printf("%s\t%s\n", item->string,
-                                       item->util ? (const char *)item->util : "");
-                       else {
-                               if (i && !strcmp((item - 1)->string, item->string))
-                                       continue;
-                               printf("%s\n", item->string);
-                       }
-               }
-       }
-       return result;
-}
-
-int cmd_remote(int argc, const char **argv, const char *prefix)
-{
-       struct option options[] = {
-               OPT__VERBOSE(&verbose),
-               OPT_END()
-       };
-       int result;
-
-       argc = parse_options(argc, argv, options, builtin_remote_usage,
-               PARSE_OPT_STOP_AT_NON_OPTION);
-
-       if (argc < 1)
-               result = show_all();
-       else if (!strcmp(argv[0], "add"))
-               result = add(argc, argv);
-       else if (!strcmp(argv[0], "rename"))
-               result = mv(argc, argv);
-       else if (!strcmp(argv[0], "rm"))
-               result = rm(argc, argv);
-       else if (!strcmp(argv[0], "set-head"))
-               result = set_head(argc, argv);
-       else if (!strcmp(argv[0], "show"))
-               result = show(argc, argv);
-       else if (!strcmp(argv[0], "prune"))
-               result = prune(argc, argv);
-       else if (!strcmp(argv[0], "update"))
-               result = update(argc, argv);
-       else {
-               error("Unknown subcommand: %s", argv[0]);
-               usage_with_options(builtin_remote_usage, options);
-       }
-
-       return result ? 1 : 0;
-}
diff --git a/builtin-rerere.c b/builtin-rerere.c
deleted file mode 100644 (file)
index 020af73..0000000
+++ /dev/null
@@ -1,136 +0,0 @@
-#include "builtin.h"
-#include "cache.h"
-#include "dir.h"
-#include "string-list.h"
-#include "rerere.h"
-#include "xdiff/xdiff.h"
-#include "xdiff-interface.h"
-
-static const char git_rerere_usage[] =
-"git rerere [clear | status | diff | gc]";
-
-/* these values are days */
-static int cutoff_noresolve = 15;
-static int cutoff_resolve = 60;
-
-static time_t rerere_created_at(const char *name)
-{
-       struct stat st;
-       return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
-}
-
-static void unlink_rr_item(const char *name)
-{
-       unlink(rerere_path(name, "thisimage"));
-       unlink(rerere_path(name, "preimage"));
-       unlink(rerere_path(name, "postimage"));
-       rmdir(git_path("rr-cache/%s", name));
-}
-
-static int git_rerere_gc_config(const char *var, const char *value, void *cb)
-{
-       if (!strcmp(var, "gc.rerereresolved"))
-               cutoff_resolve = git_config_int(var, value);
-       else if (!strcmp(var, "gc.rerereunresolved"))
-               cutoff_noresolve = git_config_int(var, value);
-       else
-               return git_default_config(var, value, cb);
-       return 0;
-}
-
-static void garbage_collect(struct string_list *rr)
-{
-       struct string_list to_remove = { NULL, 0, 0, 1 };
-       DIR *dir;
-       struct dirent *e;
-       int i, cutoff;
-       time_t now = time(NULL), then;
-
-       git_config(git_rerere_gc_config, NULL);
-       dir = opendir(git_path("rr-cache"));
-       while ((e = readdir(dir))) {
-               if (is_dot_or_dotdot(e->d_name))
-                       continue;
-               then = rerere_created_at(e->d_name);
-               if (!then)
-                       continue;
-               cutoff = (has_rerere_resolution(e->d_name)
-                         ? cutoff_resolve : cutoff_noresolve);
-               if (then < now - cutoff * 86400)
-                       string_list_append(e->d_name, &to_remove);
-       }
-       for (i = 0; i < to_remove.nr; i++)
-               unlink_rr_item(to_remove.items[i].string);
-       string_list_clear(&to_remove, 0);
-}
-
-static int outf(void *dummy, mmbuffer_t *ptr, int nbuf)
-{
-       int i;
-       for (i = 0; i < nbuf; i++)
-               if (write_in_full(1, ptr[i].ptr, ptr[i].size) != ptr[i].size)
-                       return -1;
-       return 0;
-}
-
-static int diff_two(const char *file1, const char *label1,
-               const char *file2, const char *label2)
-{
-       xpparam_t xpp;
-       xdemitconf_t xecfg;
-       xdemitcb_t ecb;
-       mmfile_t minus, plus;
-
-       if (read_mmfile(&minus, file1) || read_mmfile(&plus, file2))
-               return 1;
-
-       printf("--- a/%s\n+++ b/%s\n", label1, label2);
-       fflush(stdout);
-       memset(&xpp, 0, sizeof(xpp));
-       xpp.flags = XDF_NEED_MINIMAL;
-       memset(&xecfg, 0, sizeof(xecfg));
-       xecfg.ctxlen = 3;
-       ecb.outf = outf;
-       xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb);
-
-       free(minus.ptr);
-       free(plus.ptr);
-       return 0;
-}
-
-int cmd_rerere(int argc, const char **argv, const char *prefix)
-{
-       struct string_list merge_rr = { NULL, 0, 0, 1 };
-       int i, fd;
-
-       if (argc < 2)
-               return rerere();
-
-       fd = setup_rerere(&merge_rr);
-       if (fd < 0)
-               return 0;
-
-       if (!strcmp(argv[1], "clear")) {
-               for (i = 0; i < merge_rr.nr; i++) {
-                       const char *name = (const char *)merge_rr.items[i].util;
-                       if (!has_rerere_resolution(name))
-                               unlink_rr_item(name);
-               }
-               unlink(git_path("rr-cache/MERGE_RR"));
-       } else if (!strcmp(argv[1], "gc"))
-               garbage_collect(&merge_rr);
-       else if (!strcmp(argv[1], "status"))
-               for (i = 0; i < merge_rr.nr; i++)
-                       printf("%s\n", merge_rr.items[i].string);
-       else if (!strcmp(argv[1], "diff"))
-               for (i = 0; i < merge_rr.nr; i++) {
-                       const char *path = merge_rr.items[i].string;
-                       const char *name = (const char *)merge_rr.items[i].util;
-                       diff_two(rerere_path(name, "preimage"), path, path, path);
-               }
-       else
-               usage(git_rerere_usage);
-
-       string_list_clear(&merge_rr, 1);
-       return 0;
-}
diff --git a/builtin-reset.c b/builtin-reset.c
deleted file mode 100644 (file)
index 7e7ebab..0000000
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
- * "git reset" builtin command
- *
- * Copyright (c) 2007 Carlos Rica
- *
- * Based on git-reset.sh, which is
- *
- * Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano
- */
-#include "cache.h"
-#include "tag.h"
-#include "object.h"
-#include "commit.h"
-#include "run-command.h"
-#include "refs.h"
-#include "diff.h"
-#include "diffcore.h"
-#include "tree.h"
-#include "branch.h"
-#include "parse-options.h"
-
-static const char * const git_reset_usage[] = {
-       "git reset [--mixed | --soft | --hard | --merge] [-q] [<commit>]",
-       "git reset [--mixed] <commit> [--] <paths>...",
-       NULL
-};
-
-enum reset_type { MIXED, SOFT, HARD, MERGE, NONE };
-static const char *reset_type_names[] = { "mixed", "soft", "hard", "merge", NULL };
-
-static char *args_to_str(const char **argv)
-{
-       char *buf = NULL;
-       unsigned long len, space = 0, nr = 0;
-
-       for (; *argv; argv++) {
-               len = strlen(*argv);
-               ALLOC_GROW(buf, nr + 1 + len, space);
-               if (nr)
-                       buf[nr++] = ' ';
-               memcpy(buf + nr, *argv, len);
-               nr += len;
-       }
-       ALLOC_GROW(buf, nr + 1, space);
-       buf[nr] = '\0';
-
-       return buf;
-}
-
-static inline int is_merge(void)
-{
-       return !access(git_path("MERGE_HEAD"), F_OK);
-}
-
-static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet)
-{
-       int i = 0;
-       const char *args[6];
-
-       args[i++] = "read-tree";
-       if (!quiet)
-               args[i++] = "-v";
-       switch (reset_type) {
-       case MERGE:
-               args[i++] = "-u";
-               args[i++] = "-m";
-               break;
-       case HARD:
-               args[i++] = "-u";
-               /* fallthrough */
-       default:
-               args[i++] = "--reset";
-       }
-       args[i++] = sha1_to_hex(sha1);
-       args[i] = NULL;
-
-       return run_command_v_opt(args, RUN_GIT_CMD);
-}
-
-static void print_new_head_line(struct commit *commit)
-{
-       const char *hex, *body;
-
-       hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
-       printf("HEAD is now at %s", hex);
-       body = strstr(commit->buffer, "\n\n");
-       if (body) {
-               const char *eol;
-               size_t len;
-               body += 2;
-               eol = strchr(body, '\n');
-               len = eol ? eol - body : strlen(body);
-               printf(" %.*s\n", (int) len, body);
-       }
-       else
-               printf("\n");
-}
-
-static int update_index_refresh(int fd, struct lock_file *index_lock, int flags)
-{
-       int result;
-
-       if (!index_lock) {
-               index_lock = xcalloc(1, sizeof(struct lock_file));
-               fd = hold_locked_index(index_lock, 1);
-       }
-
-       if (read_cache() < 0)
-               return error("Could not read index");
-
-       result = refresh_cache(flags) ? 1 : 0;
-       if (write_cache(fd, active_cache, active_nr) ||
-                       commit_locked_index(index_lock))
-               return error ("Could not refresh index");
-       return result;
-}
-
-static void update_index_from_diff(struct diff_queue_struct *q,
-               struct diff_options *opt, void *data)
-{
-       int i;
-       int *discard_flag = data;
-
-       /* do_diff_cache() mangled the index */
-       discard_cache();
-       *discard_flag = 1;
-       read_cache();
-
-       for (i = 0; i < q->nr; i++) {
-               struct diff_filespec *one = q->queue[i]->one;
-               if (one->mode) {
-                       struct cache_entry *ce;
-                       ce = make_cache_entry(one->mode, one->sha1, one->path,
-                               0, 0);
-                       if (!ce)
-                               die("make_cache_entry failed for path '%s'",
-                                   one->path);
-                       add_cache_entry(ce, ADD_CACHE_OK_TO_ADD |
-                               ADD_CACHE_OK_TO_REPLACE);
-               } else
-                       remove_file_from_cache(one->path);
-       }
-}
-
-static int read_from_tree(const char *prefix, const char **argv,
-               unsigned char *tree_sha1, int refresh_flags)
-{
-       struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
-       int index_fd, index_was_discarded = 0;
-       struct diff_options opt;
-
-       memset(&opt, 0, sizeof(opt));
-       diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt);
-       opt.output_format = DIFF_FORMAT_CALLBACK;
-       opt.format_callback = update_index_from_diff;
-       opt.format_callback_data = &index_was_discarded;
-
-       index_fd = hold_locked_index(lock, 1);
-       index_was_discarded = 0;
-       read_cache();
-       if (do_diff_cache(tree_sha1, &opt))
-               return 1;
-       diffcore_std(&opt);
-       diff_flush(&opt);
-       diff_tree_release_paths(&opt);
-
-       if (!index_was_discarded)
-               /* The index is still clobbered from do_diff_cache() */
-               discard_cache();
-       return update_index_refresh(index_fd, lock, refresh_flags);
-}
-
-static void prepend_reflog_action(const char *action, char *buf, size_t size)
-{
-       const char *sep = ": ";
-       const char *rla = getenv("GIT_REFLOG_ACTION");
-       if (!rla)
-               rla = sep = "";
-       if (snprintf(buf, size, "%s%s%s", rla, sep, action) >= size)
-               warning("Reflog action message too long: %.*s...", 50, buf);
-}
-
-int cmd_reset(int argc, const char **argv, const char *prefix)
-{
-       int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;
-       const char *rev = "HEAD";
-       unsigned char sha1[20], *orig = NULL, sha1_orig[20],
-                               *old_orig = NULL, sha1_old_orig[20];
-       struct commit *commit;
-       char *reflog_action, msg[1024];
-       const struct option options[] = {
-               OPT_SET_INT(0, "mixed", &reset_type,
-                                               "reset HEAD and index", MIXED),
-               OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT),
-               OPT_SET_INT(0, "hard", &reset_type,
-                               "reset HEAD, index and working tree", HARD),
-               OPT_SET_INT(0, "merge", &reset_type,
-                               "reset HEAD, index and working tree", MERGE),
-               OPT_BOOLEAN('q', NULL, &quiet,
-                               "disable showing new HEAD in hard reset and progress message"),
-               OPT_END()
-       };
-
-       git_config(git_default_config, NULL);
-
-       argc = parse_options(argc, argv, options, git_reset_usage,
-                                               PARSE_OPT_KEEP_DASHDASH);
-       reflog_action = args_to_str(argv);
-       setenv("GIT_REFLOG_ACTION", reflog_action, 0);
-
-       /*
-        * Possible arguments are:
-        *
-        * git reset [-opts] <rev> <paths>...
-        * git reset [-opts] <rev> -- <paths>...
-        * git reset [-opts] -- <paths>...
-        * git reset [-opts] <paths>...
-        *
-        * At this point, argv[i] points immediately after [-opts].
-        */
-
-       if (i < argc) {
-               if (!strcmp(argv[i], "--")) {
-                       i++; /* reset to HEAD, possibly with paths */
-               } else if (i + 1 < argc && !strcmp(argv[i+1], "--")) {
-                       rev = argv[i];
-                       i += 2;
-               }
-               /*
-                * Otherwise, argv[i] could be either <rev> or <paths> and
-                * has to be unambiguous.
-                */
-               else if (!get_sha1(argv[i], sha1)) {
-                       /*
-                        * Ok, argv[i] looks like a rev; it should not
-                        * be a filename.
-                        */
-                       verify_non_filename(prefix, argv[i]);
-                       rev = argv[i++];
-               } else {
-                       /* Otherwise we treat this as a filename */
-                       verify_filename(prefix, argv[i]);
-               }
-       }
-
-       if (get_sha1(rev, sha1))
-               die("Failed to resolve '%s' as a valid ref.", rev);
-
-       commit = lookup_commit_reference(sha1);
-       if (!commit)
-               die("Could not parse object '%s'.", rev);
-       hashcpy(sha1, commit->object.sha1);
-
-       /* git reset tree [--] paths... can be used to
-        * load chosen paths from the tree into the index without
-        * affecting the working tree nor HEAD. */
-       if (i < argc) {
-               if (reset_type == MIXED)
-                       warning("--mixed option is deprecated with paths.");
-               else if (reset_type != NONE)
-                       die("Cannot do %s reset with paths.",
-                                       reset_type_names[reset_type]);
-               return read_from_tree(prefix, argv + i, sha1,
-                               quiet ? REFRESH_QUIET : REFRESH_SAY_CHANGED);
-       }
-       if (reset_type == NONE)
-               reset_type = MIXED; /* by default */
-
-       if (reset_type == HARD && is_bare_repository())
-               die("hard reset makes no sense in a bare repository");
-
-       /* 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 (reset_type == SOFT) {
-               if (is_merge() || read_cache() < 0 || unmerged_cache())
-                       die("Cannot do a soft reset in the middle of a merge.");
-       }
-       else if (reset_index_file(sha1, reset_type, quiet))
-               die("Could not reset index file to revision '%s'.", rev);
-
-       /* Any resets update HEAD to the head being switched to,
-        * saving the previous head in ORIG_HEAD before. */
-       if (!get_sha1("ORIG_HEAD", sha1_old_orig))
-               old_orig = sha1_old_orig;
-       if (!get_sha1("HEAD", sha1_orig)) {
-               orig = sha1_orig;
-               prepend_reflog_action("updating ORIG_HEAD", msg, sizeof(msg));
-               update_ref(msg, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR);
-       }
-       else if (old_orig)
-               delete_ref("ORIG_HEAD", old_orig, 0);
-       prepend_reflog_action("updating HEAD", msg, sizeof(msg));
-       update_ref_status = update_ref(msg, "HEAD", sha1, orig, 0, MSG_ON_ERR);
-
-       switch (reset_type) {
-       case HARD:
-               if (!update_ref_status && !quiet)
-                       print_new_head_line(commit);
-               break;
-       case SOFT: /* Nothing else to do. */
-               break;
-       case MIXED: /* Report what has not been updated. */
-               update_index_refresh(0, NULL,
-                               quiet ? REFRESH_QUIET : REFRESH_SAY_CHANGED);
-               break;
-       }
-
-       remove_branch_state();
-
-       free(reflog_action);
-
-       return update_ref_status;
-}
diff --git a/builtin-rev-list.c b/builtin-rev-list.c
deleted file mode 100644 (file)
index 38a8f23..0000000
+++ /dev/null
@@ -1,399 +0,0 @@
-#include "cache.h"
-#include "commit.h"
-#include "diff.h"
-#include "revision.h"
-#include "list-objects.h"
-#include "builtin.h"
-#include "log-tree.h"
-#include "graph.h"
-#include "bisect.h"
-
-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"
-"    --branches\n"
-"    --tags\n"
-"    --remotes\n"
-"    --stdin\n"
-"    --quiet\n"
-"  ordering output:\n"
-"    --topo-order\n"
-"    --date-order\n"
-"    --reverse\n"
-"  formatting output:\n"
-"    --parents\n"
-"    --children\n"
-"    --objects | --objects-edge\n"
-"    --unpacked\n"
-"    --header | --pretty\n"
-"    --abbrev=nr | --no-abbrev\n"
-"    --abbrev-commit\n"
-"    --left-right\n"
-"  special purpose:\n"
-"    --bisect\n"
-"    --bisect-vars\n"
-"    --bisect-all"
-;
-
-static void finish_commit(struct commit *commit, void *data);
-static void show_commit(struct commit *commit, void *data)
-{
-       struct rev_list_info *info = data;
-       struct rev_info *revs = info->revs;
-
-       graph_show_commit(revs->graph);
-
-       if (info->show_timestamp)
-               printf("%lu ", commit->date);
-       if (info->header_prefix)
-               fputs(info->header_prefix, stdout);
-
-       if (!revs->graph) {
-               if (commit->object.flags & BOUNDARY)
-                       putchar('-');
-               else if (commit->object.flags & UNINTERESTING)
-                       putchar('^');
-               else if (revs->left_right) {
-                       if (commit->object.flags & SYMMETRIC_LEFT)
-                               putchar('<');
-                       else
-                               putchar('>');
-               }
-       }
-       if (revs->abbrev_commit && revs->abbrev)
-               fputs(find_unique_abbrev(commit->object.sha1, revs->abbrev),
-                     stdout);
-       else
-               fputs(sha1_to_hex(commit->object.sha1), stdout);
-       if (revs->print_parents) {
-               struct commit_list *parents = commit->parents;
-               while (parents) {
-                       printf(" %s", sha1_to_hex(parents->item->object.sha1));
-                       parents = parents->next;
-               }
-       }
-       if (revs->children.name) {
-               struct commit_list *children;
-
-               children = lookup_decoration(&revs->children, &commit->object);
-               while (children) {
-                       printf(" %s", sha1_to_hex(children->item->object.sha1));
-                       children = children->next;
-               }
-       }
-       show_decorations(revs, commit);
-       if (revs->commit_format == CMIT_FMT_ONELINE)
-               putchar(' ');
-       else
-               putchar('\n');
-
-       if (revs->verbose_header && commit->buffer) {
-               struct strbuf buf = STRBUF_INIT;
-               pretty_print_commit(revs->commit_format, commit,
-                                   &buf, revs->abbrev, NULL, NULL,
-                                   revs->date_mode, 0);
-               if (revs->graph) {
-                       if (buf.len) {
-                               if (revs->commit_format != CMIT_FMT_ONELINE)
-                                       graph_show_oneline(revs->graph);
-
-                               graph_show_commit_msg(revs->graph, &buf);
-
-                               /*
-                                * Add a newline after the commit message.
-                                *
-                                * Usually, this newline produces a blank
-                                * padding line between entries, in which case
-                                * we need to add graph padding on this line.
-                                *
-                                * However, the commit message may not end in a
-                                * newline.  In this case the newline simply
-                                * ends the last line of the commit message,
-                                * and we don't need any graph output.  (This
-                                * always happens with CMIT_FMT_ONELINE, and it
-                                * happens with CMIT_FMT_USERFORMAT when the
-                                * format doesn't explicitly end in a newline.)
-                                */
-                               if (buf.len && buf.buf[buf.len - 1] == '\n')
-                                       graph_show_padding(revs->graph);
-                               putchar('\n');
-                       } else {
-                               /*
-                                * If the message buffer is empty, just show
-                                * the rest of the graph output for this
-                                * commit.
-                                */
-                               if (graph_show_remainder(revs->graph))
-                                       putchar('\n');
-                       }
-               } else {
-                       if (buf.len)
-                               printf("%s%c", buf.buf, info->hdr_termination);
-               }
-               strbuf_release(&buf);
-       } else {
-               if (graph_show_remainder(revs->graph))
-                       putchar('\n');
-       }
-       maybe_flush_or_die(stdout, "stdout");
-       finish_commit(commit, data);
-}
-
-static void finish_commit(struct commit *commit, void *data)
-{
-       if (commit->parents) {
-               free_commit_list(commit->parents);
-               commit->parents = NULL;
-       }
-       free(commit->buffer);
-       commit->buffer = NULL;
-}
-
-static void finish_object(struct object *obj, const struct name_path *path, const char *name)
-{
-       if (obj->type == OBJ_BLOB && !has_sha1_file(obj->sha1))
-               die("missing blob object '%s'", sha1_to_hex(obj->sha1));
-}
-
-static void show_object(struct object *obj, const struct name_path *path, const char *component)
-{
-       char *name = path_name(path, component);
-       /* An object with name "foo\n0000000..." can be used to
-        * confuse downstream "git pack-objects" very badly.
-        */
-       const char *ep = strchr(name, '\n');
-
-       finish_object(obj, path, name);
-       if (ep) {
-               printf("%s %.*s\n", sha1_to_hex(obj->sha1),
-                      (int) (ep - name),
-                      name);
-       }
-       else
-               printf("%s %s\n", sha1_to_hex(obj->sha1), name);
-       free(name);
-}
-
-static void show_edge(struct commit *commit)
-{
-       printf("-%s\n", sha1_to_hex(commit->object.sha1));
-}
-
-static inline int log2i(int n)
-{
-       int log2 = 0;
-
-       for (; n > 1; n >>= 1)
-               log2++;
-
-       return log2;
-}
-
-static inline int exp2i(int n)
-{
-       return 1 << n;
-}
-
-/*
- * Estimate the number of bisect steps left (after the current step)
- *
- * For any x between 0 included and 2^n excluded, the probability for
- * n - 1 steps left looks like:
- *
- * P(2^n + x) == (2^n - x) / (2^n + x)
- *
- * and P(2^n + x) < 0.5 means 2^n < 3x
- */
-static int estimate_bisect_steps(int all)
-{
-       int n, x, e;
-
-       if (all < 3)
-               return 0;
-
-       n = log2i(all);
-       e = exp2i(n);
-       x = all - e;
-
-       return (e < 3 * x) ? n : n - 1;
-}
-
-static void show_tried_revs(struct commit_list *tried, int stringed)
-{
-       printf("bisect_tried='");
-       for (;tried; tried = tried->next) {
-               char *format = tried->next ? "%s|" : "%s";
-               printf(format, sha1_to_hex(tried->item->object.sha1));
-       }
-       printf(stringed ? "' &&\n" : "'\n");
-}
-
-int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
-{
-       int cnt, flags = info->bisect_show_flags;
-       char hex[41] = "", *format;
-       struct commit_list *tried;
-       struct rev_info *revs = info->revs;
-
-       if (!revs->commits && !(flags & BISECT_SHOW_TRIED))
-               return 1;
-
-       revs->commits = filter_skipped(revs->commits, &tried, flags & BISECT_SHOW_ALL);
-
-       /*
-        * revs->commits can reach "reaches" commits among
-        * "all" commits.  If it is good, then there are
-        * (all-reaches) commits left to be bisected.
-        * On the other hand, if it is bad, then the set
-        * to bisect is "reaches".
-        * A bisect set of size N has (N-1) commits further
-        * to test, as we already know one bad one.
-        */
-       cnt = all - reaches;
-       if (cnt < reaches)
-               cnt = reaches;
-
-       if (revs->commits)
-               strcpy(hex, sha1_to_hex(revs->commits->item->object.sha1));
-
-       if (flags & BISECT_SHOW_ALL) {
-               traverse_commit_list(revs, show_commit, show_object, info);
-               printf("------\n");
-       }
-
-       if (flags & BISECT_SHOW_TRIED)
-               show_tried_revs(tried, flags & BISECT_SHOW_STRINGED);
-       format = (flags & BISECT_SHOW_STRINGED) ?
-               "bisect_rev=%s &&\n"
-               "bisect_nr=%d &&\n"
-               "bisect_good=%d &&\n"
-               "bisect_bad=%d &&\n"
-               "bisect_all=%d &&\n"
-               "bisect_steps=%d\n"
-               :
-               "bisect_rev=%s\n"
-               "bisect_nr=%d\n"
-               "bisect_good=%d\n"
-               "bisect_bad=%d\n"
-               "bisect_all=%d\n"
-               "bisect_steps=%d\n";
-       printf(format,
-              hex,
-              cnt - 1,
-              all - reaches - 1,
-              reaches - 1,
-              all,
-              estimate_bisect_steps(all));
-
-       return 0;
-}
-
-int cmd_rev_list(int argc, const char **argv, const char *prefix)
-{
-       struct rev_info revs;
-       struct rev_list_info info;
-       int i;
-       int read_from_stdin = 0;
-       int bisect_list = 0;
-       int bisect_show_vars = 0;
-       int bisect_find_all = 0;
-       int quiet = 0;
-
-       git_config(git_default_config, NULL);
-       init_revisions(&revs, prefix);
-       revs.abbrev = 0;
-       revs.commit_format = CMIT_FMT_UNSPECIFIED;
-       argc = setup_revisions(argc, argv, &revs, NULL);
-
-       memset(&info, 0, sizeof(info));
-       info.revs = &revs;
-
-       quiet = DIFF_OPT_TST(&revs.diffopt, QUIET);
-       for (i = 1 ; i < argc; i++) {
-               const char *arg = argv[i];
-
-               if (!strcmp(arg, "--header")) {
-                       revs.verbose_header = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--timestamp")) {
-                       info.show_timestamp = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--bisect")) {
-                       bisect_list = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--bisect-all")) {
-                       bisect_list = 1;
-                       bisect_find_all = 1;
-                       info.bisect_show_flags = BISECT_SHOW_ALL;
-                       revs.show_decorations = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--bisect-vars")) {
-                       bisect_list = 1;
-                       bisect_show_vars = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--stdin")) {
-                       if (read_from_stdin++)
-                               die("--stdin given twice?");
-                       read_revisions_from_stdin(&revs);
-                       continue;
-               }
-               usage(rev_list_usage);
-
-       }
-       if (revs.commit_format != CMIT_FMT_UNSPECIFIED) {
-               /* The command line has a --pretty  */
-               info.hdr_termination = '\n';
-               if (revs.commit_format == CMIT_FMT_ONELINE)
-                       info.header_prefix = "";
-               else
-                       info.header_prefix = "commit ";
-       }
-       else if (revs.verbose_header)
-               /* Only --header was specified */
-               revs.commit_format = CMIT_FMT_RAW;
-
-       if ((!revs.commits &&
-            (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) &&
-             !revs.pending.nr)) ||
-           revs.diff)
-               usage(rev_list_usage);
-
-       save_commit_buffer = revs.verbose_header ||
-               revs.grep_filter.pattern_list;
-       if (bisect_list)
-               revs.limited = 1;
-
-       if (prepare_revision_walk(&revs))
-               die("revision walk setup failed");
-       if (revs.tree_objects)
-               mark_edges_uninteresting(revs.commits, &revs, show_edge);
-
-       if (bisect_list) {
-               int reaches = reaches, all = all;
-
-               revs.commits = find_bisection(revs.commits, &reaches, &all,
-                                             bisect_find_all);
-
-               if (bisect_show_vars)
-                       return show_bisect_vars(&info, reaches, all);
-       }
-
-       traverse_commit_list(&revs,
-                            quiet ? finish_commit : show_commit,
-                            quiet ? finish_object : show_object,
-                            &info);
-
-       return 0;
-}
diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c
deleted file mode 100644 (file)
index 22c6d6a..0000000
+++ /dev/null
@@ -1,655 +0,0 @@
-/*
- * rev-parse.c
- *
- * Copyright (C) Linus Torvalds, 2005
- */
-#include "cache.h"
-#include "commit.h"
-#include "refs.h"
-#include "quote.h"
-#include "builtin.h"
-#include "parse-options.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;
-
-#define NORMAL 0
-#define REVERSED 1
-static int show_type = NORMAL;
-
-#define SHOW_SYMBOLIC_ASIS 1
-#define SHOW_SYMBOLIC_FULL 2
-static int symbolic;
-static int abbrev;
-static int abbrev_ref;
-static int abbrev_ref_strict;
-static int output_sq;
-
-/*
- * 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);
-}
-
-/* Like show(), but with a negation prefix according to type */
-static void show_with_type(int type, const char *arg)
-{
-       if (type != show_type)
-               putchar('^');
-       show(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;
-
-       if ((symbolic || abbrev_ref) && name) {
-               if (symbolic == SHOW_SYMBOLIC_FULL || abbrev_ref) {
-                       unsigned char discard[20];
-                       char *full;
-
-                       switch (dwim_ref(name, strlen(name), discard, &full)) {
-                       case 0:
-                               /*
-                                * Not found -- not a ref.  We could
-                                * emit "name" here, but symbolic-full
-                                * users are interested in finding the
-                                * refs spelled in full, and they would
-                                * need to filter non-refs if we did so.
-                                */
-                               break;
-                       case 1: /* happy */
-                               if (abbrev_ref)
-                                       full = shorten_unambiguous_ref(full,
-                                               abbrev_ref_strict);
-                               show_with_type(type, full);
-                               break;
-                       default: /* ambiguous */
-                               error("refname '%s' is ambiguous", name);
-                               break;
-                       }
-               } else {
-                       show_with_type(type, name);
-               }
-       }
-       else if (abbrev)
-               show_with_type(type, find_unique_abbrev(sha1, abbrev));
-       else
-               show_with_type(type, 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 int 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 1;
-               }
-       }
-       return 0;
-}
-
-static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
-{
-       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;
-}
-
-static int try_difference(const char *arg)
-{
-       char *dotdot;
-       unsigned char sha1[20];
-       unsigned char end[20];
-       const char *next;
-       const char *this;
-       int symmetric;
-
-       if (!(dotdot = strstr(arg, "..")))
-               return 0;
-       next = dotdot + 2;
-       this = arg;
-       symmetric = (*next == '.');
-
-       *dotdot = 0;
-       next += symmetric;
-
-       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(symmetric ? NORMAL : REVERSED, sha1, this);
-               if (symmetric) {
-                       struct commit_list *exclude;
-                       struct commit *a, *b;
-                       a = lookup_commit_reference(sha1);
-                       b = lookup_commit_reference(end);
-                       exclude = get_merge_bases(a, b, 1);
-                       while (exclude) {
-                               struct commit_list *n = exclude->next;
-                               show_rev(REVERSED,
-                                        exclude->item->object.sha1,NULL);
-                               free(exclude);
-                               exclude = n;
-                       }
-               }
-               return 1;
-       }
-       *dotdot = '.';
-       return 0;
-}
-
-static int try_parent_shorthands(const char *arg)
-{
-       char *dotdot;
-       unsigned char sha1[20];
-       struct commit *commit;
-       struct commit_list *parents;
-       int parents_only;
-
-       if ((dotdot = strstr(arg, "^!")))
-               parents_only = 0;
-       else if ((dotdot = strstr(arg, "^@")))
-               parents_only = 1;
-
-       if (!dotdot || dotdot[2])
-               return 0;
-
-       *dotdot = 0;
-       if (get_sha1(arg, sha1))
-               return 0;
-
-       if (!parents_only)
-               show_rev(NORMAL, sha1, arg);
-       commit = lookup_commit_reference(sha1);
-       for (parents = commit->parents; parents; parents = parents->next)
-               show_rev(parents_only ? NORMAL : REVERSED,
-                               parents->item->object.sha1, arg);
-
-       return 1;
-}
-
-static int parseopt_dump(const struct option *o, const char *arg, int unset)
-{
-       struct strbuf *parsed = o->value;
-       if (unset)
-               strbuf_addf(parsed, " --no-%s", o->long_name);
-       else if (o->short_name)
-               strbuf_addf(parsed, " -%c", o->short_name);
-       else
-               strbuf_addf(parsed, " --%s", o->long_name);
-       if (arg) {
-               strbuf_addch(parsed, ' ');
-               sq_quote_buf(parsed, arg);
-       }
-       return 0;
-}
-
-static const char *skipspaces(const char *s)
-{
-       while (isspace(*s))
-               s++;
-       return s;
-}
-
-static int cmd_parseopt(int argc, const char **argv, const char *prefix)
-{
-       static int keep_dashdash = 0;
-       static char const * const parseopt_usage[] = {
-               "git rev-parse --parseopt [options] -- [<args>...]",
-               NULL
-       };
-       static struct option parseopt_opts[] = {
-               OPT_BOOLEAN(0, "keep-dashdash", &keep_dashdash,
-                                       "keep the `--` passed as an arg"),
-               OPT_END(),
-       };
-
-       struct strbuf sb = STRBUF_INIT, parsed = STRBUF_INIT;
-       const char **usage = NULL;
-       struct option *opts = NULL;
-       int onb = 0, osz = 0, unb = 0, usz = 0;
-
-       strbuf_addstr(&parsed, "set --");
-       argc = parse_options(argc, argv, parseopt_opts, parseopt_usage,
-                            PARSE_OPT_KEEP_DASHDASH);
-       if (argc < 1 || strcmp(argv[0], "--"))
-               usage_with_options(parseopt_usage, parseopt_opts);
-
-       /* get the usage up to the first line with a -- on it */
-       for (;;) {
-               if (strbuf_getline(&sb, stdin, '\n') == EOF)
-                       die("premature end of input");
-               ALLOC_GROW(usage, unb + 1, usz);
-               if (!strcmp("--", sb.buf)) {
-                       if (unb < 1)
-                               die("no usage string given before the `--' separator");
-                       usage[unb] = NULL;
-                       break;
-               }
-               usage[unb++] = strbuf_detach(&sb, NULL);
-       }
-
-       /* parse: (<short>|<short>,<long>|<long>)[=?]? SP+ <help> */
-       while (strbuf_getline(&sb, stdin, '\n') != EOF) {
-               const char *s;
-               struct option *o;
-
-               if (!sb.len)
-                       continue;
-
-               ALLOC_GROW(opts, onb + 1, osz);
-               memset(opts + onb, 0, sizeof(opts[onb]));
-
-               o = &opts[onb++];
-               s = strchr(sb.buf, ' ');
-               if (!s || *sb.buf == ' ') {
-                       o->type = OPTION_GROUP;
-                       o->help = xstrdup(skipspaces(sb.buf));
-                       continue;
-               }
-
-               o->type = OPTION_CALLBACK;
-               o->help = xstrdup(skipspaces(s));
-               o->value = &parsed;
-               o->flags = PARSE_OPT_NOARG;
-               o->callback = &parseopt_dump;
-               while (s > sb.buf && strchr("*=?!", s[-1])) {
-                       switch (*--s) {
-                       case '=':
-                               o->flags &= ~PARSE_OPT_NOARG;
-                               break;
-                       case '?':
-                               o->flags &= ~PARSE_OPT_NOARG;
-                               o->flags |= PARSE_OPT_OPTARG;
-                               break;
-                       case '!':
-                               o->flags |= PARSE_OPT_NONEG;
-                               break;
-                       case '*':
-                               o->flags |= PARSE_OPT_HIDDEN;
-                               break;
-                       }
-               }
-
-               if (s - sb.buf == 1) /* short option only */
-                       o->short_name = *sb.buf;
-               else if (sb.buf[1] != ',') /* long option only */
-                       o->long_name = xmemdupz(sb.buf, s - sb.buf);
-               else {
-                       o->short_name = *sb.buf;
-                       o->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2);
-               }
-       }
-       strbuf_release(&sb);
-
-       /* put an OPT_END() */
-       ALLOC_GROW(opts, onb + 1, osz);
-       memset(opts + onb, 0, sizeof(opts[onb]));
-       argc = parse_options(argc, argv, opts, usage,
-                            keep_dashdash ? PARSE_OPT_KEEP_DASHDASH : 0);
-
-       strbuf_addf(&parsed, " --");
-       sq_quote_argv(&parsed, argv, 0);
-       puts(parsed.buf);
-       return 0;
-}
-
-static void die_no_single_rev(int quiet)
-{
-       if (quiet)
-               exit(1);
-       else
-               die("Needed a single revision");
-}
-
-int cmd_rev_parse(int argc, const char **argv, const char *prefix)
-{
-       int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0;
-       unsigned char sha1[20];
-       const char *name = NULL;
-
-       if (argc > 1 && !strcmp("--parseopt", argv[1]))
-               return cmd_parseopt(argc - 1, argv + 1, prefix);
-
-       prefix = setup_git_directory();
-       git_config(git_default_config, NULL);
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-
-               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 (!prefixcmp(arg, "-n")) {
-                       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, "--quiet") || !strcmp(arg, "-q")) {
-                               quiet = 1;
-                               continue;
-                       }
-                       if (!strcmp(arg, "--short") ||
-                           !prefixcmp(arg, "--short=")) {
-                               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 = SHOW_SYMBOLIC_ASIS;
-                               continue;
-                       }
-                       if (!strcmp(arg, "--symbolic-full-name")) {
-                               symbolic = SHOW_SYMBOLIC_FULL;
-                               continue;
-                       }
-                       if (!prefixcmp(arg, "--abbrev-ref") &&
-                           (!arg[12] || arg[12] == '=')) {
-                               abbrev_ref = 1;
-                               abbrev_ref_strict = warn_ambiguous_refs;
-                               if (arg[12] == '=') {
-                                       if (!strcmp(arg + 13, "strict"))
-                                               abbrev_ref_strict = 1;
-                                       else if (!strcmp(arg + 13, "loose"))
-                                               abbrev_ref_strict = 0;
-                                       else
-                                               die("unknown mode for %s", arg);
-                               }
-                               continue;
-                       }
-                       if (!strcmp(arg, "--all")) {
-                               for_each_ref(show_reference, NULL);
-                               continue;
-                       }
-                       if (!strcmp(arg, "--branches")) {
-                               for_each_branch_ref(show_reference, NULL);
-                               continue;
-                       }
-                       if (!strcmp(arg, "--tags")) {
-                               for_each_tag_ref(show_reference, NULL);
-                               continue;
-                       }
-                       if (!strcmp(arg, "--remotes")) {
-                               for_each_remote_ref(show_reference, NULL);
-                               continue;
-                       }
-                       if (!strcmp(arg, "--show-prefix")) {
-                               if (prefix)
-                                       puts(prefix);
-                               continue;
-                       }
-                       if (!strcmp(arg, "--show-cdup")) {
-                               const char *pfx = prefix;
-                               if (!is_inside_work_tree()) {
-                                       const char *work_tree =
-                                               get_git_work_tree();
-                                       if (work_tree)
-                                               printf("%s\n", work_tree);
-                                       continue;
-                               }
-                               while (pfx) {
-                                       pfx = strchr(pfx, '/');
-                                       if (pfx) {
-                                               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 (!strcmp(arg, "--is-inside-git-dir")) {
-                               printf("%s\n", is_inside_git_dir() ? "true"
-                                               : "false");
-                               continue;
-                       }
-                       if (!strcmp(arg, "--is-inside-work-tree")) {
-                               printf("%s\n", is_inside_work_tree() ? "true"
-                                               : "false");
-                               continue;
-                       }
-                       if (!strcmp(arg, "--is-bare-repository")) {
-                               printf("%s\n", is_bare_repository() ? "true"
-                                               : "false");
-                               continue;
-                       }
-                       if (!prefixcmp(arg, "--since=")) {
-                               show_datestring("--max-age=", arg+8);
-                               continue;
-                       }
-                       if (!prefixcmp(arg, "--after=")) {
-                               show_datestring("--max-age=", arg+8);
-                               continue;
-                       }
-                       if (!prefixcmp(arg, "--before=")) {
-                               show_datestring("--min-age=", arg+9);
-                               continue;
-                       }
-                       if (!prefixcmp(arg, "--until=")) {
-                               show_datestring("--min-age=", arg+8);
-                               continue;
-                       }
-                       if (show_flag(arg) && verify)
-                               die_no_single_rev(quiet);
-                       continue;
-               }
-
-               /* Not a flag argument */
-               if (try_difference(arg))
-                       continue;
-               if (try_parent_shorthands(arg))
-                       continue;
-               name = arg;
-               type = NORMAL;
-               if (*arg == '^') {
-                       name++;
-                       type = REVERSED;
-               }
-               if (!get_sha1(name, sha1)) {
-                       if (verify)
-                               revs_count++;
-                       else
-                               show_rev(type, sha1, name);
-                       continue;
-               }
-               if (verify)
-                       die_no_single_rev(quiet);
-               as_is = 1;
-               if (!show_file(arg))
-                       continue;
-               verify_filename(prefix, arg);
-       }
-       if (verify) {
-               if (revs_count == 1) {
-                       show_rev(type, sha1, name);
-                       return 0;
-               } else if (revs_count == 0 && show_default())
-                       return 0;
-               die_no_single_rev(quiet);
-       } else
-               show_default();
-       return 0;
-}
diff --git a/builtin-revert.c b/builtin-revert.c
deleted file mode 100644 (file)
index 3f2614e..0000000
+++ /dev/null
@@ -1,449 +0,0 @@
-#include "cache.h"
-#include "builtin.h"
-#include "object.h"
-#include "commit.h"
-#include "tag.h"
-#include "wt-status.h"
-#include "run-command.h"
-#include "exec_cmd.h"
-#include "utf8.h"
-#include "parse-options.h"
-#include "cache-tree.h"
-#include "diff.h"
-#include "revision.h"
-#include "rerere.h"
-#include "merge-recursive.h"
-
-/*
- * This implements the builtins revert and cherry-pick.
- *
- * Copyright (c) 2007 Johannes E. Schindelin
- *
- * Based on git-revert.sh, which is
- *
- * Copyright (c) 2005 Linus Torvalds
- * Copyright (c) 2005 Junio C Hamano
- */
-
-static const char * const revert_usage[] = {
-       "git revert [options] <commit-ish>",
-       NULL
-};
-
-static const char * const cherry_pick_usage[] = {
-       "git cherry-pick [options] <commit-ish>",
-       NULL
-};
-
-static int edit, no_replay, no_commit, mainline, signoff;
-static enum { REVERT, CHERRY_PICK } action;
-static struct commit *commit;
-
-static const char *me;
-
-#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
-
-static void parse_args(int argc, const char **argv)
-{
-       const char * const * usage_str =
-               action == REVERT ?  revert_usage : cherry_pick_usage;
-       unsigned char sha1[20];
-       const char *arg;
-       int noop;
-       struct option options[] = {
-               OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"),
-               OPT_BOOLEAN('e', "edit", &edit, "edit the commit message"),
-               OPT_BOOLEAN('x', NULL, &no_replay, "append commit name when cherry-picking"),
-               OPT_BOOLEAN('r', NULL, &noop, "no-op (backward compatibility)"),
-               OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
-               OPT_INTEGER('m', "mainline", &mainline, "parent number"),
-               OPT_END(),
-       };
-
-       if (parse_options(argc, argv, options, usage_str, 0) != 1)
-               usage_with_options(usage_str, options);
-       arg = argv[0];
-
-       if (get_sha1(arg, sha1))
-               die ("Cannot find '%s'", arg);
-       commit = (struct commit *)parse_object(sha1);
-       if (!commit)
-               die ("Could not find %s", sha1_to_hex(sha1));
-       if (commit->object.type == OBJ_TAG) {
-               commit = (struct commit *)
-                       deref_tag((struct object *)commit, arg, strlen(arg));
-       }
-       if (commit->object.type != OBJ_COMMIT)
-               die ("'%s' does not point to a commit", arg);
-}
-
-static char *get_oneline(const char *message)
-{
-       char *result;
-       const char *p = message, *abbrev, *eol;
-       int abbrev_len, oneline_len;
-
-       if (!p)
-               die ("Could not read commit message of %s",
-                               sha1_to_hex(commit->object.sha1));
-       while (*p && (*p != '\n' || p[1] != '\n'))
-               p++;
-
-       if (*p) {
-               p += 2;
-               for (eol = p + 1; *eol && *eol != '\n'; eol++)
-                       ; /* do nothing */
-       } else
-               eol = p;
-       abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
-       abbrev_len = strlen(abbrev);
-       oneline_len = eol - p;
-       result = xmalloc(abbrev_len + 5 + oneline_len);
-       memcpy(result, abbrev, abbrev_len);
-       memcpy(result + abbrev_len, "... ", 4);
-       memcpy(result + abbrev_len + 4, p, oneline_len);
-       result[abbrev_len + 4 + oneline_len] = '\0';
-       return result;
-}
-
-static char *get_encoding(const char *message)
-{
-       const char *p = message, *eol;
-
-       if (!p)
-               die ("Could not read commit message of %s",
-                               sha1_to_hex(commit->object.sha1));
-       while (*p && *p != '\n') {
-               for (eol = p + 1; *eol && *eol != '\n'; eol++)
-                       ; /* do nothing */
-               if (!prefixcmp(p, "encoding ")) {
-                       char *result = xmalloc(eol - 8 - p);
-                       strlcpy(result, p + 9, eol - 8 - p);
-                       return result;
-               }
-               p = eol;
-               if (*p == '\n')
-                       p++;
-       }
-       return NULL;
-}
-
-static struct lock_file msg_file;
-static int msg_fd;
-
-static void add_to_msg(const char *string)
-{
-       int len = strlen(string);
-       if (write_in_full(msg_fd, string, len) < 0)
-               die ("Could not write to MERGE_MSG");
-}
-
-static void add_message_to_msg(const char *message)
-{
-       const char *p = message;
-       while (*p && (*p != '\n' || p[1] != '\n'))
-               p++;
-
-       if (!*p)
-               add_to_msg(sha1_to_hex(commit->object.sha1));
-
-       p += 2;
-       add_to_msg(p);
-       return;
-}
-
-static void set_author_ident_env(const char *message)
-{
-       const char *p = message;
-       if (!p)
-               die ("Could not read commit message of %s",
-                               sha1_to_hex(commit->object.sha1));
-       while (*p && *p != '\n') {
-               const char *eol;
-
-               for (eol = p; *eol && *eol != '\n'; eol++)
-                       ; /* do nothing */
-               if (!prefixcmp(p, "author ")) {
-                       char *line, *pend, *email, *timestamp;
-
-                       p += 7;
-                       line = xmemdupz(p, eol - p);
-                       email = strchr(line, '<');
-                       if (!email)
-                               die ("Could not extract author email from %s",
-                                       sha1_to_hex(commit->object.sha1));
-                       if (email == line)
-                               pend = line;
-                       else
-                               for (pend = email; pend != line + 1 &&
-                                               isspace(pend[-1]); pend--);
-                                       ; /* do nothing */
-                       *pend = '\0';
-                       email++;
-                       timestamp = strchr(email, '>');
-                       if (!timestamp)
-                               die ("Could not extract author time from %s",
-                                       sha1_to_hex(commit->object.sha1));
-                       *timestamp = '\0';
-                       for (timestamp++; *timestamp && isspace(*timestamp);
-                                       timestamp++)
-                               ; /* do nothing */
-                       setenv("GIT_AUTHOR_NAME", line, 1);
-                       setenv("GIT_AUTHOR_EMAIL", email, 1);
-                       setenv("GIT_AUTHOR_DATE", timestamp, 1);
-                       free(line);
-                       return;
-               }
-               p = eol;
-               if (*p == '\n')
-                       p++;
-       }
-       die ("No author information found in %s",
-                       sha1_to_hex(commit->object.sha1));
-}
-
-static char *help_msg(const unsigned char *sha1)
-{
-       static char helpbuf[1024];
-       char *msg = getenv("GIT_CHERRY_PICK_HELP");
-
-       if (msg)
-               return msg;
-
-       strcpy(helpbuf, "  After resolving the conflicts,\n"
-              "mark the corrected paths with 'git add <paths>' "
-              "or 'git rm <paths>' and commit the result.");
-
-       if (action == CHERRY_PICK) {
-               sprintf(helpbuf + strlen(helpbuf),
-                       "\nWhen commiting, use the option "
-                       "'-c %s' to retain authorship and message.",
-                       find_unique_abbrev(sha1, DEFAULT_ABBREV));
-       }
-       return helpbuf;
-}
-
-static struct tree *empty_tree(void)
-{
-       struct tree *tree = xcalloc(1, sizeof(struct tree));
-
-       tree->object.parsed = 1;
-       tree->object.type = OBJ_TREE;
-       pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);
-       return tree;
-}
-
-static int revert_or_cherry_pick(int argc, const char **argv)
-{
-       unsigned char head[20];
-       struct commit *base, *next, *parent;
-       int i, index_fd, clean;
-       char *oneline, *reencoded_message = NULL;
-       const char *message, *encoding;
-       char *defmsg = git_pathdup("MERGE_MSG");
-       struct merge_options o;
-       struct tree *result, *next_tree, *base_tree, *head_tree;
-       static struct lock_file index_lock;
-
-       git_config(git_default_config, NULL);
-       me = action == REVERT ? "revert" : "cherry-pick";
-       setenv(GIT_REFLOG_ACTION, me, 0);
-       parse_args(argc, argv);
-
-       /* this is copied from the shell script, but it's never triggered... */
-       if (action == REVERT && !no_replay)
-               die("revert is incompatible with replay");
-
-       if (read_cache() < 0)
-               die("git %s: failed to read the index", me);
-       if (no_commit) {
-               /*
-                * We do not intend to commit immediately.  We just want to
-                * merge the differences in, so let's compute the tree
-                * that represents the "current" state for merge-recursive
-                * to work on.
-                */
-               if (write_cache_as_tree(head, 0, NULL))
-                       die ("Your index file is unmerged.");
-       } else {
-               if (get_sha1("HEAD", head))
-                       die ("You do not have a valid HEAD");
-               if (index_differs_from("HEAD", 0))
-                       die ("Dirty index: cannot %s", me);
-       }
-       discard_cache();
-
-       index_fd = hold_locked_index(&index_lock, 1);
-
-       if (!commit->parents) {
-               if (action == REVERT)
-                       die ("Cannot revert a root commit");
-               parent = NULL;
-       }
-       else if (commit->parents->next) {
-               /* Reverting or cherry-picking a merge commit */
-               int cnt;
-               struct commit_list *p;
-
-               if (!mainline)
-                       die("Commit %s is a merge but no -m option was given.",
-                           sha1_to_hex(commit->object.sha1));
-
-               for (cnt = 1, p = commit->parents;
-                    cnt != mainline && p;
-                    cnt++)
-                       p = p->next;
-               if (cnt != mainline || !p)
-                       die("Commit %s does not have parent %d",
-                           sha1_to_hex(commit->object.sha1), mainline);
-               parent = p->item;
-       } else if (0 < mainline)
-               die("Mainline was specified but commit %s is not a merge.",
-                   sha1_to_hex(commit->object.sha1));
-       else
-               parent = commit->parents->item;
-
-       if (!(message = commit->buffer))
-               die ("Cannot get commit message for %s",
-                               sha1_to_hex(commit->object.sha1));
-
-       if (parent && parse_commit(parent) < 0)
-               die("%s: cannot parse parent commit %s",
-                   me, sha1_to_hex(parent->object.sha1));
-
-       /*
-        * "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.
-        */
-
-       msg_fd = hold_lock_file_for_update(&msg_file, defmsg,
-                                          LOCK_DIE_ON_ERROR);
-
-       encoding = get_encoding(message);
-       if (!encoding)
-               encoding = "utf-8";
-       if (!git_commit_encoding)
-               git_commit_encoding = "utf-8";
-       if ((reencoded_message = reencode_string(message,
-                                       git_commit_encoding, encoding)))
-               message = reencoded_message;
-
-       oneline = get_oneline(message);
-
-       if (action == REVERT) {
-               char *oneline_body = strchr(oneline, ' ');
-
-               base = commit;
-               next = parent;
-               add_to_msg("Revert \"");
-               add_to_msg(oneline_body + 1);
-               add_to_msg("\"\n\nThis reverts commit ");
-               add_to_msg(sha1_to_hex(commit->object.sha1));
-
-               if (commit->parents->next) {
-                       add_to_msg(", reversing\nchanges made to ");
-                       add_to_msg(sha1_to_hex(parent->object.sha1));
-               }
-               add_to_msg(".\n");
-       } else {
-               base = parent;
-               next = commit;
-               set_author_ident_env(message);
-               add_message_to_msg(message);
-               if (no_replay) {
-                       add_to_msg("(cherry picked from commit ");
-                       add_to_msg(sha1_to_hex(commit->object.sha1));
-                       add_to_msg(")\n");
-               }
-       }
-
-       read_cache();
-       init_merge_options(&o);
-       o.branch1 = "HEAD";
-       o.branch2 = oneline;
-
-       head_tree = parse_tree_indirect(head);
-       next_tree = next ? next->tree : empty_tree();
-       base_tree = base ? base->tree : empty_tree();
-
-       clean = merge_trees(&o,
-                           head_tree,
-                           next_tree, base_tree, &result);
-
-       if (active_cache_changed &&
-           (write_cache(index_fd, active_cache, active_nr) ||
-            commit_locked_index(&index_lock)))
-               die("%s: Unable to write new index file", me);
-       rollback_lock_file(&index_lock);
-
-       if (!clean) {
-               add_to_msg("\nConflicts:\n\n");
-               for (i = 0; i < active_nr;) {
-                       struct cache_entry *ce = active_cache[i++];
-                       if (ce_stage(ce)) {
-                               add_to_msg("\t");
-                               add_to_msg(ce->name);
-                               add_to_msg("\n");
-                               while (i < active_nr && !strcmp(ce->name,
-                                               active_cache[i]->name))
-                                       i++;
-                       }
-               }
-               if (commit_lock_file(&msg_file) < 0)
-                       die ("Error wrapping up %s", defmsg);
-               fprintf(stderr, "Automatic %s failed.%s\n",
-                       me, help_msg(commit->object.sha1));
-               rerere();
-               exit(1);
-       }
-       if (commit_lock_file(&msg_file) < 0)
-               die ("Error wrapping up %s", defmsg);
-       fprintf(stderr, "Finished one %s.\n", 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.
-        */
-
-       if (!no_commit) {
-               /* 6 is max possible length of our args array including NULL */
-               const char *args[6];
-               int i = 0;
-               args[i++] = "commit";
-               args[i++] = "-n";
-               if (signoff)
-                       args[i++] = "-s";
-               if (!edit) {
-                       args[i++] = "-F";
-                       args[i++] = defmsg;
-               }
-               args[i] = NULL;
-               return execv_git_cmd(args);
-       }
-       free(reencoded_message);
-       free(defmsg);
-
-       return 0;
-}
-
-int cmd_revert(int argc, const char **argv, const char *prefix)
-{
-       if (isatty(0))
-               edit = 1;
-       no_replay = 1;
-       action = REVERT;
-       return revert_or_cherry_pick(argc, argv);
-}
-
-int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
-{
-       no_replay = 0;
-       action = CHERRY_PICK;
-       return revert_or_cherry_pick(argc, argv);
-}
diff --git a/builtin-rm.c b/builtin-rm.c
deleted file mode 100644 (file)
index 269d608..0000000
+++ /dev/null
@@ -1,270 +0,0 @@
-/*
- * "git rm" builtin command
- *
- * Copyright (C) Linus Torvalds 2006
- */
-#include "cache.h"
-#include "builtin.h"
-#include "dir.h"
-#include "cache-tree.h"
-#include "tree-walk.h"
-#include "parse-options.h"
-
-static const char * const builtin_rm_usage[] = {
-       "git rm [options] [--] <file>...",
-       NULL
-};
-
-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 check_local_mod(unsigned char *head, int index_only)
-{
-       /*
-        * Items in list are already sorted in the cache order,
-        * so we could do this a lot more efficiently by using
-        * tree_desc based traversal if we wanted to, but I am
-        * lazy, and who cares if removal of files is a tad
-        * slower than the theoretical maximum speed?
-        */
-       int i, no_head;
-       int errs = 0;
-
-       no_head = is_null_sha1(head);
-       for (i = 0; i < list.nr; i++) {
-               struct stat st;
-               int pos;
-               struct cache_entry *ce;
-               const char *name = list.name[i];
-               unsigned char sha1[20];
-               unsigned mode;
-               int local_changes = 0;
-               int staged_changes = 0;
-
-               pos = cache_name_pos(name, strlen(name));
-               if (pos < 0)
-                       continue; /* removing unmerged entry */
-               ce = active_cache[pos];
-
-               if (lstat(ce->name, &st) < 0) {
-                       if (errno != ENOENT)
-                               warning("'%s': %s", ce->name, strerror(errno));
-                       /* It already vanished from the working tree */
-                       continue;
-               }
-               else if (S_ISDIR(st.st_mode)) {
-                       /* if a file was removed and it is now a
-                        * directory, that is the same as ENOENT as
-                        * far as git is concerned; we do not track
-                        * directories.
-                        */
-                       continue;
-               }
-
-               /*
-                * "rm" of a path that has changes need to be treated
-                * carefully not to allow losing local changes
-                * accidentally.  A local change could be (1) file in
-                * work tree is different since the index; and/or (2)
-                * the user staged a content that is different from
-                * the current commit in the index.
-                *
-                * In such a case, you would need to --force the
-                * removal.  However, "rm --cached" (remove only from
-                * the index) is safe if the index matches the file in
-                * the work tree or the HEAD commit, as it means that
-                * the content being removed is available elsewhere.
-                */
-
-               /*
-                * Is the index different from the file in the work tree?
-                */
-               if (ce_match_stat(ce, &st, 0))
-                       local_changes = 1;
-
-               /*
-                * Is the index different from the HEAD commit?  By
-                * definition, before the very initial commit,
-                * anything staged in the index is treated by the same
-                * way as changed from the HEAD.
-                */
-               if (no_head
-                    || get_tree_entry(head, name, sha1, &mode)
-                    || ce->ce_mode != create_ce_mode(mode)
-                    || hashcmp(ce->sha1, sha1))
-                       staged_changes = 1;
-
-               /*
-                * If the index does not match the file in the work
-                * tree and if it does not match the HEAD commit
-                * either, (1) "git rm" without --cached definitely
-                * will lose information; (2) "git rm --cached" will
-                * lose information unless it is about removing an
-                * "intent to add" entry.
-                */
-               if (local_changes && staged_changes) {
-                       if (!index_only || !(ce->ce_flags & CE_INTENT_TO_ADD))
-                               errs = error("'%s' has staged content different "
-                                            "from both the file and the HEAD\n"
-                                            "(use -f to force removal)", name);
-               }
-               else if (!index_only) {
-                       if (staged_changes)
-                               errs = error("'%s' has changes staged in the index\n"
-                                            "(use --cached to keep the file, "
-                                            "or -f to force removal)", name);
-                       if (local_changes)
-                               errs = error("'%s' has local modifications\n"
-                                            "(use --cached to keep the file, "
-                                            "or -f to force removal)", name);
-               }
-       }
-       return errs;
-}
-
-static struct lock_file lock_file;
-
-static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
-static int ignore_unmatch = 0;
-
-static struct option builtin_rm_options[] = {
-       OPT__DRY_RUN(&show_only),
-       OPT__QUIET(&quiet),
-       OPT_BOOLEAN( 0 , "cached",         &index_only, "only remove from the index"),
-       OPT_BOOLEAN('f', "force",          &force,      "override the up-to-date check"),
-       OPT_BOOLEAN('r', NULL,             &recursive,  "allow recursive removal"),
-       OPT_BOOLEAN( 0 , "ignore-unmatch", &ignore_unmatch,
-                               "exit with a zero status even if nothing matched"),
-       OPT_END(),
-};
-
-int cmd_rm(int argc, const char **argv, const char *prefix)
-{
-       int i, newfd;
-       const char **pathspec;
-       char *seen;
-
-       git_config(git_default_config, NULL);
-
-       argc = parse_options(argc, argv, builtin_rm_options, builtin_rm_usage, 0);
-       if (!argc)
-               usage_with_options(builtin_rm_usage, builtin_rm_options);
-
-       if (!index_only)
-               setup_work_tree();
-
-       newfd = hold_locked_index(&lock_file, 1);
-
-       if (read_cache() < 0)
-               die("index file corrupt");
-       refresh_cache(REFRESH_QUIET);
-
-       pathspec = get_pathspec(prefix, argv);
-       seen = NULL;
-       for (i = 0; pathspec[i] ; i++)
-               /* nothing */;
-       seen = xcalloc(i, 1);
-
-       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;
-               int seen_any = 0;
-               for (i = 0; (match = pathspec[i]) != NULL ; i++) {
-                       if (!seen[i]) {
-                               if (!ignore_unmatch) {
-                                       die("pathspec '%s' did not match any files",
-                                           match);
-                               }
-                       }
-                       else {
-                               seen_any = 1;
-                       }
-                       if (!recursive && seen[i] == MATCHED_RECURSIVELY)
-                               die("not removing '%s' recursively without -r",
-                                   *match ? match : ".");
-               }
-
-               if (! seen_any)
-                       exit(0);
-       }
-
-       /*
-        * If not forced, the file, the index and the HEAD (if exists)
-        * must match; but the file can already been removed, since
-        * this sequence is a natural "novice" way:
-        *
-        *      rm F; git rm F
-        *
-        * Further, if HEAD commit exists, "diff-index --cached" must
-        * report no changes unless forced.
-        */
-       if (!force) {
-               unsigned char sha1[20];
-               if (get_sha1("HEAD", sha1))
-                       hashclr(sha1);
-               if (check_local_mod(sha1, index_only))
-                       exit(1);
-       }
-
-       /*
-        * 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];
-               if (!quiet)
-                       printf("rm '%s'\n", path);
-
-               if (remove_file_from_cache(path))
-                       die("git rm: unable to remove %s", path);
-       }
-
-       if (show_only)
-               return 0;
-
-       /*
-        * Then, unless we used "--cached", remove the filenames from
-        * the workspace. If we fail to remove the first one, we
-        * abort the "git rm" (but once we've successfully removed
-        * any file at all, we'll go ahead and commit to it all:
-        * by then we've already committed ourselves and can't fail
-        * in the middle)
-        */
-       if (!index_only) {
-               int removed = 0;
-               for (i = 0; i < list.nr; i++) {
-                       const char *path = list.name[i];
-                       if (!remove_path(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_locked_index(&lock_file))
-                       die("Unable to write new index file");
-       }
-
-       return 0;
-}
diff --git a/builtin-send-pack.c b/builtin-send-pack.c
deleted file mode 100644 (file)
index d5a1c48..0000000
+++ /dev/null
@@ -1,591 +0,0 @@
-#include "cache.h"
-#include "commit.h"
-#include "refs.h"
-#include "pkt-line.h"
-#include "run-command.h"
-#include "remote.h"
-#include "send-pack.h"
-
-static const char send_pack_usage[] =
-"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
-"  --all and explicit <ref> specification are mutually exclusive.";
-
-static struct send_pack_args args;
-
-static int feed_object(const unsigned char *sha1, int fd, int negative)
-{
-       char buf[42];
-
-       if (negative && !has_sha1_file(sha1))
-               return 1;
-
-       memcpy(buf + negative, sha1_to_hex(sha1), 40);
-       if (negative)
-               buf[0] = '^';
-       buf[40 + negative] = '\n';
-       return write_or_whine(fd, buf, 41 + negative, "send-pack: send refs");
-}
-
-/*
- * Make a pack stream and spit it out into file descriptor fd
- */
-static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *extra, struct send_pack_args *args)
-{
-       /*
-        * The child becomes pack-objects --revs; we feed
-        * the revision parameters to it via its stdin and
-        * let its stdout go back to the other end.
-        */
-       const char *argv[] = {
-               "pack-objects",
-               "--all-progress",
-               "--revs",
-               "--stdout",
-               NULL,
-               NULL,
-       };
-       struct child_process po;
-       int i;
-
-       if (args->use_thin_pack)
-               argv[4] = "--thin";
-       memset(&po, 0, sizeof(po));
-       po.argv = argv;
-       po.in = -1;
-       po.out = fd;
-       po.git_cmd = 1;
-       if (start_command(&po))
-               die("git pack-objects failed (%s)", strerror(errno));
-
-       /*
-        * We feed the pack-objects we just spawned with revision
-        * parameters by writing to the pipe.
-        */
-       for (i = 0; i < extra->nr; i++)
-               if (!feed_object(extra->array[i], po.in, 1))
-                       break;
-
-       while (refs) {
-               if (!is_null_sha1(refs->old_sha1) &&
-                   !feed_object(refs->old_sha1, po.in, 1))
-                       break;
-               if (!is_null_sha1(refs->new_sha1) &&
-                   !feed_object(refs->new_sha1, po.in, 0))
-                       break;
-               refs = refs->next;
-       }
-
-       close(po.in);
-       if (finish_command(&po))
-               return error("pack-objects died with strange error");
-       return 0;
-}
-
-static int receive_status(int in, struct ref *refs)
-{
-       struct ref *hint;
-       char line[1000];
-       int ret = 0;
-       int len = packet_read_line(in, line, sizeof(line));
-       if (len < 10 || memcmp(line, "unpack ", 7))
-               return error("did not receive remote status");
-       if (memcmp(line, "unpack ok\n", 10)) {
-               char *p = line + strlen(line) - 1;
-               if (*p == '\n')
-                       *p = '\0';
-               error("unpack failed: %s", line + 7);
-               ret = -1;
-       }
-       hint = NULL;
-       while (1) {
-               char *refname;
-               char *msg;
-               len = packet_read_line(in, line, sizeof(line));
-               if (!len)
-                       break;
-               if (len < 3 ||
-                   (memcmp(line, "ok ", 3) && memcmp(line, "ng ", 3))) {
-                       fprintf(stderr, "protocol error: %s\n", line);
-                       ret = -1;
-                       break;
-               }
-
-               line[strlen(line)-1] = '\0';
-               refname = line + 3;
-               msg = strchr(refname, ' ');
-               if (msg)
-                       *msg++ = '\0';
-
-               /* first try searching at our hint, falling back to all refs */
-               if (hint)
-                       hint = find_ref_by_name(hint, refname);
-               if (!hint)
-                       hint = find_ref_by_name(refs, refname);
-               if (!hint) {
-                       warning("remote reported status on unknown ref: %s",
-                                       refname);
-                       continue;
-               }
-               if (hint->status != REF_STATUS_EXPECTING_REPORT) {
-                       warning("remote reported status on unexpected ref: %s",
-                                       refname);
-                       continue;
-               }
-
-               if (line[0] == 'o' && line[1] == 'k')
-                       hint->status = REF_STATUS_OK;
-               else {
-                       hint->status = REF_STATUS_REMOTE_REJECT;
-                       ret = -1;
-               }
-               if (msg)
-                       hint->remote_status = xstrdup(msg);
-               /* start our next search from the next ref */
-               hint = hint->next;
-       }
-       return ret;
-}
-
-static void update_tracking_ref(struct remote *remote, struct ref *ref)
-{
-       struct refspec rs;
-
-       if (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE)
-               return;
-
-       rs.src = ref->name;
-       rs.dst = NULL;
-
-       if (!remote_find_tracking(remote, &rs)) {
-               if (args.verbose)
-                       fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst);
-               if (ref->deletion) {
-                       delete_ref(rs.dst, NULL, 0);
-               } else
-                       update_ref("update by push", rs.dst,
-                                       ref->new_sha1, NULL, 0, 0);
-               free(rs.dst);
-       }
-}
-
-#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
-
-static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg)
-{
-       fprintf(stderr, " %c %-*s ", flag, SUMMARY_WIDTH, summary);
-       if (from)
-               fprintf(stderr, "%s -> %s", prettify_ref(from), prettify_ref(to));
-       else
-               fputs(prettify_ref(to), stderr);
-       if (msg) {
-               fputs(" (", stderr);
-               fputs(msg, stderr);
-               fputc(')', stderr);
-       }
-       fputc('\n', stderr);
-}
-
-static const char *status_abbrev(unsigned char sha1[20])
-{
-       return find_unique_abbrev(sha1, DEFAULT_ABBREV);
-}
-
-static void print_ok_ref_status(struct ref *ref)
-{
-       if (ref->deletion)
-               print_ref_status('-', "[deleted]", ref, NULL, NULL);
-       else if (is_null_sha1(ref->old_sha1))
-               print_ref_status('*',
-                       (!prefixcmp(ref->name, "refs/tags/") ? "[new tag]" :
-                         "[new branch]"),
-                       ref, ref->peer_ref, NULL);
-       else {
-               char quickref[84];
-               char type;
-               const char *msg;
-
-               strcpy(quickref, status_abbrev(ref->old_sha1));
-               if (ref->nonfastforward) {
-                       strcat(quickref, "...");
-                       type = '+';
-                       msg = "forced update";
-               } else {
-                       strcat(quickref, "..");
-                       type = ' ';
-                       msg = NULL;
-               }
-               strcat(quickref, status_abbrev(ref->new_sha1));
-
-               print_ref_status(type, quickref, ref, ref->peer_ref, msg);
-       }
-}
-
-static int print_one_push_status(struct ref *ref, const char *dest, int count)
-{
-       if (!count)
-               fprintf(stderr, "To %s\n", dest);
-
-       switch(ref->status) {
-       case REF_STATUS_NONE:
-               print_ref_status('X', "[no match]", ref, NULL, NULL);
-               break;
-       case REF_STATUS_REJECT_NODELETE:
-               print_ref_status('!', "[rejected]", ref, NULL,
-                               "remote does not support deleting refs");
-               break;
-       case REF_STATUS_UPTODATE:
-               print_ref_status('=', "[up to date]", ref,
-                               ref->peer_ref, NULL);
-               break;
-       case REF_STATUS_REJECT_NONFASTFORWARD:
-               print_ref_status('!', "[rejected]", ref, ref->peer_ref,
-                               "non-fast forward");
-               break;
-       case REF_STATUS_REMOTE_REJECT:
-               print_ref_status('!', "[remote rejected]", ref,
-                               ref->deletion ? NULL : ref->peer_ref,
-                               ref->remote_status);
-               break;
-       case REF_STATUS_EXPECTING_REPORT:
-               print_ref_status('!', "[remote failure]", ref,
-                               ref->deletion ? NULL : ref->peer_ref,
-                               "remote failed to report status");
-               break;
-       case REF_STATUS_OK:
-               print_ok_ref_status(ref);
-               break;
-       }
-
-       return 1;
-}
-
-static void print_push_status(const char *dest, struct ref *refs)
-{
-       struct ref *ref;
-       int n = 0;
-
-       if (args.verbose) {
-               for (ref = refs; ref; ref = ref->next)
-                       if (ref->status == REF_STATUS_UPTODATE)
-                               n += print_one_push_status(ref, dest, n);
-       }
-
-       for (ref = refs; ref; ref = ref->next)
-               if (ref->status == REF_STATUS_OK)
-                       n += print_one_push_status(ref, dest, n);
-
-       for (ref = refs; ref; ref = ref->next) {
-               if (ref->status != REF_STATUS_NONE &&
-                   ref->status != REF_STATUS_UPTODATE &&
-                   ref->status != REF_STATUS_OK)
-                       n += print_one_push_status(ref, dest, n);
-       }
-}
-
-static int refs_pushed(struct ref *ref)
-{
-       for (; ref; ref = ref->next) {
-               switch(ref->status) {
-               case REF_STATUS_NONE:
-               case REF_STATUS_UPTODATE:
-                       break;
-               default:
-                       return 1;
-               }
-       }
-       return 0;
-}
-
-int send_pack(struct send_pack_args *args,
-             int fd[], struct child_process *conn,
-             struct ref *remote_refs,
-             struct extra_have_objects *extra_have)
-{
-       int in = fd[0];
-       int out = fd[1];
-       struct ref *ref;
-       int new_refs;
-       int ask_for_status_report = 0;
-       int allow_deleting_refs = 0;
-       int expect_status_report = 0;
-       int ret;
-
-       /* Does the other end support the reporting? */
-       if (server_supports("report-status"))
-               ask_for_status_report = 1;
-       if (server_supports("delete-refs"))
-               allow_deleting_refs = 1;
-
-       if (!remote_refs) {
-               fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
-                       "Perhaps you should specify a branch such as 'master'.\n");
-               return 0;
-       }
-
-       /*
-        * Finally, tell the other end!
-        */
-       new_refs = 0;
-       for (ref = remote_refs; ref; ref = ref->next) {
-
-               if (ref->peer_ref)
-                       hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
-               else if (!args->send_mirror)
-                       continue;
-
-               ref->deletion = is_null_sha1(ref->new_sha1);
-               if (ref->deletion && !allow_deleting_refs) {
-                       ref->status = REF_STATUS_REJECT_NODELETE;
-                       continue;
-               }
-               if (!ref->deletion &&
-                   !hashcmp(ref->old_sha1, ref->new_sha1)) {
-                       ref->status = REF_STATUS_UPTODATE;
-                       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.
-                *
-                * (4) regardless of all of the above, removing :B is
-                *     always allowed.
-                */
-
-               ref->nonfastforward =
-                   !ref->deletion &&
-                   !is_null_sha1(ref->old_sha1) &&
-                   (!has_sha1_file(ref->old_sha1)
-                     || !ref_newer(ref->new_sha1, ref->old_sha1));
-
-               if (ref->nonfastforward && !ref->force && !args->force_update) {
-                       ref->status = REF_STATUS_REJECT_NONFASTFORWARD;
-                       continue;
-               }
-
-               if (!ref->deletion)
-                       new_refs++;
-
-               if (!args->dry_run) {
-                       char *old_hex = sha1_to_hex(ref->old_sha1);
-                       char *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);
-               }
-               ref->status = expect_status_report ?
-                       REF_STATUS_EXPECTING_REPORT :
-                       REF_STATUS_OK;
-       }
-
-       packet_flush(out);
-       if (new_refs && !args->dry_run) {
-               if (pack_objects(out, remote_refs, extra_have, args) < 0) {
-                       for (ref = remote_refs; ref; ref = ref->next)
-                               ref->status = REF_STATUS_NONE;
-                       return -1;
-               }
-       }
-
-       if (expect_status_report)
-               ret = receive_status(in, remote_refs);
-       else
-               ret = 0;
-
-       if (ret < 0)
-               return ret;
-       for (ref = remote_refs; ref; ref = ref->next) {
-               switch (ref->status) {
-               case REF_STATUS_NONE:
-               case REF_STATUS_UPTODATE:
-               case REF_STATUS_OK:
-                       break;
-               default:
-                       return -1;
-               }
-       }
-       return 0;
-}
-
-static void verify_remote_names(int nr_heads, const char **heads)
-{
-       int i;
-
-       for (i = 0; i < nr_heads; i++) {
-               const char *local = heads[i];
-               const char *remote = strrchr(heads[i], ':');
-
-               if (*local == '+')
-                       local++;
-
-               /* A matching refspec is okay.  */
-               if (remote == local && remote[1] == '\0')
-                       continue;
-
-               remote = remote ? (remote + 1) : local;
-               switch (check_ref_format(remote)) {
-               case 0: /* ok */
-               case CHECK_REF_FORMAT_ONELEVEL:
-                       /* ok but a single level -- that is fine for
-                        * a match pattern.
-                        */
-               case CHECK_REF_FORMAT_WILDCARD:
-                       /* ok but ends with a pattern-match character */
-                       continue;
-               }
-               die("remote part of refspec is not a valid name in %s",
-                   heads[i]);
-       }
-}
-
-int cmd_send_pack(int argc, const char **argv, const char *prefix)
-{
-       int i, nr_refspecs = 0;
-       const char **refspecs = NULL;
-       const char *remote_name = NULL;
-       struct remote *remote = NULL;
-       const char *dest = NULL;
-       int fd[2];
-       struct child_process *conn;
-       struct extra_have_objects extra_have;
-       struct ref *remote_refs, **remote_tail, *local_refs;
-       int ret;
-       int send_all = 0;
-       const char *receivepack = "git-receive-pack";
-       int flags;
-
-       argv++;
-       for (i = 1; i < argc; i++, argv++) {
-               const char *arg = *argv;
-
-               if (*arg == '-') {
-                       if (!prefixcmp(arg, "--receive-pack=")) {
-                               receivepack = arg + 15;
-                               continue;
-                       }
-                       if (!prefixcmp(arg, "--exec=")) {
-                               receivepack = arg + 7;
-                               continue;
-                       }
-                       if (!prefixcmp(arg, "--remote=")) {
-                               remote_name = arg + 9;
-                               continue;
-                       }
-                       if (!strcmp(arg, "--all")) {
-                               send_all = 1;
-                               continue;
-                       }
-                       if (!strcmp(arg, "--dry-run")) {
-                               args.dry_run = 1;
-                               continue;
-                       }
-                       if (!strcmp(arg, "--mirror")) {
-                               args.send_mirror = 1;
-                               continue;
-                       }
-                       if (!strcmp(arg, "--force")) {
-                               args.force_update = 1;
-                               continue;
-                       }
-                       if (!strcmp(arg, "--verbose")) {
-                               args.verbose = 1;
-                               continue;
-                       }
-                       if (!strcmp(arg, "--thin")) {
-                               args.use_thin_pack = 1;
-                               continue;
-                       }
-                       usage(send_pack_usage);
-               }
-               if (!dest) {
-                       dest = arg;
-                       continue;
-               }
-               refspecs = (const char **) argv;
-               nr_refspecs = argc - i;
-               break;
-       }
-       if (!dest)
-               usage(send_pack_usage);
-       /*
-        * --all and --mirror are incompatible; neither makes sense
-        * with any refspecs.
-        */
-       if ((refspecs && (send_all || args.send_mirror)) ||
-           (send_all && args.send_mirror))
-               usage(send_pack_usage);
-
-       if (remote_name) {
-               remote = remote_get(remote_name);
-               if (!remote_has_url(remote, dest)) {
-                       die("Destination %s is not a uri for %s",
-                           dest, remote_name);
-               }
-       }
-
-       conn = git_connect(fd, dest, receivepack, args.verbose ? CONNECT_VERBOSE : 0);
-
-       memset(&extra_have, 0, sizeof(extra_have));
-
-       get_remote_heads(fd[0], &remote_refs, 0, NULL, REF_NORMAL,
-                        &extra_have);
-
-       verify_remote_names(nr_refspecs, refspecs);
-
-       local_refs = get_local_heads();
-
-       flags = MATCH_REFS_NONE;
-
-       if (send_all)
-               flags |= MATCH_REFS_ALL;
-       if (args.send_mirror)
-               flags |= MATCH_REFS_MIRROR;
-
-       /* match them up */
-       remote_tail = &remote_refs;
-       while (*remote_tail)
-               remote_tail = &((*remote_tail)->next);
-       if (match_refs(local_refs, remote_refs, &remote_tail,
-                      nr_refspecs, refspecs, flags)) {
-               return -1;
-       }
-
-       ret = send_pack(&args, fd, conn, remote_refs, &extra_have);
-
-       close(fd[1]);
-       close(fd[0]);
-
-       ret |= finish_connect(conn);
-
-       print_push_status(dest, remote_refs);
-
-       if (!args.dry_run && remote) {
-               struct ref *ref;
-               for (ref = remote_refs; ref; ref = ref->next)
-                       update_tracking_ref(remote, ref);
-       }
-
-       if (!ret && !refs_pushed(remote_refs))
-               fprintf(stderr, "Everything up-to-date\n");
-
-       return ret;
-}
diff --git a/builtin-shortlog.c b/builtin-shortlog.c
deleted file mode 100644 (file)
index b28091b..0000000
+++ /dev/null
@@ -1,337 +0,0 @@
-#include "builtin.h"
-#include "cache.h"
-#include "commit.h"
-#include "diff.h"
-#include "string-list.h"
-#include "revision.h"
-#include "utf8.h"
-#include "mailmap.h"
-#include "shortlog.h"
-#include "parse-options.h"
-
-static char const * const shortlog_usage[] = {
-       "git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] [<commit-id>... ]",
-       "",
-       "[rev-opts] are documented in git-rev-list(1)",
-       NULL
-};
-
-static int compare_by_number(const void *a1, const void *a2)
-{
-       const struct string_list_item *i1 = a1, *i2 = a2;
-       const struct string_list *l1 = i1->util, *l2 = i2->util;
-
-       if (l1->nr < l2->nr)
-               return 1;
-       else if (l1->nr == l2->nr)
-               return 0;
-       else
-               return -1;
-}
-
-const char *format_subject(struct strbuf *sb, const char *msg,
-                          const char *line_separator);
-
-static void insert_one_record(struct shortlog *log,
-                             const char *author,
-                             const char *oneline)
-{
-       const char *dot3 = log->common_repo_prefix;
-       char *buffer, *p;
-       struct string_list_item *item;
-       char namebuf[1024];
-       char emailbuf[1024];
-       size_t len;
-       const char *eol;
-       const char *boemail, *eoemail;
-       struct strbuf subject = STRBUF_INIT;
-
-       boemail = strchr(author, '<');
-       if (!boemail)
-               return;
-       eoemail = strchr(boemail, '>');
-       if (!eoemail)
-               return;
-
-       /* copy author name to namebuf, to support matching on both name and email */
-       memcpy(namebuf, author, boemail - author);
-       len = boemail - author;
-       while(len > 0 && isspace(namebuf[len-1]))
-               len--;
-       namebuf[len] = 0;
-
-       /* copy email name to emailbuf, to allow email replacement as well */
-       memcpy(emailbuf, boemail+1, eoemail - boemail);
-       emailbuf[eoemail - boemail - 1] = 0;
-
-       if (!map_user(&log->mailmap, emailbuf, sizeof(emailbuf), namebuf, sizeof(namebuf))) {
-               while (author < boemail && isspace(*author))
-                       author++;
-               for (len = 0;
-                    len < sizeof(namebuf) - 1 && author + len < boemail;
-                    len++)
-                       namebuf[len] = author[len];
-               while (0 < len && isspace(namebuf[len-1]))
-                       len--;
-               namebuf[len] = '\0';
-       }
-       else
-               len = strlen(namebuf);
-
-       if (log->email) {
-               size_t room = sizeof(namebuf) - len - 1;
-               int maillen = strlen(emailbuf);
-               snprintf(namebuf + len, room, " <%.*s>", maillen, emailbuf);
-       }
-
-       item = string_list_insert(namebuf, &log->list);
-       if (item->util == NULL)
-               item->util = xcalloc(1, sizeof(struct string_list));
-
-       /* Skip any leading whitespace, including any blank lines. */
-       while (*oneline && isspace(*oneline))
-               oneline++;
-       eol = strchr(oneline, '\n');
-       if (!eol)
-               eol = oneline + strlen(oneline);
-       if (!prefixcmp(oneline, "[PATCH")) {
-               char *eob = strchr(oneline, ']');
-               if (eob && (!eol || eob < eol))
-                       oneline = eob + 1;
-       }
-       while (*oneline && isspace(*oneline) && *oneline != '\n')
-               oneline++;
-       format_subject(&subject, oneline, " ");
-       buffer = strbuf_detach(&subject, NULL);
-
-       if (dot3) {
-               int dot3len = strlen(dot3);
-               if (dot3len > 5) {
-                       while ((p = strstr(buffer, dot3)) != NULL) {
-                               int taillen = strlen(p) - dot3len;
-                               memcpy(p, "/.../", 5);
-                               memmove(p + 5, p + dot3len, taillen + 1);
-                       }
-               }
-       }
-
-       string_list_append(buffer, item->util);
-}
-
-static void read_from_stdin(struct shortlog *log)
-{
-       char author[1024], oneline[1024];
-
-       while (fgets(author, sizeof(author), stdin) != NULL) {
-               if (!(author[0] == 'A' || author[0] == 'a') ||
-                   prefixcmp(author + 1, "uthor: "))
-                       continue;
-               while (fgets(oneline, sizeof(oneline), stdin) &&
-                      oneline[0] != '\n')
-                       ; /* discard headers */
-               while (fgets(oneline, sizeof(oneline), stdin) &&
-                      oneline[0] == '\n')
-                       ; /* discard blanks */
-               insert_one_record(log, author + 8, oneline);
-       }
-}
-
-void shortlog_add_commit(struct shortlog *log, struct commit *commit)
-{
-       const char *author = NULL, *buffer;
-
-       buffer = commit->buffer;
-       while (*buffer && *buffer != '\n') {
-               const char *eol = strchr(buffer, '\n');
-
-               if (eol == NULL)
-                       eol = buffer + strlen(buffer);
-               else
-                       eol++;
-
-               if (!prefixcmp(buffer, "author "))
-                       author = buffer + 7;
-               buffer = eol;
-       }
-       if (!author)
-               die("Missing author: %s",
-                   sha1_to_hex(commit->object.sha1));
-       if (log->user_format) {
-               struct strbuf buf = STRBUF_INIT;
-
-               pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &buf,
-                       DEFAULT_ABBREV, "", "", DATE_NORMAL, 0);
-               insert_one_record(log, author, buf.buf);
-               strbuf_release(&buf);
-               return;
-       }
-       if (*buffer)
-               buffer++;
-       insert_one_record(log, author, !*buffer ? "<none>" : buffer);
-}
-
-static void get_from_rev(struct rev_info *rev, struct shortlog *log)
-{
-       struct commit *commit;
-
-       if (prepare_revision_walk(rev))
-               die("revision walk setup failed");
-       while ((commit = get_revision(rev)) != NULL)
-               shortlog_add_commit(log, commit);
-}
-
-static int parse_uint(char const **arg, int comma, int defval)
-{
-       unsigned long ul;
-       int ret;
-       char *endp;
-
-       ul = strtoul(*arg, &endp, 10);
-       if (*endp && *endp != comma)
-               return -1;
-       if (ul > INT_MAX)
-               return -1;
-       ret = *arg == endp ? defval : (int)ul;
-       *arg = *endp ? endp + 1 : endp;
-       return ret;
-}
-
-static const char wrap_arg_usage[] = "-w[<width>[,<indent1>[,<indent2>]]]";
-#define DEFAULT_WRAPLEN 76
-#define DEFAULT_INDENT1 6
-#define DEFAULT_INDENT2 9
-
-static int parse_wrap_args(const struct option *opt, const char *arg, int unset)
-{
-       struct shortlog *log = opt->value;
-
-       log->wrap_lines = !unset;
-       if (unset)
-               return 0;
-       if (!arg) {
-               log->wrap = DEFAULT_WRAPLEN;
-               log->in1 = DEFAULT_INDENT1;
-               log->in2 = DEFAULT_INDENT2;
-               return 0;
-       }
-
-       log->wrap = parse_uint(&arg, ',', DEFAULT_WRAPLEN);
-       log->in1 = parse_uint(&arg, ',', DEFAULT_INDENT1);
-       log->in2 = parse_uint(&arg, '\0', DEFAULT_INDENT2);
-       if (log->wrap < 0 || log->in1 < 0 || log->in2 < 0)
-               return error(wrap_arg_usage);
-       if (log->wrap &&
-           ((log->in1 && log->wrap <= log->in1) ||
-            (log->in2 && log->wrap <= log->in2)))
-               return error(wrap_arg_usage);
-       return 0;
-}
-
-void shortlog_init(struct shortlog *log)
-{
-       memset(log, 0, sizeof(*log));
-
-       read_mailmap(&log->mailmap, &log->common_repo_prefix);
-
-       log->list.strdup_strings = 1;
-       log->wrap = DEFAULT_WRAPLEN;
-       log->in1 = DEFAULT_INDENT1;
-       log->in2 = DEFAULT_INDENT2;
-}
-
-int cmd_shortlog(int argc, const char **argv, const char *prefix)
-{
-       static struct shortlog log;
-       static struct rev_info rev;
-       int nongit;
-
-       static const struct option options[] = {
-               OPT_BOOLEAN('n', "numbered", &log.sort_by_number,
-                           "sort output according to the number of commits per author"),
-               OPT_BOOLEAN('s', "summary", &log.summary,
-                           "Suppress commit descriptions, only provides commit count"),
-               OPT_BOOLEAN('e', "email", &log.email,
-                           "Show the email address of each author"),
-               { OPTION_CALLBACK, 'w', NULL, &log, "w[,i1[,i2]]",
-                       "Linewrap output", PARSE_OPT_OPTARG, &parse_wrap_args },
-               OPT_END(),
-       };
-
-       struct parse_opt_ctx_t ctx;
-
-       prefix = setup_git_directory_gently(&nongit);
-       git_config(git_default_config, NULL);
-       shortlog_init(&log);
-       init_revisions(&rev, prefix);
-       parse_options_start(&ctx, argc, argv, PARSE_OPT_KEEP_DASHDASH |
-                           PARSE_OPT_KEEP_ARGV0);
-
-       for (;;) {
-               switch (parse_options_step(&ctx, options, shortlog_usage)) {
-               case PARSE_OPT_HELP:
-                       exit(129);
-               case PARSE_OPT_DONE:
-                       goto parse_done;
-               }
-               parse_revision_opt(&rev, &ctx, options, shortlog_usage);
-       }
-parse_done:
-       argc = parse_options_end(&ctx);
-
-       if (setup_revisions(argc, argv, &rev, NULL) != 1) {
-               error("unrecognized argument: %s", argv[1]);
-               usage_with_options(shortlog_usage, options);
-       }
-
-       log.user_format = rev.commit_format == CMIT_FMT_USERFORMAT;
-
-       /* assume HEAD if from a tty */
-       if (!nongit && !rev.pending.nr && isatty(0))
-               add_head_to_pending(&rev);
-       if (rev.pending.nr == 0) {
-               read_from_stdin(&log);
-       }
-       else
-               get_from_rev(&rev, &log);
-
-       shortlog_output(&log);
-       return 0;
-}
-
-void shortlog_output(struct shortlog *log)
-{
-       int i, j;
-       if (log->sort_by_number)
-               qsort(log->list.items, log->list.nr, sizeof(struct string_list_item),
-                       compare_by_number);
-       for (i = 0; i < log->list.nr; i++) {
-               struct string_list *onelines = log->list.items[i].util;
-
-               if (log->summary) {
-                       printf("%6d\t%s\n", onelines->nr, log->list.items[i].string);
-               } else {
-                       printf("%s (%d):\n", log->list.items[i].string, onelines->nr);
-                       for (j = onelines->nr - 1; j >= 0; j--) {
-                               const char *msg = onelines->items[j].string;
-
-                               if (log->wrap_lines) {
-                                       int col = print_wrapped_text(msg, log->in1, log->in2, log->wrap);
-                                       if (col != log->wrap)
-                                               putchar('\n');
-                               }
-                               else
-                                       printf("      %s\n", msg);
-                       }
-                       putchar('\n');
-               }
-
-               onelines->strdup_strings = 1;
-               string_list_clear(onelines, 0);
-               free(onelines);
-               log->list.items[i].util = NULL;
-       }
-
-       log->list.strdup_strings = 1;
-       string_list_clear(&log->list, 1);
-       clear_mailmap(&log->mailmap);
-}
diff --git a/builtin-show-branch.c b/builtin-show-branch.c
deleted file mode 100644 (file)
index 828e6f8..0000000
+++ /dev/null
@@ -1,916 +0,0 @@
-#include "cache.h"
-#include "commit.h"
-#include "refs.h"
-#include "builtin.h"
-
-static const char show_branch_usage[] =
-"git show-branch [--sparse] [--current] [--all] [--remotes] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...] | --reflog[=n[,b]] <branch>";
-static const char show_branch_usage_reflog[] =
-"--reflog is incompatible with --all, --remotes, --independent or --merge-base";
-
-static int default_num;
-static int default_alloc;
-static const char **default_arg;
-
-#define UNINTERESTING  01
-
-#define REV_SHIFT       2
-#define MAX_REVS       (FLAG_BITS - REV_SHIFT) /* should not exceed bits_per_int - REV_SHIFT */
-
-#define DEFAULT_REFLOG 4
-
-static struct commit *interesting(struct commit_list *list)
-{
-       while (list) {
-               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->util)
-               commit->util = xmalloc(sizeof(struct commit_name));
-       name = commit->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->util;
-       struct commit_name *parent_name = parent->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->util)
-                       break;
-               if (!c->parents)
-                       break;
-               p = c->parents->item;
-               if (!p->util) {
-                       name_parent(c, p);
-                       i++;
-               }
-               else
-                       break;
-               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->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->util)
-                               continue;
-                       n = c->util;
-                       parents = c->parents;
-                       nth = 0;
-                       while (parents) {
-                               struct commit *p = parents->item;
-                               char newname[1000], *en;
-                               parents = parents->next;
-                               nth++;
-                               if (p->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, xstrdup(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) {
-               commit_list_insert(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.  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)
-{
-       struct strbuf pretty = STRBUF_INIT;
-       const char *pretty_str = "(unavailable)";
-       struct commit_name *name = commit->util;
-
-       if (commit->object.parsed) {
-               pretty_print_commit(CMIT_FMT_ONELINE, commit,
-                                   &pretty, 0, NULL, NULL, 0, 0);
-               pretty_str = pretty.buf;
-       }
-       if (!prefixcmp(pretty_str, "[PATCH] "))
-               pretty_str += 8;
-
-       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(pretty_str);
-       strbuf_release(&pretty);
-}
-
-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,
-                     int allow_dups)
-{
-       struct commit *commit = lookup_commit_reference_gently(sha1, 1);
-       int i;
-
-       if (!commit)
-               return 0;
-
-       if (!allow_dups) {
-               /* Avoid adding the same thing twice */
-               for (i = 0; i < ref_name_cnt; i++)
-                       if (!strcmp(refname, ref_name[i]))
-                               return 0;
-       }
-       if (MAX_REVS <= ref_name_cnt) {
-               warning("ignoring %s; cannot handle more than %d refs",
-                       refname, MAX_REVS);
-               return 0;
-       }
-       ref_name[ref_name_cnt++] = xstrdup(refname);
-       ref_name[ref_name_cnt] = NULL;
-       return 0;
-}
-
-static int append_head_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
-{
-       unsigned char tmp[20];
-       int ofs = 11;
-       if (prefixcmp(refname, "refs/heads/"))
-               return 0;
-       /* If both heads/foo and tags/foo exists, get_sha1 would
-        * get confused.
-        */
-       if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
-               ofs = 5;
-       return append_ref(refname + ofs, sha1, 0);
-}
-
-static int append_remote_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
-{
-       unsigned char tmp[20];
-       int ofs = 13;
-       if (prefixcmp(refname, "refs/remotes/"))
-               return 0;
-       /* If both heads/foo and tags/foo exists, get_sha1 would
-        * get confused.
-        */
-       if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
-               ofs = 5;
-       return append_ref(refname + ofs, sha1, 0);
-}
-
-static int append_tag_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
-{
-       if (prefixcmp(refname, "refs/tags/"))
-               return 0;
-       return append_ref(refname + 5, sha1, 0);
-}
-
-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, int flag, void *cb_data)
-{
-       /* 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 (!prefixcmp(refname, "refs/heads/"))
-               return append_head_ref(refname, sha1, flag, cb_data);
-       if (!prefixcmp(refname, "refs/tags/"))
-               return append_tag_ref(refname, sha1, flag, cb_data);
-       return append_ref(refname, sha1, 0);
-}
-
-static void snarf_refs(int head, int remotes)
-{
-       if (head) {
-               int orig_cnt = ref_name_cnt;
-               for_each_ref(append_head_ref, NULL);
-               sort_ref_range(orig_cnt, ref_name_cnt);
-       }
-       if (remotes) {
-               int orig_cnt = ref_name_cnt;
-               for_each_ref(append_remote_ref, NULL);
-               sort_ref_range(orig_cnt, ref_name_cnt);
-       }
-}
-
-static int rev_is_head(char *head, int headlen, char *name,
-                      unsigned char *head_sha1, unsigned char *sha1)
-{
-       if ((!head[0]) ||
-           (head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
-               return 0;
-       if (!prefixcmp(head, "refs/heads/"))
-               head += 11;
-       if (!prefixcmp(name, "refs/heads/"))
-               name += 11;
-       else if (!prefixcmp(name, "heads/"))
-               name += 6;
-       return !strcmp(head, name);
-}
-
-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, 0);
-               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, NULL);
-               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, void *cb)
-{
-       if (!strcmp(var, "showbranch.default")) {
-               if (!value)
-                       return config_error_nonbool(var);
-               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++] = xstrdup(value);
-               default_arg[default_num] = NULL;
-               return 0;
-       }
-
-       return git_default_config(var, value, cb);
-}
-
-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;
-}
-
-static void parse_reflog_param(const char *arg, int *cnt, const char **base)
-{
-       char *ep;
-       *cnt = strtoul(arg, &ep, 10);
-       if (*ep == ',')
-               *base = ep + 1;
-       else if (*ep)
-               die("unrecognized reflog param '%s'", arg + 9);
-       else
-               *base = NULL;
-       if (*cnt <= 0)
-               *cnt = DEFAULT_REFLOG;
-}
-
-int cmd_show_branch(int ac, const char **av, const char *prefix)
-{
-       struct commit *rev[MAX_REVS], *commit;
-       char *reflog_msg[MAX_REVS];
-       struct commit_list *list = NULL, *seen = NULL;
-       unsigned int rev_mask[MAX_REVS];
-       int num_rev, i, extra = 0;
-       int all_heads = 0, all_remotes = 0;
-       int all_mask, all_revs;
-       int lifo = 1;
-       char head[128];
-       const char *head_p;
-       int head_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;
-       int reflog = 0;
-       const char *reflog_base = NULL;
-
-       git_config(git_show_branch_config, NULL);
-
-       /* 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") || !strcmp(arg, "-a"))
-                       all_heads = all_remotes = 1;
-               else if (!strcmp(arg, "--remotes") || !strcmp(arg, "-r"))
-                       all_remotes = 1;
-               else if (!strcmp(arg, "--more"))
-                       extra = 1;
-               else if (!strcmp(arg, "--list"))
-                       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 (!prefixcmp(arg, "--more="))
-                       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 if (!strcmp(arg, "--reflog") || !strcmp(arg, "-g")) {
-                       reflog = DEFAULT_REFLOG;
-               }
-               else if (!prefixcmp(arg, "--reflog="))
-                       parse_reflog_param(arg + 9, &reflog, &reflog_base);
-               else if (!prefixcmp(arg, "-g="))
-                       parse_reflog_param(arg + 3, &reflog, &reflog_base);
-               else
-                       usage(show_branch_usage);
-               ac--; av++;
-       }
-       ac--; av++;
-
-       if (extra || reflog) {
-               /* "listing" mode is incompatible with
-                * independent nor merge-base modes.
-                */
-               if (independent || merge_base)
-                       usage(show_branch_usage);
-               if (reflog && ((0 < extra) || all_heads || all_remotes))
-                       /*
-                        * Asking for --more in reflog mode does not
-                        * make sense.  --list is Ok.
-                        *
-                        * Also --all and --remotes do not make sense either.
-                        */
-                       usage(show_branch_usage_reflog);
-       }
-
-       /* If nothing is specified, show all branches by default */
-       if (ac + all_heads + all_remotes == 0)
-               all_heads = 1;
-
-       if (reflog) {
-               unsigned char sha1[20];
-               char nth_desc[256];
-               char *ref;
-               int base = 0;
-
-               if (ac == 0) {
-                       static const char *fake_av[2];
-                       const char *refname;
-
-                       refname = resolve_ref("HEAD", sha1, 1, NULL);
-                       fake_av[0] = xstrdup(refname);
-                       fake_av[1] = NULL;
-                       av = fake_av;
-                       ac = 1;
-               }
-               if (ac != 1)
-                       die("--reflog option needs one branch name");
-
-               if (MAX_REVS < reflog)
-                       die("Only %d entries can be shown at one time.",
-                           MAX_REVS);
-               if (!dwim_ref(*av, strlen(*av), sha1, &ref))
-                       die("No such ref %s", *av);
-
-               /* Has the base been specified? */
-               if (reflog_base) {
-                       char *ep;
-                       base = strtoul(reflog_base, &ep, 10);
-                       if (*ep) {
-                               /* Ah, that is a date spec... */
-                               unsigned long at;
-                               at = approxidate(reflog_base);
-                               read_ref_at(ref, at, -1, sha1, NULL,
-                                           NULL, NULL, &base);
-                       }
-               }
-
-               for (i = 0; i < reflog; i++) {
-                       char *logmsg, *m;
-                       const char *msg;
-                       unsigned long timestamp;
-                       int tz;
-
-                       if (read_ref_at(ref, 0, base+i, sha1, &logmsg,
-                                       &timestamp, &tz, NULL)) {
-                               reflog = i;
-                               break;
-                       }
-                       msg = strchr(logmsg, '\t');
-                       if (!msg)
-                               msg = "(none)";
-                       else
-                               msg++;
-                       m = xmalloc(strlen(msg) + 200);
-                       sprintf(m, "(%s) %s",
-                               show_date(timestamp, tz, 1),
-                               msg);
-                       reflog_msg[i] = m;
-                       free(logmsg);
-                       sprintf(nth_desc, "%s@{%d}", *av, base+i);
-                       append_ref(nth_desc, sha1, 1);
-               }
-       }
-       else if (all_heads + all_remotes)
-               snarf_refs(all_heads, all_remotes);
-       else {
-               while (0 < ac) {
-                       append_one_rev(*av);
-                       ac--; av++;
-               }
-       }
-
-       head_p = resolve_ref("HEAD", head_sha1, 1, NULL);
-       if (head_p) {
-               head_len = strlen(head_p);
-               memcpy(head, head_p, head_len + 1);
-       }
-       else {
-               head_len = 0;
-               head[0] = 0;
-       }
-
-       if (with_current_branch && head_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,
-                                       head_len,
-                                       ref_name[i],
-                                       head_sha1, NULL))
-                               has_head++;
-               }
-               if (!has_head) {
-                       int offset = !prefixcmp(head, "refs/heads/") ? 11 : 0;
-                       append_one_rev(head + offset);
-               }
-       }
-
-       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);
-
-       sort_by_date(&seen);
-
-       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,
-                                                 head_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]);
-                       }
-
-                       if (!reflog) {
-                               /* header lines never need name */
-                               show_one_commit(rev[i], 1);
-                       }
-                       else
-                               puts(reflog_msg[i]);
-
-                       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-show-ref.c b/builtin-show-ref.c
deleted file mode 100644 (file)
index dc76c50..0000000
+++ /dev/null
@@ -1,256 +0,0 @@
-#include "builtin.h"
-#include "cache.h"
-#include "refs.h"
-#include "object.h"
-#include "tag.h"
-#include "string-list.h"
-
-static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=<length>]] [--abbrev[=<length>]] [--tags] [--heads] [--] [pattern*] < ref-list";
-
-static int deref_tags = 0, show_head = 0, tags_only = 0, heads_only = 0,
-       found_match = 0, verify = 0, quiet = 0, hash_only = 0, abbrev = 0;
-static const char **pattern;
-
-static void show_one(const char *refname, const unsigned char *sha1)
-{
-       const char *hex = find_unique_abbrev(sha1, abbrev);
-       if (hash_only)
-               printf("%s\n", hex);
-       else
-               printf("%s %s\n", hex, refname);
-}
-
-static int show_ref(const char *refname, const unsigned char *sha1, int flag, void *cbdata)
-{
-       struct object *obj;
-       const char *hex;
-       unsigned char peeled[20];
-
-       if (tags_only || heads_only) {
-               int match;
-
-               match = heads_only && !prefixcmp(refname, "refs/heads/");
-               match |= tags_only && !prefixcmp(refname, "refs/tags/");
-               if (!match)
-                       return 0;
-       }
-       if (pattern) {
-               int reflen = strlen(refname);
-               const char **p = pattern, *m;
-               while ((m = *p++) != NULL) {
-                       int len = strlen(m);
-                       if (len > reflen)
-                               continue;
-                       if (memcmp(m, refname + reflen - len, len))
-                               continue;
-                       if (len == reflen)
-                               goto match;
-                       /* "--verify" requires an exact match */
-                       if (verify)
-                               continue;
-                       if (refname[reflen - len - 1] == '/')
-                               goto match;
-               }
-               return 0;
-       }
-
-match:
-       found_match++;
-
-       /* This changes the semantics slightly that even under quiet we
-        * detect and return error if the repository is corrupt and
-        * ref points at a nonexistent object.
-        */
-       if (!has_sha1_file(sha1))
-               die("git show-ref: bad ref %s (%s)", refname,
-                   sha1_to_hex(sha1));
-
-       if (quiet)
-               return 0;
-
-       show_one(refname, sha1);
-
-       if (!deref_tags)
-               return 0;
-
-       if ((flag & REF_ISPACKED) && !peel_ref(refname, peeled)) {
-               if (!is_null_sha1(peeled)) {
-                       hex = find_unique_abbrev(peeled, abbrev);
-                       printf("%s %s^{}\n", hex, refname);
-               }
-       }
-       else {
-               obj = parse_object(sha1);
-               if (!obj)
-                       die("git show-ref: bad ref %s (%s)", refname,
-                           sha1_to_hex(sha1));
-               if (obj->type == OBJ_TAG) {
-                       obj = deref_tag(obj, refname, 0);
-                       if (!obj)
-                               die("git show-ref: bad tag at ref %s (%s)", refname,
-                                   sha1_to_hex(sha1));
-                       hex = find_unique_abbrev(obj->sha1, abbrev);
-                       printf("%s %s^{}\n", hex, refname);
-               }
-       }
-       return 0;
-}
-
-static int add_existing(const char *refname, const unsigned char *sha1, int flag, void *cbdata)
-{
-       struct string_list *list = (struct string_list *)cbdata;
-       string_list_insert(refname, list);
-       return 0;
-}
-
-/*
- * read "^(?:<anything>\s)?<refname>(?:\^\{\})?$" from the standard input,
- * and
- * (1) strip "^{}" at the end of line if any;
- * (2) ignore if match is provided and does not head-match refname;
- * (3) warn if refname is not a well-formed refname and skip;
- * (4) ignore if refname is a ref that exists in the local repository;
- * (5) otherwise output the line.
- */
-static int exclude_existing(const char *match)
-{
-       static struct string_list existing_refs = { NULL, 0, 0, 0 };
-       char buf[1024];
-       int matchlen = match ? strlen(match) : 0;
-
-       for_each_ref(add_existing, &existing_refs);
-       while (fgets(buf, sizeof(buf), stdin)) {
-               char *ref;
-               int len = strlen(buf);
-
-               if (len > 0 && buf[len - 1] == '\n')
-                       buf[--len] = '\0';
-               if (3 <= len && !strcmp(buf + len - 3, "^{}")) {
-                       len -= 3;
-                       buf[len] = '\0';
-               }
-               for (ref = buf + len; buf < ref; ref--)
-                       if (isspace(ref[-1]))
-                               break;
-               if (match) {
-                       int reflen = buf + len - ref;
-                       if (reflen < matchlen)
-                               continue;
-                       if (strncmp(ref, match, matchlen))
-                               continue;
-               }
-               if (check_ref_format(ref)) {
-                       warning("ref '%s' ignored", ref);
-                       continue;
-               }
-               if (!string_list_has_string(&existing_refs, ref)) {
-                       printf("%s\n", buf);
-               }
-       }
-       return 0;
-}
-
-int cmd_show_ref(int argc, const char **argv, const char *prefix)
-{
-       int i;
-
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-               if (*arg != '-') {
-                       pattern = argv + i;
-                       break;
-               }
-               if (!strcmp(arg, "--")) {
-                       pattern = argv + i + 1;
-                       if (!*pattern)
-                               pattern = NULL;
-                       break;
-               }
-               if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet")) {
-                       quiet = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-h") || !strcmp(arg, "--head")) {
-                       show_head = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-d") || !strcmp(arg, "--dereference")) {
-                       deref_tags = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-s") || !strcmp(arg, "--hash")) {
-                       hash_only = 1;
-                       continue;
-               }
-               if (!prefixcmp(arg, "--hash=") ||
-                   (!prefixcmp(arg, "--abbrev") &&
-                    (arg[8] == '=' || arg[8] == '\0'))) {
-                       if (arg[2] != 'h' && !arg[8])
-                               /* --abbrev only */
-                               abbrev = DEFAULT_ABBREV;
-                       else {
-                               /* --hash= or --abbrev= */
-                               char *end;
-                               if (arg[2] == 'h') {
-                                       hash_only = 1;
-                                       arg += 7;
-                               }
-                               else
-                                       arg += 9;
-                               abbrev = strtoul(arg, &end, 10);
-                               if (*end || abbrev > 40)
-                                       usage(show_ref_usage);
-                               if (abbrev < MINIMUM_ABBREV)
-                                       abbrev = MINIMUM_ABBREV;
-                       }
-                       continue;
-               }
-               if (!strcmp(arg, "--verify")) {
-                       verify = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--tags")) {
-                       tags_only = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--heads")) {
-                       heads_only = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--exclude-existing"))
-                       return exclude_existing(NULL);
-               if (!prefixcmp(arg, "--exclude-existing="))
-                       return exclude_existing(arg + 19);
-               usage(show_ref_usage);
-       }
-
-       if (verify) {
-               if (!pattern)
-                       die("--verify requires a reference");
-               while (*pattern) {
-                       unsigned char sha1[20];
-
-                       if (!prefixcmp(*pattern, "refs/") &&
-                           resolve_ref(*pattern, sha1, 1, NULL)) {
-                               if (!quiet)
-                                       show_one(*pattern, sha1);
-                       }
-                       else if (!quiet)
-                               die("'%s' - not a valid ref", *pattern);
-                       else
-                               return 1;
-                       pattern++;
-               }
-               return 0;
-       }
-
-       if (show_head)
-               head_ref(show_ref, NULL);
-       for_each_ref(show_ref, NULL);
-       if (!found_match) {
-               if (verify && !quiet)
-                       die("No match");
-               return 1;
-       }
-       return 0;
-}
diff --git a/builtin-stripspace.c b/builtin-stripspace.c
deleted file mode 100644 (file)
index d6e3896..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-#include "builtin.h"
-#include "cache.h"
-
-/*
- * Returns the length of a line, without trailing spaces.
- *
- * If the line ends with newline, it will be removed too.
- */
-static size_t cleanup(char *line, size_t len)
-{
-       while (len) {
-               unsigned char c = line[len - 1];
-               if (!isspace(c))
-                       break;
-               len--;
-       }
-
-       return len;
-}
-
-/*
- * Remove empty lines from the beginning and end
- * and also trailing spaces from every line.
- *
- * Note that the buffer will not be NUL-terminated.
- *
- * Turn multiple consecutive empty lines between paragraphs
- * into just one empty line.
- *
- * If the input has only empty lines and spaces,
- * no output will be produced.
- *
- * If last line does not have a newline at the end, one is added.
- *
- * Enable skip_comments to skip every line starting with "#".
- */
-void stripspace(struct strbuf *sb, int skip_comments)
-{
-       int empties = 0;
-       size_t i, j, len, newlen;
-       char *eol;
-
-       /* We may have to add a newline. */
-       strbuf_grow(sb, 1);
-
-       for (i = j = 0; i < sb->len; i += len, j += newlen) {
-               eol = memchr(sb->buf + i, '\n', sb->len - i);
-               len = eol ? eol - (sb->buf + i) + 1 : sb->len - i;
-
-               if (skip_comments && len && sb->buf[i] == '#') {
-                       newlen = 0;
-                       continue;
-               }
-               newlen = cleanup(sb->buf + i, len);
-
-               /* Not just an empty line? */
-               if (newlen) {
-                       if (empties > 0 && j > 0)
-                               sb->buf[j++] = '\n';
-                       empties = 0;
-                       memmove(sb->buf + j, sb->buf + i, newlen);
-                       sb->buf[newlen + j++] = '\n';
-               } else {
-                       empties++;
-               }
-       }
-
-       strbuf_setlen(sb, j);
-}
-
-int cmd_stripspace(int argc, const char **argv, const char *prefix)
-{
-       struct strbuf buf = STRBUF_INIT;
-       int strip_comments = 0;
-
-       if (argc > 1 && (!strcmp(argv[1], "-s") ||
-                               !strcmp(argv[1], "--strip-comments")))
-               strip_comments = 1;
-
-       if (strbuf_read(&buf, 0, 1024) < 0)
-               die("could not read the input");
-
-       stripspace(&buf, strip_comments);
-
-       write_or_die(1, buf.buf, buf.len);
-       strbuf_release(&buf);
-       return 0;
-}
diff --git a/builtin-symbolic-ref.c b/builtin-symbolic-ref.c
deleted file mode 100644 (file)
index 6ae6bcc..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-#include "builtin.h"
-#include "cache.h"
-#include "refs.h"
-#include "parse-options.h"
-
-static const char * const git_symbolic_ref_usage[] = {
-       "git symbolic-ref [options] name [ref]",
-       NULL
-};
-
-static void check_symref(const char *HEAD, int quiet)
-{
-       unsigned char sha1[20];
-       int flag;
-       const char *refs_heads_master = resolve_ref(HEAD, sha1, 0, &flag);
-
-       if (!refs_heads_master)
-               die("No such ref: %s", HEAD);
-       else if (!(flag & REF_ISSYMREF)) {
-               if (!quiet)
-                       die("ref %s is not a symbolic ref", HEAD);
-               else
-                       exit(1);
-       }
-       puts(refs_heads_master);
-}
-
-int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
-{
-       int quiet = 0;
-       const char *msg = NULL;
-       struct option options[] = {
-               OPT__QUIET(&quiet),
-               OPT_STRING('m', NULL, &msg, "reason", "reason of the update"),
-               OPT_END(),
-       };
-
-       git_config(git_default_config, NULL);
-       argc = parse_options(argc, argv, options, git_symbolic_ref_usage, 0);
-       if (msg &&!*msg)
-               die("Refusing to perform update with empty message");
-       switch (argc) {
-       case 1:
-               check_symref(argv[0], quiet);
-               break;
-       case 2:
-               if (!strcmp(argv[0], "HEAD") &&
-                   prefixcmp(argv[1], "refs/"))
-                       die("Refusing to point HEAD outside of refs/");
-               create_symref(argv[0], argv[1], msg);
-               break;
-       default:
-               usage_with_options(git_symbolic_ref_usage, options);
-       }
-       return 0;
-}
diff --git a/builtin-tag.c b/builtin-tag.c
deleted file mode 100644 (file)
index 01e7374..0000000
+++ /dev/null
@@ -1,487 +0,0 @@
-/*
- * Builtin "git tag"
- *
- * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>,
- *                    Carlos Rica <jasampler@gmail.com>
- * Based on git-tag.sh and mktag.c by Linus Torvalds.
- */
-
-#include "cache.h"
-#include "builtin.h"
-#include "refs.h"
-#include "tag.h"
-#include "run-command.h"
-#include "parse-options.h"
-
-static const char * const git_tag_usage[] = {
-       "git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
-       "git tag -d <tagname>...",
-       "git tag -l [-n[<num>]] [<pattern>]",
-       "git tag -v <tagname>...",
-       NULL
-};
-
-static char signingkey[1000];
-
-struct tag_filter {
-       const char *pattern;
-       int lines;
-       struct commit_list *with_commit;
-};
-
-#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
-
-static int show_reference(const char *refname, const unsigned char *sha1,
-                         int flag, void *cb_data)
-{
-       struct tag_filter *filter = cb_data;
-
-       if (!fnmatch(filter->pattern, refname, 0)) {
-               int i;
-               unsigned long size;
-               enum object_type type;
-               char *buf, *sp, *eol;
-               size_t len;
-
-               if (filter->with_commit) {
-                       struct commit *commit;
-
-                       commit = lookup_commit_reference_gently(sha1, 1);
-                       if (!commit)
-                               return 0;
-                       if (!is_descendant_of(commit, filter->with_commit))
-                               return 0;
-               }
-
-               if (!filter->lines) {
-                       printf("%s\n", refname);
-                       return 0;
-               }
-               printf("%-15s ", refname);
-
-               buf = read_sha1_file(sha1, &type, &size);
-               if (!buf || !size)
-                       return 0;
-
-               /* skip header */
-               sp = strstr(buf, "\n\n");
-               if (!sp) {
-                       free(buf);
-                       return 0;
-               }
-               /* only take up to "lines" lines, and strip the signature */
-               for (i = 0, sp += 2;
-                               i < filter->lines && sp < buf + size &&
-                               prefixcmp(sp, PGP_SIGNATURE "\n");
-                               i++) {
-                       if (i)
-                               printf("\n    ");
-                       eol = memchr(sp, '\n', size - (sp - buf));
-                       len = eol ? eol - sp : size - (sp - buf);
-                       fwrite(sp, len, 1, stdout);
-                       if (!eol)
-                               break;
-                       sp = eol + 1;
-               }
-               putchar('\n');
-               free(buf);
-       }
-
-       return 0;
-}
-
-static int list_tags(const char *pattern, int lines,
-                       struct commit_list *with_commit)
-{
-       struct tag_filter filter;
-
-       if (pattern == NULL)
-               pattern = "*";
-
-       filter.pattern = pattern;
-       filter.lines = lines;
-       filter.with_commit = with_commit;
-
-       for_each_tag_ref(show_reference, (void *) &filter);
-
-       return 0;
-}
-
-typedef int (*each_tag_name_fn)(const char *name, const char *ref,
-                               const unsigned char *sha1);
-
-static int for_each_tag_name(const char **argv, each_tag_name_fn fn)
-{
-       const char **p;
-       char ref[PATH_MAX];
-       int had_error = 0;
-       unsigned char sha1[20];
-
-       for (p = argv; *p; p++) {
-               if (snprintf(ref, sizeof(ref), "refs/tags/%s", *p)
-                                       >= sizeof(ref)) {
-                       error("tag name too long: %.*s...", 50, *p);
-                       had_error = 1;
-                       continue;
-               }
-               if (!resolve_ref(ref, sha1, 1, NULL)) {
-                       error("tag '%s' not found.", *p);
-                       had_error = 1;
-                       continue;
-               }
-               if (fn(*p, ref, sha1))
-                       had_error = 1;
-       }
-       return had_error;
-}
-
-static int delete_tag(const char *name, const char *ref,
-                               const unsigned char *sha1)
-{
-       if (delete_ref(ref, sha1, 0))
-               return 1;
-       printf("Deleted tag '%s'\n", name);
-       return 0;
-}
-
-static int verify_tag(const char *name, const char *ref,
-                               const unsigned char *sha1)
-{
-       const char *argv_verify_tag[] = {"git-verify-tag",
-                                       "-v", "SHA1_HEX", NULL};
-       argv_verify_tag[2] = sha1_to_hex(sha1);
-
-       if (run_command_v_opt(argv_verify_tag, 0))
-               return error("could not verify the tag '%s'", name);
-       return 0;
-}
-
-static int do_sign(struct strbuf *buffer)
-{
-       struct child_process gpg;
-       const char *args[4];
-       char *bracket;
-       int len;
-       int i, j;
-
-       if (!*signingkey) {
-               if (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME),
-                               sizeof(signingkey)) > sizeof(signingkey) - 1)
-                       return error("committer info too long.");
-               bracket = strchr(signingkey, '>');
-               if (bracket)
-                       bracket[1] = '\0';
-       }
-
-       /* When the username signingkey is bad, program could be terminated
-        * because gpg exits without reading and then write gets SIGPIPE. */
-       signal(SIGPIPE, SIG_IGN);
-
-       memset(&gpg, 0, sizeof(gpg));
-       gpg.argv = args;
-       gpg.in = -1;
-       gpg.out = -1;
-       args[0] = "gpg";
-       args[1] = "-bsau";
-       args[2] = signingkey;
-       args[3] = NULL;
-
-       if (start_command(&gpg))
-               return error("could not run gpg.");
-
-       if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) {
-               close(gpg.in);
-               close(gpg.out);
-               finish_command(&gpg);
-               return error("gpg did not accept the tag data");
-       }
-       close(gpg.in);
-       len = strbuf_read(buffer, gpg.out, 1024);
-       close(gpg.out);
-
-       if (finish_command(&gpg) || !len || len < 0)
-               return error("gpg failed to sign the tag");
-
-       /* Strip CR from the line endings, in case we are on Windows. */
-       for (i = j = 0; i < buffer->len; i++)
-               if (buffer->buf[i] != '\r') {
-                       if (i != j)
-                               buffer->buf[j] = buffer->buf[i];
-                       j++;
-               }
-       strbuf_setlen(buffer, j);
-
-       return 0;
-}
-
-static const char tag_template[] =
-       "\n"
-       "#\n"
-       "# Write a tag message\n"
-       "#\n";
-
-static void set_signingkey(const char *value)
-{
-       if (strlcpy(signingkey, value, sizeof(signingkey)) >= sizeof(signingkey))
-               die("signing key value too long (%.10s...)", value);
-}
-
-static int git_tag_config(const char *var, const char *value, void *cb)
-{
-       if (!strcmp(var, "user.signingkey")) {
-               if (!value)
-                       return config_error_nonbool(var);
-               set_signingkey(value);
-               return 0;
-       }
-
-       return git_default_config(var, value, cb);
-}
-
-static void write_tag_body(int fd, const unsigned char *sha1)
-{
-       unsigned long size;
-       enum object_type type;
-       char *buf, *sp, *eob;
-       size_t len;
-
-       buf = read_sha1_file(sha1, &type, &size);
-       if (!buf)
-               return;
-       /* skip header */
-       sp = strstr(buf, "\n\n");
-
-       if (!sp || !size || type != OBJ_TAG) {
-               free(buf);
-               return;
-       }
-       sp += 2; /* skip the 2 LFs */
-       eob = strstr(sp, "\n" PGP_SIGNATURE "\n");
-       if (eob)
-               len = eob - sp;
-       else
-               len = buf + size - sp;
-       write_or_die(fd, sp, len);
-
-       free(buf);
-}
-
-static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result)
-{
-       if (sign && do_sign(buf) < 0)
-               return error("unable to sign the tag");
-       if (write_sha1_file(buf->buf, buf->len, tag_type, result) < 0)
-               return error("unable to write tag file");
-       return 0;
-}
-
-static void create_tag(const unsigned char *object, const char *tag,
-                      struct strbuf *buf, int message, int sign,
-                      unsigned char *prev, unsigned char *result)
-{
-       enum object_type type;
-       char header_buf[1024];
-       int header_len;
-       char *path = NULL;
-
-       type = sha1_object_info(object, NULL);
-       if (type <= OBJ_NONE)
-           die("bad object type.");
-
-       header_len = snprintf(header_buf, sizeof(header_buf),
-                         "object %s\n"
-                         "type %s\n"
-                         "tag %s\n"
-                         "tagger %s\n\n",
-                         sha1_to_hex(object),
-                         typename(type),
-                         tag,
-                         git_committer_info(IDENT_ERROR_ON_NO_NAME));
-
-       if (header_len > sizeof(header_buf) - 1)
-               die("tag header too big.");
-
-       if (!message) {
-               int fd;
-
-               /* write the template message before editing: */
-               path = git_pathdup("TAG_EDITMSG");
-               fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
-               if (fd < 0)
-                       die("could not create file '%s': %s",
-                                               path, strerror(errno));
-
-               if (!is_null_sha1(prev))
-                       write_tag_body(fd, prev);
-               else
-                       write_or_die(fd, tag_template, strlen(tag_template));
-               close(fd);
-
-               if (launch_editor(path, buf, NULL)) {
-                       fprintf(stderr,
-                       "Please supply the message using either -m or -F option.\n");
-                       exit(1);
-               }
-       }
-
-       stripspace(buf, 1);
-
-       if (!message && !buf->len)
-               die("no tag message?");
-
-       strbuf_insert(buf, 0, header_buf, header_len);
-
-       if (build_tag_object(buf, sign, result) < 0) {
-               if (path)
-                       fprintf(stderr, "The tag message has been left in %s\n",
-                               path);
-               exit(128);
-       }
-       if (path) {
-               unlink(path);
-               free(path);
-       }
-}
-
-struct msg_arg {
-       int given;
-       struct strbuf buf;
-};
-
-static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
-{
-       struct msg_arg *msg = opt->value;
-
-       if (!arg)
-               return -1;
-       if (msg->buf.len)
-               strbuf_addstr(&(msg->buf), "\n\n");
-       strbuf_addstr(&(msg->buf), arg);
-       msg->given = 1;
-       return 0;
-}
-
-int cmd_tag(int argc, const char **argv, const char *prefix)
-{
-       struct strbuf buf = STRBUF_INIT;
-       unsigned char object[20], prev[20];
-       char ref[PATH_MAX];
-       const char *object_ref, *tag;
-       struct ref_lock *lock;
-
-       int annotate = 0, sign = 0, force = 0, lines = -1,
-               list = 0, delete = 0, verify = 0;
-       const char *msgfile = NULL, *keyid = NULL;
-       struct msg_arg msg = { 0, STRBUF_INIT };
-       struct commit_list *with_commit = NULL;
-       struct option options[] = {
-               OPT_BOOLEAN('l', NULL, &list, "list tag names"),
-               { OPTION_INTEGER, 'n', NULL, &lines, NULL,
-                               "print n lines of each tag message",
-                               PARSE_OPT_OPTARG, NULL, 1 },
-               OPT_BOOLEAN('d', NULL, &delete, "delete tags"),
-               OPT_BOOLEAN('v', NULL, &verify, "verify tags"),
-
-               OPT_GROUP("Tag creation options"),
-               OPT_BOOLEAN('a', NULL, &annotate,
-                                       "annotated tag, needs a message"),
-               OPT_CALLBACK('m', NULL, &msg, "msg",
-                            "message for the tag", parse_msg_arg),
-               OPT_STRING('F', NULL, &msgfile, "file", "message in a file"),
-               OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"),
-               OPT_STRING('u', NULL, &keyid, "key-id",
-                                       "use another key to sign the tag"),
-               OPT_BOOLEAN('f', NULL, &force, "replace the tag if exists"),
-
-               OPT_GROUP("Tag listing options"),
-               {
-                       OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
-                       "print only tags that contain the commit",
-                       PARSE_OPT_LASTARG_DEFAULT,
-                       parse_opt_with_commit, (intptr_t)"HEAD",
-               },
-               OPT_END()
-       };
-
-       git_config(git_tag_config, NULL);
-
-       argc = parse_options(argc, argv, options, git_tag_usage, 0);
-       msgfile = parse_options_fix_filename(prefix, msgfile);
-
-       if (keyid) {
-               sign = 1;
-               set_signingkey(keyid);
-       }
-       if (sign)
-               annotate = 1;
-       if (argc == 0 && !(delete || verify))
-               list = 1;
-
-       if ((annotate || msg.given || msgfile || force) &&
-           (list || delete || verify))
-               usage_with_options(git_tag_usage, options);
-
-       if (list + delete + verify > 1)
-               usage_with_options(git_tag_usage, options);
-       if (list)
-               return list_tags(argv[0], lines == -1 ? 0 : lines,
-                                with_commit);
-       if (lines != -1)
-               die("-n option is only allowed with -l.");
-       if (with_commit)
-               die("--contains option is only allowed with -l.");
-       if (delete)
-               return for_each_tag_name(argv, delete_tag);
-       if (verify)
-               return for_each_tag_name(argv, verify_tag);
-
-       if (msg.given || msgfile) {
-               if (msg.given && msgfile)
-                       die("only one -F or -m option is allowed.");
-               annotate = 1;
-               if (msg.given)
-                       strbuf_addbuf(&buf, &(msg.buf));
-               else {
-                       if (!strcmp(msgfile, "-")) {
-                               if (strbuf_read(&buf, 0, 1024) < 0)
-                                       die("cannot read %s", msgfile);
-                       } else {
-                               if (strbuf_read_file(&buf, msgfile, 1024) < 0)
-                                       die("could not open or read '%s': %s",
-                                               msgfile, strerror(errno));
-                       }
-               }
-       }
-
-       tag = argv[0];
-
-       object_ref = argc == 2 ? argv[1] : "HEAD";
-       if (argc > 2)
-               die("too many params");
-
-       if (get_sha1(object_ref, object))
-               die("Failed to resolve '%s' as a valid ref.", object_ref);
-
-       if (snprintf(ref, sizeof(ref), "refs/tags/%s", tag) > sizeof(ref) - 1)
-               die("tag name too long: %.*s...", 50, tag);
-       if (check_ref_format(ref))
-               die("'%s' is not a valid tag name.", tag);
-
-       if (!resolve_ref(ref, prev, 1, NULL))
-               hashclr(prev);
-       else if (!force)
-               die("tag '%s' already exists", tag);
-
-       if (annotate)
-               create_tag(object, tag, &buf, msg.given || msgfile,
-                          sign, prev, object);
-
-       lock = lock_any_ref_for_update(ref, prev, 0);
-       if (!lock)
-               die("%s: cannot lock the ref", ref);
-       if (write_ref_sha1(lock, object, NULL) < 0)
-               die("%s: cannot update the ref", ref);
-
-       strbuf_release(&buf);
-       return 0;
-}
diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c
deleted file mode 100644 (file)
index f88e721..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (c) 2005, 2006 Rene Scharfe
- */
-#include "cache.h"
-#include "commit.h"
-#include "tar.h"
-#include "builtin.h"
-#include "quote.h"
-
-static const char tar_tree_usage[] =
-"git tar-tree [--remote=<repo>] <tree-ish> [basedir]\n"
-"*** Note that this command is now deprecated; use \"git archive\" instead.";
-
-int cmd_tar_tree(int argc, const char **argv, const char *prefix)
-{
-       /*
-        * "git tar-tree" is now a wrapper around "git archive --format=tar"
-        *
-        * $0 --remote=<repo> arg... ==>
-        *      git archive --format=tar --remote=<repo> arg...
-        * $0 tree-ish ==>
-        *      git archive --format=tar tree-ish
-        * $0 tree-ish basedir ==>
-        *      git archive --format-tar --prefix=basedir tree-ish
-        */
-       int i;
-       const char **nargv = xcalloc(sizeof(*nargv), argc + 3);
-       char *basedir_arg;
-       int nargc = 0;
-
-       nargv[nargc++] = "archive";
-       nargv[nargc++] = "--format=tar";
-
-       if (2 <= argc && !prefixcmp(argv[1], "--remote=")) {
-               nargv[nargc++] = argv[1];
-               argv++;
-               argc--;
-       }
-
-       /*
-        * Because it's just a compatibility wrapper, tar-tree supports only
-        * the old behaviour of reading attributes from the work tree.
-        */
-       nargv[nargc++] = "--worktree-attributes";
-
-       switch (argc) {
-       default:
-               usage(tar_tree_usage);
-               break;
-       case 3:
-               /* base-path */
-               basedir_arg = xmalloc(strlen(argv[2]) + 11);
-               sprintf(basedir_arg, "--prefix=%s/", argv[2]);
-               nargv[nargc++] = basedir_arg;
-               /* fallthru */
-       case 2:
-               /* tree-ish */
-               nargv[nargc++] = argv[1];
-       }
-       nargv[nargc] = NULL;
-
-       fprintf(stderr,
-               "*** \"git tar-tree\" is now deprecated.\n"
-               "*** Running \"git archive\" instead.\n***");
-       for (i = 0; i < nargc; i++) {
-               fputc(' ', stderr);
-               sq_quote_print(stderr, nargv[i]);
-       }
-       fputc('\n', stderr);
-       return cmd_archive(nargc, nargv, prefix);
-}
-
-/* ustar header + extended global header content */
-#define RECORDSIZE     (512)
-#define HEADERSIZE (2 * RECORDSIZE)
-
-int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix)
-{
-       char buffer[HEADERSIZE];
-       struct ustar_header *header = (struct ustar_header *)buffer;
-       char *content = buffer + RECORDSIZE;
-       ssize_t n;
-
-       n = read_in_full(0, buffer, HEADERSIZE);
-       if (n < HEADERSIZE)
-               die("git get-tar-commit-id: read error");
-       if (header->typeflag[0] != 'g')
-               return 1;
-       if (memcmp(content, "52 comment=", 11))
-               return 1;
-
-       n = write_in_full(1, content + 11, 41);
-       if (n < 41)
-               die("git get-tar-commit-id: write error");
-
-       return 0;
-}
diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c
deleted file mode 100644 (file)
index 9a77323..0000000
+++ /dev/null
@@ -1,564 +0,0 @@
-#include "builtin.h"
-#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 "tree-walk.h"
-#include "progress.h"
-#include "decorate.h"
-#include "fsck.h"
-
-static int dry_run, quiet, recover, has_errors, strict;
-static const char unpack_usage[] = "git unpack-objects [-n] [-q] [-r] [--strict] < pack-file";
-
-/* We always read in 4kB chunks. */
-static unsigned char buffer[4096];
-static unsigned int offset, len;
-static off_t consumed_bytes;
-static git_SHA_CTX ctx;
-
-/*
- * When running under --strict mode, objects whose reachability are
- * suspect are kept in core without getting written in the object
- * store.
- */
-struct obj_buffer {
-       char *buffer;
-       unsigned long size;
-};
-
-static struct decoration obj_decorate;
-
-static struct obj_buffer *lookup_object_buffer(struct object *base)
-{
-       return lookup_decoration(&obj_decorate, base);
-}
-
-static void add_object_buffer(struct object *object, char *buffer, unsigned long size)
-{
-       struct obj_buffer *obj;
-       obj = xcalloc(1, sizeof(struct obj_buffer));
-       obj->buffer = buffer;
-       obj->size = size;
-       if (add_decoration(&obj_decorate, object, obj))
-               die("object %s tried to add buffer twice!", sha1_to_hex(object->sha1));
-}
-
-/*
- * 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 (min > sizeof(buffer))
-               die("cannot fill %d bytes", min);
-       if (offset) {
-               git_SHA1_Update(&ctx, buffer, offset);
-               memmove(buffer, buffer + offset, len);
-               offset = 0;
-       }
-       do {
-               ssize_t 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;
-
-       /* make sure off_t is sufficiently large not to wrap */
-       if (consumed_bytes > consumed_bytes + bytes)
-               die("pack too large for current definition of off_t");
-       consumed_bytes += 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;
-       git_inflate_init(&stream);
-
-       for (;;) {
-               int ret = git_inflate(&stream, 0);
-               use(len - stream.avail_in);
-               if (stream.total_out == size && ret == Z_STREAM_END)
-                       break;
-               if (ret != Z_OK) {
-                       error("inflate returned %d\n", ret);
-                       free(buf);
-                       buf = NULL;
-                       if (!recover)
-                               exit(1);
-                       has_errors = 1;
-                       break;
-               }
-               stream.next_in = fill(1);
-               stream.avail_in = len;
-       }
-       git_inflate_end(&stream);
-       return buf;
-}
-
-struct delta_info {
-       unsigned char base_sha1[20];
-       unsigned nr;
-       off_t base_offset;
-       unsigned long size;
-       void *delta;
-       struct delta_info *next;
-};
-
-static struct delta_info *delta_list;
-
-static void add_delta_to_list(unsigned nr, unsigned const char *base_sha1,
-                             off_t base_offset,
-                             void *delta, unsigned long size)
-{
-       struct delta_info *info = xmalloc(sizeof(*info));
-
-       hashcpy(info->base_sha1, base_sha1);
-       info->base_offset = base_offset;
-       info->size = size;
-       info->delta = delta;
-       info->nr = nr;
-       info->next = delta_list;
-       delta_list = info;
-}
-
-struct obj_info {
-       off_t offset;
-       unsigned char sha1[20];
-       struct object *obj;
-};
-
-#define FLAG_OPEN (1u<<20)
-#define FLAG_WRITTEN (1u<<21)
-
-static struct obj_info *obj_list;
-unsigned nr_objects;
-
-/*
- * Called only from check_object() after it verified this object
- * is Ok.
- */
-static void write_cached_object(struct object *obj)
-{
-       unsigned char sha1[20];
-       struct obj_buffer *obj_buf = lookup_object_buffer(obj);
-       if (write_sha1_file(obj_buf->buffer, obj_buf->size, typename(obj->type), sha1) < 0)
-               die("failed to write object %s", sha1_to_hex(obj->sha1));
-       obj->flags |= FLAG_WRITTEN;
-}
-
-/*
- * At the very end of the processing, write_rest() scans the objects
- * that have reachability requirements and calls this function.
- * Verify its reachability and validity recursively and write it out.
- */
-static int check_object(struct object *obj, int type, void *data)
-{
-       if (!obj)
-               return 0;
-
-       if (obj->flags & FLAG_WRITTEN)
-               return 1;
-
-       if (type != OBJ_ANY && obj->type != type)
-               die("object type mismatch");
-
-       if (!(obj->flags & FLAG_OPEN)) {
-               unsigned long size;
-               int type = sha1_object_info(obj->sha1, &size);
-               if (type != obj->type || type <= 0)
-                       die("object of unexpected type");
-               obj->flags |= FLAG_WRITTEN;
-               return 1;
-       }
-
-       if (fsck_object(obj, 1, fsck_error_function))
-               die("Error in object");
-       if (!fsck_walk(obj, check_object, 0))
-               die("Error on reachable objects of %s", sha1_to_hex(obj->sha1));
-       write_cached_object(obj);
-       return 1;
-}
-
-static void write_rest(void)
-{
-       unsigned i;
-       for (i = 0; i < nr_objects; i++)
-               check_object(obj_list[i].obj, OBJ_ANY, 0);
-}
-
-static void added_object(unsigned nr, enum object_type type,
-                        void *data, unsigned long size);
-
-/*
- * Write out nr-th object from the list, now we know the contents
- * of it.  Under --strict, this buffers structured objects in-core,
- * to be checked at the end.
- */
-static void write_object(unsigned nr, enum object_type type,
-                        void *buf, unsigned long size)
-{
-       if (!strict) {
-               if (write_sha1_file(buf, size, typename(type), obj_list[nr].sha1) < 0)
-                       die("failed to write object");
-               added_object(nr, type, buf, size);
-               free(buf);
-               obj_list[nr].obj = NULL;
-       } else if (type == OBJ_BLOB) {
-               struct blob *blob;
-               if (write_sha1_file(buf, size, typename(type), obj_list[nr].sha1) < 0)
-                       die("failed to write object");
-               added_object(nr, type, buf, size);
-               free(buf);
-
-               blob = lookup_blob(obj_list[nr].sha1);
-               if (blob)
-                       blob->object.flags |= FLAG_WRITTEN;
-               else
-                       die("invalid blob object");
-               obj_list[nr].obj = NULL;
-       } else {
-               struct object *obj;
-               int eaten;
-               hash_sha1_file(buf, size, typename(type), obj_list[nr].sha1);
-               added_object(nr, type, buf, size);
-               obj = parse_object_buffer(obj_list[nr].sha1, type, size, buf, &eaten);
-               if (!obj)
-                       die("invalid %s", typename(type));
-               add_object_buffer(obj, buf, size);
-               obj->flags |= FLAG_OPEN;
-               obj_list[nr].obj = obj;
-       }
-}
-
-static void resolve_delta(unsigned nr, enum object_type 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(nr, type, result, result_size);
-}
-
-/*
- * We now know the contents of an object (which is nr-th in the pack);
- * resolve all the deltified objects that are based on it.
- */
-static void added_object(unsigned nr, enum object_type type,
-                        void *data, unsigned long size)
-{
-       struct delta_info **p = &delta_list;
-       struct delta_info *info;
-
-       while ((info = *p) != NULL) {
-               if (!hashcmp(info->base_sha1, obj_list[nr].sha1) ||
-                   info->base_offset == obj_list[nr].offset) {
-                       *p = info->next;
-                       p = &delta_list;
-                       resolve_delta(info->nr, type, data, size,
-                                     info->delta, info->size);
-                       free(info);
-                       continue;
-               }
-               p = &info->next;
-       }
-}
-
-static void unpack_non_delta_entry(enum object_type type, unsigned long size,
-                                  unsigned nr)
-{
-       void *buf = get_data(size);
-
-       if (!dry_run && buf)
-               write_object(nr, type, buf, size);
-       else
-               free(buf);
-}
-
-static int resolve_against_held(unsigned nr, const unsigned char *base,
-                               void *delta_data, unsigned long delta_size)
-{
-       struct object *obj;
-       struct obj_buffer *obj_buffer;
-       obj = lookup_object(base);
-       if (!obj)
-               return 0;
-       obj_buffer = lookup_object_buffer(obj);
-       if (!obj_buffer)
-               return 0;
-       resolve_delta(nr, obj->type, obj_buffer->buffer,
-                     obj_buffer->size, delta_data, delta_size);
-       return 1;
-}
-
-static void unpack_delta_entry(enum object_type type, unsigned long delta_size,
-                              unsigned nr)
-{
-       void *delta_data, *base;
-       unsigned long base_size;
-       unsigned char base_sha1[20];
-
-       if (type == OBJ_REF_DELTA) {
-               hashcpy(base_sha1, fill(20));
-               use(20);
-               delta_data = get_data(delta_size);
-               if (dry_run || !delta_data) {
-                       free(delta_data);
-                       return;
-               }
-               if (has_sha1_file(base_sha1))
-                       ; /* Ok we have this one */
-               else if (resolve_against_held(nr, base_sha1,
-                                             delta_data, delta_size))
-                       return; /* we are done */
-               else {
-                       /* cannot resolve yet --- queue it */
-                       hashcpy(obj_list[nr].sha1, null_sha1);
-                       add_delta_to_list(nr, base_sha1, 0, delta_data, delta_size);
-                       return;
-               }
-       } else {
-               unsigned base_found = 0;
-               unsigned char *pack, c;
-               off_t base_offset;
-               unsigned lo, mid, hi;
-
-               pack = fill(1);
-               c = *pack;
-               use(1);
-               base_offset = c & 127;
-               while (c & 128) {
-                       base_offset += 1;
-                       if (!base_offset || MSB(base_offset, 7))
-                               die("offset value overflow for delta base object");
-                       pack = fill(1);
-                       c = *pack;
-                       use(1);
-                       base_offset = (base_offset << 7) + (c & 127);
-               }
-               base_offset = obj_list[nr].offset - base_offset;
-               if (base_offset <= 0 || base_offset >= obj_list[nr].offset)
-                       die("offset value out of bound for delta base object");
-
-               delta_data = get_data(delta_size);
-               if (dry_run || !delta_data) {
-                       free(delta_data);
-                       return;
-               }
-               lo = 0;
-               hi = nr;
-               while (lo < hi) {
-                       mid = (lo + hi)/2;
-                       if (base_offset < obj_list[mid].offset) {
-                               hi = mid;
-                       } else if (base_offset > obj_list[mid].offset) {
-                               lo = mid + 1;
-                       } else {
-                               hashcpy(base_sha1, obj_list[mid].sha1);
-                               base_found = !is_null_sha1(base_sha1);
-                               break;
-                       }
-               }
-               if (!base_found) {
-                       /*
-                        * The delta base object is itself a delta that
-                        * has not been resolved yet.
-                        */
-                       hashcpy(obj_list[nr].sha1, null_sha1);
-                       add_delta_to_list(nr, null_sha1, base_offset, delta_data, delta_size);
-                       return;
-               }
-       }
-
-       if (resolve_against_held(nr, base_sha1, delta_data, delta_size))
-               return;
-
-       base = read_sha1_file(base_sha1, &type, &base_size);
-       if (!base) {
-               error("failed to read delta-pack base object %s",
-                     sha1_to_hex(base_sha1));
-               if (!recover)
-                       exit(1);
-               has_errors = 1;
-               return;
-       }
-       resolve_delta(nr, type, base, base_size, delta_data, delta_size);
-       free(base);
-}
-
-static void unpack_one(unsigned nr)
-{
-       unsigned shift;
-       unsigned char *pack, c;
-       unsigned long size;
-       enum object_type type;
-
-       obj_list[nr].offset = consumed_bytes;
-
-       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;
-       }
-
-       switch (type) {
-       case OBJ_COMMIT:
-       case OBJ_TREE:
-       case OBJ_BLOB:
-       case OBJ_TAG:
-               unpack_non_delta_entry(type, size, nr);
-               return;
-       case OBJ_REF_DELTA:
-       case OBJ_OFS_DELTA:
-               unpack_delta_entry(type, size, nr);
-               return;
-       default:
-               error("bad object type %d", type);
-               has_errors = 1;
-               if (recover)
-                       return;
-               exit(1);
-       }
-}
-
-static void unpack_all(void)
-{
-       int i;
-       struct progress *progress = NULL;
-       struct pack_header *hdr = fill(sizeof(struct pack_header));
-
-       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 %"PRIu32,
-                       ntohl(hdr->hdr_version));
-       use(sizeof(struct pack_header));
-
-       if (!quiet)
-               progress = start_progress("Unpacking objects", nr_objects);
-       obj_list = xcalloc(nr_objects, sizeof(*obj_list));
-       for (i = 0; i < nr_objects; i++) {
-               unpack_one(i);
-               display_progress(progress, i + 1);
-       }
-       stop_progress(&progress);
-
-       if (delta_list)
-               die("unresolved deltas left after unpacking");
-}
-
-int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
-{
-       int i;
-       unsigned char sha1[20];
-
-       git_config(git_default_config, NULL);
-
-       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;
-                       }
-                       if (!strcmp(arg, "-r")) {
-                               recover = 1;
-                               continue;
-                       }
-                       if (!strcmp(arg, "--strict")) {
-                               strict = 1;
-                               continue;
-                       }
-                       if (!prefixcmp(arg, "--pack_header=")) {
-                               struct pack_header *hdr;
-                               char *c;
-
-                               hdr = (struct pack_header *)buffer;
-                               hdr->hdr_signature = htonl(PACK_SIGNATURE);
-                               hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10));
-                               if (*c != ',')
-                                       die("bad %s", arg);
-                               hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10));
-                               if (*c)
-                                       die("bad %s", arg);
-                               len = sizeof(*hdr);
-                               continue;
-                       }
-                       usage(unpack_usage);
-               }
-
-               /* We don't take any non-flag arguments now.. Maybe some day */
-               usage(unpack_usage);
-       }
-       git_SHA1_Init(&ctx);
-       unpack_all();
-       git_SHA1_Update(&ctx, buffer, offset);
-       git_SHA1_Final(sha1, &ctx);
-       if (strict)
-               write_rest();
-       if (hashcmp(fill(20), sha1))
-               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 */
-       return has_errors;
-}
diff --git a/builtin-update-index.c b/builtin-update-index.c
deleted file mode 100644 (file)
index 92beaaf..0000000
+++ /dev/null
@@ -1,755 +0,0 @@
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Linus Torvalds, 2005
- */
-#include "cache.h"
-#include "quote.h"
-#include "cache-tree.h"
-#include "tree-walk.h"
-#include "builtin.h"
-#include "refs.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;
-#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 |= CE_VALID;
-                       break;
-               case UNMARK_VALID:
-                       active_cache[pos]->ce_flags &= ~CE_VALID;
-                       break;
-               }
-               cache_tree_invalidate_path(active_cache_tree, path);
-               active_cache_changed = 1;
-               return 0;
-       }
-       return -1;
-}
-
-static int remove_one_path(const char *path)
-{
-       if (!allow_remove)
-               return error("%s: does not exist and --remove not passed", path);
-       if (remove_file_from_cache(path))
-               return error("%s: cannot remove from the index", path);
-       return 0;
-}
-
-/*
- * Handle a path that couldn't be lstat'ed. It's either:
- *  - missing file (ENOENT or ENOTDIR). That's ok if we're
- *    supposed to be removing it and the removal actually
- *    succeeds.
- *  - permission error. That's never ok.
- */
-static int process_lstat_error(const char *path, int err)
-{
-       if (err == ENOENT || err == ENOTDIR)
-               return remove_one_path(path);
-       return error("lstat(\"%s\"): %s", path, strerror(errno));
-}
-
-static int add_one_path(struct cache_entry *old, const char *path, int len, struct stat *st)
-{
-       int option, size;
-       struct cache_entry *ce;
-
-       /* Was the old index entry already up-to-date? */
-       if (old && !ce_stage(old) && !ce_match_stat(old, st, 0))
-               return 0;
-
-       size = cache_entry_size(len);
-       ce = xcalloc(1, size);
-       memcpy(ce->name, path, len);
-       ce->ce_flags = len;
-       fill_stat_cache_info(ce, st);
-       ce->ce_mode = ce_mode_from_stat(old, st->st_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;
-}
-
-/*
- * Handle a path that was a directory. Four cases:
- *
- *  - it's already a gitlink in the index, and we keep it that
- *    way, and update it if we can (if we cannot find the HEAD,
- *    we're going to keep it unchanged in the index!)
- *
- *  - it's a *file* in the index, in which case it should be
- *    removed as a file if removal is allowed, since it doesn't
- *    exist as such any more. If removal isn't allowed, it's
- *    an error.
- *
- *    (NOTE! This is old and arguably fairly strange behaviour.
- *    We might want to make this an error unconditionally, and
- *    use "--force-remove" if you actually want to force removal).
- *
- *  - it used to exist as a subdirectory (ie multiple files with
- *    this particular prefix) in the index, in which case it's wrong
- *    to try to update it as a directory.
- *
- *  - it doesn't exist at all in the index, but it is a valid
- *    git directory, and it should be *added* as a gitlink.
- */
-static int process_directory(const char *path, int len, struct stat *st)
-{
-       unsigned char sha1[20];
-       int pos = cache_name_pos(path, len);
-
-       /* Exact match: file or existing gitlink */
-       if (pos >= 0) {
-               struct cache_entry *ce = active_cache[pos];
-               if (S_ISGITLINK(ce->ce_mode)) {
-
-                       /* Do nothing to the index if there is no HEAD! */
-                       if (resolve_gitlink_ref(path, "HEAD", sha1) < 0)
-                               return 0;
-
-                       return add_one_path(ce, path, len, st);
-               }
-               /* Should this be an unconditional error? */
-               return remove_one_path(path);
-       }
-
-       /* Inexact match: is there perhaps a subdirectory match? */
-       pos = -pos-1;
-       while (pos < active_nr) {
-               struct cache_entry *ce = active_cache[pos++];
-
-               if (strncmp(ce->name, path, len))
-                       break;
-               if (ce->name[len] > '/')
-                       break;
-               if (ce->name[len] < '/')
-                       continue;
-
-               /* Subdirectory match - error out */
-               return error("%s: is a directory - add individual files instead", path);
-       }
-
-       /* No match - should we add it as a gitlink? */
-       if (!resolve_gitlink_ref(path, "HEAD", sha1))
-               return add_one_path(NULL, path, len, st);
-
-       /* Error out. */
-       return error("%s: is a directory - add files inside instead", path);
-}
-
-/*
- * Process a regular file
- */
-static int process_file(const char *path, int len, struct stat *st)
-{
-       int pos = cache_name_pos(path, len);
-       struct cache_entry *ce = pos < 0 ? NULL : active_cache[pos];
-
-       if (ce && S_ISGITLINK(ce->ce_mode))
-               return error("%s is already a gitlink, not replacing", path);
-
-       return add_one_path(ce, path, len, st);
-}
-
-static int process_path(const char *path)
-{
-       int len;
-       struct stat st;
-
-       len = strlen(path);
-       if (has_symlink_leading_path(path, len))
-               return error("'%s' is beyond a symbolic link", path);
-
-       /*
-        * First things first: get the stat information, to decide
-        * what to do about the pathname!
-        */
-       if (lstat(path, &st) < 0)
-               return process_lstat_error(path, errno);
-
-       if (S_ISDIR(st.st_mode))
-               return process_directory(path, len, &st);
-
-       return process_file(path, len, &st);
-}
-
-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 error("Invalid path '%s'", path);
-
-       len = strlen(path);
-       size = cache_entry_size(len);
-       ce = xcalloc(1, size);
-
-       hashcpy(ce->sha1, sha1);
-       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 |= 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);
-       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 = ce->ce_mode;
-       if (!S_ISREG(mode))
-               goto fail;
-       switch (flip) {
-       case '+':
-               ce->ce_mode |= 0111; break;
-       case '-':
-               ce->ce_mode &= ~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 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;
-       }
-
-       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 (process_path(p))
-               die("Unable to process path %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;
-       struct strbuf uq = STRBUF_INIT;
-
-       while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
-               char *ptr, *tab;
-               char *path_name;
-               unsigned char sha1[20];
-               unsigned int mode;
-               unsigned long ul;
-               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.
-                */
-               errno = 0;
-               ul = strtoul(buf.buf, &ptr, 8);
-               if (ptr == buf.buf || *ptr != ' '
-                   || errno || (unsigned int) ul != ul)
-                       goto bad_line;
-               mode = ul;
-
-               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;
-
-               path_name = ptr;
-               if (line_termination && path_name[0] == '"') {
-                       strbuf_reset(&uq);
-                       if (unquote_c_style(&uq, path_name, NULL)) {
-                               die("git update-index: bad quoting of path name");
-                       }
-                       path_name = uq.buf;
-               }
-
-               if (!verify_path(path_name)) {
-                       fprintf(stderr, "Ignoring path %s\n", path_name);
-                       continue;
-               }
-
-               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);
-               }
-               continue;
-
-       bad_line:
-               die("malformed index info %s", buf.buf);
-       }
-       strbuf_release(&buf);
-       strbuf_release(&uq);
-}
-
-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 | -g] [--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);
-
-       hashcpy(ce->sha1, sha1);
-       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 (!hashcmp(ce_2->sha1, ce_3->sha1) &&
-           ce_2->ce_mode == ce_3->ce_mode) {
-               fprintf(stderr, "%s: identical in both, skipping.\n",
-                       path);
-               goto free_return;
-       }
-
-       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("HEAD", head_sha1))
-               die("No HEAD -- no initial commit yet?");
-       if (read_ref("MERGE_HEAD", merge_head_sha1)) {
-               fprintf(stderr, "Not in the middle of a merge.\n");
-               exit(0);
-       }
-}
-
-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("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 &&
-                   !hashcmp(ce->sha1, old->sha1)) {
-                       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 cmd_update_index(int argc, const char **argv, const char *prefix)
-{
-       int i, newfd, entries, has_errors = 0, line_termination = '\n';
-       int allow_options = 1;
-       int read_from_stdin = 0;
-       int prefix_length = prefix ? strlen(prefix) : 0;
-       char set_executable_bit = 0;
-       unsigned int refresh_flags = 0;
-       int lock_error = 0;
-       struct lock_file *lock_file;
-
-       git_config(git_default_config, NULL);
-
-       /* We can't free this memory, it becomes part of a linked list parsed atexit() */
-       lock_file = xcalloc(1, sizeof(struct lock_file));
-
-       newfd = hold_locked_index(lock_file, 0);
-       if (newfd < 0)
-               lock_error = errno;
-
-       entries = read_cache();
-       if (entries < 0)
-               die("cache corrupted");
-
-       for (i = 1 ; i < argc; i++) {
-               const char *path = argv[i];
-               const char *p;
-
-               if (allow_options && *path == '-') {
-                       if (!strcmp(path, "--")) {
-                               allow_options = 0;
-                               continue;
-                       }
-                       if (!strcmp(path, "-q")) {
-                               refresh_flags |= REFRESH_QUIET;
-                               continue;
-                       }
-                       if (!strcmp(path, "--ignore-submodules")) {
-                               refresh_flags |= REFRESH_IGNORE_SUBMODULES;
-                               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")) {
-                               setup_work_tree();
-                               has_errors |= refresh_cache(refresh_flags);
-                               continue;
-                       }
-                       if (!strcmp(path, "--really-refresh")) {
-                               setup_work_tree();
-                               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 (strtoul_ui(argv[i+1], 8, &mode) ||
-                                   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") || !strcmp(path, "-g")) {
-                               setup_work_tree();
-                               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);
-               }
-               setup_work_tree();
-               p = prefix_path(prefix, prefix_length, path);
-               update_one(p, NULL, 0);
-               if (set_executable_bit)
-                       chmod_path(set_executable_bit, p);
-               if (p < path || p > path + strlen(path))
-                       free((char *)p);
-       }
-       if (read_from_stdin) {
-               struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
-
-               setup_work_tree();
-               while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
-                       const char *p;
-                       if (line_termination && buf.buf[0] == '"') {
-                               strbuf_reset(&nbuf);
-                               if (unquote_c_style(&nbuf, buf.buf, NULL))
-                                       die("line is badly quoted");
-                               strbuf_swap(&buf, &nbuf);
-                       }
-                       p = prefix_path(prefix, prefix_length, buf.buf);
-                       update_one(p, NULL, 0);
-                       if (set_executable_bit)
-                               chmod_path(set_executable_bit, p);
-                       if (p < buf.buf || p > buf.buf + buf.len)
-                               free((char *)p);
-               }
-               strbuf_release(&nbuf);
-               strbuf_release(&buf);
-       }
-
- finish:
-       if (active_cache_changed) {
-               if (newfd < 0) {
-                       if (refresh_flags & REFRESH_QUIET)
-                               exit(128);
-                       unable_to_lock_index_die(get_index_file(), lock_error);
-               }
-               if (write_cache(newfd, active_cache, active_nr) ||
-                   commit_locked_index(lock_file))
-                       die("Unable to write new index file");
-       }
-
-       rollback_lock_file(lock_file);
-
-       return has_errors ? 1 : 0;
-}
diff --git a/builtin-update-ref.c b/builtin-update-ref.c
deleted file mode 100644 (file)
index 378dc1b..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-#include "cache.h"
-#include "refs.h"
-#include "builtin.h"
-#include "parse-options.h"
-
-static const char * const git_update_ref_usage[] = {
-       "git update-ref [options] -d <refname> [<oldval>]",
-       "git update-ref [options]    <refname> <newval> [<oldval>]",
-       NULL
-};
-
-int cmd_update_ref(int argc, const char **argv, const char *prefix)
-{
-       const char *refname, *oldval, *msg=NULL;
-       unsigned char sha1[20], oldsha1[20];
-       int delete = 0, no_deref = 0, flags = 0;
-       struct option options[] = {
-               OPT_STRING( 'm', NULL, &msg, "reason", "reason of the update"),
-               OPT_BOOLEAN('d', NULL, &delete, "deletes the reference"),
-               OPT_BOOLEAN( 0 , "no-deref", &no_deref,
-                                       "update <refname> not the one it points to"),
-               OPT_END(),
-       };
-
-       git_config(git_default_config, NULL);
-       argc = parse_options(argc, argv, options, git_update_ref_usage, 0);
-       if (msg && !*msg)
-               die("Refusing to perform update with empty message.");
-
-       if (delete) {
-               if (argc < 1 || argc > 2)
-                       usage_with_options(git_update_ref_usage, options);
-               refname = argv[0];
-               oldval = argv[1];
-       } else {
-               const char *value;
-               if (argc < 2 || argc > 3)
-                       usage_with_options(git_update_ref_usage, options);
-               refname = argv[0];
-               value = argv[1];
-               oldval = argv[2];
-               if (get_sha1(value, sha1))
-                       die("%s: not a valid SHA1", value);
-       }
-
-       hashclr(oldsha1); /* all-zero hash in case oldval is the empty string */
-       if (oldval && *oldval && get_sha1(oldval, oldsha1))
-               die("%s: not a valid old SHA1", oldval);
-
-       if (no_deref)
-               flags = REF_NODEREF;
-       if (delete)
-               return delete_ref(refname, oldval ? oldsha1 : NULL, flags);
-       else
-               return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
-                                 flags, DIE_ON_ERR);
-}
diff --git a/builtin-upload-archive.c b/builtin-upload-archive.c
deleted file mode 100644 (file)
index 0206b41..0000000
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (c) 2006 Franck Bui-Huu
- */
-#include "cache.h"
-#include "builtin.h"
-#include "archive.h"
-#include "pkt-line.h"
-#include "sideband.h"
-
-static const char upload_archive_usage[] =
-       "git upload-archive <repo>";
-
-static const char deadchild[] =
-"git upload-archive: archiver died with error";
-
-static const char lostchild[] =
-"git upload-archive: archiver process was lost";
-
-#define MAX_ARGS (64)
-
-static int run_upload_archive(int argc, const char **argv, const char *prefix)
-{
-       const char *sent_argv[MAX_ARGS];
-       const char *arg_cmd = "argument ";
-       char *p, buf[4096];
-       int sent_argc;
-       int len;
-
-       if (argc != 2)
-               usage(upload_archive_usage);
-
-       if (strlen(argv[1]) + 1 > sizeof(buf))
-               die("insanely long repository name");
-
-       strcpy(buf, argv[1]); /* enter-repo smudges its argument */
-
-       if (!enter_repo(buf, 0))
-               die("'%s' does not appear to be a git repository", buf);
-
-       /* put received options in sent_argv[] */
-       sent_argc = 1;
-       sent_argv[0] = "git-upload-archive";
-       for (p = buf;;) {
-               /* This will die if not enough free space in buf */
-               len = packet_read_line(0, p, (buf + sizeof buf) - p);
-               if (len == 0)
-                       break;  /* got a flush */
-               if (sent_argc > MAX_ARGS - 2)
-                       die("Too many options (>%d)", MAX_ARGS - 2);
-
-               if (p[len-1] == '\n') {
-                       p[--len] = 0;
-               }
-               if (len < strlen(arg_cmd) ||
-                   strncmp(arg_cmd, p, strlen(arg_cmd)))
-                       die("'argument' token or flush expected");
-
-               len -= strlen(arg_cmd);
-               memmove(p, p + strlen(arg_cmd), len);
-               sent_argv[sent_argc++] = p;
-               p += len;
-               *p++ = 0;
-       }
-       sent_argv[sent_argc] = NULL;
-
-       /* parse all options sent by the client */
-       return write_archive(sent_argc, sent_argv, prefix, 0);
-}
-
-static void error_clnt(const char *fmt, ...)
-{
-       char buf[1024];
-       va_list params;
-       int len;
-
-       va_start(params, fmt);
-       len = vsprintf(buf, fmt, params);
-       va_end(params);
-       send_sideband(1, 3, buf, len, LARGE_PACKET_MAX);
-       die("sent error to the client: %s", buf);
-}
-
-static void process_input(int child_fd, int band)
-{
-       char buf[16384];
-       ssize_t sz = read(child_fd, buf, sizeof(buf));
-       if (sz < 0) {
-               if (errno != EAGAIN && errno != EINTR)
-                       error_clnt("read error: %s\n", strerror(errno));
-               return;
-       }
-       send_sideband(1, band, buf, sz, LARGE_PACKET_MAX);
-}
-
-int cmd_upload_archive(int argc, const char **argv, const char *prefix)
-{
-       pid_t writer;
-       int fd1[2], fd2[2];
-       /*
-        * Set up sideband subprocess.
-        *
-        * We (parent) monitor and read from child, sending its fd#1 and fd#2
-        * multiplexed out to our fd#1.  If the child dies, we tell the other
-        * end over channel #3.
-        */
-       if (pipe(fd1) < 0 || pipe(fd2) < 0) {
-               int err = errno;
-               packet_write(1, "NACK pipe failed on the remote side\n");
-               die("upload-archive: %s", strerror(err));
-       }
-       writer = fork();
-       if (writer < 0) {
-               int err = errno;
-               packet_write(1, "NACK fork failed on the remote side\n");
-               die("upload-archive: %s", strerror(err));
-       }
-       if (!writer) {
-               /* child - connect fd#1 and fd#2 to the pipe */
-               dup2(fd1[1], 1);
-               dup2(fd2[1], 2);
-               close(fd1[1]); close(fd2[1]);
-               close(fd1[0]); close(fd2[0]); /* we do not read from pipe */
-
-               exit(run_upload_archive(argc, argv, prefix));
-       }
-
-       /* parent - read from child, multiplex and send out to fd#1 */
-       close(fd1[1]); close(fd2[1]); /* we do not write to pipe */
-       packet_write(1, "ACK\n");
-       packet_flush(1);
-
-       while (1) {
-               struct pollfd pfd[2];
-               int status;
-
-               pfd[0].fd = fd1[0];
-               pfd[0].events = POLLIN;
-               pfd[1].fd = fd2[0];
-               pfd[1].events = POLLIN;
-               if (poll(pfd, 2, -1) < 0) {
-                       if (errno != EINTR) {
-                               error("poll failed resuming: %s",
-                                     strerror(errno));
-                               sleep(1);
-                       }
-                       continue;
-               }
-               if (pfd[0].revents & POLLIN)
-                       /* Data stream ready */
-                       process_input(pfd[0].fd, 1);
-               if (pfd[1].revents & POLLIN)
-                       /* Status stream ready */
-                       process_input(pfd[1].fd, 2);
-               /* Always finish to read data when available */
-               if ((pfd[0].revents | pfd[1].revents) & POLLIN)
-                       continue;
-
-               if (waitpid(writer, &status, 0) < 0)
-                       error_clnt("%s", lostchild);
-               else if (!WIFEXITED(status) || WEXITSTATUS(status) > 0)
-                       error_clnt("%s", deadchild);
-               packet_flush(1);
-               break;
-       }
-       return 0;
-}
diff --git a/builtin-verify-pack.c b/builtin-verify-pack.c
deleted file mode 100644 (file)
index 0ee0a9a..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-#include "builtin.h"
-#include "cache.h"
-#include "pack.h"
-#include "pack-revindex.h"
-
-#define MAX_CHAIN 50
-
-static void show_pack_info(struct packed_git *p)
-{
-       uint32_t nr_objects, i, chain_histogram[MAX_CHAIN+1];
-
-       nr_objects = p->num_objects;
-       memset(chain_histogram, 0, sizeof(chain_histogram));
-
-       for (i = 0; i < nr_objects; i++) {
-               const unsigned char *sha1;
-               unsigned char base_sha1[20];
-               const char *type;
-               unsigned long size;
-               unsigned long store_size;
-               off_t offset;
-               unsigned int delta_chain_length;
-
-               sha1 = nth_packed_object_sha1(p, i);
-               if (!sha1)
-                       die("internal error pack-check nth-packed-object");
-               offset = nth_packed_object_offset(p, i);
-               type = packed_object_info_detail(p, offset, &size, &store_size,
-                                                &delta_chain_length,
-                                                base_sha1);
-               printf("%s ", sha1_to_hex(sha1));
-               if (!delta_chain_length)
-                       printf("%-6s %lu %lu %"PRIuMAX"\n",
-                              type, size, store_size, (uintmax_t)offset);
-               else {
-                       printf("%-6s %lu %lu %"PRIuMAX" %u %s\n",
-                              type, size, store_size, (uintmax_t)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 = %"PRIu32": %"PRIu32" object%s\n", i,
-                      chain_histogram[i], chain_histogram[i] > 1 ? "s" : "");
-       }
-       if (chain_histogram[0])
-               printf("chain length > %d: %"PRIu32" object%s\n", MAX_CHAIN,
-                      chain_histogram[0], chain_histogram[0] > 1 ? "s" : "");
-}
-
-static int verify_one_pack(const char *path, int verbose)
-{
-       char arg[PATH_MAX];
-       int len;
-       struct packed_git *pack;
-       int err;
-
-       len = strlcpy(arg, path, PATH_MAX);
-       if (len >= PATH_MAX)
-               return error("name too long: %s", path);
-
-       /*
-        * In addition to "foo.idx" we accept "foo.pack" and "foo";
-        * normalize these forms to "foo.idx" for add_packed_git().
-        */
-       if (has_extension(arg, ".pack")) {
-               strcpy(arg + len - 5, ".idx");
-               len--;
-       } else if (!has_extension(arg, ".idx")) {
-               if (len + 4 >= PATH_MAX)
-                       return error("name too long: %s.idx", arg);
-               strcpy(arg + len, ".idx");
-               len += 4;
-       }
-
-       /*
-        * add_packed_git() uses our buffer (containing "foo.idx") to
-        * build the pack filename ("foo.pack").  Make sure it fits.
-        */
-       if (len + 1 >= PATH_MAX) {
-               arg[len - 4] = '\0';
-               return error("name too long: %s.pack", arg);
-       }
-
-       pack = add_packed_git(arg, len, 1);
-       if (!pack)
-               return error("packfile %s not found.", arg);
-
-       install_packed_git(pack);
-       err = verify_pack(pack);
-
-       if (verbose) {
-               if (err)
-                       printf("%s: bad\n", pack->pack_name);
-               else {
-                       show_pack_info(pack);
-                       printf("%s: ok\n", pack->pack_name);
-               }
-       }
-
-       return err;
-}
-
-static const char verify_pack_usage[] = "git verify-pack [-v] <pack>...";
-
-int cmd_verify_pack(int argc, const char **argv, const char *prefix)
-{
-       int err = 0;
-       int verbose = 0;
-       int no_more_options = 0;
-       int nothing_done = 1;
-
-       git_config(git_default_config, NULL);
-       while (1 < argc) {
-               if (!no_more_options && argv[1][0] == '-') {
-                       if (!strcmp("-v", argv[1]))
-                               verbose = 1;
-                       else if (!strcmp("--", argv[1]))
-                               no_more_options = 1;
-                       else
-                               usage(verify_pack_usage);
-               }
-               else {
-                       if (verify_one_pack(argv[1], verbose))
-                               err = 1;
-                       discard_revindex();
-                       nothing_done = 0;
-               }
-               argc--; argv++;
-       }
-
-       if (nothing_done)
-               usage(verify_pack_usage);
-
-       return err;
-}
diff --git a/builtin-verify-tag.c b/builtin-verify-tag.c
deleted file mode 100644 (file)
index 729a159..0000000
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Builtin "git verify-tag"
- *
- * Copyright (c) 2007 Carlos Rica <jasampler@gmail.com>
- *
- * Based on git-verify-tag.sh
- */
-#include "cache.h"
-#include "builtin.h"
-#include "tag.h"
-#include "run-command.h"
-#include <signal.h>
-
-static const char builtin_verify_tag_usage[] =
-               "git verify-tag [-v|--verbose] <tag>...";
-
-#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
-
-static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
-{
-       struct child_process gpg;
-       const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL};
-       char path[PATH_MAX], *eol;
-       size_t len;
-       int fd, ret;
-
-       fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX");
-       if (fd < 0)
-               return error("could not create temporary file '%s': %s",
-                                               path, strerror(errno));
-       if (write_in_full(fd, buf, size) < 0)
-               return error("failed writing temporary file '%s': %s",
-                                               path, strerror(errno));
-       close(fd);
-
-       /* find the length without signature */
-       len = 0;
-       while (len < size && prefixcmp(buf + len, PGP_SIGNATURE)) {
-               eol = memchr(buf + len, '\n', size - len);
-               len += eol ? eol - (buf + len) + 1 : size - len;
-       }
-       if (verbose)
-               write_in_full(1, buf, len);
-
-       memset(&gpg, 0, sizeof(gpg));
-       gpg.argv = args_gpg;
-       gpg.in = -1;
-       args_gpg[2] = path;
-       if (start_command(&gpg)) {
-               unlink(path);
-               return error("could not run gpg.");
-       }
-
-       write_in_full(gpg.in, buf, len);
-       close(gpg.in);
-       ret = finish_command(&gpg);
-
-       unlink(path);
-
-       return ret;
-}
-
-static int verify_tag(const char *name, int verbose)
-{
-       enum object_type type;
-       unsigned char sha1[20];
-       char *buf;
-       unsigned long size;
-       int ret;
-
-       if (get_sha1(name, sha1))
-               return error("tag '%s' not found.", name);
-
-       type = sha1_object_info(sha1, NULL);
-       if (type != OBJ_TAG)
-               return error("%s: cannot verify a non-tag object of type %s.",
-                               name, typename(type));
-
-       buf = read_sha1_file(sha1, &type, &size);
-       if (!buf)
-               return error("%s: unable to read file.", name);
-
-       ret = run_gpg_verify(buf, size, verbose);
-
-       free(buf);
-       return ret;
-}
-
-int cmd_verify_tag(int argc, const char **argv, const char *prefix)
-{
-       int i = 1, verbose = 0, had_error = 0;
-
-       git_config(git_default_config, NULL);
-
-       if (argc > 1 &&
-           (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))) {
-               verbose = 1;
-               i++;
-       }
-
-       if (argc <= i)
-               usage(builtin_verify_tag_usage);
-
-       /* sometimes the program was terminated because this signal
-        * was received in the process of writing the gpg input: */
-       signal(SIGPIPE, SIG_IGN);
-       while (i < argc)
-               if (verify_tag(argv[i++], verbose))
-                       had_error = 1;
-       return had_error;
-}
diff --git a/builtin-write-tree.c b/builtin-write-tree.c
deleted file mode 100644 (file)
index 9d64050..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Linus Torvalds, 2005
- */
-#include "builtin.h"
-#include "cache.h"
-#include "tree.h"
-#include "cache-tree.h"
-
-static const char write_tree_usage[] =
-"git write-tree [--missing-ok] [--prefix=<prefix>/]";
-
-int cmd_write_tree(int argc, const char **argv, const char *unused_prefix)
-{
-       int missing_ok = 0, ret;
-       const char *prefix = NULL;
-       unsigned char sha1[20];
-       const char *me = "git-write-tree";
-
-       git_config(git_default_config, NULL);
-       while (1 < argc) {
-               const char *arg = argv[1];
-               if (!strcmp(arg, "--missing-ok"))
-                       missing_ok = 1;
-               else if (!prefixcmp(arg, "--prefix="))
-                       prefix = arg + 9;
-               else
-                       usage(write_tree_usage);
-               argc--; argv++;
-       }
-
-       if (argc > 2)
-               die("too many options");
-
-       ret = write_cache_as_tree(sha1, missing_ok, prefix);
-       switch (ret) {
-       case 0:
-               printf("%s\n", sha1_to_hex(sha1));
-               break;
-       case WRITE_TREE_UNREADABLE_INDEX:
-               die("%s: error reading the index", me);
-               break;
-       case WRITE_TREE_UNMERGED_INDEX:
-               die("%s: error building trees", me);
-               break;
-       case WRITE_TREE_PREFIX_ERROR:
-               die("%s: prefix %s not found", me, prefix);
-               break;
-       }
-       return ret;
-}
index 425ff8e89b361c34b3336cda58794682c66b57f3..0398d247400e55f2a2ededc054902374c7516d5b 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -5,22 +5,38 @@
 #include "strbuf.h"
 #include "cache.h"
 #include "commit.h"
+#include "notes.h"
 
 extern const char git_version_string[];
 extern const char git_usage_string[];
 extern const char git_more_info_string[];
 
-extern void list_common_cmds_help(void);
-extern const char *help_unknown_cmd(const char *cmd);
 extern void prune_packed_objects(int);
-extern int read_line_with_nul(char *buf, int size, FILE *file);
 extern int fmt_merge_msg(int merge_summary, struct strbuf *in,
        struct strbuf *out);
-extern int commit_tree(const char *msg, unsigned char *tree,
-               struct commit_list *parents, unsigned char *ret,
-               const char *author);
+extern int fmt_merge_msg_shortlog(struct strbuf *in, struct strbuf *out);
+extern int commit_notes(struct notes_tree *t, const char *msg);
+
+struct notes_rewrite_cfg {
+       struct notes_tree **trees;
+       const char *cmd;
+       int enabled;
+       combine_notes_fn combine;
+       struct string_list *refs;
+       int refs_from_env;
+       int mode_from_env;
+};
+
+combine_notes_fn parse_combine_notes_fn(const char *v);
+struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd);
+int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
+                         const unsigned char *from_obj, const unsigned char *to_obj);
+void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c);
+
 extern int check_pager_config(const char *cmd);
 
+extern int textconv_object(const char *path, const unsigned char *sha1, char **buf, unsigned long *buf_size);
+
 extern int cmd_add(int argc, const char **argv, const char *prefix);
 extern int cmd_annotate(int argc, const char **argv, const char *prefix);
 extern int cmd_apply(int argc, const char **argv, const char *prefix);
@@ -49,7 +65,6 @@ extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_fast_export(int argc, const char **argv, const char *prefix);
 extern int cmd_fetch(int argc, const char **argv, const char *prefix);
 extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix);
-extern int cmd_fetch__tool(int argc, const char **argv, const char *prefix);
 extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix);
 extern int cmd_for_each_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_format_patch(int argc, const char **argv, const char *prefix);
@@ -57,8 +72,10 @@ extern int cmd_fsck(int argc, const char **argv, const char *prefix);
 extern int cmd_gc(int argc, const char **argv, const char *prefix);
 extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);
 extern int cmd_grep(int argc, const char **argv, const char *prefix);
+extern int cmd_hash_object(int argc, const char **argv, const char *prefix);
 extern int cmd_help(int argc, const char **argv, const char *prefix);
 extern int cmd_http_fetch(int argc, const char **argv, const char *prefix);
+extern int cmd_index_pack(int argc, const char **argv, const char *prefix);
 extern int cmd_init_db(int argc, const char **argv, const char *prefix);
 extern int cmd_log(int argc, const char **argv, const char *prefix);
 extern int cmd_log_reflog(int argc, const char **argv, const char *prefix);
@@ -69,12 +86,19 @@ extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
 extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
 extern int cmd_merge(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
+extern int cmd_merge_index(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_recursive(int argc, const char **argv, const char *prefix);
+extern int cmd_merge_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_mktag(int argc, const char **argv, const char *prefix);
+extern int cmd_mktree(int argc, const char **argv, const char *prefix);
 extern int cmd_mv(int argc, const char **argv, const char *prefix);
 extern int cmd_name_rev(int argc, const char **argv, const char *prefix);
+extern int cmd_notes(int argc, const char **argv, const char *prefix);
 extern int cmd_pack_objects(int argc, const char **argv, const char *prefix);
+extern int cmd_pack_redundant(int argc, const char **argv, const char *prefix);
+extern int cmd_patch_id(int argc, const char **argv, const char *prefix);
 extern int cmd_pickaxe(int argc, const char **argv, const char *prefix);
 extern int cmd_prune(int argc, const char **argv, const char *prefix);
 extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
@@ -99,11 +123,14 @@ extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_tag(int argc, const char **argv, const char *prefix);
 extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_unpack_file(int argc, const char **argv, const char *prefix);
 extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix);
 extern int cmd_update_index(int argc, const char **argv, const char *prefix);
 extern int cmd_update_ref(int argc, const char **argv, const char *prefix);
+extern int cmd_update_server_info(int argc, const char **argv, const char *prefix);
 extern int cmd_upload_archive(int argc, const char **argv, const char *prefix);
 extern int cmd_upload_tar(int argc, const char **argv, const char *prefix);
+extern int cmd_var(int argc, const char **argv, const char *prefix);
 extern int cmd_verify_tag(int argc, const char **argv, const char *prefix);
 extern int cmd_version(int argc, const char **argv, const char *prefix);
 extern int cmd_whatchanged(int argc, const char **argv, const char *prefix);
@@ -111,5 +138,6 @@ extern int cmd_write_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_verify_pack(int argc, const char **argv, const char *prefix);
 extern int cmd_show_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_pack_refs(int argc, const char **argv, const char *prefix);
+extern int cmd_replace(int argc, const char **argv, const char *prefix);
 
 #endif
diff --git a/builtin/add.c b/builtin/add.c
new file mode 100644 (file)
index 0000000..56a4e0a
--- /dev/null
@@ -0,0 +1,472 @@
+/*
+ * "git add" builtin command
+ *
+ * Copyright (C) 2006 Linus Torvalds
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "dir.h"
+#include "exec_cmd.h"
+#include "cache-tree.h"
+#include "run-command.h"
+#include "parse-options.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "revision.h"
+
+static const char * const builtin_add_usage[] = {
+       "git add [options] [--] <filepattern>...",
+       NULL
+};
+static int patch_interactive, add_interactive, edit_interactive;
+static int take_worktree_changes;
+
+struct update_callback_data
+{
+       int flags;
+       int add_errors;
+};
+
+static void update_callback(struct diff_queue_struct *q,
+                           struct diff_options *opt, void *cbdata)
+{
+       int i;
+       struct update_callback_data *data = cbdata;
+
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               const char *path = p->one->path;
+               switch (p->status) {
+               default:
+                       die("unexpected diff status %c", p->status);
+               case DIFF_STATUS_UNMERGED:
+                       /*
+                        * ADD_CACHE_IGNORE_REMOVAL is unset if "git
+                        * add -u" is calling us, In such a case, a
+                        * missing work tree file needs to be removed
+                        * if there is an unmerged entry at stage #2,
+                        * but such a diff record is followed by
+                        * another with DIFF_STATUS_DELETED (and if
+                        * there is no stage #2, we won't see DELETED
+                        * nor MODIFIED).  We can simply continue
+                        * either way.
+                        */
+                       if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL))
+                               continue;
+                       /*
+                        * Otherwise, it is "git add path" is asking
+                        * to explicitly add it; we fall through.  A
+                        * missing work tree file is an error and is
+                        * caught by add_file_to_index() in such a
+                        * case.
+                        */
+               case DIFF_STATUS_MODIFIED:
+               case DIFF_STATUS_TYPE_CHANGED:
+                       if (add_file_to_index(&the_index, path, data->flags)) {
+                               if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
+                                       die("updating files failed");
+                               data->add_errors++;
+                       }
+                       break;
+               case DIFF_STATUS_DELETED:
+                       if (data->flags & ADD_CACHE_IGNORE_REMOVAL)
+                               break;
+                       if (!(data->flags & ADD_CACHE_PRETEND))
+                               remove_file_from_index(&the_index, path);
+                       if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
+                               printf("remove '%s'\n", path);
+                       break;
+               }
+       }
+}
+
+int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
+{
+       struct update_callback_data data;
+       struct rev_info rev;
+       init_revisions(&rev, prefix);
+       setup_revisions(0, NULL, &rev, NULL);
+       rev.prune_data = pathspec;
+       rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
+       rev.diffopt.format_callback = update_callback;
+       data.flags = flags;
+       data.add_errors = 0;
+       rev.diffopt.format_callback_data = &data;
+       run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
+       return !!data.add_errors;
+}
+
+static void fill_pathspec_matches(const char **pathspec, char *seen, int specs)
+{
+       int num_unmatched = 0, i;
+
+       /*
+        * Since we are walking the index as if we were walking the directory,
+        * we have to mark the matched pathspec as seen; otherwise we will
+        * mistakenly think that the user gave a pathspec that did not match
+        * anything.
+        */
+       for (i = 0; i < specs; i++)
+               if (!seen[i])
+                       num_unmatched++;
+       if (!num_unmatched)
+               return;
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen);
+       }
+}
+
+static char *find_used_pathspec(const char **pathspec)
+{
+       char *seen;
+       int i;
+
+       for (i = 0; pathspec[i];  i++)
+               ; /* just counting */
+       seen = xcalloc(i, 1);
+       fill_pathspec_matches(pathspec, seen, i);
+       return seen;
+}
+
+static char *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 = xcalloc(specs, 1);
+
+       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))
+                       *dst++ = entry;
+       }
+       dir->nr = dst - dir->entries;
+       fill_pathspec_matches(pathspec, seen, specs);
+       return seen;
+}
+
+static void treat_gitlinks(const char **pathspec)
+{
+       int i;
+
+       if (!pathspec || !*pathspec)
+               return;
+
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (S_ISGITLINK(ce->ce_mode)) {
+                       int len = ce_namelen(ce), j;
+                       for (j = 0; pathspec[j]; j++) {
+                               int len2 = strlen(pathspec[j]);
+                               if (len2 <= len || pathspec[j][len] != '/' ||
+                                   memcmp(ce->name, pathspec[j], len))
+                                       continue;
+                               if (len2 == len + 1)
+                                       /* strip trailing slash */
+                                       pathspec[j] = xstrndup(ce->name, len);
+                               else
+                                       die ("Path '%s' is in submodule '%.*s'",
+                                               pathspec[j], len, ce->name);
+                       }
+               }
+       }
+}
+
+static void refresh(int verbose, const char **pathspec)
+{
+       char *seen;
+       int i, specs;
+
+       for (specs = 0; pathspec[specs];  specs++)
+               /* nothing */;
+       seen = xcalloc(specs, 1);
+       refresh_index(&the_index, verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET,
+                     pathspec, seen, "Unstaged changes after refreshing the index:");
+       for (i = 0; i < specs; i++) {
+               if (!seen[i])
+                       die("pathspec '%s' did not match any files", pathspec[i]);
+       }
+        free(seen);
+}
+
+static const char **validate_pathspec(int argc, const char **argv, const char *prefix)
+{
+       const char **pathspec = get_pathspec(prefix, argv);
+
+       if (pathspec) {
+               const char **p;
+               for (p = pathspec; *p; p++) {
+                       if (has_symlink_leading_path(*p, strlen(*p))) {
+                               int len = prefix ? strlen(prefix) : 0;
+                               die("'%s' is beyond a symbolic link", *p + len);
+                       }
+               }
+       }
+
+       return pathspec;
+}
+
+int run_add_interactive(const char *revision, const char *patch_mode,
+                       const char **pathspec)
+{
+       int status, ac, pc = 0;
+       const char **args;
+
+       if (pathspec)
+               while (pathspec[pc])
+                       pc++;
+
+       args = xcalloc(sizeof(const char *), (pc + 5));
+       ac = 0;
+       args[ac++] = "add--interactive";
+       if (patch_mode)
+               args[ac++] = patch_mode;
+       if (revision)
+               args[ac++] = revision;
+       args[ac++] = "--";
+       if (pc) {
+               memcpy(&(args[ac]), pathspec, sizeof(const char *) * pc);
+               ac += pc;
+       }
+       args[ac] = NULL;
+
+       status = run_command_v_opt(args, RUN_GIT_CMD);
+       free(args);
+       return status;
+}
+
+int interactive_add(int argc, const char **argv, const char *prefix)
+{
+       const char **pathspec = NULL;
+
+       if (argc) {
+               pathspec = validate_pathspec(argc, argv, prefix);
+               if (!pathspec)
+                       return -1;
+       }
+
+       return run_add_interactive(NULL,
+                                  patch_interactive ? "--patch" : NULL,
+                                  pathspec);
+}
+
+static int edit_patch(int argc, const char **argv, const char *prefix)
+{
+       char *file = xstrdup(git_path("ADD_EDIT.patch"));
+       const char *apply_argv[] = { "apply", "--recount", "--cached",
+               NULL, NULL };
+       struct child_process child;
+       struct rev_info rev;
+       int out;
+       struct stat st;
+
+       apply_argv[3] = file;
+
+       git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
+
+       if (read_cache() < 0)
+               die ("Could not read the index");
+
+       init_revisions(&rev, prefix);
+       rev.diffopt.context = 7;
+
+       argc = setup_revisions(argc, argv, &rev, NULL);
+       rev.diffopt.output_format = DIFF_FORMAT_PATCH;
+       out = open(file, O_CREAT | O_WRONLY, 0644);
+       if (out < 0)
+               die ("Could not open '%s' for writing.", file);
+       rev.diffopt.file = xfdopen(out, "w");
+       rev.diffopt.close_file = 1;
+       if (run_diff_files(&rev, 0))
+               die ("Could not write patch");
+
+       launch_editor(file, NULL, NULL);
+
+       if (stat(file, &st))
+               die_errno("Could not stat '%s'", file);
+       if (!st.st_size)
+               die("Empty patch. Aborted.");
+
+       memset(&child, 0, sizeof(child));
+       child.git_cmd = 1;
+       child.argv = apply_argv;
+       if (run_command(&child))
+               die ("Could not apply '%s'", file);
+
+       unlink(file);
+       return 0;
+}
+
+static struct lock_file lock_file;
+
+static const char ignore_error[] =
+"The following paths are ignored by one of your .gitignore files:\n";
+
+static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0;
+static int ignore_add_errors, addremove, intent_to_add, ignore_missing = 0;
+
+static struct option builtin_add_options[] = {
+       OPT__DRY_RUN(&show_only),
+       OPT__VERBOSE(&verbose),
+       OPT_GROUP(""),
+       OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"),
+       OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"),
+       OPT_BOOLEAN('e', "edit", &edit_interactive, "edit current diff and apply"),
+       OPT_BOOLEAN('f', "force", &ignored_too, "allow adding otherwise ignored files"),
+       OPT_BOOLEAN('u', "update", &take_worktree_changes, "update tracked files"),
+       OPT_BOOLEAN('N', "intent-to-add", &intent_to_add, "record only the fact that the path will be added later"),
+       OPT_BOOLEAN('A', "all", &addremove, "add all, noticing removal of tracked files"),
+       OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"),
+       OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, "just skip files which cannot be added because of errors"),
+       OPT_BOOLEAN( 0 , "ignore-missing", &ignore_missing, "check if - even missing - files are ignored in dry run"),
+       OPT_END(),
+};
+
+static int add_config(const char *var, const char *value, void *cb)
+{
+       if (!strcasecmp(var, "add.ignore-errors")) {
+               ignore_add_errors = git_config_bool(var, value);
+               return 0;
+       }
+       return git_default_config(var, value, cb);
+}
+
+static int add_files(struct dir_struct *dir, int flags)
+{
+       int i, exit_status = 0;
+
+       if (dir->ignored_nr) {
+               fprintf(stderr, ignore_error);
+               for (i = 0; i < dir->ignored_nr; i++)
+                       fprintf(stderr, "%s\n", dir->ignored[i]->name);
+               fprintf(stderr, "Use -f if you really want to add them.\n");
+               die("no files added");
+       }
+
+       for (i = 0; i < dir->nr; i++)
+               if (add_file_to_cache(dir->entries[i]->name, flags)) {
+                       if (!ignore_add_errors)
+                               die("adding files failed");
+                       exit_status = 1;
+               }
+       return exit_status;
+}
+
+int cmd_add(int argc, const char **argv, const char *prefix)
+{
+       int exit_status = 0;
+       int newfd;
+       const char **pathspec;
+       struct dir_struct dir;
+       int flags;
+       int add_new_files;
+       int require_pathspec;
+       char *seen = NULL;
+
+       git_config(add_config, NULL);
+
+       argc = parse_options(argc, argv, prefix, builtin_add_options,
+                         builtin_add_usage, PARSE_OPT_KEEP_ARGV0);
+       if (patch_interactive)
+               add_interactive = 1;
+       if (add_interactive)
+               exit(interactive_add(argc - 1, argv + 1, prefix));
+
+       if (edit_interactive)
+               return(edit_patch(argc, argv, prefix));
+       argc--;
+       argv++;
+
+       if (addremove && take_worktree_changes)
+               die("-A and -u are mutually incompatible");
+       if (!show_only && ignore_missing)
+               die("Option --ignore-missing can only be used together with --dry-run");
+       if ((addremove || take_worktree_changes) && !argc) {
+               static const char *here[2] = { ".", NULL };
+               argc = 1;
+               argv = here;
+       }
+
+       add_new_files = !take_worktree_changes && !refresh_only;
+       require_pathspec = !take_worktree_changes;
+
+       newfd = hold_locked_index(&lock_file, 1);
+
+       flags = ((verbose ? ADD_CACHE_VERBOSE : 0) |
+                (show_only ? ADD_CACHE_PRETEND : 0) |
+                (intent_to_add ? ADD_CACHE_INTENT : 0) |
+                (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) |
+                (!(addremove || take_worktree_changes)
+                 ? ADD_CACHE_IGNORE_REMOVAL : 0));
+
+       if (require_pathspec && argc == 0) {
+               fprintf(stderr, "Nothing specified, nothing added.\n");
+               fprintf(stderr, "Maybe you wanted to say 'git add .'?\n");
+               return 0;
+       }
+       pathspec = validate_pathspec(argc, argv, prefix);
+
+       if (read_cache() < 0)
+               die("index file corrupt");
+       treat_gitlinks(pathspec);
+
+       if (add_new_files) {
+               int baselen;
+
+               /* Set up the default git porcelain excludes */
+               memset(&dir, 0, sizeof(dir));
+               if (!ignored_too) {
+                       dir.flags |= DIR_COLLECT_IGNORED;
+                       setup_standard_excludes(&dir);
+               }
+
+               /* This picks up the paths that are not tracked */
+               baselen = fill_directory(&dir, pathspec);
+               if (pathspec)
+                       seen = prune_directory(&dir, pathspec, baselen);
+       }
+
+       if (refresh_only) {
+               refresh(verbose, pathspec);
+               goto finish;
+       }
+
+       if (pathspec) {
+               int i;
+               if (!seen)
+                       seen = find_used_pathspec(pathspec);
+               for (i = 0; pathspec[i]; i++) {
+                       if (!seen[i] && pathspec[i][0]
+                           && !file_exists(pathspec[i])) {
+                               if (ignore_missing) {
+                                       if (excluded(&dir, pathspec[i], DT_UNKNOWN))
+                                               dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i]));
+                               } else
+                                       die("pathspec '%s' did not match any files",
+                                           pathspec[i]);
+                       }
+               }
+               free(seen);
+       }
+
+       exit_status |= add_files_to_cache(prefix, pathspec, flags);
+
+       if (add_new_files)
+               exit_status |= add_files(&dir, flags);
+
+ finish:
+       if (active_cache_changed) {
+               if (write_cache(newfd, active_cache, active_nr) ||
+                   commit_locked_index(&lock_file))
+                       die("Unable to write new index file");
+       }
+
+       return exit_status;
+}
diff --git a/builtin/annotate.c b/builtin/annotate.c
new file mode 100644 (file)
index 0000000..fc43eed
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * "git annotate" builtin alias
+ *
+ * Copyright (C) 2006 Ryan Anderson
+ */
+#include "git-compat-util.h"
+#include "builtin.h"
+
+int cmd_annotate(int argc, const char **argv, const char *prefix)
+{
+       const char **nargv;
+       int i;
+       nargv = xmalloc(sizeof(char *) * (argc + 2));
+
+       nargv[0] = "annotate";
+       nargv[1] = "-c";
+
+       for (i = 1; i < argc; i++) {
+               nargv[i+1] = argv[i];
+       }
+       nargv[argc + 1] = NULL;
+
+       return cmd_blame(argc + 1, nargv, prefix);
+}
diff --git a/builtin/apply.c b/builtin/apply.c
new file mode 100644 (file)
index 0000000..23c18c5
--- /dev/null
@@ -0,0 +1,3937 @@
+/*
+ * apply.c
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ *
+ * This applies patches on top of some (arbitrary) version of the SCM.
+ *
+ */
+#include "cache.h"
+#include "cache-tree.h"
+#include "quote.h"
+#include "blob.h"
+#include "delta.h"
+#include "builtin.h"
+#include "string-list.h"
+#include "dir.h"
+#include "parse-options.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 unidiff_zero;
+static int p_value = 1;
+static int p_value_known;
+static int check_index;
+static int update_index;
+static int cached;
+static int diffstat;
+static int numstat;
+static int summary;
+static int check;
+static int apply = 1;
+static int apply_in_reverse;
+static int apply_with_reject;
+static int apply_verbosely;
+static int no_add;
+static const char *fake_ancestor;
+static int line_termination = '\n';
+static unsigned int p_context = UINT_MAX;
+static const char * const apply_usage[] = {
+       "git apply [options] [<patch>...]",
+       NULL
+};
+
+static enum ws_error_action {
+       nowarn_ws_error,
+       warn_on_ws_error,
+       die_on_ws_error,
+       correct_ws_error
+} ws_error_action = warn_on_ws_error;
+static int whitespace_error;
+static int squelch_whitespace_errors = 5;
+static int applied_after_fixing_ws;
+
+static enum ws_ignore {
+       ignore_ws_none,
+       ignore_ws_change
+} ws_ignore_action = ignore_ws_none;
+
+
+static const char *patch_input_file;
+static const char *root;
+static int root_len;
+static int read_stdin = 1;
+static int options;
+
+static void parse_whitespace_option(const char *option)
+{
+       if (!option) {
+               ws_error_action = warn_on_ws_error;
+               return;
+       }
+       if (!strcmp(option, "warn")) {
+               ws_error_action = warn_on_ws_error;
+               return;
+       }
+       if (!strcmp(option, "nowarn")) {
+               ws_error_action = nowarn_ws_error;
+               return;
+       }
+       if (!strcmp(option, "error")) {
+               ws_error_action = die_on_ws_error;
+               return;
+       }
+       if (!strcmp(option, "error-all")) {
+               ws_error_action = die_on_ws_error;
+               squelch_whitespace_errors = 0;
+               return;
+       }
+       if (!strcmp(option, "strip") || !strcmp(option, "fix")) {
+               ws_error_action = correct_ws_error;
+               return;
+       }
+       die("unrecognized whitespace option '%s'", option);
+}
+
+static void parse_ignorewhitespace_option(const char *option)
+{
+       if (!option || !strcmp(option, "no") ||
+           !strcmp(option, "false") || !strcmp(option, "never") ||
+           !strcmp(option, "none")) {
+               ws_ignore_action = ignore_ws_none;
+               return;
+       }
+       if (!strcmp(option, "change")) {
+               ws_ignore_action = ignore_ws_change;
+               return;
+       }
+       die("unrecognized whitespace ignore option '%s'", option);
+}
+
+static void set_default_whitespace_mode(const char *whitespace_option)
+{
+       if (!whitespace_option && !apply_default_whitespace)
+               ws_error_action = (apply ? warn_on_ws_error : nowarn_ws_error);
+}
+
+/*
+ * 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;
+
+/*
+ * This represents one "hunk" from a patch, starting with
+ * "@@ -oldpos,oldlines +newpos,newlines @@" marker.  The
+ * patch text is pointed at by patch, and its byte length
+ * is stored in size.  leading and trailing are the number
+ * of context lines.
+ */
+struct fragment {
+       unsigned long leading, trailing;
+       unsigned long oldpos, oldlines;
+       unsigned long newpos, newlines;
+       const char *patch;
+       int size;
+       int rejected;
+       int linenr;
+       struct fragment *next;
+};
+
+/*
+ * When dealing with a binary patch, we reuse "leading" field
+ * to store the type of the binary hunk, either deflated "delta"
+ * or deflated "literal".
+ */
+#define binary_patch_method leading
+#define BINARY_DELTA_DEFLATED  1
+#define BINARY_LITERAL_DEFLATED 2
+
+/*
+ * This represents a "patch" to a file, both metainfo changes
+ * such as creation/deletion, filemode and content changes represented
+ * as a series of fragments.
+ */
+struct patch {
+       char *new_name, *old_name, *def_name;
+       unsigned int old_mode, new_mode;
+       int is_new, is_delete;  /* -1 = unknown, 0 = false, 1 = true */
+       int rejected;
+       unsigned ws_rule;
+       unsigned long deflate_origlen;
+       int lines_added, lines_deleted;
+       int score;
+       unsigned int is_toplevel_relative:1;
+       unsigned int inaccurate_eof:1;
+       unsigned int is_binary:1;
+       unsigned int is_copy:1;
+       unsigned int is_rename:1;
+       unsigned int recount:1;
+       struct fragment *fragments;
+       char *result;
+       size_t resultsize;
+       char old_sha1_prefix[41];
+       char new_sha1_prefix[41];
+       struct patch *next;
+};
+
+/*
+ * A line in a file, len-bytes long (includes the terminating LF,
+ * except for an incomplete line at the end if the file ends with
+ * one), and its contents hashes to 'hash'.
+ */
+struct line {
+       size_t len;
+       unsigned hash : 24;
+       unsigned flag : 8;
+#define LINE_COMMON     1
+};
+
+/*
+ * This represents a "file", which is an array of "lines".
+ */
+struct image {
+       char *buf;
+       size_t len;
+       size_t nr;
+       size_t alloc;
+       struct line *line_allocated;
+       struct line *line;
+};
+
+/*
+ * Records filenames that have been touched, in order to handle
+ * the case where more than one patches touch the same file.
+ */
+
+static struct string_list fn_table;
+
+static uint32_t hash_line(const char *cp, size_t len)
+{
+       size_t i;
+       uint32_t h;
+       for (i = 0, h = 0; i < len; i++) {
+               if (!isspace(cp[i])) {
+                       h = h * 3 + (cp[i] & 0xff);
+               }
+       }
+       return h;
+}
+
+/*
+ * Compare lines s1 of length n1 and s2 of length n2, ignoring
+ * whitespace difference. Returns 1 if they match, 0 otherwise
+ */
+static int fuzzy_matchlines(const char *s1, size_t n1,
+                           const char *s2, size_t n2)
+{
+       const char *last1 = s1 + n1 - 1;
+       const char *last2 = s2 + n2 - 1;
+       int result = 0;
+
+       if (n1 < 0 || n2 < 0)
+               return 0;
+
+       /* ignore line endings */
+       while ((*last1 == '\r') || (*last1 == '\n'))
+               last1--;
+       while ((*last2 == '\r') || (*last2 == '\n'))
+               last2--;
+
+       /* skip leading whitespace */
+       while (isspace(*s1) && (s1 <= last1))
+               s1++;
+       while (isspace(*s2) && (s2 <= last2))
+               s2++;
+       /* early return if both lines are empty */
+       if ((s1 > last1) && (s2 > last2))
+               return 1;
+       while (!result) {
+               result = *s1++ - *s2++;
+               /*
+                * Skip whitespace inside. We check for whitespace on
+                * both buffers because we don't want "a b" to match
+                * "ab"
+                */
+               if (isspace(*s1) && isspace(*s2)) {
+                       while (isspace(*s1) && s1 <= last1)
+                               s1++;
+                       while (isspace(*s2) && s2 <= last2)
+                               s2++;
+               }
+               /*
+                * If we reached the end on one side only,
+                * lines don't match
+                */
+               if (
+                   ((s2 > last2) && (s1 <= last1)) ||
+                   ((s1 > last1) && (s2 <= last2)))
+                       return 0;
+               if ((s1 > last1) && (s2 > last2))
+                       break;
+       }
+
+       return !result;
+}
+
+static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag)
+{
+       ALLOC_GROW(img->line_allocated, img->nr + 1, img->alloc);
+       img->line_allocated[img->nr].len = len;
+       img->line_allocated[img->nr].hash = hash_line(bol, len);
+       img->line_allocated[img->nr].flag = flag;
+       img->nr++;
+}
+
+static void prepare_image(struct image *image, char *buf, size_t len,
+                         int prepare_linetable)
+{
+       const char *cp, *ep;
+
+       memset(image, 0, sizeof(*image));
+       image->buf = buf;
+       image->len = len;
+
+       if (!prepare_linetable)
+               return;
+
+       ep = image->buf + image->len;
+       cp = image->buf;
+       while (cp < ep) {
+               const char *next;
+               for (next = cp; next < ep && *next != '\n'; next++)
+                       ;
+               if (next < ep)
+                       next++;
+               add_line_info(image, cp, next - cp, 0);
+               cp = next;
+       }
+       image->line = image->line_allocated;
+}
+
+static void clear_image(struct image *image)
+{
+       free(image->buf);
+       image->buf = NULL;
+       image->len = 0;
+}
+
+static void say_patch_name(FILE *output, const char *pre,
+                          struct patch *patch, const char *post)
+{
+       fputs(pre, output);
+       if (patch->old_name && patch->new_name &&
+           strcmp(patch->old_name, patch->new_name)) {
+               quote_c_style(patch->old_name, NULL, output, 0);
+               fputs(" => ", output);
+               quote_c_style(patch->new_name, NULL, output, 0);
+       } else {
+               const char *n = patch->new_name;
+               if (!n)
+                       n = patch->old_name;
+               quote_c_style(n, NULL, output, 0);
+       }
+       fputs(post, output);
+}
+
+#define CHUNKSIZE (8192)
+#define SLOP (16)
+
+static void read_patch_file(struct strbuf *sb, int fd)
+{
+       if (strbuf_read(sb, fd, 0) < 0)
+               die_errno("git apply: failed to read");
+
+       /*
+        * 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.
+        */
+       strbuf_grow(sb, SLOP);
+       memset(sb->buf + sb->len, 0, SLOP);
+}
+
+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;
+}
+
+/* remove double slashes to make --index work with such filenames */
+static char *squash_slash(char *name)
+{
+       int i = 0, j = 0;
+
+       if (!name)
+               return NULL;
+
+       while (name[i]) {
+               if ((name[j++] = name[i++]) == '/')
+                       while (name[i] == '/')
+                               i++;
+       }
+       name[j] = '\0';
+       return name;
+}
+
+static char *find_name_gnu(const char *line, char *def, int p_value)
+{
+       struct strbuf name = STRBUF_INIT;
+       char *cp;
+
+       /*
+        * Proposed "new-style" GNU patch/diff format; see
+        * http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2
+        */
+       if (unquote_c_style(&name, line, NULL)) {
+               strbuf_release(&name);
+               return NULL;
+       }
+
+       for (cp = name.buf; p_value; p_value--) {
+               cp = strchr(cp, '/');
+               if (!cp) {
+                       strbuf_release(&name);
+                       return NULL;
+               }
+               cp++;
+       }
+
+       /* name can later be freed, so we need
+        * to memmove, not just return cp
+        */
+       strbuf_remove(&name, 0, cp - name.buf);
+       free(def);
+       if (root)
+               strbuf_insert(&name, 0, root, root_len);
+       return squash_slash(strbuf_detach(&name, NULL));
+}
+
+static size_t tz_len(const char *line, size_t len)
+{
+       const char *tz, *p;
+
+       if (len < strlen(" +0500") || line[len-strlen(" +0500")] != ' ')
+               return 0;
+       tz = line + len - strlen(" +0500");
+
+       if (tz[1] != '+' && tz[1] != '-')
+               return 0;
+
+       for (p = tz + 2; p != line + len; p++)
+               if (!isdigit(*p))
+                       return 0;
+
+       return line + len - tz;
+}
+
+static size_t date_len(const char *line, size_t len)
+{
+       const char *date, *p;
+
+       if (len < strlen("72-02-05") || line[len-strlen("-05")] != '-')
+               return 0;
+       p = date = line + len - strlen("72-02-05");
+
+       if (!isdigit(*p++) || !isdigit(*p++) || *p++ != '-' ||
+           !isdigit(*p++) || !isdigit(*p++) || *p++ != '-' ||
+           !isdigit(*p++) || !isdigit(*p++))   /* Not a date. */
+               return 0;
+
+       if (date - line >= strlen("19") &&
+           isdigit(date[-1]) && isdigit(date[-2]))     /* 4-digit year */
+               date -= strlen("19");
+
+       return line + len - date;
+}
+
+static size_t short_time_len(const char *line, size_t len)
+{
+       const char *time, *p;
+
+       if (len < strlen(" 07:01:32") || line[len-strlen(":32")] != ':')
+               return 0;
+       p = time = line + len - strlen(" 07:01:32");
+
+       /* Permit 1-digit hours? */
+       if (*p++ != ' ' ||
+           !isdigit(*p++) || !isdigit(*p++) || *p++ != ':' ||
+           !isdigit(*p++) || !isdigit(*p++) || *p++ != ':' ||
+           !isdigit(*p++) || !isdigit(*p++))   /* Not a time. */
+               return 0;
+
+       return line + len - time;
+}
+
+static size_t fractional_time_len(const char *line, size_t len)
+{
+       const char *p;
+       size_t n;
+
+       /* Expected format: 19:41:17.620000023 */
+       if (!len || !isdigit(line[len - 1]))
+               return 0;
+       p = line + len - 1;
+
+       /* Fractional seconds. */
+       while (p > line && isdigit(*p))
+               p--;
+       if (*p != '.')
+               return 0;
+
+       /* Hours, minutes, and whole seconds. */
+       n = short_time_len(line, p - line);
+       if (!n)
+               return 0;
+
+       return line + len - p + n;
+}
+
+static size_t trailing_spaces_len(const char *line, size_t len)
+{
+       const char *p;
+
+       /* Expected format: ' ' x (1 or more)  */
+       if (!len || line[len - 1] != ' ')
+               return 0;
+
+       p = line + len;
+       while (p != line) {
+               p--;
+               if (*p != ' ')
+                       return line + len - (p + 1);
+       }
+
+       /* All spaces! */
+       return len;
+}
+
+static size_t diff_timestamp_len(const char *line, size_t len)
+{
+       const char *end = line + len;
+       size_t n;
+
+       /*
+        * Posix: 2010-07-05 19:41:17
+        * GNU: 2010-07-05 19:41:17.620000023 -0500
+        */
+
+       if (!isdigit(end[-1]))
+               return 0;
+
+       n = tz_len(line, end - line);
+       end -= n;
+
+       n = short_time_len(line, end - line);
+       if (!n)
+               n = fractional_time_len(line, end - line);
+       end -= n;
+
+       n = date_len(line, end - line);
+       if (!n) /* No date.  Too bad. */
+               return 0;
+       end -= n;
+
+       if (end == line)        /* No space before date. */
+               return 0;
+       if (end[-1] == '\t') {  /* Success! */
+               end--;
+               return line + len - end;
+       }
+       if (end[-1] != ' ')     /* No space before date. */
+               return 0;
+
+       /* Whitespace damage. */
+       end -= trailing_spaces_len(line, end - line);
+       return line + len - end;
+}
+
+static char *find_name_common(const char *line, char *def, int p_value,
+                               const char *end, int terminate)
+{
+       int len;
+       const char *start = NULL;
+
+       if (p_value == 0)
+               start = line;
+       while (line != end) {
+               char c = *line;
+
+               if (!end && 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 squash_slash(def);
+       len = line - start;
+       if (!len)
+               return squash_slash(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 squash_slash(def);
+               free(def);
+       }
+
+       if (root) {
+               char *ret = xmalloc(root_len + len + 1);
+               strcpy(ret, root);
+               memcpy(ret + root_len, start, len);
+               ret[root_len + len] = '\0';
+               return squash_slash(ret);
+       }
+
+       return squash_slash(xmemdupz(start, len));
+}
+
+static char *find_name(const char *line, char *def, int p_value, int terminate)
+{
+       if (*line == '"') {
+               char *name = find_name_gnu(line, def, p_value);
+               if (name)
+                       return name;
+       }
+
+       return find_name_common(line, def, p_value, NULL, terminate);
+}
+
+static char *find_name_traditional(const char *line, char *def, int p_value)
+{
+       size_t len = strlen(line);
+       size_t date_len;
+
+       if (*line == '"') {
+               char *name = find_name_gnu(line, def, p_value);
+               if (name)
+                       return name;
+       }
+
+       len = strchrnul(line, '\n') - line;
+       date_len = diff_timestamp_len(line, len);
+       if (!date_len)
+               return find_name_common(line, def, p_value, NULL, TERM_TAB);
+       len -= date_len;
+
+       return find_name_common(line, def, p_value, line + len, 0);
+}
+
+static int count_slashes(const char *cp)
+{
+       int cnt = 0;
+       char ch;
+
+       while ((ch = *cp++))
+               if (ch == '/')
+                       cnt++;
+       return cnt;
+}
+
+/*
+ * Given the string after "--- " or "+++ ", guess the appropriate
+ * p_value for the given patch.
+ */
+static int guess_p_value(const char *nameline)
+{
+       char *name, *cp;
+       int val = -1;
+
+       if (is_dev_null(nameline))
+               return -1;
+       name = find_name_traditional(nameline, NULL, 0);
+       if (!name)
+               return -1;
+       cp = strchr(name, '/');
+       if (!cp)
+               val = 0;
+       else if (prefix) {
+               /*
+                * Does it begin with "a/$our-prefix" and such?  Then this is
+                * very likely to apply to our directory.
+                */
+               if (!strncmp(name, prefix, prefix_length))
+                       val = count_slashes(prefix);
+               else {
+                       cp++;
+                       if (!strncmp(cp, prefix, prefix_length))
+                               val = count_slashes(prefix) + 1;
+               }
+       }
+       free(name);
+       return val;
+}
+
+/*
+ * Does the ---/+++ line has the POSIX timestamp after the last HT?
+ * GNU diff puts epoch there to signal a creation/deletion event.  Is
+ * this such a timestamp?
+ */
+static int has_epoch_timestamp(const char *nameline)
+{
+       /*
+        * We are only interested in epoch timestamp; any non-zero
+        * fraction cannot be one, hence "(\.0+)?" in the regexp below.
+        * For the same reason, the date must be either 1969-12-31 or
+        * 1970-01-01, and the seconds part must be "00".
+        */
+       const char stamp_regexp[] =
+               "^(1969-12-31|1970-01-01)"
+               " "
+               "[0-2][0-9]:[0-5][0-9]:00(\\.0+)?"
+               " "
+               "([-+][0-2][0-9][0-5][0-9])\n";
+       const char *timestamp = NULL, *cp;
+       static regex_t *stamp;
+       regmatch_t m[10];
+       int zoneoffset;
+       int hourminute;
+       int status;
+
+       for (cp = nameline; *cp != '\n'; cp++) {
+               if (*cp == '\t')
+                       timestamp = cp + 1;
+       }
+       if (!timestamp)
+               return 0;
+       if (!stamp) {
+               stamp = xmalloc(sizeof(*stamp));
+               if (regcomp(stamp, stamp_regexp, REG_EXTENDED)) {
+                       warning("Cannot prepare timestamp regexp %s",
+                               stamp_regexp);
+                       return 0;
+               }
+       }
+
+       status = regexec(stamp, timestamp, ARRAY_SIZE(m), m, 0);
+       if (status) {
+               if (status != REG_NOMATCH)
+                       warning("regexec returned %d for input: %s",
+                               status, timestamp);
+               return 0;
+       }
+
+       zoneoffset = strtol(timestamp + m[3].rm_so + 1, NULL, 10);
+       zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100);
+       if (timestamp[m[3].rm_so] == '-')
+               zoneoffset = -zoneoffset;
+
+       /*
+        * YYYY-MM-DD hh:mm:ss must be from either 1969-12-31
+        * (west of GMT) or 1970-01-01 (east of GMT)
+        */
+       if ((zoneoffset < 0 && memcmp(timestamp, "1969-12-31", 10)) ||
+           (0 <= zoneoffset && memcmp(timestamp, "1970-01-01", 10)))
+               return 0;
+
+       hourminute = (strtol(timestamp + 11, NULL, 10) * 60 +
+                     strtol(timestamp + 14, NULL, 10) -
+                     zoneoffset);
+
+       return ((zoneoffset < 0 && hourminute == 1440) ||
+               (0 <= zoneoffset && !hourminute));
+}
+
+/*
+ * Get the name etc info from the ---/+++ lines of a traditional patch header
+ *
+ * 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 (!p_value_known) {
+               int p, q;
+               p = guess_p_value(first);
+               q = guess_p_value(second);
+               if (p < 0) p = q;
+               if (0 <= p && p == q) {
+                       p_value = p;
+                       p_value_known = 1;
+               }
+       }
+       if (is_dev_null(first)) {
+               patch->is_new = 1;
+               patch->is_delete = 0;
+               name = find_name_traditional(second, NULL, p_value);
+               patch->new_name = name;
+       } else if (is_dev_null(second)) {
+               patch->is_new = 0;
+               patch->is_delete = 1;
+               name = find_name_traditional(first, NULL, p_value);
+               patch->old_name = name;
+       } else {
+               name = find_name_traditional(first, NULL, p_value);
+               name = find_name_traditional(second, name, p_value);
+               if (has_epoch_timestamp(first)) {
+                       patch->is_new = 1;
+                       patch->is_delete = 0;
+                       patch->new_name = name;
+               } else if (has_epoch_timestamp(second)) {
+                       patch->is_new = 0;
+                       patch->is_delete = 1;
+                       patch->old_name = name;
+               } else {
+                       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, p_value, TERM_TAB);
+
+       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, p_value, TERM_TAB);
+               if (!another || memcmp(another, name, len + 1))
+                       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->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 nslash = p_value;
+       int i;
+
+       for (i = 0; i < llen; i++) {
+               int ch = line[i];
+               if (ch == '/' && --nslash <= 0)
+                       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)
+{
+       const char *name;
+       const char *second = NULL;
+       size_t len;
+
+       line += strlen("diff --git ");
+       llen -= strlen("diff --git ");
+
+       if (*line == '"') {
+               const char *cp;
+               struct strbuf first = STRBUF_INIT;
+               struct strbuf sp = STRBUF_INIT;
+
+               if (unquote_c_style(&first, line, &second))
+                       goto free_and_fail1;
+
+               /* advance to the first slash */
+               cp = stop_at_slash(first.buf, first.len);
+               /* we do not accept absolute paths */
+               if (!cp || cp == first.buf)
+                       goto free_and_fail1;
+               strbuf_remove(&first, 0, cp + 1 - first.buf);
+
+               /*
+                * 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_and_fail1;
+               if (*second == '"') {
+                       if (unquote_c_style(&sp, second, NULL))
+                               goto free_and_fail1;
+                       cp = stop_at_slash(sp.buf, sp.len);
+                       if (!cp || cp == sp.buf)
+                               goto free_and_fail1;
+                       /* They must match, otherwise ignore */
+                       if (strcmp(cp + 1, first.buf))
+                               goto free_and_fail1;
+                       strbuf_release(&sp);
+                       return strbuf_detach(&first, NULL);
+               }
+
+               /* unquoted second */
+               cp = stop_at_slash(second, line + llen - second);
+               if (!cp || cp == second)
+                       goto free_and_fail1;
+               cp++;
+               if (line + llen - cp != first.len + 1 ||
+                   memcmp(first.buf, cp, first.len))
+                       goto free_and_fail1;
+               return strbuf_detach(&first, NULL);
+
+       free_and_fail1:
+               strbuf_release(&first);
+               strbuf_release(&sp);
+               return NULL;
+       }
+
+       /* 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 == '"') {
+                       struct strbuf sp = STRBUF_INIT;
+                       const char *np;
+
+                       if (unquote_c_style(&sp, second, NULL))
+                               goto free_and_fail2;
+
+                       np = stop_at_slash(sp.buf, sp.len);
+                       if (!np || np == sp.buf)
+                               goto free_and_fail2;
+                       np++;
+
+                       len = sp.buf + sp.len - np;
+                       if (len < second - name &&
+                           !strncmp(np, name, len) &&
+                           isspace(name[len])) {
+                               /* Good */
+                               strbuf_remove(&sp, 0, np - sp.buf);
+                               return strbuf_detach(&sp, NULL);
+                       }
+
+               free_and_fail2:
+                       strbuf_release(&sp);
+                       return NULL;
+               }
+       }
+
+       /*
+        * Accept a name only if it shows up twice, exactly the same
+        * form.
+        */
+       for (len = 0 ; ; len++) {
+               switch (name[len]) {
+               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)) {
+                               return xmemdupz(name, len);
+                       }
+               }
+       }
+}
+
+/* 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);
+       if (patch->def_name && root) {
+               char *s = xmalloc(root_len + strlen(patch->def_name) + 1);
+               strcpy(s, root);
+               strcpy(s + root_len, patch->def_name);
+               free(patch->def_name);
+               patch->def_name = s;
+       }
+
+       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;
+}
+
+static void recount_diff(char *line, int size, struct fragment *fragment)
+{
+       int oldlines = 0, newlines = 0, ret = 0;
+
+       if (size < 1) {
+               warning("recount: ignore empty hunk");
+               return;
+       }
+
+       for (;;) {
+               int len = linelen(line, size);
+               size -= len;
+               line += len;
+
+               if (size < 1)
+                       break;
+
+               switch (*line) {
+               case ' ': case '\n':
+                       newlines++;
+                       /* fall through */
+               case '-':
+                       oldlines++;
+                       continue;
+               case '+':
+                       newlines++;
+                       continue;
+               case '\\':
+                       continue;
+               case '@':
+                       ret = size < 3 || prefixcmp(line, "@@ ");
+                       break;
+               case 'd':
+                       ret = size < 5 || prefixcmp(line, "diff ");
+                       break;
+               default:
+                       ret = -1;
+                       break;
+               }
+               if (ret) {
+                       warning("recount: unexpected line: %.*s",
+                               (int)linelen(line, size), line);
+                       return;
+               }
+               break;
+       }
+       fragment->oldlines = oldlines;
+       fragment->newlines = newlines;
+}
+
+/*
+ * 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_toplevel_relative = 0;
+       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 fragments.
+                * 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;
+                       die("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 when removing "
+                                           "%d leading pathname components (line %d)" , p_value, linenr);
+                               patch->old_name = patch->new_name = patch->def_name;
+                       }
+                       patch->is_toplevel_relative = 1;
+                       *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 ("@@ -0,0 +1 @@\n" is the shortest).
+                */
+               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;
+}
+
+static void record_ws_error(unsigned result, const char *line, int len, int linenr)
+{
+       char *err;
+
+       if (!result)
+               return;
+
+       whitespace_error++;
+       if (squelch_whitespace_errors &&
+           squelch_whitespace_errors < whitespace_error)
+               return;
+
+       err = whitespace_error_string(result);
+       fprintf(stderr, "%s:%d: %s.\n%.*s\n",
+               patch_input_file, linenr, err, len, line);
+       free(err);
+}
+
+static void check_whitespace(const char *line, int len, unsigned ws_rule)
+{
+       unsigned result = ws_check(line + 1, len - 1, ws_rule);
+
+       record_ws_error(result, line + 1, len - 2, linenr);
+}
+
+/*
+ * 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;
+       if (offset > 0 && patch->recount)
+               recount_diff(line + offset, size - offset, fragment);
+       oldlines = fragment->oldlines;
+       newlines = fragment->newlines;
+       leading = 0;
+       trailing = 0;
+
+       /* Parse the thing.. */
+       line += len;
+       size -= len;
+       linenr++;
+       added = deleted = 0;
+       for (offset = len;
+            0 < size;
+            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 '\n': /* newer GNU diff, an empty context line */
+               case ' ':
+                       oldlines--;
+                       newlines--;
+                       if (!deleted && !added)
+                               leading++;
+                       trailing++;
+                       break;
+               case '-':
+                       if (apply_in_reverse &&
+                           ws_error_action != nowarn_ws_error)
+                               check_whitespace(line, len, patch->ws_rule);
+                       deleted++;
+                       oldlines--;
+                       trailing = 0;
+                       break;
+               case '+':
+                       if (!apply_in_reverse &&
+                           ws_error_action != nowarn_ws_error)
+                               check_whitespace(line, len, patch->ws_rule);
+                       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;
+
+       if (0 < patch->is_new && oldlines)
+               return error("new file depends on old contents");
+       if (0 < patch->is_delete && newlines)
+               return error("deleted file still has contents");
+       return offset;
+}
+
+static int parse_single_patch(char *line, unsigned long size, struct patch *patch)
+{
+       unsigned long offset = 0;
+       unsigned long oldlines = 0, newlines = 0, context = 0;
+       struct fragment **fragp = &patch->fragments;
+
+       while (size > 4 && !memcmp(line, "@@ -", 4)) {
+               struct fragment *fragment;
+               int len;
+
+               fragment = xcalloc(1, sizeof(*fragment));
+               fragment->linenr = linenr;
+               len = parse_fragment(line, size, patch, fragment);
+               if (len <= 0)
+                       die("corrupt patch at line %d", linenr);
+               fragment->patch = line;
+               fragment->size = len;
+               oldlines += fragment->oldlines;
+               newlines += fragment->newlines;
+               context += fragment->leading + fragment->trailing;
+
+               *fragp = fragment;
+               fragp = &fragment->next;
+
+               offset += len;
+               line += len;
+               size -= len;
+       }
+
+       /*
+        * If something was removed (i.e. we have old-lines) it cannot
+        * be creation, and if something was added it cannot be
+        * deletion.  However, the reverse is not true; --unified=0
+        * patches that only add are not necessarily creation even
+        * though they do not have any old lines, and ones that only
+        * delete are not necessarily deletion.
+        *
+        * Unfortunately, a real creation/deletion patch do _not_ have
+        * any context line by definition, so we cannot safely tell it
+        * apart with --unified=0 insanity.  At least if the patch has
+        * more than one hunk it is not creation or deletion.
+        */
+       if (patch->is_new < 0 &&
+           (oldlines || (patch->fragments && patch->fragments->next)))
+               patch->is_new = 0;
+       if (patch->is_delete < 0 &&
+           (newlines || (patch->fragments && patch->fragments->next)))
+               patch->is_delete = 0;
+
+       if (0 < patch->is_new && oldlines)
+               die("new file %s depends on old contents", patch->new_name);
+       if (0 < patch->is_delete && newlines)
+               die("deleted file %s still has contents", patch->old_name);
+       if (!patch->is_delete && !newlines && context)
+               fprintf(stderr, "** warning: file %s becomes empty but "
+                       "is not deleted\n", patch->new_name);
+
+       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 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;
+       git_inflate_init(&stream);
+       st = git_inflate(&stream, Z_FINISH);
+       git_inflate_end(&stream);
+       if ((st != Z_STREAM_END) || stream.total_out != inflated_size) {
+               free(out);
+               return NULL;
+       }
+       return out;
+}
+
+static struct fragment *parse_binary_hunk(char **buf_p,
+                                         unsigned long *sz_p,
+                                         int *status_p,
+                                         int *used_p)
+{
+       /*
+        * Expect a line that begins with binary patch method ("literal"
+        * or "delta"), followed by the length of data before deflating.
+        * a sequence of 'length-byte' followed by base-85 encoded data
+        * should follow, terminated by a newline.
+        *
+        * 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.
+        */
+       int llen, used;
+       unsigned long size = *sz_p;
+       char *buffer = *buf_p;
+       int patch_method;
+       unsigned long origlen;
+       char *data = NULL;
+       int hunk_size = 0;
+       struct fragment *frag;
+
+       llen = linelen(buffer, size);
+       used = llen;
+
+       *status_p = 0;
+
+       if (!prefixcmp(buffer, "delta ")) {
+               patch_method = BINARY_DELTA_DEFLATED;
+               origlen = strtoul(buffer + 6, NULL, 10);
+       }
+       else if (!prefixcmp(buffer, "literal ")) {
+               patch_method = BINARY_LITERAL_DEFLATED;
+               origlen = strtoul(buffer + 8, NULL, 10);
+       }
+       else
+               return NULL;
+
+       linenr++;
+       buffer += llen;
+       while (1) {
+               int byte_length, max_byte_length, newsize;
+               llen = linelen(buffer, size);
+               used += llen;
+               linenr++;
+               if (llen == 1) {
+                       /* consume the blank line */
+                       buffer++;
+                       size--;
+                       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 = hunk_size + byte_length;
+               data = xrealloc(data, newsize);
+               if (decode_85(data + hunk_size, buffer + 1, byte_length))
+                       goto corrupt;
+               hunk_size = newsize;
+               buffer += llen;
+               size -= llen;
+       }
+
+       frag = xcalloc(1, sizeof(*frag));
+       frag->patch = inflate_it(data, hunk_size, origlen);
+       if (!frag->patch)
+               goto corrupt;
+       free(data);
+       frag->size = origlen;
+       *buf_p = buffer;
+       *sz_p = size;
+       *used_p = used;
+       frag->binary_patch_method = patch_method;
+       return frag;
+
+ corrupt:
+       free(data);
+       *status_p = -1;
+       error("corrupt binary patch at line %d: %.*s",
+             linenr-1, llen-1, buffer);
+       return NULL;
+}
+
+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 "literal" or
+        * "delta") and the length of data before deflating; a
+        * sequence of 'length-byte' followed by base-85 encoded data
+        * follows.
+        *
+        * When a binary patch is reversible, there is another binary
+        * hunk in the same format, starting with patch method (either
+        * "literal" or "delta") with the length of data, and a sequence
+        * of length-byte + base-85 encoded data, terminated with another
+        * empty line.  This data, when applied to the postimage, produces
+        * the preimage.
+        */
+       struct fragment *forward;
+       struct fragment *reverse;
+       int status;
+       int used, used_1;
+
+       forward = parse_binary_hunk(&buffer, &size, &status, &used);
+       if (!forward && !status)
+               /* there has to be one hunk (forward hunk) */
+               return error("unrecognized binary patch at line %d", linenr-1);
+       if (status)
+               /* otherwise we already gave an error message */
+               return status;
+
+       reverse = parse_binary_hunk(&buffer, &size, &status, &used_1);
+       if (reverse)
+               used += used_1;
+       else if (status) {
+               /*
+                * Not having reverse hunk is not an error, but having
+                * a corrupt reverse hunk is.
+                */
+               free((void*) forward->patch);
+               free(forward);
+               return status;
+       }
+       forward->next = reverse;
+       patch->fragments = forward;
+       patch->is_binary = 1;
+       return used;
+}
+
+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;
+
+       patch->ws_rule = whitespace_rule(patch->new_name
+                                        ? patch->new_name
+                                        : patch->old_name);
+
+       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 text patch
+                * without metadata change.  A binary patch appears
+                * empty to us here.
+                */
+               if ((apply || check) &&
+                   (!patch->is_binary && !metadata_changes(patch)))
+                       die("patch with only garbage at line %d", linenr);
+       }
+
+       return offset + hdrsize + patchsize;
+}
+
+#define swap(a,b) myswap((a),(b),sizeof(a))
+
+#define myswap(a, b, size) do {                \
+       unsigned char mytmp[size];      \
+       memcpy(mytmp, &a, size);                \
+       memcpy(&a, &b, size);           \
+       memcpy(&b, mytmp, size);                \
+} while (0)
+
+static void reverse_patches(struct patch *p)
+{
+       for (; p; p = p->next) {
+               struct fragment *frag = p->fragments;
+
+               swap(p->new_name, p->old_name);
+               swap(p->new_mode, p->old_mode);
+               swap(p->is_new, p->is_delete);
+               swap(p->lines_added, p->lines_deleted);
+               swap(p->old_sha1_prefix, p->new_sha1_prefix);
+
+               for (; frag; frag = frag->next) {
+                       swap(frag->newpos, frag->oldpos);
+                       swap(frag->newlines, frag->oldlines);
+               }
+       }
+}
+
+static const char pluses[] =
+"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
+static const char minuses[]=
+"----------------------------------------------------------------------";
+
+static void show_stats(struct patch *patch)
+{
+       struct strbuf qname = STRBUF_INIT;
+       char *cp = patch->new_name ? patch->new_name : patch->old_name;
+       int max, add, del;
+
+       quote_c_style(cp, &qname, NULL, 0);
+
+       /*
+        * "scale" the filename
+        */
+       max = max_len;
+       if (max > 50)
+               max = 50;
+
+       if (qname.len > max) {
+               cp = strchr(qname.buf + qname.len + 3 - max, '/');
+               if (!cp)
+                       cp = qname.buf + qname.len + 3 - max;
+               strbuf_splice(&qname, 0, cp - qname.buf, "...", 3);
+       }
+
+       if (patch->is_binary) {
+               printf(" %-*s |  Bin\n", max, qname.buf);
+               strbuf_release(&qname);
+               return;
+       }
+
+       printf(" %-*s |", max, qname.buf);
+       strbuf_release(&qname);
+
+       /*
+        * scale the add/delete
+        */
+       max = max + max_change > 70 ? 70 - max : max_change;
+       add = patch->lines_added;
+       del = patch->lines_deleted;
+
+       if (max_change > 0) {
+               int total = ((add + del) * max + max_change / 2) / max_change;
+               add = (add * max + max_change / 2) / max_change;
+               del = total - add;
+       }
+       printf("%5d %.*s%.*s\n", patch->lines_added + patch->lines_deleted,
+               add, pluses, del, minuses);
+}
+
+static int read_old_data(struct stat *st, const char *path, struct strbuf *buf)
+{
+       switch (st->st_mode & S_IFMT) {
+       case S_IFLNK:
+               if (strbuf_readlink(buf, path, st->st_size) < 0)
+                       return error("unable to read symlink %s", path);
+               return 0;
+       case S_IFREG:
+               if (strbuf_read_file(buf, path, st->st_size) != st->st_size)
+                       return error("unable to open or read %s", path);
+               convert_to_git(path, buf->buf, buf->len, buf, 0);
+               return 0;
+       default:
+               return -1;
+       }
+}
+
+/*
+ * Update the preimage, and the common lines in postimage,
+ * from buffer buf of length len. If postlen is 0 the postimage
+ * is updated in place, otherwise it's updated on a new buffer
+ * of length postlen
+ */
+
+static void update_pre_post_images(struct image *preimage,
+                                  struct image *postimage,
+                                  char *buf,
+                                  size_t len, size_t postlen)
+{
+       int i, ctx;
+       char *new, *old, *fixed;
+       struct image fixed_preimage;
+
+       /*
+        * Update the preimage with whitespace fixes.  Note that we
+        * are not losing preimage->buf -- apply_one_fragment() will
+        * free "oldlines".
+        */
+       prepare_image(&fixed_preimage, buf, len, 1);
+       assert(fixed_preimage.nr == preimage->nr);
+       for (i = 0; i < preimage->nr; i++)
+               fixed_preimage.line[i].flag = preimage->line[i].flag;
+       free(preimage->line_allocated);
+       *preimage = fixed_preimage;
+
+       /*
+        * Adjust the common context lines in postimage. This can be
+        * done in-place when we are just doing whitespace fixing,
+        * which does not make the string grow, but needs a new buffer
+        * when ignoring whitespace causes the update, since in this case
+        * we could have e.g. tabs converted to multiple spaces.
+        * We trust the caller to tell us if the update can be done
+        * in place (postlen==0) or not.
+        */
+       old = postimage->buf;
+       if (postlen)
+               new = postimage->buf = xmalloc(postlen);
+       else
+               new = old;
+       fixed = preimage->buf;
+       for (i = ctx = 0; i < postimage->nr; i++) {
+               size_t len = postimage->line[i].len;
+               if (!(postimage->line[i].flag & LINE_COMMON)) {
+                       /* an added line -- no counterparts in preimage */
+                       memmove(new, old, len);
+                       old += len;
+                       new += len;
+                       continue;
+               }
+
+               /* a common context -- skip it in the original postimage */
+               old += len;
+
+               /* and find the corresponding one in the fixed preimage */
+               while (ctx < preimage->nr &&
+                      !(preimage->line[ctx].flag & LINE_COMMON)) {
+                       fixed += preimage->line[ctx].len;
+                       ctx++;
+               }
+               if (preimage->nr <= ctx)
+                       die("oops");
+
+               /* and copy it in, while fixing the line length */
+               len = preimage->line[ctx].len;
+               memcpy(new, fixed, len);
+               new += len;
+               fixed += len;
+               postimage->line[i].len = len;
+               ctx++;
+       }
+
+       /* Fix the length of the whole thing */
+       postimage->len = new - postimage->buf;
+}
+
+static int match_fragment(struct image *img,
+                         struct image *preimage,
+                         struct image *postimage,
+                         unsigned long try,
+                         int try_lno,
+                         unsigned ws_rule,
+                         int match_beginning, int match_end)
+{
+       int i;
+       char *fixed_buf, *buf, *orig, *target;
+       struct strbuf fixed;
+       size_t fixed_len;
+       int preimage_limit;
+
+       if (preimage->nr + try_lno <= img->nr) {
+               /*
+                * The hunk falls within the boundaries of img.
+                */
+               preimage_limit = preimage->nr;
+               if (match_end && (preimage->nr + try_lno != img->nr))
+                       return 0;
+       } else if (ws_error_action == correct_ws_error &&
+                  (ws_rule & WS_BLANK_AT_EOF)) {
+               /*
+                * This hunk extends beyond the end of img, and we are
+                * removing blank lines at the end of the file.  This
+                * many lines from the beginning of the preimage must
+                * match with img, and the remainder of the preimage
+                * must be blank.
+                */
+               preimage_limit = img->nr - try_lno;
+       } else {
+               /*
+                * The hunk extends beyond the end of the img and
+                * we are not removing blanks at the end, so we
+                * should reject the hunk at this position.
+                */
+               return 0;
+       }
+
+       if (match_beginning && try_lno)
+               return 0;
+
+       /* Quick hash check */
+       for (i = 0; i < preimage_limit; i++)
+               if (preimage->line[i].hash != img->line[try_lno + i].hash)
+                       return 0;
+
+       if (preimage_limit == preimage->nr) {
+               /*
+                * Do we have an exact match?  If we were told to match
+                * at the end, size must be exactly at try+fragsize,
+                * otherwise try+fragsize must be still within the preimage,
+                * and either case, the old piece should match the preimage
+                * exactly.
+                */
+               if ((match_end
+                    ? (try + preimage->len == img->len)
+                    : (try + preimage->len <= img->len)) &&
+                   !memcmp(img->buf + try, preimage->buf, preimage->len))
+                       return 1;
+       } else {
+               /*
+                * The preimage extends beyond the end of img, so
+                * there cannot be an exact match.
+                *
+                * There must be one non-blank context line that match
+                * a line before the end of img.
+                */
+               char *buf_end;
+
+               buf = preimage->buf;
+               buf_end = buf;
+               for (i = 0; i < preimage_limit; i++)
+                       buf_end += preimage->line[i].len;
+
+               for ( ; buf < buf_end; buf++)
+                       if (!isspace(*buf))
+                               break;
+               if (buf == buf_end)
+                       return 0;
+       }
+
+       /*
+        * No exact match. If we are ignoring whitespace, run a line-by-line
+        * fuzzy matching. We collect all the line length information because
+        * we need it to adjust whitespace if we match.
+        */
+       if (ws_ignore_action == ignore_ws_change) {
+               size_t imgoff = 0;
+               size_t preoff = 0;
+               size_t postlen = postimage->len;
+               size_t extra_chars;
+               char *preimage_eof;
+               char *preimage_end;
+               for (i = 0; i < preimage_limit; i++) {
+                       size_t prelen = preimage->line[i].len;
+                       size_t imglen = img->line[try_lno+i].len;
+
+                       if (!fuzzy_matchlines(img->buf + try + imgoff, imglen,
+                                             preimage->buf + preoff, prelen))
+                               return 0;
+                       if (preimage->line[i].flag & LINE_COMMON)
+                               postlen += imglen - prelen;
+                       imgoff += imglen;
+                       preoff += prelen;
+               }
+
+               /*
+                * Ok, the preimage matches with whitespace fuzz.
+                *
+                * imgoff now holds the true length of the target that
+                * matches the preimage before the end of the file.
+                *
+                * Count the number of characters in the preimage that fall
+                * beyond the end of the file and make sure that all of them
+                * are whitespace characters. (This can only happen if
+                * we are removing blank lines at the end of the file.)
+                */
+               buf = preimage_eof = preimage->buf + preoff;
+               for ( ; i < preimage->nr; i++)
+                       preoff += preimage->line[i].len;
+               preimage_end = preimage->buf + preoff;
+               for ( ; buf < preimage_end; buf++)
+                       if (!isspace(*buf))
+                               return 0;
+
+               /*
+                * Update the preimage and the common postimage context
+                * lines to use the same whitespace as the target.
+                * If whitespace is missing in the target (i.e.
+                * if the preimage extends beyond the end of the file),
+                * use the whitespace from the preimage.
+                */
+               extra_chars = preimage_end - preimage_eof;
+               strbuf_init(&fixed, imgoff + extra_chars);
+               strbuf_add(&fixed, img->buf + try, imgoff);
+               strbuf_add(&fixed, preimage_eof, extra_chars);
+               fixed_buf = strbuf_detach(&fixed, &fixed_len);
+               update_pre_post_images(preimage, postimage,
+                               fixed_buf, fixed_len, postlen);
+               return 1;
+       }
+
+       if (ws_error_action != correct_ws_error)
+               return 0;
+
+       /*
+        * The hunk does not apply byte-by-byte, but the hash says
+        * it might with whitespace fuzz. We haven't been asked to
+        * ignore whitespace, we were asked to correct whitespace
+        * errors, so let's try matching after whitespace correction.
+        *
+        * The preimage may extend beyond the end of the file,
+        * but in this loop we will only handle the part of the
+        * preimage that falls within the file.
+        */
+       strbuf_init(&fixed, preimage->len + 1);
+       orig = preimage->buf;
+       target = img->buf + try;
+       for (i = 0; i < preimage_limit; i++) {
+               size_t oldlen = preimage->line[i].len;
+               size_t tgtlen = img->line[try_lno + i].len;
+               size_t fixstart = fixed.len;
+               struct strbuf tgtfix;
+               int match;
+
+               /* Try fixing the line in the preimage */
+               ws_fix_copy(&fixed, orig, oldlen, ws_rule, NULL);
+
+               /* Try fixing the line in the target */
+               strbuf_init(&tgtfix, tgtlen);
+               ws_fix_copy(&tgtfix, target, tgtlen, ws_rule, NULL);
+
+               /*
+                * If they match, either the preimage was based on
+                * a version before our tree fixed whitespace breakage,
+                * or we are lacking a whitespace-fix patch the tree
+                * the preimage was based on already had (i.e. target
+                * has whitespace breakage, the preimage doesn't).
+                * In either case, we are fixing the whitespace breakages
+                * so we might as well take the fix together with their
+                * real change.
+                */
+               match = (tgtfix.len == fixed.len - fixstart &&
+                        !memcmp(tgtfix.buf, fixed.buf + fixstart,
+                                            fixed.len - fixstart));
+
+               strbuf_release(&tgtfix);
+               if (!match)
+                       goto unmatch_exit;
+
+               orig += oldlen;
+               target += tgtlen;
+       }
+
+
+       /*
+        * Now handle the lines in the preimage that falls beyond the
+        * end of the file (if any). They will only match if they are
+        * empty or only contain whitespace (if WS_BLANK_AT_EOL is
+        * false).
+        */
+       for ( ; i < preimage->nr; i++) {
+               size_t fixstart = fixed.len; /* start of the fixed preimage */
+               size_t oldlen = preimage->line[i].len;
+               int j;
+
+               /* Try fixing the line in the preimage */
+               ws_fix_copy(&fixed, orig, oldlen, ws_rule, NULL);
+
+               for (j = fixstart; j < fixed.len; j++)
+                       if (!isspace(fixed.buf[j]))
+                               goto unmatch_exit;
+
+               orig += oldlen;
+       }
+
+       /*
+        * Yes, the preimage is based on an older version that still
+        * has whitespace breakages unfixed, and fixing them makes the
+        * hunk match.  Update the context lines in the postimage.
+        */
+       fixed_buf = strbuf_detach(&fixed, &fixed_len);
+       update_pre_post_images(preimage, postimage,
+                              fixed_buf, fixed_len, 0);
+       return 1;
+
+ unmatch_exit:
+       strbuf_release(&fixed);
+       return 0;
+}
+
+static int find_pos(struct image *img,
+                   struct image *preimage,
+                   struct image *postimage,
+                   int line,
+                   unsigned ws_rule,
+                   int match_beginning, int match_end)
+{
+       int i;
+       unsigned long backwards, forwards, try;
+       int backwards_lno, forwards_lno, try_lno;
+
+       /*
+        * If match_beginning or match_end is specified, there is no
+        * point starting from a wrong line that will never match and
+        * wander around and wait for a match at the specified end.
+        */
+       if (match_beginning)
+               line = 0;
+       else if (match_end)
+               line = img->nr - preimage->nr;
+
+       /*
+        * Because the comparison is unsigned, the following test
+        * will also take care of a negative line number that can
+        * result when match_end and preimage is larger than the target.
+        */
+       if ((size_t) line > img->nr)
+               line = img->nr;
+
+       try = 0;
+       for (i = 0; i < line; i++)
+               try += img->line[i].len;
+
+       /*
+        * 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 = try;
+       backwards_lno = line;
+       forwards = try;
+       forwards_lno = line;
+       try_lno = line;
+
+       for (i = 0; ; i++) {
+               if (match_fragment(img, preimage, postimage,
+                                  try, try_lno, ws_rule,
+                                  match_beginning, match_end))
+                       return try_lno;
+
+       again:
+               if (backwards_lno == 0 && forwards_lno == img->nr)
+                       break;
+
+               if (i & 1) {
+                       if (backwards_lno == 0) {
+                               i++;
+                               goto again;
+                       }
+                       backwards_lno--;
+                       backwards -= img->line[backwards_lno].len;
+                       try = backwards;
+                       try_lno = backwards_lno;
+               } else {
+                       if (forwards_lno == img->nr) {
+                               i++;
+                               goto again;
+                       }
+                       forwards += img->line[forwards_lno].len;
+                       forwards_lno++;
+                       try = forwards;
+                       try_lno = forwards_lno;
+               }
+
+       }
+       return -1;
+}
+
+static void remove_first_line(struct image *img)
+{
+       img->buf += img->line[0].len;
+       img->len -= img->line[0].len;
+       img->line++;
+       img->nr--;
+}
+
+static void remove_last_line(struct image *img)
+{
+       img->len -= img->line[--img->nr].len;
+}
+
+static void update_image(struct image *img,
+                        int applied_pos,
+                        struct image *preimage,
+                        struct image *postimage)
+{
+       /*
+        * remove the copy of preimage at offset in img
+        * and replace it with postimage
+        */
+       int i, nr;
+       size_t remove_count, insert_count, applied_at = 0;
+       char *result;
+       int preimage_limit;
+
+       /*
+        * If we are removing blank lines at the end of img,
+        * the preimage may extend beyond the end.
+        * If that is the case, we must be careful only to
+        * remove the part of the preimage that falls within
+        * the boundaries of img. Initialize preimage_limit
+        * to the number of lines in the preimage that falls
+        * within the boundaries.
+        */
+       preimage_limit = preimage->nr;
+       if (preimage_limit > img->nr - applied_pos)
+               preimage_limit = img->nr - applied_pos;
+
+       for (i = 0; i < applied_pos; i++)
+               applied_at += img->line[i].len;
+
+       remove_count = 0;
+       for (i = 0; i < preimage_limit; i++)
+               remove_count += img->line[applied_pos + i].len;
+       insert_count = postimage->len;
+
+       /* Adjust the contents */
+       result = xmalloc(img->len + insert_count - remove_count + 1);
+       memcpy(result, img->buf, applied_at);
+       memcpy(result + applied_at, postimage->buf, postimage->len);
+       memcpy(result + applied_at + postimage->len,
+              img->buf + (applied_at + remove_count),
+              img->len - (applied_at + remove_count));
+       free(img->buf);
+       img->buf = result;
+       img->len += insert_count - remove_count;
+       result[img->len] = '\0';
+
+       /* Adjust the line table */
+       nr = img->nr + postimage->nr - preimage_limit;
+       if (preimage_limit < postimage->nr) {
+               /*
+                * NOTE: this knows that we never call remove_first_line()
+                * on anything other than pre/post image.
+                */
+               img->line = xrealloc(img->line, nr * sizeof(*img->line));
+               img->line_allocated = img->line;
+       }
+       if (preimage_limit != postimage->nr)
+               memmove(img->line + applied_pos + postimage->nr,
+                       img->line + applied_pos + preimage_limit,
+                       (img->nr - (applied_pos + preimage_limit)) *
+                       sizeof(*img->line));
+       memcpy(img->line + applied_pos,
+              postimage->line,
+              postimage->nr * sizeof(*img->line));
+       img->nr = nr;
+}
+
+static int apply_one_fragment(struct image *img, struct fragment *frag,
+                             int inaccurate_eof, unsigned ws_rule)
+{
+       int match_beginning, match_end;
+       const char *patch = frag->patch;
+       int size = frag->size;
+       char *old, *oldlines;
+       struct strbuf newlines;
+       int new_blank_lines_at_end = 0;
+       unsigned long leading, trailing;
+       int pos, applied_pos;
+       struct image preimage;
+       struct image postimage;
+
+       memset(&preimage, 0, sizeof(preimage));
+       memset(&postimage, 0, sizeof(postimage));
+       oldlines = xmalloc(size);
+       strbuf_init(&newlines, size);
+
+       old = oldlines;
+       while (size > 0) {
+               char first;
+               int len = linelen(patch, size);
+               int plen;
+               int added_blank_line = 0;
+               int is_blank_context = 0;
+               size_t start;
+
+               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--;
+               first = *patch;
+               if (apply_in_reverse) {
+                       if (first == '-')
+                               first = '+';
+                       else if (first == '+')
+                               first = '-';
+               }
+
+               switch (first) {
+               case '\n':
+                       /* Newer GNU diff, empty context line */
+                       if (plen < 0)
+                               /* ... followed by '\No newline'; nothing */
+                               break;
+                       *old++ = '\n';
+                       strbuf_addch(&newlines, '\n');
+                       add_line_info(&preimage, "\n", 1, LINE_COMMON);
+                       add_line_info(&postimage, "\n", 1, LINE_COMMON);
+                       is_blank_context = 1;
+                       break;
+               case ' ':
+                       if (plen && (ws_rule & WS_BLANK_AT_EOF) &&
+                           ws_blank_line(patch + 1, plen, ws_rule))
+                               is_blank_context = 1;
+               case '-':
+                       memcpy(old, patch + 1, plen);
+                       add_line_info(&preimage, old, plen,
+                                     (first == ' ' ? LINE_COMMON : 0));
+                       old += plen;
+                       if (first == '-')
+                               break;
+               /* Fall-through for ' ' */
+               case '+':
+                       /* --no-add does not add new lines */
+                       if (first == '+' && no_add)
+                               break;
+
+                       start = newlines.len;
+                       if (first != '+' ||
+                           !whitespace_error ||
+                           ws_error_action != correct_ws_error) {
+                               strbuf_add(&newlines, patch + 1, plen);
+                       }
+                       else {
+                               ws_fix_copy(&newlines, patch + 1, plen, ws_rule, &applied_after_fixing_ws);
+                       }
+                       add_line_info(&postimage, newlines.buf + start, newlines.len - start,
+                                     (first == '+' ? 0 : LINE_COMMON));
+                       if (first == '+' &&
+                           (ws_rule & WS_BLANK_AT_EOF) &&
+                           ws_blank_line(patch + 1, plen, ws_rule))
+                               added_blank_line = 1;
+                       break;
+               case '@': case '\\':
+                       /* Ignore it, we already handled it */
+                       break;
+               default:
+                       if (apply_verbosely)
+                               error("invalid start of line: '%c'", first);
+                       return -1;
+               }
+               if (added_blank_line)
+                       new_blank_lines_at_end++;
+               else if (is_blank_context)
+                       ;
+               else
+                       new_blank_lines_at_end = 0;
+               patch += len;
+               size -= len;
+       }
+       if (inaccurate_eof &&
+           old > oldlines && old[-1] == '\n' &&
+           newlines.len > 0 && newlines.buf[newlines.len - 1] == '\n') {
+               old--;
+               strbuf_setlen(&newlines, newlines.len - 1);
+       }
+
+       leading = frag->leading;
+       trailing = frag->trailing;
+
+       /*
+        * A hunk to change lines at the beginning would begin with
+        * @@ -1,L +N,M @@
+        * but we need to be careful.  -U0 that inserts before the second
+        * line also has this pattern.
+        *
+        * And a hunk to add to an empty file would begin with
+        * @@ -0,0 +N,M @@
+        *
+        * In other words, a hunk that is (frag->oldpos <= 1) with or
+        * without leading context must match at the beginning.
+        */
+       match_beginning = (!frag->oldpos ||
+                          (frag->oldpos == 1 && !unidiff_zero));
+
+       /*
+        * A hunk without trailing lines must match at the end.
+        * However, we simply cannot tell if a hunk must match end
+        * from the lack of trailing lines if the patch was generated
+        * with unidiff without any context.
+        */
+       match_end = !unidiff_zero && !trailing;
+
+       pos = frag->newpos ? (frag->newpos - 1) : 0;
+       preimage.buf = oldlines;
+       preimage.len = old - oldlines;
+       postimage.buf = newlines.buf;
+       postimage.len = newlines.len;
+       preimage.line = preimage.line_allocated;
+       postimage.line = postimage.line_allocated;
+
+       for (;;) {
+
+               applied_pos = find_pos(img, &preimage, &postimage, pos,
+                                      ws_rule, match_beginning, match_end);
+
+               if (applied_pos >= 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(&preimage);
+                       remove_first_line(&postimage);
+                       pos--;
+                       leading--;
+               }
+               if (trailing > leading) {
+                       remove_last_line(&preimage);
+                       remove_last_line(&postimage);
+                       trailing--;
+               }
+       }
+
+       if (applied_pos >= 0) {
+               if (new_blank_lines_at_end &&
+                   preimage.nr + applied_pos >= img->nr &&
+                   (ws_rule & WS_BLANK_AT_EOF) &&
+                   ws_error_action != nowarn_ws_error) {
+                       record_ws_error(WS_BLANK_AT_EOF, "+", 1, frag->linenr);
+                       if (ws_error_action == correct_ws_error) {
+                               while (new_blank_lines_at_end--)
+                                       remove_last_line(&postimage);
+                       }
+                       /*
+                        * We would want to prevent write_out_results()
+                        * from taking place in apply_patch() that follows
+                        * the callchain led us here, which is:
+                        * apply_patch->check_patch_list->check_patch->
+                        * apply_data->apply_fragments->apply_one_fragment
+                        */
+                       if (ws_error_action == die_on_ws_error)
+                               apply = 0;
+               }
+
+               /*
+                * 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, applied_pos+1);
+               update_image(img, applied_pos, &preimage, &postimage);
+       } else {
+               if (apply_verbosely)
+                       error("while searching for:\n%.*s",
+                             (int)(old - oldlines), oldlines);
+       }
+
+       free(oldlines);
+       strbuf_release(&newlines);
+       free(preimage.line_allocated);
+       free(postimage.line_allocated);
+
+       return (applied_pos < 0);
+}
+
+static int apply_binary_fragment(struct image *img, struct patch *patch)
+{
+       struct fragment *fragment = patch->fragments;
+       unsigned long len;
+       void *dst;
+
+       /* Binary patch is irreversible without the optional second hunk */
+       if (apply_in_reverse) {
+               if (!fragment->next)
+                       return error("cannot reverse-apply a binary patch "
+                                    "without the reverse hunk to '%s'",
+                                    patch->new_name
+                                    ? patch->new_name : patch->old_name);
+               fragment = fragment->next;
+       }
+       switch (fragment->binary_patch_method) {
+       case BINARY_DELTA_DEFLATED:
+               dst = patch_delta(img->buf, img->len, fragment->patch,
+                                 fragment->size, &len);
+               if (!dst)
+                       return -1;
+               clear_image(img);
+               img->buf = dst;
+               img->len = len;
+               return 0;
+       case BINARY_LITERAL_DEFLATED:
+               clear_image(img);
+               img->len = fragment->size;
+               img->buf = xmalloc(img->len+1);
+               memcpy(img->buf, fragment->patch, img->len);
+               img->buf[img->len] = '\0';
+               return 0;
+       }
+       return -1;
+}
+
+static int apply_binary(struct image *img, struct patch *patch)
+{
+       const char *name = patch->old_name ? patch->old_name : patch->new_name;
+       unsigned char sha1[20];
+
+       /*
+        * 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.
+                */
+               hash_sha1_file(img->buf, img->len, blob_type, sha1);
+               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 (img->len)
+                       return error("the patch applies to an empty "
+                                    "'%s' but it is not empty", name);
+       }
+
+       get_sha1_hex(patch->new_sha1_prefix, sha1);
+       if (is_null_sha1(sha1)) {
+               clear_image(img);
+               return 0; /* deletion patch */
+       }
+
+       if (has_sha1_file(sha1)) {
+               /* We already have the postimage */
+               enum object_type type;
+               unsigned long size;
+               char *result;
+
+               result = read_sha1_file(sha1, &type, &size);
+               if (!result)
+                       return error("the necessary postimage %s for "
+                                    "'%s' cannot be read",
+                                    patch->new_sha1_prefix, name);
+               clear_image(img);
+               img->buf = result;
+               img->len = size;
+       } else {
+               /*
+                * We have verified buf matches the preimage;
+                * apply the patch data to it, which is stored
+                * in the patch->fragments->{patch,size}.
+                */
+               if (apply_binary_fragment(img, patch))
+                       return error("binary patch does not apply to '%s'",
+                                    name);
+
+               /* verify that the result matches */
+               hash_sha1_file(img->buf, img->len, blob_type, sha1);
+               if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix))
+                       return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)",
+                               name, patch->new_sha1_prefix, sha1_to_hex(sha1));
+       }
+
+       return 0;
+}
+
+static int apply_fragments(struct image *img, struct patch *patch)
+{
+       struct fragment *frag = patch->fragments;
+       const char *name = patch->old_name ? patch->old_name : patch->new_name;
+       unsigned ws_rule = patch->ws_rule;
+       unsigned inaccurate_eof = patch->inaccurate_eof;
+
+       if (patch->is_binary)
+               return apply_binary(img, patch);
+
+       while (frag) {
+               if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule)) {
+                       error("patch failed: %s:%ld", name, frag->oldpos);
+                       if (!apply_with_reject)
+                               return -1;
+                       frag->rejected = 1;
+               }
+               frag = frag->next;
+       }
+       return 0;
+}
+
+static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
+{
+       if (!ce)
+               return 0;
+
+       if (S_ISGITLINK(ce->ce_mode)) {
+               strbuf_grow(buf, 100);
+               strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(ce->sha1));
+       } else {
+               enum object_type type;
+               unsigned long sz;
+               char *result;
+
+               result = read_sha1_file(ce->sha1, &type, &sz);
+               if (!result)
+                       return -1;
+               /* XXX read_sha1_file NUL-terminates */
+               strbuf_attach(buf, result, sz, sz + 1);
+       }
+       return 0;
+}
+
+static struct patch *in_fn_table(const char *name)
+{
+       struct string_list_item *item;
+
+       if (name == NULL)
+               return NULL;
+
+       item = string_list_lookup(&fn_table, name);
+       if (item != NULL)
+               return (struct patch *)item->util;
+
+       return NULL;
+}
+
+/*
+ * item->util in the filename table records the status of the path.
+ * Usually it points at a patch (whose result records the contents
+ * of it after applying it), but it could be PATH_WAS_DELETED for a
+ * path that a previously applied patch has already removed.
+ */
+ #define PATH_TO_BE_DELETED ((struct patch *) -2)
+#define PATH_WAS_DELETED ((struct patch *) -1)
+
+static int to_be_deleted(struct patch *patch)
+{
+       return patch == PATH_TO_BE_DELETED;
+}
+
+static int was_deleted(struct patch *patch)
+{
+       return patch == PATH_WAS_DELETED;
+}
+
+static void add_to_fn_table(struct patch *patch)
+{
+       struct string_list_item *item;
+
+       /*
+        * Always add new_name unless patch is a deletion
+        * This should cover the cases for normal diffs,
+        * file creations and copies
+        */
+       if (patch->new_name != NULL) {
+               item = string_list_insert(&fn_table, patch->new_name);
+               item->util = patch;
+       }
+
+       /*
+        * store a failure on rename/deletion cases because
+        * later chunks shouldn't patch old names
+        */
+       if ((patch->new_name == NULL) || (patch->is_rename)) {
+               item = string_list_insert(&fn_table, patch->old_name);
+               item->util = PATH_WAS_DELETED;
+       }
+}
+
+static void prepare_fn_table(struct patch *patch)
+{
+       /*
+        * store information about incoming file deletion
+        */
+       while (patch) {
+               if ((patch->new_name == NULL) || (patch->is_rename)) {
+                       struct string_list_item *item;
+                       item = string_list_insert(&fn_table, patch->old_name);
+                       item->util = PATH_TO_BE_DELETED;
+               }
+               patch = patch->next;
+       }
+}
+
+static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce)
+{
+       struct strbuf buf = STRBUF_INIT;
+       struct image image;
+       size_t len;
+       char *img;
+       struct patch *tpatch;
+
+       if (!(patch->is_copy || patch->is_rename) &&
+           (tpatch = in_fn_table(patch->old_name)) != NULL && !to_be_deleted(tpatch)) {
+               if (was_deleted(tpatch)) {
+                       return error("patch %s has been renamed/deleted",
+                               patch->old_name);
+               }
+               /* We have a patched copy in memory use that */
+               strbuf_add(&buf, tpatch->result, tpatch->resultsize);
+       } else if (cached) {
+               if (read_file_or_gitlink(ce, &buf))
+                       return error("read of %s failed", patch->old_name);
+       } else if (patch->old_name) {
+               if (S_ISGITLINK(patch->old_mode)) {
+                       if (ce) {
+                               read_file_or_gitlink(ce, &buf);
+                       } else {
+                               /*
+                                * There is no way to apply subproject
+                                * patch without looking at the index.
+                                */
+                               patch->fragments = NULL;
+                       }
+               } else {
+                       if (read_old_data(st, patch->old_name, &buf))
+                               return error("read of %s failed", patch->old_name);
+               }
+       }
+
+       img = strbuf_detach(&buf, &len);
+       prepare_image(&image, img, len, !patch->is_binary);
+
+       if (apply_fragments(&image, patch) < 0)
+               return -1; /* note with --reject this succeeds. */
+       patch->result = image.buf;
+       patch->resultsize = image.len;
+       add_to_fn_table(patch);
+       free(image.line_allocated);
+
+       if (0 < patch->is_delete && patch->resultsize)
+               return error("removal patch leaves file contents");
+
+       return 0;
+}
+
+static int check_to_create_blob(const char *new_name, int ok_if_exists)
+{
+       struct stat nst;
+       if (!lstat(new_name, &nst)) {
+               if (S_ISDIR(nst.st_mode) || ok_if_exists)
+                       return 0;
+               /*
+                * A leading component of new_name might be a symlink
+                * that is going to be removed with this patch, but
+                * still pointing at somewhere that has the path.
+                * In such a case, path "new_name" does not exist as
+                * far as git is concerned.
+                */
+               if (has_symlink_leading_path(new_name, strlen(new_name)))
+                       return 0;
+
+               return error("%s: already exists in working directory", new_name);
+       }
+       else if ((errno != ENOENT) && (errno != ENOTDIR))
+               return error("%s: %s", new_name, strerror(errno));
+       return 0;
+}
+
+static int verify_index_match(struct cache_entry *ce, struct stat *st)
+{
+       if (S_ISGITLINK(ce->ce_mode)) {
+               if (!S_ISDIR(st->st_mode))
+                       return -1;
+               return 0;
+       }
+       return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
+}
+
+static int check_preimage(struct patch *patch, struct cache_entry **ce, struct stat *st)
+{
+       const char *old_name = patch->old_name;
+       struct patch *tpatch = NULL;
+       int stat_ret = 0;
+       unsigned st_mode = 0;
+
+       /*
+        * Make sure that we do not have local modifications from the
+        * index when we are looking at the index.  Also make sure
+        * we have the preimage file to be patched in the work tree,
+        * unless --cached, which tells git to apply only in the index.
+        */
+       if (!old_name)
+               return 0;
+
+       assert(patch->is_new <= 0);
+
+       if (!(patch->is_copy || patch->is_rename) &&
+           (tpatch = in_fn_table(old_name)) != NULL && !to_be_deleted(tpatch)) {
+               if (was_deleted(tpatch))
+                       return error("%s: has been deleted/renamed", old_name);
+               st_mode = tpatch->new_mode;
+       } else if (!cached) {
+               stat_ret = lstat(old_name, st);
+               if (stat_ret && errno != ENOENT)
+                       return error("%s: %s", old_name, strerror(errno));
+       }
+
+       if (to_be_deleted(tpatch))
+               tpatch = NULL;
+
+       if (check_index && !tpatch) {
+               int pos = cache_name_pos(old_name, strlen(old_name));
+               if (pos < 0) {
+                       if (patch->is_new < 0)
+                               goto is_new;
+                       return error("%s: does not exist in index", old_name);
+               }
+               *ce = active_cache[pos];
+               if (stat_ret < 0) {
+                       struct checkout costate;
+                       /* checkout */
+                       memset(&costate, 0, sizeof(costate));
+                       costate.base_dir = "";
+                       costate.refresh_cache = 1;
+                       if (checkout_entry(*ce, &costate, NULL) ||
+                           lstat(old_name, st))
+                               return -1;
+               }
+               if (!cached && verify_index_match(*ce, st))
+                       return error("%s: does not match index", old_name);
+               if (cached)
+                       st_mode = (*ce)->ce_mode;
+       } else if (stat_ret < 0) {
+               if (patch->is_new < 0)
+                       goto is_new;
+               return error("%s: %s", old_name, strerror(errno));
+       }
+
+       if (!cached && !tpatch)
+               st_mode = ce_mode_from_stat(*ce, 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)
+               warning("%s has type %o, expected %o",
+                       old_name, st_mode, patch->old_mode);
+       if (!patch->new_mode && !patch->is_delete)
+               patch->new_mode = st_mode;
+       return 0;
+
+ is_new:
+       patch->is_new = 1;
+       patch->is_delete = 0;
+       patch->old_name = NULL;
+       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;
+       struct patch *tpatch;
+       int ok_if_exists;
+       int status;
+
+       patch->rejected = 1; /* we will drop this after we succeed */
+
+       status = check_preimage(patch, &ce, &st);
+       if (status)
+               return status;
+       old_name = patch->old_name;
+
+       if ((tpatch = in_fn_table(new_name)) &&
+                       (was_deleted(tpatch) || to_be_deleted(tpatch)))
+               /*
+                * A type-change diff is always split into a patch to
+                * delete old, immediately followed by a patch to
+                * create new (see diff.c::run_diff()); in such a case
+                * it is Ok that the entry to be deleted by the
+                * previous patch is still in the working tree and in
+                * the index.
+                */
+               ok_if_exists = 1;
+       else
+               ok_if_exists = 0;
+
+       if (new_name &&
+           ((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) {
+               if (check_index &&
+                   cache_name_pos(new_name, strlen(new_name)) >= 0 &&
+                   !ok_if_exists)
+                       return error("%s: already exists in index", new_name);
+               if (!cached) {
+                       int err = check_to_create_blob(new_name, ok_if_exists);
+                       if (err)
+                               return err;
+               }
+               if (!patch->new_mode) {
+                       if (0 < 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);
+       patch->rejected = 0;
+       return 0;
+}
+
+static int check_patch_list(struct patch *patch)
+{
+       int err = 0;
+
+       prepare_fn_table(patch);
+       while (patch) {
+               if (apply_verbosely)
+                       say_patch_name(stderr,
+                                      "Checking patch ", patch, "...\n");
+               err |= check_patch(patch);
+               patch = patch->next;
+       }
+       return err;
+}
+
+/* This function tries to read the sha1 from the current index */
+static int get_current_sha1(const char *path, unsigned char *sha1)
+{
+       int pos;
+
+       if (read_cache() < 0)
+               return -1;
+       pos = cache_name_pos(path, strlen(path));
+       if (pos < 0)
+               return -1;
+       hashcpy(sha1, active_cache[pos]->sha1);
+       return 0;
+}
+
+/* Build an index that contains the just the files needed for a 3way merge */
+static void build_fake_ancestor(struct patch *list, const char *filename)
+{
+       struct patch *patch;
+       struct index_state result = { NULL };
+       int fd;
+
+       /* 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];
+               struct cache_entry *ce;
+               const char *name;
+
+               name = patch->old_name ? patch->old_name : patch->new_name;
+               if (0 < patch->is_new)
+                       continue;
+               else if (get_sha1(patch->old_sha1_prefix, sha1))
+                       /* git diff has no index line for mode/type changes */
+                       if (!patch->lines_added && !patch->lines_deleted) {
+                               if (get_current_sha1(patch->old_name, sha1))
+                                       die("mode change for %s, which is not "
+                                               "in current HEAD", name);
+                               sha1_ptr = sha1;
+                       } else
+                               die("sha1 information is lacking or useless "
+                                       "(%s).", name);
+               else
+                       sha1_ptr = sha1;
+
+               ce = make_cache_entry(patch->old_mode, sha1_ptr, name, 0, 0);
+               if (!ce)
+                       die("make_cache_entry failed for path '%s'", name);
+               if (add_index_entry(&result, ce, ADD_CACHE_OK_TO_ADD))
+                       die ("Could not add %s to temporary index", name);
+       }
+
+       fd = open(filename, O_WRONLY | O_CREAT, 0666);
+       if (fd < 0 || write_index(&result, fd) || close(fd))
+               die ("Could not write temporary index to %s", filename);
+
+       discard_index(&result);
+}
+
+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;
+               if (patch->is_binary)
+                       printf("-\t-\t");
+               else
+                       printf("%d\t%d\t", patch->lines_added, patch->lines_deleted);
+               write_name_quoted(name, stdout, line_termination);
+       }
+}
+
+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, int rmdir_empty)
+{
+       if (update_index) {
+               if (remove_file_from_cache(patch->old_name) < 0)
+                       die("unable to remove %s from index", patch->old_name);
+       }
+       if (!cached) {
+               if (!remove_or_warn(patch->old_mode, patch->old_name) && rmdir_empty) {
+                       remove_path(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 (!update_index)
+               return;
+
+       ce = xcalloc(1, ce_size);
+       memcpy(ce->name, path, namelen);
+       ce->ce_mode = create_ce_mode(mode);
+       ce->ce_flags = namelen;
+       if (S_ISGITLINK(mode)) {
+               const char *s = buf;
+
+               if (get_sha1_hex(s + strlen("Subproject commit "), ce->sha1))
+                       die("corrupt patch for subproject %s", path);
+       } else {
+               if (!cached) {
+                       if (lstat(path, &st) < 0)
+                               die_errno("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;
+       struct strbuf nbuf = STRBUF_INIT;
+
+       if (S_ISGITLINK(mode)) {
+               struct stat st;
+               if (!lstat(path, &st) && S_ISDIR(st.st_mode))
+                       return 0;
+               return mkdir(path, 0777);
+       }
+
+       if (has_symlinks && S_ISLNK(mode))
+               /* Although buf:size is counted string, it also is NUL
+                * terminated.
+                */
+               return symlink(buf, path);
+
+       fd = open(path, O_CREAT | O_EXCL | O_WRONLY, (mode & 0100) ? 0777 : 0666);
+       if (fd < 0)
+               return -1;
+
+       if (convert_to_working_tree(path, buf, size, &nbuf)) {
+               size = nbuf.len;
+               buf  = nbuf.buf;
+       }
+       write_or_die(fd, buf, size);
+       strbuf_release(&nbuf);
+
+       if (close(fd) < 0)
+               die_errno("closing file '%s'", path);
+       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 || errno == EACCES) {
+               /* We may be trying to create a file where a directory
+                * used to be.
+                */
+               struct stat st;
+               if (!lstat(path, &st) && (!S_ISDIR(st.st_mode) || !rmdir(path)))
+                       errno = EEXIST;
+       }
+
+       if (errno == EEXIST) {
+               unsigned int nr = getpid();
+
+               for (;;) {
+                       char newpath[PATH_MAX];
+                       mksnpath(newpath, sizeof(newpath), "%s~%u", path, nr);
+                       if (!try_create_file(newpath, mode, buf, size)) {
+                               if (!rename(newpath, path))
+                                       return;
+                               unlink_or_warn(newpath);
+                               break;
+                       }
+                       if (errno != EEXIST)
+                               break;
+                       ++nr;
+               }
+       }
+       die_errno("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);
+}
+
+/* phase zero is to remove, phase one is to create */
+static void write_out_one_result(struct patch *patch, int phase)
+{
+       if (patch->is_delete > 0) {
+               if (phase == 0)
+                       remove_file(patch, 1);
+               return;
+       }
+       if (patch->is_new > 0 || patch->is_copy) {
+               if (phase == 1)
+                       create_file(patch);
+               return;
+       }
+       /*
+        * Rename or modification boils down to the same
+        * thing: remove the old, write the new
+        */
+       if (phase == 0)
+               remove_file(patch, patch->is_rename);
+       if (phase == 1)
+               create_file(patch);
+}
+
+static int write_out_one_reject(struct patch *patch)
+{
+       FILE *rej;
+       char namebuf[PATH_MAX];
+       struct fragment *frag;
+       int cnt = 0;
+
+       for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) {
+               if (!frag->rejected)
+                       continue;
+               cnt++;
+       }
+
+       if (!cnt) {
+               if (apply_verbosely)
+                       say_patch_name(stderr,
+                                      "Applied patch ", patch, " cleanly.\n");
+               return 0;
+       }
+
+       /* This should not happen, because a removal patch that leaves
+        * contents are marked "rejected" at the patch level.
+        */
+       if (!patch->new_name)
+               die("internal error");
+
+       /* Say this even without --verbose */
+       say_patch_name(stderr, "Applying patch ", patch, " with");
+       fprintf(stderr, " %d rejects...\n", cnt);
+
+       cnt = strlen(patch->new_name);
+       if (ARRAY_SIZE(namebuf) <= cnt + 5) {
+               cnt = ARRAY_SIZE(namebuf) - 5;
+               warning("truncating .rej filename to %.*s.rej",
+                       cnt - 1, patch->new_name);
+       }
+       memcpy(namebuf, patch->new_name, cnt);
+       memcpy(namebuf + cnt, ".rej", 5);
+
+       rej = fopen(namebuf, "w");
+       if (!rej)
+               return error("cannot open %s: %s", namebuf, strerror(errno));
+
+       /* Normal git tools never deal with .rej, so do not pretend
+        * this is a git patch by saying --git nor give extended
+        * headers.  While at it, maybe please "kompare" that wants
+        * the trailing TAB and some garbage at the end of line ;-).
+        */
+       fprintf(rej, "diff a/%s b/%s\t(rejected hunks)\n",
+               patch->new_name, patch->new_name);
+       for (cnt = 1, frag = patch->fragments;
+            frag;
+            cnt++, frag = frag->next) {
+               if (!frag->rejected) {
+                       fprintf(stderr, "Hunk #%d applied cleanly.\n", cnt);
+                       continue;
+               }
+               fprintf(stderr, "Rejected hunk #%d.\n", cnt);
+               fprintf(rej, "%.*s", frag->size, frag->patch);
+               if (frag->patch[frag->size-1] != '\n')
+                       fputc('\n', rej);
+       }
+       fclose(rej);
+       return -1;
+}
+
+static int write_out_results(struct patch *list, int skipped_patch)
+{
+       int phase;
+       int errs = 0;
+       struct patch *l;
+
+       if (!list && !skipped_patch)
+               return error("No changes");
+
+       for (phase = 0; phase < 2; phase++) {
+               l = list;
+               while (l) {
+                       if (l->rejected)
+                               errs = 1;
+                       else {
+                               write_out_one_result(l, phase);
+                               if (phase == 1 && write_out_one_reject(l))
+                                       errs = 1;
+                       }
+                       l = l->next;
+               }
+       }
+       return errs;
+}
+
+static struct lock_file lock_file;
+
+static struct string_list limit_by_name;
+static int has_include;
+static void add_name_limit(const char *name, int exclude)
+{
+       struct string_list_item *it;
+
+       it = string_list_append(&limit_by_name, name);
+       it->util = exclude ? NULL : (void *) 1;
+}
+
+static int use_patch(struct patch *p)
+{
+       const char *pathname = p->new_name ? p->new_name : p->old_name;
+       int i;
+
+       /* Paths outside are not touched regardless of "--include" */
+       if (0 < prefix_length) {
+               int pathlen = strlen(pathname);
+               if (pathlen <= prefix_length ||
+                   memcmp(prefix, pathname, prefix_length))
+                       return 0;
+       }
+
+       /* See if it matches any of exclude/include rule */
+       for (i = 0; i < limit_by_name.nr; i++) {
+               struct string_list_item *it = &limit_by_name.items[i];
+               if (!fnmatch(it->string, pathname, 0))
+                       return (it->util != NULL);
+       }
+
+       /*
+        * If we had any include, a path that does not match any rule is
+        * not used.  Otherwise, we saw bunch of exclude rules (or none)
+        * and such a path is used.
+        */
+       return !has_include;
+}
+
+
+static void prefix_one(char **name)
+{
+       char *old_name = *name;
+       if (!old_name)
+               return;
+       *name = xstrdup(prefix_filename(prefix, prefix_length, *name));
+       free(old_name);
+}
+
+static void prefix_patches(struct patch *p)
+{
+       if (!prefix || p->is_toplevel_relative)
+               return;
+       for ( ; p; p = p->next) {
+               if (p->new_name == p->old_name) {
+                       char *prefixed = p->new_name;
+                       prefix_one(&prefixed);
+                       p->new_name = p->old_name = prefixed;
+               }
+               else {
+                       prefix_one(&p->new_name);
+                       prefix_one(&p->old_name);
+               }
+       }
+}
+
+#define INACCURATE_EOF (1<<0)
+#define RECOUNT                (1<<1)
+
+static int apply_patch(int fd, const char *filename, int options)
+{
+       size_t offset;
+       struct strbuf buf = STRBUF_INIT;
+       struct patch *list = NULL, **listp = &list;
+       int skipped_patch = 0;
+
+       /* FIXME - memory leak when using multiple patch files as inputs */
+       memset(&fn_table, 0, sizeof(struct string_list));
+       patch_input_file = filename;
+       read_patch_file(&buf, fd);
+       offset = 0;
+       while (offset < buf.len) {
+               struct patch *patch;
+               int nr;
+
+               patch = xcalloc(1, sizeof(*patch));
+               patch->inaccurate_eof = !!(options & INACCURATE_EOF);
+               patch->recount =  !!(options & RECOUNT);
+               nr = parse_chunk(buf.buf + offset, buf.len - offset, patch);
+               if (nr < 0)
+                       break;
+               if (apply_in_reverse)
+                       reverse_patches(patch);
+               if (prefix)
+                       prefix_patches(patch);
+               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;
+       }
+
+       if (whitespace_error && (ws_error_action == die_on_ws_error))
+               apply = 0;
+
+       update_index = check_index && apply;
+       if (update_index && newfd < 0)
+               newfd = hold_locked_index(&lock_file, 1);
+
+       if (check_index) {
+               if (read_cache() < 0)
+                       die("unable to read index file");
+       }
+
+       if ((check || apply) &&
+           check_patch_list(list) < 0 &&
+           !apply_with_reject)
+               exit(1);
+
+       if (apply && write_out_results(list, skipped_patch))
+               exit(1);
+
+       if (fake_ancestor)
+               build_fake_ancestor(list, fake_ancestor);
+
+       if (diffstat)
+               stat_patch_list(list);
+
+       if (numstat)
+               numstat_patch_list(list);
+
+       if (summary)
+               summary_patch_list(list);
+
+       strbuf_release(&buf);
+       return 0;
+}
+
+static int git_apply_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "apply.whitespace"))
+               return git_config_string(&apply_default_whitespace, var, value);
+       else if (!strcmp(var, "apply.ignorewhitespace"))
+               return git_config_string(&apply_default_ignorewhitespace, var, value);
+       return git_default_config(var, value, cb);
+}
+
+static int option_parse_exclude(const struct option *opt,
+                               const char *arg, int unset)
+{
+       add_name_limit(arg, 1);
+       return 0;
+}
+
+static int option_parse_include(const struct option *opt,
+                               const char *arg, int unset)
+{
+       add_name_limit(arg, 0);
+       has_include = 1;
+       return 0;
+}
+
+static int option_parse_p(const struct option *opt,
+                         const char *arg, int unset)
+{
+       p_value = atoi(arg);
+       p_value_known = 1;
+       return 0;
+}
+
+static int option_parse_z(const struct option *opt,
+                         const char *arg, int unset)
+{
+       if (unset)
+               line_termination = '\n';
+       else
+               line_termination = 0;
+       return 0;
+}
+
+static int option_parse_space_change(const struct option *opt,
+                         const char *arg, int unset)
+{
+       if (unset)
+               ws_ignore_action = ignore_ws_none;
+       else
+               ws_ignore_action = ignore_ws_change;
+       return 0;
+}
+
+static int option_parse_whitespace(const struct option *opt,
+                                  const char *arg, int unset)
+{
+       const char **whitespace_option = opt->value;
+
+       *whitespace_option = arg;
+       parse_whitespace_option(arg);
+       return 0;
+}
+
+static int option_parse_directory(const struct option *opt,
+                                 const char *arg, int unset)
+{
+       root_len = strlen(arg);
+       if (root_len && arg[root_len - 1] != '/') {
+               char *new_root;
+               root = new_root = xmalloc(root_len + 2);
+               strcpy(new_root, arg);
+               strcpy(new_root + root_len++, "/");
+       } else
+               root = arg;
+       return 0;
+}
+
+int cmd_apply(int argc, const char **argv, const char *prefix_)
+{
+       int i;
+       int errs = 0;
+       int is_not_gitdir = !startup_info->have_repository;
+       int binary;
+       int force_apply = 0;
+
+       const char *whitespace_option = NULL;
+
+       struct option builtin_apply_options[] = {
+               { OPTION_CALLBACK, 0, "exclude", NULL, "path",
+                       "don't apply changes matching the given path",
+                       0, option_parse_exclude },
+               { OPTION_CALLBACK, 0, "include", NULL, "path",
+                       "apply changes matching the given path",
+                       0, option_parse_include },
+               { OPTION_CALLBACK, 'p', NULL, NULL, "num",
+                       "remove <num> leading slashes from traditional diff paths",
+                       0, option_parse_p },
+               OPT_BOOLEAN(0, "no-add", &no_add,
+                       "ignore additions made by the patch"),
+               OPT_BOOLEAN(0, "stat", &diffstat,
+                       "instead of applying the patch, output diffstat for the input"),
+               { OPTION_BOOLEAN, 0, "allow-binary-replacement", &binary,
+                 NULL, "old option, now no-op",
+                 PARSE_OPT_HIDDEN | PARSE_OPT_NOARG },
+               { OPTION_BOOLEAN, 0, "binary", &binary,
+                 NULL, "old option, now no-op",
+                 PARSE_OPT_HIDDEN | PARSE_OPT_NOARG },
+               OPT_BOOLEAN(0, "numstat", &numstat,
+                       "shows number of added and deleted lines in decimal notation"),
+               OPT_BOOLEAN(0, "summary", &summary,
+                       "instead of applying the patch, output a summary for the input"),
+               OPT_BOOLEAN(0, "check", &check,
+                       "instead of applying the patch, see if the patch is applicable"),
+               OPT_BOOLEAN(0, "index", &check_index,
+                       "make sure the patch is applicable to the current index"),
+               OPT_BOOLEAN(0, "cached", &cached,
+                       "apply a patch without touching the working tree"),
+               OPT_BOOLEAN(0, "apply", &force_apply,
+                       "also apply the patch (use with --stat/--summary/--check)"),
+               OPT_FILENAME(0, "build-fake-ancestor", &fake_ancestor,
+                       "build a temporary index based on embedded index information"),
+               { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
+                       "paths are separated with NUL character",
+                       PARSE_OPT_NOARG, option_parse_z },
+               OPT_INTEGER('C', NULL, &p_context,
+                               "ensure at least <n> lines of context match"),
+               { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, "action",
+                       "detect new or modified lines that have whitespace errors",
+                       0, option_parse_whitespace },
+               { OPTION_CALLBACK, 0, "ignore-space-change", NULL, NULL,
+                       "ignore changes in whitespace when finding context",
+                       PARSE_OPT_NOARG, option_parse_space_change },
+               { OPTION_CALLBACK, 0, "ignore-whitespace", NULL, NULL,
+                       "ignore changes in whitespace when finding context",
+                       PARSE_OPT_NOARG, option_parse_space_change },
+               OPT_BOOLEAN('R', "reverse", &apply_in_reverse,
+                       "apply the patch in reverse"),
+               OPT_BOOLEAN(0, "unidiff-zero", &unidiff_zero,
+                       "don't expect at least one line of context"),
+               OPT_BOOLEAN(0, "reject", &apply_with_reject,
+                       "leave the rejected hunks in corresponding *.rej files"),
+               OPT__VERBOSE(&apply_verbosely),
+               OPT_BIT(0, "inaccurate-eof", &options,
+                       "tolerate incorrectly detected missing new-line at the end of file",
+                       INACCURATE_EOF),
+               OPT_BIT(0, "recount", &options,
+                       "do not trust the line counts in the hunk headers",
+                       RECOUNT),
+               { OPTION_CALLBACK, 0, "directory", NULL, "root",
+                       "prepend <root> to all filenames",
+                       0, option_parse_directory },
+               OPT_END()
+       };
+
+       prefix = prefix_;
+       prefix_length = prefix ? strlen(prefix) : 0;
+       git_config(git_apply_config, NULL);
+       if (apply_default_whitespace)
+               parse_whitespace_option(apply_default_whitespace);
+       if (apply_default_ignorewhitespace)
+               parse_ignorewhitespace_option(apply_default_ignorewhitespace);
+
+       argc = parse_options(argc, argv, prefix, builtin_apply_options,
+                       apply_usage, 0);
+
+       if (apply_with_reject)
+               apply = apply_verbosely = 1;
+       if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor))
+               apply = 0;
+       if (check_index && is_not_gitdir)
+               die("--index outside a repository");
+       if (cached) {
+               if (is_not_gitdir)
+                       die("--cached outside a repository");
+               check_index = 1;
+       }
+       for (i = 0; i < argc; i++) {
+               const char *arg = argv[i];
+               int fd;
+
+               if (!strcmp(arg, "-")) {
+                       errs |= apply_patch(0, "<stdin>", options);
+                       read_stdin = 0;
+                       continue;
+               } else if (0 < prefix_length)
+                       arg = prefix_filename(prefix, prefix_length, arg);
+
+               fd = open(arg, O_RDONLY);
+               if (fd < 0)
+                       die_errno("can't open patch '%s'", arg);
+               read_stdin = 0;
+               set_default_whitespace_mode(whitespace_option);
+               errs |= apply_patch(fd, arg, options);
+               close(fd);
+       }
+       set_default_whitespace_mode(whitespace_option);
+       if (read_stdin)
+               errs |= apply_patch(0, "<stdin>", options);
+       if (whitespace_error) {
+               if (squelch_whitespace_errors &&
+                   squelch_whitespace_errors < whitespace_error) {
+                       int squelched =
+                               whitespace_error - squelch_whitespace_errors;
+                       warning("squelched %d "
+                               "whitespace error%s",
+                               squelched,
+                               squelched == 1 ? "" : "s");
+               }
+               if (ws_error_action == die_on_ws_error)
+                       die("%d line%s add%s whitespace errors.",
+                           whitespace_error,
+                           whitespace_error == 1 ? "" : "s",
+                           whitespace_error == 1 ? "s" : "");
+               if (applied_after_fixing_ws && apply)
+                       warning("%d line%s applied after"
+                               " fixing whitespace errors.",
+                               applied_after_fixing_ws,
+                               applied_after_fixing_ws == 1 ? "" : "s");
+               else if (whitespace_error)
+                       warning("%d line%s add%s whitespace errors.",
+                               whitespace_error,
+                               whitespace_error == 1 ? "" : "s",
+                               whitespace_error == 1 ? "s" : "");
+       }
+
+       if (update_index) {
+               if (write_cache(newfd, active_cache, active_nr) ||
+                   commit_locked_index(&lock_file))
+                       die("Unable to write new index file");
+       }
+
+       return !!errs;
+}
diff --git a/builtin/archive.c b/builtin/archive.c
new file mode 100644 (file)
index 0000000..6a887f5
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2006 Franck Bui-Huu
+ * Copyright (c) 2006 Rene Scharfe
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "archive.h"
+#include "transport.h"
+#include "parse-options.h"
+#include "pkt-line.h"
+#include "sideband.h"
+
+static void create_output_file(const char *output_file)
+{
+       int output_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
+       if (output_fd < 0)
+               die_errno("could not create archive file '%s'", output_file);
+       if (output_fd != 1) {
+               if (dup2(output_fd, 1) < 0)
+                       die_errno("could not redirect output");
+               else
+                       close(output_fd);
+       }
+}
+
+static int run_remote_archiver(int argc, const char **argv,
+                              const char *remote, const char *exec)
+{
+       char buf[LARGE_PACKET_MAX];
+       int fd[2], i, len, rv;
+       struct transport *transport;
+       struct remote *_remote;
+
+       _remote = remote_get(remote);
+       if (!_remote->url[0])
+               die("git archive: Remote with no URL");
+       transport = transport_get(_remote, _remote->url[0]);
+       transport_connect(transport, "git-upload-archive", exec, fd);
+
+       for (i = 1; i < argc; i++)
+               packet_write(fd[1], "argument %s\n", argv[i]);
+       packet_flush(fd[1]);
+
+       len = packet_read_line(fd[0], buf, sizeof(buf));
+       if (!len)
+               die("git archive: expected ACK/NAK, got EOF");
+       if (buf[len-1] == '\n')
+               buf[--len] = 0;
+       if (strcmp(buf, "ACK")) {
+               if (len > 5 && !prefixcmp(buf, "NACK "))
+                       die("git archive: NACK %s", buf + 5);
+               die("git archive: protocol error");
+       }
+
+       len = packet_read_line(fd[0], buf, sizeof(buf));
+       if (len)
+               die("git archive: expected a flush");
+
+       /* Now, start reading from fd[0] and spit it out to stdout */
+       rv = recv_sideband("archive", fd[0], 1);
+       rv |= transport_disconnect(transport);
+
+       return !!rv;
+}
+
+static const char *format_from_name(const char *filename)
+{
+       const char *ext = strrchr(filename, '.');
+       if (!ext)
+               return NULL;
+       ext++;
+       if (!strcasecmp(ext, "zip"))
+               return "--format=zip";
+       return NULL;
+}
+
+#define PARSE_OPT_KEEP_ALL ( PARSE_OPT_KEEP_DASHDASH |         \
+                            PARSE_OPT_KEEP_ARGV0 |     \
+                            PARSE_OPT_KEEP_UNKNOWN |   \
+                            PARSE_OPT_NO_INTERNAL_HELP )
+
+int cmd_archive(int argc, const char **argv, const char *prefix)
+{
+       const char *exec = "git-upload-archive";
+       const char *output = NULL;
+       const char *remote = NULL;
+       const char *format_option = NULL;
+       struct option local_opts[] = {
+               OPT_STRING('o', "output", &output, "file",
+                       "write the archive to this file"),
+               OPT_STRING(0, "remote", &remote, "repo",
+                       "retrieve the archive from remote repository <repo>"),
+               OPT_STRING(0, "exec", &exec, "cmd",
+                       "path to the remote git-upload-archive command"),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, prefix, local_opts, NULL,
+                            PARSE_OPT_KEEP_ALL);
+
+       if (output) {
+               create_output_file(output);
+               format_option = format_from_name(output);
+       }
+
+       /*
+        * We have enough room in argv[] to muck it in place, because
+        * --output must have been given on the original command line
+        * if we get to this point, and parse_options() must have eaten
+        * it, i.e. we can add back one element to the array.
+        *
+        * We add a fake --format option at the beginning, with the
+        * format inferred from our output filename.  This way explicit
+        * --format options can override it, and the fake option is
+        * inserted before any "--" that might have been given.
+        */
+       if (format_option) {
+               memmove(argv + 2, argv + 1, sizeof(*argv) * argc);
+               argv[1] = format_option;
+               argv[++argc] = NULL;
+       }
+
+       if (remote)
+               return run_remote_archiver(argc, argv, remote, exec);
+
+       setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
+
+       return write_archive(argc, argv, prefix, 1);
+}
diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c
new file mode 100644 (file)
index 0000000..5b22639
--- /dev/null
@@ -0,0 +1,28 @@
+#include "builtin.h"
+#include "cache.h"
+#include "parse-options.h"
+#include "bisect.h"
+
+static const char * const git_bisect_helper_usage[] = {
+       "git bisect--helper --next-all",
+       NULL
+};
+
+int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
+{
+       int next_all = 0;
+       struct option options[] = {
+               OPT_BOOLEAN(0, "next-all", &next_all,
+                           "perform 'git bisect next'"),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, prefix, options,
+                            git_bisect_helper_usage, 0);
+
+       if (!next_all)
+               usage_with_options(git_bisect_helper_usage, options);
+
+       /* next-all */
+       return bisect_next_all(prefix);
+}
diff --git a/builtin/blame.c b/builtin/blame.c
new file mode 100644 (file)
index 0000000..1015354
--- /dev/null
@@ -0,0 +1,2530 @@
+/*
+ * Blame
+ *
+ * Copyright (c) 2006, Junio C Hamano
+ */
+
+#include "cache.h"
+#include "builtin.h"
+#include "blob.h"
+#include "commit.h"
+#include "tag.h"
+#include "tree-walk.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "quote.h"
+#include "xdiff-interface.h"
+#include "cache-tree.h"
+#include "string-list.h"
+#include "mailmap.h"
+#include "parse-options.h"
+#include "utf8.h"
+#include "userdiff.h"
+
+static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file";
+
+static const char *blame_opt_usage[] = {
+       blame_usage,
+       "",
+       "[rev-opts] are documented in git-rev-list(1)",
+       NULL
+};
+
+static int longest_file;
+static int longest_author;
+static int max_orig_digits;
+static int max_digits;
+static int max_score_digits;
+static int show_root;
+static int reverse;
+static int blank_boundary;
+static int incremental;
+static int xdl_opts;
+
+static enum date_mode blame_date_mode = DATE_ISO8601;
+static size_t blame_date_width;
+
+static struct string_list mailmap;
+
+#ifndef DEBUG
+#define DEBUG 0
+#endif
+
+/* stats */
+static int num_read_blob;
+static int num_get_patch;
+static int num_commits;
+
+#define PICKAXE_BLAME_MOVE             01
+#define PICKAXE_BLAME_COPY             02
+#define PICKAXE_BLAME_COPY_HARDER      04
+#define PICKAXE_BLAME_COPY_HARDEST     010
+
+/*
+ * blame for a blame_entry with score lower than these thresholds
+ * is not passed to the parent using move/copy logic.
+ */
+static unsigned blame_move_score;
+static unsigned blame_copy_score;
+#define BLAME_DEFAULT_MOVE_SCORE       20
+#define BLAME_DEFAULT_COPY_SCORE       40
+
+/* bits #0..7 in revision.h, #8..11 used for merge_bases() in commit.c */
+#define METAINFO_SHOWN         (1u<<12)
+#define MORE_THAN_ONE_PATH     (1u<<13)
+
+/*
+ * One blob in a commit that is being suspected
+ */
+struct origin {
+       int refcnt;
+       struct origin *previous;
+       struct commit *commit;
+       mmfile_t file;
+       unsigned char blob_sha1[20];
+       char path[FLEX_ARRAY];
+};
+
+/*
+ * Prepare diff_filespec and convert it using diff textconv API
+ * if the textconv driver exists.
+ * Return 1 if the conversion succeeds, 0 otherwise.
+ */
+int textconv_object(const char *path,
+                   const unsigned char *sha1,
+                   char **buf,
+                   unsigned long *buf_size)
+{
+       struct diff_filespec *df;
+       struct userdiff_driver *textconv;
+
+       df = alloc_filespec(path);
+       fill_filespec(df, sha1, S_IFREG | 0664);
+       textconv = get_textconv(df);
+       if (!textconv) {
+               free_filespec(df);
+               return 0;
+       }
+
+       *buf_size = fill_textconv(textconv, df, buf);
+       free_filespec(df);
+       return 1;
+}
+
+/*
+ * Given an origin, prepare mmfile_t structure to be used by the
+ * diff machinery
+ */
+static void fill_origin_blob(struct diff_options *opt,
+                            struct origin *o, mmfile_t *file)
+{
+       if (!o->file.ptr) {
+               enum object_type type;
+               unsigned long file_size;
+
+               num_read_blob++;
+               if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
+                   textconv_object(o->path, o->blob_sha1, &file->ptr, &file_size))
+                       ;
+               else
+                       file->ptr = read_sha1_file(o->blob_sha1, &type, &file_size);
+               file->size = file_size;
+
+               if (!file->ptr)
+                       die("Cannot read blob %s for path %s",
+                           sha1_to_hex(o->blob_sha1),
+                           o->path);
+               o->file = *file;
+       }
+       else
+               *file = o->file;
+}
+
+/*
+ * Origin is refcounted and usually we keep the blob contents to be
+ * reused.
+ */
+static inline struct origin *origin_incref(struct origin *o)
+{
+       if (o)
+               o->refcnt++;
+       return o;
+}
+
+static void origin_decref(struct origin *o)
+{
+       if (o && --o->refcnt <= 0) {
+               if (o->previous)
+                       origin_decref(o->previous);
+               free(o->file.ptr);
+               free(o);
+       }
+}
+
+static void drop_origin_blob(struct origin *o)
+{
+       if (o->file.ptr) {
+               free(o->file.ptr);
+               o->file.ptr = NULL;
+       }
+}
+
+/*
+ * Each group of lines is described by a blame_entry; it can be split
+ * as we pass blame to the parents.  They form a linked list in the
+ * scoreboard structure, sorted by the target line number.
+ */
+struct blame_entry {
+       struct blame_entry *prev;
+       struct blame_entry *next;
+
+       /* the first line of this group in the final image;
+        * internally all line numbers are 0 based.
+        */
+       int lno;
+
+       /* how many lines this group has */
+       int num_lines;
+
+       /* the commit that introduced this group into the final image */
+       struct origin *suspect;
+
+       /* true if the suspect is truly guilty; false while we have not
+        * checked if the group came from one of its parents.
+        */
+       char guilty;
+
+       /* true if the entry has been scanned for copies in the current parent
+        */
+       char scanned;
+
+       /* the line number of the first line of this group in the
+        * suspect's file; internally all line numbers are 0 based.
+        */
+       int s_lno;
+
+       /* how significant this entry is -- cached to avoid
+        * scanning the lines over and over.
+        */
+       unsigned score;
+};
+
+/*
+ * The current state of the blame assignment.
+ */
+struct scoreboard {
+       /* the final commit (i.e. where we started digging from) */
+       struct commit *final;
+       struct rev_info *revs;
+       const char *path;
+
+       /*
+        * The contents in the final image.
+        * Used by many functions to obtain contents of the nth line,
+        * indexed with scoreboard.lineno[blame_entry.lno].
+        */
+       const char *final_buf;
+       unsigned long final_buf_size;
+
+       /* linked list of blames */
+       struct blame_entry *ent;
+
+       /* look-up a line in the final buffer */
+       int num_lines;
+       int *lineno;
+};
+
+static inline int same_suspect(struct origin *a, struct origin *b)
+{
+       if (a == b)
+               return 1;
+       if (a->commit != b->commit)
+               return 0;
+       return !strcmp(a->path, b->path);
+}
+
+static void sanity_check_refcnt(struct scoreboard *);
+
+/*
+ * If two blame entries that are next to each other came from
+ * contiguous lines in the same origin (i.e. <commit, path> pair),
+ * merge them together.
+ */
+static void coalesce(struct scoreboard *sb)
+{
+       struct blame_entry *ent, *next;
+
+       for (ent = sb->ent; ent && (next = ent->next); ent = next) {
+               if (same_suspect(ent->suspect, next->suspect) &&
+                   ent->guilty == next->guilty &&
+                   ent->s_lno + ent->num_lines == next->s_lno) {
+                       ent->num_lines += next->num_lines;
+                       ent->next = next->next;
+                       if (ent->next)
+                               ent->next->prev = ent;
+                       origin_decref(next->suspect);
+                       free(next);
+                       ent->score = 0;
+                       next = ent; /* again */
+               }
+       }
+
+       if (DEBUG) /* sanity */
+               sanity_check_refcnt(sb);
+}
+
+/*
+ * Given a commit and a path in it, create a new origin structure.
+ * The callers that add blame to the scoreboard should use
+ * get_origin() to obtain shared, refcounted copy instead of calling
+ * this function directly.
+ */
+static struct origin *make_origin(struct commit *commit, const char *path)
+{
+       struct origin *o;
+       o = xcalloc(1, sizeof(*o) + strlen(path) + 1);
+       o->commit = commit;
+       o->refcnt = 1;
+       strcpy(o->path, path);
+       return o;
+}
+
+/*
+ * Locate an existing origin or create a new one.
+ */
+static struct origin *get_origin(struct scoreboard *sb,
+                                struct commit *commit,
+                                const char *path)
+{
+       struct blame_entry *e;
+
+       for (e = sb->ent; e; e = e->next) {
+               if (e->suspect->commit == commit &&
+                   !strcmp(e->suspect->path, path))
+                       return origin_incref(e->suspect);
+       }
+       return make_origin(commit, path);
+}
+
+/*
+ * Fill the blob_sha1 field of an origin if it hasn't, so that later
+ * call to fill_origin_blob() can use it to locate the data.  blob_sha1
+ * for an origin is also used to pass the blame for the entire file to
+ * the parent to detect the case where a child's blob is identical to
+ * that of its parent's.
+ */
+static int fill_blob_sha1(struct origin *origin)
+{
+       unsigned mode;
+       if (!is_null_sha1(origin->blob_sha1))
+               return 0;
+       if (get_tree_entry(origin->commit->object.sha1,
+                          origin->path,
+                          origin->blob_sha1, &mode))
+               goto error_out;
+       if (sha1_object_info(origin->blob_sha1, NULL) != OBJ_BLOB)
+               goto error_out;
+       return 0;
+ error_out:
+       hashclr(origin->blob_sha1);
+       return -1;
+}
+
+/*
+ * We have an origin -- check if the same path exists in the
+ * parent and return an origin structure to represent it.
+ */
+static struct origin *find_origin(struct scoreboard *sb,
+                                 struct commit *parent,
+                                 struct origin *origin)
+{
+       struct origin *porigin = NULL;
+       struct diff_options diff_opts;
+       const char *paths[2];
+
+       if (parent->util) {
+               /*
+                * Each commit object can cache one origin in that
+                * commit.  This is a freestanding copy of origin and
+                * not refcounted.
+                */
+               struct origin *cached = parent->util;
+               if (!strcmp(cached->path, origin->path)) {
+                       /*
+                        * The same path between origin and its parent
+                        * without renaming -- the most common case.
+                        */
+                       porigin = get_origin(sb, parent, cached->path);
+
+                       /*
+                        * If the origin was newly created (i.e. get_origin
+                        * would call make_origin if none is found in the
+                        * scoreboard), it does not know the blob_sha1,
+                        * so copy it.  Otherwise porigin was in the
+                        * scoreboard and already knows blob_sha1.
+                        */
+                       if (porigin->refcnt == 1)
+                               hashcpy(porigin->blob_sha1, cached->blob_sha1);
+                       return porigin;
+               }
+               /* otherwise it was not very useful; free it */
+               free(parent->util);
+               parent->util = NULL;
+       }
+
+       /* See if the origin->path is different between parent
+        * and origin first.  Most of the time they are the
+        * same and diff-tree is fairly efficient about this.
+        */
+       diff_setup(&diff_opts);
+       DIFF_OPT_SET(&diff_opts, RECURSIVE);
+       diff_opts.detect_rename = 0;
+       diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+       paths[0] = origin->path;
+       paths[1] = NULL;
+
+       diff_tree_setup_paths(paths, &diff_opts);
+       if (diff_setup_done(&diff_opts) < 0)
+               die("diff-setup");
+
+       if (is_null_sha1(origin->commit->object.sha1))
+               do_diff_cache(parent->tree->object.sha1, &diff_opts);
+       else
+               diff_tree_sha1(parent->tree->object.sha1,
+                              origin->commit->tree->object.sha1,
+                              "", &diff_opts);
+       diffcore_std(&diff_opts);
+
+       if (!diff_queued_diff.nr) {
+               /* The path is the same as parent */
+               porigin = get_origin(sb, parent, origin->path);
+               hashcpy(porigin->blob_sha1, origin->blob_sha1);
+       } else {
+               /*
+                * Since origin->path is a pathspec, if the parent
+                * commit had it as a directory, we will see a whole
+                * bunch of deletion of files in the directory that we
+                * do not care about.
+                */
+               int i;
+               struct diff_filepair *p = NULL;
+               for (i = 0; i < diff_queued_diff.nr; i++) {
+                       const char *name;
+                       p = diff_queued_diff.queue[i];
+                       name = p->one->path ? p->one->path : p->two->path;
+                       if (!strcmp(name, origin->path))
+                               break;
+               }
+               if (!p)
+                       die("internal error in blame::find_origin");
+               switch (p->status) {
+               default:
+                       die("internal error in blame::find_origin (%c)",
+                           p->status);
+               case 'M':
+                       porigin = get_origin(sb, parent, origin->path);
+                       hashcpy(porigin->blob_sha1, p->one->sha1);
+                       break;
+               case 'A':
+               case 'T':
+                       /* Did not exist in parent, or type changed */
+                       break;
+               }
+       }
+       diff_flush(&diff_opts);
+       diff_tree_release_paths(&diff_opts);
+       if (porigin) {
+               /*
+                * Create a freestanding copy that is not part of
+                * the refcounted origin found in the scoreboard, and
+                * cache it in the commit.
+                */
+               struct origin *cached;
+
+               cached = make_origin(porigin->commit, porigin->path);
+               hashcpy(cached->blob_sha1, porigin->blob_sha1);
+               parent->util = cached;
+       }
+       return porigin;
+}
+
+/*
+ * We have an origin -- find the path that corresponds to it in its
+ * parent and return an origin structure to represent it.
+ */
+static struct origin *find_rename(struct scoreboard *sb,
+                                 struct commit *parent,
+                                 struct origin *origin)
+{
+       struct origin *porigin = NULL;
+       struct diff_options diff_opts;
+       int i;
+       const char *paths[2];
+
+       diff_setup(&diff_opts);
+       DIFF_OPT_SET(&diff_opts, RECURSIVE);
+       diff_opts.detect_rename = DIFF_DETECT_RENAME;
+       diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+       diff_opts.single_follow = origin->path;
+       paths[0] = NULL;
+       diff_tree_setup_paths(paths, &diff_opts);
+       if (diff_setup_done(&diff_opts) < 0)
+               die("diff-setup");
+
+       if (is_null_sha1(origin->commit->object.sha1))
+               do_diff_cache(parent->tree->object.sha1, &diff_opts);
+       else
+               diff_tree_sha1(parent->tree->object.sha1,
+                              origin->commit->tree->object.sha1,
+                              "", &diff_opts);
+       diffcore_std(&diff_opts);
+
+       for (i = 0; i < diff_queued_diff.nr; i++) {
+               struct diff_filepair *p = diff_queued_diff.queue[i];
+               if ((p->status == 'R' || p->status == 'C') &&
+                   !strcmp(p->two->path, origin->path)) {
+                       porigin = get_origin(sb, parent, p->one->path);
+                       hashcpy(porigin->blob_sha1, p->one->sha1);
+                       break;
+               }
+       }
+       diff_flush(&diff_opts);
+       diff_tree_release_paths(&diff_opts);
+       return porigin;
+}
+
+/*
+ * Link in a new blame entry to the scoreboard.  Entries that cover the
+ * same line range have been removed from the scoreboard previously.
+ */
+static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e)
+{
+       struct blame_entry *ent, *prev = NULL;
+
+       origin_incref(e->suspect);
+
+       for (ent = sb->ent; ent && ent->lno < e->lno; ent = ent->next)
+               prev = ent;
+
+       /* prev, if not NULL, is the last one that is below e */
+       e->prev = prev;
+       if (prev) {
+               e->next = prev->next;
+               prev->next = e;
+       }
+       else {
+               e->next = sb->ent;
+               sb->ent = e;
+       }
+       if (e->next)
+               e->next->prev = e;
+}
+
+/*
+ * src typically is on-stack; we want to copy the information in it to
+ * a malloced blame_entry that is already on the linked list of the
+ * scoreboard.  The origin of dst loses a refcnt while the origin of src
+ * gains one.
+ */
+static void dup_entry(struct blame_entry *dst, struct blame_entry *src)
+{
+       struct blame_entry *p, *n;
+
+       p = dst->prev;
+       n = dst->next;
+       origin_incref(src->suspect);
+       origin_decref(dst->suspect);
+       memcpy(dst, src, sizeof(*src));
+       dst->prev = p;
+       dst->next = n;
+       dst->score = 0;
+}
+
+static const char *nth_line(struct scoreboard *sb, int lno)
+{
+       return sb->final_buf + sb->lineno[lno];
+}
+
+/*
+ * It is known that lines between tlno to same came from parent, and e
+ * has an overlap with that range.  it also is known that parent's
+ * line plno corresponds to e's line tlno.
+ *
+ *                <---- e ----->
+ *                   <------>
+ *                   <------------>
+ *             <------------>
+ *             <------------------>
+ *
+ * Split e into potentially three parts; before this chunk, the chunk
+ * to be blamed for the parent, and after that portion.
+ */
+static void split_overlap(struct blame_entry *split,
+                         struct blame_entry *e,
+                         int tlno, int plno, int same,
+                         struct origin *parent)
+{
+       int chunk_end_lno;
+       memset(split, 0, sizeof(struct blame_entry [3]));
+
+       if (e->s_lno < tlno) {
+               /* there is a pre-chunk part not blamed on parent */
+               split[0].suspect = origin_incref(e->suspect);
+               split[0].lno = e->lno;
+               split[0].s_lno = e->s_lno;
+               split[0].num_lines = tlno - e->s_lno;
+               split[1].lno = e->lno + tlno - e->s_lno;
+               split[1].s_lno = plno;
+       }
+       else {
+               split[1].lno = e->lno;
+               split[1].s_lno = plno + (e->s_lno - tlno);
+       }
+
+       if (same < e->s_lno + e->num_lines) {
+               /* there is a post-chunk part not blamed on parent */
+               split[2].suspect = origin_incref(e->suspect);
+               split[2].lno = e->lno + (same - e->s_lno);
+               split[2].s_lno = e->s_lno + (same - e->s_lno);
+               split[2].num_lines = e->s_lno + e->num_lines - same;
+               chunk_end_lno = split[2].lno;
+       }
+       else
+               chunk_end_lno = e->lno + e->num_lines;
+       split[1].num_lines = chunk_end_lno - split[1].lno;
+
+       /*
+        * if it turns out there is nothing to blame the parent for,
+        * forget about the splitting.  !split[1].suspect signals this.
+        */
+       if (split[1].num_lines < 1)
+               return;
+       split[1].suspect = origin_incref(parent);
+}
+
+/*
+ * split_overlap() divided an existing blame e into up to three parts
+ * in split.  Adjust the linked list of blames in the scoreboard to
+ * reflect the split.
+ */
+static void split_blame(struct scoreboard *sb,
+                       struct blame_entry *split,
+                       struct blame_entry *e)
+{
+       struct blame_entry *new_entry;
+
+       if (split[0].suspect && split[2].suspect) {
+               /* The first part (reuse storage for the existing entry e) */
+               dup_entry(e, &split[0]);
+
+               /* The last part -- me */
+               new_entry = xmalloc(sizeof(*new_entry));
+               memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
+               add_blame_entry(sb, new_entry);
+
+               /* ... and the middle part -- parent */
+               new_entry = xmalloc(sizeof(*new_entry));
+               memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
+               add_blame_entry(sb, new_entry);
+       }
+       else if (!split[0].suspect && !split[2].suspect)
+               /*
+                * The parent covers the entire area; reuse storage for
+                * e and replace it with the parent.
+                */
+               dup_entry(e, &split[1]);
+       else if (split[0].suspect) {
+               /* me and then parent */
+               dup_entry(e, &split[0]);
+
+               new_entry = xmalloc(sizeof(*new_entry));
+               memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
+               add_blame_entry(sb, new_entry);
+       }
+       else {
+               /* parent and then me */
+               dup_entry(e, &split[1]);
+
+               new_entry = xmalloc(sizeof(*new_entry));
+               memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
+               add_blame_entry(sb, new_entry);
+       }
+
+       if (DEBUG) { /* sanity */
+               struct blame_entry *ent;
+               int lno = sb->ent->lno, corrupt = 0;
+
+               for (ent = sb->ent; ent; ent = ent->next) {
+                       if (lno != ent->lno)
+                               corrupt = 1;
+                       if (ent->s_lno < 0)
+                               corrupt = 1;
+                       lno += ent->num_lines;
+               }
+               if (corrupt) {
+                       lno = sb->ent->lno;
+                       for (ent = sb->ent; ent; ent = ent->next) {
+                               printf("L %8d l %8d n %8d\n",
+                                      lno, ent->lno, ent->num_lines);
+                               lno = ent->lno + ent->num_lines;
+                       }
+                       die("oops");
+               }
+       }
+}
+
+/*
+ * After splitting the blame, the origins used by the
+ * on-stack blame_entry should lose one refcnt each.
+ */
+static void decref_split(struct blame_entry *split)
+{
+       int i;
+
+       for (i = 0; i < 3; i++)
+               origin_decref(split[i].suspect);
+}
+
+/*
+ * Helper for blame_chunk().  blame_entry e is known to overlap with
+ * the patch hunk; split it and pass blame to the parent.
+ */
+static void blame_overlap(struct scoreboard *sb, struct blame_entry *e,
+                         int tlno, int plno, int same,
+                         struct origin *parent)
+{
+       struct blame_entry split[3];
+
+       split_overlap(split, e, tlno, plno, same, parent);
+       if (split[1].suspect)
+               split_blame(sb, split, e);
+       decref_split(split);
+}
+
+/*
+ * Find the line number of the last line the target is suspected for.
+ */
+static int find_last_in_target(struct scoreboard *sb, struct origin *target)
+{
+       struct blame_entry *e;
+       int last_in_target = -1;
+
+       for (e = sb->ent; e; e = e->next) {
+               if (e->guilty || !same_suspect(e->suspect, target))
+                       continue;
+               if (last_in_target < e->s_lno + e->num_lines)
+                       last_in_target = e->s_lno + e->num_lines;
+       }
+       return last_in_target;
+}
+
+/*
+ * Process one hunk from the patch between the current suspect for
+ * blame_entry e and its parent.  Find and split the overlap, and
+ * pass blame to the overlapping part to the parent.
+ */
+static void blame_chunk(struct scoreboard *sb,
+                       int tlno, int plno, int same,
+                       struct origin *target, struct origin *parent)
+{
+       struct blame_entry *e;
+
+       for (e = sb->ent; e; e = e->next) {
+               if (e->guilty || !same_suspect(e->suspect, target))
+                       continue;
+               if (same <= e->s_lno)
+                       continue;
+               if (tlno < e->s_lno + e->num_lines)
+                       blame_overlap(sb, e, tlno, plno, same, parent);
+       }
+}
+
+struct blame_chunk_cb_data {
+       struct scoreboard *sb;
+       struct origin *target;
+       struct origin *parent;
+       long plno;
+       long tlno;
+};
+
+static void blame_chunk_cb(void *data, long same, long p_next, long t_next)
+{
+       struct blame_chunk_cb_data *d = data;
+       blame_chunk(d->sb, d->tlno, d->plno, same, d->target, d->parent);
+       d->plno = p_next;
+       d->tlno = t_next;
+}
+
+/*
+ * We are looking at the origin 'target' and aiming to pass blame
+ * for the lines it is suspected to its parent.  Run diff to find
+ * which lines came from parent and pass blame for them.
+ */
+static int pass_blame_to_parent(struct scoreboard *sb,
+                               struct origin *target,
+                               struct origin *parent)
+{
+       int last_in_target;
+       mmfile_t file_p, file_o;
+       struct blame_chunk_cb_data d;
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
+       memset(&d, 0, sizeof(d));
+       d.sb = sb; d.target = target; d.parent = parent;
+       last_in_target = find_last_in_target(sb, target);
+       if (last_in_target < 0)
+               return 1; /* nothing remains for this target */
+
+       fill_origin_blob(&sb->revs->diffopt, parent, &file_p);
+       fill_origin_blob(&sb->revs->diffopt, target, &file_o);
+       num_get_patch++;
+
+       memset(&xpp, 0, sizeof(xpp));
+       xpp.flags = xdl_opts;
+       memset(&xecfg, 0, sizeof(xecfg));
+       xecfg.ctxlen = 0;
+       xdi_diff_hunks(&file_p, &file_o, blame_chunk_cb, &d, &xpp, &xecfg);
+       /* The rest (i.e. anything after tlno) are the same as the parent */
+       blame_chunk(sb, d.tlno, d.plno, last_in_target, target, parent);
+
+       return 0;
+}
+
+/*
+ * The lines in blame_entry after splitting blames many times can become
+ * very small and trivial, and at some point it becomes pointless to
+ * blame the parents.  E.g. "\t\t}\n\t}\n\n" appears everywhere in any
+ * ordinary C program, and it is not worth to say it was copied from
+ * totally unrelated file in the parent.
+ *
+ * Compute how trivial the lines in the blame_entry are.
+ */
+static unsigned ent_score(struct scoreboard *sb, struct blame_entry *e)
+{
+       unsigned score;
+       const char *cp, *ep;
+
+       if (e->score)
+               return e->score;
+
+       score = 1;
+       cp = nth_line(sb, e->lno);
+       ep = nth_line(sb, e->lno + e->num_lines);
+       while (cp < ep) {
+               unsigned ch = *((unsigned char *)cp);
+               if (isalnum(ch))
+                       score++;
+               cp++;
+       }
+       e->score = score;
+       return score;
+}
+
+/*
+ * best_so_far[] and this[] are both a split of an existing blame_entry
+ * that passes blame to the parent.  Maintain best_so_far the best split
+ * so far, by comparing this and best_so_far and copying this into
+ * bst_so_far as needed.
+ */
+static void copy_split_if_better(struct scoreboard *sb,
+                                struct blame_entry *best_so_far,
+                                struct blame_entry *this)
+{
+       int i;
+
+       if (!this[1].suspect)
+               return;
+       if (best_so_far[1].suspect) {
+               if (ent_score(sb, &this[1]) < ent_score(sb, &best_so_far[1]))
+                       return;
+       }
+
+       for (i = 0; i < 3; i++)
+               origin_incref(this[i].suspect);
+       decref_split(best_so_far);
+       memcpy(best_so_far, this, sizeof(struct blame_entry [3]));
+}
+
+/*
+ * We are looking at a part of the final image represented by
+ * ent (tlno and same are offset by ent->s_lno).
+ * tlno is where we are looking at in the final image.
+ * up to (but not including) same match preimage.
+ * plno is where we are looking at in the preimage.
+ *
+ * <-------------- final image ---------------------->
+ *       <------ent------>
+ *         ^tlno ^same
+ *    <---------preimage----->
+ *         ^plno
+ *
+ * All line numbers are 0-based.
+ */
+static void handle_split(struct scoreboard *sb,
+                        struct blame_entry *ent,
+                        int tlno, int plno, int same,
+                        struct origin *parent,
+                        struct blame_entry *split)
+{
+       if (ent->num_lines <= tlno)
+               return;
+       if (tlno < same) {
+               struct blame_entry this[3];
+               tlno += ent->s_lno;
+               same += ent->s_lno;
+               split_overlap(this, ent, tlno, plno, same, parent);
+               copy_split_if_better(sb, split, this);
+               decref_split(this);
+       }
+}
+
+struct handle_split_cb_data {
+       struct scoreboard *sb;
+       struct blame_entry *ent;
+       struct origin *parent;
+       struct blame_entry *split;
+       long plno;
+       long tlno;
+};
+
+static void handle_split_cb(void *data, long same, long p_next, long t_next)
+{
+       struct handle_split_cb_data *d = data;
+       handle_split(d->sb, d->ent, d->tlno, d->plno, same, d->parent, d->split);
+       d->plno = p_next;
+       d->tlno = t_next;
+}
+
+/*
+ * Find the lines from parent that are the same as ent so that
+ * we can pass blames to it.  file_p has the blob contents for
+ * the parent.
+ */
+static void find_copy_in_blob(struct scoreboard *sb,
+                             struct blame_entry *ent,
+                             struct origin *parent,
+                             struct blame_entry *split,
+                             mmfile_t *file_p)
+{
+       const char *cp;
+       int cnt;
+       mmfile_t file_o;
+       struct handle_split_cb_data d;
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
+       memset(&d, 0, sizeof(d));
+       d.sb = sb; d.ent = ent; d.parent = parent; d.split = split;
+       /*
+        * Prepare mmfile that contains only the lines in ent.
+        */
+       cp = nth_line(sb, ent->lno);
+       file_o.ptr = (char *) cp;
+       cnt = ent->num_lines;
+
+       while (cnt && cp < sb->final_buf + sb->final_buf_size) {
+               if (*cp++ == '\n')
+                       cnt--;
+       }
+       file_o.size = cp - file_o.ptr;
+
+       /*
+        * file_o is a part of final image we are annotating.
+        * file_p partially may match that image.
+        */
+       memset(&xpp, 0, sizeof(xpp));
+       xpp.flags = xdl_opts;
+       memset(&xecfg, 0, sizeof(xecfg));
+       xecfg.ctxlen = 1;
+       memset(split, 0, sizeof(struct blame_entry [3]));
+       xdi_diff_hunks(file_p, &file_o, handle_split_cb, &d, &xpp, &xecfg);
+       /* remainder, if any, all match the preimage */
+       handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split);
+}
+
+/*
+ * See if lines currently target is suspected for can be attributed to
+ * parent.
+ */
+static int find_move_in_parent(struct scoreboard *sb,
+                              struct origin *target,
+                              struct origin *parent)
+{
+       int last_in_target, made_progress;
+       struct blame_entry *e, split[3];
+       mmfile_t file_p;
+
+       last_in_target = find_last_in_target(sb, target);
+       if (last_in_target < 0)
+               return 1; /* nothing remains for this target */
+
+       fill_origin_blob(&sb->revs->diffopt, parent, &file_p);
+       if (!file_p.ptr)
+               return 0;
+
+       made_progress = 1;
+       while (made_progress) {
+               made_progress = 0;
+               for (e = sb->ent; e; e = e->next) {
+                       if (e->guilty || !same_suspect(e->suspect, target) ||
+                           ent_score(sb, e) < blame_move_score)
+                               continue;
+                       find_copy_in_blob(sb, e, parent, split, &file_p);
+                       if (split[1].suspect &&
+                           blame_move_score < ent_score(sb, &split[1])) {
+                               split_blame(sb, split, e);
+                               made_progress = 1;
+                       }
+                       decref_split(split);
+               }
+       }
+       return 0;
+}
+
+struct blame_list {
+       struct blame_entry *ent;
+       struct blame_entry split[3];
+};
+
+/*
+ * Count the number of entries the target is suspected for,
+ * and prepare a list of entry and the best split.
+ */
+static struct blame_list *setup_blame_list(struct scoreboard *sb,
+                                          struct origin *target,
+                                          int min_score,
+                                          int *num_ents_p)
+{
+       struct blame_entry *e;
+       int num_ents, i;
+       struct blame_list *blame_list = NULL;
+
+       for (e = sb->ent, num_ents = 0; e; e = e->next)
+               if (!e->scanned && !e->guilty &&
+                   same_suspect(e->suspect, target) &&
+                   min_score < ent_score(sb, e))
+                       num_ents++;
+       if (num_ents) {
+               blame_list = xcalloc(num_ents, sizeof(struct blame_list));
+               for (e = sb->ent, i = 0; e; e = e->next)
+                       if (!e->scanned && !e->guilty &&
+                           same_suspect(e->suspect, target) &&
+                           min_score < ent_score(sb, e))
+                               blame_list[i++].ent = e;
+       }
+       *num_ents_p = num_ents;
+       return blame_list;
+}
+
+/*
+ * Reset the scanned status on all entries.
+ */
+static void reset_scanned_flag(struct scoreboard *sb)
+{
+       struct blame_entry *e;
+       for (e = sb->ent; e; e = e->next)
+               e->scanned = 0;
+}
+
+/*
+ * For lines target is suspected for, see if we can find code movement
+ * across file boundary from the parent commit.  porigin is the path
+ * in the parent we already tried.
+ */
+static int find_copy_in_parent(struct scoreboard *sb,
+                              struct origin *target,
+                              struct commit *parent,
+                              struct origin *porigin,
+                              int opt)
+{
+       struct diff_options diff_opts;
+       const char *paths[1];
+       int i, j;
+       int retval;
+       struct blame_list *blame_list;
+       int num_ents;
+
+       blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents);
+       if (!blame_list)
+               return 1; /* nothing remains for this target */
+
+       diff_setup(&diff_opts);
+       DIFF_OPT_SET(&diff_opts, RECURSIVE);
+       diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+
+       paths[0] = NULL;
+       diff_tree_setup_paths(paths, &diff_opts);
+       if (diff_setup_done(&diff_opts) < 0)
+               die("diff-setup");
+
+       /* Try "find copies harder" on new path if requested;
+        * we do not want to use diffcore_rename() actually to
+        * match things up; find_copies_harder is set only to
+        * force diff_tree_sha1() to feed all filepairs to diff_queue,
+        * and this code needs to be after diff_setup_done(), which
+        * usually makes find-copies-harder imply copy detection.
+        */
+       if ((opt & PICKAXE_BLAME_COPY_HARDEST)
+           || ((opt & PICKAXE_BLAME_COPY_HARDER)
+               && (!porigin || strcmp(target->path, porigin->path))))
+               DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
+
+       if (is_null_sha1(target->commit->object.sha1))
+               do_diff_cache(parent->tree->object.sha1, &diff_opts);
+       else
+               diff_tree_sha1(parent->tree->object.sha1,
+                              target->commit->tree->object.sha1,
+                              "", &diff_opts);
+
+       if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER))
+               diffcore_std(&diff_opts);
+
+       retval = 0;
+       while (1) {
+               int made_progress = 0;
+
+               for (i = 0; i < diff_queued_diff.nr; i++) {
+                       struct diff_filepair *p = diff_queued_diff.queue[i];
+                       struct origin *norigin;
+                       mmfile_t file_p;
+                       struct blame_entry this[3];
+
+                       if (!DIFF_FILE_VALID(p->one))
+                               continue; /* does not exist in parent */
+                       if (S_ISGITLINK(p->one->mode))
+                               continue; /* ignore git links */
+                       if (porigin && !strcmp(p->one->path, porigin->path))
+                               /* find_move already dealt with this path */
+                               continue;
+
+                       norigin = get_origin(sb, parent, p->one->path);
+                       hashcpy(norigin->blob_sha1, p->one->sha1);
+                       fill_origin_blob(&sb->revs->diffopt, norigin, &file_p);
+                       if (!file_p.ptr)
+                               continue;
+
+                       for (j = 0; j < num_ents; j++) {
+                               find_copy_in_blob(sb, blame_list[j].ent,
+                                                 norigin, this, &file_p);
+                               copy_split_if_better(sb, blame_list[j].split,
+                                                    this);
+                               decref_split(this);
+                       }
+                       origin_decref(norigin);
+               }
+
+               for (j = 0; j < num_ents; j++) {
+                       struct blame_entry *split = blame_list[j].split;
+                       if (split[1].suspect &&
+                           blame_copy_score < ent_score(sb, &split[1])) {
+                               split_blame(sb, split, blame_list[j].ent);
+                               made_progress = 1;
+                       }
+                       else
+                               blame_list[j].ent->scanned = 1;
+                       decref_split(split);
+               }
+               free(blame_list);
+
+               if (!made_progress)
+                       break;
+               blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents);
+               if (!blame_list) {
+                       retval = 1;
+                       break;
+               }
+       }
+       reset_scanned_flag(sb);
+       diff_flush(&diff_opts);
+       diff_tree_release_paths(&diff_opts);
+       return retval;
+}
+
+/*
+ * The blobs of origin and porigin exactly match, so everything
+ * origin is suspected for can be blamed on the parent.
+ */
+static void pass_whole_blame(struct scoreboard *sb,
+                            struct origin *origin, struct origin *porigin)
+{
+       struct blame_entry *e;
+
+       if (!porigin->file.ptr && origin->file.ptr) {
+               /* Steal its file */
+               porigin->file = origin->file;
+               origin->file.ptr = NULL;
+       }
+       for (e = sb->ent; e; e = e->next) {
+               if (!same_suspect(e->suspect, origin))
+                       continue;
+               origin_incref(porigin);
+               origin_decref(e->suspect);
+               e->suspect = porigin;
+       }
+}
+
+/*
+ * We pass blame from the current commit to its parents.  We keep saying
+ * "parent" (and "porigin"), but what we mean is to find scapegoat to
+ * exonerate ourselves.
+ */
+static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit)
+{
+       if (!reverse)
+               return commit->parents;
+       return lookup_decoration(&revs->children, &commit->object);
+}
+
+static int num_scapegoats(struct rev_info *revs, struct commit *commit)
+{
+       int cnt;
+       struct commit_list *l = first_scapegoat(revs, commit);
+       for (cnt = 0; l; l = l->next)
+               cnt++;
+       return cnt;
+}
+
+#define MAXSG 16
+
+static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
+{
+       struct rev_info *revs = sb->revs;
+       int i, pass, num_sg;
+       struct commit *commit = origin->commit;
+       struct commit_list *sg;
+       struct origin *sg_buf[MAXSG];
+       struct origin *porigin, **sg_origin = sg_buf;
+
+       num_sg = num_scapegoats(revs, commit);
+       if (!num_sg)
+               goto finish;
+       else if (num_sg < ARRAY_SIZE(sg_buf))
+               memset(sg_buf, 0, sizeof(sg_buf));
+       else
+               sg_origin = xcalloc(num_sg, sizeof(*sg_origin));
+
+       /*
+        * The first pass looks for unrenamed path to optimize for
+        * common cases, then we look for renames in the second pass.
+        */
+       for (pass = 0; pass < 2; pass++) {
+               struct origin *(*find)(struct scoreboard *,
+                                      struct commit *, struct origin *);
+               find = pass ? find_rename : find_origin;
+
+               for (i = 0, sg = first_scapegoat(revs, commit);
+                    i < num_sg && sg;
+                    sg = sg->next, i++) {
+                       struct commit *p = sg->item;
+                       int j, same;
+
+                       if (sg_origin[i])
+                               continue;
+                       if (parse_commit(p))
+                               continue;
+                       porigin = find(sb, p, origin);
+                       if (!porigin)
+                               continue;
+                       if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) {
+                               pass_whole_blame(sb, origin, porigin);
+                               origin_decref(porigin);
+                               goto finish;
+                       }
+                       for (j = same = 0; j < i; j++)
+                               if (sg_origin[j] &&
+                                   !hashcmp(sg_origin[j]->blob_sha1,
+                                            porigin->blob_sha1)) {
+                                       same = 1;
+                                       break;
+                               }
+                       if (!same)
+                               sg_origin[i] = porigin;
+                       else
+                               origin_decref(porigin);
+               }
+       }
+
+       num_commits++;
+       for (i = 0, sg = first_scapegoat(revs, commit);
+            i < num_sg && sg;
+            sg = sg->next, i++) {
+               struct origin *porigin = sg_origin[i];
+               if (!porigin)
+                       continue;
+               if (!origin->previous) {
+                       origin_incref(porigin);
+                       origin->previous = porigin;
+               }
+               if (pass_blame_to_parent(sb, origin, porigin))
+                       goto finish;
+       }
+
+       /*
+        * Optionally find moves in parents' files.
+        */
+       if (opt & PICKAXE_BLAME_MOVE)
+               for (i = 0, sg = first_scapegoat(revs, commit);
+                    i < num_sg && sg;
+                    sg = sg->next, i++) {
+                       struct origin *porigin = sg_origin[i];
+                       if (!porigin)
+                               continue;
+                       if (find_move_in_parent(sb, origin, porigin))
+                               goto finish;
+               }
+
+       /*
+        * Optionally find copies from parents' files.
+        */
+       if (opt & PICKAXE_BLAME_COPY)
+               for (i = 0, sg = first_scapegoat(revs, commit);
+                    i < num_sg && sg;
+                    sg = sg->next, i++) {
+                       struct origin *porigin = sg_origin[i];
+                       if (find_copy_in_parent(sb, origin, sg->item,
+                                               porigin, opt))
+                               goto finish;
+               }
+
+ finish:
+       for (i = 0; i < num_sg; i++) {
+               if (sg_origin[i]) {
+                       drop_origin_blob(sg_origin[i]);
+                       origin_decref(sg_origin[i]);
+               }
+       }
+       drop_origin_blob(origin);
+       if (sg_buf != sg_origin)
+               free(sg_origin);
+}
+
+/*
+ * Information on commits, used for output.
+ */
+struct commit_info
+{
+       const char *author;
+       const char *author_mail;
+       unsigned long author_time;
+       const char *author_tz;
+
+       /* filled only when asked for details */
+       const char *committer;
+       const char *committer_mail;
+       unsigned long committer_time;
+       const char *committer_tz;
+
+       const char *summary;
+};
+
+/*
+ * Parse author/committer line in the commit object buffer
+ */
+static void get_ac_line(const char *inbuf, const char *what,
+                       int person_len, char *person,
+                       int mail_len, char *mail,
+                       unsigned long *time, const char **tz)
+{
+       int len, tzlen, maillen;
+       char *tmp, *endp, *timepos, *mailpos;
+
+       tmp = strstr(inbuf, what);
+       if (!tmp)
+               goto error_out;
+       tmp += strlen(what);
+       endp = strchr(tmp, '\n');
+       if (!endp)
+               len = strlen(tmp);
+       else
+               len = endp - tmp;
+       if (person_len <= len) {
+       error_out:
+               /* Ugh */
+               *tz = "(unknown)";
+               strcpy(person, *tz);
+               strcpy(mail, *tz);
+               *time = 0;
+               return;
+       }
+       memcpy(person, tmp, len);
+
+       tmp = person;
+       tmp += len;
+       *tmp = 0;
+       while (person < tmp && *tmp != ' ')
+               tmp--;
+       if (tmp <= person)
+               goto error_out;
+       *tz = tmp+1;
+       tzlen = (person+len)-(tmp+1);
+
+       *tmp = 0;
+       while (person < tmp && *tmp != ' ')
+               tmp--;
+       if (tmp <= person)
+               goto error_out;
+       *time = strtoul(tmp, NULL, 10);
+       timepos = tmp;
+
+       *tmp = 0;
+       while (person < tmp && *tmp != ' ')
+               tmp--;
+       if (tmp <= person)
+               return;
+       mailpos = tmp + 1;
+       *tmp = 0;
+       maillen = timepos - tmp;
+       memcpy(mail, mailpos, maillen);
+
+       if (!mailmap.nr)
+               return;
+
+       /*
+        * mailmap expansion may make the name longer.
+        * make room by pushing stuff down.
+        */
+       tmp = person + person_len - (tzlen + 1);
+       memmove(tmp, *tz, tzlen);
+       tmp[tzlen] = 0;
+       *tz = tmp;
+
+       /*
+        * Now, convert both name and e-mail using mailmap
+        */
+       if (map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) {
+               /* Add a trailing '>' to email, since map_user returns plain emails
+                  Note: It already has '<', since we replace from mail+1 */
+               mailpos = memchr(mail, '\0', mail_len);
+               if (mailpos && mailpos-mail < mail_len - 1) {
+                       *mailpos = '>';
+                       *(mailpos+1) = '\0';
+               }
+       }
+}
+
+static void get_commit_info(struct commit *commit,
+                           struct commit_info *ret,
+                           int detailed)
+{
+       int len;
+       const char *subject;
+       char *reencoded, *message;
+       static char author_name[1024];
+       static char author_mail[1024];
+       static char committer_name[1024];
+       static char committer_mail[1024];
+       static char summary_buf[1024];
+
+       /*
+        * We've operated without save_commit_buffer, so
+        * we now need to populate them for output.
+        */
+       if (!commit->buffer) {
+               enum object_type type;
+               unsigned long size;
+               commit->buffer =
+                       read_sha1_file(commit->object.sha1, &type, &size);
+               if (!commit->buffer)
+                       die("Cannot read commit %s",
+                           sha1_to_hex(commit->object.sha1));
+       }
+       reencoded = reencode_commit_message(commit, NULL);
+       message   = reencoded ? reencoded : commit->buffer;
+       ret->author = author_name;
+       ret->author_mail = author_mail;
+       get_ac_line(message, "\nauthor ",
+                   sizeof(author_name), author_name,
+                   sizeof(author_mail), author_mail,
+                   &ret->author_time, &ret->author_tz);
+
+       if (!detailed) {
+               free(reencoded);
+               return;
+       }
+
+       ret->committer = committer_name;
+       ret->committer_mail = committer_mail;
+       get_ac_line(message, "\ncommitter ",
+                   sizeof(committer_name), committer_name,
+                   sizeof(committer_mail), committer_mail,
+                   &ret->committer_time, &ret->committer_tz);
+
+       ret->summary = summary_buf;
+       len = find_commit_subject(message, &subject);
+       if (len && len < sizeof(summary_buf)) {
+               memcpy(summary_buf, subject, len);
+               summary_buf[len] = 0;
+       } else {
+               sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1));
+       }
+       free(reencoded);
+}
+
+/*
+ * To allow LF and other nonportable characters in pathnames,
+ * they are c-style quoted as needed.
+ */
+static void write_filename_info(const char *path)
+{
+       printf("filename ");
+       write_name_quoted(path, stdout, '\n');
+}
+
+/*
+ * Porcelain/Incremental format wants to show a lot of details per
+ * commit.  Instead of repeating this every line, emit it only once,
+ * the first time each commit appears in the output.
+ */
+static int emit_one_suspect_detail(struct origin *suspect)
+{
+       struct commit_info ci;
+
+       if (suspect->commit->object.flags & METAINFO_SHOWN)
+               return 0;
+
+       suspect->commit->object.flags |= METAINFO_SHOWN;
+       get_commit_info(suspect->commit, &ci, 1);
+       printf("author %s\n", ci.author);
+       printf("author-mail %s\n", ci.author_mail);
+       printf("author-time %lu\n", ci.author_time);
+       printf("author-tz %s\n", ci.author_tz);
+       printf("committer %s\n", ci.committer);
+       printf("committer-mail %s\n", ci.committer_mail);
+       printf("committer-time %lu\n", ci.committer_time);
+       printf("committer-tz %s\n", ci.committer_tz);
+       printf("summary %s\n", ci.summary);
+       if (suspect->commit->object.flags & UNINTERESTING)
+               printf("boundary\n");
+       if (suspect->previous) {
+               struct origin *prev = suspect->previous;
+               printf("previous %s ", sha1_to_hex(prev->commit->object.sha1));
+               write_name_quoted(prev->path, stdout, '\n');
+       }
+       return 1;
+}
+
+/*
+ * The blame_entry is found to be guilty for the range.  Mark it
+ * as such, and show it in incremental output.
+ */
+static void found_guilty_entry(struct blame_entry *ent)
+{
+       if (ent->guilty)
+               return;
+       ent->guilty = 1;
+       if (incremental) {
+               struct origin *suspect = ent->suspect;
+
+               printf("%s %d %d %d\n",
+                      sha1_to_hex(suspect->commit->object.sha1),
+                      ent->s_lno + 1, ent->lno + 1, ent->num_lines);
+               emit_one_suspect_detail(suspect);
+               write_filename_info(suspect->path);
+               maybe_flush_or_die(stdout, "stdout");
+       }
+}
+
+/*
+ * The main loop -- while the scoreboard has lines whose true origin
+ * is still unknown, pick one blame_entry, and allow its current
+ * suspect to pass blames to its parents.
+ */
+static void assign_blame(struct scoreboard *sb, int opt)
+{
+       struct rev_info *revs = sb->revs;
+
+       while (1) {
+               struct blame_entry *ent;
+               struct commit *commit;
+               struct origin *suspect = NULL;
+
+               /* find one suspect to break down */
+               for (ent = sb->ent; !suspect && ent; ent = ent->next)
+                       if (!ent->guilty)
+                               suspect = ent->suspect;
+               if (!suspect)
+                       return; /* all done */
+
+               /*
+                * We will use this suspect later in the loop,
+                * so hold onto it in the meantime.
+                */
+               origin_incref(suspect);
+               commit = suspect->commit;
+               if (!commit->object.parsed)
+                       parse_commit(commit);
+               if (reverse ||
+                   (!(commit->object.flags & UNINTERESTING) &&
+                    !(revs->max_age != -1 && commit->date < revs->max_age)))
+                       pass_blame(sb, suspect, opt);
+               else {
+                       commit->object.flags |= UNINTERESTING;
+                       if (commit->object.parsed)
+                               mark_parents_uninteresting(commit);
+               }
+               /* treat root commit as boundary */
+               if (!commit->parents && !show_root)
+                       commit->object.flags |= UNINTERESTING;
+
+               /* Take responsibility for the remaining entries */
+               for (ent = sb->ent; ent; ent = ent->next)
+                       if (same_suspect(ent->suspect, suspect))
+                               found_guilty_entry(ent);
+               origin_decref(suspect);
+
+               if (DEBUG) /* sanity */
+                       sanity_check_refcnt(sb);
+       }
+}
+
+static const char *format_time(unsigned long time, const char *tz_str,
+                              int show_raw_time)
+{
+       static char time_buf[128];
+       const char *time_str;
+       int time_len;
+       int tz;
+
+       if (show_raw_time) {
+               sprintf(time_buf, "%lu %s", time, tz_str);
+       }
+       else {
+               tz = atoi(tz_str);
+               time_str = show_date(time, tz, blame_date_mode);
+               time_len = strlen(time_str);
+               memcpy(time_buf, time_str, time_len);
+               memset(time_buf + time_len, ' ', blame_date_width - time_len);
+       }
+       return time_buf;
+}
+
+#define OUTPUT_ANNOTATE_COMPAT 001
+#define OUTPUT_LONG_OBJECT_NAME        002
+#define OUTPUT_RAW_TIMESTAMP   004
+#define OUTPUT_PORCELAIN       010
+#define OUTPUT_SHOW_NAME       020
+#define OUTPUT_SHOW_NUMBER     040
+#define OUTPUT_SHOW_SCORE      0100
+#define OUTPUT_NO_AUTHOR       0200
+
+static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
+{
+       int cnt;
+       const char *cp;
+       struct origin *suspect = ent->suspect;
+       char hex[41];
+
+       strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
+       printf("%s%c%d %d %d\n",
+              hex,
+              ent->guilty ? ' ' : '*', /* purely for debugging */
+              ent->s_lno + 1,
+              ent->lno + 1,
+              ent->num_lines);
+       if (emit_one_suspect_detail(suspect) ||
+           (suspect->commit->object.flags & MORE_THAN_ONE_PATH))
+               write_filename_info(suspect->path);
+
+       cp = nth_line(sb, ent->lno);
+       for (cnt = 0; cnt < ent->num_lines; cnt++) {
+               char ch;
+               if (cnt)
+                       printf("%s %d %d\n", hex,
+                              ent->s_lno + 1 + cnt,
+                              ent->lno + 1 + cnt);
+               putchar('\t');
+               do {
+                       ch = *cp++;
+                       putchar(ch);
+               } while (ch != '\n' &&
+                        cp < sb->final_buf + sb->final_buf_size);
+       }
+
+       if (sb->final_buf_size && cp[-1] != '\n')
+               putchar('\n');
+}
+
+static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
+{
+       int cnt;
+       const char *cp;
+       struct origin *suspect = ent->suspect;
+       struct commit_info ci;
+       char hex[41];
+       int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP);
+
+       get_commit_info(suspect->commit, &ci, 1);
+       strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
+
+       cp = nth_line(sb, ent->lno);
+       for (cnt = 0; cnt < ent->num_lines; cnt++) {
+               char ch;
+               int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8;
+
+               if (suspect->commit->object.flags & UNINTERESTING) {
+                       if (blank_boundary)
+                               memset(hex, ' ', length);
+                       else if (!(opt & OUTPUT_ANNOTATE_COMPAT)) {
+                               length--;
+                               putchar('^');
+                       }
+               }
+
+               printf("%.*s", length, hex);
+               if (opt & OUTPUT_ANNOTATE_COMPAT)
+                       printf("\t(%10s\t%10s\t%d)", ci.author,
+                              format_time(ci.author_time, ci.author_tz,
+                                          show_raw_time),
+                              ent->lno + 1 + cnt);
+               else {
+                       if (opt & OUTPUT_SHOW_SCORE)
+                               printf(" %*d %02d",
+                                      max_score_digits, ent->score,
+                                      ent->suspect->refcnt);
+                       if (opt & OUTPUT_SHOW_NAME)
+                               printf(" %-*.*s", longest_file, longest_file,
+                                      suspect->path);
+                       if (opt & OUTPUT_SHOW_NUMBER)
+                               printf(" %*d", max_orig_digits,
+                                      ent->s_lno + 1 + cnt);
+
+                       if (!(opt & OUTPUT_NO_AUTHOR)) {
+                               int pad = longest_author - utf8_strwidth(ci.author);
+                               printf(" (%s%*s %10s",
+                                      ci.author, pad, "",
+                                      format_time(ci.author_time,
+                                                  ci.author_tz,
+                                                  show_raw_time));
+                       }
+                       printf(" %*d) ",
+                              max_digits, ent->lno + 1 + cnt);
+               }
+               do {
+                       ch = *cp++;
+                       putchar(ch);
+               } while (ch != '\n' &&
+                        cp < sb->final_buf + sb->final_buf_size);
+       }
+
+       if (sb->final_buf_size && cp[-1] != '\n')
+               putchar('\n');
+}
+
+static void output(struct scoreboard *sb, int option)
+{
+       struct blame_entry *ent;
+
+       if (option & OUTPUT_PORCELAIN) {
+               for (ent = sb->ent; ent; ent = ent->next) {
+                       struct blame_entry *oth;
+                       struct origin *suspect = ent->suspect;
+                       struct commit *commit = suspect->commit;
+                       if (commit->object.flags & MORE_THAN_ONE_PATH)
+                               continue;
+                       for (oth = ent->next; oth; oth = oth->next) {
+                               if ((oth->suspect->commit != commit) ||
+                                   !strcmp(oth->suspect->path, suspect->path))
+                                       continue;
+                               commit->object.flags |= MORE_THAN_ONE_PATH;
+                               break;
+                       }
+               }
+       }
+
+       for (ent = sb->ent; ent; ent = ent->next) {
+               if (option & OUTPUT_PORCELAIN)
+                       emit_porcelain(sb, ent);
+               else {
+                       emit_other(sb, ent, option);
+               }
+       }
+}
+
+/*
+ * To allow quick access to the contents of nth line in the
+ * final image, prepare an index in the scoreboard.
+ */
+static int prepare_lines(struct scoreboard *sb)
+{
+       const char *buf = sb->final_buf;
+       unsigned long len = sb->final_buf_size;
+       int num = 0, incomplete = 0, bol = 1;
+
+       if (len && buf[len-1] != '\n')
+               incomplete++; /* incomplete line at the end */
+       while (len--) {
+               if (bol) {
+                       sb->lineno = xrealloc(sb->lineno,
+                                             sizeof(int *) * (num + 1));
+                       sb->lineno[num] = buf - sb->final_buf;
+                       bol = 0;
+               }
+               if (*buf++ == '\n') {
+                       num++;
+                       bol = 1;
+               }
+       }
+       sb->lineno = xrealloc(sb->lineno,
+                             sizeof(int *) * (num + incomplete + 1));
+       sb->lineno[num + incomplete] = buf - sb->final_buf;
+       sb->num_lines = num + incomplete;
+       return sb->num_lines;
+}
+
+/*
+ * Add phony grafts for use with -S; this is primarily to
+ * support git's cvsserver that wants to give a linear history
+ * to its clients.
+ */
+static int read_ancestry(const char *graft_file)
+{
+       FILE *fp = fopen(graft_file, "r");
+       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)
+                       register_commit_graft(graft, 0);
+       }
+       fclose(fp);
+       return 0;
+}
+
+/*
+ * How many columns do we need to show line numbers in decimal?
+ */
+static int lineno_width(int lines)
+{
+       int i, width;
+
+       for (width = 1, i = 10; i <= lines; width++)
+               i *= 10;
+       return width;
+}
+
+/*
+ * How many columns do we need to show line numbers, authors,
+ * and filenames?
+ */
+static void find_alignment(struct scoreboard *sb, int *option)
+{
+       int longest_src_lines = 0;
+       int longest_dst_lines = 0;
+       unsigned largest_score = 0;
+       struct blame_entry *e;
+
+       for (e = sb->ent; e; e = e->next) {
+               struct origin *suspect = e->suspect;
+               struct commit_info ci;
+               int num;
+
+               if (strcmp(suspect->path, sb->path))
+                       *option |= OUTPUT_SHOW_NAME;
+               num = strlen(suspect->path);
+               if (longest_file < num)
+                       longest_file = num;
+               if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
+                       suspect->commit->object.flags |= METAINFO_SHOWN;
+                       get_commit_info(suspect->commit, &ci, 1);
+                       num = utf8_strwidth(ci.author);
+                       if (longest_author < num)
+                               longest_author = num;
+               }
+               num = e->s_lno + e->num_lines;
+               if (longest_src_lines < num)
+                       longest_src_lines = num;
+               num = e->lno + e->num_lines;
+               if (longest_dst_lines < num)
+                       longest_dst_lines = num;
+               if (largest_score < ent_score(sb, e))
+                       largest_score = ent_score(sb, e);
+       }
+       max_orig_digits = lineno_width(longest_src_lines);
+       max_digits = lineno_width(longest_dst_lines);
+       max_score_digits = lineno_width(largest_score);
+}
+
+/*
+ * For debugging -- origin is refcounted, and this asserts that
+ * we do not underflow.
+ */
+static void sanity_check_refcnt(struct scoreboard *sb)
+{
+       int baa = 0;
+       struct blame_entry *ent;
+
+       for (ent = sb->ent; ent; ent = ent->next) {
+               /* Nobody should have zero or negative refcnt */
+               if (ent->suspect->refcnt <= 0) {
+                       fprintf(stderr, "%s in %s has negative refcnt %d\n",
+                               ent->suspect->path,
+                               sha1_to_hex(ent->suspect->commit->object.sha1),
+                               ent->suspect->refcnt);
+                       baa = 1;
+               }
+       }
+       if (baa) {
+               int opt = 0160;
+               find_alignment(sb, &opt);
+               output(sb, opt);
+               die("Baa %d!", baa);
+       }
+}
+
+/*
+ * Used for the command line parsing; check if the path exists
+ * in the working tree.
+ */
+static int has_string_in_work_tree(const char *path)
+{
+       struct stat st;
+       return !lstat(path, &st);
+}
+
+static unsigned parse_score(const char *arg)
+{
+       char *end;
+       unsigned long score = strtoul(arg, &end, 10);
+       if (*end)
+               return 0;
+       return score;
+}
+
+static const char *add_prefix(const char *prefix, const char *path)
+{
+       return prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
+}
+
+/*
+ * Parsing of (comma separated) one item in the -L option
+ */
+static const char *parse_loc(const char *spec,
+                            struct scoreboard *sb, long lno,
+                            long begin, long *ret)
+{
+       char *term;
+       const char *line;
+       long num;
+       int reg_error;
+       regex_t regexp;
+       regmatch_t match[1];
+
+       /* Allow "-L <something>,+20" to mean starting at <something>
+        * for 20 lines, or "-L <something>,-5" for 5 lines ending at
+        * <something>.
+        */
+       if (1 < begin && (spec[0] == '+' || spec[0] == '-')) {
+               num = strtol(spec + 1, &term, 10);
+               if (term != spec + 1) {
+                       if (spec[0] == '-')
+                               num = 0 - num;
+                       if (0 < num)
+                               *ret = begin + num - 2;
+                       else if (!num)
+                               *ret = begin;
+                       else
+                               *ret = begin + num;
+                       return term;
+               }
+               return spec;
+       }
+       num = strtol(spec, &term, 10);
+       if (term != spec) {
+               *ret = num;
+               return term;
+       }
+       if (spec[0] != '/')
+               return spec;
+
+       /* it could be a regexp of form /.../ */
+       for (term = (char *) spec + 1; *term && *term != '/'; term++) {
+               if (*term == '\\')
+                       term++;
+       }
+       if (*term != '/')
+               return spec;
+
+       /* try [spec+1 .. term-1] as regexp */
+       *term = 0;
+       begin--; /* input is in human terms */
+       line = nth_line(sb, begin);
+
+       if (!(reg_error = regcomp(&regexp, spec + 1, REG_NEWLINE)) &&
+           !(reg_error = regexec(&regexp, line, 1, match, 0))) {
+               const char *cp = line + match[0].rm_so;
+               const char *nline;
+
+               while (begin++ < lno) {
+                       nline = nth_line(sb, begin);
+                       if (line <= cp && cp < nline)
+                               break;
+                       line = nline;
+               }
+               *ret = begin;
+               regfree(&regexp);
+               *term++ = '/';
+               return term;
+       }
+       else {
+               char errbuf[1024];
+               regerror(reg_error, &regexp, errbuf, 1024);
+               die("-L parameter '%s': %s", spec + 1, errbuf);
+       }
+}
+
+/*
+ * Parsing of -L option
+ */
+static void prepare_blame_range(struct scoreboard *sb,
+                               const char *bottomtop,
+                               long lno,
+                               long *bottom, long *top)
+{
+       const char *term;
+
+       term = parse_loc(bottomtop, sb, lno, 1, bottom);
+       if (*term == ',') {
+               term = parse_loc(term + 1, sb, lno, *bottom + 1, top);
+               if (*term)
+                       usage(blame_usage);
+       }
+       if (*term)
+               usage(blame_usage);
+}
+
+static int git_blame_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "blame.showroot")) {
+               show_root = git_config_bool(var, value);
+               return 0;
+       }
+       if (!strcmp(var, "blame.blankboundary")) {
+               blank_boundary = git_config_bool(var, value);
+               return 0;
+       }
+       if (!strcmp(var, "blame.date")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               blame_date_mode = parse_date_format(value);
+               return 0;
+       }
+
+       switch (userdiff_config(var, value)) {
+       case 0:
+               break;
+       case -1:
+               return -1;
+       default:
+               return 0;
+       }
+
+       return git_default_config(var, value, cb);
+}
+
+/*
+ * Prepare a dummy commit that represents the work tree (or staged) item.
+ * Note that annotating work tree item never works in the reverse.
+ */
+static struct commit *fake_working_tree_commit(struct diff_options *opt,
+                                              const char *path,
+                                              const char *contents_from)
+{
+       struct commit *commit;
+       struct origin *origin;
+       unsigned char head_sha1[20];
+       struct strbuf buf = STRBUF_INIT;
+       const char *ident;
+       time_t now;
+       int size, len;
+       struct cache_entry *ce;
+       unsigned mode;
+
+       if (get_sha1("HEAD", head_sha1))
+               die("No such ref: HEAD");
+
+       time(&now);
+       commit = xcalloc(1, sizeof(*commit));
+       commit->parents = xcalloc(1, sizeof(*commit->parents));
+       commit->parents->item = lookup_commit_reference(head_sha1);
+       commit->object.parsed = 1;
+       commit->date = now;
+       commit->object.type = OBJ_COMMIT;
+
+       origin = make_origin(commit, path);
+
+       if (!contents_from || strcmp("-", contents_from)) {
+               struct stat st;
+               const char *read_from;
+               unsigned long buf_len;
+
+               if (contents_from) {
+                       if (stat(contents_from, &st) < 0)
+                               die_errno("Cannot stat '%s'", contents_from);
+                       read_from = contents_from;
+               }
+               else {
+                       if (lstat(path, &st) < 0)
+                               die_errno("Cannot lstat '%s'", path);
+                       read_from = path;
+               }
+               mode = canon_mode(st.st_mode);
+
+               switch (st.st_mode & S_IFMT) {
+               case S_IFREG:
+                       if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
+                           textconv_object(read_from, null_sha1, &buf.buf, &buf_len))
+                               buf.len = buf_len;
+                       else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
+                               die_errno("cannot open or read '%s'", read_from);
+                       break;
+               case S_IFLNK:
+                       if (strbuf_readlink(&buf, read_from, st.st_size) < 0)
+                               die_errno("cannot readlink '%s'", read_from);
+                       break;
+               default:
+                       die("unsupported file type %s", read_from);
+               }
+       }
+       else {
+               /* Reading from stdin */
+               contents_from = "standard input";
+               mode = 0;
+               if (strbuf_read(&buf, 0, 0) < 0)
+                       die_errno("failed to read from stdin");
+       }
+       convert_to_git(path, buf.buf, buf.len, &buf, 0);
+       origin->file.ptr = buf.buf;
+       origin->file.size = buf.len;
+       pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_sha1);
+       commit->util = origin;
+
+       /*
+        * Read the current index, replace the path entry with
+        * origin->blob_sha1 without mucking with its mode or type
+        * bits; we are not going to write this index out -- we just
+        * want to run "diff-index --cached".
+        */
+       discard_cache();
+       read_cache();
+
+       len = strlen(path);
+       if (!mode) {
+               int pos = cache_name_pos(path, len);
+               if (0 <= pos)
+                       mode = active_cache[pos]->ce_mode;
+               else
+                       /* Let's not bother reading from HEAD tree */
+                       mode = S_IFREG | 0644;
+       }
+       size = cache_entry_size(len);
+       ce = xcalloc(1, size);
+       hashcpy(ce->sha1, origin->blob_sha1);
+       memcpy(ce->name, path, len);
+       ce->ce_flags = create_ce_flags(len, 0);
+       ce->ce_mode = create_ce_mode(mode);
+       add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
+
+       /*
+        * We are not going to write this out, so this does not matter
+        * right now, but someday we might optimize diff-index --cached
+        * with cache-tree information.
+        */
+       cache_tree_invalidate_path(active_cache_tree, path);
+
+       commit->buffer = xmalloc(400);
+       ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0);
+       snprintf(commit->buffer, 400,
+               "tree 0000000000000000000000000000000000000000\n"
+               "parent %s\n"
+               "author %s\n"
+               "committer %s\n\n"
+               "Version of %s from %s\n",
+               sha1_to_hex(head_sha1),
+               ident, ident, path, contents_from ? contents_from : path);
+       return commit;
+}
+
+static const char *prepare_final(struct scoreboard *sb)
+{
+       int i;
+       const char *final_commit_name = NULL;
+       struct rev_info *revs = sb->revs;
+
+       /*
+        * There must be one and only one positive commit in the
+        * revs->pending array.
+        */
+       for (i = 0; i < revs->pending.nr; i++) {
+               struct object *obj = revs->pending.objects[i].item;
+               if (obj->flags & UNINTERESTING)
+                       continue;
+               while (obj->type == OBJ_TAG)
+                       obj = deref_tag(obj, NULL, 0);
+               if (obj->type != OBJ_COMMIT)
+                       die("Non commit %s?", revs->pending.objects[i].name);
+               if (sb->final)
+                       die("More than one commit to dig from %s and %s?",
+                           revs->pending.objects[i].name,
+                           final_commit_name);
+               sb->final = (struct commit *) obj;
+               final_commit_name = revs->pending.objects[i].name;
+       }
+       return final_commit_name;
+}
+
+static const char *prepare_initial(struct scoreboard *sb)
+{
+       int i;
+       const char *final_commit_name = NULL;
+       struct rev_info *revs = sb->revs;
+
+       /*
+        * There must be one and only one negative commit, and it must be
+        * the boundary.
+        */
+       for (i = 0; i < revs->pending.nr; i++) {
+               struct object *obj = revs->pending.objects[i].item;
+               if (!(obj->flags & UNINTERESTING))
+                       continue;
+               while (obj->type == OBJ_TAG)
+                       obj = deref_tag(obj, NULL, 0);
+               if (obj->type != OBJ_COMMIT)
+                       die("Non commit %s?", revs->pending.objects[i].name);
+               if (sb->final)
+                       die("More than one commit to dig down to %s and %s?",
+                           revs->pending.objects[i].name,
+                           final_commit_name);
+               sb->final = (struct commit *) obj;
+               final_commit_name = revs->pending.objects[i].name;
+       }
+       if (!final_commit_name)
+               die("No commit to dig down to?");
+       return final_commit_name;
+}
+
+static int blame_copy_callback(const struct option *option, const char *arg, int unset)
+{
+       int *opt = option->value;
+
+       /*
+        * -C enables copy from removed files;
+        * -C -C enables copy from existing files, but only
+        *       when blaming a new file;
+        * -C -C -C enables copy from existing files for
+        *          everybody
+        */
+       if (*opt & PICKAXE_BLAME_COPY_HARDER)
+               *opt |= PICKAXE_BLAME_COPY_HARDEST;
+       if (*opt & PICKAXE_BLAME_COPY)
+               *opt |= PICKAXE_BLAME_COPY_HARDER;
+       *opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE;
+
+       if (arg)
+               blame_copy_score = parse_score(arg);
+       return 0;
+}
+
+static int blame_move_callback(const struct option *option, const char *arg, int unset)
+{
+       int *opt = option->value;
+
+       *opt |= PICKAXE_BLAME_MOVE;
+
+       if (arg)
+               blame_move_score = parse_score(arg);
+       return 0;
+}
+
+static int blame_bottomtop_callback(const struct option *option, const char *arg, int unset)
+{
+       const char **bottomtop = option->value;
+       if (!arg)
+               return -1;
+       if (*bottomtop)
+               die("More than one '-L n,m' option given");
+       *bottomtop = arg;
+       return 0;
+}
+
+int cmd_blame(int argc, const char **argv, const char *prefix)
+{
+       struct rev_info revs;
+       const char *path;
+       struct scoreboard sb;
+       struct origin *o;
+       struct blame_entry *ent;
+       long dashdash_pos, bottom, top, lno;
+       const char *final_commit_name = NULL;
+       enum object_type type;
+
+       static const char *bottomtop = NULL;
+       static int output_option = 0, opt = 0;
+       static int show_stats = 0;
+       static const char *revs_file = NULL;
+       static const char *contents_from = NULL;
+       static const struct option options[] = {
+               OPT_BOOLEAN(0, "incremental", &incremental, "Show blame entries as we find them, incrementally"),
+               OPT_BOOLEAN('b', NULL, &blank_boundary, "Show blank SHA-1 for boundary commits (Default: off)"),
+               OPT_BOOLEAN(0, "root", &show_root, "Do not treat root commits as boundaries (Default: off)"),
+               OPT_BOOLEAN(0, "show-stats", &show_stats, "Show work cost statistics"),
+               OPT_BIT(0, "score-debug", &output_option, "Show output score for blame entries", OUTPUT_SHOW_SCORE),
+               OPT_BIT('f', "show-name", &output_option, "Show original filename (Default: auto)", OUTPUT_SHOW_NAME),
+               OPT_BIT('n', "show-number", &output_option, "Show original linenumber (Default: off)", OUTPUT_SHOW_NUMBER),
+               OPT_BIT('p', "porcelain", &output_option, "Show in a format designed for machine consumption", OUTPUT_PORCELAIN),
+               OPT_BIT('c', NULL, &output_option, "Use the same output mode as git-annotate (Default: off)", OUTPUT_ANNOTATE_COMPAT),
+               OPT_BIT('t', NULL, &output_option, "Show raw timestamp (Default: off)", OUTPUT_RAW_TIMESTAMP),
+               OPT_BIT('l', NULL, &output_option, "Show long commit SHA1 (Default: off)", OUTPUT_LONG_OBJECT_NAME),
+               OPT_BIT('s', NULL, &output_option, "Suppress author name and timestamp (Default: off)", OUTPUT_NO_AUTHOR),
+               OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE),
+               OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from <file> instead of calling git-rev-list"),
+               OPT_STRING(0, "contents", &contents_from, "file", "Use <file>'s contents as the final image"),
+               { OPTION_CALLBACK, 'C', NULL, &opt, "score", "Find line copies within and across files", PARSE_OPT_OPTARG, blame_copy_callback },
+               { OPTION_CALLBACK, 'M', NULL, &opt, "score", "Find line movements within and across files", PARSE_OPT_OPTARG, blame_move_callback },
+               OPT_CALLBACK('L', NULL, &bottomtop, "n,m", "Process only line range n,m, counting from 1", blame_bottomtop_callback),
+               OPT_END()
+       };
+
+       struct parse_opt_ctx_t ctx;
+       int cmd_is_annotate = !strcmp(argv[0], "annotate");
+
+       git_config(git_blame_config, NULL);
+       init_revisions(&revs, NULL);
+       revs.date_mode = blame_date_mode;
+       DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV);
+
+       save_commit_buffer = 0;
+       dashdash_pos = 0;
+
+       parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH |
+                           PARSE_OPT_KEEP_ARGV0);
+       for (;;) {
+               switch (parse_options_step(&ctx, options, blame_opt_usage)) {
+               case PARSE_OPT_HELP:
+                       exit(129);
+               case PARSE_OPT_DONE:
+                       if (ctx.argv[0])
+                               dashdash_pos = ctx.cpidx;
+                       goto parse_done;
+               }
+
+               if (!strcmp(ctx.argv[0], "--reverse")) {
+                       ctx.argv[0] = "--children";
+                       reverse = 1;
+               }
+               parse_revision_opt(&revs, &ctx, options, blame_opt_usage);
+       }
+parse_done:
+       argc = parse_options_end(&ctx);
+
+       if (revs_file && read_ancestry(revs_file))
+               die_errno("reading graft file '%s' failed", revs_file);
+
+       if (cmd_is_annotate) {
+               output_option |= OUTPUT_ANNOTATE_COMPAT;
+               blame_date_mode = DATE_ISO8601;
+       } else {
+               blame_date_mode = revs.date_mode;
+       }
+
+       /* The maximum width used to show the dates */
+       switch (blame_date_mode) {
+       case DATE_RFC2822:
+               blame_date_width = sizeof("Thu, 19 Oct 2006 16:00:04 -0700");
+               break;
+       case DATE_ISO8601:
+               blame_date_width = sizeof("2006-10-19 16:00:04 -0700");
+               break;
+       case DATE_RAW:
+               blame_date_width = sizeof("1161298804 -0700");
+               break;
+       case DATE_SHORT:
+               blame_date_width = sizeof("2006-10-19");
+               break;
+       case DATE_RELATIVE:
+               /* "normal" is used as the fallback for "relative" */
+       case DATE_LOCAL:
+       case DATE_NORMAL:
+               blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
+               break;
+       }
+       blame_date_width -= 1; /* strip the null */
+
+       if (DIFF_OPT_TST(&revs.diffopt, FIND_COPIES_HARDER))
+               opt |= (PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE |
+                       PICKAXE_BLAME_COPY_HARDER);
+
+       if (!blame_move_score)
+               blame_move_score = BLAME_DEFAULT_MOVE_SCORE;
+       if (!blame_copy_score)
+               blame_copy_score = BLAME_DEFAULT_COPY_SCORE;
+
+       /*
+        * We have collected options unknown to us in argv[1..unk]
+        * which are to be passed to revision machinery if we are
+        * going to do the "bottom" processing.
+        *
+        * The remaining are:
+        *
+        * (1) if dashdash_pos != 0, it is either
+        *     "blame [revisions] -- <path>" or
+        *     "blame -- <path> <rev>"
+        *
+        * (2) otherwise, it is one of the two:
+        *     "blame [revisions] <path>"
+        *     "blame <path> <rev>"
+        *
+        * Note that we must strip out <path> from the arguments: we do not
+        * want the path pruning but we may want "bottom" processing.
+        */
+       if (dashdash_pos) {
+               switch (argc - dashdash_pos - 1) {
+               case 2: /* (1b) */
+                       if (argc != 4)
+                               usage_with_options(blame_opt_usage, options);
+                       /* reorder for the new way: <rev> -- <path> */
+                       argv[1] = argv[3];
+                       argv[3] = argv[2];
+                       argv[2] = "--";
+                       /* FALLTHROUGH */
+               case 1: /* (1a) */
+                       path = add_prefix(prefix, argv[--argc]);
+                       argv[argc] = NULL;
+                       break;
+               default:
+                       usage_with_options(blame_opt_usage, options);
+               }
+       } else {
+               if (argc < 2)
+                       usage_with_options(blame_opt_usage, options);
+               path = add_prefix(prefix, argv[argc - 1]);
+               if (argc == 3 && !has_string_in_work_tree(path)) { /* (2b) */
+                       path = add_prefix(prefix, argv[1]);
+                       argv[1] = argv[2];
+               }
+               argv[argc - 1] = "--";
+
+               setup_work_tree();
+               if (!has_string_in_work_tree(path))
+                       die_errno("cannot stat path '%s'", path);
+       }
+
+       revs.disable_stdin = 1;
+       setup_revisions(argc, argv, &revs, NULL);
+       memset(&sb, 0, sizeof(sb));
+
+       sb.revs = &revs;
+       if (!reverse)
+               final_commit_name = prepare_final(&sb);
+       else if (contents_from)
+               die("--contents and --children do not blend well.");
+       else
+               final_commit_name = prepare_initial(&sb);
+
+       if (!sb.final) {
+               /*
+                * "--not A B -- path" without anything positive;
+                * do not default to HEAD, but use the working tree
+                * or "--contents".
+                */
+               setup_work_tree();
+               sb.final = fake_working_tree_commit(&sb.revs->diffopt,
+                                                   path, contents_from);
+               add_pending_object(&revs, &(sb.final->object), ":");
+       }
+       else if (contents_from)
+               die("Cannot use --contents with final commit object name");
+
+       /*
+        * If we have bottom, this will mark the ancestors of the
+        * bottom commits we would reach while traversing as
+        * uninteresting.
+        */
+       if (prepare_revision_walk(&revs))
+               die("revision walk setup failed");
+
+       if (is_null_sha1(sb.final->object.sha1)) {
+               char *buf;
+               o = sb.final->util;
+               buf = xmalloc(o->file.size + 1);
+               memcpy(buf, o->file.ptr, o->file.size + 1);
+               sb.final_buf = buf;
+               sb.final_buf_size = o->file.size;
+       }
+       else {
+               o = get_origin(&sb, sb.final, path);
+               if (fill_blob_sha1(o))
+                       die("no such path %s in %s", path, final_commit_name);
+
+               if (DIFF_OPT_TST(&sb.revs->diffopt, ALLOW_TEXTCONV) &&
+                   textconv_object(path, o->blob_sha1, (char **) &sb.final_buf,
+                                   &sb.final_buf_size))
+                       ;
+               else
+                       sb.final_buf = read_sha1_file(o->blob_sha1, &type,
+                                                     &sb.final_buf_size);
+
+               if (!sb.final_buf)
+                       die("Cannot read blob %s for path %s",
+                           sha1_to_hex(o->blob_sha1),
+                           path);
+       }
+       num_read_blob++;
+       lno = prepare_lines(&sb);
+
+       bottom = top = 0;
+       if (bottomtop)
+               prepare_blame_range(&sb, bottomtop, lno, &bottom, &top);
+       if (bottom && top && top < bottom) {
+               long tmp;
+               tmp = top; top = bottom; bottom = tmp;
+       }
+       if (bottom < 1)
+               bottom = 1;
+       if (top < 1)
+               top = lno;
+       bottom--;
+       if (lno < top || lno < bottom)
+               die("file %s has only %lu lines", path, lno);
+
+       ent = xcalloc(1, sizeof(*ent));
+       ent->lno = bottom;
+       ent->num_lines = top - bottom;
+       ent->suspect = o;
+       ent->s_lno = bottom;
+
+       sb.ent = ent;
+       sb.path = path;
+
+       read_mailmap(&mailmap, NULL);
+
+       if (!incremental)
+               setup_pager();
+
+       assign_blame(&sb, opt);
+
+       if (incremental)
+               return 0;
+
+       coalesce(&sb);
+
+       if (!(output_option & OUTPUT_PORCELAIN))
+               find_alignment(&sb, &output_option);
+
+       output(&sb, output_option);
+       free((void *)sb.final_buf);
+       for (ent = sb.ent; ent; ) {
+               struct blame_entry *e = ent->next;
+               free(ent);
+               ent = e;
+       }
+
+       if (show_stats) {
+               printf("num read blob: %d\n", num_read_blob);
+               printf("num get patch: %d\n", num_get_patch);
+               printf("num commits: %d\n", num_commits);
+       }
+       return 0;
+}
diff --git a/builtin/branch.c b/builtin/branch.c
new file mode 100644 (file)
index 0000000..87976f0
--- /dev/null
@@ -0,0 +1,712 @@
+/*
+ * Builtin "git branch"
+ *
+ * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com>
+ * Based on git-branch.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "color.h"
+#include "refs.h"
+#include "commit.h"
+#include "builtin.h"
+#include "remote.h"
+#include "parse-options.h"
+#include "branch.h"
+#include "diff.h"
+#include "revision.h"
+
+static const char * const builtin_branch_usage[] = {
+       "git branch [options] [-r | -a] [--merged | --no-merged]",
+       "git branch [options] [-l] [-f] <branchname> [<start-point>]",
+       "git branch [options] [-r] (-d | -D) <branchname>",
+       "git branch [options] (-m | -M) [<oldbranch>] <newbranch>",
+       NULL
+};
+
+#define REF_LOCAL_BRANCH    0x01
+#define REF_REMOTE_BRANCH   0x02
+
+static const char *head;
+static unsigned char head_sha1[20];
+
+static int branch_use_color = -1;
+static char branch_colors[][COLOR_MAXLEN] = {
+       GIT_COLOR_RESET,
+       GIT_COLOR_NORMAL,       /* PLAIN */
+       GIT_COLOR_RED,          /* REMOTE */
+       GIT_COLOR_NORMAL,       /* LOCAL */
+       GIT_COLOR_GREEN,        /* CURRENT */
+};
+enum color_branch {
+       BRANCH_COLOR_RESET = 0,
+       BRANCH_COLOR_PLAIN = 1,
+       BRANCH_COLOR_REMOTE = 2,
+       BRANCH_COLOR_LOCAL = 3,
+       BRANCH_COLOR_CURRENT = 4
+};
+
+static enum merge_filter {
+       NO_FILTER = 0,
+       SHOW_NOT_MERGED,
+       SHOW_MERGED
+} merge_filter;
+static unsigned char merge_filter_ref[20];
+
+static int parse_branch_color_slot(const char *var, int ofs)
+{
+       if (!strcasecmp(var+ofs, "plain"))
+               return BRANCH_COLOR_PLAIN;
+       if (!strcasecmp(var+ofs, "reset"))
+               return BRANCH_COLOR_RESET;
+       if (!strcasecmp(var+ofs, "remote"))
+               return BRANCH_COLOR_REMOTE;
+       if (!strcasecmp(var+ofs, "local"))
+               return BRANCH_COLOR_LOCAL;
+       if (!strcasecmp(var+ofs, "current"))
+               return BRANCH_COLOR_CURRENT;
+       return -1;
+}
+
+static int git_branch_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "color.branch")) {
+               branch_use_color = git_config_colorbool(var, value, -1);
+               return 0;
+       }
+       if (!prefixcmp(var, "color.branch.")) {
+               int slot = parse_branch_color_slot(var, 13);
+               if (slot < 0)
+                       return 0;
+               if (!value)
+                       return config_error_nonbool(var);
+               color_parse(value, var, branch_colors[slot]);
+               return 0;
+       }
+       return git_color_default_config(var, value, cb);
+}
+
+static const char *branch_get_color(enum color_branch ix)
+{
+       if (branch_use_color > 0)
+               return branch_colors[ix];
+       return "";
+}
+
+static int branch_merged(int kind, const char *name,
+                        struct commit *rev, struct commit *head_rev)
+{
+       /*
+        * This checks whether the merge bases of branch and HEAD (or
+        * the other branch this branch builds upon) contains the
+        * branch, which means that the branch has already been merged
+        * safely to HEAD (or the other branch).
+        */
+       struct commit *reference_rev = NULL;
+       const char *reference_name = NULL;
+       int merged;
+
+       if (kind == REF_LOCAL_BRANCH) {
+               struct branch *branch = branch_get(name);
+               unsigned char sha1[20];
+
+               if (branch &&
+                   branch->merge &&
+                   branch->merge[0] &&
+                   branch->merge[0]->dst &&
+                   (reference_name =
+                    resolve_ref(branch->merge[0]->dst, sha1, 1, NULL)) != NULL)
+                       reference_rev = lookup_commit_reference(sha1);
+       }
+       if (!reference_rev)
+               reference_rev = head_rev;
+
+       merged = in_merge_bases(rev, &reference_rev, 1);
+
+       /*
+        * After the safety valve is fully redefined to "check with
+        * upstream, if any, otherwise with HEAD", we should just
+        * return the result of the in_merge_bases() above without
+        * any of the following code, but during the transition period,
+        * a gentle reminder is in order.
+        */
+       if ((head_rev != reference_rev) &&
+           in_merge_bases(rev, &head_rev, 1) != merged) {
+               if (merged)
+                       warning("deleting branch '%s' that has been merged to\n"
+                               "         '%s', but it is not yet merged to HEAD.",
+                               name, reference_name);
+               else
+                       warning("not deleting branch '%s' that is not yet merged to\n"
+                               "         '%s', even though it is merged to HEAD.",
+                               name, reference_name);
+       }
+       return merged;
+}
+
+static int delete_branches(int argc, const char **argv, int force, int kinds)
+{
+       struct commit *rev, *head_rev = NULL;
+       unsigned char sha1[20];
+       char *name = NULL;
+       const char *fmt, *remote;
+       int i;
+       int ret = 0;
+       struct strbuf bname = STRBUF_INIT;
+
+       switch (kinds) {
+       case REF_REMOTE_BRANCH:
+               fmt = "refs/remotes/%s";
+               remote = "remote ";
+               force = 1;
+               break;
+       case REF_LOCAL_BRANCH:
+               fmt = "refs/heads/%s";
+               remote = "";
+               break;
+       default:
+               die("cannot use -a with -d");
+       }
+
+       if (!force) {
+               head_rev = lookup_commit_reference(head_sha1);
+               if (!head_rev)
+                       die("Couldn't look up commit object for HEAD");
+       }
+       for (i = 0; i < argc; i++, strbuf_release(&bname)) {
+               strbuf_branchname(&bname, argv[i]);
+               if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) {
+                       error("Cannot delete the branch '%s' "
+                             "which you are currently on.", bname.buf);
+                       ret = 1;
+                       continue;
+               }
+
+               free(name);
+
+               name = xstrdup(mkpath(fmt, bname.buf));
+               if (!resolve_ref(name, sha1, 1, NULL)) {
+                       error("%sbranch '%s' not found.",
+                                       remote, bname.buf);
+                       ret = 1;
+                       continue;
+               }
+
+               rev = lookup_commit_reference(sha1);
+               if (!rev) {
+                       error("Couldn't look up commit object for '%s'", name);
+                       ret = 1;
+                       continue;
+               }
+
+               if (!force && !branch_merged(kinds, bname.buf, rev, head_rev)) {
+                       error("The branch '%s' is not fully merged.\n"
+                             "If you are sure you want to delete it, "
+                             "run 'git branch -D %s'.", bname.buf, bname.buf);
+                       ret = 1;
+                       continue;
+               }
+
+               if (delete_ref(name, sha1, 0)) {
+                       error("Error deleting %sbranch '%s'", remote,
+                             bname.buf);
+                       ret = 1;
+               } else {
+                       struct strbuf buf = STRBUF_INIT;
+                       printf("Deleted %sbranch %s (was %s).\n", remote,
+                              bname.buf,
+                              find_unique_abbrev(sha1, DEFAULT_ABBREV));
+                       strbuf_addf(&buf, "branch.%s", bname.buf);
+                       if (git_config_rename_section(buf.buf, NULL) < 0)
+                               warning("Update of config-file failed");
+                       strbuf_release(&buf);
+               }
+       }
+
+       free(name);
+
+       return(ret);
+}
+
+struct ref_item {
+       char *name;
+       char *dest;
+       unsigned int kind, len;
+       struct commit *commit;
+};
+
+struct ref_list {
+       struct rev_info revs;
+       int index, alloc, maxwidth, verbose, abbrev;
+       struct ref_item *list;
+       struct commit_list *with_commit;
+       int kinds;
+};
+
+static char *resolve_symref(const char *src, const char *prefix)
+{
+       unsigned char sha1[20];
+       int flag;
+       const char *dst, *cp;
+
+       dst = resolve_ref(src, sha1, 0, &flag);
+       if (!(dst && (flag & REF_ISSYMREF)))
+               return NULL;
+       if (prefix && (cp = skip_prefix(dst, prefix)))
+               dst = cp;
+       return xstrdup(dst);
+}
+
+struct append_ref_cb {
+       struct ref_list *ref_list;
+       int ret;
+};
+
+static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
+{
+       struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data);
+       struct ref_list *ref_list = cb->ref_list;
+       struct ref_item *newitem;
+       struct commit *commit;
+       int kind, i;
+       const char *prefix, *orig_refname = refname;
+
+       static struct {
+               int kind;
+               const char *prefix;
+               int pfxlen;
+       } ref_kind[] = {
+               { REF_LOCAL_BRANCH, "refs/heads/", 11 },
+               { REF_REMOTE_BRANCH, "refs/remotes/", 13 },
+       };
+
+       /* Detect kind */
+       for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
+               prefix = ref_kind[i].prefix;
+               if (strncmp(refname, prefix, ref_kind[i].pfxlen))
+                       continue;
+               kind = ref_kind[i].kind;
+               refname += ref_kind[i].pfxlen;
+               break;
+       }
+       if (ARRAY_SIZE(ref_kind) <= i)
+               return 0;
+
+       /* Don't add types the caller doesn't want */
+       if ((kind & ref_list->kinds) == 0)
+               return 0;
+
+       commit = NULL;
+       if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) {
+               commit = lookup_commit_reference_gently(sha1, 1);
+               if (!commit) {
+                       cb->ret = error("branch '%s' does not point at a commit", refname);
+                       return 0;
+               }
+
+               /* Filter with with_commit if specified */
+               if (!is_descendant_of(commit, ref_list->with_commit))
+                       return 0;
+
+               if (merge_filter != NO_FILTER)
+                       add_pending_object(&ref_list->revs,
+                                          (struct object *)commit, refname);
+       }
+
+       /* Resize buffer */
+       if (ref_list->index >= ref_list->alloc) {
+               ref_list->alloc = alloc_nr(ref_list->alloc);
+               ref_list->list = xrealloc(ref_list->list,
+                               ref_list->alloc * sizeof(struct ref_item));
+       }
+
+       /* Record the new item */
+       newitem = &(ref_list->list[ref_list->index++]);
+       newitem->name = xstrdup(refname);
+       newitem->kind = kind;
+       newitem->commit = commit;
+       newitem->len = strlen(refname);
+       newitem->dest = resolve_symref(orig_refname, prefix);
+       /* adjust for "remotes/" */
+       if (newitem->kind == REF_REMOTE_BRANCH &&
+           ref_list->kinds != REF_REMOTE_BRANCH)
+               newitem->len += 8;
+       if (newitem->len > ref_list->maxwidth)
+               ref_list->maxwidth = newitem->len;
+
+       return 0;
+}
+
+static void free_ref_list(struct ref_list *ref_list)
+{
+       int i;
+
+       for (i = 0; i < ref_list->index; i++) {
+               free(ref_list->list[i].name);
+               free(ref_list->list[i].dest);
+       }
+       free(ref_list->list);
+}
+
+static int ref_cmp(const void *r1, const void *r2)
+{
+       struct ref_item *c1 = (struct ref_item *)(r1);
+       struct ref_item *c2 = (struct ref_item *)(r2);
+
+       if (c1->kind != c2->kind)
+               return c1->kind - c2->kind;
+       return strcmp(c1->name, c2->name);
+}
+
+static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
+               int show_upstream_ref)
+{
+       int ours, theirs;
+       struct branch *branch = branch_get(branch_name);
+
+       if (!stat_tracking_info(branch, &ours, &theirs)) {
+               if (branch && branch->merge && branch->merge[0]->dst &&
+                   show_upstream_ref)
+                       strbuf_addf(stat, "[%s] ",
+                           shorten_unambiguous_ref(branch->merge[0]->dst, 0));
+               return;
+       }
+
+       strbuf_addch(stat, '[');
+       if (show_upstream_ref)
+               strbuf_addf(stat, "%s: ",
+                       shorten_unambiguous_ref(branch->merge[0]->dst, 0));
+       if (!ours)
+               strbuf_addf(stat, "behind %d] ", theirs);
+       else if (!theirs)
+               strbuf_addf(stat, "ahead %d] ", ours);
+       else
+               strbuf_addf(stat, "ahead %d, behind %d] ", ours, theirs);
+}
+
+static int matches_merge_filter(struct commit *commit)
+{
+       int is_merged;
+
+       if (merge_filter == NO_FILTER)
+               return 1;
+
+       is_merged = !!(commit->object.flags & UNINTERESTING);
+       return (is_merged == (merge_filter == SHOW_MERGED));
+}
+
+static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
+                          int abbrev, int current, char *prefix)
+{
+       char c;
+       int color;
+       struct commit *commit = item->commit;
+       struct strbuf out = STRBUF_INIT, name = STRBUF_INIT;
+
+       if (!matches_merge_filter(commit))
+               return;
+
+       switch (item->kind) {
+       case REF_LOCAL_BRANCH:
+               color = BRANCH_COLOR_LOCAL;
+               break;
+       case REF_REMOTE_BRANCH:
+               color = BRANCH_COLOR_REMOTE;
+               break;
+       default:
+               color = BRANCH_COLOR_PLAIN;
+               break;
+       }
+
+       c = ' ';
+       if (current) {
+               c = '*';
+               color = BRANCH_COLOR_CURRENT;
+       }
+
+       strbuf_addf(&name, "%s%s", prefix, item->name);
+       if (verbose)
+               strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color),
+                           maxwidth, name.buf,
+                           branch_get_color(BRANCH_COLOR_RESET));
+       else
+               strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color),
+                           name.buf, branch_get_color(BRANCH_COLOR_RESET));
+
+       if (item->dest)
+               strbuf_addf(&out, " -> %s", item->dest);
+       else if (verbose) {
+               struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT;
+               const char *sub = " **** invalid ref ****";
+
+               commit = item->commit;
+               if (commit && !parse_commit(commit)) {
+                       struct pretty_print_context ctx = {0};
+                       pretty_print_commit(CMIT_FMT_ONELINE, commit,
+                                           &subject, &ctx);
+                       sub = subject.buf;
+               }
+
+               if (item->kind == REF_LOCAL_BRANCH)
+                       fill_tracking_info(&stat, item->name, verbose > 1);
+
+               strbuf_addf(&out, " %s %s%s",
+                       find_unique_abbrev(item->commit->object.sha1, abbrev),
+                       stat.buf, sub);
+               strbuf_release(&stat);
+               strbuf_release(&subject);
+       }
+       printf("%s\n", out.buf);
+       strbuf_release(&name);
+       strbuf_release(&out);
+}
+
+static int calc_maxwidth(struct ref_list *refs)
+{
+       int i, w = 0;
+       for (i = 0; i < refs->index; i++) {
+               if (!matches_merge_filter(refs->list[i].commit))
+                       continue;
+               if (refs->list[i].len > w)
+                       w = refs->list[i].len;
+       }
+       return w;
+}
+
+
+static void show_detached(struct ref_list *ref_list)
+{
+       struct commit *head_commit = lookup_commit_reference_gently(head_sha1, 1);
+
+       if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) {
+               struct ref_item item;
+               item.name = xstrdup("(no branch)");
+               item.len = strlen(item.name);
+               item.kind = REF_LOCAL_BRANCH;
+               item.dest = NULL;
+               item.commit = head_commit;
+               if (item.len > ref_list->maxwidth)
+                       ref_list->maxwidth = item.len;
+               print_ref_item(&item, ref_list->maxwidth, ref_list->verbose, ref_list->abbrev, 1, "");
+               free(item.name);
+       }
+}
+
+static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit)
+{
+       int i;
+       struct append_ref_cb cb;
+       struct ref_list ref_list;
+
+       memset(&ref_list, 0, sizeof(ref_list));
+       ref_list.kinds = kinds;
+       ref_list.verbose = verbose;
+       ref_list.abbrev = abbrev;
+       ref_list.with_commit = with_commit;
+       if (merge_filter != NO_FILTER)
+               init_revisions(&ref_list.revs, NULL);
+       cb.ref_list = &ref_list;
+       cb.ret = 0;
+       for_each_rawref(append_ref, &cb);
+       if (merge_filter != NO_FILTER) {
+               struct commit *filter;
+               filter = lookup_commit_reference_gently(merge_filter_ref, 0);
+               filter->object.flags |= UNINTERESTING;
+               add_pending_object(&ref_list.revs,
+                                  (struct object *) filter, "");
+               ref_list.revs.limited = 1;
+               prepare_revision_walk(&ref_list.revs);
+               if (verbose)
+                       ref_list.maxwidth = calc_maxwidth(&ref_list);
+       }
+
+       qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
+
+       detached = (detached && (kinds & REF_LOCAL_BRANCH));
+       if (detached)
+               show_detached(&ref_list);
+
+       for (i = 0; i < ref_list.index; i++) {
+               int current = !detached &&
+                       (ref_list.list[i].kind == REF_LOCAL_BRANCH) &&
+                       !strcmp(ref_list.list[i].name, head);
+               char *prefix = (kinds != REF_REMOTE_BRANCH &&
+                               ref_list.list[i].kind == REF_REMOTE_BRANCH)
+                               ? "remotes/" : "";
+               print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose,
+                              abbrev, current, prefix);
+       }
+
+       free_ref_list(&ref_list);
+
+       if (cb.ret)
+               error("some refs could not be read");
+
+       return cb.ret;
+}
+
+static void rename_branch(const char *oldname, const char *newname, int force)
+{
+       struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
+       unsigned char sha1[20];
+       struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
+       int recovery = 0;
+
+       if (!oldname)
+               die("cannot rename the current branch while not on any.");
+
+       if (strbuf_check_branch_ref(&oldref, oldname)) {
+               /*
+                * Bad name --- this could be an attempt to rename a
+                * ref that we used to allow to be created by accident.
+                */
+               if (resolve_ref(oldref.buf, sha1, 1, NULL))
+                       recovery = 1;
+               else
+                       die("Invalid branch name: '%s'", oldname);
+       }
+
+       if (strbuf_check_branch_ref(&newref, newname))
+               die("Invalid branch name: '%s'", newname);
+
+       if (resolve_ref(newref.buf, sha1, 1, NULL) && !force)
+               die("A branch named '%s' already exists.", newref.buf + 11);
+
+       strbuf_addf(&logmsg, "Branch: renamed %s to %s",
+                oldref.buf, newref.buf);
+
+       if (rename_ref(oldref.buf, newref.buf, logmsg.buf))
+               die("Branch rename failed");
+       strbuf_release(&logmsg);
+
+       if (recovery)
+               warning("Renamed a misnamed branch '%s' away", oldref.buf + 11);
+
+       /* no need to pass logmsg here as HEAD didn't really move */
+       if (!strcmp(oldname, head) && create_symref("HEAD", newref.buf, NULL))
+               die("Branch renamed to %s, but HEAD is not updated!", newname);
+
+       strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11);
+       strbuf_release(&oldref);
+       strbuf_addf(&newsection, "branch.%s", newref.buf + 11);
+       strbuf_release(&newref);
+       if (git_config_rename_section(oldsection.buf, newsection.buf) < 0)
+               die("Branch is renamed, but update of config-file failed");
+       strbuf_release(&oldsection);
+       strbuf_release(&newsection);
+}
+
+static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset)
+{
+       merge_filter = ((opt->long_name[0] == 'n')
+                       ? SHOW_NOT_MERGED
+                       : SHOW_MERGED);
+       if (unset)
+               merge_filter = SHOW_NOT_MERGED; /* b/c for --no-merged */
+       if (!arg)
+               arg = "HEAD";
+       if (get_sha1(arg, merge_filter_ref))
+               die("malformed object name %s", arg);
+       return 0;
+}
+
+int cmd_branch(int argc, const char **argv, const char *prefix)
+{
+       int delete = 0, rename = 0, force_create = 0;
+       int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;
+       int reflog = 0;
+       enum branch_track track;
+       int kinds = REF_LOCAL_BRANCH;
+       struct commit_list *with_commit = NULL;
+
+       struct option options[] = {
+               OPT_GROUP("Generic options"),
+               OPT__VERBOSE(&verbose),
+               OPT_SET_INT('t', "track",  &track, "set up tracking mode (see git-pull(1))",
+                       BRANCH_TRACK_EXPLICIT),
+               OPT_SET_INT( 0, "set-upstream",  &track, "change upstream info",
+                       BRANCH_TRACK_OVERRIDE),
+               OPT__COLOR(&branch_use_color, "use colored output"),
+               OPT_SET_INT('r', NULL,     &kinds, "act on remote-tracking branches",
+                       REF_REMOTE_BRANCH),
+               {
+                       OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
+                       "print only branches that contain the commit",
+                       PARSE_OPT_LASTARG_DEFAULT,
+                       parse_opt_with_commit, (intptr_t)"HEAD",
+               },
+               {
+                       OPTION_CALLBACK, 0, "with", &with_commit, "commit",
+                       "print only branches that contain the commit",
+                       PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
+                       parse_opt_with_commit, (intptr_t) "HEAD",
+               },
+               OPT__ABBREV(&abbrev),
+
+               OPT_GROUP("Specific git-branch actions:"),
+               OPT_SET_INT('a', NULL, &kinds, "list both remote-tracking and local branches",
+                       REF_REMOTE_BRANCH | REF_LOCAL_BRANCH),
+               OPT_BIT('d', NULL, &delete, "delete fully merged branch", 1),
+               OPT_BIT('D', NULL, &delete, "delete branch (even if not merged)", 2),
+               OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1),
+               OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2),
+               OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"),
+               OPT_BOOLEAN('f', "force", &force_create, "force creation (when already exists)"),
+               {
+                       OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
+                       "commit", "print only not merged branches",
+                       PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
+                       opt_parse_merge_filter, (intptr_t) "HEAD",
+               },
+               {
+                       OPTION_CALLBACK, 0, "merged", &merge_filter_ref,
+                       "commit", "print only merged branches",
+                       PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
+                       opt_parse_merge_filter, (intptr_t) "HEAD",
+               },
+               OPT_END(),
+       };
+
+       git_config(git_branch_config, NULL);
+
+       if (branch_use_color == -1)
+               branch_use_color = git_use_color_default;
+
+       track = git_branch_track;
+
+       head = resolve_ref("HEAD", head_sha1, 0, NULL);
+       if (!head)
+               die("Failed to resolve HEAD as a valid ref.");
+       head = xstrdup(head);
+       if (!strcmp(head, "HEAD")) {
+               detached = 1;
+       } else {
+               if (prefixcmp(head, "refs/heads/"))
+                       die("HEAD not found below refs/heads!");
+               head += 11;
+       }
+       hashcpy(merge_filter_ref, head_sha1);
+
+       argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
+                            0);
+       if (!!delete + !!rename + !!force_create > 1)
+               usage_with_options(builtin_branch_usage, options);
+
+       if (delete)
+               return delete_branches(argc, argv, delete > 1, kinds);
+       else if (argc == 0)
+               return print_ref_list(kinds, detached, verbose, abbrev, with_commit);
+       else if (rename && (argc == 1))
+               rename_branch(head, argv[0], rename > 1);
+       else if (rename && (argc == 2))
+               rename_branch(argv[0], argv[1], rename > 1);
+       else if (argc <= 2) {
+               if (kinds != REF_LOCAL_BRANCH)
+                       die("-a and -r options to 'git branch' do not make sense with a branch name");
+               create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
+                             force_create, reflog, track);
+       } else
+               usage_with_options(builtin_branch_usage, options);
+
+       return 0;
+}
diff --git a/builtin/bundle.c b/builtin/bundle.c
new file mode 100644 (file)
index 0000000..80649ba
--- /dev/null
@@ -0,0 +1,65 @@
+#include "builtin.h"
+#include "cache.h"
+#include "bundle.h"
+
+/*
+ * Basic handler for bundle files to connect repositories via sneakernet.
+ * Invocation must include action.
+ * This function can create a bundle or provide information on an existing
+ * bundle supporting "fetch", "pull", and "ls-remote".
+ */
+
+static const char builtin_bundle_usage[] =
+  "git bundle create <file> <git-rev-list args>\n"
+  "   or: git bundle verify <file>\n"
+  "   or: git bundle list-heads <file> [refname...]\n"
+  "   or: git bundle unbundle <file> [refname...]";
+
+int cmd_bundle(int argc, const char **argv, const char *prefix)
+{
+       struct bundle_header header;
+       const char *cmd, *bundle_file;
+       int bundle_fd = -1;
+       char buffer[PATH_MAX];
+
+       if (argc < 3)
+               usage(builtin_bundle_usage);
+
+       cmd = argv[1];
+       bundle_file = argv[2];
+       argc -= 2;
+       argv += 2;
+
+       if (prefix && bundle_file[0] != '/') {
+               snprintf(buffer, sizeof(buffer), "%s/%s", prefix, bundle_file);
+               bundle_file = buffer;
+       }
+
+       memset(&header, 0, sizeof(header));
+       if (strcmp(cmd, "create") && (bundle_fd =
+                               read_bundle_header(bundle_file, &header)) < 0)
+               return 1;
+
+       if (!strcmp(cmd, "verify")) {
+               close(bundle_fd);
+               if (verify_bundle(&header, 1))
+                       return 1;
+               fprintf(stderr, "%s is okay\n", bundle_file);
+               return 0;
+       }
+       if (!strcmp(cmd, "list-heads")) {
+               close(bundle_fd);
+               return !!list_bundle_refs(&header, argc, argv);
+       }
+       if (!strcmp(cmd, "create")) {
+               if (!startup_info->have_repository)
+                       die("Need a repository to create a bundle.");
+               return !!create_bundle(&header, bundle_file, argc, argv);
+       } else if (!strcmp(cmd, "unbundle")) {
+               if (!startup_info->have_repository)
+                       die("Need a repository to unbundle.");
+               return !!unbundle(&header, bundle_fd) ||
+                       list_bundle_refs(&header, argc, argv);
+       } else
+               usage(builtin_bundle_usage);
+}
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
new file mode 100644 (file)
index 0000000..76ec3fe
--- /dev/null
@@ -0,0 +1,290 @@
+/*
+ * 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"
+#include "parse-options.h"
+#include "diff.h"
+#include "userdiff.h"
+
+#define BATCH 1
+#define BATCH_CHECK 2
+
+static void 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.
+                        */
+                       write_or_die(1, 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 */
+                                               write_or_die(1, tagger,
+                                                            cp - tagger);
+                                               break;
+                                       }
+                                       while (sp < cp &&
+                                              !('0' <= *sp && *sp <= '9'))
+                                               sp++;
+                                       write_or_die(1, tagger, sp - tagger);
+                                       date = strtoul(sp, &ep, 10);
+                                       tz = strtol(ep, NULL, 10);
+                                       sp = show_date(date, tz, 0);
+                                       write_or_die(1, 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)
+               write_or_die(1, cp, endp - cp);
+}
+
+static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
+{
+       unsigned char sha1[20];
+       enum object_type type;
+       char *buf;
+       unsigned long size;
+       struct object_context obj_context;
+
+       if (get_sha1_with_context(obj_name, sha1, &obj_context))
+               die("Not a valid object name %s", obj_name);
+
+       buf = NULL;
+       switch (opt) {
+       case 't':
+               type = sha1_object_info(sha1, NULL);
+               if (type > 0) {
+                       printf("%s\n", typename(type));
+                       return 0;
+               }
+               break;
+
+       case 's':
+               type = sha1_object_info(sha1, &size);
+               if (type > 0) {
+                       printf("%lu\n", size);
+                       return 0;
+               }
+               break;
+
+       case 'e':
+               return !has_sha1_file(sha1);
+
+       case 'p':
+               type = sha1_object_info(sha1, NULL);
+               if (type < 0)
+                       die("Not a valid object name %s", obj_name);
+
+               /* custom pretty-print here */
+               if (type == OBJ_TREE) {
+                       const char *ls_args[3] = { NULL };
+                       ls_args[0] =  "ls-tree";
+                       ls_args[1] =  obj_name;
+                       return cmd_ls_tree(2, ls_args, NULL);
+               }
+
+               buf = read_sha1_file(sha1, &type, &size);
+               if (!buf)
+                       die("Cannot read object %s", obj_name);
+               if (type == OBJ_TAG) {
+                       pprint_tag(sha1, buf, size);
+                       return 0;
+               }
+
+               /* otherwise just spit out the data */
+               break;
+
+       case 'c':
+               if (!obj_context.path[0])
+                       die("git cat-file --textconv %s: <object> must be <sha1:path>",
+                           obj_name);
+
+               if (!textconv_object(obj_context.path, sha1, &buf, &size))
+                       die("git cat-file --textconv: unable to run textconv on %s",
+                           obj_name);
+               break;
+
+       case 0:
+               buf = read_object_with_reference(sha1, exp_type, &size, NULL);
+               break;
+
+       default:
+               die("git cat-file: unknown option: %s", exp_type);
+       }
+
+       if (!buf)
+               die("git cat-file %s: bad file", obj_name);
+
+       write_or_die(1, buf, size);
+       return 0;
+}
+
+static int batch_one_object(const char *obj_name, int print_contents)
+{
+       unsigned char sha1[20];
+       enum object_type type = 0;
+       unsigned long size;
+       void *contents = contents;
+
+       if (!obj_name)
+          return 1;
+
+       if (get_sha1(obj_name, sha1)) {
+               printf("%s missing\n", obj_name);
+               fflush(stdout);
+               return 0;
+       }
+
+       if (print_contents == BATCH)
+               contents = read_sha1_file(sha1, &type, &size);
+       else
+               type = sha1_object_info(sha1, &size);
+
+       if (type <= 0) {
+               printf("%s missing\n", obj_name);
+               fflush(stdout);
+               return 0;
+       }
+
+       printf("%s %s %lu\n", sha1_to_hex(sha1), typename(type), size);
+       fflush(stdout);
+
+       if (print_contents == BATCH) {
+               write_or_die(1, contents, size);
+               printf("\n");
+               fflush(stdout);
+               free(contents);
+       }
+
+       return 0;
+}
+
+static int batch_objects(int print_contents)
+{
+       struct strbuf buf = STRBUF_INIT;
+
+       while (strbuf_getline(&buf, stdin, '\n') != EOF) {
+               int error = batch_one_object(buf.buf, print_contents);
+               if (error)
+                       return error;
+       }
+
+       return 0;
+}
+
+static const char * const cat_file_usage[] = {
+       "git cat-file (-t|-s|-e|-p|<type>|--textconv) <object>",
+       "git cat-file (--batch|--batch-check) < <list_of_objects>",
+       NULL
+};
+
+static int git_cat_file_config(const char *var, const char *value, void *cb)
+{
+       switch (userdiff_config(var, value)) {
+       case 0:
+               break;
+       case -1:
+               return -1;
+       default:
+               return 0;
+       }
+
+       return git_default_config(var, value, cb);
+}
+
+int cmd_cat_file(int argc, const char **argv, const char *prefix)
+{
+       int opt = 0, batch = 0;
+       const char *exp_type = NULL, *obj_name = NULL;
+
+       const struct option options[] = {
+               OPT_GROUP("<type> can be one of: blob, tree, commit, tag"),
+               OPT_SET_INT('t', NULL, &opt, "show object type", 't'),
+               OPT_SET_INT('s', NULL, &opt, "show object size", 's'),
+               OPT_SET_INT('e', NULL, &opt,
+                           "exit with zero when there's no error", 'e'),
+               OPT_SET_INT('p', NULL, &opt, "pretty-print object's content", 'p'),
+               OPT_SET_INT(0, "textconv", &opt,
+                           "for blob objects, run textconv on object's content", 'c'),
+               OPT_SET_INT(0, "batch", &batch,
+                           "show info and content of objects fed from the standard input",
+                           BATCH),
+               OPT_SET_INT(0, "batch-check", &batch,
+                           "show info about objects fed from the standard input",
+                           BATCH_CHECK),
+               OPT_END()
+       };
+
+       git_config(git_cat_file_config, NULL);
+
+       if (argc != 3 && argc != 2)
+               usage_with_options(cat_file_usage, options);
+
+       argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0);
+
+       if (opt) {
+               if (argc == 1)
+                       obj_name = argv[0];
+               else
+                       usage_with_options(cat_file_usage, options);
+       }
+       if (!opt && !batch) {
+               if (argc == 2) {
+                       exp_type = argv[0];
+                       obj_name = argv[1];
+               } else
+                       usage_with_options(cat_file_usage, options);
+       }
+       if (batch && (opt || argc)) {
+               usage_with_options(cat_file_usage, options);
+       }
+
+       if (batch)
+               return batch_objects(batch);
+
+       return cat_one_file(opt, exp_type, obj_name);
+}
diff --git a/builtin/check-attr.c b/builtin/check-attr.c
new file mode 100644 (file)
index 0000000..3016d29
--- /dev/null
@@ -0,0 +1,123 @@
+#include "builtin.h"
+#include "cache.h"
+#include "attr.h"
+#include "quote.h"
+#include "parse-options.h"
+
+static int stdin_paths;
+static const char * const check_attr_usage[] = {
+"git check-attr attr... [--] pathname...",
+"git check-attr --stdin attr... < <list-of-paths>",
+NULL
+};
+
+static int null_term_line;
+
+static const struct option check_attr_options[] = {
+       OPT_BOOLEAN(0 , "stdin", &stdin_paths, "read file names from stdin"),
+       OPT_BOOLEAN('z', NULL, &null_term_line,
+               "input paths are terminated by a null character"),
+       OPT_END()
+};
+
+static void check_attr(int cnt, struct git_attr_check *check,
+       const char** name, const char *file)
+{
+       int j;
+       if (git_checkattr(file, cnt, check))
+               die("git_checkattr died");
+       for (j = 0; j < cnt; j++) {
+               const char *value = check[j].value;
+
+               if (ATTR_TRUE(value))
+                       value = "set";
+               else if (ATTR_FALSE(value))
+                       value = "unset";
+               else if (ATTR_UNSET(value))
+                       value = "unspecified";
+
+               quote_c_style(file, NULL, stdout, 0);
+               printf(": %s: %s\n", name[j], value);
+       }
+}
+
+static void check_attr_stdin_paths(int cnt, struct git_attr_check *check,
+       const char** name)
+{
+       struct strbuf buf, nbuf;
+       int line_termination = null_term_line ? 0 : '\n';
+
+       strbuf_init(&buf, 0);
+       strbuf_init(&nbuf, 0);
+       while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
+               if (line_termination && buf.buf[0] == '"') {
+                       strbuf_reset(&nbuf);
+                       if (unquote_c_style(&nbuf, buf.buf, NULL))
+                               die("line is badly quoted");
+                       strbuf_swap(&buf, &nbuf);
+               }
+               check_attr(cnt, check, name, buf.buf);
+               maybe_flush_or_die(stdout, "attribute to stdout");
+       }
+       strbuf_release(&buf);
+       strbuf_release(&nbuf);
+}
+
+int cmd_check_attr(int argc, const char **argv, const char *prefix)
+{
+       struct git_attr_check *check;
+       int cnt, i, doubledash;
+       const char *errstr = NULL;
+
+       argc = parse_options(argc, argv, prefix, check_attr_options,
+                            check_attr_usage, PARSE_OPT_KEEP_DASHDASH);
+       if (!argc)
+               usage_with_options(check_attr_usage, check_attr_options);
+
+       if (read_cache() < 0) {
+               die("invalid cache");
+       }
+
+       doubledash = -1;
+       for (i = 0; doubledash < 0 && i < argc; i++) {
+               if (!strcmp(argv[i], "--"))
+                       doubledash = i;
+       }
+
+       /* If there is no double dash, we handle only one attribute */
+       if (doubledash < 0) {
+               cnt = 1;
+               doubledash = 0;
+       } else
+               cnt = doubledash;
+       doubledash++;
+
+       if (cnt <= 0)
+               errstr = "No attribute specified";
+       else if (stdin_paths && doubledash < argc)
+               errstr = "Can't specify files with --stdin";
+       if (errstr) {
+               error("%s", errstr);
+               usage_with_options(check_attr_usage, check_attr_options);
+       }
+
+       check = xcalloc(cnt, sizeof(*check));
+       for (i = 0; i < cnt; i++) {
+               const char *name;
+               struct git_attr *a;
+               name = argv[i];
+               a = git_attr(name);
+               if (!a)
+                       return error("%s: not a valid attribute name", name);
+               check[i].attr = a;
+       }
+
+       if (stdin_paths)
+               check_attr_stdin_paths(cnt, check, argv);
+       else {
+               for (i = doubledash; i < argc; i++)
+                       check_attr(cnt, check, argv, argv[i]);
+               maybe_flush_or_die(stdout, "attribute to stdout");
+       }
+       return 0;
+}
diff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c
new file mode 100644 (file)
index 0000000..ae3f281
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * GIT - The information manager from hell
+ */
+
+#include "cache.h"
+#include "refs.h"
+#include "builtin.h"
+#include "strbuf.h"
+
+static const char builtin_check_ref_format_usage[] =
+"git check-ref-format [--print] <refname>\n"
+"   or: git check-ref-format --branch <branchname-shorthand>";
+
+/*
+ * Replace each run of adjacent slashes in src with a single slash,
+ * and write the result to dst.
+ *
+ * This function is similar to normalize_path_copy(), but stripped down
+ * to meet check_ref_format's simpler needs.
+ */
+static void collapse_slashes(char *dst, const char *src)
+{
+       char ch;
+       char prev = '\0';
+
+       while ((ch = *src++) != '\0') {
+               if (prev == '/' && ch == prev)
+                       continue;
+
+               *dst++ = ch;
+               prev = ch;
+       }
+       *dst = '\0';
+}
+
+static int check_ref_format_branch(const char *arg)
+{
+       struct strbuf sb = STRBUF_INIT;
+       int nongit;
+
+       setup_git_directory_gently(&nongit);
+       if (strbuf_check_branch_ref(&sb, arg))
+               die("'%s' is not a valid branch name", arg);
+       printf("%s\n", sb.buf + 11);
+       return 0;
+}
+
+static int check_ref_format_print(const char *arg)
+{
+       char *refname = xmalloc(strlen(arg) + 1);
+
+       if (check_ref_format(arg))
+               return 1;
+       collapse_slashes(refname, arg);
+       printf("%s\n", refname);
+       return 0;
+}
+
+int cmd_check_ref_format(int argc, const char **argv, const char *prefix)
+{
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage(builtin_check_ref_format_usage);
+
+       if (argc == 3 && !strcmp(argv[1], "--branch"))
+               return check_ref_format_branch(argv[2]);
+       if (argc == 3 && !strcmp(argv[1], "--print"))
+               return check_ref_format_print(argv[2]);
+       if (argc != 2)
+               usage(builtin_check_ref_format_usage);
+       return !!check_ref_format(argv[1]);
+}
diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c
new file mode 100644 (file)
index 0000000..a7a5ee1
--- /dev/null
@@ -0,0 +1,315 @@
+/*
+ * 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 "builtin.h"
+#include "cache.h"
+#include "quote.h"
+#include "cache-tree.h"
+#include "parse-options.h"
+
+#define CHECKOUT_ALL 4
+static int line_termination = '\n';
+static int checkout_stage; /* default to checkout stage0 */
+static int to_tempfile;
+static char topath[4][PATH_MAX + 1];
+
+static struct checkout state;
+
+static void write_tempfile_record(const char *name, int prefix_length)
+{
+       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(name + prefix_length, stdout, line_termination);
+
+       for (i = 0; i < 4; i++) {
+               topath[i][0] = 0;
+       }
+}
+
+static int checkout_file(const char *name, int prefix_length)
+{
+       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, prefix_length);
+               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 void checkout_all(const char *prefix, int prefix_length)
+{
+       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, prefix_length);
+               }
+               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, prefix_length);
+       if (errs)
+               /* we have already done our error reporting.
+                * exit with the same code as die().
+                */
+               exit(128);
+}
+
+static const char * const builtin_checkout_index_usage[] = {
+       "git checkout-index [options] [--] <file>...",
+       NULL
+};
+
+static struct lock_file lock_file;
+
+static int option_parse_u(const struct option *opt,
+                             const char *arg, int unset)
+{
+       int *newfd = opt->value;
+
+       state.refresh_cache = 1;
+       if (*newfd < 0)
+               *newfd = hold_locked_index(&lock_file, 1);
+       return 0;
+}
+
+static int option_parse_z(const struct option *opt,
+                         const char *arg, int unset)
+{
+       if (unset)
+               line_termination = '\n';
+       else
+               line_termination = 0;
+       return 0;
+}
+
+static int option_parse_prefix(const struct option *opt,
+                              const char *arg, int unset)
+{
+       state.base_dir = arg;
+       state.base_dir_len = strlen(arg);
+       return 0;
+}
+
+static int option_parse_stage(const struct option *opt,
+                             const char *arg, int unset)
+{
+       if (!strcmp(arg, "all")) {
+               to_tempfile = 1;
+               checkout_stage = CHECKOUT_ALL;
+       } else {
+               int ch = arg[0];
+               if ('1' <= ch && ch <= '3')
+                       checkout_stage = arg[0] - '0';
+               else
+                       die("stage should be between 1 and 3 or all");
+       }
+       return 0;
+}
+
+int cmd_checkout_index(int argc, const char **argv, const char *prefix)
+{
+       int i;
+       int newfd = -1;
+       int all = 0;
+       int read_from_stdin = 0;
+       int prefix_length;
+       int force = 0, quiet = 0, not_new = 0;
+       struct option builtin_checkout_index_options[] = {
+               OPT_BOOLEAN('a', "all", &all,
+                       "checks out all files in the index"),
+               OPT_BOOLEAN('f', "force", &force,
+                       "forces overwrite of existing files"),
+               OPT__QUIET(&quiet),
+               OPT_BOOLEAN('n', "no-create", &not_new,
+                       "don't checkout new files"),
+               { OPTION_CALLBACK, 'u', "index", &newfd, NULL,
+                       "update stat information in the index file",
+                       PARSE_OPT_NOARG, option_parse_u },
+               { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
+                       "paths are separated with NUL character",
+                       PARSE_OPT_NOARG, option_parse_z },
+               OPT_BOOLEAN(0, "stdin", &read_from_stdin,
+                       "read list of paths from the standard input"),
+               OPT_BOOLEAN(0, "temp", &to_tempfile,
+                       "write the content to temporary files"),
+               OPT_CALLBACK(0, "prefix", NULL, "string",
+                       "when creating files, prepend <string>",
+                       option_parse_prefix),
+               OPT_CALLBACK(0, "stage", NULL, NULL,
+                       "copy out the files from named stage",
+                       option_parse_stage),
+               OPT_END()
+       };
+
+       git_config(git_default_config, NULL);
+       state.base_dir = "";
+       prefix_length = prefix ? strlen(prefix) : 0;
+
+       if (read_cache() < 0) {
+               die("invalid cache");
+       }
+
+       argc = parse_options(argc, argv, prefix, builtin_checkout_index_options,
+                       builtin_checkout_index_usage, 0);
+       state.force = force;
+       state.quiet = quiet;
+       state.not_new = not_new;
+
+       if (state.base_dir_len || to_tempfile) {
+               /* when --prefix is specified we do not
+                * want to update cache.
+                */
+               if (state.refresh_cache) {
+                       rollback_lock_file(&lock_file);
+                       newfd = -1;
+               }
+               state.refresh_cache = 0;
+       }
+
+       /* Check out named files first */
+       for (i = 0; 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, prefix_length);
+               if (p < arg || p > arg + strlen(arg))
+                       free((char *)p);
+       }
+
+       if (read_from_stdin) {
+               struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
+
+               if (all)
+                       die("git checkout-index: don't mix '--all' and '--stdin'");
+
+               while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
+                       const char *p;
+                       if (line_termination && buf.buf[0] == '"') {
+                               strbuf_reset(&nbuf);
+                               if (unquote_c_style(&nbuf, buf.buf, NULL))
+                                       die("line is badly quoted");
+                               strbuf_swap(&buf, &nbuf);
+                       }
+                       p = prefix_path(prefix, prefix_length, buf.buf);
+                       checkout_file(p, prefix_length);
+                       if (p < buf.buf || p > buf.buf + buf.len)
+                               free((char *)p);
+               }
+               strbuf_release(&nbuf);
+               strbuf_release(&buf);
+       }
+
+       if (all)
+               checkout_all(prefix, prefix_length);
+
+       if (0 <= newfd &&
+           (write_cache(newfd, active_cache, active_nr) ||
+            commit_locked_index(&lock_file)))
+               die("Unable to write new index file");
+       return 0;
+}
diff --git a/builtin/checkout.c b/builtin/checkout.c
new file mode 100644 (file)
index 0000000..560eae1
--- /dev/null
@@ -0,0 +1,919 @@
+#include "cache.h"
+#include "builtin.h"
+#include "parse-options.h"
+#include "refs.h"
+#include "commit.h"
+#include "tree.h"
+#include "tree-walk.h"
+#include "cache-tree.h"
+#include "unpack-trees.h"
+#include "dir.h"
+#include "run-command.h"
+#include "merge-recursive.h"
+#include "branch.h"
+#include "diff.h"
+#include "revision.h"
+#include "remote.h"
+#include "blob.h"
+#include "xdiff-interface.h"
+#include "ll-merge.h"
+#include "resolve-undo.h"
+#include "submodule.h"
+
+static const char * const checkout_usage[] = {
+       "git checkout [options] <branch>",
+       "git checkout [options] [<branch>] -- <file>...",
+       NULL,
+};
+
+struct checkout_opts {
+       int quiet;
+       int merge;
+       int force;
+       int writeout_stage;
+       int writeout_error;
+
+       /* not set by parse_options */
+       int branch_exists;
+
+       const char *new_branch;
+       const char *new_branch_force;
+       const char *new_orphan_branch;
+       int new_branch_log;
+       enum branch_track track;
+       struct diff_options diff_options;
+};
+
+static int post_checkout_hook(struct commit *old, struct commit *new,
+                             int changed)
+{
+       return run_hook(NULL, "post-checkout",
+                       sha1_to_hex(old ? old->object.sha1 : null_sha1),
+                       sha1_to_hex(new ? new->object.sha1 : null_sha1),
+                       changed ? "1" : "0", NULL);
+       /* "new" can be NULL when checking out from the index before
+          a commit exists. */
+
+}
+
+static int update_some(const unsigned char *sha1, const char *base, int baselen,
+               const char *pathname, unsigned mode, int stage, void *context)
+{
+       int len;
+       struct cache_entry *ce;
+
+       if (S_ISDIR(mode))
+               return READ_TREE_RECURSIVE;
+
+       len = baselen + strlen(pathname);
+       ce = xcalloc(1, cache_entry_size(len));
+       hashcpy(ce->sha1, sha1);
+       memcpy(ce->name, base, baselen);
+       memcpy(ce->name + baselen, pathname, len - baselen);
+       ce->ce_flags = create_ce_flags(len, 0);
+       ce->ce_mode = create_ce_mode(mode);
+       add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
+       return 0;
+}
+
+static int read_tree_some(struct tree *tree, const char **pathspec)
+{
+       read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL);
+
+       /* update the index with the given tree's info
+        * for all args, expanding wildcards, and exit
+        * with any non-zero return code.
+        */
+       return 0;
+}
+
+static int skip_same_name(struct cache_entry *ce, int pos)
+{
+       while (++pos < active_nr &&
+              !strcmp(active_cache[pos]->name, ce->name))
+               ; /* skip */
+       return pos;
+}
+
+static int check_stage(int stage, struct cache_entry *ce, int pos)
+{
+       while (pos < active_nr &&
+              !strcmp(active_cache[pos]->name, ce->name)) {
+               if (ce_stage(active_cache[pos]) == stage)
+                       return 0;
+               pos++;
+       }
+       return error("path '%s' does not have %s version",
+                    ce->name,
+                    (stage == 2) ? "our" : "their");
+}
+
+static int check_all_stages(struct cache_entry *ce, int pos)
+{
+       if (ce_stage(ce) != 1 ||
+           active_nr <= pos + 2 ||
+           strcmp(active_cache[pos+1]->name, ce->name) ||
+           ce_stage(active_cache[pos+1]) != 2 ||
+           strcmp(active_cache[pos+2]->name, ce->name) ||
+           ce_stage(active_cache[pos+2]) != 3)
+               return error("path '%s' does not have all three versions",
+                            ce->name);
+       return 0;
+}
+
+static int checkout_stage(int stage, struct cache_entry *ce, int pos,
+                         struct checkout *state)
+{
+       while (pos < active_nr &&
+              !strcmp(active_cache[pos]->name, ce->name)) {
+               if (ce_stage(active_cache[pos]) == stage)
+                       return checkout_entry(active_cache[pos], state, NULL);
+               pos++;
+       }
+       return error("path '%s' does not have %s version",
+                    ce->name,
+                    (stage == 2) ? "our" : "their");
+}
+
+static int checkout_merged(int pos, struct checkout *state)
+{
+       struct cache_entry *ce = active_cache[pos];
+       const char *path = ce->name;
+       mmfile_t ancestor, ours, theirs;
+       int status;
+       unsigned char sha1[20];
+       mmbuffer_t result_buf;
+
+       if (ce_stage(ce) != 1 ||
+           active_nr <= pos + 2 ||
+           strcmp(active_cache[pos+1]->name, path) ||
+           ce_stage(active_cache[pos+1]) != 2 ||
+           strcmp(active_cache[pos+2]->name, path) ||
+           ce_stage(active_cache[pos+2]) != 3)
+               return error("path '%s' does not have all 3 versions", path);
+
+       read_mmblob(&ancestor, active_cache[pos]->sha1);
+       read_mmblob(&ours, active_cache[pos+1]->sha1);
+       read_mmblob(&theirs, active_cache[pos+2]->sha1);
+
+       /*
+        * NEEDSWORK: re-create conflicts from merges with
+        * merge.renormalize set, too
+        */
+       status = ll_merge(&result_buf, path, &ancestor, "base",
+                         &ours, "ours", &theirs, "theirs", 0);
+       free(ancestor.ptr);
+       free(ours.ptr);
+       free(theirs.ptr);
+       if (status < 0 || !result_buf.ptr) {
+               free(result_buf.ptr);
+               return error("path '%s': cannot merge", path);
+       }
+
+       /*
+        * NEEDSWORK:
+        * There is absolutely no reason to write this as a blob object
+        * and create a phony cache entry just to leak.  This hack is
+        * primarily to get to the write_entry() machinery that massages
+        * the contents to work-tree format and writes out which only
+        * allows it for a cache entry.  The code in write_entry() needs
+        * to be refactored to allow us to feed a <buffer, size, mode>
+        * instead of a cache entry.  Such a refactoring would help
+        * merge_recursive as well (it also writes the merge result to the
+        * object database even when it may contain conflicts).
+        */
+       if (write_sha1_file(result_buf.ptr, result_buf.size,
+                           blob_type, sha1))
+               die("Unable to add merge result for '%s'", path);
+       ce = make_cache_entry(create_ce_mode(active_cache[pos+1]->ce_mode),
+                             sha1,
+                             path, 2, 0);
+       if (!ce)
+               die("make_cache_entry failed for path '%s'", path);
+       status = checkout_entry(ce, state, NULL);
+       return status;
+}
+
+static int checkout_paths(struct tree *source_tree, const char **pathspec,
+                         struct checkout_opts *opts)
+{
+       int pos;
+       struct checkout state;
+       static char *ps_matched;
+       unsigned char rev[20];
+       int flag;
+       struct commit *head;
+       int errs = 0;
+       int stage = opts->writeout_stage;
+       int merge = opts->merge;
+       int newfd;
+       struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+       newfd = hold_locked_index(lock_file, 1);
+       if (read_cache_preload(pathspec) < 0)
+               return error("corrupt index file");
+
+       if (source_tree)
+               read_tree_some(source_tree, pathspec);
+
+       for (pos = 0; pathspec[pos]; pos++)
+               ;
+       ps_matched = xcalloc(1, pos);
+
+       for (pos = 0; pos < active_nr; pos++) {
+               struct cache_entry *ce = active_cache[pos];
+               match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched);
+       }
+
+       if (report_path_error(ps_matched, pathspec, 0))
+               return 1;
+
+       /* "checkout -m path" to recreate conflicted state */
+       if (opts->merge)
+               unmerge_cache(pathspec);
+
+       /* Any unmerged paths? */
+       for (pos = 0; pos < active_nr; pos++) {
+               struct cache_entry *ce = active_cache[pos];
+               if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
+                       if (!ce_stage(ce))
+                               continue;
+                       if (opts->force) {
+                               warning("path '%s' is unmerged", ce->name);
+                       } else if (stage) {
+                               errs |= check_stage(stage, ce, pos);
+                       } else if (opts->merge) {
+                               errs |= check_all_stages(ce, pos);
+                       } else {
+                               errs = 1;
+                               error("path '%s' is unmerged", ce->name);
+                       }
+                       pos = skip_same_name(ce, pos) - 1;
+               }
+       }
+       if (errs)
+               return 1;
+
+       /* Now we are committed to check them out */
+       memset(&state, 0, sizeof(state));
+       state.force = 1;
+       state.refresh_cache = 1;
+       for (pos = 0; pos < active_nr; pos++) {
+               struct cache_entry *ce = active_cache[pos];
+               if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
+                       if (!ce_stage(ce)) {
+                               errs |= checkout_entry(ce, &state, NULL);
+                               continue;
+                       }
+                       if (stage)
+                               errs |= checkout_stage(stage, ce, pos, &state);
+                       else if (merge)
+                               errs |= checkout_merged(pos, &state);
+                       pos = skip_same_name(ce, pos) - 1;
+               }
+       }
+
+       if (write_cache(newfd, active_cache, active_nr) ||
+           commit_locked_index(lock_file))
+               die("unable to write new index file");
+
+       resolve_ref("HEAD", rev, 0, &flag);
+       head = lookup_commit_reference_gently(rev, 1);
+
+       errs |= post_checkout_hook(head, head, 0);
+       return errs;
+}
+
+static void show_local_changes(struct object *head, struct diff_options *opts)
+{
+       struct rev_info rev;
+       /* I think we want full paths, even if we're in a subdirectory. */
+       init_revisions(&rev, NULL);
+       rev.diffopt.flags = opts->flags;
+       rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
+       if (diff_setup_done(&rev.diffopt) < 0)
+               die("diff_setup_done failed");
+       add_pending_object(&rev, head, NULL);
+       run_diff_index(&rev, 0);
+}
+
+static void describe_detached_head(char *msg, struct commit *commit)
+{
+       struct strbuf sb = STRBUF_INIT;
+       struct pretty_print_context ctx = {0};
+       parse_commit(commit);
+       pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, &ctx);
+       fprintf(stderr, "%s %s... %s\n", msg,
+               find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf);
+       strbuf_release(&sb);
+}
+
+static int reset_tree(struct tree *tree, struct checkout_opts *o, int worktree)
+{
+       struct unpack_trees_options opts;
+       struct tree_desc tree_desc;
+
+       memset(&opts, 0, sizeof(opts));
+       opts.head_idx = -1;
+       opts.update = worktree;
+       opts.skip_unmerged = !worktree;
+       opts.reset = 1;
+       opts.merge = 1;
+       opts.fn = oneway_merge;
+       opts.verbose_update = !o->quiet;
+       opts.src_index = &the_index;
+       opts.dst_index = &the_index;
+       parse_tree(tree);
+       init_tree_desc(&tree_desc, tree->buffer, tree->size);
+       switch (unpack_trees(1, &tree_desc, &opts)) {
+       case -2:
+               o->writeout_error = 1;
+               /*
+                * We return 0 nevertheless, as the index is all right
+                * and more importantly we have made best efforts to
+                * update paths in the work tree, and we cannot revert
+                * them.
+                */
+       case 0:
+               return 0;
+       default:
+               return 128;
+       }
+}
+
+struct branch_info {
+       const char *name; /* The short name used */
+       const char *path; /* The full name of a real branch */
+       struct commit *commit; /* The named commit */
+};
+
+static void setup_branch_path(struct branch_info *branch)
+{
+       struct strbuf buf = STRBUF_INIT;
+
+       strbuf_branchname(&buf, branch->name);
+       if (strcmp(buf.buf, branch->name))
+               branch->name = xstrdup(buf.buf);
+       strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
+       branch->path = strbuf_detach(&buf, NULL);
+}
+
+static int merge_working_tree(struct checkout_opts *opts,
+                             struct branch_info *old, struct branch_info *new)
+{
+       int ret;
+       struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+       int newfd = hold_locked_index(lock_file, 1);
+
+       if (read_cache_preload(NULL) < 0)
+               return error("corrupt index file");
+
+       resolve_undo_clear();
+       if (opts->force) {
+               ret = reset_tree(new->commit->tree, opts, 1);
+               if (ret)
+                       return ret;
+       } else {
+               struct tree_desc trees[2];
+               struct tree *tree;
+               struct unpack_trees_options topts;
+
+               memset(&topts, 0, sizeof(topts));
+               topts.head_idx = -1;
+               topts.src_index = &the_index;
+               topts.dst_index = &the_index;
+
+               setup_unpack_trees_porcelain(&topts, "checkout");
+
+               refresh_cache(REFRESH_QUIET);
+
+               if (unmerged_cache()) {
+                       error("you need to resolve your current index first");
+                       return 1;
+               }
+
+               /* 2-way merge to the new branch */
+               topts.initial_checkout = is_cache_unborn();
+               topts.update = 1;
+               topts.merge = 1;
+               topts.gently = opts->merge && old->commit;
+               topts.verbose_update = !opts->quiet;
+               topts.fn = twoway_merge;
+               topts.dir = xcalloc(1, sizeof(*topts.dir));
+               topts.dir->flags |= DIR_SHOW_IGNORED;
+               topts.dir->exclude_per_dir = ".gitignore";
+               tree = parse_tree_indirect(old->commit ?
+                                          old->commit->object.sha1 :
+                                          (unsigned char *)EMPTY_TREE_SHA1_BIN);
+               init_tree_desc(&trees[0], tree->buffer, tree->size);
+               tree = parse_tree_indirect(new->commit->object.sha1);
+               init_tree_desc(&trees[1], tree->buffer, tree->size);
+
+               ret = unpack_trees(2, trees, &topts);
+               if (ret == -1) {
+                       /*
+                        * Unpack couldn't do a trivial merge; either
+                        * give up or do a real merge, depending on
+                        * whether the merge flag was used.
+                        */
+                       struct tree *result;
+                       struct tree *work;
+                       struct merge_options o;
+                       if (!opts->merge)
+                               return 1;
+
+                       /*
+                        * Without old->commit, the below is the same as
+                        * the two-tree unpack we already tried and failed.
+                        */
+                       if (!old->commit)
+                               return 1;
+
+                       /* Do more real merge */
+
+                       /*
+                        * We update the index fully, then write the
+                        * tree from the index, then merge the new
+                        * branch with the current tree, with the old
+                        * branch as the base. Then we reset the index
+                        * (but not the working tree) to the new
+                        * branch, leaving the working tree as the
+                        * merged version, but skipping unmerged
+                        * entries in the index.
+                        */
+
+                       add_files_to_cache(NULL, NULL, 0);
+                       /*
+                        * NEEDSWORK: carrying over local changes
+                        * when branches have different end-of-line
+                        * normalization (or clean+smudge rules) is
+                        * a pain; plumb in an option to set
+                        * o.renormalize?
+                        */
+                       init_merge_options(&o);
+                       o.verbosity = 0;
+                       work = write_tree_from_memory(&o);
+
+                       ret = reset_tree(new->commit->tree, opts, 1);
+                       if (ret)
+                               return ret;
+                       o.ancestor = old->name;
+                       o.branch1 = new->name;
+                       o.branch2 = "local";
+                       merge_trees(&o, new->commit->tree, work,
+                               old->commit->tree, &result);
+                       ret = reset_tree(new->commit->tree, opts, 0);
+                       if (ret)
+                               return ret;
+               }
+       }
+
+       if (write_cache(newfd, active_cache, active_nr) ||
+           commit_locked_index(lock_file))
+               die("unable to write new index file");
+
+       if (!opts->force && !opts->quiet)
+               show_local_changes(&new->commit->object, &opts->diff_options);
+
+       return 0;
+}
+
+static void report_tracking(struct branch_info *new)
+{
+       struct strbuf sb = STRBUF_INIT;
+       struct branch *branch = branch_get(new->name);
+
+       if (!format_tracking_info(branch, &sb))
+               return;
+       fputs(sb.buf, stdout);
+       strbuf_release(&sb);
+}
+
+static void detach_advice(const char *old_path, const char *new_name)
+{
+       const char fmt[] =
+       "Note: checking out '%s'.\n\n"
+       "You are in 'detached HEAD' state. You can look around, make experimental\n"
+       "changes and commit them, and you can discard any commits you make in this\n"
+       "state without impacting any branches by performing another checkout.\n\n"
+       "If you want to create a new branch to retain commits you create, you may\n"
+       "do so (now or later) by using -b with the checkout command again. Example:\n\n"
+       "  git checkout -b new_branch_name\n\n";
+
+       fprintf(stderr, fmt, new_name);
+}
+
+static void update_refs_for_switch(struct checkout_opts *opts,
+                                  struct branch_info *old,
+                                  struct branch_info *new)
+{
+       struct strbuf msg = STRBUF_INIT;
+       const char *old_desc;
+       if (opts->new_branch) {
+               if (opts->new_orphan_branch) {
+                       if (opts->new_branch_log && !log_all_ref_updates) {
+                               int temp;
+                               char log_file[PATH_MAX];
+                               char *ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch);
+
+                               temp = log_all_ref_updates;
+                               log_all_ref_updates = 1;
+                               if (log_ref_setup(ref_name, log_file, sizeof(log_file))) {
+                                       fprintf(stderr, "Can not do reflog for '%s'\n",
+                                           opts->new_orphan_branch);
+                                       log_all_ref_updates = temp;
+                                       return;
+                               }
+                               log_all_ref_updates = temp;
+                       }
+               }
+               else
+                       create_branch(old->name, opts->new_branch, new->name,
+                                     opts->new_branch_force ? 1 : 0,
+                                     opts->new_branch_log, opts->track);
+               new->name = opts->new_branch;
+               setup_branch_path(new);
+       }
+
+       old_desc = old->name;
+       if (!old_desc && old->commit)
+               old_desc = sha1_to_hex(old->commit->object.sha1);
+       strbuf_addf(&msg, "checkout: moving from %s to %s",
+                   old_desc ? old_desc : "(invalid)", new->name);
+
+       if (new->path) {
+               create_symref("HEAD", new->path, msg.buf);
+               if (!opts->quiet) {
+                       if (old->path && !strcmp(new->path, old->path))
+                               fprintf(stderr, "Already on '%s'\n",
+                                       new->name);
+                       else if (opts->new_branch)
+                               fprintf(stderr, "Switched to%s branch '%s'\n",
+                                       opts->branch_exists ? " and reset" : " a new",
+                                       new->name);
+                       else
+                               fprintf(stderr, "Switched to branch '%s'\n",
+                                       new->name);
+               }
+               if (old->path && old->name) {
+                       char log_file[PATH_MAX], ref_file[PATH_MAX];
+
+                       git_snpath(log_file, sizeof(log_file), "logs/%s", old->path);
+                       git_snpath(ref_file, sizeof(ref_file), "%s", old->path);
+                       if (!file_exists(ref_file) && file_exists(log_file))
+                               remove_path(log_file);
+               }
+       } else if (strcmp(new->name, "HEAD")) {
+               update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
+                          REF_NODEREF, DIE_ON_ERR);
+               if (!opts->quiet) {
+                       if (old->path && advice_detached_head)
+                               detach_advice(old->path, new->name);
+                       describe_detached_head("HEAD is now at", new->commit);
+               }
+       }
+       remove_branch_state();
+       strbuf_release(&msg);
+       if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD")))
+               report_tracking(new);
+}
+
+static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
+{
+       int ret = 0;
+       struct branch_info old;
+       unsigned char rev[20];
+       int flag;
+       memset(&old, 0, sizeof(old));
+       old.path = resolve_ref("HEAD", rev, 0, &flag);
+       old.commit = lookup_commit_reference_gently(rev, 1);
+       if (!(flag & REF_ISSYMREF))
+               old.path = NULL;
+
+       if (old.path && !prefixcmp(old.path, "refs/heads/"))
+               old.name = old.path + strlen("refs/heads/");
+
+       if (!new->name) {
+               new->name = "HEAD";
+               new->commit = old.commit;
+               if (!new->commit)
+                       die("You are on a branch yet to be born");
+               parse_commit(new->commit);
+       }
+
+       ret = merge_working_tree(opts, &old, new);
+       if (ret)
+               return ret;
+
+       /*
+        * If we were on a detached HEAD, but have now moved to
+        * a new commit, we want to mention the old commit once more
+        * to remind the user that it might be lost.
+        */
+       if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
+               describe_detached_head("Previous HEAD position was", old.commit);
+
+       update_refs_for_switch(opts, &old, new);
+
+       ret = post_checkout_hook(old.commit, new->commit, 1);
+       return ret || opts->writeout_error;
+}
+
+static int git_checkout_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "diff.ignoresubmodules")) {
+               struct checkout_opts *opts = cb;
+               handle_ignore_submodules_arg(&opts->diff_options, value);
+               return 0;
+       }
+
+       if (!prefixcmp(var, "submodule."))
+               return parse_submodule_config_option(var, value);
+
+       return git_xmerge_config(var, value, NULL);
+}
+
+static int interactive_checkout(const char *revision, const char **pathspec,
+                               struct checkout_opts *opts)
+{
+       return run_add_interactive(revision, "--patch=checkout", pathspec);
+}
+
+struct tracking_name_data {
+       const char *name;
+       char *remote;
+       int unique;
+};
+
+static int check_tracking_name(const char *refname, const unsigned char *sha1,
+                              int flags, void *cb_data)
+{
+       struct tracking_name_data *cb = cb_data;
+       const char *slash;
+
+       if (prefixcmp(refname, "refs/remotes/"))
+               return 0;
+       slash = strchr(refname + 13, '/');
+       if (!slash || strcmp(slash + 1, cb->name))
+               return 0;
+       if (cb->remote) {
+               cb->unique = 0;
+               return 0;
+       }
+       cb->remote = xstrdup(refname);
+       return 0;
+}
+
+static const char *unique_tracking_name(const char *name)
+{
+       struct tracking_name_data cb_data = { NULL, NULL, 1 };
+       cb_data.name = name;
+       for_each_ref(check_tracking_name, &cb_data);
+       if (cb_data.unique)
+               return cb_data.remote;
+       free(cb_data.remote);
+       return NULL;
+}
+
+int cmd_checkout(int argc, const char **argv, const char *prefix)
+{
+       struct checkout_opts opts;
+       unsigned char rev[20];
+       const char *arg;
+       struct branch_info new;
+       struct tree *source_tree = NULL;
+       char *conflict_style = NULL;
+       int patch_mode = 0;
+       int dwim_new_local_branch = 1;
+       struct option options[] = {
+               OPT__QUIET(&opts.quiet),
+               OPT_STRING('b', NULL, &opts.new_branch, "branch",
+                          "create and checkout a new branch"),
+               OPT_STRING('B', NULL, &opts.new_branch_force, "branch",
+                          "create/reset and checkout a branch"),
+               OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"),
+               OPT_SET_INT('t', "track",  &opts.track, "track",
+                       BRANCH_TRACK_EXPLICIT),
+               OPT_STRING(0, "orphan", &opts.new_orphan_branch, "new branch", "new unparented branch"),
+               OPT_SET_INT('2', "ours", &opts.writeout_stage, "stage",
+                           2),
+               OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage",
+                           3),
+               OPT_BOOLEAN('f', "force", &opts.force, "force"),
+               OPT_BOOLEAN('m', "merge", &opts.merge, "merge"),
+               OPT_STRING(0, "conflict", &conflict_style, "style",
+                          "conflict style (merge or diff3)"),
+               OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
+               { OPTION_BOOLEAN, 0, "guess", &dwim_new_local_branch, NULL,
+                 "second guess 'git checkout no-such-branch'",
+                 PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
+               OPT_END(),
+       };
+       int has_dash_dash;
+
+       memset(&opts, 0, sizeof(opts));
+       memset(&new, 0, sizeof(new));
+
+       gitmodules_config();
+       git_config(git_checkout_config, &opts);
+
+       opts.track = BRANCH_TRACK_UNSPECIFIED;
+
+       argc = parse_options(argc, argv, prefix, options, checkout_usage,
+                            PARSE_OPT_KEEP_DASHDASH);
+
+       /* we can assume from now on new_branch = !new_branch_force */
+       if (opts.new_branch && opts.new_branch_force)
+               die("-B cannot be used with -b");
+
+       /* copy -B over to -b, so that we can just check the latter */
+       if (opts.new_branch_force)
+               opts.new_branch = opts.new_branch_force;
+
+       if (patch_mode && (opts.track > 0 || opts.new_branch
+                          || opts.new_branch_log || opts.merge || opts.force))
+               die ("--patch is incompatible with all other options");
+
+       /* --track without -b should DWIM */
+       if (0 < opts.track && !opts.new_branch) {
+               const char *argv0 = argv[0];
+               if (!argc || !strcmp(argv0, "--"))
+                       die ("--track needs a branch name");
+               if (!prefixcmp(argv0, "refs/"))
+                       argv0 += 5;
+               if (!prefixcmp(argv0, "remotes/"))
+                       argv0 += 8;
+               argv0 = strchr(argv0, '/');
+               if (!argv0 || !argv0[1])
+                       die ("Missing branch name; try -b");
+               opts.new_branch = argv0 + 1;
+       }
+
+       if (opts.new_orphan_branch) {
+               if (opts.new_branch)
+                       die("--orphan and -b|-B are mutually exclusive");
+               if (opts.track > 0)
+                       die("--orphan cannot be used with -t");
+               opts.new_branch = opts.new_orphan_branch;
+       }
+
+       if (conflict_style) {
+               opts.merge = 1; /* implied */
+               git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
+       }
+
+       if (opts.force && opts.merge)
+               die("git checkout: -f and -m are incompatible");
+
+       /*
+        * case 1: git checkout <ref> -- [<paths>]
+        *
+        *   <ref> must be a valid tree, everything after the '--' must be
+        *   a path.
+        *
+        * case 2: git checkout -- [<paths>]
+        *
+        *   everything after the '--' must be paths.
+        *
+        * case 3: git checkout <something> [<paths>]
+        *
+        *   With no paths, if <something> is a commit, that is to
+        *   switch to the branch or detach HEAD at it.  As a special case,
+        *   if <something> is A...B (missing A or B means HEAD but you can
+        *   omit at most one side), and if there is a unique merge base
+        *   between A and B, A...B names that merge base.
+        *
+        *   With no paths, if <something> is _not_ a commit, no -t nor -b
+        *   was given, and there is a tracking branch whose name is
+        *   <something> in one and only one remote, then this is a short-hand
+        *   to fork local <something> from that remote tracking branch.
+        *
+        *   Otherwise <something> shall not be ambiguous.
+        *   - If it's *only* a reference, treat it like case (1).
+        *   - If it's only a path, treat it like case (2).
+        *   - else: fail.
+        *
+        */
+       if (argc) {
+               if (!strcmp(argv[0], "--")) {       /* case (2) */
+                       argv++;
+                       argc--;
+                       goto no_reference;
+               }
+
+               arg = argv[0];
+               has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
+
+               if (!strcmp(arg, "-"))
+                       arg = "@{-1}";
+
+               if (get_sha1_mb(arg, rev)) {
+                       if (has_dash_dash)          /* case (1) */
+                               die("invalid reference: %s", arg);
+                       if (!patch_mode &&
+                           dwim_new_local_branch &&
+                           opts.track == BRANCH_TRACK_UNSPECIFIED &&
+                           !opts.new_branch &&
+                           !check_filename(NULL, arg) &&
+                           argc == 1) {
+                               const char *remote = unique_tracking_name(arg);
+                               if (!remote || get_sha1(remote, rev))
+                                       goto no_reference;
+                               opts.new_branch = arg;
+                               arg = remote;
+                               /* DWIMmed to create local branch */
+                       }
+                       else
+                               goto no_reference;
+               }
+
+               /* we can't end up being in (2) anymore, eat the argument */
+               argv++;
+               argc--;
+
+               new.name = arg;
+               if ((new.commit = lookup_commit_reference_gently(rev, 1))) {
+                       setup_branch_path(&new);
+
+                       if ((check_ref_format(new.path) == CHECK_REF_FORMAT_OK) &&
+                           resolve_ref(new.path, rev, 1, NULL))
+                               ;
+                       else
+                               new.path = NULL;
+                       parse_commit(new.commit);
+                       source_tree = new.commit->tree;
+               } else
+                       source_tree = parse_tree_indirect(rev);
+
+               if (!source_tree)                   /* case (1): want a tree */
+                       die("reference is not a tree: %s", arg);
+               if (!has_dash_dash) {/* case (3 -> 1) */
+                       /*
+                        * Do not complain the most common case
+                        *      git checkout branch
+                        * even if there happen to be a file called 'branch';
+                        * it would be extremely annoying.
+                        */
+                       if (argc)
+                               verify_non_filename(NULL, arg);
+               }
+               else {
+                       argv++;
+                       argc--;
+               }
+       }
+
+no_reference:
+
+       if (opts.track == BRANCH_TRACK_UNSPECIFIED)
+               opts.track = git_branch_track;
+
+       if (argc) {
+               const char **pathspec = get_pathspec(prefix, argv);
+
+               if (!pathspec)
+                       die("invalid path specification");
+
+               if (patch_mode)
+                       return interactive_checkout(new.name, pathspec, &opts);
+
+               /* Checkout paths */
+               if (opts.new_branch) {
+                       if (argc == 1) {
+                               die("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]);
+                       } else {
+                               die("git checkout: updating paths is incompatible with switching branches.");
+                       }
+               }
+
+               if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
+                       die("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index.");
+
+               return checkout_paths(source_tree, pathspec, &opts);
+       }
+
+       if (patch_mode)
+               return interactive_checkout(new.name, NULL, &opts);
+
+       if (opts.new_branch) {
+               struct strbuf buf = STRBUF_INIT;
+               if (strbuf_check_branch_ref(&buf, opts.new_branch))
+                       die("git checkout: we do not like '%s' as a branch name.",
+                           opts.new_branch);
+               if (!get_sha1(buf.buf, rev)) {
+                       opts.branch_exists = 1;
+                       if (!opts.new_branch_force)
+                               die("git checkout: branch %s already exists",
+                                   opts.new_branch);
+               }
+               strbuf_release(&buf);
+       }
+
+       if (new.name && !new.commit) {
+               die("Cannot switch branch to a non-commit.");
+       }
+       if (opts.writeout_stage)
+               die("--ours/--theirs is incompatible with switching branches.");
+
+       return switch_branches(&opts, &new);
+}
diff --git a/builtin/clean.c b/builtin/clean.c
new file mode 100644 (file)
index 0000000..c8798f5
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * "git clean" builtin command
+ *
+ * Copyright (C) 2007 Shawn Bohrer
+ *
+ * Based on git-clean.sh by Pavel Roskin
+ */
+
+#include "builtin.h"
+#include "cache.h"
+#include "dir.h"
+#include "parse-options.h"
+#include "string-list.h"
+#include "quote.h"
+
+static int force = -1; /* unset */
+
+static const char *const builtin_clean_usage[] = {
+       "git clean [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>...",
+       NULL
+};
+
+static int git_clean_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "clean.requireforce"))
+               force = !git_config_bool(var, value);
+       return git_default_config(var, value, cb);
+}
+
+static int exclude_cb(const struct option *opt, const char *arg, int unset)
+{
+       struct string_list *exclude_list = opt->value;
+       string_list_append(exclude_list, arg);
+       return 0;
+}
+
+int cmd_clean(int argc, const char **argv, const char *prefix)
+{
+       int i;
+       int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0;
+       int ignored_only = 0, baselen = 0, config_set = 0, errors = 0;
+       int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
+       struct strbuf directory = STRBUF_INIT;
+       struct dir_struct dir;
+       static const char **pathspec;
+       struct strbuf buf = STRBUF_INIT;
+       struct string_list exclude_list = STRING_LIST_INIT_NODUP;
+       const char *qname;
+       char *seen = NULL;
+       struct option options[] = {
+               OPT__QUIET(&quiet),
+               OPT__DRY_RUN(&show_only),
+               OPT_BOOLEAN('f', "force", &force, "force"),
+               OPT_BOOLEAN('d', NULL, &remove_directories,
+                               "remove whole directories"),
+               { OPTION_CALLBACK, 'e', "exclude", &exclude_list, "pattern",
+                 "exclude <pattern>", PARSE_OPT_NONEG, exclude_cb },
+               OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"),
+               OPT_BOOLEAN('X', NULL, &ignored_only,
+                               "remove only ignored files"),
+               OPT_END()
+       };
+
+       git_config(git_clean_config, NULL);
+       if (force < 0)
+               force = 0;
+       else
+               config_set = 1;
+
+       argc = parse_options(argc, argv, prefix, options, builtin_clean_usage,
+                            0);
+
+       memset(&dir, 0, sizeof(dir));
+       if (ignored_only)
+               dir.flags |= DIR_SHOW_IGNORED;
+
+       if (ignored && ignored_only)
+               die("-x and -X cannot be used together");
+
+       if (!show_only && !force)
+               die("clean.requireForce %s to true and neither -n nor -f given; "
+                   "refusing to clean", config_set ? "set" : "defaults");
+
+       if (force > 1)
+               rm_flags = 0;
+
+       dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
+
+       if (read_cache() < 0)
+               die("index file corrupt");
+
+       if (!ignored)
+               setup_standard_excludes(&dir);
+
+       for (i = 0; i < exclude_list.nr; i++)
+               add_exclude(exclude_list.items[i].string, "", 0, dir.exclude_list);
+
+       pathspec = get_pathspec(prefix, argv);
+
+       fill_directory(&dir, pathspec);
+
+       if (pathspec)
+               seen = xmalloc(argc > 0 ? argc : 1);
+
+       for (i = 0; i < dir.nr; i++) {
+               struct dir_entry *ent = dir.entries[i];
+               int len, pos;
+               int matches = 0;
+               struct cache_entry *ce;
+               struct stat st;
+
+               /*
+                * Remove the '/' at the end that directory
+                * walking adds for directory entries.
+                */
+               len = ent->len;
+               if (len && ent->name[len-1] == '/')
+                       len--;
+               pos = cache_name_pos(ent->name, len);
+               if (0 <= pos)
+                       continue;       /* exact match */
+               pos = -pos - 1;
+               if (pos < active_nr) {
+                       ce = active_cache[pos];
+                       if (ce_namelen(ce) == len &&
+                           !memcmp(ce->name, ent->name, len))
+                               continue; /* Yup, this one exists unmerged */
+               }
+
+               /*
+                * we might have removed this as part of earlier
+                * recursive directory removal, so lstat() here could
+                * fail with ENOENT.
+                */
+               if (lstat(ent->name, &st))
+                       continue;
+
+               if (pathspec) {
+                       memset(seen, 0, argc > 0 ? argc : 1);
+                       matches = match_pathspec(pathspec, ent->name, len,
+                                                baselen, seen);
+               }
+
+               if (S_ISDIR(st.st_mode)) {
+                       strbuf_addstr(&directory, ent->name);
+                       qname = quote_path_relative(directory.buf, directory.len, &buf, prefix);
+                       if (show_only && (remove_directories ||
+                           (matches == MATCHED_EXACTLY))) {
+                               printf("Would remove %s\n", qname);
+                       } else if (remove_directories ||
+                                  (matches == MATCHED_EXACTLY)) {
+                               if (!quiet)
+                                       printf("Removing %s\n", qname);
+                               if (remove_dir_recursively(&directory,
+                                                          rm_flags) != 0) {
+                                       warning("failed to remove '%s'", qname);
+                                       errors++;
+                               }
+                       } else if (show_only) {
+                               printf("Would not remove %s\n", qname);
+                       } else {
+                               printf("Not removing %s\n", qname);
+                       }
+                       strbuf_reset(&directory);
+               } else {
+                       if (pathspec && !matches)
+                               continue;
+                       qname = quote_path_relative(ent->name, -1, &buf, prefix);
+                       if (show_only) {
+                               printf("Would remove %s\n", qname);
+                               continue;
+                       } else if (!quiet) {
+                               printf("Removing %s\n", qname);
+                       }
+                       if (unlink(ent->name) != 0) {
+                               warning("failed to remove '%s'", qname);
+                               errors++;
+                       }
+               }
+       }
+       free(seen);
+
+       strbuf_release(&directory);
+       string_list_clear(&exclude_list, 0);
+       return (errors != 0);
+}
diff --git a/builtin/clone.c b/builtin/clone.c
new file mode 100644 (file)
index 0000000..19ed640
--- /dev/null
@@ -0,0 +1,672 @@
+/*
+ * Builtin "git clone"
+ *
+ * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>,
+ *              2008 Daniel Barkalow <barkalow@iabervon.org>
+ * Based on git-commit.sh by Junio C Hamano and Linus Torvalds
+ *
+ * Clone a repository into a different directory that does not yet exist.
+ */
+
+#include "cache.h"
+#include "parse-options.h"
+#include "fetch-pack.h"
+#include "refs.h"
+#include "tree.h"
+#include "tree-walk.h"
+#include "unpack-trees.h"
+#include "transport.h"
+#include "strbuf.h"
+#include "dir.h"
+#include "pack-refs.h"
+#include "sigchain.h"
+#include "branch.h"
+#include "remote.h"
+#include "run-command.h"
+
+/*
+ * Overall FIXMEs:
+ *  - respect DB_ENVIRONMENT for .git/objects.
+ *
+ * Implementation notes:
+ *  - dropping use-separate-remote and no-separate-remote compatibility
+ *
+ */
+static const char * const builtin_clone_usage[] = {
+       "git clone [options] [--] <repo> [<dir>]",
+       NULL
+};
+
+static int option_no_checkout, option_bare, option_mirror;
+static int option_local, option_no_hardlinks, option_shared, option_recursive;
+static char *option_template, *option_reference, *option_depth;
+static char *option_origin = NULL;
+static char *option_branch = NULL;
+static char *option_upload_pack = "git-upload-pack";
+static int option_verbosity;
+static int option_progress;
+
+static struct option builtin_clone_options[] = {
+       OPT__VERBOSITY(&option_verbosity),
+       OPT_BOOLEAN(0, "progress", &option_progress,
+                       "force progress reporting"),
+       OPT_BOOLEAN('n', "no-checkout", &option_no_checkout,
+                   "don't create a checkout"),
+       OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"),
+       { OPTION_BOOLEAN, 0, "naked", &option_bare, NULL,
+               "create a bare repository",
+               PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
+       OPT_BOOLEAN(0, "mirror", &option_mirror,
+                   "create a mirror repository (implies bare)"),
+       OPT_BOOLEAN('l', "local", &option_local,
+                   "to clone from a local repository"),
+       OPT_BOOLEAN(0, "no-hardlinks", &option_no_hardlinks,
+                   "don't use local hardlinks, always copy"),
+       OPT_BOOLEAN('s', "shared", &option_shared,
+                   "setup as shared repository"),
+       OPT_BOOLEAN(0, "recursive", &option_recursive,
+                   "initialize submodules in the clone"),
+       OPT_STRING(0, "template", &option_template, "path",
+                  "path the template repository"),
+       OPT_STRING(0, "reference", &option_reference, "repo",
+                  "reference repository"),
+       OPT_STRING('o', "origin", &option_origin, "branch",
+                  "use <branch> instead of 'origin' to track upstream"),
+       OPT_STRING('b', "branch", &option_branch, "branch",
+                  "checkout <branch> instead of the remote's HEAD"),
+       OPT_STRING('u', "upload-pack", &option_upload_pack, "path",
+                  "path to git-upload-pack on the remote"),
+       OPT_STRING(0, "depth", &option_depth, "depth",
+                   "create a shallow clone of that depth"),
+
+       OPT_END()
+};
+
+static const char *argv_submodule[] = {
+       "submodule", "update", "--init", "--recursive", NULL
+};
+
+static char *get_repo_path(const char *repo, int *is_bundle)
+{
+       static char *suffix[] = { "/.git", ".git", "" };
+       static char *bundle_suffix[] = { ".bundle", "" };
+       struct stat st;
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(suffix); i++) {
+               const char *path;
+               path = mkpath("%s%s", repo, suffix[i]);
+               if (is_directory(path)) {
+                       *is_bundle = 0;
+                       return xstrdup(make_nonrelative_path(path));
+               }
+       }
+
+       for (i = 0; i < ARRAY_SIZE(bundle_suffix); i++) {
+               const char *path;
+               path = mkpath("%s%s", repo, bundle_suffix[i]);
+               if (!stat(path, &st) && S_ISREG(st.st_mode)) {
+                       *is_bundle = 1;
+                       return xstrdup(make_nonrelative_path(path));
+               }
+       }
+
+       return NULL;
+}
+
+static char *guess_dir_name(const char *repo, int is_bundle, int is_bare)
+{
+       const char *end = repo + strlen(repo), *start;
+       char *dir;
+
+       /*
+        * Strip trailing spaces, slashes and /.git
+        */
+       while (repo < end && (is_dir_sep(end[-1]) || isspace(end[-1])))
+               end--;
+       if (end - repo > 5 && is_dir_sep(end[-5]) &&
+           !strncmp(end - 4, ".git", 4)) {
+               end -= 5;
+               while (repo < end && is_dir_sep(end[-1]))
+                       end--;
+       }
+
+       /*
+        * Find last component, but be prepared that repo could have
+        * the form  "remote.example.com:foo.git", i.e. no slash
+        * in the directory part.
+        */
+       start = end;
+       while (repo < start && !is_dir_sep(start[-1]) && start[-1] != ':')
+               start--;
+
+       /*
+        * Strip .{bundle,git}.
+        */
+       if (is_bundle) {
+               if (end - start > 7 && !strncmp(end - 7, ".bundle", 7))
+                       end -= 7;
+       } else {
+               if (end - start > 4 && !strncmp(end - 4, ".git", 4))
+                       end -= 4;
+       }
+
+       if (is_bare) {
+               struct strbuf result = STRBUF_INIT;
+               strbuf_addf(&result, "%.*s.git", (int)(end - start), start);
+               dir = strbuf_detach(&result, NULL);
+       } else
+               dir = xstrndup(start, end - start);
+       /*
+        * Replace sequences of 'control' characters and whitespace
+        * with one ascii space, remove leading and trailing spaces.
+        */
+       if (*dir) {
+               char *out = dir;
+               int prev_space = 1 /* strip leading whitespace */;
+               for (end = dir; *end; ++end) {
+                       char ch = *end;
+                       if ((unsigned char)ch < '\x20')
+                               ch = '\x20';
+                       if (isspace(ch)) {
+                               if (prev_space)
+                                       continue;
+                               prev_space = 1;
+                       } else
+                               prev_space = 0;
+                       *out++ = ch;
+               }
+               *out = '\0';
+               if (out > dir && prev_space)
+                       out[-1] = '\0';
+       }
+       return dir;
+}
+
+static void strip_trailing_slashes(char *dir)
+{
+       char *end = dir + strlen(dir);
+
+       while (dir < end - 1 && is_dir_sep(end[-1]))
+               end--;
+       *end = '\0';
+}
+
+static void setup_reference(const char *repo)
+{
+       const char *ref_git;
+       char *ref_git_copy;
+
+       struct remote *remote;
+       struct transport *transport;
+       const struct ref *extra;
+
+       ref_git = make_absolute_path(option_reference);
+
+       if (is_directory(mkpath("%s/.git/objects", ref_git)))
+               ref_git = mkpath("%s/.git", ref_git);
+       else if (!is_directory(mkpath("%s/objects", ref_git)))
+               die("reference repository '%s' is not a local directory.",
+                   option_reference);
+
+       ref_git_copy = xstrdup(ref_git);
+
+       add_to_alternates_file(ref_git_copy);
+
+       remote = remote_get(ref_git_copy);
+       transport = transport_get(remote, ref_git_copy);
+       for (extra = transport_get_remote_refs(transport); extra;
+            extra = extra->next)
+               add_extra_ref(extra->name, extra->old_sha1, 0);
+
+       transport_disconnect(transport);
+
+       free(ref_git_copy);
+}
+
+static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest)
+{
+       struct dirent *de;
+       struct stat buf;
+       int src_len, dest_len;
+       DIR *dir;
+
+       dir = opendir(src->buf);
+       if (!dir)
+               die_errno("failed to open '%s'", src->buf);
+
+       if (mkdir(dest->buf, 0777)) {
+               if (errno != EEXIST)
+                       die_errno("failed to create directory '%s'", dest->buf);
+               else if (stat(dest->buf, &buf))
+                       die_errno("failed to stat '%s'", dest->buf);
+               else if (!S_ISDIR(buf.st_mode))
+                       die("%s exists and is not a directory", dest->buf);
+       }
+
+       strbuf_addch(src, '/');
+       src_len = src->len;
+       strbuf_addch(dest, '/');
+       dest_len = dest->len;
+
+       while ((de = readdir(dir)) != NULL) {
+               strbuf_setlen(src, src_len);
+               strbuf_addstr(src, de->d_name);
+               strbuf_setlen(dest, dest_len);
+               strbuf_addstr(dest, de->d_name);
+               if (stat(src->buf, &buf)) {
+                       warning ("failed to stat %s\n", src->buf);
+                       continue;
+               }
+               if (S_ISDIR(buf.st_mode)) {
+                       if (de->d_name[0] != '.')
+                               copy_or_link_directory(src, dest);
+                       continue;
+               }
+
+               if (unlink(dest->buf) && errno != ENOENT)
+                       die_errno("failed to unlink '%s'", dest->buf);
+               if (!option_no_hardlinks) {
+                       if (!link(src->buf, dest->buf))
+                               continue;
+                       if (option_local)
+                               die_errno("failed to create link '%s'", dest->buf);
+                       option_no_hardlinks = 1;
+               }
+               if (copy_file_with_time(dest->buf, src->buf, 0666))
+                       die_errno("failed to copy file to '%s'", dest->buf);
+       }
+       closedir(dir);
+}
+
+static const struct ref *clone_local(const char *src_repo,
+                                    const char *dest_repo)
+{
+       const struct ref *ret;
+       struct strbuf src = STRBUF_INIT;
+       struct strbuf dest = STRBUF_INIT;
+       struct remote *remote;
+       struct transport *transport;
+
+       if (option_shared)
+               add_to_alternates_file(src_repo);
+       else {
+               strbuf_addf(&src, "%s/objects", src_repo);
+               strbuf_addf(&dest, "%s/objects", dest_repo);
+               copy_or_link_directory(&src, &dest);
+               strbuf_release(&src);
+               strbuf_release(&dest);
+       }
+
+       remote = remote_get(src_repo);
+       transport = transport_get(remote, src_repo);
+       ret = transport_get_remote_refs(transport);
+       transport_disconnect(transport);
+       if (0 <= option_verbosity)
+               printf("done.\n");
+       return ret;
+}
+
+static const char *junk_work_tree;
+static const char *junk_git_dir;
+static pid_t junk_pid;
+
+static void remove_junk(void)
+{
+       struct strbuf sb = STRBUF_INIT;
+       if (getpid() != junk_pid)
+               return;
+       if (junk_git_dir) {
+               strbuf_addstr(&sb, junk_git_dir);
+               remove_dir_recursively(&sb, 0);
+               strbuf_reset(&sb);
+       }
+       if (junk_work_tree) {
+               strbuf_addstr(&sb, junk_work_tree);
+               remove_dir_recursively(&sb, 0);
+               strbuf_reset(&sb);
+       }
+}
+
+static void remove_junk_on_signal(int signo)
+{
+       remove_junk();
+       sigchain_pop(signo);
+       raise(signo);
+}
+
+static struct ref *wanted_peer_refs(const struct ref *refs,
+               struct refspec *refspec)
+{
+       struct ref *local_refs = NULL;
+       struct ref **tail = &local_refs;
+
+       get_fetch_map(refs, refspec, &tail, 0);
+       if (!option_mirror)
+               get_fetch_map(refs, tag_refspec, &tail, 0);
+
+       return local_refs;
+}
+
+static void write_remote_refs(const struct ref *local_refs)
+{
+       const struct ref *r;
+
+       for (r = local_refs; r; r = r->next)
+               add_extra_ref(r->peer_ref->name, r->old_sha1, 0);
+
+       pack_refs(PACK_REFS_ALL);
+       clear_extra_refs();
+}
+
+int cmd_clone(int argc, const char **argv, const char *prefix)
+{
+       int is_bundle = 0, is_local;
+       struct stat buf;
+       const char *repo_name, *repo, *work_tree, *git_dir;
+       char *path, *dir;
+       int dest_exists;
+       const struct ref *refs, *remote_head;
+       const struct ref *remote_head_points_at;
+       const struct ref *our_head_points_at;
+       struct ref *mapped_refs;
+       struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
+       struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
+       struct transport *transport = NULL;
+       char *src_ref_prefix = "refs/heads/";
+       int err = 0;
+
+       struct refspec *refspec;
+       const char *fetch_pattern;
+
+       junk_pid = getpid();
+
+       argc = parse_options(argc, argv, prefix, builtin_clone_options,
+                            builtin_clone_usage, 0);
+
+       if (argc > 2)
+               usage_msg_opt("Too many arguments.",
+                       builtin_clone_usage, builtin_clone_options);
+
+       if (argc == 0)
+               usage_msg_opt("You must specify a repository to clone.",
+                       builtin_clone_usage, builtin_clone_options);
+
+       if (option_mirror)
+               option_bare = 1;
+
+       if (option_bare) {
+               if (option_origin)
+                       die("--bare and --origin %s options are incompatible.",
+                           option_origin);
+               option_no_checkout = 1;
+       }
+
+       if (!option_origin)
+               option_origin = "origin";
+
+       repo_name = argv[0];
+
+       path = get_repo_path(repo_name, &is_bundle);
+       if (path)
+               repo = xstrdup(make_nonrelative_path(repo_name));
+       else if (!strchr(repo_name, ':'))
+               repo = xstrdup(make_absolute_path(repo_name));
+       else
+               repo = repo_name;
+       is_local = path && !is_bundle;
+       if (is_local && option_depth)
+               warning("--depth is ignored in local clones; use file:// instead.");
+
+       if (argc == 2)
+               dir = xstrdup(argv[1]);
+       else
+               dir = guess_dir_name(repo_name, is_bundle, option_bare);
+       strip_trailing_slashes(dir);
+
+       dest_exists = !stat(dir, &buf);
+       if (dest_exists && !is_empty_dir(dir))
+               die("destination path '%s' already exists and is not "
+                       "an empty directory.", dir);
+
+       strbuf_addf(&reflog_msg, "clone: from %s", repo);
+
+       if (option_bare)
+               work_tree = NULL;
+       else {
+               work_tree = getenv("GIT_WORK_TREE");
+               if (work_tree && !stat(work_tree, &buf))
+                       die("working tree '%s' already exists.", work_tree);
+       }
+
+       if (option_bare || work_tree)
+               git_dir = xstrdup(dir);
+       else {
+               work_tree = dir;
+               git_dir = xstrdup(mkpath("%s/.git", dir));
+       }
+
+       if (!option_bare) {
+               junk_work_tree = work_tree;
+               if (safe_create_leading_directories_const(work_tree) < 0)
+                       die_errno("could not create leading directories of '%s'",
+                                 work_tree);
+               if (!dest_exists && mkdir(work_tree, 0755))
+                       die_errno("could not create work tree dir '%s'.",
+                                 work_tree);
+               set_git_work_tree(work_tree);
+       }
+       junk_git_dir = git_dir;
+       atexit(remove_junk);
+       sigchain_push_common(remove_junk_on_signal);
+
+       setenv(CONFIG_ENVIRONMENT, mkpath("%s/config", git_dir), 1);
+
+       if (safe_create_leading_directories_const(git_dir) < 0)
+               die("could not create leading directories of '%s'", git_dir);
+       set_git_dir(make_absolute_path(git_dir));
+
+       if (0 <= option_verbosity)
+               printf("Cloning into %s%s...\n",
+                      option_bare ? "bare repository " : "", dir);
+       init_db(option_template, INIT_DB_QUIET);
+
+       /*
+        * At this point, the config exists, so we do not need the
+        * environment variable.  We actually need to unset it, too, to
+        * re-enable parsing of the global configs.
+        */
+       unsetenv(CONFIG_ENVIRONMENT);
+
+       git_config(git_default_config, NULL);
+
+       if (option_bare) {
+               if (option_mirror)
+                       src_ref_prefix = "refs/";
+               strbuf_addstr(&branch_top, src_ref_prefix);
+
+               git_config_set("core.bare", "true");
+       } else {
+               strbuf_addf(&branch_top, "refs/remotes/%s/", option_origin);
+       }
+
+       strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf);
+
+       if (option_mirror || !option_bare) {
+               /* Configure the remote */
+               strbuf_addf(&key, "remote.%s.fetch", option_origin);
+               git_config_set_multivar(key.buf, value.buf, "^$", 0);
+               strbuf_reset(&key);
+
+               if (option_mirror) {
+                       strbuf_addf(&key, "remote.%s.mirror", option_origin);
+                       git_config_set(key.buf, "true");
+                       strbuf_reset(&key);
+               }
+       }
+
+       strbuf_addf(&key, "remote.%s.url", option_origin);
+       git_config_set(key.buf, repo);
+       strbuf_reset(&key);
+
+       if (option_reference)
+               setup_reference(git_dir);
+
+       fetch_pattern = value.buf;
+       refspec = parse_fetch_refspec(1, &fetch_pattern);
+
+       strbuf_reset(&value);
+
+       if (is_local) {
+               refs = clone_local(path, git_dir);
+               mapped_refs = wanted_peer_refs(refs, refspec);
+       } else {
+               struct remote *remote = remote_get(option_origin);
+               transport = transport_get(remote, remote->url[0]);
+
+               if (!transport->get_refs_list || !transport->fetch)
+                       die("Don't know how to clone %s", transport->url);
+
+               transport_set_option(transport, TRANS_OPT_KEEP, "yes");
+
+               if (option_depth)
+                       transport_set_option(transport, TRANS_OPT_DEPTH,
+                                            option_depth);
+
+               transport_set_verbosity(transport, option_verbosity, option_progress);
+
+               if (option_upload_pack)
+                       transport_set_option(transport, TRANS_OPT_UPLOADPACK,
+                                            option_upload_pack);
+
+               refs = transport_get_remote_refs(transport);
+               if (refs) {
+                       mapped_refs = wanted_peer_refs(refs, refspec);
+                       transport_fetch_refs(transport, mapped_refs);
+               }
+       }
+
+       if (refs) {
+               clear_extra_refs();
+
+               write_remote_refs(mapped_refs);
+
+               remote_head = find_ref_by_name(refs, "HEAD");
+               remote_head_points_at =
+                       guess_remote_head(remote_head, mapped_refs, 0);
+
+               if (option_branch) {
+                       struct strbuf head = STRBUF_INIT;
+                       strbuf_addstr(&head, src_ref_prefix);
+                       strbuf_addstr(&head, option_branch);
+                       our_head_points_at =
+                               find_ref_by_name(mapped_refs, head.buf);
+                       strbuf_release(&head);
+
+                       if (!our_head_points_at) {
+                               warning("Remote branch %s not found in "
+                                       "upstream %s, using HEAD instead",
+                                       option_branch, option_origin);
+                               our_head_points_at = remote_head_points_at;
+                       }
+               }
+               else
+                       our_head_points_at = remote_head_points_at;
+       }
+       else {
+               warning("You appear to have cloned an empty repository.");
+               our_head_points_at = NULL;
+               remote_head_points_at = NULL;
+               remote_head = NULL;
+               option_no_checkout = 1;
+               if (!option_bare)
+                       install_branch_config(0, "master", option_origin,
+                                             "refs/heads/master");
+       }
+
+       if (remote_head_points_at && !option_bare) {
+               struct strbuf head_ref = STRBUF_INIT;
+               strbuf_addstr(&head_ref, branch_top.buf);
+               strbuf_addstr(&head_ref, "HEAD");
+               create_symref(head_ref.buf,
+                             remote_head_points_at->peer_ref->name,
+                             reflog_msg.buf);
+       }
+
+       if (our_head_points_at) {
+               /* Local default branch link */
+               create_symref("HEAD", our_head_points_at->name, NULL);
+               if (!option_bare) {
+                       const char *head = skip_prefix(our_head_points_at->name,
+                                                      "refs/heads/");
+                       update_ref(reflog_msg.buf, "HEAD",
+                                  our_head_points_at->old_sha1,
+                                  NULL, 0, DIE_ON_ERR);
+                       install_branch_config(0, head, option_origin,
+                                             our_head_points_at->name);
+               }
+       } else if (remote_head) {
+               /* Source had detached HEAD pointing somewhere. */
+               if (!option_bare) {
+                       update_ref(reflog_msg.buf, "HEAD",
+                                  remote_head->old_sha1,
+                                  NULL, REF_NODEREF, DIE_ON_ERR);
+                       our_head_points_at = remote_head;
+               }
+       } else {
+               /* Nothing to checkout out */
+               if (!option_no_checkout)
+                       warning("remote HEAD refers to nonexistent ref, "
+                               "unable to checkout.\n");
+               option_no_checkout = 1;
+       }
+
+       if (transport) {
+               transport_unlock_pack(transport);
+               transport_disconnect(transport);
+       }
+
+       if (!option_no_checkout) {
+               struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+               struct unpack_trees_options opts;
+               struct tree *tree;
+               struct tree_desc t;
+               int fd;
+
+               /* We need to be in the new work tree for the checkout */
+               setup_work_tree();
+
+               fd = hold_locked_index(lock_file, 1);
+
+               memset(&opts, 0, sizeof opts);
+               opts.update = 1;
+               opts.merge = 1;
+               opts.fn = oneway_merge;
+               opts.verbose_update = (option_verbosity > 0);
+               opts.src_index = &the_index;
+               opts.dst_index = &the_index;
+
+               tree = parse_tree_indirect(our_head_points_at->old_sha1);
+               parse_tree(tree);
+               init_tree_desc(&t, tree->buffer, tree->size);
+               unpack_trees(1, &t, &opts);
+
+               if (write_cache(fd, active_cache, active_nr) ||
+                   commit_locked_index(lock_file))
+                       die("unable to write new index file");
+
+               err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
+                               sha1_to_hex(our_head_points_at->old_sha1), "1",
+                               NULL);
+
+               if (!err && option_recursive)
+                       err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
+       }
+
+       strbuf_release(&reflog_msg);
+       strbuf_release(&branch_top);
+       strbuf_release(&key);
+       strbuf_release(&value);
+       junk_pid = 0;
+       return err;
+}
diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c
new file mode 100644 (file)
index 0000000..87f0591
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "commit.h"
+#include "tree.h"
+#include "builtin.h"
+#include "utf8.h"
+
+static const char commit_tree_usage[] = "git commit-tree <sha1> [-p <sha1>]* < changelog";
+
+static void new_parent(struct commit *parent, struct commit_list **parents_p)
+{
+       unsigned char *sha1 = parent->object.sha1;
+       struct commit_list *parents;
+       for (parents = *parents_p; parents; parents = parents->next) {
+               if (parents->item == parent) {
+                       error("duplicate parent %s ignored", sha1_to_hex(sha1));
+                       return;
+               }
+               parents_p = &parents->next;
+       }
+       commit_list_insert(parent, parents_p);
+}
+
+int cmd_commit_tree(int argc, const char **argv, const char *prefix)
+{
+       int i;
+       struct commit_list *parents = NULL;
+       unsigned char tree_sha1[20];
+       unsigned char commit_sha1[20];
+       struct strbuf buffer = STRBUF_INIT;
+
+       git_config(git_default_config, NULL);
+
+       if (argc < 2 || !strcmp(argv[1], "-h"))
+               usage(commit_tree_usage);
+       if (get_sha1(argv[1], tree_sha1))
+               die("Not a valid object name %s", argv[1]);
+
+       for (i = 2; i < argc; i += 2) {
+               unsigned char sha1[20];
+               const char *a, *b;
+               a = argv[i]; b = argv[i+1];
+               if (!b || strcmp(a, "-p"))
+                       usage(commit_tree_usage);
+
+               if (get_sha1(b, sha1))
+                       die("Not a valid object name %s", b);
+               assert_sha1_type(sha1, OBJ_COMMIT);
+               new_parent(lookup_commit(sha1), &parents);
+       }
+
+       if (strbuf_read(&buffer, 0, 0) < 0)
+               die_errno("git commit-tree: failed to read");
+
+       if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {
+               printf("%s\n", sha1_to_hex(commit_sha1));
+               return 0;
+       }
+       else
+               return 1;
+}
diff --git a/builtin/commit.c b/builtin/commit.c
new file mode 100644 (file)
index 0000000..66fdd22
--- /dev/null
@@ -0,0 +1,1407 @@
+/*
+ * Builtin "git commit"
+ *
+ * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>
+ * Based on git-commit.sh by Junio C Hamano and Linus Torvalds
+ */
+
+#include "cache.h"
+#include "cache-tree.h"
+#include "color.h"
+#include "dir.h"
+#include "builtin.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "commit.h"
+#include "revision.h"
+#include "wt-status.h"
+#include "run-command.h"
+#include "refs.h"
+#include "log-tree.h"
+#include "strbuf.h"
+#include "utf8.h"
+#include "parse-options.h"
+#include "string-list.h"
+#include "rerere.h"
+#include "unpack-trees.h"
+#include "quote.h"
+#include "submodule.h"
+
+static const char * const builtin_commit_usage[] = {
+       "git commit [options] [--] <filepattern>...",
+       NULL
+};
+
+static const char * const builtin_status_usage[] = {
+       "git status [options] [--] <filepattern>...",
+       NULL
+};
+
+static const char implicit_ident_advice[] =
+"Your name and email address were configured automatically based\n"
+"on your username and hostname. Please check that they are accurate.\n"
+"You can suppress this message by setting them explicitly:\n"
+"\n"
+"    git config --global user.name \"Your Name\"\n"
+"    git config --global user.email you@example.com\n"
+"\n"
+"If the identity used for this commit is wrong, you can fix it with:\n"
+"\n"
+"    git commit --amend --author='Your Name <you@example.com>'\n";
+
+static const char empty_amend_advice[] =
+"You asked to amend the most recent commit, but doing so would make\n"
+"it empty. You can repeat your command with --allow-empty, or you can\n"
+"remove the commit entirely with \"git reset HEAD^\".\n";
+
+static unsigned char head_sha1[20];
+
+static char *use_message_buffer;
+static const char commit_editmsg[] = "COMMIT_EDITMSG";
+static struct lock_file index_lock; /* real index */
+static struct lock_file false_lock; /* used only for partial commits */
+static enum {
+       COMMIT_AS_IS = 1,
+       COMMIT_NORMAL,
+       COMMIT_PARTIAL
+} commit_style;
+
+static const char *logfile, *force_author;
+static const char *template_file;
+static char *edit_message, *use_message;
+static char *author_name, *author_email, *author_date;
+static int all, edit_flag, also, interactive, only, amend, signoff;
+static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
+static int no_post_rewrite, allow_empty_message;
+static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
+/*
+ * The default commit message cleanup mode will remove the lines
+ * beginning with # (shell comments) and leading and trailing
+ * whitespaces (empty lines or containing only whitespaces)
+ * if editor is used, and only the whitespaces if the message
+ * is specified explicitly.
+ */
+static enum {
+       CLEANUP_SPACE,
+       CLEANUP_NONE,
+       CLEANUP_ALL
+} cleanup_mode;
+static char *cleanup_arg;
+
+static int use_editor = 1, initial_commit, in_merge, include_status = 1;
+static int show_ignored_in_status;
+static const char *only_include_assumed;
+static struct strbuf message;
+
+static int null_termination;
+static enum {
+       STATUS_FORMAT_LONG,
+       STATUS_FORMAT_SHORT,
+       STATUS_FORMAT_PORCELAIN
+} status_format = STATUS_FORMAT_LONG;
+static int status_show_branch;
+
+static int opt_parse_m(const struct option *opt, const char *arg, int unset)
+{
+       struct strbuf *buf = opt->value;
+       if (unset)
+               strbuf_setlen(buf, 0);
+       else {
+               strbuf_addstr(buf, arg);
+               strbuf_addstr(buf, "\n\n");
+       }
+       return 0;
+}
+
+static struct option builtin_commit_options[] = {
+       OPT__QUIET(&quiet),
+       OPT__VERBOSE(&verbose),
+
+       OPT_GROUP("Commit message options"),
+       OPT_FILENAME('F', "file", &logfile, "read log from file"),
+       OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"),
+       OPT_STRING(0, "date", &force_date, "DATE", "override date for commit"),
+       OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m),
+       OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"),
+       OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"),
+       OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"),
+       OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
+       OPT_FILENAME('t', "template", &template_file, "use specified template file"),
+       OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
+       OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
+       OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"),
+       /* end commit message options */
+
+       OPT_GROUP("Commit contents options"),
+       OPT_BOOLEAN('a', "all", &all, "commit all changed files"),
+       OPT_BOOLEAN('i', "include", &also, "add specified files to index for commit"),
+       OPT_BOOLEAN(0, "interactive", &interactive, "interactively add files"),
+       OPT_BOOLEAN('o', "only", &only, "commit only specified files"),
+       OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"),
+       OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"),
+       OPT_SET_INT(0, "short", &status_format, "show status concisely",
+                   STATUS_FORMAT_SHORT),
+       OPT_BOOLEAN(0, "branch", &status_show_branch, "show branch information"),
+       OPT_SET_INT(0, "porcelain", &status_format,
+                   "show porcelain output format", STATUS_FORMAT_PORCELAIN),
+       OPT_BOOLEAN('z', "null", &null_termination,
+                   "terminate entries with NUL"),
+       OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
+       OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"),
+       { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+       /* end commit contents options */
+
+       { OPTION_BOOLEAN, 0, "allow-empty", &allow_empty, NULL,
+         "ok to record an empty change",
+         PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
+       { OPTION_BOOLEAN, 0, "allow-empty-message", &allow_empty_message, NULL,
+         "ok to record a change with an empty message",
+         PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
+
+       OPT_END()
+};
+
+static void rollback_index_files(void)
+{
+       switch (commit_style) {
+       case COMMIT_AS_IS:
+               break; /* nothing to do */
+       case COMMIT_NORMAL:
+               rollback_lock_file(&index_lock);
+               break;
+       case COMMIT_PARTIAL:
+               rollback_lock_file(&index_lock);
+               rollback_lock_file(&false_lock);
+               break;
+       }
+}
+
+static int commit_index_files(void)
+{
+       int err = 0;
+
+       switch (commit_style) {
+       case COMMIT_AS_IS:
+               break; /* nothing to do */
+       case COMMIT_NORMAL:
+               err = commit_lock_file(&index_lock);
+               break;
+       case COMMIT_PARTIAL:
+               err = commit_lock_file(&index_lock);
+               rollback_lock_file(&false_lock);
+               break;
+       }
+
+       return err;
+}
+
+/*
+ * Take a union of paths in the index and the named tree (typically, "HEAD"),
+ * and return the paths that match the given pattern in list.
+ */
+static int list_paths(struct string_list *list, const char *with_tree,
+                     const char *prefix, const char **pattern)
+{
+       int i;
+       char *m;
+
+       for (i = 0; pattern[i]; i++)
+               ;
+       m = xcalloc(1, i);
+
+       if (with_tree)
+               overlay_tree_on_cache(with_tree, prefix);
+
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               struct string_list_item *item;
+
+               if (ce->ce_flags & CE_UPDATE)
+                       continue;
+               if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m))
+                       continue;
+               item = string_list_insert(list, ce->name);
+               if (ce_skip_worktree(ce))
+                       item->util = item; /* better a valid pointer than a fake one */
+       }
+
+       return report_path_error(m, pattern, prefix ? strlen(prefix) : 0);
+}
+
+static void add_remove_files(struct string_list *list)
+{
+       int i;
+       for (i = 0; i < list->nr; i++) {
+               struct stat st;
+               struct string_list_item *p = &(list->items[i]);
+
+               /* p->util is skip-worktree */
+               if (p->util)
+                       continue;
+
+               if (!lstat(p->string, &st)) {
+                       if (add_to_cache(p->string, &st, 0))
+                               die("updating files failed");
+               } else
+                       remove_file_from_cache(p->string);
+       }
+}
+
+static void create_base_index(void)
+{
+       struct tree *tree;
+       struct unpack_trees_options opts;
+       struct tree_desc t;
+
+       if (initial_commit) {
+               discard_cache();
+               return;
+       }
+
+       memset(&opts, 0, sizeof(opts));
+       opts.head_idx = 1;
+       opts.index_only = 1;
+       opts.merge = 1;
+       opts.src_index = &the_index;
+       opts.dst_index = &the_index;
+
+       opts.fn = oneway_merge;
+       tree = parse_tree_indirect(head_sha1);
+       if (!tree)
+               die("failed to unpack HEAD tree object");
+       parse_tree(tree);
+       init_tree_desc(&t, tree->buffer, tree->size);
+       if (unpack_trees(1, &t, &opts))
+               exit(128); /* We've already reported the error, finish dying */
+}
+
+static void refresh_cache_or_die(int refresh_flags)
+{
+       /*
+        * refresh_flags contains REFRESH_QUIET, so the only errors
+        * are for unmerged entries.
+        */
+       if (refresh_cache(refresh_flags | REFRESH_IN_PORCELAIN))
+               die_resolve_conflict("commit");
+}
+
+static char *prepare_index(int argc, const char **argv, const char *prefix, int is_status)
+{
+       int fd;
+       struct string_list partial;
+       const char **pathspec = NULL;
+       int refresh_flags = REFRESH_QUIET;
+
+       if (is_status)
+               refresh_flags |= REFRESH_UNMERGED;
+       if (interactive) {
+               if (interactive_add(argc, argv, prefix) != 0)
+                       die("interactive add failed");
+               if (read_cache_preload(NULL) < 0)
+                       die("index file corrupt");
+               commit_style = COMMIT_AS_IS;
+               return get_index_file();
+       }
+
+       if (*argv)
+               pathspec = get_pathspec(prefix, argv);
+
+       if (read_cache_preload(pathspec) < 0)
+               die("index file corrupt");
+
+       /*
+        * Non partial, non as-is commit.
+        *
+        * (1) get the real index;
+        * (2) update the_index as necessary;
+        * (3) write the_index out to the real index (still locked);
+        * (4) return the name of the locked index file.
+        *
+        * The caller should run hooks on the locked real index, and
+        * (A) if all goes well, commit the real index;
+        * (B) on failure, rollback the real index.
+        */
+       if (all || (also && pathspec && *pathspec)) {
+               fd = hold_locked_index(&index_lock, 1);
+               add_files_to_cache(also ? prefix : NULL, pathspec, 0);
+               refresh_cache_or_die(refresh_flags);
+               if (write_cache(fd, active_cache, active_nr) ||
+                   close_lock_file(&index_lock))
+                       die("unable to write new_index file");
+               commit_style = COMMIT_NORMAL;
+               return index_lock.filename;
+       }
+
+       /*
+        * As-is commit.
+        *
+        * (1) return the name of the real index file.
+        *
+        * The caller should run hooks on the real index,
+        * and create commit from the_index.
+        * We still need to refresh the index here.
+        */
+       if (!pathspec || !*pathspec) {
+               fd = hold_locked_index(&index_lock, 1);
+               refresh_cache_or_die(refresh_flags);
+               if (active_cache_changed) {
+                       if (write_cache(fd, active_cache, active_nr) ||
+                           commit_locked_index(&index_lock))
+                               die("unable to write new_index file");
+               } else {
+                       rollback_lock_file(&index_lock);
+               }
+               commit_style = COMMIT_AS_IS;
+               return get_index_file();
+       }
+
+       /*
+        * A partial commit.
+        *
+        * (0) find the set of affected paths;
+        * (1) get lock on the real index file;
+        * (2) update the_index with the given paths;
+        * (3) write the_index out to the real index (still locked);
+        * (4) get lock on the false index file;
+        * (5) reset the_index from HEAD;
+        * (6) update the_index the same way as (2);
+        * (7) write the_index out to the false index file;
+        * (8) return the name of the false index file (still locked);
+        *
+        * The caller should run hooks on the locked false index, and
+        * create commit from it.  Then
+        * (A) if all goes well, commit the real index;
+        * (B) on failure, rollback the real index;
+        * In either case, rollback the false index.
+        */
+       commit_style = COMMIT_PARTIAL;
+
+       if (in_merge)
+               die("cannot do a partial commit during a merge.");
+
+       memset(&partial, 0, sizeof(partial));
+       partial.strdup_strings = 1;
+       if (list_paths(&partial, initial_commit ? NULL : "HEAD", prefix, pathspec))
+               exit(1);
+
+       discard_cache();
+       if (read_cache() < 0)
+               die("cannot read the index");
+
+       fd = hold_locked_index(&index_lock, 1);
+       add_remove_files(&partial);
+       refresh_cache(REFRESH_QUIET);
+       if (write_cache(fd, active_cache, active_nr) ||
+           close_lock_file(&index_lock))
+               die("unable to write new_index file");
+
+       fd = hold_lock_file_for_update(&false_lock,
+                                      git_path("next-index-%"PRIuMAX,
+                                               (uintmax_t) getpid()),
+                                      LOCK_DIE_ON_ERROR);
+
+       create_base_index();
+       add_remove_files(&partial);
+       refresh_cache(REFRESH_QUIET);
+
+       if (write_cache(fd, active_cache, active_nr) ||
+           close_lock_file(&false_lock))
+               die("unable to write temporary index file");
+
+       discard_cache();
+       read_cache_from(false_lock.filename);
+
+       return false_lock.filename;
+}
+
+static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn,
+                     struct wt_status *s)
+{
+       unsigned char sha1[20];
+
+       if (s->relative_paths)
+               s->prefix = prefix;
+
+       if (amend) {
+               s->amend = 1;
+               s->reference = "HEAD^1";
+       }
+       s->verbose = verbose;
+       s->index_file = index_file;
+       s->fp = fp;
+       s->nowarn = nowarn;
+       s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
+
+       wt_status_collect(s);
+
+       switch (status_format) {
+       case STATUS_FORMAT_SHORT:
+               wt_shortstatus_print(s, null_termination, status_show_branch);
+               break;
+       case STATUS_FORMAT_PORCELAIN:
+               wt_porcelain_print(s, null_termination);
+               break;
+       case STATUS_FORMAT_LONG:
+               wt_status_print(s);
+               break;
+       }
+
+       return s->commitable;
+}
+
+static int is_a_merge(const unsigned char *sha1)
+{
+       struct commit *commit = lookup_commit(sha1);
+       if (!commit || parse_commit(commit))
+               die("could not parse HEAD commit");
+       return !!(commit->parents && commit->parents->next);
+}
+
+static const char sign_off_header[] = "Signed-off-by: ";
+
+static void determine_author_info(void)
+{
+       char *name, *email, *date;
+
+       name = getenv("GIT_AUTHOR_NAME");
+       email = getenv("GIT_AUTHOR_EMAIL");
+       date = getenv("GIT_AUTHOR_DATE");
+
+       if (use_message && !renew_authorship) {
+               const char *a, *lb, *rb, *eol;
+
+               a = strstr(use_message_buffer, "\nauthor ");
+               if (!a)
+                       die("invalid commit: %s", use_message);
+
+               lb = strchrnul(a + strlen("\nauthor "), '<');
+               rb = strchrnul(lb, '>');
+               eol = strchrnul(rb, '\n');
+               if (!*lb || !*rb || !*eol)
+                       die("invalid commit: %s", use_message);
+
+               if (lb == a + strlen("\nauthor "))
+                       /* \nauthor <foo@example.com> */
+                       name = xcalloc(1, 1);
+               else
+                       name = xmemdupz(a + strlen("\nauthor "),
+                                       (lb - strlen(" ") -
+                                        (a + strlen("\nauthor "))));
+               email = xmemdupz(lb + strlen("<"), rb - (lb + strlen("<")));
+               date = xmemdupz(rb + strlen("> "), eol - (rb + strlen("> ")));
+       }
+
+       if (force_author) {
+               const char *lb = strstr(force_author, " <");
+               const char *rb = strchr(force_author, '>');
+
+               if (!lb || !rb)
+                       die("malformed --author parameter");
+               name = xstrndup(force_author, lb - force_author);
+               email = xstrndup(lb + 2, rb - (lb + 2));
+       }
+
+       if (force_date)
+               date = force_date;
+
+       author_name = name;
+       author_email = email;
+       author_date = date;
+}
+
+static int ends_rfc2822_footer(struct strbuf *sb)
+{
+       int ch;
+       int hit = 0;
+       int i, j, k;
+       int len = sb->len;
+       int first = 1;
+       const char *buf = sb->buf;
+
+       for (i = len - 1; i > 0; i--) {
+               if (hit && buf[i] == '\n')
+                       break;
+               hit = (buf[i] == '\n');
+       }
+
+       while (i < len - 1 && buf[i] == '\n')
+               i++;
+
+       for (; i < len; i = k) {
+               for (k = i; k < len && buf[k] != '\n'; k++)
+                       ; /* do nothing */
+               k++;
+
+               if ((buf[k] == ' ' || buf[k] == '\t') && !first)
+                       continue;
+
+               first = 0;
+
+               for (j = 0; i + j < len; j++) {
+                       ch = buf[i + j];
+                       if (ch == ':')
+                               break;
+                       if (isalnum(ch) ||
+                           (ch == '-'))
+                               continue;
+                       return 0;
+               }
+       }
+       return 1;
+}
+
+static int prepare_to_commit(const char *index_file, const char *prefix,
+                            struct wt_status *s)
+{
+       struct stat statbuf;
+       int commitable, saved_color_setting;
+       struct strbuf sb = STRBUF_INIT;
+       char *buffer;
+       FILE *fp;
+       const char *hook_arg1 = NULL;
+       const char *hook_arg2 = NULL;
+       int ident_shown = 0;
+
+       if (!no_verify && run_hook(index_file, "pre-commit", NULL))
+               return 0;
+
+       if (message.len) {
+               strbuf_addbuf(&sb, &message);
+               hook_arg1 = "message";
+       } else if (logfile && !strcmp(logfile, "-")) {
+               if (isatty(0))
+                       fprintf(stderr, "(reading log message from standard input)\n");
+               if (strbuf_read(&sb, 0, 0) < 0)
+                       die_errno("could not read log from standard input");
+               hook_arg1 = "message";
+       } else if (logfile) {
+               if (strbuf_read_file(&sb, logfile, 0) < 0)
+                       die_errno("could not read log file '%s'",
+                                 logfile);
+               hook_arg1 = "message";
+       } else if (use_message) {
+               buffer = strstr(use_message_buffer, "\n\n");
+               if (!buffer || buffer[2] == '\0')
+                       die("commit has empty message");
+               strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
+               hook_arg1 = "commit";
+               hook_arg2 = use_message;
+       } else if (!stat(git_path("MERGE_MSG"), &statbuf)) {
+               if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0)
+                       die_errno("could not read MERGE_MSG");
+               hook_arg1 = "merge";
+       } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) {
+               if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0)
+                       die_errno("could not read SQUASH_MSG");
+               hook_arg1 = "squash";
+       } else if (template_file && !stat(template_file, &statbuf)) {
+               if (strbuf_read_file(&sb, template_file, 0) < 0)
+                       die_errno("could not read '%s'", template_file);
+               hook_arg1 = "template";
+       }
+
+       /*
+        * This final case does not modify the template message,
+        * it just sets the argument to the prepare-commit-msg hook.
+        */
+       else if (in_merge)
+               hook_arg1 = "merge";
+
+       fp = fopen(git_path(commit_editmsg), "w");
+       if (fp == NULL)
+               die_errno("could not open '%s'", git_path(commit_editmsg));
+
+       if (cleanup_mode != CLEANUP_NONE)
+               stripspace(&sb, 0);
+
+       if (signoff) {
+               struct strbuf sob = STRBUF_INIT;
+               int i;
+
+               strbuf_addstr(&sob, sign_off_header);
+               strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"),
+                                            getenv("GIT_COMMITTER_EMAIL")));
+               strbuf_addch(&sob, '\n');
+               for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--)
+                       ; /* do nothing */
+               if (prefixcmp(sb.buf + i, sob.buf)) {
+                       if (!i || !ends_rfc2822_footer(&sb))
+                               strbuf_addch(&sb, '\n');
+                       strbuf_addbuf(&sb, &sob);
+               }
+               strbuf_release(&sob);
+       }
+
+       if (fwrite(sb.buf, 1, sb.len, fp) < sb.len)
+               die_errno("could not write commit template");
+
+       strbuf_release(&sb);
+
+       determine_author_info();
+
+       /* This checks if committer ident is explicitly given */
+       git_committer_info(0);
+       if (use_editor && include_status) {
+               char *author_ident;
+               const char *committer_ident;
+
+               if (in_merge)
+                       fprintf(fp,
+                               "#\n"
+                               "# It looks like you may be committing a MERGE.\n"
+                               "# If this is not correct, please remove the file\n"
+                               "#      %s\n"
+                               "# and try again.\n"
+                               "#\n",
+                               git_path("MERGE_HEAD"));
+
+               fprintf(fp,
+                       "\n"
+                       "# Please enter the commit message for your changes.");
+               if (cleanup_mode == CLEANUP_ALL)
+                       fprintf(fp,
+                               " Lines starting\n"
+                               "# with '#' will be ignored, and an empty"
+                               " message aborts the commit.\n");
+               else /* CLEANUP_SPACE, that is. */
+                       fprintf(fp,
+                               " Lines starting\n"
+                               "# with '#' will be kept; you may remove them"
+                               " yourself if you want to.\n"
+                               "# An empty message aborts the commit.\n");
+               if (only_include_assumed)
+                       fprintf(fp, "# %s\n", only_include_assumed);
+
+               author_ident = xstrdup(fmt_name(author_name, author_email));
+               committer_ident = fmt_name(getenv("GIT_COMMITTER_NAME"),
+                                          getenv("GIT_COMMITTER_EMAIL"));
+               if (strcmp(author_ident, committer_ident))
+                       fprintf(fp,
+                               "%s"
+                               "# Author:    %s\n",
+                               ident_shown++ ? "" : "#\n",
+                               author_ident);
+               free(author_ident);
+
+               if (!user_ident_sufficiently_given())
+                       fprintf(fp,
+                               "%s"
+                               "# Committer: %s\n",
+                               ident_shown++ ? "" : "#\n",
+                               committer_ident);
+
+               if (ident_shown)
+                       fprintf(fp, "#\n");
+
+               saved_color_setting = s->use_color;
+               s->use_color = 0;
+               commitable = run_status(fp, index_file, prefix, 1, s);
+               s->use_color = saved_color_setting;
+       } else {
+               unsigned char sha1[20];
+               const char *parent = "HEAD";
+
+               if (!active_nr && read_cache() < 0)
+                       die("Cannot read index");
+
+               if (amend)
+                       parent = "HEAD^1";
+
+               if (get_sha1(parent, sha1))
+                       commitable = !!active_nr;
+               else
+                       commitable = index_differs_from(parent, 0);
+       }
+
+       fclose(fp);
+
+       if (!commitable && !in_merge && !allow_empty &&
+           !(amend && is_a_merge(head_sha1))) {
+               run_status(stdout, index_file, prefix, 0, s);
+               if (amend)
+                       fputs(empty_amend_advice, stderr);
+               return 0;
+       }
+
+       /*
+        * Re-read the index as pre-commit hook could have updated it,
+        * and write it out as a tree.  We must do this before we invoke
+        * the editor and after we invoke run_status above.
+        */
+       discard_cache();
+       read_cache_from(index_file);
+       if (!active_cache_tree)
+               active_cache_tree = cache_tree();
+       if (cache_tree_update(active_cache_tree,
+                             active_cache, active_nr, 0, 0) < 0) {
+               error("Error building trees");
+               return 0;
+       }
+
+       if (run_hook(index_file, "prepare-commit-msg",
+                    git_path(commit_editmsg), hook_arg1, hook_arg2, NULL))
+               return 0;
+
+       if (use_editor) {
+               char index[PATH_MAX];
+               const char *env[2] = { NULL };
+               env[0] =  index;
+               snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
+               if (launch_editor(git_path(commit_editmsg), NULL, env)) {
+                       fprintf(stderr,
+                       "Please supply the message using either -m or -F option.\n");
+                       exit(1);
+               }
+       }
+
+       if (!no_verify &&
+           run_hook(index_file, "commit-msg", git_path(commit_editmsg), NULL)) {
+               return 0;
+       }
+
+       return 1;
+}
+
+/*
+ * Find out if the message in the strbuf contains only whitespace and
+ * Signed-off-by lines.
+ */
+static int message_is_empty(struct strbuf *sb)
+{
+       struct strbuf tmpl = STRBUF_INIT;
+       const char *nl;
+       int eol, i, start = 0;
+
+       if (cleanup_mode == CLEANUP_NONE && sb->len)
+               return 0;
+
+       /* See if the template is just a prefix of the message. */
+       if (template_file && strbuf_read_file(&tmpl, template_file, 0) > 0) {
+               stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
+               if (start + tmpl.len <= sb->len &&
+                   memcmp(tmpl.buf, sb->buf + start, tmpl.len) == 0)
+                       start += tmpl.len;
+       }
+       strbuf_release(&tmpl);
+
+       /* Check if the rest is just whitespace and Signed-of-by's. */
+       for (i = start; i < sb->len; i++) {
+               nl = memchr(sb->buf + i, '\n', sb->len - i);
+               if (nl)
+                       eol = nl - sb->buf;
+               else
+                       eol = sb->len;
+
+               if (strlen(sign_off_header) <= eol - i &&
+                   !prefixcmp(sb->buf + i, sign_off_header)) {
+                       i = eol;
+                       continue;
+               }
+               while (i < eol)
+                       if (!isspace(sb->buf[i++]))
+                               return 0;
+       }
+
+       return 1;
+}
+
+static const char *find_author_by_nickname(const char *name)
+{
+       struct rev_info revs;
+       struct commit *commit;
+       struct strbuf buf = STRBUF_INIT;
+       const char *av[20];
+       int ac = 0;
+
+       init_revisions(&revs, NULL);
+       strbuf_addf(&buf, "--author=%s", name);
+       av[++ac] = "--all";
+       av[++ac] = "-i";
+       av[++ac] = buf.buf;
+       av[++ac] = NULL;
+       setup_revisions(ac, av, &revs, NULL);
+       prepare_revision_walk(&revs);
+       commit = get_revision(&revs);
+       if (commit) {
+               struct pretty_print_context ctx = {0};
+               ctx.date_mode = DATE_NORMAL;
+               strbuf_release(&buf);
+               format_commit_message(commit, "%an <%ae>", &buf, &ctx);
+               return strbuf_detach(&buf, NULL);
+       }
+       die("No existing author found with '%s'", name);
+}
+
+
+static void handle_untracked_files_arg(struct wt_status *s)
+{
+       if (!untracked_files_arg)
+               ; /* default already initialized */
+       else if (!strcmp(untracked_files_arg, "no"))
+               s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
+       else if (!strcmp(untracked_files_arg, "normal"))
+               s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
+       else if (!strcmp(untracked_files_arg, "all"))
+               s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
+       else
+               die("Invalid untracked files mode '%s'", untracked_files_arg);
+}
+
+static int parse_and_validate_options(int argc, const char *argv[],
+                                     const char * const usage[],
+                                     const char *prefix,
+                                     struct wt_status *s)
+{
+       int f = 0;
+
+       argc = parse_options(argc, argv, prefix, builtin_commit_options, usage,
+                            0);
+
+       if (force_author && !strchr(force_author, '>'))
+               force_author = find_author_by_nickname(force_author);
+
+       if (force_author && renew_authorship)
+               die("Using both --reset-author and --author does not make sense");
+
+       if (logfile || message.len || use_message)
+               use_editor = 0;
+       if (edit_flag)
+               use_editor = 1;
+       if (!use_editor)
+               setenv("GIT_EDITOR", ":", 1);
+
+       if (get_sha1("HEAD", head_sha1))
+               initial_commit = 1;
+
+       /* Sanity check options */
+       if (amend && initial_commit)
+               die("You have nothing to amend.");
+       if (amend && in_merge)
+               die("You are in the middle of a merge -- cannot amend.");
+
+       if (use_message)
+               f++;
+       if (edit_message)
+               f++;
+       if (logfile)
+               f++;
+       if (f > 1)
+               die("Only one of -c/-C/-F can be used.");
+       if (message.len && f > 0)
+               die("Option -m cannot be combined with -c/-C/-F.");
+       if (edit_message)
+               use_message = edit_message;
+       if (amend && !use_message)
+               use_message = "HEAD";
+       if (!use_message && renew_authorship)
+               die("--reset-author can be used only with -C, -c or --amend.");
+       if (use_message) {
+               unsigned char sha1[20];
+               static char utf8[] = "UTF-8";
+               const char *out_enc;
+               char *enc, *end;
+               struct commit *commit;
+
+               if (get_sha1(use_message, sha1))
+                       die("could not lookup commit %s", use_message);
+               commit = lookup_commit_reference(sha1);
+               if (!commit || parse_commit(commit))
+                       die("could not parse commit %s", use_message);
+
+               enc = strstr(commit->buffer, "\nencoding");
+               if (enc) {
+                       end = strchr(enc + 10, '\n');
+                       enc = xstrndup(enc + 10, end - (enc + 10));
+               } else {
+                       enc = utf8;
+               }
+               out_enc = git_commit_encoding ? git_commit_encoding : utf8;
+
+               if (strcmp(out_enc, enc))
+                       use_message_buffer =
+                               reencode_string(commit->buffer, out_enc, enc);
+
+               /*
+                * If we failed to reencode the buffer, just copy it
+                * byte for byte so the user can try to fix it up.
+                * This also handles the case where input and output
+                * encodings are identical.
+                */
+               if (use_message_buffer == NULL)
+                       use_message_buffer = xstrdup(commit->buffer);
+               if (enc != utf8)
+                       free(enc);
+       }
+
+       if (!!also + !!only + !!all + !!interactive > 1)
+               die("Only one of --include/--only/--all/--interactive can be used.");
+       if (argc == 0 && (also || (only && !amend)))
+               die("No paths with --include/--only does not make sense.");
+       if (argc == 0 && only && amend)
+               only_include_assumed = "Clever... amending the last one with dirty index.";
+       if (argc > 0 && !also && !only)
+               only_include_assumed = "Explicit paths specified without -i nor -o; assuming --only paths...";
+       if (!cleanup_arg || !strcmp(cleanup_arg, "default"))
+               cleanup_mode = use_editor ? CLEANUP_ALL : CLEANUP_SPACE;
+       else if (!strcmp(cleanup_arg, "verbatim"))
+               cleanup_mode = CLEANUP_NONE;
+       else if (!strcmp(cleanup_arg, "whitespace"))
+               cleanup_mode = CLEANUP_SPACE;
+       else if (!strcmp(cleanup_arg, "strip"))
+               cleanup_mode = CLEANUP_ALL;
+       else
+               die("Invalid cleanup mode %s", cleanup_arg);
+
+       handle_untracked_files_arg(s);
+
+       if (all && argc > 0)
+               die("Paths with -a does not make sense.");
+       else if (interactive && argc > 0)
+               die("Paths with --interactive does not make sense.");
+
+       if (null_termination && status_format == STATUS_FORMAT_LONG)
+               status_format = STATUS_FORMAT_PORCELAIN;
+       if (status_format != STATUS_FORMAT_LONG)
+               dry_run = 1;
+
+       return argc;
+}
+
+static int dry_run_commit(int argc, const char **argv, const char *prefix,
+                         struct wt_status *s)
+{
+       int commitable;
+       const char *index_file;
+
+       index_file = prepare_index(argc, argv, prefix, 1);
+       commitable = run_status(stdout, index_file, prefix, 0, s);
+       rollback_index_files();
+
+       return commitable ? 0 : 1;
+}
+
+static int parse_status_slot(const char *var, int offset)
+{
+       if (!strcasecmp(var+offset, "header"))
+               return WT_STATUS_HEADER;
+       if (!strcasecmp(var+offset, "updated")
+               || !strcasecmp(var+offset, "added"))
+               return WT_STATUS_UPDATED;
+       if (!strcasecmp(var+offset, "changed"))
+               return WT_STATUS_CHANGED;
+       if (!strcasecmp(var+offset, "untracked"))
+               return WT_STATUS_UNTRACKED;
+       if (!strcasecmp(var+offset, "nobranch"))
+               return WT_STATUS_NOBRANCH;
+       if (!strcasecmp(var+offset, "unmerged"))
+               return WT_STATUS_UNMERGED;
+       return -1;
+}
+
+static int git_status_config(const char *k, const char *v, void *cb)
+{
+       struct wt_status *s = cb;
+
+       if (!strcmp(k, "status.submodulesummary")) {
+               int is_bool;
+               s->submodule_summary = git_config_bool_or_int(k, v, &is_bool);
+               if (is_bool && s->submodule_summary)
+                       s->submodule_summary = -1;
+               return 0;
+       }
+       if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
+               s->use_color = git_config_colorbool(k, v, -1);
+               return 0;
+       }
+       if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
+               int slot = parse_status_slot(k, 13);
+               if (slot < 0)
+                       return 0;
+               if (!v)
+                       return config_error_nonbool(k);
+               color_parse(v, k, s->color_palette[slot]);
+               return 0;
+       }
+       if (!strcmp(k, "status.relativepaths")) {
+               s->relative_paths = git_config_bool(k, v);
+               return 0;
+       }
+       if (!strcmp(k, "status.showuntrackedfiles")) {
+               if (!v)
+                       return config_error_nonbool(k);
+               else if (!strcmp(v, "no"))
+                       s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
+               else if (!strcmp(v, "normal"))
+                       s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
+               else if (!strcmp(v, "all"))
+                       s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
+               else
+                       return error("Invalid untracked files mode '%s'", v);
+               return 0;
+       }
+       return git_diff_ui_config(k, v, NULL);
+}
+
+int cmd_status(int argc, const char **argv, const char *prefix)
+{
+       struct wt_status s;
+       int fd;
+       unsigned char sha1[20];
+       static struct option builtin_status_options[] = {
+               OPT__VERBOSE(&verbose),
+               OPT_SET_INT('s', "short", &status_format,
+                           "show status concisely", STATUS_FORMAT_SHORT),
+               OPT_BOOLEAN('b', "branch", &status_show_branch,
+                           "show branch information"),
+               OPT_SET_INT(0, "porcelain", &status_format,
+                           "show porcelain output format",
+                           STATUS_FORMAT_PORCELAIN),
+               OPT_BOOLEAN('z', "null", &null_termination,
+                           "terminate entries with NUL"),
+               { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg,
+                 "mode",
+                 "show untracked files, optional modes: all, normal, no. (Default: all)",
+                 PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+               OPT_BOOLEAN(0, "ignored", &show_ignored_in_status,
+                           "show ignored files"),
+               { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, "when",
+                 "ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)",
+                 PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+               OPT_END(),
+       };
+
+       if (null_termination && status_format == STATUS_FORMAT_LONG)
+               status_format = STATUS_FORMAT_PORCELAIN;
+
+       wt_status_prepare(&s);
+       gitmodules_config();
+       git_config(git_status_config, &s);
+       in_merge = file_exists(git_path("MERGE_HEAD"));
+       argc = parse_options(argc, argv, prefix,
+                            builtin_status_options,
+                            builtin_status_usage, 0);
+       handle_untracked_files_arg(&s);
+       if (show_ignored_in_status)
+               s.show_ignored_files = 1;
+       if (*argv)
+               s.pathspec = get_pathspec(prefix, argv);
+
+       read_cache_preload(s.pathspec);
+       refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL);
+
+       fd = hold_locked_index(&index_lock, 0);
+       if (0 <= fd) {
+               if (active_cache_changed &&
+                   !write_cache(fd, active_cache, active_nr))
+                       commit_locked_index(&index_lock);
+               else
+                       rollback_lock_file(&index_lock);
+       }
+
+       s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
+       s.in_merge = in_merge;
+       s.ignore_submodule_arg = ignore_submodule_arg;
+       wt_status_collect(&s);
+
+       if (s.relative_paths)
+               s.prefix = prefix;
+       if (s.use_color == -1)
+               s.use_color = git_use_color_default;
+       if (diff_use_color_default == -1)
+               diff_use_color_default = git_use_color_default;
+
+       switch (status_format) {
+       case STATUS_FORMAT_SHORT:
+               wt_shortstatus_print(&s, null_termination, status_show_branch);
+               break;
+       case STATUS_FORMAT_PORCELAIN:
+               wt_porcelain_print(&s, null_termination);
+               break;
+       case STATUS_FORMAT_LONG:
+               s.verbose = verbose;
+               s.ignore_submodule_arg = ignore_submodule_arg;
+               wt_status_print(&s);
+               break;
+       }
+       return 0;
+}
+
+static void print_summary(const char *prefix, const unsigned char *sha1)
+{
+       struct rev_info rev;
+       struct commit *commit;
+       struct strbuf format = STRBUF_INIT;
+       unsigned char junk_sha1[20];
+       const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL);
+       struct pretty_print_context pctx = {0};
+       struct strbuf author_ident = STRBUF_INIT;
+       struct strbuf committer_ident = STRBUF_INIT;
+
+       commit = lookup_commit(sha1);
+       if (!commit)
+               die("couldn't look up newly created commit");
+       if (!commit || parse_commit(commit))
+               die("could not parse newly created commit");
+
+       strbuf_addstr(&format, "format:%h] %s");
+
+       format_commit_message(commit, "%an <%ae>", &author_ident, &pctx);
+       format_commit_message(commit, "%cn <%ce>", &committer_ident, &pctx);
+       if (strbuf_cmp(&author_ident, &committer_ident)) {
+               strbuf_addstr(&format, "\n Author: ");
+               strbuf_addbuf_percentquote(&format, &author_ident);
+       }
+       if (!user_ident_sufficiently_given()) {
+               strbuf_addstr(&format, "\n Committer: ");
+               strbuf_addbuf_percentquote(&format, &committer_ident);
+               if (advice_implicit_identity) {
+                       strbuf_addch(&format, '\n');
+                       strbuf_addstr(&format, implicit_ident_advice);
+               }
+       }
+       strbuf_release(&author_ident);
+       strbuf_release(&committer_ident);
+
+       init_revisions(&rev, prefix);
+       setup_revisions(0, NULL, &rev, NULL);
+
+       rev.diff = 1;
+       rev.diffopt.output_format =
+               DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY;
+
+       rev.verbose_header = 1;
+       rev.show_root_diff = 1;
+       get_commit_format(format.buf, &rev);
+       rev.always_show_header = 0;
+       rev.diffopt.detect_rename = 1;
+       rev.diffopt.rename_limit = 100;
+       rev.diffopt.break_opt = 0;
+       diff_setup_done(&rev.diffopt);
+
+       printf("[%s%s ",
+               !prefixcmp(head, "refs/heads/") ?
+                       head + 11 :
+                       !strcmp(head, "HEAD") ?
+                               "detached HEAD" :
+                               head,
+               initial_commit ? " (root-commit)" : "");
+
+       if (!log_tree_commit(&rev, commit)) {
+               rev.always_show_header = 1;
+               rev.use_terminator = 1;
+               log_tree_commit(&rev, commit);
+       }
+
+       strbuf_release(&format);
+}
+
+static int git_commit_config(const char *k, const char *v, void *cb)
+{
+       struct wt_status *s = cb;
+
+       if (!strcmp(k, "commit.template"))
+               return git_config_pathname(&template_file, k, v);
+       if (!strcmp(k, "commit.status")) {
+               include_status = git_config_bool(k, v);
+               return 0;
+       }
+
+       return git_status_config(k, v, s);
+}
+
+static const char post_rewrite_hook[] = "hooks/post-rewrite";
+
+static int run_rewrite_hook(const unsigned char *oldsha1,
+                           const unsigned char *newsha1)
+{
+       /* oldsha1 SP newsha1 LF NUL */
+       static char buf[2*40 + 3];
+       struct child_process proc;
+       const char *argv[3];
+       int code;
+       size_t n;
+
+       if (access(git_path(post_rewrite_hook), X_OK) < 0)
+               return 0;
+
+       argv[0] = git_path(post_rewrite_hook);
+       argv[1] = "amend";
+       argv[2] = NULL;
+
+       memset(&proc, 0, sizeof(proc));
+       proc.argv = argv;
+       proc.in = -1;
+       proc.stdout_to_stderr = 1;
+
+       code = start_command(&proc);
+       if (code)
+               return code;
+       n = snprintf(buf, sizeof(buf), "%s %s\n",
+                    sha1_to_hex(oldsha1), sha1_to_hex(newsha1));
+       write_in_full(proc.in, buf, n);
+       close(proc.in);
+       return finish_command(&proc);
+}
+
+int cmd_commit(int argc, const char **argv, const char *prefix)
+{
+       struct strbuf sb = STRBUF_INIT;
+       const char *index_file, *reflog_msg;
+       char *nl, *p;
+       unsigned char commit_sha1[20];
+       struct ref_lock *ref_lock;
+       struct commit_list *parents = NULL, **pptr = &parents;
+       struct stat statbuf;
+       int allow_fast_forward = 1;
+       struct wt_status s;
+
+       wt_status_prepare(&s);
+       git_config(git_commit_config, &s);
+       in_merge = file_exists(git_path("MERGE_HEAD"));
+       s.in_merge = in_merge;
+
+       if (s.use_color == -1)
+               s.use_color = git_use_color_default;
+       argc = parse_and_validate_options(argc, argv, builtin_commit_usage,
+                                         prefix, &s);
+       if (dry_run) {
+               if (diff_use_color_default == -1)
+                       diff_use_color_default = git_use_color_default;
+               return dry_run_commit(argc, argv, prefix, &s);
+       }
+       index_file = prepare_index(argc, argv, prefix, 0);
+
+       /* Set up everything for writing the commit object.  This includes
+          running hooks, writing the trees, and interacting with the user.  */
+       if (!prepare_to_commit(index_file, prefix, &s)) {
+               rollback_index_files();
+               return 1;
+       }
+
+       /* Determine parents */
+       reflog_msg = getenv("GIT_REFLOG_ACTION");
+       if (initial_commit) {
+               if (!reflog_msg)
+                       reflog_msg = "commit (initial)";
+       } else if (amend) {
+               struct commit_list *c;
+               struct commit *commit;
+
+               if (!reflog_msg)
+                       reflog_msg = "commit (amend)";
+               commit = lookup_commit(head_sha1);
+               if (!commit || parse_commit(commit))
+                       die("could not parse HEAD commit");
+
+               for (c = commit->parents; c; c = c->next)
+                       pptr = &commit_list_insert(c->item, pptr)->next;
+       } else if (in_merge) {
+               struct strbuf m = STRBUF_INIT;
+               FILE *fp;
+
+               if (!reflog_msg)
+                       reflog_msg = "commit (merge)";
+               pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
+               fp = fopen(git_path("MERGE_HEAD"), "r");
+               if (fp == NULL)
+                       die_errno("could not open '%s' for reading",
+                                 git_path("MERGE_HEAD"));
+               while (strbuf_getline(&m, fp, '\n') != EOF) {
+                       unsigned char sha1[20];
+                       if (get_sha1_hex(m.buf, sha1) < 0)
+                               die("Corrupt MERGE_HEAD file (%s)", m.buf);
+                       pptr = &commit_list_insert(lookup_commit(sha1), pptr)->next;
+               }
+               fclose(fp);
+               strbuf_release(&m);
+               if (!stat(git_path("MERGE_MODE"), &statbuf)) {
+                       if (strbuf_read_file(&sb, git_path("MERGE_MODE"), 0) < 0)
+                               die_errno("could not read MERGE_MODE");
+                       if (!strcmp(sb.buf, "no-ff"))
+                               allow_fast_forward = 0;
+               }
+               if (allow_fast_forward)
+                       parents = reduce_heads(parents);
+       } else {
+               if (!reflog_msg)
+                       reflog_msg = "commit";
+               pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
+       }
+
+       /* Finally, get the commit message */
+       strbuf_reset(&sb);
+       if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) {
+               int saved_errno = errno;
+               rollback_index_files();
+               die("could not read commit message: %s", strerror(saved_errno));
+       }
+
+       /* Truncate the message just before the diff, if any. */
+       if (verbose) {
+               p = strstr(sb.buf, "\ndiff --git ");
+               if (p != NULL)
+                       strbuf_setlen(&sb, p - sb.buf + 1);
+       }
+
+       if (cleanup_mode != CLEANUP_NONE)
+               stripspace(&sb, cleanup_mode == CLEANUP_ALL);
+       if (message_is_empty(&sb) && !allow_empty_message) {
+               rollback_index_files();
+               fprintf(stderr, "Aborting commit due to empty commit message.\n");
+               exit(1);
+       }
+
+       if (commit_tree(sb.buf, active_cache_tree->sha1, parents, commit_sha1,
+                       fmt_ident(author_name, author_email, author_date,
+                               IDENT_ERROR_ON_NO_NAME))) {
+               rollback_index_files();
+               die("failed to write commit object");
+       }
+
+       ref_lock = lock_any_ref_for_update("HEAD",
+                                          initial_commit ? NULL : head_sha1,
+                                          0);
+
+       nl = strchr(sb.buf, '\n');
+       if (nl)
+               strbuf_setlen(&sb, nl + 1 - sb.buf);
+       else
+               strbuf_addch(&sb, '\n');
+       strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg));
+       strbuf_insert(&sb, strlen(reflog_msg), ": ", 2);
+
+       if (!ref_lock) {
+               rollback_index_files();
+               die("cannot lock HEAD ref");
+       }
+       if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0) {
+               rollback_index_files();
+               die("cannot update HEAD ref");
+       }
+
+       unlink(git_path("MERGE_HEAD"));
+       unlink(git_path("MERGE_MSG"));
+       unlink(git_path("MERGE_MODE"));
+       unlink(git_path("SQUASH_MSG"));
+
+       if (commit_index_files())
+               die ("Repository has been updated, but unable to write\n"
+                    "new_index file. Check that disk is not full or quota is\n"
+                    "not exceeded, and then \"git reset HEAD\" to recover.");
+
+       rerere(0);
+       run_hook(get_index_file(), "post-commit", NULL);
+       if (amend && !no_post_rewrite) {
+               struct notes_rewrite_cfg *cfg;
+               cfg = init_copy_notes_for_rewrite("amend");
+               if (cfg) {
+                       copy_note_for_rewrite(cfg, head_sha1, commit_sha1);
+                       finish_copy_notes_for_rewrite(cfg);
+               }
+               run_rewrite_hook(head_sha1, commit_sha1);
+       }
+       if (!quiet)
+               print_summary(prefix, commit_sha1);
+
+       return 0;
+}
diff --git a/builtin/config.c b/builtin/config.c
new file mode 100644 (file)
index 0000000..ca4a0db
--- /dev/null
@@ -0,0 +1,502 @@
+#include "builtin.h"
+#include "cache.h"
+#include "color.h"
+#include "parse-options.h"
+
+static const char *const builtin_config_usage[] = {
+       "git config [options]",
+       NULL
+};
+
+static char *key;
+static regex_t *key_regexp;
+static regex_t *regexp;
+static int show_keys;
+static int use_key_regexp;
+static int do_all;
+static int do_not_match;
+static int seen;
+static char delim = '=';
+static char key_delim = ' ';
+static char term = '\n';
+
+static int use_global_config, use_system_config, use_local_config;
+static const char *given_config_file;
+static int actions, types;
+static const char *get_color_slot, *get_colorbool_slot;
+static int end_null;
+
+#define ACTION_GET (1<<0)
+#define ACTION_GET_ALL (1<<1)
+#define ACTION_GET_REGEXP (1<<2)
+#define ACTION_REPLACE_ALL (1<<3)
+#define ACTION_ADD (1<<4)
+#define ACTION_UNSET (1<<5)
+#define ACTION_UNSET_ALL (1<<6)
+#define ACTION_RENAME_SECTION (1<<7)
+#define ACTION_REMOVE_SECTION (1<<8)
+#define ACTION_LIST (1<<9)
+#define ACTION_EDIT (1<<10)
+#define ACTION_SET (1<<11)
+#define ACTION_SET_ALL (1<<12)
+#define ACTION_GET_COLOR (1<<13)
+#define ACTION_GET_COLORBOOL (1<<14)
+
+#define TYPE_BOOL (1<<0)
+#define TYPE_INT (1<<1)
+#define TYPE_BOOL_OR_INT (1<<2)
+#define TYPE_PATH (1<<3)
+
+static struct option builtin_config_options[] = {
+       OPT_GROUP("Config file location"),
+       OPT_BOOLEAN(0, "global", &use_global_config, "use global config file"),
+       OPT_BOOLEAN(0, "system", &use_system_config, "use system config file"),
+       OPT_BOOLEAN(0, "local", &use_local_config, "use repository config file"),
+       OPT_STRING('f', "file", &given_config_file, "FILE", "use given config file"),
+       OPT_GROUP("Action"),
+       OPT_BIT(0, "get", &actions, "get value: name [value-regex]", ACTION_GET),
+       OPT_BIT(0, "get-all", &actions, "get all values: key [value-regex]", ACTION_GET_ALL),
+       OPT_BIT(0, "get-regexp", &actions, "get values for regexp: name-regex [value-regex]", ACTION_GET_REGEXP),
+       OPT_BIT(0, "replace-all", &actions, "replace all matching variables: name value [value_regex]", ACTION_REPLACE_ALL),
+       OPT_BIT(0, "add", &actions, "adds a new variable: name value", ACTION_ADD),
+       OPT_BIT(0, "unset", &actions, "removes a variable: name [value-regex]", ACTION_UNSET),
+       OPT_BIT(0, "unset-all", &actions, "removes all matches: name [value-regex]", ACTION_UNSET_ALL),
+       OPT_BIT(0, "rename-section", &actions, "rename section: old-name new-name", ACTION_RENAME_SECTION),
+       OPT_BIT(0, "remove-section", &actions, "remove a section: name", ACTION_REMOVE_SECTION),
+       OPT_BIT('l', "list", &actions, "list all", ACTION_LIST),
+       OPT_BIT('e', "edit", &actions, "opens an editor", ACTION_EDIT),
+       OPT_STRING(0, "get-color", &get_color_slot, "slot", "find the color configured: [default]"),
+       OPT_STRING(0, "get-colorbool", &get_colorbool_slot, "slot", "find the color setting: [stdout-is-tty]"),
+       OPT_GROUP("Type"),
+       OPT_BIT(0, "bool", &types, "value is \"true\" or \"false\"", TYPE_BOOL),
+       OPT_BIT(0, "int", &types, "value is decimal number", TYPE_INT),
+       OPT_BIT(0, "bool-or-int", &types, "value is --bool or --int", TYPE_BOOL_OR_INT),
+       OPT_BIT(0, "path", &types, "value is a path (file or directory name)", TYPE_PATH),
+       OPT_GROUP("Other"),
+       OPT_BOOLEAN('z', "null", &end_null, "terminate values with NUL byte"),
+       OPT_END(),
+};
+
+static void check_argc(int argc, int min, int max) {
+       if (argc >= min && argc <= max)
+               return;
+       error("wrong number of arguments");
+       usage_with_options(builtin_config_usage, builtin_config_options);
+}
+
+static int show_all_config(const char *key_, const char *value_, void *cb)
+{
+       if (value_)
+               printf("%s%c%s%c", key_, delim, value_, term);
+       else
+               printf("%s%c", key_, term);
+       return 0;
+}
+
+static int show_config(const char *key_, const char *value_, void *cb)
+{
+       char value[256];
+       const char *vptr = value;
+       int must_free_vptr = 0;
+       int dup_error = 0;
+
+       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_?value_:""), 0, NULL, 0)))
+               return 0;
+
+       if (show_keys) {
+               if (value_)
+                       printf("%s%c", key_, key_delim);
+               else
+                       printf("%s", key_);
+       }
+       if (seen && !do_all)
+               dup_error = 1;
+       if (types == TYPE_INT)
+               sprintf(value, "%d", git_config_int(key_, value_?value_:""));
+       else if (types == TYPE_BOOL)
+               vptr = git_config_bool(key_, value_) ? "true" : "false";
+       else if (types == TYPE_BOOL_OR_INT) {
+               int is_bool, v;
+               v = git_config_bool_or_int(key_, value_, &is_bool);
+               if (is_bool)
+                       vptr = v ? "true" : "false";
+               else
+                       sprintf(value, "%d", v);
+       } else if (types == TYPE_PATH) {
+               git_config_pathname(&vptr, key_, value_);
+               must_free_vptr = 1;
+       }
+       else
+               vptr = value_?value_:"";
+       seen++;
+       if (dup_error) {
+               error("More than one value for the key %s: %s",
+                               key_, vptr);
+       }
+       else
+               printf("%s%c", vptr, term);
+       if (must_free_vptr)
+               /* If vptr must be freed, it's a pointer to a
+                * dynamically allocated buffer, it's safe to cast to
+                * const.
+               */
+               free((char *)vptr);
+
+       return 0;
+}
+
+static int get_value(const char *key_, const char *regex_)
+{
+       int ret = -1;
+       char *tl;
+       char *global = NULL, *repo_config = NULL;
+       const char *system_wide = NULL, *local;
+
+       local = config_exclusive_filename;
+       if (!local) {
+               const char *home = getenv("HOME");
+               local = repo_config = git_pathdup("config");
+               if (git_config_global() && home)
+                       global = xstrdup(mkpath("%s/.gitconfig", home));
+               if (git_config_system())
+                       system_wide = git_etc_gitconfig();
+       }
+
+       key = xstrdup(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*)xmalloc(sizeof(regex_t));
+               if (regcomp(key_regexp, key, REG_EXTENDED)) {
+                       fprintf(stderr, "Invalid key pattern: %s\n", key_);
+                       goto free_strings;
+               }
+       }
+
+       if (regex_) {
+               if (regex_[0] == '!') {
+                       do_not_match = 1;
+                       regex_++;
+               }
+
+               regexp = (regex_t*)xmalloc(sizeof(regex_t));
+               if (regcomp(regexp, regex_, REG_EXTENDED)) {
+                       fprintf(stderr, "Invalid pattern: %s\n", regex_);
+                       goto free_strings;
+               }
+       }
+
+       if (do_all && system_wide)
+               git_config_from_file(show_config, system_wide, NULL);
+       if (do_all && global)
+               git_config_from_file(show_config, global, NULL);
+       if (do_all)
+               git_config_from_file(show_config, local, NULL);
+       git_config_from_parameters(show_config, NULL);
+       if (!do_all && !seen)
+               git_config_from_file(show_config, local, NULL);
+       if (!do_all && !seen && global)
+               git_config_from_file(show_config, global, NULL);
+       if (!do_all && !seen && system_wide)
+               git_config_from_file(show_config, system_wide, NULL);
+
+       free(key);
+       if (regexp) {
+               regfree(regexp);
+               free(regexp);
+       }
+
+       if (do_all)
+               ret = !seen;
+       else
+               ret = (seen == 1) ? 0 : seen > 1 ? 2 : 1;
+
+free_strings:
+       free(repo_config);
+       free(global);
+       return ret;
+}
+
+static char *normalize_value(const char *key, const char *value)
+{
+       char *normalized;
+
+       if (!value)
+               return NULL;
+
+       if (types == 0 || types == TYPE_PATH)
+               /*
+                * We don't do normalization for TYPE_PATH here: If
+                * the path is like ~/foobar/, we prefer to store
+                * "~/foobar/" in the config file, and to expand the ~
+                * when retrieving the value.
+                */
+               normalized = xstrdup(value);
+       else {
+               normalized = xmalloc(64);
+               if (types == TYPE_INT) {
+                       int v = git_config_int(key, value);
+                       sprintf(normalized, "%d", v);
+               }
+               else if (types == TYPE_BOOL)
+                       sprintf(normalized, "%s",
+                               git_config_bool(key, value) ? "true" : "false");
+               else if (types == TYPE_BOOL_OR_INT) {
+                       int is_bool, v;
+                       v = git_config_bool_or_int(key, value, &is_bool);
+                       if (!is_bool)
+                               sprintf(normalized, "%d", v);
+                       else
+                               sprintf(normalized, "%s", v ? "true" : "false");
+               }
+       }
+
+       return normalized;
+}
+
+static int get_color_found;
+static const char *get_color_slot;
+static const char *get_colorbool_slot;
+static char parsed_color[COLOR_MAXLEN];
+
+static int git_get_color_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, get_color_slot)) {
+               if (!value)
+                       config_error_nonbool(var);
+               color_parse(value, var, parsed_color);
+               get_color_found = 1;
+       }
+       return 0;
+}
+
+static void get_color(const char *def_color)
+{
+       get_color_found = 0;
+       parsed_color[0] = '\0';
+       git_config(git_get_color_config, NULL);
+
+       if (!get_color_found && def_color)
+               color_parse(def_color, "command line", parsed_color);
+
+       fputs(parsed_color, stdout);
+}
+
+static int stdout_is_tty;
+static int get_colorbool_found;
+static int get_diff_color_found;
+static int git_get_colorbool_config(const char *var, const char *value,
+               void *cb)
+{
+       if (!strcmp(var, get_colorbool_slot)) {
+               get_colorbool_found =
+                       git_config_colorbool(var, value, stdout_is_tty);
+       }
+       if (!strcmp(var, "diff.color")) {
+               get_diff_color_found =
+                       git_config_colorbool(var, value, stdout_is_tty);
+       }
+       if (!strcmp(var, "color.ui")) {
+               git_use_color_default = git_config_colorbool(var, value, stdout_is_tty);
+               return 0;
+       }
+       return 0;
+}
+
+static int get_colorbool(int print)
+{
+       get_colorbool_found = -1;
+       get_diff_color_found = -1;
+       git_config(git_get_colorbool_config, NULL);
+
+       if (get_colorbool_found < 0) {
+               if (!strcmp(get_colorbool_slot, "color.diff"))
+                       get_colorbool_found = get_diff_color_found;
+               if (get_colorbool_found < 0)
+                       get_colorbool_found = git_use_color_default;
+       }
+
+       if (print) {
+               printf("%s\n", get_colorbool_found ? "true" : "false");
+               return 0;
+       } else
+               return get_colorbool_found ? 0 : 1;
+}
+
+int cmd_config(int argc, const char **argv, const char *prefix)
+{
+       int nongit = !startup_info->have_repository;
+       char *value;
+
+       config_exclusive_filename = getenv(CONFIG_ENVIRONMENT);
+
+       argc = parse_options(argc, argv, prefix, builtin_config_options,
+                            builtin_config_usage,
+                            PARSE_OPT_STOP_AT_NON_OPTION);
+
+       if (use_global_config + use_system_config + use_local_config + !!given_config_file > 1) {
+               error("only one config file at a time.");
+               usage_with_options(builtin_config_usage, builtin_config_options);
+       }
+
+       if (use_global_config) {
+               char *home = getenv("HOME");
+               if (home) {
+                       char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
+                       config_exclusive_filename = user_config;
+               } else {
+                       die("$HOME not set");
+               }
+       }
+       else if (use_system_config)
+               config_exclusive_filename = git_etc_gitconfig();
+       else if (use_local_config)
+               config_exclusive_filename = git_pathdup("config");
+       else if (given_config_file) {
+               if (!is_absolute_path(given_config_file) && prefix)
+                       config_exclusive_filename = prefix_filename(prefix,
+                                                                   strlen(prefix),
+                                                                   given_config_file);
+               else
+                       config_exclusive_filename = given_config_file;
+       }
+
+       if (end_null) {
+               term = '\0';
+               delim = '\n';
+               key_delim = '\n';
+       }
+
+       if (HAS_MULTI_BITS(types)) {
+               error("only one type at a time.");
+               usage_with_options(builtin_config_usage, builtin_config_options);
+       }
+
+       if (get_color_slot)
+           actions |= ACTION_GET_COLOR;
+       if (get_colorbool_slot)
+           actions |= ACTION_GET_COLORBOOL;
+
+       if ((get_color_slot || get_colorbool_slot) && types) {
+               error("--get-color and variable type are incoherent");
+               usage_with_options(builtin_config_usage, builtin_config_options);
+       }
+
+       if (HAS_MULTI_BITS(actions)) {
+               error("only one action at a time.");
+               usage_with_options(builtin_config_usage, builtin_config_options);
+       }
+       if (actions == 0)
+               switch (argc) {
+               case 1: actions = ACTION_GET; break;
+               case 2: actions = ACTION_SET; break;
+               case 3: actions = ACTION_SET_ALL; break;
+               default:
+                       usage_with_options(builtin_config_usage, builtin_config_options);
+               }
+
+       if (actions == ACTION_LIST) {
+               check_argc(argc, 0, 0);
+               if (git_config(show_all_config, NULL) < 0) {
+                       if (config_exclusive_filename)
+                               die_errno("unable to read config file '%s'",
+                                         config_exclusive_filename);
+                       else
+                               die("error processing config file(s)");
+               }
+       }
+       else if (actions == ACTION_EDIT) {
+               check_argc(argc, 0, 0);
+               if (!config_exclusive_filename && nongit)
+                       die("not in a git directory");
+               git_config(git_default_config, NULL);
+               launch_editor(config_exclusive_filename ?
+                             config_exclusive_filename : git_path("config"),
+                             NULL, NULL);
+       }
+       else if (actions == ACTION_SET) {
+               check_argc(argc, 2, 2);
+               value = normalize_value(argv[0], argv[1]);
+               return git_config_set(argv[0], value);
+       }
+       else if (actions == ACTION_SET_ALL) {
+               check_argc(argc, 2, 3);
+               value = normalize_value(argv[0], argv[1]);
+               return git_config_set_multivar(argv[0], value, argv[2], 0);
+       }
+       else if (actions == ACTION_ADD) {
+               check_argc(argc, 2, 2);
+               value = normalize_value(argv[0], argv[1]);
+               return git_config_set_multivar(argv[0], value, "^$", 0);
+       }
+       else if (actions == ACTION_REPLACE_ALL) {
+               check_argc(argc, 2, 3);
+               value = normalize_value(argv[0], argv[1]);
+               return git_config_set_multivar(argv[0], value, argv[2], 1);
+       }
+       else if (actions == ACTION_GET) {
+               check_argc(argc, 1, 2);
+               return get_value(argv[0], argv[1]);
+       }
+       else if (actions == ACTION_GET_ALL) {
+               do_all = 1;
+               check_argc(argc, 1, 2);
+               return get_value(argv[0], argv[1]);
+       }
+       else if (actions == ACTION_GET_REGEXP) {
+               show_keys = 1;
+               use_key_regexp = 1;
+               do_all = 1;
+               check_argc(argc, 1, 2);
+               return get_value(argv[0], argv[1]);
+       }
+       else if (actions == ACTION_UNSET) {
+               check_argc(argc, 1, 2);
+               if (argc == 2)
+                       return git_config_set_multivar(argv[0], NULL, argv[1], 0);
+               else
+                       return git_config_set(argv[0], NULL);
+       }
+       else if (actions == ACTION_UNSET_ALL) {
+               check_argc(argc, 1, 2);
+               return git_config_set_multivar(argv[0], NULL, argv[1], 1);
+       }
+       else if (actions == ACTION_RENAME_SECTION) {
+               int ret;
+               check_argc(argc, 2, 2);
+               ret = git_config_rename_section(argv[0], argv[1]);
+               if (ret < 0)
+                       return ret;
+               if (ret == 0)
+                       die("No such section!");
+       }
+       else if (actions == ACTION_REMOVE_SECTION) {
+               int ret;
+               check_argc(argc, 1, 1);
+               ret = git_config_rename_section(argv[0], NULL);
+               if (ret < 0)
+                       return ret;
+               if (ret == 0)
+                       die("No such section!");
+       }
+       else if (actions == ACTION_GET_COLOR) {
+               get_color(argv[0]);
+       }
+       else if (actions == ACTION_GET_COLORBOOL) {
+               if (argc == 1)
+                       stdout_is_tty = git_config_bool("command line", argv[0]);
+               else if (argc == 0)
+                       stdout_is_tty = isatty(1);
+               return get_colorbool(argc != 0);
+       }
+
+       return 0;
+}
diff --git a/builtin/count-objects.c b/builtin/count-objects.c
new file mode 100644 (file)
index 0000000..2bdd8eb
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * Builtin "git count-objects".
+ *
+ * Copyright (c) 2006 Junio C Hamano
+ */
+
+#include "cache.h"
+#include "dir.h"
+#include "builtin.h"
+#include "parse-options.h"
+
+static void count_objects(DIR *d, char *path, int len, int verbose,
+                         unsigned long *loose,
+                         off_t *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 (is_dot_or_dotdot(ent->d_name))
+                       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) += xsize_t(on_disk_bytes(st));
+               }
+               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)++;
+       }
+}
+
+static char const * const count_objects_usage[] = {
+       "git count-objects [-v]",
+       NULL
+};
+
+int cmd_count_objects(int argc, const char **argv, const char *prefix)
+{
+       int i, 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;
+       off_t loose_size = 0;
+       struct option opts[] = {
+               OPT__VERBOSE(&verbose),
+               OPT_END(),
+       };
+
+       argc = parse_options(argc, argv, prefix, opts, count_objects_usage, 0);
+       /* we do not take arguments other than flags for now */
+       if (argc)
+               usage_with_options(count_objects_usage, opts);
+       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;
+               unsigned long num_pack = 0;
+               off_t size_pack = 0;
+               if (!packed_git)
+                       prepare_packed_git();
+               for (p = packed_git; p; p = p->next) {
+                       if (!p->pack_local)
+                               continue;
+                       if (open_pack_index(p))
+                               continue;
+                       packed += p->num_objects;
+                       size_pack += p->pack_size + p->index_size;
+                       num_pack++;
+               }
+               printf("count: %lu\n", loose);
+               printf("size: %lu\n", (unsigned long) (loose_size / 1024));
+               printf("in-pack: %lu\n", packed);
+               printf("packs: %lu\n", num_pack);
+               printf("size-pack: %lu\n", (unsigned long) (size_pack / 1024));
+               printf("prune-packable: %lu\n", packed_loose);
+               printf("garbage: %lu\n", garbage);
+       }
+       else
+               printf("%lu objects, %lu kilobytes\n",
+                      loose, (unsigned long) (loose_size / 1024));
+       return 0;
+}
diff --git a/builtin/describe.c b/builtin/describe.c
new file mode 100644 (file)
index 0000000..43caff2
--- /dev/null
@@ -0,0 +1,437 @@
+#include "cache.h"
+#include "commit.h"
+#include "tag.h"
+#include "refs.h"
+#include "builtin.h"
+#include "exec_cmd.h"
+#include "parse-options.h"
+#include "diff.h"
+
+#define SEEN           (1u<<0)
+#define MAX_TAGS       (FLAG_BITS - 1)
+
+static const char * const describe_usage[] = {
+       "git describe [options] <committish>*",
+       "git describe [options] --dirty",
+       NULL
+};
+
+static int debug;      /* Display lots of verbose info */
+static int all;        /* Any valid ref can be used */
+static int tags;       /* Allow lightweight tags */
+static int longformat;
+static int abbrev = DEFAULT_ABBREV;
+static int max_candidates = 10;
+static int found_names;
+static const char *pattern;
+static int always;
+static const char *dirty;
+
+/* diff-index command arguments to check if working tree is dirty. */
+static const char *diff_index_args[] = {
+       "diff-index", "--quiet", "HEAD", "--", NULL
+};
+
+
+struct commit_name {
+       struct tag *tag;
+       unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */
+       unsigned name_checked:1;
+       unsigned char sha1[20];
+       char path[FLEX_ARRAY]; /* more */
+};
+static const char *prio_names[] = {
+       "head", "lightweight", "annotated",
+};
+
+static int replace_name(struct commit_name *e,
+                              int prio,
+                              const unsigned char *sha1,
+                              struct tag **tag)
+{
+       if (!e || e->prio < prio)
+               return 1;
+
+       if (e->prio == 2 && prio == 2) {
+               /* Multiple annotated tags point to the same commit.
+                * Select one to keep based upon their tagger date.
+                */
+               struct tag *t;
+
+               if (!e->tag) {
+                       t = lookup_tag(e->sha1);
+                       if (!t || parse_tag(t))
+                               return 1;
+                       e->tag = t;
+               }
+
+               t = lookup_tag(sha1);
+               if (!t || parse_tag(t))
+                       return 0;
+               *tag = t;
+
+               if (e->tag->date < t->date)
+                       return 1;
+       }
+
+       return 0;
+}
+
+static void add_to_known_names(const char *path,
+                              struct commit *commit,
+                              int prio,
+                              const unsigned char *sha1)
+{
+       struct commit_name *e = commit->util;
+       struct tag *tag = NULL;
+       if (replace_name(e, prio, sha1, &tag)) {
+               size_t len = strlen(path)+1;
+               free(e);
+               e = xmalloc(sizeof(struct commit_name) + len);
+               e->tag = tag;
+               e->prio = prio;
+               e->name_checked = 0;
+               hashcpy(e->sha1, sha1);
+               memcpy(e->path, path, len);
+               commit->util = e;
+       }
+       found_names = 1;
+}
+
+static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+{
+       int might_be_tag = !prefixcmp(path, "refs/tags/");
+       struct commit *commit;
+       struct object *object;
+       unsigned char peeled[20];
+       int is_tag, prio;
+
+       if (!all && !might_be_tag)
+               return 0;
+
+       if (!peel_ref(path, peeled) && !is_null_sha1(peeled)) {
+               commit = lookup_commit_reference_gently(peeled, 1);
+               if (!commit)
+                       return 0;
+               is_tag = !!hashcmp(sha1, commit->object.sha1);
+       } else {
+               commit = lookup_commit_reference_gently(sha1, 1);
+               object = parse_object(sha1);
+               if (!commit || !object)
+                       return 0;
+               is_tag = object->type == OBJ_TAG;
+       }
+
+       /* If --all, then any refs are used.
+        * If --tags, then any tags are used.
+        * Otherwise only annotated tags are used.
+        */
+       if (might_be_tag) {
+               if (is_tag)
+                       prio = 2;
+               else
+                       prio = 1;
+
+               if (pattern && fnmatch(pattern, path + 10, 0))
+                       prio = 0;
+       }
+       else
+               prio = 0;
+
+       if (!all) {
+               if (!prio)
+                       return 0;
+       }
+       add_to_known_names(all ? path + 5 : path + 10, commit, prio, sha1);
+       return 0;
+}
+
+struct possible_tag {
+       struct commit_name *name;
+       int depth;
+       int found_order;
+       unsigned flag_within;
+};
+
+static int compare_pt(const void *a_, const void *b_)
+{
+       struct possible_tag *a = (struct possible_tag *)a_;
+       struct possible_tag *b = (struct possible_tag *)b_;
+       if (a->depth != b->depth)
+               return a->depth - b->depth;
+       if (a->found_order != b->found_order)
+               return a->found_order - b->found_order;
+       return 0;
+}
+
+static unsigned long finish_depth_computation(
+       struct commit_list **list,
+       struct possible_tag *best)
+{
+       unsigned long seen_commits = 0;
+       while (*list) {
+               struct commit *c = pop_commit(list);
+               struct commit_list *parents = c->parents;
+               seen_commits++;
+               if (c->object.flags & best->flag_within) {
+                       struct commit_list *a = *list;
+                       while (a) {
+                               struct commit *i = a->item;
+                               if (!(i->object.flags & best->flag_within))
+                                       break;
+                               a = a->next;
+                       }
+                       if (!a)
+                               break;
+               } else
+                       best->depth++;
+               while (parents) {
+                       struct commit *p = parents->item;
+                       parse_commit(p);
+                       if (!(p->object.flags & SEEN))
+                               insert_by_date(p, list);
+                       p->object.flags |= c->object.flags;
+                       parents = parents->next;
+               }
+       }
+       return seen_commits;
+}
+
+static void display_name(struct commit_name *n)
+{
+       if (n->prio == 2 && !n->tag) {
+               n->tag = lookup_tag(n->sha1);
+               if (!n->tag || parse_tag(n->tag))
+                       die("annotated tag %s not available", n->path);
+       }
+       if (n->tag && !n->name_checked) {
+               if (!n->tag->tag)
+                       die("annotated tag %s has no embedded name", n->path);
+               if (strcmp(n->tag->tag, all ? n->path + 5 : n->path))
+                       warning("tag '%s' is really '%s' here", n->tag->tag, n->path);
+               n->name_checked = 1;
+       }
+
+       if (n->tag)
+               printf("%s", n->tag->tag);
+       else
+               printf("%s", n->path);
+}
+
+static void show_suffix(int depth, const unsigned char *sha1)
+{
+       printf("-%d-g%s", depth, find_unique_abbrev(sha1, abbrev));
+}
+
+static void describe(const char *arg, int last_one)
+{
+       unsigned char sha1[20];
+       struct commit *cmit, *gave_up_on = NULL;
+       struct commit_list *list;
+       struct commit_name *n;
+       struct possible_tag all_matches[MAX_TAGS];
+       unsigned int match_cnt = 0, annotated_cnt = 0, cur_match;
+       unsigned long seen_commits = 0;
+       unsigned int unannotated_cnt = 0;
+
+       if (get_sha1(arg, sha1))
+               die("Not a valid object name %s", arg);
+       cmit = lookup_commit_reference(sha1);
+       if (!cmit)
+               die("%s is not a valid '%s' object", arg, commit_type);
+
+       n = cmit->util;
+       if (n && (tags || all || n->prio == 2)) {
+               /*
+                * Exact match to an existing ref.
+                */
+               display_name(n);
+               if (longformat)
+                       show_suffix(0, n->tag ? n->tag->tagged->sha1 : sha1);
+               if (dirty)
+                       printf("%s", dirty);
+               printf("\n");
+               return;
+       }
+
+       if (!max_candidates)
+               die("no tag exactly matches '%s'", sha1_to_hex(cmit->object.sha1));
+       if (debug)
+               fprintf(stderr, "searching to describe %s\n", arg);
+
+       list = NULL;
+       cmit->object.flags = SEEN;
+       commit_list_insert(cmit, &list);
+       while (list) {
+               struct commit *c = pop_commit(&list);
+               struct commit_list *parents = c->parents;
+               seen_commits++;
+               n = c->util;
+               if (n) {
+                       if (!tags && !all && n->prio < 2) {
+                               unannotated_cnt++;
+                       } else if (match_cnt < max_candidates) {
+                               struct possible_tag *t = &all_matches[match_cnt++];
+                               t->name = n;
+                               t->depth = seen_commits - 1;
+                               t->flag_within = 1u << match_cnt;
+                               t->found_order = match_cnt;
+                               c->object.flags |= t->flag_within;
+                               if (n->prio == 2)
+                                       annotated_cnt++;
+                       }
+                       else {
+                               gave_up_on = c;
+                               break;
+                       }
+               }
+               for (cur_match = 0; cur_match < match_cnt; cur_match++) {
+                       struct possible_tag *t = &all_matches[cur_match];
+                       if (!(c->object.flags & t->flag_within))
+                               t->depth++;
+               }
+               if (annotated_cnt && !list) {
+                       if (debug)
+                               fprintf(stderr, "finished search at %s\n",
+                                       sha1_to_hex(c->object.sha1));
+                       break;
+               }
+               while (parents) {
+                       struct commit *p = parents->item;
+                       parse_commit(p);
+                       if (!(p->object.flags & SEEN))
+                               insert_by_date(p, &list);
+                       p->object.flags |= c->object.flags;
+                       parents = parents->next;
+               }
+       }
+
+       if (!match_cnt) {
+               const unsigned char *sha1 = cmit->object.sha1;
+               if (always) {
+                       printf("%s", find_unique_abbrev(sha1, abbrev));
+                       if (dirty)
+                               printf("%s", dirty);
+                       printf("\n");
+                       return;
+               }
+               if (unannotated_cnt)
+                       die("No annotated tags can describe '%s'.\n"
+                           "However, there were unannotated tags: try --tags.",
+                           sha1_to_hex(sha1));
+               else
+                       die("No tags can describe '%s'.\n"
+                           "Try --always, or create some tags.",
+                           sha1_to_hex(sha1));
+       }
+
+       qsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt);
+
+       if (gave_up_on) {
+               insert_by_date(gave_up_on, &list);
+               seen_commits--;
+       }
+       seen_commits += finish_depth_computation(&list, &all_matches[0]);
+       free_commit_list(list);
+
+       if (debug) {
+               for (cur_match = 0; cur_match < match_cnt; cur_match++) {
+                       struct possible_tag *t = &all_matches[cur_match];
+                       fprintf(stderr, " %-11s %8d %s\n",
+                               prio_names[t->name->prio],
+                               t->depth, t->name->path);
+               }
+               fprintf(stderr, "traversed %lu commits\n", seen_commits);
+               if (gave_up_on) {
+                       fprintf(stderr,
+                               "more than %i tags found; listed %i most recent\n"
+                               "gave up search at %s\n",
+                               max_candidates, max_candidates,
+                               sha1_to_hex(gave_up_on->object.sha1));
+               }
+       }
+
+       display_name(all_matches[0].name);
+       if (abbrev)
+               show_suffix(all_matches[0].depth, cmit->object.sha1);
+       if (dirty)
+               printf("%s", dirty);
+       printf("\n");
+
+       if (!last_one)
+               clear_commit_marks(cmit, -1);
+}
+
+int cmd_describe(int argc, const char **argv, const char *prefix)
+{
+       int contains = 0;
+       struct option options[] = {
+               OPT_BOOLEAN(0, "contains",   &contains, "find the tag that comes after the commit"),
+               OPT_BOOLEAN(0, "debug",      &debug, "debug search strategy on stderr"),
+               OPT_BOOLEAN(0, "all",        &all, "use any ref in .git/refs"),
+               OPT_BOOLEAN(0, "tags",       &tags, "use any tag in .git/refs/tags"),
+               OPT_BOOLEAN(0, "long",       &longformat, "always use long format"),
+               OPT__ABBREV(&abbrev),
+               OPT_SET_INT(0, "exact-match", &max_candidates,
+                           "only output exact matches", 0),
+               OPT_INTEGER(0, "candidates", &max_candidates,
+                           "consider <n> most recent tags (default: 10)"),
+               OPT_STRING(0, "match",       &pattern, "pattern",
+                          "only consider tags matching <pattern>"),
+               OPT_BOOLEAN(0, "always",     &always,
+                          "show abbreviated commit object as fallback"),
+               {OPTION_STRING, 0, "dirty",  &dirty, "mark",
+                          "append <mark> on dirty working tree (default: \"-dirty\")",
+                PARSE_OPT_OPTARG, NULL, (intptr_t) "-dirty"},
+               OPT_END(),
+       };
+
+       argc = parse_options(argc, argv, prefix, options, describe_usage, 0);
+       if (max_candidates < 0)
+               max_candidates = 0;
+       else if (max_candidates > MAX_TAGS)
+               max_candidates = MAX_TAGS;
+
+       save_commit_buffer = 0;
+
+       if (longformat && abbrev == 0)
+               die("--long is incompatible with --abbrev=0");
+
+       if (contains) {
+               const char **args = xmalloc((7 + argc) * sizeof(char *));
+               int i = 0;
+               args[i++] = "name-rev";
+               args[i++] = "--name-only";
+               args[i++] = "--no-undefined";
+               if (always)
+                       args[i++] = "--always";
+               if (!all) {
+                       args[i++] = "--tags";
+                       if (pattern) {
+                               char *s = xmalloc(strlen("--refs=refs/tags/") + strlen(pattern) + 1);
+                               sprintf(s, "--refs=refs/tags/%s", pattern);
+                               args[i++] = s;
+                       }
+               }
+               memcpy(args + i, argv, argc * sizeof(char *));
+               args[i + argc] = NULL;
+               return cmd_name_rev(i + argc, args, prefix);
+       }
+
+       for_each_ref(get_name, NULL);
+       if (!found_names && !always)
+               die("No names found, cannot describe anything.");
+
+       if (argc == 0) {
+               if (dirty && !cmd_diff_index(ARRAY_SIZE(diff_index_args) - 1, diff_index_args, prefix))
+                       dirty = NULL;
+               describe("HEAD", 1);
+       } else if (dirty) {
+               die("--dirty is incompatible with committishes");
+       } else {
+               while (argc-- > 0) {
+                       describe(*argv++, argc == 0);
+               }
+       }
+       return 0;
+}
diff --git a/builtin/diff-files.c b/builtin/diff-files.c
new file mode 100644 (file)
index 0000000..951c7c8
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * 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"
+#include "submodule.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, const char *prefix)
+{
+       struct rev_info rev;
+       int result;
+       unsigned options = 0;
+
+       init_revisions(&rev, prefix);
+       gitmodules_config();
+       git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
+       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"))
+                       options |= DIFF_SILENT_ON_REMOVED;
+               else
+                       usage(diff_files_usage);
+               argv++; argc--;
+       }
+       if (!rev.diffopt.output_format)
+               rev.diffopt.output_format = DIFF_FORMAT_RAW;
+
+       /*
+        * Make sure there are NO revision (i.e. pending object) parameter,
+        * rev.max_count is reasonable (0 <= n <= 3), and
+        * there is no other revision filtering parameters.
+        */
+       if (rev.pending.nr ||
+           rev.min_age != -1 || rev.max_age != -1 ||
+           3 < rev.max_count)
+               usage(diff_files_usage);
+
+       /*
+        * "diff-files --base -p" should not combine merges because it
+        * was not asked to.  "diff-files -c -p" should not densify
+        * (the user should ask with "diff-files --cc" explicitly).
+        */
+       if (rev.max_count == -1 && !rev.combine_merges &&
+           (rev.diffopt.output_format & DIFF_FORMAT_PATCH))
+               rev.combine_merges = rev.dense_combined_merges = 1;
+
+       if (read_cache_preload(rev.diffopt.paths) < 0) {
+               perror("read_cache_preload");
+               return -1;
+       }
+       result = run_diff_files(&rev, options);
+       return diff_result_code(&rev.diffopt, result);
+}
diff --git a/builtin/diff-index.c b/builtin/diff-index.c
new file mode 100644 (file)
index 0000000..2eb32bd
--- /dev/null
@@ -0,0 +1,52 @@
+#include "cache.h"
+#include "diff.h"
+#include "commit.h"
+#include "revision.h"
+#include "builtin.h"
+#include "submodule.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, const char *prefix)
+{
+       struct rev_info rev;
+       int cached = 0;
+       int i;
+       int result;
+
+       init_revisions(&rev, prefix);
+       gitmodules_config();
+       git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
+       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);
+       }
+       if (!rev.diffopt.output_format)
+               rev.diffopt.output_format = DIFF_FORMAT_RAW;
+
+       /*
+        * Make sure there is one revision (i.e. pending object),
+        * and there is no revision filtering parameters.
+        */
+       if (rev.pending.nr != 1 ||
+           rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1)
+               usage(diff_cache_usage);
+       if (!cached)
+               setup_work_tree();
+       if (read_cache() < 0) {
+               perror("read_cache");
+               return -1;
+       }
+       result = run_diff_index(&rev, cached);
+       return diff_result_code(&rev.diffopt, result);
+}
diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c
new file mode 100644 (file)
index 0000000..0d2a3e9
--- /dev/null
@@ -0,0 +1,182 @@
+#include "cache.h"
+#include "diff.h"
+#include "commit.h"
+#include "log-tree.h"
+#include "builtin.h"
+#include "submodule.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);
+}
+
+/* Diff one or more commits. */
+static int stdin_diff_commit(struct commit *commit, char *line, int len)
+{
+       unsigned char sha1[20];
+       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);
+}
+
+/* Diff two trees. */
+static int stdin_diff_trees(struct tree *tree1, char *line, int len)
+{
+       unsigned char sha1[20];
+       struct tree *tree2;
+       if (len != 82 || !isspace(line[40]) || get_sha1_hex(line + 41, sha1))
+               return error("Need exactly two trees, separated by a space");
+       tree2 = lookup_tree(sha1);
+       if (!tree2 || parse_tree(tree2))
+               return -1;
+       printf("%s %s\n", sha1_to_hex(tree1->object.sha1),
+                         sha1_to_hex(tree2->object.sha1));
+       diff_tree_sha1(tree1->object.sha1, tree2->object.sha1,
+                      "", &log_tree_opt.diffopt);
+       log_tree_diff_flush(&log_tree_opt);
+       return 0;
+}
+
+static int diff_tree_stdin(char *line)
+{
+       int len = strlen(line);
+       unsigned char sha1[20];
+       struct object *obj;
+
+       if (!len || line[len-1] != '\n')
+               return -1;
+       line[len-1] = 0;
+       if (get_sha1_hex(line, sha1))
+               return -1;
+       obj = lookup_unknown_object(sha1);
+       if (!obj || !obj->parsed)
+               obj = parse_object(sha1);
+       if (!obj)
+               return -1;
+       if (obj->type == OBJ_COMMIT)
+               return stdin_diff_commit((struct commit *)obj, line, len);
+       if (obj->type == OBJ_TREE)
+               return stdin_diff_trees((struct tree *)obj, line, len);
+       error("Object %s is a %s, not a commit or tree",
+             sha1_to_hex(sha1), typename(obj->type));
+       return -1;
+}
+
+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;
+
+static void diff_tree_tweak_rev(struct rev_info *rev, struct setup_revision_opt *opt)
+{
+       if (!rev->diffopt.output_format) {
+               if (rev->dense_combined_merges)
+                       rev->diffopt.output_format = DIFF_FORMAT_PATCH;
+               else
+                       rev->diffopt.output_format = DIFF_FORMAT_RAW;
+       }
+}
+
+int cmd_diff_tree(int argc, const char **argv, const char *prefix)
+{
+       int nr_sha1;
+       char line[1000];
+       struct object *tree1, *tree2;
+       static struct rev_info *opt = &log_tree_opt;
+       struct setup_revision_opt s_r_opt;
+       int read_stdin = 0;
+
+       init_revisions(opt, prefix);
+       gitmodules_config();
+       git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
+       opt->abbrev = 0;
+       opt->diff = 1;
+       opt->disable_stdin = 1;
+       memset(&s_r_opt, 0, sizeof(s_r_opt));
+       s_r_opt.tweak = diff_tree_tweak_rev;
+       argc = setup_revisions(argc, argv, opt, &s_r_opt);
+
+       while (--argc > 0) {
+               const char *arg = *++argv;
+
+               if (!strcmp(arg, "--stdin")) {
+                       read_stdin = 1;
+                       continue;
+               }
+               usage(diff_tree_usage);
+       }
+
+       /*
+        * NOTE! We expect "a ^b" to be equal to "a..b", so we
+        * reverse the order of the objects if the second one
+        * is marked UNINTERESTING.
+        */
+       nr_sha1 = opt->pending.nr;
+       switch (nr_sha1) {
+       case 0:
+               if (!read_stdin)
+                       usage(diff_tree_usage);
+               break;
+       case 1:
+               tree1 = opt->pending.objects[0].item;
+               diff_tree_commit_sha1(tree1->sha1);
+               break;
+       case 2:
+               tree1 = opt->pending.objects[0].item;
+               tree2 = opt->pending.objects[1].item;
+               if (tree2->flags & UNINTERESTING) {
+                       struct object *tmp = tree2;
+                       tree2 = tree1;
+                       tree1 = tmp;
+               }
+               diff_tree_sha1(tree1->sha1,
+                              tree2->sha1,
+                              "", &opt->diffopt);
+               log_tree_diff_flush(opt);
+               break;
+       }
+
+       if (read_stdin) {
+               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 diff_result_code(&opt->diffopt, 0);
+}
diff --git a/builtin/diff.c b/builtin/diff.c
new file mode 100644 (file)
index 0000000..a43d326
--- /dev/null
@@ -0,0 +1,429 @@
+/*
+ * Builtin "git diff"
+ *
+ * Copyright (c) 2006 Junio C Hamano
+ */
+#include "cache.h"
+#include "color.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"
+#include "submodule.h"
+
+struct blobinfo {
+       unsigned char sha1[20];
+       const char *name;
+       unsigned mode;
+};
+
+static const char builtin_diff_usage[] =
+"git diff <options> <rev>{0,2} -- <path>*";
+
+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 (!is_null_sha1(old_sha1) && !is_null_sha1(new_sha1) &&
+           !hashcmp(old_sha1, new_sha1) && (old_mode == new_mode))
+               return;
+
+       if (DIFF_OPT_TST(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;
+       }
+
+       if (opt->prefix &&
+           (strncmp(old_name, opt->prefix, opt->prefix_length) ||
+            strncmp(new_name, opt->prefix, opt->prefix_length)))
+               return;
+
+       one = alloc_filespec(old_name);
+       two = alloc_filespec(new_name);
+       fill_filespec(one, old_sha1, old_mode);
+       fill_filespec(two, new_sha1, new_mode);
+
+       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;
+
+       if (argc > 1)
+               usage(builtin_diff_usage);
+
+       if (lstat(path, &st))
+               die_errno("failed to stat '%s'", path);
+       if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)))
+               die("'%s': not a regular file or symlink", path);
+
+       diff_set_mnemonic_prefix(&revs->diffopt, "o/", "w/");
+
+       if (blob[0].mode == S_IFINVALID)
+               blob[0].mode = canon_mode(st.st_mode);
+
+       stuff_change(&revs->diffopt,
+                    blob[0].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)
+{
+       unsigned mode = canon_mode(S_IFREG | 0644);
+
+       if (argc > 1)
+               usage(builtin_diff_usage);
+
+       if (blob[0].mode == S_IFINVALID)
+               blob[0].mode = mode;
+
+       if (blob[1].mode == S_IFINVALID)
+               blob[1].mode = mode;
+
+       stuff_change(&revs->diffopt,
+                    blob[0].mode, blob[1].mode,
+                    blob[0].sha1, blob[1].sha1,
+                    blob[0].name, blob[1].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") || !strcmp(arg, "--staged"))
+                       cached = 1;
+               else
+                       usage(builtin_diff_usage);
+               argv++; argc--;
+       }
+       if (!cached)
+               setup_work_tree();
+       /*
+        * Make sure there is one revision (i.e. pending object),
+        * and there is no revision filtering parameters.
+        */
+       if (revs->pending.nr != 1 ||
+           revs->max_count != -1 || revs->min_age != -1 ||
+           revs->max_age != -1)
+               usage(builtin_diff_usage);
+       if (read_cache_preload(revs->diffopt.paths) < 0) {
+               perror("read_cache_preload");
+               return -1;
+       }
+       return run_diff_index(revs, cached);
+}
+
+static int builtin_diff_tree(struct rev_info *revs,
+                            int argc, const char **argv,
+                            struct object_array_entry *ent)
+{
+       const unsigned char *(sha1[2]);
+       int swap = 0;
+
+       if (argc > 1)
+               usage(builtin_diff_usage);
+
+       /* We saw two trees, ent[0] and ent[1].
+        * if ent[1] is uninteresting, they are swapped
+        */
+       if (ent[1].item->flags & UNINTERESTING)
+               swap = 1;
+       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_array_entry *ent,
+                                int ents)
+{
+       const unsigned char (*parent)[20];
+       int i;
+
+       if (argc > 1)
+               usage(builtin_diff_usage);
+
+       if (!revs->dense_combined_merges && !revs->combine_merges)
+               revs->dense_combined_merges = revs->combine_merges = 1;
+       parent = xmalloc(ents * sizeof(*parent));
+       for (i = 0; i < ents; i++)
+               hashcpy((unsigned char *)(parent + i), ent[i].item->sha1);
+       diff_tree_combined(parent[0], parent + 1, ents - 1,
+                          revs->dense_combined_merges, revs);
+       return 0;
+}
+
+static void refresh_index_quietly(void)
+{
+       struct lock_file *lock_file;
+       int fd;
+
+       lock_file = xcalloc(1, sizeof(struct lock_file));
+       fd = hold_locked_index(lock_file, 0);
+       if (fd < 0)
+               return;
+       discard_cache();
+       read_cache();
+       refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED);
+
+       if (active_cache_changed &&
+           !write_cache(fd, active_cache, active_nr))
+               commit_locked_index(lock_file);
+
+       rollback_lock_file(lock_file);
+}
+
+static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv)
+{
+       int result;
+       unsigned int options = 0;
+
+       while (1 < argc && argv[1][0] == '-') {
+               if (!strcmp(argv[1], "--base"))
+                       revs->max_count = 1;
+               else if (!strcmp(argv[1], "--ours"))
+                       revs->max_count = 2;
+               else if (!strcmp(argv[1], "--theirs"))
+                       revs->max_count = 3;
+               else if (!strcmp(argv[1], "-q"))
+                       options |= DIFF_SILENT_ON_REMOVED;
+               else if (!strcmp(argv[1], "-h"))
+                       usage(builtin_diff_usage);
+               else
+                       return error("invalid option: %s", argv[1]);
+               argv++; argc--;
+       }
+
+       /*
+        * "diff --base" should not combine merges because it was not
+        * asked to.  "diff -c" should not densify (if the user wants
+        * dense one, --cc can be explicitly asked for, or just rely
+        * on the default).
+        */
+       if (revs->max_count == -1 && !revs->combine_merges &&
+           (revs->diffopt.output_format & DIFF_FORMAT_PATCH))
+               revs->combine_merges = revs->dense_combined_merges = 1;
+
+       setup_work_tree();
+       if (read_cache_preload(revs->diffopt.paths) < 0) {
+               perror("read_cache_preload");
+               return -1;
+       }
+       result = run_diff_files(revs, options);
+       return diff_result_code(&revs->diffopt, result);
+}
+
+int cmd_diff(int argc, const char **argv, const char *prefix)
+{
+       int i;
+       struct rev_info rev;
+       struct object_array_entry ent[100];
+       int ents = 0, blobs = 0, paths = 0;
+       const char *path = NULL;
+       struct blobinfo blob[2];
+       int nongit;
+       int result = 0;
+
+       /*
+        * 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)
+        *
+        * N=0, M=0, P=2:
+        *      compare two filesystem entities (aka --no-index).
+        *
+        * Other cases are errors.
+        */
+
+       prefix = setup_git_directory_gently(&nongit);
+       gitmodules_config();
+       git_config(git_diff_ui_config, NULL);
+
+       if (diff_use_color_default == -1)
+               diff_use_color_default = git_use_color_default;
+
+       init_revisions(&rev, prefix);
+
+       /* If this is a no-index diff, just run it and exit there. */
+       diff_no_index(&rev, argc, argv, nongit, prefix);
+
+       /* Otherwise, we are doing the usual "git" diff */
+       rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index;
+
+       /* Default to let external and textconv be used */
+       DIFF_OPT_SET(&rev.diffopt, ALLOW_EXTERNAL);
+       DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
+
+       if (nongit)
+               die("Not a git repository");
+       argc = setup_revisions(argc, argv, &rev, NULL);
+       if (!rev.diffopt.output_format) {
+               rev.diffopt.output_format = DIFF_FORMAT_PATCH;
+               if (diff_setup_done(&rev.diffopt) < 0)
+                       die("diff_setup_done failed");
+       }
+
+       DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
+
+       /*
+        * If the user asked for our exit code then don't start a
+        * pager or we would end up reporting its exit code instead.
+        */
+       if (!DIFF_OPT_TST(&rev.diffopt, EXIT_WITH_STATUS) &&
+           check_pager_config("diff") != 0)
+               setup_pager();
+
+       /*
+        * Do we have --cached and not have a pending object, then
+        * default to HEAD by hand.  Eek.
+        */
+       if (!rev.pending.nr) {
+               int i;
+               for (i = 1; i < argc; i++) {
+                       const char *arg = argv[i];
+                       if (!strcmp(arg, "--"))
+                               break;
+                       else if (!strcmp(arg, "--cached") ||
+                                !strcmp(arg, "--staged")) {
+                               add_head_to_pending(&rev);
+                               if (!rev.pending.nr)
+                                       die("No HEAD commit to compare with (yet)");
+                               break;
+                       }
+               }
+       }
+
+       for (i = 0; i < rev.pending.nr; i++) {
+               struct object_array_entry *list = rev.pending.objects+i;
+               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 (obj->type == OBJ_COMMIT)
+                       obj = &((struct commit *)obj)->tree->object;
+               if (obj->type == OBJ_TREE) {
+                       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 (obj->type == OBJ_BLOB) {
+                       if (2 <= blobs)
+                               die("more than two blobs given: '%s'", name);
+                       hashcpy(blob[blobs].sha1, obj->sha1);
+                       blob[blobs].name = name;
+                       blob[blobs].mode = list->mode;
+                       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:
+                       result = builtin_diff_files(&rev, argc, argv);
+                       break;
+               case 1:
+                       if (paths != 1)
+                               usage(builtin_diff_usage);
+                       result = builtin_diff_b_f(&rev, argc, argv, blob, path);
+                       break;
+               case 2:
+                       if (paths)
+                               usage(builtin_diff_usage);
+                       result = builtin_diff_blobs(&rev, argc, argv, blob);
+                       break;
+               default:
+                       usage(builtin_diff_usage);
+               }
+       }
+       else if (blobs)
+               usage(builtin_diff_usage);
+       else if (ents == 1)
+               result = builtin_diff_index(&rev, argc, argv);
+       else if (ents == 2)
+               result = builtin_diff_tree(&rev, argc, argv, ent);
+       else if (ent[0].item->flags & UNINTERESTING) {
+               /*
+                * diff A...B where there is at least one merge base
+                * between A and B.  We have ent[0] == merge-base,
+                * ent[ents-2] == A, and ent[ents-1] == B.  Show diff
+                * between the base and B.  Note that we pick one
+                * merge base at random if there are more than one.
+                */
+               ent[1] = ent[ents-1];
+               result = builtin_diff_tree(&rev, argc, argv, ent);
+       } else
+               result = builtin_diff_combined(&rev, argc, argv,
+                                              ent, ents);
+       result = diff_result_code(&rev.diffopt, result);
+       if (1 < rev.diffopt.skip_stat_unmatch)
+               refresh_index_quietly();
+       return result;
+}
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
new file mode 100644 (file)
index 0000000..c8fd46b
--- /dev/null
@@ -0,0 +1,679 @@
+/*
+ * "git fast-export" builtin command
+ *
+ * Copyright (C) 2007 Johannes E. Schindelin
+ */
+#include "builtin.h"
+#include "cache.h"
+#include "commit.h"
+#include "object.h"
+#include "tag.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "log-tree.h"
+#include "revision.h"
+#include "decorate.h"
+#include "string-list.h"
+#include "utf8.h"
+#include "parse-options.h"
+
+static const char *fast_export_usage[] = {
+       "git fast-export [rev-list-opts]",
+       NULL
+};
+
+static int progress;
+static enum { ABORT, VERBATIM, WARN, STRIP } signed_tag_mode = ABORT;
+static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ABORT;
+static int fake_missing_tagger;
+static int no_data;
+static int full_tree;
+
+static int parse_opt_signed_tag_mode(const struct option *opt,
+                                    const char *arg, int unset)
+{
+       if (unset || !strcmp(arg, "abort"))
+               signed_tag_mode = ABORT;
+       else if (!strcmp(arg, "verbatim") || !strcmp(arg, "ignore"))
+               signed_tag_mode = VERBATIM;
+       else if (!strcmp(arg, "warn"))
+               signed_tag_mode = WARN;
+       else if (!strcmp(arg, "strip"))
+               signed_tag_mode = STRIP;
+       else
+               return error("Unknown signed-tag mode: %s", arg);
+       return 0;
+}
+
+static int parse_opt_tag_of_filtered_mode(const struct option *opt,
+                                         const char *arg, int unset)
+{
+       if (unset || !strcmp(arg, "abort"))
+               tag_of_filtered_mode = ABORT;
+       else if (!strcmp(arg, "drop"))
+               tag_of_filtered_mode = DROP;
+       else if (!strcmp(arg, "rewrite"))
+               tag_of_filtered_mode = REWRITE;
+       else
+               return error("Unknown tag-of-filtered mode: %s", arg);
+       return 0;
+}
+
+static struct decoration idnums;
+static uint32_t last_idnum;
+
+static int has_unshown_parent(struct commit *commit)
+{
+       struct commit_list *parent;
+
+       for (parent = commit->parents; parent; parent = parent->next)
+               if (!(parent->item->object.flags & SHOWN) &&
+                   !(parent->item->object.flags & UNINTERESTING))
+                       return 1;
+       return 0;
+}
+
+/* Since intptr_t is C99, we do not use it here */
+static inline uint32_t *mark_to_ptr(uint32_t mark)
+{
+       return ((uint32_t *)NULL) + mark;
+}
+
+static inline uint32_t ptr_to_mark(void * mark)
+{
+       return (uint32_t *)mark - (uint32_t *)NULL;
+}
+
+static inline void mark_object(struct object *object, uint32_t mark)
+{
+       add_decoration(&idnums, object, mark_to_ptr(mark));
+}
+
+static inline void mark_next_object(struct object *object)
+{
+       mark_object(object, ++last_idnum);
+}
+
+static int get_object_mark(struct object *object)
+{
+       void *decoration = lookup_decoration(&idnums, object);
+       if (!decoration)
+               return 0;
+       return ptr_to_mark(decoration);
+}
+
+static void show_progress(void)
+{
+       static int counter = 0;
+       if (!progress)
+               return;
+       if ((++counter % progress) == 0)
+               printf("progress %d objects\n", counter);
+}
+
+static void handle_object(const unsigned char *sha1)
+{
+       unsigned long size;
+       enum object_type type;
+       char *buf;
+       struct object *object;
+
+       if (no_data)
+               return;
+
+       if (is_null_sha1(sha1))
+               return;
+
+       object = parse_object(sha1);
+       if (!object)
+               die ("Could not read blob %s", sha1_to_hex(sha1));
+
+       if (object->flags & SHOWN)
+               return;
+
+       buf = read_sha1_file(sha1, &type, &size);
+       if (!buf)
+               die ("Could not read blob %s", sha1_to_hex(sha1));
+
+       mark_next_object(object);
+
+       printf("blob\nmark :%"PRIu32"\ndata %lu\n", last_idnum, size);
+       if (size && fwrite(buf, size, 1, stdout) != 1)
+               die_errno ("Could not write blob '%s'", sha1_to_hex(sha1));
+       printf("\n");
+
+       show_progress();
+
+       object->flags |= SHOWN;
+       free(buf);
+}
+
+static int depth_first(const void *a_, const void *b_)
+{
+       const struct diff_filepair *a = *((const struct diff_filepair **)a_);
+       const struct diff_filepair *b = *((const struct diff_filepair **)b_);
+       const char *name_a, *name_b;
+       int len_a, len_b, len;
+       int cmp;
+
+       name_a = a->one ? a->one->path : a->two->path;
+       name_b = b->one ? b->one->path : b->two->path;
+
+       len_a = strlen(name_a);
+       len_b = strlen(name_b);
+       len = (len_a < len_b) ? len_a : len_b;
+
+       /* strcmp will sort 'd' before 'd/e', we want 'd/e' before 'd' */
+       cmp = memcmp(name_a, name_b, len);
+       if (cmp)
+               return cmp;
+       cmp = len_b - len_a;
+       if (cmp)
+               return cmp;
+       /*
+        * Move 'R'ename entries last so that all references of the file
+        * appear in the output before it is renamed (e.g., when a file
+        * was copied and renamed in the same commit).
+        */
+       return (a->status == 'R') - (b->status == 'R');
+}
+
+static void show_filemodify(struct diff_queue_struct *q,
+                           struct diff_options *options, void *data)
+{
+       int i;
+
+       /*
+        * Handle files below a directory first, in case they are all deleted
+        * and the directory changes to a file or symlink.
+        */
+       qsort(q->queue, q->nr, sizeof(q->queue[0]), depth_first);
+
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filespec *ospec = q->queue[i]->one;
+               struct diff_filespec *spec = q->queue[i]->two;
+
+               switch (q->queue[i]->status) {
+               case DIFF_STATUS_DELETED:
+                       printf("D %s\n", spec->path);
+                       break;
+
+               case DIFF_STATUS_COPIED:
+               case DIFF_STATUS_RENAMED:
+                       printf("%c \"%s\" \"%s\"\n", q->queue[i]->status,
+                              ospec->path, spec->path);
+
+                       if (!hashcmp(ospec->sha1, spec->sha1) &&
+                           ospec->mode == spec->mode)
+                               break;
+                       /* fallthrough */
+
+               case DIFF_STATUS_TYPE_CHANGED:
+               case DIFF_STATUS_MODIFIED:
+               case DIFF_STATUS_ADDED:
+                       /*
+                        * Links refer to objects in another repositories;
+                        * output the SHA-1 verbatim.
+                        */
+                       if (no_data || S_ISGITLINK(spec->mode))
+                               printf("M %06o %s %s\n", spec->mode,
+                                      sha1_to_hex(spec->sha1), spec->path);
+                       else {
+                               struct object *object = lookup_object(spec->sha1);
+                               printf("M %06o :%d %s\n", spec->mode,
+                                      get_object_mark(object), spec->path);
+                       }
+                       break;
+
+               default:
+                       die("Unexpected comparison status '%c' for %s, %s",
+                               q->queue[i]->status,
+                               ospec->path ? ospec->path : "none",
+                               spec->path ? spec->path : "none");
+               }
+       }
+}
+
+static const char *find_encoding(const char *begin, const char *end)
+{
+       const char *needle = "\nencoding ";
+       char *bol, *eol;
+
+       bol = memmem(begin, end ? end - begin : strlen(begin),
+                    needle, strlen(needle));
+       if (!bol)
+               return git_commit_encoding;
+       bol += strlen(needle);
+       eol = strchrnul(bol, '\n');
+       *eol = '\0';
+       return bol;
+}
+
+static void handle_commit(struct commit *commit, struct rev_info *rev)
+{
+       int saved_output_format = rev->diffopt.output_format;
+       const char *author, *author_end, *committer, *committer_end;
+       const char *encoding, *message;
+       char *reencoded = NULL;
+       struct commit_list *p;
+       int i;
+
+       rev->diffopt.output_format = DIFF_FORMAT_CALLBACK;
+
+       parse_commit(commit);
+       author = strstr(commit->buffer, "\nauthor ");
+       if (!author)
+               die ("Could not find author in commit %s",
+                    sha1_to_hex(commit->object.sha1));
+       author++;
+       author_end = strchrnul(author, '\n');
+       committer = strstr(author_end, "\ncommitter ");
+       if (!committer)
+               die ("Could not find committer in commit %s",
+                    sha1_to_hex(commit->object.sha1));
+       committer++;
+       committer_end = strchrnul(committer, '\n');
+       message = strstr(committer_end, "\n\n");
+       encoding = find_encoding(committer_end, message);
+       if (message)
+               message += 2;
+
+       if (commit->parents &&
+           get_object_mark(&commit->parents->item->object) != 0 &&
+           !full_tree) {
+               parse_commit(commit->parents->item);
+               diff_tree_sha1(commit->parents->item->tree->object.sha1,
+                              commit->tree->object.sha1, "", &rev->diffopt);
+       }
+       else
+               diff_root_tree_sha1(commit->tree->object.sha1,
+                                   "", &rev->diffopt);
+
+       /* Export the referenced blobs, and remember the marks. */
+       for (i = 0; i < diff_queued_diff.nr; i++)
+               if (!S_ISGITLINK(diff_queued_diff.queue[i]->two->mode))
+                       handle_object(diff_queued_diff.queue[i]->two->sha1);
+
+       mark_next_object(&commit->object);
+       if (!is_encoding_utf8(encoding))
+               reencoded = reencode_string(message, "UTF-8", encoding);
+       if (!commit->parents)
+               printf("reset %s\n", (const char*)commit->util);
+       printf("commit %s\nmark :%"PRIu32"\n%.*s\n%.*s\ndata %u\n%s",
+              (const char *)commit->util, last_idnum,
+              (int)(author_end - author), author,
+              (int)(committer_end - committer), committer,
+              (unsigned)(reencoded
+                         ? strlen(reencoded) : message
+                         ? strlen(message) : 0),
+              reencoded ? reencoded : message ? message : "");
+       free(reencoded);
+
+       for (i = 0, p = commit->parents; p; p = p->next) {
+               int mark = get_object_mark(&p->item->object);
+               if (!mark)
+                       continue;
+               if (i == 0)
+                       printf("from :%d\n", mark);
+               else
+                       printf("merge :%d\n", mark);
+               i++;
+       }
+
+       if (full_tree)
+               printf("deleteall\n");
+       log_tree_diff_flush(rev);
+       rev->diffopt.output_format = saved_output_format;
+
+       printf("\n");
+
+       show_progress();
+}
+
+static void handle_tail(struct object_array *commits, struct rev_info *revs)
+{
+       struct commit *commit;
+       while (commits->nr) {
+               commit = (struct commit *)commits->objects[commits->nr - 1].item;
+               if (has_unshown_parent(commit))
+                       return;
+               handle_commit(commit, revs);
+               commits->nr--;
+       }
+}
+
+static void handle_tag(const char *name, struct tag *tag)
+{
+       unsigned long size;
+       enum object_type type;
+       char *buf;
+       const char *tagger, *tagger_end, *message;
+       size_t message_size = 0;
+       struct object *tagged;
+       int tagged_mark;
+       struct commit *p;
+
+       /* Trees have no identifer in fast-export output, thus we have no way
+        * to output tags of trees, tags of tags of trees, etc.  Simply omit
+        * such tags.
+        */
+       tagged = tag->tagged;
+       while (tagged->type == OBJ_TAG) {
+               tagged = ((struct tag *)tagged)->tagged;
+       }
+       if (tagged->type == OBJ_TREE) {
+               warning("Omitting tag %s,\nsince tags of trees (or tags of tags of trees, etc.) are not supported.",
+                       sha1_to_hex(tag->object.sha1));
+               return;
+       }
+
+       buf = read_sha1_file(tag->object.sha1, &type, &size);
+       if (!buf)
+               die ("Could not read tag %s", sha1_to_hex(tag->object.sha1));
+       message = memmem(buf, size, "\n\n", 2);
+       if (message) {
+               message += 2;
+               message_size = strlen(message);
+       }
+       tagger = memmem(buf, message ? message - buf : size, "\ntagger ", 8);
+       if (!tagger) {
+               if (fake_missing_tagger)
+                       tagger = "tagger Unspecified Tagger "
+                               "<unspecified-tagger> 0 +0000";
+               else
+                       tagger = "";
+               tagger_end = tagger + strlen(tagger);
+       } else {
+               tagger++;
+               tagger_end = strchrnul(tagger, '\n');
+       }
+
+       /* handle signed tags */
+       if (message) {
+               const char *signature = strstr(message,
+                                              "\n-----BEGIN PGP SIGNATURE-----\n");
+               if (signature)
+                       switch(signed_tag_mode) {
+                       case ABORT:
+                               die ("Encountered signed tag %s; use "
+                                    "--signed-tag=<mode> to handle it.",
+                                    sha1_to_hex(tag->object.sha1));
+                       case WARN:
+                               warning ("Exporting signed tag %s",
+                                        sha1_to_hex(tag->object.sha1));
+                               /* fallthru */
+                       case VERBATIM:
+                               break;
+                       case STRIP:
+                               message_size = signature + 1 - message;
+                               break;
+                       }
+       }
+
+       /* handle tag->tagged having been filtered out due to paths specified */
+       tagged = tag->tagged;
+       tagged_mark = get_object_mark(tagged);
+       if (!tagged_mark) {
+               switch(tag_of_filtered_mode) {
+               case ABORT:
+                       die ("Tag %s tags unexported object; use "
+                            "--tag-of-filtered-object=<mode> to handle it.",
+                            sha1_to_hex(tag->object.sha1));
+               case DROP:
+                       /* Ignore this tag altogether */
+                       return;
+               case REWRITE:
+                       if (tagged->type != OBJ_COMMIT) {
+                               die ("Tag %s tags unexported %s!",
+                                    sha1_to_hex(tag->object.sha1),
+                                    typename(tagged->type));
+                       }
+                       p = (struct commit *)tagged;
+                       for (;;) {
+                               if (p->parents && p->parents->next)
+                                       break;
+                               if (p->object.flags & UNINTERESTING)
+                                       break;
+                               if (!(p->object.flags & TREESAME))
+                                       break;
+                               if (!p->parents)
+                                       die ("Can't find replacement commit for tag %s\n",
+                                            sha1_to_hex(tag->object.sha1));
+                               p = p->parents->item;
+                       }
+                       tagged_mark = get_object_mark(&p->object);
+               }
+       }
+
+       if (!prefixcmp(name, "refs/tags/"))
+               name += 10;
+       printf("tag %s\nfrom :%d\n%.*s%sdata %d\n%.*s\n",
+              name, tagged_mark,
+              (int)(tagger_end - tagger), tagger,
+              tagger == tagger_end ? "" : "\n",
+              (int)message_size, (int)message_size, message ? message : "");
+}
+
+static void get_tags_and_duplicates(struct object_array *pending,
+                                   struct string_list *extra_refs)
+{
+       struct tag *tag;
+       int i;
+
+       for (i = 0; i < pending->nr; i++) {
+               struct object_array_entry *e = pending->objects + i;
+               unsigned char sha1[20];
+               struct commit *commit = commit;
+               char *full_name;
+
+               if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1)
+                       continue;
+
+               switch (e->item->type) {
+               case OBJ_COMMIT:
+                       commit = (struct commit *)e->item;
+                       break;
+               case OBJ_TAG:
+                       tag = (struct tag *)e->item;
+
+                       /* handle nested tags */
+                       while (tag && tag->object.type == OBJ_TAG) {
+                               parse_object(tag->object.sha1);
+                               string_list_append(extra_refs, full_name)->util = tag;
+                               tag = (struct tag *)tag->tagged;
+                       }
+                       if (!tag)
+                               die ("Tag %s points nowhere?", e->name);
+                       switch(tag->object.type) {
+                       case OBJ_COMMIT:
+                               commit = (struct commit *)tag;
+                               break;
+                       case OBJ_BLOB:
+                               handle_object(tag->object.sha1);
+                               continue;
+                       default: /* OBJ_TAG (nested tags) is already handled */
+                               warning("Tag points to object of unexpected type %s, skipping.",
+                                       typename(tag->object.type));
+                               continue;
+                       }
+                       break;
+               default:
+                       warning("%s: Unexpected object of type %s, skipping.",
+                               e->name,
+                               typename(e->item->type));
+                       continue;
+               }
+               if (commit->util)
+                       /* more than one name for the same object */
+                       string_list_append(extra_refs, full_name)->util = commit;
+               else
+                       commit->util = full_name;
+       }
+}
+
+static void handle_tags_and_duplicates(struct string_list *extra_refs)
+{
+       struct commit *commit;
+       int i;
+
+       for (i = extra_refs->nr - 1; i >= 0; i--) {
+               const char *name = extra_refs->items[i].string;
+               struct object *object = extra_refs->items[i].util;
+               switch (object->type) {
+               case OBJ_TAG:
+                       handle_tag(name, (struct tag *)object);
+                       break;
+               case OBJ_COMMIT:
+                       /* create refs pointing to already seen commits */
+                       commit = (struct commit *)object;
+                       printf("reset %s\nfrom :%d\n\n", name,
+                              get_object_mark(&commit->object));
+                       show_progress();
+                       break;
+               }
+       }
+}
+
+static void export_marks(char *file)
+{
+       unsigned int i;
+       uint32_t mark;
+       struct object_decoration *deco = idnums.hash;
+       FILE *f;
+       int e = 0;
+
+       f = fopen(file, "w");
+       if (!f)
+               die_errno("Unable to open marks file %s for writing.", file);
+
+       for (i = 0; i < idnums.size; i++) {
+               if (deco->base && deco->base->type == 1) {
+                       mark = ptr_to_mark(deco->decoration);
+                       if (fprintf(f, ":%"PRIu32" %s\n", mark,
+                               sha1_to_hex(deco->base->sha1)) < 0) {
+                           e = 1;
+                           break;
+                       }
+               }
+               deco++;
+       }
+
+       e |= ferror(f);
+       e |= fclose(f);
+       if (e)
+               error("Unable to write marks file %s.", file);
+}
+
+static void import_marks(char *input_file)
+{
+       char line[512];
+       FILE *f = fopen(input_file, "r");
+       if (!f)
+               die_errno("cannot read '%s'", input_file);
+
+       while (fgets(line, sizeof(line), f)) {
+               uint32_t mark;
+               char *line_end, *mark_end;
+               unsigned char sha1[20];
+               struct object *object;
+
+               line_end = strchr(line, '\n');
+               if (line[0] != ':' || !line_end)
+                       die("corrupt mark line: %s", line);
+               *line_end = '\0';
+
+               mark = strtoumax(line + 1, &mark_end, 10);
+               if (!mark || mark_end == line + 1
+                       || *mark_end != ' ' || get_sha1(mark_end + 1, sha1))
+                       die("corrupt mark line: %s", line);
+
+               object = parse_object(sha1);
+               if (!object)
+                       die ("Could not read blob %s", sha1_to_hex(sha1));
+
+               if (object->flags & SHOWN)
+                       error("Object %s already has a mark", sha1);
+
+               mark_object(object, mark);
+               if (last_idnum < mark)
+                       last_idnum = mark;
+
+               object->flags |= SHOWN;
+       }
+       fclose(f);
+}
+
+int cmd_fast_export(int argc, const char **argv, const char *prefix)
+{
+       struct rev_info revs;
+       struct object_array commits = OBJECT_ARRAY_INIT;
+       struct string_list extra_refs = STRING_LIST_INIT_NODUP;
+       struct commit *commit;
+       char *export_filename = NULL, *import_filename = NULL;
+       struct option options[] = {
+               OPT_INTEGER(0, "progress", &progress,
+                           "show progress after <n> objects"),
+               OPT_CALLBACK(0, "signed-tags", &signed_tag_mode, "mode",
+                            "select handling of signed tags",
+                            parse_opt_signed_tag_mode),
+               OPT_CALLBACK(0, "tag-of-filtered-object", &tag_of_filtered_mode, "mode",
+                            "select handling of tags that tag filtered objects",
+                            parse_opt_tag_of_filtered_mode),
+               OPT_STRING(0, "export-marks", &export_filename, "FILE",
+                            "Dump marks to this file"),
+               OPT_STRING(0, "import-marks", &import_filename, "FILE",
+                            "Import marks from this file"),
+               OPT_BOOLEAN(0, "fake-missing-tagger", &fake_missing_tagger,
+                            "Fake a tagger when tags lack one"),
+               OPT_BOOLEAN(0, "full-tree", &full_tree,
+                            "Output full tree for each commit"),
+               { OPTION_NEGBIT, 0, "data", &no_data, NULL,
+                       "Skip output of blob data",
+                       PARSE_OPT_NOARG | PARSE_OPT_NEGHELP, NULL, 1 },
+               OPT_END()
+       };
+
+       if (argc == 1)
+               usage_with_options (fast_export_usage, options);
+
+       /* we handle encodings */
+       git_config(git_default_config, NULL);
+
+       init_revisions(&revs, prefix);
+       revs.topo_order = 1;
+       revs.show_source = 1;
+       revs.rewrite_parents = 1;
+       argc = setup_revisions(argc, argv, &revs, NULL);
+       argc = parse_options(argc, argv, prefix, options, fast_export_usage, 0);
+       if (argc > 1)
+               usage_with_options (fast_export_usage, options);
+
+       if (import_filename)
+               import_marks(import_filename);
+
+       if (import_filename && revs.prune_data)
+               full_tree = 1;
+
+       get_tags_and_duplicates(&revs.pending, &extra_refs);
+
+       if (prepare_revision_walk(&revs))
+               die("revision walk setup failed");
+       revs.diffopt.format_callback = show_filemodify;
+       DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
+       while ((commit = get_revision(&revs))) {
+               if (has_unshown_parent(commit)) {
+                       add_object_array(&commit->object, NULL, &commits);
+               }
+               else {
+                       handle_commit(commit, &revs);
+                       handle_tail(&commits, &revs);
+               }
+       }
+
+       handle_tags_and_duplicates(&extra_refs);
+
+       if (export_filename)
+               export_marks(export_filename);
+
+       return 0;
+}
diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c
new file mode 100644 (file)
index 0000000..dbd8b7b
--- /dev/null
@@ -0,0 +1,976 @@
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include "commit.h"
+#include "tag.h"
+#include "exec_cmd.h"
+#include "pack.h"
+#include "sideband.h"
+#include "fetch-pack.h"
+#include "remote.h"
+#include "run-command.h"
+
+static int transfer_unpack_limit = -1;
+static int fetch_unpack_limit = -1;
+static int unpack_limit = 100;
+static int prefer_ofs_delta = 1;
+static struct fetch_pack_args args = {
+       /* .uploadpack = */ "git-upload-pack",
+};
+
+static const char fetch_pack_usage[] =
+"git fetch-pack [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]";
+
+#define COMPLETE       (1U << 0)
+#define COMMON         (1U << 1)
+#define COMMON_REF     (1U << 2)
+#define SEEN           (1U << 3)
+#define POPPED         (1U << 4)
+
+static int marked;
+
+/*
+ * After sending this many "have"s if we do not get any new ACK , we
+ * give up traversing our history.
+ */
+#define MAX_IN_VAIN 256
+
+static struct commit_list *rev_list;
+static int non_common_revs, multi_ack, use_sideband;
+
+static void rev_list_push(struct commit *commit, int mark)
+{
+       if (!(commit->object.flags & mark)) {
+               commit->object.flags |= mark;
+
+               if (!(commit->object.parsed))
+                       if (parse_commit(commit))
+                               return;
+
+               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, int flag, void *cb_data)
+{
+       struct object *o = deref_tag(parse_object(sha1), path, 0);
+
+       if (o && o->type == OBJ_COMMIT)
+               rev_list_push((struct commit *)o, SEEN);
+
+       return 0;
+}
+
+static int clear_marks(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+{
+       struct object *o = deref_tag(parse_object(sha1), path, 0);
+
+       if (o && o->type == OBJ_COMMIT)
+               clear_commit_marks((struct commit *)o,
+                                  COMMON | COMMON_REF | SEEN | POPPED);
+       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)
+                               if (parse_commit(commit))
+                                       return;
+
+                       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);
+               parents = commit->parents;
+
+               commit->object.flags |= POPPED;
+               if (!(commit->object.flags & COMMON))
+                       non_common_revs--;
+
+               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;
+}
+
+enum ack_type {
+       NAK = 0,
+       ACK,
+       ACK_continue,
+       ACK_common,
+       ACK_ready
+};
+
+static void consume_shallow_list(int fd)
+{
+       if (args.stateless_rpc && args.depth > 0) {
+               /* If we sent a depth we will get back "duplicate"
+                * shallow and unshallow commands every time there
+                * is a block of have lines exchanged.
+                */
+               char line[1000];
+               while (packet_read_line(fd, line, sizeof(line))) {
+                       if (!prefixcmp(line, "shallow "))
+                               continue;
+                       if (!prefixcmp(line, "unshallow "))
+                               continue;
+                       die("git fetch-pack: expected shallow list");
+               }
+       }
+}
+
+static enum ack_type 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 NAK;
+       if (!prefixcmp(line, "ACK ")) {
+               if (!get_sha1_hex(line+4, result_sha1)) {
+                       if (strstr(line+45, "continue"))
+                               return ACK_continue;
+                       if (strstr(line+45, "common"))
+                               return ACK_common;
+                       if (strstr(line+45, "ready"))
+                               return ACK_ready;
+                       return ACK;
+               }
+       }
+       die("git fetch_pack: expected ACK/NAK, got '%s'", line);
+}
+
+static void send_request(int fd, struct strbuf *buf)
+{
+       if (args.stateless_rpc) {
+               send_sideband(fd, -1, buf->buf, buf->len, LARGE_PACKET_MAX);
+               packet_flush(fd);
+       } else
+               safe_write(fd, buf->buf, buf->len);
+}
+
+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;
+       unsigned in_vain = 0;
+       int got_continue = 0;
+       struct strbuf req_buf = STRBUF_INIT;
+       size_t state_len = 0;
+
+       if (args.stateless_rpc && multi_ack == 1)
+               die("--stateless-rpc requires multi_ack_detailed");
+       if (marked)
+               for_each_ref(clear_marks, NULL);
+       marked = 1;
+
+       for_each_ref(rev_list_insert_ref, NULL);
+
+       fetching = 0;
+       for ( ; refs ; refs = refs->next) {
+               unsigned char *remote = refs->old_sha1;
+               const char *remote_hex;
+               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;
+               }
+
+               remote_hex = sha1_to_hex(remote);
+               if (!fetching) {
+                       struct strbuf c = STRBUF_INIT;
+                       if (multi_ack == 2)     strbuf_addstr(&c, " multi_ack_detailed");
+                       if (multi_ack == 1)     strbuf_addstr(&c, " multi_ack");
+                       if (use_sideband == 2)  strbuf_addstr(&c, " side-band-64k");
+                       if (use_sideband == 1)  strbuf_addstr(&c, " side-band");
+                       if (args.use_thin_pack) strbuf_addstr(&c, " thin-pack");
+                       if (args.no_progress)   strbuf_addstr(&c, " no-progress");
+                       if (args.include_tag)   strbuf_addstr(&c, " include-tag");
+                       if (prefer_ofs_delta)   strbuf_addstr(&c, " ofs-delta");
+                       packet_buf_write(&req_buf, "want %s%s\n", remote_hex, c.buf);
+                       strbuf_release(&c);
+               } else
+                       packet_buf_write(&req_buf, "want %s\n", remote_hex);
+               fetching++;
+       }
+
+       if (!fetching) {
+               strbuf_release(&req_buf);
+               packet_flush(fd[1]);
+               return 1;
+       }
+
+       if (is_repository_shallow())
+               write_shallow_commits(&req_buf, 1);
+       if (args.depth > 0)
+               packet_buf_write(&req_buf, "deepen %d", args.depth);
+       packet_buf_flush(&req_buf);
+       state_len = req_buf.len;
+
+       if (args.depth > 0) {
+               char line[1024];
+               unsigned char sha1[20];
+
+               send_request(fd[1], &req_buf);
+               while (packet_read_line(fd[0], line, sizeof(line))) {
+                       if (!prefixcmp(line, "shallow ")) {
+                               if (get_sha1_hex(line + 8, sha1))
+                                       die("invalid shallow line: %s", line);
+                               register_shallow(sha1);
+                               continue;
+                       }
+                       if (!prefixcmp(line, "unshallow ")) {
+                               if (get_sha1_hex(line + 10, sha1))
+                                       die("invalid unshallow line: %s", line);
+                               if (!lookup_object(sha1))
+                                       die("object not found: %s", line);
+                               /* make sure that it is parsed as shallow */
+                               if (!parse_object(sha1))
+                                       die("error in object: %s", line);
+                               if (unregister_shallow(sha1))
+                                       die("no shallow found: %s", line);
+                               continue;
+                       }
+                       die("expected shallow/unshallow, got %s", line);
+               }
+       } else if (!args.stateless_rpc)
+               send_request(fd[1], &req_buf);
+
+       if (!args.stateless_rpc) {
+               /* If we aren't using the stateless-rpc interface
+                * we don't need to retain the headers.
+                */
+               strbuf_setlen(&req_buf, 0);
+               state_len = 0;
+       }
+
+       flushes = 0;
+       retval = -1;
+       while ((sha1 = get_rev())) {
+               packet_buf_write(&req_buf, "have %s\n", sha1_to_hex(sha1));
+               if (args.verbose)
+                       fprintf(stderr, "have %s\n", sha1_to_hex(sha1));
+               in_vain++;
+               if (!(31 & ++count)) {
+                       int ack;
+
+                       packet_buf_flush(&req_buf);
+                       send_request(fd[1], &req_buf);
+                       strbuf_setlen(&req_buf, state_len);
+                       flushes++;
+
+                       /*
+                        * We keep one window "ahead" of the other side, and
+                        * will wait for an ACK only on the next one
+                        */
+                       if (!args.stateless_rpc && count == 32)
+                               continue;
+
+                       consume_shallow_list(fd[0]);
+                       do {
+                               ack = get_ack(fd[0], result_sha1);
+                               if (args.verbose && ack)
+                                       fprintf(stderr, "got ack %d %s\n", ack,
+                                                       sha1_to_hex(result_sha1));
+                               switch (ack) {
+                               case ACK:
+                                       flushes = 0;
+                                       multi_ack = 0;
+                                       retval = 0;
+                                       goto done;
+                               case ACK_common:
+                               case ACK_ready:
+                               case ACK_continue: {
+                                       struct commit *commit =
+                                               lookup_commit(result_sha1);
+                                       if (args.stateless_rpc
+                                        && ack == ACK_common
+                                        && !(commit->object.flags & COMMON)) {
+                                               /* We need to replay the have for this object
+                                                * on the next RPC request so the peer knows
+                                                * it is in common with us.
+                                                */
+                                               const char *hex = sha1_to_hex(result_sha1);
+                                               packet_buf_write(&req_buf, "have %s\n", hex);
+                                               state_len = req_buf.len;
+                                       }
+                                       mark_common(commit, 0, 1);
+                                       retval = 0;
+                                       in_vain = 0;
+                                       got_continue = 1;
+                                       break;
+                                       }
+                               }
+                       } while (ack);
+                       flushes--;
+                       if (got_continue && MAX_IN_VAIN < in_vain) {
+                               if (args.verbose)
+                                       fprintf(stderr, "giving up\n");
+                               break; /* give up */
+                       }
+               }
+       }
+done:
+       packet_buf_write(&req_buf, "done\n");
+       send_request(fd[1], &req_buf);
+       if (args.verbose)
+               fprintf(stderr, "done\n");
+       if (retval != 0) {
+               multi_ack = 0;
+               flushes++;
+       }
+       strbuf_release(&req_buf);
+
+       consume_shallow_list(fd[0]);
+       while (flushes || multi_ack) {
+               int ack = get_ack(fd[0], result_sha1);
+               if (ack) {
+                       if (args.verbose)
+                               fprintf(stderr, "got ack (%d) %s\n", ack,
+                                       sha1_to_hex(result_sha1));
+                       if (ack == ACK)
+                               return 0;
+                       multi_ack = 1;
+                       continue;
+               }
+               flushes--;
+       }
+       /* it is no error to fetch into a completely empty repo */
+       return count ? retval : 0;
+}
+
+static struct commit_list *complete;
+
+static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+{
+       struct object *o = parse_object(sha1);
+
+       while (o && o->type == OBJ_TAG) {
+               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 == OBJ_COMMIT) {
+               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 (args.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 && !args.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 (args.fetch_all &&
+                        (!args.depth || prefixcmp(ref->name, "refs/tags/") )) {
+                       *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 (!args.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;
+
+       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 == OBJ_COMMIT) {
+                       struct commit *commit = (struct commit *)o;
+                       if (!cutoff || cutoff < commit->date)
+                               cutoff = commit->date;
+               }
+       }
+
+       if (!args.depth) {
+               for_each_ref(mark_complete, NULL);
+               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 != OBJ_COMMIT || !(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 (!args.verbose)
+                               continue;
+                       fprintf(stderr,
+                               "want %s (%s)\n", sha1_to_hex(remote),
+                               ref->name);
+                       continue;
+               }
+
+               hashcpy(ref->new_sha1, local);
+               if (!args.verbose)
+                       continue;
+               fprintf(stderr,
+                       "already have %s (%s)\n", sha1_to_hex(remote),
+                       ref->name);
+       }
+       return retval;
+}
+
+static int sideband_demux(int in, int out, void *data)
+{
+       int *xd = data;
+
+       int ret = recv_sideband("fetch-pack", xd[0], out);
+       close(out);
+       return ret;
+}
+
+static int get_pack(int xd[2], char **pack_lockfile)
+{
+       struct async demux;
+       const char *argv[20];
+       char keep_arg[256];
+       char hdr_arg[256];
+       const char **av;
+       int do_keep = args.keep_pack;
+       struct child_process cmd;
+
+       memset(&demux, 0, sizeof(demux));
+       if (use_sideband) {
+               /* xd[] is talking with upload-pack; subprocess reads from
+                * xd[0], spits out band#2 to stderr, and feeds us band#1
+                * through demux->out.
+                */
+               demux.proc = sideband_demux;
+               demux.data = xd;
+               demux.out = -1;
+               if (start_async(&demux))
+                       die("fetch-pack: unable to fork off sideband"
+                           " demultiplexer");
+       }
+       else
+               demux.out = xd[0];
+
+       memset(&cmd, 0, sizeof(cmd));
+       cmd.argv = argv;
+       av = argv;
+       *hdr_arg = 0;
+       if (!args.keep_pack && unpack_limit) {
+               struct pack_header header;
+
+               if (read_pack_header(demux.out, &header))
+                       die("protocol error: bad pack header");
+               snprintf(hdr_arg, sizeof(hdr_arg),
+                        "--pack_header=%"PRIu32",%"PRIu32,
+                        ntohl(header.hdr_version), ntohl(header.hdr_entries));
+               if (ntohl(header.hdr_entries) < unpack_limit)
+                       do_keep = 0;
+               else
+                       do_keep = 1;
+       }
+
+       if (do_keep) {
+               if (pack_lockfile)
+                       cmd.out = -1;
+               *av++ = "index-pack";
+               *av++ = "--stdin";
+               if (!args.quiet && !args.no_progress)
+                       *av++ = "-v";
+               if (args.use_thin_pack)
+                       *av++ = "--fix-thin";
+               if (args.lock_pack || unpack_limit) {
+                       int s = sprintf(keep_arg,
+                                       "--keep=fetch-pack %"PRIuMAX " on ", (uintmax_t) getpid());
+                       if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
+                               strcpy(keep_arg + s, "localhost");
+                       *av++ = keep_arg;
+               }
+       }
+       else {
+               *av++ = "unpack-objects";
+               if (args.quiet)
+                       *av++ = "-q";
+       }
+       if (*hdr_arg)
+               *av++ = hdr_arg;
+       *av++ = NULL;
+
+       cmd.in = demux.out;
+       cmd.git_cmd = 1;
+       if (start_command(&cmd))
+               die("fetch-pack: unable to fork off %s", argv[0]);
+       if (do_keep && pack_lockfile) {
+               *pack_lockfile = index_pack_lockfile(cmd.out);
+               close(cmd.out);
+       }
+
+       if (finish_command(&cmd))
+               die("%s failed", argv[0]);
+       if (use_sideband && finish_async(&demux))
+               die("error in sideband demultiplexer");
+       return 0;
+}
+
+static struct ref *do_fetch_pack(int fd[2],
+               const struct ref *orig_ref,
+               int nr_match,
+               char **match,
+               char **pack_lockfile)
+{
+       struct ref *ref = copy_ref_list(orig_ref);
+       unsigned char sha1[20];
+
+       if (is_repository_shallow() && !server_supports("shallow"))
+               die("Server does not support shallow clients");
+       if (server_supports("multi_ack_detailed")) {
+               if (args.verbose)
+                       fprintf(stderr, "Server supports multi_ack_detailed\n");
+               multi_ack = 2;
+       }
+       else if (server_supports("multi_ack")) {
+               if (args.verbose)
+                       fprintf(stderr, "Server supports multi_ack\n");
+               multi_ack = 1;
+       }
+       if (server_supports("side-band-64k")) {
+               if (args.verbose)
+                       fprintf(stderr, "Server supports side-band-64k\n");
+               use_sideband = 2;
+       }
+       else if (server_supports("side-band")) {
+               if (args.verbose)
+                       fprintf(stderr, "Server supports side-band\n");
+               use_sideband = 1;
+       }
+       if (server_supports("ofs-delta")) {
+               if (args.verbose)
+                       fprintf(stderr, "Server supports ofs-delta\n");
+       } else
+               prefer_ofs_delta = 0;
+       if (everything_local(&ref, nr_match, match)) {
+               packet_flush(fd[1]);
+               goto all_done;
+       }
+       if (find_common(fd, sha1, ref) < 0)
+               if (!args.keep_pack)
+                       /* When cloning, it is not unusual to have
+                        * no common commit.
+                        */
+                       warning("no common commits");
+
+       if (args.stateless_rpc)
+               packet_flush(fd[1]);
+       if (get_pack(fd, pack_lockfile))
+               die("git fetch-pack: fetch failed.");
+
+ all_done:
+       return ref;
+}
+
+static int remove_duplicates(int nr_heads, char **heads)
+{
+       int src, dst;
+
+       for (src = dst = 0; src < nr_heads; src++) {
+               /* If heads[src] is different from any of
+                * heads[0..dst], push it in.
+                */
+               int i;
+               for (i = 0; i < dst; i++) {
+                       if (!strcmp(heads[i], heads[src]))
+                               break;
+               }
+               if (i < dst)
+                       continue;
+               if (src != dst)
+                       heads[dst] = heads[src];
+               dst++;
+       }
+       return dst;
+}
+
+static int fetch_pack_config(const char *var, const char *value, void *cb)
+{
+       if (strcmp(var, "fetch.unpacklimit") == 0) {
+               fetch_unpack_limit = git_config_int(var, value);
+               return 0;
+       }
+
+       if (strcmp(var, "transfer.unpacklimit") == 0) {
+               transfer_unpack_limit = git_config_int(var, value);
+               return 0;
+       }
+
+       if (strcmp(var, "repack.usedeltabaseoffset") == 0) {
+               prefer_ofs_delta = git_config_bool(var, value);
+               return 0;
+       }
+
+       return git_default_config(var, value, cb);
+}
+
+static struct lock_file lock;
+
+static void fetch_pack_setup(void)
+{
+       static int did_setup;
+       if (did_setup)
+               return;
+       git_config(fetch_pack_config, NULL);
+       if (0 <= transfer_unpack_limit)
+               unpack_limit = transfer_unpack_limit;
+       else if (0 <= fetch_unpack_limit)
+               unpack_limit = fetch_unpack_limit;
+       did_setup = 1;
+}
+
+int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
+{
+       int i, ret, nr_heads;
+       struct ref *ref = NULL;
+       char *dest = NULL, **heads;
+       int fd[2];
+       char *pack_lockfile = NULL;
+       char **pack_lockfile_ptr = NULL;
+       struct child_process *conn;
+
+       nr_heads = 0;
+       heads = NULL;
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (*arg == '-') {
+                       if (!prefixcmp(arg, "--upload-pack=")) {
+                               args.uploadpack = arg + 14;
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--exec=")) {
+                               args.uploadpack = arg + 7;
+                               continue;
+                       }
+                       if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) {
+                               args.quiet = 1;
+                               continue;
+                       }
+                       if (!strcmp("--keep", arg) || !strcmp("-k", arg)) {
+                               args.lock_pack = args.keep_pack;
+                               args.keep_pack = 1;
+                               continue;
+                       }
+                       if (!strcmp("--thin", arg)) {
+                               args.use_thin_pack = 1;
+                               continue;
+                       }
+                       if (!strcmp("--include-tag", arg)) {
+                               args.include_tag = 1;
+                               continue;
+                       }
+                       if (!strcmp("--all", arg)) {
+                               args.fetch_all = 1;
+                               continue;
+                       }
+                       if (!strcmp("-v", arg)) {
+                               args.verbose = 1;
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--depth=")) {
+                               args.depth = strtol(arg + 8, NULL, 0);
+                               continue;
+                       }
+                       if (!strcmp("--no-progress", arg)) {
+                               args.no_progress = 1;
+                               continue;
+                       }
+                       if (!strcmp("--stateless-rpc", arg)) {
+                               args.stateless_rpc = 1;
+                               continue;
+                       }
+                       if (!strcmp("--lock-pack", arg)) {
+                               args.lock_pack = 1;
+                               pack_lockfile_ptr = &pack_lockfile;
+                               continue;
+                       }
+                       usage(fetch_pack_usage);
+               }
+               dest = (char *)arg;
+               heads = (char **)(argv + i + 1);
+               nr_heads = argc - i - 1;
+               break;
+       }
+       if (!dest)
+               usage(fetch_pack_usage);
+
+       if (args.stateless_rpc) {
+               conn = NULL;
+               fd[0] = 0;
+               fd[1] = 1;
+       } else {
+               conn = git_connect(fd, (char *)dest, args.uploadpack,
+                                  args.verbose ? CONNECT_VERBOSE : 0);
+       }
+
+       get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL);
+
+       ref = fetch_pack(&args, fd, conn, ref, dest,
+               nr_heads, heads, pack_lockfile_ptr);
+       if (pack_lockfile) {
+               printf("lock %s\n", pack_lockfile);
+               fflush(stdout);
+       }
+       close(fd[0]);
+       close(fd[1]);
+       if (finish_connect(conn))
+               ref = NULL;
+       ret = !ref;
+
+       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;
+                       }
+       }
+       while (ref) {
+               printf("%s %s\n",
+                      sha1_to_hex(ref->old_sha1), ref->name);
+               ref = ref->next;
+       }
+
+       return ret;
+}
+
+struct ref *fetch_pack(struct fetch_pack_args *my_args,
+                      int fd[], struct child_process *conn,
+                      const struct ref *ref,
+               const char *dest,
+               int nr_heads,
+               char **heads,
+               char **pack_lockfile)
+{
+       struct stat st;
+       struct ref *ref_cpy;
+
+       fetch_pack_setup();
+       if (&args != my_args)
+               memcpy(&args, my_args, sizeof(args));
+       if (args.depth > 0) {
+               if (stat(git_path("shallow"), &st))
+                       st.st_mtime = 0;
+       }
+
+       if (heads && nr_heads)
+               nr_heads = remove_duplicates(nr_heads, heads);
+       if (!ref) {
+               packet_flush(fd[1]);
+               die("no matching remote head");
+       }
+       ref_cpy = do_fetch_pack(fd, ref, nr_heads, heads, pack_lockfile);
+
+       if (args.depth > 0) {
+               struct cache_time mtime;
+               struct strbuf sb = STRBUF_INIT;
+               char *shallow = git_path("shallow");
+               int fd;
+
+               mtime.sec = st.st_mtime;
+               mtime.nsec = ST_MTIME_NSEC(st);
+               if (stat(shallow, &st)) {
+                       if (mtime.sec)
+                               die("shallow file was removed during fetch");
+               } else if (st.st_mtime != mtime.sec
+#ifdef USE_NSEC
+                               || ST_MTIME_NSEC(st) != mtime.nsec
+#endif
+                         )
+                       die("shallow file was changed during fetch");
+
+               fd = hold_lock_file_for_update(&lock, shallow,
+                                              LOCK_DIE_ON_ERROR);
+               if (!write_shallow_commits(&sb, 0)
+                || write_in_full(fd, sb.buf, sb.len) != sb.len) {
+                       unlink_or_warn(shallow);
+                       rollback_lock_file(&lock);
+               } else {
+                       commit_lock_file(&lock);
+               }
+               strbuf_release(&sb);
+       }
+
+       reprepare_packed_git();
+       return ref_cpy;
+}
diff --git a/builtin/fetch.c b/builtin/fetch.c
new file mode 100644 (file)
index 0000000..6fc5047
--- /dev/null
@@ -0,0 +1,934 @@
+/*
+ * "git fetch"
+ */
+#include "cache.h"
+#include "refs.h"
+#include "commit.h"
+#include "builtin.h"
+#include "string-list.h"
+#include "remote.h"
+#include "transport.h"
+#include "run-command.h"
+#include "parse-options.h"
+#include "sigchain.h"
+#include "transport.h"
+
+static const char * const builtin_fetch_usage[] = {
+       "git fetch [<options>] [<repository> [<refspec>...]]",
+       "git fetch [<options>] <group>",
+       "git fetch --multiple [<options>] [<repository> | <group>]...",
+       "git fetch --all [<options>]",
+       NULL
+};
+
+enum {
+       TAGS_UNSET = 0,
+       TAGS_DEFAULT = 1,
+       TAGS_SET = 2
+};
+
+static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
+static int progress;
+static int tags = TAGS_DEFAULT;
+static const char *depth;
+static const char *upload_pack;
+static struct strbuf default_rla = STRBUF_INIT;
+static struct transport *transport;
+
+static struct option builtin_fetch_options[] = {
+       OPT__VERBOSITY(&verbosity),
+       OPT_BOOLEAN(0, "all", &all,
+                   "fetch from all remotes"),
+       OPT_BOOLEAN('a', "append", &append,
+                   "append to .git/FETCH_HEAD instead of overwriting"),
+       OPT_STRING(0, "upload-pack", &upload_pack, "PATH",
+                  "path to upload pack on remote end"),
+       OPT_BOOLEAN('f', "force", &force,
+                   "force overwrite of local branch"),
+       OPT_BOOLEAN('m', "multiple", &multiple,
+                   "fetch from multiple remotes"),
+       OPT_SET_INT('t', "tags", &tags,
+                   "fetch all tags and associated objects", TAGS_SET),
+       OPT_SET_INT('n', NULL, &tags,
+                   "do not fetch all tags (--no-tags)", TAGS_UNSET),
+       OPT_BOOLEAN('p', "prune", &prune,
+                   "prune tracking branches no longer on remote"),
+       OPT_BOOLEAN(0, "dry-run", &dry_run,
+                   "dry run"),
+       OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
+       OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
+                   "allow updating of HEAD ref"),
+       OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
+       OPT_STRING(0, "depth", &depth, "DEPTH",
+                  "deepen history of shallow clone"),
+       OPT_END()
+};
+
+static void unlock_pack(void)
+{
+       if (transport)
+               transport_unlock_pack(transport);
+}
+
+static void unlock_pack_on_signal(int signo)
+{
+       unlock_pack();
+       sigchain_pop(signo);
+       raise(signo);
+}
+
+static void add_merge_config(struct ref **head,
+                          const struct ref *remote_refs,
+                          struct branch *branch,
+                          struct ref ***tail)
+{
+       int i;
+
+       for (i = 0; i < branch->merge_nr; i++) {
+               struct ref *rm, **old_tail = *tail;
+               struct refspec refspec;
+
+               for (rm = *head; rm; rm = rm->next) {
+                       if (branch_merge_matches(branch, i, rm->name)) {
+                               rm->merge = 1;
+                               break;
+                       }
+               }
+               if (rm)
+                       continue;
+
+               /*
+                * Not fetched to a tracking branch?  We need to fetch
+                * it anyway to allow this branch's "branch.$name.merge"
+                * to be honored by 'git pull', but we do not have to
+                * fail if branch.$name.merge is misconfigured to point
+                * at a nonexisting branch.  If we were indeed called by
+                * 'git pull', it will notice the misconfiguration because
+                * there is no entry in the resulting FETCH_HEAD marked
+                * for merging.
+                */
+               memset(&refspec, 0, sizeof(refspec));
+               refspec.src = branch->merge[i]->src;
+               get_fetch_map(remote_refs, &refspec, tail, 1);
+               for (rm = *old_tail; rm; rm = rm->next)
+                       rm->merge = 1;
+       }
+}
+
+static void find_non_local_tags(struct transport *transport,
+                       struct ref **head,
+                       struct ref ***tail);
+
+static struct ref *get_ref_map(struct transport *transport,
+                              struct refspec *refs, int ref_count, int tags,
+                              int *autotags)
+{
+       int i;
+       struct ref *rm;
+       struct ref *ref_map = NULL;
+       struct ref **tail = &ref_map;
+
+       const struct ref *remote_refs = transport_get_remote_refs(transport);
+
+       if (ref_count || tags == TAGS_SET) {
+               for (i = 0; i < ref_count; i++) {
+                       get_fetch_map(remote_refs, &refs[i], &tail, 0);
+                       if (refs[i].dst && refs[i].dst[0])
+                               *autotags = 1;
+               }
+               /* Merge everything on the command line, but not --tags */
+               for (rm = ref_map; rm; rm = rm->next)
+                       rm->merge = 1;
+               if (tags == TAGS_SET)
+                       get_fetch_map(remote_refs, tag_refspec, &tail, 0);
+       } else {
+               /* Use the defaults */
+               struct remote *remote = transport->remote;
+               struct branch *branch = branch_get(NULL);
+               int has_merge = branch_has_merge_config(branch);
+               if (remote &&
+                   (remote->fetch_refspec_nr ||
+                    /* Note: has_merge implies non-NULL branch->remote_name */
+                    (has_merge && !strcmp(branch->remote_name, remote->name)))) {
+                       for (i = 0; i < remote->fetch_refspec_nr; i++) {
+                               get_fetch_map(remote_refs, &remote->fetch[i], &tail, 0);
+                               if (remote->fetch[i].dst &&
+                                   remote->fetch[i].dst[0])
+                                       *autotags = 1;
+                               if (!i && !has_merge && ref_map &&
+                                   !remote->fetch[0].pattern)
+                                       ref_map->merge = 1;
+                       }
+                       /*
+                        * if the remote we're fetching from is the same
+                        * as given in branch.<name>.remote, we add the
+                        * ref given in branch.<name>.merge, too.
+                        *
+                        * Note: has_merge implies non-NULL branch->remote_name
+                        */
+                       if (has_merge &&
+                           !strcmp(branch->remote_name, remote->name))
+                               add_merge_config(&ref_map, remote_refs, branch, &tail);
+               } else {
+                       ref_map = get_remote_ref(remote_refs, "HEAD");
+                       if (!ref_map)
+                               die("Couldn't find remote ref HEAD");
+                       ref_map->merge = 1;
+                       tail = &ref_map->next;
+               }
+       }
+       if (tags == TAGS_DEFAULT && *autotags)
+               find_non_local_tags(transport, &ref_map, &tail);
+       ref_remove_duplicates(ref_map);
+
+       return ref_map;
+}
+
+#define STORE_REF_ERROR_OTHER 1
+#define STORE_REF_ERROR_DF_CONFLICT 2
+
+static int s_update_ref(const char *action,
+                       struct ref *ref,
+                       int check_old)
+{
+       char msg[1024];
+       char *rla = getenv("GIT_REFLOG_ACTION");
+       static struct ref_lock *lock;
+
+       if (dry_run)
+               return 0;
+       if (!rla)
+               rla = default_rla.buf;
+       snprintf(msg, sizeof(msg), "%s: %s", rla, action);
+       lock = lock_any_ref_for_update(ref->name,
+                                      check_old ? ref->old_sha1 : NULL, 0);
+       if (!lock)
+               return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
+                                         STORE_REF_ERROR_OTHER;
+       if (write_ref_sha1(lock, ref->new_sha1, msg) < 0)
+               return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
+                                         STORE_REF_ERROR_OTHER;
+       return 0;
+}
+
+#define REFCOL_WIDTH  10
+
+static int update_local_ref(struct ref *ref,
+                           const char *remote,
+                           char *display)
+{
+       struct commit *current = NULL, *updated;
+       enum object_type type;
+       struct branch *current_branch = branch_get(NULL);
+       const char *pretty_ref = prettify_refname(ref->name);
+
+       *display = 0;
+       type = sha1_object_info(ref->new_sha1, NULL);
+       if (type < 0)
+               die("object %s not found", sha1_to_hex(ref->new_sha1));
+
+       if (!hashcmp(ref->old_sha1, ref->new_sha1)) {
+               if (verbosity > 0)
+                       sprintf(display, "= %-*s %-*s -> %s", TRANSPORT_SUMMARY_WIDTH,
+                               "[up to date]", REFCOL_WIDTH, remote,
+                               pretty_ref);
+               return 0;
+       }
+
+       if (current_branch &&
+           !strcmp(ref->name, current_branch->name) &&
+           !(update_head_ok || is_bare_repository()) &&
+           !is_null_sha1(ref->old_sha1)) {
+               /*
+                * If this is the head, and it's not okay to update
+                * the head, and the old value of the head isn't empty...
+                */
+               sprintf(display, "! %-*s %-*s -> %s  (can't fetch in current branch)",
+                       TRANSPORT_SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
+                       pretty_ref);
+               return 1;
+       }
+
+       if (!is_null_sha1(ref->old_sha1) &&
+           !prefixcmp(ref->name, "refs/tags/")) {
+               int r;
+               r = s_update_ref("updating tag", ref, 0);
+               sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '-',
+                       TRANSPORT_SUMMARY_WIDTH, "[tag update]", REFCOL_WIDTH, remote,
+                       pretty_ref, r ? "  (unable to update local ref)" : "");
+               return r;
+       }
+
+       current = lookup_commit_reference_gently(ref->old_sha1, 1);
+       updated = lookup_commit_reference_gently(ref->new_sha1, 1);
+       if (!current || !updated) {
+               const char *msg;
+               const char *what;
+               int r;
+               if (!strncmp(ref->name, "refs/tags/", 10)) {
+                       msg = "storing tag";
+                       what = "[new tag]";
+               }
+               else {
+                       msg = "storing head";
+                       what = "[new branch]";
+               }
+
+               r = s_update_ref(msg, ref, 0);
+               sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '*',
+                       TRANSPORT_SUMMARY_WIDTH, what, REFCOL_WIDTH, remote, pretty_ref,
+                       r ? "  (unable to update local ref)" : "");
+               return r;
+       }
+
+       if (in_merge_bases(current, &updated, 1)) {
+               char quickref[83];
+               int r;
+               strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
+               strcat(quickref, "..");
+               strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
+               r = s_update_ref("fast-forward", ref, 1);
+               sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ',
+                       TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
+                       pretty_ref, r ? "  (unable to update local ref)" : "");
+               return r;
+       } else if (force || ref->force) {
+               char quickref[84];
+               int r;
+               strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
+               strcat(quickref, "...");
+               strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
+               r = s_update_ref("forced-update", ref, 1);
+               sprintf(display, "%c %-*s %-*s -> %s  (%s)", r ? '!' : '+',
+                       TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
+                       pretty_ref,
+                       r ? "unable to update local ref" : "forced update");
+               return r;
+       } else {
+               sprintf(display, "! %-*s %-*s -> %s  (non-fast-forward)",
+                       TRANSPORT_SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
+                       pretty_ref);
+               return 1;
+       }
+}
+
+static int store_updated_refs(const char *raw_url, const char *remote_name,
+               struct ref *ref_map)
+{
+       FILE *fp;
+       struct commit *commit;
+       int url_len, i, note_len, shown_url = 0, rc = 0;
+       char note[1024];
+       const char *what, *kind;
+       struct ref *rm;
+       char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
+
+       fp = fopen(filename, "a");
+       if (!fp)
+               return error("cannot open %s: %s\n", filename, strerror(errno));
+
+       if (raw_url)
+               url = transport_anonymize_url(raw_url);
+       else
+               url = xstrdup("foreign");
+       for (rm = ref_map; rm; rm = rm->next) {
+               struct ref *ref = NULL;
+
+               if (rm->peer_ref) {
+                       ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1);
+                       strcpy(ref->name, rm->peer_ref->name);
+                       hashcpy(ref->old_sha1, rm->peer_ref->old_sha1);
+                       hashcpy(ref->new_sha1, rm->old_sha1);
+                       ref->force = rm->peer_ref->force;
+               }
+
+               commit = lookup_commit_reference_gently(rm->old_sha1, 1);
+               if (!commit)
+                       rm->merge = 0;
+
+               if (!strcmp(rm->name, "HEAD")) {
+                       kind = "";
+                       what = "";
+               }
+               else if (!prefixcmp(rm->name, "refs/heads/")) {
+                       kind = "branch";
+                       what = rm->name + 11;
+               }
+               else if (!prefixcmp(rm->name, "refs/tags/")) {
+                       kind = "tag";
+                       what = rm->name + 10;
+               }
+               else if (!prefixcmp(rm->name, "refs/remotes/")) {
+                       kind = "remote branch";
+                       what = rm->name + 13;
+               }
+               else {
+                       kind = "";
+                       what = rm->name;
+               }
+
+               url_len = strlen(url);
+               for (i = url_len - 1; url[i] == '/' && 0 <= i; i--)
+                       ;
+               url_len = i + 1;
+               if (4 < i && !strncmp(".git", url + i - 3, 4))
+                       url_len = i - 3;
+
+               note_len = 0;
+               if (*what) {
+                       if (*kind)
+                               note_len += sprintf(note + note_len, "%s ",
+                                                   kind);
+                       note_len += sprintf(note + note_len, "'%s' of ", what);
+               }
+               note[note_len] = '\0';
+               fprintf(fp, "%s\t%s\t%s",
+                       sha1_to_hex(commit ? commit->object.sha1 :
+                                   rm->old_sha1),
+                       rm->merge ? "" : "not-for-merge",
+                       note);
+               for (i = 0; i < url_len; ++i)
+                       if ('\n' == url[i])
+                               fputs("\\n", fp);
+                       else
+                               fputc(url[i], fp);
+               fputc('\n', fp);
+
+               if (ref) {
+                       rc |= update_local_ref(ref, what, note);
+                       free(ref);
+               } else
+                       sprintf(note, "* %-*s %-*s -> FETCH_HEAD",
+                               TRANSPORT_SUMMARY_WIDTH, *kind ? kind : "branch",
+                                REFCOL_WIDTH, *what ? what : "HEAD");
+               if (*note) {
+                       if (verbosity >= 0 && !shown_url) {
+                               fprintf(stderr, "From %.*s\n",
+                                               url_len, url);
+                               shown_url = 1;
+                       }
+                       if (verbosity >= 0)
+                               fprintf(stderr, " %s\n", note);
+               }
+       }
+       free(url);
+       fclose(fp);
+       if (rc & STORE_REF_ERROR_DF_CONFLICT)
+               error("some local refs could not be updated; try running\n"
+                     " 'git remote prune %s' to remove any old, conflicting "
+                     "branches", remote_name);
+       return rc;
+}
+
+/*
+ * We would want to bypass the object transfer altogether if
+ * everything we are going to fetch already exists and is connected
+ * locally.
+ *
+ * The refs we are going to fetch are in ref_map.  If running
+ *
+ *  $ git rev-list --objects --stdin --not --all
+ *
+ * (feeding all the refs in ref_map on its standard input)
+ * does not error out, that means everything reachable from the
+ * refs we are going to fetch exists and is connected to some of
+ * our existing refs.
+ */
+static int quickfetch(struct ref *ref_map)
+{
+       struct child_process revlist;
+       struct ref *ref;
+       int err;
+       const char *argv[] = {"rev-list",
+               "--quiet", "--objects", "--stdin", "--not", "--all", NULL};
+
+       /*
+        * If we are deepening a shallow clone we already have these
+        * objects reachable.  Running rev-list here will return with
+        * a good (0) exit status and we'll bypass the fetch that we
+        * really need to perform.  Claiming failure now will ensure
+        * we perform the network exchange to deepen our history.
+        */
+       if (depth)
+               return -1;
+
+       if (!ref_map)
+               return 0;
+
+       memset(&revlist, 0, sizeof(revlist));
+       revlist.argv = argv;
+       revlist.git_cmd = 1;
+       revlist.no_stdout = 1;
+       revlist.no_stderr = 1;
+       revlist.in = -1;
+
+       err = start_command(&revlist);
+       if (err) {
+               error("could not run rev-list");
+               return err;
+       }
+
+       /*
+        * If rev-list --stdin encounters an unknown commit, it terminates,
+        * which will cause SIGPIPE in the write loop below.
+        */
+       sigchain_push(SIGPIPE, SIG_IGN);
+
+       for (ref = ref_map; ref; ref = ref->next) {
+               if (write_in_full(revlist.in, sha1_to_hex(ref->old_sha1), 40) < 0 ||
+                   write_str_in_full(revlist.in, "\n") < 0) {
+                       if (errno != EPIPE && errno != EINVAL)
+                               error("failed write to rev-list: %s", strerror(errno));
+                       err = -1;
+                       break;
+               }
+       }
+
+       if (close(revlist.in)) {
+               error("failed to close rev-list's stdin: %s", strerror(errno));
+               err = -1;
+       }
+
+       sigchain_pop(SIGPIPE);
+
+       return finish_command(&revlist) || err;
+}
+
+static int fetch_refs(struct transport *transport, struct ref *ref_map)
+{
+       int ret = quickfetch(ref_map);
+       if (ret)
+               ret = transport_fetch_refs(transport, ref_map);
+       if (!ret)
+               ret |= store_updated_refs(transport->url,
+                               transport->remote->name,
+                               ref_map);
+       transport_unlock_pack(transport);
+       return ret;
+}
+
+static int prune_refs(struct transport *transport, struct ref *ref_map)
+{
+       int result = 0;
+       struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map);
+       const char *dangling_msg = dry_run
+               ? "   (%s will become dangling)\n"
+               : "   (%s has become dangling)\n";
+
+       for (ref = stale_refs; ref; ref = ref->next) {
+               if (!dry_run)
+                       result |= delete_ref(ref->name, NULL, 0);
+               if (verbosity >= 0) {
+                       fprintf(stderr, " x %-*s %-*s -> %s\n",
+                               TRANSPORT_SUMMARY_WIDTH, "[deleted]",
+                               REFCOL_WIDTH, "(none)", prettify_refname(ref->name));
+                       warn_dangling_symref(stderr, dangling_msg, ref->name);
+               }
+       }
+       free_refs(stale_refs);
+       return result;
+}
+
+static int add_existing(const char *refname, const unsigned char *sha1,
+                       int flag, void *cbdata)
+{
+       struct string_list *list = (struct string_list *)cbdata;
+       struct string_list_item *item = string_list_insert(list, refname);
+       item->util = (void *)sha1;
+       return 0;
+}
+
+static int will_fetch(struct ref **head, const unsigned char *sha1)
+{
+       struct ref *rm = *head;
+       while (rm) {
+               if (!hashcmp(rm->old_sha1, sha1))
+                       return 1;
+               rm = rm->next;
+       }
+       return 0;
+}
+
+static void find_non_local_tags(struct transport *transport,
+                       struct ref **head,
+                       struct ref ***tail)
+{
+       struct string_list existing_refs = STRING_LIST_INIT_NODUP;
+       struct string_list remote_refs = STRING_LIST_INIT_NODUP;
+       const struct ref *ref;
+       struct string_list_item *item = NULL;
+
+       for_each_ref(add_existing, &existing_refs);
+       for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
+               if (prefixcmp(ref->name, "refs/tags"))
+                       continue;
+
+               /*
+                * The peeled ref always follows the matching base
+                * ref, so if we see a peeled ref that we don't want
+                * to fetch then we can mark the ref entry in the list
+                * as one to ignore by setting util to NULL.
+                */
+               if (!suffixcmp(ref->name, "^{}")) {
+                       if (item && !has_sha1_file(ref->old_sha1) &&
+                           !will_fetch(head, ref->old_sha1) &&
+                           !has_sha1_file(item->util) &&
+                           !will_fetch(head, item->util))
+                               item->util = NULL;
+                       item = NULL;
+                       continue;
+               }
+
+               /*
+                * If item is non-NULL here, then we previously saw a
+                * ref not followed by a peeled reference, so we need
+                * to check if it is a lightweight tag that we want to
+                * fetch.
+                */
+               if (item && !has_sha1_file(item->util) &&
+                   !will_fetch(head, item->util))
+                       item->util = NULL;
+
+               item = NULL;
+
+               /* skip duplicates and refs that we already have */
+               if (string_list_has_string(&remote_refs, ref->name) ||
+                   string_list_has_string(&existing_refs, ref->name))
+                       continue;
+
+               item = string_list_insert(&remote_refs, ref->name);
+               item->util = (void *)ref->old_sha1;
+       }
+       string_list_clear(&existing_refs, 0);
+
+       /*
+        * We may have a final lightweight tag that needs to be
+        * checked to see if it needs fetching.
+        */
+       if (item && !has_sha1_file(item->util) &&
+           !will_fetch(head, item->util))
+               item->util = NULL;
+
+       /*
+        * For all the tags in the remote_refs string list,
+        * add them to the list of refs to be fetched
+        */
+       for_each_string_list_item(item, &remote_refs) {
+               /* Unless we have already decided to ignore this item... */
+               if (item->util)
+               {
+                       struct ref *rm = alloc_ref(item->string);
+                       rm->peer_ref = alloc_ref(item->string);
+                       hashcpy(rm->old_sha1, item->util);
+                       **tail = rm;
+                       *tail = &rm->next;
+               }
+       }
+
+       string_list_clear(&remote_refs, 0);
+}
+
+static void check_not_current_branch(struct ref *ref_map)
+{
+       struct branch *current_branch = branch_get(NULL);
+
+       if (is_bare_repository() || !current_branch)
+               return;
+
+       for (; ref_map; ref_map = ref_map->next)
+               if (ref_map->peer_ref && !strcmp(current_branch->refname,
+                                       ref_map->peer_ref->name))
+                       die("Refusing to fetch into current branch %s "
+                           "of non-bare repository", current_branch->refname);
+}
+
+static int truncate_fetch_head(void)
+{
+       char *filename = git_path("FETCH_HEAD");
+       FILE *fp = fopen(filename, "w");
+
+       if (!fp)
+               return error("cannot open %s: %s\n", filename, strerror(errno));
+       fclose(fp);
+       return 0;
+}
+
+static int do_fetch(struct transport *transport,
+                   struct refspec *refs, int ref_count)
+{
+       struct string_list existing_refs = STRING_LIST_INIT_NODUP;
+       struct string_list_item *peer_item = NULL;
+       struct ref *ref_map;
+       struct ref *rm;
+       int autotags = (transport->remote->fetch_tags == 1);
+
+       for_each_ref(add_existing, &existing_refs);
+
+       if (tags == TAGS_DEFAULT) {
+               if (transport->remote->fetch_tags == 2)
+                       tags = TAGS_SET;
+               if (transport->remote->fetch_tags == -1)
+                       tags = TAGS_UNSET;
+       }
+
+       if (!transport->get_refs_list || !transport->fetch)
+               die("Don't know how to fetch from %s", transport->url);
+
+       /* if not appending, truncate FETCH_HEAD */
+       if (!append && !dry_run) {
+               int errcode = truncate_fetch_head();
+               if (errcode)
+                       return errcode;
+       }
+
+       ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags);
+       if (!update_head_ok)
+               check_not_current_branch(ref_map);
+
+       for (rm = ref_map; rm; rm = rm->next) {
+               if (rm->peer_ref) {
+                       peer_item = string_list_lookup(&existing_refs,
+                                                      rm->peer_ref->name);
+                       if (peer_item)
+                               hashcpy(rm->peer_ref->old_sha1,
+                                       peer_item->util);
+               }
+       }
+
+       if (tags == TAGS_DEFAULT && autotags)
+               transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
+       if (fetch_refs(transport, ref_map)) {
+               free_refs(ref_map);
+               return 1;
+       }
+       if (prune)
+               prune_refs(transport, ref_map);
+       free_refs(ref_map);
+
+       /* if neither --no-tags nor --tags was specified, do automated tag
+        * following ... */
+       if (tags == TAGS_DEFAULT && autotags) {
+               struct ref **tail = &ref_map;
+               ref_map = NULL;
+               find_non_local_tags(transport, &ref_map, &tail);
+               if (ref_map) {
+                       transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
+                       transport_set_option(transport, TRANS_OPT_DEPTH, "0");
+                       fetch_refs(transport, ref_map);
+               }
+               free_refs(ref_map);
+       }
+
+       return 0;
+}
+
+static void set_option(const char *name, const char *value)
+{
+       int r = transport_set_option(transport, name, value);
+       if (r < 0)
+               die("Option \"%s\" value \"%s\" is not valid for %s",
+                       name, value, transport->url);
+       if (r > 0)
+               warning("Option \"%s\" is ignored for %s\n",
+                       name, transport->url);
+}
+
+static int get_one_remote_for_fetch(struct remote *remote, void *priv)
+{
+       struct string_list *list = priv;
+       if (!remote->skip_default_update)
+               string_list_append(list, remote->name);
+       return 0;
+}
+
+struct remote_group_data {
+       const char *name;
+       struct string_list *list;
+};
+
+static int get_remote_group(const char *key, const char *value, void *priv)
+{
+       struct remote_group_data *g = priv;
+
+       if (!prefixcmp(key, "remotes.") &&
+                       !strcmp(key + 8, g->name)) {
+               /* split list by white space */
+               int space = strcspn(value, " \t\n");
+               while (*value) {
+                       if (space > 1) {
+                               string_list_append(g->list,
+                                                  xstrndup(value, space));
+                       }
+                       value += space + (value[space] != '\0');
+                       space = strcspn(value, " \t\n");
+               }
+       }
+
+       return 0;
+}
+
+static int add_remote_or_group(const char *name, struct string_list *list)
+{
+       int prev_nr = list->nr;
+       struct remote_group_data g;
+       g.name = name; g.list = list;
+
+       git_config(get_remote_group, &g);
+       if (list->nr == prev_nr) {
+               struct remote *remote;
+               if (!remote_is_configured(name))
+                       return 0;
+               remote = remote_get(name);
+               string_list_append(list, remote->name);
+       }
+       return 1;
+}
+
+static int fetch_multiple(struct string_list *list)
+{
+       int i, result = 0;
+       const char *argv[11] = { "fetch", "--append" };
+       int argc = 2;
+
+       if (dry_run)
+               argv[argc++] = "--dry-run";
+       if (prune)
+               argv[argc++] = "--prune";
+       if (update_head_ok)
+               argv[argc++] = "--update-head-ok";
+       if (force)
+               argv[argc++] = "--force";
+       if (keep)
+               argv[argc++] = "--keep";
+       if (verbosity >= 2)
+               argv[argc++] = "-v";
+       if (verbosity >= 1)
+               argv[argc++] = "-v";
+       else if (verbosity < 0)
+               argv[argc++] = "-q";
+
+       if (!append && !dry_run) {
+               int errcode = truncate_fetch_head();
+               if (errcode)
+                       return errcode;
+       }
+
+       for (i = 0; i < list->nr; i++) {
+               const char *name = list->items[i].string;
+               argv[argc] = name;
+               argv[argc + 1] = NULL;
+               if (verbosity >= 0)
+                       printf("Fetching %s\n", name);
+               if (run_command_v_opt(argv, RUN_GIT_CMD)) {
+                       error("Could not fetch %s", name);
+                       result = 1;
+               }
+       }
+
+       return result;
+}
+
+static int fetch_one(struct remote *remote, int argc, const char **argv)
+{
+       int i;
+       static const char **refs = NULL;
+       int ref_nr = 0;
+       int exit_code;
+
+       if (!remote)
+               die("No remote repository specified.  Please, specify either a URL or a\n"
+                   "remote name from which new revisions should be fetched.");
+
+       transport = transport_get(remote, NULL);
+       transport_set_verbosity(transport, verbosity, progress);
+       if (upload_pack)
+               set_option(TRANS_OPT_UPLOADPACK, upload_pack);
+       if (keep)
+               set_option(TRANS_OPT_KEEP, "yes");
+       if (depth)
+               set_option(TRANS_OPT_DEPTH, depth);
+
+       if (argc > 0) {
+               int j = 0;
+               refs = xcalloc(argc + 1, sizeof(const char *));
+               for (i = 0; i < argc; i++) {
+                       if (!strcmp(argv[i], "tag")) {
+                               char *ref;
+                               i++;
+                               if (i >= argc)
+                                       die("You need to specify a tag name.");
+                               ref = xmalloc(strlen(argv[i]) * 2 + 22);
+                               strcpy(ref, "refs/tags/");
+                               strcat(ref, argv[i]);
+                               strcat(ref, ":refs/tags/");
+                               strcat(ref, argv[i]);
+                               refs[j++] = ref;
+                       } else
+                               refs[j++] = argv[i];
+               }
+               refs[j] = NULL;
+               ref_nr = j;
+       }
+
+       sigchain_push_common(unlock_pack_on_signal);
+       atexit(unlock_pack);
+       exit_code = do_fetch(transport,
+                       parse_fetch_refspec(ref_nr, refs), ref_nr);
+       transport_disconnect(transport);
+       transport = NULL;
+       return exit_code;
+}
+
+int cmd_fetch(int argc, const char **argv, const char *prefix)
+{
+       int i;
+       struct string_list list = STRING_LIST_INIT_NODUP;
+       struct remote *remote;
+       int result = 0;
+
+       /* Record the command line for the reflog */
+       strbuf_addstr(&default_rla, "fetch");
+       for (i = 1; i < argc; i++)
+               strbuf_addf(&default_rla, " %s", argv[i]);
+
+       argc = parse_options(argc, argv, prefix,
+                            builtin_fetch_options, builtin_fetch_usage, 0);
+
+       if (all) {
+               if (argc == 1)
+                       die("fetch --all does not take a repository argument");
+               else if (argc > 1)
+                       die("fetch --all does not make sense with refspecs");
+               (void) for_each_remote(get_one_remote_for_fetch, &list);
+               result = fetch_multiple(&list);
+       } else if (argc == 0) {
+               /* No arguments -- use default remote */
+               remote = remote_get(NULL);
+               result = fetch_one(remote, argc, argv);
+       } else if (multiple) {
+               /* All arguments are assumed to be remotes or groups */
+               for (i = 0; i < argc; i++)
+                       if (!add_remote_or_group(argv[i], &list))
+                               die("No such remote or remote group: %s", argv[i]);
+               result = fetch_multiple(&list);
+       } else {
+               /* Single remote or group */
+               (void) add_remote_or_group(argv[0], &list);
+               if (list.nr > 1) {
+                       /* More than one remote */
+                       if (argc > 1)
+                               die("Fetching a group and specifying refspecs does not make sense");
+                       result = fetch_multiple(&list);
+               } else {
+                       /* Zero or one remotes */
+                       remote = remote_get(argv[0]);
+                       result = fetch_one(remote, argc-1, argv+1);
+               }
+       }
+
+       /* All names were strdup()ed or strndup()ed */
+       list.strdup_strings = 1;
+       string_list_clear(&list, 0);
+
+       return result;
+}
diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c
new file mode 100644 (file)
index 0000000..e7e12ee
--- /dev/null
@@ -0,0 +1,368 @@
+#include "builtin.h"
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "tag.h"
+#include "string-list.h"
+
+static const char * const fmt_merge_msg_usage[] = {
+       "git fmt-merge-msg [-m <message>] [--log|--no-log] [--file <file>]",
+       NULL
+};
+
+static int merge_summary;
+
+static int fmt_merge_msg_config(const char *key, const char *value, void *cb)
+{
+       static int found_merge_log = 0;
+       if (!strcmp("merge.log", key)) {
+               found_merge_log = 1;
+               merge_summary = git_config_bool(key, value);
+       }
+       if (!found_merge_log && !strcmp("merge.summary", key))
+               merge_summary = git_config_bool(key, value);
+       return 0;
+}
+
+struct src_data {
+       struct string_list branch, tag, r_branch, generic;
+       int head_status;
+};
+
+void init_src_data(struct src_data *data)
+{
+       data->branch.strdup_strings = 1;
+       data->tag.strdup_strings = 1;
+       data->r_branch.strdup_strings = 1;
+       data->generic.strdup_strings = 1;
+}
+
+static struct string_list srcs = STRING_LIST_INIT_DUP;
+static struct string_list origins = STRING_LIST_INIT_DUP;
+
+static int handle_line(char *line)
+{
+       int i, len = strlen(line);
+       unsigned char *sha1;
+       char *src, *origin;
+       struct src_data *src_data;
+       struct string_list_item *item;
+       int pulling_head = 0;
+
+       if (len < 43 || line[40] != '\t')
+               return 1;
+
+       if (!prefixcmp(line + 41, "not-for-merge"))
+               return 0;
+
+       if (line[41] != '\t')
+               return 2;
+
+       line[40] = 0;
+       sha1 = xmalloc(20);
+       i = get_sha1(line, sha1);
+       line[40] = '\t';
+       if (i)
+               return 3;
+
+       if (line[len - 1] == '\n')
+               line[len - 1] = 0;
+       line += 42;
+
+       src = strstr(line, " of ");
+       if (src) {
+               *src = 0;
+               src += 4;
+               pulling_head = 0;
+       } else {
+               src = line;
+               pulling_head = 1;
+       }
+
+       item = unsorted_string_list_lookup(&srcs, src);
+       if (!item) {
+               item = string_list_append(&srcs, src);
+               item->util = xcalloc(1, sizeof(struct src_data));
+               init_src_data(item->util);
+       }
+       src_data = item->util;
+
+       if (pulling_head) {
+               origin = src;
+               src_data->head_status |= 1;
+       } else if (!prefixcmp(line, "branch ")) {
+               origin = line + 7;
+               string_list_append(&src_data->branch, origin);
+               src_data->head_status |= 2;
+       } else if (!prefixcmp(line, "tag ")) {
+               origin = line;
+               string_list_append(&src_data->tag, origin + 4);
+               src_data->head_status |= 2;
+       } else if (!prefixcmp(line, "remote branch ")) {
+               origin = line + 14;
+               string_list_append(&src_data->r_branch, origin);
+               src_data->head_status |= 2;
+       } else {
+               origin = src;
+               string_list_append(&src_data->generic, line);
+               src_data->head_status |= 2;
+       }
+
+       if (!strcmp(".", src) || !strcmp(src, origin)) {
+               int len = strlen(origin);
+               if (origin[0] == '\'' && origin[len - 1] == '\'')
+                       origin = xmemdupz(origin + 1, len - 2);
+       } else {
+               char *new_origin = xmalloc(strlen(origin) + strlen(src) + 5);
+               sprintf(new_origin, "%s of %s", origin, src);
+               origin = new_origin;
+       }
+       string_list_append(&origins, origin)->util = sha1;
+       return 0;
+}
+
+static void print_joined(const char *singular, const char *plural,
+               struct string_list *list, struct strbuf *out)
+{
+       if (list->nr == 0)
+               return;
+       if (list->nr == 1) {
+               strbuf_addf(out, "%s%s", singular, list->items[0].string);
+       } else {
+               int i;
+               strbuf_addstr(out, plural);
+               for (i = 0; i < list->nr - 1; i++)
+                       strbuf_addf(out, "%s%s", i > 0 ? ", " : "",
+                                   list->items[i].string);
+               strbuf_addf(out, " and %s", list->items[list->nr - 1].string);
+       }
+}
+
+static void shortlog(const char *name, unsigned char *sha1,
+               struct commit *head, struct rev_info *rev, int limit,
+               struct strbuf *out)
+{
+       int i, count = 0;
+       struct commit *commit;
+       struct object *branch;
+       struct string_list subjects = STRING_LIST_INIT_DUP;
+       int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
+       struct strbuf sb = STRBUF_INIT;
+
+       branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40);
+       if (!branch || branch->type != OBJ_COMMIT)
+               return;
+
+       setup_revisions(0, NULL, rev, NULL);
+       rev->ignore_merges = 1;
+       add_pending_object(rev, branch, name);
+       add_pending_object(rev, &head->object, "^HEAD");
+       head->object.flags |= UNINTERESTING;
+       if (prepare_revision_walk(rev))
+               die("revision walk setup failed");
+       while ((commit = get_revision(rev)) != NULL) {
+               struct pretty_print_context ctx = {0};
+
+               /* ignore merges */
+               if (commit->parents && commit->parents->next)
+                       continue;
+
+               count++;
+               if (subjects.nr > limit)
+                       continue;
+
+               format_commit_message(commit, "%s", &sb, &ctx);
+               strbuf_ltrim(&sb);
+
+               if (!sb.len)
+                       string_list_append(&subjects,
+                                          sha1_to_hex(commit->object.sha1));
+               else
+                       string_list_append(&subjects, strbuf_detach(&sb, NULL));
+       }
+
+       if (count > limit)
+               strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
+       else
+               strbuf_addf(out, "\n* %s:\n", name);
+
+       for (i = 0; i < subjects.nr; i++)
+               if (i >= limit)
+                       strbuf_addf(out, "  ...\n");
+               else
+                       strbuf_addf(out, "  %s\n", subjects.items[i].string);
+
+       clear_commit_marks((struct commit *)branch, flags);
+       clear_commit_marks(head, flags);
+       free_commit_list(rev->commits);
+       rev->commits = NULL;
+       rev->pending.nr = 0;
+
+       string_list_clear(&subjects, 0);
+}
+
+static void do_fmt_merge_msg_title(struct strbuf *out,
+       const char *current_branch) {
+       int i = 0;
+       char *sep = "";
+
+       strbuf_addstr(out, "Merge ");
+       for (i = 0; i < srcs.nr; i++) {
+               struct src_data *src_data = srcs.items[i].util;
+               const char *subsep = "";
+
+               strbuf_addstr(out, sep);
+               sep = "; ";
+
+               if (src_data->head_status == 1) {
+                       strbuf_addstr(out, srcs.items[i].string);
+                       continue;
+               }
+               if (src_data->head_status == 3) {
+                       subsep = ", ";
+                       strbuf_addstr(out, "HEAD");
+               }
+               if (src_data->branch.nr) {
+                       strbuf_addstr(out, subsep);
+                       subsep = ", ";
+                       print_joined("branch ", "branches ", &src_data->branch,
+                                       out);
+               }
+               if (src_data->r_branch.nr) {
+                       strbuf_addstr(out, subsep);
+                       subsep = ", ";
+                       print_joined("remote branch ", "remote branches ",
+                                       &src_data->r_branch, out);
+               }
+               if (src_data->tag.nr) {
+                       strbuf_addstr(out, subsep);
+                       subsep = ", ";
+                       print_joined("tag ", "tags ", &src_data->tag, out);
+               }
+               if (src_data->generic.nr) {
+                       strbuf_addstr(out, subsep);
+                       print_joined("commit ", "commits ", &src_data->generic,
+                                       out);
+               }
+               if (strcmp(".", srcs.items[i].string))
+                       strbuf_addf(out, " of %s", srcs.items[i].string);
+       }
+
+       if (!strcmp("master", current_branch))
+               strbuf_addch(out, '\n');
+       else
+               strbuf_addf(out, " into %s\n", current_branch);
+}
+
+static int do_fmt_merge_msg(int merge_title, int merge_summary,
+       struct strbuf *in, struct strbuf *out) {
+       int limit = 20, i = 0, pos = 0;
+       unsigned char head_sha1[20];
+       const char *current_branch;
+
+       /* get current branch */
+       current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);
+       if (!current_branch)
+               die("No current branch");
+       if (!prefixcmp(current_branch, "refs/heads/"))
+               current_branch += 11;
+
+       /* get a line */
+       while (pos < in->len) {
+               int len;
+               char *newline, *p = in->buf + pos;
+
+               newline = strchr(p, '\n');
+               len = newline ? newline - p : strlen(p);
+               pos += len + !!newline;
+               i++;
+               p[len] = 0;
+               if (handle_line(p))
+                       die ("Error in line %d: %.*s", i, len, p);
+       }
+
+       if (!srcs.nr)
+               return 0;
+
+       if (merge_title)
+               do_fmt_merge_msg_title(out, current_branch);
+
+       if (merge_summary) {
+               struct commit *head;
+               struct rev_info rev;
+
+               head = lookup_commit(head_sha1);
+               init_revisions(&rev, NULL);
+               rev.commit_format = CMIT_FMT_ONELINE;
+               rev.ignore_merges = 1;
+               rev.limited = 1;
+
+               if (suffixcmp(out->buf, "\n"))
+                       strbuf_addch(out, '\n');
+
+               for (i = 0; i < origins.nr; i++)
+                       shortlog(origins.items[i].string, origins.items[i].util,
+                                       head, &rev, limit, out);
+       }
+       return 0;
+}
+
+int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) {
+       return do_fmt_merge_msg(1, merge_summary, in, out);
+}
+
+int fmt_merge_msg_shortlog(struct strbuf *in, struct strbuf *out) {
+       return do_fmt_merge_msg(0, 1, in, out);
+}
+
+int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
+{
+       const char *inpath = NULL;
+       const char *message = NULL;
+       struct option options[] = {
+               OPT_BOOLEAN(0, "log",     &merge_summary, "populate log with the shortlog"),
+               { OPTION_BOOLEAN, 0, "summary", &merge_summary, NULL,
+                 "alias for --log (deprecated)",
+                 PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
+               OPT_STRING('m', "message", &message, "text",
+                       "use <text> as start of message"),
+               OPT_FILENAME('F', "file", &inpath, "file to read from"),
+               OPT_END()
+       };
+
+       FILE *in = stdin;
+       struct strbuf input = STRBUF_INIT, output = STRBUF_INIT;
+       int ret;
+
+       git_config(fmt_merge_msg_config, NULL);
+       argc = parse_options(argc, argv, prefix, options, fmt_merge_msg_usage,
+                            0);
+       if (argc > 0)
+               usage_with_options(fmt_merge_msg_usage, options);
+       if (message && !merge_summary) {
+               char nl = '\n';
+               write_in_full(STDOUT_FILENO, message, strlen(message));
+               write_in_full(STDOUT_FILENO, &nl, 1);
+               return 0;
+       }
+
+       if (inpath && strcmp(inpath, "-")) {
+               in = fopen(inpath, "r");
+               if (!in)
+                       die_errno("cannot open '%s'", inpath);
+       }
+
+       if (strbuf_read(&input, fileno(in), 0) < 0)
+               die_errno("could not read input file");
+       if (message) {
+               strbuf_addstr(&output, message);
+               ret = fmt_merge_msg_shortlog(&input, &output);
+       } else {
+               ret = fmt_merge_msg(merge_summary, &input, &output);
+       }
+       if (ret)
+               return ret;
+       write_in_full(STDOUT_FILENO, output.buf, output.len);
+       return 0;
+}
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
new file mode 100644 (file)
index 0000000..89e75c6
--- /dev/null
@@ -0,0 +1,1000 @@
+#include "builtin.h"
+#include "cache.h"
+#include "refs.h"
+#include "object.h"
+#include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "quote.h"
+#include "parse-options.h"
+#include "remote.h"
+
+/* Quoting styles */
+#define QUOTE_NONE 0
+#define QUOTE_SHELL 1
+#define QUOTE_PERL 2
+#define QUOTE_PYTHON 4
+#define QUOTE_TCL 8
+
+typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
+
+struct atom_value {
+       const char *s;
+       unsigned long ul; /* used for sorting when not FIELD_STR */
+};
+
+struct ref_sort {
+       struct ref_sort *next;
+       int atom; /* index into used_atom array */
+       unsigned reverse : 1;
+};
+
+struct refinfo {
+       char *refname;
+       unsigned char objectname[20];
+       int flag;
+       const char *symref;
+       struct atom_value *value;
+};
+
+static struct {
+       const char *name;
+       cmp_type cmp_type;
+} valid_atom[] = {
+       { "refname" },
+       { "objecttype" },
+       { "objectsize", FIELD_ULONG },
+       { "objectname" },
+       { "tree" },
+       { "parent" },
+       { "numparent", FIELD_ULONG },
+       { "object" },
+       { "type" },
+       { "tag" },
+       { "author" },
+       { "authorname" },
+       { "authoremail" },
+       { "authordate", FIELD_TIME },
+       { "committer" },
+       { "committername" },
+       { "committeremail" },
+       { "committerdate", FIELD_TIME },
+       { "tagger" },
+       { "taggername" },
+       { "taggeremail" },
+       { "taggerdate", FIELD_TIME },
+       { "creator" },
+       { "creatordate", FIELD_TIME },
+       { "subject" },
+       { "body" },
+       { "contents" },
+       { "upstream" },
+       { "symref" },
+       { "flag" },
+};
+
+/*
+ * An atom is a valid field atom listed above, possibly prefixed with
+ * a "*" to denote deref_tag().
+ *
+ * We parse given format string and sort specifiers, and make a list
+ * of properties that we need to extract out of objects.  refinfo
+ * structure will hold an array of values extracted that can be
+ * indexed with the "atom number", which is an index into this
+ * array.
+ */
+static const char **used_atom;
+static cmp_type *used_atom_type;
+static int used_atom_cnt, sort_atom_limit, need_tagged, need_symref;
+
+/*
+ * Used to parse format string and sort specifiers
+ */
+static int parse_atom(const char *atom, const char *ep)
+{
+       const char *sp;
+       int i, at;
+
+       sp = atom;
+       if (*sp == '*' && sp < ep)
+               sp++; /* deref */
+       if (ep <= sp)
+               die("malformed field name: %.*s", (int)(ep-atom), atom);
+
+       /* Do we have the atom already used elsewhere? */
+       for (i = 0; i < used_atom_cnt; i++) {
+               int len = strlen(used_atom[i]);
+               if (len == ep - atom && !memcmp(used_atom[i], atom, len))
+                       return i;
+       }
+
+       /* Is the atom a valid one? */
+       for (i = 0; i < ARRAY_SIZE(valid_atom); i++) {
+               int len = strlen(valid_atom[i].name);
+               /*
+                * If the atom name has a colon, strip it and everything after
+                * it off - it specifies the format for this entry, and
+                * shouldn't be used for checking against the valid_atom
+                * table.
+                */
+               const char *formatp = strchr(sp, ':');
+               if (!formatp || ep < formatp)
+                       formatp = ep;
+               if (len == formatp - sp && !memcmp(valid_atom[i].name, sp, len))
+                       break;
+       }
+
+       if (ARRAY_SIZE(valid_atom) <= i)
+               die("unknown field name: %.*s", (int)(ep-atom), atom);
+
+       /* Add it in, including the deref prefix */
+       at = used_atom_cnt;
+       used_atom_cnt++;
+       used_atom = xrealloc(used_atom,
+                            (sizeof *used_atom) * used_atom_cnt);
+       used_atom_type = xrealloc(used_atom_type,
+                                 (sizeof(*used_atom_type) * used_atom_cnt));
+       used_atom[at] = xmemdupz(atom, ep - atom);
+       used_atom_type[at] = valid_atom[i].cmp_type;
+       if (*atom == '*')
+               need_tagged = 1;
+       if (!strcmp(used_atom[at], "symref"))
+               need_symref = 1;
+       return at;
+}
+
+/*
+ * In a format string, find the next occurrence of %(atom).
+ */
+static const char *find_next(const char *cp)
+{
+       while (*cp) {
+               if (*cp == '%') {
+                       /*
+                        * %( is the start of an atom;
+                        * %% is a quoted per-cent.
+                        */
+                       if (cp[1] == '(')
+                               return cp;
+                       else if (cp[1] == '%')
+                               cp++; /* skip over two % */
+                       /* otherwise this is a singleton, literal % */
+               }
+               cp++;
+       }
+       return NULL;
+}
+
+/*
+ * Make sure the format string is well formed, and parse out
+ * the used atoms.
+ */
+static int verify_format(const char *format)
+{
+       const char *cp, *sp;
+       for (cp = format; *cp && (sp = find_next(cp)); ) {
+               const char *ep = strchr(sp, ')');
+               if (!ep)
+                       return error("malformed format string %s", sp);
+               /* sp points at "%(" and ep points at the closing ")" */
+               parse_atom(sp + 2, ep);
+               cp = ep + 1;
+       }
+       return 0;
+}
+
+/*
+ * Given an object name, read the object data and size, and return a
+ * "struct object".  If the object data we are returning is also borrowed
+ * by the "struct object" representation, set *eaten as well---it is a
+ * signal from parse_object_buffer to us not to free the buffer.
+ */
+static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned long *sz, int *eaten)
+{
+       enum object_type type;
+       void *buf = read_sha1_file(sha1, &type, sz);
+
+       if (buf)
+               *obj = parse_object_buffer(sha1, type, *sz, buf, eaten);
+       else
+               *obj = NULL;
+       return buf;
+}
+
+/* See grab_values */
+static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       int i;
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+               if (!strcmp(name, "objecttype"))
+                       v->s = typename(obj->type);
+               else if (!strcmp(name, "objectsize")) {
+                       char *s = xmalloc(40);
+                       sprintf(s, "%lu", sz);
+                       v->ul = sz;
+                       v->s = s;
+               }
+               else if (!strcmp(name, "objectname")) {
+                       char *s = xmalloc(41);
+                       strcpy(s, sha1_to_hex(obj->sha1));
+                       v->s = s;
+               }
+               else if (!strcmp(name, "objectname:short")) {
+                       v->s = xstrdup(find_unique_abbrev(obj->sha1,
+                                                         DEFAULT_ABBREV));
+               }
+       }
+}
+
+/* See grab_values */
+static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       int i;
+       struct tag *tag = (struct tag *) obj;
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+               if (!strcmp(name, "tag"))
+                       v->s = tag->tag;
+               else if (!strcmp(name, "type") && tag->tagged)
+                       v->s = typename(tag->tagged->type);
+               else if (!strcmp(name, "object") && tag->tagged) {
+                       char *s = xmalloc(41);
+                       strcpy(s, sha1_to_hex(tag->tagged->sha1));
+                       v->s = s;
+               }
+       }
+}
+
+static int num_parents(struct commit *commit)
+{
+       struct commit_list *parents;
+       int i;
+
+       for (i = 0, parents = commit->parents;
+            parents;
+            parents = parents->next)
+               i++;
+       return i;
+}
+
+/* See grab_values */
+static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       int i;
+       struct commit *commit = (struct commit *) obj;
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+               if (!strcmp(name, "tree")) {
+                       char *s = xmalloc(41);
+                       strcpy(s, sha1_to_hex(commit->tree->object.sha1));
+                       v->s = s;
+               }
+               if (!strcmp(name, "numparent")) {
+                       char *s = xmalloc(40);
+                       v->ul = num_parents(commit);
+                       sprintf(s, "%lu", v->ul);
+                       v->s = s;
+               }
+               else if (!strcmp(name, "parent")) {
+                       int num = num_parents(commit);
+                       int i;
+                       struct commit_list *parents;
+                       char *s = xmalloc(41 * num + 1);
+                       v->s = s;
+                       for (i = 0, parents = commit->parents;
+                            parents;
+                            parents = parents->next, i = i + 41) {
+                               struct commit *parent = parents->item;
+                               strcpy(s+i, sha1_to_hex(parent->object.sha1));
+                               if (parents->next)
+                                       s[i+40] = ' ';
+                       }
+                       if (!i)
+                               *s = '\0';
+               }
+       }
+}
+
+static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz)
+{
+       const char *eol;
+       while (*buf) {
+               if (!strncmp(buf, who, wholen) &&
+                   buf[wholen] == ' ')
+                       return buf + wholen + 1;
+               eol = strchr(buf, '\n');
+               if (!eol)
+                       return "";
+               eol++;
+               if (*eol == '\n')
+                       return ""; /* end of header */
+               buf = eol;
+       }
+       return "";
+}
+
+static const char *copy_line(const char *buf)
+{
+       const char *eol = strchrnul(buf, '\n');
+       return xmemdupz(buf, eol - buf);
+}
+
+static const char *copy_name(const char *buf)
+{
+       const char *cp;
+       for (cp = buf; *cp && *cp != '\n'; cp++) {
+               if (!strncmp(cp, " <", 2))
+                       return xmemdupz(buf, cp - buf);
+       }
+       return "";
+}
+
+static const char *copy_email(const char *buf)
+{
+       const char *email = strchr(buf, '<');
+       const char *eoemail;
+       if (!email)
+               return "";
+       eoemail = strchr(email, '>');
+       if (!eoemail)
+               return "";
+       return xmemdupz(email, eoemail + 1 - email);
+}
+
+static void grab_date(const char *buf, struct atom_value *v, const char *atomname)
+{
+       const char *eoemail = strstr(buf, "> ");
+       char *zone;
+       unsigned long timestamp;
+       long tz;
+       enum date_mode date_mode = DATE_NORMAL;
+       const char *formatp;
+
+       /*
+        * We got here because atomname ends in "date" or "date<something>";
+        * it's not possible that <something> is not ":<format>" because
+        * parse_atom() wouldn't have allowed it, so we can assume that no
+        * ":" means no format is specified, and use the default.
+        */
+       formatp = strchr(atomname, ':');
+       if (formatp != NULL) {
+               formatp++;
+               date_mode = parse_date_format(formatp);
+       }
+
+       if (!eoemail)
+               goto bad;
+       timestamp = strtoul(eoemail + 2, &zone, 10);
+       if (timestamp == ULONG_MAX)
+               goto bad;
+       tz = strtol(zone, NULL, 10);
+       if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE)
+               goto bad;
+       v->s = xstrdup(show_date(timestamp, tz, date_mode));
+       v->ul = timestamp;
+       return;
+ bad:
+       v->s = "";
+       v->ul = 0;
+}
+
+/* See grab_values */
+static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       int i;
+       int wholen = strlen(who);
+       const char *wholine = NULL;
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+               if (strncmp(who, name, wholen))
+                       continue;
+               if (name[wholen] != 0 &&
+                   strcmp(name + wholen, "name") &&
+                   strcmp(name + wholen, "email") &&
+                   prefixcmp(name + wholen, "date"))
+                       continue;
+               if (!wholine)
+                       wholine = find_wholine(who, wholen, buf, sz);
+               if (!wholine)
+                       return; /* no point looking for it */
+               if (name[wholen] == 0)
+                       v->s = copy_line(wholine);
+               else if (!strcmp(name + wholen, "name"))
+                       v->s = copy_name(wholine);
+               else if (!strcmp(name + wholen, "email"))
+                       v->s = copy_email(wholine);
+               else if (!prefixcmp(name + wholen, "date"))
+                       grab_date(wholine, v, name);
+       }
+
+       /*
+        * For a tag or a commit object, if "creator" or "creatordate" is
+        * requested, do something special.
+        */
+       if (strcmp(who, "tagger") && strcmp(who, "committer"))
+               return; /* "author" for commit object is not wanted */
+       if (!wholine)
+               wholine = find_wholine(who, wholen, buf, sz);
+       if (!wholine)
+               return;
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+
+               if (!prefixcmp(name, "creatordate"))
+                       grab_date(wholine, v, name);
+               else if (!strcmp(name, "creator"))
+                       v->s = copy_line(wholine);
+       }
+}
+
+static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body)
+{
+       while (*buf) {
+               const char *eol = strchr(buf, '\n');
+               if (!eol)
+                       return;
+               if (eol[1] == '\n') {
+                       buf = eol + 1;
+                       break; /* found end of header */
+               }
+               buf = eol + 1;
+       }
+       while (*buf == '\n')
+               buf++;
+       if (!*buf)
+               return;
+       *sub = buf; /* first non-empty line */
+       buf = strchr(buf, '\n');
+       if (!buf) {
+               *body = "";
+               return; /* no body */
+       }
+       while (*buf == '\n')
+               buf++; /* skip blank between subject and body */
+       *body = buf;
+}
+
+/* See grab_values */
+static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       int i;
+       const char *subpos = NULL, *bodypos = NULL;
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &val[i];
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+               if (strcmp(name, "subject") &&
+                   strcmp(name, "body") &&
+                   strcmp(name, "contents"))
+                       continue;
+               if (!subpos)
+                       find_subpos(buf, sz, &subpos, &bodypos);
+               if (!subpos)
+                       return;
+
+               if (!strcmp(name, "subject"))
+                       v->s = copy_line(subpos);
+               else if (!strcmp(name, "body"))
+                       v->s = xstrdup(bodypos);
+               else if (!strcmp(name, "contents"))
+                       v->s = xstrdup(subpos);
+       }
+}
+
+/*
+ * We want to have empty print-string for field requests
+ * that do not apply (e.g. "authordate" for a tag object)
+ */
+static void fill_missing_values(struct atom_value *val)
+{
+       int i;
+       for (i = 0; i < used_atom_cnt; i++) {
+               struct atom_value *v = &val[i];
+               if (v->s == NULL)
+                       v->s = "";
+       }
+}
+
+/*
+ * val is a list of atom_value to hold returned values.  Extract
+ * the values for atoms in used_atom array out of (obj, buf, sz).
+ * when deref is false, (obj, buf, sz) is the object that is
+ * pointed at by the ref itself; otherwise it is the object the
+ * ref (which is a tag) refers to.
+ */
+static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+       grab_common_values(val, deref, obj, buf, sz);
+       switch (obj->type) {
+       case OBJ_TAG:
+               grab_tag_values(val, deref, obj, buf, sz);
+               grab_sub_body_contents(val, deref, obj, buf, sz);
+               grab_person("tagger", val, deref, obj, buf, sz);
+               break;
+       case OBJ_COMMIT:
+               grab_commit_values(val, deref, obj, buf, sz);
+               grab_sub_body_contents(val, deref, obj, buf, sz);
+               grab_person("author", val, deref, obj, buf, sz);
+               grab_person("committer", val, deref, obj, buf, sz);
+               break;
+       case OBJ_TREE:
+               /* grab_tree_values(val, deref, obj, buf, sz); */
+               break;
+       case OBJ_BLOB:
+               /* grab_blob_values(val, deref, obj, buf, sz); */
+               break;
+       default:
+               die("Eh?  Object of type %d?", obj->type);
+       }
+}
+
+static inline char *copy_advance(char *dst, const char *src)
+{
+       while (*src)
+               *dst++ = *src++;
+       return dst;
+}
+
+/*
+ * Parse the object referred by ref, and grab needed value.
+ */
+static void populate_value(struct refinfo *ref)
+{
+       void *buf;
+       struct object *obj;
+       int eaten, i;
+       unsigned long size;
+       const unsigned char *tagged;
+
+       ref->value = xcalloc(sizeof(struct atom_value), used_atom_cnt);
+
+       if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {
+               unsigned char unused1[20];
+               const char *symref;
+               symref = resolve_ref(ref->refname, unused1, 1, NULL);
+               if (symref)
+                       ref->symref = xstrdup(symref);
+               else
+                       ref->symref = "";
+       }
+
+       /* Fill in specials first */
+       for (i = 0; i < used_atom_cnt; i++) {
+               const char *name = used_atom[i];
+               struct atom_value *v = &ref->value[i];
+               int deref = 0;
+               const char *refname;
+               const char *formatp;
+
+               if (*name == '*') {
+                       deref = 1;
+                       name++;
+               }
+
+               if (!prefixcmp(name, "refname"))
+                       refname = ref->refname;
+               else if (!prefixcmp(name, "symref"))
+                       refname = ref->symref ? ref->symref : "";
+               else if (!prefixcmp(name, "upstream")) {
+                       struct branch *branch;
+                       /* only local branches may have an upstream */
+                       if (prefixcmp(ref->refname, "refs/heads/"))
+                               continue;
+                       branch = branch_get(ref->refname + 11);
+
+                       if (!branch || !branch->merge || !branch->merge[0] ||
+                           !branch->merge[0]->dst)
+                               continue;
+                       refname = branch->merge[0]->dst;
+               }
+               else if (!strcmp(name, "flag")) {
+                       char buf[256], *cp = buf;
+                       if (ref->flag & REF_ISSYMREF)
+                               cp = copy_advance(cp, ",symref");
+                       if (ref->flag & REF_ISPACKED)
+                               cp = copy_advance(cp, ",packed");
+                       if (cp == buf)
+                               v->s = "";
+                       else {
+                               *cp = '\0';
+                               v->s = xstrdup(buf + 1);
+                       }
+                       continue;
+               }
+               else
+                       continue;
+
+               formatp = strchr(name, ':');
+               /* look for "short" refname format */
+               if (formatp) {
+                       formatp++;
+                       if (!strcmp(formatp, "short"))
+                               refname = shorten_unambiguous_ref(refname,
+                                                     warn_ambiguous_refs);
+                       else
+                               die("unknown %.*s format %s",
+                                   (int)(formatp - name), name, formatp);
+               }
+
+               if (!deref)
+                       v->s = refname;
+               else {
+                       int len = strlen(refname);
+                       char *s = xmalloc(len + 4);
+                       sprintf(s, "%s^{}", refname);
+                       v->s = s;
+               }
+       }
+
+       for (i = 0; i < used_atom_cnt; i++) {
+               struct atom_value *v = &ref->value[i];
+               if (v->s == NULL)
+                       goto need_obj;
+       }
+       return;
+
+ need_obj:
+       buf = get_obj(ref->objectname, &obj, &size, &eaten);
+       if (!buf)
+               die("missing object %s for %s",
+                   sha1_to_hex(ref->objectname), ref->refname);
+       if (!obj)
+               die("parse_object_buffer failed on %s for %s",
+                   sha1_to_hex(ref->objectname), ref->refname);
+
+       grab_values(ref->value, 0, obj, buf, size);
+       if (!eaten)
+               free(buf);
+
+       /*
+        * If there is no atom that wants to know about tagged
+        * object, we are done.
+        */
+       if (!need_tagged || (obj->type != OBJ_TAG))
+               return;
+
+       /*
+        * If it is a tag object, see if we use a value that derefs
+        * the object, and if we do grab the object it refers to.
+        */
+       tagged = ((struct tag *)obj)->tagged->sha1;
+
+       /*
+        * NEEDSWORK: This derefs tag only once, which
+        * is good to deal with chains of trust, but
+        * is not consistent with what deref_tag() does
+        * which peels the onion to the core.
+        */
+       buf = get_obj(tagged, &obj, &size, &eaten);
+       if (!buf)
+               die("missing object %s for %s",
+                   sha1_to_hex(tagged), ref->refname);
+       if (!obj)
+               die("parse_object_buffer failed on %s for %s",
+                   sha1_to_hex(tagged), ref->refname);
+       grab_values(ref->value, 1, obj, buf, size);
+       if (!eaten)
+               free(buf);
+}
+
+/*
+ * Given a ref, return the value for the atom.  This lazily gets value
+ * out of the object by calling populate value.
+ */
+static void get_value(struct refinfo *ref, int atom, struct atom_value **v)
+{
+       if (!ref->value) {
+               populate_value(ref);
+               fill_missing_values(ref->value);
+       }
+       *v = &ref->value[atom];
+}
+
+struct grab_ref_cbdata {
+       struct refinfo **grab_array;
+       const char **grab_pattern;
+       int grab_cnt;
+};
+
+/*
+ * A call-back given to for_each_ref().  Filter refs and keep them for
+ * later object processing.
+ */
+static int grab_single_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+       struct grab_ref_cbdata *cb = cb_data;
+       struct refinfo *ref;
+       int cnt;
+
+       if (*cb->grab_pattern) {
+               const char **pattern;
+               int namelen = strlen(refname);
+               for (pattern = cb->grab_pattern; *pattern; pattern++) {
+                       const char *p = *pattern;
+                       int plen = strlen(p);
+
+                       if ((plen <= namelen) &&
+                           !strncmp(refname, p, plen) &&
+                           (refname[plen] == '\0' ||
+                            refname[plen] == '/' ||
+                            p[plen-1] == '/'))
+                               break;
+                       if (!fnmatch(p, refname, FNM_PATHNAME))
+                               break;
+               }
+               if (!*pattern)
+                       return 0;
+       }
+
+       /*
+        * We do not open the object yet; sort may only need refname
+        * to do its job and the resulting list may yet to be pruned
+        * by maxcount logic.
+        */
+       ref = xcalloc(1, sizeof(*ref));
+       ref->refname = xstrdup(refname);
+       hashcpy(ref->objectname, sha1);
+       ref->flag = flag;
+
+       cnt = cb->grab_cnt;
+       cb->grab_array = xrealloc(cb->grab_array,
+                                 sizeof(*cb->grab_array) * (cnt + 1));
+       cb->grab_array[cnt++] = ref;
+       cb->grab_cnt = cnt;
+       return 0;
+}
+
+static int cmp_ref_sort(struct ref_sort *s, struct refinfo *a, struct refinfo *b)
+{
+       struct atom_value *va, *vb;
+       int cmp;
+       cmp_type cmp_type = used_atom_type[s->atom];
+
+       get_value(a, s->atom, &va);
+       get_value(b, s->atom, &vb);
+       switch (cmp_type) {
+       case FIELD_STR:
+               cmp = strcmp(va->s, vb->s);
+               break;
+       default:
+               if (va->ul < vb->ul)
+                       cmp = -1;
+               else if (va->ul == vb->ul)
+                       cmp = 0;
+               else
+                       cmp = 1;
+               break;
+       }
+       return (s->reverse) ? -cmp : cmp;
+}
+
+static struct ref_sort *ref_sort;
+static int compare_refs(const void *a_, const void *b_)
+{
+       struct refinfo *a = *((struct refinfo **)a_);
+       struct refinfo *b = *((struct refinfo **)b_);
+       struct ref_sort *s;
+
+       for (s = ref_sort; s; s = s->next) {
+               int cmp = cmp_ref_sort(s, a, b);
+               if (cmp)
+                       return cmp;
+       }
+       return 0;
+}
+
+static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs)
+{
+       ref_sort = sort;
+       qsort(refs, num_refs, sizeof(struct refinfo *), compare_refs);
+}
+
+static void print_value(struct refinfo *ref, int atom, int quote_style)
+{
+       struct atom_value *v;
+       get_value(ref, atom, &v);
+       switch (quote_style) {
+       case QUOTE_NONE:
+               fputs(v->s, stdout);
+               break;
+       case QUOTE_SHELL:
+               sq_quote_print(stdout, v->s);
+               break;
+       case QUOTE_PERL:
+               perl_quote_print(stdout, v->s);
+               break;
+       case QUOTE_PYTHON:
+               python_quote_print(stdout, v->s);
+               break;
+       case QUOTE_TCL:
+               tcl_quote_print(stdout, v->s);
+               break;
+       }
+}
+
+static int hex1(char ch)
+{
+       if ('0' <= ch && ch <= '9')
+               return ch - '0';
+       else if ('a' <= ch && ch <= 'f')
+               return ch - 'a' + 10;
+       else if ('A' <= ch && ch <= 'F')
+               return ch - 'A' + 10;
+       return -1;
+}
+static int hex2(const char *cp)
+{
+       if (cp[0] && cp[1])
+               return (hex1(cp[0]) << 4) | hex1(cp[1]);
+       else
+               return -1;
+}
+
+static void emit(const char *cp, const char *ep)
+{
+       while (*cp && (!ep || cp < ep)) {
+               if (*cp == '%') {
+                       if (cp[1] == '%')
+                               cp++;
+                       else {
+                               int ch = hex2(cp + 1);
+                               if (0 <= ch) {
+                                       putchar(ch);
+                                       cp += 3;
+                                       continue;
+                               }
+                       }
+               }
+               putchar(*cp);
+               cp++;
+       }
+}
+
+static void show_ref(struct refinfo *info, const char *format, int quote_style)
+{
+       const char *cp, *sp, *ep;
+
+       for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
+               ep = strchr(sp, ')');
+               if (cp < sp)
+                       emit(cp, sp);
+               print_value(info, parse_atom(sp + 2, ep), quote_style);
+       }
+       if (*cp) {
+               sp = cp + strlen(cp);
+               emit(cp, sp);
+       }
+       putchar('\n');
+}
+
+static struct ref_sort *default_sort(void)
+{
+       static const char cstr_name[] = "refname";
+
+       struct ref_sort *sort = xcalloc(1, sizeof(*sort));
+
+       sort->next = NULL;
+       sort->atom = parse_atom(cstr_name, cstr_name + strlen(cstr_name));
+       return sort;
+}
+
+static int opt_parse_sort(const struct option *opt, const char *arg, int unset)
+{
+       struct ref_sort **sort_tail = opt->value;
+       struct ref_sort *s;
+       int len;
+
+       if (!arg) /* should --no-sort void the list ? */
+               return -1;
+
+       *sort_tail = s = xcalloc(1, sizeof(*s));
+
+       if (*arg == '-') {
+               s->reverse = 1;
+               arg++;
+       }
+       len = strlen(arg);
+       s->atom = parse_atom(arg, arg+len);
+       return 0;
+}
+
+static char const * const for_each_ref_usage[] = {
+       "git for-each-ref [options] [<pattern>]",
+       NULL
+};
+
+int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
+{
+       int i, num_refs;
+       const char *format = "%(objectname) %(objecttype)\t%(refname)";
+       struct ref_sort *sort = NULL, **sort_tail = &sort;
+       int maxcount = 0, quote_style = 0;
+       struct refinfo **refs;
+       struct grab_ref_cbdata cbdata;
+
+       struct option opts[] = {
+               OPT_BIT('s', "shell", &quote_style,
+                       "quote placeholders suitably for shells", QUOTE_SHELL),
+               OPT_BIT('p', "perl",  &quote_style,
+                       "quote placeholders suitably for perl", QUOTE_PERL),
+               OPT_BIT(0 , "python", &quote_style,
+                       "quote placeholders suitably for python", QUOTE_PYTHON),
+               OPT_BIT(0 , "tcl",  &quote_style,
+                       "quote placeholders suitably for tcl", QUOTE_TCL),
+
+               OPT_GROUP(""),
+               OPT_INTEGER( 0 , "count", &maxcount, "show only <n> matched refs"),
+               OPT_STRING(  0 , "format", &format, "format", "format to use for the output"),
+               OPT_CALLBACK(0 , "sort", sort_tail, "key",
+                           "field name to sort on", &opt_parse_sort),
+               OPT_END(),
+       };
+
+       parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0);
+       if (maxcount < 0) {
+               error("invalid --count argument: `%d'", maxcount);
+               usage_with_options(for_each_ref_usage, opts);
+       }
+       if (HAS_MULTI_BITS(quote_style)) {
+               error("more than one quoting style?");
+               usage_with_options(for_each_ref_usage, opts);
+       }
+       if (verify_format(format))
+               usage_with_options(for_each_ref_usage, opts);
+
+       if (!sort)
+               sort = default_sort();
+       sort_atom_limit = used_atom_cnt;
+
+       /* for warn_ambiguous_refs */
+       git_config(git_default_config, NULL);
+
+       memset(&cbdata, 0, sizeof(cbdata));
+       cbdata.grab_pattern = argv;
+       for_each_rawref(grab_single_ref, &cbdata);
+       refs = cbdata.grab_array;
+       num_refs = cbdata.grab_cnt;
+
+       sort_refs(sort, refs, num_refs);
+
+       if (!maxcount || num_refs < maxcount)
+               maxcount = num_refs;
+       for (i = 0; i < maxcount; i++)
+               show_ref(refs[i], format, quote_style);
+       return 0;
+}
diff --git a/builtin/fsck.c b/builtin/fsck.c
new file mode 100644 (file)
index 0000000..0929c7f
--- /dev/null
@@ -0,0 +1,684 @@
+#include "builtin.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"
+#include "fsck.h"
+#include "parse-options.h"
+#include "dir.h"
+
+#define REACHABLE 0x0001
+#define SEEN      0x0002
+
+static int show_root;
+static int show_tags;
+static int show_unreachable;
+static int include_reflogs = 1;
+static int check_full = 1;
+static int check_strict;
+static int keep_cache_objects;
+static unsigned char head_sha1[20];
+static const char *head_points_at;
+static int errors_found;
+static int write_lost_and_found;
+static int verbose;
+#define ERROR_OBJECT 01
+#define ERROR_REACHABLE 02
+
+#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, typename(obj->type), sha1_to_hex(obj->sha1));
+       vfprintf(stderr, err, params);
+       fputs("\n", stderr);
+}
+
+__attribute__((format (printf, 2, 3)))
+static int objerror(struct object *obj, const char *err, ...)
+{
+       va_list params;
+       va_start(params, err);
+       errors_found |= ERROR_OBJECT;
+       objreport(obj, "error", err, params);
+       va_end(params);
+       return -1;
+}
+
+__attribute__((format (printf, 3, 4)))
+static int fsck_error_func(struct object *obj, int type, const char *err, ...)
+{
+       va_list params;
+       va_start(params, err);
+       objreport(obj, (type == FSCK_WARN) ? "warning" : "error", err, params);
+       va_end(params);
+       return (type == FSCK_WARN) ? 0 : 1;
+}
+
+static struct object_array pending;
+
+static int mark_object(struct object *obj, int type, void *data)
+{
+       struct object *parent = data;
+
+       if (!obj) {
+               printf("broken link from %7s %s\n",
+                          typename(parent->type), sha1_to_hex(parent->sha1));
+               printf("broken link from %7s %s\n",
+                          (type == OBJ_ANY ? "unknown" : typename(type)), "unknown");
+               errors_found |= ERROR_REACHABLE;
+               return 1;
+       }
+
+       if (type != OBJ_ANY && obj->type != type)
+               objerror(parent, "wrong object type in link");
+
+       if (obj->flags & REACHABLE)
+               return 0;
+       obj->flags |= REACHABLE;
+       if (!obj->parsed) {
+               if (parent && !has_sha1_file(obj->sha1)) {
+                       printf("broken link from %7s %s\n",
+                                typename(parent->type), sha1_to_hex(parent->sha1));
+                       printf("              to %7s %s\n",
+                                typename(obj->type), sha1_to_hex(obj->sha1));
+                       errors_found |= ERROR_REACHABLE;
+               }
+               return 1;
+       }
+
+       add_object_array(obj, (void *) parent, &pending);
+       return 0;
+}
+
+static void mark_object_reachable(struct object *obj)
+{
+       mark_object(obj, OBJ_ANY, NULL);
+}
+
+static int traverse_one_object(struct object *obj, struct object *parent)
+{
+       int result;
+       struct tree *tree = NULL;
+
+       if (obj->type == OBJ_TREE) {
+               obj->parsed = 0;
+               tree = (struct tree *)obj;
+               if (parse_tree(tree) < 0)
+                       return 1; /* error already displayed */
+       }
+       result = fsck_walk(obj, mark_object, obj);
+       if (tree) {
+               free(tree->buffer);
+               tree->buffer = NULL;
+       }
+       return result;
+}
+
+static int traverse_reachable(void)
+{
+       int result = 0;
+       while (pending.nr) {
+               struct object_array_entry *entry;
+               struct object *obj, *parent;
+
+               entry = pending.objects + --pending.nr;
+               obj = entry->item;
+               parent = (struct object *) entry->name;
+               result |= traverse_one_object(obj, parent);
+       }
+       return !!result;
+}
+
+static int mark_used(struct object *obj, int type, void *data)
+{
+       if (!obj)
+               return 1;
+       obj->used = 1;
+       return 0;
+}
+
+/*
+ * Check a single reachable object
+ */
+static void check_reachable_object(struct object *obj)
+{
+       /*
+        * We obviously want the object to be parsed,
+        * except if it was in a pack-file and we didn't
+        * do a full fsck
+        */
+       if (!obj->parsed) {
+               if (has_sha1_pack(obj->sha1))
+                       return; /* it is in pack - forget about it */
+               printf("missing %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1));
+               errors_found |= ERROR_REACHABLE;
+               return;
+       }
+}
+
+/*
+ * Check a single unreachable object
+ */
+static void check_unreachable_object(struct object *obj)
+{
+       /*
+        * Missing unreachable object? Ignore it. It's not like
+        * we miss it (since it can't be reached), nor do we want
+        * to complain about it being unreachable (since it does
+        * not exist).
+        */
+       if (!obj->parsed)
+               return;
+
+       /*
+        * Unreachable object that exists? Show it if asked to,
+        * since this is something that is prunable.
+        */
+       if (show_unreachable) {
+               printf("unreachable %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1));
+               return;
+       }
+
+       /*
+        * "!used" means that nothing at all points to it, including
+        * other unreachable objects. In other words, it's the "tip"
+        * of some set of unreachable objects, usually a commit that
+        * got dropped.
+        *
+        * Such starting points are more interesting than some random
+        * set of unreachable objects, so we show them even if the user
+        * hasn't asked for _all_ unreachable objects. If you have
+        * deleted a branch by mistake, this is a prime candidate to
+        * start looking at, for example.
+        */
+       if (!obj->used) {
+               printf("dangling %s %s\n", typename(obj->type),
+                      sha1_to_hex(obj->sha1));
+               if (write_lost_and_found) {
+                       char *filename = git_path("lost-found/%s/%s",
+                               obj->type == OBJ_COMMIT ? "commit" : "other",
+                               sha1_to_hex(obj->sha1));
+                       FILE *f;
+
+                       if (safe_create_leading_directories(filename)) {
+                               error("Could not create lost-found");
+                               return;
+                       }
+                       if (!(f = fopen(filename, "w")))
+                               die_errno("Could not open '%s'", filename);
+                       if (obj->type == OBJ_BLOB) {
+                               enum object_type type;
+                               unsigned long size;
+                               char *buf = read_sha1_file(obj->sha1,
+                                               &type, &size);
+                               if (buf) {
+                                       if (fwrite(buf, size, 1, f) != 1)
+                                               die_errno("Could not write '%s'",
+                                                         filename);
+                                       free(buf);
+                               }
+                       } else
+                               fprintf(f, "%s\n", sha1_to_hex(obj->sha1));
+                       if (fclose(f))
+                               die_errno("Could not finish '%s'",
+                                         filename);
+               }
+               return;
+       }
+
+       /*
+        * Otherwise? It's there, it's unreachable, and some other unreachable
+        * object points to it. Ignore it - it's not interesting, and we showed
+        * all the interesting cases above.
+        */
+}
+
+static void check_object(struct object *obj)
+{
+       if (verbose)
+               fprintf(stderr, "Checking %s\n", sha1_to_hex(obj->sha1));
+
+       if (obj->flags & REACHABLE)
+               check_reachable_object(obj);
+       else
+               check_unreachable_object(obj);
+}
+
+static void check_connectivity(void)
+{
+       int i, max;
+
+       /* Traverse the pending reachable objects */
+       traverse_reachable();
+
+       /* Look up all the requirements, warn about missing objects.. */
+       max = get_max_object_index();
+       if (verbose)
+               fprintf(stderr, "Checking connectivity (%d objects)\n", max);
+
+       for (i = 0; i < max; i++) {
+               struct object *obj = get_indexed_object(i);
+
+               if (obj)
+                       check_object(obj);
+       }
+}
+
+static int fsck_sha1(const unsigned char *sha1)
+{
+       struct object *obj = parse_object(sha1);
+       if (!obj) {
+               errors_found |= ERROR_OBJECT;
+               return error("%s: object corrupt or missing",
+                            sha1_to_hex(sha1));
+       }
+       if (obj->flags & SEEN)
+               return 0;
+       obj->flags |= SEEN;
+
+       if (verbose)
+               fprintf(stderr, "Checking %s %s\n",
+                       typename(obj->type), sha1_to_hex(obj->sha1));
+
+       if (fsck_walk(obj, mark_used, NULL))
+               objerror(obj, "broken links");
+       if (fsck_object(obj, check_strict, fsck_error_func))
+               return -1;
+
+       if (obj->type == OBJ_TREE) {
+               struct tree *item = (struct tree *) obj;
+
+               free(item->buffer);
+               item->buffer = NULL;
+       }
+
+       if (obj->type == OBJ_COMMIT) {
+               struct commit *commit = (struct commit *) obj;
+
+               free(commit->buffer);
+               commit->buffer = NULL;
+
+               if (!commit->parents && show_root)
+                       printf("root %s\n", sha1_to_hex(commit->object.sha1));
+       }
+
+       if (obj->type == OBJ_TAG) {
+               struct tag *tag = (struct tag *) obj;
+
+               if (show_tags && tag->tagged) {
+                       printf("tagged %s %s", typename(tag->tagged->type), sha1_to_hex(tag->tagged->sha1));
+                       printf(" (%s) in %s\n", tag->tag, sha1_to_hex(tag->object.sha1));
+               }
+       }
+
+       return 0;
+}
+
+/*
+ * 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;
+       hashcpy(entry->sha1, sha1);
+       nr = sha1_list.nr;
+       if (nr == MAX_SHA1_ENTRIES) {
+               fsck_sha1_list();
+               nr = 0;
+       }
+       sha1_list.entry[nr] = entry;
+       sha1_list.nr = ++nr;
+}
+
+static void fsck_dir(int i, char *path)
+{
+       DIR *dir = opendir(path);
+       struct dirent *de;
+
+       if (!dir)
+               return;
+
+       if (verbose)
+               fprintf(stderr, "Checking directory %s\n", path);
+
+       while ((de = readdir(dir)) != NULL) {
+               char name[100];
+               unsigned char sha1[20];
+
+               if (is_dot_or_dotdot(de->d_name))
+                       continue;
+               if (strlen(de->d_name) == 38) {
+                       sprintf(name, "%02x", i);
+                       memcpy(name+2, de->d_name, 39);
+                       if (get_sha1_hex(name, sha1) < 0)
+                               break;
+                       add_sha1_list(sha1, DIRENT_SORT_HINT(de));
+                       continue;
+               }
+               if (!prefixcmp(de->d_name, "tmp_obj_"))
+                       continue;
+               fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
+       }
+       closedir(dir);
+}
+
+static int default_refs;
+
+static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+               const char *email, unsigned long timestamp, int tz,
+               const char *message, void *cb_data)
+{
+       struct object *obj;
+
+       if (verbose)
+               fprintf(stderr, "Checking reflog %s->%s\n",
+                       sha1_to_hex(osha1), sha1_to_hex(nsha1));
+
+       if (!is_null_sha1(osha1)) {
+               obj = lookup_object(osha1);
+               if (obj) {
+                       obj->used = 1;
+                       mark_object_reachable(obj);
+               }
+       }
+       obj = lookup_object(nsha1);
+       if (obj) {
+               obj->used = 1;
+               mark_object_reachable(obj);
+       }
+       return 0;
+}
+
+static int fsck_handle_reflog(const char *logname, const unsigned char *sha1, int flag, void *cb_data)
+{
+       for_each_reflog_ent(logname, fsck_handle_reflog_ent, NULL);
+       return 0;
+}
+
+static int is_branch(const char *refname)
+{
+       return !strcmp(refname, "HEAD") || !prefixcmp(refname, "refs/heads/");
+}
+
+static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+       struct object *obj;
+
+       obj = parse_object(sha1);
+       if (!obj) {
+               error("%s: invalid sha1 pointer %s", refname, sha1_to_hex(sha1));
+               /* We'll continue with the rest despite the error.. */
+               return 0;
+       }
+       if (obj->type != OBJ_COMMIT && is_branch(refname))
+               error("%s: not a commit", refname);
+       default_refs++;
+       obj->used = 1;
+       mark_object_reachable(obj);
+
+       return 0;
+}
+
+static void get_default_heads(void)
+{
+       if (head_points_at && !is_null_sha1(head_sha1))
+               fsck_handle_ref("HEAD", head_sha1, 0, NULL);
+       for_each_ref(fsck_handle_ref, NULL);
+       if (include_reflogs)
+               for_each_reflog(fsck_handle_reflog, NULL);
+
+       /*
+        * Not having any default heads isn't really fatal, but
+        * it does mean that "--unreachable" no longer makes any
+        * sense (since in this case everything will obviously
+        * be unreachable by definition.
+        *
+        * Showing dangling objects is valid, though (as those
+        * dangling objects are likely lost heads).
+        *
+        * So we just print a warning about it, and clear the
+        * "show_unreachable" flag.
+        */
+       if (!default_refs) {
+               fprintf(stderr, "notice: No default references\n");
+               show_unreachable = 0;
+       }
+}
+
+static void fsck_object_dir(const char *path)
+{
+       int i;
+
+       if (verbose)
+               fprintf(stderr, "Checking object directory\n");
+
+       for (i = 0; i < 256; i++) {
+               static char dir[4096];
+               sprintf(dir, "%s/%02x", path, i);
+               fsck_dir(i, dir);
+       }
+       fsck_sha1_list();
+}
+
+static int fsck_head_link(void)
+{
+       int flag;
+       int null_is_error = 0;
+
+       if (verbose)
+               fprintf(stderr, "Checking HEAD link\n");
+
+       head_points_at = resolve_ref("HEAD", head_sha1, 0, &flag);
+       if (!head_points_at)
+               return error("Invalid HEAD");
+       if (!strcmp(head_points_at, "HEAD"))
+               /* detached HEAD */
+               null_is_error = 1;
+       else if (prefixcmp(head_points_at, "refs/heads/"))
+               return error("HEAD points to something strange (%s)",
+                            head_points_at);
+       if (is_null_sha1(head_sha1)) {
+               if (null_is_error)
+                       return error("HEAD: detached HEAD points at nothing");
+               fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n",
+                       head_points_at + 11);
+       }
+       return 0;
+}
+
+static int fsck_cache_tree(struct cache_tree *it)
+{
+       int i;
+       int err = 0;
+
+       if (verbose)
+               fprintf(stderr, "Checking cache tree\n");
+
+       if (0 <= it->entry_count) {
+               struct object *obj = parse_object(it->sha1);
+               if (!obj) {
+                       error("%s: invalid sha1 pointer in cache-tree",
+                             sha1_to_hex(it->sha1));
+                       return 1;
+               }
+               mark_object_reachable(obj);
+               obj->used = 1;
+               if (obj->type != OBJ_TREE)
+                       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;
+}
+
+static char const * const fsck_usage[] = {
+       "git fsck [options] [<object>...]",
+       NULL
+};
+
+static struct option fsck_opts[] = {
+       OPT__VERBOSE(&verbose),
+       OPT_BOOLEAN(0, "unreachable", &show_unreachable, "show unreachable objects"),
+       OPT_BOOLEAN(0, "tags", &show_tags, "report tags"),
+       OPT_BOOLEAN(0, "root", &show_root, "report root nodes"),
+       OPT_BOOLEAN(0, "cache", &keep_cache_objects, "make index objects head nodes"),
+       OPT_BOOLEAN(0, "reflogs", &include_reflogs, "make reflogs head nodes (default)"),
+       OPT_BOOLEAN(0, "full", &check_full, "also consider packs and alternate objects"),
+       OPT_BOOLEAN(0, "strict", &check_strict, "enable more strict checking"),
+       OPT_BOOLEAN(0, "lost-found", &write_lost_and_found,
+                               "write dangling objects in .git/lost-found"),
+       OPT_END(),
+};
+
+int cmd_fsck(int argc, const char **argv, const char *prefix)
+{
+       int i, heads;
+       struct alternate_object_database *alt;
+
+       errors_found = 0;
+       read_replace_refs = 0;
+
+       argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0);
+       if (write_lost_and_found) {
+               check_full = 1;
+               include_reflogs = 0;
+       }
+
+       fsck_head_link();
+       fsck_object_dir(get_object_directory());
+
+       prepare_alt_odb();
+       for (alt = alt_odb_list; alt; alt = alt->next) {
+               char namebuf[PATH_MAX];
+               int namelen = alt->name - alt->base;
+               memcpy(namebuf, alt->base, namelen);
+               namebuf[namelen - 1] = 0;
+               fsck_object_dir(namebuf);
+       }
+
+       if (check_full) {
+               struct packed_git *p;
+
+               prepare_packed_git();
+               for (p = packed_git; p; p = p->next)
+                       /* verify gives error messages itself */
+                       verify_pack(p);
+
+               for (p = packed_git; p; p = p->next) {
+                       uint32_t j, num;
+                       if (open_pack_index(p))
+                               continue;
+                       num = p->num_objects;
+                       for (j = 0; j < num; j++)
+                               fsck_sha1(nth_packed_object_sha1(p, j));
+               }
+       }
+
+       heads = 0;
+       for (i = 0; i < argc; i++) {
+               const char *arg = argv[i];
+               unsigned char sha1[20];
+               if (!get_sha1(arg, sha1)) {
+                       struct object *obj = lookup_object(sha1);
+
+                       /* Error is printed by lookup_object(). */
+                       if (!obj)
+                               continue;
+
+                       obj->used = 1;
+                       mark_object_reachable(obj);
+                       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) {
+               read_cache();
+               for (i = 0; i < active_nr; i++) {
+                       unsigned int mode;
+                       struct blob *blob;
+                       struct object *obj;
+
+                       mode = active_cache[i]->ce_mode;
+                       if (S_ISGITLINK(mode))
+                               continue;
+                       blob = lookup_blob(active_cache[i]->sha1);
+                       if (!blob)
+                               continue;
+                       obj = &blob->object;
+                       obj->used = 1;
+                       mark_object_reachable(obj);
+               }
+               if (active_cache_tree)
+                       fsck_cache_tree(active_cache_tree);
+       }
+
+       check_connectivity();
+       return errors_found;
+}
diff --git a/builtin/gc.c b/builtin/gc.c
new file mode 100644 (file)
index 0000000..c304638
--- /dev/null
@@ -0,0 +1,255 @@
+/*
+ * git gc builtin command
+ *
+ * Cleanup unreachable files and optimize the repository.
+ *
+ * Copyright (c) 2007 James Bowes
+ *
+ * Based on git-gc.sh, which is
+ *
+ * Copyright (c) 2006 Shawn O. Pearce
+ */
+
+#include "builtin.h"
+#include "cache.h"
+#include "parse-options.h"
+#include "run-command.h"
+
+#define FAILED_RUN "failed to run %s"
+
+static const char * const builtin_gc_usage[] = {
+       "git gc [options]",
+       NULL
+};
+
+static int pack_refs = 1;
+static int aggressive_window = 250;
+static int gc_auto_threshold = 6700;
+static int gc_auto_pack_limit = 50;
+static const char *prune_expire = "2.weeks.ago";
+
+#define MAX_ADD 10
+static const char *argv_pack_refs[] = {"pack-refs", "--all", "--prune", NULL};
+static const char *argv_reflog[] = {"reflog", "expire", "--all", NULL};
+static const char *argv_repack[MAX_ADD] = {"repack", "-d", "-l", NULL};
+static const char *argv_prune[] = {"prune", "--expire", NULL, NULL};
+static const char *argv_rerere[] = {"rerere", "gc", NULL};
+
+static int gc_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "gc.packrefs")) {
+               if (value && !strcmp(value, "notbare"))
+                       pack_refs = -1;
+               else
+                       pack_refs = git_config_bool(var, value);
+               return 0;
+       }
+       if (!strcmp(var, "gc.aggressivewindow")) {
+               aggressive_window = git_config_int(var, value);
+               return 0;
+       }
+       if (!strcmp(var, "gc.auto")) {
+               gc_auto_threshold = git_config_int(var, value);
+               return 0;
+       }
+       if (!strcmp(var, "gc.autopacklimit")) {
+               gc_auto_pack_limit = git_config_int(var, value);
+               return 0;
+       }
+       if (!strcmp(var, "gc.pruneexpire")) {
+               if (value && strcmp(value, "now")) {
+                       unsigned long now = approxidate("now");
+                       if (approxidate(value) >= now)
+                               return error("Invalid %s: '%s'", var, value);
+               }
+               return git_config_string(&prune_expire, var, value);
+       }
+       return git_default_config(var, value, cb);
+}
+
+static void append_option(const char **cmd, const char *opt, int max_length)
+{
+       int i;
+
+       for (i = 0; cmd[i]; i++)
+               ;
+
+       if (i + 2 >= max_length)
+               die("Too many options specified");
+       cmd[i++] = opt;
+       cmd[i] = NULL;
+}
+
+static int too_many_loose_objects(void)
+{
+       /*
+        * Quickly check if a "gc" is needed, by estimating how
+        * many loose objects there are.  Because SHA-1 is evenly
+        * distributed, we can check only one and get a reasonable
+        * estimate.
+        */
+       char path[PATH_MAX];
+       const char *objdir = get_object_directory();
+       DIR *dir;
+       struct dirent *ent;
+       int auto_threshold;
+       int num_loose = 0;
+       int needed = 0;
+
+       if (gc_auto_threshold <= 0)
+               return 0;
+
+       if (sizeof(path) <= snprintf(path, sizeof(path), "%s/17", objdir)) {
+               warning("insanely long object directory %.*s", 50, objdir);
+               return 0;
+       }
+       dir = opendir(path);
+       if (!dir)
+               return 0;
+
+       auto_threshold = (gc_auto_threshold + 255) / 256;
+       while ((ent = readdir(dir)) != NULL) {
+               if (strspn(ent->d_name, "0123456789abcdef") != 38 ||
+                   ent->d_name[38] != '\0')
+                       continue;
+               if (++num_loose > auto_threshold) {
+                       needed = 1;
+                       break;
+               }
+       }
+       closedir(dir);
+       return needed;
+}
+
+static int too_many_packs(void)
+{
+       struct packed_git *p;
+       int cnt;
+
+       if (gc_auto_pack_limit <= 0)
+               return 0;
+
+       prepare_packed_git();
+       for (cnt = 0, p = packed_git; p; p = p->next) {
+               if (!p->pack_local)
+                       continue;
+               if (p->pack_keep)
+                       continue;
+               /*
+                * Perhaps check the size of the pack and count only
+                * very small ones here?
+                */
+               cnt++;
+       }
+       return gc_auto_pack_limit <= cnt;
+}
+
+static int need_to_gc(void)
+{
+       /*
+        * Setting gc.auto to 0 or negative can disable the
+        * automatic gc.
+        */
+       if (gc_auto_threshold <= 0)
+               return 0;
+
+       /*
+        * If there are too many loose objects, but not too many
+        * packs, we run "repack -d -l".  If there are too many packs,
+        * we run "repack -A -d -l".  Otherwise we tell the caller
+        * there is no need.
+        */
+       if (too_many_packs())
+               append_option(argv_repack,
+                             prune_expire && !strcmp(prune_expire, "now") ?
+                             "-a" : "-A",
+                             MAX_ADD);
+       else if (!too_many_loose_objects())
+               return 0;
+
+       if (run_hook(NULL, "pre-auto-gc", NULL))
+               return 0;
+       return 1;
+}
+
+int cmd_gc(int argc, const char **argv, const char *prefix)
+{
+       int aggressive = 0;
+       int auto_gc = 0;
+       int quiet = 0;
+       char buf[80];
+
+       struct option builtin_gc_options[] = {
+               OPT__QUIET(&quiet),
+               { OPTION_STRING, 0, "prune", &prune_expire, "date",
+                       "prune unreferenced objects",
+                       PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire },
+               OPT_BOOLEAN(0, "aggressive", &aggressive, "be more thorough (increased runtime)"),
+               OPT_BOOLEAN(0, "auto", &auto_gc, "enable auto-gc mode"),
+               OPT_END()
+       };
+
+       git_config(gc_config, NULL);
+
+       if (pack_refs < 0)
+               pack_refs = !is_bare_repository();
+
+       argc = parse_options(argc, argv, prefix, builtin_gc_options,
+                            builtin_gc_usage, 0);
+       if (argc > 0)
+               usage_with_options(builtin_gc_usage, builtin_gc_options);
+
+       if (aggressive) {
+               append_option(argv_repack, "-f", MAX_ADD);
+               append_option(argv_repack, "--depth=250", MAX_ADD);
+               if (aggressive_window > 0) {
+                       sprintf(buf, "--window=%d", aggressive_window);
+                       append_option(argv_repack, buf, MAX_ADD);
+               }
+       }
+       if (quiet)
+               append_option(argv_repack, "-q", MAX_ADD);
+
+       if (auto_gc) {
+               /*
+                * Auto-gc should be least intrusive as possible.
+                */
+               if (!need_to_gc())
+                       return 0;
+               fprintf(stderr,
+                       "Auto packing the repository for optimum performance.%s\n",
+                       quiet
+                       ? ""
+                       : (" You may also\n"
+                          "run \"git gc\" manually. See "
+                          "\"git help gc\" for more information."));
+       } else
+               append_option(argv_repack,
+                             prune_expire && !strcmp(prune_expire, "now")
+                             ? "-a" : "-A",
+                             MAX_ADD);
+
+       if (pack_refs && run_command_v_opt(argv_pack_refs, RUN_GIT_CMD))
+               return error(FAILED_RUN, argv_pack_refs[0]);
+
+       if (run_command_v_opt(argv_reflog, RUN_GIT_CMD))
+               return error(FAILED_RUN, argv_reflog[0]);
+
+       if (run_command_v_opt(argv_repack, RUN_GIT_CMD))
+               return error(FAILED_RUN, argv_repack[0]);
+
+       if (prune_expire) {
+               argv_prune[2] = prune_expire;
+               if (run_command_v_opt(argv_prune, RUN_GIT_CMD))
+                       return error(FAILED_RUN, argv_prune[0]);
+       }
+
+       if (run_command_v_opt(argv_rerere, RUN_GIT_CMD))
+               return error(FAILED_RUN, argv_rerere[0]);
+
+       if (auto_gc && too_many_loose_objects())
+               warning("There are too many unreachable loose objects; "
+                       "run 'git prune' to remove them.");
+
+       return 0;
+}
diff --git a/builtin/grep.c b/builtin/grep.c
new file mode 100644 (file)
index 0000000..da32f3d
--- /dev/null
@@ -0,0 +1,1114 @@
+/*
+ * 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 "parse-options.h"
+#include "string-list.h"
+#include "run-command.h"
+#include "userdiff.h"
+#include "grep.h"
+#include "quote.h"
+#include "dir.h"
+
+#ifndef NO_PTHREADS
+#include <pthread.h>
+#include "thread-utils.h"
+#endif
+
+static char const * const grep_usage[] = {
+       "git grep [options] [-e] <pattern> [<rev>...] [[--] path...]",
+       NULL
+};
+
+static int use_threads = 1;
+
+#ifndef NO_PTHREADS
+#define THREADS 8
+static pthread_t threads[THREADS];
+
+static void *load_sha1(const unsigned char *sha1, unsigned long *size,
+                      const char *name);
+static void *load_file(const char *filename, size_t *sz);
+
+enum work_type {WORK_SHA1, WORK_FILE};
+
+/* We use one producer thread and THREADS consumer
+ * threads. The producer adds struct work_items to 'todo' and the
+ * consumers pick work items from the same array.
+ */
+struct work_item
+{
+       enum work_type type;
+       char *name;
+
+       /* if type == WORK_SHA1, then 'identifier' is a SHA1,
+        * otherwise type == WORK_FILE, and 'identifier' is a NUL
+        * terminated filename.
+        */
+       void *identifier;
+       char done;
+       struct strbuf out;
+};
+
+/* In the range [todo_done, todo_start) in 'todo' we have work_items
+ * that have been or are processed by a consumer thread. We haven't
+ * written the result for these to stdout yet.
+ *
+ * The work_items in [todo_start, todo_end) are waiting to be picked
+ * up by a consumer thread.
+ *
+ * The ranges are modulo TODO_SIZE.
+ */
+#define TODO_SIZE 128
+static struct work_item todo[TODO_SIZE];
+static int todo_start;
+static int todo_end;
+static int todo_done;
+
+/* Has all work items been added? */
+static int all_work_added;
+
+/* This lock protects all the variables above. */
+static pthread_mutex_t grep_mutex;
+
+/* Used to serialize calls to read_sha1_file. */
+static pthread_mutex_t read_sha1_mutex;
+
+#define grep_lock() pthread_mutex_lock(&grep_mutex)
+#define grep_unlock() pthread_mutex_unlock(&grep_mutex)
+#define read_sha1_lock() pthread_mutex_lock(&read_sha1_mutex)
+#define read_sha1_unlock() pthread_mutex_unlock(&read_sha1_mutex)
+
+/* Signalled when a new work_item is added to todo. */
+static pthread_cond_t cond_add;
+
+/* Signalled when the result from one work_item is written to
+ * stdout.
+ */
+static pthread_cond_t cond_write;
+
+/* Signalled when we are finished with everything. */
+static pthread_cond_t cond_result;
+
+static int print_hunk_marks_between_files;
+static int printed_something;
+
+static void add_work(enum work_type type, char *name, void *id)
+{
+       grep_lock();
+
+       while ((todo_end+1) % ARRAY_SIZE(todo) == todo_done) {
+               pthread_cond_wait(&cond_write, &grep_mutex);
+       }
+
+       todo[todo_end].type = type;
+       todo[todo_end].name = name;
+       todo[todo_end].identifier = id;
+       todo[todo_end].done = 0;
+       strbuf_reset(&todo[todo_end].out);
+       todo_end = (todo_end + 1) % ARRAY_SIZE(todo);
+
+       pthread_cond_signal(&cond_add);
+       grep_unlock();
+}
+
+static struct work_item *get_work(void)
+{
+       struct work_item *ret;
+
+       grep_lock();
+       while (todo_start == todo_end && !all_work_added) {
+               pthread_cond_wait(&cond_add, &grep_mutex);
+       }
+
+       if (todo_start == todo_end && all_work_added) {
+               ret = NULL;
+       } else {
+               ret = &todo[todo_start];
+               todo_start = (todo_start + 1) % ARRAY_SIZE(todo);
+       }
+       grep_unlock();
+       return ret;
+}
+
+static void grep_sha1_async(struct grep_opt *opt, char *name,
+                           const unsigned char *sha1)
+{
+       unsigned char *s;
+       s = xmalloc(20);
+       memcpy(s, sha1, 20);
+       add_work(WORK_SHA1, name, s);
+}
+
+static void grep_file_async(struct grep_opt *opt, char *name,
+                           const char *filename)
+{
+       add_work(WORK_FILE, name, xstrdup(filename));
+}
+
+static void work_done(struct work_item *w)
+{
+       int old_done;
+
+       grep_lock();
+       w->done = 1;
+       old_done = todo_done;
+       for(; todo[todo_done].done && todo_done != todo_start;
+           todo_done = (todo_done+1) % ARRAY_SIZE(todo)) {
+               w = &todo[todo_done];
+               if (w->out.len) {
+                       if (print_hunk_marks_between_files && printed_something)
+                               write_or_die(1, "--\n", 3);
+                       write_or_die(1, w->out.buf, w->out.len);
+                       printed_something = 1;
+               }
+               free(w->name);
+               free(w->identifier);
+       }
+
+       if (old_done != todo_done)
+               pthread_cond_signal(&cond_write);
+
+       if (all_work_added && todo_done == todo_end)
+               pthread_cond_signal(&cond_result);
+
+       grep_unlock();
+}
+
+static void *run(void *arg)
+{
+       int hit = 0;
+       struct grep_opt *opt = arg;
+
+       while (1) {
+               struct work_item *w = get_work();
+               if (!w)
+                       break;
+
+               opt->output_priv = w;
+               if (w->type == WORK_SHA1) {
+                       unsigned long sz;
+                       void* data = load_sha1(w->identifier, &sz, w->name);
+
+                       if (data) {
+                               hit |= grep_buffer(opt, w->name, data, sz);
+                               free(data);
+                       }
+               } else if (w->type == WORK_FILE) {
+                       size_t sz;
+                       void* data = load_file(w->identifier, &sz);
+                       if (data) {
+                               hit |= grep_buffer(opt, w->name, data, sz);
+                               free(data);
+                       }
+               } else {
+                       assert(0);
+               }
+
+               work_done(w);
+       }
+       free_grep_patterns(arg);
+       free(arg);
+
+       return (void*) (intptr_t) hit;
+}
+
+static void strbuf_out(struct grep_opt *opt, const void *buf, size_t size)
+{
+       struct work_item *w = opt->output_priv;
+       strbuf_add(&w->out, buf, size);
+}
+
+static void start_threads(struct grep_opt *opt)
+{
+       int i;
+
+       pthread_mutex_init(&grep_mutex, NULL);
+       pthread_mutex_init(&read_sha1_mutex, NULL);
+       pthread_cond_init(&cond_add, NULL);
+       pthread_cond_init(&cond_write, NULL);
+       pthread_cond_init(&cond_result, NULL);
+
+       for (i = 0; i < ARRAY_SIZE(todo); i++) {
+               strbuf_init(&todo[i].out, 0);
+       }
+
+       for (i = 0; i < ARRAY_SIZE(threads); i++) {
+               int err;
+               struct grep_opt *o = grep_opt_dup(opt);
+               o->output = strbuf_out;
+               compile_grep_patterns(o);
+               err = pthread_create(&threads[i], NULL, run, o);
+
+               if (err)
+                       die("grep: failed to create thread: %s",
+                           strerror(err));
+       }
+}
+
+static int wait_all(void)
+{
+       int hit = 0;
+       int i;
+
+       grep_lock();
+       all_work_added = 1;
+
+       /* Wait until all work is done. */
+       while (todo_done != todo_end)
+               pthread_cond_wait(&cond_result, &grep_mutex);
+
+       /* Wake up all the consumer threads so they can see that there
+        * is no more work to do.
+        */
+       pthread_cond_broadcast(&cond_add);
+       grep_unlock();
+
+       for (i = 0; i < ARRAY_SIZE(threads); i++) {
+               void *h;
+               pthread_join(threads[i], &h);
+               hit |= (int) (intptr_t) h;
+       }
+
+       pthread_mutex_destroy(&grep_mutex);
+       pthread_mutex_destroy(&read_sha1_mutex);
+       pthread_cond_destroy(&cond_add);
+       pthread_cond_destroy(&cond_write);
+       pthread_cond_destroy(&cond_result);
+
+       return hit;
+}
+#else /* !NO_PTHREADS */
+#define read_sha1_lock()
+#define read_sha1_unlock()
+
+static int wait_all(void)
+{
+       return 0;
+}
+#endif
+
+static int grep_config(const char *var, const char *value, void *cb)
+{
+       struct grep_opt *opt = cb;
+       char *color = NULL;
+
+       switch (userdiff_config(var, value)) {
+       case 0: break;
+       case -1: return -1;
+       default: return 0;
+       }
+
+       if (!strcmp(var, "color.grep"))
+               opt->color = git_config_colorbool(var, value, -1);
+       else if (!strcmp(var, "color.grep.context"))
+               color = opt->color_context;
+       else if (!strcmp(var, "color.grep.filename"))
+               color = opt->color_filename;
+       else if (!strcmp(var, "color.grep.function"))
+               color = opt->color_function;
+       else if (!strcmp(var, "color.grep.linenumber"))
+               color = opt->color_lineno;
+       else if (!strcmp(var, "color.grep.match"))
+               color = opt->color_match;
+       else if (!strcmp(var, "color.grep.selected"))
+               color = opt->color_selected;
+       else if (!strcmp(var, "color.grep.separator"))
+               color = opt->color_sep;
+       else
+               return git_color_default_config(var, value, cb);
+       if (color) {
+               if (!value)
+                       return config_error_nonbool(var);
+               color_parse(value, var, color);
+       }
+       return 0;
+}
+
+/*
+ * Return non-zero if max_depth is negative or path has no more then max_depth
+ * slashes.
+ */
+static int accept_subdir(const char *path, int max_depth)
+{
+       if (max_depth < 0)
+               return 1;
+
+       while ((path = strchr(path, '/')) != NULL) {
+               max_depth--;
+               if (max_depth < 0)
+                       return 0;
+               path++;
+       }
+       return 1;
+}
+
+/*
+ * Return non-zero if name is a subdirectory of match and is not too deep.
+ */
+static int is_subdir(const char *name, int namelen,
+               const char *match, int matchlen, int max_depth)
+{
+       if (matchlen > namelen || strncmp(name, match, matchlen))
+               return 0;
+
+       if (name[matchlen] == '\0') /* exact match */
+               return 1;
+
+       if (!matchlen || match[matchlen-1] == '/' || name[matchlen] == '/')
+               return accept_subdir(name + matchlen + 1, max_depth);
+
+       return 0;
+}
+
+/*
+ * 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 max_depth)
+{
+       int namelen, i;
+       if (!paths || !*paths)
+               return accept_subdir(name, max_depth);
+       namelen = strlen(name);
+       for (i = 0; paths[i]; i++) {
+               const char *match = paths[i];
+               int matchlen = strlen(match);
+               const char *cp, *meta;
+
+               if (is_subdir(name, namelen, match, matchlen, max_depth))
+                       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;
+}
+
+static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
+{
+       void *data;
+
+       if (use_threads) {
+               read_sha1_lock();
+               data = read_sha1_file(sha1, type, size);
+               read_sha1_unlock();
+       } else {
+               data = read_sha1_file(sha1, type, size);
+       }
+       return data;
+}
+
+static void *load_sha1(const unsigned char *sha1, unsigned long *size,
+                      const char *name)
+{
+       enum object_type type;
+       void *data = lock_and_read_sha1_file(sha1, &type, size);
+
+       if (!data)
+               error("'%s': unable to read %s", name, sha1_to_hex(sha1));
+
+       return data;
+}
+
+static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
+                    const char *filename, int tree_name_len)
+{
+       struct strbuf pathbuf = STRBUF_INIT;
+       char *name;
+
+       if (opt->relative && opt->prefix_length) {
+               quote_path_relative(filename + tree_name_len, -1, &pathbuf,
+                                   opt->prefix);
+               strbuf_insert(&pathbuf, 0, filename, tree_name_len);
+       } else {
+               strbuf_addstr(&pathbuf, filename);
+       }
+
+       name = strbuf_detach(&pathbuf, NULL);
+
+#ifndef NO_PTHREADS
+       if (use_threads) {
+               grep_sha1_async(opt, name, sha1);
+               return 0;
+       } else
+#endif
+       {
+               int hit;
+               unsigned long sz;
+               void *data = load_sha1(sha1, &sz, name);
+               if (!data)
+                       hit = 0;
+               else
+                       hit = grep_buffer(opt, name, data, sz);
+
+               free(data);
+               free(name);
+               return hit;
+       }
+}
+
+static void *load_file(const char *filename, size_t *sz)
+{
+       struct stat st;
+       char *data;
+       int i;
+
+       if (lstat(filename, &st) < 0) {
+       err_ret:
+               if (errno != ENOENT)
+                       error("'%s': %s", filename, strerror(errno));
+               return 0;
+       }
+       if (!S_ISREG(st.st_mode))
+               return 0;
+       *sz = xsize_t(st.st_size);
+       i = open(filename, O_RDONLY);
+       if (i < 0)
+               goto err_ret;
+       data = xmalloc(*sz + 1);
+       if (st.st_size != read_in_full(i, data, *sz)) {
+               error("'%s': short read %s", filename, strerror(errno));
+               close(i);
+               free(data);
+               return 0;
+       }
+       close(i);
+       data[*sz] = 0;
+       return data;
+}
+
+static int grep_file(struct grep_opt *opt, const char *filename)
+{
+       struct strbuf buf = STRBUF_INIT;
+       char *name;
+
+       if (opt->relative && opt->prefix_length)
+               quote_path_relative(filename, -1, &buf, opt->prefix);
+       else
+               strbuf_addstr(&buf, filename);
+       name = strbuf_detach(&buf, NULL);
+
+#ifndef NO_PTHREADS
+       if (use_threads) {
+               grep_file_async(opt, name, filename);
+               return 0;
+       } else
+#endif
+       {
+               int hit;
+               size_t sz;
+               void *data = load_file(filename, &sz);
+               if (!data)
+                       hit = 0;
+               else
+                       hit = grep_buffer(opt, name, data, sz);
+
+               free(data);
+               free(name);
+               return hit;
+       }
+}
+
+static void append_path(struct grep_opt *opt, const void *data, size_t len)
+{
+       struct string_list *path_list = opt->output_priv;
+
+       if (len == 1 && *(const char *)data == '\0')
+               return;
+       string_list_append(path_list, xstrndup(data, len));
+}
+
+static void run_pager(struct grep_opt *opt, const char *prefix)
+{
+       struct string_list *path_list = opt->output_priv;
+       const char **argv = xmalloc(sizeof(const char *) * (path_list->nr + 1));
+       int i, status;
+
+       for (i = 0; i < path_list->nr; i++)
+               argv[i] = path_list->items[i].string;
+       argv[path_list->nr] = NULL;
+
+       if (prefix && chdir(prefix))
+               die("Failed to chdir: %s", prefix);
+       status = run_command_v_opt(argv, RUN_USING_SHELL);
+       if (status)
+               exit(status);
+       free(argv);
+}
+
+static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
+{
+       int hit = 0;
+       int nr;
+       read_cache();
+
+       for (nr = 0; nr < active_nr; nr++) {
+               struct cache_entry *ce = active_cache[nr];
+               if (!S_ISREG(ce->ce_mode))
+                       continue;
+               if (!pathspec_matches(paths, ce->name, opt->max_depth))
+                       continue;
+               /*
+                * If CE_VALID is on, we assume worktree file and its cache entry
+                * are identical, even if worktree file has been modified, so use
+                * cache version instead
+                */
+               if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) {
+                       if (ce_stage(ce))
+                               continue;
+                       hit |= grep_sha1(opt, ce->sha1, ce->name, 0);
+               }
+               else
+                       hit |= grep_file(opt, ce->name);
+               if (ce_stage(ce)) {
+                       do {
+                               nr++;
+                       } while (nr < active_nr &&
+                                !strcmp(ce->name, active_cache[nr]->name));
+                       nr--; /* compensate for loop control */
+               }
+               if (hit && opt->status_only)
+                       break;
+       }
+       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;
+       int tn_len = strlen(tree_name);
+       struct strbuf pathbuf;
+
+       strbuf_init(&pathbuf, PATH_MAX + tn_len);
+
+       if (tn_len) {
+               strbuf_add(&pathbuf, tree_name, tn_len);
+               strbuf_addch(&pathbuf, ':');
+               tn_len = pathbuf.len;
+       }
+       strbuf_addstr(&pathbuf, base);
+       len = pathbuf.len;
+
+       while (tree_entry(tree, &entry)) {
+               int te_len = tree_entry_len(entry.path, entry.sha1);
+               pathbuf.len = len;
+               strbuf_add(&pathbuf, entry.path, te_len);
+
+               if (S_ISDIR(entry.mode))
+                       /* Match "abc/" against pathspec to
+                        * decide if we want to descend into "abc"
+                        * directory.
+                        */
+                       strbuf_addch(&pathbuf, '/');
+
+               down = pathbuf.buf + tn_len;
+               if (!pathspec_matches(paths, down, opt->max_depth))
+                       ;
+               else if (S_ISREG(entry.mode))
+                       hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len);
+               else if (S_ISDIR(entry.mode)) {
+                       enum object_type type;
+                       struct tree_desc sub;
+                       void *data;
+                       unsigned long size;
+
+                       data = lock_and_read_sha1_file(entry.sha1, &type, &size);
+                       if (!data)
+                               die("unable to read tree (%s)",
+                                   sha1_to_hex(entry.sha1));
+                       init_tree_desc(&sub, data, size);
+                       hit |= grep_tree(opt, paths, &sub, tree_name, down);
+                       free(data);
+               }
+               if (hit && opt->status_only)
+                       break;
+       }
+       strbuf_release(&pathbuf);
+       return hit;
+}
+
+static int grep_object(struct grep_opt *opt, const char **paths,
+                      struct object *obj, const char *name)
+{
+       if (obj->type == OBJ_BLOB)
+               return grep_sha1(opt, obj->sha1, name, 0);
+       if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) {
+               struct tree_desc tree;
+               void *data;
+               unsigned long size;
+               int hit;
+               data = read_object_with_reference(obj->sha1, tree_type,
+                                                 &size, NULL);
+               if (!data)
+                       die("unable to read tree (%s)", sha1_to_hex(obj->sha1));
+               init_tree_desc(&tree, data, size);
+               hit = grep_tree(opt, paths, &tree, name, "");
+               free(data);
+               return hit;
+       }
+       die("unable to grep from object of type %s", typename(obj->type));
+}
+
+static int grep_objects(struct grep_opt *opt, const char **paths,
+                       const struct object_array *list)
+{
+       unsigned int i;
+       int hit = 0;
+       const unsigned int nr = list->nr;
+
+       for (i = 0; i < nr; i++) {
+               struct object *real_obj;
+               real_obj = deref_tag(list->objects[i].item, NULL, 0);
+               if (grep_object(opt, paths, real_obj, list->objects[i].name)) {
+                       hit = 1;
+                       if (opt->status_only)
+                               break;
+               }
+       }
+       return hit;
+}
+
+static int grep_directory(struct grep_opt *opt, const char **paths)
+{
+       struct dir_struct dir;
+       int i, hit = 0;
+
+       memset(&dir, 0, sizeof(dir));
+       setup_standard_excludes(&dir);
+
+       fill_directory(&dir, paths);
+       for (i = 0; i < dir.nr; i++) {
+               hit |= grep_file(opt, dir.entries[i]->name);
+               if (hit && opt->status_only)
+                       break;
+       }
+       return hit;
+}
+
+static int context_callback(const struct option *opt, const char *arg,
+                           int unset)
+{
+       struct grep_opt *grep_opt = opt->value;
+       int value;
+       const char *endp;
+
+       if (unset) {
+               grep_opt->pre_context = grep_opt->post_context = 0;
+               return 0;
+       }
+       value = strtol(arg, (char **)&endp, 10);
+       if (*endp) {
+               return error("switch `%c' expects a numerical value",
+                            opt->short_name);
+       }
+       grep_opt->pre_context = grep_opt->post_context = value;
+       return 0;
+}
+
+static int file_callback(const struct option *opt, const char *arg, int unset)
+{
+       struct grep_opt *grep_opt = opt->value;
+       FILE *patterns;
+       int lno = 0;
+       struct strbuf sb = STRBUF_INIT;
+
+       patterns = fopen(arg, "r");
+       if (!patterns)
+               die_errno("cannot open '%s'", arg);
+       while (strbuf_getline(&sb, patterns, '\n') == 0) {
+               char *s;
+               size_t len;
+
+               /* ignore empty line like grep does */
+               if (sb.len == 0)
+                       continue;
+
+               s = strbuf_detach(&sb, &len);
+               append_grep_pat(grep_opt, s, len, arg, ++lno, GREP_PATTERN);
+       }
+       fclose(patterns);
+       strbuf_release(&sb);
+       return 0;
+}
+
+static int not_callback(const struct option *opt, const char *arg, int unset)
+{
+       struct grep_opt *grep_opt = opt->value;
+       append_grep_pattern(grep_opt, "--not", "command line", 0, GREP_NOT);
+       return 0;
+}
+
+static int and_callback(const struct option *opt, const char *arg, int unset)
+{
+       struct grep_opt *grep_opt = opt->value;
+       append_grep_pattern(grep_opt, "--and", "command line", 0, GREP_AND);
+       return 0;
+}
+
+static int open_callback(const struct option *opt, const char *arg, int unset)
+{
+       struct grep_opt *grep_opt = opt->value;
+       append_grep_pattern(grep_opt, "(", "command line", 0, GREP_OPEN_PAREN);
+       return 0;
+}
+
+static int close_callback(const struct option *opt, const char *arg, int unset)
+{
+       struct grep_opt *grep_opt = opt->value;
+       append_grep_pattern(grep_opt, ")", "command line", 0, GREP_CLOSE_PAREN);
+       return 0;
+}
+
+static int pattern_callback(const struct option *opt, const char *arg,
+                           int unset)
+{
+       struct grep_opt *grep_opt = opt->value;
+       append_grep_pattern(grep_opt, arg, "-e option", 0, GREP_PATTERN);
+       return 0;
+}
+
+static int help_callback(const struct option *opt, const char *arg, int unset)
+{
+       return -1;
+}
+
+int cmd_grep(int argc, const char **argv, const char *prefix)
+{
+       int hit = 0;
+       int cached = 0;
+       int seen_dashdash = 0;
+       int external_grep_allowed__ignored;
+       const char *show_in_pager = NULL, *default_pager = "dummy";
+       struct grep_opt opt;
+       struct object_array list = OBJECT_ARRAY_INIT;
+       const char **paths = NULL;
+       struct string_list path_list = STRING_LIST_INIT_NODUP;
+       int i;
+       int dummy;
+       int use_index = 1;
+       struct option options[] = {
+               OPT_BOOLEAN(0, "cached", &cached,
+                       "search in index instead of in the work tree"),
+               OPT_BOOLEAN(0, "index", &use_index,
+                       "--no-index finds in contents not managed by git"),
+               OPT_GROUP(""),
+               OPT_BOOLEAN('v', "invert-match", &opt.invert,
+                       "show non-matching lines"),
+               OPT_BOOLEAN('i', "ignore-case", &opt.ignore_case,
+                       "case insensitive matching"),
+               OPT_BOOLEAN('w', "word-regexp", &opt.word_regexp,
+                       "match patterns only at word boundaries"),
+               OPT_SET_INT('a', "text", &opt.binary,
+                       "process binary files as text", GREP_BINARY_TEXT),
+               OPT_SET_INT('I', NULL, &opt.binary,
+                       "don't match patterns in binary files",
+                       GREP_BINARY_NOMATCH),
+               { OPTION_INTEGER, 0, "max-depth", &opt.max_depth, "depth",
+                       "descend at most <depth> levels", PARSE_OPT_NONEG,
+                       NULL, 1 },
+               OPT_GROUP(""),
+               OPT_BIT('E', "extended-regexp", &opt.regflags,
+                       "use extended POSIX regular expressions", REG_EXTENDED),
+               OPT_NEGBIT('G', "basic-regexp", &opt.regflags,
+                       "use basic POSIX regular expressions (default)",
+                       REG_EXTENDED),
+               OPT_BOOLEAN('F', "fixed-strings", &opt.fixed,
+                       "interpret patterns as fixed strings"),
+               OPT_GROUP(""),
+               OPT_BOOLEAN('n', NULL, &opt.linenum, "show line numbers"),
+               OPT_NEGBIT('h', NULL, &opt.pathname, "don't show filenames", 1),
+               OPT_BIT('H', NULL, &opt.pathname, "show filenames", 1),
+               OPT_NEGBIT(0, "full-name", &opt.relative,
+                       "show filenames relative to top directory", 1),
+               OPT_BOOLEAN('l', "files-with-matches", &opt.name_only,
+                       "show only filenames instead of matching lines"),
+               OPT_BOOLEAN(0, "name-only", &opt.name_only,
+                       "synonym for --files-with-matches"),
+               OPT_BOOLEAN('L', "files-without-match",
+                       &opt.unmatch_name_only,
+                       "show only the names of files without match"),
+               OPT_BOOLEAN('z', "null", &opt.null_following_name,
+                       "print NUL after filenames"),
+               OPT_BOOLEAN('c', "count", &opt.count,
+                       "show the number of matches instead of matching lines"),
+               OPT__COLOR(&opt.color, "highlight matches"),
+               OPT_GROUP(""),
+               OPT_CALLBACK('C', NULL, &opt, "n",
+                       "show <n> context lines before and after matches",
+                       context_callback),
+               OPT_INTEGER('B', NULL, &opt.pre_context,
+                       "show <n> context lines before matches"),
+               OPT_INTEGER('A', NULL, &opt.post_context,
+                       "show <n> context lines after matches"),
+               OPT_NUMBER_CALLBACK(&opt, "shortcut for -C NUM",
+                       context_callback),
+               OPT_BOOLEAN('p', "show-function", &opt.funcname,
+                       "show a line with the function name before matches"),
+               OPT_GROUP(""),
+               OPT_CALLBACK('f', NULL, &opt, "file",
+                       "read patterns from file", file_callback),
+               { OPTION_CALLBACK, 'e', NULL, &opt, "pattern",
+                       "match <pattern>", PARSE_OPT_NONEG, pattern_callback },
+               { OPTION_CALLBACK, 0, "and", &opt, NULL,
+                 "combine patterns specified with -e",
+                 PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback },
+               OPT_BOOLEAN(0, "or", &dummy, ""),
+               { OPTION_CALLBACK, 0, "not", &opt, NULL, "",
+                 PARSE_OPT_NOARG | PARSE_OPT_NONEG, not_callback },
+               { OPTION_CALLBACK, '(', NULL, &opt, NULL, "",
+                 PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
+                 open_callback },
+               { OPTION_CALLBACK, ')', NULL, &opt, NULL, "",
+                 PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
+                 close_callback },
+               OPT_BOOLEAN('q', "quiet", &opt.status_only,
+                           "indicate hit with exit status without output"),
+               OPT_BOOLEAN(0, "all-match", &opt.all_match,
+                       "show only matches from files that match all patterns"),
+               OPT_GROUP(""),
+               { OPTION_STRING, 'O', "open-files-in-pager", &show_in_pager,
+                       "pager", "show matching files in the pager",
+                       PARSE_OPT_OPTARG, NULL, (intptr_t)default_pager },
+               OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed__ignored,
+                           "allow calling of grep(1) (ignored by this build)"),
+               { OPTION_CALLBACK, 0, "help-all", &options, NULL, "show usage",
+                 PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback },
+               OPT_END()
+       };
+
+       /*
+        * 'git grep -h', unlike 'git grep -h <pattern>', is a request
+        * to show usage information and exit.
+        */
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage_with_options(grep_usage, options);
+
+       memset(&opt, 0, sizeof(opt));
+       opt.prefix = prefix;
+       opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0;
+       opt.relative = 1;
+       opt.pathname = 1;
+       opt.pattern_tail = &opt.pattern_list;
+       opt.header_tail = &opt.header_list;
+       opt.regflags = REG_NEWLINE;
+       opt.max_depth = -1;
+
+       strcpy(opt.color_context, "");
+       strcpy(opt.color_filename, "");
+       strcpy(opt.color_function, "");
+       strcpy(opt.color_lineno, "");
+       strcpy(opt.color_match, GIT_COLOR_BOLD_RED);
+       strcpy(opt.color_selected, "");
+       strcpy(opt.color_sep, GIT_COLOR_CYAN);
+       opt.color = -1;
+       git_config(grep_config, &opt);
+       if (opt.color == -1)
+               opt.color = git_use_color_default;
+
+       /*
+        * If there is no -- then the paths must exist in the working
+        * tree.  If there is no explicit pattern specified with -e or
+        * -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
+        * unrecognized non option is the beginning of the refs list
+        * that continues up to the -- (if exists), and then paths.
+        */
+       argc = parse_options(argc, argv, prefix, options, grep_usage,
+                            PARSE_OPT_KEEP_DASHDASH |
+                            PARSE_OPT_STOP_AT_NON_OPTION |
+                            PARSE_OPT_NO_INTERNAL_HELP);
+
+       if (use_index && !startup_info->have_repository)
+               /* die the same way as if we did it at the beginning */
+               setup_git_directory();
+
+       /*
+        * skip a -- separator; we know it cannot be
+        * separating revisions from pathnames if
+        * we haven't even had any patterns yet
+        */
+       if (argc > 0 && !opt.pattern_list && !strcmp(argv[0], "--")) {
+               argv++;
+               argc--;
+       }
+
+       /* First unrecognized non-option token */
+       if (argc > 0 && !opt.pattern_list) {
+               append_grep_pattern(&opt, argv[0], "command line", 0,
+                                   GREP_PATTERN);
+               argv++;
+               argc--;
+       }
+
+       if (show_in_pager == default_pager)
+               show_in_pager = git_pager(1);
+       if (show_in_pager) {
+               opt.color = 0;
+               opt.name_only = 1;
+               opt.null_following_name = 1;
+               opt.output_priv = &path_list;
+               opt.output = append_path;
+               string_list_append(&path_list, show_in_pager);
+               use_threads = 0;
+       }
+
+       if (!opt.pattern_list)
+               die("no pattern given.");
+       if (!opt.fixed && opt.ignore_case)
+               opt.regflags |= REG_ICASE;
+       if ((opt.regflags != REG_NEWLINE) && opt.fixed)
+               die("cannot mix --fixed-strings and regexp");
+
+#ifndef NO_PTHREADS
+       if (online_cpus() == 1 || !grep_threads_ok(&opt))
+               use_threads = 0;
+
+       if (use_threads) {
+               if (opt.pre_context || opt.post_context)
+                       print_hunk_marks_between_files = 1;
+               start_threads(&opt);
+       }
+#else
+       use_threads = 0;
+#endif
+
+       compile_grep_patterns(&opt);
+
+       /* Check revs and then paths */
+       for (i = 0; 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);
+                       if (!object)
+                               die("bad object %s", arg);
+                       add_object_array(object, arg, &list);
+                       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 (show_in_pager && (cached || list.nr))
+               die("--open-files-in-pager only works on the worktree");
+
+       if (show_in_pager && opt.pattern_list && !opt.pattern_list->next) {
+               const char *pager = path_list.items[0].string;
+               int len = strlen(pager);
+
+               if (len > 4 && is_dir_sep(pager[len - 5]))
+                       pager += len - 4;
+
+               if (!strcmp("less", pager) || !strcmp("vi", pager)) {
+                       struct strbuf buf = STRBUF_INIT;
+                       strbuf_addf(&buf, "+/%s%s",
+                                       strcmp("less", pager) ? "" : "*",
+                                       opt.pattern_list->pattern);
+                       string_list_append(&path_list, buf.buf);
+                       strbuf_detach(&buf, NULL);
+               }
+       }
+
+       if (!show_in_pager)
+               setup_pager();
+
+
+       if (!use_index) {
+               if (cached)
+                       die("--cached cannot be used with --no-index.");
+               if (list.nr)
+                       die("--no-index cannot be used with revs.");
+               hit = grep_directory(&opt, paths);
+       } else if (!list.nr) {
+               if (!cached)
+                       setup_work_tree();
+
+               hit = grep_cache(&opt, paths, cached);
+       } else {
+               if (cached)
+                       die("both --cached and trees are given.");
+               hit = grep_objects(&opt, paths, &list);
+       }
+
+       if (use_threads)
+               hit |= wait_all();
+       if (hit && show_in_pager)
+               run_pager(&opt, prefix);
+       free_grep_patterns(&opt);
+       return !hit;
+}
diff --git a/builtin/hash-object.c b/builtin/hash-object.c
new file mode 100644 (file)
index 0000000..080af1a
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ * Copyright (C) Junio C Hamano, 2005
+ */
+#include "cache.h"
+#include "blob.h"
+#include "quote.h"
+#include "parse-options.h"
+#include "exec_cmd.h"
+
+static void hash_fd(int fd, const char *type, int write_object, const char *path)
+{
+       struct stat st;
+       unsigned char sha1[20];
+       if (fstat(fd, &st) < 0 ||
+           index_fd(sha1, fd, &st, write_object, type_from_string(type), path))
+               die(write_object
+                   ? "Unable to add %s to database"
+                   : "Unable to hash %s", path);
+       printf("%s\n", sha1_to_hex(sha1));
+       maybe_flush_or_die(stdout, "hash to stdout");
+}
+
+static void hash_object(const char *path, const char *type, int write_object,
+                       const char *vpath)
+{
+       int fd;
+       fd = open(path, O_RDONLY);
+       if (fd < 0)
+               die_errno("Cannot open '%s'", path);
+       hash_fd(fd, type, write_object, vpath);
+}
+
+static int no_filters;
+
+static void hash_stdin_paths(const char *type, int write_objects)
+{
+       struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
+
+       while (strbuf_getline(&buf, stdin, '\n') != EOF) {
+               if (buf.buf[0] == '"') {
+                       strbuf_reset(&nbuf);
+                       if (unquote_c_style(&nbuf, buf.buf, NULL))
+                               die("line is badly quoted");
+                       strbuf_swap(&buf, &nbuf);
+               }
+               hash_object(buf.buf, type, write_objects,
+                   no_filters ? NULL : buf.buf);
+       }
+       strbuf_release(&buf);
+       strbuf_release(&nbuf);
+}
+
+static const char * const hash_object_usage[] = {
+       "git hash-object [-t <type>] [-w] [--path=<file>|--no-filters] [--stdin] [--] <file>...",
+       "git hash-object  --stdin-paths < <list-of-paths>",
+       NULL
+};
+
+static const char *type;
+static int write_object;
+static int hashstdin;
+static int stdin_paths;
+static const char *vpath;
+
+static const struct option hash_object_options[] = {
+       OPT_STRING('t', NULL, &type, "type", "object type"),
+       OPT_BOOLEAN('w', NULL, &write_object, "write the object into the object database"),
+       OPT_BOOLEAN( 0 , "stdin", &hashstdin, "read the object from stdin"),
+       OPT_BOOLEAN( 0 , "stdin-paths", &stdin_paths, "read file names from stdin"),
+       OPT_BOOLEAN( 0 , "no-filters", &no_filters, "store file as is without filters"),
+       OPT_STRING( 0 , "path", &vpath, "file", "process file as it were from this path"),
+       OPT_END()
+};
+
+int cmd_hash_object(int argc, const char **argv, const char *prefix)
+{
+       int i;
+       int prefix_length = -1;
+       const char *errstr = NULL;
+
+       type = blob_type;
+
+       argc = parse_options(argc, argv, NULL, hash_object_options,
+                            hash_object_usage, 0);
+
+       if (write_object) {
+               prefix = setup_git_directory();
+               prefix_length = prefix ? strlen(prefix) : 0;
+               if (vpath && prefix)
+                       vpath = prefix_filename(prefix, prefix_length, vpath);
+       }
+
+       git_config(git_default_config, NULL);
+
+       if (stdin_paths) {
+               if (hashstdin)
+                       errstr = "Can't use --stdin-paths with --stdin";
+               else if (argc)
+                       errstr = "Can't specify files with --stdin-paths";
+               else if (vpath)
+                       errstr = "Can't use --stdin-paths with --path";
+       }
+       else {
+               if (hashstdin > 1)
+                       errstr = "Multiple --stdin arguments are not supported";
+               if (vpath && no_filters)
+                       errstr = "Can't use --path with --no-filters";
+       }
+
+       if (errstr) {
+               error("%s", errstr);
+               usage_with_options(hash_object_usage, hash_object_options);
+       }
+
+       if (hashstdin)
+               hash_fd(0, type, write_object, vpath);
+
+       for (i = 0 ; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (0 <= prefix_length)
+                       arg = prefix_filename(prefix, prefix_length, arg);
+               hash_object(arg, type, write_object,
+                           no_filters ? NULL : vpath ? vpath : arg);
+       }
+
+       if (stdin_paths)
+               hash_stdin_paths(type, write_object);
+
+       return 0;
+}
diff --git a/builtin/help.c b/builtin/help.c
new file mode 100644 (file)
index 0000000..61ff798
--- /dev/null
@@ -0,0 +1,466 @@
+/*
+ * builtin-help.c
+ *
+ * Builtin help command
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "exec_cmd.h"
+#include "common-cmds.h"
+#include "parse-options.h"
+#include "run-command.h"
+#include "help.h"
+
+static struct man_viewer_list {
+       struct man_viewer_list *next;
+       char name[FLEX_ARRAY];
+} *man_viewer_list;
+
+static struct man_viewer_info_list {
+       struct man_viewer_info_list *next;
+       const char *info;
+       char name[FLEX_ARRAY];
+} *man_viewer_info_list;
+
+enum help_format {
+       HELP_FORMAT_NONE,
+       HELP_FORMAT_MAN,
+       HELP_FORMAT_INFO,
+       HELP_FORMAT_WEB
+};
+
+static int show_all = 0;
+static enum help_format help_format = HELP_FORMAT_NONE;
+static struct option builtin_help_options[] = {
+       OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
+       OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
+       OPT_SET_INT('w', "web", &help_format, "show manual in web browser",
+                       HELP_FORMAT_WEB),
+       OPT_SET_INT('i', "info", &help_format, "show info page",
+                       HELP_FORMAT_INFO),
+       OPT_END(),
+};
+
+static const char * const builtin_help_usage[] = {
+       "git help [--all] [--man|--web|--info] [command]",
+       NULL
+};
+
+static enum help_format parse_help_format(const char *format)
+{
+       if (!strcmp(format, "man"))
+               return HELP_FORMAT_MAN;
+       if (!strcmp(format, "info"))
+               return HELP_FORMAT_INFO;
+       if (!strcmp(format, "web") || !strcmp(format, "html"))
+               return HELP_FORMAT_WEB;
+       die("unrecognized help format '%s'", format);
+}
+
+static const char *get_man_viewer_info(const char *name)
+{
+       struct man_viewer_info_list *viewer;
+
+       for (viewer = man_viewer_info_list; viewer; viewer = viewer->next)
+       {
+               if (!strcasecmp(name, viewer->name))
+                       return viewer->info;
+       }
+       return NULL;
+}
+
+static int check_emacsclient_version(void)
+{
+       struct strbuf buffer = STRBUF_INIT;
+       struct child_process ec_process;
+       const char *argv_ec[] = { "emacsclient", "--version", NULL };
+       int version;
+
+       /* emacsclient prints its version number on stderr */
+       memset(&ec_process, 0, sizeof(ec_process));
+       ec_process.argv = argv_ec;
+       ec_process.err = -1;
+       ec_process.stdout_to_stderr = 1;
+       if (start_command(&ec_process))
+               return error("Failed to start emacsclient.");
+
+       strbuf_read(&buffer, ec_process.err, 20);
+       close(ec_process.err);
+
+       /*
+        * Don't bother checking return value, because "emacsclient --version"
+        * seems to always exits with code 1.
+        */
+       finish_command(&ec_process);
+
+       if (prefixcmp(buffer.buf, "emacsclient")) {
+               strbuf_release(&buffer);
+               return error("Failed to parse emacsclient version.");
+       }
+
+       strbuf_remove(&buffer, 0, strlen("emacsclient"));
+       version = atoi(buffer.buf);
+
+       if (version < 22) {
+               strbuf_release(&buffer);
+               return error("emacsclient version '%d' too old (< 22).",
+                       version);
+       }
+
+       strbuf_release(&buffer);
+       return 0;
+}
+
+static void exec_woman_emacs(const char *path, const char *page)
+{
+       if (!check_emacsclient_version()) {
+               /* This works only with emacsclient version >= 22. */
+               struct strbuf man_page = STRBUF_INIT;
+
+               if (!path)
+                       path = "emacsclient";
+               strbuf_addf(&man_page, "(woman \"%s\")", page);
+               execlp(path, "emacsclient", "-e", man_page.buf, (char *)NULL);
+               warning("failed to exec '%s': %s", path, strerror(errno));
+       }
+}
+
+static void exec_man_konqueror(const char *path, const char *page)
+{
+       const char *display = getenv("DISPLAY");
+       if (display && *display) {
+               struct strbuf man_page = STRBUF_INIT;
+               const char *filename = "kfmclient";
+
+               /* It's simpler to launch konqueror using kfmclient. */
+               if (path) {
+                       const char *file = strrchr(path, '/');
+                       if (file && !strcmp(file + 1, "konqueror")) {
+                               char *new = xstrdup(path);
+                               char *dest = strrchr(new, '/');
+
+                               /* strlen("konqueror") == strlen("kfmclient") */
+                               strcpy(dest + 1, "kfmclient");
+                               path = new;
+                       }
+                       if (file)
+                               filename = file;
+               } else
+                       path = "kfmclient";
+               strbuf_addf(&man_page, "man:%s(1)", page);
+               execlp(path, filename, "newTab", man_page.buf, (char *)NULL);
+               warning("failed to exec '%s': %s", path, strerror(errno));
+       }
+}
+
+static void exec_man_man(const char *path, const char *page)
+{
+       if (!path)
+               path = "man";
+       execlp(path, "man", page, (char *)NULL);
+       warning("failed to exec '%s': %s", path, strerror(errno));
+}
+
+static void exec_man_cmd(const char *cmd, const char *page)
+{
+       struct strbuf shell_cmd = STRBUF_INIT;
+       strbuf_addf(&shell_cmd, "%s %s", cmd, page);
+       execl("/bin/sh", "sh", "-c", shell_cmd.buf, (char *)NULL);
+       warning("failed to exec '%s': %s", cmd, strerror(errno));
+}
+
+static void add_man_viewer(const char *name)
+{
+       struct man_viewer_list **p = &man_viewer_list;
+       size_t len = strlen(name);
+
+       while (*p)
+               p = &((*p)->next);
+       *p = xcalloc(1, (sizeof(**p) + len + 1));
+       strncpy((*p)->name, name, len);
+}
+
+static int supported_man_viewer(const char *name, size_t len)
+{
+       return (!strncasecmp("man", name, len) ||
+               !strncasecmp("woman", name, len) ||
+               !strncasecmp("konqueror", name, len));
+}
+
+static void do_add_man_viewer_info(const char *name,
+                                  size_t len,
+                                  const char *value)
+{
+       struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1);
+
+       strncpy(new->name, name, len);
+       new->info = xstrdup(value);
+       new->next = man_viewer_info_list;
+       man_viewer_info_list = new;
+}
+
+static int add_man_viewer_path(const char *name,
+                              size_t len,
+                              const char *value)
+{
+       if (supported_man_viewer(name, len))
+               do_add_man_viewer_info(name, len, value);
+       else
+               warning("'%s': path for unsupported man viewer.\n"
+                       "Please consider using 'man.<tool>.cmd' instead.",
+                       name);
+
+       return 0;
+}
+
+static int add_man_viewer_cmd(const char *name,
+                             size_t len,
+                             const char *value)
+{
+       if (supported_man_viewer(name, len))
+               warning("'%s': cmd for supported man viewer.\n"
+                       "Please consider using 'man.<tool>.path' instead.",
+                       name);
+       else
+               do_add_man_viewer_info(name, len, value);
+
+       return 0;
+}
+
+static int add_man_viewer_info(const char *var, const char *value)
+{
+       const char *name = var + 4;
+       const char *subkey = strrchr(name, '.');
+
+       if (!subkey)
+               return 0;
+
+       if (!strcmp(subkey, ".path")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               return add_man_viewer_path(name, subkey - name, value);
+       }
+       if (!strcmp(subkey, ".cmd")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               return add_man_viewer_cmd(name, subkey - name, value);
+       }
+
+       return 0;
+}
+
+static int git_help_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "help.format")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               help_format = parse_help_format(value);
+               return 0;
+       }
+       if (!strcmp(var, "man.viewer")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               add_man_viewer(value);
+               return 0;
+       }
+       if (!prefixcmp(var, "man."))
+               return add_man_viewer_info(var, value);
+
+       return git_default_config(var, value, cb);
+}
+
+static struct cmdnames main_cmds, other_cmds;
+
+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));
+               puts(common_cmds[i].help);
+       }
+}
+
+static int is_git_command(const char *s)
+{
+       return is_in_cmdlist(&main_cmds, s) ||
+               is_in_cmdlist(&other_cmds, s);
+}
+
+static const char *prepend(const char *prefix, const char *cmd)
+{
+       size_t pre_len = strlen(prefix);
+       size_t cmd_len = strlen(cmd);
+       char *p = xmalloc(pre_len + cmd_len + 1);
+       memcpy(p, prefix, pre_len);
+       strcpy(p + pre_len, cmd);
+       return p;
+}
+
+static const char *cmd_to_page(const char *git_cmd)
+{
+       if (!git_cmd)
+               return "git";
+       else if (!prefixcmp(git_cmd, "git"))
+               return git_cmd;
+       else if (is_git_command(git_cmd))
+               return prepend("git-", git_cmd);
+       else
+               return prepend("git", git_cmd);
+}
+
+static void setup_man_path(void)
+{
+       struct strbuf new_path = STRBUF_INIT;
+       const char *old_path = getenv("MANPATH");
+
+       /* We should always put ':' after our path. If there is no
+        * old_path, the ':' at the end will let 'man' to try
+        * system-wide paths after ours to find the manual page. If
+        * there is old_path, we need ':' as delimiter. */
+       strbuf_addstr(&new_path, system_path(GIT_MAN_PATH));
+       strbuf_addch(&new_path, ':');
+       if (old_path)
+               strbuf_addstr(&new_path, old_path);
+
+       setenv("MANPATH", new_path.buf, 1);
+
+       strbuf_release(&new_path);
+}
+
+static void exec_viewer(const char *name, const char *page)
+{
+       const char *info = get_man_viewer_info(name);
+
+       if (!strcasecmp(name, "man"))
+               exec_man_man(info, page);
+       else if (!strcasecmp(name, "woman"))
+               exec_woman_emacs(info, page);
+       else if (!strcasecmp(name, "konqueror"))
+               exec_man_konqueror(info, page);
+       else if (info)
+               exec_man_cmd(info, page);
+       else
+               warning("'%s': unknown man viewer.", name);
+}
+
+static void show_man_page(const char *git_cmd)
+{
+       struct man_viewer_list *viewer;
+       const char *page = cmd_to_page(git_cmd);
+       const char *fallback = getenv("GIT_MAN_VIEWER");
+
+       setup_man_path();
+       for (viewer = man_viewer_list; viewer; viewer = viewer->next)
+       {
+               exec_viewer(viewer->name, page); /* will return when unable */
+       }
+       if (fallback)
+               exec_viewer(fallback, page);
+       exec_viewer("man", page);
+       die("no man viewer handled the request");
+}
+
+static void show_info_page(const char *git_cmd)
+{
+       const char *page = cmd_to_page(git_cmd);
+       setenv("INFOPATH", system_path(GIT_INFO_PATH), 1);
+       execlp("info", "info", "gitman", page, (char *)NULL);
+       die("no info viewer handled the request");
+}
+
+static void get_html_page_path(struct strbuf *page_path, const char *page)
+{
+       struct stat st;
+       const char *html_path = system_path(GIT_HTML_PATH);
+
+       /* Check that we have a git documentation directory. */
+       if (stat(mkpath("%s/git.html", html_path), &st)
+           || !S_ISREG(st.st_mode))
+               die("'%s': not a documentation directory.", html_path);
+
+       strbuf_init(page_path, 0);
+       strbuf_addf(page_path, "%s/%s.html", html_path, page);
+}
+
+/*
+ * If open_html is not defined in a platform-specific way (see for
+ * example compat/mingw.h), we use the script web--browse to display
+ * HTML.
+ */
+#ifndef open_html
+static void open_html(const char *path)
+{
+       execl_git_cmd("web--browse", "-c", "help.browser", path, (char *)NULL);
+}
+#endif
+
+static void show_html_page(const char *git_cmd)
+{
+       const char *page = cmd_to_page(git_cmd);
+       struct strbuf page_path; /* it leaks but we exec bellow */
+
+       get_html_page_path(&page_path, page);
+
+       open_html(page_path.buf);
+}
+
+int cmd_help(int argc, const char **argv, const char *prefix)
+{
+       int nongit;
+       const char *alias;
+       enum help_format parsed_help_format;
+       load_command_list("git-", &main_cmds, &other_cmds);
+
+       argc = parse_options(argc, argv, prefix, builtin_help_options,
+                       builtin_help_usage, 0);
+       parsed_help_format = help_format;
+
+       if (show_all) {
+               printf("usage: %s\n\n", git_usage_string);
+               list_commands("git commands", &main_cmds, &other_cmds);
+               printf("%s\n", git_more_info_string);
+               return 0;
+       }
+
+       if (!argv[0]) {
+               printf("usage: %s\n\n", git_usage_string);
+               list_common_cmds_help();
+               printf("\n%s\n", git_more_info_string);
+               return 0;
+       }
+
+       setup_git_directory_gently(&nongit);
+       git_config(git_help_config, NULL);
+
+       if (parsed_help_format != HELP_FORMAT_NONE)
+               help_format = parsed_help_format;
+
+       alias = alias_lookup(argv[0]);
+       if (alias && !is_git_command(argv[0])) {
+               printf("`git %s' is aliased to `%s'\n", argv[0], alias);
+               return 0;
+       }
+
+       switch (help_format) {
+       case HELP_FORMAT_NONE:
+       case HELP_FORMAT_MAN:
+               show_man_page(argv[0]);
+               break;
+       case HELP_FORMAT_INFO:
+               show_info_page(argv[0]);
+               break;
+       case HELP_FORMAT_WEB:
+               show_html_page(argv[0]);
+               break;
+       }
+
+       return 0;
+}
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
new file mode 100644 (file)
index 0000000..2e680d7
--- /dev/null
@@ -0,0 +1,1032 @@
+#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"
+#include "progress.h"
+#include "fsck.h"
+#include "exec_cmd.h"
+
+static const char index_pack_usage[] =
+"git index-pack [-v] [-o <index-file>] [{ --keep | --keep=<msg> }] [--strict] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }";
+
+struct object_entry
+{
+       struct pack_idx_entry idx;
+       unsigned long size;
+       unsigned int hdr_size;
+       enum object_type type;
+       enum object_type real_type;
+};
+
+union delta_base {
+       unsigned char sha1[20];
+       off_t offset;
+};
+
+struct base_data {
+       struct base_data *base;
+       struct base_data *child;
+       struct object_entry *obj;
+       void *data;
+       unsigned long size;
+};
+
+/*
+ * Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want
+ * to memcmp() only the first 20 bytes.
+ */
+#define UNION_BASE_SZ  20
+
+#define FLAG_LINK (1u<<20)
+#define FLAG_CHECKED (1u<<21)
+
+struct delta_entry
+{
+       union delta_base base;
+       int obj_no;
+};
+
+static struct object_entry *objects;
+static struct delta_entry *deltas;
+static struct base_data *base_cache;
+static size_t base_cache_used;
+static int nr_objects;
+static int nr_deltas;
+static int nr_resolved_deltas;
+
+static int from_stdin;
+static int strict;
+static int verbose;
+
+static struct progress *progress;
+
+/* We always read in 4kB chunks. */
+static unsigned char input_buffer[4096];
+static unsigned int input_offset, input_len;
+static off_t consumed_bytes;
+static git_SHA_CTX input_ctx;
+static uint32_t input_crc32;
+static int input_fd, output_fd, pack_fd;
+
+static int mark_link(struct object *obj, int type, void *data)
+{
+       if (!obj)
+               return -1;
+
+       if (type != OBJ_ANY && obj->type != type)
+               die("object type mismatch at %s", sha1_to_hex(obj->sha1));
+
+       obj->flags |= FLAG_LINK;
+       return 0;
+}
+
+/* The content of each linked object must have been checked
+   or it must be already present in the object database */
+static void check_object(struct object *obj)
+{
+       if (!obj)
+               return;
+
+       if (!(obj->flags & FLAG_LINK))
+               return;
+
+       if (!(obj->flags & FLAG_CHECKED)) {
+               unsigned long size;
+               int type = sha1_object_info(obj->sha1, &size);
+               if (type != obj->type || type <= 0)
+                       die("object of unexpected type");
+               obj->flags |= FLAG_CHECKED;
+               return;
+       }
+}
+
+static void check_objects(void)
+{
+       unsigned i, max;
+
+       max = get_max_object_index();
+       for (i = 0; i < max; i++)
+               check_object(get_indexed_object(i));
+}
+
+
+/* Discard current buffer used content. */
+static void flush(void)
+{
+       if (input_offset) {
+               if (output_fd >= 0)
+                       write_or_die(output_fd, input_buffer, input_offset);
+               git_SHA1_Update(&input_ctx, input_buffer, input_offset);
+               memmove(input_buffer, input_buffer + input_offset, input_len);
+               input_offset = 0;
+       }
+}
+
+/*
+ * 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 <= input_len)
+               return input_buffer + input_offset;
+       if (min > sizeof(input_buffer))
+               die("cannot fill %d bytes", min);
+       flush();
+       do {
+               ssize_t ret = xread(input_fd, input_buffer + input_len,
+                               sizeof(input_buffer) - input_len);
+               if (ret <= 0) {
+                       if (!ret)
+                               die("early EOF");
+                       die_errno("read error on input");
+               }
+               input_len += ret;
+               if (from_stdin)
+                       display_throughput(progress, consumed_bytes + input_len);
+       } while (input_len < min);
+       return input_buffer;
+}
+
+static void use(int bytes)
+{
+       if (bytes > input_len)
+               die("used more bytes than were available");
+       input_crc32 = crc32(input_crc32, input_buffer + input_offset, bytes);
+       input_len -= bytes;
+       input_offset += bytes;
+
+       /* make sure off_t is sufficiently large not to wrap */
+       if (consumed_bytes > consumed_bytes + bytes)
+               die("pack too large for current definition of off_t");
+       consumed_bytes += bytes;
+}
+
+static const char *open_pack_file(const char *pack_name)
+{
+       if (from_stdin) {
+               input_fd = 0;
+               if (!pack_name) {
+                       static char tmpfile[PATH_MAX];
+                       output_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
+                                               "pack/tmp_pack_XXXXXX");
+                       pack_name = xstrdup(tmpfile);
+               } else
+                       output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
+               if (output_fd < 0)
+                       die_errno("unable to create '%s'", pack_name);
+               pack_fd = output_fd;
+       } else {
+               input_fd = open(pack_name, O_RDONLY);
+               if (input_fd < 0)
+                       die_errno("cannot open packfile '%s'", pack_name);
+               output_fd = -1;
+               pack_fd = input_fd;
+       }
+       git_SHA1_Init(&input_ctx);
+       return pack_name;
+}
+
+static void parse_pack_header(void)
+{
+       struct pack_header *hdr = fill(sizeof(struct pack_header));
+
+       /* Header consistency check */
+       if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
+               die("pack signature mismatch");
+       if (!pack_version_ok(hdr->hdr_version))
+               die("pack version %"PRIu32" unsupported",
+                       ntohl(hdr->hdr_version));
+
+       nr_objects = ntohl(hdr->hdr_entries);
+       use(sizeof(struct pack_header));
+}
+
+static NORETURN void bad_object(unsigned long offset, const char *format,
+                      ...) __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("pack has bad object at offset %lu: %s", offset, buf);
+}
+
+static void free_base_data(struct base_data *c)
+{
+       if (c->data) {
+               free(c->data);
+               c->data = NULL;
+               base_cache_used -= c->size;
+       }
+}
+
+static void prune_base_data(struct base_data *retain)
+{
+       struct base_data *b;
+       for (b = base_cache;
+            base_cache_used > delta_base_cache_limit && b;
+            b = b->child) {
+               if (b->data && b != retain)
+                       free_base_data(b);
+       }
+}
+
+static void link_base_data(struct base_data *base, struct base_data *c)
+{
+       if (base)
+               base->child = c;
+       else
+               base_cache = c;
+
+       c->base = base;
+       c->child = NULL;
+       if (c->data)
+               base_cache_used += c->size;
+       prune_base_data(c);
+}
+
+static void unlink_base_data(struct base_data *c)
+{
+       struct base_data *base = c->base;
+       if (base)
+               base->child = NULL;
+       else
+               base_cache = NULL;
+       free_base_data(c);
+}
+
+static void *unpack_entry_data(unsigned long offset, unsigned long size)
+{
+       int status;
+       z_stream stream;
+       void *buf = xmalloc(size);
+
+       memset(&stream, 0, sizeof(stream));
+       git_inflate_init(&stream);
+       stream.next_out = buf;
+       stream.avail_out = size;
+
+       do {
+               stream.next_in = fill(1);
+               stream.avail_in = input_len;
+               status = git_inflate(&stream, 0);
+               use(input_len - stream.avail_in);
+       } while (status == Z_OK);
+       if (stream.total_out != size || status != Z_STREAM_END)
+               bad_object(offset, "inflate returned %d", status);
+       git_inflate_end(&stream);
+       return buf;
+}
+
+static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_base)
+{
+       unsigned char *p;
+       unsigned long size, c;
+       off_t base_offset;
+       unsigned shift;
+       void *data;
+
+       obj->idx.offset = consumed_bytes;
+       input_crc32 = crc32(0, Z_NULL, 0);
+
+       p = fill(1);
+       c = *p;
+       use(1);
+       obj->type = (c >> 4) & 7;
+       size = (c & 15);
+       shift = 4;
+       while (c & 0x80) {
+               p = fill(1);
+               c = *p;
+               use(1);
+               size += (c & 0x7f) << shift;
+               shift += 7;
+       }
+       obj->size = size;
+
+       switch (obj->type) {
+       case OBJ_REF_DELTA:
+               hashcpy(delta_base->sha1, fill(20));
+               use(20);
+               break;
+       case OBJ_OFS_DELTA:
+               memset(delta_base, 0, sizeof(*delta_base));
+               p = fill(1);
+               c = *p;
+               use(1);
+               base_offset = c & 127;
+               while (c & 128) {
+                       base_offset += 1;
+                       if (!base_offset || MSB(base_offset, 7))
+                               bad_object(obj->idx.offset, "offset value overflow for delta base object");
+                       p = fill(1);
+                       c = *p;
+                       use(1);
+                       base_offset = (base_offset << 7) + (c & 127);
+               }
+               delta_base->offset = obj->idx.offset - base_offset;
+               if (delta_base->offset <= 0 || delta_base->offset >= obj->idx.offset)
+                       bad_object(obj->idx.offset, "delta base offset is out of bound");
+               break;
+       case OBJ_COMMIT:
+       case OBJ_TREE:
+       case OBJ_BLOB:
+       case OBJ_TAG:
+               break;
+       default:
+               bad_object(obj->idx.offset, "unknown object type %d", obj->type);
+       }
+       obj->hdr_size = consumed_bytes - obj->idx.offset;
+
+       data = unpack_entry_data(obj->idx.offset, obj->size);
+       obj->idx.crc32 = input_crc32;
+       return data;
+}
+
+static void *get_data_from_pack(struct object_entry *obj)
+{
+       off_t from = obj[0].idx.offset + obj[0].hdr_size;
+       unsigned long len = obj[1].idx.offset - from;
+       unsigned char *data, *inbuf;
+       z_stream stream;
+       int status;
+
+       data = xmalloc(obj->size);
+       inbuf = xmalloc((len < 64*1024) ? len : 64*1024);
+
+       memset(&stream, 0, sizeof(stream));
+       git_inflate_init(&stream);
+       stream.next_out = data;
+       stream.avail_out = obj->size;
+
+       do {
+               ssize_t n = (len < 64*1024) ? len : 64*1024;
+               n = pread(pack_fd, inbuf, n, from);
+               if (n < 0)
+                       die_errno("cannot pread pack file");
+               if (!n)
+                       die("premature end of pack file, %lu bytes missing", len);
+               from += n;
+               len -= n;
+               stream.next_in = inbuf;
+               stream.avail_in = n;
+               status = git_inflate(&stream, 0);
+       } while (len && status == Z_OK && !stream.avail_in);
+
+       /* This has been inflated OK when first encountered, so... */
+       if (status != Z_STREAM_END || stream.total_out != obj->size)
+               die("serious inflate inconsistency");
+
+       git_inflate_end(&stream);
+       free(inbuf);
+       return data;
+}
+
+static int find_delta(const union delta_base *base)
+{
+       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, &delta->base, UNION_BASE_SZ);
+                if (!cmp)
+                        return next;
+                if (cmp < 0) {
+                        last = next;
+                        continue;
+                }
+                first = next+1;
+        }
+        return -first-1;
+}
+
+static void find_delta_children(const union delta_base *base,
+                               int *first_index, int *last_index)
+{
+       int first = find_delta(base);
+       int last = first;
+       int end = nr_deltas - 1;
+
+       if (first < 0) {
+               *first_index = 0;
+               *last_index = -1;
+               return;
+       }
+       while (first > 0 && !memcmp(&deltas[first - 1].base, base, UNION_BASE_SZ))
+               --first;
+       while (last < end && !memcmp(&deltas[last + 1].base, base, UNION_BASE_SZ))
+               ++last;
+       *first_index = first;
+       *last_index = last;
+}
+
+static void sha1_object(const void *data, unsigned long size,
+                       enum object_type type, unsigned char *sha1)
+{
+       hash_sha1_file(data, size, typename(type), sha1);
+       if (has_sha1_file(sha1)) {
+               void *has_data;
+               enum object_type has_type;
+               unsigned long has_size;
+               has_data = read_sha1_file(sha1, &has_type, &has_size);
+               if (!has_data)
+                       die("cannot read existing object %s", sha1_to_hex(sha1));
+               if (size != has_size || type != has_type ||
+                   memcmp(data, has_data, size) != 0)
+                       die("SHA1 COLLISION FOUND WITH %s !", sha1_to_hex(sha1));
+               free(has_data);
+       }
+       if (strict) {
+               if (type == OBJ_BLOB) {
+                       struct blob *blob = lookup_blob(sha1);
+                       if (blob)
+                               blob->object.flags |= FLAG_CHECKED;
+                       else
+                               die("invalid blob object %s", sha1_to_hex(sha1));
+               } else {
+                       struct object *obj;
+                       int eaten;
+                       void *buf = (void *) data;
+
+                       /*
+                        * we do not need to free the memory here, as the
+                        * buf is deleted by the caller.
+                        */
+                       obj = parse_object_buffer(sha1, type, size, buf, &eaten);
+                       if (!obj)
+                               die("invalid %s", typename(type));
+                       if (fsck_object(obj, 1, fsck_error_function))
+                               die("Error in object");
+                       if (fsck_walk(obj, mark_link, NULL))
+                               die("Not all child objects of %s are reachable", sha1_to_hex(obj->sha1));
+
+                       if (obj->type == OBJ_TREE) {
+                               struct tree *item = (struct tree *) obj;
+                               item->buffer = NULL;
+                       }
+                       if (obj->type == OBJ_COMMIT) {
+                               struct commit *commit = (struct commit *) obj;
+                               commit->buffer = NULL;
+                       }
+                       obj->flags |= FLAG_CHECKED;
+               }
+       }
+}
+
+static void *get_base_data(struct base_data *c)
+{
+       if (!c->data) {
+               struct object_entry *obj = c->obj;
+
+               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
+                       void *base = get_base_data(c->base);
+                       void *raw = get_data_from_pack(obj);
+                       c->data = patch_delta(
+                               base, c->base->size,
+                               raw, obj->size,
+                               &c->size);
+                       free(raw);
+                       if (!c->data)
+                               bad_object(obj->idx.offset, "failed to apply delta");
+               } else {
+                       c->data = get_data_from_pack(obj);
+                       c->size = obj->size;
+               }
+
+               base_cache_used += c->size;
+               prune_base_data(c);
+       }
+       return c->data;
+}
+
+static void resolve_delta(struct object_entry *delta_obj,
+                         struct base_data *base, struct base_data *result)
+{
+       void *base_data, *delta_data;
+
+       delta_obj->real_type = base->obj->real_type;
+       delta_data = get_data_from_pack(delta_obj);
+       base_data = get_base_data(base);
+       result->obj = delta_obj;
+       result->data = patch_delta(base_data, base->size,
+                                  delta_data, delta_obj->size, &result->size);
+       free(delta_data);
+       if (!result->data)
+               bad_object(delta_obj->idx.offset, "failed to apply delta");
+       sha1_object(result->data, result->size, delta_obj->real_type,
+                   delta_obj->idx.sha1);
+       nr_resolved_deltas++;
+}
+
+static void find_unresolved_deltas(struct base_data *base,
+                                  struct base_data *prev_base)
+{
+       int i, ref_first, ref_last, ofs_first, ofs_last;
+
+       /*
+        * This is a recursive function. Those brackets should help reducing
+        * stack usage by limiting the scope of the delta_base union.
+        */
+       {
+               union delta_base base_spec;
+
+               hashcpy(base_spec.sha1, base->obj->idx.sha1);
+               find_delta_children(&base_spec, &ref_first, &ref_last);
+
+               memset(&base_spec, 0, sizeof(base_spec));
+               base_spec.offset = base->obj->idx.offset;
+               find_delta_children(&base_spec, &ofs_first, &ofs_last);
+       }
+
+       if (ref_last == -1 && ofs_last == -1) {
+               free(base->data);
+               return;
+       }
+
+       link_base_data(prev_base, base);
+
+       for (i = ref_first; i <= ref_last; i++) {
+               struct object_entry *child = objects + deltas[i].obj_no;
+               if (child->real_type == OBJ_REF_DELTA) {
+                       struct base_data result;
+                       resolve_delta(child, base, &result);
+                       if (i == ref_last && ofs_last == -1)
+                               free_base_data(base);
+                       find_unresolved_deltas(&result, base);
+               }
+       }
+
+       for (i = ofs_first; i <= ofs_last; i++) {
+               struct object_entry *child = objects + deltas[i].obj_no;
+               if (child->real_type == OBJ_OFS_DELTA) {
+                       struct base_data result;
+                       resolve_delta(child, base, &result);
+                       if (i == ofs_last)
+                               free_base_data(base);
+                       find_unresolved_deltas(&result, base);
+               }
+       }
+
+       unlink_base_data(base);
+}
+
+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, &delta_b->base, UNION_BASE_SZ);
+}
+
+/* Parse all objects and return the pack content SHA1 hash */
+static void parse_pack_objects(unsigned char *sha1)
+{
+       int i;
+       struct delta_entry *delta = deltas;
+       struct stat st;
+
+       /*
+        * First pass:
+        * - find locations of all objects;
+        * - calculate SHA1 of all non-delta objects;
+        * - remember base (SHA1 or offset) for all deltas.
+        */
+       if (verbose)
+               progress = start_progress(
+                               from_stdin ? "Receiving objects" : "Indexing objects",
+                               nr_objects);
+       for (i = 0; i < nr_objects; i++) {
+               struct object_entry *obj = &objects[i];
+               void *data = unpack_raw_entry(obj, &delta->base);
+               obj->real_type = obj->type;
+               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
+                       nr_deltas++;
+                       delta->obj_no = i;
+                       delta++;
+               } else
+                       sha1_object(data, obj->size, obj->type, obj->idx.sha1);
+               free(data);
+               display_progress(progress, i+1);
+       }
+       objects[i].idx.offset = consumed_bytes;
+       stop_progress(&progress);
+
+       /* Check pack integrity */
+       flush();
+       git_SHA1_Final(sha1, &input_ctx);
+       if (hashcmp(fill(20), sha1))
+               die("pack is corrupted (SHA1 mismatch)");
+       use(20);
+
+       /* If input_fd is a file, we should have reached its end now. */
+       if (fstat(input_fd, &st))
+               die_errno("cannot fstat packfile");
+       if (S_ISREG(st.st_mode) &&
+                       lseek(input_fd, 0, SEEK_CUR) - input_len != st.st_size)
+               die("pack has junk at the end");
+
+       if (!nr_deltas)
+               return;
+
+       /* Sort deltas by base SHA1/offset 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.
+        */
+       if (verbose)
+               progress = start_progress("Resolving deltas", nr_deltas);
+       for (i = 0; i < nr_objects; i++) {
+               struct object_entry *obj = &objects[i];
+               struct base_data base_obj;
+
+               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA)
+                       continue;
+               base_obj.obj = obj;
+               base_obj.data = NULL;
+               find_unresolved_deltas(&base_obj, NULL);
+               display_progress(progress, nr_resolved_deltas);
+       }
+}
+
+static int write_compressed(struct sha1file *f, void *in, unsigned int size)
+{
+       z_stream stream;
+       int status;
+       unsigned char outbuf[4096];
+
+       memset(&stream, 0, sizeof(stream));
+       deflateInit(&stream, zlib_compression_level);
+       stream.next_in = in;
+       stream.avail_in = size;
+
+       do {
+               stream.next_out = outbuf;
+               stream.avail_out = sizeof(outbuf);
+               status = deflate(&stream, Z_FINISH);
+               sha1write(f, outbuf, sizeof(outbuf) - stream.avail_out);
+       } while (status == Z_OK);
+
+       if (status != Z_STREAM_END)
+               die("unable to deflate appended object (%d)", status);
+       size = stream.total_out;
+       deflateEnd(&stream);
+       return size;
+}
+
+static struct object_entry *append_obj_to_pack(struct sha1file *f,
+                              const unsigned char *sha1, void *buf,
+                              unsigned long size, enum object_type type)
+{
+       struct object_entry *obj = &objects[nr_objects++];
+       unsigned char header[10];
+       unsigned long s = size;
+       int n = 0;
+       unsigned char c = (type << 4) | (s & 15);
+       s >>= 4;
+       while (s) {
+               header[n++] = c | 0x80;
+               c = s & 0x7f;
+               s >>= 7;
+       }
+       header[n++] = c;
+       crc32_begin(f);
+       sha1write(f, header, n);
+       obj[0].size = size;
+       obj[0].hdr_size = n;
+       obj[0].type = type;
+       obj[0].real_type = type;
+       obj[1].idx.offset = obj[0].idx.offset + n;
+       obj[1].idx.offset += write_compressed(f, buf, size);
+       obj[0].idx.crc32 = crc32_end(f);
+       sha1flush(f);
+       hashcpy(obj->idx.sha1, sha1);
+       return obj;
+}
+
+static int delta_pos_compare(const void *_a, const void *_b)
+{
+       struct delta_entry *a = *(struct delta_entry **)_a;
+       struct delta_entry *b = *(struct delta_entry **)_b;
+       return a->obj_no - b->obj_no;
+}
+
+static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
+{
+       struct delta_entry **sorted_by_pos;
+       int i, n = 0;
+
+       /*
+        * Since many unresolved deltas may well be themselves base objects
+        * for more unresolved deltas, we really want to include the
+        * smallest number of base objects that would cover as much delta
+        * as possible by picking the
+        * trunc deltas first, allowing for other deltas to resolve without
+        * additional base objects.  Since most base objects are to be found
+        * before deltas depending on them, a good heuristic is to start
+        * resolving deltas in the same order as their position in the pack.
+        */
+       sorted_by_pos = xmalloc(nr_unresolved * sizeof(*sorted_by_pos));
+       for (i = 0; i < nr_deltas; i++) {
+               if (objects[deltas[i].obj_no].real_type != OBJ_REF_DELTA)
+                       continue;
+               sorted_by_pos[n++] = &deltas[i];
+       }
+       qsort(sorted_by_pos, n, sizeof(*sorted_by_pos), delta_pos_compare);
+
+       for (i = 0; i < n; i++) {
+               struct delta_entry *d = sorted_by_pos[i];
+               enum object_type type;
+               struct base_data base_obj;
+
+               if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
+                       continue;
+               base_obj.data = read_sha1_file(d->base.sha1, &type, &base_obj.size);
+               if (!base_obj.data)
+                       continue;
+
+               if (check_sha1_signature(d->base.sha1, base_obj.data,
+                               base_obj.size, typename(type)))
+                       die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
+               base_obj.obj = append_obj_to_pack(f, d->base.sha1,
+                                       base_obj.data, base_obj.size, type);
+               find_unresolved_deltas(&base_obj, NULL);
+               display_progress(progress, nr_resolved_deltas);
+       }
+       free(sorted_by_pos);
+}
+
+static void final(const char *final_pack_name, const char *curr_pack_name,
+                 const char *final_index_name, const char *curr_index_name,
+                 const char *keep_name, const char *keep_msg,
+                 unsigned char *sha1)
+{
+       const char *report = "pack";
+       char name[PATH_MAX];
+       int err;
+
+       if (!from_stdin) {
+               close(input_fd);
+       } else {
+               fsync_or_die(output_fd, curr_pack_name);
+               err = close(output_fd);
+               if (err)
+                       die_errno("error while closing pack file");
+       }
+
+       if (keep_msg) {
+               int keep_fd, keep_msg_len = strlen(keep_msg);
+
+               if (!keep_name)
+                       keep_fd = odb_pack_keep(name, sizeof(name), sha1);
+               else
+                       keep_fd = open(keep_name, O_RDWR|O_CREAT|O_EXCL, 0600);
+
+               if (keep_fd < 0) {
+                       if (errno != EEXIST)
+                               die_errno("cannot write keep file '%s'",
+                                         keep_name);
+               } else {
+                       if (keep_msg_len > 0) {
+                               write_or_die(keep_fd, keep_msg, keep_msg_len);
+                               write_or_die(keep_fd, "\n", 1);
+                       }
+                       if (close(keep_fd) != 0)
+                               die_errno("cannot close written keep file '%s'",
+                                   keep_name);
+                       report = "keep";
+               }
+       }
+
+       if (final_pack_name != curr_pack_name) {
+               if (!final_pack_name) {
+                       snprintf(name, sizeof(name), "%s/pack/pack-%s.pack",
+                                get_object_directory(), sha1_to_hex(sha1));
+                       final_pack_name = name;
+               }
+               if (move_temp_to_file(curr_pack_name, final_pack_name))
+                       die("cannot store pack file");
+       } else if (from_stdin)
+               chmod(final_pack_name, 0444);
+
+       if (final_index_name != curr_index_name) {
+               if (!final_index_name) {
+                       snprintf(name, sizeof(name), "%s/pack/pack-%s.idx",
+                                get_object_directory(), sha1_to_hex(sha1));
+                       final_index_name = name;
+               }
+               if (move_temp_to_file(curr_index_name, final_index_name))
+                       die("cannot store index file");
+       } else
+               chmod(final_index_name, 0444);
+
+       if (!from_stdin) {
+               printf("%s\n", sha1_to_hex(sha1));
+       } else {
+               char buf[48];
+               int len = snprintf(buf, sizeof(buf), "%s\t%s\n",
+                                  report, sha1_to_hex(sha1));
+               write_or_die(1, buf, len);
+
+               /*
+                * Let's just mimic git-unpack-objects here and write
+                * the last part of the input buffer to stdout.
+                */
+               while (input_len) {
+                       err = xwrite(1, input_buffer + input_offset, input_len);
+                       if (err <= 0)
+                               break;
+                       input_len -= err;
+                       input_offset += err;
+               }
+       }
+}
+
+static int git_index_pack_config(const char *k, const char *v, void *cb)
+{
+       if (!strcmp(k, "pack.indexversion")) {
+               pack_idx_default_version = git_config_int(k, v);
+               if (pack_idx_default_version > 2)
+                       die("bad pack.indexversion=%"PRIu32,
+                               pack_idx_default_version);
+               return 0;
+       }
+       return git_default_config(k, v, cb);
+}
+
+int cmd_index_pack(int argc, const char **argv, const char *prefix)
+{
+       int i, fix_thin_pack = 0;
+       const char *curr_pack, *curr_index;
+       const char *index_name = NULL, *pack_name = NULL;
+       const char *keep_name = NULL, *keep_msg = NULL;
+       char *index_name_buf = NULL, *keep_name_buf = NULL;
+       struct pack_idx_entry **idx_objects;
+       unsigned char pack_sha1[20];
+
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage(index_pack_usage);
+
+       read_replace_refs = 0;
+
+       git_config(git_index_pack_config, NULL);
+       if (prefix && chdir(prefix))
+               die("Cannot come back to cwd");
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (*arg == '-') {
+                       if (!strcmp(arg, "--stdin")) {
+                               from_stdin = 1;
+                       } else if (!strcmp(arg, "--fix-thin")) {
+                               fix_thin_pack = 1;
+                       } else if (!strcmp(arg, "--strict")) {
+                               strict = 1;
+                       } else if (!strcmp(arg, "--keep")) {
+                               keep_msg = "";
+                       } else if (!prefixcmp(arg, "--keep=")) {
+                               keep_msg = arg + 7;
+                       } else if (!prefixcmp(arg, "--pack_header=")) {
+                               struct pack_header *hdr;
+                               char *c;
+
+                               hdr = (struct pack_header *)input_buffer;
+                               hdr->hdr_signature = htonl(PACK_SIGNATURE);
+                               hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10));
+                               if (*c != ',')
+                                       die("bad %s", arg);
+                               hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10));
+                               if (*c)
+                                       die("bad %s", arg);
+                               input_len = sizeof(*hdr);
+                       } else if (!strcmp(arg, "-v")) {
+                               verbose = 1;
+                       } else if (!strcmp(arg, "-o")) {
+                               if (index_name || (i+1) >= argc)
+                                       usage(index_pack_usage);
+                               index_name = argv[++i];
+                       } else if (!prefixcmp(arg, "--index-version=")) {
+                               char *c;
+                               pack_idx_default_version = strtoul(arg + 16, &c, 10);
+                               if (pack_idx_default_version > 2)
+                                       die("bad %s", arg);
+                               if (*c == ',')
+                                       pack_idx_off32_limit = strtoul(c+1, &c, 0);
+                               if (*c || pack_idx_off32_limit & 0x80000000)
+                                       die("bad %s", arg);
+                       } else
+                               usage(index_pack_usage);
+                       continue;
+               }
+
+               if (pack_name)
+                       usage(index_pack_usage);
+               pack_name = arg;
+       }
+
+       if (!pack_name && !from_stdin)
+               usage(index_pack_usage);
+       if (fix_thin_pack && !from_stdin)
+               die("--fix-thin cannot be used without --stdin");
+       if (!index_name && pack_name) {
+               int len = strlen(pack_name);
+               if (!has_extension(pack_name, ".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;
+       }
+       if (keep_msg && !keep_name && pack_name) {
+               int len = strlen(pack_name);
+               if (!has_extension(pack_name, ".pack"))
+                       die("packfile name '%s' does not end with '.pack'",
+                           pack_name);
+               keep_name_buf = xmalloc(len);
+               memcpy(keep_name_buf, pack_name, len - 5);
+               strcpy(keep_name_buf + len - 5, ".keep");
+               keep_name = keep_name_buf;
+       }
+
+       curr_pack = open_pack_file(pack_name);
+       parse_pack_header();
+       objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry));
+       deltas = xmalloc(nr_objects * sizeof(struct delta_entry));
+       parse_pack_objects(pack_sha1);
+       if (nr_deltas == nr_resolved_deltas) {
+               stop_progress(&progress);
+               /* Flush remaining pack final 20-byte SHA1. */
+               flush();
+       } else {
+               if (fix_thin_pack) {
+                       struct sha1file *f;
+                       unsigned char read_sha1[20], tail_sha1[20];
+                       char msg[48];
+                       int nr_unresolved = nr_deltas - nr_resolved_deltas;
+                       int nr_objects_initial = nr_objects;
+                       if (nr_unresolved <= 0)
+                               die("confusion beyond insanity");
+                       objects = xrealloc(objects,
+                                          (nr_objects + nr_unresolved + 1)
+                                          * sizeof(*objects));
+                       f = sha1fd(output_fd, curr_pack);
+                       fix_unresolved_deltas(f, nr_unresolved);
+                       sprintf(msg, "completed with %d local objects",
+                               nr_objects - nr_objects_initial);
+                       stop_progress_msg(&progress, msg);
+                       sha1close(f, tail_sha1, 0);
+                       hashcpy(read_sha1, pack_sha1);
+                       fixup_pack_header_footer(output_fd, pack_sha1,
+                                                curr_pack, nr_objects,
+                                                read_sha1, consumed_bytes-20);
+                       if (hashcmp(read_sha1, tail_sha1) != 0)
+                               die("Unexpected tail checksum for %s "
+                                   "(disk corruption?)", curr_pack);
+               }
+               if (nr_deltas != nr_resolved_deltas)
+                       die("pack has %d unresolved deltas",
+                           nr_deltas - nr_resolved_deltas);
+       }
+       free(deltas);
+       if (strict)
+               check_objects();
+
+       idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *));
+       for (i = 0; i < nr_objects; i++)
+               idx_objects[i] = &objects[i].idx;
+       curr_index = write_idx_file(index_name, idx_objects, nr_objects, pack_sha1);
+       free(idx_objects);
+
+       final(pack_name, curr_pack,
+               index_name, curr_index,
+               keep_name, keep_msg,
+               pack_sha1);
+       free(objects);
+       free(index_name_buf);
+       free(keep_name_buf);
+       if (pack_name == NULL)
+               free((void *) curr_pack);
+       if (index_name == NULL)
+               free((void *) curr_index);
+
+       return 0;
+}
diff --git a/builtin/init-db.c b/builtin/init-db.c
new file mode 100644 (file)
index 0000000..0271285
--- /dev/null
@@ -0,0 +1,515 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "exec_cmd.h"
+#include "parse-options.h"
+
+#ifndef DEFAULT_GIT_TEMPLATE_DIR
+#define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates"
+#endif
+
+#ifdef NO_TRUSTABLE_FILEMODE
+#define TEST_FILEMODE 0
+#else
+#define TEST_FILEMODE 1
+#endif
+
+static int init_is_bare_repository = 0;
+static int init_shared_repository = -1;
+static const char *init_db_template_dir;
+
+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", dir);
+}
+
+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" 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_errno("cannot stat '%s'", path);
+               }
+               else
+                       exists = 1;
+
+               if (lstat(template, &st_template))
+                       die_errno("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_errno("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_errno("cannot readlink '%s'", template);
+                       if (sizeof(lnk) <= len)
+                               die("insanely long symlink %s", template);
+                       lnk[len] = 0;
+                       if (symlink(lnk, path))
+                               die_errno("cannot symlink '%s' '%s'", lnk, path);
+               }
+               else if (S_ISREG(st_template.st_mode)) {
+                       if (copy_file(path, template, st_template.st_mode))
+                               die_errno("cannot copy '%s' to '%s'", template,
+                                         path);
+               }
+               else
+                       error("ignoring template %s", template);
+       }
+}
+
+static void copy_templates(const char *template_dir)
+{
+       char path[PATH_MAX];
+       char template_path[PATH_MAX];
+       int template_len;
+       DIR *dir;
+       const char *git_dir = get_git_dir();
+       int len = strlen(git_dir);
+
+       if (!template_dir)
+               template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
+       if (!template_dir)
+               template_dir = init_db_template_dir;
+       if (!template_dir)
+               template_dir = system_path(DEFAULT_GIT_TEMPLATE_DIR);
+       if (!template_dir[0])
+               return;
+       template_len = strlen(template_dir);
+       if (PATH_MAX <= (template_len+strlen("/config")))
+               die("insanely long template path %s", template_dir);
+       strcpy(template_path, template_dir);
+       if (template_path[template_len-1] != '/') {
+               template_path[template_len++] = '/';
+               template_path[template_len] = 0;
+       }
+       dir = opendir(template_path);
+       if (!dir) {
+               warning("templates not found %s", 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, NULL);
+       template_path[template_len] = 0;
+
+       if (repository_format_version &&
+           repository_format_version != GIT_REPO_VERSION) {
+               warning("not copying templates of "
+                       "a wrong format version %d from '%s'",
+                       repository_format_version,
+                       template_dir);
+               closedir(dir);
+               return;
+       }
+
+       memcpy(path, git_dir, len);
+       if (len && path[len - 1] != '/')
+               path[len++] = '/';
+       path[len] = 0;
+       copy_templates_1(path, len,
+                        template_path, template_len,
+                        dir);
+       closedir(dir);
+}
+
+static int git_init_db_config(const char *k, const char *v, void *cb)
+{
+       if (!strcmp(k, "init.templatedir"))
+               return git_config_pathname(&init_db_template_dir, k, v);
+
+       return 0;
+}
+
+static int create_default_files(const char *template_path)
+{
+       const char *git_dir = get_git_dir();
+       unsigned len = strlen(git_dir);
+       static char path[PATH_MAX];
+       struct stat st1;
+       char repo_version_string[10];
+       char junk[2];
+       int reinit;
+       int filemode;
+
+       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}
+        */
+       safe_create_dir(git_path("refs"), 1);
+       safe_create_dir(git_path("refs/heads"), 1);
+       safe_create_dir(git_path("refs/tags"), 1);
+
+       /* Just look for `init.templatedir` */
+       git_config(git_init_db_config, NULL);
+
+       /* First copy the templates -- we might have the default
+        * config file there, in which case we would want to read
+        * from it after installing.
+        */
+       copy_templates(template_path);
+
+       git_config(git_default_config, NULL);
+       is_bare_repository_cfg = init_is_bare_repository;
+
+       /* reading existing config may have overwrote it */
+       if (init_shared_repository != -1)
+               shared_repository = init_shared_repository;
+
+       /*
+        * We would have created the above under user's umask -- under
+        * shared-repository settings, we would need to fix them up.
+        */
+       if (shared_repository) {
+               adjust_shared_perm(get_git_dir());
+               adjust_shared_perm(git_path("refs"));
+               adjust_shared_perm(git_path("refs/heads"));
+               adjust_shared_perm(git_path("refs/tags"));
+       }
+
+       /*
+        * Create the default symlink from ".git/HEAD" to the "master"
+        * branch, if it does not exist yet.
+        */
+       strcpy(path + len, "HEAD");
+       reinit = (!access(path, R_OK)
+                 || readlink(path, junk, sizeof(junk)-1) != -1);
+       if (!reinit) {
+               if (create_symref("HEAD", "refs/heads/master", NULL) < 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 */
+       filemode = TEST_FILEMODE;
+       if (TEST_FILEMODE && !lstat(path, &st1)) {
+               struct stat st2;
+               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");
+
+       if (is_bare_repository())
+               git_config_set("core.bare", "true");
+       else {
+               const char *work_tree = get_git_work_tree();
+               git_config_set("core.bare", "false");
+               /* allow template config file to override the default */
+               if (log_all_ref_updates == -1)
+                   git_config_set("core.logallrefupdates", "true");
+               if (prefixcmp(git_dir, work_tree) ||
+                   strcmp(git_dir + strlen(work_tree), "/.git")) {
+                       git_config_set("core.worktree", work_tree);
+               }
+       }
+
+       if (!reinit) {
+               /* Check if symlink is supported in the work tree */
+               path[len] = 0;
+               strcpy(path + len, "tXXXXXX");
+               if (!close(xmkstemp(path)) &&
+                   !unlink(path) &&
+                   !symlink("testing", path) &&
+                   !lstat(path, &st1) &&
+                   S_ISLNK(st1.st_mode))
+                       unlink(path); /* good */
+               else
+                       git_config_set("core.symlinks", "false");
+
+               /* Check if the filesystem is case-insensitive */
+               path[len] = 0;
+               strcpy(path + len, "CoNfIg");
+               if (!access(path, F_OK))
+                       git_config_set("core.ignorecase", "true");
+       }
+
+       return reinit;
+}
+
+int init_db(const char *template_dir, unsigned int flags)
+{
+       const char *sha1_dir;
+       char *path;
+       int len, reinit;
+
+       safe_create_dir(get_git_dir(), 0);
+
+       init_is_bare_repository = is_bare_repository();
+
+       /* 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();
+
+       reinit = create_default_files(template_dir);
+
+       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) {
+               char buf[10];
+               /* We do not spell "group" and such, so that
+                * the configuration can be read by older version
+                * of git. Note, we use octal numbers for new share modes,
+                * and compatibility values for PERM_GROUP and
+                * PERM_EVERYBODY.
+                */
+               if (shared_repository < 0)
+                       /* force to the mode value */
+                       sprintf(buf, "0%o", -shared_repository);
+               else if (shared_repository == PERM_GROUP)
+                       sprintf(buf, "%d", OLD_PERM_GROUP);
+               else if (shared_repository == PERM_EVERYBODY)
+                       sprintf(buf, "%d", OLD_PERM_EVERYBODY);
+               else
+                       die("oops");
+               git_config_set("core.sharedrepository", buf);
+               git_config_set("receive.denyNonFastforwards", "true");
+       }
+
+       if (!(flags & INIT_DB_QUIET)) {
+               const char *git_dir = get_git_dir();
+               int len = strlen(git_dir);
+               printf("%s%s Git repository in %s%s\n",
+                      reinit ? "Reinitialized existing" : "Initialized empty",
+                      shared_repository ? " shared" : "",
+                      git_dir, len && git_dir[len-1] != '/' ? "/" : "");
+       }
+
+       return 0;
+}
+
+static int guess_repository_type(const char *git_dir)
+{
+       char cwd[PATH_MAX];
+       const char *slash;
+
+       /*
+        * "GIT_DIR=. git init" is always bare.
+        * "GIT_DIR=`pwd` git init" too.
+        */
+       if (!strcmp(".", git_dir))
+               return 1;
+       if (!getcwd(cwd, sizeof(cwd)))
+               die_errno("cannot tell cwd");
+       if (!strcmp(git_dir, cwd))
+               return 1;
+       /*
+        * "GIT_DIR=.git or GIT_DIR=something/.git is usually not.
+        */
+       if (!strcmp(git_dir, ".git"))
+               return 0;
+       slash = strrchr(git_dir, '/');
+       if (slash && !strcmp(slash, "/.git"))
+               return 0;
+
+       /*
+        * Otherwise it is often bare.  At this point
+        * we are just guessing.
+        */
+       return 1;
+}
+
+static int shared_callback(const struct option *opt, const char *arg, int unset)
+{
+       *((int *) opt->value) = (arg) ? git_config_perm("arg", arg) : PERM_GROUP;
+       return 0;
+}
+
+static const char *const init_db_usage[] = {
+       "git init [-q | --quiet] [--bare] [--template=<template-directory>] [--shared[=<permissions>]] [directory]",
+       NULL
+};
+
+/*
+ * 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, const char *prefix)
+{
+       const char *git_dir;
+       const char *template_dir = NULL;
+       unsigned int flags = 0;
+       const struct option init_db_options[] = {
+               OPT_STRING(0, "template", &template_dir, "template-directory",
+                               "provide the directory from which templates will be used"),
+               OPT_SET_INT(0, "bare", &is_bare_repository_cfg,
+                               "create a bare repository", 1),
+               { OPTION_CALLBACK, 0, "shared", &init_shared_repository,
+                       "permissions",
+                       "specify that the git repository is to be shared amongst several users",
+                       PARSE_OPT_OPTARG | PARSE_OPT_NONEG, shared_callback, 0},
+               OPT_BIT('q', "quiet", &flags, "be quiet", INIT_DB_QUIET),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
+
+       if (argc == 1) {
+               int mkdir_tried = 0;
+       retry:
+               if (chdir(argv[0]) < 0) {
+                       if (!mkdir_tried) {
+                               int saved;
+                               /*
+                                * At this point we haven't read any configuration,
+                                * and we know shared_repository should always be 0;
+                                * but just in case we play safe.
+                                */
+                               saved = shared_repository;
+                               shared_repository = 0;
+                               switch (safe_create_leading_directories_const(argv[0])) {
+                               case -3:
+                                       errno = EEXIST;
+                                       /* fallthru */
+                               case -1:
+                                       die_errno("cannot mkdir %s", argv[0]);
+                                       break;
+                               default:
+                                       break;
+                               }
+                               shared_repository = saved;
+                               if (mkdir(argv[0], 0777) < 0)
+                                       die_errno("cannot mkdir %s", argv[0]);
+                               mkdir_tried = 1;
+                               goto retry;
+                       }
+                       die_errno("cannot chdir to %s", argv[0]);
+               }
+       } else if (0 < argc) {
+               usage(init_db_usage[0]);
+       }
+       if (is_bare_repository_cfg == 1) {
+               static char git_dir[PATH_MAX+1];
+
+               setenv(GIT_DIR_ENVIRONMENT,
+                       getcwd(git_dir, sizeof(git_dir)), argc > 0);
+       }
+
+       if (init_shared_repository != -1)
+               shared_repository = init_shared_repository;
+
+       /*
+        * GIT_WORK_TREE makes sense only in conjunction with GIT_DIR
+        * without --bare.  Catch the error early.
+        */
+       git_dir = getenv(GIT_DIR_ENVIRONMENT);
+       if ((!git_dir || is_bare_repository_cfg == 1)
+           && getenv(GIT_WORK_TREE_ENVIRONMENT))
+               die("%s (or --work-tree=<directory>) not allowed without "
+                   "specifying %s (or --git-dir=<directory>)",
+                   GIT_WORK_TREE_ENVIRONMENT,
+                   GIT_DIR_ENVIRONMENT);
+
+       /*
+        * Set up the default .git directory contents
+        */
+       if (!git_dir)
+               git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
+
+       if (is_bare_repository_cfg < 0)
+               is_bare_repository_cfg = guess_repository_type(git_dir);
+
+       if (!is_bare_repository_cfg) {
+               if (git_dir) {
+                       const char *git_dir_parent = strrchr(git_dir, '/');
+                       if (git_dir_parent) {
+                               char *rel = xstrndup(git_dir, git_dir_parent - git_dir);
+                               git_work_tree_cfg = xstrdup(make_absolute_path(rel));
+                               free(rel);
+                       }
+               }
+               if (!git_work_tree_cfg) {
+                       git_work_tree_cfg = xcalloc(PATH_MAX, 1);
+                       if (!getcwd(git_work_tree_cfg, PATH_MAX))
+                               die_errno ("Cannot access current working directory");
+               }
+               if (access(get_git_work_tree(), X_OK))
+                       die_errno ("Cannot access work tree '%s'",
+                                  get_git_work_tree());
+       }
+
+       set_git_dir(make_absolute_path(git_dir));
+
+       return init_db(template_dir, flags);
+}
diff --git a/builtin/log.c b/builtin/log.c
new file mode 100644 (file)
index 0000000..eaa1ee0
--- /dev/null
@@ -0,0 +1,1464 @@
+/*
+ * Builtin "git log" and related commands (show, whatchanged)
+ *
+ * (C) Copyright 2006 Linus Torvalds
+ *              2006 Junio Hamano
+ */
+#include "cache.h"
+#include "color.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "log-tree.h"
+#include "builtin.h"
+#include "tag.h"
+#include "reflog-walk.h"
+#include "patch-ids.h"
+#include "run-command.h"
+#include "shortlog.h"
+#include "remote.h"
+#include "string-list.h"
+#include "parse-options.h"
+
+/* Set a default date-time format for git log ("log.date" config variable) */
+static const char *default_date_mode = NULL;
+
+static int default_show_root = 1;
+static int decoration_style;
+static const char *fmt_patch_subject_prefix = "PATCH";
+static const char *fmt_pretty;
+
+static const char * const builtin_log_usage =
+       "git log [<options>] [<since>..<until>] [[--] <path>...]\n"
+       "   or: git show [options] <object>...";
+
+static int parse_decoration_style(const char *var, const char *value)
+{
+       switch (git_config_maybe_bool(var, value)) {
+       case 1:
+               return DECORATE_SHORT_REFS;
+       case 0:
+               return 0;
+       default:
+               break;
+       }
+       if (!strcmp(value, "full"))
+               return DECORATE_FULL_REFS;
+       else if (!strcmp(value, "short"))
+               return DECORATE_SHORT_REFS;
+       return -1;
+}
+
+static void cmd_log_init(int argc, const char **argv, const char *prefix,
+                        struct rev_info *rev, struct setup_revision_opt *opt)
+{
+       int i;
+       int decoration_given = 0;
+       struct userformat_want w;
+
+       rev->abbrev = DEFAULT_ABBREV;
+       rev->commit_format = CMIT_FMT_DEFAULT;
+       if (fmt_pretty)
+               get_commit_format(fmt_pretty, rev);
+       rev->verbose_header = 1;
+       DIFF_OPT_SET(&rev->diffopt, RECURSIVE);
+       rev->show_root_diff = default_show_root;
+       rev->subject_prefix = fmt_patch_subject_prefix;
+       DIFF_OPT_SET(&rev->diffopt, ALLOW_TEXTCONV);
+
+       if (default_date_mode)
+               rev->date_mode = parse_date_format(default_date_mode);
+
+       /*
+        * Check for -h before setup_revisions(), or "git log -h" will
+        * fail when run without a git directory.
+        */
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage(builtin_log_usage);
+       argc = setup_revisions(argc, argv, rev, opt);
+
+       memset(&w, 0, sizeof(w));
+       userformat_find_requirements(NULL, &w);
+
+       if (!rev->show_notes_given && (!rev->pretty_given || w.notes))
+               rev->show_notes = 1;
+       if (rev->show_notes)
+               init_display_notes(&rev->notes_opt);
+
+       if (rev->diffopt.pickaxe || rev->diffopt.filter)
+               rev->always_show_header = 0;
+       if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) {
+               rev->always_show_header = 0;
+               if (rev->diffopt.nr_paths != 1)
+                       usage("git logs can only follow renames on one pathname at a time");
+       }
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (!strcmp(arg, "--decorate")) {
+                       decoration_style = DECORATE_SHORT_REFS;
+                       decoration_given = 1;
+               } else if (!prefixcmp(arg, "--decorate=")) {
+                       const char *v = skip_prefix(arg, "--decorate=");
+                       decoration_style = parse_decoration_style(arg, v);
+                       if (decoration_style < 0)
+                               die("invalid --decorate option: %s", arg);
+                       decoration_given = 1;
+               } else if (!strcmp(arg, "--no-decorate")) {
+                       decoration_style = 0;
+               } else if (!strcmp(arg, "--source")) {
+                       rev->show_source = 1;
+               } else if (!strcmp(arg, "-h")) {
+                       usage(builtin_log_usage);
+               } else
+                       die("unrecognized argument: %s", arg);
+       }
+
+       /*
+        * defeat log.decorate configuration interacting with --pretty=raw
+        * from the command line.
+        */
+       if (!decoration_given && rev->pretty_given
+           && rev->commit_format == CMIT_FMT_RAW)
+               decoration_style = 0;
+
+       if (decoration_style) {
+               rev->show_decorations = 1;
+               load_ref_decorations(decoration_style);
+       }
+       setup_pager();
+}
+
+/*
+ * This gives a rough estimate for how many commits we
+ * will print out in the list.
+ */
+static int estimate_commit_count(struct rev_info *rev, struct commit_list *list)
+{
+       int n = 0;
+
+       while (list) {
+               struct commit *commit = list->item;
+               unsigned int flags = commit->object.flags;
+               list = list->next;
+               if (!(flags & (TREESAME | UNINTERESTING)))
+                       n++;
+       }
+       return n;
+}
+
+static void show_early_header(struct rev_info *rev, const char *stage, int nr)
+{
+       if (rev->shown_one) {
+               rev->shown_one = 0;
+               if (rev->commit_format != CMIT_FMT_ONELINE)
+                       putchar(rev->diffopt.line_termination);
+       }
+       printf("Final output: %d %s\n", nr, stage);
+}
+
+static struct itimerval early_output_timer;
+
+static void log_show_early(struct rev_info *revs, struct commit_list *list)
+{
+       int i = revs->early_output;
+       int show_header = 1;
+
+       sort_in_topological_order(&list, revs->lifo);
+       while (list && i) {
+               struct commit *commit = list->item;
+               switch (simplify_commit(revs, commit)) {
+               case commit_show:
+                       if (show_header) {
+                               int n = estimate_commit_count(revs, list);
+                               show_early_header(revs, "incomplete", n);
+                               show_header = 0;
+                       }
+                       log_tree_commit(revs, commit);
+                       i--;
+                       break;
+               case commit_ignore:
+                       break;
+               case commit_error:
+                       return;
+               }
+               list = list->next;
+       }
+
+       /* Did we already get enough commits for the early output? */
+       if (!i)
+               return;
+
+       /*
+        * ..if no, then repeat it twice a second until we
+        * do.
+        *
+        * NOTE! We don't use "it_interval", because if the
+        * reader isn't listening, we want our output to be
+        * throttled by the writing, and not have the timer
+        * trigger every second even if we're blocked on a
+        * reader!
+        */
+       early_output_timer.it_value.tv_sec = 0;
+       early_output_timer.it_value.tv_usec = 500000;
+       setitimer(ITIMER_REAL, &early_output_timer, NULL);
+}
+
+static void early_output(int signal)
+{
+       show_early_output = log_show_early;
+}
+
+static void setup_early_output(struct rev_info *rev)
+{
+       struct sigaction sa;
+
+       /*
+        * Set up the signal handler, minimally intrusively:
+        * we only set a single volatile integer word (not
+        * using sigatomic_t - trying to avoid unnecessary
+        * system dependencies and headers), and using
+        * SA_RESTART.
+        */
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_handler = early_output;
+       sigemptyset(&sa.sa_mask);
+       sa.sa_flags = SA_RESTART;
+       sigaction(SIGALRM, &sa, NULL);
+
+       /*
+        * If we can get the whole output in less than a
+        * tenth of a second, don't even bother doing the
+        * early-output thing..
+        *
+        * This is a one-time-only trigger.
+        */
+       early_output_timer.it_value.tv_sec = 0;
+       early_output_timer.it_value.tv_usec = 100000;
+       setitimer(ITIMER_REAL, &early_output_timer, NULL);
+}
+
+static void finish_early_output(struct rev_info *rev)
+{
+       int n = estimate_commit_count(rev, rev->commits);
+       signal(SIGALRM, SIG_IGN);
+       show_early_header(rev, "done", n);
+}
+
+static int cmd_log_walk(struct rev_info *rev)
+{
+       struct commit *commit;
+
+       if (rev->early_output)
+               setup_early_output(rev);
+
+       if (prepare_revision_walk(rev))
+               die("revision walk setup failed");
+
+       if (rev->early_output)
+               finish_early_output(rev);
+
+       /*
+        * For --check and --exit-code, the exit code is based on CHECK_FAILED
+        * and HAS_CHANGES being accumulated in rev->diffopt, so be careful to
+        * retain that state information if replacing rev->diffopt in this loop
+        */
+       while ((commit = get_revision(rev)) != NULL) {
+               log_tree_commit(rev, commit);
+               if (!rev->reflog_info) {
+                       /* we allow cycles in reflog ancestry */
+                       free(commit->buffer);
+                       commit->buffer = NULL;
+               }
+               free_commit_list(commit->parents);
+               commit->parents = NULL;
+       }
+       if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF &&
+           DIFF_OPT_TST(&rev->diffopt, CHECK_FAILED)) {
+               return 02;
+       }
+       return diff_result_code(&rev->diffopt, 0);
+}
+
+static int git_log_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "format.pretty"))
+               return git_config_string(&fmt_pretty, var, value);
+       if (!strcmp(var, "format.subjectprefix"))
+               return git_config_string(&fmt_patch_subject_prefix, var, value);
+       if (!strcmp(var, "log.date"))
+               return git_config_string(&default_date_mode, var, value);
+       if (!strcmp(var, "log.decorate")) {
+               decoration_style = parse_decoration_style(var, value);
+               if (decoration_style < 0)
+                       decoration_style = 0; /* maybe warn? */
+               return 0;
+       }
+       if (!strcmp(var, "log.showroot")) {
+               default_show_root = git_config_bool(var, value);
+               return 0;
+       }
+       if (!prefixcmp(var, "color.decorate."))
+               return parse_decorate_color_config(var, 15, value);
+
+       return git_diff_ui_config(var, value, cb);
+}
+
+int cmd_whatchanged(int argc, const char **argv, const char *prefix)
+{
+       struct rev_info rev;
+       struct setup_revision_opt opt;
+
+       git_config(git_log_config, NULL);
+
+       if (diff_use_color_default == -1)
+               diff_use_color_default = git_use_color_default;
+
+       init_revisions(&rev, prefix);
+       rev.diff = 1;
+       rev.simplify_history = 0;
+       memset(&opt, 0, sizeof(opt));
+       opt.def = "HEAD";
+       cmd_log_init(argc, argv, prefix, &rev, &opt);
+       if (!rev.diffopt.output_format)
+               rev.diffopt.output_format = DIFF_FORMAT_RAW;
+       return cmd_log_walk(&rev);
+}
+
+static void show_tagger(char *buf, int len, struct rev_info *rev)
+{
+       struct strbuf out = STRBUF_INIT;
+
+       pp_user_info("Tagger", rev->commit_format, &out, buf, rev->date_mode,
+               git_log_output_encoding ?
+               git_log_output_encoding: git_commit_encoding);
+       printf("%s", out.buf);
+       strbuf_release(&out);
+}
+
+static int show_object(const unsigned char *sha1, int show_tag_object,
+       struct rev_info *rev)
+{
+       unsigned long size;
+       enum object_type type;
+       char *buf = read_sha1_file(sha1, &type, &size);
+       int offset = 0;
+
+       if (!buf)
+               return error("Could not read object %s", sha1_to_hex(sha1));
+
+       if (show_tag_object)
+               while (offset < size && buf[offset] != '\n') {
+                       int new_offset = offset + 1;
+                       while (new_offset < size && buf[new_offset++] != '\n')
+                               ; /* do nothing */
+                       if (!prefixcmp(buf + offset, "tagger "))
+                               show_tagger(buf + offset + 7,
+                                           new_offset - offset - 7, rev);
+                       offset = new_offset;
+               }
+
+       if (offset < size)
+               fwrite(buf + offset, size - offset, 1, stdout);
+       free(buf);
+       return 0;
+}
+
+static int show_tree_object(const unsigned char *sha1,
+               const char *base, int baselen,
+               const char *pathname, unsigned mode, int stage, void *context)
+{
+       printf("%s%s\n", pathname, S_ISDIR(mode) ? "/" : "");
+       return 0;
+}
+
+static void show_rev_tweak_rev(struct rev_info *rev, struct setup_revision_opt *opt)
+{
+       if (rev->ignore_merges) {
+               /* There was no "-m" on the command line */
+               rev->ignore_merges = 0;
+               if (!rev->first_parent_only && !rev->combine_merges) {
+                       /* No "--first-parent", "-c", nor "--cc" */
+                       rev->combine_merges = 1;
+                       rev->dense_combined_merges = 1;
+               }
+       }
+       if (!rev->diffopt.output_format)
+               rev->diffopt.output_format = DIFF_FORMAT_PATCH;
+}
+
+int cmd_show(int argc, const char **argv, const char *prefix)
+{
+       struct rev_info rev;
+       struct object_array_entry *objects;
+       struct setup_revision_opt opt;
+       int i, count, ret = 0;
+
+       git_config(git_log_config, NULL);
+
+       if (diff_use_color_default == -1)
+               diff_use_color_default = git_use_color_default;
+
+       init_revisions(&rev, prefix);
+       rev.diff = 1;
+       rev.always_show_header = 1;
+       rev.no_walk = 1;
+       memset(&opt, 0, sizeof(opt));
+       opt.def = "HEAD";
+       opt.tweak = show_rev_tweak_rev;
+       cmd_log_init(argc, argv, prefix, &rev, &opt);
+
+       count = rev.pending.nr;
+       objects = rev.pending.objects;
+       for (i = 0; i < count && !ret; i++) {
+               struct object *o = objects[i].item;
+               const char *name = objects[i].name;
+               switch (o->type) {
+               case OBJ_BLOB:
+                       ret = show_object(o->sha1, 0, NULL);
+                       break;
+               case OBJ_TAG: {
+                       struct tag *t = (struct tag *)o;
+
+                       if (rev.shown_one)
+                               putchar('\n');
+                       printf("%stag %s%s\n",
+                                       diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
+                                       t->tag,
+                                       diff_get_color_opt(&rev.diffopt, DIFF_RESET));
+                       ret = show_object(o->sha1, 1, &rev);
+                       rev.shown_one = 1;
+                       if (ret)
+                               break;
+                       o = parse_object(t->tagged->sha1);
+                       if (!o)
+                               ret = error("Could not read object %s",
+                                           sha1_to_hex(t->tagged->sha1));
+                       objects[i].item = o;
+                       i--;
+                       break;
+               }
+               case OBJ_TREE:
+                       if (rev.shown_one)
+                               putchar('\n');
+                       printf("%stree %s%s\n\n",
+                                       diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
+                                       name,
+                                       diff_get_color_opt(&rev.diffopt, DIFF_RESET));
+                       read_tree_recursive((struct tree *)o, "", 0, 0, NULL,
+                                       show_tree_object, NULL);
+                       rev.shown_one = 1;
+                       break;
+               case OBJ_COMMIT:
+                       rev.pending.nr = rev.pending.alloc = 0;
+                       rev.pending.objects = NULL;
+                       add_object_array(o, name, &rev.pending);
+                       ret = cmd_log_walk(&rev);
+                       break;
+               default:
+                       ret = error("Unknown type: %d", o->type);
+               }
+       }
+       free(objects);
+       return ret;
+}
+
+/*
+ * This is equivalent to "git log -g --abbrev-commit --pretty=oneline"
+ */
+int cmd_log_reflog(int argc, const char **argv, const char *prefix)
+{
+       struct rev_info rev;
+       struct setup_revision_opt opt;
+
+       git_config(git_log_config, NULL);
+
+       if (diff_use_color_default == -1)
+               diff_use_color_default = git_use_color_default;
+
+       init_revisions(&rev, prefix);
+       init_reflog_walk(&rev.reflog_info);
+       rev.abbrev_commit = 1;
+       rev.verbose_header = 1;
+       memset(&opt, 0, sizeof(opt));
+       opt.def = "HEAD";
+       cmd_log_init(argc, argv, prefix, &rev, &opt);
+
+       /*
+        * This means that we override whatever commit format the user gave
+        * on the cmd line.  Sad, but cmd_log_init() currently doesn't
+        * allow us to set a different default.
+        */
+       rev.commit_format = CMIT_FMT_ONELINE;
+       rev.use_terminator = 1;
+       rev.always_show_header = 1;
+
+       return cmd_log_walk(&rev);
+}
+
+int cmd_log(int argc, const char **argv, const char *prefix)
+{
+       struct rev_info rev;
+       struct setup_revision_opt opt;
+
+       git_config(git_log_config, NULL);
+
+       if (diff_use_color_default == -1)
+               diff_use_color_default = git_use_color_default;
+
+       init_revisions(&rev, prefix);
+       rev.always_show_header = 1;
+       memset(&opt, 0, sizeof(opt));
+       opt.def = "HEAD";
+       cmd_log_init(argc, argv, prefix, &rev, &opt);
+       return cmd_log_walk(&rev);
+}
+
+/* format-patch */
+
+static const char *fmt_patch_suffix = ".patch";
+static int numbered = 0;
+static int auto_number = 1;
+
+static char *default_attach = NULL;
+
+static struct string_list extra_hdr;
+static struct string_list extra_to;
+static struct string_list extra_cc;
+
+static void add_header(const char *value)
+{
+       struct string_list_item *item;
+       int len = strlen(value);
+       while (len && value[len - 1] == '\n')
+               len--;
+
+       if (!strncasecmp(value, "to: ", 4)) {
+               item = string_list_append(&extra_to, value + 4);
+               len -= 4;
+       } else if (!strncasecmp(value, "cc: ", 4)) {
+               item = string_list_append(&extra_cc, value + 4);
+               len -= 4;
+       } else {
+               item = string_list_append(&extra_hdr, value);
+       }
+
+       item->string[len] = '\0';
+}
+
+#define THREAD_SHALLOW 1
+#define THREAD_DEEP 2
+static int thread;
+static int do_signoff;
+static const char *signature = git_version_string;
+
+static int git_format_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "format.headers")) {
+               if (!value)
+                       die("format.headers without value");
+               add_header(value);
+               return 0;
+       }
+       if (!strcmp(var, "format.suffix"))
+               return git_config_string(&fmt_patch_suffix, var, value);
+       if (!strcmp(var, "format.to")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               string_list_append(&extra_to, value);
+               return 0;
+       }
+       if (!strcmp(var, "format.cc")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               string_list_append(&extra_cc, value);
+               return 0;
+       }
+       if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
+               return 0;
+       }
+       if (!strcmp(var, "format.numbered")) {
+               if (value && !strcasecmp(value, "auto")) {
+                       auto_number = 1;
+                       return 0;
+               }
+               numbered = git_config_bool(var, value);
+               auto_number = auto_number && numbered;
+               return 0;
+       }
+       if (!strcmp(var, "format.attach")) {
+               if (value && *value)
+                       default_attach = xstrdup(value);
+               else
+                       default_attach = xstrdup(git_version_string);
+               return 0;
+       }
+       if (!strcmp(var, "format.thread")) {
+               if (value && !strcasecmp(value, "deep")) {
+                       thread = THREAD_DEEP;
+                       return 0;
+               }
+               if (value && !strcasecmp(value, "shallow")) {
+                       thread = THREAD_SHALLOW;
+                       return 0;
+               }
+               thread = git_config_bool(var, value) && THREAD_SHALLOW;
+               return 0;
+       }
+       if (!strcmp(var, "format.signoff")) {
+               do_signoff = git_config_bool(var, value);
+               return 0;
+       }
+       if (!strcmp(var, "format.signature"))
+               return git_config_string(&signature, var, value);
+
+       return git_log_config(var, value, cb);
+}
+
+static FILE *realstdout = NULL;
+static const char *output_directory = NULL;
+static int outdir_offset;
+
+static int reopen_stdout(struct commit *commit, struct rev_info *rev)
+{
+       struct strbuf filename = STRBUF_INIT;
+       int suffix_len = strlen(fmt_patch_suffix) + 1;
+
+       if (output_directory) {
+               strbuf_addstr(&filename, output_directory);
+               if (filename.len >=
+                   PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len)
+                       return error("name of output directory is too long");
+               if (filename.buf[filename.len - 1] != '/')
+                       strbuf_addch(&filename, '/');
+       }
+
+       get_patch_filename(commit, rev->nr, fmt_patch_suffix, &filename);
+
+       if (!DIFF_OPT_TST(&rev->diffopt, QUICK))
+               fprintf(realstdout, "%s\n", filename.buf + outdir_offset);
+
+       if (freopen(filename.buf, "w", stdout) == NULL)
+               return error("Cannot open patch file %s", filename.buf);
+
+       strbuf_release(&filename);
+       return 0;
+}
+
+static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const char *prefix)
+{
+       struct rev_info check_rev;
+       struct commit *commit;
+       struct object *o1, *o2;
+       unsigned flags1, flags2;
+
+       if (rev->pending.nr != 2)
+               die("Need exactly one range.");
+
+       o1 = rev->pending.objects[0].item;
+       flags1 = o1->flags;
+       o2 = rev->pending.objects[1].item;
+       flags2 = o2->flags;
+
+       if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING))
+               die("Not a range.");
+
+       init_patch_ids(ids);
+
+       /* given a range a..b get all patch ids for b..a */
+       init_revisions(&check_rev, prefix);
+       o1->flags ^= UNINTERESTING;
+       o2->flags ^= UNINTERESTING;
+       add_pending_object(&check_rev, o1, "o1");
+       add_pending_object(&check_rev, o2, "o2");
+       if (prepare_revision_walk(&check_rev))
+               die("revision walk setup failed");
+
+       while ((commit = get_revision(&check_rev)) != NULL) {
+               /* ignore merges */
+               if (commit->parents && commit->parents->next)
+                       continue;
+
+               add_commit_patch_id(commit, ids);
+       }
+
+       /* reset for next revision walk */
+       clear_commit_marks((struct commit *)o1,
+                       SEEN | UNINTERESTING | SHOWN | ADDED);
+       clear_commit_marks((struct commit *)o2,
+                       SEEN | UNINTERESTING | SHOWN | ADDED);
+       o1->flags = flags1;
+       o2->flags = flags2;
+}
+
+static void gen_message_id(struct rev_info *info, char *base)
+{
+       const char *committer = git_committer_info(IDENT_WARN_ON_NO_NAME);
+       const char *email_start = strrchr(committer, '<');
+       const char *email_end = strrchr(committer, '>');
+       struct strbuf buf = STRBUF_INIT;
+       if (!email_start || !email_end || email_start > email_end - 1)
+               die("Could not extract email from committer identity.");
+       strbuf_addf(&buf, "%s.%lu.git.%.*s", base,
+                   (unsigned long) time(NULL),
+                   (int)(email_end - email_start - 1), email_start + 1);
+       info->message_id = strbuf_detach(&buf, NULL);
+}
+
+static void print_signature(void)
+{
+       if (signature && *signature)
+               printf("-- \n%s\n\n", signature);
+}
+
+static void make_cover_letter(struct rev_info *rev, int use_stdout,
+                             int numbered, int numbered_files,
+                             struct commit *origin,
+                             int nr, struct commit **list, struct commit *head)
+{
+       const char *committer;
+       const char *subject_start = NULL;
+       const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n";
+       const char *msg;
+       const char *extra_headers = rev->extra_headers;
+       struct shortlog log;
+       struct strbuf sb = STRBUF_INIT;
+       int i;
+       const char *encoding = "UTF-8";
+       struct diff_options opts;
+       int need_8bit_cte = 0;
+       struct commit *commit = NULL;
+
+       if (rev->commit_format != CMIT_FMT_EMAIL)
+               die("Cover letter needs email format");
+
+       committer = git_committer_info(0);
+
+       if (!numbered_files) {
+               /*
+                * We fake a commit for the cover letter so we get the filename
+                * desired.
+                */
+               commit = xcalloc(1, sizeof(*commit));
+               commit->buffer = xmalloc(400);
+               snprintf(commit->buffer, 400,
+                       "tree 0000000000000000000000000000000000000000\n"
+                       "parent %s\n"
+                       "author %s\n"
+                       "committer %s\n\n"
+                       "cover letter\n",
+                       sha1_to_hex(head->object.sha1), committer, committer);
+       }
+
+       if (!use_stdout && reopen_stdout(commit, rev))
+               return;
+
+       if (commit) {
+
+               free(commit->buffer);
+               free(commit);
+       }
+
+       log_write_email_headers(rev, head, &subject_start, &extra_headers,
+                               &need_8bit_cte);
+
+       for (i = 0; !need_8bit_cte && i < nr; i++)
+               if (has_non_ascii(list[i]->buffer))
+                       need_8bit_cte = 1;
+
+       msg = body;
+       pp_user_info(NULL, CMIT_FMT_EMAIL, &sb, committer, DATE_RFC2822,
+                    encoding);
+       pp_title_line(CMIT_FMT_EMAIL, &msg, &sb, subject_start, extra_headers,
+                     encoding, need_8bit_cte);
+       pp_remainder(CMIT_FMT_EMAIL, &msg, &sb, 0);
+       printf("%s\n", sb.buf);
+
+       strbuf_release(&sb);
+
+       shortlog_init(&log);
+       log.wrap_lines = 1;
+       log.wrap = 72;
+       log.in1 = 2;
+       log.in2 = 4;
+       for (i = 0; i < nr; i++)
+               shortlog_add_commit(&log, list[i]);
+
+       shortlog_output(&log);
+
+       /*
+        * We can only do diffstat with a unique reference point
+        */
+       if (!origin)
+               return;
+
+       memcpy(&opts, &rev->diffopt, sizeof(opts));
+       opts.output_format = DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+
+       diff_setup_done(&opts);
+
+       diff_tree_sha1(origin->tree->object.sha1,
+                      head->tree->object.sha1,
+                      "", &opts);
+       diffcore_std(&opts);
+       diff_flush(&opts);
+
+       printf("\n");
+       print_signature();
+}
+
+static const char *clean_message_id(const char *msg_id)
+{
+       char ch;
+       const char *a, *z, *m;
+
+       m = msg_id;
+       while ((ch = *m) && (isspace(ch) || (ch == '<')))
+               m++;
+       a = m;
+       z = NULL;
+       while ((ch = *m)) {
+               if (!isspace(ch) && (ch != '>'))
+                       z = m;
+               m++;
+       }
+       if (!z)
+               die("insane in-reply-to: %s", msg_id);
+       if (++z == m)
+               return a;
+       return xmemdupz(a, z - a);
+}
+
+static const char *set_outdir(const char *prefix, const char *output_directory)
+{
+       if (output_directory && is_absolute_path(output_directory))
+               return output_directory;
+
+       if (!prefix || !*prefix) {
+               if (output_directory)
+                       return output_directory;
+               /* The user did not explicitly ask for "./" */
+               outdir_offset = 2;
+               return "./";
+       }
+
+       outdir_offset = strlen(prefix);
+       if (!output_directory)
+               return prefix;
+
+       return xstrdup(prefix_filename(prefix, outdir_offset,
+                                      output_directory));
+}
+
+static const char * const builtin_format_patch_usage[] = {
+       "git format-patch [options] [<since> | <revision range>]",
+       NULL
+};
+
+static int keep_subject = 0;
+
+static int keep_callback(const struct option *opt, const char *arg, int unset)
+{
+       ((struct rev_info *)opt->value)->total = -1;
+       keep_subject = 1;
+       return 0;
+}
+
+static int subject_prefix = 0;
+
+static int subject_prefix_callback(const struct option *opt, const char *arg,
+                           int unset)
+{
+       subject_prefix = 1;
+       ((struct rev_info *)opt->value)->subject_prefix = arg;
+       return 0;
+}
+
+static int numbered_cmdline_opt = 0;
+
+static int numbered_callback(const struct option *opt, const char *arg,
+                            int unset)
+{
+       *(int *)opt->value = numbered_cmdline_opt = unset ? 0 : 1;
+       if (unset)
+               auto_number =  0;
+       return 0;
+}
+
+static int no_numbered_callback(const struct option *opt, const char *arg,
+                               int unset)
+{
+       return numbered_callback(opt, arg, 1);
+}
+
+static int output_directory_callback(const struct option *opt, const char *arg,
+                             int unset)
+{
+       const char **dir = (const char **)opt->value;
+       if (*dir)
+               die("Two output directories?");
+       *dir = arg;
+       return 0;
+}
+
+static int thread_callback(const struct option *opt, const char *arg, int unset)
+{
+       int *thread = (int *)opt->value;
+       if (unset)
+               *thread = 0;
+       else if (!arg || !strcmp(arg, "shallow"))
+               *thread = THREAD_SHALLOW;
+       else if (!strcmp(arg, "deep"))
+               *thread = THREAD_DEEP;
+       else
+               return 1;
+       return 0;
+}
+
+static int attach_callback(const struct option *opt, const char *arg, int unset)
+{
+       struct rev_info *rev = (struct rev_info *)opt->value;
+       if (unset)
+               rev->mime_boundary = NULL;
+       else if (arg)
+               rev->mime_boundary = arg;
+       else
+               rev->mime_boundary = git_version_string;
+       rev->no_inline = unset ? 0 : 1;
+       return 0;
+}
+
+static int inline_callback(const struct option *opt, const char *arg, int unset)
+{
+       struct rev_info *rev = (struct rev_info *)opt->value;
+       if (unset)
+               rev->mime_boundary = NULL;
+       else if (arg)
+               rev->mime_boundary = arg;
+       else
+               rev->mime_boundary = git_version_string;
+       rev->no_inline = 0;
+       return 0;
+}
+
+static int header_callback(const struct option *opt, const char *arg, int unset)
+{
+       if (unset) {
+               string_list_clear(&extra_hdr, 0);
+               string_list_clear(&extra_to, 0);
+               string_list_clear(&extra_cc, 0);
+       } else {
+           add_header(arg);
+       }
+       return 0;
+}
+
+static int to_callback(const struct option *opt, const char *arg, int unset)
+{
+       if (unset)
+               string_list_clear(&extra_to, 0);
+       else
+               string_list_append(&extra_to, arg);
+       return 0;
+}
+
+static int cc_callback(const struct option *opt, const char *arg, int unset)
+{
+       if (unset)
+               string_list_clear(&extra_cc, 0);
+       else
+               string_list_append(&extra_cc, arg);
+       return 0;
+}
+
+int cmd_format_patch(int argc, const char **argv, const char *prefix)
+{
+       struct commit *commit;
+       struct commit **list = NULL;
+       struct rev_info rev;
+       struct setup_revision_opt s_r_opt;
+       int nr = 0, total, i;
+       int use_stdout = 0;
+       int start_number = -1;
+       int numbered_files = 0;         /* _just_ numbers */
+       int ignore_if_in_upstream = 0;
+       int cover_letter = 0;
+       int boundary_count = 0;
+       int no_binary_diff = 0;
+       struct commit *origin = NULL, *head = NULL;
+       const char *in_reply_to = NULL;
+       struct patch_ids ids;
+       char *add_signoff = NULL;
+       struct strbuf buf = STRBUF_INIT;
+       int use_patch_format = 0;
+       const struct option builtin_format_patch_options[] = {
+               { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
+                           "use [PATCH n/m] even with a single patch",
+                           PARSE_OPT_NOARG, numbered_callback },
+               { OPTION_CALLBACK, 'N', "no-numbered", &numbered, NULL,
+                           "use [PATCH] even with multiple patches",
+                           PARSE_OPT_NOARG, no_numbered_callback },
+               OPT_BOOLEAN('s', "signoff", &do_signoff, "add Signed-off-by:"),
+               OPT_BOOLEAN(0, "stdout", &use_stdout,
+                           "print patches to standard out"),
+               OPT_BOOLEAN(0, "cover-letter", &cover_letter,
+                           "generate a cover letter"),
+               OPT_BOOLEAN(0, "numbered-files", &numbered_files,
+                           "use simple number sequence for output file names"),
+               OPT_STRING(0, "suffix", &fmt_patch_suffix, "sfx",
+                           "use <sfx> instead of '.patch'"),
+               OPT_INTEGER(0, "start-number", &start_number,
+                           "start numbering patches at <n> instead of 1"),
+               { OPTION_CALLBACK, 0, "subject-prefix", &rev, "prefix",
+                           "Use [<prefix>] instead of [PATCH]",
+                           PARSE_OPT_NONEG, subject_prefix_callback },
+               { OPTION_CALLBACK, 'o', "output-directory", &output_directory,
+                           "dir", "store resulting files in <dir>",
+                           PARSE_OPT_NONEG, output_directory_callback },
+               { OPTION_CALLBACK, 'k', "keep-subject", &rev, NULL,
+                           "don't strip/add [PATCH]",
+                           PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback },
+               OPT_BOOLEAN(0, "no-binary", &no_binary_diff,
+                           "don't output binary diffs"),
+               OPT_BOOLEAN(0, "ignore-if-in-upstream", &ignore_if_in_upstream,
+                           "don't include a patch matching a commit upstream"),
+               { OPTION_BOOLEAN, 'p', "no-stat", &use_patch_format, NULL,
+                 "show patch format instead of default (patch + stat)",
+                 PARSE_OPT_NONEG | PARSE_OPT_NOARG },
+               OPT_GROUP("Messaging"),
+               { OPTION_CALLBACK, 0, "add-header", NULL, "header",
+                           "add email header", 0, header_callback },
+               { OPTION_CALLBACK, 0, "to", NULL, "email", "add To: header",
+                           0, to_callback },
+               { OPTION_CALLBACK, 0, "cc", NULL, "email", "add Cc: header",
+                           0, cc_callback },
+               OPT_STRING(0, "in-reply-to", &in_reply_to, "message-id",
+                           "make first mail a reply to <message-id>"),
+               { OPTION_CALLBACK, 0, "attach", &rev, "boundary",
+                           "attach the patch", PARSE_OPT_OPTARG,
+                           attach_callback },
+               { OPTION_CALLBACK, 0, "inline", &rev, "boundary",
+                           "inline the patch",
+                           PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
+                           inline_callback },
+               { OPTION_CALLBACK, 0, "thread", &thread, "style",
+                           "enable message threading, styles: shallow, deep",
+                           PARSE_OPT_OPTARG, thread_callback },
+               OPT_STRING(0, "signature", &signature, "signature",
+                           "add a signature"),
+               OPT_END()
+       };
+
+       extra_hdr.strdup_strings = 1;
+       extra_to.strdup_strings = 1;
+       extra_cc.strdup_strings = 1;
+       git_config(git_format_config, NULL);
+       init_revisions(&rev, prefix);
+       rev.commit_format = CMIT_FMT_EMAIL;
+       rev.verbose_header = 1;
+       rev.diff = 1;
+       rev.combine_merges = 0;
+       rev.ignore_merges = 1;
+       DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
+       rev.subject_prefix = fmt_patch_subject_prefix;
+       memset(&s_r_opt, 0, sizeof(s_r_opt));
+       s_r_opt.def = "HEAD";
+
+       if (default_attach) {
+               rev.mime_boundary = default_attach;
+               rev.no_inline = 1;
+       }
+
+       /*
+        * Parse the arguments before setup_revisions(), or something
+        * like "git format-patch -o a123 HEAD^.." may fail; a123 is
+        * possibly a valid SHA1.
+        */
+       argc = parse_options(argc, argv, prefix, builtin_format_patch_options,
+                            builtin_format_patch_usage,
+                            PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
+                            PARSE_OPT_KEEP_DASHDASH);
+
+       if (do_signoff) {
+               const char *committer;
+               const char *endpos;
+               committer = git_committer_info(IDENT_ERROR_ON_NO_NAME);
+               endpos = strchr(committer, '>');
+               if (!endpos)
+                       die("bogus committer info %s", committer);
+               add_signoff = xmemdupz(committer, endpos - committer + 1);
+       }
+
+       for (i = 0; i < extra_hdr.nr; i++) {
+               strbuf_addstr(&buf, extra_hdr.items[i].string);
+               strbuf_addch(&buf, '\n');
+       }
+
+       if (extra_to.nr)
+               strbuf_addstr(&buf, "To: ");
+       for (i = 0; i < extra_to.nr; i++) {
+               if (i)
+                       strbuf_addstr(&buf, "    ");
+               strbuf_addstr(&buf, extra_to.items[i].string);
+               if (i + 1 < extra_to.nr)
+                       strbuf_addch(&buf, ',');
+               strbuf_addch(&buf, '\n');
+       }
+
+       if (extra_cc.nr)
+               strbuf_addstr(&buf, "Cc: ");
+       for (i = 0; i < extra_cc.nr; i++) {
+               if (i)
+                       strbuf_addstr(&buf, "    ");
+               strbuf_addstr(&buf, extra_cc.items[i].string);
+               if (i + 1 < extra_cc.nr)
+                       strbuf_addch(&buf, ',');
+               strbuf_addch(&buf, '\n');
+       }
+
+       rev.extra_headers = strbuf_detach(&buf, NULL);
+
+       if (start_number < 0)
+               start_number = 1;
+
+       /*
+        * If numbered is set solely due to format.numbered in config,
+        * and it would conflict with --keep-subject (-k) from the
+        * command line, reset "numbered".
+        */
+       if (numbered && keep_subject && !numbered_cmdline_opt)
+               numbered = 0;
+
+       if (numbered && keep_subject)
+               die ("-n and -k are mutually exclusive.");
+       if (keep_subject && subject_prefix)
+               die ("--subject-prefix and -k are mutually exclusive.");
+
+       argc = setup_revisions(argc, argv, &rev, &s_r_opt);
+       if (argc > 1)
+               die ("unrecognized argument: %s", argv[1]);
+
+       if (rev.diffopt.output_format & DIFF_FORMAT_NAME)
+               die("--name-only does not make sense");
+       if (rev.diffopt.output_format & DIFF_FORMAT_NAME_STATUS)
+               die("--name-status does not make sense");
+       if (rev.diffopt.output_format & DIFF_FORMAT_CHECKDIFF)
+               die("--check does not make sense");
+
+       if (!use_patch_format &&
+               (!rev.diffopt.output_format ||
+                rev.diffopt.output_format == DIFF_FORMAT_PATCH))
+               rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY;
+
+       /* Always generate a patch */
+       rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
+
+       if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff)
+               DIFF_OPT_SET(&rev.diffopt, BINARY);
+
+       if (rev.show_notes)
+               init_display_notes(&rev.notes_opt);
+
+       if (!use_stdout)
+               output_directory = set_outdir(prefix, output_directory);
+
+       if (output_directory) {
+               if (use_stdout)
+                       die("standard output, or directory, which one?");
+               if (mkdir(output_directory, 0777) < 0 && errno != EEXIST)
+                       die_errno("Could not create directory '%s'",
+                                 output_directory);
+       }
+
+       if (rev.pending.nr == 1) {
+               if (rev.max_count < 0 && !rev.show_root_diff) {
+                       /*
+                        * This is traditional behaviour of "git format-patch
+                        * origin" that prepares what the origin side still
+                        * does not have.
+                        */
+                       rev.pending.objects[0].item->flags |= UNINTERESTING;
+                       add_head_to_pending(&rev);
+               }
+               /*
+                * Otherwise, it is "format-patch -22 HEAD", and/or
+                * "format-patch --root HEAD".  The user wants
+                * get_revision() to do the usual traversal.
+                */
+       }
+
+       /*
+        * We cannot move this anywhere earlier because we do want to
+        * know if --root was given explicitly from the command line.
+        */
+       rev.show_root_diff = 1;
+
+       if (cover_letter) {
+               /* remember the range */
+               int i;
+               for (i = 0; i < rev.pending.nr; i++) {
+                       struct object *o = rev.pending.objects[i].item;
+                       if (!(o->flags & UNINTERESTING))
+                               head = (struct commit *)o;
+               }
+               /* We can't generate a cover letter without any patches */
+               if (!head)
+                       return 0;
+       }
+
+       if (ignore_if_in_upstream) {
+               /* Don't say anything if head and upstream are the same. */
+               if (rev.pending.nr == 2) {
+                       struct object_array_entry *o = rev.pending.objects;
+                       if (hashcmp(o[0].item->sha1, o[1].item->sha1) == 0)
+                               return 0;
+               }
+               get_patch_ids(&rev, &ids, prefix);
+       }
+
+       if (!use_stdout)
+               realstdout = xfdopen(xdup(1), "w");
+
+       if (prepare_revision_walk(&rev))
+               die("revision walk setup failed");
+       rev.boundary = 1;
+       while ((commit = get_revision(&rev)) != NULL) {
+               if (commit->object.flags & BOUNDARY) {
+                       boundary_count++;
+                       origin = (boundary_count == 1) ? commit : NULL;
+                       continue;
+               }
+
+               /* ignore merges */
+               if (commit->parents && commit->parents->next)
+                       continue;
+
+               if (ignore_if_in_upstream &&
+                               has_commit_patch_id(commit, &ids))
+                       continue;
+
+               nr++;
+               list = xrealloc(list, nr * sizeof(list[0]));
+               list[nr - 1] = commit;
+       }
+       total = nr;
+       if (!keep_subject && auto_number && total > 1)
+               numbered = 1;
+       if (numbered)
+               rev.total = total + start_number - 1;
+       if (in_reply_to || thread || cover_letter)
+               rev.ref_message_ids = xcalloc(1, sizeof(struct string_list));
+       if (in_reply_to) {
+               const char *msgid = clean_message_id(in_reply_to);
+               string_list_append(rev.ref_message_ids, msgid);
+       }
+       rev.numbered_files = numbered_files;
+       rev.patch_suffix = fmt_patch_suffix;
+       if (cover_letter) {
+               if (thread)
+                       gen_message_id(&rev, "cover");
+               make_cover_letter(&rev, use_stdout, numbered, numbered_files,
+                                 origin, nr, list, head);
+               total++;
+               start_number--;
+       }
+       rev.add_signoff = add_signoff;
+       while (0 <= --nr) {
+               int shown;
+               commit = list[nr];
+               rev.nr = total - nr + (start_number - 1);
+               /* Make the second and subsequent mails replies to the first */
+               if (thread) {
+                       /* Have we already had a message ID? */
+                       if (rev.message_id) {
+                               /*
+                                * For deep threading: make every mail
+                                * a reply to the previous one, no
+                                * matter what other options are set.
+                                *
+                                * For shallow threading:
+                                *
+                                * Without --cover-letter and
+                                * --in-reply-to, make every mail a
+                                * reply to the one before.
+                                *
+                                * With --in-reply-to but no
+                                * --cover-letter, make every mail a
+                                * reply to the <reply-to>.
+                                *
+                                * With --cover-letter, make every
+                                * mail but the cover letter a reply
+                                * to the cover letter.  The cover
+                                * letter is a reply to the
+                                * --in-reply-to, if specified.
+                                */
+                               if (thread == THREAD_SHALLOW
+                                   && rev.ref_message_ids->nr > 0
+                                   && (!cover_letter || rev.nr > 1))
+                                       free(rev.message_id);
+                               else
+                                       string_list_append(rev.ref_message_ids,
+                                                          rev.message_id);
+                       }
+                       gen_message_id(&rev, sha1_to_hex(commit->object.sha1));
+               }
+
+               if (!use_stdout && reopen_stdout(numbered_files ? NULL : commit,
+                                                &rev))
+                       die("Failed to create output files");
+               shown = log_tree_commit(&rev, commit);
+               free(commit->buffer);
+               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
+                               print_signature();
+               }
+               if (!use_stdout)
+                       fclose(stdout);
+       }
+       free(list);
+       string_list_clear(&extra_to, 0);
+       string_list_clear(&extra_cc, 0);
+       string_list_clear(&extra_hdr, 0);
+       if (ignore_if_in_upstream)
+               free_patch_ids(&ids);
+       return 0;
+}
+
+static int add_pending_commit(const char *arg, struct rev_info *revs, int flags)
+{
+       unsigned char sha1[20];
+       if (get_sha1(arg, sha1) == 0) {
+               struct commit *commit = lookup_commit_reference(sha1);
+               if (commit) {
+                       commit->object.flags |= flags;
+                       add_pending_object(revs, &commit->object, arg);
+                       return 0;
+               }
+       }
+       return -1;
+}
+
+static const char * const cherry_usage[] = {
+       "git cherry [-v] [<upstream> [<head> [<limit>]]]",
+       NULL
+};
+
+int cmd_cherry(int argc, const char **argv, const char *prefix)
+{
+       struct rev_info revs;
+       struct patch_ids ids;
+       struct commit *commit;
+       struct commit_list *list = NULL;
+       struct branch *current_branch;
+       const char *upstream;
+       const char *head = "HEAD";
+       const char *limit = NULL;
+       int verbose = 0, abbrev = 0;
+
+       struct option options[] = {
+               OPT__ABBREV(&abbrev),
+               OPT__VERBOSE(&verbose),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, prefix, options, cherry_usage, 0);
+
+       switch (argc) {
+       case 3:
+               limit = argv[2];
+               /* FALLTHROUGH */
+       case 2:
+               head = argv[1];
+               /* FALLTHROUGH */
+       case 1:
+               upstream = argv[0];
+               break;
+       default:
+               current_branch = branch_get(NULL);
+               if (!current_branch || !current_branch->merge
+                                       || !current_branch->merge[0]
+                                       || !current_branch->merge[0]->dst) {
+                       fprintf(stderr, "Could not find a tracked"
+                                       " remote branch, please"
+                                       " specify <upstream> manually.\n");
+                       usage_with_options(cherry_usage, options);
+               }
+
+               upstream = current_branch->merge[0]->dst;
+       }
+
+       init_revisions(&revs, prefix);
+       revs.diff = 1;
+       revs.combine_merges = 0;
+       revs.ignore_merges = 1;
+       DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
+
+       if (add_pending_commit(head, &revs, 0))
+               die("Unknown commit %s", head);
+       if (add_pending_commit(upstream, &revs, UNINTERESTING))
+               die("Unknown commit %s", upstream);
+
+       /* Don't say anything if head and upstream are the same. */
+       if (revs.pending.nr == 2) {
+               struct object_array_entry *o = revs.pending.objects;
+               if (hashcmp(o[0].item->sha1, o[1].item->sha1) == 0)
+                       return 0;
+       }
+
+       get_patch_ids(&revs, &ids, prefix);
+
+       if (limit && add_pending_commit(limit, &revs, UNINTERESTING))
+               die("Unknown commit %s", limit);
+
+       /* reverse the list of commits */
+       if (prepare_revision_walk(&revs))
+               die("revision walk setup failed");
+       while ((commit = get_revision(&revs)) != NULL) {
+               /* ignore merges */
+               if (commit->parents && commit->parents->next)
+                       continue;
+
+               commit_list_insert(commit, &list);
+       }
+
+       while (list) {
+               char sign = '+';
+
+               commit = list->item;
+               if (has_commit_patch_id(commit, &ids))
+                       sign = '-';
+
+               if (verbose) {
+                       struct strbuf buf = STRBUF_INIT;
+                       struct pretty_print_context ctx = {0};
+                       pretty_print_commit(CMIT_FMT_ONELINE, commit,
+                                           &buf, &ctx);
+                       printf("%c %s %s\n", sign,
+                              find_unique_abbrev(commit->object.sha1, abbrev),
+                              buf.buf);
+                       strbuf_release(&buf);
+               }
+               else {
+                       printf("%c %s\n", sign,
+                              find_unique_abbrev(commit->object.sha1, abbrev));
+               }
+
+               list = list->next;
+       }
+
+       free_patch_ids(&ids);
+       return 0;
+}
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
new file mode 100644 (file)
index 0000000..bb4f612
--- /dev/null
@@ -0,0 +1,618 @@
+/*
+ * 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 "cache.h"
+#include "quote.h"
+#include "dir.h"
+#include "builtin.h"
+#include "tree.h"
+#include "parse-options.h"
+#include "resolve-undo.h"
+#include "string-list.h"
+
+static int abbrev;
+static int show_deleted;
+static int show_cached;
+static int show_others;
+static int show_stage;
+static int show_unmerged;
+static int show_resolve_undo;
+static int show_modified;
+static int show_killed;
+static int show_valid_bit;
+static int line_terminator = '\n';
+static int debug_mode;
+
+static const char *prefix;
+static int max_prefix_len;
+static int prefix_len;
+static const char **pathspec;
+static int error_unmatch;
+static char *ps_matched;
+static const char *with_tree;
+static int exc_given;
+
+static const char *tag_cached = "";
+static const char *tag_unmerged = "";
+static const char *tag_removed = "";
+static const char *tag_other = "";
+static const char *tag_killed = "";
+static const char *tag_modified = "";
+static const char *tag_skip_worktree = "";
+static const char *tag_resolve_undo = "";
+
+static void write_name(const char* name, size_t len)
+{
+       write_name_quoted_relative(name, len, prefix, prefix_len, stdout,
+                       line_terminator);
+}
+
+static void show_dir_entry(const char *tag, struct dir_entry *ent)
+{
+       int len = max_prefix_len;
+
+       if (len >= ent->len)
+               die("git ls-files: internal error - directory entry not superset of prefix");
+
+       if (!match_pathspec(pathspec, ent->name, ent->len, len, ps_matched))
+               return;
+
+       fputs(tag, stdout);
+       write_name(ent->name, ent->len);
+}
+
+static void show_other_files(struct dir_struct *dir)
+{
+       int i;
+
+       for (i = 0; i < dir->nr; i++) {
+               struct dir_entry *ent = dir->entries[i];
+               if (!cache_name_is_other(ent->name, ent->len))
+                       continue;
+               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 = max_prefix_len;
+
+       if (len >= ce_namelen(ce))
+               die("git ls-files: internal error - cache entry not superset of prefix");
+
+       if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), len, ps_matched))
+               return;
+
+       if (tag && *tag && show_valid_bit &&
+           (ce->ce_flags & 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);
+       } else {
+               printf("%s%06o %s %d\t",
+                      tag,
+                      ce->ce_mode,
+                      find_unique_abbrev(ce->sha1,abbrev),
+                      ce_stage(ce));
+       }
+       write_name(ce->name, ce_namelen(ce));
+       if (debug_mode) {
+               printf("  ctime: %d:%d\n", ce->ce_ctime.sec, ce->ce_ctime.nsec);
+               printf("  mtime: %d:%d\n", ce->ce_mtime.sec, ce->ce_mtime.nsec);
+               printf("  dev: %d\tino: %d\n", ce->ce_dev, ce->ce_ino);
+               printf("  uid: %d\tgid: %d\n", ce->ce_uid, ce->ce_gid);
+               printf("  size: %d\tflags: %x\n", ce->ce_size, ce->ce_flags);
+       }
+}
+
+static void show_ru_info(void)
+{
+       struct string_list_item *item;
+
+       if (!the_index.resolve_undo)
+               return;
+
+       for_each_string_list_item(item, the_index.resolve_undo) {
+               const char *path = item->string;
+               struct resolve_undo_info *ui = item->util;
+               int i, len;
+
+               len = strlen(path);
+               if (len < max_prefix_len)
+                       continue; /* outside of the prefix */
+               if (!match_pathspec(pathspec, path, len, max_prefix_len, ps_matched))
+                       continue; /* uninterested */
+               for (i = 0; i < 3; i++) {
+                       if (!ui->mode[i])
+                               continue;
+                       printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i],
+                              find_unique_abbrev(ui->sha1[i], abbrev),
+                              i + 1);
+                       write_name(path, len);
+               }
+       }
+}
+
+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) {
+               fill_directory(dir, pathspec);
+               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];
+                       int dtype = ce_to_dtype(ce);
+                       if (dir->flags & DIR_SHOW_IGNORED &&
+                           !excluded(dir, ce->name, &dtype))
+                               continue;
+                       if (show_unmerged && !ce_stage(ce))
+                               continue;
+                       if (ce->ce_flags & CE_UPDATE)
+                               continue;
+                       show_ce_entry(ce_stage(ce) ? tag_unmerged :
+                               (ce_skip_worktree(ce) ? tag_skip_worktree : 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;
+                       int dtype = ce_to_dtype(ce);
+                       if (dir->flags & DIR_SHOW_IGNORED &&
+                           !excluded(dir, ce->name, &dtype))
+                               continue;
+                       if (ce->ce_flags & CE_UPDATE)
+                               continue;
+                       if (ce_skip_worktree(ce))
+                               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(const char *prefix)
+{
+       int pos = cache_name_pos(prefix, max_prefix_len);
+       unsigned int first, last;
+
+       if (pos < 0)
+               pos = -pos-1;
+       memmove(active_cache, active_cache + pos,
+               (active_nr - pos) * sizeof(struct cache_entry *));
+       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, max_prefix_len)) {
+                       first = next+1;
+                       continue;
+               }
+               last = next;
+       }
+       active_nr = last;
+}
+
+static const char *pathspec_prefix(const char *prefix)
+{
+       const char **p, *n, *prev;
+       unsigned long max;
+
+       if (!pathspec) {
+               max_prefix_len = prefix ? strlen(prefix) : 0;
+               return prefix;
+       }
+
+       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;
+               }
+       }
+
+       max_prefix_len = max;
+       return max ? xmemdupz(prev, max) : NULL;
+}
+
+static void strip_trailing_slash_from_submodules(void)
+{
+       const char **p;
+
+       for (p = pathspec; *p != NULL; p++) {
+               int len = strlen(*p), pos;
+
+               if (len < 1 || (*p)[len - 1] != '/')
+                       continue;
+               pos = cache_name_pos(*p, len - 1);
+               if (pos >= 0 && S_ISGITLINK(active_cache[pos]->ce_mode))
+                       *p = xstrndup(*p, len - 1);
+       }
+}
+
+/*
+ * Read the tree specified with --with-tree option
+ * (typically, HEAD) into stage #1 and then
+ * squash them down to stage #0.  This is used for
+ * --error-unmatch to list and check the path patterns
+ * that were given from the command line.  We are not
+ * going to write this index out.
+ */
+void overlay_tree_on_cache(const char *tree_name, const char *prefix)
+{
+       struct tree *tree;
+       unsigned char sha1[20];
+       const char **match;
+       struct cache_entry *last_stage0 = NULL;
+       int i;
+
+       if (get_sha1(tree_name, sha1))
+               die("tree-ish %s not found.", tree_name);
+       tree = parse_tree_indirect(sha1);
+       if (!tree)
+               die("bad tree-ish %s", tree_name);
+
+       /* Hoist the unmerged entries up to stage #3 to make room */
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (!ce_stage(ce))
+                       continue;
+               ce->ce_flags |= CE_STAGEMASK;
+       }
+
+       if (prefix) {
+               static const char *(matchbuf[2]);
+               matchbuf[0] = prefix;
+               matchbuf[1] = NULL;
+               match = matchbuf;
+       } else
+               match = NULL;
+       if (read_tree(tree, 1, match))
+               die("unable to read tree entries %s", tree_name);
+
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               switch (ce_stage(ce)) {
+               case 0:
+                       last_stage0 = ce;
+                       /* fallthru */
+               default:
+                       continue;
+               case 1:
+                       /*
+                        * If there is stage #0 entry for this, we do not
+                        * need to show it.  We use CE_UPDATE bit to mark
+                        * such an entry.
+                        */
+                       if (last_stage0 &&
+                           !strcmp(last_stage0->name, ce->name))
+                               ce->ce_flags |= CE_UPDATE;
+               }
+       }
+}
+
+int report_path_error(const char *ps_matched, const char **pathspec, int prefix_len)
+{
+       /*
+        * Make sure all pathspec matched; otherwise it is an error.
+        */
+       int num, errors = 0;
+       for (num = 0; pathspec[num]; num++) {
+               int other, found_dup;
+
+               if (ps_matched[num])
+                       continue;
+               /*
+                * The caller might have fed identical pathspec
+                * twice.  Do not barf on such a mistake.
+                */
+               for (found_dup = other = 0;
+                    !found_dup && pathspec[other];
+                    other++) {
+                       if (other == num || !ps_matched[other])
+                               continue;
+                       if (!strcmp(pathspec[other], pathspec[num]))
+                               /*
+                                * Ok, we have a match already.
+                                */
+                               found_dup = 1;
+               }
+               if (found_dup)
+                       continue;
+
+               error("pathspec '%s' did not match any file(s) known to git.",
+                     pathspec[num] + prefix_len);
+               errors++;
+       }
+       return errors;
+}
+
+static const char * const ls_files_usage[] = {
+       "git ls-files [options] [<file>]*",
+       NULL
+};
+
+static int option_parse_z(const struct option *opt,
+                         const char *arg, int unset)
+{
+       line_terminator = unset ? '\n' : '\0';
+
+       return 0;
+}
+
+static int option_parse_exclude(const struct option *opt,
+                               const char *arg, int unset)
+{
+       struct exclude_list *list = opt->value;
+
+       exc_given = 1;
+       add_exclude(arg, "", 0, list);
+
+       return 0;
+}
+
+static int option_parse_exclude_from(const struct option *opt,
+                                    const char *arg, int unset)
+{
+       struct dir_struct *dir = opt->value;
+
+       exc_given = 1;
+       add_excludes_from_file(dir, arg);
+
+       return 0;
+}
+
+static int option_parse_exclude_standard(const struct option *opt,
+                                        const char *arg, int unset)
+{
+       struct dir_struct *dir = opt->value;
+
+       exc_given = 1;
+       setup_standard_excludes(dir);
+
+       return 0;
+}
+
+int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
+{
+       int require_work_tree = 0, show_tag = 0;
+       const char *max_prefix;
+       struct dir_struct dir;
+       struct option builtin_ls_files_options[] = {
+               { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
+                       "paths are separated with NUL character",
+                       PARSE_OPT_NOARG, option_parse_z },
+               OPT_BOOLEAN('t', NULL, &show_tag,
+                       "identify the file status with tags"),
+               OPT_BOOLEAN('v', NULL, &show_valid_bit,
+                       "use lowercase letters for 'assume unchanged' files"),
+               OPT_BOOLEAN('c', "cached", &show_cached,
+                       "show cached files in the output (default)"),
+               OPT_BOOLEAN('d', "deleted", &show_deleted,
+                       "show deleted files in the output"),
+               OPT_BOOLEAN('m', "modified", &show_modified,
+                       "show modified files in the output"),
+               OPT_BOOLEAN('o', "others", &show_others,
+                       "show other files in the output"),
+               OPT_BIT('i', "ignored", &dir.flags,
+                       "show ignored files in the output",
+                       DIR_SHOW_IGNORED),
+               OPT_BOOLEAN('s', "stage", &show_stage,
+                       "show staged contents' object name in the output"),
+               OPT_BOOLEAN('k', "killed", &show_killed,
+                       "show files on the filesystem that need to be removed"),
+               OPT_BIT(0, "directory", &dir.flags,
+                       "show 'other' directories' name only",
+                       DIR_SHOW_OTHER_DIRECTORIES),
+               OPT_NEGBIT(0, "empty-directory", &dir.flags,
+                       "don't show empty directories",
+                       DIR_HIDE_EMPTY_DIRECTORIES),
+               OPT_BOOLEAN('u', "unmerged", &show_unmerged,
+                       "show unmerged files in the output"),
+               OPT_BOOLEAN(0, "resolve-undo", &show_resolve_undo,
+                           "show resolve-undo information"),
+               { OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], "pattern",
+                       "skip files matching pattern",
+                       0, option_parse_exclude },
+               { OPTION_CALLBACK, 'X', "exclude-from", &dir, "file",
+                       "exclude patterns are read from <file>",
+                       0, option_parse_exclude_from },
+               OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, "file",
+                       "read additional per-directory exclude patterns in <file>"),
+               { OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL,
+                       "add the standard git exclusions",
+                       PARSE_OPT_NOARG, option_parse_exclude_standard },
+               { OPTION_SET_INT, 0, "full-name", &prefix_len, NULL,
+                       "make the output relative to the project top directory",
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL },
+               OPT_BOOLEAN(0, "error-unmatch", &error_unmatch,
+                       "if any <file> is not in the index, treat this as an error"),
+               OPT_STRING(0, "with-tree", &with_tree, "tree-ish",
+                       "pretend that paths removed since <tree-ish> are still present"),
+               OPT__ABBREV(&abbrev),
+               OPT_BOOLEAN(0, "debug", &debug_mode, "show debugging data"),
+               OPT_END()
+       };
+
+       memset(&dir, 0, sizeof(dir));
+       prefix = cmd_prefix;
+       if (prefix)
+               prefix_len = strlen(prefix);
+       git_config(git_default_config, NULL);
+
+       if (read_cache() < 0)
+               die("index file corrupt");
+
+       argc = parse_options(argc, argv, prefix, builtin_ls_files_options,
+                       ls_files_usage, 0);
+       if (show_tag || show_valid_bit) {
+               tag_cached = "H ";
+               tag_unmerged = "M ";
+               tag_removed = "R ";
+               tag_modified = "C ";
+               tag_other = "? ";
+               tag_killed = "K ";
+               tag_skip_worktree = "S ";
+               tag_resolve_undo = "U ";
+       }
+       if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed)
+               require_work_tree = 1;
+       if (show_unmerged)
+               /*
+                * There's no point in showing unmerged unless
+                * you also show the stage information.
+                */
+               show_stage = 1;
+       if (dir.exclude_per_dir)
+               exc_given = 1;
+
+       if (require_work_tree && !is_inside_work_tree())
+               setup_work_tree();
+
+       pathspec = get_pathspec(prefix, argv);
+
+       /* be nice with submodule paths ending in a slash */
+       if (pathspec)
+               strip_trailing_slash_from_submodules();
+
+       /* Find common prefix for all pathspec's */
+       max_prefix = pathspec_prefix(prefix);
+
+       /* 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.flags & DIR_SHOW_IGNORED) && !exc_given)
+               die("ls-files --ignored needs some exclude pattern");
+
+       /* With no flags, we default to showing the cached files */
+       if (!(show_stage | show_deleted | show_others | show_unmerged |
+             show_killed | show_modified | show_resolve_undo))
+               show_cached = 1;
+
+       if (max_prefix)
+               prune_cache(max_prefix);
+       if (with_tree) {
+               /*
+                * Basic sanity check; show-stages and show-unmerged
+                * would not make any sense with this option.
+                */
+               if (show_stage || show_unmerged)
+                       die("ls-files --with-tree is incompatible with -s or -u");
+               overlay_tree_on_cache(with_tree, max_prefix);
+       }
+       show_files(&dir);
+       if (show_resolve_undo)
+               show_ru_info();
+
+       if (ps_matched) {
+               int bad;
+               bad = report_path_error(ps_matched, pathspec, prefix_len);
+               if (bad)
+                       fprintf(stderr, "Did you forget to 'git add'?\n");
+
+               return bad ? 1 : 0;
+       }
+
+       return 0;
+}
diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c
new file mode 100644 (file)
index 0000000..97eed40
--- /dev/null
@@ -0,0 +1,115 @@
+#include "builtin.h"
+#include "cache.h"
+#include "transport.h"
+#include "remote.h"
+
+static const char ls_remote_usage[] =
+"git ls-remote [--heads] [--tags]  [-u <exec> | --upload-pack <exec>]\n"
+"                     [-q|--quiet] [<repository> [<refs>...]]";
+
+/*
+ * Is there one among the list of patterns that match the tail part
+ * of the path?
+ */
+static int tail_match(const char **pattern, const char *path)
+{
+       const char *p;
+       char pathbuf[PATH_MAX];
+
+       if (!pattern)
+               return 1; /* no restriction */
+
+       if (snprintf(pathbuf, sizeof(pathbuf), "/%s", path) > sizeof(pathbuf))
+               return error("insanely long ref %.*s...", 20, path);
+       while ((p = *(pattern++)) != NULL) {
+               if (!fnmatch(p, pathbuf, 0))
+                       return 1;
+       }
+       return 0;
+}
+
+int cmd_ls_remote(int argc, const char **argv, const char *prefix)
+{
+       int i;
+       const char *dest = NULL;
+       unsigned flags = 0;
+       int quiet = 0;
+       const char *uploadpack = NULL;
+       const char **pattern = NULL;
+
+       struct remote *remote;
+       struct transport *transport;
+       const struct ref *ref;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (*arg == '-') {
+                       if (!prefixcmp(arg, "--upload-pack=")) {
+                               uploadpack = arg + 14;
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--exec=")) {
+                               uploadpack = arg + 7;
+                               continue;
+                       }
+                       if (!strcmp("--tags", arg) || !strcmp("-t", arg)) {
+                               flags |= REF_TAGS;
+                               continue;
+                       }
+                       if (!strcmp("--heads", arg) || !strcmp("-h", arg)) {
+                               flags |= REF_HEADS;
+                               continue;
+                       }
+                       if (!strcmp("--refs", arg)) {
+                               flags |= REF_NORMAL;
+                               continue;
+                       }
+                       if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) {
+                               quiet = 1;
+                               continue;
+                       }
+                       usage(ls_remote_usage);
+               }
+               dest = arg;
+               i++;
+               break;
+       }
+
+       if (argv[i]) {
+               int j;
+               pattern = xcalloc(sizeof(const char *), argc - i + 1);
+               for (j = i; j < argc; j++) {
+                       int len = strlen(argv[j]);
+                       char *p = xmalloc(len + 3);
+                       sprintf(p, "*/%s", argv[j]);
+                       pattern[j - i] = p;
+               }
+       }
+       remote = remote_get(dest);
+       if (!remote) {
+               if (dest)
+                       die("bad repository '%s'", dest);
+               die("No remote configured to list refs from.");
+       }
+       if (!remote->url_nr)
+               die("remote %s has no configured URL", dest);
+       transport = transport_get(remote, NULL);
+       if (uploadpack != NULL)
+               transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack);
+
+       ref = transport_get_remote_refs(transport);
+       if (transport_disconnect(transport))
+               return 1;
+
+       if (!dest && !quiet)
+               fprintf(stderr, "From %s\n", *remote->url);
+       for ( ; ref; ref = ref->next) {
+               if (!check_ref_type(ref, flags))
+                       continue;
+               if (!tail_match(pattern, ref->name))
+                       continue;
+               printf("%s      %s\n", sha1_to_hex(ref->old_sha1), ref->name);
+       }
+       return 0;
+}
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
new file mode 100644 (file)
index 0000000..a818756
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "blob.h"
+#include "tree.h"
+#include "commit.h"
+#include "quote.h"
+#include "builtin.h"
+#include "parse-options.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
+#define LS_SHOW_SIZE 16
+static int abbrev;
+static int ls_options;
+static const char **pathspec;
+static int chomp_prefix;
+static const char *ls_tree_prefix;
+
+static const  char * const ls_tree_usage[] = {
+       "git ls-tree [<options>] <tree-ish> [path...]",
+       NULL
+};
+
+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 (spec[len] && spec[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, void *context)
+{
+       int retval = 0;
+       const char *type = blob_type;
+
+       if (S_ISGITLINK(mode)) {
+               /*
+                * Maybe we want to have some recursive version here?
+                *
+                * Something similar to this incomplete example:
+                *
+               if (show_subprojects(base, baselen, pathname))
+                       retval = READ_TREE_RECURSIVE;
+                *
+                */
+               type = commit_type;
+       } else 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(ls_tree_prefix, base, chomp_prefix)))
+               return 0;
+
+       if (!(ls_options & LS_NAME_ONLY)) {
+               if (ls_options & LS_SHOW_SIZE) {
+                       char size_text[24];
+                       if (!strcmp(type, blob_type)) {
+                               unsigned long size;
+                               if (sha1_object_info(sha1, &size) == OBJ_BAD)
+                                       strcpy(size_text, "BAD");
+                               else
+                                       snprintf(size_text, sizeof(size_text),
+                                                "%lu", size);
+                       } else
+                               strcpy(size_text, "-");
+                       printf("%06o %s %s %7s\t", mode, type,
+                              find_unique_abbrev(sha1, abbrev),
+                              size_text);
+               } else
+                       printf("%06o %s %s\t", mode, type,
+                              find_unique_abbrev(sha1, abbrev));
+       }
+       write_name_quotedpfx(base + chomp_prefix, baselen - chomp_prefix,
+                         pathname, stdout, line_termination);
+       return retval;
+}
+
+int cmd_ls_tree(int argc, const char **argv, const char *prefix)
+{
+       unsigned char sha1[20];
+       struct tree *tree;
+       int full_tree = 0;
+       const struct option ls_tree_options[] = {
+               OPT_BIT('d', NULL, &ls_options, "only show trees",
+                       LS_TREE_ONLY),
+               OPT_BIT('r', NULL, &ls_options, "recurse into subtrees",
+                       LS_RECURSIVE),
+               OPT_BIT('t', NULL, &ls_options, "show trees when recursing",
+                       LS_SHOW_TREES),
+               OPT_SET_INT('z', NULL, &line_termination,
+                           "terminate entries with NUL byte", 0),
+               OPT_BIT('l', "long", &ls_options, "include object size",
+                       LS_SHOW_SIZE),
+               OPT_BIT(0, "name-only", &ls_options, "list only filenames",
+                       LS_NAME_ONLY),
+               OPT_BIT(0, "name-status", &ls_options, "list only filenames",
+                       LS_NAME_ONLY),
+               OPT_SET_INT(0, "full-name", &chomp_prefix,
+                           "use full path names", 0),
+               OPT_BOOLEAN(0, "full-tree", &full_tree,
+                           "list entire tree; not just current directory "
+                           "(implies --full-name)"),
+               OPT__ABBREV(&abbrev),
+               OPT_END()
+       };
+
+       git_config(git_default_config, NULL);
+       ls_tree_prefix = prefix;
+       if (prefix && *prefix)
+               chomp_prefix = strlen(prefix);
+
+       argc = parse_options(argc, argv, prefix, ls_tree_options,
+                            ls_tree_usage, 0);
+       if (full_tree) {
+               ls_tree_prefix = prefix = NULL;
+               chomp_prefix = 0;
+       }
+       /* -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 < 1)
+               usage_with_options(ls_tree_usage, ls_tree_options);
+       if (get_sha1(argv[0], sha1))
+               die("Not a valid object name %s", argv[0]);
+
+       pathspec = get_pathspec(prefix, argv + 1);
+       tree = parse_tree_indirect(sha1);
+       if (!tree)
+               die("not a tree object");
+       read_tree_recursive(tree, "", 0, 0, pathspec, show_tree, NULL);
+
+       return 0;
+}
diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c
new file mode 100644 (file)
index 0000000..2320d98
--- /dev/null
@@ -0,0 +1,1064 @@
+/*
+ * Another stupid program, this one parsing the headers of an
+ * email to figure out authorship and subject
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "utf8.h"
+#include "strbuf.h"
+
+static FILE *cmitmsg, *patchfile, *fin, *fout;
+
+static int keep_subject;
+static int keep_non_patch_brackets_in_subject;
+static const char *metainfo_charset;
+static struct strbuf line = STRBUF_INIT;
+static struct strbuf name = STRBUF_INIT;
+static struct strbuf email = STRBUF_INIT;
+
+static enum  {
+       TE_DONTCARE, TE_QP, TE_BASE64
+} transfer_encoding;
+static enum  {
+       TYPE_TEXT, TYPE_OTHER
+} message_type;
+
+static struct strbuf charset = STRBUF_INIT;
+static int patch_lines;
+static struct strbuf **p_hdr_data, **s_hdr_data;
+static int use_scissors;
+static int use_inbody_headers = 1;
+
+#define MAX_HDR_PARSED 10
+#define MAX_BOUNDARIES 5
+
+static void cleanup_space(struct strbuf *sb);
+
+
+static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email)
+{
+       struct strbuf *src = name;
+       if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') ||
+               strchr(name->buf, '<') || strchr(name->buf, '>'))
+               src = email;
+       else if (name == out)
+               return;
+       strbuf_reset(out);
+       strbuf_addbuf(out, src);
+}
+
+static void parse_bogus_from(const struct strbuf *line)
+{
+       /* John Doe <johndoe> */
+
+       char *bra, *ket;
+       /* This is fallback, so do not bother if we already have an
+        * e-mail address.
+        */
+       if (email.len)
+               return;
+
+       bra = strchr(line->buf, '<');
+       if (!bra)
+               return;
+       ket = strchr(bra, '>');
+       if (!ket)
+               return;
+
+       strbuf_reset(&email);
+       strbuf_add(&email, bra + 1, ket - bra - 1);
+
+       strbuf_reset(&name);
+       strbuf_add(&name, line->buf, bra - line->buf);
+       strbuf_trim(&name);
+       get_sane_name(&name, &name, &email);
+}
+
+static void handle_from(const struct strbuf *from)
+{
+       char *at;
+       size_t el;
+       struct strbuf f;
+
+       strbuf_init(&f, from->len);
+       strbuf_addbuf(&f, from);
+
+       at = strchr(f.buf, '@');
+       if (!at) {
+               parse_bogus_from(from);
+               return;
+       }
+
+       /*
+        * If we already have one email, don't take any confusing lines
+        */
+       if (email.len && strchr(at + 1, '@')) {
+               strbuf_release(&f);
+               return;
+       }
+
+       /* Pick up the string around '@', possibly delimited with <>
+        * pair; that is the email part.
+        */
+       while (at > f.buf) {
+               char c = at[-1];
+               if (isspace(c))
+                       break;
+               if (c == '<') {
+                       at[-1] = ' ';
+                       break;
+               }
+               at--;
+       }
+       el = strcspn(at, " \n\t\r\v\f>");
+       strbuf_reset(&email);
+       strbuf_add(&email, at, el);
+       strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0));
+
+       /* The remainder is name.  It could be
+        *
+        * - "John Doe <john.doe@xz>"                   (a), or
+        * - "john.doe@xz (John Doe)"                   (b), or
+        * - "John (zzz) Doe <john.doe@xz> (Comment)"   (c)
+        *
+        * but we have removed the email part, so
+        *
+        * - remove extra spaces which could stay after email (case 'c'), and
+        * - trim from both ends, possibly removing the () pair at the end
+        *   (cases 'a' and 'b').
+        */
+       cleanup_space(&f);
+       strbuf_trim(&f);
+       if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') {
+               strbuf_remove(&f, 0, 1);
+               strbuf_setlen(&f, f.len - 1);
+       }
+
+       get_sane_name(&name, &f, &email);
+       strbuf_release(&f);
+}
+
+static void handle_header(struct strbuf **out, const struct strbuf *line)
+{
+       if (!*out) {
+               *out = xmalloc(sizeof(struct strbuf));
+               strbuf_init(*out, line->len);
+       } else
+               strbuf_reset(*out);
+
+       strbuf_addbuf(*out, line);
+}
+
+/* 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, struct strbuf *attr)
+{
+       const char *ends, *ap = strcasestr(line, name);
+       size_t sz;
+
+       if (!ap) {
+               strbuf_setlen(attr, 0);
+               return 0;
+       }
+       ap += strlen(name);
+       if (*ap == '"') {
+               ap++;
+               ends = "\"";
+       }
+       else
+               ends = "; \t";
+       sz = strcspn(ap, ends);
+       strbuf_add(attr, ap, sz);
+       return 1;
+}
+
+static struct strbuf *content[MAX_BOUNDARIES];
+
+static struct strbuf **content_top = content;
+
+static void handle_content_type(struct strbuf *line)
+{
+       struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
+       strbuf_init(boundary, line->len);
+
+       if (!strcasestr(line->buf, "text/"))
+                message_type = TYPE_OTHER;
+       if (slurp_attr(line->buf, "boundary=", boundary)) {
+               strbuf_insert(boundary, 0, "--", 2);
+               if (++content_top > &content[MAX_BOUNDARIES]) {
+                       fprintf(stderr, "Too many boundaries to handle\n");
+                       exit(1);
+               }
+               *content_top = boundary;
+               boundary = NULL;
+       }
+       slurp_attr(line->buf, "charset=", &charset);
+
+       if (boundary) {
+               strbuf_release(boundary);
+               free(boundary);
+       }
+}
+
+static void handle_content_transfer_encoding(const struct strbuf *line)
+{
+       if (strcasestr(line->buf, "base64"))
+               transfer_encoding = TE_BASE64;
+       else if (strcasestr(line->buf, "quoted-printable"))
+               transfer_encoding = TE_QP;
+       else
+               transfer_encoding = TE_DONTCARE;
+}
+
+static int is_multipart_boundary(const struct strbuf *line)
+{
+       return (((*content_top)->len <= line->len) &&
+               !memcmp(line->buf, (*content_top)->buf, (*content_top)->len));
+}
+
+static void cleanup_subject(struct strbuf *subject)
+{
+       size_t at = 0;
+
+       while (at < subject->len) {
+               char *pos;
+               size_t remove;
+
+               switch (subject->buf[at]) {
+               case 'r': case 'R':
+                       if (subject->len <= at + 3)
+                               break;
+                       if (!memcmp(subject->buf + at + 1, "e:", 2)) {
+                               strbuf_remove(subject, at, 3);
+                               continue;
+                       }
+                       at++;
+                       break;
+               case ' ': case '\t': case ':':
+                       strbuf_remove(subject, at, 1);
+                       continue;
+               case '[':
+                       pos = strchr(subject->buf + at, ']');
+                       if (!pos)
+                               break;
+                       remove = pos - subject->buf + at + 1;
+                       if (!keep_non_patch_brackets_in_subject ||
+                           (7 <= remove &&
+                            memmem(subject->buf + at, remove, "PATCH", 5)))
+                               strbuf_remove(subject, at, remove);
+                       else
+                               at += remove;
+                       continue;
+               }
+               break;
+       }
+       strbuf_trim(subject);
+}
+
+static void cleanup_space(struct strbuf *sb)
+{
+       size_t pos, cnt;
+       for (pos = 0; pos < sb->len; pos++) {
+               if (isspace(sb->buf[pos])) {
+                       sb->buf[pos] = ' ';
+                       for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++);
+                       strbuf_remove(sb, pos + 1, cnt);
+               }
+       }
+}
+
+static void decode_header(struct strbuf *line);
+static const char *header[MAX_HDR_PARSED] = {
+       "From","Subject","Date",
+};
+
+static inline int cmp_header(const struct strbuf *line, const char *hdr)
+{
+       int len = strlen(hdr);
+       return !strncasecmp(line->buf, hdr, len) && line->len > len &&
+                       line->buf[len] == ':' && isspace(line->buf[len + 1]);
+}
+
+static int check_header(const struct strbuf *line,
+                               struct strbuf *hdr_data[], int overwrite)
+{
+       int i, ret = 0, len;
+       struct strbuf sb = STRBUF_INIT;
+       /* search for the interesting parts */
+       for (i = 0; header[i]; i++) {
+               int len = strlen(header[i]);
+               if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) {
+                       /* Unwrap inline B and Q encoding, and optionally
+                        * normalize the meta information to utf8.
+                        */
+                       strbuf_add(&sb, line->buf + len + 2, line->len - len - 2);
+                       decode_header(&sb);
+                       handle_header(&hdr_data[i], &sb);
+                       ret = 1;
+                       goto check_header_out;
+               }
+       }
+
+       /* Content stuff */
+       if (cmp_header(line, "Content-Type")) {
+               len = strlen("Content-Type: ");
+               strbuf_add(&sb, line->buf + len, line->len - len);
+               decode_header(&sb);
+               strbuf_insert(&sb, 0, "Content-Type: ", len);
+               handle_content_type(&sb);
+               ret = 1;
+               goto check_header_out;
+       }
+       if (cmp_header(line, "Content-Transfer-Encoding")) {
+               len = strlen("Content-Transfer-Encoding: ");
+               strbuf_add(&sb, line->buf + len, line->len - len);
+               decode_header(&sb);
+               handle_content_transfer_encoding(&sb);
+               ret = 1;
+               goto check_header_out;
+       }
+
+       /* for inbody stuff */
+       if (!prefixcmp(line->buf, ">From") && isspace(line->buf[5])) {
+               ret = 1; /* Should this return 0? */
+               goto check_header_out;
+       }
+       if (!prefixcmp(line->buf, "[PATCH]") && isspace(line->buf[7])) {
+               for (i = 0; header[i]; i++) {
+                       if (!memcmp("Subject", header[i], 7)) {
+                               handle_header(&hdr_data[i], line);
+                               ret = 1;
+                               goto check_header_out;
+                       }
+               }
+       }
+
+check_header_out:
+       strbuf_release(&sb);
+       return ret;
+}
+
+static int is_rfc2822_header(const struct strbuf *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->buf;
+
+       /* Count mbox From headers as headers */
+       if (!prefixcmp(cp, "From ") || !prefixcmp(cp, ">From "))
+               return 1;
+
+       while ((ch = *cp++)) {
+               if (ch == ':')
+                       return 1;
+               if ((33 <= ch && ch <= 57) ||
+                   (59 <= ch && ch <= 126))
+                       continue;
+               break;
+       }
+       return 0;
+}
+
+static int read_one_header_line(struct strbuf *line, FILE *in)
+{
+       /* Get the first part of the line. */
+       if (strbuf_getline(line, in, '\n'))
+               return 0;
+
+       /*
+        * Is it an empty line or not a valid rfc2822 header?
+        * If so, stop here, and return false ("not a header")
+        */
+       strbuf_rtrim(line);
+       if (!line->len || !is_rfc2822_header(line)) {
+               /* Re-add the newline */
+               strbuf_addch(line, '\n');
+               return 0;
+       }
+
+       /*
+        * Now we need to eat all the continuation lines..
+        * Yuck, 2822 header "folding"
+        */
+       for (;;) {
+               int peek;
+               struct strbuf continuation = STRBUF_INIT;
+
+               peek = fgetc(in); ungetc(peek, in);
+               if (peek != ' ' && peek != '\t')
+                       break;
+               if (strbuf_getline(&continuation, in, '\n'))
+                       break;
+               continuation.buf[0] = '\n';
+               strbuf_rtrim(&continuation);
+               strbuf_addbuf(line, &continuation);
+       }
+
+       return 1;
+}
+
+static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047)
+{
+       const char *in = q_seg->buf;
+       int c;
+       struct strbuf *out = xmalloc(sizeof(struct strbuf));
+       strbuf_init(out, q_seg->len);
+
+       while ((c = *in++) != 0) {
+               if (c == '=') {
+                       int d = *in++;
+                       if (d == '\n' || !d)
+                               break; /* drop trailing newline */
+                       strbuf_addch(out, (hexval(d) << 4) | hexval(*in++));
+                       continue;
+               }
+               if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */
+                       c = 0x20;
+               strbuf_addch(out, c);
+       }
+       return out;
+}
+
+static struct strbuf *decode_b_segment(const struct strbuf *b_seg)
+{
+       /* Decode in..ep, possibly in-place to ot */
+       int c, pos = 0, acc = 0;
+       const char *in = b_seg->buf;
+       struct strbuf *out = xmalloc(sizeof(struct strbuf));
+       strbuf_init(out, b_seg->len);
+
+       while ((c = *in++) != 0) {
+               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
+                       continue; /* garbage */
+               switch (pos++) {
+               case 0:
+                       acc = (c << 2);
+                       break;
+               case 1:
+                       strbuf_addch(out, (acc | (c >> 4)));
+                       acc = (c & 15) << 4;
+                       break;
+               case 2:
+                       strbuf_addch(out, (acc | (c >> 2)));
+                       acc = (c & 3) << 6;
+                       break;
+               case 3:
+                       strbuf_addch(out, (acc | c));
+                       acc = pos = 0;
+                       break;
+               }
+       }
+       return out;
+}
+
+/*
+ * When there is no known charset, guess.
+ *
+ * Right now we assume that if the target is UTF-8 (the default),
+ * and it already looks like UTF-8 (which includes US-ASCII as its
+ * subset, of course) then that is what it is and there is nothing
+ * to do.
+ *
+ * Otherwise, we default to assuming it is Latin1 for historical
+ * reasons.
+ */
+static const char *guess_charset(const struct strbuf *line, const char *target_charset)
+{
+       if (is_encoding_utf8(target_charset)) {
+               if (is_utf8(line->buf))
+                       return NULL;
+       }
+       return "ISO8859-1";
+}
+
+static void convert_to_utf8(struct strbuf *line, const char *charset)
+{
+       char *out;
+
+       if (!charset || !*charset) {
+               charset = guess_charset(line, metainfo_charset);
+               if (!charset)
+                       return;
+       }
+
+       if (!strcasecmp(metainfo_charset, charset))
+               return;
+       out = reencode_string(line->buf, metainfo_charset, charset);
+       if (!out)
+               die("cannot convert from %s to %s",
+                   charset, metainfo_charset);
+       strbuf_attach(line, out, strlen(out), strlen(out));
+}
+
+static int decode_header_bq(struct strbuf *it)
+{
+       char *in, *ep, *cp;
+       struct strbuf outbuf = STRBUF_INIT, *dec;
+       struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT;
+       int rfc2047 = 0;
+
+       in = it->buf;
+       while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) {
+               int encoding;
+               strbuf_reset(&charset_q);
+               strbuf_reset(&piecebuf);
+               rfc2047 = 1;
+
+               if (in != ep) {
+                       /*
+                        * We are about to process an encoded-word
+                        * that begins at ep, but there is something
+                        * before the encoded word.
+                        */
+                       char *scan;
+                       for (scan = in; scan < ep; scan++)
+                               if (!isspace(*scan))
+                                       break;
+
+                       if (scan != ep || in == it->buf) {
+                               /*
+                                * We should not lose that "something",
+                                * unless we have just processed an
+                                * encoded-word, and there is only LWS
+                                * before the one we are about to process.
+                                */
+                               strbuf_add(&outbuf, in, ep - in);
+                       }
+               }
+               /* E.g.
+                * ep : "=?iso-2022-jp?B?GyR...?= foo"
+                * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
+                */
+               ep += 2;
+
+               if (ep - it->buf >= it->len || !(cp = strchr(ep, '?')))
+                       goto decode_header_bq_out;
+
+               if (cp + 3 - it->buf > it->len)
+                       goto decode_header_bq_out;
+               strbuf_add(&charset_q, ep, cp - ep);
+
+               encoding = cp[1];
+               if (!encoding || cp[2] != '?')
+                       goto decode_header_bq_out;
+               ep = strstr(cp + 3, "?=");
+               if (!ep)
+                       goto decode_header_bq_out;
+               strbuf_add(&piecebuf, cp + 3, ep - cp - 3);
+               switch (tolower(encoding)) {
+               default:
+                       goto decode_header_bq_out;
+               case 'b':
+                       dec = decode_b_segment(&piecebuf);
+                       break;
+               case 'q':
+                       dec = decode_q_segment(&piecebuf, 1);
+                       break;
+               }
+               if (metainfo_charset)
+                       convert_to_utf8(dec, charset_q.buf);
+
+               strbuf_addbuf(&outbuf, dec);
+               strbuf_release(dec);
+               free(dec);
+               in = ep + 2;
+       }
+       strbuf_addstr(&outbuf, in);
+       strbuf_reset(it);
+       strbuf_addbuf(it, &outbuf);
+decode_header_bq_out:
+       strbuf_release(&outbuf);
+       strbuf_release(&charset_q);
+       strbuf_release(&piecebuf);
+       return rfc2047;
+}
+
+static void decode_header(struct strbuf *it)
+{
+       if (decode_header_bq(it))
+               return;
+       /* otherwise "it" is a straight copy of the input.
+        * This can be binary guck but there is no charset specified.
+        */
+       if (metainfo_charset)
+               convert_to_utf8(it, "");
+}
+
+static void decode_transfer_encoding(struct strbuf *line)
+{
+       struct strbuf *ret;
+
+       switch (transfer_encoding) {
+       case TE_QP:
+               ret = decode_q_segment(line, 0);
+               break;
+       case TE_BASE64:
+               ret = decode_b_segment(line);
+               break;
+       case TE_DONTCARE:
+       default:
+               return;
+       }
+       strbuf_reset(line);
+       strbuf_addbuf(line, ret);
+       strbuf_release(ret);
+       free(ret);
+}
+
+static void handle_filter(struct strbuf *line);
+
+static int find_boundary(void)
+{
+       while (!strbuf_getline(&line, fin, '\n')) {
+               if (*content_top && is_multipart_boundary(&line))
+                       return 1;
+       }
+       return 0;
+}
+
+static int handle_boundary(void)
+{
+       struct strbuf newline = STRBUF_INIT;
+
+       strbuf_addch(&newline, '\n');
+again:
+       if (line.len >= (*content_top)->len + 2 &&
+           !memcmp(line.buf + (*content_top)->len, "--", 2)) {
+               /* we hit an end boundary */
+               /* pop the current boundary off the stack */
+               strbuf_release(*content_top);
+               free(*content_top);
+               *content_top = NULL;
+
+               /* technically won't happen as is_multipart_boundary()
+                  will fail first.  But just in case..
+                */
+               if (--content_top < content) {
+                       fprintf(stderr, "Detected mismatched boundaries, "
+                                       "can't recover\n");
+                       exit(1);
+               }
+               handle_filter(&newline);
+               strbuf_release(&newline);
+
+               /* skip to the next boundary */
+               if (!find_boundary())
+                       return 0;
+               goto again;
+       }
+
+       /* set some defaults */
+       transfer_encoding = TE_DONTCARE;
+       strbuf_reset(&charset);
+       message_type = TYPE_TEXT;
+
+       /* slurp in this section's info */
+       while (read_one_header_line(&line, fin))
+               check_header(&line, p_hdr_data, 0);
+
+       strbuf_release(&newline);
+       /* replenish line */
+       if (strbuf_getline(&line, fin, '\n'))
+               return 0;
+       strbuf_addch(&line, '\n');
+       return 1;
+}
+
+static inline int patchbreak(const struct strbuf *line)
+{
+       size_t i;
+
+       /* Beginning of a "diff -" header? */
+       if (!prefixcmp(line->buf, "diff -"))
+               return 1;
+
+       /* CVS "Index: " line? */
+       if (!prefixcmp(line->buf, "Index: "))
+               return 1;
+
+       /*
+        * "--- <filename>" starts patches without headers
+        * "---<sp>*" is a manual separator
+        */
+       if (line->len < 4)
+               return 0;
+
+       if (!prefixcmp(line->buf, "---")) {
+               /* space followed by a filename? */
+               if (line->buf[3] == ' ' && !isspace(line->buf[4]))
+                       return 1;
+               /* Just whitespace? */
+               for (i = 3; i < line->len; i++) {
+                       unsigned char c = line->buf[i];
+                       if (c == '\n')
+                               return 1;
+                       if (!isspace(c))
+                               break;
+               }
+               return 0;
+       }
+       return 0;
+}
+
+static int is_scissors_line(const struct strbuf *line)
+{
+       size_t i, len = line->len;
+       int scissors = 0, gap = 0;
+       int first_nonblank = -1;
+       int last_nonblank = 0, visible, perforation = 0, in_perforation = 0;
+       const char *buf = line->buf;
+
+       for (i = 0; i < len; i++) {
+               if (isspace(buf[i])) {
+                       if (in_perforation) {
+                               perforation++;
+                               gap++;
+                       }
+                       continue;
+               }
+               last_nonblank = i;
+               if (first_nonblank < 0)
+                       first_nonblank = i;
+               if (buf[i] == '-') {
+                       in_perforation = 1;
+                       perforation++;
+                       continue;
+               }
+               if (i + 1 < len &&
+                   (!memcmp(buf + i, ">8", 2) || !memcmp(buf + i, "8<", 2) ||
+                    !memcmp(buf + i, ">%", 2) || !memcmp(buf + i, "%<", 2))) {
+                       in_perforation = 1;
+                       perforation += 2;
+                       scissors += 2;
+                       i++;
+                       continue;
+               }
+               in_perforation = 0;
+       }
+
+       /*
+        * The mark must be at least 8 bytes long (e.g. "-- >8 --").
+        * Even though there can be arbitrary cruft on the same line
+        * (e.g. "cut here"), in order to avoid misidentification, the
+        * perforation must occupy more than a third of the visible
+        * width of the line, and dashes and scissors must occupy more
+        * than half of the perforation.
+        */
+
+       visible = last_nonblank - first_nonblank + 1;
+       return (scissors && 8 <= visible &&
+               visible < perforation * 3 &&
+               gap * 2 < perforation);
+}
+
+static int handle_commit_msg(struct strbuf *line)
+{
+       static int still_looking = 1;
+
+       if (!cmitmsg)
+               return 0;
+
+       if (still_looking) {
+               if (!line->len || (line->len == 1 && line->buf[0] == '\n'))
+                       return 0;
+       }
+
+       if (use_inbody_headers && still_looking) {
+               still_looking = check_header(line, s_hdr_data, 0);
+               if (still_looking)
+                       return 0;
+       } else
+               /* Only trim the first (blank) line of the commit message
+                * when ignoring in-body headers.
+                */
+               still_looking = 0;
+
+       /* normalize the log message to UTF-8. */
+       if (metainfo_charset)
+               convert_to_utf8(line, charset.buf);
+
+       if (use_scissors && is_scissors_line(line)) {
+               int i;
+               if (fseek(cmitmsg, 0L, SEEK_SET))
+                       die_errno("Could not rewind output message file");
+               if (ftruncate(fileno(cmitmsg), 0))
+                       die_errno("Could not truncate output message file at scissors");
+               still_looking = 1;
+
+               /*
+                * We may have already read "secondary headers"; purge
+                * them to give ourselves a clean restart.
+                */
+               for (i = 0; header[i]; i++) {
+                       if (s_hdr_data[i])
+                               strbuf_release(s_hdr_data[i]);
+                       s_hdr_data[i] = NULL;
+               }
+               return 0;
+       }
+
+       if (patchbreak(line)) {
+               fclose(cmitmsg);
+               cmitmsg = NULL;
+               return 1;
+       }
+
+       fputs(line->buf, cmitmsg);
+       return 0;
+}
+
+static void handle_patch(const struct strbuf *line)
+{
+       fwrite(line->buf, 1, line->len, patchfile);
+       patch_lines++;
+}
+
+static void handle_filter(struct strbuf *line)
+{
+       static int filter = 0;
+
+       /* filter tells us which part we left off on */
+       switch (filter) {
+       case 0:
+               if (!handle_commit_msg(line))
+                       break;
+               filter++;
+       case 1:
+               handle_patch(line);
+               break;
+       }
+}
+
+static void handle_body(void)
+{
+       struct strbuf prev = STRBUF_INIT;
+
+       /* Skip up to the first boundary */
+       if (*content_top) {
+               if (!find_boundary())
+                       goto handle_body_out;
+       }
+
+       do {
+               /* process any boundary lines */
+               if (*content_top && is_multipart_boundary(&line)) {
+                       /* flush any leftover */
+                       if (prev.len) {
+                               handle_filter(&prev);
+                               strbuf_reset(&prev);
+                       }
+                       if (!handle_boundary())
+                               goto handle_body_out;
+               }
+
+               /* Unwrap transfer encoding */
+               decode_transfer_encoding(&line);
+
+               switch (transfer_encoding) {
+               case TE_BASE64:
+               case TE_QP:
+               {
+                       struct strbuf **lines, **it, *sb;
+
+                       /* Prepend any previous partial lines */
+                       strbuf_insert(&line, 0, prev.buf, prev.len);
+                       strbuf_reset(&prev);
+
+                       /* binary data most likely doesn't have newlines */
+                       if (message_type != TYPE_TEXT) {
+                               handle_filter(&line);
+                               break;
+                       }
+                       /*
+                        * This is a decoded line that may contain
+                        * multiple new lines.  Pass only one chunk
+                        * at a time to handle_filter()
+                        */
+                       lines = strbuf_split(&line, '\n');
+                       for (it = lines; (sb = *it); it++) {
+                               if (*(it + 1) == NULL) /* The last line */
+                                       if (sb->buf[sb->len - 1] != '\n') {
+                                               /* Partial line, save it for later. */
+                                               strbuf_addbuf(&prev, sb);
+                                               break;
+                                       }
+                               handle_filter(sb);
+                       }
+                       /*
+                        * The partial chunk is saved in "prev" and will be
+                        * appended by the next iteration of read_line_with_nul().
+                        */
+                       strbuf_list_free(lines);
+                       break;
+               }
+               default:
+                       handle_filter(&line);
+               }
+
+       } while (!strbuf_getwholeline(&line, fin, '\n'));
+
+handle_body_out:
+       strbuf_release(&prev);
+}
+
+static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data)
+{
+       const char *sp = data->buf;
+       while (1) {
+               char *ep = strchr(sp, '\n');
+               int len;
+               if (!ep)
+                       len = strlen(sp);
+               else
+                       len = ep - sp;
+               fprintf(fout, "%s: %.*s\n", hdr, len, sp);
+               if (!ep)
+                       break;
+               sp = ep + 1;
+       }
+}
+
+static void handle_info(void)
+{
+       struct strbuf *hdr;
+       int i;
+
+       for (i = 0; header[i]; i++) {
+               /* only print inbody headers if we output a patch file */
+               if (patch_lines && s_hdr_data[i])
+                       hdr = s_hdr_data[i];
+               else if (p_hdr_data[i])
+                       hdr = p_hdr_data[i];
+               else
+                       continue;
+
+               if (!memcmp(header[i], "Subject", 7)) {
+                       if (!keep_subject) {
+                               cleanup_subject(hdr);
+                               cleanup_space(hdr);
+                       }
+                       output_header_lines(fout, "Subject", hdr);
+               } else if (!memcmp(header[i], "From", 4)) {
+                       cleanup_space(hdr);
+                       handle_from(hdr);
+                       fprintf(fout, "Author: %s\n", name.buf);
+                       fprintf(fout, "Email: %s\n", email.buf);
+               } else {
+                       cleanup_space(hdr);
+                       fprintf(fout, "%s: %s\n", header[i], hdr->buf);
+               }
+       }
+       fprintf(fout, "\n");
+}
+
+static int mailinfo(FILE *in, FILE *out, const char *msg, const char *patch)
+{
+       int peek;
+       fin = in;
+       fout = out;
+
+       cmitmsg = fopen(msg, "w");
+       if (!cmitmsg) {
+               perror(msg);
+               return -1;
+       }
+       patchfile = fopen(patch, "w");
+       if (!patchfile) {
+               perror(patch);
+               fclose(cmitmsg);
+               return -1;
+       }
+
+       p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*p_hdr_data));
+       s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*s_hdr_data));
+
+       do {
+               peek = fgetc(in);
+       } while (isspace(peek));
+       ungetc(peek, in);
+
+       /* process the email header */
+       while (read_one_header_line(&line, fin))
+               check_header(&line, p_hdr_data, 1);
+
+       handle_body();
+       handle_info();
+
+       return 0;
+}
+
+static int git_mailinfo_config(const char *var, const char *value, void *unused)
+{
+       if (prefixcmp(var, "mailinfo."))
+               return git_default_config(var, value, unused);
+       if (!strcmp(var, "mailinfo.scissors")) {
+               use_scissors = git_config_bool(var, value);
+               return 0;
+       }
+       /* perhaps others here */
+       return 0;
+}
+
+static const char mailinfo_usage[] =
+       "git mailinfo [-k|-b] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] msg patch < mail >info";
+
+int cmd_mailinfo(int argc, const char **argv, const char *prefix)
+{
+       const char *def_charset;
+
+       /* NEEDSWORK: might want to do the optional .git/ directory
+        * discovery
+        */
+       git_config(git_mailinfo_config, NULL);
+
+       def_charset = (git_commit_encoding ? git_commit_encoding : "UTF-8");
+       metainfo_charset = def_charset;
+
+       while (1 < argc && argv[1][0] == '-') {
+               if (!strcmp(argv[1], "-k"))
+                       keep_subject = 1;
+               else if (!strcmp(argv[1], "-b"))
+                       keep_non_patch_brackets_in_subject = 1;
+               else if (!strcmp(argv[1], "-u"))
+                       metainfo_charset = def_charset;
+               else if (!strcmp(argv[1], "-n"))
+                       metainfo_charset = NULL;
+               else if (!prefixcmp(argv[1], "--encoding="))
+                       metainfo_charset = argv[1] + 11;
+               else if (!strcmp(argv[1], "--scissors"))
+                       use_scissors = 1;
+               else if (!strcmp(argv[1], "--no-scissors"))
+                       use_scissors = 0;
+               else if (!strcmp(argv[1], "--no-inbody-headers"))
+                       use_inbody_headers = 0;
+               else
+                       usage(mailinfo_usage);
+               argc--; argv++;
+       }
+
+       if (argc != 3)
+               usage(mailinfo_usage);
+
+       return !!mailinfo(stdin, stdout, argv[1], argv[2]);
+}
diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c
new file mode 100644 (file)
index 0000000..99654d0
--- /dev/null
@@ -0,0 +1,309 @@
+/*
+ * 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 "cache.h"
+#include "builtin.h"
+#include "string-list.h"
+#include "strbuf.h"
+
+static const char git_mailsplit_usage[] =
+"git mailsplit [-d<prec>] [-f<n>] [-b] [--keep-cr] -o<directory> [<mbox>|<Maildir>...]";
+
+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;
+}
+
+static struct strbuf buf = STRBUF_INIT;
+static int keep_cr;
+
+/* 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 fd;
+       int status = 0;
+       int is_bare = !is_from_line(buf.buf, buf.len);
+
+       if (is_bare && !allow_bare)
+               goto corrupt;
+
+       fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
+       if (fd < 0)
+               die_errno("cannot open output file '%s'", name);
+       output = xfdopen(fd, "w");
+
+       /* Copy it out, while searching for a line that begins with
+        * "From " and having something that looks like a date format.
+        */
+       for (;;) {
+               if (!keep_cr && buf.len > 1 && buf.buf[buf.len-1] == '\n' &&
+                       buf.buf[buf.len-2] == '\r') {
+                       strbuf_setlen(&buf, buf.len-2);
+                       strbuf_addch(&buf, '\n');
+               }
+
+               if (fwrite(buf.buf, 1, buf.len, output) != buf.len)
+                       die_errno("cannot write output");
+
+               if (strbuf_getwholeline(&buf, mbox, '\n')) {
+                       if (feof(mbox)) {
+                               status = 1;
+                               break;
+                       }
+                       die_errno("cannot read mbox");
+               }
+               if (!is_bare && is_from_line(buf.buf, 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);
+}
+
+static int populate_maildir_list(struct string_list *list, const char *path)
+{
+       DIR *dir;
+       struct dirent *dent;
+       char name[PATH_MAX];
+       char *subs[] = { "cur", "new", NULL };
+       char **sub;
+
+       for (sub = subs; *sub; ++sub) {
+               snprintf(name, sizeof(name), "%s/%s", path, *sub);
+               if ((dir = opendir(name)) == NULL) {
+                       if (errno == ENOENT)
+                               continue;
+                       error("cannot opendir %s (%s)", name, strerror(errno));
+                       return -1;
+               }
+
+               while ((dent = readdir(dir)) != NULL) {
+                       if (dent->d_name[0] == '.')
+                               continue;
+                       snprintf(name, sizeof(name), "%s/%s", *sub, dent->d_name);
+                       string_list_insert(list, name);
+               }
+
+               closedir(dir);
+       }
+
+       return 0;
+}
+
+static int split_maildir(const char *maildir, const char *dir,
+       int nr_prec, int skip)
+{
+       char file[PATH_MAX];
+       char name[PATH_MAX];
+       int ret = -1;
+       int i;
+       struct string_list list = STRING_LIST_INIT_DUP;
+
+       if (populate_maildir_list(&list, maildir) < 0)
+               goto out;
+
+       for (i = 0; i < list.nr; i++) {
+               FILE *f;
+               snprintf(file, sizeof(file), "%s/%s", maildir, list.items[i].string);
+               f = fopen(file, "r");
+               if (!f) {
+                       error("cannot open mail %s (%s)", file, strerror(errno));
+                       goto out;
+               }
+
+               if (strbuf_getwholeline(&buf, f, '\n')) {
+                       error("cannot read mail %s (%s)", file, strerror(errno));
+                       goto out;
+               }
+
+               sprintf(name, "%s/%0*d", dir, nr_prec, ++skip);
+               split_one(f, name, 1);
+
+               fclose(f);
+       }
+
+       ret = skip;
+out:
+       string_list_clear(&list, 1);
+       return ret;
+}
+
+static int split_mbox(const char *file, const char *dir, int allow_bare,
+                     int nr_prec, int skip)
+{
+       char name[PATH_MAX];
+       int ret = -1;
+       int peek;
+
+       FILE *f = !strcmp(file, "-") ? stdin : fopen(file, "r");
+       int file_done = 0;
+
+       if (!f) {
+               error("cannot open mbox %s", file);
+               goto out;
+       }
+
+       do {
+               peek = fgetc(f);
+       } while (isspace(peek));
+       ungetc(peek, f);
+
+       if (strbuf_getwholeline(&buf, f, '\n')) {
+               /* empty stdin is OK */
+               if (f != stdin) {
+                       error("cannot read mbox %s", file);
+                       goto out;
+               }
+               file_done = 1;
+       }
+
+       while (!file_done) {
+               sprintf(name, "%s/%0*d", dir, nr_prec, ++skip);
+               file_done = split_one(f, name, allow_bare);
+       }
+
+       if (f != stdin)
+               fclose(f);
+
+       ret = skip;
+out:
+       return ret;
+}
+
+int cmd_mailsplit(int argc, const char **argv, const char *prefix)
+{
+       int nr = 0, nr_prec = 4, num = 0;
+       int allow_bare = 0;
+       const char *dir = NULL;
+       const char **argp;
+       static const char *stdin_only[] = { "-", NULL };
+
+       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] == 'h' ) {
+                       usage(git_mailsplit_usage);
+               } else if ( arg[1] == 'b' && !arg[2] ) {
+                       allow_bare = 1;
+               } else if (!strcmp(arg, "--keep-cr")) {
+                       keep_cr = 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;
+       }
+
+       while (*argp) {
+               const char *arg = *argp++;
+               struct stat argstat;
+               int ret = 0;
+
+               if (arg[0] == '-' && arg[1] == 0) {
+                       ret = split_mbox(arg, dir, allow_bare, nr_prec, nr);
+                       if (ret < 0) {
+                               error("cannot split patches from stdin");
+                               return 1;
+                       }
+                       num += (ret - nr);
+                       nr = ret;
+                       continue;
+               }
+
+               if (stat(arg, &argstat) == -1) {
+                       error("cannot stat %s (%s)", arg, strerror(errno));
+                       return 1;
+               }
+
+               if (S_ISDIR(argstat.st_mode))
+                       ret = split_maildir(arg, dir, nr_prec, nr);
+               else
+                       ret = split_mbox(arg, dir, allow_bare, nr_prec, nr);
+
+               if (ret < 0) {
+                       error("cannot split patches from %s", arg);
+                       return 1;
+               }
+               num += (ret - nr);
+               nr = ret;
+       }
+
+       printf("%d\n", num);
+
+       return 0;
+}
diff --git a/builtin/merge-base.c b/builtin/merge-base.c
new file mode 100644 (file)
index 0000000..96dd160
--- /dev/null
@@ -0,0 +1,101 @@
+#include "builtin.h"
+#include "cache.h"
+#include "commit.h"
+#include "parse-options.h"
+
+static int show_merge_base(struct commit **rev, int rev_nr, int show_all)
+{
+       struct commit_list *result;
+
+       result = get_merge_bases_many(rev[0], rev_nr - 1, rev + 1, 0);
+
+       if (!result)
+               return 1;
+
+       while (result) {
+               printf("%s\n", sha1_to_hex(result->item->object.sha1));
+               if (!show_all)
+                       return 0;
+               result = result->next;
+       }
+
+       return 0;
+}
+
+static const char * const merge_base_usage[] = {
+       "git merge-base [-a|--all] [--octopus] <commit> <commit>...",
+       "git merge-base --independent <commit>...",
+       NULL
+};
+
+static struct commit *get_commit_reference(const char *arg)
+{
+       unsigned char revkey[20];
+       struct commit *r;
+
+       if (get_sha1(arg, revkey))
+               die("Not a valid object name %s", arg);
+       r = lookup_commit_reference(revkey);
+       if (!r)
+               die("Not a valid commit name %s", arg);
+
+       return r;
+}
+
+static int handle_octopus(int count, const char **args, int reduce, int show_all)
+{
+       struct commit_list *revs = NULL;
+       struct commit_list *result;
+       int i;
+
+       if (reduce)
+               show_all = 1;
+
+       for (i = count - 1; i >= 0; i--)
+               commit_list_insert(get_commit_reference(args[i]), &revs);
+
+       result = reduce ? reduce_heads(revs) : get_octopus_merge_bases(revs);
+
+       if (!result)
+               return 1;
+
+       while (result) {
+               printf("%s\n", sha1_to_hex(result->item->object.sha1));
+               if (!show_all)
+                       return 0;
+               result = result->next;
+       }
+
+       return 0;
+}
+
+int cmd_merge_base(int argc, const char **argv, const char *prefix)
+{
+       struct commit **rev;
+       int rev_nr = 0;
+       int show_all = 0;
+       int octopus = 0;
+       int reduce = 0;
+
+       struct option options[] = {
+               OPT_BOOLEAN('a', "all", &show_all, "output all common ancestors"),
+               OPT_BOOLEAN(0, "octopus", &octopus, "find ancestors for a single n-way merge"),
+               OPT_BOOLEAN(0, "independent", &reduce, "list revs not reachable from others"),
+               OPT_END()
+       };
+
+       git_config(git_default_config, NULL);
+       argc = parse_options(argc, argv, prefix, options, merge_base_usage, 0);
+       if (!octopus && !reduce && argc < 2)
+               usage_with_options(merge_base_usage, options);
+       if (reduce && (show_all || octopus))
+               die("--independent cannot be used with other options");
+
+       if (octopus || reduce)
+               return handle_octopus(argc, argv, reduce, show_all);
+
+       rev = xmalloc(argc * sizeof(*rev));
+       while (argc-- > 0)
+               rev[rev_nr++] = get_commit_reference(*argv++);
+       return show_merge_base(rev, rev_nr, show_all);
+}
diff --git a/builtin/merge-file.c b/builtin/merge-file.c
new file mode 100644 (file)
index 0000000..b6664d4
--- /dev/null
@@ -0,0 +1,101 @@
+#include "builtin.h"
+#include "cache.h"
+#include "xdiff/xdiff.h"
+#include "xdiff-interface.h"
+#include "parse-options.h"
+
+static const char *const merge_file_usage[] = {
+       "git merge-file [options] [-L name1 [-L orig [-L name2]]] file1 orig_file file2",
+       NULL
+};
+
+static int label_cb(const struct option *opt, const char *arg, int unset)
+{
+       static int label_count = 0;
+       const char **names = (const char **)opt->value;
+
+       if (label_count >= 3)
+               return error("too many labels on the command line");
+       names[label_count++] = arg;
+       return 0;
+}
+
+int cmd_merge_file(int argc, const char **argv, const char *prefix)
+{
+       const char *names[3] = { NULL, NULL, NULL };
+       mmfile_t mmfs[3];
+       mmbuffer_t result = {NULL, 0};
+       xmparam_t xmp = {{0}};
+       int ret = 0, i = 0, to_stdout = 0;
+       int quiet = 0;
+       struct option options[] = {
+               OPT_BOOLEAN('p', "stdout", &to_stdout, "send results to standard output"),
+               OPT_SET_INT(0, "diff3", &xmp.style, "use a diff3 based merge", XDL_MERGE_DIFF3),
+               OPT_SET_INT(0, "ours", &xmp.favor, "for conflicts, use our version",
+                           XDL_MERGE_FAVOR_OURS),
+               OPT_SET_INT(0, "theirs", &xmp.favor, "for conflicts, use their version",
+                           XDL_MERGE_FAVOR_THEIRS),
+               OPT_SET_INT(0, "union", &xmp.favor, "for conflicts, use a union version",
+                           XDL_MERGE_FAVOR_UNION),
+               OPT_INTEGER(0, "marker-size", &xmp.marker_size,
+                           "for conflicts, use this marker size"),
+               OPT__QUIET(&quiet),
+               OPT_CALLBACK('L', NULL, names, "name",
+                            "set labels for file1/orig_file/file2", &label_cb),
+               OPT_END(),
+       };
+
+       xmp.level = XDL_MERGE_ZEALOUS_ALNUM;
+       xmp.style = 0;
+       xmp.favor = 0;
+
+       if (startup_info->have_repository) {
+               /* Read the configuration file */
+               git_config(git_xmerge_config, NULL);
+               if (0 <= git_xmerge_style)
+                       xmp.style = git_xmerge_style;
+       }
+
+       argc = parse_options(argc, argv, prefix, options, merge_file_usage, 0);
+       if (argc != 3)
+               usage_with_options(merge_file_usage, options);
+       if (quiet) {
+               if (!freopen("/dev/null", "w", stderr))
+                       return error("failed to redirect stderr to /dev/null: "
+                                    "%s\n", strerror(errno));
+       }
+
+       for (i = 0; i < 3; i++) {
+               if (!names[i])
+                       names[i] = argv[i];
+               if (read_mmfile(mmfs + i, argv[i]))
+                       return -1;
+               if (buffer_is_binary(mmfs[i].ptr, mmfs[i].size))
+                       return error("Cannot merge binary files: %s\n",
+                                       argv[i]);
+       }
+
+       xmp.ancestor = names[1];
+       xmp.file1 = names[0];
+       xmp.file2 = names[2];
+       ret = xdl_merge(mmfs + 1, mmfs + 0, mmfs + 2, &xmp, &result);
+
+       for (i = 0; i < 3; i++)
+               free(mmfs[i].ptr);
+
+       if (ret >= 0) {
+               const char *filename = argv[0];
+               FILE *f = to_stdout ? stdout : fopen(filename, "wb");
+
+               if (!f)
+                       ret = error("Could not open %s for writing", filename);
+               else if (result.size &&
+                        fwrite(result.ptr, result.size, 1, f) != 1)
+                       ret = error("Could not write to %s", filename);
+               else if (fclose(f))
+                       ret = error("Could not close %s", filename);
+               free(result.ptr);
+       }
+
+       return ret;
+}
diff --git a/builtin/merge-index.c b/builtin/merge-index.c
new file mode 100644 (file)
index 0000000..2c4cf5e
--- /dev/null
@@ -0,0 +1,111 @@
+#include "cache.h"
+#include "run-command.h"
+#include "exec_cmd.h"
+
+static const char *pgm;
+static int one_shot, quiet;
+static int err;
+
+static int merge_entry(int pos, const char *path)
+{
+       int found;
+       const char *arguments[] = { pgm, "", "", "", path, "", "", "", NULL };
+       char hexbuf[4][60];
+       char ownbuf[4][60];
+
+       if (pos >= active_nr)
+               die("git merge-index: %s not in the cache", path);
+       found = 0;
+       do {
+               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", ce->ce_mode);
+               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);
+
+       if (run_command_v_opt(arguments, 0)) {
+               if (one_shot)
+                       err++;
+               else {
+                       if (!quiet)
+                               die("merge program failed");
+                       exit(1);
+               }
+       }
+       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 cmd_merge_index(int argc, const char **argv, const char *prefix)
+{
+       int i, force_file = 0;
+
+       /* Without this we cannot rely on waitpid() to tell
+        * what happened to our children.
+        */
+       signal(SIGCHLD, SIG_DFL);
+
+       if (argc < 3)
+               usage("git merge-index [-o] [-q] <merge-program> (-a | [--] <filename>*)");
+
+       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++) {
+               const 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/builtin/merge-ours.c b/builtin/merge-ours.c
new file mode 100644 (file)
index 0000000..6844116
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Implementation of git-merge-ours.sh as builtin
+ *
+ * Copyright (c) 2007 Thomas Harning Jr
+ * Original:
+ * Original Copyright (c) 2005 Junio C Hamano
+ *
+ * Pretend we resolved the heads, but declare our tree trumps everybody else.
+ */
+#include "git-compat-util.h"
+#include "builtin.h"
+
+static const char builtin_merge_ours_usage[] =
+       "git merge-ours <base>... -- HEAD <remote>...";
+
+static const char *diff_index_args[] = {
+       "diff-index", "--quiet", "--cached", "HEAD", "--", NULL
+};
+#define NARGS (ARRAY_SIZE(diff_index_args) - 1)
+
+int cmd_merge_ours(int argc, const char **argv, const char *prefix)
+{
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage(builtin_merge_ours_usage);
+
+       /*
+        * 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.
+        */
+       if (cmd_diff_index(NARGS, diff_index_args, prefix))
+               exit(2);
+       exit(0);
+}
diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c
new file mode 100644 (file)
index 0000000..78b9db7
--- /dev/null
@@ -0,0 +1,91 @@
+#include "cache.h"
+#include "commit.h"
+#include "tag.h"
+#include "merge-recursive.h"
+
+static const char builtin_merge_recursive_usage[] =
+       "git %s <base>... -- <head> <remote> ...";
+
+static const char *better_branch_name(const char *branch)
+{
+       static char githead_env[8 + 40 + 1];
+       char *name;
+
+       if (strlen(branch) != 40)
+               return branch;
+       sprintf(githead_env, "GITHEAD_%s", branch);
+       name = getenv(githead_env);
+       return name ? name : branch;
+}
+
+int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
+{
+       const unsigned char *bases[21];
+       unsigned bases_count = 0;
+       int i, failed;
+       unsigned char h1[20], h2[20];
+       struct merge_options o;
+       struct commit *result;
+
+       init_merge_options(&o);
+       if (argv[0] && !suffixcmp(argv[0], "-subtree"))
+               o.subtree_shift = "";
+
+       if (argc < 4)
+               usagef(builtin_merge_recursive_usage, argv[0]);
+
+       for (i = 1; i < argc; ++i) {
+               const char *arg = argv[i];
+
+               if (!prefixcmp(arg, "--")) {
+                       if (!arg[2])
+                               break;
+                       if (!strcmp(arg+2, "ours"))
+                               o.recursive_variant = MERGE_RECURSIVE_OURS;
+                       else if (!strcmp(arg+2, "theirs"))
+                               o.recursive_variant = MERGE_RECURSIVE_THEIRS;
+                       else if (!strcmp(arg+2, "subtree"))
+                               o.subtree_shift = "";
+                       else if (!prefixcmp(arg+2, "subtree="))
+                               o.subtree_shift = arg + 10;
+                       else if (!strcmp(arg+2, "renormalize"))
+                               o.renormalize = 1;
+                       else if (!strcmp(arg+2, "no-renormalize"))
+                               o.renormalize = 0;
+                       else
+                               die("Unknown option %s", arg);
+                       continue;
+               }
+               if (bases_count < ARRAY_SIZE(bases)-1) {
+                       unsigned char *sha = xmalloc(20);
+                       if (get_sha1(argv[i], sha))
+                               die("Could not parse object '%s'", argv[i]);
+                       bases[bases_count++] = sha;
+               }
+               else
+                       warning("Cannot handle more than %d bases. "
+                               "Ignoring %s.",
+                               (int)ARRAY_SIZE(bases)-1, argv[i]);
+       }
+       if (argc - i != 3) /* "--" "<head>" "<remote>" */
+               die("Not handling anything other than two heads merge.");
+
+       o.branch1 = argv[++i];
+       o.branch2 = argv[++i];
+
+       if (get_sha1(o.branch1, h1))
+               die("Could not resolve ref '%s'", o.branch1);
+       if (get_sha1(o.branch2, h2))
+               die("Could not resolve ref '%s'", o.branch2);
+
+       o.branch1 = better_branch_name(o.branch1);
+       o.branch2 = better_branch_name(o.branch2);
+
+       if (o.verbosity >= 3)
+               printf("Merging %s with %s\n", o.branch1, o.branch2);
+
+       failed = merge_recursive_generic(&o, h1, h2, bases_count, bases, &result);
+       if (failed < 0)
+               return 128; /* die() error code */
+       return failed;
+}
diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c
new file mode 100644 (file)
index 0000000..9b25ddc
--- /dev/null
@@ -0,0 +1,359 @@
+#include "cache.h"
+#include "tree-walk.h"
+#include "xdiff-interface.h"
+#include "blob.h"
+#include "exec_cmd.h"
+
+static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>";
+static int resolve_directories = 1;
+
+struct merge_list {
+       struct merge_list *next;
+       struct merge_list *link;        /* other stages for this object */
+
+       unsigned int stage : 2,
+                    flags : 30;
+       unsigned int mode;
+       const char *path;
+       struct blob *blob;
+};
+
+static struct merge_list *merge_result, **merge_result_end = &merge_result;
+
+static void add_merge_entry(struct merge_list *entry)
+{
+       *merge_result_end = entry;
+       merge_result_end = &entry->next;
+}
+
+static void merge_trees(struct tree_desc t[3], const char *base);
+
+static const char *explanation(struct merge_list *entry)
+{
+       switch (entry->stage) {
+       case 0:
+               return "merged";
+       case 3:
+               return "added in remote";
+       case 2:
+               if (entry->link)
+                       return "added in both";
+               return "added in local";
+       }
+
+       /* Existed in base */
+       entry = entry->link;
+       if (!entry)
+               return "removed in both";
+
+       if (entry->link)
+               return "changed in both";
+
+       if (entry->stage == 3)
+               return "removed in local";
+       return "removed in remote";
+}
+
+extern void *merge_file(const char *, struct blob *, struct blob *, struct blob *, unsigned long *);
+
+static void *result(struct merge_list *entry, unsigned long *size)
+{
+       enum object_type type;
+       struct blob *base, *our, *their;
+       const char *path = entry->path;
+
+       if (!entry->stage)
+               return read_sha1_file(entry->blob->object.sha1, &type, size);
+       base = NULL;
+       if (entry->stage == 1) {
+               base = entry->blob;
+               entry = entry->link;
+       }
+       our = NULL;
+       if (entry && entry->stage == 2) {
+               our = entry->blob;
+               entry = entry->link;
+       }
+       their = NULL;
+       if (entry)
+               their = entry->blob;
+       return merge_file(path, base, our, their, size);
+}
+
+static void *origin(struct merge_list *entry, unsigned long *size)
+{
+       enum object_type type;
+       while (entry) {
+               if (entry->stage == 2)
+                       return read_sha1_file(entry->blob->object.sha1, &type, size);
+               entry = entry->link;
+       }
+       return NULL;
+}
+
+static int show_outf(void *priv_, mmbuffer_t *mb, int nbuf)
+{
+       int i;
+       for (i = 0; i < nbuf; i++)
+               printf("%.*s", (int) mb[i].size, mb[i].ptr);
+       return 0;
+}
+
+static void show_diff(struct merge_list *entry)
+{
+       unsigned long size;
+       mmfile_t src, dst;
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
+       xdemitcb_t ecb;
+
+       xpp.flags = 0;
+       memset(&xecfg, 0, sizeof(xecfg));
+       xecfg.ctxlen = 3;
+       ecb.outf = show_outf;
+       ecb.priv = NULL;
+
+       src.ptr = origin(entry, &size);
+       if (!src.ptr)
+               size = 0;
+       src.size = size;
+       dst.ptr = result(entry, &size);
+       if (!dst.ptr)
+               size = 0;
+       dst.size = size;
+       xdi_diff(&src, &dst, &xpp, &xecfg, &ecb);
+       free(src.ptr);
+       free(dst.ptr);
+}
+
+static void show_result_list(struct merge_list *entry)
+{
+       printf("%s\n", explanation(entry));
+       do {
+               struct merge_list *link = entry->link;
+               static const char *desc[4] = { "result", "base", "our", "their" };
+               printf("  %-6s %o %s %s\n", desc[entry->stage], entry->mode, sha1_to_hex(entry->blob->object.sha1), entry->path);
+               entry = link;
+       } while (entry);
+}
+
+static void show_result(void)
+{
+       struct merge_list *walk;
+
+       walk = merge_result;
+       while (walk) {
+               show_result_list(walk);
+               show_diff(walk);
+               walk = walk->next;
+       }
+}
+
+/* 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 &&
+               !hashcmp(a->sha1, b->sha1) &&
+               a->mode == b->mode;
+}
+
+static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsigned char *sha1, const char *path)
+{
+       struct merge_list *res = xcalloc(1, sizeof(*res));
+
+       res->stage = stage;
+       res->path = path;
+       res->mode = mode;
+       res->blob = lookup_blob(sha1);
+       return res;
+}
+
+static char *traverse_path(const struct traverse_info *info, const struct name_entry *n)
+{
+       char *path = xmalloc(traverse_path_len(info, n) + 1);
+       return make_traverse_path(path, info, n);
+}
+
+static void resolve(const struct traverse_info *info, struct name_entry *branch1, struct name_entry *result)
+{
+       struct merge_list *orig, *final;
+       const char *path;
+
+       /* If it's already branch1, don't bother showing it */
+       if (!branch1)
+               return;
+
+       path = traverse_path(info, result);
+       orig = create_entry(2, branch1->mode, branch1->sha1, path);
+       final = create_entry(0, result->mode, result->sha1, path);
+
+       final->link = orig;
+
+       add_merge_entry(final);
+}
+
+static int unresolved_directory(const struct traverse_info *info, struct name_entry n[3])
+{
+       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;
+       newbase = traverse_path(info, p);
+       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 struct merge_list *link_entry(unsigned stage, const struct traverse_info *info, struct name_entry *n, struct merge_list *entry)
+{
+       const char *path;
+       struct merge_list *link;
+
+       if (!n->mode)
+               return entry;
+       if (entry)
+               path = entry->path;
+       else
+               path = traverse_path(info, n);
+       link = create_entry(stage, n->mode, n->sha1, path);
+       link->link = entry;
+       return link;
+}
+
+static void unresolved(const struct traverse_info *info, struct name_entry n[3])
+{
+       struct merge_list *entry = NULL;
+
+       if (unresolved_directory(info, n))
+               return;
+
+       /*
+        * Do them in reverse order so that the resulting link
+        * list has the stages in order - link_entry adds new
+        * links at the front.
+        */
+       entry = link_entry(3, info, n + 2, entry);
+       entry = link_entry(2, info, n + 1, entry);
+       entry = link_entry(1, info, n + 0, entry);
+
+       add_merge_entry(entry);
+}
+
+/*
+ * 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 int threeway_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *info)
+{
+       /* Same in both? */
+       if (same_entry(entry+1, entry+2)) {
+               if (entry[0].sha1) {
+                       resolve(info, NULL, entry+1);
+                       return mask;
+               }
+       }
+
+       if (same_entry(entry+0, entry+1)) {
+               if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) {
+                       resolve(info, entry+1, entry+2);
+                       return mask;
+               }
+       }
+
+       if (same_entry(entry+0, entry+2)) {
+               if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) {
+                       resolve(info, NULL, entry+1);
+                       return mask;
+               }
+       }
+
+       unresolved(info, entry);
+       return mask;
+}
+
+static void merge_trees(struct tree_desc t[3], const char *base)
+{
+       struct traverse_info info;
+
+       setup_traverse_info(&info, base);
+       info.fn = threeway_callback;
+       traverse_trees(3, t, &info);
+}
+
+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 cmd_merge_tree(int argc, const char **argv, const char *prefix)
+{
+       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);
+
+       show_result();
+       return 0;
+}
diff --git a/builtin/merge.c b/builtin/merge.c
new file mode 100644 (file)
index 0000000..5f65c0c
--- /dev/null
@@ -0,0 +1,1319 @@
+/*
+ * Builtin "git merge"
+ *
+ * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
+ *
+ * Based on git-merge.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "parse-options.h"
+#include "builtin.h"
+#include "run-command.h"
+#include "diff.h"
+#include "refs.h"
+#include "commit.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "unpack-trees.h"
+#include "cache-tree.h"
+#include "dir.h"
+#include "utf8.h"
+#include "log-tree.h"
+#include "color.h"
+#include "rerere.h"
+#include "help.h"
+#include "merge-recursive.h"
+#include "resolve-undo.h"
+
+#define DEFAULT_TWOHEAD (1<<0)
+#define DEFAULT_OCTOPUS (1<<1)
+#define NO_FAST_FORWARD (1<<2)
+#define NO_TRIVIAL      (1<<3)
+
+struct strategy {
+       const char *name;
+       unsigned attr;
+};
+
+static const char * const builtin_merge_usage[] = {
+       "git merge [options] <remote>...",
+       "git merge [options] <msg> HEAD <remote>",
+       NULL
+};
+
+static int show_diffstat = 1, option_log, squash;
+static int option_commit = 1, allow_fast_forward = 1;
+static int fast_forward_only;
+static int allow_trivial = 1, have_message;
+static struct strbuf merge_msg;
+static struct commit_list *remoteheads;
+static unsigned char head[20], stash[20];
+static struct strategy **use_strategies;
+static size_t use_strategies_nr, use_strategies_alloc;
+static const char **xopts;
+static size_t xopts_nr, xopts_alloc;
+static const char *branch;
+static int option_renormalize;
+static int verbosity;
+static int allow_rerere_auto;
+
+static struct strategy all_strategy[] = {
+       { "recursive",  DEFAULT_TWOHEAD | NO_TRIVIAL },
+       { "octopus",    DEFAULT_OCTOPUS },
+       { "resolve",    0 },
+       { "ours",       NO_FAST_FORWARD | NO_TRIVIAL },
+       { "subtree",    NO_FAST_FORWARD | NO_TRIVIAL },
+};
+
+static const char *pull_twohead, *pull_octopus;
+
+static int option_parse_message(const struct option *opt,
+                               const char *arg, int unset)
+{
+       struct strbuf *buf = opt->value;
+
+       if (unset)
+               strbuf_setlen(buf, 0);
+       else if (arg) {
+               strbuf_addf(buf, "%s%s", buf->len ? "\n\n" : "", arg);
+               have_message = 1;
+       } else
+               return error("switch `m' requires a value");
+       return 0;
+}
+
+static struct strategy *get_strategy(const char *name)
+{
+       int i;
+       struct strategy *ret;
+       static struct cmdnames main_cmds, other_cmds;
+       static int loaded;
+
+       if (!name)
+               return NULL;
+
+       for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
+               if (!strcmp(name, all_strategy[i].name))
+                       return &all_strategy[i];
+
+       if (!loaded) {
+               struct cmdnames not_strategies;
+               loaded = 1;
+
+               memset(&not_strategies, 0, sizeof(struct cmdnames));
+               load_command_list("git-merge-", &main_cmds, &other_cmds);
+               for (i = 0; i < main_cmds.cnt; i++) {
+                       int j, found = 0;
+                       struct cmdname *ent = main_cmds.names[i];
+                       for (j = 0; j < ARRAY_SIZE(all_strategy); j++)
+                               if (!strncmp(ent->name, all_strategy[j].name, ent->len)
+                                               && !all_strategy[j].name[ent->len])
+                                       found = 1;
+                       if (!found)
+                               add_cmdname(&not_strategies, ent->name, ent->len);
+               }
+               exclude_cmds(&main_cmds, &not_strategies);
+       }
+       if (!is_in_cmdlist(&main_cmds, name) && !is_in_cmdlist(&other_cmds, name)) {
+               fprintf(stderr, "Could not find merge strategy '%s'.\n", name);
+               fprintf(stderr, "Available strategies are:");
+               for (i = 0; i < main_cmds.cnt; i++)
+                       fprintf(stderr, " %s", main_cmds.names[i]->name);
+               fprintf(stderr, ".\n");
+               if (other_cmds.cnt) {
+                       fprintf(stderr, "Available custom strategies are:");
+                       for (i = 0; i < other_cmds.cnt; i++)
+                               fprintf(stderr, " %s", other_cmds.names[i]->name);
+                       fprintf(stderr, ".\n");
+               }
+               exit(1);
+       }
+
+       ret = xcalloc(1, sizeof(struct strategy));
+       ret->name = xstrdup(name);
+       ret->attr = NO_TRIVIAL;
+       return ret;
+}
+
+static void append_strategy(struct strategy *s)
+{
+       ALLOC_GROW(use_strategies, use_strategies_nr + 1, use_strategies_alloc);
+       use_strategies[use_strategies_nr++] = s;
+}
+
+static int option_parse_strategy(const struct option *opt,
+                                const char *name, int unset)
+{
+       if (unset)
+               return 0;
+
+       append_strategy(get_strategy(name));
+       return 0;
+}
+
+static int option_parse_x(const struct option *opt,
+                         const char *arg, int unset)
+{
+       if (unset)
+               return 0;
+
+       ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc);
+       xopts[xopts_nr++] = xstrdup(arg);
+       return 0;
+}
+
+static int option_parse_n(const struct option *opt,
+                         const char *arg, int unset)
+{
+       show_diffstat = unset;
+       return 0;
+}
+
+static struct option builtin_merge_options[] = {
+       { OPTION_CALLBACK, 'n', NULL, NULL, NULL,
+               "do not show a diffstat at the end of the merge",
+               PARSE_OPT_NOARG, option_parse_n },
+       OPT_BOOLEAN(0, "stat", &show_diffstat,
+               "show a diffstat at the end of the merge"),
+       OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
+       OPT_BOOLEAN(0, "log", &option_log,
+               "add list of one-line log to merge commit message"),
+       OPT_BOOLEAN(0, "squash", &squash,
+               "create a single commit instead of doing a merge"),
+       OPT_BOOLEAN(0, "commit", &option_commit,
+               "perform a commit if the merge succeeds (default)"),
+       OPT_BOOLEAN(0, "ff", &allow_fast_forward,
+               "allow fast-forward (default)"),
+       OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
+               "abort if fast-forward is not possible"),
+       OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
+       OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
+               "merge strategy to use", option_parse_strategy),
+       OPT_CALLBACK('X', "strategy-option", &xopts, "option=value",
+               "option for selected merge strategy", option_parse_x),
+       OPT_CALLBACK('m', "message", &merge_msg, "message",
+               "message to be used for the merge commit (if any)",
+               option_parse_message),
+       OPT__VERBOSITY(&verbosity),
+       OPT_END()
+};
+
+/* Cleans up metadata that is uninteresting after a succeeded merge. */
+static void drop_save(void)
+{
+       unlink(git_path("MERGE_HEAD"));
+       unlink(git_path("MERGE_MSG"));
+       unlink(git_path("MERGE_MODE"));
+}
+
+static void save_state(void)
+{
+       int len;
+       struct child_process cp;
+       struct strbuf buffer = STRBUF_INIT;
+       const char *argv[] = {"stash", "create", NULL};
+
+       memset(&cp, 0, sizeof(cp));
+       cp.argv = argv;
+       cp.out = -1;
+       cp.git_cmd = 1;
+
+       if (start_command(&cp))
+               die("could not run stash.");
+       len = strbuf_read(&buffer, cp.out, 1024);
+       close(cp.out);
+
+       if (finish_command(&cp) || len < 0)
+               die("stash failed");
+       else if (!len)
+               return;
+       strbuf_setlen(&buffer, buffer.len-1);
+       if (get_sha1(buffer.buf, stash))
+               die("not a valid object: %s", buffer.buf);
+}
+
+static void reset_hard(unsigned const char *sha1, int verbose)
+{
+       int i = 0;
+       const char *args[6];
+
+       args[i++] = "read-tree";
+       if (verbose)
+               args[i++] = "-v";
+       args[i++] = "--reset";
+       args[i++] = "-u";
+       args[i++] = sha1_to_hex(sha1);
+       args[i] = NULL;
+
+       if (run_command_v_opt(args, RUN_GIT_CMD))
+               die("read-tree failed");
+}
+
+static void restore_state(void)
+{
+       struct strbuf sb = STRBUF_INIT;
+       const char *args[] = { "stash", "apply", NULL, NULL };
+
+       if (is_null_sha1(stash))
+               return;
+
+       reset_hard(head, 1);
+
+       args[2] = sha1_to_hex(stash);
+
+       /*
+        * It is OK to ignore error here, for example when there was
+        * nothing to restore.
+        */
+       run_command_v_opt(args, RUN_GIT_CMD);
+
+       strbuf_release(&sb);
+       refresh_cache(REFRESH_QUIET);
+}
+
+/* This is called when no merge was necessary. */
+static void finish_up_to_date(const char *msg)
+{
+       if (verbosity >= 0)
+               printf("%s%s\n", squash ? " (nothing to squash)" : "", msg);
+       drop_save();
+}
+
+static void squash_message(void)
+{
+       struct rev_info rev;
+       struct commit *commit;
+       struct strbuf out = STRBUF_INIT;
+       struct commit_list *j;
+       int fd;
+       struct pretty_print_context ctx = {0};
+
+       printf("Squash commit -- not updating HEAD\n");
+       fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
+       if (fd < 0)
+               die_errno("Could not write to '%s'", git_path("SQUASH_MSG"));
+
+       init_revisions(&rev, NULL);
+       rev.ignore_merges = 1;
+       rev.commit_format = CMIT_FMT_MEDIUM;
+
+       commit = lookup_commit(head);
+       commit->object.flags |= UNINTERESTING;
+       add_pending_object(&rev, &commit->object, NULL);
+
+       for (j = remoteheads; j; j = j->next)
+               add_pending_object(&rev, &j->item->object, NULL);
+
+       setup_revisions(0, NULL, &rev, NULL);
+       if (prepare_revision_walk(&rev))
+               die("revision walk setup failed");
+
+       ctx.abbrev = rev.abbrev;
+       ctx.date_mode = rev.date_mode;
+
+       strbuf_addstr(&out, "Squashed commit of the following:\n");
+       while ((commit = get_revision(&rev)) != NULL) {
+               strbuf_addch(&out, '\n');
+               strbuf_addf(&out, "commit %s\n",
+                       sha1_to_hex(commit->object.sha1));
+               pretty_print_commit(rev.commit_format, commit, &out, &ctx);
+       }
+       if (write(fd, out.buf, out.len) < 0)
+               die_errno("Writing SQUASH_MSG");
+       if (close(fd))
+               die_errno("Finishing SQUASH_MSG");
+       strbuf_release(&out);
+}
+
+static void finish(const unsigned char *new_head, const char *msg)
+{
+       struct strbuf reflog_message = STRBUF_INIT;
+
+       if (!msg)
+               strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
+       else {
+               if (verbosity >= 0)
+                       printf("%s\n", msg);
+               strbuf_addf(&reflog_message, "%s: %s",
+                       getenv("GIT_REFLOG_ACTION"), msg);
+       }
+       if (squash) {
+               squash_message();
+       } else {
+               if (verbosity >= 0 && !merge_msg.len)
+                       printf("No merge message -- not updating HEAD\n");
+               else {
+                       const char *argv_gc_auto[] = { "gc", "--auto", NULL };
+                       update_ref(reflog_message.buf, "HEAD",
+                               new_head, head, 0,
+                               DIE_ON_ERR);
+                       /*
+                        * We ignore errors in 'gc --auto', since the
+                        * user should see them.
+                        */
+                       run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+               }
+       }
+       if (new_head && show_diffstat) {
+               struct diff_options opts;
+               diff_setup(&opts);
+               opts.output_format |=
+                       DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+               opts.detect_rename = DIFF_DETECT_RENAME;
+               if (diff_use_color_default > 0)
+                       DIFF_OPT_SET(&opts, COLOR_DIFF);
+               if (diff_setup_done(&opts) < 0)
+                       die("diff_setup_done failed");
+               diff_tree_sha1(head, new_head, "", &opts);
+               diffcore_std(&opts);
+               diff_flush(&opts);
+       }
+
+       /* Run a post-merge hook */
+       run_hook(NULL, "post-merge", squash ? "1" : "0", NULL);
+
+       strbuf_release(&reflog_message);
+}
+
+/* Get the name for the merge commit's message. */
+static void merge_name(const char *remote, struct strbuf *msg)
+{
+       struct object *remote_head;
+       unsigned char branch_head[20], buf_sha[20];
+       struct strbuf buf = STRBUF_INIT;
+       struct strbuf bname = STRBUF_INIT;
+       const char *ptr;
+       char *found_ref;
+       int len, early;
+
+       strbuf_branchname(&bname, remote);
+       remote = bname.buf;
+
+       memset(branch_head, 0, sizeof(branch_head));
+       remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+       if (!remote_head)
+               die("'%s' does not point to a commit", remote);
+
+       if (dwim_ref(remote, strlen(remote), branch_head, &found_ref) > 0) {
+               if (!prefixcmp(found_ref, "refs/heads/")) {
+                       strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
+                                   sha1_to_hex(branch_head), remote);
+                       goto cleanup;
+               }
+               if (!prefixcmp(found_ref, "refs/remotes/")) {
+                       strbuf_addf(msg, "%s\t\tremote branch '%s' of .\n",
+                                   sha1_to_hex(branch_head), remote);
+                       goto cleanup;
+               }
+       }
+
+       /* See if remote matches <name>^^^.. or <name>~<number> */
+       for (len = 0, ptr = remote + strlen(remote);
+            remote < ptr && ptr[-1] == '^';
+            ptr--)
+               len++;
+       if (len)
+               early = 1;
+       else {
+               early = 0;
+               ptr = strrchr(remote, '~');
+               if (ptr) {
+                       int seen_nonzero = 0;
+
+                       len++; /* count ~ */
+                       while (*++ptr && isdigit(*ptr)) {
+                               seen_nonzero |= (*ptr != '0');
+                               len++;
+                       }
+                       if (*ptr)
+                               len = 0; /* not ...~<number> */
+                       else if (seen_nonzero)
+                               early = 1;
+                       else if (len == 1)
+                               early = 1; /* "name~" is "name~1"! */
+               }
+       }
+       if (len) {
+               struct strbuf truname = STRBUF_INIT;
+               strbuf_addstr(&truname, "refs/heads/");
+               strbuf_addstr(&truname, remote);
+               strbuf_setlen(&truname, truname.len - len);
+               if (resolve_ref(truname.buf, buf_sha, 1, NULL)) {
+                       strbuf_addf(msg,
+                                   "%s\t\tbranch '%s'%s of .\n",
+                                   sha1_to_hex(remote_head->sha1),
+                                   truname.buf + 11,
+                                   (early ? " (early part)" : ""));
+                       strbuf_release(&truname);
+                       goto cleanup;
+               }
+       }
+
+       if (!strcmp(remote, "FETCH_HEAD") &&
+                       !access(git_path("FETCH_HEAD"), R_OK)) {
+               FILE *fp;
+               struct strbuf line = STRBUF_INIT;
+               char *ptr;
+
+               fp = fopen(git_path("FETCH_HEAD"), "r");
+               if (!fp)
+                       die_errno("could not open '%s' for reading",
+                                 git_path("FETCH_HEAD"));
+               strbuf_getline(&line, fp, '\n');
+               fclose(fp);
+               ptr = strstr(line.buf, "\tnot-for-merge\t");
+               if (ptr)
+                       strbuf_remove(&line, ptr-line.buf+1, 13);
+               strbuf_addbuf(msg, &line);
+               strbuf_release(&line);
+               goto cleanup;
+       }
+       strbuf_addf(msg, "%s\t\tcommit '%s'\n",
+               sha1_to_hex(remote_head->sha1), remote);
+cleanup:
+       strbuf_release(&buf);
+       strbuf_release(&bname);
+}
+
+static int git_merge_config(const char *k, const char *v, void *cb)
+{
+       if (branch && !prefixcmp(k, "branch.") &&
+               !prefixcmp(k + 7, branch) &&
+               !strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+               const char **argv;
+               int argc;
+               char *buf;
+
+               buf = xstrdup(v);
+               argc = split_cmdline(buf, &argv);
+               if (argc < 0)
+                       die("Bad branch.%s.mergeoptions string: %s", branch,
+                           split_cmdline_strerror(argc));
+               argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+               memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+               argc++;
+               parse_options(argc, argv, NULL, builtin_merge_options,
+                             builtin_merge_usage, 0);
+               free(buf);
+       }
+
+       if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+               show_diffstat = git_config_bool(k, v);
+       else if (!strcmp(k, "pull.twohead"))
+               return git_config_string(&pull_twohead, k, v);
+       else if (!strcmp(k, "pull.octopus"))
+               return git_config_string(&pull_octopus, k, v);
+       else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary"))
+               option_log = git_config_bool(k, v);
+       else if (!strcmp(k, "merge.renormalize"))
+               option_renormalize = git_config_bool(k, v);
+       return git_diff_ui_config(k, v, cb);
+}
+
+static int read_tree_trivial(unsigned char *common, unsigned char *head,
+                            unsigned char *one)
+{
+       int i, nr_trees = 0;
+       struct tree *trees[MAX_UNPACK_TREES];
+       struct tree_desc t[MAX_UNPACK_TREES];
+       struct unpack_trees_options opts;
+
+       memset(&opts, 0, sizeof(opts));
+       opts.head_idx = 2;
+       opts.src_index = &the_index;
+       opts.dst_index = &the_index;
+       opts.update = 1;
+       opts.verbose_update = 1;
+       opts.trivial_merges_only = 1;
+       opts.merge = 1;
+       trees[nr_trees] = parse_tree_indirect(common);
+       if (!trees[nr_trees++])
+               return -1;
+       trees[nr_trees] = parse_tree_indirect(head);
+       if (!trees[nr_trees++])
+               return -1;
+       trees[nr_trees] = parse_tree_indirect(one);
+       if (!trees[nr_trees++])
+               return -1;
+       opts.fn = threeway_merge;
+       cache_tree_free(&active_cache_tree);
+       for (i = 0; i < nr_trees; i++) {
+               parse_tree(trees[i]);
+               init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+       }
+       if (unpack_trees(nr_trees, t, &opts))
+               return -1;
+       return 0;
+}
+
+static void write_tree_trivial(unsigned char *sha1)
+{
+       if (write_cache_as_tree(sha1, 0, NULL))
+               die("git write-tree failed to write a tree");
+}
+
+int try_merge_command(const char *strategy, struct commit_list *common,
+                     const char *head_arg, struct commit_list *remotes)
+{
+       const char **args;
+       int i = 0, x = 0, ret;
+       struct commit_list *j;
+       struct strbuf buf = STRBUF_INIT;
+
+       args = xmalloc((4 + xopts_nr + commit_list_count(common) +
+                       commit_list_count(remotes)) * sizeof(char *));
+       strbuf_addf(&buf, "merge-%s", strategy);
+       args[i++] = buf.buf;
+       for (x = 0; x < xopts_nr; x++) {
+               char *s = xmalloc(strlen(xopts[x])+2+1);
+               strcpy(s, "--");
+               strcpy(s+2, xopts[x]);
+               args[i++] = s;
+       }
+       for (j = common; j; j = j->next)
+               args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+       args[i++] = "--";
+       args[i++] = head_arg;
+       for (j = remotes; j; j = j->next)
+               args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+       args[i] = NULL;
+       ret = run_command_v_opt(args, RUN_GIT_CMD);
+       strbuf_release(&buf);
+       i = 1;
+       for (x = 0; x < xopts_nr; x++)
+               free((void *)args[i++]);
+       for (j = common; j; j = j->next)
+               free((void *)args[i++]);
+       i += 2;
+       for (j = remotes; j; j = j->next)
+               free((void *)args[i++]);
+       free(args);
+       discard_cache();
+       if (read_cache() < 0)
+               die("failed to read the cache");
+       resolve_undo_clear();
+
+       return ret;
+}
+
+static int try_merge_strategy(const char *strategy, struct commit_list *common,
+                             const char *head_arg)
+{
+       int index_fd;
+       struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+
+       index_fd = hold_locked_index(lock, 1);
+       refresh_cache(REFRESH_QUIET);
+       if (active_cache_changed &&
+                       (write_cache(index_fd, active_cache, active_nr) ||
+                        commit_locked_index(lock)))
+               return error("Unable to write index.");
+       rollback_lock_file(lock);
+
+       if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) {
+               int clean, x;
+               struct commit *result;
+               struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+               int index_fd;
+               struct commit_list *reversed = NULL;
+               struct merge_options o;
+               struct commit_list *j;
+
+               if (remoteheads->next) {
+                       error("Not handling anything other than two heads merge.");
+                       return 2;
+               }
+
+               init_merge_options(&o);
+               if (!strcmp(strategy, "subtree"))
+                       o.subtree_shift = "";
+
+               o.renormalize = option_renormalize;
+
+               /*
+                * NEEDSWORK: merge with table in builtin/merge-recursive
+                */
+               for (x = 0; x < xopts_nr; x++) {
+                       if (!strcmp(xopts[x], "ours"))
+                               o.recursive_variant = MERGE_RECURSIVE_OURS;
+                       else if (!strcmp(xopts[x], "theirs"))
+                               o.recursive_variant = MERGE_RECURSIVE_THEIRS;
+                       else if (!strcmp(xopts[x], "subtree"))
+                               o.subtree_shift = "";
+                       else if (!prefixcmp(xopts[x], "subtree="))
+                               o.subtree_shift = xopts[x]+8;
+                       else if (!strcmp(xopts[x], "renormalize"))
+                               o.renormalize = 1;
+                       else if (!strcmp(xopts[x], "no-renormalize"))
+                               o.renormalize = 0;
+                       else
+                               die("Unknown option for merge-recursive: -X%s", xopts[x]);
+               }
+
+               o.branch1 = head_arg;
+               o.branch2 = remoteheads->item->util;
+
+               for (j = common; j; j = j->next)
+                       commit_list_insert(j->item, &reversed);
+
+               index_fd = hold_locked_index(lock, 1);
+               clean = merge_recursive(&o, lookup_commit(head),
+                               remoteheads->item, reversed, &result);
+               if (active_cache_changed &&
+                               (write_cache(index_fd, active_cache, active_nr) ||
+                                commit_locked_index(lock)))
+                       die ("unable to write %s", get_index_file());
+               rollback_lock_file(lock);
+               return clean ? 0 : 1;
+       } else {
+               return try_merge_command(strategy, common, head_arg, remoteheads);
+       }
+}
+
+static void count_diff_files(struct diff_queue_struct *q,
+                            struct diff_options *opt, void *data)
+{
+       int *count = data;
+
+       (*count) += q->nr;
+}
+
+static int count_unmerged_entries(void)
+{
+       int i, ret = 0;
+
+       for (i = 0; i < active_nr; i++)
+               if (ce_stage(active_cache[i]))
+                       ret++;
+
+       return ret;
+}
+
+int checkout_fast_forward(const unsigned char *head, const unsigned char *remote)
+{
+       struct tree *trees[MAX_UNPACK_TREES];
+       struct unpack_trees_options opts;
+       struct tree_desc t[MAX_UNPACK_TREES];
+       int i, fd, nr_trees = 0;
+       struct dir_struct dir;
+       struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+       refresh_cache(REFRESH_QUIET);
+
+       fd = hold_locked_index(lock_file, 1);
+
+       memset(&trees, 0, sizeof(trees));
+       memset(&opts, 0, sizeof(opts));
+       memset(&t, 0, sizeof(t));
+       memset(&dir, 0, sizeof(dir));
+       dir.flags |= DIR_SHOW_IGNORED;
+       dir.exclude_per_dir = ".gitignore";
+       opts.dir = &dir;
+
+       opts.head_idx = 1;
+       opts.src_index = &the_index;
+       opts.dst_index = &the_index;
+       opts.update = 1;
+       opts.verbose_update = 1;
+       opts.merge = 1;
+       opts.fn = twoway_merge;
+       setup_unpack_trees_porcelain(&opts, "merge");
+
+       trees[nr_trees] = parse_tree_indirect(head);
+       if (!trees[nr_trees++])
+               return -1;
+       trees[nr_trees] = parse_tree_indirect(remote);
+       if (!trees[nr_trees++])
+               return -1;
+       for (i = 0; i < nr_trees; i++) {
+               parse_tree(trees[i]);
+               init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+       }
+       if (unpack_trees(nr_trees, t, &opts))
+               return -1;
+       if (write_cache(fd, active_cache, active_nr) ||
+               commit_locked_index(lock_file))
+               die("unable to write new index file");
+       return 0;
+}
+
+static void split_merge_strategies(const char *string, struct strategy **list,
+                                  int *nr, int *alloc)
+{
+       char *p, *q, *buf;
+
+       if (!string)
+               return;
+
+       buf = xstrdup(string);
+       q = buf;
+       for (;;) {
+               p = strchr(q, ' ');
+               if (!p) {
+                       ALLOC_GROW(*list, *nr + 1, *alloc);
+                       (*list)[(*nr)++].name = xstrdup(q);
+                       free(buf);
+                       return;
+               } else {
+                       *p = '\0';
+                       ALLOC_GROW(*list, *nr + 1, *alloc);
+                       (*list)[(*nr)++].name = xstrdup(q);
+                       q = ++p;
+               }
+       }
+}
+
+static void add_strategies(const char *string, unsigned attr)
+{
+       struct strategy *list = NULL;
+       int list_alloc = 0, list_nr = 0, i;
+
+       memset(&list, 0, sizeof(list));
+       split_merge_strategies(string, &list, &list_nr, &list_alloc);
+       if (list) {
+               for (i = 0; i < list_nr; i++)
+                       append_strategy(get_strategy(list[i].name));
+               return;
+       }
+       for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
+               if (all_strategy[i].attr & attr)
+                       append_strategy(&all_strategy[i]);
+
+}
+
+static int merge_trivial(void)
+{
+       unsigned char result_tree[20], result_commit[20];
+       struct commit_list *parent = xmalloc(sizeof(*parent));
+
+       write_tree_trivial(result_tree);
+       printf("Wonderful.\n");
+       parent->item = lookup_commit(head);
+       parent->next = xmalloc(sizeof(*parent->next));
+       parent->next->item = remoteheads->item;
+       parent->next->next = NULL;
+       commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
+       finish(result_commit, "In-index merge");
+       drop_save();
+       return 0;
+}
+
+static int finish_automerge(struct commit_list *common,
+                           unsigned char *result_tree,
+                           const char *wt_strategy)
+{
+       struct commit_list *parents = NULL, *j;
+       struct strbuf buf = STRBUF_INIT;
+       unsigned char result_commit[20];
+
+       free_commit_list(common);
+       if (allow_fast_forward) {
+               parents = remoteheads;
+               commit_list_insert(lookup_commit(head), &parents);
+               parents = reduce_heads(parents);
+       } else {
+               struct commit_list **pptr = &parents;
+
+               pptr = &commit_list_insert(lookup_commit(head),
+                               pptr)->next;
+               for (j = remoteheads; j; j = j->next)
+                       pptr = &commit_list_insert(j->item, pptr)->next;
+       }
+       free_commit_list(remoteheads);
+       strbuf_addch(&merge_msg, '\n');
+       commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
+       strbuf_addf(&buf, "Merge made by %s.", wt_strategy);
+       finish(result_commit, buf.buf);
+       strbuf_release(&buf);
+       drop_save();
+       return 0;
+}
+
+static int suggest_conflicts(int renormalizing)
+{
+       FILE *fp;
+       int pos;
+
+       fp = fopen(git_path("MERGE_MSG"), "a");
+       if (!fp)
+               die_errno("Could not open '%s' for writing",
+                         git_path("MERGE_MSG"));
+       fprintf(fp, "\nConflicts:\n");
+       for (pos = 0; pos < active_nr; pos++) {
+               struct cache_entry *ce = active_cache[pos];
+
+               if (ce_stage(ce)) {
+                       fprintf(fp, "\t%s\n", ce->name);
+                       while (pos + 1 < active_nr &&
+                                       !strcmp(ce->name,
+                                               active_cache[pos + 1]->name))
+                               pos++;
+               }
+       }
+       fclose(fp);
+       rerere(allow_rerere_auto);
+       printf("Automatic merge failed; "
+                       "fix conflicts and then commit the result.\n");
+       return 1;
+}
+
+static struct commit *is_old_style_invocation(int argc, const char **argv)
+{
+       struct commit *second_token = NULL;
+       if (argc > 2) {
+               unsigned char second_sha1[20];
+
+               if (get_sha1(argv[1], second_sha1))
+                       return NULL;
+               second_token = lookup_commit_reference_gently(second_sha1, 0);
+               if (!second_token)
+                       die("'%s' is not a commit", argv[1]);
+               if (hashcmp(second_token->object.sha1, head))
+                       return NULL;
+       }
+       return second_token;
+}
+
+static int evaluate_result(void)
+{
+       int cnt = 0;
+       struct rev_info rev;
+
+       /* Check how many files differ. */
+       init_revisions(&rev, "");
+       setup_revisions(0, NULL, &rev, NULL);
+       rev.diffopt.output_format |=
+               DIFF_FORMAT_CALLBACK;
+       rev.diffopt.format_callback = count_diff_files;
+       rev.diffopt.format_callback_data = &cnt;
+       run_diff_files(&rev, 0);
+
+       /*
+        * Check how many unmerged entries are
+        * there.
+        */
+       cnt += count_unmerged_entries();
+
+       return cnt;
+}
+
+int cmd_merge(int argc, const char **argv, const char *prefix)
+{
+       unsigned char result_tree[20];
+       struct strbuf buf = STRBUF_INIT;
+       const char *head_arg;
+       int flag, head_invalid = 0, i;
+       int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
+       struct commit_list *common = NULL;
+       const char *best_strategy = NULL, *wt_strategy = NULL;
+       struct commit_list **remotes = &remoteheads;
+
+       if (read_cache_unmerged()) {
+               die_resolve_conflict("merge");
+       }
+       if (file_exists(git_path("MERGE_HEAD"))) {
+               /*
+                * There is no unmerged entry, don't advise 'git
+                * add/rm <file>', just 'git commit'.
+                */
+               if (advice_resolve_conflict)
+                       die("You have not concluded your merge (MERGE_HEAD exists).\n"
+                           "Please, commit your changes before you can merge.");
+               else
+                       die("You have not concluded your merge (MERGE_HEAD exists).");
+       }
+
+       resolve_undo_clear();
+       /*
+        * Check if we are _not_ on a detached HEAD, i.e. if there is a
+        * current branch.
+        */
+       branch = resolve_ref("HEAD", head, 0, &flag);
+       if (branch && !prefixcmp(branch, "refs/heads/"))
+               branch += 11;
+       if (is_null_sha1(head))
+               head_invalid = 1;
+
+       git_config(git_merge_config, NULL);
+
+       /* for color.ui */
+       if (diff_use_color_default == -1)
+               diff_use_color_default = git_use_color_default;
+
+       argc = parse_options(argc, argv, prefix, builtin_merge_options,
+                       builtin_merge_usage, 0);
+       if (verbosity < 0)
+               show_diffstat = 0;
+
+       if (squash) {
+               if (!allow_fast_forward)
+                       die("You cannot combine --squash with --no-ff.");
+               option_commit = 0;
+       }
+
+       if (!allow_fast_forward && fast_forward_only)
+               die("You cannot combine --no-ff with --ff-only.");
+
+       if (!argc)
+               usage_with_options(builtin_merge_usage,
+                       builtin_merge_options);
+
+       /*
+        * This could be traditional "merge <msg> HEAD <commit>..."  and
+        * the way we can tell it is to see if the second token is HEAD,
+        * but some people might have misused the interface and used a
+        * committish that is the same as HEAD there instead.
+        * Traditional format never would have "-m" so it is an
+        * additional safety measure to check for it.
+        */
+
+       if (!have_message && is_old_style_invocation(argc, argv)) {
+               strbuf_addstr(&merge_msg, argv[0]);
+               head_arg = argv[1];
+               argv += 2;
+               argc -= 2;
+       } else if (head_invalid) {
+               struct object *remote_head;
+               /*
+                * If the merged head is a valid one there is no reason
+                * to forbid "git merge" into a branch yet to be born.
+                * We do the same for "git pull".
+                */
+               if (argc != 1)
+                       die("Can merge only exactly one commit into "
+                               "empty head");
+               if (squash)
+                       die("Squash commit into empty head not supported yet");
+               if (!allow_fast_forward)
+                       die("Non-fast-forward commit does not make sense into "
+                           "an empty head");
+               remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+               if (!remote_head)
+                       die("%s - not something we can merge", argv[0]);
+               update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
+                               DIE_ON_ERR);
+               reset_hard(remote_head->sha1, 0);
+               return 0;
+       } else {
+               struct strbuf merge_names = STRBUF_INIT;
+
+               /* We are invoked directly as the first-class UI. */
+               head_arg = "HEAD";
+
+               /*
+                * All the rest are the commits being merged;
+                * prepare the standard merge summary message to
+                * be appended to the given message.  If remote
+                * is invalid we will die later in the common
+                * codepath so we discard the error in this
+                * loop.
+                */
+               for (i = 0; i < argc; i++)
+                       merge_name(argv[i], &merge_names);
+
+               if (have_message && option_log)
+                       fmt_merge_msg_shortlog(&merge_names, &merge_msg);
+               else if (!have_message)
+                       fmt_merge_msg(option_log, &merge_names, &merge_msg);
+
+
+               if (!(have_message && !option_log) && merge_msg.len)
+                       strbuf_setlen(&merge_msg, merge_msg.len-1);
+       }
+
+       if (head_invalid || !argc)
+               usage_with_options(builtin_merge_usage,
+                       builtin_merge_options);
+
+       strbuf_addstr(&buf, "merge");
+       for (i = 0; i < argc; i++)
+               strbuf_addf(&buf, " %s", argv[i]);
+       setenv("GIT_REFLOG_ACTION", buf.buf, 0);
+       strbuf_reset(&buf);
+
+       for (i = 0; i < argc; i++) {
+               struct object *o;
+               struct commit *commit;
+
+               o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
+               if (!o)
+                       die("%s - not something we can merge", argv[i]);
+               commit = lookup_commit(o->sha1);
+               commit->util = (void *)argv[i];
+               remotes = &commit_list_insert(commit, remotes)->next;
+
+               strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
+               setenv(buf.buf, argv[i], 1);
+               strbuf_reset(&buf);
+       }
+
+       if (!use_strategies) {
+               if (!remoteheads->next)
+                       add_strategies(pull_twohead, DEFAULT_TWOHEAD);
+               else
+                       add_strategies(pull_octopus, DEFAULT_OCTOPUS);
+       }
+
+       for (i = 0; i < use_strategies_nr; i++) {
+               if (use_strategies[i]->attr & NO_FAST_FORWARD)
+                       allow_fast_forward = 0;
+               if (use_strategies[i]->attr & NO_TRIVIAL)
+                       allow_trivial = 0;
+       }
+
+       if (!remoteheads->next)
+               common = get_merge_bases(lookup_commit(head),
+                               remoteheads->item, 1);
+       else {
+               struct commit_list *list = remoteheads;
+               commit_list_insert(lookup_commit(head), &list);
+               common = get_octopus_merge_bases(list);
+               free(list);
+       }
+
+       update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
+               DIE_ON_ERR);
+
+       if (!common)
+               ; /* No common ancestors found. We need a real merge. */
+       else if (!remoteheads->next && !common->next &&
+                       common->item == remoteheads->item) {
+               /*
+                * If head can reach all the merge then we are up to date.
+                * but first the most common case of merging one remote.
+                */
+               finish_up_to_date("Already up-to-date.");
+               return 0;
+       } else if (allow_fast_forward && !remoteheads->next &&
+                       !common->next &&
+                       !hashcmp(common->item->object.sha1, head)) {
+               /* Again the most common case of merging one remote. */
+               struct strbuf msg = STRBUF_INIT;
+               struct object *o;
+               char hex[41];
+
+               strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
+
+               if (verbosity >= 0)
+                       printf("Updating %s..%s\n",
+                               hex,
+                               find_unique_abbrev(remoteheads->item->object.sha1,
+                               DEFAULT_ABBREV));
+               strbuf_addstr(&msg, "Fast-forward");
+               if (have_message)
+                       strbuf_addstr(&msg,
+                               " (no commit created; -m option ignored)");
+               o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
+                       0, NULL, OBJ_COMMIT);
+               if (!o)
+                       return 1;
+
+               if (checkout_fast_forward(head, remoteheads->item->object.sha1))
+                       return 1;
+
+               finish(o->sha1, msg.buf);
+               drop_save();
+               return 0;
+       } else if (!remoteheads->next && common->next)
+               ;
+               /*
+                * We are not doing octopus and not fast-forward.  Need
+                * a real merge.
+                */
+       else if (!remoteheads->next && !common->next && option_commit) {
+               /*
+                * We are not doing octopus, not fast-forward, and have
+                * only one common.
+                */
+               refresh_cache(REFRESH_QUIET);
+               if (allow_trivial && !fast_forward_only) {
+                       /* See if it is really trivial. */
+                       git_committer_info(IDENT_ERROR_ON_NO_NAME);
+                       printf("Trying really trivial in-index merge...\n");
+                       if (!read_tree_trivial(common->item->object.sha1,
+                                       head, remoteheads->item->object.sha1))
+                               return merge_trivial();
+                       printf("Nope.\n");
+               }
+       } else {
+               /*
+                * An octopus.  If we can reach all the remote we are up
+                * to date.
+                */
+               int up_to_date = 1;
+               struct commit_list *j;
+
+               for (j = remoteheads; j; j = j->next) {
+                       struct commit_list *common_one;
+
+                       /*
+                        * Here we *have* to calculate the individual
+                        * merge_bases again, otherwise "git merge HEAD^
+                        * HEAD^^" would be missed.
+                        */
+                       common_one = get_merge_bases(lookup_commit(head),
+                               j->item, 1);
+                       if (hashcmp(common_one->item->object.sha1,
+                               j->item->object.sha1)) {
+                               up_to_date = 0;
+                               break;
+                       }
+               }
+               if (up_to_date) {
+                       finish_up_to_date("Already up-to-date. Yeeah!");
+                       return 0;
+               }
+       }
+
+       if (fast_forward_only)
+               die("Not possible to fast-forward, aborting.");
+
+       /* We are going to make a new commit. */
+       git_committer_info(IDENT_ERROR_ON_NO_NAME);
+
+       /*
+        * 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.
+        */
+       if (use_strategies_nr != 1) {
+               /*
+                * Stash away the local changes so that we can try more
+                * than one.
+                */
+               save_state();
+       } else {
+               memcpy(stash, null_sha1, 20);
+       }
+
+       for (i = 0; i < use_strategies_nr; i++) {
+               int ret;
+               if (i) {
+                       printf("Rewinding the tree to pristine...\n");
+                       restore_state();
+               }
+               if (use_strategies_nr != 1)
+                       printf("Trying merge strategy %s...\n",
+                               use_strategies[i]->name);
+               /*
+                * Remember which strategy left the state in the working
+                * tree.
+                */
+               wt_strategy = use_strategies[i]->name;
+
+               ret = try_merge_strategy(use_strategies[i]->name,
+                       common, head_arg);
+               if (!option_commit && !ret) {
+                       merge_was_ok = 1;
+                       /*
+                        * This is necessary here just to avoid writing
+                        * the tree, but later we will *not* exit with
+                        * status code 1 because merge_was_ok is set.
+                        */
+                       ret = 1;
+               }
+
+               if (ret) {
+                       /*
+                        * 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 (ret == 1) {
+                               int cnt = evaluate_result();
+
+                               if (best_cnt <= 0 || cnt <= best_cnt) {
+                                       best_strategy = use_strategies[i]->name;
+                                       best_cnt = cnt;
+                               }
+                       }
+                       if (merge_was_ok)
+                               break;
+                       else
+                               continue;
+               }
+
+               /* Automerge succeeded. */
+               write_tree_trivial(result_tree);
+               automerge_was_ok = 1;
+               break;
+       }
+
+       /*
+        * If we have a resulting tree, that means the strategy module
+        * auto resolved the merge cleanly.
+        */
+       if (automerge_was_ok)
+               return finish_automerge(common, result_tree, wt_strategy);
+
+       /*
+        * Pick the result from the best strategy and have the user fix
+        * it up.
+        */
+       if (!best_strategy) {
+               restore_state();
+               if (use_strategies_nr > 1)
+                       fprintf(stderr,
+                               "No merge strategy handled the merge.\n");
+               else
+                       fprintf(stderr, "Merge with strategy %s failed.\n",
+                               use_strategies[0]->name);
+               return 2;
+       } else if (best_strategy == wt_strategy)
+               ; /* We already have its result in the working tree. */
+       else {
+               printf("Rewinding the tree to pristine...\n");
+               restore_state();
+               printf("Using the %s to prepare resolving by hand.\n",
+                       best_strategy);
+               try_merge_strategy(best_strategy, common, head_arg);
+       }
+
+       if (squash)
+               finish(NULL, NULL);
+       else {
+               int fd;
+               struct commit_list *j;
+
+               for (j = remoteheads; j; j = j->next)
+                       strbuf_addf(&buf, "%s\n",
+                               sha1_to_hex(j->item->object.sha1));
+               fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
+               if (fd < 0)
+                       die_errno("Could not open '%s' for writing",
+                                 git_path("MERGE_HEAD"));
+               if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+                       die_errno("Could not write to '%s'", git_path("MERGE_HEAD"));
+               close(fd);
+               strbuf_addch(&merge_msg, '\n');
+               fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+               if (fd < 0)
+                       die_errno("Could not open '%s' for writing",
+                                 git_path("MERGE_MSG"));
+               if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
+                       merge_msg.len)
+                       die_errno("Could not write to '%s'", git_path("MERGE_MSG"));
+               close(fd);
+               fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
+               if (fd < 0)
+                       die_errno("Could not open '%s' for writing",
+                                 git_path("MERGE_MODE"));
+               strbuf_reset(&buf);
+               if (!allow_fast_forward)
+                       strbuf_addf(&buf, "no-ff");
+               if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+                       die_errno("Could not write to '%s'", git_path("MERGE_MODE"));
+               close(fd);
+       }
+
+       if (merge_was_ok) {
+               fprintf(stderr, "Automatic merge went well; "
+                       "stopped before committing as requested\n");
+               return 0;
+       } else
+               return suggest_conflicts(option_renormalize);
+}
diff --git a/builtin/mktag.c b/builtin/mktag.c
new file mode 100644 (file)
index 0000000..1cb0f3f
--- /dev/null
@@ -0,0 +1,179 @@
+#include "cache.h"
+#include "tag.h"
+#include "exec_cmd.h"
+
+/*
+ * A signature file has a very simple fixed format: four lines
+ * of "object <sha1>" + "type <typename>" + "tag <tagname>" +
+ * "tagger <committer>", followed by a blank line, a free-form tag
+ * message and a signature block that git itself doesn't care about,
+ * but that can be verified with gpg or similar.
+ *
+ * The first four lines are guaranteed to be at least 83 bytes:
+ * "object <sha1>\n" is 48 bytes, "type tag\n" at 9 bytes is the
+ * shortest possible type-line, "tag .\n" at 6 bytes is the shortest
+ * single-character-tag line, and "tagger . <> 0 +0000\n" at 20 bytes is
+ * the shortest possible tagger-line.
+ */
+
+/*
+ * We refuse to tag something we can't verify. Just because.
+ */
+static int verify_object(const unsigned char *sha1, const char *expected_type)
+{
+       int ret = -1;
+       enum object_type type;
+       unsigned long size;
+       const unsigned char *repl;
+       void *buffer = read_sha1_file_repl(sha1, &type, &size, &repl);
+
+       if (buffer) {
+               if (type == type_from_string(expected_type))
+                       ret = check_sha1_signature(repl, buffer, size, expected_type);
+               free(buffer);
+       }
+       return ret;
+}
+
+#ifdef NO_C99_FORMAT
+#define PD_FMT "%d"
+#else
+#define PD_FMT "%td"
+#endif
+
+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, *lb, *rb;
+       size_t len;
+
+       if (size < 84)
+               return error("wanna fool me ? you obviously got the size wrong !");
+
+       buffer[size] = 0;
+
+       /* Verify object line */
+       object = buffer;
+       if (memcmp(object, "object ", 7))
+               return error("char%d: does not start with \"object \"", 0);
+
+       if (get_sha1_hex(object + 7, sha1))
+               return error("char%d: could not get SHA1 hash", 7);
+
+       /* Verify type line */
+       type_line = object + 48;
+       if (memcmp(type_line - 1, "\ntype ", 6))
+               return error("char%d: could not find \"\\ntype \"", 47);
+
+       /* Verify tag-line */
+       tag_line = strchr(type_line, '\n');
+       if (!tag_line)
+               return error("char" PD_FMT ": could not find next \"\\n\"", type_line - buffer);
+       tag_line++;
+       if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n')
+               return error("char" PD_FMT ": no \"tag \" found", tag_line - buffer);
+
+       /* Get the actual type */
+       typelen = tag_line - type_line - strlen("type \n");
+       if (typelen >= sizeof(type))
+               return error("char" PD_FMT ": type too long", type_line+5 - buffer);
+
+       memcpy(type, type_line+5, typelen);
+       type[typelen] = 0;
+
+       /* Verify that the object matches */
+       if (verify_object(sha1, type))
+               return error("char%d: could not verify object %s", 7, sha1_to_hex(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" PD_FMT ": could not verify tag name", tag_line - buffer);
+       }
+
+       /* Verify the tagger line */
+       tagger_line = tag_line;
+
+       if (memcmp(tagger_line, "tagger ", 7))
+               return error("char" PD_FMT ": could not find \"tagger \"",
+                       tagger_line - buffer);
+
+       /*
+        * Check for correct form for name and email
+        * i.e. " <" followed by "> " on _this_ line
+        * No angle brackets within the name or email address fields.
+        * No spaces within the email address field.
+        */
+       tagger_line += 7;
+       if (!(lb = strstr(tagger_line, " <")) || !(rb = strstr(lb+2, "> ")) ||
+               strpbrk(tagger_line, "<>\n") != lb+1 ||
+               strpbrk(lb+2, "><\n ") != rb)
+               return error("char" PD_FMT ": malformed tagger field",
+                       tagger_line - buffer);
+
+       /* Check for author name, at least one character, space is acceptable */
+       if (lb == tagger_line)
+               return error("char" PD_FMT ": missing tagger name",
+                       tagger_line - buffer);
+
+       /* timestamp, 1 or more digits followed by space */
+       tagger_line = rb + 2;
+       if (!(len = strspn(tagger_line, "0123456789")))
+               return error("char" PD_FMT ": missing tag timestamp",
+                       tagger_line - buffer);
+       tagger_line += len;
+       if (*tagger_line != ' ')
+               return error("char" PD_FMT ": malformed tag timestamp",
+                       tagger_line - buffer);
+       tagger_line++;
+
+       /* timezone, 5 digits [+-]hhmm, max. 1400 */
+       if (!((tagger_line[0] == '+' || tagger_line[0] == '-') &&
+             strspn(tagger_line+1, "0123456789") == 4 &&
+             tagger_line[5] == '\n' && atoi(tagger_line+1) <= 1400))
+               return error("char" PD_FMT ": malformed tag timezone",
+                       tagger_line - buffer);
+       tagger_line += 6;
+
+       /* Verify the blank line separating the header from the body */
+       if (*tagger_line != '\n')
+               return error("char" PD_FMT ": trailing garbage in tag header",
+                       tagger_line - buffer);
+
+       /* The actual stuff afterwards we don't care about.. */
+       return 0;
+}
+
+#undef PD_FMT
+
+int cmd_mktag(int argc, const char **argv, const char *prefix)
+{
+       struct strbuf buf = STRBUF_INIT;
+       unsigned char result_sha1[20];
+
+       if (argc != 1)
+               usage("git mktag < signaturefile");
+
+       if (strbuf_read(&buf, 0, 4096) < 0) {
+               die_errno("could not read from stdin");
+       }
+
+       /* Verify it for some basic sanity: it needs to start with
+          "object <sha1>\ntype\ntagger " */
+       if (verify_tag(buf.buf, buf.len) < 0)
+               die("invalid tag signature file");
+
+       if (write_sha1_file(buf.buf, buf.len, tag_type, result_sha1) < 0)
+               die("unable to write tag file");
+
+       strbuf_release(&buf);
+       printf("%s\n", sha1_to_hex(result_sha1));
+       return 0;
+}
diff --git a/builtin/mktree.c b/builtin/mktree.c
new file mode 100644 (file)
index 0000000..098395f
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * GIT - the stupid content tracker
+ *
+ * Copyright (c) Junio C Hamano, 2006, 2009
+ */
+#include "builtin.h"
+#include "quote.h"
+#include "tree.h"
+#include "parse-options.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;
+       hashcpy(ent->sha1, sha1);
+       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)
+{
+       struct strbuf buf;
+       size_t size;
+       int i;
+
+       qsort(entries, used, sizeof(*entries), ent_compare);
+       for (size = i = 0; i < used; i++)
+               size += 32 + entries[i]->len;
+
+       strbuf_init(&buf, size);
+       for (i = 0; i < used; i++) {
+               struct treeent *ent = entries[i];
+               strbuf_addf(&buf, "%o %s%c", ent->mode, ent->name, '\0');
+               strbuf_add(&buf, ent->sha1, 20);
+       }
+
+       write_sha1_file(buf.buf, buf.len, tree_type, sha1);
+}
+
+static const char *mktree_usage[] = {
+       "git mktree [-z] [--missing] [--batch]",
+       NULL
+};
+
+static void mktree_line(char *buf, size_t len, int line_termination, int allow_missing)
+{
+       char *ptr, *ntr;
+       unsigned mode;
+       enum object_type mode_type; /* object type derived from mode */
+       enum object_type obj_type; /* object type derived from sha */
+       char *path;
+       unsigned char sha1[20];
+
+       ptr = buf;
+       /*
+        * Read 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", buf);
+       ptr = ntr + 1; /* type */
+       ntr = strchr(ptr, ' ');
+       if (!ntr || buf + len <= ntr + 40 ||
+           ntr[41] != '\t' ||
+           get_sha1_hex(ntr + 1, sha1))
+               die("input format error: %s", buf);
+
+       /* It is perfectly normal if we do not have a commit from a submodule */
+       if (S_ISGITLINK(mode))
+               allow_missing = 1;
+
+
+       *ntr++ = 0; /* now at the beginning of SHA1 */
+
+       path = ntr + 41;  /* at the beginning of name */
+       if (line_termination && path[0] == '"') {
+               struct strbuf p_uq = STRBUF_INIT;
+               if (unquote_c_style(&p_uq, path, NULL))
+                       die("invalid quoting");
+               path = strbuf_detach(&p_uq, NULL);
+       }
+
+       /*
+        * Object type is redundantly derivable three ways.
+        * These should all agree.
+        */
+       mode_type = object_type(mode);
+       if (mode_type != type_from_string(ptr)) {
+               die("entry '%s' object type (%s) doesn't match mode type (%s)",
+                       path, ptr, typename(mode_type));
+       }
+
+       /* Check the type of object identified by sha1 */
+       obj_type = sha1_object_info(sha1, NULL);
+       if (obj_type < 0) {
+               if (allow_missing) {
+                       ; /* no problem - missing objects are presumed to be of the right type */
+               } else {
+                       die("entry '%s' object %s is unavailable", path, sha1_to_hex(sha1));
+               }
+       } else {
+               if (obj_type != mode_type) {
+                       /*
+                        * The object exists but is of the wrong type.
+                        * This is a problem regardless of allow_missing
+                        * because the new tree entry will never be correct.
+                        */
+                       die("entry '%s' object %s is a %s but specified type was (%s)",
+                               path, sha1_to_hex(sha1), typename(obj_type), typename(mode_type));
+               }
+       }
+
+       append_to_tree(mode, sha1, path);
+}
+
+int cmd_mktree(int ac, const char **av, const char *prefix)
+{
+       struct strbuf sb = STRBUF_INIT;
+       unsigned char sha1[20];
+       int line_termination = '\n';
+       int allow_missing = 0;
+       int is_batch_mode = 0;
+       int got_eof = 0;
+
+       const struct option option[] = {
+               OPT_SET_INT('z', NULL, &line_termination, "input is NUL terminated", '\0'),
+               OPT_SET_INT( 0 , "missing", &allow_missing, "allow missing objects", 1),
+               OPT_SET_INT( 0 , "batch", &is_batch_mode, "allow creation of more than one tree", 1),
+               OPT_END()
+       };
+
+       ac = parse_options(ac, av, prefix, option, mktree_usage, 0);
+
+       while (!got_eof) {
+               while (1) {
+                       if (strbuf_getline(&sb, stdin, line_termination) == EOF) {
+                               got_eof = 1;
+                               break;
+                       }
+                       if (sb.buf[0] == '\0') {
+                               /* empty lines denote tree boundaries in batch mode */
+                               if (is_batch_mode)
+                                       break;
+                               die("input format error: (blank line only valid in batch mode)");
+                       }
+                       mktree_line(sb.buf, sb.len, line_termination, allow_missing);
+               }
+               if (is_batch_mode && got_eof && used < 1) {
+                       /*
+                        * Execution gets here if the last tree entry is terminated with a
+                        * new-line.  The final new-line has been made optional to be
+                        * consistent with the original non-batch behaviour of mktree.
+                        */
+                       ; /* skip creating an empty tree */
+               } else {
+                       write_tree(sha1);
+                       puts(sha1_to_hex(sha1));
+                       fflush(stdout);
+               }
+               used=0; /* reset tree entry buffer for re-use in batch mode */
+       }
+       strbuf_release(&sb);
+       exit(0);
+}
diff --git a/builtin/mv.c b/builtin/mv.c
new file mode 100644 (file)
index 0000000..cdbb094
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * "git mv" builtin command
+ *
+ * Copyright (C) 2006 Johannes Schindelin
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "dir.h"
+#include "cache-tree.h"
+#include "string-list.h"
+#include "parse-options.h"
+
+static const char * const builtin_mv_usage[] = {
+       "git mv [options] <source>... <destination>",
+       NULL
+};
+
+static const char **copy_pathspec(const char *prefix, const char **pathspec,
+                                 int count, int base_name)
+{
+       int i;
+       const char **result = xmalloc((count + 1) * sizeof(const char *));
+       memcpy(result, pathspec, count * sizeof(const char *));
+       result[count] = NULL;
+       for (i = 0; i < count; i++) {
+               int length = strlen(result[i]);
+               int to_copy = length;
+               while (to_copy > 0 && is_dir_sep(result[i][to_copy - 1]))
+                       to_copy--;
+               if (to_copy != length || base_name) {
+                       char *it = xmemdupz(result[i], to_copy);
+                       result[i] = base_name ? strdup(basename(it)) : it;
+               }
+       }
+       return get_pathspec(prefix, result);
+}
+
+static const char *add_slash(const char *path)
+{
+       int len = strlen(path);
+       if (path[len - 1] != '/') {
+               char *with_slash = xmalloc(len + 2);
+               memcpy(with_slash, path, len);
+               with_slash[len++] = '/';
+               with_slash[len] = 0;
+               return with_slash;
+       }
+       return path;
+}
+
+static struct lock_file lock_file;
+
+int cmd_mv(int argc, const char **argv, const char *prefix)
+{
+       int i, newfd;
+       int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;
+       struct option builtin_mv_options[] = {
+               OPT__DRY_RUN(&show_only),
+               OPT_BOOLEAN('f', "force", &force, "force move/rename even if target exists"),
+               OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"),
+               OPT_END(),
+       };
+       const char **source, **destination, **dest_path;
+       enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;
+       struct stat st;
+       struct string_list src_for_dst = STRING_LIST_INIT_NODUP;
+
+       git_config(git_default_config, NULL);
+
+       argc = parse_options(argc, argv, prefix, builtin_mv_options,
+                            builtin_mv_usage, 0);
+       if (--argc < 1)
+               usage_with_options(builtin_mv_usage, builtin_mv_options);
+
+       newfd = hold_locked_index(&lock_file, 1);
+       if (read_cache() < 0)
+               die("index file corrupt");
+
+       source = copy_pathspec(prefix, argv, argc, 0);
+       modes = xcalloc(argc, sizeof(enum update_mode));
+       dest_path = copy_pathspec(prefix, argv + argc, 1, 0);
+
+       if (dest_path[0][0] == '\0')
+               /* special case: "." was normalized to "" */
+               destination = copy_pathspec(dest_path[0], argv, argc, 1);
+       else if (!lstat(dest_path[0], &st) &&
+                       S_ISDIR(st.st_mode)) {
+               dest_path[0] = add_slash(dest_path[0]);
+               destination = copy_pathspec(dest_path[0], argv, argc, 1);
+       } else {
+               if (argc != 1)
+                       usage_with_options(builtin_mv_usage, builtin_mv_options);
+               destination = dest_path;
+       }
+
+       /* Checking */
+       for (i = 0; i < argc; i++) {
+               const char *src = source[i], *dst = destination[i];
+               int length, src_is_dir;
+               const char *bad = NULL;
+
+               if (show_only)
+                       printf("Checking rename of '%s' to '%s'\n", src, dst);
+
+               length = strlen(src);
+               if (lstat(src, &st) < 0)
+                       bad = "bad source";
+               else if (!strncmp(src, dst, length) &&
+                               (dst[length] == 0 || dst[length] == '/')) {
+                       bad = "can not move directory into itself";
+               } else if ((src_is_dir = S_ISDIR(st.st_mode))
+                               && lstat(dst, &st) == 0)
+                       bad = "cannot move directory over file";
+               else if (src_is_dir) {
+                       const char *src_w_slash = add_slash(src);
+                       int len_w_slash = length + 1;
+                       int first, last;
+
+                       modes[i] = WORKING_DIRECTORY;
+
+                       first = cache_name_pos(src_w_slash, len_w_slash);
+                       if (first >= 0)
+                               die ("Huh? %.*s is in index?",
+                                               len_w_slash, src_w_slash);
+
+                       first = -1 - first;
+                       for (last = first; last < active_nr; last++) {
+                               const char *path = active_cache[last]->name;
+                               if (strncmp(path, src_w_slash, len_w_slash))
+                                       break;
+                       }
+                       free((char *)src_w_slash);
+
+                       if (last - first < 1)
+                               bad = "source directory is empty";
+                       else {
+                               int j, dst_len;
+
+                               if (last - first > 0) {
+                                       source = xrealloc(source,
+                                                       (argc + last - first)
+                                                       * sizeof(char *));
+                                       destination = xrealloc(destination,
+                                                       (argc + last - first)
+                                                       * sizeof(char *));
+                                       modes = xrealloc(modes,
+                                                       (argc + last - first)
+                                                       * sizeof(enum update_mode));
+                               }
+
+                               dst = add_slash(dst);
+                               dst_len = strlen(dst);
+
+                               for (j = 0; j < last - first; j++) {
+                                       const char *path =
+                                               active_cache[first + j]->name;
+                                       source[argc + j] = path;
+                                       destination[argc + j] =
+                                               prefix_path(dst, dst_len,
+                                                       path + length + 1);
+                                       modes[argc + j] = INDEX;
+                               }
+                               argc += last - first;
+                       }
+               } else if (cache_name_pos(src, length) < 0)
+                       bad = "not under version control";
+               else if (lstat(dst, &st) == 0) {
+                       bad = "destination exists";
+                       if (force) {
+                               /*
+                                * only files can overwrite each other:
+                                * check both source and destination
+                                */
+                               if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
+                                       warning("%s; will overwrite!", bad);
+                                       bad = NULL;
+                               } else
+                                       bad = "Cannot overwrite";
+                       }
+               } else if (string_list_has_string(&src_for_dst, dst))
+                       bad = "multiple sources for the same target";
+               else
+                       string_list_insert(&src_for_dst, dst);
+
+               if (bad) {
+                       if (ignore_errors) {
+                               if (--argc > 0) {
+                                       memmove(source + i, source + i + 1,
+                                               (argc - i) * sizeof(char *));
+                                       memmove(destination + i,
+                                               destination + i + 1,
+                                               (argc - i) * sizeof(char *));
+                                       i--;
+                               }
+                       } else
+                               die ("%s, source=%s, destination=%s",
+                                    bad, src, dst);
+               }
+       }
+
+       for (i = 0; i < argc; i++) {
+               const char *src = source[i], *dst = destination[i];
+               enum update_mode mode = modes[i];
+               int pos;
+               if (show_only || verbose)
+                       printf("Renaming %s to %s\n", src, dst);
+               if (!show_only && mode != INDEX &&
+                               rename(src, dst) < 0 && !ignore_errors)
+                       die_errno ("renaming '%s' failed", src);
+
+               if (mode == WORKING_DIRECTORY)
+                       continue;
+
+               pos = cache_name_pos(src, strlen(src));
+               assert(pos >= 0);
+               if (!show_only)
+                       rename_cache_entry_at(pos, dst);
+       }
+
+       if (active_cache_changed) {
+               if (write_cache(newfd, active_cache, active_nr) ||
+                   commit_locked_index(&lock_file))
+                       die("Unable to write new index file");
+       }
+
+       return 0;
+}
diff --git a/builtin/name-rev.c b/builtin/name-rev.c
new file mode 100644 (file)
index 0000000..31f5c1c
--- /dev/null
@@ -0,0 +1,305 @@
+#include "builtin.h"
+#include "cache.h"
+#include "commit.h"
+#include "tag.h"
+#include "refs.h"
+#include "parse-options.h"
+
+#define CUTOFF_DATE_SLOP 86400 /* one day */
+
+typedef struct rev_name {
+       const char *tip_name;
+       int generation;
+       int distance;
+} rev_name;
+
+static long cutoff = LONG_MAX;
+
+/* How many generations are maximally preferred over _one_ merge traversal? */
+#define MERGE_TRAVERSAL_WEIGHT 65535
+
+static void name_rev(struct commit *commit,
+               const char *tip_name, int generation, int distance,
+               int deref)
+{
+       struct rev_name *name = (struct rev_name *)commit->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->util = name;
+               goto copy_data;
+       } else if (name->distance > distance) {
+copy_data:
+               name->tip_name = tip_name;
+               name->generation = generation;
+               name->distance = distance;
+       } else
+               return;
+
+       for (parents = commit->parents;
+                       parents;
+                       parents = parents->next, parent_number++) {
+               if (parent_number > 1) {
+                       int len = strlen(tip_name);
+                       char *new_name = xmalloc(len +
+                               1 + decimal_length(generation) +  /* ~<n> */
+                               1 + 2 +                           /* ^NN */
+                               1);
+
+                       if (len > 2 && !strcmp(tip_name + len - 2, "^0"))
+                               len -= 2;
+                       if (generation > 0)
+                               sprintf(new_name, "%.*s~%d^%d", len, tip_name,
+                                               generation, parent_number);
+                       else
+                               sprintf(new_name, "%.*s^%d", len, tip_name,
+                                               parent_number);
+
+                       name_rev(parents->item, new_name, 0,
+                               distance + MERGE_TRAVERSAL_WEIGHT, 0);
+               } else {
+                       name_rev(parents->item, tip_name, generation + 1,
+                               distance + 1, 0);
+               }
+       }
+}
+
+struct name_ref_data {
+       int tags_only;
+       int name_only;
+       const char *ref_filter;
+};
+
+static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data)
+{
+       struct object *o = parse_object(sha1);
+       struct name_ref_data *data = cb_data;
+       int deref = 0;
+
+       if (data->tags_only && prefixcmp(path, "refs/tags/"))
+               return 0;
+
+       if (data->ref_filter && fnmatch(data->ref_filter, path, 0))
+               return 0;
+
+       while (o && o->type == OBJ_TAG) {
+               struct tag *t = (struct tag *) o;
+               if (!t->tagged)
+                       break; /* broken repository */
+               o = parse_object(t->tagged->sha1);
+               deref = 1;
+       }
+       if (o && o->type == OBJ_COMMIT) {
+               struct commit *commit = (struct commit *)o;
+
+               if (!prefixcmp(path, "refs/heads/"))
+                       path = path + 11;
+               else if (data->tags_only
+                   && data->name_only
+                   && !prefixcmp(path, "refs/tags/"))
+                       path = path + 10;
+               else if (!prefixcmp(path, "refs/"))
+                       path = path + 5;
+
+               name_rev(commit, xstrdup(path), 0, 0, deref);
+       }
+       return 0;
+}
+
+/* returns a static buffer */
+static const char *get_rev_name(const struct object *o)
+{
+       static char buffer[1024];
+       struct rev_name *n;
+       struct commit *c;
+
+       if (o->type != OBJ_COMMIT)
+               return NULL;
+       c = (struct commit *) o;
+       n = c->util;
+       if (!n)
+               return NULL;
+
+       if (!n->generation)
+               return n->tip_name;
+       else {
+               int len = strlen(n->tip_name);
+               if (len > 2 && !strcmp(n->tip_name + len - 2, "^0"))
+                       len -= 2;
+               snprintf(buffer, sizeof(buffer), "%.*s~%d", len, n->tip_name,
+                               n->generation);
+
+               return buffer;
+       }
+}
+
+static void show_name(const struct object *obj,
+                     const char *caller_name,
+                     int always, int allow_undefined, int name_only)
+{
+       const char *name;
+       const unsigned char *sha1 = obj->sha1;
+
+       if (!name_only)
+               printf("%s ", caller_name ? caller_name : sha1_to_hex(sha1));
+       name = get_rev_name(obj);
+       if (name)
+               printf("%s\n", name);
+       else if (allow_undefined)
+               printf("undefined\n");
+       else if (always)
+               printf("%s\n", find_unique_abbrev(sha1, DEFAULT_ABBREV));
+       else
+               die("cannot describe '%s'", sha1_to_hex(sha1));
+}
+
+static char const * const name_rev_usage[] = {
+       "git name-rev [options] ( --all | --stdin | <commit>... )",
+       NULL
+};
+
+static void name_rev_line(char *p, struct name_ref_data *data)
+{
+       int forty = 0;
+       char *p_start;
+       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 = NULL;
+                       char c = *(p+1);
+                       int p_len = p - p_start + 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 (!name)
+                               continue;
+
+                       if (data->name_only)
+                               printf("%.*s%s", p_len - 40, p_start, name);
+                       else
+                               printf("%.*s (%s)", p_len, p_start, name);
+                       p_start = p + 1;
+               }
+       }
+
+       /* flush */
+       if (p_start != p)
+               fwrite(p_start, p - p_start, 1, stdout);
+}
+
+int cmd_name_rev(int argc, const char **argv, const char *prefix)
+{
+       struct object_array revs = OBJECT_ARRAY_INIT;
+       int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0;
+       struct name_ref_data data = { 0, 0, NULL };
+       struct option opts[] = {
+               OPT_BOOLEAN(0, "name-only", &data.name_only, "print only names (no SHA-1)"),
+               OPT_BOOLEAN(0, "tags", &data.tags_only, "only use tags to name the commits"),
+               OPT_STRING(0, "refs", &data.ref_filter, "pattern",
+                                  "only use refs matching <pattern>"),
+               OPT_GROUP(""),
+               OPT_BOOLEAN(0, "all", &all, "list all commits reachable from all refs"),
+               OPT_BOOLEAN(0, "stdin", &transform_stdin, "read from stdin"),
+               OPT_BOOLEAN(0, "undefined", &allow_undefined, "allow to print `undefined` names"),
+               OPT_BOOLEAN(0, "always",     &always,
+                          "show abbreviated commit object as fallback"),
+               OPT_END(),
+       };
+
+       git_config(git_default_config, NULL);
+       argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0);
+       if (!!all + !!transform_stdin + !!argc > 1) {
+               error("Specify either a list, or --all, not both!");
+               usage_with_options(name_rev_usage, opts);
+       }
+       if (all || transform_stdin)
+               cutoff = 0;
+
+       for (; argc; argc--, argv++) {
+               unsigned char sha1[20];
+               struct object *o;
+               struct commit *commit;
+
+               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 != OBJ_COMMIT) {
+                       fprintf(stderr, "Could not get commit for %s. Skipping.\n",
+                                       *argv);
+                       continue;
+               }
+
+               commit = (struct commit *)o;
+               if (cutoff > commit->date)
+                       cutoff = commit->date;
+               add_object_array((struct object *)commit, *argv, &revs);
+       }
+
+       if (cutoff)
+               cutoff = cutoff - CUTOFF_DATE_SLOP;
+       for_each_ref(name_ref, &data);
+
+       if (transform_stdin) {
+               char buffer[2048];
+
+               while (!feof(stdin)) {
+                       char *p = fgets(buffer, sizeof(buffer), stdin);
+                       if (!p)
+                               break;
+                       name_rev_line(p, &data);
+               }
+       } else if (all) {
+               int i, max;
+
+               max = get_max_object_index();
+               for (i = 0; i < max; i++) {
+                       struct object *obj = get_indexed_object(i);
+                       if (!obj)
+                               continue;
+                       show_name(obj, NULL,
+                                 always, allow_undefined, data.name_only);
+               }
+       } else {
+               int i;
+               for (i = 0; i < revs.nr; i++)
+                       show_name(revs.objects[i].item, revs.objects[i].name,
+                                 always, allow_undefined, data.name_only);
+       }
+
+       return 0;
+}
diff --git a/builtin/notes.c b/builtin/notes.c
new file mode 100644 (file)
index 0000000..6d07aac
--- /dev/null
@@ -0,0 +1,878 @@
+/*
+ * Builtin "git notes"
+ *
+ * Copyright (c) 2010 Johan Herland <johan@herland.net>
+ *
+ * Based on git-notes.sh by Johannes Schindelin,
+ * and builtin-tag.c by Kristian Høgsberg and Carlos Rica.
+ */
+
+#include "cache.h"
+#include "builtin.h"
+#include "notes.h"
+#include "blob.h"
+#include "commit.h"
+#include "refs.h"
+#include "exec_cmd.h"
+#include "run-command.h"
+#include "parse-options.h"
+#include "string-list.h"
+
+static const char * const git_notes_usage[] = {
+       "git notes [--ref <notes_ref>] [list [<object>]]",
+       "git notes [--ref <notes_ref>] add [-f] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
+       "git notes [--ref <notes_ref>] copy [-f] <from-object> <to-object>",
+       "git notes [--ref <notes_ref>] append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
+       "git notes [--ref <notes_ref>] edit [<object>]",
+       "git notes [--ref <notes_ref>] show [<object>]",
+       "git notes [--ref <notes_ref>] remove [<object>]",
+       "git notes [--ref <notes_ref>] prune [-n | -v]",
+       NULL
+};
+
+static const char * const git_notes_list_usage[] = {
+       "git notes [list [<object>]]",
+       NULL
+};
+
+static const char * const git_notes_add_usage[] = {
+       "git notes add [<options>] [<object>]",
+       NULL
+};
+
+static const char * const git_notes_copy_usage[] = {
+       "git notes copy [<options>] <from-object> <to-object>",
+       "git notes copy --stdin [<from-object> <to-object>]...",
+       NULL
+};
+
+static const char * const git_notes_append_usage[] = {
+       "git notes append [<options>] [<object>]",
+       NULL
+};
+
+static const char * const git_notes_edit_usage[] = {
+       "git notes edit [<object>]",
+       NULL
+};
+
+static const char * const git_notes_show_usage[] = {
+       "git notes show [<object>]",
+       NULL
+};
+
+static const char * const git_notes_remove_usage[] = {
+       "git notes remove [<object>]",
+       NULL
+};
+
+static const char * const git_notes_prune_usage[] = {
+       "git notes prune [<options>]",
+       NULL
+};
+
+static const char note_template[] =
+       "\n"
+       "#\n"
+       "# Write/edit the notes for the following object:\n"
+       "#\n";
+
+struct msg_arg {
+       int given;
+       int use_editor;
+       struct strbuf buf;
+};
+
+static int list_each_note(const unsigned char *object_sha1,
+               const unsigned char *note_sha1, char *note_path,
+               void *cb_data)
+{
+       printf("%s %s\n", sha1_to_hex(note_sha1), sha1_to_hex(object_sha1));
+       return 0;
+}
+
+static void write_note_data(int fd, const unsigned char *sha1)
+{
+       unsigned long size;
+       enum object_type type;
+       char *buf = read_sha1_file(sha1, &type, &size);
+       if (buf) {
+               if (size)
+                       write_or_die(fd, buf, size);
+               free(buf);
+       }
+}
+
+static void write_commented_object(int fd, const unsigned char *object)
+{
+       const char *show_args[5] =
+               {"show", "--stat", "--no-notes", sha1_to_hex(object), NULL};
+       struct child_process show;
+       struct strbuf buf = STRBUF_INIT;
+       FILE *show_out;
+
+       /* Invoke "git show --stat --no-notes $object" */
+       memset(&show, 0, sizeof(show));
+       show.argv = show_args;
+       show.no_stdin = 1;
+       show.out = -1;
+       show.err = 0;
+       show.git_cmd = 1;
+       if (start_command(&show))
+               die("unable to start 'show' for object '%s'",
+                   sha1_to_hex(object));
+
+       /* Open the output as FILE* so strbuf_getline() can be used. */
+       show_out = xfdopen(show.out, "r");
+       if (show_out == NULL)
+               die_errno("can't fdopen 'show' output fd");
+
+       /* Prepend "# " to each output line and write result to 'fd' */
+       while (strbuf_getline(&buf, show_out, '\n') != EOF) {
+               write_or_die(fd, "# ", 2);
+               write_or_die(fd, buf.buf, buf.len);
+               write_or_die(fd, "\n", 1);
+       }
+       strbuf_release(&buf);
+       if (fclose(show_out))
+               die_errno("failed to close pipe to 'show' for object '%s'",
+                         sha1_to_hex(object));
+       if (finish_command(&show))
+               die("failed to finish 'show' for object '%s'",
+                   sha1_to_hex(object));
+}
+
+static void create_note(const unsigned char *object, struct msg_arg *msg,
+                       int append_only, const unsigned char *prev,
+                       unsigned char *result)
+{
+       char *path = NULL;
+
+       if (msg->use_editor || !msg->given) {
+               int fd;
+
+               /* write the template message before editing: */
+               path = git_pathdup("NOTES_EDITMSG");
+               fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
+               if (fd < 0)
+                       die_errno("could not create file '%s'", path);
+
+               if (msg->given)
+                       write_or_die(fd, msg->buf.buf, msg->buf.len);
+               else if (prev && !append_only)
+                       write_note_data(fd, prev);
+               write_or_die(fd, note_template, strlen(note_template));
+
+               write_commented_object(fd, object);
+
+               close(fd);
+               strbuf_reset(&(msg->buf));
+
+               if (launch_editor(path, &(msg->buf), NULL)) {
+                       die("Please supply the note contents using either -m" \
+                           " or -F option");
+               }
+               stripspace(&(msg->buf), 1);
+       }
+
+       if (prev && append_only) {
+               /* Append buf to previous note contents */
+               unsigned long size;
+               enum object_type type;
+               char *prev_buf = read_sha1_file(prev, &type, &size);
+
+               strbuf_grow(&(msg->buf), size + 1);
+               if (msg->buf.len && prev_buf && size)
+                       strbuf_insert(&(msg->buf), 0, "\n", 1);
+               if (prev_buf && size)
+                       strbuf_insert(&(msg->buf), 0, prev_buf, size);
+               free(prev_buf);
+       }
+
+       if (!msg->buf.len) {
+               fprintf(stderr, "Removing note for object %s\n",
+                       sha1_to_hex(object));
+               hashclr(result);
+       } else {
+               if (write_sha1_file(msg->buf.buf, msg->buf.len, blob_type, result)) {
+                       error("unable to write note object");
+                       if (path)
+                               error("The note contents has been left in %s",
+                                     path);
+                       exit(128);
+               }
+       }
+
+       if (path) {
+               unlink_or_warn(path);
+               free(path);
+       }
+}
+
+static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
+{
+       struct msg_arg *msg = opt->value;
+
+       strbuf_grow(&(msg->buf), strlen(arg) + 2);
+       if (msg->buf.len)
+               strbuf_addch(&(msg->buf), '\n');
+       strbuf_addstr(&(msg->buf), arg);
+       stripspace(&(msg->buf), 0);
+
+       msg->given = 1;
+       return 0;
+}
+
+static int parse_file_arg(const struct option *opt, const char *arg, int unset)
+{
+       struct msg_arg *msg = opt->value;
+
+       if (msg->buf.len)
+               strbuf_addch(&(msg->buf), '\n');
+       if (!strcmp(arg, "-")) {
+               if (strbuf_read(&(msg->buf), 0, 1024) < 0)
+                       die_errno("cannot read '%s'", arg);
+       } else if (strbuf_read_file(&(msg->buf), arg, 1024) < 0)
+               die_errno("could not open or read '%s'", arg);
+       stripspace(&(msg->buf), 0);
+
+       msg->given = 1;
+       return 0;
+}
+
+static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)
+{
+       struct msg_arg *msg = opt->value;
+       char *buf;
+       unsigned char object[20];
+       enum object_type type;
+       unsigned long len;
+
+       if (msg->buf.len)
+               strbuf_addch(&(msg->buf), '\n');
+
+       if (get_sha1(arg, object))
+               die("Failed to resolve '%s' as a valid ref.", arg);
+       if (!(buf = read_sha1_file(object, &type, &len)) || !len) {
+               free(buf);
+               die("Failed to read object '%s'.", arg);;
+       }
+       strbuf_add(&(msg->buf), buf, len);
+       free(buf);
+
+       msg->given = 1;
+       return 0;
+}
+
+static int parse_reedit_arg(const struct option *opt, const char *arg, int unset)
+{
+       struct msg_arg *msg = opt->value;
+       msg->use_editor = 1;
+       return parse_reuse_arg(opt, arg, unset);
+}
+
+int commit_notes(struct notes_tree *t, const char *msg)
+{
+       struct commit_list *parent;
+       unsigned char tree_sha1[20], prev_commit[20], new_commit[20];
+       struct strbuf buf = STRBUF_INIT;
+
+       if (!t)
+               t = &default_notes_tree;
+       if (!t->initialized || !t->ref || !*t->ref)
+               die("Cannot commit uninitialized/unreferenced notes tree");
+       if (!t->dirty)
+               return 0; /* don't have to commit an unchanged tree */
+
+       /* Prepare commit message and reflog message */
+       strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */
+       strbuf_addstr(&buf, msg);
+       if (buf.buf[buf.len - 1] != '\n')
+               strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */
+
+       /* Convert notes tree to tree object */
+       if (write_notes_tree(t, tree_sha1))
+               die("Failed to write current notes tree to database");
+
+       /* Create new commit for the tree object */
+       if (!read_ref(t->ref, prev_commit)) { /* retrieve parent commit */
+               parent = xmalloc(sizeof(*parent));
+               parent->item = lookup_commit(prev_commit);
+               parent->next = NULL;
+       } else {
+               hashclr(prev_commit);
+               parent = NULL;
+       }
+       if (commit_tree(buf.buf + 7, tree_sha1, parent, new_commit, NULL))
+               die("Failed to commit notes tree to database");
+
+       /* Update notes ref with new commit */
+       update_ref(buf.buf, t->ref, new_commit, prev_commit, 0, DIE_ON_ERR);
+
+       strbuf_release(&buf);
+       return 0;
+}
+
+combine_notes_fn parse_combine_notes_fn(const char *v)
+{
+       if (!strcasecmp(v, "overwrite"))
+               return combine_notes_overwrite;
+       else if (!strcasecmp(v, "ignore"))
+               return combine_notes_ignore;
+       else if (!strcasecmp(v, "concatenate"))
+               return combine_notes_concatenate;
+       else
+               return NULL;
+}
+
+static int notes_rewrite_config(const char *k, const char *v, void *cb)
+{
+       struct notes_rewrite_cfg *c = cb;
+       if (!prefixcmp(k, "notes.rewrite.") && !strcmp(k+14, c->cmd)) {
+               c->enabled = git_config_bool(k, v);
+               return 0;
+       } else if (!c->mode_from_env && !strcmp(k, "notes.rewritemode")) {
+               if (!v)
+                       config_error_nonbool(k);
+               c->combine = parse_combine_notes_fn(v);
+               if (!c->combine) {
+                       error("Bad notes.rewriteMode value: '%s'", v);
+                       return 1;
+               }
+               return 0;
+       } else if (!c->refs_from_env && !strcmp(k, "notes.rewriteref")) {
+               /* note that a refs/ prefix is implied in the
+                * underlying for_each_glob_ref */
+               if (!prefixcmp(v, "refs/notes/"))
+                       string_list_add_refs_by_glob(c->refs, v);
+               else
+                       warning("Refusing to rewrite notes in %s"
+                               " (outside of refs/notes/)", v);
+               return 0;
+       }
+
+       return 0;
+}
+
+
+struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd)
+{
+       struct notes_rewrite_cfg *c = xmalloc(sizeof(struct notes_rewrite_cfg));
+       const char *rewrite_mode_env = getenv(GIT_NOTES_REWRITE_MODE_ENVIRONMENT);
+       const char *rewrite_refs_env = getenv(GIT_NOTES_REWRITE_REF_ENVIRONMENT);
+       c->cmd = cmd;
+       c->enabled = 1;
+       c->combine = combine_notes_concatenate;
+       c->refs = xcalloc(1, sizeof(struct string_list));
+       c->refs->strdup_strings = 1;
+       c->refs_from_env = 0;
+       c->mode_from_env = 0;
+       if (rewrite_mode_env) {
+               c->mode_from_env = 1;
+               c->combine = parse_combine_notes_fn(rewrite_mode_env);
+               if (!c->combine)
+                       error("Bad " GIT_NOTES_REWRITE_MODE_ENVIRONMENT
+                             " value: '%s'", rewrite_mode_env);
+       }
+       if (rewrite_refs_env) {
+               c->refs_from_env = 1;
+               string_list_add_refs_from_colon_sep(c->refs, rewrite_refs_env);
+       }
+       git_config(notes_rewrite_config, c);
+       if (!c->enabled || !c->refs->nr) {
+               string_list_clear(c->refs, 0);
+               free(c->refs);
+               free(c);
+               return NULL;
+       }
+       c->trees = load_notes_trees(c->refs);
+       string_list_clear(c->refs, 0);
+       free(c->refs);
+       return c;
+}
+
+int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
+                         const unsigned char *from_obj, const unsigned char *to_obj)
+{
+       int ret = 0;
+       int i;
+       for (i = 0; c->trees[i]; i++)
+               ret = copy_note(c->trees[i], from_obj, to_obj, 1, c->combine) || ret;
+       return ret;
+}
+
+void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c)
+{
+       int i;
+       for (i = 0; c->trees[i]; i++) {
+               commit_notes(c->trees[i], "Notes added by 'git notes copy'");
+               free_notes(c->trees[i]);
+       }
+       free(c->trees);
+       free(c);
+}
+
+int notes_copy_from_stdin(int force, const char *rewrite_cmd)
+{
+       struct strbuf buf = STRBUF_INIT;
+       struct notes_rewrite_cfg *c = NULL;
+       struct notes_tree *t = NULL;
+       int ret = 0;
+
+       if (rewrite_cmd) {
+               c = init_copy_notes_for_rewrite(rewrite_cmd);
+               if (!c)
+                       return 0;
+       } else {
+               init_notes(NULL, NULL, NULL, 0);
+               t = &default_notes_tree;
+       }
+
+       while (strbuf_getline(&buf, stdin, '\n') != EOF) {
+               unsigned char from_obj[20], to_obj[20];
+               struct strbuf **split;
+               int err;
+
+               split = strbuf_split(&buf, ' ');
+               if (!split[0] || !split[1])
+                       die("Malformed input line: '%s'.", buf.buf);
+               strbuf_rtrim(split[0]);
+               strbuf_rtrim(split[1]);
+               if (get_sha1(split[0]->buf, from_obj))
+                       die("Failed to resolve '%s' as a valid ref.", split[0]->buf);
+               if (get_sha1(split[1]->buf, to_obj))
+                       die("Failed to resolve '%s' as a valid ref.", split[1]->buf);
+
+               if (rewrite_cmd)
+                       err = copy_note_for_rewrite(c, from_obj, to_obj);
+               else
+                       err = copy_note(t, from_obj, to_obj, force,
+                                       combine_notes_overwrite);
+
+               if (err) {
+                       error("Failed to copy notes from '%s' to '%s'",
+                             split[0]->buf, split[1]->buf);
+                       ret = 1;
+               }
+
+               strbuf_list_free(split);
+       }
+
+       if (!rewrite_cmd) {
+               commit_notes(t, "Notes added by 'git notes copy'");
+               free_notes(t);
+       } else {
+               finish_copy_notes_for_rewrite(c);
+       }
+       return ret;
+}
+
+static struct notes_tree *init_notes_check(const char *subcommand)
+{
+       struct notes_tree *t;
+       init_notes(NULL, NULL, NULL, 0);
+       t = &default_notes_tree;
+
+       if (prefixcmp(t->ref, "refs/notes/"))
+               die("Refusing to %s notes in %s (outside of refs/notes/)",
+                   subcommand, t->ref);
+       return t;
+}
+
+static int list(int argc, const char **argv, const char *prefix)
+{
+       struct notes_tree *t;
+       unsigned char object[20];
+       const unsigned char *note;
+       int retval = -1;
+       struct option options[] = {
+               OPT_END()
+       };
+
+       if (argc)
+               argc = parse_options(argc, argv, prefix, options,
+                                    git_notes_list_usage, 0);
+
+       if (1 < argc) {
+               error("too many parameters");
+               usage_with_options(git_notes_list_usage, options);
+       }
+
+       t = init_notes_check("list");
+       if (argc) {
+               if (get_sha1(argv[0], object))
+                       die("Failed to resolve '%s' as a valid ref.", argv[0]);
+               note = get_note(t, object);
+               if (note) {
+                       puts(sha1_to_hex(note));
+                       retval = 0;
+               } else
+                       retval = error("No note found for object %s.",
+                                      sha1_to_hex(object));
+       } else
+               retval = for_each_note(t, 0, list_each_note, NULL);
+
+       free_notes(t);
+       return retval;
+}
+
+static int add(int argc, const char **argv, const char *prefix)
+{
+       int retval = 0, force = 0;
+       const char *object_ref;
+       struct notes_tree *t;
+       unsigned char object[20], new_note[20];
+       char logmsg[100];
+       const unsigned char *note;
+       struct msg_arg msg = { 0, 0, STRBUF_INIT };
+       struct option options[] = {
+               { OPTION_CALLBACK, 'm', "message", &msg, "MSG",
+                       "note contents as a string", PARSE_OPT_NONEG,
+                       parse_msg_arg},
+               { OPTION_CALLBACK, 'F', "file", &msg, "FILE",
+                       "note contents in a file", PARSE_OPT_NONEG,
+                       parse_file_arg},
+               { OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT",
+                       "reuse and edit specified note object", PARSE_OPT_NONEG,
+                       parse_reedit_arg},
+               { OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT",
+                       "reuse specified note object", PARSE_OPT_NONEG,
+                       parse_reuse_arg},
+               OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, prefix, options, git_notes_add_usage,
+                            0);
+
+       if (1 < argc) {
+               error("too many parameters");
+               usage_with_options(git_notes_add_usage, options);
+       }
+
+       object_ref = argc ? argv[0] : "HEAD";
+
+       if (get_sha1(object_ref, object))
+               die("Failed to resolve '%s' as a valid ref.", object_ref);
+
+       t = init_notes_check("add");
+       note = get_note(t, object);
+
+       if (note) {
+               if (!force) {
+                       retval = error("Cannot add notes. Found existing notes "
+                                      "for object %s. Use '-f' to overwrite "
+                                      "existing notes", sha1_to_hex(object));
+                       goto out;
+               }
+               fprintf(stderr, "Overwriting existing notes for object %s\n",
+                       sha1_to_hex(object));
+       }
+
+       create_note(object, &msg, 0, note, new_note);
+
+       if (is_null_sha1(new_note))
+               remove_note(t, object);
+       else
+               add_note(t, object, new_note, combine_notes_overwrite);
+
+       snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
+                is_null_sha1(new_note) ? "removed" : "added", "add");
+       commit_notes(t, logmsg);
+out:
+       free_notes(t);
+       strbuf_release(&(msg.buf));
+       return retval;
+}
+
+static int copy(int argc, const char **argv, const char *prefix)
+{
+       int retval = 0, force = 0, from_stdin = 0;
+       const unsigned char *from_note, *note;
+       const char *object_ref;
+       unsigned char object[20], from_obj[20];
+       struct notes_tree *t;
+       const char *rewrite_cmd = NULL;
+       struct option options[] = {
+               OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
+               OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
+               OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
+                          "load rewriting config for <command> (implies "
+                          "--stdin)"),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, prefix, options, git_notes_copy_usage,
+                            0);
+
+       if (from_stdin || rewrite_cmd) {
+               if (argc) {
+                       error("too many parameters");
+                       usage_with_options(git_notes_copy_usage, options);
+               } else {
+                       return notes_copy_from_stdin(force, rewrite_cmd);
+               }
+       }
+
+       if (argc < 2) {
+               error("too few parameters");
+               usage_with_options(git_notes_copy_usage, options);
+       }
+       if (2 < argc) {
+               error("too many parameters");
+               usage_with_options(git_notes_copy_usage, options);
+       }
+
+       if (get_sha1(argv[0], from_obj))
+               die("Failed to resolve '%s' as a valid ref.", argv[0]);
+
+       object_ref = 1 < argc ? argv[1] : "HEAD";
+
+       if (get_sha1(object_ref, object))
+               die("Failed to resolve '%s' as a valid ref.", object_ref);
+
+       t = init_notes_check("copy");
+       note = get_note(t, object);
+
+       if (note) {
+               if (!force) {
+                       retval = error("Cannot copy notes. Found existing "
+                                      "notes for object %s. Use '-f' to "
+                                      "overwrite existing notes",
+                                      sha1_to_hex(object));
+                       goto out;
+               }
+               fprintf(stderr, "Overwriting existing notes for object %s\n",
+                       sha1_to_hex(object));
+       }
+
+       from_note = get_note(t, from_obj);
+       if (!from_note) {
+               retval = error("Missing notes on source object %s. Cannot "
+                              "copy.", sha1_to_hex(from_obj));
+               goto out;
+       }
+
+       add_note(t, object, from_note, combine_notes_overwrite);
+       commit_notes(t, "Notes added by 'git notes copy'");
+out:
+       free_notes(t);
+       return retval;
+}
+
+static int append_edit(int argc, const char **argv, const char *prefix)
+{
+       const char *object_ref;
+       struct notes_tree *t;
+       unsigned char object[20], new_note[20];
+       const unsigned char *note;
+       char logmsg[100];
+       const char * const *usage;
+       struct msg_arg msg = { 0, 0, STRBUF_INIT };
+       struct option options[] = {
+               { OPTION_CALLBACK, 'm', "message", &msg, "MSG",
+                       "note contents as a string", PARSE_OPT_NONEG,
+                       parse_msg_arg},
+               { OPTION_CALLBACK, 'F', "file", &msg, "FILE",
+                       "note contents in a file", PARSE_OPT_NONEG,
+                       parse_file_arg},
+               { OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT",
+                       "reuse and edit specified note object", PARSE_OPT_NONEG,
+                       parse_reedit_arg},
+               { OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT",
+                       "reuse specified note object", PARSE_OPT_NONEG,
+                       parse_reuse_arg},
+               OPT_END()
+       };
+       int edit = !strcmp(argv[0], "edit");
+
+       usage = edit ? git_notes_edit_usage : git_notes_append_usage;
+       argc = parse_options(argc, argv, prefix, options, usage,
+                            PARSE_OPT_KEEP_ARGV0);
+
+       if (2 < argc) {
+               error("too many parameters");
+               usage_with_options(usage, options);
+       }
+
+       if (msg.given && edit)
+               fprintf(stderr, "The -m/-F/-c/-C options have been deprecated "
+                       "for the 'edit' subcommand.\n"
+                       "Please use 'git notes add -f -m/-F/-c/-C' instead.\n");
+
+       object_ref = 1 < argc ? argv[1] : "HEAD";
+
+       if (get_sha1(object_ref, object))
+               die("Failed to resolve '%s' as a valid ref.", object_ref);
+
+       t = init_notes_check(argv[0]);
+       note = get_note(t, object);
+
+       create_note(object, &msg, !edit, note, new_note);
+
+       if (is_null_sha1(new_note))
+               remove_note(t, object);
+       else
+               add_note(t, object, new_note, combine_notes_overwrite);
+
+       snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
+                is_null_sha1(new_note) ? "removed" : "added", argv[0]);
+       commit_notes(t, logmsg);
+       free_notes(t);
+       strbuf_release(&(msg.buf));
+       return 0;
+}
+
+static int show(int argc, const char **argv, const char *prefix)
+{
+       const char *object_ref;
+       struct notes_tree *t;
+       unsigned char object[20];
+       const unsigned char *note;
+       int retval;
+       struct option options[] = {
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, prefix, options, git_notes_show_usage,
+                            0);
+
+       if (1 < argc) {
+               error("too many parameters");
+               usage_with_options(git_notes_show_usage, options);
+       }
+
+       object_ref = argc ? argv[0] : "HEAD";
+
+       if (get_sha1(object_ref, object))
+               die("Failed to resolve '%s' as a valid ref.", object_ref);
+
+       t = init_notes_check("show");
+       note = get_note(t, object);
+
+       if (!note)
+               retval = error("No note found for object %s.",
+                              sha1_to_hex(object));
+       else {
+               const char *show_args[3] = {"show", sha1_to_hex(note), NULL};
+               retval = execv_git_cmd(show_args);
+       }
+       free_notes(t);
+       return retval;
+}
+
+static int remove_cmd(int argc, const char **argv, const char *prefix)
+{
+       struct option options[] = {
+               OPT_END()
+       };
+       const char *object_ref;
+       struct notes_tree *t;
+       unsigned char object[20];
+       int retval;
+
+       argc = parse_options(argc, argv, prefix, options,
+                            git_notes_remove_usage, 0);
+
+       if (1 < argc) {
+               error("too many parameters");
+               usage_with_options(git_notes_remove_usage, options);
+       }
+
+       object_ref = argc ? argv[0] : "HEAD";
+
+       if (get_sha1(object_ref, object))
+               die("Failed to resolve '%s' as a valid ref.", object_ref);
+
+       t = init_notes_check("remove");
+
+       retval = remove_note(t, object);
+       if (retval)
+               fprintf(stderr, "Object %s has no note\n", sha1_to_hex(object));
+       else {
+               fprintf(stderr, "Removing note for object %s\n",
+                       sha1_to_hex(object));
+
+               commit_notes(t, "Notes removed by 'git notes remove'");
+       }
+       free_notes(t);
+       return retval;
+}
+
+static int prune(int argc, const char **argv, const char *prefix)
+{
+       struct notes_tree *t;
+       int show_only = 0, verbose = 0;
+       struct option options[] = {
+               OPT_BOOLEAN('n', "dry-run", &show_only,
+                           "do not remove, show only"),
+               OPT_BOOLEAN('v', "verbose", &verbose, "report pruned notes"),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, prefix, options, git_notes_prune_usage,
+                            0);
+
+       if (argc) {
+               error("too many parameters");
+               usage_with_options(git_notes_prune_usage, options);
+       }
+
+       t = init_notes_check("prune");
+
+       prune_notes(t, (verbose ? NOTES_PRUNE_VERBOSE : 0) |
+               (show_only ? NOTES_PRUNE_VERBOSE|NOTES_PRUNE_DRYRUN : 0) );
+       if (!show_only)
+               commit_notes(t, "Notes removed by 'git notes prune'");
+       free_notes(t);
+       return 0;
+}
+
+int cmd_notes(int argc, const char **argv, const char *prefix)
+{
+       int result;
+       const char *override_notes_ref = NULL;
+       struct option options[] = {
+               OPT_STRING(0, "ref", &override_notes_ref, "notes_ref",
+                          "use notes from <notes_ref>"),
+               OPT_END()
+       };
+
+       git_config(git_default_config, NULL);
+       argc = parse_options(argc, argv, prefix, options, git_notes_usage,
+                            PARSE_OPT_STOP_AT_NON_OPTION);
+
+       if (override_notes_ref) {
+               struct strbuf sb = STRBUF_INIT;
+               if (!prefixcmp(override_notes_ref, "refs/notes/"))
+                       /* we're happy */;
+               else if (!prefixcmp(override_notes_ref, "notes/"))
+                       strbuf_addstr(&sb, "refs/");
+               else
+                       strbuf_addstr(&sb, "refs/notes/");
+               strbuf_addstr(&sb, override_notes_ref);
+               setenv("GIT_NOTES_REF", sb.buf, 1);
+               strbuf_release(&sb);
+       }
+
+       if (argc < 1 || !strcmp(argv[0], "list"))
+               result = list(argc, argv, prefix);
+       else if (!strcmp(argv[0], "add"))
+               result = add(argc, argv, prefix);
+       else if (!strcmp(argv[0], "copy"))
+               result = copy(argc, argv, prefix);
+       else if (!strcmp(argv[0], "append") || !strcmp(argv[0], "edit"))
+               result = append_edit(argc, argv, prefix);
+       else if (!strcmp(argv[0], "show"))
+               result = show(argc, argv, prefix);
+       else if (!strcmp(argv[0], "remove"))
+               result = remove_cmd(argc, argv, prefix);
+       else if (!strcmp(argv[0], "prune"))
+               result = prune(argc, argv, prefix);
+       else {
+               result = error("Unknown subcommand: %s", argv[0]);
+               usage_with_options(git_notes_usage, options);
+       }
+
+       return result ? 1 : 0;
+}
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
new file mode 100644 (file)
index 0000000..0e81673
--- /dev/null
@@ -0,0 +1,2345 @@
+#include "builtin.h"
+#include "cache.h"
+#include "attr.h"
+#include "object.h"
+#include "blob.h"
+#include "commit.h"
+#include "tag.h"
+#include "tree.h"
+#include "delta.h"
+#include "pack.h"
+#include "pack-revindex.h"
+#include "csum-file.h"
+#include "tree-walk.h"
+#include "diff.h"
+#include "revision.h"
+#include "list-objects.h"
+#include "progress.h"
+#include "refs.h"
+
+#ifndef NO_PTHREADS
+#include <pthread.h>
+#include "thread-utils.h"
+#endif
+
+static const char pack_usage[] =
+  "git pack-objects [{ -q | --progress | --all-progress }]\n"
+  "        [--all-progress-implied]\n"
+  "        [--max-pack-size=N] [--local] [--incremental]\n"
+  "        [--window=N] [--window-memory=N] [--depth=N]\n"
+  "        [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset]\n"
+  "        [--threads=N] [--non-empty] [--revs [--unpacked | --all]*]\n"
+  "        [--reflog] [--stdout | base-name] [--include-tag]\n"
+  "        [--keep-unreachable | --unpack-unreachable \n"
+  "        [<ref-list | <object-list]";
+
+struct object_entry {
+       struct pack_idx_entry idx;
+       unsigned long size;     /* uncompressed size */
+       struct packed_git *in_pack;     /* already in pack */
+       off_t in_pack_offset;
+       struct object_entry *delta;     /* delta base object */
+       struct object_entry *delta_child; /* deltified objects who bases me */
+       struct object_entry *delta_sibling; /* other deltified objects who
+                                            * uses the same base as me
+                                            */
+       void *delta_data;       /* cached delta (uncompressed) */
+       unsigned long delta_size;       /* delta data size (uncompressed) */
+       unsigned long z_delta_size;     /* delta data size (compressed) */
+       unsigned int hash;      /* name hint hash */
+       enum object_type type;
+       enum object_type in_pack_type;  /* could be delta */
+       unsigned char in_pack_header_size;
+       unsigned char preferred_base; /* we do not pack this, but is available
+                                      * to be used as the base object to delta
+                                      * objects against.
+                                      */
+       unsigned char no_try_delta;
+};
+
+/*
+ * Objects we are going to pack are collected 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.
+ */
+static struct object_entry *objects;
+static struct pack_idx_entry **written_list;
+static uint32_t nr_objects, nr_alloc, nr_result, nr_written;
+
+static int non_empty;
+static int reuse_delta = 1, reuse_object = 1;
+static int keep_unreachable, unpack_unreachable, include_tag;
+static int local;
+static int incremental;
+static int ignore_packed_keep;
+static int allow_ofs_delta;
+static const char *base_name;
+static int progress = 1;
+static int window = 10;
+static unsigned long pack_size_limit, pack_size_limit_cfg;
+static int depth = 50;
+static int delta_search_threads;
+static int pack_to_stdout;
+static int num_preferred_base;
+static struct progress *progress_state;
+static int pack_compression_level = Z_DEFAULT_COMPRESSION;
+static int pack_compression_seen;
+
+static unsigned long delta_cache_size = 0;
+static unsigned long max_delta_cache_size = 256 * 1024 * 1024;
+static unsigned long cache_max_small_delta_size = 1000;
+
+static unsigned long window_memory_limit = 0;
+
+/*
+ * The object names in objects array are hashed with this hashtable,
+ * to help looking up the entry by object name.
+ * This hashtable is built after all the objects are seen.
+ */
+static int *object_ix;
+static int object_ix_hashsz;
+
+/*
+ * stats
+ */
+static uint32_t written, written_delta;
+static uint32_t reused, reused_delta;
+
+
+static void *get_delta(struct object_entry *entry)
+{
+       unsigned long size, base_size, delta_size;
+       void *buf, *base_buf, *delta_buf;
+       enum object_type type;
+
+       buf = read_sha1_file(entry->idx.sha1, &type, &size);
+       if (!buf)
+               die("unable to read %s", sha1_to_hex(entry->idx.sha1));
+       base_buf = read_sha1_file(entry->delta->idx.sha1, &type, &base_size);
+       if (!base_buf)
+               die("unable to read %s", sha1_to_hex(entry->delta->idx.sha1));
+       delta_buf = diff_delta(base_buf, base_size,
+                              buf, size, &delta_size, 0);
+       if (!delta_buf || delta_size != entry->delta_size)
+               die("delta size changed");
+       free(buf);
+       free(base_buf);
+       return delta_buf;
+}
+
+static unsigned long do_compress(void **pptr, unsigned long size)
+{
+       z_stream stream;
+       void *in, *out;
+       unsigned long maxsize;
+
+       memset(&stream, 0, sizeof(stream));
+       deflateInit(&stream, pack_compression_level);
+       maxsize = deflateBound(&stream, size);
+
+       in = *pptr;
+       out = xmalloc(maxsize);
+       *pptr = out;
+
+       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);
+
+       free(in);
+       return stream.total_out;
+}
+
+/*
+ * we are going to reuse the existing object data as is.  make
+ * sure it is not corrupt.
+ */
+static int check_pack_inflate(struct packed_git *p,
+               struct pack_window **w_curs,
+               off_t offset,
+               off_t len,
+               unsigned long expect)
+{
+       z_stream stream;
+       unsigned char fakebuf[4096], *in;
+       int st;
+
+       memset(&stream, 0, sizeof(stream));
+       git_inflate_init(&stream);
+       do {
+               in = use_pack(p, w_curs, offset, &stream.avail_in);
+               stream.next_in = in;
+               stream.next_out = fakebuf;
+               stream.avail_out = sizeof(fakebuf);
+               st = git_inflate(&stream, Z_FINISH);
+               offset += stream.next_in - in;
+       } while (st == Z_OK || st == Z_BUF_ERROR);
+       git_inflate_end(&stream);
+       return (st == Z_STREAM_END &&
+               stream.total_out == expect &&
+               stream.total_in == len) ? 0 : -1;
+}
+
+static void copy_pack_data(struct sha1file *f,
+               struct packed_git *p,
+               struct pack_window **w_curs,
+               off_t offset,
+               off_t len)
+{
+       unsigned char *in;
+       unsigned int avail;
+
+       while (len) {
+               in = use_pack(p, w_curs, offset, &avail);
+               if (avail > len)
+                       avail = (unsigned int)len;
+               sha1write(f, in, avail);
+               offset += avail;
+               len -= avail;
+       }
+}
+
+static unsigned long write_object(struct sha1file *f,
+                                 struct object_entry *entry,
+                                 off_t write_offset)
+{
+       unsigned long size, limit, datalen;
+       void *buf;
+       unsigned char header[10], dheader[10];
+       unsigned hdrlen;
+       enum object_type type;
+       int usable_delta, to_reuse;
+
+       if (!pack_to_stdout)
+               crc32_begin(f);
+
+       type = entry->type;
+
+       /* apply size limit if limited packsize and not first object */
+       if (!pack_size_limit || !nr_written)
+               limit = 0;
+       else if (pack_size_limit <= write_offset)
+               /*
+                * the earlier object did not fit the limit; avoid
+                * mistaking this with unlimited (i.e. limit = 0).
+                */
+               limit = 1;
+       else
+               limit = pack_size_limit - write_offset;
+
+       if (!entry->delta)
+               usable_delta = 0;       /* no delta */
+       else if (!pack_size_limit)
+              usable_delta = 1;        /* unlimited packfile */
+       else if (entry->delta->idx.offset == (off_t)-1)
+               usable_delta = 0;       /* base was written to another pack */
+       else if (entry->delta->idx.offset)
+               usable_delta = 1;       /* base already exists in this pack */
+       else
+               usable_delta = 0;       /* base could end up in another pack */
+
+       if (!reuse_object)
+               to_reuse = 0;   /* explicit */
+       else if (!entry->in_pack)
+               to_reuse = 0;   /* can't reuse what we don't have */
+       else if (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA)
+                               /* check_object() decided it for us ... */
+               to_reuse = usable_delta;
+                               /* ... but pack split may override that */
+       else if (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) {
+               no_reuse:
+               if (!usable_delta) {
+                       buf = read_sha1_file(entry->idx.sha1, &type, &size);
+                       if (!buf)
+                               die("unable to read %s", sha1_to_hex(entry->idx.sha1));
+                       /*
+                        * make sure no cached delta data remains from a
+                        * previous attempt before a pack split occurred.
+                        */
+                       free(entry->delta_data);
+                       entry->delta_data = NULL;
+                       entry->z_delta_size = 0;
+               } else if (entry->delta_data) {
+                       size = entry->delta_size;
+                       buf = entry->delta_data;
+                       entry->delta_data = NULL;
+                       type = (allow_ofs_delta && entry->delta->idx.offset) ?
+                               OBJ_OFS_DELTA : OBJ_REF_DELTA;
+               } else {
+                       buf = get_delta(entry);
+                       size = entry->delta_size;
+                       type = (allow_ofs_delta && entry->delta->idx.offset) ?
+                               OBJ_OFS_DELTA : OBJ_REF_DELTA;
+               }
+
+               if (entry->z_delta_size)
+                       datalen = entry->z_delta_size;
+               else
+                       datalen = do_compress(&buf, size);
+
+               /*
+                * The object header is a byte of 'type' followed by zero or
+                * more bytes of length.
+                */
+               hdrlen = encode_in_pack_object_header(type, size, header);
+
+               if (type == OBJ_OFS_DELTA) {
+                       /*
+                        * Deltas with relative base contain an additional
+                        * encoding of the relative offset for the delta
+                        * base from this object's position in the pack.
+                        */
+                       off_t ofs = entry->idx.offset - entry->delta->idx.offset;
+                       unsigned pos = sizeof(dheader) - 1;
+                       dheader[pos] = ofs & 127;
+                       while (ofs >>= 7)
+                               dheader[--pos] = 128 | (--ofs & 127);
+                       if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) {
+                               free(buf);
+                               return 0;
+                       }
+                       sha1write(f, header, hdrlen);
+                       sha1write(f, dheader + pos, sizeof(dheader) - pos);
+                       hdrlen += sizeof(dheader) - pos;
+               } else if (type == OBJ_REF_DELTA) {
+                       /*
+                        * Deltas with a base reference contain
+                        * an additional 20 bytes for the base sha1.
+                        */
+                       if (limit && hdrlen + 20 + datalen + 20 >= limit) {
+                               free(buf);
+                               return 0;
+                       }
+                       sha1write(f, header, hdrlen);
+                       sha1write(f, entry->delta->idx.sha1, 20);
+                       hdrlen += 20;
+               } else {
+                       if (limit && hdrlen + datalen + 20 >= limit) {
+                               free(buf);
+                               return 0;
+                       }
+                       sha1write(f, header, hdrlen);
+               }
+               sha1write(f, buf, datalen);
+               free(buf);
+       }
+       else {
+               struct packed_git *p = entry->in_pack;
+               struct pack_window *w_curs = NULL;
+               struct revindex_entry *revidx;
+               off_t offset;
+
+               if (entry->delta)
+                       type = (allow_ofs_delta && entry->delta->idx.offset) ?
+                               OBJ_OFS_DELTA : OBJ_REF_DELTA;
+               hdrlen = encode_in_pack_object_header(type, entry->size, header);
+
+               offset = entry->in_pack_offset;
+               revidx = find_pack_revindex(p, offset);
+               datalen = revidx[1].offset - offset;
+               if (!pack_to_stdout && p->index_version > 1 &&
+                   check_pack_crc(p, &w_curs, offset, datalen, revidx->nr)) {
+                       error("bad packed object CRC for %s", sha1_to_hex(entry->idx.sha1));
+                       unuse_pack(&w_curs);
+                       goto no_reuse;
+               }
+
+               offset += entry->in_pack_header_size;
+               datalen -= entry->in_pack_header_size;
+               if (!pack_to_stdout && p->index_version == 1 &&
+                   check_pack_inflate(p, &w_curs, offset, datalen, entry->size)) {
+                       error("corrupt packed object for %s", sha1_to_hex(entry->idx.sha1));
+                       unuse_pack(&w_curs);
+                       goto no_reuse;
+               }
+
+               if (type == OBJ_OFS_DELTA) {
+                       off_t ofs = entry->idx.offset - entry->delta->idx.offset;
+                       unsigned pos = sizeof(dheader) - 1;
+                       dheader[pos] = ofs & 127;
+                       while (ofs >>= 7)
+                               dheader[--pos] = 128 | (--ofs & 127);
+                       if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) {
+                               unuse_pack(&w_curs);
+                               return 0;
+                       }
+                       sha1write(f, header, hdrlen);
+                       sha1write(f, dheader + pos, sizeof(dheader) - pos);
+                       hdrlen += sizeof(dheader) - pos;
+                       reused_delta++;
+               } else if (type == OBJ_REF_DELTA) {
+                       if (limit && hdrlen + 20 + datalen + 20 >= limit) {
+                               unuse_pack(&w_curs);
+                               return 0;
+                       }
+                       sha1write(f, header, hdrlen);
+                       sha1write(f, entry->delta->idx.sha1, 20);
+                       hdrlen += 20;
+                       reused_delta++;
+               } else {
+                       if (limit && hdrlen + datalen + 20 >= limit) {
+                               unuse_pack(&w_curs);
+                               return 0;
+                       }
+                       sha1write(f, header, hdrlen);
+               }
+               copy_pack_data(f, p, &w_curs, offset, datalen);
+               unuse_pack(&w_curs);
+               reused++;
+       }
+       if (usable_delta)
+               written_delta++;
+       written++;
+       if (!pack_to_stdout)
+               entry->idx.crc32 = crc32_end(f);
+       return hdrlen + datalen;
+}
+
+static int write_one(struct sha1file *f,
+                              struct object_entry *e,
+                              off_t *offset)
+{
+       unsigned long size;
+
+       /* offset is non zero if object is written already. */
+       if (e->idx.offset || e->preferred_base)
+               return -1;
+
+       /* if we are deltified, write out base object first. */
+       if (e->delta && !write_one(f, e->delta, offset))
+               return 0;
+
+       e->idx.offset = *offset;
+       size = write_object(f, e, *offset);
+       if (!size) {
+               e->idx.offset = 0;
+               return 0;
+       }
+       written_list[nr_written++] = &e->idx;
+
+       /* make sure off_t is sufficiently large not to wrap */
+       if (*offset > *offset + size)
+               die("pack too large for current definition of off_t");
+       *offset += size;
+       return 1;
+}
+
+static void write_pack_file(void)
+{
+       uint32_t i = 0, j;
+       struct sha1file *f;
+       off_t offset;
+       struct pack_header hdr;
+       uint32_t nr_remaining = nr_result;
+       time_t last_mtime = 0;
+
+       if (progress > pack_to_stdout)
+               progress_state = start_progress("Writing objects", nr_result);
+       written_list = xmalloc(nr_objects * sizeof(*written_list));
+
+       do {
+               unsigned char sha1[20];
+               char *pack_tmp_name = NULL;
+
+               if (pack_to_stdout) {
+                       f = sha1fd_throughput(1, "<stdout>", progress_state);
+               } else {
+                       char tmpname[PATH_MAX];
+                       int fd;
+                       fd = odb_mkstemp(tmpname, sizeof(tmpname),
+                                        "pack/tmp_pack_XXXXXX");
+                       pack_tmp_name = xstrdup(tmpname);
+                       f = sha1fd(fd, pack_tmp_name);
+               }
+
+               hdr.hdr_signature = htonl(PACK_SIGNATURE);
+               hdr.hdr_version = htonl(PACK_VERSION);
+               hdr.hdr_entries = htonl(nr_remaining);
+               sha1write(f, &hdr, sizeof(hdr));
+               offset = sizeof(hdr);
+               nr_written = 0;
+               for (; i < nr_objects; i++) {
+                       if (!write_one(f, objects + i, &offset))
+                               break;
+                       display_progress(progress_state, written);
+               }
+
+               /*
+                * Did we write the wrong # entries in the header?
+                * If so, rewrite it like in fast-import
+                */
+               if (pack_to_stdout) {
+                       sha1close(f, sha1, CSUM_CLOSE);
+               } else if (nr_written == nr_remaining) {
+                       sha1close(f, sha1, CSUM_FSYNC);
+               } else {
+                       int fd = sha1close(f, sha1, 0);
+                       fixup_pack_header_footer(fd, sha1, pack_tmp_name,
+                                                nr_written, sha1, offset);
+                       close(fd);
+               }
+
+               if (!pack_to_stdout) {
+                       struct stat st;
+                       const char *idx_tmp_name;
+                       char tmpname[PATH_MAX];
+
+                       idx_tmp_name = write_idx_file(NULL, written_list,
+                                                     nr_written, sha1);
+
+                       snprintf(tmpname, sizeof(tmpname), "%s-%s.pack",
+                                base_name, sha1_to_hex(sha1));
+                       free_pack_by_name(tmpname);
+                       if (adjust_shared_perm(pack_tmp_name))
+                               die_errno("unable to make temporary pack file readable");
+                       if (rename(pack_tmp_name, tmpname))
+                               die_errno("unable to rename temporary pack file");
+
+                       /*
+                        * Packs are runtime accessed in their mtime
+                        * order since newer packs are more likely to contain
+                        * younger objects.  So if we are creating multiple
+                        * packs then we should modify the mtime of later ones
+                        * to preserve this property.
+                        */
+                       if (stat(tmpname, &st) < 0) {
+                               warning("failed to stat %s: %s",
+                                       tmpname, strerror(errno));
+                       } else if (!last_mtime) {
+                               last_mtime = st.st_mtime;
+                       } else {
+                               struct utimbuf utb;
+                               utb.actime = st.st_atime;
+                               utb.modtime = --last_mtime;
+                               if (utime(tmpname, &utb) < 0)
+                                       warning("failed utime() on %s: %s",
+                                               tmpname, strerror(errno));
+                       }
+
+                       snprintf(tmpname, sizeof(tmpname), "%s-%s.idx",
+                                base_name, sha1_to_hex(sha1));
+                       if (adjust_shared_perm(idx_tmp_name))
+                               die_errno("unable to make temporary index file readable");
+                       if (rename(idx_tmp_name, tmpname))
+                               die_errno("unable to rename temporary index file");
+
+                       free((void *) idx_tmp_name);
+                       free(pack_tmp_name);
+                       puts(sha1_to_hex(sha1));
+               }
+
+               /* mark written objects as written to previous pack */
+               for (j = 0; j < nr_written; j++) {
+                       written_list[j]->offset = (off_t)-1;
+               }
+               nr_remaining -= nr_written;
+       } while (nr_remaining && i < nr_objects);
+
+       free(written_list);
+       stop_progress(&progress_state);
+       if (written != nr_result)
+               die("wrote %"PRIu32" objects while expecting %"PRIu32,
+                       written, nr_result);
+}
+
+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 (!hashcmp(sha1, objects[object_ix[i] - 1].idx.sha1))
+                       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)
+{
+       uint32_t 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->idx.sha1);
+               if (0 <= ix)
+                       continue;
+               ix = -1 - ix;
+               object_ix[ix] = i + 1;
+       }
+}
+
+static unsigned name_hash(const char *name)
+{
+       unsigned c, hash = 0;
+
+       if (!name)
+               return 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 void setup_delta_attr_check(struct git_attr_check *check)
+{
+       static struct git_attr *attr_delta;
+
+       if (!attr_delta)
+               attr_delta = git_attr("delta");
+
+       check[0].attr = attr_delta;
+}
+
+static int no_try_delta(const char *path)
+{
+       struct git_attr_check check[1];
+
+       setup_delta_attr_check(check);
+       if (git_checkattr(path, ARRAY_SIZE(check), check))
+               return 0;
+       if (ATTR_FALSE(check->value))
+               return 1;
+       return 0;
+}
+
+static int add_object_entry(const unsigned char *sha1, enum object_type type,
+                           const char *name, int exclude)
+{
+       struct object_entry *entry;
+       struct packed_git *p, *found_pack = NULL;
+       off_t found_offset = 0;
+       int ix;
+       unsigned hash = name_hash(name);
+
+       ix = nr_objects ? locate_object_entry_hash(sha1) : -1;
+       if (ix >= 0) {
+               if (exclude) {
+                       entry = objects + object_ix[ix] - 1;
+                       if (!entry->preferred_base)
+                               nr_result--;
+                       entry->preferred_base = 1;
+               }
+               return 0;
+       }
+
+       if (!exclude && local && has_loose_object_nonlocal(sha1))
+               return 0;
+
+       for (p = packed_git; p; p = p->next) {
+               off_t offset = find_pack_entry_one(sha1, p);
+               if (offset) {
+                       if (!found_pack) {
+                               found_offset = offset;
+                               found_pack = p;
+                       }
+                       if (exclude)
+                               break;
+                       if (incremental)
+                               return 0;
+                       if (local && !p->pack_local)
+                               return 0;
+                       if (ignore_packed_keep && p->pack_local && p->pack_keep)
+                               return 0;
+               }
+       }
+
+       if (nr_objects >= nr_alloc) {
+               nr_alloc = (nr_alloc  + 1024) * 3 / 2;
+               objects = xrealloc(objects, nr_alloc * sizeof(*entry));
+       }
+
+       entry = objects + nr_objects++;
+       memset(entry, 0, sizeof(*entry));
+       hashcpy(entry->idx.sha1, sha1);
+       entry->hash = hash;
+       if (type)
+               entry->type = type;
+       if (exclude)
+               entry->preferred_base = 1;
+       else
+               nr_result++;
+       if (found_pack) {
+               entry->in_pack = found_pack;
+               entry->in_pack_offset = found_offset;
+       }
+
+       if (object_ix_hashsz * 3 <= nr_objects * 4)
+               rehash_objects();
+       else
+               object_ix[-1 - ix] = nr_objects;
+
+       display_progress(progress_state, nr_objects);
+
+       if (name && no_try_delta(name))
+               entry->no_try_delta = 1;
+
+       return 1;
+}
+
+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;
+       enum object_type type;
+       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 && !hashcmp(ent->sha1, sha1)) {
+                       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 (type != OBJ_TREE) {
+               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;
+       }
+       hashcpy(nent->sha1, sha1);
+       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;
+       int cmp;
+
+       while (tree_entry(tree,&entry)) {
+               if (S_ISGITLINK(entry.mode))
+                       continue;
+               cmp = tree_entry_len(entry.path, entry.sha1) != cmplen ? 1 :
+                     memcmp(name, entry.path, cmplen);
+               if (cmp > 0)
+                       continue;
+               if (cmp < 0)
+                       return;
+               if (name[cmplen] != '/') {
+                       add_object_entry(entry.sha1,
+                                        object_type(entry.mode),
+                                        fullname, 1);
+                       return;
+               }
+               if (S_ISDIR(entry.mode)) {
+                       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;
+                       init_tree_desc(&sub, tree->tree_data, 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(const char *name)
+{
+       struct pbase_tree *it;
+       int cmplen;
+       unsigned hash = name_hash(name);
+
+       if (!num_preferred_base || check_pbase_path(hash))
+               return;
+
+       cmplen = name_cmp_len(name);
+       for (it = pbase_tree; it; it = it->next) {
+               if (cmplen == 0) {
+                       add_object_entry(it->pcache.sha1, OBJ_TREE, NULL, 1);
+               }
+               else {
+                       struct tree_desc tree;
+                       init_tree_desc(&tree, it->pcache.tree_data, 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];
+
+       if (window <= num_preferred_base++)
+               return;
+
+       data = read_object_with_reference(sha1, tree_type, &size, tree_sha1);
+       if (!data)
+               return;
+
+       for (it = pbase_tree; it; it = it->next) {
+               if (!hashcmp(it->pcache.sha1, tree_sha1)) {
+                       free(data);
+                       return;
+               }
+       }
+
+       it = xcalloc(1, sizeof(*it));
+       it->next = pbase_tree;
+       pbase_tree = it;
+
+       hashcpy(it->pcache.sha1, tree_sha1);
+       it->pcache.tree_data = data;
+       it->pcache.tree_size = size;
+}
+
+static void cleanup_preferred_base(void)
+{
+       struct pbase_tree *it;
+       unsigned i;
+
+       it = pbase_tree;
+       pbase_tree = NULL;
+       while (it) {
+               struct pbase_tree *this = it;
+               it = this->next;
+               free(this->pcache.tree_data);
+               free(this);
+       }
+
+       for (i = 0; i < ARRAY_SIZE(pbase_tree_cache); i++) {
+               if (!pbase_tree_cache[i])
+                       continue;
+               free(pbase_tree_cache[i]->tree_data);
+               free(pbase_tree_cache[i]);
+               pbase_tree_cache[i] = NULL;
+       }
+
+       free(done_pbase_paths);
+       done_pbase_paths = NULL;
+       done_pbase_paths_num = done_pbase_paths_alloc = 0;
+}
+
+static void check_object(struct object_entry *entry)
+{
+       if (entry->in_pack) {
+               struct packed_git *p = entry->in_pack;
+               struct pack_window *w_curs = NULL;
+               const unsigned char *base_ref = NULL;
+               struct object_entry *base_entry;
+               unsigned long used, used_0;
+               unsigned int avail;
+               off_t ofs;
+               unsigned char *buf, c;
+
+               buf = use_pack(p, &w_curs, entry->in_pack_offset, &avail);
+
+               /*
+                * We want in_pack_type even if we do not reuse delta
+                * since non-delta representations could still be reused.
+                */
+               used = unpack_object_header_buffer(buf, avail,
+                                                  &entry->in_pack_type,
+                                                  &entry->size);
+               if (used == 0)
+                       goto give_up;
+
+               /*
+                * Determine if this is a delta and if so whether we can
+                * reuse it or not.  Otherwise let's find out as cheaply as
+                * possible what the actual type and size for this object is.
+                */
+               switch (entry->in_pack_type) {
+               default:
+                       /* Not a delta hence we've already got all we need. */
+                       entry->type = entry->in_pack_type;
+                       entry->in_pack_header_size = used;
+                       if (entry->type < OBJ_COMMIT || entry->type > OBJ_BLOB)
+                               goto give_up;
+                       unuse_pack(&w_curs);
+                       return;
+               case OBJ_REF_DELTA:
+                       if (reuse_delta && !entry->preferred_base)
+                               base_ref = use_pack(p, &w_curs,
+                                               entry->in_pack_offset + used, NULL);
+                       entry->in_pack_header_size = used + 20;
+                       break;
+               case OBJ_OFS_DELTA:
+                       buf = use_pack(p, &w_curs,
+                                      entry->in_pack_offset + used, NULL);
+                       used_0 = 0;
+                       c = buf[used_0++];
+                       ofs = c & 127;
+                       while (c & 128) {
+                               ofs += 1;
+                               if (!ofs || MSB(ofs, 7)) {
+                                       error("delta base offset overflow in pack for %s",
+                                             sha1_to_hex(entry->idx.sha1));
+                                       goto give_up;
+                               }
+                               c = buf[used_0++];
+                               ofs = (ofs << 7) + (c & 127);
+                       }
+                       ofs = entry->in_pack_offset - ofs;
+                       if (ofs <= 0 || ofs >= entry->in_pack_offset) {
+                               error("delta base offset out of bound for %s",
+                                     sha1_to_hex(entry->idx.sha1));
+                               goto give_up;
+                       }
+                       if (reuse_delta && !entry->preferred_base) {
+                               struct revindex_entry *revidx;
+                               revidx = find_pack_revindex(p, ofs);
+                               if (!revidx)
+                                       goto give_up;
+                               base_ref = nth_packed_object_sha1(p, revidx->nr);
+                       }
+                       entry->in_pack_header_size = used + used_0;
+                       break;
+               }
+
+               if (base_ref && (base_entry = locate_object_entry(base_ref))) {
+                       /*
+                        * If base_ref was set above that means we wish to
+                        * reuse delta data, and we even found that base
+                        * in the list of objects we want to pack. Goodie!
+                        *
+                        * 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.
+                        */
+                       entry->type = entry->in_pack_type;
+                       entry->delta = base_entry;
+                       entry->delta_size = entry->size;
+                       entry->delta_sibling = base_entry->delta_child;
+                       base_entry->delta_child = entry;
+                       unuse_pack(&w_curs);
+                       return;
+               }
+
+               if (entry->type) {
+                       /*
+                        * This must be a delta and we already know what the
+                        * final object type is.  Let's extract the actual
+                        * object size from the delta header.
+                        */
+                       entry->size = get_size_from_delta(p, &w_curs,
+                                       entry->in_pack_offset + entry->in_pack_header_size);
+                       if (entry->size == 0)
+                               goto give_up;
+                       unuse_pack(&w_curs);
+                       return;
+               }
+
+               /*
+                * No choice but to fall back to the recursive delta walk
+                * with sha1_object_info() to find about the object type
+                * at this point...
+                */
+               give_up:
+               unuse_pack(&w_curs);
+       }
+
+       entry->type = sha1_object_info(entry->idx.sha1, &entry->size);
+       /*
+        * The error condition is checked in prepare_pack().  This is
+        * to permit a missing preferred base object to be ignored
+        * as a preferred base.  Doing so can result in a larger
+        * pack file, but the transfer will still take place.
+        */
+}
+
+static int pack_offset_sort(const void *_a, const void *_b)
+{
+       const struct object_entry *a = *(struct object_entry **)_a;
+       const struct object_entry *b = *(struct object_entry **)_b;
+
+       /* avoid filesystem trashing with loose objects */
+       if (!a->in_pack && !b->in_pack)
+               return hashcmp(a->idx.sha1, b->idx.sha1);
+
+       if (a->in_pack < b->in_pack)
+               return -1;
+       if (a->in_pack > b->in_pack)
+               return 1;
+       return a->in_pack_offset < b->in_pack_offset ? -1 :
+                       (a->in_pack_offset > b->in_pack_offset);
+}
+
+static void get_object_details(void)
+{
+       uint32_t i;
+       struct object_entry **sorted_by_offset;
+
+       sorted_by_offset = xcalloc(nr_objects, sizeof(struct object_entry *));
+       for (i = 0; i < nr_objects; i++)
+               sorted_by_offset[i] = objects + i;
+       qsort(sorted_by_offset, nr_objects, sizeof(*sorted_by_offset), pack_offset_sort);
+
+       for (i = 0; i < nr_objects; i++)
+               check_object(sorted_by_offset[i]);
+
+       free(sorted_by_offset);
+}
+
+/*
+ * We search for deltas in a list sorted by type, by filename hash, and then
+ * 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.  The deepest deltas are therefore the oldest objects which are
+ * less susceptible to be accessed often.
+ */
+static int type_size_sort(const void *_a, const void *_b)
+{
+       const struct object_entry *a = *(struct object_entry **)_a;
+       const struct object_entry *b = *(struct object_entry **)_b;
+
+       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);  /* newest first */
+}
+
+struct unpacked {
+       struct object_entry *entry;
+       void *data;
+       struct delta_index *index;
+       unsigned depth;
+};
+
+static int delta_cacheable(unsigned long src_size, unsigned long trg_size,
+                          unsigned long delta_size)
+{
+       if (max_delta_cache_size && delta_cache_size + delta_size > max_delta_cache_size)
+               return 0;
+
+       if (delta_size < cache_max_small_delta_size)
+               return 1;
+
+       /* cache delta, if objects are large enough compared to delta size */
+       if ((src_size >> 20) + (trg_size >> 21) > (delta_size >> 10))
+               return 1;
+
+       return 0;
+}
+
+#ifndef NO_PTHREADS
+
+static pthread_mutex_t read_mutex;
+#define read_lock()            pthread_mutex_lock(&read_mutex)
+#define read_unlock()          pthread_mutex_unlock(&read_mutex)
+
+static pthread_mutex_t cache_mutex;
+#define cache_lock()           pthread_mutex_lock(&cache_mutex)
+#define cache_unlock()         pthread_mutex_unlock(&cache_mutex)
+
+static pthread_mutex_t progress_mutex;
+#define progress_lock()                pthread_mutex_lock(&progress_mutex)
+#define progress_unlock()      pthread_mutex_unlock(&progress_mutex)
+
+#else
+
+#define read_lock()            (void)0
+#define read_unlock()          (void)0
+#define cache_lock()           (void)0
+#define cache_unlock()         (void)0
+#define progress_lock()                (void)0
+#define progress_unlock()      (void)0
+
+#endif
+
+static int try_delta(struct unpacked *trg, struct unpacked *src,
+                    unsigned max_depth, unsigned long *mem_usage)
+{
+       struct object_entry *trg_entry = trg->entry;
+       struct object_entry *src_entry = src->entry;
+       unsigned long trg_size, src_size, delta_size, sizediff, max_size, sz;
+       unsigned ref_depth;
+       enum object_type type;
+       void *delta_buf;
+
+       /* Don't bother doing diffs between different types */
+       if (trg_entry->type != src_entry->type)
+               return -1;
+
+       /*
+        * We do not bother to try a delta that we discarded
+        * on an earlier try, but only when reusing delta data.
+        */
+       if (reuse_delta && trg_entry->in_pack &&
+           trg_entry->in_pack == src_entry->in_pack &&
+           trg_entry->in_pack_type != OBJ_REF_DELTA &&
+           trg_entry->in_pack_type != OBJ_OFS_DELTA)
+               return 0;
+
+       /* Let's not bust the allowed depth. */
+       if (src->depth >= max_depth)
+               return 0;
+
+       /* Now some size filtering heuristics. */
+       trg_size = trg_entry->size;
+       if (!trg_entry->delta) {
+               max_size = trg_size/2 - 20;
+               ref_depth = 1;
+       } else {
+               max_size = trg_entry->delta_size;
+               ref_depth = trg->depth;
+       }
+       max_size = (uint64_t)max_size * (max_depth - src->depth) /
+                                               (max_depth - ref_depth + 1);
+       if (max_size == 0)
+               return 0;
+       src_size = src_entry->size;
+       sizediff = src_size < trg_size ? trg_size - src_size : 0;
+       if (sizediff >= max_size)
+               return 0;
+       if (trg_size < src_size / 32)
+               return 0;
+
+       /* Load data if not already done */
+       if (!trg->data) {
+               read_lock();
+               trg->data = read_sha1_file(trg_entry->idx.sha1, &type, &sz);
+               read_unlock();
+               if (!trg->data)
+                       die("object %s cannot be read",
+                           sha1_to_hex(trg_entry->idx.sha1));
+               if (sz != trg_size)
+                       die("object %s inconsistent object length (%lu vs %lu)",
+                           sha1_to_hex(trg_entry->idx.sha1), sz, trg_size);
+               *mem_usage += sz;
+       }
+       if (!src->data) {
+               read_lock();
+               src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz);
+               read_unlock();
+               if (!src->data)
+                       die("object %s cannot be read",
+                           sha1_to_hex(src_entry->idx.sha1));
+               if (sz != src_size)
+                       die("object %s inconsistent object length (%lu vs %lu)",
+                           sha1_to_hex(src_entry->idx.sha1), sz, src_size);
+               *mem_usage += sz;
+       }
+       if (!src->index) {
+               src->index = create_delta_index(src->data, src_size);
+               if (!src->index) {
+                       static int warned = 0;
+                       if (!warned++)
+                               warning("suboptimal pack - out of memory");
+                       return 0;
+               }
+               *mem_usage += sizeof_delta_index(src->index);
+       }
+
+       delta_buf = create_delta(src->index, trg->data, trg_size, &delta_size, max_size);
+       if (!delta_buf)
+               return 0;
+
+       if (trg_entry->delta) {
+               /* Prefer only shallower same-sized deltas. */
+               if (delta_size == trg_entry->delta_size &&
+                   src->depth + 1 >= trg->depth) {
+                       free(delta_buf);
+                       return 0;
+               }
+       }
+
+       /*
+        * Handle memory allocation outside of the cache
+        * accounting lock.  Compiler will optimize the strangeness
+        * away when NO_PTHREADS is defined.
+        */
+       free(trg_entry->delta_data);
+       cache_lock();
+       if (trg_entry->delta_data) {
+               delta_cache_size -= trg_entry->delta_size;
+               trg_entry->delta_data = NULL;
+       }
+       if (delta_cacheable(src_size, trg_size, delta_size)) {
+               delta_cache_size += delta_size;
+               cache_unlock();
+               trg_entry->delta_data = xrealloc(delta_buf, delta_size);
+       } else {
+               cache_unlock();
+               free(delta_buf);
+       }
+
+       trg_entry->delta = src_entry;
+       trg_entry->delta_size = delta_size;
+       trg->depth = src->depth + 1;
+
+       return 1;
+}
+
+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 unsigned long free_unpacked(struct unpacked *n)
+{
+       unsigned long freed_mem = sizeof_delta_index(n->index);
+       free_delta_index(n->index);
+       n->index = NULL;
+       if (n->data) {
+               freed_mem += n->entry->size;
+               free(n->data);
+               n->data = NULL;
+       }
+       n->entry = NULL;
+       n->depth = 0;
+       return freed_mem;
+}
+
+static void find_deltas(struct object_entry **list, unsigned *list_size,
+                       int window, int depth, unsigned *processed)
+{
+       uint32_t i, idx = 0, count = 0;
+       struct unpacked *array;
+       unsigned long mem_usage = 0;
+
+       array = xcalloc(window, sizeof(struct unpacked));
+
+       for (;;) {
+               struct object_entry *entry;
+               struct unpacked *n = array + idx;
+               int j, max_depth, best_base = -1;
+
+               progress_lock();
+               if (!*list_size) {
+                       progress_unlock();
+                       break;
+               }
+               entry = *list++;
+               (*list_size)--;
+               if (!entry->preferred_base) {
+                       (*processed)++;
+                       display_progress(progress_state, *processed);
+               }
+               progress_unlock();
+
+               mem_usage -= free_unpacked(n);
+               n->entry = entry;
+
+               while (window_memory_limit &&
+                      mem_usage > window_memory_limit &&
+                      count > 1) {
+                       uint32_t tail = (idx + window - count) % window;
+                       mem_usage -= free_unpacked(array + tail);
+                       count--;
+               }
+
+               /* We do not compute delta to *create* objects we are not
+                * going to pack.
+                */
+               if (entry->preferred_base)
+                       goto next;
+
+               /*
+                * 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.
+                */
+               max_depth = depth;
+               if (entry->delta_child) {
+                       max_depth -= check_delta_limit(entry, 0);
+                       if (max_depth <= 0)
+                               goto next;
+               }
+
+               j = window;
+               while (--j > 0) {
+                       int ret;
+                       uint32_t other_idx = idx + j;
+                       struct unpacked *m;
+                       if (other_idx >= window)
+                               other_idx -= window;
+                       m = array + other_idx;
+                       if (!m->entry)
+                               break;
+                       ret = try_delta(n, m, max_depth, &mem_usage);
+                       if (ret < 0)
+                               break;
+                       else if (ret > 0)
+                               best_base = other_idx;
+               }
+
+               /*
+                * If we decided to cache the delta data, then it is best
+                * to compress it right away.  First because we have to do
+                * it anyway, and doing it here while we're threaded will
+                * save a lot of time in the non threaded write phase,
+                * as well as allow for caching more deltas within
+                * the same cache size limit.
+                * ...
+                * But only if not writing to stdout, since in that case
+                * the network is most likely throttling writes anyway,
+                * and therefore it is best to go to the write phase ASAP
+                * instead, as we can afford spending more time compressing
+                * between writes at that moment.
+                */
+               if (entry->delta_data && !pack_to_stdout) {
+                       entry->z_delta_size = do_compress(&entry->delta_data,
+                                                         entry->delta_size);
+                       cache_lock();
+                       delta_cache_size -= entry->delta_size;
+                       delta_cache_size += entry->z_delta_size;
+                       cache_unlock();
+               }
+
+               /* 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 && max_depth <= n->depth)
+                       continue;
+
+               /*
+                * Move the best delta base up in the window, after the
+                * currently deltified object, to keep it longer.  It will
+                * be the first base object to be attempted next.
+                */
+               if (entry->delta) {
+                       struct unpacked swap = array[best_base];
+                       int dist = (window + idx - best_base) % window;
+                       int dst = best_base;
+                       while (dist--) {
+                               int src = (dst + 1) % window;
+                               array[dst] = array[src];
+                               dst = src;
+                       }
+                       array[dst] = swap;
+               }
+
+               next:
+               idx++;
+               if (count + 1 < window)
+                       count++;
+               if (idx >= window)
+                       idx = 0;
+       }
+
+       for (i = 0; i < window; ++i) {
+               free_delta_index(array[i].index);
+               free(array[i].data);
+       }
+       free(array);
+}
+
+#ifndef NO_PTHREADS
+
+static void try_to_free_from_threads(size_t size)
+{
+       read_lock();
+       release_pack_memory(size, -1);
+       read_unlock();
+}
+
+try_to_free_t old_try_to_free_routine;
+
+/*
+ * The main thread waits on the condition that (at least) one of the workers
+ * has stopped working (which is indicated in the .working member of
+ * struct thread_params).
+ * When a work thread has completed its work, it sets .working to 0 and
+ * signals the main thread and waits on the condition that .data_ready
+ * becomes 1.
+ */
+
+struct thread_params {
+       pthread_t thread;
+       struct object_entry **list;
+       unsigned list_size;
+       unsigned remaining;
+       int window;
+       int depth;
+       int working;
+       int data_ready;
+       pthread_mutex_t mutex;
+       pthread_cond_t cond;
+       unsigned *processed;
+};
+
+static pthread_cond_t progress_cond;
+
+/*
+ * Mutex and conditional variable can't be statically-initialized on Windows.
+ */
+static void init_threaded_search(void)
+{
+       init_recursive_mutex(&read_mutex);
+       pthread_mutex_init(&cache_mutex, NULL);
+       pthread_mutex_init(&progress_mutex, NULL);
+       pthread_cond_init(&progress_cond, NULL);
+       old_try_to_free_routine = set_try_to_free_routine(try_to_free_from_threads);
+}
+
+static void cleanup_threaded_search(void)
+{
+       set_try_to_free_routine(old_try_to_free_routine);
+       pthread_cond_destroy(&progress_cond);
+       pthread_mutex_destroy(&read_mutex);
+       pthread_mutex_destroy(&cache_mutex);
+       pthread_mutex_destroy(&progress_mutex);
+}
+
+static void *threaded_find_deltas(void *arg)
+{
+       struct thread_params *me = arg;
+
+       while (me->remaining) {
+               find_deltas(me->list, &me->remaining,
+                           me->window, me->depth, me->processed);
+
+               progress_lock();
+               me->working = 0;
+               pthread_cond_signal(&progress_cond);
+               progress_unlock();
+
+               /*
+                * We must not set ->data_ready before we wait on the
+                * condition because the main thread may have set it to 1
+                * before we get here. In order to be sure that new
+                * work is available if we see 1 in ->data_ready, it
+                * was initialized to 0 before this thread was spawned
+                * and we reset it to 0 right away.
+                */
+               pthread_mutex_lock(&me->mutex);
+               while (!me->data_ready)
+                       pthread_cond_wait(&me->cond, &me->mutex);
+               me->data_ready = 0;
+               pthread_mutex_unlock(&me->mutex);
+       }
+       /* leave ->working 1 so that this doesn't get more work assigned */
+       return NULL;
+}
+
+static void ll_find_deltas(struct object_entry **list, unsigned list_size,
+                          int window, int depth, unsigned *processed)
+{
+       struct thread_params *p;
+       int i, ret, active_threads = 0;
+
+       init_threaded_search();
+
+       if (!delta_search_threads)      /* --threads=0 means autodetect */
+               delta_search_threads = online_cpus();
+       if (delta_search_threads <= 1) {
+               find_deltas(list, &list_size, window, depth, processed);
+               cleanup_threaded_search();
+               return;
+       }
+       if (progress > pack_to_stdout)
+               fprintf(stderr, "Delta compression using up to %d threads.\n",
+                               delta_search_threads);
+       p = xcalloc(delta_search_threads, sizeof(*p));
+
+       /* Partition the work amongst work threads. */
+       for (i = 0; i < delta_search_threads; i++) {
+               unsigned sub_size = list_size / (delta_search_threads - i);
+
+               /* don't use too small segments or no deltas will be found */
+               if (sub_size < 2*window && i+1 < delta_search_threads)
+                       sub_size = 0;
+
+               p[i].window = window;
+               p[i].depth = depth;
+               p[i].processed = processed;
+               p[i].working = 1;
+               p[i].data_ready = 0;
+
+               /* try to split chunks on "path" boundaries */
+               while (sub_size && sub_size < list_size &&
+                      list[sub_size]->hash &&
+                      list[sub_size]->hash == list[sub_size-1]->hash)
+                       sub_size++;
+
+               p[i].list = list;
+               p[i].list_size = sub_size;
+               p[i].remaining = sub_size;
+
+               list += sub_size;
+               list_size -= sub_size;
+       }
+
+       /* Start work threads. */
+       for (i = 0; i < delta_search_threads; i++) {
+               if (!p[i].list_size)
+                       continue;
+               pthread_mutex_init(&p[i].mutex, NULL);
+               pthread_cond_init(&p[i].cond, NULL);
+               ret = pthread_create(&p[i].thread, NULL,
+                                    threaded_find_deltas, &p[i]);
+               if (ret)
+                       die("unable to create thread: %s", strerror(ret));
+               active_threads++;
+       }
+
+       /*
+        * Now let's wait for work completion.  Each time a thread is done
+        * with its work, we steal half of the remaining work from the
+        * thread with the largest number of unprocessed objects and give
+        * it to that newly idle thread.  This ensure good load balancing
+        * until the remaining object list segments are simply too short
+        * to be worth splitting anymore.
+        */
+       while (active_threads) {
+               struct thread_params *target = NULL;
+               struct thread_params *victim = NULL;
+               unsigned sub_size = 0;
+
+               progress_lock();
+               for (;;) {
+                       for (i = 0; !target && i < delta_search_threads; i++)
+                               if (!p[i].working)
+                                       target = &p[i];
+                       if (target)
+                               break;
+                       pthread_cond_wait(&progress_cond, &progress_mutex);
+               }
+
+               for (i = 0; i < delta_search_threads; i++)
+                       if (p[i].remaining > 2*window &&
+                           (!victim || victim->remaining < p[i].remaining))
+                               victim = &p[i];
+               if (victim) {
+                       sub_size = victim->remaining / 2;
+                       list = victim->list + victim->list_size - sub_size;
+                       while (sub_size && list[0]->hash &&
+                              list[0]->hash == list[-1]->hash) {
+                               list++;
+                               sub_size--;
+                       }
+                       if (!sub_size) {
+                               /*
+                                * It is possible for some "paths" to have
+                                * so many objects that no hash boundary
+                                * might be found.  Let's just steal the
+                                * exact half in that case.
+                                */
+                               sub_size = victim->remaining / 2;
+                               list -= sub_size;
+                       }
+                       target->list = list;
+                       victim->list_size -= sub_size;
+                       victim->remaining -= sub_size;
+               }
+               target->list_size = sub_size;
+               target->remaining = sub_size;
+               target->working = 1;
+               progress_unlock();
+
+               pthread_mutex_lock(&target->mutex);
+               target->data_ready = 1;
+               pthread_cond_signal(&target->cond);
+               pthread_mutex_unlock(&target->mutex);
+
+               if (!sub_size) {
+                       pthread_join(target->thread, NULL);
+                       pthread_cond_destroy(&target->cond);
+                       pthread_mutex_destroy(&target->mutex);
+                       active_threads--;
+               }
+       }
+       cleanup_threaded_search();
+       free(p);
+}
+
+#else
+#define ll_find_deltas(l, s, w, d, p)  find_deltas(l, &s, w, d, p)
+#endif
+
+static int add_ref_tag(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+{
+       unsigned char peeled[20];
+
+       if (!prefixcmp(path, "refs/tags/") && /* is a tag? */
+           !peel_ref(path, peeled)        && /* peelable? */
+           !is_null_sha1(peeled)          && /* annotated tag? */
+           locate_object_entry(peeled))      /* object packed? */
+               add_object_entry(sha1, OBJ_TAG, NULL, 0);
+       return 0;
+}
+
+static void prepare_pack(int window, int depth)
+{
+       struct object_entry **delta_list;
+       uint32_t i, nr_deltas;
+       unsigned n;
+
+       get_object_details();
+
+       /*
+        * If we're locally repacking then we need to be doubly careful
+        * from now on in order to make sure no stealth corruption gets
+        * propagated to the new pack.  Clients receiving streamed packs
+        * should validate everything they get anyway so no need to incur
+        * the additional cost here in that case.
+        */
+       if (!pack_to_stdout)
+               do_check_packed_object_crc = 1;
+
+       if (!nr_objects || !window || !depth)
+               return;
+
+       delta_list = xmalloc(nr_objects * sizeof(*delta_list));
+       nr_deltas = n = 0;
+
+       for (i = 0; i < nr_objects; i++) {
+               struct object_entry *entry = objects + i;
+
+               if (entry->delta)
+                       /* This happens if we decided to reuse existing
+                        * delta from a pack.  "reuse_delta &&" is implied.
+                        */
+                       continue;
+
+               if (entry->size < 50)
+                       continue;
+
+               if (entry->no_try_delta)
+                       continue;
+
+               if (!entry->preferred_base) {
+                       nr_deltas++;
+                       if (entry->type < 0)
+                               die("unable to get type of object %s",
+                                   sha1_to_hex(entry->idx.sha1));
+               } else {
+                       if (entry->type < 0) {
+                               /*
+                                * This object is not found, but we
+                                * don't have to include it anyway.
+                                */
+                               continue;
+                       }
+               }
+
+               delta_list[n++] = entry;
+       }
+
+       if (nr_deltas && n > 1) {
+               unsigned nr_done = 0;
+               if (progress)
+                       progress_state = start_progress("Compressing objects",
+                                                       nr_deltas);
+               qsort(delta_list, n, sizeof(*delta_list), type_size_sort);
+               ll_find_deltas(delta_list, n, window+1, depth, &nr_done);
+               stop_progress(&progress_state);
+               if (nr_done != nr_deltas)
+                       die("inconsistency with delta count");
+       }
+       free(delta_list);
+}
+
+static int git_pack_config(const char *k, const char *v, void *cb)
+{
+       if (!strcmp(k, "pack.window")) {
+               window = git_config_int(k, v);
+               return 0;
+       }
+       if (!strcmp(k, "pack.windowmemory")) {
+               window_memory_limit = git_config_ulong(k, v);
+               return 0;
+       }
+       if (!strcmp(k, "pack.depth")) {
+               depth = git_config_int(k, v);
+               return 0;
+       }
+       if (!strcmp(k, "pack.compression")) {
+               int level = git_config_int(k, v);
+               if (level == -1)
+                       level = Z_DEFAULT_COMPRESSION;
+               else if (level < 0 || level > Z_BEST_COMPRESSION)
+                       die("bad pack compression level %d", level);
+               pack_compression_level = level;
+               pack_compression_seen = 1;
+               return 0;
+       }
+       if (!strcmp(k, "pack.deltacachesize")) {
+               max_delta_cache_size = git_config_int(k, v);
+               return 0;
+       }
+       if (!strcmp(k, "pack.deltacachelimit")) {
+               cache_max_small_delta_size = git_config_int(k, v);
+               return 0;
+       }
+       if (!strcmp(k, "pack.threads")) {
+               delta_search_threads = git_config_int(k, v);
+               if (delta_search_threads < 0)
+                       die("invalid number of threads specified (%d)",
+                           delta_search_threads);
+#ifdef NO_PTHREADS
+               if (delta_search_threads != 1)
+                       warning("no threads support, ignoring %s", k);
+#endif
+               return 0;
+       }
+       if (!strcmp(k, "pack.indexversion")) {
+               pack_idx_default_version = git_config_int(k, v);
+               if (pack_idx_default_version > 2)
+                       die("bad pack.indexversion=%"PRIu32,
+                               pack_idx_default_version);
+               return 0;
+       }
+       if (!strcmp(k, "pack.packsizelimit")) {
+               pack_size_limit_cfg = git_config_ulong(k, v);
+               return 0;
+       }
+       return git_default_config(k, v, cb);
+}
+
+static void read_object_list_from_stdin(void)
+{
+       char line[40 + 1 + PATH_MAX + 2];
+       unsigned char sha1[20];
+
+       for (;;) {
+               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_errno("fgets");
+                       clearerr(stdin);
+                       continue;
+               }
+               if (line[0] == '-') {
+                       if (get_sha1_hex(line+1, sha1))
+                               die("expected edge sha1, got garbage:\n %s",
+                                   line);
+                       add_preferred_base(sha1);
+                       continue;
+               }
+               if (get_sha1_hex(line, sha1))
+                       die("expected sha1, got garbage:\n %s", line);
+
+               add_preferred_base_object(line+41);
+               add_object_entry(sha1, 0, line+41, 0);
+       }
+}
+
+#define OBJECT_ADDED (1u<<20)
+
+static void show_commit(struct commit *commit, void *data)
+{
+       add_object_entry(commit->object.sha1, OBJ_COMMIT, NULL, 0);
+       commit->object.flags |= OBJECT_ADDED;
+}
+
+static void show_object(struct object *obj, const struct name_path *path, const char *last)
+{
+       char *name = path_name(path, last);
+
+       add_preferred_base_object(name);
+       add_object_entry(obj->sha1, obj->type, name, 0);
+       obj->flags |= OBJECT_ADDED;
+
+       /*
+        * We will have generated the hash from the name,
+        * but not saved a pointer to it - we can free it
+        */
+       free((char *)name);
+}
+
+static void show_edge(struct commit *commit)
+{
+       add_preferred_base(commit->object.sha1);
+}
+
+struct in_pack_object {
+       off_t offset;
+       struct object *object;
+};
+
+struct in_pack {
+       int alloc;
+       int nr;
+       struct in_pack_object *array;
+};
+
+static void mark_in_pack_object(struct object *object, struct packed_git *p, struct in_pack *in_pack)
+{
+       in_pack->array[in_pack->nr].offset = find_pack_entry_one(object->sha1, p);
+       in_pack->array[in_pack->nr].object = object;
+       in_pack->nr++;
+}
+
+/*
+ * Compare the objects in the offset order, in order to emulate the
+ * "git rev-list --objects" output that produced the pack originally.
+ */
+static int ofscmp(const void *a_, const void *b_)
+{
+       struct in_pack_object *a = (struct in_pack_object *)a_;
+       struct in_pack_object *b = (struct in_pack_object *)b_;
+
+       if (a->offset < b->offset)
+               return -1;
+       else if (a->offset > b->offset)
+               return 1;
+       else
+               return hashcmp(a->object->sha1, b->object->sha1);
+}
+
+static void add_objects_in_unpacked_packs(struct rev_info *revs)
+{
+       struct packed_git *p;
+       struct in_pack in_pack;
+       uint32_t i;
+
+       memset(&in_pack, 0, sizeof(in_pack));
+
+       for (p = packed_git; p; p = p->next) {
+               const unsigned char *sha1;
+               struct object *o;
+
+               if (!p->pack_local || p->pack_keep)
+                       continue;
+               if (open_pack_index(p))
+                       die("cannot open pack index");
+
+               ALLOC_GROW(in_pack.array,
+                          in_pack.nr + p->num_objects,
+                          in_pack.alloc);
+
+               for (i = 0; i < p->num_objects; i++) {
+                       sha1 = nth_packed_object_sha1(p, i);
+                       o = lookup_unknown_object(sha1);
+                       if (!(o->flags & OBJECT_ADDED))
+                               mark_in_pack_object(o, p, &in_pack);
+                       o->flags |= OBJECT_ADDED;
+               }
+       }
+
+       if (in_pack.nr) {
+               qsort(in_pack.array, in_pack.nr, sizeof(in_pack.array[0]),
+                     ofscmp);
+               for (i = 0; i < in_pack.nr; i++) {
+                       struct object *o = in_pack.array[i].object;
+                       add_object_entry(o->sha1, o->type, "", 0);
+               }
+       }
+       free(in_pack.array);
+}
+
+static int has_sha1_pack_kept_or_nonlocal(const unsigned char *sha1)
+{
+       static struct packed_git *last_found = (void *)1;
+       struct packed_git *p;
+
+       p = (last_found != (void *)1) ? last_found : packed_git;
+
+       while (p) {
+               if ((!p->pack_local || p->pack_keep) &&
+                       find_pack_entry_one(sha1, p)) {
+                       last_found = p;
+                       return 1;
+               }
+               if (p == last_found)
+                       p = packed_git;
+               else
+                       p = p->next;
+               if (p == last_found)
+                       p = p->next;
+       }
+       return 0;
+}
+
+static void loosen_unused_packed_objects(struct rev_info *revs)
+{
+       struct packed_git *p;
+       uint32_t i;
+       const unsigned char *sha1;
+
+       for (p = packed_git; p; p = p->next) {
+               if (!p->pack_local || p->pack_keep)
+                       continue;
+
+               if (open_pack_index(p))
+                       die("cannot open pack index");
+
+               for (i = 0; i < p->num_objects; i++) {
+                       sha1 = nth_packed_object_sha1(p, i);
+                       if (!locate_object_entry(sha1) &&
+                               !has_sha1_pack_kept_or_nonlocal(sha1))
+                               if (force_object_loose(sha1, p->mtime))
+                                       die("unable to force loose object");
+               }
+       }
+}
+
+static void get_object_list(int ac, const char **av)
+{
+       struct rev_info revs;
+       char line[1000];
+       int flags = 0;
+
+       init_revisions(&revs, NULL);
+       save_commit_buffer = 0;
+       setup_revisions(ac, av, &revs, NULL);
+
+       while (fgets(line, sizeof(line), stdin) != NULL) {
+               int len = strlen(line);
+               if (len && line[len - 1] == '\n')
+                       line[--len] = 0;
+               if (!len)
+                       break;
+               if (*line == '-') {
+                       if (!strcmp(line, "--not")) {
+                               flags ^= UNINTERESTING;
+                               continue;
+                       }
+                       die("not a rev '%s'", line);
+               }
+               if (handle_revision_arg(line, &revs, flags, 1))
+                       die("bad revision '%s'", line);
+       }
+
+       if (prepare_revision_walk(&revs))
+               die("revision walk setup failed");
+       mark_edges_uninteresting(revs.commits, &revs, show_edge);
+       traverse_commit_list(&revs, show_commit, show_object, NULL);
+
+       if (keep_unreachable)
+               add_objects_in_unpacked_packs(&revs);
+       if (unpack_unreachable)
+               loosen_unused_packed_objects(&revs);
+}
+
+int cmd_pack_objects(int argc, const char **argv, const char *prefix)
+{
+       int use_internal_rev_list = 0;
+       int thin = 0;
+       int all_progress_implied = 0;
+       uint32_t i;
+       const char **rp_av;
+       int rp_ac_alloc = 64;
+       int rp_ac;
+
+       read_replace_refs = 0;
+
+       rp_av = xcalloc(rp_ac_alloc, sizeof(*rp_av));
+
+       rp_av[0] = "pack-objects";
+       rp_av[1] = "--objects"; /* --thin will make it --objects-edge */
+       rp_ac = 2;
+
+       git_config(git_pack_config, NULL);
+       if (!pack_compression_seen && core_compression_seen)
+               pack_compression_level = core_compression_level;
+
+       progress = isatty(2);
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (*arg != '-')
+                       break;
+
+               if (!strcmp("--non-empty", arg)) {
+                       non_empty = 1;
+                       continue;
+               }
+               if (!strcmp("--local", arg)) {
+                       local = 1;
+                       continue;
+               }
+               if (!strcmp("--incremental", arg)) {
+                       incremental = 1;
+                       continue;
+               }
+               if (!strcmp("--honor-pack-keep", arg)) {
+                       ignore_packed_keep = 1;
+                       continue;
+               }
+               if (!prefixcmp(arg, "--compression=")) {
+                       char *end;
+                       int level = strtoul(arg+14, &end, 0);
+                       if (!arg[14] || *end)
+                               usage(pack_usage);
+                       if (level == -1)
+                               level = Z_DEFAULT_COMPRESSION;
+                       else if (level < 0 || level > Z_BEST_COMPRESSION)
+                               die("bad pack compression level %d", level);
+                       pack_compression_level = level;
+                       continue;
+               }
+               if (!prefixcmp(arg, "--max-pack-size=")) {
+                       pack_size_limit_cfg = 0;
+                       if (!git_parse_ulong(arg+16, &pack_size_limit))
+                               usage(pack_usage);
+                       continue;
+               }
+               if (!prefixcmp(arg, "--window=")) {
+                       char *end;
+                       window = strtoul(arg+9, &end, 0);
+                       if (!arg[9] || *end)
+                               usage(pack_usage);
+                       continue;
+               }
+               if (!prefixcmp(arg, "--window-memory=")) {
+                       if (!git_parse_ulong(arg+16, &window_memory_limit))
+                               usage(pack_usage);
+                       continue;
+               }
+               if (!prefixcmp(arg, "--threads=")) {
+                       char *end;
+                       delta_search_threads = strtoul(arg+10, &end, 0);
+                       if (!arg[10] || *end || delta_search_threads < 0)
+                               usage(pack_usage);
+#ifdef NO_PTHREADS
+                       if (delta_search_threads != 1)
+                               warning("no threads support, "
+                                       "ignoring %s", arg);
+#endif
+                       continue;
+               }
+               if (!prefixcmp(arg, "--depth=")) {
+                       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("--all-progress", arg)) {
+                       progress = 2;
+                       continue;
+               }
+               if (!strcmp("--all-progress-implied", arg)) {
+                       all_progress_implied = 1;
+                       continue;
+               }
+               if (!strcmp("-q", arg)) {
+                       progress = 0;
+                       continue;
+               }
+               if (!strcmp("--no-reuse-delta", arg)) {
+                       reuse_delta = 0;
+                       continue;
+               }
+               if (!strcmp("--no-reuse-object", arg)) {
+                       reuse_object = reuse_delta = 0;
+                       continue;
+               }
+               if (!strcmp("--delta-base-offset", arg)) {
+                       allow_ofs_delta = 1;
+                       continue;
+               }
+               if (!strcmp("--stdout", arg)) {
+                       pack_to_stdout = 1;
+                       continue;
+               }
+               if (!strcmp("--revs", arg)) {
+                       use_internal_rev_list = 1;
+                       continue;
+               }
+               if (!strcmp("--keep-unreachable", arg)) {
+                       keep_unreachable = 1;
+                       continue;
+               }
+               if (!strcmp("--unpack-unreachable", arg)) {
+                       unpack_unreachable = 1;
+                       continue;
+               }
+               if (!strcmp("--include-tag", arg)) {
+                       include_tag = 1;
+                       continue;
+               }
+               if (!strcmp("--unpacked", arg) ||
+                   !strcmp("--reflog", arg) ||
+                   !strcmp("--all", arg)) {
+                       use_internal_rev_list = 1;
+                       if (rp_ac >= rp_ac_alloc - 1) {
+                               rp_ac_alloc = alloc_nr(rp_ac_alloc);
+                               rp_av = xrealloc(rp_av,
+                                                rp_ac_alloc * sizeof(*rp_av));
+                       }
+                       rp_av[rp_ac++] = arg;
+                       continue;
+               }
+               if (!strcmp("--thin", arg)) {
+                       use_internal_rev_list = 1;
+                       thin = 1;
+                       rp_av[1] = "--objects-edge";
+                       continue;
+               }
+               if (!prefixcmp(arg, "--index-version=")) {
+                       char *c;
+                       pack_idx_default_version = strtoul(arg + 16, &c, 10);
+                       if (pack_idx_default_version > 2)
+                               die("bad %s", arg);
+                       if (*c == ',')
+                               pack_idx_off32_limit = strtoul(c+1, &c, 0);
+                       if (*c || pack_idx_off32_limit & 0x80000000)
+                               die("bad %s", arg);
+                       continue;
+               }
+               if (!strcmp(arg, "--keep-true-parents")) {
+                       grafts_replace_parents = 0;
+                       continue;
+               }
+               usage(pack_usage);
+       }
+
+       /* Traditionally "pack-objects [options] base extra" failed;
+        * we would however want to take refs parameter that would
+        * have been given to upstream rev-list ourselves, which means
+        * we somehow want to say what the base name is.  So the
+        * syntax would be:
+        *
+        * pack-objects [options] base <refs...>
+        *
+        * in other words, we would treat the first non-option as the
+        * base_name and send everything else to the internal revision
+        * walker.
+        */
+
+       if (!pack_to_stdout)
+               base_name = argv[i++];
+
+       if (pack_to_stdout != !base_name)
+               usage(pack_usage);
+
+       if (!pack_to_stdout && !pack_size_limit)
+               pack_size_limit = pack_size_limit_cfg;
+       if (pack_to_stdout && pack_size_limit)
+               die("--max-pack-size cannot be used to build a pack for transfer.");
+       if (pack_size_limit && pack_size_limit < 1024*1024) {
+               warning("minimum pack size limit is 1 MiB");
+               pack_size_limit = 1024*1024;
+       }
+
+       if (!pack_to_stdout && thin)
+               die("--thin cannot be used to build an indexable pack.");
+
+       if (keep_unreachable && unpack_unreachable)
+               die("--keep-unreachable and --unpack-unreachable are incompatible.");
+
+       if (progress && all_progress_implied)
+               progress = 2;
+
+       prepare_packed_git();
+
+       if (progress)
+               progress_state = start_progress("Counting objects", 0);
+       if (!use_internal_rev_list)
+               read_object_list_from_stdin();
+       else {
+               rp_av[rp_ac] = NULL;
+               get_object_list(rp_ac, rp_av);
+       }
+       cleanup_preferred_base();
+       if (include_tag && nr_result)
+               for_each_ref(add_ref_tag, NULL);
+       stop_progress(&progress_state);
+
+       if (non_empty && !nr_result)
+               return 0;
+       if (nr_result)
+               prepare_pack(window, depth);
+       write_pack_file();
+       if (progress)
+               fprintf(stderr, "Total %"PRIu32" (delta %"PRIu32"),"
+                       " reused %"PRIu32" (delta %"PRIu32")\n",
+                       written, written_delta, reused, reused_delta);
+       return 0;
+}
diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c
new file mode 100644 (file)
index 0000000..41e1615
--- /dev/null
@@ -0,0 +1,696 @@
+/*
+*
+* Copyright 2005, Lukas Sandstrom <lukass@etek.chalmers.se>
+*
+* This file is licensed under the GPL v2.
+*
+*/
+
+#include "cache.h"
+#include "exec_cmd.h"
+
+#define BLKSIZE 512
+
+static const char pack_redundant_usage[] =
+"git pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>";
+
+static int load_all_packs, verbose, alt_odb;
+
+struct llist_item {
+       struct llist_item *next;
+       const 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;
+
+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,
+                                              const 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,
+                                                  const unsigned char *sha1)
+{
+       return llist_insert(list, list->back, sha1);
+}
+
+static inline struct llist_item *llist_insert_sorted_unique(struct llist *list,
+                       const unsigned char *sha1, struct llist_item *hint)
+{
+       struct llist_item *prev = NULL, *l;
+
+       l = (hint == NULL) ? list->front : hint;
+       while (l) {
+               int cmp = hashcmp(l->sha1, sha1);
+               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 = hashcmp(l->sha1, sha1);
+               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)
+{
+       unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step;
+       const unsigned char *p1_base, *p2_base;
+       struct llist_item *p1_hint = NULL, *p2_hint = NULL;
+
+       p1_base = p1->pack->index_data;
+       p2_base = p2->pack->index_data;
+       p1_base += 256 * 4 + ((p1->pack->index_version < 2) ? 4 : 8);
+       p2_base += 256 * 4 + ((p2->pack->index_version < 2) ? 4 : 8);
+       p1_step = (p1->pack->index_version < 2) ? 24 : 20;
+       p2_step = (p2->pack->index_version < 2) ? 24 : 20;
+
+       while (p1_off < p1->pack->num_objects * p1_step &&
+              p2_off < p2->pack->num_objects * p2_step)
+       {
+               int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off);
+               /* 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 += p1_step;
+                       p2_off += p2_step;
+                       continue;
+               }
+               if (cmp < 0) { /* p1 has the object, p2 doesn't */
+                       p1_off += p1_step;
+               } else { /* p2 has the object, p1 doesn't */
+                       p2_off += p2_step;
+               }
+       }
+}
+
+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;
+       unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step;
+       const unsigned char *p1_base, *p2_base;
+
+       p1_base = p1->index_data;
+       p2_base = p2->index_data;
+       p1_base += 256 * 4 + ((p1->index_version < 2) ? 4 : 8);
+       p2_base += 256 * 4 + ((p2->index_version < 2) ? 4 : 8);
+       p1_step = (p1->index_version < 2) ? 24 : 20;
+       p2_step = (p2->index_version < 2) ? 24 : 20;
+
+       while (p1_off < p1->num_objects * p1_step &&
+              p2_off < p2->num_objects * p2_step)
+       {
+               int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off);
+               /* cmp ~ p1 - p2 */
+               if (cmp == 0) {
+                       ret++;
+                       p1_off += p1_step;
+                       p2_off += p2_step;
+                       continue;
+               }
+               if (cmp < 0) { /* p1 has the object, p2 doesn't */
+                       p1_off += p1_step;
+               } else { /* p2 has the object, p1 doesn't */
+                       p2_off += p2_step;
+               }
+       }
+       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 off_t pack_set_bytecount(struct pack_list *pl)
+{
+       off_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;
+       off_t min_perm_size = 0, 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!");
+
+       /* find the permutation with the smallest size */
+       perm = perm_ok;
+       while (perm) {
+               perm_size = pack_set_bytecount(perm->pl);
+               if (!min_perm_size || 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;
+       unsigned long off = 0, step;
+       const unsigned char *base;
+
+       if (!p->pack_local && !(alt_odb || verbose))
+               return NULL;
+
+       l.pack = p;
+       llist_init(&l.all_objects);
+
+       if (open_pack_index(p))
+               return NULL;
+
+       base = p->index_data;
+       base += 256 * 4 + ((p->index_version < 2) ? 4 : 8);
+       step = (p->index_version < 2) ? 24 : 20;
+       while (off < p->num_objects * step) {
+               llist_insert_back(l.all_objects, base + off);
+               off += step;
+       }
+       /* 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(const char *filename)
+{
+       struct packed_git *p = packed_git;
+
+       if (strlen(filename) < 40)
+               die("Bad pack filename: %s", filename);
+
+       while (p) {
+               if (strstr(p->pack_name, filename))
+                       return add_pack(p);
+               p = p->next;
+       }
+       die("Filename %s not found in packed_git", filename);
+}
+
+static void load_all(void)
+{
+       struct packed_git *p = packed_git;
+
+       while (p) {
+               add_pack(p);
+               p = p->next;
+       }
+}
+
+int cmd_pack_redundant(int argc, const char **argv, const char *prefix)
+{
+       int i;
+       struct pack_list *min, *red, *pl;
+       struct llist *ignore;
+       unsigned char *sha1;
+       char buf[42]; /* 40 byte sha1 + \n + \0 */
+
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage(pack_redundant_usage);
+
+       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!");
+
+       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/builtin/pack-refs.c b/builtin/pack-refs.c
new file mode 100644 (file)
index 0000000..091860b
--- /dev/null
@@ -0,0 +1,21 @@
+#include "cache.h"
+#include "parse-options.h"
+#include "pack-refs.h"
+
+static char const * const pack_refs_usage[] = {
+       "git pack-refs [options]",
+       NULL
+};
+
+int cmd_pack_refs(int argc, const char **argv, const char *prefix)
+{
+       unsigned int flags = PACK_REFS_PRUNE;
+       struct option opts[] = {
+               OPT_BIT(0, "all",   &flags, "pack everything", PACK_REFS_ALL),
+               OPT_BIT(0, "prune", &flags, "prune loose refs (default)", PACK_REFS_PRUNE),
+               OPT_END(),
+       };
+       if (parse_options(argc, argv, prefix, opts, pack_refs_usage, 0))
+               usage_with_options(pack_refs_usage, opts);
+       return pack_refs(flags);
+}
diff --git a/builtin/patch-id.c b/builtin/patch-id.c
new file mode 100644 (file)
index 0000000..5125300
--- /dev/null
@@ -0,0 +1,154 @@
+#include "cache.h"
+#include "exec_cmd.h"
+
+static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c)
+{
+       unsigned char result[20];
+       char name[50];
+
+       if (!patchlen)
+               return;
+
+       git_SHA1_Final(result, c);
+       memcpy(name, sha1_to_hex(id), 41);
+       printf("%s %s\n", sha1_to_hex(result), name);
+       git_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 int scan_hunk_header(const char *p, int *p_before, int *p_after)
+{
+       static const char digits[] = "0123456789";
+       const char *q, *r;
+       int n;
+
+       q = p + 4;
+       n = strspn(q, digits);
+       if (q[n] == ',') {
+               q += n + 1;
+               n = strspn(q, digits);
+       }
+       if (n == 0 || q[n] != ' ' || q[n+1] != '+')
+               return 0;
+
+       r = q + n + 2;
+       n = strspn(r, digits);
+       if (r[n] == ',') {
+               r += n + 1;
+               n = strspn(r, digits);
+       }
+       if (n == 0)
+               return 0;
+
+       *p_before = atoi(q);
+       *p_after = atoi(r);
+       return 1;
+}
+
+int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx)
+{
+       static char line[1000];
+       int patchlen = 0, found_next = 0;
+       int before = -1, after = -1;
+
+       while (fgets(line, sizeof(line), stdin) != NULL) {
+               char *p = line;
+               int len;
+
+               if (!memcmp(line, "diff-tree ", 10))
+                       p += 10;
+               else if (!memcmp(line, "commit ", 7))
+                       p += 7;
+               else if (!memcmp(line, "From ", 5))
+                       p += 5;
+
+               if (!get_sha1_hex(p, next_sha1)) {
+                       found_next = 1;
+                       break;
+               }
+
+               /* Ignore commit comments */
+               if (!patchlen && memcmp(line, "diff ", 5))
+                       continue;
+
+               /* Parsing diff header?  */
+               if (before == -1) {
+                       if (!memcmp(line, "index ", 6))
+                               continue;
+                       else if (!memcmp(line, "--- ", 4))
+                               before = after = 1;
+                       else if (!isalpha(line[0]))
+                               break;
+               }
+
+               /* Looking for a valid hunk header?  */
+               if (before == 0 && after == 0) {
+                       if (!memcmp(line, "@@ -", 4)) {
+                               /* Parse next hunk, but ignore line numbers.  */
+                               scan_hunk_header(line, &before, &after);
+                               continue;
+                       }
+
+                       /* Split at the end of the patch.  */
+                       if (memcmp(line, "diff ", 5))
+                               break;
+
+                       /* Else we're parsing another header.  */
+                       before = after = -1;
+               }
+
+               /* If we get here, we're inside a hunk.  */
+               if (line[0] == '-' || line[0] == ' ')
+                       before--;
+               if (line[0] == '+' || line[0] == ' ')
+                       after--;
+
+               /* Compute the sha without whitespace */
+               len = remove_space(line);
+               patchlen += len;
+               git_SHA1_Update(ctx, line, len);
+       }
+
+       if (!found_next)
+               hashclr(next_sha1);
+
+       return patchlen;
+}
+
+static void generate_id_list(void)
+{
+       unsigned char sha1[20], n[20];
+       git_SHA_CTX ctx;
+       int patchlen;
+
+       git_SHA1_Init(&ctx);
+       hashclr(sha1);
+       while (!feof(stdin)) {
+               patchlen = get_one_patchid(n, &ctx);
+               flush_current_id(patchlen, sha1, &ctx);
+               hashcpy(sha1, n);
+       }
+}
+
+static const char patch_id_usage[] = "git patch-id < patch";
+
+int cmd_patch_id(int argc, const char **argv, const char *prefix)
+{
+       if (argc != 1)
+               usage(patch_id_usage);
+
+       generate_id_list();
+       return 0;
+}
diff --git a/builtin/prune-packed.c b/builtin/prune-packed.c
new file mode 100644 (file)
index 0000000..f9463de
--- /dev/null
@@ -0,0 +1,86 @@
+#include "builtin.h"
+#include "cache.h"
+#include "progress.h"
+#include "parse-options.h"
+
+static const char * const prune_packed_usage[] = {
+       "git prune-packed [-n|--dry-run] [-q|--quiet]",
+       NULL
+};
+
+#define DRY_RUN 01
+#define VERBOSE 02
+
+static struct progress *progress;
+
+static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts)
+{
+       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 (opts & DRY_RUN)
+                       printf("rm -f %s\n", pathname);
+               else
+                       unlink_or_warn(pathname);
+               display_progress(progress, i + 1);
+       }
+       pathname[len] = 0;
+       rmdir(pathname);
+}
+
+void prune_packed_objects(int opts)
+{
+       int i;
+       static char pathname[PATH_MAX];
+       const char *dir = get_object_directory();
+       int len = strlen(dir);
+
+       if (opts == VERBOSE)
+               progress = start_progress_delay("Removing duplicate objects",
+                       256, 95, 2);
+
+       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;
+
+               display_progress(progress, i + 1);
+               sprintf(pathname + len, "%02x/", i);
+               d = opendir(pathname);
+               if (!d)
+                       continue;
+               prune_dir(i, d, pathname, len + 3, opts);
+               closedir(d);
+       }
+       stop_progress(&progress);
+}
+
+int cmd_prune_packed(int argc, const char **argv, const char *prefix)
+{
+       int opts = isatty(2) ? VERBOSE : 0;
+       const struct option prune_packed_options[] = {
+               OPT_BIT('n', "dry-run", &opts, "dry run", DRY_RUN),
+               OPT_NEGBIT('q', "quiet", &opts, "be quiet", VERBOSE),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, prefix, prune_packed_options,
+                            prune_packed_usage, 0);
+
+       prune_packed_objects(opts);
+       return 0;
+}
diff --git a/builtin/prune.c b/builtin/prune.c
new file mode 100644 (file)
index 0000000..99218ba
--- /dev/null
@@ -0,0 +1,165 @@
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "builtin.h"
+#include "reachable.h"
+#include "parse-options.h"
+#include "dir.h"
+
+static const char * const prune_usage[] = {
+       "git prune [-n] [-v] [--expire <time>] [--] [<head>...]",
+       NULL
+};
+static int show_only;
+static int verbose;
+static unsigned long expire;
+
+static int prune_tmp_object(const char *path, const char *filename)
+{
+       const char *fullpath = mkpath("%s/%s", path, filename);
+       struct stat st;
+       if (lstat(fullpath, &st))
+               return error("Could not stat '%s'", fullpath);
+       if (st.st_mtime > expire)
+               return 0;
+       printf("Removing stale temporary file %s\n", fullpath);
+       if (!show_only)
+               unlink_or_warn(fullpath);
+       return 0;
+}
+
+static int prune_object(char *path, const char *filename, const unsigned char *sha1)
+{
+       const char *fullpath = mkpath("%s/%s", path, filename);
+       struct stat st;
+       if (lstat(fullpath, &st))
+               return error("Could not stat '%s'", fullpath);
+       if (st.st_mtime > expire)
+               return 0;
+       if (show_only || verbose) {
+               enum object_type type = sha1_object_info(sha1, NULL);
+               printf("%s %s\n", sha1_to_hex(sha1),
+                      (type > 0) ? typename(type) : "unknown");
+       }
+       if (!show_only)
+               unlink_or_warn(fullpath);
+       return 0;
+}
+
+static int prune_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];
+
+               if (is_dot_or_dotdot(de->d_name))
+                       continue;
+               if (strlen(de->d_name) == 38) {
+                       sprintf(name, "%02x", i);
+                       memcpy(name+2, de->d_name, 39);
+                       if (get_sha1_hex(name, sha1) < 0)
+                               break;
+
+                       /*
+                        * Do we know about this object?
+                        * It must have been reachable
+                        */
+                       if (lookup_object(sha1))
+                               continue;
+
+                       prune_object(path, de->d_name, sha1);
+                       continue;
+               }
+               if (!prefixcmp(de->d_name, "tmp_obj_")) {
+                       prune_tmp_object(path, de->d_name);
+                       continue;
+               }
+               fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
+       }
+       if (!show_only)
+               rmdir(path);
+       closedir(dir);
+       return 0;
+}
+
+static void prune_object_dir(const char *path)
+{
+       int i;
+       for (i = 0; i < 256; i++) {
+               static char dir[4096];
+               sprintf(dir, "%s/%02x", path, i);
+               prune_dir(i, dir);
+       }
+}
+
+/*
+ * Write errors (particularly out of space) can result in
+ * failed temporary packs (and more rarely indexes and other
+ * files beginning with "tmp_") accumulating in the object
+ * and the pack directories.
+ */
+static void remove_temporary_files(const char *path)
+{
+       DIR *dir;
+       struct dirent *de;
+
+       dir = opendir(path);
+       if (!dir) {
+               fprintf(stderr, "Unable to open directory %s\n", path);
+               return;
+       }
+       while ((de = readdir(dir)) != NULL)
+               if (!prefixcmp(de->d_name, "tmp_"))
+                       prune_tmp_object(path, de->d_name);
+       closedir(dir);
+}
+
+int cmd_prune(int argc, const char **argv, const char *prefix)
+{
+       struct rev_info revs;
+       const struct option options[] = {
+               OPT_BOOLEAN('n', "dry-run", &show_only,
+                           "do not remove, show only"),
+               OPT_BOOLEAN('v', "verbose", &verbose, "report pruned objects"),
+               OPT_DATE(0, "expire", &expire,
+                        "expire objects older than <time>"),
+               OPT_END()
+       };
+       char *s;
+
+       expire = ULONG_MAX;
+       save_commit_buffer = 0;
+       read_replace_refs = 0;
+       init_revisions(&revs, prefix);
+
+       argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
+       while (argc--) {
+               unsigned char sha1[20];
+               const char *name = *argv++;
+
+               if (!get_sha1(name, sha1)) {
+                       struct object *object = parse_object(sha1);
+                       if (!object)
+                               die("bad object: %s", name);
+                       add_pending_object(&revs, object, "");
+               }
+               else
+                       die("unrecognized argument: %s", name);
+       }
+       mark_reachable_objects(&revs, 1);
+       prune_object_dir(get_object_directory());
+
+       prune_packed_objects(show_only);
+       remove_temporary_files(get_object_directory());
+       s = xstrdup(mkpath("%s/pack", get_object_directory()));
+       remove_temporary_files(s);
+       free(s);
+       return 0;
+}
diff --git a/builtin/push.c b/builtin/push.c
new file mode 100644 (file)
index 0000000..e655eb7
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+ * "git push"
+ */
+#include "cache.h"
+#include "refs.h"
+#include "run-command.h"
+#include "builtin.h"
+#include "remote.h"
+#include "transport.h"
+#include "parse-options.h"
+
+static const char * const push_usage[] = {
+       "git push [<options>] [<repository> [<refspec>...]]",
+       NULL,
+};
+
+static int thin;
+static int deleterefs;
+static const char *receivepack;
+static int verbosity;
+static int progress;
+
+static const char **refspec;
+static int refspec_nr;
+static int refspec_alloc;
+
+static void add_refspec(const char *ref)
+{
+       refspec_nr++;
+       ALLOC_GROW(refspec, refspec_nr, refspec_alloc);
+       refspec[refspec_nr-1] = ref;
+}
+
+static void set_refspecs(const char **refs, int nr)
+{
+       int i;
+       for (i = 0; i < nr; i++) {
+               const char *ref = refs[i];
+               if (!strcmp("tag", ref)) {
+                       char *tag;
+                       int len;
+                       if (nr <= ++i)
+                               die("tag shorthand without <tag>");
+                       len = strlen(refs[i]) + 11;
+                       if (deleterefs) {
+                               tag = xmalloc(len+1);
+                               strcpy(tag, ":refs/tags/");
+                       } else {
+                               tag = xmalloc(len);
+                               strcpy(tag, "refs/tags/");
+                       }
+                       strcat(tag, refs[i]);
+                       ref = tag;
+               } else if (deleterefs && !strchr(ref, ':')) {
+                       char *delref;
+                       int len = strlen(ref)+1;
+                       delref = xmalloc(len+1);
+                       strcpy(delref, ":");
+                       strcat(delref, ref);
+                       ref = delref;
+               } else if (deleterefs)
+                       die("--delete only accepts plain target ref names");
+               add_refspec(ref);
+       }
+}
+
+static void setup_push_tracking(void)
+{
+       struct strbuf refspec = STRBUF_INIT;
+       struct branch *branch = branch_get(NULL);
+       if (!branch)
+               die("You are not currently on a branch.");
+       if (!branch->merge_nr || !branch->merge)
+               die("The current branch %s is not tracking anything.",
+                   branch->name);
+       if (branch->merge_nr != 1)
+               die("The current branch %s is tracking multiple branches, "
+                   "refusing to push.", branch->name);
+       strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src);
+       add_refspec(refspec.buf);
+}
+
+static void setup_default_push_refspecs(void)
+{
+       switch (push_default) {
+       default:
+       case PUSH_DEFAULT_MATCHING:
+               add_refspec(":");
+               break;
+
+       case PUSH_DEFAULT_TRACKING:
+               setup_push_tracking();
+               break;
+
+       case PUSH_DEFAULT_CURRENT:
+               add_refspec("HEAD");
+               break;
+
+       case PUSH_DEFAULT_NOTHING:
+               die("You didn't specify any refspecs to push, and "
+                   "push.default is \"nothing\".");
+               break;
+       }
+}
+
+static int push_with_options(struct transport *transport, int flags)
+{
+       int err;
+       int nonfastforward;
+
+       transport_set_verbosity(transport, verbosity, progress);
+
+       if (receivepack)
+               transport_set_option(transport,
+                                    TRANS_OPT_RECEIVEPACK, receivepack);
+       if (thin)
+               transport_set_option(transport, TRANS_OPT_THIN, "yes");
+
+       if (verbosity > 0)
+               fprintf(stderr, "Pushing to %s\n", transport->url);
+       err = transport_push(transport, refspec_nr, refspec, flags,
+                            &nonfastforward);
+       if (err != 0)
+               error("failed to push some refs to '%s'", transport->url);
+
+       err |= transport_disconnect(transport);
+
+       if (!err)
+               return 0;
+
+       if (nonfastforward && advice_push_nonfastforward) {
+               fprintf(stderr, "To prevent you from losing history, non-fast-forward updates were rejected\n"
+                               "Merge the remote changes (e.g. 'git pull') before pushing again.  See the\n"
+                               "'Note about fast-forwards' section of 'git push --help' for details.\n");
+       }
+
+       return 1;
+}
+
+static int do_push(const char *repo, int flags)
+{
+       int i, errs;
+       struct remote *remote = remote_get(repo);
+       const char **url;
+       int url_nr;
+
+       if (!remote) {
+               if (repo)
+                       die("bad repository '%s'", repo);
+               die("No destination configured to push to.");
+       }
+
+       if (remote->mirror)
+               flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
+
+       if ((flags & TRANSPORT_PUSH_ALL) && refspec) {
+               if (!strcmp(*refspec, "refs/tags/*"))
+                       return error("--all and --tags are incompatible");
+               return error("--all can't be combined with refspecs");
+       }
+
+       if ((flags & TRANSPORT_PUSH_MIRROR) && refspec) {
+               if (!strcmp(*refspec, "refs/tags/*"))
+                       return error("--mirror and --tags are incompatible");
+               return error("--mirror can't be combined with refspecs");
+       }
+
+       if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) ==
+                               (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) {
+               return error("--all and --mirror are incompatible");
+       }
+
+       if (!refspec && !(flags & TRANSPORT_PUSH_ALL)) {
+               if (remote->push_refspec_nr) {
+                       refspec = remote->push_refspec;
+                       refspec_nr = remote->push_refspec_nr;
+               } else if (!(flags & TRANSPORT_PUSH_MIRROR))
+                       setup_default_push_refspecs();
+       }
+       errs = 0;
+       if (remote->pushurl_nr) {
+               url = remote->pushurl;
+               url_nr = remote->pushurl_nr;
+       } else {
+               url = remote->url;
+               url_nr = remote->url_nr;
+       }
+       if (url_nr) {
+               for (i = 0; i < url_nr; i++) {
+                       struct transport *transport =
+                               transport_get(remote, url[i]);
+                       if (push_with_options(transport, flags))
+                               errs++;
+               }
+       } else {
+               struct transport *transport =
+                       transport_get(remote, NULL);
+
+               if (push_with_options(transport, flags))
+                       errs++;
+       }
+       return !!errs;
+}
+
+int cmd_push(int argc, const char **argv, const char *prefix)
+{
+       int flags = 0;
+       int tags = 0;
+       int rc;
+       const char *repo = NULL;        /* default repository */
+       struct option options[] = {
+               OPT__VERBOSITY(&verbosity),
+               OPT_STRING( 0 , "repo", &repo, "repository", "repository"),
+               OPT_BIT( 0 , "all", &flags, "push all refs", TRANSPORT_PUSH_ALL),
+               OPT_BIT( 0 , "mirror", &flags, "mirror all refs",
+                           (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)),
+               OPT_BOOLEAN( 0, "delete", &deleterefs, "delete refs"),
+               OPT_BOOLEAN( 0 , "tags", &tags, "push tags (can't be used with --all or --mirror)"),
+               OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN),
+               OPT_BIT( 0,  "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN),
+               OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE),
+               OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"),
+               OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"),
+               OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"),
+               OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status",
+                       TRANSPORT_PUSH_SET_UPSTREAM),
+               OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
+               OPT_END()
+       };
+
+       git_config(git_default_config, NULL);
+       argc = parse_options(argc, argv, prefix, options, push_usage, 0);
+
+       if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR))))
+               die("--delete is incompatible with --all, --mirror and --tags");
+       if (deleterefs && argc < 2)
+               die("--delete doesn't make sense without any refs");
+
+       if (tags)
+               add_refspec("refs/tags/*");
+
+       if (argc > 0) {
+               repo = argv[0];
+               set_refspecs(argv + 1, argc - 1);
+       }
+
+       rc = do_push(repo, flags);
+       if (rc == -1)
+               usage_with_options(push_usage, options);
+       else
+               return rc;
+}
diff --git a/builtin/read-tree.c b/builtin/read-tree.c
new file mode 100644 (file)
index 0000000..9ad1e66
--- /dev/null
@@ -0,0 +1,230 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+
+#include "cache.h"
+#include "object.h"
+#include "tree.h"
+#include "tree-walk.h"
+#include "cache-tree.h"
+#include "unpack-trees.h"
+#include "dir.h"
+#include "builtin.h"
+#include "parse-options.h"
+#include "resolve-undo.h"
+
+static int nr_trees;
+static struct tree *trees[MAX_UNPACK_TREES];
+
+static int list_tree(unsigned char *sha1)
+{
+       struct tree *tree;
+
+       if (nr_trees >= MAX_UNPACK_TREES)
+               die("I cannot read more than %d trees", MAX_UNPACK_TREES);
+       tree = parse_tree_indirect(sha1);
+       if (!tree)
+               return -1;
+       trees[nr_trees++] = tree;
+       return 0;
+}
+
+static const char * const read_tree_usage[] = {
+       "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]]",
+       NULL
+};
+
+static int index_output_cb(const struct option *opt, const char *arg,
+                                int unset)
+{
+       set_alternate_index_output(arg);
+       return 0;
+}
+
+static int exclude_per_directory_cb(const struct option *opt, const char *arg,
+                                   int unset)
+{
+       struct dir_struct *dir;
+       struct unpack_trees_options *opts;
+
+       opts = (struct unpack_trees_options *)opt->value;
+
+       if (opts->dir)
+               die("more than one --exclude-per-directory given.");
+
+       dir = xcalloc(1, sizeof(*opts->dir));
+       dir->flags |= DIR_SHOW_IGNORED;
+       dir->exclude_per_dir = arg;
+       opts->dir = dir;
+       /* We do not need to nor want to do read-directory
+        * here; we are merely interested in reusing the
+        * per directory ignore stack mechanism.
+        */
+       return 0;
+}
+
+static void debug_stage(const char *label, struct cache_entry *ce,
+                       struct unpack_trees_options *o)
+{
+       printf("%s ", label);
+       if (!ce)
+               printf("(missing)\n");
+       else if (ce == o->df_conflict_entry)
+               printf("(conflict)\n");
+       else
+               printf("%06o #%d %s %.8s\n",
+                      ce->ce_mode, ce_stage(ce), ce->name,
+                      sha1_to_hex(ce->sha1));
+}
+
+static int debug_merge(struct cache_entry **stages, struct unpack_trees_options *o)
+{
+       int i;
+
+       printf("* %d-way merge\n", o->merge_size);
+       debug_stage("index", stages[0], o);
+       for (i = 1; i <= o->merge_size; i++) {
+               char buf[24];
+               sprintf(buf, "ent#%d", i);
+               debug_stage(buf, stages[i], o);
+       }
+       return 0;
+}
+
+static struct lock_file lock_file;
+
+int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
+{
+       int i, newfd, stage = 0;
+       unsigned char sha1[20];
+       struct tree_desc t[MAX_UNPACK_TREES];
+       struct unpack_trees_options opts;
+       int prefix_set = 0;
+       const struct option read_tree_options[] = {
+               { OPTION_CALLBACK, 0, "index-output", NULL, "FILE",
+                 "write resulting index to <FILE>",
+                 PARSE_OPT_NONEG, index_output_cb },
+               OPT__VERBOSE(&opts.verbose_update),
+               OPT_GROUP("Merging"),
+               OPT_SET_INT('m', NULL, &opts.merge,
+                           "perform a merge in addition to a read", 1),
+               OPT_SET_INT(0, "trivial", &opts.trivial_merges_only,
+                           "3-way merge if no file level merging required", 1),
+               OPT_SET_INT(0, "aggressive", &opts.aggressive,
+                           "3-way merge in presence of adds and removes", 1),
+               OPT_SET_INT(0, "reset", &opts.reset,
+                           "same as -m, but discard unmerged entries", 1),
+               { OPTION_STRING, 0, "prefix", &opts.prefix, "<subdirectory>/",
+                 "read the tree into the index under <subdirectory>/",
+                 PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP },
+               OPT_SET_INT('u', NULL, &opts.update,
+                           "update working tree with merge result", 1),
+               { OPTION_CALLBACK, 0, "exclude-per-directory", &opts,
+                 "gitignore",
+                 "allow explicitly ignored files to be overwritten",
+                 PARSE_OPT_NONEG, exclude_per_directory_cb },
+               OPT_SET_INT('i', NULL, &opts.index_only,
+                           "don't check the working tree after merging", 1),
+               OPT_SET_INT(0, "no-sparse-checkout", &opts.skip_sparse_checkout,
+                           "skip applying sparse checkout filter", 1),
+               OPT_SET_INT(0, "debug-unpack", &opts.debug_unpack,
+                           "debug unpack-trees", 1),
+               OPT_END()
+       };
+
+       memset(&opts, 0, sizeof(opts));
+       opts.head_idx = -1;
+       opts.src_index = &the_index;
+       opts.dst_index = &the_index;
+
+       git_config(git_default_config, NULL);
+
+       argc = parse_options(argc, argv, unused_prefix, read_tree_options,
+                            read_tree_usage, 0);
+
+       newfd = hold_locked_index(&lock_file, 1);
+
+       prefix_set = opts.prefix ? 1 : 0;
+       if (1 < opts.merge + opts.reset + prefix_set)
+               die("Which one? -m, --reset, or --prefix?");
+
+       if (opts.reset || opts.merge || opts.prefix) {
+               if (read_cache_unmerged() && (opts.prefix || opts.merge))
+                       die("You need to resolve your current index first");
+               stage = opts.merge = 1;
+       }
+       resolve_undo_clear();
+
+       for (i = 0; i < argc; i++) {
+               const char *arg = argv[i];
+
+               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 (1 < opts.index_only + opts.update)
+               die("-u and -i at the same time makes no sense");
+       if ((opts.update||opts.index_only) && !opts.merge)
+               die("%s is meaningless without -m, --reset, or --prefix",
+                   opts.update ? "-u" : "-i");
+       if ((opts.dir && !opts.update))
+               die("--exclude-per-directory is meaningless unless -u");
+       if (opts.merge && !opts.index_only)
+               setup_work_tree();
+
+       if (opts.merge) {
+               if (stage < 2)
+                       die("just how do you expect me to merge %d trees?", stage-1);
+               switch (stage - 1) {
+               case 1:
+                       opts.fn = opts.prefix ? bind_merge : oneway_merge;
+                       break;
+               case 2:
+                       opts.fn = twoway_merge;
+                       opts.initial_checkout = is_cache_unborn();
+                       break;
+               case 3:
+               default:
+                       opts.fn = threeway_merge;
+                       break;
+               }
+
+               if (stage - 1 >= 3)
+                       opts.head_idx = stage - 2;
+               else
+                       opts.head_idx = 1;
+       }
+
+       if (opts.debug_unpack)
+               opts.fn = debug_merge;
+
+       cache_tree_free(&active_cache_tree);
+       for (i = 0; i < nr_trees; i++) {
+               struct tree *tree = trees[i];
+               parse_tree(tree);
+               init_tree_desc(t+i, tree->buffer, tree->size);
+       }
+       if (unpack_trees(nr_trees, t, &opts))
+               return 128;
+
+       if (opts.debug_unpack)
+               return 0; /* do not write the index out */
+
+       /*
+        * 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 (nr_trees == 1 && !opts.prefix)
+               prime_cache_tree(&active_cache_tree, trees[0]);
+
+       if (write_cache(newfd, active_cache, active_nr) ||
+           commit_locked_index(&lock_file))
+               die("unable to write new index file");
+       return 0;
+}
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
new file mode 100644 (file)
index 0000000..760817d
--- /dev/null
@@ -0,0 +1,854 @@
+#include "cache.h"
+#include "pack.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include "sideband.h"
+#include "run-command.h"
+#include "exec_cmd.h"
+#include "commit.h"
+#include "object.h"
+#include "remote.h"
+#include "transport.h"
+#include "string-list.h"
+
+static const char receive_pack_usage[] = "git receive-pack <git-dir>";
+
+enum deny_action {
+       DENY_UNCONFIGURED,
+       DENY_IGNORE,
+       DENY_WARN,
+       DENY_REFUSE
+};
+
+static int deny_deletes;
+static int deny_non_fast_forwards;
+static enum deny_action deny_current_branch = DENY_UNCONFIGURED;
+static enum deny_action deny_delete_current = DENY_UNCONFIGURED;
+static int receive_fsck_objects;
+static int receive_unpack_limit = -1;
+static int transfer_unpack_limit = -1;
+static int unpack_limit = 100;
+static int report_status;
+static int use_sideband;
+static int prefer_ofs_delta = 1;
+static int auto_update_server_info;
+static int auto_gc = 1;
+static const char *head_name;
+static int sent_capabilities;
+
+static enum deny_action parse_deny_action(const char *var, const char *value)
+{
+       if (value) {
+               if (!strcasecmp(value, "ignore"))
+                       return DENY_IGNORE;
+               if (!strcasecmp(value, "warn"))
+                       return DENY_WARN;
+               if (!strcasecmp(value, "refuse"))
+                       return DENY_REFUSE;
+       }
+       if (git_config_bool(var, value))
+               return DENY_REFUSE;
+       return DENY_IGNORE;
+}
+
+static int receive_pack_config(const char *var, const char *value, void *cb)
+{
+       if (strcmp(var, "receive.denydeletes") == 0) {
+               deny_deletes = git_config_bool(var, value);
+               return 0;
+       }
+
+       if (strcmp(var, "receive.denynonfastforwards") == 0) {
+               deny_non_fast_forwards = git_config_bool(var, value);
+               return 0;
+       }
+
+       if (strcmp(var, "receive.unpacklimit") == 0) {
+               receive_unpack_limit = git_config_int(var, value);
+               return 0;
+       }
+
+       if (strcmp(var, "transfer.unpacklimit") == 0) {
+               transfer_unpack_limit = git_config_int(var, value);
+               return 0;
+       }
+
+       if (strcmp(var, "receive.fsckobjects") == 0) {
+               receive_fsck_objects = git_config_bool(var, value);
+               return 0;
+       }
+
+       if (!strcmp(var, "receive.denycurrentbranch")) {
+               deny_current_branch = parse_deny_action(var, value);
+               return 0;
+       }
+
+       if (strcmp(var, "receive.denydeletecurrent") == 0) {
+               deny_delete_current = parse_deny_action(var, value);
+               return 0;
+       }
+
+       if (strcmp(var, "repack.usedeltabaseoffset") == 0) {
+               prefer_ofs_delta = git_config_bool(var, value);
+               return 0;
+       }
+
+       if (strcmp(var, "receive.updateserverinfo") == 0) {
+               auto_update_server_info = git_config_bool(var, value);
+               return 0;
+       }
+
+       if (strcmp(var, "receive.autogc") == 0) {
+               auto_gc = git_config_bool(var, value);
+               return 0;
+       }
+
+       return git_default_config(var, value, cb);
+}
+
+static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+{
+       if (sent_capabilities)
+               packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
+       else
+               packet_write(1, "%s %s%c%s%s\n",
+                            sha1_to_hex(sha1), path, 0,
+                            " report-status delete-refs side-band-64k",
+                            prefer_ofs_delta ? " ofs-delta" : "");
+       sent_capabilities = 1;
+       return 0;
+}
+
+static void write_head_info(void)
+{
+       for_each_ref(show_ref, NULL);
+       if (!sent_capabilities)
+               show_ref("capabilities^{}", null_sha1, 0, NULL);
+
+}
+
+struct command {
+       struct command *next;
+       const char *error_string;
+       unsigned int skip_update;
+       unsigned char old_sha1[20];
+       unsigned char new_sha1[20];
+       char ref_name[FLEX_ARRAY]; /* more */
+};
+
+static const char pre_receive_hook[] = "hooks/pre-receive";
+static const char post_receive_hook[] = "hooks/post-receive";
+
+static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2)));
+static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
+
+static void report_message(const char *prefix, const char *err, va_list params)
+{
+       int sz = strlen(prefix);
+       char msg[4096];
+
+       strncpy(msg, prefix, sz);
+       sz += vsnprintf(msg + sz, sizeof(msg) - sz, err, params);
+       if (sz > (sizeof(msg) - 1))
+               sz = sizeof(msg) - 1;
+       msg[sz++] = '\n';
+
+       if (use_sideband)
+               send_sideband(1, 2, msg, sz, use_sideband);
+       else
+               xwrite(2, msg, sz);
+}
+
+static void rp_warning(const char *err, ...)
+{
+       va_list params;
+       va_start(params, err);
+       report_message("warning: ", err, params);
+       va_end(params);
+}
+
+static void rp_error(const char *err, ...)
+{
+       va_list params;
+       va_start(params, err);
+       report_message("error: ", err, params);
+       va_end(params);
+}
+
+static int copy_to_sideband(int in, int out, void *arg)
+{
+       char data[128];
+       while (1) {
+               ssize_t sz = xread(in, data, sizeof(data));
+               if (sz <= 0)
+                       break;
+               send_sideband(1, 2, data, sz, use_sideband);
+       }
+       close(in);
+       return 0;
+}
+
+static int run_receive_hook(struct command *commands, const char *hook_name)
+{
+       static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4];
+       struct command *cmd;
+       struct child_process proc;
+       struct async muxer;
+       const char *argv[2];
+       int have_input = 0, code;
+
+       for (cmd = commands; !have_input && cmd; cmd = cmd->next) {
+               if (!cmd->error_string)
+                       have_input = 1;
+       }
+
+       if (!have_input || access(hook_name, X_OK) < 0)
+               return 0;
+
+       argv[0] = hook_name;
+       argv[1] = NULL;
+
+       memset(&proc, 0, sizeof(proc));
+       proc.argv = argv;
+       proc.in = -1;
+       proc.stdout_to_stderr = 1;
+
+       if (use_sideband) {
+               memset(&muxer, 0, sizeof(muxer));
+               muxer.proc = copy_to_sideband;
+               muxer.in = -1;
+               code = start_async(&muxer);
+               if (code)
+                       return code;
+               proc.err = muxer.in;
+       }
+
+       code = start_command(&proc);
+       if (code) {
+               if (use_sideband)
+                       finish_async(&muxer);
+               return code;
+       }
+
+       for (cmd = commands; cmd; cmd = cmd->next) {
+               if (!cmd->error_string) {
+                       size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n",
+                               sha1_to_hex(cmd->old_sha1),
+                               sha1_to_hex(cmd->new_sha1),
+                               cmd->ref_name);
+                       if (write_in_full(proc.in, buf, n) != n)
+                               break;
+               }
+       }
+       close(proc.in);
+       if (use_sideband)
+               finish_async(&muxer);
+       return finish_command(&proc);
+}
+
+static int run_update_hook(struct command *cmd)
+{
+       static const char update_hook[] = "hooks/update";
+       const char *argv[5];
+       struct child_process proc;
+       int code;
+
+       if (access(update_hook, X_OK) < 0)
+               return 0;
+
+       argv[0] = update_hook;
+       argv[1] = cmd->ref_name;
+       argv[2] = sha1_to_hex(cmd->old_sha1);
+       argv[3] = sha1_to_hex(cmd->new_sha1);
+       argv[4] = NULL;
+
+       memset(&proc, 0, sizeof(proc));
+       proc.no_stdin = 1;
+       proc.stdout_to_stderr = 1;
+       proc.err = use_sideband ? -1 : 0;
+       proc.argv = argv;
+
+       code = start_command(&proc);
+       if (code)
+               return code;
+       if (use_sideband)
+               copy_to_sideband(proc.err, -1, NULL);
+       return finish_command(&proc);
+}
+
+static int is_ref_checked_out(const char *ref)
+{
+       if (is_bare_repository())
+               return 0;
+
+       if (!head_name)
+               return 0;
+       return !strcmp(head_name, ref);
+}
+
+static char *refuse_unconfigured_deny_msg[] = {
+       "By default, updating the current branch in a non-bare repository",
+       "is denied, because it will make the index and work tree inconsistent",
+       "with what you pushed, and will require 'git reset --hard' to match",
+       "the work tree to HEAD.",
+       "",
+       "You can set 'receive.denyCurrentBranch' configuration variable to",
+       "'ignore' or 'warn' in the remote repository to allow pushing into",
+       "its current branch; however, this is not recommended unless you",
+       "arranged to update its work tree to match what you pushed in some",
+       "other way.",
+       "",
+       "To squelch this message and still keep the default behaviour, set",
+       "'receive.denyCurrentBranch' configuration variable to 'refuse'."
+};
+
+static void refuse_unconfigured_deny(void)
+{
+       int i;
+       for (i = 0; i < ARRAY_SIZE(refuse_unconfigured_deny_msg); i++)
+               rp_error("%s", refuse_unconfigured_deny_msg[i]);
+}
+
+static char *refuse_unconfigured_deny_delete_current_msg[] = {
+       "By default, deleting the current branch is denied, because the next",
+       "'git clone' won't result in any file checked out, causing confusion.",
+       "",
+       "You can set 'receive.denyDeleteCurrent' configuration variable to",
+       "'warn' or 'ignore' in the remote repository to allow deleting the",
+       "current branch, with or without a warning message.",
+       "",
+       "To squelch this message, you can set it to 'refuse'."
+};
+
+static void refuse_unconfigured_deny_delete_current(void)
+{
+       int i;
+       for (i = 0;
+            i < ARRAY_SIZE(refuse_unconfigured_deny_delete_current_msg);
+            i++)
+               rp_error("%s", refuse_unconfigured_deny_delete_current_msg[i]);
+}
+
+static const char *update(struct command *cmd)
+{
+       const char *name = cmd->ref_name;
+       unsigned char *old_sha1 = cmd->old_sha1;
+       unsigned char *new_sha1 = cmd->new_sha1;
+       struct ref_lock *lock;
+
+       /* only refs/... are allowed */
+       if (prefixcmp(name, "refs/") || check_ref_format(name + 5)) {
+               rp_error("refusing to create funny ref '%s' remotely", name);
+               return "funny refname";
+       }
+
+       if (is_ref_checked_out(name)) {
+               switch (deny_current_branch) {
+               case DENY_IGNORE:
+                       break;
+               case DENY_WARN:
+                       rp_warning("updating the current branch");
+                       break;
+               case DENY_REFUSE:
+               case DENY_UNCONFIGURED:
+                       rp_error("refusing to update checked out branch: %s", name);
+                       if (deny_current_branch == DENY_UNCONFIGURED)
+                               refuse_unconfigured_deny();
+                       return "branch is currently checked out";
+               }
+       }
+
+       if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) {
+               error("unpack should have generated %s, "
+                     "but I can't find it!", sha1_to_hex(new_sha1));
+               return "bad pack";
+       }
+
+       if (!is_null_sha1(old_sha1) && is_null_sha1(new_sha1)) {
+               if (deny_deletes && !prefixcmp(name, "refs/heads/")) {
+                       rp_error("denying ref deletion for %s", name);
+                       return "deletion prohibited";
+               }
+
+               if (!strcmp(name, head_name)) {
+                       switch (deny_delete_current) {
+                       case DENY_IGNORE:
+                               break;
+                       case DENY_WARN:
+                               rp_warning("deleting the current branch");
+                               break;
+                       case DENY_REFUSE:
+                       case DENY_UNCONFIGURED:
+                               if (deny_delete_current == DENY_UNCONFIGURED)
+                                       refuse_unconfigured_deny_delete_current();
+                               rp_error("refusing to delete the current branch: %s", name);
+                               return "deletion of the current branch prohibited";
+                       }
+               }
+       }
+
+       if (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
+           !is_null_sha1(old_sha1) &&
+           !prefixcmp(name, "refs/heads/")) {
+               struct object *old_object, *new_object;
+               struct commit *old_commit, *new_commit;
+               struct commit_list *bases, *ent;
+
+               old_object = parse_object(old_sha1);
+               new_object = parse_object(new_sha1);
+
+               if (!old_object || !new_object ||
+                   old_object->type != OBJ_COMMIT ||
+                   new_object->type != OBJ_COMMIT) {
+                       error("bad sha1 objects for %s", name);
+                       return "bad ref";
+               }
+               old_commit = (struct commit *)old_object;
+               new_commit = (struct commit *)new_object;
+               bases = get_merge_bases(old_commit, new_commit, 1);
+               for (ent = bases; ent; ent = ent->next)
+                       if (!hashcmp(old_sha1, ent->item->object.sha1))
+                               break;
+               free_commit_list(bases);
+               if (!ent) {
+                       rp_error("denying non-fast-forward %s"
+                                " (you should pull first)", name);
+                       return "non-fast-forward";
+               }
+       }
+       if (run_update_hook(cmd)) {
+               rp_error("hook declined to update %s", name);
+               return "hook declined";
+       }
+
+       if (is_null_sha1(new_sha1)) {
+               if (!parse_object(old_sha1)) {
+                       rp_warning("Allowing deletion of corrupt ref.");
+                       old_sha1 = NULL;
+               }
+               if (delete_ref(name, old_sha1, 0)) {
+                       rp_error("failed to delete %s", name);
+                       return "failed to delete";
+               }
+               return NULL; /* good */
+       }
+       else {
+               lock = lock_any_ref_for_update(name, old_sha1, 0);
+               if (!lock) {
+                       rp_error("failed to lock %s", name);
+                       return "failed to lock";
+               }
+               if (write_ref_sha1(lock, new_sha1, "push")) {
+                       return "failed to write"; /* error() already called */
+               }
+               return NULL; /* good */
+       }
+}
+
+static char update_post_hook[] = "hooks/post-update";
+
+static void run_update_post_hook(struct command *commands)
+{
+       struct command *cmd;
+       int argc;
+       const char **argv;
+       struct child_process proc;
+
+       for (argc = 0, cmd = commands; cmd; cmd = cmd->next) {
+               if (cmd->error_string)
+                       continue;
+               argc++;
+       }
+       if (!argc || access(update_post_hook, X_OK) < 0)
+               return;
+       argv = xmalloc(sizeof(*argv) * (2 + argc));
+       argv[0] = update_post_hook;
+
+       for (argc = 1, cmd = commands; cmd; cmd = cmd->next) {
+               char *p;
+               if (cmd->error_string)
+                       continue;
+               p = xmalloc(strlen(cmd->ref_name) + 1);
+               strcpy(p, cmd->ref_name);
+               argv[argc] = p;
+               argc++;
+       }
+       argv[argc] = NULL;
+
+       memset(&proc, 0, sizeof(proc));
+       proc.no_stdin = 1;
+       proc.stdout_to_stderr = 1;
+       proc.err = use_sideband ? -1 : 0;
+       proc.argv = argv;
+
+       if (!start_command(&proc)) {
+               if (use_sideband)
+                       copy_to_sideband(proc.err, -1, NULL);
+               finish_command(&proc);
+       }
+}
+
+static void check_aliased_update(struct command *cmd, struct string_list *list)
+{
+       struct string_list_item *item;
+       struct command *dst_cmd;
+       unsigned char sha1[20];
+       char cmd_oldh[41], cmd_newh[41], dst_oldh[41], dst_newh[41];
+       int flag;
+
+       const char *dst_name = resolve_ref(cmd->ref_name, sha1, 0, &flag);
+
+       if (!(flag & REF_ISSYMREF))
+               return;
+
+       if ((item = string_list_lookup(list, dst_name)) == NULL)
+               return;
+
+       cmd->skip_update = 1;
+
+       dst_cmd = (struct command *) item->util;
+
+       if (!hashcmp(cmd->old_sha1, dst_cmd->old_sha1) &&
+           !hashcmp(cmd->new_sha1, dst_cmd->new_sha1))
+               return;
+
+       dst_cmd->skip_update = 1;
+
+       strcpy(cmd_oldh, find_unique_abbrev(cmd->old_sha1, DEFAULT_ABBREV));
+       strcpy(cmd_newh, find_unique_abbrev(cmd->new_sha1, DEFAULT_ABBREV));
+       strcpy(dst_oldh, find_unique_abbrev(dst_cmd->old_sha1, DEFAULT_ABBREV));
+       strcpy(dst_newh, find_unique_abbrev(dst_cmd->new_sha1, DEFAULT_ABBREV));
+       rp_error("refusing inconsistent update between symref '%s' (%s..%s) and"
+                " its target '%s' (%s..%s)",
+                cmd->ref_name, cmd_oldh, cmd_newh,
+                dst_cmd->ref_name, dst_oldh, dst_newh);
+
+       cmd->error_string = dst_cmd->error_string =
+               "inconsistent aliased update";
+}
+
+static void check_aliased_updates(struct command *commands)
+{
+       struct command *cmd;
+       struct string_list ref_list = STRING_LIST_INIT_NODUP;
+
+       for (cmd = commands; cmd; cmd = cmd->next) {
+               struct string_list_item *item =
+                       string_list_append(&ref_list, cmd->ref_name);
+               item->util = (void *)cmd;
+       }
+       sort_string_list(&ref_list);
+
+       for (cmd = commands; cmd; cmd = cmd->next)
+               check_aliased_update(cmd, &ref_list);
+
+       string_list_clear(&ref_list, 0);
+}
+
+static void execute_commands(struct command *commands, const char *unpacker_error)
+{
+       struct command *cmd;
+       unsigned char sha1[20];
+
+       if (unpacker_error) {
+               for (cmd = commands; cmd; cmd = cmd->next)
+                       cmd->error_string = "n/a (unpacker error)";
+               return;
+       }
+
+       if (run_receive_hook(commands, pre_receive_hook)) {
+               for (cmd = commands; cmd; cmd = cmd->next)
+                       cmd->error_string = "pre-receive hook declined";
+               return;
+       }
+
+       check_aliased_updates(commands);
+
+       head_name = resolve_ref("HEAD", sha1, 0, NULL);
+
+       for (cmd = commands; cmd; cmd = cmd->next)
+               if (!cmd->skip_update)
+                       cmd->error_string = update(cmd);
+}
+
+static struct command *read_head_info(void)
+{
+       struct command *commands = NULL;
+       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;
+                       if (strstr(refname + reflen + 1, "side-band-64k"))
+                               use_sideband = LARGE_PACKET_MAX;
+               }
+               cmd = xcalloc(1, sizeof(struct command) + len - 80);
+               hashcpy(cmd->old_sha1, old_sha1);
+               hashcpy(cmd->new_sha1, new_sha1);
+               memcpy(cmd->ref_name, line + 82, len - 81);
+               *p = cmd;
+               p = &cmd->next;
+       }
+       return commands;
+}
+
+static const char *parse_pack_header(struct pack_header *hdr)
+{
+       switch (read_pack_header(0, hdr)) {
+       case PH_ERROR_EOF:
+               return "eof before pack header was fully read";
+
+       case PH_ERROR_PACK_SIGNATURE:
+               return "protocol error (pack signature mismatch detected)";
+
+       case PH_ERROR_PROTOCOL:
+               return "protocol error (pack version unsupported)";
+
+       default:
+               return "unknown error in parse_pack_header";
+
+       case 0:
+               return NULL;
+       }
+}
+
+static const char *pack_lockfile;
+
+static const char *unpack(void)
+{
+       struct pack_header hdr;
+       const char *hdr_err;
+       char hdr_arg[38];
+
+       hdr_err = parse_pack_header(&hdr);
+       if (hdr_err)
+               return hdr_err;
+       snprintf(hdr_arg, sizeof(hdr_arg),
+                       "--pack_header=%"PRIu32",%"PRIu32,
+                       ntohl(hdr.hdr_version), ntohl(hdr.hdr_entries));
+
+       if (ntohl(hdr.hdr_entries) < unpack_limit) {
+               int code, i = 0;
+               const char *unpacker[4];
+               unpacker[i++] = "unpack-objects";
+               if (receive_fsck_objects)
+                       unpacker[i++] = "--strict";
+               unpacker[i++] = hdr_arg;
+               unpacker[i++] = NULL;
+               code = run_command_v_opt(unpacker, RUN_GIT_CMD);
+               if (!code)
+                       return NULL;
+               return "unpack-objects abnormal exit";
+       } else {
+               const char *keeper[7];
+               int s, status, i = 0;
+               char keep_arg[256];
+               struct child_process ip;
+
+               s = sprintf(keep_arg, "--keep=receive-pack %"PRIuMAX" on ", (uintmax_t) getpid());
+               if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
+                       strcpy(keep_arg + s, "localhost");
+
+               keeper[i++] = "index-pack";
+               keeper[i++] = "--stdin";
+               if (receive_fsck_objects)
+                       keeper[i++] = "--strict";
+               keeper[i++] = "--fix-thin";
+               keeper[i++] = hdr_arg;
+               keeper[i++] = keep_arg;
+               keeper[i++] = NULL;
+               memset(&ip, 0, sizeof(ip));
+               ip.argv = keeper;
+               ip.out = -1;
+               ip.git_cmd = 1;
+               status = start_command(&ip);
+               if (status) {
+                       return "index-pack fork failed";
+               }
+               pack_lockfile = index_pack_lockfile(ip.out);
+               close(ip.out);
+               status = finish_command(&ip);
+               if (!status) {
+                       reprepare_packed_git();
+                       return NULL;
+               }
+               return "index-pack abnormal exit";
+       }
+}
+
+static void report(struct command *commands, const char *unpack_status)
+{
+       struct command *cmd;
+       struct strbuf buf = STRBUF_INIT;
+
+       packet_buf_write(&buf, "unpack %s\n",
+                        unpack_status ? unpack_status : "ok");
+       for (cmd = commands; cmd; cmd = cmd->next) {
+               if (!cmd->error_string)
+                       packet_buf_write(&buf, "ok %s\n",
+                                        cmd->ref_name);
+               else
+                       packet_buf_write(&buf, "ng %s %s\n",
+                                        cmd->ref_name, cmd->error_string);
+       }
+       packet_buf_flush(&buf);
+
+       if (use_sideband)
+               send_sideband(1, 1, buf.buf, buf.len, use_sideband);
+       else
+               safe_write(1, buf.buf, buf.len);
+       strbuf_release(&buf);
+}
+
+static int delete_only(struct command *commands)
+{
+       struct command *cmd;
+       for (cmd = commands; cmd; cmd = cmd->next) {
+               if (!is_null_sha1(cmd->new_sha1))
+                       return 0;
+       }
+       return 1;
+}
+
+static int add_refs_from_alternate(struct alternate_object_database *e, void *unused)
+{
+       char *other;
+       size_t len;
+       struct remote *remote;
+       struct transport *transport;
+       const struct ref *extra;
+
+       e->name[-1] = '\0';
+       other = xstrdup(make_absolute_path(e->base));
+       e->name[-1] = '/';
+       len = strlen(other);
+
+       while (other[len-1] == '/')
+               other[--len] = '\0';
+       if (len < 8 || memcmp(other + len - 8, "/objects", 8))
+               return 0;
+       /* Is this a git repository with refs? */
+       memcpy(other + len - 8, "/refs", 6);
+       if (!is_directory(other))
+               return 0;
+       other[len - 8] = '\0';
+       remote = remote_get(other);
+       transport = transport_get(remote, other);
+       for (extra = transport_get_remote_refs(transport);
+            extra;
+            extra = extra->next) {
+               add_extra_ref(".have", extra->old_sha1, 0);
+       }
+       transport_disconnect(transport);
+       free(other);
+       return 0;
+}
+
+static void add_alternate_refs(void)
+{
+       foreach_alt_odb(add_refs_from_alternate, NULL);
+}
+
+int cmd_receive_pack(int argc, const char **argv, const char *prefix)
+{
+       int advertise_refs = 0;
+       int stateless_rpc = 0;
+       int i;
+       char *dir = NULL;
+       struct command *commands;
+
+       argv++;
+       for (i = 1; i < argc; i++) {
+               const char *arg = *argv++;
+
+               if (*arg == '-') {
+                       if (!strcmp(arg, "--advertise-refs")) {
+                               advertise_refs = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--stateless-rpc")) {
+                               stateless_rpc = 1;
+                               continue;
+                       }
+
+                       usage(receive_pack_usage);
+               }
+               if (dir)
+                       usage(receive_pack_usage);
+               dir = xstrdup(arg);
+       }
+       if (!dir)
+               usage(receive_pack_usage);
+
+       setup_path();
+
+       if (!enter_repo(dir, 0))
+               die("'%s' does not appear to be a git repository", dir);
+
+       if (is_repository_shallow())
+               die("attempt to push into a shallow repository");
+
+       git_config(receive_pack_config, NULL);
+
+       if (0 <= transfer_unpack_limit)
+               unpack_limit = transfer_unpack_limit;
+       else if (0 <= receive_unpack_limit)
+               unpack_limit = receive_unpack_limit;
+
+       if (advertise_refs || !stateless_rpc) {
+               add_alternate_refs();
+               write_head_info();
+               clear_extra_refs();
+
+               /* EOF */
+               packet_flush(1);
+       }
+       if (advertise_refs)
+               return 0;
+
+       if ((commands = read_head_info()) != NULL) {
+               const char *unpack_status = NULL;
+
+               if (!delete_only(commands))
+                       unpack_status = unpack();
+               execute_commands(commands, unpack_status);
+               if (pack_lockfile)
+                       unlink_or_warn(pack_lockfile);
+               if (report_status)
+                       report(commands, unpack_status);
+               run_receive_hook(commands, post_receive_hook);
+               run_update_post_hook(commands);
+               if (auto_gc) {
+                       const char *argv_gc_auto[] = {
+                               "gc", "--auto", "--quiet", NULL,
+                       };
+                       run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+               }
+               if (auto_update_server_info)
+                       update_server_info(0);
+       }
+       if (use_sideband)
+               packet_flush(1);
+       return 0;
+}
diff --git a/builtin/reflog.c b/builtin/reflog.c
new file mode 100644 (file)
index 0000000..ebf610e
--- /dev/null
@@ -0,0 +1,782 @@
+#include "cache.h"
+#include "builtin.h"
+#include "commit.h"
+#include "refs.h"
+#include "dir.h"
+#include "tree-walk.h"
+#include "diff.h"
+#include "revision.h"
+#include "reachable.h"
+
+/*
+ * reflog expire
+ */
+
+static const char reflog_expire_usage[] =
+"git reflog expire [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
+static const char reflog_delete_usage[] =
+"git reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>...";
+
+static unsigned long default_reflog_expire;
+static unsigned long default_reflog_expire_unreachable;
+
+struct cmd_reflog_expire_cb {
+       struct rev_info revs;
+       int dry_run;
+       int stalefix;
+       int rewrite;
+       int updateref;
+       int verbose;
+       unsigned long expire_total;
+       unsigned long expire_unreachable;
+       int recno;
+};
+
+struct expire_reflog_cb {
+       FILE *newlog;
+       enum {
+               UE_NORMAL,
+               UE_ALWAYS,
+               UE_HEAD
+       } unreachable_expire_kind;
+       struct commit_list *mark_list;
+       unsigned long mark_limit;
+       struct cmd_reflog_expire_cb *cmd;
+       unsigned char last_kept_sha1[20];
+};
+
+struct collected_reflog {
+       unsigned char sha1[20];
+       char reflog[FLEX_ARRAY];
+};
+struct collect_reflog_cb {
+       struct collected_reflog **e;
+       int alloc;
+       int nr;
+};
+
+#define INCOMPLETE     (1u<<10)
+#define STUDYING       (1u<<11)
+#define REACHABLE      (1u<<12)
+
+static int tree_is_complete(const unsigned char *sha1)
+{
+       struct tree_desc desc;
+       struct name_entry entry;
+       int complete;
+       struct tree *tree;
+
+       tree = lookup_tree(sha1);
+       if (!tree)
+               return 0;
+       if (tree->object.flags & SEEN)
+               return 1;
+       if (tree->object.flags & INCOMPLETE)
+               return 0;
+
+       if (!tree->buffer) {
+               enum object_type type;
+               unsigned long size;
+               void *data = read_sha1_file(sha1, &type, &size);
+               if (!data) {
+                       tree->object.flags |= INCOMPLETE;
+                       return 0;
+               }
+               tree->buffer = data;
+               tree->size = size;
+       }
+       init_tree_desc(&desc, tree->buffer, tree->size);
+       complete = 1;
+       while (tree_entry(&desc, &entry)) {
+               if (!has_sha1_file(entry.sha1) ||
+                   (S_ISDIR(entry.mode) && !tree_is_complete(entry.sha1))) {
+                       tree->object.flags |= INCOMPLETE;
+                       complete = 0;
+               }
+       }
+       free(tree->buffer);
+       tree->buffer = NULL;
+
+       if (complete)
+               tree->object.flags |= SEEN;
+       return complete;
+}
+
+static int commit_is_complete(struct commit *commit)
+{
+       struct object_array study;
+       struct object_array found;
+       int is_incomplete = 0;
+       int i;
+
+       /* early return */
+       if (commit->object.flags & SEEN)
+               return 1;
+       if (commit->object.flags & INCOMPLETE)
+               return 0;
+       /*
+        * Find all commits that are reachable and are not marked as
+        * SEEN.  Then make sure the trees and blobs contained are
+        * complete.  After that, mark these commits also as SEEN.
+        * If some of the objects that are needed to complete this
+        * commit are missing, mark this commit as INCOMPLETE.
+        */
+       memset(&study, 0, sizeof(study));
+       memset(&found, 0, sizeof(found));
+       add_object_array(&commit->object, NULL, &study);
+       add_object_array(&commit->object, NULL, &found);
+       commit->object.flags |= STUDYING;
+       while (study.nr) {
+               struct commit *c;
+               struct commit_list *parent;
+
+               c = (struct commit *)study.objects[--study.nr].item;
+               if (!c->object.parsed && !parse_object(c->object.sha1))
+                       c->object.flags |= INCOMPLETE;
+
+               if (c->object.flags & INCOMPLETE) {
+                       is_incomplete = 1;
+                       break;
+               }
+               else if (c->object.flags & SEEN)
+                       continue;
+               for (parent = c->parents; parent; parent = parent->next) {
+                       struct commit *p = parent->item;
+                       if (p->object.flags & STUDYING)
+                               continue;
+                       p->object.flags |= STUDYING;
+                       add_object_array(&p->object, NULL, &study);
+                       add_object_array(&p->object, NULL, &found);
+               }
+       }
+       if (!is_incomplete) {
+               /*
+                * make sure all commits in "found" array have all the
+                * necessary objects.
+                */
+               for (i = 0; i < found.nr; i++) {
+                       struct commit *c =
+                               (struct commit *)found.objects[i].item;
+                       if (!tree_is_complete(c->tree->object.sha1)) {
+                               is_incomplete = 1;
+                               c->object.flags |= INCOMPLETE;
+                       }
+               }
+               if (!is_incomplete) {
+                       /* mark all found commits as complete, iow SEEN */
+                       for (i = 0; i < found.nr; i++)
+                               found.objects[i].item->flags |= SEEN;
+               }
+       }
+       /* clear flags from the objects we traversed */
+       for (i = 0; i < found.nr; i++)
+               found.objects[i].item->flags &= ~STUDYING;
+       if (is_incomplete)
+               commit->object.flags |= INCOMPLETE;
+       else {
+               /*
+                * If we come here, we have (1) traversed the ancestry chain
+                * from the "commit" until we reach SEEN commits (which are
+                * known to be complete), and (2) made sure that the commits
+                * encountered during the above traversal refer to trees that
+                * are complete.  Which means that we know *all* the commits
+                * we have seen during this process are complete.
+                */
+               for (i = 0; i < found.nr; i++)
+                       found.objects[i].item->flags |= SEEN;
+       }
+       /* free object arrays */
+       free(study.objects);
+       free(found.objects);
+       return !is_incomplete;
+}
+
+static int keep_entry(struct commit **it, unsigned char *sha1)
+{
+       struct commit *commit;
+
+       if (is_null_sha1(sha1))
+               return 1;
+       commit = lookup_commit_reference_gently(sha1, 1);
+       if (!commit)
+               return 0;
+
+       /*
+        * Make sure everything in this commit exists.
+        *
+        * We have walked all the objects reachable from the refs
+        * and cache earlier.  The commits reachable by this commit
+        * must meet SEEN commits -- and then we should mark them as
+        * SEEN as well.
+        */
+       if (!commit_is_complete(commit))
+               return 0;
+       *it = commit;
+       return 1;
+}
+
+/*
+ * Starting from commits in the cb->mark_list, mark commits that are
+ * reachable from them.  Stop the traversal at commits older than
+ * the expire_limit and queue them back, so that the caller can call
+ * us again to restart the traversal with longer expire_limit.
+ */
+static void mark_reachable(struct expire_reflog_cb *cb)
+{
+       struct commit *commit;
+       struct commit_list *pending;
+       unsigned long expire_limit = cb->mark_limit;
+       struct commit_list *leftover = NULL;
+
+       for (pending = cb->mark_list; pending; pending = pending->next)
+               pending->item->object.flags &= ~REACHABLE;
+
+       pending = cb->mark_list;
+       while (pending) {
+               struct commit_list *entry = pending;
+               struct commit_list *parent;
+               pending = entry->next;
+               commit = entry->item;
+               free(entry);
+               if (commit->object.flags & REACHABLE)
+                       continue;
+               if (parse_commit(commit))
+                       continue;
+               commit->object.flags |= REACHABLE;
+               if (commit->date < expire_limit) {
+                       commit_list_insert(commit, &leftover);
+                       continue;
+               }
+               commit->object.flags |= REACHABLE;
+               parent = commit->parents;
+               while (parent) {
+                       commit = parent->item;
+                       parent = parent->next;
+                       if (commit->object.flags & REACHABLE)
+                               continue;
+                       commit_list_insert(commit, &pending);
+               }
+       }
+       cb->mark_list = leftover;
+}
+
+static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1)
+{
+       /*
+        * We may or may not have the commit yet - if not, look it
+        * up using the supplied sha1.
+        */
+       if (!commit) {
+               if (is_null_sha1(sha1))
+                       return 0;
+
+               commit = lookup_commit_reference_gently(sha1, 1);
+
+               /* Not a commit -- keep it */
+               if (!commit)
+                       return 0;
+       }
+
+       /* Reachable from the current ref?  Don't prune. */
+       if (commit->object.flags & REACHABLE)
+               return 0;
+
+       if (cb->mark_list && cb->mark_limit) {
+               cb->mark_limit = 0; /* dig down to the root */
+               mark_reachable(cb);
+       }
+
+       return !(commit->object.flags & REACHABLE);
+}
+
+static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+               const char *email, unsigned long timestamp, int tz,
+               const char *message, void *cb_data)
+{
+       struct expire_reflog_cb *cb = cb_data;
+       struct commit *old, *new;
+
+       if (timestamp < cb->cmd->expire_total)
+               goto prune;
+
+       if (cb->cmd->rewrite)
+               osha1 = cb->last_kept_sha1;
+
+       old = new = NULL;
+       if (cb->cmd->stalefix &&
+           (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1)))
+               goto prune;
+
+       if (timestamp < cb->cmd->expire_unreachable) {
+               if (cb->unreachable_expire_kind == UE_ALWAYS)
+                       goto prune;
+               if (unreachable(cb, old, osha1) || unreachable(cb, new, nsha1))
+                       goto prune;
+       }
+
+       if (cb->cmd->recno && --(cb->cmd->recno) == 0)
+               goto prune;
+
+       if (cb->newlog) {
+               char sign = (tz < 0) ? '-' : '+';
+               int zone = (tz < 0) ? (-tz) : tz;
+               fprintf(cb->newlog, "%s %s %s %lu %c%04d\t%s",
+                       sha1_to_hex(osha1), sha1_to_hex(nsha1),
+                       email, timestamp, sign, zone,
+                       message);
+               hashcpy(cb->last_kept_sha1, nsha1);
+       }
+       if (cb->cmd->verbose)
+               printf("keep %s", message);
+       return 0;
+ prune:
+       if (!cb->newlog || cb->cmd->verbose)
+               printf("%sprune %s", cb->newlog ? "" : "would ", message);
+       return 0;
+}
+
+static int push_tip_to_list(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
+{
+       struct commit_list **list = cb_data;
+       struct commit *tip_commit;
+       if (flags & REF_ISSYMREF)
+               return 0;
+       tip_commit = lookup_commit_reference_gently(sha1, 1);
+       if (!tip_commit)
+               return 0;
+       commit_list_insert(tip_commit, list);
+       return 0;
+}
+
+static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
+{
+       struct cmd_reflog_expire_cb *cmd = cb_data;
+       struct expire_reflog_cb cb;
+       struct ref_lock *lock;
+       char *log_file, *newlog_path = NULL;
+       struct commit *tip_commit;
+       struct commit_list *tips;
+       int status = 0;
+
+       memset(&cb, 0, sizeof(cb));
+
+       /*
+        * we take the lock for the ref itself to prevent it from
+        * getting updated.
+        */
+       lock = lock_any_ref_for_update(ref, sha1, 0);
+       if (!lock)
+               return error("cannot lock ref '%s'", ref);
+       log_file = git_pathdup("logs/%s", ref);
+       if (!file_exists(log_file))
+               goto finish;
+       if (!cmd->dry_run) {
+               newlog_path = git_pathdup("logs/%s.lock", ref);
+               cb.newlog = fopen(newlog_path, "w");
+       }
+
+       cb.cmd = cmd;
+
+       if (!cmd->expire_unreachable || !strcmp(ref, "HEAD")) {
+               tip_commit = NULL;
+               cb.unreachable_expire_kind = UE_HEAD;
+       } else {
+               tip_commit = lookup_commit_reference_gently(sha1, 1);
+               if (!tip_commit)
+                       cb.unreachable_expire_kind = UE_ALWAYS;
+               else
+                       cb.unreachable_expire_kind = UE_NORMAL;
+       }
+
+       if (cmd->expire_unreachable <= cmd->expire_total)
+               cb.unreachable_expire_kind = UE_ALWAYS;
+
+       cb.mark_list = NULL;
+       tips = NULL;
+       if (cb.unreachable_expire_kind != UE_ALWAYS) {
+               if (cb.unreachable_expire_kind == UE_HEAD) {
+                       struct commit_list *elem;
+                       for_each_ref(push_tip_to_list, &tips);
+                       for (elem = tips; elem; elem = elem->next)
+                               commit_list_insert(elem->item, &cb.mark_list);
+               } else {
+                       commit_list_insert(tip_commit, &cb.mark_list);
+               }
+               cb.mark_limit = cmd->expire_total;
+               mark_reachable(&cb);
+       }
+
+       for_each_reflog_ent(ref, expire_reflog_ent, &cb);
+
+       if (cb.unreachable_expire_kind != UE_ALWAYS) {
+               if (cb.unreachable_expire_kind == UE_HEAD) {
+                       struct commit_list *elem;
+                       for (elem = tips; elem; elem = elem->next)
+                               clear_commit_marks(tip_commit, REACHABLE);
+                       free_commit_list(tips);
+               } else {
+                       clear_commit_marks(tip_commit, REACHABLE);
+               }
+       }
+ finish:
+       if (cb.newlog) {
+               if (fclose(cb.newlog)) {
+                       status |= error("%s: %s", strerror(errno),
+                                       newlog_path);
+                       unlink(newlog_path);
+               } else if (cmd->updateref &&
+                       (write_in_full(lock->lock_fd,
+                               sha1_to_hex(cb.last_kept_sha1), 40) != 40 ||
+                        write_str_in_full(lock->lock_fd, "\n") != 1 ||
+                        close_ref(lock) < 0)) {
+                       status |= error("Couldn't write %s",
+                               lock->lk->filename);
+                       unlink(newlog_path);
+               } else if (rename(newlog_path, log_file)) {
+                       status |= error("cannot rename %s to %s",
+                                       newlog_path, log_file);
+                       unlink(newlog_path);
+               } else if (cmd->updateref && commit_ref(lock)) {
+                       status |= error("Couldn't set %s", lock->ref_name);
+               } else {
+                       adjust_shared_perm(log_file);
+               }
+       }
+       free(newlog_path);
+       free(log_file);
+       unlock_ref(lock);
+       return status;
+}
+
+static int collect_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
+{
+       struct collected_reflog *e;
+       struct collect_reflog_cb *cb = cb_data;
+       size_t namelen = strlen(ref);
+
+       e = xmalloc(sizeof(*e) + namelen + 1);
+       hashcpy(e->sha1, sha1);
+       memcpy(e->reflog, ref, namelen + 1);
+       ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc);
+       cb->e[cb->nr++] = e;
+       return 0;
+}
+
+static struct reflog_expire_cfg {
+       struct reflog_expire_cfg *next;
+       unsigned long expire_total;
+       unsigned long expire_unreachable;
+       size_t len;
+       char pattern[FLEX_ARRAY];
+} *reflog_expire_cfg, **reflog_expire_cfg_tail;
+
+static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)
+{
+       struct reflog_expire_cfg *ent;
+
+       if (!reflog_expire_cfg_tail)
+               reflog_expire_cfg_tail = &reflog_expire_cfg;
+
+       for (ent = reflog_expire_cfg; ent; ent = ent->next)
+               if (ent->len == len &&
+                   !memcmp(ent->pattern, pattern, len))
+                       return ent;
+
+       ent = xcalloc(1, (sizeof(*ent) + len));
+       memcpy(ent->pattern, pattern, len);
+       ent->len = len;
+       *reflog_expire_cfg_tail = ent;
+       reflog_expire_cfg_tail = &(ent->next);
+       return ent;
+}
+
+static int parse_expire_cfg_value(const char *var, const char *value, unsigned long *expire)
+{
+       if (!value)
+               return config_error_nonbool(var);
+       if (!strcmp(value, "never") || !strcmp(value, "false")) {
+               *expire = 0;
+               return 0;
+       }
+       *expire = approxidate(value);
+       return 0;
+}
+
+/* expiry timer slot */
+#define EXPIRE_TOTAL   01
+#define EXPIRE_UNREACH 02
+
+static int reflog_expire_config(const char *var, const char *value, void *cb)
+{
+       const char *lastdot = strrchr(var, '.');
+       unsigned long expire;
+       int slot;
+       struct reflog_expire_cfg *ent;
+
+       if (!lastdot || prefixcmp(var, "gc."))
+               return git_default_config(var, value, cb);
+
+       if (!strcmp(lastdot, ".reflogexpire")) {
+               slot = EXPIRE_TOTAL;
+               if (parse_expire_cfg_value(var, value, &expire))
+                       return -1;
+       } else if (!strcmp(lastdot, ".reflogexpireunreachable")) {
+               slot = EXPIRE_UNREACH;
+               if (parse_expire_cfg_value(var, value, &expire))
+                       return -1;
+       } else
+               return git_default_config(var, value, cb);
+
+       if (lastdot == var + 2) {
+               switch (slot) {
+               case EXPIRE_TOTAL:
+                       default_reflog_expire = expire;
+                       break;
+               case EXPIRE_UNREACH:
+                       default_reflog_expire_unreachable = expire;
+                       break;
+               }
+               return 0;
+       }
+
+       ent = find_cfg_ent(var + 3, lastdot - (var+3));
+       if (!ent)
+               return -1;
+       switch (slot) {
+       case EXPIRE_TOTAL:
+               ent->expire_total = expire;
+               break;
+       case EXPIRE_UNREACH:
+               ent->expire_unreachable = expire;
+               break;
+       }
+       return 0;
+}
+
+static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, const char *ref)
+{
+       struct reflog_expire_cfg *ent;
+
+       if (slot == (EXPIRE_TOTAL|EXPIRE_UNREACH))
+               return; /* both given explicitly -- nothing to tweak */
+
+       for (ent = reflog_expire_cfg; ent; ent = ent->next) {
+               if (!fnmatch(ent->pattern, ref, 0)) {
+                       if (!(slot & EXPIRE_TOTAL))
+                               cb->expire_total = ent->expire_total;
+                       if (!(slot & EXPIRE_UNREACH))
+                               cb->expire_unreachable = ent->expire_unreachable;
+                       return;
+               }
+       }
+
+       /*
+        * If unconfigured, make stash never expire
+        */
+       if (!strcmp(ref, "refs/stash")) {
+               if (!(slot & EXPIRE_TOTAL))
+                       cb->expire_total = 0;
+               if (!(slot & EXPIRE_UNREACH))
+                       cb->expire_unreachable = 0;
+               return;
+       }
+
+       /* Nothing matched -- use the default value */
+       if (!(slot & EXPIRE_TOTAL))
+               cb->expire_total = default_reflog_expire;
+       if (!(slot & EXPIRE_UNREACH))
+               cb->expire_unreachable = default_reflog_expire_unreachable;
+}
+
+static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
+{
+       struct cmd_reflog_expire_cb cb;
+       unsigned long now = time(NULL);
+       int i, status, do_all;
+       int explicit_expiry = 0;
+
+       default_reflog_expire_unreachable = now - 30 * 24 * 3600;
+       default_reflog_expire = now - 90 * 24 * 3600;
+       git_config(reflog_expire_config, NULL);
+
+       save_commit_buffer = 0;
+       do_all = status = 0;
+       memset(&cb, 0, sizeof(cb));
+
+       cb.expire_total = default_reflog_expire;
+       cb.expire_unreachable = default_reflog_expire_unreachable;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
+                       cb.dry_run = 1;
+               else if (!prefixcmp(arg, "--expire=")) {
+                       cb.expire_total = approxidate(arg + 9);
+                       explicit_expiry |= EXPIRE_TOTAL;
+               }
+               else if (!prefixcmp(arg, "--expire-unreachable=")) {
+                       cb.expire_unreachable = approxidate(arg + 21);
+                       explicit_expiry |= EXPIRE_UNREACH;
+               }
+               else if (!strcmp(arg, "--stale-fix"))
+                       cb.stalefix = 1;
+               else if (!strcmp(arg, "--rewrite"))
+                       cb.rewrite = 1;
+               else if (!strcmp(arg, "--updateref"))
+                       cb.updateref = 1;
+               else if (!strcmp(arg, "--all"))
+                       do_all = 1;
+               else if (!strcmp(arg, "--verbose"))
+                       cb.verbose = 1;
+               else if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               else if (arg[0] == '-')
+                       usage(reflog_expire_usage);
+               else
+                       break;
+       }
+
+       /*
+        * We can trust the commits and objects reachable from refs
+        * even in older repository.  We cannot trust what's reachable
+        * from reflog if the repository was pruned with older git.
+        */
+       if (cb.stalefix) {
+               init_revisions(&cb.revs, prefix);
+               if (cb.verbose)
+                       printf("Marking reachable objects...");
+               mark_reachable_objects(&cb.revs, 0);
+               if (cb.verbose)
+                       putchar('\n');
+       }
+
+       if (do_all) {
+               struct collect_reflog_cb collected;
+               int i;
+
+               memset(&collected, 0, sizeof(collected));
+               for_each_reflog(collect_reflog, &collected);
+               for (i = 0; i < collected.nr; i++) {
+                       struct collected_reflog *e = collected.e[i];
+                       set_reflog_expiry_param(&cb, explicit_expiry, e->reflog);
+                       status |= expire_reflog(e->reflog, e->sha1, 0, &cb);
+                       free(e);
+               }
+               free(collected.e);
+       }
+
+       for (; i < argc; i++) {
+               char *ref;
+               unsigned char sha1[20];
+               if (!dwim_log(argv[i], strlen(argv[i]), sha1, &ref)) {
+                       status |= error("%s points nowhere!", argv[i]);
+                       continue;
+               }
+               set_reflog_expiry_param(&cb, explicit_expiry, ref);
+               status |= expire_reflog(ref, sha1, 0, &cb);
+       }
+       return status;
+}
+
+static int count_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+               const char *email, unsigned long timestamp, int tz,
+               const char *message, void *cb_data)
+{
+       struct cmd_reflog_expire_cb *cb = cb_data;
+       if (!cb->expire_total || timestamp < cb->expire_total)
+               cb->recno++;
+       return 0;
+}
+
+static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
+{
+       struct cmd_reflog_expire_cb cb;
+       int i, status = 0;
+
+       memset(&cb, 0, sizeof(cb));
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
+                       cb.dry_run = 1;
+               else if (!strcmp(arg, "--rewrite"))
+                       cb.rewrite = 1;
+               else if (!strcmp(arg, "--updateref"))
+                       cb.updateref = 1;
+               else if (!strcmp(arg, "--verbose"))
+                       cb.verbose = 1;
+               else if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               else if (arg[0] == '-')
+                       usage(reflog_delete_usage);
+               else
+                       break;
+       }
+
+       if (argc - i < 1)
+               return error("Nothing to delete?");
+
+       for ( ; i < argc; i++) {
+               const char *spec = strstr(argv[i], "@{");
+               unsigned char sha1[20];
+               char *ep, *ref;
+               int recno;
+
+               if (!spec) {
+                       status |= error("Not a reflog: %s", argv[i]);
+                       continue;
+               }
+
+               if (!dwim_log(argv[i], spec - argv[i], sha1, &ref)) {
+                       status |= error("no reflog for '%s'", argv[i]);
+                       continue;
+               }
+
+               recno = strtoul(spec + 2, &ep, 10);
+               if (*ep == '}') {
+                       cb.recno = -recno;
+                       for_each_reflog_ent(ref, count_reflog_ent, &cb);
+               } else {
+                       cb.expire_total = approxidate(spec + 2);
+                       for_each_reflog_ent(ref, count_reflog_ent, &cb);
+                       cb.expire_total = 0;
+               }
+
+               status |= expire_reflog(ref, sha1, 0, &cb);
+               free(ref);
+       }
+       return status;
+}
+
+/*
+ * main "reflog"
+ */
+
+static const char reflog_usage[] =
+"git reflog [ show | expire | delete ]";
+
+int cmd_reflog(int argc, const char **argv, const char *prefix)
+{
+       if (argc > 1 && !strcmp(argv[1], "-h"))
+               usage(reflog_usage);
+
+       /* With no command, we default to showing it. */
+       if (argc < 2 || *argv[1] == '-')
+               return cmd_log_reflog(argc, argv, prefix);
+
+       if (!strcmp(argv[1], "show"))
+               return cmd_log_reflog(argc - 1, argv + 1, prefix);
+
+       if (!strcmp(argv[1], "expire"))
+               return cmd_reflog_expire(argc - 1, argv + 1, prefix);
+
+       if (!strcmp(argv[1], "delete"))
+               return cmd_reflog_delete(argc - 1, argv + 1, prefix);
+
+       /* Not a recognized reflog command..*/
+       usage(reflog_usage);
+}
diff --git a/builtin/remote.c b/builtin/remote.c
new file mode 100644 (file)
index 0000000..48e0a6b
--- /dev/null
@@ -0,0 +1,1549 @@
+#include "cache.h"
+#include "parse-options.h"
+#include "transport.h"
+#include "remote.h"
+#include "string-list.h"
+#include "strbuf.h"
+#include "run-command.h"
+#include "refs.h"
+
+static const char * const builtin_remote_usage[] = {
+       "git remote [-v | --verbose]",
+       "git remote add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>",
+       "git remote rename <old> <new>",
+       "git remote rm <name>",
+       "git remote set-head <name> (-a | -d | <branch>)",
+       "git remote [-v | --verbose] show [-n] <name>",
+       "git remote prune [-n | --dry-run] <name>",
+       "git remote [-v | --verbose] update [-p | --prune] [group | remote]",
+       "git remote set-branches <name> [--add] <branch>...",
+       "git remote set-url <name> <newurl> [<oldurl>]",
+       "git remote set-url --add <name> <newurl>",
+       "git remote set-url --delete <name> <url>",
+       NULL
+};
+
+static const char * const builtin_remote_add_usage[] = {
+       "git remote add [<options>] <name> <url>",
+       NULL
+};
+
+static const char * const builtin_remote_rename_usage[] = {
+       "git remote rename <old> <new>",
+       NULL
+};
+
+static const char * const builtin_remote_rm_usage[] = {
+       "git remote rm <name>",
+       NULL
+};
+
+static const char * const builtin_remote_sethead_usage[] = {
+       "git remote set-head <name> (-a | -d | <branch>])",
+       NULL
+};
+
+static const char * const builtin_remote_setbranches_usage[] = {
+       "git remote set-branches <name> <branch>...",
+       "git remote set-branches --add <name> <branch>...",
+       NULL
+};
+
+static const char * const builtin_remote_show_usage[] = {
+       "git remote show [<options>] <name>",
+       NULL
+};
+
+static const char * const builtin_remote_prune_usage[] = {
+       "git remote prune [<options>] <name>",
+       NULL
+};
+
+static const char * const builtin_remote_update_usage[] = {
+       "git remote update [<options>] [<group> | <remote>]...",
+       NULL
+};
+
+static const char * const builtin_remote_seturl_usage[] = {
+       "git remote set-url [--push] <name> <newurl> [<oldurl>]",
+       "git remote set-url --add <name> <newurl>",
+       "git remote set-url --delete <name> <url>",
+       NULL
+};
+
+#define GET_REF_STATES (1<<0)
+#define GET_HEAD_NAMES (1<<1)
+#define GET_PUSH_REF_STATES (1<<2)
+
+static int verbose;
+
+static int show_all(void);
+static int prune_remote(const char *remote, int dry_run);
+
+static inline int postfixcmp(const char *string, const char *postfix)
+{
+       int len1 = strlen(string), len2 = strlen(postfix);
+       if (len1 < len2)
+               return 1;
+       return strcmp(string + len1 - len2, postfix);
+}
+
+static int opt_parse_track(const struct option *opt, const char *arg, int not)
+{
+       struct string_list *list = opt->value;
+       if (not)
+               string_list_clear(list, 0);
+       else
+               string_list_append(list, arg);
+       return 0;
+}
+
+static int fetch_remote(const char *name)
+{
+       const char *argv[] = { "fetch", name, NULL, NULL };
+       if (verbose) {
+               argv[1] = "-v";
+               argv[2] = name;
+       }
+       printf("Updating %s\n", name);
+       if (run_command_v_opt(argv, RUN_GIT_CMD))
+               return error("Could not fetch %s", name);
+       return 0;
+}
+
+enum {
+       TAGS_UNSET = 0,
+       TAGS_DEFAULT = 1,
+       TAGS_SET = 2
+};
+
+static int add_branch(const char *key, const char *branchname,
+               const char *remotename, int mirror, struct strbuf *tmp)
+{
+       strbuf_reset(tmp);
+       strbuf_addch(tmp, '+');
+       if (mirror)
+               strbuf_addf(tmp, "refs/%s:refs/%s",
+                               branchname, branchname);
+       else
+               strbuf_addf(tmp, "refs/heads/%s:refs/remotes/%s/%s",
+                               branchname, remotename, branchname);
+       return git_config_set_multivar(key, tmp->buf, "^$", 0);
+}
+
+static int add(int argc, const char **argv)
+{
+       int fetch = 0, mirror = 0, fetch_tags = TAGS_DEFAULT;
+       struct string_list track = STRING_LIST_INIT_NODUP;
+       const char *master = NULL;
+       struct remote *remote;
+       struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
+       const char *name, *url;
+       int i;
+
+       struct option options[] = {
+               OPT_BOOLEAN('f', "fetch", &fetch, "fetch the remote branches"),
+               OPT_SET_INT(0, "tags", &fetch_tags,
+                           "import all tags and associated objects when fetching",
+                           TAGS_SET),
+               OPT_SET_INT(0, NULL, &fetch_tags,
+                           "or do not fetch any tag at all (--no-tags)", TAGS_UNSET),
+               OPT_CALLBACK('t', "track", &track, "branch",
+                       "branch(es) to track", opt_parse_track),
+               OPT_STRING('m', "master", &master, "branch", "master branch"),
+               OPT_BOOLEAN(0, "mirror", &mirror, "no separate remotes"),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, NULL, options, builtin_remote_add_usage,
+                            0);
+
+       if (argc < 2)
+               usage_with_options(builtin_remote_add_usage, options);
+
+       name = argv[0];
+       url = argv[1];
+
+       remote = remote_get(name);
+       if (remote && (remote->url_nr > 1 || strcmp(name, remote->url[0]) ||
+                       remote->fetch_refspec_nr))
+               die("remote %s already exists.", name);
+
+       strbuf_addf(&buf2, "refs/heads/test:refs/remotes/%s/test", name);
+       if (!valid_fetch_refspec(buf2.buf))
+               die("'%s' is not a valid remote name", name);
+
+       strbuf_addf(&buf, "remote.%s.url", name);
+       if (git_config_set(buf.buf, url))
+               return 1;
+
+       strbuf_reset(&buf);
+       strbuf_addf(&buf, "remote.%s.fetch", name);
+
+       if (track.nr == 0)
+               string_list_append(&track, "*");
+       for (i = 0; i < track.nr; i++) {
+               if (add_branch(buf.buf, track.items[i].string,
+                               name, mirror, &buf2))
+                       return 1;
+       }
+
+       if (mirror) {
+               strbuf_reset(&buf);
+               strbuf_addf(&buf, "remote.%s.mirror", name);
+               if (git_config_set(buf.buf, "true"))
+                       return 1;
+       }
+
+       if (fetch_tags != TAGS_DEFAULT) {
+               strbuf_reset(&buf);
+               strbuf_addf(&buf, "remote.%s.tagopt", name);
+               if (git_config_set(buf.buf,
+                       fetch_tags == TAGS_SET ? "--tags" : "--no-tags"))
+                       return 1;
+       }
+
+       if (fetch && fetch_remote(name))
+               return 1;
+
+       if (master) {
+               strbuf_reset(&buf);
+               strbuf_addf(&buf, "refs/remotes/%s/HEAD", name);
+
+               strbuf_reset(&buf2);
+               strbuf_addf(&buf2, "refs/remotes/%s/%s", name, master);
+
+               if (create_symref(buf.buf, buf2.buf, "remote add"))
+                       return error("Could not setup master '%s'", master);
+       }
+
+       strbuf_release(&buf);
+       strbuf_release(&buf2);
+       string_list_clear(&track, 0);
+
+       return 0;
+}
+
+struct branch_info {
+       char *remote_name;
+       struct string_list merge;
+       int rebase;
+};
+
+static struct string_list branch_list;
+
+static const char *abbrev_ref(const char *name, const char *prefix)
+{
+       const char *abbrev = skip_prefix(name, prefix);
+       if (abbrev)
+               return abbrev;
+       return name;
+}
+#define abbrev_branch(name) abbrev_ref((name), "refs/heads/")
+
+static int config_read_branches(const char *key, const char *value, void *cb)
+{
+       if (!prefixcmp(key, "branch.")) {
+               const char *orig_key = key;
+               char *name;
+               struct string_list_item *item;
+               struct branch_info *info;
+               enum { REMOTE, MERGE, REBASE } type;
+
+               key += 7;
+               if (!postfixcmp(key, ".remote")) {
+                       name = xstrndup(key, strlen(key) - 7);
+                       type = REMOTE;
+               } else if (!postfixcmp(key, ".merge")) {
+                       name = xstrndup(key, strlen(key) - 6);
+                       type = MERGE;
+               } else if (!postfixcmp(key, ".rebase")) {
+                       name = xstrndup(key, strlen(key) - 7);
+                       type = REBASE;
+               } else
+                       return 0;
+
+               item = string_list_insert(&branch_list, name);
+
+               if (!item->util)
+                       item->util = xcalloc(sizeof(struct branch_info), 1);
+               info = item->util;
+               if (type == REMOTE) {
+                       if (info->remote_name)
+                               warning("more than one %s", orig_key);
+                       info->remote_name = xstrdup(value);
+               } else if (type == MERGE) {
+                       char *space = strchr(value, ' ');
+                       value = abbrev_branch(value);
+                       while (space) {
+                               char *merge;
+                               merge = xstrndup(value, space - value);
+                               string_list_append(&info->merge, merge);
+                               value = abbrev_branch(space + 1);
+                               space = strchr(value, ' ');
+                       }
+                       string_list_append(&info->merge, xstrdup(value));
+               } else
+                       info->rebase = git_config_bool(orig_key, value);
+       }
+       return 0;
+}
+
+static void read_branches(void)
+{
+       if (branch_list.nr)
+               return;
+       git_config(config_read_branches, NULL);
+}
+
+struct ref_states {
+       struct remote *remote;
+       struct string_list new, stale, tracked, heads, push;
+       int queried;
+};
+
+static int get_ref_states(const struct ref *remote_refs, struct ref_states *states)
+{
+       struct ref *fetch_map = NULL, **tail = &fetch_map;
+       struct ref *ref, *stale_refs;
+       int i;
+
+       for (i = 0; i < states->remote->fetch_refspec_nr; i++)
+               if (get_fetch_map(remote_refs, states->remote->fetch + i, &tail, 1))
+                       die("Could not get fetch map for refspec %s",
+                               states->remote->fetch_refspec[i]);
+
+       states->new.strdup_strings = 1;
+       states->tracked.strdup_strings = 1;
+       states->stale.strdup_strings = 1;
+       for (ref = fetch_map; ref; ref = ref->next) {
+               unsigned char sha1[20];
+               if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1))
+                       string_list_append(&states->new, abbrev_branch(ref->name));
+               else
+                       string_list_append(&states->tracked, abbrev_branch(ref->name));
+       }
+       stale_refs = get_stale_heads(states->remote, fetch_map);
+       for (ref = stale_refs; ref; ref = ref->next) {
+               struct string_list_item *item =
+                       string_list_append(&states->stale, abbrev_branch(ref->name));
+               item->util = xstrdup(ref->name);
+       }
+       free_refs(stale_refs);
+       free_refs(fetch_map);
+
+       sort_string_list(&states->new);
+       sort_string_list(&states->tracked);
+       sort_string_list(&states->stale);
+
+       return 0;
+}
+
+struct push_info {
+       char *dest;
+       int forced;
+       enum {
+               PUSH_STATUS_CREATE = 0,
+               PUSH_STATUS_DELETE,
+               PUSH_STATUS_UPTODATE,
+               PUSH_STATUS_FASTFORWARD,
+               PUSH_STATUS_OUTOFDATE,
+               PUSH_STATUS_NOTQUERIED
+       } status;
+};
+
+static int get_push_ref_states(const struct ref *remote_refs,
+       struct ref_states *states)
+{
+       struct remote *remote = states->remote;
+       struct ref *ref, *local_refs, *push_map;
+       if (remote->mirror)
+               return 0;
+
+       local_refs = get_local_heads();
+       push_map = copy_ref_list(remote_refs);
+
+       match_refs(local_refs, &push_map, remote->push_refspec_nr,
+                  remote->push_refspec, MATCH_REFS_NONE);
+
+       states->push.strdup_strings = 1;
+       for (ref = push_map; ref; ref = ref->next) {
+               struct string_list_item *item;
+               struct push_info *info;
+
+               if (!ref->peer_ref)
+                       continue;
+               hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
+
+               item = string_list_append(&states->push,
+                                         abbrev_branch(ref->peer_ref->name));
+               item->util = xcalloc(sizeof(struct push_info), 1);
+               info = item->util;
+               info->forced = ref->force;
+               info->dest = xstrdup(abbrev_branch(ref->name));
+
+               if (is_null_sha1(ref->new_sha1)) {
+                       info->status = PUSH_STATUS_DELETE;
+               } else if (!hashcmp(ref->old_sha1, ref->new_sha1))
+                       info->status = PUSH_STATUS_UPTODATE;
+               else if (is_null_sha1(ref->old_sha1))
+                       info->status = PUSH_STATUS_CREATE;
+               else if (has_sha1_file(ref->old_sha1) &&
+                        ref_newer(ref->new_sha1, ref->old_sha1))
+                       info->status = PUSH_STATUS_FASTFORWARD;
+               else
+                       info->status = PUSH_STATUS_OUTOFDATE;
+       }
+       free_refs(local_refs);
+       free_refs(push_map);
+       return 0;
+}
+
+static int get_push_ref_states_noquery(struct ref_states *states)
+{
+       int i;
+       struct remote *remote = states->remote;
+       struct string_list_item *item;
+       struct push_info *info;
+
+       if (remote->mirror)
+               return 0;
+
+       states->push.strdup_strings = 1;
+       if (!remote->push_refspec_nr) {
+               item = string_list_append(&states->push, "(matching)");
+               info = item->util = xcalloc(sizeof(struct push_info), 1);
+               info->status = PUSH_STATUS_NOTQUERIED;
+               info->dest = xstrdup(item->string);
+       }
+       for (i = 0; i < remote->push_refspec_nr; i++) {
+               struct refspec *spec = remote->push + i;
+               if (spec->matching)
+                       item = string_list_append(&states->push, "(matching)");
+               else if (strlen(spec->src))
+                       item = string_list_append(&states->push, spec->src);
+               else
+                       item = string_list_append(&states->push, "(delete)");
+
+               info = item->util = xcalloc(sizeof(struct push_info), 1);
+               info->forced = spec->force;
+               info->status = PUSH_STATUS_NOTQUERIED;
+               info->dest = xstrdup(spec->dst ? spec->dst : item->string);
+       }
+       return 0;
+}
+
+static int get_head_names(const struct ref *remote_refs, struct ref_states *states)
+{
+       struct ref *ref, *matches;
+       struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
+       struct refspec refspec;
+
+       refspec.force = 0;
+       refspec.pattern = 1;
+       refspec.src = refspec.dst = "refs/heads/*";
+       states->heads.strdup_strings = 1;
+       get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
+       matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
+                                   fetch_map, 1);
+       for (ref = matches; ref; ref = ref->next)
+               string_list_append(&states->heads, abbrev_branch(ref->name));
+
+       free_refs(fetch_map);
+       free_refs(matches);
+
+       return 0;
+}
+
+struct known_remote {
+       struct known_remote *next;
+       struct remote *remote;
+};
+
+struct known_remotes {
+       struct remote *to_delete;
+       struct known_remote *list;
+};
+
+static int add_known_remote(struct remote *remote, void *cb_data)
+{
+       struct known_remotes *all = cb_data;
+       struct known_remote *r;
+
+       if (!strcmp(all->to_delete->name, remote->name))
+               return 0;
+
+       r = xmalloc(sizeof(*r));
+       r->remote = remote;
+       r->next = all->list;
+       all->list = r;
+       return 0;
+}
+
+struct branches_for_remote {
+       struct remote *remote;
+       struct string_list *branches, *skipped;
+       struct known_remotes *keep;
+};
+
+static int add_branch_for_removal(const char *refname,
+       const unsigned char *sha1, int flags, void *cb_data)
+{
+       struct branches_for_remote *branches = cb_data;
+       struct refspec refspec;
+       struct string_list_item *item;
+       struct known_remote *kr;
+
+       memset(&refspec, 0, sizeof(refspec));
+       refspec.dst = (char *)refname;
+       if (remote_find_tracking(branches->remote, &refspec))
+               return 0;
+
+       /* don't delete a branch if another remote also uses it */
+       for (kr = branches->keep->list; kr; kr = kr->next) {
+               memset(&refspec, 0, sizeof(refspec));
+               refspec.dst = (char *)refname;
+               if (!remote_find_tracking(kr->remote, &refspec))
+                       return 0;
+       }
+
+       /* don't delete non-remote refs */
+       if (prefixcmp(refname, "refs/remotes")) {
+               /* advise user how to delete local branches */
+               if (!prefixcmp(refname, "refs/heads/"))
+                       string_list_append(branches->skipped,
+                                          abbrev_branch(refname));
+               /* silently skip over other non-remote refs */
+               return 0;
+       }
+
+       /* make sure that symrefs are deleted */
+       if (flags & REF_ISSYMREF)
+               return unlink(git_path("%s", refname));
+
+       item = string_list_append(branches->branches, refname);
+       item->util = xmalloc(20);
+       hashcpy(item->util, sha1);
+
+       return 0;
+}
+
+struct rename_info {
+       const char *old;
+       const char *new;
+       struct string_list *remote_branches;
+};
+
+static int read_remote_branches(const char *refname,
+       const unsigned char *sha1, int flags, void *cb_data)
+{
+       struct rename_info *rename = cb_data;
+       struct strbuf buf = STRBUF_INIT;
+       struct string_list_item *item;
+       int flag;
+       unsigned char orig_sha1[20];
+       const char *symref;
+
+       strbuf_addf(&buf, "refs/remotes/%s", rename->old);
+       if (!prefixcmp(refname, buf.buf)) {
+               item = string_list_append(rename->remote_branches, xstrdup(refname));
+               symref = resolve_ref(refname, orig_sha1, 1, &flag);
+               if (flag & REF_ISSYMREF)
+                       item->util = xstrdup(symref);
+               else
+                       item->util = NULL;
+       }
+
+       return 0;
+}
+
+static int migrate_file(struct remote *remote)
+{
+       struct strbuf buf = STRBUF_INIT;
+       int i;
+       char *path = NULL;
+
+       strbuf_addf(&buf, "remote.%s.url", remote->name);
+       for (i = 0; i < remote->url_nr; i++)
+               if (git_config_set_multivar(buf.buf, remote->url[i], "^$", 0))
+                       return error("Could not append '%s' to '%s'",
+                                       remote->url[i], buf.buf);
+       strbuf_reset(&buf);
+       strbuf_addf(&buf, "remote.%s.push", remote->name);
+       for (i = 0; i < remote->push_refspec_nr; i++)
+               if (git_config_set_multivar(buf.buf, remote->push_refspec[i], "^$", 0))
+                       return error("Could not append '%s' to '%s'",
+                                       remote->push_refspec[i], buf.buf);
+       strbuf_reset(&buf);
+       strbuf_addf(&buf, "remote.%s.fetch", remote->name);
+       for (i = 0; i < remote->fetch_refspec_nr; i++)
+               if (git_config_set_multivar(buf.buf, remote->fetch_refspec[i], "^$", 0))
+                       return error("Could not append '%s' to '%s'",
+                                       remote->fetch_refspec[i], buf.buf);
+       if (remote->origin == REMOTE_REMOTES)
+               path = git_path("remotes/%s", remote->name);
+       else if (remote->origin == REMOTE_BRANCHES)
+               path = git_path("branches/%s", remote->name);
+       if (path)
+               unlink_or_warn(path);
+       return 0;
+}
+
+static int mv(int argc, const char **argv)
+{
+       struct option options[] = {
+               OPT_END()
+       };
+       struct remote *oldremote, *newremote;
+       struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT;
+       struct string_list remote_branches = STRING_LIST_INIT_NODUP;
+       struct rename_info rename;
+       int i;
+
+       if (argc != 3)
+               usage_with_options(builtin_remote_rename_usage, options);
+
+       rename.old = argv[1];
+       rename.new = argv[2];
+       rename.remote_branches = &remote_branches;
+
+       oldremote = remote_get(rename.old);
+       if (!oldremote)
+               die("No such remote: %s", rename.old);
+
+       if (!strcmp(rename.old, rename.new) && oldremote->origin != REMOTE_CONFIG)
+               return migrate_file(oldremote);
+
+       newremote = remote_get(rename.new);
+       if (newremote && (newremote->url_nr > 1 || newremote->fetch_refspec_nr))
+               die("remote %s already exists.", rename.new);
+
+       strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new);
+       if (!valid_fetch_refspec(buf.buf))
+               die("'%s' is not a valid remote name", rename.new);
+
+       strbuf_reset(&buf);
+       strbuf_addf(&buf, "remote.%s", rename.old);
+       strbuf_addf(&buf2, "remote.%s", rename.new);
+       if (git_config_rename_section(buf.buf, buf2.buf) < 1)
+               return error("Could not rename config section '%s' to '%s'",
+                               buf.buf, buf2.buf);
+
+       strbuf_reset(&buf);
+       strbuf_addf(&buf, "remote.%s.fetch", rename.new);
+       if (git_config_set_multivar(buf.buf, NULL, NULL, 1))
+               return error("Could not remove config section '%s'", buf.buf);
+       for (i = 0; i < oldremote->fetch_refspec_nr; i++) {
+               char *ptr;
+
+               strbuf_reset(&buf2);
+               strbuf_addstr(&buf2, oldremote->fetch_refspec[i]);
+               ptr = strstr(buf2.buf, rename.old);
+               if (ptr)
+                       strbuf_splice(&buf2, ptr-buf2.buf, strlen(rename.old),
+                                       rename.new, strlen(rename.new));
+               if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0))
+                       return error("Could not append '%s'", buf.buf);
+       }
+
+       read_branches();
+       for (i = 0; i < branch_list.nr; i++) {
+               struct string_list_item *item = branch_list.items + i;
+               struct branch_info *info = item->util;
+               if (info->remote_name && !strcmp(info->remote_name, rename.old)) {
+                       strbuf_reset(&buf);
+                       strbuf_addf(&buf, "branch.%s.remote", item->string);
+                       if (git_config_set(buf.buf, rename.new)) {
+                               return error("Could not set '%s'", buf.buf);
+                       }
+               }
+       }
+
+       /*
+        * First remove symrefs, then rename the rest, finally create
+        * the new symrefs.
+        */
+       for_each_ref(read_remote_branches, &rename);
+       for (i = 0; i < remote_branches.nr; i++) {
+               struct string_list_item *item = remote_branches.items + i;
+               int flag = 0;
+               unsigned char sha1[20];
+
+               resolve_ref(item->string, sha1, 1, &flag);
+               if (!(flag & REF_ISSYMREF))
+                       continue;
+               if (delete_ref(item->string, NULL, REF_NODEREF))
+                       die("deleting '%s' failed", item->string);
+       }
+       for (i = 0; i < remote_branches.nr; i++) {
+               struct string_list_item *item = remote_branches.items + i;
+
+               if (item->util)
+                       continue;
+               strbuf_reset(&buf);
+               strbuf_addstr(&buf, item->string);
+               strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old),
+                               rename.new, strlen(rename.new));
+               strbuf_reset(&buf2);
+               strbuf_addf(&buf2, "remote: renamed %s to %s",
+                               item->string, buf.buf);
+               if (rename_ref(item->string, buf.buf, buf2.buf))
+                       die("renaming '%s' failed", item->string);
+       }
+       for (i = 0; i < remote_branches.nr; i++) {
+               struct string_list_item *item = remote_branches.items + i;
+
+               if (!item->util)
+                       continue;
+               strbuf_reset(&buf);
+               strbuf_addstr(&buf, item->string);
+               strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old),
+                               rename.new, strlen(rename.new));
+               strbuf_reset(&buf2);
+               strbuf_addstr(&buf2, item->util);
+               strbuf_splice(&buf2, strlen("refs/remotes/"), strlen(rename.old),
+                               rename.new, strlen(rename.new));
+               strbuf_reset(&buf3);
+               strbuf_addf(&buf3, "remote: renamed %s to %s",
+                               item->string, buf.buf);
+               if (create_symref(buf.buf, buf2.buf, buf3.buf))
+                       die("creating '%s' failed", buf.buf);
+       }
+       return 0;
+}
+
+static int remove_branches(struct string_list *branches)
+{
+       int i, result = 0;
+       for (i = 0; i < branches->nr; i++) {
+               struct string_list_item *item = branches->items + i;
+               const char *refname = item->string;
+               unsigned char *sha1 = item->util;
+
+               if (delete_ref(refname, sha1, 0))
+                       result |= error("Could not remove branch %s", refname);
+       }
+       return result;
+}
+
+static int rm(int argc, const char **argv)
+{
+       struct option options[] = {
+               OPT_END()
+       };
+       struct remote *remote;
+       struct strbuf buf = STRBUF_INIT;
+       struct known_remotes known_remotes = { NULL, NULL };
+       struct string_list branches = STRING_LIST_INIT_DUP;
+       struct string_list skipped = STRING_LIST_INIT_DUP;
+       struct branches_for_remote cb_data;
+       int i, result;
+
+       memset(&cb_data, 0, sizeof(cb_data));
+       cb_data.branches = &branches;
+       cb_data.skipped = &skipped;
+       cb_data.keep = &known_remotes;
+
+       if (argc != 2)
+               usage_with_options(builtin_remote_rm_usage, options);
+
+       remote = remote_get(argv[1]);
+       if (!remote)
+               die("No such remote: %s", argv[1]);
+
+       known_remotes.to_delete = remote;
+       for_each_remote(add_known_remote, &known_remotes);
+
+       strbuf_addf(&buf, "remote.%s", remote->name);
+       if (git_config_rename_section(buf.buf, NULL) < 1)
+               return error("Could not remove config section '%s'", buf.buf);
+
+       read_branches();
+       for (i = 0; i < branch_list.nr; i++) {
+               struct string_list_item *item = branch_list.items + i;
+               struct branch_info *info = item->util;
+               if (info->remote_name && !strcmp(info->remote_name, remote->name)) {
+                       const char *keys[] = { "remote", "merge", NULL }, **k;
+                       for (k = keys; *k; k++) {
+                               strbuf_reset(&buf);
+                               strbuf_addf(&buf, "branch.%s.%s",
+                                               item->string, *k);
+                               if (git_config_set(buf.buf, NULL)) {
+                                       strbuf_release(&buf);
+                                       return -1;
+                               }
+                       }
+               }
+       }
+
+       /*
+        * We cannot just pass a function to for_each_ref() which deletes
+        * the branches one by one, since for_each_ref() relies on cached
+        * refs, which are invalidated when deleting a branch.
+        */
+       cb_data.remote = remote;
+       result = for_each_ref(add_branch_for_removal, &cb_data);
+       strbuf_release(&buf);
+
+       if (!result)
+               result = remove_branches(&branches);
+       string_list_clear(&branches, 1);
+
+       if (skipped.nr) {
+               fprintf(stderr, skipped.nr == 1 ?
+                       "Note: A non-remote branch was not removed; "
+                       "to delete it, use:\n" :
+                       "Note: Non-remote branches were not removed; "
+                       "to delete them, use:\n");
+               for (i = 0; i < skipped.nr; i++)
+                       fprintf(stderr, "  git branch -d %s\n",
+                               skipped.items[i].string);
+       }
+       string_list_clear(&skipped, 0);
+
+       return result;
+}
+
+static void clear_push_info(void *util, const char *string)
+{
+       struct push_info *info = util;
+       free(info->dest);
+       free(info);
+}
+
+static void free_remote_ref_states(struct ref_states *states)
+{
+       string_list_clear(&states->new, 0);
+       string_list_clear(&states->stale, 1);
+       string_list_clear(&states->tracked, 0);
+       string_list_clear(&states->heads, 0);
+       string_list_clear_func(&states->push, clear_push_info);
+}
+
+static int append_ref_to_tracked_list(const char *refname,
+       const unsigned char *sha1, int flags, void *cb_data)
+{
+       struct ref_states *states = cb_data;
+       struct refspec refspec;
+
+       if (flags & REF_ISSYMREF)
+               return 0;
+
+       memset(&refspec, 0, sizeof(refspec));
+       refspec.dst = (char *)refname;
+       if (!remote_find_tracking(states->remote, &refspec))
+               string_list_append(&states->tracked, abbrev_branch(refspec.src));
+
+       return 0;
+}
+
+static int get_remote_ref_states(const char *name,
+                                struct ref_states *states,
+                                int query)
+{
+       struct transport *transport;
+       const struct ref *remote_refs;
+
+       states->remote = remote_get(name);
+       if (!states->remote)
+               return error("No such remote: %s", name);
+
+       read_branches();
+
+       if (query) {
+               transport = transport_get(states->remote, states->remote->url_nr > 0 ?
+                       states->remote->url[0] : NULL);
+               remote_refs = transport_get_remote_refs(transport);
+               transport_disconnect(transport);
+
+               states->queried = 1;
+               if (query & GET_REF_STATES)
+                       get_ref_states(remote_refs, states);
+               if (query & GET_HEAD_NAMES)
+                       get_head_names(remote_refs, states);
+               if (query & GET_PUSH_REF_STATES)
+                       get_push_ref_states(remote_refs, states);
+       } else {
+               for_each_ref(append_ref_to_tracked_list, states);
+               sort_string_list(&states->tracked);
+               get_push_ref_states_noquery(states);
+       }
+
+       return 0;
+}
+
+struct show_info {
+       struct string_list *list;
+       struct ref_states *states;
+       int width, width2;
+       int any_rebase;
+};
+
+static int add_remote_to_show_info(struct string_list_item *item, void *cb_data)
+{
+       struct show_info *info = cb_data;
+       int n = strlen(item->string);
+       if (n > info->width)
+               info->width = n;
+       string_list_insert(info->list, item->string);
+       return 0;
+}
+
+static int show_remote_info_item(struct string_list_item *item, void *cb_data)
+{
+       struct show_info *info = cb_data;
+       struct ref_states *states = info->states;
+       const char *name = item->string;
+
+       if (states->queried) {
+               const char *fmt = "%s";
+               const char *arg = "";
+               if (string_list_has_string(&states->new, name)) {
+                       fmt = " new (next fetch will store in remotes/%s)";
+                       arg = states->remote->name;
+               } else if (string_list_has_string(&states->tracked, name))
+                       arg = " tracked";
+               else if (string_list_has_string(&states->stale, name))
+                       arg = " stale (use 'git remote prune' to remove)";
+               else
+                       arg = " ???";
+               printf("    %-*s", info->width, name);
+               printf(fmt, arg);
+               printf("\n");
+       } else
+               printf("    %s\n", name);
+
+       return 0;
+}
+
+static int add_local_to_show_info(struct string_list_item *branch_item, void *cb_data)
+{
+       struct show_info *show_info = cb_data;
+       struct ref_states *states = show_info->states;
+       struct branch_info *branch_info = branch_item->util;
+       struct string_list_item *item;
+       int n;
+
+       if (!branch_info->merge.nr || !branch_info->remote_name ||
+           strcmp(states->remote->name, branch_info->remote_name))
+               return 0;
+       if ((n = strlen(branch_item->string)) > show_info->width)
+               show_info->width = n;
+       if (branch_info->rebase)
+               show_info->any_rebase = 1;
+
+       item = string_list_insert(show_info->list, branch_item->string);
+       item->util = branch_info;
+
+       return 0;
+}
+
+static int show_local_info_item(struct string_list_item *item, void *cb_data)
+{
+       struct show_info *show_info = cb_data;
+       struct branch_info *branch_info = item->util;
+       struct string_list *merge = &branch_info->merge;
+       const char *also;
+       int i;
+
+       if (branch_info->rebase && branch_info->merge.nr > 1) {
+               error("invalid branch.%s.merge; cannot rebase onto > 1 branch",
+                       item->string);
+               return 0;
+       }
+
+       printf("    %-*s ", show_info->width, item->string);
+       if (branch_info->rebase) {
+               printf("rebases onto remote %s\n", merge->items[0].string);
+               return 0;
+       } else if (show_info->any_rebase) {
+               printf(" merges with remote %s\n", merge->items[0].string);
+               also = "    and with remote";
+       } else {
+               printf("merges with remote %s\n", merge->items[0].string);
+               also = "   and with remote";
+       }
+       for (i = 1; i < merge->nr; i++)
+               printf("    %-*s %s %s\n", show_info->width, "", also,
+                      merge->items[i].string);
+
+       return 0;
+}
+
+static int add_push_to_show_info(struct string_list_item *push_item, void *cb_data)
+{
+       struct show_info *show_info = cb_data;
+       struct push_info *push_info = push_item->util;
+       struct string_list_item *item;
+       int n;
+       if ((n = strlen(push_item->string)) > show_info->width)
+               show_info->width = n;
+       if ((n = strlen(push_info->dest)) > show_info->width2)
+               show_info->width2 = n;
+       item = string_list_append(show_info->list, push_item->string);
+       item->util = push_item->util;
+       return 0;
+}
+
+/*
+ * Sorting comparison for a string list that has push_info
+ * structs in its util field
+ */
+static int cmp_string_with_push(const void *va, const void *vb)
+{
+       const struct string_list_item *a = va;
+       const struct string_list_item *b = vb;
+       const struct push_info *a_push = a->util;
+       const struct push_info *b_push = b->util;
+       int cmp = strcmp(a->string, b->string);
+       return cmp ? cmp : strcmp(a_push->dest, b_push->dest);
+}
+
+static int show_push_info_item(struct string_list_item *item, void *cb_data)
+{
+       struct show_info *show_info = cb_data;
+       struct push_info *push_info = item->util;
+       char *src = item->string, *status = NULL;
+
+       switch (push_info->status) {
+       case PUSH_STATUS_CREATE:
+               status = "create";
+               break;
+       case PUSH_STATUS_DELETE:
+               status = "delete";
+               src = "(none)";
+               break;
+       case PUSH_STATUS_UPTODATE:
+               status = "up to date";
+               break;
+       case PUSH_STATUS_FASTFORWARD:
+               status = "fast-forwardable";
+               break;
+       case PUSH_STATUS_OUTOFDATE:
+               status = "local out of date";
+               break;
+       case PUSH_STATUS_NOTQUERIED:
+               break;
+       }
+       if (status)
+               printf("    %-*s %s to %-*s (%s)\n", show_info->width, src,
+                       push_info->forced ? "forces" : "pushes",
+                       show_info->width2, push_info->dest, status);
+       else
+               printf("    %-*s %s to %s\n", show_info->width, src,
+                       push_info->forced ? "forces" : "pushes",
+                       push_info->dest);
+       return 0;
+}
+
+static int show(int argc, const char **argv)
+{
+       int no_query = 0, result = 0, query_flag = 0;
+       struct option options[] = {
+               OPT_BOOLEAN('n', NULL, &no_query, "do not query remotes"),
+               OPT_END()
+       };
+       struct ref_states states;
+       struct string_list info_list = STRING_LIST_INIT_NODUP;
+       struct show_info info;
+
+       argc = parse_options(argc, argv, NULL, options, builtin_remote_show_usage,
+                            0);
+
+       if (argc < 1)
+               return show_all();
+
+       if (!no_query)
+               query_flag = (GET_REF_STATES | GET_HEAD_NAMES | GET_PUSH_REF_STATES);
+
+       memset(&states, 0, sizeof(states));
+       memset(&info, 0, sizeof(info));
+       info.states = &states;
+       info.list = &info_list;
+       for (; argc; argc--, argv++) {
+               int i;
+               const char **url;
+               int url_nr;
+
+               get_remote_ref_states(*argv, &states, query_flag);
+
+               printf("* remote %s\n", *argv);
+               printf("  Fetch URL: %s\n", states.remote->url_nr > 0 ?
+                       states.remote->url[0] : "(no URL)");
+               if (states.remote->pushurl_nr) {
+                       url = states.remote->pushurl;
+                       url_nr = states.remote->pushurl_nr;
+               } else {
+                       url = states.remote->url;
+                       url_nr = states.remote->url_nr;
+               }
+               for (i=0; i < url_nr; i++)
+                       printf("  Push  URL: %s\n", url[i]);
+               if (!i)
+                       printf("  Push  URL: %s\n", "(no URL)");
+               if (no_query)
+                       printf("  HEAD branch: (not queried)\n");
+               else if (!states.heads.nr)
+                       printf("  HEAD branch: (unknown)\n");
+               else if (states.heads.nr == 1)
+                       printf("  HEAD branch: %s\n", states.heads.items[0].string);
+               else {
+                       printf("  HEAD branch (remote HEAD is ambiguous,"
+                              " may be one of the following):\n");
+                       for (i = 0; i < states.heads.nr; i++)
+                               printf("    %s\n", states.heads.items[i].string);
+               }
+
+               /* remote branch info */
+               info.width = 0;
+               for_each_string_list(&states.new, add_remote_to_show_info, &info);
+               for_each_string_list(&states.tracked, add_remote_to_show_info, &info);
+               for_each_string_list(&states.stale, add_remote_to_show_info, &info);
+               if (info.list->nr)
+                       printf("  Remote branch%s:%s\n",
+                              info.list->nr > 1 ? "es" : "",
+                               no_query ? " (status not queried)" : "");
+               for_each_string_list(info.list, show_remote_info_item, &info);
+               string_list_clear(info.list, 0);
+
+               /* git pull info */
+               info.width = 0;
+               info.any_rebase = 0;
+               for_each_string_list(&branch_list, add_local_to_show_info, &info);
+               if (info.list->nr)
+                       printf("  Local branch%s configured for 'git pull':\n",
+                              info.list->nr > 1 ? "es" : "");
+               for_each_string_list(info.list, show_local_info_item, &info);
+               string_list_clear(info.list, 0);
+
+               /* git push info */
+               if (states.remote->mirror)
+                       printf("  Local refs will be mirrored by 'git push'\n");
+
+               info.width = info.width2 = 0;
+               for_each_string_list(&states.push, add_push_to_show_info, &info);
+               qsort(info.list->items, info.list->nr,
+                       sizeof(*info.list->items), cmp_string_with_push);
+               if (info.list->nr)
+                       printf("  Local ref%s configured for 'git push'%s:\n",
+                               info.list->nr > 1 ? "s" : "",
+                               no_query ? " (status not queried)" : "");
+               for_each_string_list(info.list, show_push_info_item, &info);
+               string_list_clear(info.list, 0);
+
+               free_remote_ref_states(&states);
+       }
+
+       return result;
+}
+
+static int set_head(int argc, const char **argv)
+{
+       int i, opt_a = 0, opt_d = 0, result = 0;
+       struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
+       char *head_name = NULL;
+
+       struct option options[] = {
+               OPT_BOOLEAN('a', "auto", &opt_a,
+                           "set refs/remotes/<name>/HEAD according to remote"),
+               OPT_BOOLEAN('d', "delete", &opt_d,
+                           "delete refs/remotes/<name>/HEAD"),
+               OPT_END()
+       };
+       argc = parse_options(argc, argv, NULL, options, builtin_remote_sethead_usage,
+                            0);
+       if (argc)
+               strbuf_addf(&buf, "refs/remotes/%s/HEAD", argv[0]);
+
+       if (!opt_a && !opt_d && argc == 2) {
+               head_name = xstrdup(argv[1]);
+       } else if (opt_a && !opt_d && argc == 1) {
+               struct ref_states states;
+               memset(&states, 0, sizeof(states));
+               get_remote_ref_states(argv[0], &states, GET_HEAD_NAMES);
+               if (!states.heads.nr)
+                       result |= error("Cannot determine remote HEAD");
+               else if (states.heads.nr > 1) {
+                       result |= error("Multiple remote HEAD branches. "
+                                       "Please choose one explicitly with:");
+                       for (i = 0; i < states.heads.nr; i++)
+                               fprintf(stderr, "  git remote set-head %s %s\n",
+                                       argv[0], states.heads.items[i].string);
+               } else
+                       head_name = xstrdup(states.heads.items[0].string);
+               free_remote_ref_states(&states);
+       } else if (opt_d && !opt_a && argc == 1) {
+               if (delete_ref(buf.buf, NULL, REF_NODEREF))
+                       result |= error("Could not delete %s", buf.buf);
+       } else
+               usage_with_options(builtin_remote_sethead_usage, options);
+
+       if (head_name) {
+               unsigned char sha1[20];
+               strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name);
+               /* make sure it's valid */
+               if (!resolve_ref(buf2.buf, sha1, 1, NULL))
+                       result |= error("Not a valid ref: %s", buf2.buf);
+               else if (create_symref(buf.buf, buf2.buf, "remote set-head"))
+                       result |= error("Could not setup %s", buf.buf);
+               if (opt_a)
+                       printf("%s/HEAD set to %s\n", argv[0], head_name);
+               free(head_name);
+       }
+
+       strbuf_release(&buf);
+       strbuf_release(&buf2);
+       return result;
+}
+
+static int prune(int argc, const char **argv)
+{
+       int dry_run = 0, result = 0;
+       struct option options[] = {
+               OPT__DRY_RUN(&dry_run),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, NULL, options, builtin_remote_prune_usage,
+                            0);
+
+       if (argc < 1)
+               usage_with_options(builtin_remote_prune_usage, options);
+
+       for (; argc; argc--, argv++)
+               result |= prune_remote(*argv, dry_run);
+
+       return result;
+}
+
+static int prune_remote(const char *remote, int dry_run)
+{
+       int result = 0, i;
+       struct ref_states states;
+       const char *dangling_msg = dry_run
+               ? " %s will become dangling!\n"
+               : " %s has become dangling!\n";
+
+       memset(&states, 0, sizeof(states));
+       get_remote_ref_states(remote, &states, GET_REF_STATES);
+
+       if (states.stale.nr) {
+               printf("Pruning %s\n", remote);
+               printf("URL: %s\n",
+                      states.remote->url_nr
+                      ? states.remote->url[0]
+                      : "(no URL)");
+       }
+
+       for (i = 0; i < states.stale.nr; i++) {
+               const char *refname = states.stale.items[i].util;
+
+               if (!dry_run)
+                       result |= delete_ref(refname, NULL, 0);
+
+               printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
+                      abbrev_ref(refname, "refs/remotes/"));
+               warn_dangling_symref(stdout, dangling_msg, refname);
+       }
+
+       free_remote_ref_states(&states);
+       return result;
+}
+
+static int get_remote_default(const char *key, const char *value, void *priv)
+{
+       if (strcmp(key, "remotes.default") == 0) {
+               int *found = priv;
+               *found = 1;
+       }
+       return 0;
+}
+
+static int update(int argc, const char **argv)
+{
+       int i, prune = 0;
+       struct option options[] = {
+               OPT_BOOLEAN('p', "prune", &prune,
+                           "prune remotes after fetching"),
+               OPT_END()
+       };
+       const char **fetch_argv;
+       int fetch_argc = 0;
+       int default_defined = 0;
+
+       fetch_argv = xmalloc(sizeof(char *) * (argc+5));
+
+       argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage,
+                            PARSE_OPT_KEEP_ARGV0);
+
+       fetch_argv[fetch_argc++] = "fetch";
+
+       if (prune)
+               fetch_argv[fetch_argc++] = "--prune";
+       if (verbose)
+               fetch_argv[fetch_argc++] = "-v";
+       fetch_argv[fetch_argc++] = "--multiple";
+       if (argc < 2)
+               fetch_argv[fetch_argc++] = "default";
+       for (i = 1; i < argc; i++)
+               fetch_argv[fetch_argc++] = argv[i];
+
+       if (strcmp(fetch_argv[fetch_argc-1], "default") == 0) {
+               git_config(get_remote_default, &default_defined);
+               if (!default_defined)
+                       fetch_argv[fetch_argc-1] = "--all";
+       }
+
+       fetch_argv[fetch_argc] = NULL;
+
+       return run_command_v_opt(fetch_argv, RUN_GIT_CMD);
+}
+
+static int remove_all_fetch_refspecs(const char *remote, const char *key)
+{
+       return git_config_set_multivar(key, NULL, NULL, 1);
+}
+
+static int add_branches(struct remote *remote, const char **branches,
+                       const char *key)
+{
+       const char *remotename = remote->name;
+       int mirror = remote->mirror;
+       struct strbuf refspec = STRBUF_INIT;
+
+       for (; *branches; branches++)
+               if (add_branch(key, *branches, remotename, mirror, &refspec)) {
+                       strbuf_release(&refspec);
+                       return 1;
+               }
+
+       strbuf_release(&refspec);
+       return 0;
+}
+
+static int set_remote_branches(const char *remotename, const char **branches,
+                               int add_mode)
+{
+       struct strbuf key = STRBUF_INIT;
+       struct remote *remote;
+
+       strbuf_addf(&key, "remote.%s.fetch", remotename);
+
+       if (!remote_is_configured(remotename))
+               die("No such remote '%s'", remotename);
+       remote = remote_get(remotename);
+
+       if (!add_mode && remove_all_fetch_refspecs(remotename, key.buf)) {
+               strbuf_release(&key);
+               return 1;
+       }
+       if (add_branches(remote, branches, key.buf)) {
+               strbuf_release(&key);
+               return 1;
+       }
+
+       strbuf_release(&key);
+       return 0;
+}
+
+static int set_branches(int argc, const char **argv)
+{
+       int add_mode = 0;
+       struct option options[] = {
+               OPT_BOOLEAN('\0', "add", &add_mode, "add branch"),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, NULL, options,
+                            builtin_remote_setbranches_usage, 0);
+       if (argc == 0) {
+               error("no remote specified");
+               usage_with_options(builtin_remote_seturl_usage, options);
+       }
+       argv[argc] = NULL;
+
+       return set_remote_branches(argv[0], argv + 1, add_mode);
+}
+
+static int set_url(int argc, const char **argv)
+{
+       int i, push_mode = 0, add_mode = 0, delete_mode = 0;
+       int matches = 0, negative_matches = 0;
+       const char *remotename = NULL;
+       const char *newurl = NULL;
+       const char *oldurl = NULL;
+       struct remote *remote;
+       regex_t old_regex;
+       const char **urlset;
+       int urlset_nr;
+       struct strbuf name_buf = STRBUF_INIT;
+       struct option options[] = {
+               OPT_BOOLEAN('\0', "push", &push_mode,
+                           "manipulate push URLs"),
+               OPT_BOOLEAN('\0', "add", &add_mode,
+                           "add URL"),
+               OPT_BOOLEAN('\0', "delete", &delete_mode,
+                           "delete URLs"),
+               OPT_END()
+       };
+       argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage,
+                            PARSE_OPT_KEEP_ARGV0);
+
+       if (add_mode && delete_mode)
+               die("--add --delete doesn't make sense");
+
+       if (argc < 3 || argc > 4 || ((add_mode || delete_mode) && argc != 3))
+               usage_with_options(builtin_remote_seturl_usage, options);
+
+       remotename = argv[1];
+       newurl = argv[2];
+       if (argc > 3)
+               oldurl = argv[3];
+
+       if (delete_mode)
+               oldurl = newurl;
+
+       if (!remote_is_configured(remotename))
+               die("No such remote '%s'", remotename);
+       remote = remote_get(remotename);
+
+       if (push_mode) {
+               strbuf_addf(&name_buf, "remote.%s.pushurl", remotename);
+               urlset = remote->pushurl;
+               urlset_nr = remote->pushurl_nr;
+       } else {
+               strbuf_addf(&name_buf, "remote.%s.url", remotename);
+               urlset = remote->url;
+               urlset_nr = remote->url_nr;
+       }
+
+       /* Special cases that add new entry. */
+       if ((!oldurl && !delete_mode) || add_mode) {
+               if (add_mode)
+                       git_config_set_multivar(name_buf.buf, newurl,
+                               "^$", 0);
+               else
+                       git_config_set(name_buf.buf, newurl);
+               strbuf_release(&name_buf);
+               return 0;
+       }
+
+       /* Old URL specified. Demand that one matches. */
+       if (regcomp(&old_regex, oldurl, REG_EXTENDED))
+               die("Invalid old URL pattern: %s", oldurl);
+
+       for (i = 0; i < urlset_nr; i++)
+               if (!regexec(&old_regex, urlset[i], 0, NULL, 0))
+                       matches++;
+               else
+                       negative_matches++;
+       if (!delete_mode && !matches)
+               die("No such URL found: %s", oldurl);
+       if (delete_mode && !negative_matches && !push_mode)
+               die("Will not delete all non-push URLs");
+
+       regfree(&old_regex);
+
+       if (!delete_mode)
+               git_config_set_multivar(name_buf.buf, newurl, oldurl, 0);
+       else
+               git_config_set_multivar(name_buf.buf, NULL, oldurl, 1);
+       return 0;
+}
+
+static int get_one_entry(struct remote *remote, void *priv)
+{
+       struct string_list *list = priv;
+       struct strbuf url_buf = STRBUF_INIT;
+       const char **url;
+       int i, url_nr;
+
+       if (remote->url_nr > 0) {
+               strbuf_addf(&url_buf, "%s (fetch)", remote->url[0]);
+               string_list_append(list, remote->name)->util =
+                               strbuf_detach(&url_buf, NULL);
+       } else
+               string_list_append(list, remote->name)->util = NULL;
+       if (remote->pushurl_nr) {
+               url = remote->pushurl;
+               url_nr = remote->pushurl_nr;
+       } else {
+               url = remote->url;
+               url_nr = remote->url_nr;
+       }
+       for (i = 0; i < url_nr; i++)
+       {
+               strbuf_addf(&url_buf, "%s (push)", url[i]);
+               string_list_append(list, remote->name)->util =
+                               strbuf_detach(&url_buf, NULL);
+       }
+
+       return 0;
+}
+
+static int show_all(void)
+{
+       struct string_list list = STRING_LIST_INIT_NODUP;
+       int result;
+
+       list.strdup_strings = 1;
+       result = for_each_remote(get_one_entry, &list);
+
+       if (!result) {
+               int i;
+
+               sort_string_list(&list);
+               for (i = 0; i < list.nr; i++) {
+                       struct string_list_item *item = list.items + i;
+                       if (verbose)
+                               printf("%s\t%s\n", item->string,
+                                       item->util ? (const char *)item->util : "");
+                       else {
+                               if (i && !strcmp((item - 1)->string, item->string))
+                                       continue;
+                               printf("%s\n", item->string);
+                       }
+               }
+       }
+       string_list_clear(&list, 1);
+       return result;
+}
+
+int cmd_remote(int argc, const char **argv, const char *prefix)
+{
+       struct option options[] = {
+               OPT_BOOLEAN('v', "verbose", &verbose, "be verbose; must be placed before a subcommand"),
+               OPT_END()
+       };
+       int result;
+
+       argc = parse_options(argc, argv, prefix, options, builtin_remote_usage,
+               PARSE_OPT_STOP_AT_NON_OPTION);
+
+       if (argc < 1)
+               result = show_all();
+       else if (!strcmp(argv[0], "add"))
+               result = add(argc, argv);
+       else if (!strcmp(argv[0], "rename"))
+               result = mv(argc, argv);
+       else if (!strcmp(argv[0], "rm"))
+               result = rm(argc, argv);
+       else if (!strcmp(argv[0], "set-head"))
+               result = set_head(argc, argv);
+       else if (!strcmp(argv[0], "set-branches"))
+               result = set_branches(argc, argv);
+       else if (!strcmp(argv[0], "set-url"))
+               result = set_url(argc, argv);
+       else if (!strcmp(argv[0], "show"))
+               result = show(argc, argv);
+       else if (!strcmp(argv[0], "prune"))
+               result = prune(argc, argv);
+       else if (!strcmp(argv[0], "update"))
+               result = update(argc, argv);
+       else {
+               error("Unknown subcommand: %s", argv[0]);
+               usage_with_options(builtin_remote_usage, options);
+       }
+
+       return result ? 1 : 0;
+}
diff --git a/builtin/replace.c b/builtin/replace.c
new file mode 100644 (file)
index 0000000..fe3a647
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * Builtin "git replace"
+ *
+ * Copyright (c) 2008 Christian Couder <chriscool@tuxfamily.org>
+ *
+ * Based on builtin-tag.c by Kristian Høgsberg <krh@redhat.com>
+ * and Carlos Rica <jasampler@gmail.com> that was itself based on
+ * git-tag.sh and mktag.c by Linus Torvalds.
+ */
+
+#include "cache.h"
+#include "builtin.h"
+#include "refs.h"
+#include "parse-options.h"
+
+static const char * const git_replace_usage[] = {
+       "git replace [-f] <object> <replacement>",
+       "git replace -d <object>...",
+       "git replace -l [<pattern>]",
+       NULL
+};
+
+static int show_reference(const char *refname, const unsigned char *sha1,
+                         int flag, void *cb_data)
+{
+       const char *pattern = cb_data;
+
+       if (!fnmatch(pattern, refname, 0))
+               printf("%s\n", refname);
+
+       return 0;
+}
+
+static int list_replace_refs(const char *pattern)
+{
+       if (pattern == NULL)
+               pattern = "*";
+
+       for_each_replace_ref(show_reference, (void *) pattern);
+
+       return 0;
+}
+
+typedef int (*each_replace_name_fn)(const char *name, const char *ref,
+                                   const unsigned char *sha1);
+
+static int for_each_replace_name(const char **argv, each_replace_name_fn fn)
+{
+       const char **p;
+       char ref[PATH_MAX];
+       int had_error = 0;
+       unsigned char sha1[20];
+
+       for (p = argv; *p; p++) {
+               if (snprintf(ref, sizeof(ref), "refs/replace/%s", *p)
+                                       >= sizeof(ref)) {
+                       error("replace ref name too long: %.*s...", 50, *p);
+                       had_error = 1;
+                       continue;
+               }
+               if (!resolve_ref(ref, sha1, 1, NULL)) {
+                       error("replace ref '%s' not found.", *p);
+                       had_error = 1;
+                       continue;
+               }
+               if (fn(*p, ref, sha1))
+                       had_error = 1;
+       }
+       return had_error;
+}
+
+static int delete_replace_ref(const char *name, const char *ref,
+                             const unsigned char *sha1)
+{
+       if (delete_ref(ref, sha1, 0))
+               return 1;
+       printf("Deleted replace ref '%s'\n", name);
+       return 0;
+}
+
+static int replace_object(const char *object_ref, const char *replace_ref,
+                         int force)
+{
+       unsigned char object[20], prev[20], repl[20];
+       char ref[PATH_MAX];
+       struct ref_lock *lock;
+
+       if (get_sha1(object_ref, object))
+               die("Failed to resolve '%s' as a valid ref.", object_ref);
+       if (get_sha1(replace_ref, repl))
+               die("Failed to resolve '%s' as a valid ref.", replace_ref);
+
+       if (snprintf(ref, sizeof(ref),
+                    "refs/replace/%s",
+                    sha1_to_hex(object)) > sizeof(ref) - 1)
+               die("replace ref name too long: %.*s...", 50, ref);
+       if (check_ref_format(ref))
+               die("'%s' is not a valid ref name.", ref);
+
+       if (!resolve_ref(ref, prev, 1, NULL))
+               hashclr(prev);
+       else if (!force)
+               die("replace ref '%s' already exists", ref);
+
+       lock = lock_any_ref_for_update(ref, prev, 0);
+       if (!lock)
+               die("%s: cannot lock the ref", ref);
+       if (write_ref_sha1(lock, repl, NULL) < 0)
+               die("%s: cannot update the ref", ref);
+
+       return 0;
+}
+
+int cmd_replace(int argc, const char **argv, const char *prefix)
+{
+       int list = 0, delete = 0, force = 0;
+       struct option options[] = {
+               OPT_BOOLEAN('l', NULL, &list, "list replace refs"),
+               OPT_BOOLEAN('d', NULL, &delete, "delete replace refs"),
+               OPT_BOOLEAN('f', NULL, &force, "replace the ref if it exists"),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, prefix, options, git_replace_usage, 0);
+
+       if (list && delete)
+               usage_msg_opt("-l and -d cannot be used together",
+                             git_replace_usage, options);
+
+       if (force && (list || delete))
+               usage_msg_opt("-f cannot be used with -d or -l",
+                             git_replace_usage, options);
+
+       /* Delete refs */
+       if (delete) {
+               if (argc < 1)
+                       usage_msg_opt("-d needs at least one argument",
+                                     git_replace_usage, options);
+               return for_each_replace_name(argv, delete_replace_ref);
+       }
+
+       /* Replace object */
+       if (!list && argc) {
+               if (argc != 2)
+                       usage_msg_opt("bad number of arguments",
+                                     git_replace_usage, options);
+               return replace_object(argv[0], argv[1], force);
+       }
+
+       /* List refs, even if "list" is not set */
+       if (argc > 1)
+               usage_msg_opt("only one pattern can be given with -l",
+                             git_replace_usage, options);
+       if (force)
+               usage_msg_opt("-f needs some arguments",
+                             git_replace_usage, options);
+
+       return list_replace_refs(argv[0]);
+}
diff --git a/builtin/rerere.c b/builtin/rerere.c
new file mode 100644 (file)
index 0000000..642bf35
--- /dev/null
@@ -0,0 +1,170 @@
+#include "builtin.h"
+#include "cache.h"
+#include "dir.h"
+#include "parse-options.h"
+#include "string-list.h"
+#include "rerere.h"
+#include "xdiff/xdiff.h"
+#include "xdiff-interface.h"
+
+static const char * const rerere_usage[] = {
+       "git rerere [clear | status | diff | gc]",
+       NULL,
+};
+
+/* these values are days */
+static int cutoff_noresolve = 15;
+static int cutoff_resolve = 60;
+
+static time_t rerere_created_at(const char *name)
+{
+       struct stat st;
+       return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
+}
+
+static time_t rerere_last_used_at(const char *name)
+{
+       struct stat st;
+       return stat(rerere_path(name, "postimage"), &st) ? (time_t) 0 : st.st_mtime;
+}
+
+static void unlink_rr_item(const char *name)
+{
+       unlink(rerere_path(name, "thisimage"));
+       unlink(rerere_path(name, "preimage"));
+       unlink(rerere_path(name, "postimage"));
+       rmdir(git_path("rr-cache/%s", name));
+}
+
+static int git_rerere_gc_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "gc.rerereresolved"))
+               cutoff_resolve = git_config_int(var, value);
+       else if (!strcmp(var, "gc.rerereunresolved"))
+               cutoff_noresolve = git_config_int(var, value);
+       else
+               return git_default_config(var, value, cb);
+       return 0;
+}
+
+static void garbage_collect(struct string_list *rr)
+{
+       struct string_list to_remove = STRING_LIST_INIT_DUP;
+       DIR *dir;
+       struct dirent *e;
+       int i, cutoff;
+       time_t now = time(NULL), then;
+
+       git_config(git_rerere_gc_config, NULL);
+       dir = opendir(git_path("rr-cache"));
+       if (!dir)
+               die_errno("unable to open rr-cache directory");
+       while ((e = readdir(dir))) {
+               if (is_dot_or_dotdot(e->d_name))
+                       continue;
+
+               then = rerere_last_used_at(e->d_name);
+               if (then) {
+                       cutoff = cutoff_resolve;
+               } else {
+                       then = rerere_created_at(e->d_name);
+                       if (!then)
+                               continue;
+                       cutoff = cutoff_noresolve;
+               }
+               if (then < now - cutoff * 86400)
+                       string_list_append(&to_remove, e->d_name);
+       }
+       for (i = 0; i < to_remove.nr; i++)
+               unlink_rr_item(to_remove.items[i].string);
+       string_list_clear(&to_remove, 0);
+}
+
+static int outf(void *dummy, mmbuffer_t *ptr, int nbuf)
+{
+       int i;
+       for (i = 0; i < nbuf; i++)
+               if (write_in_full(1, ptr[i].ptr, ptr[i].size) != ptr[i].size)
+                       return -1;
+       return 0;
+}
+
+static int diff_two(const char *file1, const char *label1,
+               const char *file2, const char *label2)
+{
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
+       xdemitcb_t ecb;
+       mmfile_t minus, plus;
+
+       if (read_mmfile(&minus, file1) || read_mmfile(&plus, file2))
+               return 1;
+
+       printf("--- a/%s\n+++ b/%s\n", label1, label2);
+       fflush(stdout);
+       memset(&xpp, 0, sizeof(xpp));
+       xpp.flags = 0;
+       memset(&xecfg, 0, sizeof(xecfg));
+       xecfg.ctxlen = 3;
+       ecb.outf = outf;
+       xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb);
+
+       free(minus.ptr);
+       free(plus.ptr);
+       return 0;
+}
+
+int cmd_rerere(int argc, const char **argv, const char *prefix)
+{
+       struct string_list merge_rr = STRING_LIST_INIT_DUP;
+       int i, fd, autoupdate = -1, flags = 0;
+
+       struct option options[] = {
+               OPT_SET_INT(0, "rerere-autoupdate", &autoupdate,
+                       "register clean resolutions in index", 1),
+               OPT_END(),
+       };
+
+       argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+
+       if (autoupdate == 1)
+               flags = RERERE_AUTOUPDATE;
+       if (autoupdate == 0)
+               flags = RERERE_NOAUTOUPDATE;
+
+       if (argc < 1)
+               return rerere(flags);
+
+       if (!strcmp(argv[0], "forget")) {
+               const char **pathspec = get_pathspec(prefix, argv + 1);
+               return rerere_forget(pathspec);
+       }
+
+       fd = setup_rerere(&merge_rr, flags);
+       if (fd < 0)
+               return 0;
+
+       if (!strcmp(argv[0], "clear")) {
+               for (i = 0; i < merge_rr.nr; i++) {
+                       const char *name = (const char *)merge_rr.items[i].util;
+                       if (!has_rerere_resolution(name))
+                               unlink_rr_item(name);
+               }
+               unlink_or_warn(git_path("MERGE_RR"));
+       } else if (!strcmp(argv[0], "gc"))
+               garbage_collect(&merge_rr);
+       else if (!strcmp(argv[0], "status"))
+               for (i = 0; i < merge_rr.nr; i++)
+                       printf("%s\n", merge_rr.items[i].string);
+       else if (!strcmp(argv[0], "diff"))
+               for (i = 0; i < merge_rr.nr; i++) {
+                       const char *path = merge_rr.items[i].string;
+                       const char *name = (const char *)merge_rr.items[i].util;
+                       diff_two(rerere_path(name, "preimage"), path, path, path);
+               }
+       else
+               usage_with_options(rerere_usage, options);
+
+       string_list_clear(&merge_rr, 1);
+       return 0;
+}
diff --git a/builtin/reset.c b/builtin/reset.c
new file mode 100644 (file)
index 0000000..0037be4
--- /dev/null
@@ -0,0 +1,386 @@
+/*
+ * "git reset" builtin command
+ *
+ * Copyright (c) 2007 Carlos Rica
+ *
+ * Based on git-reset.sh, which is
+ *
+ * Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano
+ */
+#include "cache.h"
+#include "tag.h"
+#include "object.h"
+#include "commit.h"
+#include "run-command.h"
+#include "refs.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "tree.h"
+#include "branch.h"
+#include "parse-options.h"
+#include "unpack-trees.h"
+#include "cache-tree.h"
+
+static const char * const git_reset_usage[] = {
+       "git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]",
+       "git reset [-q] <commit> [--] <paths>...",
+       "git reset --patch [<commit>] [--] [<paths>...]",
+       NULL
+};
+
+enum reset_type { MIXED, SOFT, HARD, MERGE, KEEP, NONE };
+static const char *reset_type_names[] = {
+       "mixed", "soft", "hard", "merge", "keep", NULL
+};
+
+static char *args_to_str(const char **argv)
+{
+       char *buf = NULL;
+       unsigned long len, space = 0, nr = 0;
+
+       for (; *argv; argv++) {
+               len = strlen(*argv);
+               ALLOC_GROW(buf, nr + 1 + len, space);
+               if (nr)
+                       buf[nr++] = ' ';
+               memcpy(buf + nr, *argv, len);
+               nr += len;
+       }
+       ALLOC_GROW(buf, nr + 1, space);
+       buf[nr] = '\0';
+
+       return buf;
+}
+
+static inline int is_merge(void)
+{
+       return !access(git_path("MERGE_HEAD"), F_OK);
+}
+
+static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet)
+{
+       int nr = 1;
+       int newfd;
+       struct tree_desc desc[2];
+       struct unpack_trees_options opts;
+       struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+
+       memset(&opts, 0, sizeof(opts));
+       opts.head_idx = 1;
+       opts.src_index = &the_index;
+       opts.dst_index = &the_index;
+       opts.fn = oneway_merge;
+       opts.merge = 1;
+       if (!quiet)
+               opts.verbose_update = 1;
+       switch (reset_type) {
+       case KEEP:
+       case MERGE:
+               opts.update = 1;
+               break;
+       case HARD:
+               opts.update = 1;
+               /* fallthrough */
+       default:
+               opts.reset = 1;
+       }
+
+       newfd = hold_locked_index(lock, 1);
+
+       read_cache_unmerged();
+
+       if (reset_type == KEEP) {
+               unsigned char head_sha1[20];
+               if (get_sha1("HEAD", head_sha1))
+                       return error("You do not have a valid HEAD.");
+               if (!fill_tree_descriptor(desc, head_sha1))
+                       return error("Failed to find tree of HEAD.");
+               nr++;
+               opts.fn = twoway_merge;
+       }
+
+       if (!fill_tree_descriptor(desc + nr - 1, sha1))
+               return error("Failed to find tree of %s.", sha1_to_hex(sha1));
+       if (unpack_trees(nr, desc, &opts))
+               return -1;
+       if (write_cache(newfd, active_cache, active_nr) ||
+           commit_locked_index(lock))
+               return error("Could not write new index file.");
+
+       return 0;
+}
+
+static void print_new_head_line(struct commit *commit)
+{
+       const char *hex, *body;
+
+       hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
+       printf("HEAD is now at %s", hex);
+       body = strstr(commit->buffer, "\n\n");
+       if (body) {
+               const char *eol;
+               size_t len;
+               body += 2;
+               eol = strchr(body, '\n');
+               len = eol ? eol - body : strlen(body);
+               printf(" %.*s\n", (int) len, body);
+       }
+       else
+               printf("\n");
+}
+
+static int update_index_refresh(int fd, struct lock_file *index_lock, int flags)
+{
+       int result;
+
+       if (!index_lock) {
+               index_lock = xcalloc(1, sizeof(struct lock_file));
+               fd = hold_locked_index(index_lock, 1);
+       }
+
+       if (read_cache() < 0)
+               return error("Could not read index");
+
+       result = refresh_index(&the_index, (flags), NULL, NULL,
+                              "Unstaged changes after reset:") ? 1 : 0;
+       if (write_cache(fd, active_cache, active_nr) ||
+                       commit_locked_index(index_lock))
+               return error ("Could not refresh index");
+       return result;
+}
+
+static void update_index_from_diff(struct diff_queue_struct *q,
+               struct diff_options *opt, void *data)
+{
+       int i;
+       int *discard_flag = data;
+
+       /* do_diff_cache() mangled the index */
+       discard_cache();
+       *discard_flag = 1;
+       read_cache();
+
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filespec *one = q->queue[i]->one;
+               if (one->mode) {
+                       struct cache_entry *ce;
+                       ce = make_cache_entry(one->mode, one->sha1, one->path,
+                               0, 0);
+                       if (!ce)
+                               die("make_cache_entry failed for path '%s'",
+                                   one->path);
+                       add_cache_entry(ce, ADD_CACHE_OK_TO_ADD |
+                               ADD_CACHE_OK_TO_REPLACE);
+               } else
+                       remove_file_from_cache(one->path);
+       }
+}
+
+static int interactive_reset(const char *revision, const char **argv,
+                            const char *prefix)
+{
+       const char **pathspec = NULL;
+
+       if (*argv)
+               pathspec = get_pathspec(prefix, argv);
+
+       return run_add_interactive(revision, "--patch=reset", pathspec);
+}
+
+static int read_from_tree(const char *prefix, const char **argv,
+               unsigned char *tree_sha1, int refresh_flags)
+{
+       struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+       int index_fd, index_was_discarded = 0;
+       struct diff_options opt;
+
+       memset(&opt, 0, sizeof(opt));
+       diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt);
+       opt.output_format = DIFF_FORMAT_CALLBACK;
+       opt.format_callback = update_index_from_diff;
+       opt.format_callback_data = &index_was_discarded;
+
+       index_fd = hold_locked_index(lock, 1);
+       index_was_discarded = 0;
+       read_cache();
+       if (do_diff_cache(tree_sha1, &opt))
+               return 1;
+       diffcore_std(&opt);
+       diff_flush(&opt);
+       diff_tree_release_paths(&opt);
+
+       if (!index_was_discarded)
+               /* The index is still clobbered from do_diff_cache() */
+               discard_cache();
+       return update_index_refresh(index_fd, lock, refresh_flags);
+}
+
+static void prepend_reflog_action(const char *action, char *buf, size_t size)
+{
+       const char *sep = ": ";
+       const char *rla = getenv("GIT_REFLOG_ACTION");
+       if (!rla)
+               rla = sep = "";
+       if (snprintf(buf, size, "%s%s%s", rla, sep, action) >= size)
+               warning("Reflog action message too long: %.*s...", 50, buf);
+}
+
+static void die_if_unmerged_cache(int reset_type)
+{
+       if (is_merge() || read_cache() < 0 || unmerged_cache())
+               die("Cannot do a %s reset in the middle of a merge.",
+                   reset_type_names[reset_type]);
+
+}
+
+int cmd_reset(int argc, const char **argv, const char *prefix)
+{
+       int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;
+       int patch_mode = 0;
+       const char *rev = "HEAD";
+       unsigned char sha1[20], *orig = NULL, sha1_orig[20],
+                               *old_orig = NULL, sha1_old_orig[20];
+       struct commit *commit;
+       char *reflog_action, msg[1024];
+       const struct option options[] = {
+               OPT__QUIET(&quiet),
+               OPT_SET_INT(0, "mixed", &reset_type,
+                                               "reset HEAD and index", MIXED),
+               OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT),
+               OPT_SET_INT(0, "hard", &reset_type,
+                               "reset HEAD, index and working tree", HARD),
+               OPT_SET_INT(0, "merge", &reset_type,
+                               "reset HEAD, index and working tree", MERGE),
+               OPT_SET_INT(0, "keep", &reset_type,
+                               "reset HEAD but keep local changes", KEEP),
+               OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
+               OPT_END()
+       };
+
+       git_config(git_default_config, NULL);
+
+       argc = parse_options(argc, argv, prefix, options, git_reset_usage,
+                                               PARSE_OPT_KEEP_DASHDASH);
+       reflog_action = args_to_str(argv);
+       setenv("GIT_REFLOG_ACTION", reflog_action, 0);
+
+       /*
+        * Possible arguments are:
+        *
+        * git reset [-opts] <rev> <paths>...
+        * git reset [-opts] <rev> -- <paths>...
+        * git reset [-opts] -- <paths>...
+        * git reset [-opts] <paths>...
+        *
+        * At this point, argv[i] points immediately after [-opts].
+        */
+
+       if (i < argc) {
+               if (!strcmp(argv[i], "--")) {
+                       i++; /* reset to HEAD, possibly with paths */
+               } else if (i + 1 < argc && !strcmp(argv[i+1], "--")) {
+                       rev = argv[i];
+                       i += 2;
+               }
+               /*
+                * Otherwise, argv[i] could be either <rev> or <paths> and
+                * has to be unambiguous.
+                */
+               else if (!get_sha1(argv[i], sha1)) {
+                       /*
+                        * Ok, argv[i] looks like a rev; it should not
+                        * be a filename.
+                        */
+                       verify_non_filename(prefix, argv[i]);
+                       rev = argv[i++];
+               } else {
+                       /* Otherwise we treat this as a filename */
+                       verify_filename(prefix, argv[i]);
+               }
+       }
+
+       if (get_sha1(rev, sha1))
+               die("Failed to resolve '%s' as a valid ref.", rev);
+
+       commit = lookup_commit_reference(sha1);
+       if (!commit)
+               die("Could not parse object '%s'.", rev);
+       hashcpy(sha1, commit->object.sha1);
+
+       if (patch_mode) {
+               if (reset_type != NONE)
+                       die("--patch is incompatible with --{hard,mixed,soft}");
+               return interactive_reset(rev, argv + i, prefix);
+       }
+
+       /* git reset tree [--] paths... can be used to
+        * load chosen paths from the tree into the index without
+        * affecting the working tree nor HEAD. */
+       if (i < argc) {
+               if (reset_type == MIXED)
+                       warning("--mixed with paths is deprecated; use 'git reset -- <paths>' instead.");
+               else if (reset_type != NONE)
+                       die("Cannot do %s reset with paths.",
+                                       reset_type_names[reset_type]);
+               return read_from_tree(prefix, argv + i, sha1,
+                               quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
+       }
+       if (reset_type == NONE)
+               reset_type = MIXED; /* by default */
+
+       if (reset_type != SOFT && reset_type != MIXED)
+               setup_work_tree();
+
+       if (reset_type == MIXED && is_bare_repository())
+               die("%s reset is not allowed in a bare repository",
+                   reset_type_names[reset_type]);
+
+       /* 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 (reset_type == SOFT)
+               die_if_unmerged_cache(reset_type);
+       else {
+               int err;
+               if (reset_type == KEEP)
+                       die_if_unmerged_cache(reset_type);
+               err = reset_index_file(sha1, reset_type, quiet);
+               if (reset_type == KEEP)
+                       err = err || reset_index_file(sha1, MIXED, quiet);
+               if (err)
+                       die("Could not reset index file to revision '%s'.", rev);
+       }
+
+       /* Any resets update HEAD to the head being switched to,
+        * saving the previous head in ORIG_HEAD before. */
+       if (!get_sha1("ORIG_HEAD", sha1_old_orig))
+               old_orig = sha1_old_orig;
+       if (!get_sha1("HEAD", sha1_orig)) {
+               orig = sha1_orig;
+               prepend_reflog_action("updating ORIG_HEAD", msg, sizeof(msg));
+               update_ref(msg, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR);
+       }
+       else if (old_orig)
+               delete_ref("ORIG_HEAD", old_orig, 0);
+       prepend_reflog_action("updating HEAD", msg, sizeof(msg));
+       update_ref_status = update_ref(msg, "HEAD", sha1, orig, 0, MSG_ON_ERR);
+
+       switch (reset_type) {
+       case HARD:
+               if (!update_ref_status && !quiet)
+                       print_new_head_line(commit);
+               break;
+       case SOFT: /* Nothing else to do. */
+               break;
+       case MIXED: /* Report what has not been updated. */
+               update_index_refresh(0, NULL,
+                               quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
+               break;
+       }
+
+       remove_branch_state();
+
+       free(reflog_action);
+
+       return update_ref_status;
+}
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
new file mode 100644 (file)
index 0000000..efe9360
--- /dev/null
@@ -0,0 +1,420 @@
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "list-objects.h"
+#include "builtin.h"
+#include "log-tree.h"
+#include "graph.h"
+#include "bisect.h"
+
+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"
+"    --branches\n"
+"    --tags\n"
+"    --remotes\n"
+"    --stdin\n"
+"    --quiet\n"
+"  ordering output:\n"
+"    --topo-order\n"
+"    --date-order\n"
+"    --reverse\n"
+"  formatting output:\n"
+"    --parents\n"
+"    --children\n"
+"    --objects | --objects-edge\n"
+"    --unpacked\n"
+"    --header | --pretty\n"
+"    --abbrev=nr | --no-abbrev\n"
+"    --abbrev-commit\n"
+"    --left-right\n"
+"  special purpose:\n"
+"    --bisect\n"
+"    --bisect-vars\n"
+"    --bisect-all"
+;
+
+static void finish_commit(struct commit *commit, void *data);
+static void show_commit(struct commit *commit, void *data)
+{
+       struct rev_list_info *info = data;
+       struct rev_info *revs = info->revs;
+
+       graph_show_commit(revs->graph);
+
+       if (revs->count) {
+               if (commit->object.flags & SYMMETRIC_LEFT)
+                       revs->count_left++;
+               else
+                       revs->count_right++;
+               finish_commit(commit, data);
+               return;
+       }
+
+       if (info->show_timestamp)
+               printf("%lu ", commit->date);
+       if (info->header_prefix)
+               fputs(info->header_prefix, stdout);
+
+       if (!revs->graph) {
+               if (commit->object.flags & BOUNDARY)
+                       putchar('-');
+               else if (commit->object.flags & UNINTERESTING)
+                       putchar('^');
+               else if (revs->left_right) {
+                       if (commit->object.flags & SYMMETRIC_LEFT)
+                               putchar('<');
+                       else
+                               putchar('>');
+               }
+       }
+       if (revs->abbrev_commit && revs->abbrev)
+               fputs(find_unique_abbrev(commit->object.sha1, revs->abbrev),
+                     stdout);
+       else
+               fputs(sha1_to_hex(commit->object.sha1), stdout);
+       if (revs->print_parents) {
+               struct commit_list *parents = commit->parents;
+               while (parents) {
+                       printf(" %s", sha1_to_hex(parents->item->object.sha1));
+                       parents = parents->next;
+               }
+       }
+       if (revs->children.name) {
+               struct commit_list *children;
+
+               children = lookup_decoration(&revs->children, &commit->object);
+               while (children) {
+                       printf(" %s", sha1_to_hex(children->item->object.sha1));
+                       children = children->next;
+               }
+       }
+       show_decorations(revs, commit);
+       if (revs->commit_format == CMIT_FMT_ONELINE)
+               putchar(' ');
+       else
+               putchar('\n');
+
+       if (revs->verbose_header && commit->buffer) {
+               struct strbuf buf = STRBUF_INIT;
+               struct pretty_print_context ctx = {0};
+               ctx.abbrev = revs->abbrev;
+               ctx.date_mode = revs->date_mode;
+               pretty_print_commit(revs->commit_format, commit, &buf, &ctx);
+               if (revs->graph) {
+                       if (buf.len) {
+                               if (revs->commit_format != CMIT_FMT_ONELINE)
+                                       graph_show_oneline(revs->graph);
+
+                               graph_show_commit_msg(revs->graph, &buf);
+
+                               /*
+                                * Add a newline after the commit message.
+                                *
+                                * Usually, this newline produces a blank
+                                * padding line between entries, in which case
+                                * we need to add graph padding on this line.
+                                *
+                                * However, the commit message may not end in a
+                                * newline.  In this case the newline simply
+                                * ends the last line of the commit message,
+                                * and we don't need any graph output.  (This
+                                * always happens with CMIT_FMT_ONELINE, and it
+                                * happens with CMIT_FMT_USERFORMAT when the
+                                * format doesn't explicitly end in a newline.)
+                                */
+                               if (buf.len && buf.buf[buf.len - 1] == '\n')
+                                       graph_show_padding(revs->graph);
+                               putchar('\n');
+                       } else {
+                               /*
+                                * If the message buffer is empty, just show
+                                * the rest of the graph output for this
+                                * commit.
+                                */
+                               if (graph_show_remainder(revs->graph))
+                                       putchar('\n');
+                               if (revs->commit_format == CMIT_FMT_ONELINE)
+                                       putchar('\n');
+                       }
+               } else {
+                       if (revs->commit_format != CMIT_FMT_USERFORMAT ||
+                           buf.len)
+                               printf("%s%c", buf.buf, info->hdr_termination);
+               }
+               strbuf_release(&buf);
+       } else {
+               if (graph_show_remainder(revs->graph))
+                       putchar('\n');
+       }
+       maybe_flush_or_die(stdout, "stdout");
+       finish_commit(commit, data);
+}
+
+static void finish_commit(struct commit *commit, void *data)
+{
+       if (commit->parents) {
+               free_commit_list(commit->parents);
+               commit->parents = NULL;
+       }
+       free(commit->buffer);
+       commit->buffer = NULL;
+}
+
+static void finish_object(struct object *obj, const struct name_path *path, const char *name)
+{
+       if (obj->type == OBJ_BLOB && !has_sha1_file(obj->sha1))
+               die("missing blob object '%s'", sha1_to_hex(obj->sha1));
+}
+
+static void show_object(struct object *obj, const struct name_path *path, const char *component)
+{
+       char *name = path_name(path, component);
+       /* An object with name "foo\n0000000..." can be used to
+        * confuse downstream "git pack-objects" very badly.
+        */
+       const char *ep = strchr(name, '\n');
+
+       finish_object(obj, path, name);
+       if (ep) {
+               printf("%s %.*s\n", sha1_to_hex(obj->sha1),
+                      (int) (ep - name),
+                      name);
+       }
+       else
+               printf("%s %s\n", sha1_to_hex(obj->sha1), name);
+       free(name);
+}
+
+static void show_edge(struct commit *commit)
+{
+       printf("-%s\n", sha1_to_hex(commit->object.sha1));
+}
+
+static inline int log2i(int n)
+{
+       int log2 = 0;
+
+       for (; n > 1; n >>= 1)
+               log2++;
+
+       return log2;
+}
+
+static inline int exp2i(int n)
+{
+       return 1 << n;
+}
+
+/*
+ * Estimate the number of bisect steps left (after the current step)
+ *
+ * For any x between 0 included and 2^n excluded, the probability for
+ * n - 1 steps left looks like:
+ *
+ * P(2^n + x) == (2^n - x) / (2^n + x)
+ *
+ * and P(2^n + x) < 0.5 means 2^n < 3x
+ */
+int estimate_bisect_steps(int all)
+{
+       int n, x, e;
+
+       if (all < 3)
+               return 0;
+
+       n = log2i(all);
+       e = exp2i(n);
+       x = all - e;
+
+       return (e < 3 * x) ? n : n - 1;
+}
+
+void print_commit_list(struct commit_list *list,
+                      const char *format_cur,
+                      const char *format_last)
+{
+       for ( ; list; list = list->next) {
+               const char *format = list->next ? format_cur : format_last;
+               printf(format, sha1_to_hex(list->item->object.sha1));
+       }
+}
+
+static void show_tried_revs(struct commit_list *tried)
+{
+       printf("bisect_tried='");
+       print_commit_list(tried, "%s|", "%s");
+       printf("'\n");
+}
+
+static void print_var_str(const char *var, const char *val)
+{
+       printf("%s='%s'\n", var, val);
+}
+
+static void print_var_int(const char *var, int val)
+{
+       printf("%s=%d\n", var, val);
+}
+
+static int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
+{
+       int cnt, flags = info->bisect_show_flags;
+       char hex[41] = "";
+       struct commit_list *tried;
+       struct rev_info *revs = info->revs;
+
+       if (!revs->commits && !(flags & BISECT_SHOW_TRIED))
+               return 1;
+
+       revs->commits = filter_skipped(revs->commits, &tried,
+                                      flags & BISECT_SHOW_ALL,
+                                      NULL, NULL);
+
+       /*
+        * revs->commits can reach "reaches" commits among
+        * "all" commits.  If it is good, then there are
+        * (all-reaches) commits left to be bisected.
+        * On the other hand, if it is bad, then the set
+        * to bisect is "reaches".
+        * A bisect set of size N has (N-1) commits further
+        * to test, as we already know one bad one.
+        */
+       cnt = all - reaches;
+       if (cnt < reaches)
+               cnt = reaches;
+
+       if (revs->commits)
+               strcpy(hex, sha1_to_hex(revs->commits->item->object.sha1));
+
+       if (flags & BISECT_SHOW_ALL) {
+               traverse_commit_list(revs, show_commit, show_object, info);
+               printf("------\n");
+       }
+
+       if (flags & BISECT_SHOW_TRIED)
+               show_tried_revs(tried);
+
+       print_var_str("bisect_rev", hex);
+       print_var_int("bisect_nr", cnt - 1);
+       print_var_int("bisect_good", all - reaches - 1);
+       print_var_int("bisect_bad", reaches - 1);
+       print_var_int("bisect_all", all);
+       print_var_int("bisect_steps", estimate_bisect_steps(all));
+
+       return 0;
+}
+
+int cmd_rev_list(int argc, const char **argv, const char *prefix)
+{
+       struct rev_info revs;
+       struct rev_list_info info;
+       int i;
+       int bisect_list = 0;
+       int bisect_show_vars = 0;
+       int bisect_find_all = 0;
+       int quiet = 0;
+
+       git_config(git_default_config, NULL);
+       init_revisions(&revs, prefix);
+       revs.abbrev = DEFAULT_ABBREV;
+       revs.commit_format = CMIT_FMT_UNSPECIFIED;
+       argc = setup_revisions(argc, argv, &revs, NULL);
+
+       memset(&info, 0, sizeof(info));
+       info.revs = &revs;
+       if (revs.bisect)
+               bisect_list = 1;
+
+       quiet = DIFF_OPT_TST(&revs.diffopt, QUICK);
+       for (i = 1 ; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (!strcmp(arg, "--header")) {
+                       revs.verbose_header = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--timestamp")) {
+                       info.show_timestamp = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--bisect")) {
+                       bisect_list = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--bisect-all")) {
+                       bisect_list = 1;
+                       bisect_find_all = 1;
+                       info.bisect_show_flags = BISECT_SHOW_ALL;
+                       revs.show_decorations = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--bisect-vars")) {
+                       bisect_list = 1;
+                       bisect_show_vars = 1;
+                       continue;
+               }
+               usage(rev_list_usage);
+
+       }
+       if (revs.commit_format != CMIT_FMT_UNSPECIFIED) {
+               /* The command line has a --pretty  */
+               info.hdr_termination = '\n';
+               if (revs.commit_format == CMIT_FMT_ONELINE)
+                       info.header_prefix = "";
+               else
+                       info.header_prefix = "commit ";
+       }
+       else if (revs.verbose_header)
+               /* Only --header was specified */
+               revs.commit_format = CMIT_FMT_RAW;
+
+       if ((!revs.commits &&
+            (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) &&
+             !revs.pending.nr)) ||
+           revs.diff)
+               usage(rev_list_usage);
+
+       save_commit_buffer = (revs.verbose_header ||
+                             revs.grep_filter.pattern_list ||
+                             revs.grep_filter.header_list);
+       if (bisect_list)
+               revs.limited = 1;
+
+       if (prepare_revision_walk(&revs))
+               die("revision walk setup failed");
+       if (revs.tree_objects)
+               mark_edges_uninteresting(revs.commits, &revs, show_edge);
+
+       if (bisect_list) {
+               int reaches = reaches, all = all;
+
+               revs.commits = find_bisection(revs.commits, &reaches, &all,
+                                             bisect_find_all);
+
+               if (bisect_show_vars)
+                       return show_bisect_vars(&info, reaches, all);
+       }
+
+       traverse_commit_list(&revs,
+                            quiet ? finish_commit : show_commit,
+                            quiet ? finish_object : show_object,
+                            &info);
+
+       if (revs.count) {
+               if (revs.left_right)
+                       printf("%d\t%d\n", revs.count_left, revs.count_right);
+               else
+                       printf("%d\n", revs.count_left + revs.count_right);
+       }
+
+       return 0;
+}
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
new file mode 100644 (file)
index 0000000..a5a1c86
--- /dev/null
@@ -0,0 +1,734 @@
+/*
+ * rev-parse.c
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "commit.h"
+#include "refs.h"
+#include "quote.h"
+#include "builtin.h"
+#include "parse-options.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;
+
+#define NORMAL 0
+#define REVERSED 1
+static int show_type = NORMAL;
+
+#define SHOW_SYMBOLIC_ASIS 1
+#define SHOW_SYMBOLIC_FULL 2
+static int symbolic;
+static int abbrev;
+static int abbrev_ref;
+static int abbrev_ref_strict;
+static int output_sq;
+
+/*
+ * 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=",
+               "--branches",
+               "--header",
+               "--max-age=",
+               "--max-count=",
+               "--min-age=",
+               "--no-merges",
+               "--objects",
+               "--objects-edge",
+               "--parents",
+               "--pretty",
+               "--remotes=",
+               "--remotes",
+               "--glob=",
+               "--sparse",
+               "--tags=",
+               "--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);
+}
+
+/* Like show(), but with a negation prefix according to type */
+static void show_with_type(int type, const char *arg)
+{
+       if (type != show_type)
+               putchar('^');
+       show(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;
+
+       if ((symbolic || abbrev_ref) && name) {
+               if (symbolic == SHOW_SYMBOLIC_FULL || abbrev_ref) {
+                       unsigned char discard[20];
+                       char *full;
+
+                       switch (dwim_ref(name, strlen(name), discard, &full)) {
+                       case 0:
+                               /*
+                                * Not found -- not a ref.  We could
+                                * emit "name" here, but symbolic-full
+                                * users are interested in finding the
+                                * refs spelled in full, and they would
+                                * need to filter non-refs if we did so.
+                                */
+                               break;
+                       case 1: /* happy */
+                               if (abbrev_ref)
+                                       full = shorten_unambiguous_ref(full,
+                                               abbrev_ref_strict);
+                               show_with_type(type, full);
+                               break;
+                       default: /* ambiguous */
+                               error("refname '%s' is ambiguous", name);
+                               break;
+                       }
+               } else {
+                       show_with_type(type, name);
+               }
+       }
+       else if (abbrev)
+               show_with_type(type, find_unique_abbrev(sha1, abbrev));
+       else
+               show_with_type(type, 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 int 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 1;
+               }
+       }
+       return 0;
+}
+
+static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+       show_rev(NORMAL, sha1, refname);
+       return 0;
+}
+
+static int anti_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+       show_rev(REVERSED, 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;
+}
+
+static int try_difference(const char *arg)
+{
+       char *dotdot;
+       unsigned char sha1[20];
+       unsigned char end[20];
+       const char *next;
+       const char *this;
+       int symmetric;
+
+       if (!(dotdot = strstr(arg, "..")))
+               return 0;
+       next = dotdot + 2;
+       this = arg;
+       symmetric = (*next == '.');
+
+       *dotdot = 0;
+       next += symmetric;
+
+       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(symmetric ? NORMAL : REVERSED, sha1, this);
+               if (symmetric) {
+                       struct commit_list *exclude;
+                       struct commit *a, *b;
+                       a = lookup_commit_reference(sha1);
+                       b = lookup_commit_reference(end);
+                       exclude = get_merge_bases(a, b, 1);
+                       while (exclude) {
+                               struct commit_list *n = exclude->next;
+                               show_rev(REVERSED,
+                                        exclude->item->object.sha1,NULL);
+                               free(exclude);
+                               exclude = n;
+                       }
+               }
+               return 1;
+       }
+       *dotdot = '.';
+       return 0;
+}
+
+static int try_parent_shorthands(const char *arg)
+{
+       char *dotdot;
+       unsigned char sha1[20];
+       struct commit *commit;
+       struct commit_list *parents;
+       int parents_only;
+
+       if ((dotdot = strstr(arg, "^!")))
+               parents_only = 0;
+       else if ((dotdot = strstr(arg, "^@")))
+               parents_only = 1;
+
+       if (!dotdot || dotdot[2])
+               return 0;
+
+       *dotdot = 0;
+       if (get_sha1(arg, sha1))
+               return 0;
+
+       if (!parents_only)
+               show_rev(NORMAL, sha1, arg);
+       commit = lookup_commit_reference(sha1);
+       for (parents = commit->parents; parents; parents = parents->next)
+               show_rev(parents_only ? NORMAL : REVERSED,
+                               parents->item->object.sha1, arg);
+
+       return 1;
+}
+
+static int parseopt_dump(const struct option *o, const char *arg, int unset)
+{
+       struct strbuf *parsed = o->value;
+       if (unset)
+               strbuf_addf(parsed, " --no-%s", o->long_name);
+       else if (o->short_name)
+               strbuf_addf(parsed, " -%c", o->short_name);
+       else
+               strbuf_addf(parsed, " --%s", o->long_name);
+       if (arg) {
+               strbuf_addch(parsed, ' ');
+               sq_quote_buf(parsed, arg);
+       }
+       return 0;
+}
+
+static const char *skipspaces(const char *s)
+{
+       while (isspace(*s))
+               s++;
+       return s;
+}
+
+static int cmd_parseopt(int argc, const char **argv, const char *prefix)
+{
+       static int keep_dashdash = 0, stop_at_non_option = 0;
+       static char const * const parseopt_usage[] = {
+               "git rev-parse --parseopt [options] -- [<args>...]",
+               NULL
+       };
+       static struct option parseopt_opts[] = {
+               OPT_BOOLEAN(0, "keep-dashdash", &keep_dashdash,
+                                       "keep the `--` passed as an arg"),
+               OPT_BOOLEAN(0, "stop-at-non-option", &stop_at_non_option,
+                                       "stop parsing after the "
+                                       "first non-option argument"),
+               OPT_END(),
+       };
+
+       struct strbuf sb = STRBUF_INIT, parsed = STRBUF_INIT;
+       const char **usage = NULL;
+       struct option *opts = NULL;
+       int onb = 0, osz = 0, unb = 0, usz = 0;
+
+       strbuf_addstr(&parsed, "set --");
+       argc = parse_options(argc, argv, prefix, parseopt_opts, parseopt_usage,
+                            PARSE_OPT_KEEP_DASHDASH);
+       if (argc < 1 || strcmp(argv[0], "--"))
+               usage_with_options(parseopt_usage, parseopt_opts);
+
+       /* get the usage up to the first line with a -- on it */
+       for (;;) {
+               if (strbuf_getline(&sb, stdin, '\n') == EOF)
+                       die("premature end of input");
+               ALLOC_GROW(usage, unb + 1, usz);
+               if (!strcmp("--", sb.buf)) {
+                       if (unb < 1)
+                               die("no usage string given before the `--' separator");
+                       usage[unb] = NULL;
+                       break;
+               }
+               usage[unb++] = strbuf_detach(&sb, NULL);
+       }
+
+       /* parse: (<short>|<short>,<long>|<long>)[=?]? SP+ <help> */
+       while (strbuf_getline(&sb, stdin, '\n') != EOF) {
+               const char *s;
+               struct option *o;
+
+               if (!sb.len)
+                       continue;
+
+               ALLOC_GROW(opts, onb + 1, osz);
+               memset(opts + onb, 0, sizeof(opts[onb]));
+
+               o = &opts[onb++];
+               s = strchr(sb.buf, ' ');
+               if (!s || *sb.buf == ' ') {
+                       o->type = OPTION_GROUP;
+                       o->help = xstrdup(skipspaces(sb.buf));
+                       continue;
+               }
+
+               o->type = OPTION_CALLBACK;
+               o->help = xstrdup(skipspaces(s));
+               o->value = &parsed;
+               o->flags = PARSE_OPT_NOARG;
+               o->callback = &parseopt_dump;
+               while (s > sb.buf && strchr("*=?!", s[-1])) {
+                       switch (*--s) {
+                       case '=':
+                               o->flags &= ~PARSE_OPT_NOARG;
+                               break;
+                       case '?':
+                               o->flags &= ~PARSE_OPT_NOARG;
+                               o->flags |= PARSE_OPT_OPTARG;
+                               break;
+                       case '!':
+                               o->flags |= PARSE_OPT_NONEG;
+                               break;
+                       case '*':
+                               o->flags |= PARSE_OPT_HIDDEN;
+                               break;
+                       }
+               }
+
+               if (s - sb.buf == 1) /* short option only */
+                       o->short_name = *sb.buf;
+               else if (sb.buf[1] != ',') /* long option only */
+                       o->long_name = xmemdupz(sb.buf, s - sb.buf);
+               else {
+                       o->short_name = *sb.buf;
+                       o->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2);
+               }
+       }
+       strbuf_release(&sb);
+
+       /* put an OPT_END() */
+       ALLOC_GROW(opts, onb + 1, osz);
+       memset(opts + onb, 0, sizeof(opts[onb]));
+       argc = parse_options(argc, argv, prefix, opts, usage,
+                       (keep_dashdash ? PARSE_OPT_KEEP_DASHDASH : 0) |
+                       (stop_at_non_option ? PARSE_OPT_STOP_AT_NON_OPTION : 0) |
+                       PARSE_OPT_SHELL_EVAL);
+
+       strbuf_addf(&parsed, " --");
+       sq_quote_argv(&parsed, argv, 0);
+       puts(parsed.buf);
+       return 0;
+}
+
+static int cmd_sq_quote(int argc, const char **argv)
+{
+       struct strbuf buf = STRBUF_INIT;
+
+       if (argc)
+               sq_quote_argv(&buf, argv, 0);
+       printf("%s\n", buf.buf);
+       strbuf_release(&buf);
+
+       return 0;
+}
+
+static void die_no_single_rev(int quiet)
+{
+       if (quiet)
+               exit(1);
+       else
+               die("Needed a single revision");
+}
+
+static const char builtin_rev_parse_usage[] =
+"git rev-parse --parseopt [options] -- [<args>...]\n"
+"   or: git rev-parse --sq-quote [<arg>...]\n"
+"   or: git rev-parse [options] [<arg>...]\n"
+"\n"
+"Run \"git rev-parse --parseopt -h\" for more information on the first usage.";
+
+int cmd_rev_parse(int argc, const char **argv, const char *prefix)
+{
+       int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0;
+       unsigned char sha1[20];
+       const char *name = NULL;
+
+       if (argc > 1 && !strcmp("--parseopt", argv[1]))
+               return cmd_parseopt(argc - 1, argv + 1, prefix);
+
+       if (argc > 1 && !strcmp("--sq-quote", argv[1]))
+               return cmd_sq_quote(argc - 2, argv + 2);
+
+       if (argc == 2 && !strcmp("--local-env-vars", argv[1])) {
+               int i;
+               for (i = 0; local_repo_env[i]; i++)
+                       printf("%s\n", local_repo_env[i]);
+               return 0;
+       }
+
+       if (argc > 1 && !strcmp("-h", argv[1]))
+               usage(builtin_rev_parse_usage);
+
+       prefix = setup_git_directory();
+       git_config(git_default_config, NULL);
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               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 (!prefixcmp(arg, "-n")) {
+                       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, "--quiet") || !strcmp(arg, "-q")) {
+                               quiet = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--short") ||
+                           !prefixcmp(arg, "--short=")) {
+                               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 = SHOW_SYMBOLIC_ASIS;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--symbolic-full-name")) {
+                               symbolic = SHOW_SYMBOLIC_FULL;
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--abbrev-ref") &&
+                           (!arg[12] || arg[12] == '=')) {
+                               abbrev_ref = 1;
+                               abbrev_ref_strict = warn_ambiguous_refs;
+                               if (arg[12] == '=') {
+                                       if (!strcmp(arg + 13, "strict"))
+                                               abbrev_ref_strict = 1;
+                                       else if (!strcmp(arg + 13, "loose"))
+                                               abbrev_ref_strict = 0;
+                                       else
+                                               die("unknown mode for %s", arg);
+                               }
+                               continue;
+                       }
+                       if (!strcmp(arg, "--all")) {
+                               for_each_ref(show_reference, NULL);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--bisect")) {
+                               for_each_ref_in("refs/bisect/bad", show_reference, NULL);
+                               for_each_ref_in("refs/bisect/good", anti_reference, NULL);
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--branches=")) {
+                               for_each_glob_ref_in(show_reference, arg + 11,
+                                       "refs/heads/", NULL);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--branches")) {
+                               for_each_branch_ref(show_reference, NULL);
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--tags=")) {
+                               for_each_glob_ref_in(show_reference, arg + 7,
+                                       "refs/tags/", NULL);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--tags")) {
+                               for_each_tag_ref(show_reference, NULL);
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--glob=")) {
+                               for_each_glob_ref(show_reference, arg + 7, NULL);
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--remotes=")) {
+                               for_each_glob_ref_in(show_reference, arg + 10,
+                                       "refs/remotes/", NULL);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--remotes")) {
+                               for_each_remote_ref(show_reference, NULL);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--show-toplevel")) {
+                               const char *work_tree = get_git_work_tree();
+                               if (work_tree)
+                                       puts(work_tree);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--show-prefix")) {
+                               if (prefix)
+                                       puts(prefix);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--show-cdup")) {
+                               const char *pfx = prefix;
+                               if (!is_inside_work_tree()) {
+                                       const char *work_tree =
+                                               get_git_work_tree();
+                                       if (work_tree)
+                                               printf("%s\n", work_tree);
+                                       continue;
+                               }
+                               while (pfx) {
+                                       pfx = strchr(pfx, '/');
+                                       if (pfx) {
+                                               pfx++;
+                                               printf("../");
+                                       }
+                               }
+                               putchar('\n');
+                               continue;
+                       }
+                       if (!strcmp(arg, "--git-dir")) {
+                               const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
+                               static char cwd[PATH_MAX];
+                               int len;
+                               if (gitdir) {
+                                       puts(gitdir);
+                                       continue;
+                               }
+                               if (!prefix) {
+                                       puts(".git");
+                                       continue;
+                               }
+                               if (!getcwd(cwd, PATH_MAX))
+                                       die_errno("unable to get current working directory");
+                               len = strlen(cwd);
+                               printf("%s%s.git\n", cwd, len && cwd[len-1] != '/' ? "/" : "");
+                               continue;
+                       }
+                       if (!strcmp(arg, "--is-inside-git-dir")) {
+                               printf("%s\n", is_inside_git_dir() ? "true"
+                                               : "false");
+                               continue;
+                       }
+                       if (!strcmp(arg, "--is-inside-work-tree")) {
+                               printf("%s\n", is_inside_work_tree() ? "true"
+                                               : "false");
+                               continue;
+                       }
+                       if (!strcmp(arg, "--is-bare-repository")) {
+                               printf("%s\n", is_bare_repository() ? "true"
+                                               : "false");
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--since=")) {
+                               show_datestring("--max-age=", arg+8);
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--after=")) {
+                               show_datestring("--max-age=", arg+8);
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--before=")) {
+                               show_datestring("--min-age=", arg+9);
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--until=")) {
+                               show_datestring("--min-age=", arg+8);
+                               continue;
+                       }
+                       if (show_flag(arg) && verify)
+                               die_no_single_rev(quiet);
+                       continue;
+               }
+
+               /* Not a flag argument */
+               if (try_difference(arg))
+                       continue;
+               if (try_parent_shorthands(arg))
+                       continue;
+               name = arg;
+               type = NORMAL;
+               if (*arg == '^') {
+                       name++;
+                       type = REVERSED;
+               }
+               if (!get_sha1(name, sha1)) {
+                       if (verify)
+                               revs_count++;
+                       else
+                               show_rev(type, sha1, name);
+                       continue;
+               }
+               if (verify)
+                       die_no_single_rev(quiet);
+               as_is = 1;
+               if (!show_file(arg))
+                       continue;
+               verify_filename(prefix, arg);
+       }
+       if (verify) {
+               if (revs_count == 1) {
+                       show_rev(type, sha1, name);
+                       return 0;
+               } else if (revs_count == 0 && show_default())
+                       return 0;
+               die_no_single_rev(quiet);
+       } else
+               show_default();
+       return 0;
+}
diff --git a/builtin/revert.c b/builtin/revert.c
new file mode 100644 (file)
index 0000000..4b47ace
--- /dev/null
@@ -0,0 +1,596 @@
+#include "cache.h"
+#include "builtin.h"
+#include "object.h"
+#include "commit.h"
+#include "tag.h"
+#include "wt-status.h"
+#include "run-command.h"
+#include "exec_cmd.h"
+#include "utf8.h"
+#include "parse-options.h"
+#include "cache-tree.h"
+#include "diff.h"
+#include "revision.h"
+#include "rerere.h"
+#include "merge-recursive.h"
+#include "refs.h"
+
+/*
+ * This implements the builtins revert and cherry-pick.
+ *
+ * Copyright (c) 2007 Johannes E. Schindelin
+ *
+ * Based on git-revert.sh, which is
+ *
+ * Copyright (c) 2005 Linus Torvalds
+ * Copyright (c) 2005 Junio C Hamano
+ */
+
+static const char * const revert_usage[] = {
+       "git revert [options] <commit-ish>",
+       NULL
+};
+
+static const char * const cherry_pick_usage[] = {
+       "git cherry-pick [options] <commit-ish>",
+       NULL
+};
+
+static int edit, no_replay, no_commit, mainline, signoff, allow_ff;
+static enum { REVERT, CHERRY_PICK } action;
+static struct commit *commit;
+static int commit_argc;
+static const char **commit_argv;
+static int allow_rerere_auto;
+
+static const char *me;
+static const char *strategy;
+
+#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
+
+static char *get_encoding(const char *message);
+
+static const char * const *revert_or_cherry_pick_usage(void)
+{
+       return action == REVERT ? revert_usage : cherry_pick_usage;
+}
+
+static void parse_args(int argc, const char **argv)
+{
+       const char * const * usage_str = revert_or_cherry_pick_usage();
+       int noop;
+       struct option options[] = {
+               OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"),
+               OPT_BOOLEAN('e', "edit", &edit, "edit the commit message"),
+               OPT_BOOLEAN('r', NULL, &noop, "no-op (backward compatibility)"),
+               OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
+               OPT_INTEGER('m', "mainline", &mainline, "parent number"),
+               OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
+               OPT_STRING(0, "strategy", &strategy, "strategy", "merge strategy"),
+               OPT_END(),
+               OPT_END(),
+               OPT_END(),
+       };
+
+       if (action == CHERRY_PICK) {
+               struct option cp_extra[] = {
+                       OPT_BOOLEAN('x', NULL, &no_replay, "append commit name"),
+                       OPT_BOOLEAN(0, "ff", &allow_ff, "allow fast-forward"),
+                       OPT_END(),
+               };
+               if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
+                       die("program error");
+       }
+
+       commit_argc = parse_options(argc, argv, NULL, options, usage_str,
+                                   PARSE_OPT_KEEP_ARGV0 |
+                                   PARSE_OPT_KEEP_UNKNOWN);
+       if (commit_argc < 2)
+               usage_with_options(usage_str, options);
+
+       commit_argv = argv;
+}
+
+struct commit_message {
+       char *parent_label;
+       const char *label;
+       const char *subject;
+       char *reencoded_message;
+       const char *message;
+};
+
+static int get_message(const char *raw_message, struct commit_message *out)
+{
+       const char *encoding;
+       const char *abbrev, *subject;
+       int abbrev_len, subject_len;
+       char *q;
+
+       if (!raw_message)
+               return -1;
+       encoding = get_encoding(raw_message);
+       if (!encoding)
+               encoding = "UTF-8";
+       if (!git_commit_encoding)
+               git_commit_encoding = "UTF-8";
+
+       out->reencoded_message = NULL;
+       out->message = raw_message;
+       if (strcmp(encoding, git_commit_encoding))
+               out->reencoded_message = reencode_string(raw_message,
+                                       git_commit_encoding, encoding);
+       if (out->reencoded_message)
+               out->message = out->reencoded_message;
+
+       abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
+       abbrev_len = strlen(abbrev);
+
+       subject_len = find_commit_subject(out->message, &subject);
+
+       out->parent_label = xmalloc(strlen("parent of ") + abbrev_len +
+                             strlen("... ") + subject_len + 1);
+       q = out->parent_label;
+       q = mempcpy(q, "parent of ", strlen("parent of "));
+       out->label = q;
+       q = mempcpy(q, abbrev, abbrev_len);
+       q = mempcpy(q, "... ", strlen("... "));
+       out->subject = q;
+       q = mempcpy(q, subject, subject_len);
+       *q = '\0';
+       return 0;
+}
+
+static void free_message(struct commit_message *msg)
+{
+       free(msg->parent_label);
+       free(msg->reencoded_message);
+}
+
+static char *get_encoding(const char *message)
+{
+       const char *p = message, *eol;
+
+       if (!p)
+               die ("Could not read commit message of %s",
+                               sha1_to_hex(commit->object.sha1));
+       while (*p && *p != '\n') {
+               for (eol = p + 1; *eol && *eol != '\n'; eol++)
+                       ; /* do nothing */
+               if (!prefixcmp(p, "encoding ")) {
+                       char *result = xmalloc(eol - 8 - p);
+                       strlcpy(result, p + 9, eol - 8 - p);
+                       return result;
+               }
+               p = eol;
+               if (*p == '\n')
+                       p++;
+       }
+       return NULL;
+}
+
+static void add_message_to_msg(struct strbuf *msgbuf, const char *message)
+{
+       const char *p = message;
+       while (*p && (*p != '\n' || p[1] != '\n'))
+               p++;
+
+       if (!*p)
+               strbuf_addstr(msgbuf, sha1_to_hex(commit->object.sha1));
+
+       p += 2;
+       strbuf_addstr(msgbuf, p);
+}
+
+static void set_author_ident_env(const char *message)
+{
+       const char *p = message;
+       if (!p)
+               die ("Could not read commit message of %s",
+                               sha1_to_hex(commit->object.sha1));
+       while (*p && *p != '\n') {
+               const char *eol;
+
+               for (eol = p; *eol && *eol != '\n'; eol++)
+                       ; /* do nothing */
+               if (!prefixcmp(p, "author ")) {
+                       char *line, *pend, *email, *timestamp;
+
+                       p += 7;
+                       line = xmemdupz(p, eol - p);
+                       email = strchr(line, '<');
+                       if (!email)
+                               die ("Could not extract author email from %s",
+                                       sha1_to_hex(commit->object.sha1));
+                       if (email == line)
+                               pend = line;
+                       else
+                               for (pend = email; pend != line + 1 &&
+                                               isspace(pend[-1]); pend--);
+                                       ; /* do nothing */
+                       *pend = '\0';
+                       email++;
+                       timestamp = strchr(email, '>');
+                       if (!timestamp)
+                               die ("Could not extract author time from %s",
+                                       sha1_to_hex(commit->object.sha1));
+                       *timestamp = '\0';
+                       for (timestamp++; *timestamp && isspace(*timestamp);
+                                       timestamp++)
+                               ; /* do nothing */
+                       setenv("GIT_AUTHOR_NAME", line, 1);
+                       setenv("GIT_AUTHOR_EMAIL", email, 1);
+                       setenv("GIT_AUTHOR_DATE", timestamp, 1);
+                       free(line);
+                       return;
+               }
+               p = eol;
+               if (*p == '\n')
+                       p++;
+       }
+       die ("No author information found in %s",
+                       sha1_to_hex(commit->object.sha1));
+}
+
+static void advise(const char *advice, ...)
+{
+       va_list params;
+
+       va_start(params, advice);
+       vreportf("hint: ", advice, params);
+       va_end(params);
+}
+
+static void print_advice(void)
+{
+       char *msg = getenv("GIT_CHERRY_PICK_HELP");
+
+       if (msg) {
+               fprintf(stderr, "%s\n", msg);
+               return;
+       }
+
+       advise("after resolving the conflicts, mark the corrected paths");
+       advise("with 'git add <paths>' or 'git rm <paths>'");
+
+       if (action == CHERRY_PICK)
+               advise("and commit the result with 'git commit -c %s'",
+                      find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
+}
+
+static void write_message(struct strbuf *msgbuf, const char *filename)
+{
+       static struct lock_file msg_file;
+
+       int msg_fd = hold_lock_file_for_update(&msg_file, filename,
+                                              LOCK_DIE_ON_ERROR);
+       if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0)
+               die_errno("Could not write to %s.", filename);
+       strbuf_release(msgbuf);
+       if (commit_lock_file(&msg_file) < 0)
+               die("Error wrapping up %s", filename);
+}
+
+static struct tree *empty_tree(void)
+{
+       struct tree *tree = xcalloc(1, sizeof(struct tree));
+
+       tree->object.parsed = 1;
+       tree->object.type = OBJ_TREE;
+       pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);
+       return tree;
+}
+
+static NORETURN void die_dirty_index(const char *me)
+{
+       if (read_cache_unmerged()) {
+               die_resolve_conflict(me);
+       } else {
+               if (advice_commit_before_merge)
+                       die("Your local changes would be overwritten by %s.\n"
+                           "Please, commit your changes or stash them to proceed.", me);
+               else
+                       die("Your local changes would be overwritten by %s.\n", me);
+       }
+}
+
+static int fast_forward_to(const unsigned char *to, const unsigned char *from)
+{
+       struct ref_lock *ref_lock;
+
+       read_cache();
+       if (checkout_fast_forward(from, to))
+               exit(1); /* the callee should have complained already */
+       ref_lock = lock_any_ref_for_update("HEAD", from, 0);
+       return write_ref_sha1(ref_lock, to, "cherry-pick");
+}
+
+static int do_recursive_merge(struct commit *base, struct commit *next,
+                             const char *base_label, const char *next_label,
+                             unsigned char *head, struct strbuf *msgbuf)
+{
+       struct merge_options o;
+       struct tree *result, *next_tree, *base_tree, *head_tree;
+       int clean, index_fd;
+       static struct lock_file index_lock;
+
+       index_fd = hold_locked_index(&index_lock, 1);
+
+       read_cache();
+
+       /*
+        * NEEDSWORK: cherry-picking between branches with
+        * different end-of-line normalization is a pain;
+        * plumb in an option to set o.renormalize?
+        * (or better: arbitrary -X options)
+        */
+       init_merge_options(&o);
+       o.ancestor = base ? base_label : "(empty tree)";
+       o.branch1 = "HEAD";
+       o.branch2 = next ? next_label : "(empty tree)";
+
+       head_tree = parse_tree_indirect(head);
+       next_tree = next ? next->tree : empty_tree();
+       base_tree = base ? base->tree : empty_tree();
+
+       clean = merge_trees(&o,
+                           head_tree,
+                           next_tree, base_tree, &result);
+
+       if (active_cache_changed &&
+           (write_cache(index_fd, active_cache, active_nr) ||
+            commit_locked_index(&index_lock)))
+               die("%s: Unable to write new index file", me);
+       rollback_lock_file(&index_lock);
+
+       if (!clean) {
+               int i;
+               strbuf_addstr(msgbuf, "\nConflicts:\n\n");
+               for (i = 0; i < active_nr;) {
+                       struct cache_entry *ce = active_cache[i++];
+                       if (ce_stage(ce)) {
+                               strbuf_addch(msgbuf, '\t');
+                               strbuf_addstr(msgbuf, ce->name);
+                               strbuf_addch(msgbuf, '\n');
+                               while (i < active_nr && !strcmp(ce->name,
+                                               active_cache[i]->name))
+                                       i++;
+                       }
+               }
+       }
+
+       return !clean;
+}
+
+/*
+ * 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.
+ */
+static int run_git_commit(const char *defmsg)
+{
+       /* 6 is max possible length of our args array including NULL */
+       const char *args[6];
+       int i = 0;
+
+       args[i++] = "commit";
+       args[i++] = "-n";
+       if (signoff)
+               args[i++] = "-s";
+       if (!edit) {
+               args[i++] = "-F";
+               args[i++] = defmsg;
+       }
+       args[i] = NULL;
+
+       return run_command_v_opt(args, RUN_GIT_CMD);
+}
+
+static int do_pick_commit(void)
+{
+       unsigned char head[20];
+       struct commit *base, *next, *parent;
+       const char *base_label, *next_label;
+       struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
+       char *defmsg = NULL;
+       struct strbuf msgbuf = STRBUF_INIT;
+       int res;
+
+       if (no_commit) {
+               /*
+                * We do not intend to commit immediately.  We just want to
+                * merge the differences in, so let's compute the tree
+                * that represents the "current" state for merge-recursive
+                * to work on.
+                */
+               if (write_cache_as_tree(head, 0, NULL))
+                       die ("Your index file is unmerged.");
+       } else {
+               if (get_sha1("HEAD", head))
+                       die ("You do not have a valid HEAD");
+               if (index_differs_from("HEAD", 0))
+                       die_dirty_index(me);
+       }
+       discard_cache();
+
+       if (!commit->parents) {
+               if (action == REVERT)
+                       die ("Cannot revert a root commit");
+               parent = NULL;
+       }
+       else if (commit->parents->next) {
+               /* Reverting or cherry-picking a merge commit */
+               int cnt;
+               struct commit_list *p;
+
+               if (!mainline)
+                       die("Commit %s is a merge but no -m option was given.",
+                           sha1_to_hex(commit->object.sha1));
+
+               for (cnt = 1, p = commit->parents;
+                    cnt != mainline && p;
+                    cnt++)
+                       p = p->next;
+               if (cnt != mainline || !p)
+                       die("Commit %s does not have parent %d",
+                           sha1_to_hex(commit->object.sha1), mainline);
+               parent = p->item;
+       } else if (0 < mainline)
+               die("Mainline was specified but commit %s is not a merge.",
+                   sha1_to_hex(commit->object.sha1));
+       else
+               parent = commit->parents->item;
+
+       if (allow_ff && !hashcmp(parent->object.sha1, head))
+               return fast_forward_to(commit->object.sha1, head);
+
+       if (parent && parse_commit(parent) < 0)
+               die("%s: cannot parse parent commit %s",
+                   me, sha1_to_hex(parent->object.sha1));
+
+       if (get_message(commit->buffer, &msg) != 0)
+               die("Cannot get commit message for %s",
+                               sha1_to_hex(commit->object.sha1));
+
+       /*
+        * "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.
+        */
+
+       defmsg = git_pathdup("MERGE_MSG");
+
+       if (action == REVERT) {
+               base = commit;
+               base_label = msg.label;
+               next = parent;
+               next_label = msg.parent_label;
+               strbuf_addstr(&msgbuf, "Revert \"");
+               strbuf_addstr(&msgbuf, msg.subject);
+               strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit ");
+               strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
+
+               if (commit->parents->next) {
+                       strbuf_addstr(&msgbuf, ", reversing\nchanges made to ");
+                       strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1));
+               }
+               strbuf_addstr(&msgbuf, ".\n");
+       } else {
+               base = parent;
+               base_label = msg.parent_label;
+               next = commit;
+               next_label = msg.label;
+               set_author_ident_env(msg.message);
+               add_message_to_msg(&msgbuf, msg.message);
+               if (no_replay) {
+                       strbuf_addstr(&msgbuf, "(cherry picked from commit ");
+                       strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
+                       strbuf_addstr(&msgbuf, ")\n");
+               }
+       }
+
+       if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) {
+               res = do_recursive_merge(base, next, base_label, next_label,
+                                        head, &msgbuf);
+               write_message(&msgbuf, defmsg);
+       } else {
+               struct commit_list *common = NULL;
+               struct commit_list *remotes = NULL;
+
+               write_message(&msgbuf, defmsg);
+
+               commit_list_insert(base, &common);
+               commit_list_insert(next, &remotes);
+               res = try_merge_command(strategy, common,
+                                       sha1_to_hex(head), remotes);
+               free_commit_list(common);
+               free_commit_list(remotes);
+       }
+
+       if (res) {
+               error("could not %s %s... %s",
+                     action == REVERT ? "revert" : "apply",
+                     find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV),
+                     msg.subject);
+               print_advice();
+               rerere(allow_rerere_auto);
+       } else {
+               if (!no_commit)
+                       res = run_git_commit(defmsg);
+       }
+
+       free_message(&msg);
+       free(defmsg);
+
+       return res;
+}
+
+static void prepare_revs(struct rev_info *revs)
+{
+       int argc;
+
+       init_revisions(revs, NULL);
+       revs->no_walk = 1;
+       if (action != REVERT)
+               revs->reverse = 1;
+
+       argc = setup_revisions(commit_argc, commit_argv, revs, NULL);
+       if (argc > 1)
+               usage(*revert_or_cherry_pick_usage());
+
+       if (prepare_revision_walk(revs))
+               die("revision walk setup failed");
+
+       if (!revs->commits)
+               die("empty commit set passed");
+}
+
+static int revert_or_cherry_pick(int argc, const char **argv)
+{
+       struct rev_info revs;
+
+       git_config(git_default_config, NULL);
+       me = action == REVERT ? "revert" : "cherry-pick";
+       setenv(GIT_REFLOG_ACTION, me, 0);
+       parse_args(argc, argv);
+
+       if (allow_ff) {
+               if (signoff)
+                       die("cherry-pick --ff cannot be used with --signoff");
+               if (no_commit)
+                       die("cherry-pick --ff cannot be used with --no-commit");
+               if (no_replay)
+                       die("cherry-pick --ff cannot be used with -x");
+               if (edit)
+                       die("cherry-pick --ff cannot be used with --edit");
+       }
+
+       if (read_cache() < 0)
+               die("git %s: failed to read the index", me);
+
+       prepare_revs(&revs);
+
+       while ((commit = get_revision(&revs))) {
+               int res = do_pick_commit();
+               if (res)
+                       return res;
+       }
+
+       return 0;
+}
+
+int cmd_revert(int argc, const char **argv, const char *prefix)
+{
+       if (isatty(0))
+               edit = 1;
+       action = REVERT;
+       return revert_or_cherry_pick(argc, argv);
+}
+
+int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
+{
+       action = CHERRY_PICK;
+       return revert_or_cherry_pick(argc, argv);
+}
diff --git a/builtin/rm.c b/builtin/rm.c
new file mode 100644 (file)
index 0000000..f3772c8
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+ * "git rm" builtin command
+ *
+ * Copyright (C) Linus Torvalds 2006
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "dir.h"
+#include "cache-tree.h"
+#include "tree-walk.h"
+#include "parse-options.h"
+
+static const char * const builtin_rm_usage[] = {
+       "git rm [options] [--] <file>...",
+       NULL
+};
+
+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 check_local_mod(unsigned char *head, int index_only)
+{
+       /*
+        * Items in list are already sorted in the cache order,
+        * so we could do this a lot more efficiently by using
+        * tree_desc based traversal if we wanted to, but I am
+        * lazy, and who cares if removal of files is a tad
+        * slower than the theoretical maximum speed?
+        */
+       int i, no_head;
+       int errs = 0;
+
+       no_head = is_null_sha1(head);
+       for (i = 0; i < list.nr; i++) {
+               struct stat st;
+               int pos;
+               struct cache_entry *ce;
+               const char *name = list.name[i];
+               unsigned char sha1[20];
+               unsigned mode;
+               int local_changes = 0;
+               int staged_changes = 0;
+
+               pos = cache_name_pos(name, strlen(name));
+               if (pos < 0)
+                       continue; /* removing unmerged entry */
+               ce = active_cache[pos];
+
+               if (lstat(ce->name, &st) < 0) {
+                       if (errno != ENOENT)
+                               warning("'%s': %s", ce->name, strerror(errno));
+                       /* It already vanished from the working tree */
+                       continue;
+               }
+               else if (S_ISDIR(st.st_mode)) {
+                       /* if a file was removed and it is now a
+                        * directory, that is the same as ENOENT as
+                        * far as git is concerned; we do not track
+                        * directories.
+                        */
+                       continue;
+               }
+
+               /*
+                * "rm" of a path that has changes need to be treated
+                * carefully not to allow losing local changes
+                * accidentally.  A local change could be (1) file in
+                * work tree is different since the index; and/or (2)
+                * the user staged a content that is different from
+                * the current commit in the index.
+                *
+                * In such a case, you would need to --force the
+                * removal.  However, "rm --cached" (remove only from
+                * the index) is safe if the index matches the file in
+                * the work tree or the HEAD commit, as it means that
+                * the content being removed is available elsewhere.
+                */
+
+               /*
+                * Is the index different from the file in the work tree?
+                */
+               if (ce_match_stat(ce, &st, 0))
+                       local_changes = 1;
+
+               /*
+                * Is the index different from the HEAD commit?  By
+                * definition, before the very initial commit,
+                * anything staged in the index is treated by the same
+                * way as changed from the HEAD.
+                */
+               if (no_head
+                    || get_tree_entry(head, name, sha1, &mode)
+                    || ce->ce_mode != create_ce_mode(mode)
+                    || hashcmp(ce->sha1, sha1))
+                       staged_changes = 1;
+
+               /*
+                * If the index does not match the file in the work
+                * tree and if it does not match the HEAD commit
+                * either, (1) "git rm" without --cached definitely
+                * will lose information; (2) "git rm --cached" will
+                * lose information unless it is about removing an
+                * "intent to add" entry.
+                */
+               if (local_changes && staged_changes) {
+                       if (!index_only || !(ce->ce_flags & CE_INTENT_TO_ADD))
+                               errs = error("'%s' has staged content different "
+                                            "from both the file and the HEAD\n"
+                                            "(use -f to force removal)", name);
+               }
+               else if (!index_only) {
+                       if (staged_changes)
+                               errs = error("'%s' has changes staged in the index\n"
+                                            "(use --cached to keep the file, "
+                                            "or -f to force removal)", name);
+                       if (local_changes)
+                               errs = error("'%s' has local modifications\n"
+                                            "(use --cached to keep the file, "
+                                            "or -f to force removal)", name);
+               }
+       }
+       return errs;
+}
+
+static struct lock_file lock_file;
+
+static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
+static int ignore_unmatch = 0;
+
+static struct option builtin_rm_options[] = {
+       OPT__DRY_RUN(&show_only),
+       OPT__QUIET(&quiet),
+       OPT_BOOLEAN( 0 , "cached",         &index_only, "only remove from the index"),
+       OPT_BOOLEAN('f', "force",          &force,      "override the up-to-date check"),
+       OPT_BOOLEAN('r', NULL,             &recursive,  "allow recursive removal"),
+       OPT_BOOLEAN( 0 , "ignore-unmatch", &ignore_unmatch,
+                               "exit with a zero status even if nothing matched"),
+       OPT_END(),
+};
+
+int cmd_rm(int argc, const char **argv, const char *prefix)
+{
+       int i, newfd;
+       const char **pathspec;
+       char *seen;
+
+       git_config(git_default_config, NULL);
+
+       argc = parse_options(argc, argv, prefix, builtin_rm_options,
+                            builtin_rm_usage, 0);
+       if (!argc)
+               usage_with_options(builtin_rm_usage, builtin_rm_options);
+
+       if (!index_only)
+               setup_work_tree();
+
+       newfd = hold_locked_index(&lock_file, 1);
+
+       if (read_cache() < 0)
+               die("index file corrupt");
+
+       pathspec = get_pathspec(prefix, argv);
+       refresh_index(&the_index, REFRESH_QUIET, pathspec, NULL, NULL);
+
+       seen = NULL;
+       for (i = 0; pathspec[i] ; i++)
+               /* nothing */;
+       seen = xcalloc(i, 1);
+
+       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;
+               int seen_any = 0;
+               for (i = 0; (match = pathspec[i]) != NULL ; i++) {
+                       if (!seen[i]) {
+                               if (!ignore_unmatch) {
+                                       die("pathspec '%s' did not match any files",
+                                           match);
+                               }
+                       }
+                       else {
+                               seen_any = 1;
+                       }
+                       if (!recursive && seen[i] == MATCHED_RECURSIVELY)
+                               die("not removing '%s' recursively without -r",
+                                   *match ? match : ".");
+               }
+
+               if (! seen_any)
+                       exit(0);
+       }
+
+       /*
+        * If not forced, the file, the index and the HEAD (if exists)
+        * must match; but the file can already been removed, since
+        * this sequence is a natural "novice" way:
+        *
+        *      rm F; git rm F
+        *
+        * Further, if HEAD commit exists, "diff-index --cached" must
+        * report no changes unless forced.
+        */
+       if (!force) {
+               unsigned char sha1[20];
+               if (get_sha1("HEAD", sha1))
+                       hashclr(sha1);
+               if (check_local_mod(sha1, index_only))
+                       exit(1);
+       }
+
+       /*
+        * 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];
+               if (!quiet)
+                       printf("rm '%s'\n", path);
+
+               if (remove_file_from_cache(path))
+                       die("git rm: unable to remove %s", path);
+       }
+
+       if (show_only)
+               return 0;
+
+       /*
+        * Then, unless we used "--cached", remove the filenames from
+        * the workspace. If we fail to remove the first one, we
+        * abort the "git rm" (but once we've successfully removed
+        * any file at all, we'll go ahead and commit to it all:
+        * by then we've already committed ourselves and can't fail
+        * in the middle)
+        */
+       if (!index_only) {
+               int removed = 0;
+               for (i = 0; i < list.nr; i++) {
+                       const char *path = list.name[i];
+                       if (!remove_path(path)) {
+                               removed = 1;
+                               continue;
+                       }
+                       if (!removed)
+                               die_errno("git rm: '%s'", path);
+               }
+       }
+
+       if (active_cache_changed) {
+               if (write_cache(newfd, active_cache, active_nr) ||
+                   commit_locked_index(&lock_file))
+                       die("Unable to write new index file");
+       }
+
+       return 0;
+}
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
new file mode 100644 (file)
index 0000000..481602d
--- /dev/null
@@ -0,0 +1,531 @@
+#include "cache.h"
+#include "commit.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include "sideband.h"
+#include "run-command.h"
+#include "remote.h"
+#include "send-pack.h"
+#include "quote.h"
+#include "transport.h"
+
+static const char send_pack_usage[] =
+"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
+"  --all and explicit <ref> specification are mutually exclusive.";
+
+static struct send_pack_args args;
+
+static int feed_object(const unsigned char *sha1, int fd, int negative)
+{
+       char buf[42];
+
+       if (negative && !has_sha1_file(sha1))
+               return 1;
+
+       memcpy(buf + negative, sha1_to_hex(sha1), 40);
+       if (negative)
+               buf[0] = '^';
+       buf[40 + negative] = '\n';
+       return write_or_whine(fd, buf, 41 + negative, "send-pack: send refs");
+}
+
+/*
+ * Make a pack stream and spit it out into file descriptor fd
+ */
+static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *extra, struct send_pack_args *args)
+{
+       /*
+        * The child becomes pack-objects --revs; we feed
+        * the revision parameters to it via its stdin and
+        * let its stdout go back to the other end.
+        */
+       const char *argv[] = {
+               "pack-objects",
+               "--all-progress-implied",
+               "--revs",
+               "--stdout",
+               NULL,
+               NULL,
+               NULL,
+               NULL,
+       };
+       struct child_process po;
+       int i;
+
+       i = 4;
+       if (args->use_thin_pack)
+               argv[i++] = "--thin";
+       if (args->use_ofs_delta)
+               argv[i++] = "--delta-base-offset";
+       if (args->quiet)
+               argv[i++] = "-q";
+       memset(&po, 0, sizeof(po));
+       po.argv = argv;
+       po.in = -1;
+       po.out = args->stateless_rpc ? -1 : fd;
+       po.git_cmd = 1;
+       if (start_command(&po))
+               die_errno("git pack-objects failed");
+
+       /*
+        * We feed the pack-objects we just spawned with revision
+        * parameters by writing to the pipe.
+        */
+       for (i = 0; i < extra->nr; i++)
+               if (!feed_object(extra->array[i], po.in, 1))
+                       break;
+
+       while (refs) {
+               if (!is_null_sha1(refs->old_sha1) &&
+                   !feed_object(refs->old_sha1, po.in, 1))
+                       break;
+               if (!is_null_sha1(refs->new_sha1) &&
+                   !feed_object(refs->new_sha1, po.in, 0))
+                       break;
+               refs = refs->next;
+       }
+
+       close(po.in);
+
+       if (args->stateless_rpc) {
+               char *buf = xmalloc(LARGE_PACKET_MAX);
+               while (1) {
+                       ssize_t n = xread(po.out, buf, LARGE_PACKET_MAX);
+                       if (n <= 0)
+                               break;
+                       send_sideband(fd, -1, buf, n, LARGE_PACKET_MAX);
+               }
+               free(buf);
+               close(po.out);
+               po.out = -1;
+       }
+
+       if (finish_command(&po))
+               return error("pack-objects died with strange error");
+       return 0;
+}
+
+static int receive_status(int in, struct ref *refs)
+{
+       struct ref *hint;
+       char line[1000];
+       int ret = 0;
+       int len = packet_read_line(in, line, sizeof(line));
+       if (len < 10 || memcmp(line, "unpack ", 7))
+               return error("did not receive remote status");
+       if (memcmp(line, "unpack ok\n", 10)) {
+               char *p = line + strlen(line) - 1;
+               if (*p == '\n')
+                       *p = '\0';
+               error("unpack failed: %s", line + 7);
+               ret = -1;
+       }
+       hint = NULL;
+       while (1) {
+               char *refname;
+               char *msg;
+               len = packet_read_line(in, line, sizeof(line));
+               if (!len)
+                       break;
+               if (len < 3 ||
+                   (memcmp(line, "ok ", 3) && memcmp(line, "ng ", 3))) {
+                       fprintf(stderr, "protocol error: %s\n", line);
+                       ret = -1;
+                       break;
+               }
+
+               line[strlen(line)-1] = '\0';
+               refname = line + 3;
+               msg = strchr(refname, ' ');
+               if (msg)
+                       *msg++ = '\0';
+
+               /* first try searching at our hint, falling back to all refs */
+               if (hint)
+                       hint = find_ref_by_name(hint, refname);
+               if (!hint)
+                       hint = find_ref_by_name(refs, refname);
+               if (!hint) {
+                       warning("remote reported status on unknown ref: %s",
+                                       refname);
+                       continue;
+               }
+               if (hint->status != REF_STATUS_EXPECTING_REPORT) {
+                       warning("remote reported status on unexpected ref: %s",
+                                       refname);
+                       continue;
+               }
+
+               if (line[0] == 'o' && line[1] == 'k')
+                       hint->status = REF_STATUS_OK;
+               else {
+                       hint->status = REF_STATUS_REMOTE_REJECT;
+                       ret = -1;
+               }
+               if (msg)
+                       hint->remote_status = xstrdup(msg);
+               /* start our next search from the next ref */
+               hint = hint->next;
+       }
+       return ret;
+}
+
+static void print_helper_status(struct ref *ref)
+{
+       struct strbuf buf = STRBUF_INIT;
+
+       for (; ref; ref = ref->next) {
+               const char *msg = NULL;
+               const char *res;
+
+               switch(ref->status) {
+               case REF_STATUS_NONE:
+                       res = "error";
+                       msg = "no match";
+                       break;
+
+               case REF_STATUS_OK:
+                       res = "ok";
+                       break;
+
+               case REF_STATUS_UPTODATE:
+                       res = "ok";
+                       msg = "up to date";
+                       break;
+
+               case REF_STATUS_REJECT_NONFASTFORWARD:
+                       res = "error";
+                       msg = "non-fast forward";
+                       break;
+
+               case REF_STATUS_REJECT_NODELETE:
+               case REF_STATUS_REMOTE_REJECT:
+                       res = "error";
+                       break;
+
+               case REF_STATUS_EXPECTING_REPORT:
+               default:
+                       continue;
+               }
+
+               strbuf_reset(&buf);
+               strbuf_addf(&buf, "%s %s", res, ref->name);
+               if (ref->remote_status)
+                       msg = ref->remote_status;
+               if (msg) {
+                       strbuf_addch(&buf, ' ');
+                       quote_two_c_style(&buf, "", msg, 0);
+               }
+               strbuf_addch(&buf, '\n');
+
+               safe_write(1, buf.buf, buf.len);
+       }
+       strbuf_release(&buf);
+}
+
+static int sideband_demux(int in, int out, void *data)
+{
+       int *fd = data;
+       int ret = recv_sideband("send-pack", fd[0], out);
+       close(out);
+       return ret;
+}
+
+int send_pack(struct send_pack_args *args,
+             int fd[], struct child_process *conn,
+             struct ref *remote_refs,
+             struct extra_have_objects *extra_have)
+{
+       int in = fd[0];
+       int out = fd[1];
+       struct strbuf req_buf = STRBUF_INIT;
+       struct ref *ref;
+       int new_refs;
+       int allow_deleting_refs = 0;
+       int status_report = 0;
+       int use_sideband = 0;
+       unsigned cmds_sent = 0;
+       int ret;
+       struct async demux;
+
+       /* Does the other end support the reporting? */
+       if (server_supports("report-status"))
+               status_report = 1;
+       if (server_supports("delete-refs"))
+               allow_deleting_refs = 1;
+       if (server_supports("ofs-delta"))
+               args->use_ofs_delta = 1;
+       if (server_supports("side-band-64k"))
+               use_sideband = 1;
+
+       if (!remote_refs) {
+               fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
+                       "Perhaps you should specify a branch such as 'master'.\n");
+               return 0;
+       }
+
+       /*
+        * Finally, tell the other end!
+        */
+       new_refs = 0;
+       for (ref = remote_refs; ref; ref = ref->next) {
+               if (!ref->peer_ref && !args->send_mirror)
+                       continue;
+
+               /* Check for statuses set by set_ref_status_for_push() */
+               switch (ref->status) {
+               case REF_STATUS_REJECT_NONFASTFORWARD:
+               case REF_STATUS_UPTODATE:
+                       continue;
+               default:
+                       ; /* do nothing */
+               }
+
+               if (ref->deletion && !allow_deleting_refs) {
+                       ref->status = REF_STATUS_REJECT_NODELETE;
+                       continue;
+               }
+
+               if (!ref->deletion)
+                       new_refs++;
+
+               if (args->dry_run) {
+                       ref->status = REF_STATUS_OK;
+               } else {
+                       char *old_hex = sha1_to_hex(ref->old_sha1);
+                       char *new_hex = sha1_to_hex(ref->new_sha1);
+
+                       if (!cmds_sent && (status_report || use_sideband)) {
+                               packet_buf_write(&req_buf, "%s %s %s%c%s%s",
+                                       old_hex, new_hex, ref->name, 0,
+                                       status_report ? " report-status" : "",
+                                       use_sideband ? " side-band-64k" : "");
+                       }
+                       else
+                               packet_buf_write(&req_buf, "%s %s %s",
+                                       old_hex, new_hex, ref->name);
+                       ref->status = status_report ?
+                               REF_STATUS_EXPECTING_REPORT :
+                               REF_STATUS_OK;
+                       cmds_sent++;
+               }
+       }
+
+       if (args->stateless_rpc) {
+               if (!args->dry_run && cmds_sent) {
+                       packet_buf_flush(&req_buf);
+                       send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX);
+               }
+       } else {
+               safe_write(out, req_buf.buf, req_buf.len);
+               packet_flush(out);
+       }
+       strbuf_release(&req_buf);
+
+       if (use_sideband && cmds_sent) {
+               memset(&demux, 0, sizeof(demux));
+               demux.proc = sideband_demux;
+               demux.data = fd;
+               demux.out = -1;
+               if (start_async(&demux))
+                       die("receive-pack: unable to fork off sideband demultiplexer");
+               in = demux.out;
+       }
+
+       if (new_refs && cmds_sent) {
+               if (pack_objects(out, remote_refs, extra_have, args) < 0) {
+                       for (ref = remote_refs; ref; ref = ref->next)
+                               ref->status = REF_STATUS_NONE;
+                       if (use_sideband)
+                               finish_async(&demux);
+                       return -1;
+               }
+       }
+       if (args->stateless_rpc && cmds_sent)
+               packet_flush(out);
+
+       if (status_report && cmds_sent)
+               ret = receive_status(in, remote_refs);
+       else
+               ret = 0;
+       if (args->stateless_rpc)
+               packet_flush(out);
+
+       if (use_sideband && cmds_sent) {
+               if (finish_async(&demux)) {
+                       error("error in sideband demultiplexer");
+                       ret = -1;
+               }
+               close(demux.out);
+       }
+
+       if (ret < 0)
+               return ret;
+
+       if (args->porcelain)
+               return 0;
+
+       for (ref = remote_refs; ref; ref = ref->next) {
+               switch (ref->status) {
+               case REF_STATUS_NONE:
+               case REF_STATUS_UPTODATE:
+               case REF_STATUS_OK:
+                       break;
+               default:
+                       return -1;
+               }
+       }
+       return 0;
+}
+
+int cmd_send_pack(int argc, const char **argv, const char *prefix)
+{
+       int i, nr_refspecs = 0;
+       const char **refspecs = NULL;
+       const char *remote_name = NULL;
+       struct remote *remote = NULL;
+       const char *dest = NULL;
+       int fd[2];
+       struct child_process *conn;
+       struct extra_have_objects extra_have;
+       struct ref *remote_refs, *local_refs;
+       int ret;
+       int helper_status = 0;
+       int send_all = 0;
+       const char *receivepack = "git-receive-pack";
+       int flags;
+       int nonfastforward = 0;
+
+       argv++;
+       for (i = 1; i < argc; i++, argv++) {
+               const char *arg = *argv;
+
+               if (*arg == '-') {
+                       if (!prefixcmp(arg, "--receive-pack=")) {
+                               receivepack = arg + 15;
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--exec=")) {
+                               receivepack = arg + 7;
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--remote=")) {
+                               remote_name = arg + 9;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--all")) {
+                               send_all = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--dry-run")) {
+                               args.dry_run = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--mirror")) {
+                               args.send_mirror = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--force")) {
+                               args.force_update = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--verbose")) {
+                               args.verbose = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--thin")) {
+                               args.use_thin_pack = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--stateless-rpc")) {
+                               args.stateless_rpc = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--helper-status")) {
+                               helper_status = 1;
+                               continue;
+                       }
+                       usage(send_pack_usage);
+               }
+               if (!dest) {
+                       dest = arg;
+                       continue;
+               }
+               refspecs = (const char **) argv;
+               nr_refspecs = argc - i;
+               break;
+       }
+       if (!dest)
+               usage(send_pack_usage);
+       /*
+        * --all and --mirror are incompatible; neither makes sense
+        * with any refspecs.
+        */
+       if ((refspecs && (send_all || args.send_mirror)) ||
+           (send_all && args.send_mirror))
+               usage(send_pack_usage);
+
+       if (remote_name) {
+               remote = remote_get(remote_name);
+               if (!remote_has_url(remote, dest)) {
+                       die("Destination %s is not a uri for %s",
+                           dest, remote_name);
+               }
+       }
+
+       if (args.stateless_rpc) {
+               conn = NULL;
+               fd[0] = 0;
+               fd[1] = 1;
+       } else {
+               conn = git_connect(fd, dest, receivepack,
+                       args.verbose ? CONNECT_VERBOSE : 0);
+       }
+
+       memset(&extra_have, 0, sizeof(extra_have));
+
+       get_remote_heads(fd[0], &remote_refs, 0, NULL, REF_NORMAL,
+                        &extra_have);
+
+       transport_verify_remote_names(nr_refspecs, refspecs);
+
+       local_refs = get_local_heads();
+
+       flags = MATCH_REFS_NONE;
+
+       if (send_all)
+               flags |= MATCH_REFS_ALL;
+       if (args.send_mirror)
+               flags |= MATCH_REFS_MIRROR;
+
+       /* match them up */
+       if (match_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags))
+               return -1;
+
+       set_ref_status_for_push(remote_refs, args.send_mirror,
+               args.force_update);
+
+       ret = send_pack(&args, fd, conn, remote_refs, &extra_have);
+
+       if (helper_status)
+               print_helper_status(remote_refs);
+
+       close(fd[1]);
+       close(fd[0]);
+
+       ret |= finish_connect(conn);
+
+       if (!helper_status)
+               transport_print_push_status(dest, remote_refs, args.verbose, 0, &nonfastforward);
+
+       if (!args.dry_run && remote) {
+               struct ref *ref;
+               for (ref = remote_refs; ref; ref = ref->next)
+                       transport_update_tracking_ref(remote, ref, args.verbose);
+       }
+
+       if (!ret && !transport_refs_pushed(remote_refs))
+               fprintf(stderr, "Everything up-to-date\n");
+
+       return ret;
+}
diff --git a/builtin/shortlog.c b/builtin/shortlog.c
new file mode 100644 (file)
index 0000000..2135b0d
--- /dev/null
@@ -0,0 +1,356 @@
+#include "builtin.h"
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "string-list.h"
+#include "revision.h"
+#include "utf8.h"
+#include "mailmap.h"
+#include "shortlog.h"
+#include "parse-options.h"
+
+static char const * const shortlog_usage[] = {
+       "git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] [<commit-id>... ]",
+       "",
+       "[rev-opts] are documented in git-rev-list(1)",
+       NULL
+};
+
+static int compare_by_number(const void *a1, const void *a2)
+{
+       const struct string_list_item *i1 = a1, *i2 = a2;
+       const struct string_list *l1 = i1->util, *l2 = i2->util;
+
+       if (l1->nr < l2->nr)
+               return 1;
+       else if (l1->nr == l2->nr)
+               return 0;
+       else
+               return -1;
+}
+
+const char *format_subject(struct strbuf *sb, const char *msg,
+                          const char *line_separator);
+
+static void insert_one_record(struct shortlog *log,
+                             const char *author,
+                             const char *oneline)
+{
+       const char *dot3 = log->common_repo_prefix;
+       char *buffer, *p;
+       struct string_list_item *item;
+       char namebuf[1024];
+       char emailbuf[1024];
+       size_t len;
+       const char *eol;
+       const char *boemail, *eoemail;
+       struct strbuf subject = STRBUF_INIT;
+
+       boemail = strchr(author, '<');
+       if (!boemail)
+               return;
+       eoemail = strchr(boemail, '>');
+       if (!eoemail)
+               return;
+
+       /* copy author name to namebuf, to support matching on both name and email */
+       memcpy(namebuf, author, boemail - author);
+       len = boemail - author;
+       while (len > 0 && isspace(namebuf[len-1]))
+               len--;
+       namebuf[len] = 0;
+
+       /* copy email name to emailbuf, to allow email replacement as well */
+       memcpy(emailbuf, boemail+1, eoemail - boemail);
+       emailbuf[eoemail - boemail - 1] = 0;
+
+       if (!map_user(&log->mailmap, emailbuf, sizeof(emailbuf), namebuf, sizeof(namebuf))) {
+               while (author < boemail && isspace(*author))
+                       author++;
+               for (len = 0;
+                    len < sizeof(namebuf) - 1 && author + len < boemail;
+                    len++)
+                       namebuf[len] = author[len];
+               while (0 < len && isspace(namebuf[len-1]))
+                       len--;
+               namebuf[len] = '\0';
+       }
+       else
+               len = strlen(namebuf);
+
+       if (log->email) {
+               size_t room = sizeof(namebuf) - len - 1;
+               int maillen = strlen(emailbuf);
+               snprintf(namebuf + len, room, " <%.*s>", maillen, emailbuf);
+       }
+
+       item = string_list_insert(&log->list, namebuf);
+       if (item->util == NULL)
+               item->util = xcalloc(1, sizeof(struct string_list));
+
+       /* Skip any leading whitespace, including any blank lines. */
+       while (*oneline && isspace(*oneline))
+               oneline++;
+       eol = strchr(oneline, '\n');
+       if (!eol)
+               eol = oneline + strlen(oneline);
+       if (!prefixcmp(oneline, "[PATCH")) {
+               char *eob = strchr(oneline, ']');
+               if (eob && (!eol || eob < eol))
+                       oneline = eob + 1;
+       }
+       while (*oneline && isspace(*oneline) && *oneline != '\n')
+               oneline++;
+       format_subject(&subject, oneline, " ");
+       buffer = strbuf_detach(&subject, NULL);
+
+       if (dot3) {
+               int dot3len = strlen(dot3);
+               if (dot3len > 5) {
+                       while ((p = strstr(buffer, dot3)) != NULL) {
+                               int taillen = strlen(p) - dot3len;
+                               memcpy(p, "/.../", 5);
+                               memmove(p + 5, p + dot3len, taillen + 1);
+                       }
+               }
+       }
+
+       string_list_append(item->util, buffer);
+}
+
+static void read_from_stdin(struct shortlog *log)
+{
+       char author[1024], oneline[1024];
+
+       while (fgets(author, sizeof(author), stdin) != NULL) {
+               if (!(author[0] == 'A' || author[0] == 'a') ||
+                   prefixcmp(author + 1, "uthor: "))
+                       continue;
+               while (fgets(oneline, sizeof(oneline), stdin) &&
+                      oneline[0] != '\n')
+                       ; /* discard headers */
+               while (fgets(oneline, sizeof(oneline), stdin) &&
+                      oneline[0] == '\n')
+                       ; /* discard blanks */
+               insert_one_record(log, author + 8, oneline);
+       }
+}
+
+void shortlog_add_commit(struct shortlog *log, struct commit *commit)
+{
+       const char *author = NULL, *buffer;
+       struct strbuf buf = STRBUF_INIT;
+       struct strbuf ufbuf = STRBUF_INIT;
+       struct pretty_print_context ctx = {0};
+
+       pretty_print_commit(CMIT_FMT_RAW, commit, &buf, &ctx);
+       buffer = buf.buf;
+       while (*buffer && *buffer != '\n') {
+               const char *eol = strchr(buffer, '\n');
+
+               if (eol == NULL)
+                       eol = buffer + strlen(buffer);
+               else
+                       eol++;
+
+               if (!prefixcmp(buffer, "author "))
+                       author = buffer + 7;
+               buffer = eol;
+       }
+       if (!author)
+               die("Missing author: %s",
+                   sha1_to_hex(commit->object.sha1));
+       if (log->user_format) {
+               struct pretty_print_context ctx = {0};
+               ctx.abbrev = log->abbrev;
+               ctx.subject = "";
+               ctx.after_subject = "";
+               ctx.date_mode = DATE_NORMAL;
+               pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &ufbuf, &ctx);
+               buffer = ufbuf.buf;
+       } else if (*buffer) {
+               buffer++;
+       }
+       insert_one_record(log, author, !*buffer ? "<none>" : buffer);
+       strbuf_release(&ufbuf);
+       strbuf_release(&buf);
+}
+
+static void get_from_rev(struct rev_info *rev, struct shortlog *log)
+{
+       struct commit *commit;
+
+       if (prepare_revision_walk(rev))
+               die("revision walk setup failed");
+       while ((commit = get_revision(rev)) != NULL)
+               shortlog_add_commit(log, commit);
+}
+
+static int parse_uint(char const **arg, int comma, int defval)
+{
+       unsigned long ul;
+       int ret;
+       char *endp;
+
+       ul = strtoul(*arg, &endp, 10);
+       if (*endp && *endp != comma)
+               return -1;
+       if (ul > INT_MAX)
+               return -1;
+       ret = *arg == endp ? defval : (int)ul;
+       *arg = *endp ? endp + 1 : endp;
+       return ret;
+}
+
+static const char wrap_arg_usage[] = "-w[<width>[,<indent1>[,<indent2>]]]";
+#define DEFAULT_WRAPLEN 76
+#define DEFAULT_INDENT1 6
+#define DEFAULT_INDENT2 9
+
+static int parse_wrap_args(const struct option *opt, const char *arg, int unset)
+{
+       struct shortlog *log = opt->value;
+
+       log->wrap_lines = !unset;
+       if (unset)
+               return 0;
+       if (!arg) {
+               log->wrap = DEFAULT_WRAPLEN;
+               log->in1 = DEFAULT_INDENT1;
+               log->in2 = DEFAULT_INDENT2;
+               return 0;
+       }
+
+       log->wrap = parse_uint(&arg, ',', DEFAULT_WRAPLEN);
+       log->in1 = parse_uint(&arg, ',', DEFAULT_INDENT1);
+       log->in2 = parse_uint(&arg, '\0', DEFAULT_INDENT2);
+       if (log->wrap < 0 || log->in1 < 0 || log->in2 < 0)
+               return error(wrap_arg_usage);
+       if (log->wrap &&
+           ((log->in1 && log->wrap <= log->in1) ||
+            (log->in2 && log->wrap <= log->in2)))
+               return error(wrap_arg_usage);
+       return 0;
+}
+
+void shortlog_init(struct shortlog *log)
+{
+       memset(log, 0, sizeof(*log));
+
+       read_mailmap(&log->mailmap, &log->common_repo_prefix);
+
+       log->list.strdup_strings = 1;
+       log->wrap = DEFAULT_WRAPLEN;
+       log->in1 = DEFAULT_INDENT1;
+       log->in2 = DEFAULT_INDENT2;
+}
+
+int cmd_shortlog(int argc, const char **argv, const char *prefix)
+{
+       static struct shortlog log;
+       static struct rev_info rev;
+       int nongit = !startup_info->have_repository;
+
+       static const struct option options[] = {
+               OPT_BOOLEAN('n', "numbered", &log.sort_by_number,
+                           "sort output according to the number of commits per author"),
+               OPT_BOOLEAN('s', "summary", &log.summary,
+                           "Suppress commit descriptions, only provides commit count"),
+               OPT_BOOLEAN('e', "email", &log.email,
+                           "Show the email address of each author"),
+               { OPTION_CALLBACK, 'w', NULL, &log, "w[,i1[,i2]]",
+                       "Linewrap output", PARSE_OPT_OPTARG, &parse_wrap_args },
+               OPT_END(),
+       };
+
+       struct parse_opt_ctx_t ctx;
+
+       git_config(git_default_config, NULL);
+       shortlog_init(&log);
+       init_revisions(&rev, prefix);
+       parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH |
+                           PARSE_OPT_KEEP_ARGV0);
+
+       for (;;) {
+               switch (parse_options_step(&ctx, options, shortlog_usage)) {
+               case PARSE_OPT_HELP:
+                       exit(129);
+               case PARSE_OPT_DONE:
+                       goto parse_done;
+               }
+               parse_revision_opt(&rev, &ctx, options, shortlog_usage);
+       }
+parse_done:
+       argc = parse_options_end(&ctx);
+
+       if (setup_revisions(argc, argv, &rev, NULL) != 1) {
+               error("unrecognized argument: %s", argv[1]);
+               usage_with_options(shortlog_usage, options);
+       }
+
+       log.user_format = rev.commit_format == CMIT_FMT_USERFORMAT;
+       log.abbrev = rev.abbrev;
+
+       /* assume HEAD if from a tty */
+       if (!nongit && !rev.pending.nr && isatty(0))
+               add_head_to_pending(&rev);
+       if (rev.pending.nr == 0) {
+               if (isatty(0))
+                       fprintf(stderr, "(reading log message from standard input)\n");
+               read_from_stdin(&log);
+       }
+       else
+               get_from_rev(&rev, &log);
+
+       shortlog_output(&log);
+       return 0;
+}
+
+static void add_wrapped_shortlog_msg(struct strbuf *sb, const char *s,
+                                    const struct shortlog *log)
+{
+       int col = strbuf_add_wrapped_text(sb, s, log->in1, log->in2, log->wrap);
+       if (col != log->wrap)
+               strbuf_addch(sb, '\n');
+}
+
+void shortlog_output(struct shortlog *log)
+{
+       int i, j;
+       struct strbuf sb = STRBUF_INIT;
+
+       if (log->sort_by_number)
+               qsort(log->list.items, log->list.nr, sizeof(struct string_list_item),
+                       compare_by_number);
+       for (i = 0; i < log->list.nr; i++) {
+               struct string_list *onelines = log->list.items[i].util;
+
+               if (log->summary) {
+                       printf("%6d\t%s\n", onelines->nr, log->list.items[i].string);
+               } else {
+                       printf("%s (%d):\n", log->list.items[i].string, onelines->nr);
+                       for (j = onelines->nr - 1; j >= 0; j--) {
+                               const char *msg = onelines->items[j].string;
+
+                               if (log->wrap_lines) {
+                                       strbuf_reset(&sb);
+                                       add_wrapped_shortlog_msg(&sb, msg, log);
+                                       fwrite(sb.buf, sb.len, 1, stdout);
+                               }
+                               else
+                                       printf("      %s\n", msg);
+                       }
+                       putchar('\n');
+               }
+
+               onelines->strdup_strings = 1;
+               string_list_clear(onelines, 0);
+               free(onelines);
+               log->list.items[i].util = NULL;
+       }
+
+       strbuf_release(&sb);
+       log->list.strdup_strings = 1;
+       string_list_clear(&log->list, 1);
+       clear_mailmap(&log->mailmap);
+}
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
new file mode 100644 (file)
index 0000000..e8719aa
--- /dev/null
@@ -0,0 +1,968 @@
+#include "cache.h"
+#include "commit.h"
+#include "refs.h"
+#include "builtin.h"
+#include "color.h"
+#include "parse-options.h"
+
+static const char* show_branch_usage[] = {
+    "git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order] [--current] [--color[=<when>] | --no-color] [--sparse] [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [<rev> | <glob>]...",
+    "git show-branch (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]",
+    NULL
+};
+
+static int showbranch_use_color = -1;
+static char column_colors[][COLOR_MAXLEN] = {
+       GIT_COLOR_RED,
+       GIT_COLOR_GREEN,
+       GIT_COLOR_YELLOW,
+       GIT_COLOR_BLUE,
+       GIT_COLOR_MAGENTA,
+       GIT_COLOR_CYAN,
+};
+
+#define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors))
+
+static int default_num;
+static int default_alloc;
+static const char **default_arg;
+
+#define UNINTERESTING  01
+
+#define REV_SHIFT       2
+#define MAX_REVS       (FLAG_BITS - REV_SHIFT) /* should not exceed bits_per_int - REV_SHIFT */
+
+#define DEFAULT_REFLOG 4
+
+static const char *get_color_code(int idx)
+{
+       if (showbranch_use_color)
+               return column_colors[idx];
+       return "";
+}
+
+static const char *get_color_reset_code(void)
+{
+       if (showbranch_use_color)
+               return GIT_COLOR_RESET;
+       return "";
+}
+
+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->util)
+               commit->util = xmalloc(sizeof(struct commit_name));
+       name = commit->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->util;
+       struct commit_name *parent_name = parent->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->util)
+                       break;
+               if (!c->parents)
+                       break;
+               p = c->parents->item;
+               if (!p->util) {
+                       name_parent(c, p);
+                       i++;
+               }
+               else
+                       break;
+               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->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->util)
+                               continue;
+                       n = c->util;
+                       parents = c->parents;
+                       nth = 0;
+                       while (parents) {
+                               struct commit *p = parents->item;
+                               char newname[1000], *en;
+                               parents = parents->next;
+                               nth++;
+                               if (p->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, xstrdup(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) {
+               commit_list_insert(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.  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)
+{
+       struct strbuf pretty = STRBUF_INIT;
+       const char *pretty_str = "(unavailable)";
+       struct commit_name *name = commit->util;
+
+       if (commit->object.parsed) {
+               struct pretty_print_context ctx = {0};
+               pretty_print_commit(CMIT_FMT_ONELINE, commit, &pretty, &ctx);
+               pretty_str = pretty.buf;
+       }
+       if (!prefixcmp(pretty_str, "[PATCH] "))
+               pretty_str += 8;
+
+       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,
+                                                 DEFAULT_ABBREV));
+       }
+       puts(pretty_str);
+       strbuf_release(&pretty);
+}
+
+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,
+                     int allow_dups)
+{
+       struct commit *commit = lookup_commit_reference_gently(sha1, 1);
+       int i;
+
+       if (!commit)
+               return 0;
+
+       if (!allow_dups) {
+               /* Avoid adding the same thing twice */
+               for (i = 0; i < ref_name_cnt; i++)
+                       if (!strcmp(refname, ref_name[i]))
+                               return 0;
+       }
+       if (MAX_REVS <= ref_name_cnt) {
+               warning("ignoring %s; cannot handle more than %d refs",
+                       refname, MAX_REVS);
+               return 0;
+       }
+       ref_name[ref_name_cnt++] = xstrdup(refname);
+       ref_name[ref_name_cnt] = NULL;
+       return 0;
+}
+
+static int append_head_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+       unsigned char tmp[20];
+       int ofs = 11;
+       if (prefixcmp(refname, "refs/heads/"))
+               return 0;
+       /* If both heads/foo and tags/foo exists, get_sha1 would
+        * get confused.
+        */
+       if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
+               ofs = 5;
+       return append_ref(refname + ofs, sha1, 0);
+}
+
+static int append_remote_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+       unsigned char tmp[20];
+       int ofs = 13;
+       if (prefixcmp(refname, "refs/remotes/"))
+               return 0;
+       /* If both heads/foo and tags/foo exists, get_sha1 would
+        * get confused.
+        */
+       if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
+               ofs = 5;
+       return append_ref(refname + ofs, sha1, 0);
+}
+
+static int append_tag_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+       if (prefixcmp(refname, "refs/tags/"))
+               return 0;
+       return append_ref(refname + 5, sha1, 0);
+}
+
+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, int flag, void *cb_data)
+{
+       /* 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 (!prefixcmp(refname, "refs/heads/"))
+               return append_head_ref(refname, sha1, flag, cb_data);
+       if (!prefixcmp(refname, "refs/tags/"))
+               return append_tag_ref(refname, sha1, flag, cb_data);
+       return append_ref(refname, sha1, 0);
+}
+
+static void snarf_refs(int head, int remotes)
+{
+       if (head) {
+               int orig_cnt = ref_name_cnt;
+               for_each_ref(append_head_ref, NULL);
+               sort_ref_range(orig_cnt, ref_name_cnt);
+       }
+       if (remotes) {
+               int orig_cnt = ref_name_cnt;
+               for_each_ref(append_remote_ref, NULL);
+               sort_ref_range(orig_cnt, ref_name_cnt);
+       }
+}
+
+static int rev_is_head(char *head, int headlen, char *name,
+                      unsigned char *head_sha1, unsigned char *sha1)
+{
+       if ((!head[0]) ||
+           (head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
+               return 0;
+       if (!prefixcmp(head, "refs/heads/"))
+               head += 11;
+       if (!prefixcmp(name, "refs/heads/"))
+               name += 11;
+       else if (!prefixcmp(name, "heads/"))
+               name += 6;
+       return !strcmp(head, name);
+}
+
+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, 0);
+               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, NULL);
+               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, void *cb)
+{
+       if (!strcmp(var, "showbranch.default")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               /*
+                * default_arg is now passed to parse_options(), so we need to
+                * mimic the real argv a bit better.
+                */
+               if (!default_num) {
+                       default_alloc = 20;
+                       default_arg = xcalloc(default_alloc, sizeof(*default_arg));
+                       default_arg[default_num++] = "show-branch";
+               } else 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++] = xstrdup(value);
+               default_arg[default_num] = NULL;
+               return 0;
+       }
+
+       if (!strcmp(var, "color.showbranch")) {
+               showbranch_use_color = git_config_colorbool(var, value, -1);
+               return 0;
+       }
+
+       return git_color_default_config(var, value, cb);
+}
+
+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;
+}
+
+static int reflog = 0;
+
+static int parse_reflog_param(const struct option *opt, const char *arg,
+                             int unset)
+{
+       char *ep;
+       const char **base = (const char **)opt->value;
+       if (!arg)
+               arg = "";
+       reflog = strtoul(arg, &ep, 10);
+       if (*ep == ',')
+               *base = ep + 1;
+       else if (*ep)
+               return error("unrecognized reflog param '%s'", arg);
+       else
+               *base = NULL;
+       if (reflog <= 0)
+               reflog = DEFAULT_REFLOG;
+       return 0;
+}
+
+int cmd_show_branch(int ac, const char **av, const char *prefix)
+{
+       struct commit *rev[MAX_REVS], *commit;
+       char *reflog_msg[MAX_REVS];
+       struct commit_list *list = NULL, *seen = NULL;
+       unsigned int rev_mask[MAX_REVS];
+       int num_rev, i, extra = 0;
+       int all_heads = 0, all_remotes = 0;
+       int all_mask, all_revs;
+       int lifo = 1;
+       char head[128];
+       const char *head_p;
+       int head_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;
+       const char *reflog_base = NULL;
+       struct option builtin_show_branch_options[] = {
+               OPT_BOOLEAN('a', "all", &all_heads,
+                           "show remote-tracking and local branches"),
+               OPT_BOOLEAN('r', "remotes", &all_remotes,
+                           "show remote-tracking branches"),
+               OPT__COLOR(&showbranch_use_color,
+                           "color '*!+-' corresponding to the branch"),
+               { OPTION_INTEGER, 0, "more", &extra, "n",
+                           "show <n> more commits after the common ancestor",
+                           PARSE_OPT_OPTARG, NULL, (intptr_t)1 },
+               OPT_SET_INT(0, "list", &extra, "synonym to more=-1", -1),
+               OPT_BOOLEAN(0, "no-name", &no_name, "suppress naming strings"),
+               OPT_BOOLEAN(0, "current", &with_current_branch,
+                           "include the current branch"),
+               OPT_BOOLEAN(0, "sha1-name", &sha1_name,
+                           "name commits with their object names"),
+               OPT_BOOLEAN(0, "merge-base", &merge_base,
+                           "show possible merge bases"),
+               OPT_BOOLEAN(0, "independent", &independent,
+                           "show refs unreachable from any other ref"),
+               OPT_BOOLEAN(0, "topo-order", &lifo,
+                           "show commits in topological order"),
+               OPT_BOOLEAN(0, "topics", &topics,
+                           "show only commits not on the first branch"),
+               OPT_SET_INT(0, "sparse", &dense,
+                           "show merges reachable from only one tip", 0),
+               OPT_SET_INT(0, "date-order", &lifo,
+                           "show commits where no parent comes before its "
+                           "children", 0),
+               { OPTION_CALLBACK, 'g', "reflog", &reflog_base, "<n>[,<base>]",
+                           "show <n> most recent ref-log entries starting at "
+                           "base",
+                           PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
+                           parse_reflog_param },
+               OPT_END()
+       };
+
+       git_config(git_show_branch_config, NULL);
+
+       if (showbranch_use_color == -1)
+               showbranch_use_color = git_use_color_default;
+
+       /* If nothing is specified, try the default first */
+       if (ac == 1 && default_num) {
+               ac = default_num;
+               av = default_arg;
+       }
+
+       ac = parse_options(ac, av, prefix, builtin_show_branch_options,
+                          show_branch_usage, PARSE_OPT_STOP_AT_NON_OPTION);
+       if (all_heads)
+               all_remotes = 1;
+
+       if (extra || reflog) {
+               /* "listing" mode is incompatible with
+                * independent nor merge-base modes.
+                */
+               if (independent || merge_base)
+                       usage_with_options(show_branch_usage,
+                                          builtin_show_branch_options);
+               if (reflog && ((0 < extra) || all_heads || all_remotes))
+                       /*
+                        * Asking for --more in reflog mode does not
+                        * make sense.  --list is Ok.
+                        *
+                        * Also --all and --remotes do not make sense either.
+                        */
+                       die("--reflog is incompatible with --all, --remotes, "
+                           "--independent or --merge-base");
+       }
+
+       /* If nothing is specified, show all branches by default */
+       if (ac + all_heads + all_remotes == 0)
+               all_heads = 1;
+
+       if (reflog) {
+               unsigned char sha1[20];
+               char nth_desc[256];
+               char *ref;
+               int base = 0;
+
+               if (ac == 0) {
+                       static const char *fake_av[2];
+                       const char *refname;
+
+                       refname = resolve_ref("HEAD", sha1, 1, NULL);
+                       fake_av[0] = xstrdup(refname);
+                       fake_av[1] = NULL;
+                       av = fake_av;
+                       ac = 1;
+               }
+               if (ac != 1)
+                       die("--reflog option needs one branch name");
+
+               if (MAX_REVS < reflog)
+                       die("Only %d entries can be shown at one time.",
+                           MAX_REVS);
+               if (!dwim_ref(*av, strlen(*av), sha1, &ref))
+                       die("No such ref %s", *av);
+
+               /* Has the base been specified? */
+               if (reflog_base) {
+                       char *ep;
+                       base = strtoul(reflog_base, &ep, 10);
+                       if (*ep) {
+                               /* Ah, that is a date spec... */
+                               unsigned long at;
+                               at = approxidate(reflog_base);
+                               read_ref_at(ref, at, -1, sha1, NULL,
+                                           NULL, NULL, &base);
+                       }
+               }
+
+               for (i = 0; i < reflog; i++) {
+                       char *logmsg, *m;
+                       const char *msg;
+                       unsigned long timestamp;
+                       int tz;
+
+                       if (read_ref_at(ref, 0, base+i, sha1, &logmsg,
+                                       &timestamp, &tz, NULL)) {
+                               reflog = i;
+                               break;
+                       }
+                       msg = strchr(logmsg, '\t');
+                       if (!msg)
+                               msg = "(none)";
+                       else
+                               msg++;
+                       m = xmalloc(strlen(msg) + 200);
+                       sprintf(m, "(%s) %s",
+                               show_date(timestamp, tz, 1),
+                               msg);
+                       reflog_msg[i] = m;
+                       free(logmsg);
+                       sprintf(nth_desc, "%s@{%d}", *av, base+i);
+                       append_ref(nth_desc, sha1, 1);
+               }
+       }
+       else if (all_heads + all_remotes)
+               snarf_refs(all_heads, all_remotes);
+       else {
+               while (0 < ac) {
+                       append_one_rev(*av);
+                       ac--; av++;
+               }
+       }
+
+       head_p = resolve_ref("HEAD", head_sha1, 1, NULL);
+       if (head_p) {
+               head_len = strlen(head_p);
+               memcpy(head, head_p, head_len + 1);
+       }
+       else {
+               head_len = 0;
+               head[0] = 0;
+       }
+
+       if (with_current_branch && head_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,
+                                       head_len,
+                                       ref_name[i],
+                                       head_sha1, NULL))
+                               has_head++;
+               }
+               if (!has_head) {
+                       int offset = !prefixcmp(head, "refs/heads/") ? 11 : 0;
+                       append_one_rev(head + offset);
+               }
+       }
+
+       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);
+
+       sort_by_date(&seen);
+
+       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,
+                                                 head_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("%s%c%s [%s] ",
+                                      get_color_code(i % COLUMN_COLORS_MAX),
+                                      is_head ? '*' : '!',
+                                      get_color_reset_code(), ref_name[i]);
+                       }
+
+                       if (!reflog) {
+                               /* header lines never need name */
+                               show_one_commit(rev[i], 1);
+                       }
+                       else
+                               puts(reflog_msg[i]);
+
+                       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 = '+';
+                               printf("%s%c%s",
+                                      get_color_code(i % COLUMN_COLORS_MAX),
+                                      mark, get_color_reset_code());
+                       }
+                       putchar(' ');
+               }
+               show_one_commit(commit, no_name);
+
+               if (shown_merge_point && --extra < 0)
+                       break;
+       }
+       return 0;
+}
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
new file mode 100644 (file)
index 0000000..be9b512
--- /dev/null
@@ -0,0 +1,249 @@
+#include "builtin.h"
+#include "cache.h"
+#include "refs.h"
+#include "object.h"
+#include "tag.h"
+#include "string-list.h"
+#include "parse-options.h"
+
+static const char * const show_ref_usage[] = {
+       "git show-ref [-q|--quiet] [--verify] [--head] [-d|--dereference] [-s|--hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] [pattern*] ",
+       "git show-ref --exclude-existing[=pattern] < ref-list",
+       NULL
+};
+
+static int deref_tags, show_head, tags_only, heads_only, found_match, verify,
+          quiet, hash_only, abbrev, exclude_arg;
+static const char **pattern;
+static const char *exclude_existing_arg;
+
+static void show_one(const char *refname, const unsigned char *sha1)
+{
+       const char *hex = find_unique_abbrev(sha1, abbrev);
+       if (hash_only)
+               printf("%s\n", hex);
+       else
+               printf("%s %s\n", hex, refname);
+}
+
+static int show_ref(const char *refname, const unsigned char *sha1, int flag, void *cbdata)
+{
+       struct object *obj;
+       const char *hex;
+       unsigned char peeled[20];
+
+       if (tags_only || heads_only) {
+               int match;
+
+               match = heads_only && !prefixcmp(refname, "refs/heads/");
+               match |= tags_only && !prefixcmp(refname, "refs/tags/");
+               if (!match)
+                       return 0;
+       }
+       if (pattern) {
+               int reflen = strlen(refname);
+               const char **p = pattern, *m;
+               while ((m = *p++) != NULL) {
+                       int len = strlen(m);
+                       if (len > reflen)
+                               continue;
+                       if (memcmp(m, refname + reflen - len, len))
+                               continue;
+                       if (len == reflen)
+                               goto match;
+                       /* "--verify" requires an exact match */
+                       if (verify)
+                               continue;
+                       if (refname[reflen - len - 1] == '/')
+                               goto match;
+               }
+               return 0;
+       }
+
+match:
+       found_match++;
+
+       /* This changes the semantics slightly that even under quiet we
+        * detect and return error if the repository is corrupt and
+        * ref points at a nonexistent object.
+        */
+       if (!has_sha1_file(sha1))
+               die("git show-ref: bad ref %s (%s)", refname,
+                   sha1_to_hex(sha1));
+
+       if (quiet)
+               return 0;
+
+       show_one(refname, sha1);
+
+       if (!deref_tags)
+               return 0;
+
+       if ((flag & REF_ISPACKED) && !peel_ref(refname, peeled)) {
+               if (!is_null_sha1(peeled)) {
+                       hex = find_unique_abbrev(peeled, abbrev);
+                       printf("%s %s^{}\n", hex, refname);
+               }
+       }
+       else {
+               obj = parse_object(sha1);
+               if (!obj)
+                       die("git show-ref: bad ref %s (%s)", refname,
+                           sha1_to_hex(sha1));
+               if (obj->type == OBJ_TAG) {
+                       obj = deref_tag(obj, refname, 0);
+                       if (!obj)
+                               die("git show-ref: bad tag at ref %s (%s)", refname,
+                                   sha1_to_hex(sha1));
+                       hex = find_unique_abbrev(obj->sha1, abbrev);
+                       printf("%s %s^{}\n", hex, refname);
+               }
+       }
+       return 0;
+}
+
+static int add_existing(const char *refname, const unsigned char *sha1, int flag, void *cbdata)
+{
+       struct string_list *list = (struct string_list *)cbdata;
+       string_list_insert(list, refname);
+       return 0;
+}
+
+/*
+ * read "^(?:<anything>\s)?<refname>(?:\^\{\})?$" from the standard input,
+ * and
+ * (1) strip "^{}" at the end of line if any;
+ * (2) ignore if match is provided and does not head-match refname;
+ * (3) warn if refname is not a well-formed refname and skip;
+ * (4) ignore if refname is a ref that exists in the local repository;
+ * (5) otherwise output the line.
+ */
+static int exclude_existing(const char *match)
+{
+       static struct string_list existing_refs = STRING_LIST_INIT_NODUP;
+       char buf[1024];
+       int matchlen = match ? strlen(match) : 0;
+
+       for_each_ref(add_existing, &existing_refs);
+       while (fgets(buf, sizeof(buf), stdin)) {
+               char *ref;
+               int len = strlen(buf);
+
+               if (len > 0 && buf[len - 1] == '\n')
+                       buf[--len] = '\0';
+               if (3 <= len && !strcmp(buf + len - 3, "^{}")) {
+                       len -= 3;
+                       buf[len] = '\0';
+               }
+               for (ref = buf + len; buf < ref; ref--)
+                       if (isspace(ref[-1]))
+                               break;
+               if (match) {
+                       int reflen = buf + len - ref;
+                       if (reflen < matchlen)
+                               continue;
+                       if (strncmp(ref, match, matchlen))
+                               continue;
+               }
+               if (check_ref_format(ref)) {
+                       warning("ref '%s' ignored", ref);
+                       continue;
+               }
+               if (!string_list_has_string(&existing_refs, ref)) {
+                       printf("%s\n", buf);
+               }
+       }
+       return 0;
+}
+
+static int hash_callback(const struct option *opt, const char *arg, int unset)
+{
+       hash_only = 1;
+       /* Use full length SHA1 if no argument */
+       if (!arg)
+               return 0;
+       return parse_opt_abbrev_cb(opt, arg, unset);
+}
+
+static int exclude_existing_callback(const struct option *opt, const char *arg,
+                                    int unset)
+{
+       exclude_arg = 1;
+       *(const char **)opt->value = arg;
+       return 0;
+}
+
+static int help_callback(const struct option *opt, const char *arg, int unset)
+{
+       return -1;
+}
+
+static const struct option show_ref_options[] = {
+       OPT_BOOLEAN(0, "tags", &tags_only, "only show tags (can be combined with heads)"),
+       OPT_BOOLEAN(0, "heads", &heads_only, "only show heads (can be combined with tags)"),
+       OPT_BOOLEAN(0, "verify", &verify, "stricter reference checking, "
+                   "requires exact ref path"),
+       { OPTION_BOOLEAN, 'h', NULL, &show_head, NULL,
+         "show the HEAD reference",
+         PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
+       OPT_BOOLEAN(0, "head", &show_head, "show the HEAD reference"),
+       OPT_BOOLEAN('d', "dereference", &deref_tags,
+                   "dereference tags into object IDs"),
+       { OPTION_CALLBACK, 's', "hash", &abbrev, "n",
+         "only show SHA1 hash using <n> digits",
+         PARSE_OPT_OPTARG, &hash_callback },
+       OPT__ABBREV(&abbrev),
+       OPT__QUIET(&quiet),
+       { OPTION_CALLBACK, 0, "exclude-existing", &exclude_existing_arg,
+         "pattern", "show refs from stdin that aren't in local repository",
+         PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback },
+       { OPTION_CALLBACK, 0, "help-all", NULL, NULL, "show usage",
+         PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback },
+       OPT_END()
+};
+
+int cmd_show_ref(int argc, const char **argv, const char *prefix)
+{
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage_with_options(show_ref_usage, show_ref_options);
+
+       argc = parse_options(argc, argv, prefix, show_ref_options,
+                            show_ref_usage, PARSE_OPT_NO_INTERNAL_HELP);
+
+       if (exclude_arg)
+               return exclude_existing(exclude_existing_arg);
+
+       pattern = argv;
+       if (!*pattern)
+               pattern = NULL;
+
+       if (verify) {
+               if (!pattern)
+                       die("--verify requires a reference");
+               while (*pattern) {
+                       unsigned char sha1[20];
+
+                       if (!prefixcmp(*pattern, "refs/") &&
+                           resolve_ref(*pattern, sha1, 1, NULL)) {
+                               if (!quiet)
+                                       show_one(*pattern, sha1);
+                       }
+                       else if (!quiet)
+                               die("'%s' - not a valid ref", *pattern);
+                       else
+                               return 1;
+                       pattern++;
+               }
+               return 0;
+       }
+
+       if (show_head)
+               head_ref(show_ref, NULL);
+       for_each_ref(show_ref, NULL);
+       if (!found_match) {
+               if (verify && !quiet)
+                       die("No match");
+               return 1;
+       }
+       return 0;
+}
diff --git a/builtin/stripspace.c b/builtin/stripspace.c
new file mode 100644 (file)
index 0000000..4d3b93f
--- /dev/null
@@ -0,0 +1,90 @@
+#include "builtin.h"
+#include "cache.h"
+
+/*
+ * Returns the length of a line, without trailing spaces.
+ *
+ * If the line ends with newline, it will be removed too.
+ */
+static size_t cleanup(char *line, size_t len)
+{
+       while (len) {
+               unsigned char c = line[len - 1];
+               if (!isspace(c))
+                       break;
+               len--;
+       }
+
+       return len;
+}
+
+/*
+ * Remove empty lines from the beginning and end
+ * and also trailing spaces from every line.
+ *
+ * Note that the buffer will not be NUL-terminated.
+ *
+ * Turn multiple consecutive empty lines between paragraphs
+ * into just one empty line.
+ *
+ * If the input has only empty lines and spaces,
+ * no output will be produced.
+ *
+ * If last line does not have a newline at the end, one is added.
+ *
+ * Enable skip_comments to skip every line starting with "#".
+ */
+void stripspace(struct strbuf *sb, int skip_comments)
+{
+       int empties = 0;
+       size_t i, j, len, newlen;
+       char *eol;
+
+       /* We may have to add a newline. */
+       strbuf_grow(sb, 1);
+
+       for (i = j = 0; i < sb->len; i += len, j += newlen) {
+               eol = memchr(sb->buf + i, '\n', sb->len - i);
+               len = eol ? eol - (sb->buf + i) + 1 : sb->len - i;
+
+               if (skip_comments && len && sb->buf[i] == '#') {
+                       newlen = 0;
+                       continue;
+               }
+               newlen = cleanup(sb->buf + i, len);
+
+               /* Not just an empty line? */
+               if (newlen) {
+                       if (empties > 0 && j > 0)
+                               sb->buf[j++] = '\n';
+                       empties = 0;
+                       memmove(sb->buf + j, sb->buf + i, newlen);
+                       sb->buf[newlen + j++] = '\n';
+               } else {
+                       empties++;
+               }
+       }
+
+       strbuf_setlen(sb, j);
+}
+
+int cmd_stripspace(int argc, const char **argv, const char *prefix)
+{
+       struct strbuf buf = STRBUF_INIT;
+       int strip_comments = 0;
+
+       if (argc == 2 && (!strcmp(argv[1], "-s") ||
+                               !strcmp(argv[1], "--strip-comments")))
+               strip_comments = 1;
+       else if (argc > 1)
+               usage("git stripspace [-s | --strip-comments] < <stream>");
+
+       if (strbuf_read(&buf, 0, 1024) < 0)
+               die_errno("could not read the input");
+
+       stripspace(&buf, strip_comments);
+
+       write_or_die(1, buf.buf, buf.len);
+       strbuf_release(&buf);
+       return 0;
+}
diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c
new file mode 100644 (file)
index 0000000..ca855a5
--- /dev/null
@@ -0,0 +1,57 @@
+#include "builtin.h"
+#include "cache.h"
+#include "refs.h"
+#include "parse-options.h"
+
+static const char * const git_symbolic_ref_usage[] = {
+       "git symbolic-ref [options] name [ref]",
+       NULL
+};
+
+static void check_symref(const char *HEAD, int quiet)
+{
+       unsigned char sha1[20];
+       int flag;
+       const char *refs_heads_master = resolve_ref(HEAD, sha1, 0, &flag);
+
+       if (!refs_heads_master)
+               die("No such ref: %s", HEAD);
+       else if (!(flag & REF_ISSYMREF)) {
+               if (!quiet)
+                       die("ref %s is not a symbolic ref", HEAD);
+               else
+                       exit(1);
+       }
+       puts(refs_heads_master);
+}
+
+int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
+{
+       int quiet = 0;
+       const char *msg = NULL;
+       struct option options[] = {
+               OPT__QUIET(&quiet),
+               OPT_STRING('m', NULL, &msg, "reason", "reason of the update"),
+               OPT_END(),
+       };
+
+       git_config(git_default_config, NULL);
+       argc = parse_options(argc, argv, prefix, options,
+                            git_symbolic_ref_usage, 0);
+       if (msg &&!*msg)
+               die("Refusing to perform update with empty message");
+       switch (argc) {
+       case 1:
+               check_symref(argv[0], quiet);
+               break;
+       case 2:
+               if (!strcmp(argv[0], "HEAD") &&
+                   prefixcmp(argv[1], "refs/"))
+                       die("Refusing to point HEAD outside of refs/");
+               create_symref(argv[0], argv[1], msg);
+               break;
+       default:
+               usage_with_options(git_symbolic_ref_usage, options);
+       }
+       return 0;
+}
diff --git a/builtin/tag.c b/builtin/tag.c
new file mode 100644 (file)
index 0000000..d311491
--- /dev/null
@@ -0,0 +1,487 @@
+/*
+ * Builtin "git tag"
+ *
+ * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>,
+ *                    Carlos Rica <jasampler@gmail.com>
+ * Based on git-tag.sh and mktag.c by Linus Torvalds.
+ */
+
+#include "cache.h"
+#include "builtin.h"
+#include "refs.h"
+#include "tag.h"
+#include "run-command.h"
+#include "parse-options.h"
+
+static const char * const git_tag_usage[] = {
+       "git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
+       "git tag -d <tagname>...",
+       "git tag -l [-n[<num>]] [<pattern>]",
+       "git tag -v <tagname>...",
+       NULL
+};
+
+static char signingkey[1000];
+
+struct tag_filter {
+       const char *pattern;
+       int lines;
+       struct commit_list *with_commit;
+};
+
+#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
+
+static int show_reference(const char *refname, const unsigned char *sha1,
+                         int flag, void *cb_data)
+{
+       struct tag_filter *filter = cb_data;
+
+       if (!fnmatch(filter->pattern, refname, 0)) {
+               int i;
+               unsigned long size;
+               enum object_type type;
+               char *buf, *sp, *eol;
+               size_t len;
+
+               if (filter->with_commit) {
+                       struct commit *commit;
+
+                       commit = lookup_commit_reference_gently(sha1, 1);
+                       if (!commit)
+                               return 0;
+                       if (!is_descendant_of(commit, filter->with_commit))
+                               return 0;
+               }
+
+               if (!filter->lines) {
+                       printf("%s\n", refname);
+                       return 0;
+               }
+               printf("%-15s ", refname);
+
+               buf = read_sha1_file(sha1, &type, &size);
+               if (!buf || !size)
+                       return 0;
+
+               /* skip header */
+               sp = strstr(buf, "\n\n");
+               if (!sp) {
+                       free(buf);
+                       return 0;
+               }
+               /* only take up to "lines" lines, and strip the signature */
+               for (i = 0, sp += 2;
+                               i < filter->lines && sp < buf + size &&
+                               prefixcmp(sp, PGP_SIGNATURE "\n");
+                               i++) {
+                       if (i)
+                               printf("\n    ");
+                       eol = memchr(sp, '\n', size - (sp - buf));
+                       len = eol ? eol - sp : size - (sp - buf);
+                       fwrite(sp, len, 1, stdout);
+                       if (!eol)
+                               break;
+                       sp = eol + 1;
+               }
+               putchar('\n');
+               free(buf);
+       }
+
+       return 0;
+}
+
+static int list_tags(const char *pattern, int lines,
+                       struct commit_list *with_commit)
+{
+       struct tag_filter filter;
+
+       if (pattern == NULL)
+               pattern = "*";
+
+       filter.pattern = pattern;
+       filter.lines = lines;
+       filter.with_commit = with_commit;
+
+       for_each_tag_ref(show_reference, (void *) &filter);
+
+       return 0;
+}
+
+typedef int (*each_tag_name_fn)(const char *name, const char *ref,
+                               const unsigned char *sha1);
+
+static int for_each_tag_name(const char **argv, each_tag_name_fn fn)
+{
+       const char **p;
+       char ref[PATH_MAX];
+       int had_error = 0;
+       unsigned char sha1[20];
+
+       for (p = argv; *p; p++) {
+               if (snprintf(ref, sizeof(ref), "refs/tags/%s", *p)
+                                       >= sizeof(ref)) {
+                       error("tag name too long: %.*s...", 50, *p);
+                       had_error = 1;
+                       continue;
+               }
+               if (!resolve_ref(ref, sha1, 1, NULL)) {
+                       error("tag '%s' not found.", *p);
+                       had_error = 1;
+                       continue;
+               }
+               if (fn(*p, ref, sha1))
+                       had_error = 1;
+       }
+       return had_error;
+}
+
+static int delete_tag(const char *name, const char *ref,
+                               const unsigned char *sha1)
+{
+       if (delete_ref(ref, sha1, 0))
+               return 1;
+       printf("Deleted tag '%s' (was %s)\n", name, find_unique_abbrev(sha1, DEFAULT_ABBREV));
+       return 0;
+}
+
+static int verify_tag(const char *name, const char *ref,
+                               const unsigned char *sha1)
+{
+       const char *argv_verify_tag[] = {"verify-tag",
+                                       "-v", "SHA1_HEX", NULL};
+       argv_verify_tag[2] = sha1_to_hex(sha1);
+
+       if (run_command_v_opt(argv_verify_tag, RUN_GIT_CMD))
+               return error("could not verify the tag '%s'", name);
+       return 0;
+}
+
+static int do_sign(struct strbuf *buffer)
+{
+       struct child_process gpg;
+       const char *args[4];
+       char *bracket;
+       int len;
+       int i, j;
+
+       if (!*signingkey) {
+               if (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME),
+                               sizeof(signingkey)) > sizeof(signingkey) - 1)
+                       return error("committer info too long.");
+               bracket = strchr(signingkey, '>');
+               if (bracket)
+                       bracket[1] = '\0';
+       }
+
+       /* When the username signingkey is bad, program could be terminated
+        * because gpg exits without reading and then write gets SIGPIPE. */
+       signal(SIGPIPE, SIG_IGN);
+
+       memset(&gpg, 0, sizeof(gpg));
+       gpg.argv = args;
+       gpg.in = -1;
+       gpg.out = -1;
+       args[0] = "gpg";
+       args[1] = "-bsau";
+       args[2] = signingkey;
+       args[3] = NULL;
+
+       if (start_command(&gpg))
+               return error("could not run gpg.");
+
+       if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) {
+               close(gpg.in);
+               close(gpg.out);
+               finish_command(&gpg);
+               return error("gpg did not accept the tag data");
+       }
+       close(gpg.in);
+       len = strbuf_read(buffer, gpg.out, 1024);
+       close(gpg.out);
+
+       if (finish_command(&gpg) || !len || len < 0)
+               return error("gpg failed to sign the tag");
+
+       /* Strip CR from the line endings, in case we are on Windows. */
+       for (i = j = 0; i < buffer->len; i++)
+               if (buffer->buf[i] != '\r') {
+                       if (i != j)
+                               buffer->buf[j] = buffer->buf[i];
+                       j++;
+               }
+       strbuf_setlen(buffer, j);
+
+       return 0;
+}
+
+static const char tag_template[] =
+       "\n"
+       "#\n"
+       "# Write a tag message\n"
+       "#\n";
+
+static void set_signingkey(const char *value)
+{
+       if (strlcpy(signingkey, value, sizeof(signingkey)) >= sizeof(signingkey))
+               die("signing key value too long (%.10s...)", value);
+}
+
+static int git_tag_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "user.signingkey")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               set_signingkey(value);
+               return 0;
+       }
+
+       return git_default_config(var, value, cb);
+}
+
+static void write_tag_body(int fd, const unsigned char *sha1)
+{
+       unsigned long size;
+       enum object_type type;
+       char *buf, *sp, *eob;
+       size_t len;
+
+       buf = read_sha1_file(sha1, &type, &size);
+       if (!buf)
+               return;
+       /* skip header */
+       sp = strstr(buf, "\n\n");
+
+       if (!sp || !size || type != OBJ_TAG) {
+               free(buf);
+               return;
+       }
+       sp += 2; /* skip the 2 LFs */
+       eob = strstr(sp, "\n" PGP_SIGNATURE "\n");
+       if (eob)
+               len = eob - sp;
+       else
+               len = buf + size - sp;
+       write_or_die(fd, sp, len);
+
+       free(buf);
+}
+
+static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result)
+{
+       if (sign && do_sign(buf) < 0)
+               return error("unable to sign the tag");
+       if (write_sha1_file(buf->buf, buf->len, tag_type, result) < 0)
+               return error("unable to write tag file");
+       return 0;
+}
+
+static void create_tag(const unsigned char *object, const char *tag,
+                      struct strbuf *buf, int message, int sign,
+                      unsigned char *prev, unsigned char *result)
+{
+       enum object_type type;
+       char header_buf[1024];
+       int header_len;
+       char *path = NULL;
+
+       type = sha1_object_info(object, NULL);
+       if (type <= OBJ_NONE)
+           die("bad object type.");
+
+       header_len = snprintf(header_buf, sizeof(header_buf),
+                         "object %s\n"
+                         "type %s\n"
+                         "tag %s\n"
+                         "tagger %s\n\n",
+                         sha1_to_hex(object),
+                         typename(type),
+                         tag,
+                         git_committer_info(IDENT_ERROR_ON_NO_NAME));
+
+       if (header_len > sizeof(header_buf) - 1)
+               die("tag header too big.");
+
+       if (!message) {
+               int fd;
+
+               /* write the template message before editing: */
+               path = git_pathdup("TAG_EDITMSG");
+               fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
+               if (fd < 0)
+                       die_errno("could not create file '%s'", path);
+
+               if (!is_null_sha1(prev))
+                       write_tag_body(fd, prev);
+               else
+                       write_or_die(fd, tag_template, strlen(tag_template));
+               close(fd);
+
+               if (launch_editor(path, buf, NULL)) {
+                       fprintf(stderr,
+                       "Please supply the message using either -m or -F option.\n");
+                       exit(1);
+               }
+       }
+
+       stripspace(buf, 1);
+
+       if (!message && !buf->len)
+               die("no tag message?");
+
+       strbuf_insert(buf, 0, header_buf, header_len);
+
+       if (build_tag_object(buf, sign, result) < 0) {
+               if (path)
+                       fprintf(stderr, "The tag message has been left in %s\n",
+                               path);
+               exit(128);
+       }
+       if (path) {
+               unlink_or_warn(path);
+               free(path);
+       }
+}
+
+struct msg_arg {
+       int given;
+       struct strbuf buf;
+};
+
+static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
+{
+       struct msg_arg *msg = opt->value;
+
+       if (!arg)
+               return -1;
+       if (msg->buf.len)
+               strbuf_addstr(&(msg->buf), "\n\n");
+       strbuf_addstr(&(msg->buf), arg);
+       msg->given = 1;
+       return 0;
+}
+
+int cmd_tag(int argc, const char **argv, const char *prefix)
+{
+       struct strbuf buf = STRBUF_INIT;
+       unsigned char object[20], prev[20];
+       char ref[PATH_MAX];
+       const char *object_ref, *tag;
+       struct ref_lock *lock;
+
+       int annotate = 0, sign = 0, force = 0, lines = -1,
+               list = 0, delete = 0, verify = 0;
+       const char *msgfile = NULL, *keyid = NULL;
+       struct msg_arg msg = { 0, STRBUF_INIT };
+       struct commit_list *with_commit = NULL;
+       struct option options[] = {
+               OPT_BOOLEAN('l', NULL, &list, "list tag names"),
+               { OPTION_INTEGER, 'n', NULL, &lines, "n",
+                               "print <n> lines of each tag message",
+                               PARSE_OPT_OPTARG, NULL, 1 },
+               OPT_BOOLEAN('d', NULL, &delete, "delete tags"),
+               OPT_BOOLEAN('v', NULL, &verify, "verify tags"),
+
+               OPT_GROUP("Tag creation options"),
+               OPT_BOOLEAN('a', NULL, &annotate,
+                                       "annotated tag, needs a message"),
+               OPT_CALLBACK('m', NULL, &msg, "msg",
+                            "message for the tag", parse_msg_arg),
+               OPT_FILENAME('F', NULL, &msgfile, "message in a file"),
+               OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"),
+               OPT_STRING('u', NULL, &keyid, "key-id",
+                                       "use another key to sign the tag"),
+               OPT_BOOLEAN('f', "force", &force, "replace the tag if exists"),
+
+               OPT_GROUP("Tag listing options"),
+               {
+                       OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
+                       "print only tags that contain the commit",
+                       PARSE_OPT_LASTARG_DEFAULT,
+                       parse_opt_with_commit, (intptr_t)"HEAD",
+               },
+               OPT_END()
+       };
+
+       git_config(git_tag_config, NULL);
+
+       argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
+
+       if (keyid) {
+               sign = 1;
+               set_signingkey(keyid);
+       }
+       if (sign)
+               annotate = 1;
+       if (argc == 0 && !(delete || verify))
+               list = 1;
+
+       if ((annotate || msg.given || msgfile || force) &&
+           (list || delete || verify))
+               usage_with_options(git_tag_usage, options);
+
+       if (list + delete + verify > 1)
+               usage_with_options(git_tag_usage, options);
+       if (list)
+               return list_tags(argv[0], lines == -1 ? 0 : lines,
+                                with_commit);
+       if (lines != -1)
+               die("-n option is only allowed with -l.");
+       if (with_commit)
+               die("--contains option is only allowed with -l.");
+       if (delete)
+               return for_each_tag_name(argv, delete_tag);
+       if (verify)
+               return for_each_tag_name(argv, verify_tag);
+
+       if (msg.given || msgfile) {
+               if (msg.given && msgfile)
+                       die("only one -F or -m option is allowed.");
+               annotate = 1;
+               if (msg.given)
+                       strbuf_addbuf(&buf, &(msg.buf));
+               else {
+                       if (!strcmp(msgfile, "-")) {
+                               if (strbuf_read(&buf, 0, 1024) < 0)
+                                       die_errno("cannot read '%s'", msgfile);
+                       } else {
+                               if (strbuf_read_file(&buf, msgfile, 1024) < 0)
+                                       die_errno("could not open or read '%s'",
+                                               msgfile);
+                       }
+               }
+       }
+
+       tag = argv[0];
+
+       object_ref = argc == 2 ? argv[1] : "HEAD";
+       if (argc > 2)
+               die("too many params");
+
+       if (get_sha1(object_ref, object))
+               die("Failed to resolve '%s' as a valid ref.", object_ref);
+
+       if (snprintf(ref, sizeof(ref), "refs/tags/%s", tag) > sizeof(ref) - 1)
+               die("tag name too long: %.*s...", 50, tag);
+       if (check_ref_format(ref))
+               die("'%s' is not a valid tag name.", tag);
+
+       if (!resolve_ref(ref, prev, 1, NULL))
+               hashclr(prev);
+       else if (!force)
+               die("tag '%s' already exists", tag);
+
+       if (annotate)
+               create_tag(object, tag, &buf, msg.given || msgfile,
+                          sign, prev, object);
+
+       lock = lock_any_ref_for_update(ref, prev, 0);
+       if (!lock)
+               die("%s: cannot lock the ref", ref);
+       if (write_ref_sha1(lock, object, NULL) < 0)
+               die("%s: cannot update the ref", ref);
+       if (force && hashcmp(prev, object))
+               printf("Updated tag '%s' (was %s)\n", tag, find_unique_abbrev(prev, DEFAULT_ABBREV));
+
+       strbuf_release(&buf);
+       return 0;
+}
diff --git a/builtin/tar-tree.c b/builtin/tar-tree.c
new file mode 100644 (file)
index 0000000..3f1e701
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2005, 2006 Rene Scharfe
+ */
+#include "cache.h"
+#include "commit.h"
+#include "tar.h"
+#include "builtin.h"
+#include "quote.h"
+
+static const char tar_tree_usage[] =
+"git tar-tree [--remote=<repo>] <tree-ish> [basedir]\n"
+"*** Note that this command is now deprecated; use \"git archive\" instead.";
+
+static const char builtin_get_tar_commit_id_usage[] =
+"git get-tar-commit-id < <tarfile>";
+
+int cmd_tar_tree(int argc, const char **argv, const char *prefix)
+{
+       /*
+        * "git tar-tree" is now a wrapper around "git archive --format=tar"
+        *
+        * $0 --remote=<repo> arg... ==>
+        *      git archive --format=tar --remote=<repo> arg...
+        * $0 tree-ish ==>
+        *      git archive --format=tar tree-ish
+        * $0 tree-ish basedir ==>
+        *      git archive --format-tar --prefix=basedir tree-ish
+        */
+       int i;
+       const char **nargv = xcalloc(sizeof(*nargv), argc + 3);
+       char *basedir_arg;
+       int nargc = 0;
+
+       nargv[nargc++] = "archive";
+       nargv[nargc++] = "--format=tar";
+
+       if (2 <= argc && !prefixcmp(argv[1], "--remote=")) {
+               nargv[nargc++] = argv[1];
+               argv++;
+               argc--;
+       }
+
+       /*
+        * Because it's just a compatibility wrapper, tar-tree supports only
+        * the old behaviour of reading attributes from the work tree.
+        */
+       nargv[nargc++] = "--worktree-attributes";
+
+       switch (argc) {
+       default:
+               usage(tar_tree_usage);
+               break;
+       case 3:
+               /* base-path */
+               basedir_arg = xmalloc(strlen(argv[2]) + 11);
+               sprintf(basedir_arg, "--prefix=%s/", argv[2]);
+               nargv[nargc++] = basedir_arg;
+               /* fallthru */
+       case 2:
+               /* tree-ish */
+               nargv[nargc++] = argv[1];
+       }
+       nargv[nargc] = NULL;
+
+       fprintf(stderr,
+               "*** \"git tar-tree\" is now deprecated.\n"
+               "*** Running \"git archive\" instead.\n***");
+       for (i = 0; i < nargc; i++) {
+               fputc(' ', stderr);
+               sq_quote_print(stderr, nargv[i]);
+       }
+       fputc('\n', stderr);
+       return cmd_archive(nargc, nargv, prefix);
+}
+
+/* ustar header + extended global header content */
+#define RECORDSIZE     (512)
+#define HEADERSIZE (2 * RECORDSIZE)
+
+int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix)
+{
+       char buffer[HEADERSIZE];
+       struct ustar_header *header = (struct ustar_header *)buffer;
+       char *content = buffer + RECORDSIZE;
+       ssize_t n;
+
+       if (argc != 1)
+               usage(builtin_get_tar_commit_id_usage);
+
+       n = read_in_full(0, buffer, HEADERSIZE);
+       if (n < HEADERSIZE)
+               die("git get-tar-commit-id: read error");
+       if (header->typeflag[0] != 'g')
+               return 1;
+       if (memcmp(content, "52 comment=", 11))
+               return 1;
+
+       n = write_in_full(1, content + 11, 41);
+       if (n < 41)
+               die_errno("git get-tar-commit-id: write error");
+
+       return 0;
+}
diff --git a/builtin/unpack-file.c b/builtin/unpack-file.c
new file mode 100644 (file)
index 0000000..608590a
--- /dev/null
@@ -0,0 +1,38 @@
+#include "cache.h"
+#include "blob.h"
+#include "exec_cmd.h"
+
+static char *create_temp_file(unsigned char *sha1)
+{
+       static char path[50];
+       void *buf;
+       enum object_type type;
+       unsigned long size;
+       int fd;
+
+       buf = read_sha1_file(sha1, &type, &size);
+       if (!buf || type != OBJ_BLOB)
+               die("unable to read blob object %s", sha1_to_hex(sha1));
+
+       strcpy(path, ".merge_file_XXXXXX");
+       fd = xmkstemp(path);
+       if (write_in_full(fd, buf, size) != size)
+               die_errno("unable to write temp-file");
+       close(fd);
+       return path;
+}
+
+int cmd_unpack_file(int argc, const char **argv, const char *prefix)
+{
+       unsigned char sha1[20];
+
+       if (argc != 2 || !strcmp(argv[1], "-h"))
+               usage("git unpack-file <sha1>");
+       if (get_sha1(argv[1], sha1))
+               die("Not a valid object name %s", argv[1]);
+
+       git_config(git_default_config, NULL);
+
+       puts(create_temp_file(sha1));
+       return 0;
+}
diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c
new file mode 100644 (file)
index 0000000..685566e
--- /dev/null
@@ -0,0 +1,568 @@
+#include "builtin.h"
+#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 "tree-walk.h"
+#include "progress.h"
+#include "decorate.h"
+#include "fsck.h"
+
+static int dry_run, quiet, recover, has_errors, strict;
+static const char unpack_usage[] = "git unpack-objects [-n] [-q] [-r] [--strict] < pack-file";
+
+/* We always read in 4kB chunks. */
+static unsigned char buffer[4096];
+static unsigned int offset, len;
+static off_t consumed_bytes;
+static git_SHA_CTX ctx;
+
+/*
+ * When running under --strict mode, objects whose reachability are
+ * suspect are kept in core without getting written in the object
+ * store.
+ */
+struct obj_buffer {
+       char *buffer;
+       unsigned long size;
+};
+
+static struct decoration obj_decorate;
+
+static struct obj_buffer *lookup_object_buffer(struct object *base)
+{
+       return lookup_decoration(&obj_decorate, base);
+}
+
+static void add_object_buffer(struct object *object, char *buffer, unsigned long size)
+{
+       struct obj_buffer *obj;
+       obj = xcalloc(1, sizeof(struct obj_buffer));
+       obj->buffer = buffer;
+       obj->size = size;
+       if (add_decoration(&obj_decorate, object, obj))
+               die("object %s tried to add buffer twice!", sha1_to_hex(object->sha1));
+}
+
+/*
+ * 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 (min > sizeof(buffer))
+               die("cannot fill %d bytes", min);
+       if (offset) {
+               git_SHA1_Update(&ctx, buffer, offset);
+               memmove(buffer, buffer + offset, len);
+               offset = 0;
+       }
+       do {
+               ssize_t ret = xread(0, buffer + len, sizeof(buffer) - len);
+               if (ret <= 0) {
+                       if (!ret)
+                               die("early EOF");
+                       die_errno("read error on input");
+               }
+               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;
+
+       /* make sure off_t is sufficiently large not to wrap */
+       if (consumed_bytes > consumed_bytes + bytes)
+               die("pack too large for current definition of off_t");
+       consumed_bytes += 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;
+       git_inflate_init(&stream);
+
+       for (;;) {
+               int ret = git_inflate(&stream, 0);
+               use(len - stream.avail_in);
+               if (stream.total_out == size && ret == Z_STREAM_END)
+                       break;
+               if (ret != Z_OK) {
+                       error("inflate returned %d\n", ret);
+                       free(buf);
+                       buf = NULL;
+                       if (!recover)
+                               exit(1);
+                       has_errors = 1;
+                       break;
+               }
+               stream.next_in = fill(1);
+               stream.avail_in = len;
+       }
+       git_inflate_end(&stream);
+       return buf;
+}
+
+struct delta_info {
+       unsigned char base_sha1[20];
+       unsigned nr;
+       off_t base_offset;
+       unsigned long size;
+       void *delta;
+       struct delta_info *next;
+};
+
+static struct delta_info *delta_list;
+
+static void add_delta_to_list(unsigned nr, unsigned const char *base_sha1,
+                             off_t base_offset,
+                             void *delta, unsigned long size)
+{
+       struct delta_info *info = xmalloc(sizeof(*info));
+
+       hashcpy(info->base_sha1, base_sha1);
+       info->base_offset = base_offset;
+       info->size = size;
+       info->delta = delta;
+       info->nr = nr;
+       info->next = delta_list;
+       delta_list = info;
+}
+
+struct obj_info {
+       off_t offset;
+       unsigned char sha1[20];
+       struct object *obj;
+};
+
+#define FLAG_OPEN (1u<<20)
+#define FLAG_WRITTEN (1u<<21)
+
+static struct obj_info *obj_list;
+static unsigned nr_objects;
+
+/*
+ * Called only from check_object() after it verified this object
+ * is Ok.
+ */
+static void write_cached_object(struct object *obj)
+{
+       unsigned char sha1[20];
+       struct obj_buffer *obj_buf = lookup_object_buffer(obj);
+       if (write_sha1_file(obj_buf->buffer, obj_buf->size, typename(obj->type), sha1) < 0)
+               die("failed to write object %s", sha1_to_hex(obj->sha1));
+       obj->flags |= FLAG_WRITTEN;
+}
+
+/*
+ * At the very end of the processing, write_rest() scans the objects
+ * that have reachability requirements and calls this function.
+ * Verify its reachability and validity recursively and write it out.
+ */
+static int check_object(struct object *obj, int type, void *data)
+{
+       if (!obj)
+               return 1;
+
+       if (obj->flags & FLAG_WRITTEN)
+               return 0;
+
+       if (type != OBJ_ANY && obj->type != type)
+               die("object type mismatch");
+
+       if (!(obj->flags & FLAG_OPEN)) {
+               unsigned long size;
+               int type = sha1_object_info(obj->sha1, &size);
+               if (type != obj->type || type <= 0)
+                       die("object of unexpected type");
+               obj->flags |= FLAG_WRITTEN;
+               return 0;
+       }
+
+       if (fsck_object(obj, 1, fsck_error_function))
+               die("Error in object");
+       if (fsck_walk(obj, check_object, NULL))
+               die("Error on reachable objects of %s", sha1_to_hex(obj->sha1));
+       write_cached_object(obj);
+       return 0;
+}
+
+static void write_rest(void)
+{
+       unsigned i;
+       for (i = 0; i < nr_objects; i++) {
+               if (obj_list[i].obj)
+                       check_object(obj_list[i].obj, OBJ_ANY, NULL);
+       }
+}
+
+static void added_object(unsigned nr, enum object_type type,
+                        void *data, unsigned long size);
+
+/*
+ * Write out nr-th object from the list, now we know the contents
+ * of it.  Under --strict, this buffers structured objects in-core,
+ * to be checked at the end.
+ */
+static void write_object(unsigned nr, enum object_type type,
+                        void *buf, unsigned long size)
+{
+       if (!strict) {
+               if (write_sha1_file(buf, size, typename(type), obj_list[nr].sha1) < 0)
+                       die("failed to write object");
+               added_object(nr, type, buf, size);
+               free(buf);
+               obj_list[nr].obj = NULL;
+       } else if (type == OBJ_BLOB) {
+               struct blob *blob;
+               if (write_sha1_file(buf, size, typename(type), obj_list[nr].sha1) < 0)
+                       die("failed to write object");
+               added_object(nr, type, buf, size);
+               free(buf);
+
+               blob = lookup_blob(obj_list[nr].sha1);
+               if (blob)
+                       blob->object.flags |= FLAG_WRITTEN;
+               else
+                       die("invalid blob object");
+               obj_list[nr].obj = NULL;
+       } else {
+               struct object *obj;
+               int eaten;
+               hash_sha1_file(buf, size, typename(type), obj_list[nr].sha1);
+               added_object(nr, type, buf, size);
+               obj = parse_object_buffer(obj_list[nr].sha1, type, size, buf, &eaten);
+               if (!obj)
+                       die("invalid %s", typename(type));
+               add_object_buffer(obj, buf, size);
+               obj->flags |= FLAG_OPEN;
+               obj_list[nr].obj = obj;
+       }
+}
+
+static void resolve_delta(unsigned nr, enum object_type 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(nr, type, result, result_size);
+}
+
+/*
+ * We now know the contents of an object (which is nr-th in the pack);
+ * resolve all the deltified objects that are based on it.
+ */
+static void added_object(unsigned nr, enum object_type type,
+                        void *data, unsigned long size)
+{
+       struct delta_info **p = &delta_list;
+       struct delta_info *info;
+
+       while ((info = *p) != NULL) {
+               if (!hashcmp(info->base_sha1, obj_list[nr].sha1) ||
+                   info->base_offset == obj_list[nr].offset) {
+                       *p = info->next;
+                       p = &delta_list;
+                       resolve_delta(info->nr, type, data, size,
+                                     info->delta, info->size);
+                       free(info);
+                       continue;
+               }
+               p = &info->next;
+       }
+}
+
+static void unpack_non_delta_entry(enum object_type type, unsigned long size,
+                                  unsigned nr)
+{
+       void *buf = get_data(size);
+
+       if (!dry_run && buf)
+               write_object(nr, type, buf, size);
+       else
+               free(buf);
+}
+
+static int resolve_against_held(unsigned nr, const unsigned char *base,
+                               void *delta_data, unsigned long delta_size)
+{
+       struct object *obj;
+       struct obj_buffer *obj_buffer;
+       obj = lookup_object(base);
+       if (!obj)
+               return 0;
+       obj_buffer = lookup_object_buffer(obj);
+       if (!obj_buffer)
+               return 0;
+       resolve_delta(nr, obj->type, obj_buffer->buffer,
+                     obj_buffer->size, delta_data, delta_size);
+       return 1;
+}
+
+static void unpack_delta_entry(enum object_type type, unsigned long delta_size,
+                              unsigned nr)
+{
+       void *delta_data, *base;
+       unsigned long base_size;
+       unsigned char base_sha1[20];
+
+       if (type == OBJ_REF_DELTA) {
+               hashcpy(base_sha1, fill(20));
+               use(20);
+               delta_data = get_data(delta_size);
+               if (dry_run || !delta_data) {
+                       free(delta_data);
+                       return;
+               }
+               if (has_sha1_file(base_sha1))
+                       ; /* Ok we have this one */
+               else if (resolve_against_held(nr, base_sha1,
+                                             delta_data, delta_size))
+                       return; /* we are done */
+               else {
+                       /* cannot resolve yet --- queue it */
+                       hashcpy(obj_list[nr].sha1, null_sha1);
+                       add_delta_to_list(nr, base_sha1, 0, delta_data, delta_size);
+                       return;
+               }
+       } else {
+               unsigned base_found = 0;
+               unsigned char *pack, c;
+               off_t base_offset;
+               unsigned lo, mid, hi;
+
+               pack = fill(1);
+               c = *pack;
+               use(1);
+               base_offset = c & 127;
+               while (c & 128) {
+                       base_offset += 1;
+                       if (!base_offset || MSB(base_offset, 7))
+                               die("offset value overflow for delta base object");
+                       pack = fill(1);
+                       c = *pack;
+                       use(1);
+                       base_offset = (base_offset << 7) + (c & 127);
+               }
+               base_offset = obj_list[nr].offset - base_offset;
+               if (base_offset <= 0 || base_offset >= obj_list[nr].offset)
+                       die("offset value out of bound for delta base object");
+
+               delta_data = get_data(delta_size);
+               if (dry_run || !delta_data) {
+                       free(delta_data);
+                       return;
+               }
+               lo = 0;
+               hi = nr;
+               while (lo < hi) {
+                       mid = (lo + hi)/2;
+                       if (base_offset < obj_list[mid].offset) {
+                               hi = mid;
+                       } else if (base_offset > obj_list[mid].offset) {
+                               lo = mid + 1;
+                       } else {
+                               hashcpy(base_sha1, obj_list[mid].sha1);
+                               base_found = !is_null_sha1(base_sha1);
+                               break;
+                       }
+               }
+               if (!base_found) {
+                       /*
+                        * The delta base object is itself a delta that
+                        * has not been resolved yet.
+                        */
+                       hashcpy(obj_list[nr].sha1, null_sha1);
+                       add_delta_to_list(nr, null_sha1, base_offset, delta_data, delta_size);
+                       return;
+               }
+       }
+
+       if (resolve_against_held(nr, base_sha1, delta_data, delta_size))
+               return;
+
+       base = read_sha1_file(base_sha1, &type, &base_size);
+       if (!base) {
+               error("failed to read delta-pack base object %s",
+                     sha1_to_hex(base_sha1));
+               if (!recover)
+                       exit(1);
+               has_errors = 1;
+               return;
+       }
+       resolve_delta(nr, type, base, base_size, delta_data, delta_size);
+       free(base);
+}
+
+static void unpack_one(unsigned nr)
+{
+       unsigned shift;
+       unsigned char *pack;
+       unsigned long size, c;
+       enum object_type type;
+
+       obj_list[nr].offset = consumed_bytes;
+
+       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;
+       }
+
+       switch (type) {
+       case OBJ_COMMIT:
+       case OBJ_TREE:
+       case OBJ_BLOB:
+       case OBJ_TAG:
+               unpack_non_delta_entry(type, size, nr);
+               return;
+       case OBJ_REF_DELTA:
+       case OBJ_OFS_DELTA:
+               unpack_delta_entry(type, size, nr);
+               return;
+       default:
+               error("bad object type %d", type);
+               has_errors = 1;
+               if (recover)
+                       return;
+               exit(1);
+       }
+}
+
+static void unpack_all(void)
+{
+       int i;
+       struct progress *progress = NULL;
+       struct pack_header *hdr = fill(sizeof(struct pack_header));
+
+       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 %"PRIu32,
+                       ntohl(hdr->hdr_version));
+       use(sizeof(struct pack_header));
+
+       if (!quiet)
+               progress = start_progress("Unpacking objects", nr_objects);
+       obj_list = xcalloc(nr_objects, sizeof(*obj_list));
+       for (i = 0; i < nr_objects; i++) {
+               unpack_one(i);
+               display_progress(progress, i + 1);
+       }
+       stop_progress(&progress);
+
+       if (delta_list)
+               die("unresolved deltas left after unpacking");
+}
+
+int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
+{
+       int i;
+       unsigned char sha1[20];
+
+       read_replace_refs = 0;
+
+       git_config(git_default_config, NULL);
+
+       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;
+                       }
+                       if (!strcmp(arg, "-r")) {
+                               recover = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--strict")) {
+                               strict = 1;
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--pack_header=")) {
+                               struct pack_header *hdr;
+                               char *c;
+
+                               hdr = (struct pack_header *)buffer;
+                               hdr->hdr_signature = htonl(PACK_SIGNATURE);
+                               hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10));
+                               if (*c != ',')
+                                       die("bad %s", arg);
+                               hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10));
+                               if (*c)
+                                       die("bad %s", arg);
+                               len = sizeof(*hdr);
+                               continue;
+                       }
+                       usage(unpack_usage);
+               }
+
+               /* We don't take any non-flag arguments now.. Maybe some day */
+               usage(unpack_usage);
+       }
+       git_SHA1_Init(&ctx);
+       unpack_all();
+       git_SHA1_Update(&ctx, buffer, offset);
+       git_SHA1_Final(sha1, &ctx);
+       if (strict)
+               write_rest();
+       if (hashcmp(fill(20), sha1))
+               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 */
+       return has_errors;
+}
diff --git a/builtin/update-index.c b/builtin/update-index.c
new file mode 100644 (file)
index 0000000..3ab214d
--- /dev/null
@@ -0,0 +1,788 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "quote.h"
+#include "cache-tree.h"
+#include "tree-walk.h"
+#include "builtin.h"
+#include "refs.h"
+#include "resolve-undo.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;
+static int mark_skip_worktree_only;
+#define MARK_FLAG 1
+#define UNMARK_FLAG 2
+
+__attribute__((format (printf, 1, 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_ce_flags(const char *path, int flag, int mark)
+{
+       int namelen = strlen(path);
+       int pos = cache_name_pos(path, namelen);
+       if (0 <= pos) {
+               if (mark)
+                       active_cache[pos]->ce_flags |= flag;
+               else
+                       active_cache[pos]->ce_flags &= ~flag;
+               cache_tree_invalidate_path(active_cache_tree, path);
+               active_cache_changed = 1;
+               return 0;
+       }
+       return -1;
+}
+
+static int remove_one_path(const char *path)
+{
+       if (!allow_remove)
+               return error("%s: does not exist and --remove not passed", path);
+       if (remove_file_from_cache(path))
+               return error("%s: cannot remove from the index", path);
+       return 0;
+}
+
+/*
+ * Handle a path that couldn't be lstat'ed. It's either:
+ *  - missing file (ENOENT or ENOTDIR). That's ok if we're
+ *    supposed to be removing it and the removal actually
+ *    succeeds.
+ *  - permission error. That's never ok.
+ */
+static int process_lstat_error(const char *path, int err)
+{
+       if (err == ENOENT || err == ENOTDIR)
+               return remove_one_path(path);
+       return error("lstat(\"%s\"): %s", path, strerror(errno));
+}
+
+static int add_one_path(struct cache_entry *old, const char *path, int len, struct stat *st)
+{
+       int option, size;
+       struct cache_entry *ce;
+
+       /* Was the old index entry already up-to-date? */
+       if (old && !ce_stage(old) && !ce_match_stat(old, st, 0))
+               return 0;
+
+       size = cache_entry_size(len);
+       ce = xcalloc(1, size);
+       memcpy(ce->name, path, len);
+       ce->ce_flags = len;
+       fill_stat_cache_info(ce, st);
+       ce->ce_mode = ce_mode_from_stat(old, st->st_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;
+}
+
+/*
+ * Handle a path that was a directory. Four cases:
+ *
+ *  - it's already a gitlink in the index, and we keep it that
+ *    way, and update it if we can (if we cannot find the HEAD,
+ *    we're going to keep it unchanged in the index!)
+ *
+ *  - it's a *file* in the index, in which case it should be
+ *    removed as a file if removal is allowed, since it doesn't
+ *    exist as such any more. If removal isn't allowed, it's
+ *    an error.
+ *
+ *    (NOTE! This is old and arguably fairly strange behaviour.
+ *    We might want to make this an error unconditionally, and
+ *    use "--force-remove" if you actually want to force removal).
+ *
+ *  - it used to exist as a subdirectory (ie multiple files with
+ *    this particular prefix) in the index, in which case it's wrong
+ *    to try to update it as a directory.
+ *
+ *  - it doesn't exist at all in the index, but it is a valid
+ *    git directory, and it should be *added* as a gitlink.
+ */
+static int process_directory(const char *path, int len, struct stat *st)
+{
+       unsigned char sha1[20];
+       int pos = cache_name_pos(path, len);
+
+       /* Exact match: file or existing gitlink */
+       if (pos >= 0) {
+               struct cache_entry *ce = active_cache[pos];
+               if (S_ISGITLINK(ce->ce_mode)) {
+
+                       /* Do nothing to the index if there is no HEAD! */
+                       if (resolve_gitlink_ref(path, "HEAD", sha1) < 0)
+                               return 0;
+
+                       return add_one_path(ce, path, len, st);
+               }
+               /* Should this be an unconditional error? */
+               return remove_one_path(path);
+       }
+
+       /* Inexact match: is there perhaps a subdirectory match? */
+       pos = -pos-1;
+       while (pos < active_nr) {
+               struct cache_entry *ce = active_cache[pos++];
+
+               if (strncmp(ce->name, path, len))
+                       break;
+               if (ce->name[len] > '/')
+                       break;
+               if (ce->name[len] < '/')
+                       continue;
+
+               /* Subdirectory match - error out */
+               return error("%s: is a directory - add individual files instead", path);
+       }
+
+       /* No match - should we add it as a gitlink? */
+       if (!resolve_gitlink_ref(path, "HEAD", sha1))
+               return add_one_path(NULL, path, len, st);
+
+       /* Error out. */
+       return error("%s: is a directory - add files inside instead", path);
+}
+
+static int process_path(const char *path)
+{
+       int pos, len;
+       struct stat st;
+       struct cache_entry *ce;
+
+       len = strlen(path);
+       if (has_symlink_leading_path(path, len))
+               return error("'%s' is beyond a symbolic link", path);
+
+       pos = cache_name_pos(path, len);
+       ce = pos < 0 ? NULL : active_cache[pos];
+       if (ce && ce_skip_worktree(ce)) {
+               /*
+                * working directory version is assumed "good"
+                * so updating it does not make sense.
+                * On the other hand, removing it from index should work
+                */
+               if (allow_remove && remove_file_from_cache(path))
+                       return error("%s: cannot remove from the index", path);
+               return 0;
+       }
+
+       /*
+        * First things first: get the stat information, to decide
+        * what to do about the pathname!
+        */
+       if (lstat(path, &st) < 0)
+               return process_lstat_error(path, errno);
+
+       if (S_ISDIR(st.st_mode))
+               return process_directory(path, len, &st);
+
+       /*
+        * Process a regular file
+        */
+       if (ce && S_ISGITLINK(ce->ce_mode))
+               return error("%s is already a gitlink, not replacing", path);
+
+       return add_one_path(ce, path, len, &st);
+}
+
+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 error("Invalid path '%s'", path);
+
+       len = strlen(path);
+       size = cache_entry_size(len);
+       ce = xcalloc(1, size);
+
+       hashcpy(ce->sha1, sha1);
+       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 |= 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);
+       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 = ce->ce_mode;
+       if (!S_ISREG(mode))
+               goto fail;
+       switch (flip) {
+       case '+':
+               ce->ce_mode |= 0111; break;
+       case '-':
+               ce->ce_mode &= ~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 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_ce_flags(p, CE_VALID, mark_valid_only == MARK_FLAG))
+                       die("Unable to mark file %s", path);
+               goto free_return;
+       }
+       if (mark_skip_worktree_only) {
+               if (mark_ce_flags(p, CE_SKIP_WORKTREE, mark_skip_worktree_only == MARK_FLAG))
+                       die("Unable to mark file %s", path);
+               goto free_return;
+       }
+
+       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 (process_path(p))
+               die("Unable to process path %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;
+       struct strbuf uq = STRBUF_INIT;
+
+       while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
+               char *ptr, *tab;
+               char *path_name;
+               unsigned char sha1[20];
+               unsigned int mode;
+               unsigned long ul;
+               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.
+                */
+               errno = 0;
+               ul = strtoul(buf.buf, &ptr, 8);
+               if (ptr == buf.buf || *ptr != ' '
+                   || errno || (unsigned int) ul != ul)
+                       goto bad_line;
+               mode = ul;
+
+               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;
+
+               path_name = ptr;
+               if (line_termination && path_name[0] == '"') {
+                       strbuf_reset(&uq);
+                       if (unquote_c_style(&uq, path_name, NULL)) {
+                               die("git update-index: bad quoting of path name");
+                       }
+                       path_name = uq.buf;
+               }
+
+               if (!verify_path(path_name)) {
+                       fprintf(stderr, "Ignoring path %s\n", path_name);
+                       continue;
+               }
+
+               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);
+               }
+               continue;
+
+       bad_line:
+               die("malformed index info %s", buf.buf);
+       }
+       strbuf_release(&buf);
+       strbuf_release(&uq);
+}
+
+static const char update_index_usage[] =
+"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--skip-worktree|--no-skip-worktree] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--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);
+
+       hashcpy(ce->sha1, sha1);
+       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 (0 <= pos) {
+               /* already merged */
+               pos = unmerge_cache_entry_at(pos);
+               if (pos < active_nr) {
+                       struct cache_entry *ce = active_cache[pos];
+                       if (ce_stage(ce) &&
+                           ce_namelen(ce) == namelen &&
+                           !memcmp(ce->name, path, namelen))
+                               return 0;
+               }
+               /* no resolve-undo information; fall back */
+       } else {
+               /* 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 (!hashcmp(ce_2->sha1, ce_3->sha1) &&
+           ce_2->ce_mode == ce_3->ce_mode) {
+               fprintf(stderr, "%s: identical in both, skipping.\n",
+                       path);
+               goto free_return;
+       }
+
+       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("HEAD", head_sha1))
+               die("No HEAD -- no initial commit yet?");
+       if (read_ref("MERGE_HEAD", merge_head_sha1)) {
+               fprintf(stderr, "Not in the middle of a merge.\n");
+               exit(0);
+       }
+}
+
+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("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 &&
+                   !hashcmp(ce->sha1, old->sha1)) {
+                       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 cmd_update_index(int argc, const char **argv, const char *prefix)
+{
+       int i, newfd, entries, has_errors = 0, line_termination = '\n';
+       int allow_options = 1;
+       int read_from_stdin = 0;
+       int prefix_length = prefix ? strlen(prefix) : 0;
+       char set_executable_bit = 0;
+       unsigned int refresh_flags = 0;
+       int lock_error = 0;
+       struct lock_file *lock_file;
+
+       git_config(git_default_config, NULL);
+
+       /* We can't free this memory, it becomes part of a linked list parsed atexit() */
+       lock_file = xcalloc(1, sizeof(struct lock_file));
+
+       newfd = hold_locked_index(lock_file, 0);
+       if (newfd < 0)
+               lock_error = errno;
+
+       entries = read_cache();
+       if (entries < 0)
+               die("cache corrupted");
+
+       for (i = 1 ; i < argc; i++) {
+               const char *path = argv[i];
+               const char *p;
+
+               if (allow_options && *path == '-') {
+                       if (!strcmp(path, "--")) {
+                               allow_options = 0;
+                               continue;
+                       }
+                       if (!strcmp(path, "-q")) {
+                               refresh_flags |= REFRESH_QUIET;
+                               continue;
+                       }
+                       if (!strcmp(path, "--ignore-submodules")) {
+                               refresh_flags |= REFRESH_IGNORE_SUBMODULES;
+                               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")) {
+                               setup_work_tree();
+                               has_errors |= refresh_cache(refresh_flags);
+                               continue;
+                       }
+                       if (!strcmp(path, "--really-refresh")) {
+                               setup_work_tree();
+                               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 (strtoul_ui(argv[i+1], 8, &mode) ||
+                                   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_FLAG;
+                               continue;
+                       }
+                       if (!strcmp(path, "--no-assume-unchanged")) {
+                               mark_valid_only = UNMARK_FLAG;
+                               continue;
+                       }
+                       if (!strcmp(path, "--no-skip-worktree")) {
+                               mark_skip_worktree_only = UNMARK_FLAG;
+                               continue;
+                       }
+                       if (!strcmp(path, "--skip-worktree")) {
+                               mark_skip_worktree_only = MARK_FLAG;
+                               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") || !strcmp(path, "-g")) {
+                               setup_work_tree();
+                               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, "--clear-resolve-undo")) {
+                               resolve_undo_clear();
+                               continue;
+                       }
+                       if (!strcmp(path, "-h") || !strcmp(path, "--help"))
+                               usage(update_index_usage);
+                       die("unknown option %s", path);
+               }
+               setup_work_tree();
+               p = prefix_path(prefix, prefix_length, path);
+               update_one(p, NULL, 0);
+               if (set_executable_bit)
+                       chmod_path(set_executable_bit, p);
+               if (p < path || p > path + strlen(path))
+                       free((char *)p);
+       }
+       if (read_from_stdin) {
+               struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
+
+               setup_work_tree();
+               while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
+                       const char *p;
+                       if (line_termination && buf.buf[0] == '"') {
+                               strbuf_reset(&nbuf);
+                               if (unquote_c_style(&nbuf, buf.buf, NULL))
+                                       die("line is badly quoted");
+                               strbuf_swap(&buf, &nbuf);
+                       }
+                       p = prefix_path(prefix, prefix_length, buf.buf);
+                       update_one(p, NULL, 0);
+                       if (set_executable_bit)
+                               chmod_path(set_executable_bit, p);
+                       if (p < buf.buf || p > buf.buf + buf.len)
+                               free((char *)p);
+               }
+               strbuf_release(&nbuf);
+               strbuf_release(&buf);
+       }
+
+ finish:
+       if (active_cache_changed) {
+               if (newfd < 0) {
+                       if (refresh_flags & REFRESH_QUIET)
+                               exit(128);
+                       unable_to_lock_index_die(get_index_file(), lock_error);
+               }
+               if (write_cache(newfd, active_cache, active_nr) ||
+                   commit_locked_index(lock_file))
+                       die("Unable to write new index file");
+       }
+
+       rollback_lock_file(lock_file);
+
+       return has_errors ? 1 : 0;
+}
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
new file mode 100644 (file)
index 0000000..76ba1d5
--- /dev/null
@@ -0,0 +1,58 @@
+#include "cache.h"
+#include "refs.h"
+#include "builtin.h"
+#include "parse-options.h"
+
+static const char * const git_update_ref_usage[] = {
+       "git update-ref [options] -d <refname> [<oldval>]",
+       "git update-ref [options]    <refname> <newval> [<oldval>]",
+       NULL
+};
+
+int cmd_update_ref(int argc, const char **argv, const char *prefix)
+{
+       const char *refname, *oldval, *msg=NULL;
+       unsigned char sha1[20], oldsha1[20];
+       int delete = 0, no_deref = 0, flags = 0;
+       struct option options[] = {
+               OPT_STRING( 'm', NULL, &msg, "reason", "reason of the update"),
+               OPT_BOOLEAN('d', NULL, &delete, "deletes the reference"),
+               OPT_BOOLEAN( 0 , "no-deref", &no_deref,
+                                       "update <refname> not the one it points to"),
+               OPT_END(),
+       };
+
+       git_config(git_default_config, NULL);
+       argc = parse_options(argc, argv, prefix, options, git_update_ref_usage,
+                            0);
+       if (msg && !*msg)
+               die("Refusing to perform update with empty message.");
+
+       if (delete) {
+               if (argc < 1 || argc > 2)
+                       usage_with_options(git_update_ref_usage, options);
+               refname = argv[0];
+               oldval = argv[1];
+       } else {
+               const char *value;
+               if (argc < 2 || argc > 3)
+                       usage_with_options(git_update_ref_usage, options);
+               refname = argv[0];
+               value = argv[1];
+               oldval = argv[2];
+               if (get_sha1(value, sha1))
+                       die("%s: not a valid SHA1", value);
+       }
+
+       hashclr(oldsha1); /* all-zero hash in case oldval is the empty string */
+       if (oldval && *oldval && get_sha1(oldval, oldsha1))
+               die("%s: not a valid old SHA1", oldval);
+
+       if (no_deref)
+               flags = REF_NODEREF;
+       if (delete)
+               return delete_ref(refname, oldval ? oldsha1 : NULL, flags);
+       else
+               return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
+                                 flags, DIE_ON_ERR);
+}
diff --git a/builtin/update-server-info.c b/builtin/update-server-info.c
new file mode 100644 (file)
index 0000000..2b3fddc
--- /dev/null
@@ -0,0 +1,25 @@
+#include "cache.h"
+#include "builtin.h"
+#include "parse-options.h"
+
+static const char * const update_server_info_usage[] = {
+       "git update-server-info [--force]",
+       NULL
+};
+
+int cmd_update_server_info(int argc, const char **argv, const char *prefix)
+{
+       int force = 0;
+       struct option options[] = {
+               OPT_BOOLEAN('f', "force", &force,
+                       "update the info files from scratch"),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, prefix, options,
+                            update_server_info_usage, 0);
+       if (argc > 0)
+               usage_with_options(update_server_info_usage, options);
+
+       return !!update_server_info(force);
+}
diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c
new file mode 100644 (file)
index 0000000..73f788e
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2006 Franck Bui-Huu
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "archive.h"
+#include "pkt-line.h"
+#include "sideband.h"
+
+static const char upload_archive_usage[] =
+       "git upload-archive <repo>";
+
+static const char deadchild[] =
+"git upload-archive: archiver died with error";
+
+static const char lostchild[] =
+"git upload-archive: archiver process was lost";
+
+#define MAX_ARGS (64)
+
+static int run_upload_archive(int argc, const char **argv, const char *prefix)
+{
+       const char *sent_argv[MAX_ARGS];
+       const char *arg_cmd = "argument ";
+       char *p, buf[4096];
+       int sent_argc;
+       int len;
+
+       if (argc != 2)
+               usage(upload_archive_usage);
+
+       if (strlen(argv[1]) + 1 > sizeof(buf))
+               die("insanely long repository name");
+
+       strcpy(buf, argv[1]); /* enter-repo smudges its argument */
+
+       if (!enter_repo(buf, 0))
+               die("'%s' does not appear to be a git repository", buf);
+
+       /* put received options in sent_argv[] */
+       sent_argc = 1;
+       sent_argv[0] = "git-upload-archive";
+       for (p = buf;;) {
+               /* This will die if not enough free space in buf */
+               len = packet_read_line(0, p, (buf + sizeof buf) - p);
+               if (len == 0)
+                       break;  /* got a flush */
+               if (sent_argc > MAX_ARGS - 2)
+                       die("Too many options (>%d)", MAX_ARGS - 2);
+
+               if (p[len-1] == '\n') {
+                       p[--len] = 0;
+               }
+               if (len < strlen(arg_cmd) ||
+                   strncmp(arg_cmd, p, strlen(arg_cmd)))
+                       die("'argument' token or flush expected");
+
+               len -= strlen(arg_cmd);
+               memmove(p, p + strlen(arg_cmd), len);
+               sent_argv[sent_argc++] = p;
+               p += len;
+               *p++ = 0;
+       }
+       sent_argv[sent_argc] = NULL;
+
+       /* parse all options sent by the client */
+       return write_archive(sent_argc, sent_argv, prefix, 0);
+}
+
+__attribute__((format (printf, 1, 2)))
+static void error_clnt(const char *fmt, ...)
+{
+       char buf[1024];
+       va_list params;
+       int len;
+
+       va_start(params, fmt);
+       len = vsprintf(buf, fmt, params);
+       va_end(params);
+       send_sideband(1, 3, buf, len, LARGE_PACKET_MAX);
+       die("sent error to the client: %s", buf);
+}
+
+static ssize_t process_input(int child_fd, int band)
+{
+       char buf[16384];
+       ssize_t sz = read(child_fd, buf, sizeof(buf));
+       if (sz < 0) {
+               if (errno != EAGAIN && errno != EINTR)
+                       error_clnt("read error: %s\n", strerror(errno));
+               return sz;
+       }
+       send_sideband(1, band, buf, sz, LARGE_PACKET_MAX);
+       return sz;
+}
+
+int cmd_upload_archive(int argc, const char **argv, const char *prefix)
+{
+       pid_t writer;
+       int fd1[2], fd2[2];
+       /*
+        * Set up sideband subprocess.
+        *
+        * We (parent) monitor and read from child, sending its fd#1 and fd#2
+        * multiplexed out to our fd#1.  If the child dies, we tell the other
+        * end over channel #3.
+        */
+       if (pipe(fd1) < 0 || pipe(fd2) < 0) {
+               int err = errno;
+               packet_write(1, "NACK pipe failed on the remote side\n");
+               die("upload-archive: %s", strerror(err));
+       }
+       writer = fork();
+       if (writer < 0) {
+               int err = errno;
+               packet_write(1, "NACK fork failed on the remote side\n");
+               die("upload-archive: %s", strerror(err));
+       }
+       if (!writer) {
+               /* child - connect fd#1 and fd#2 to the pipe */
+               dup2(fd1[1], 1);
+               dup2(fd2[1], 2);
+               close(fd1[1]); close(fd2[1]);
+               close(fd1[0]); close(fd2[0]); /* we do not read from pipe */
+
+               exit(run_upload_archive(argc, argv, prefix));
+       }
+
+       /* parent - read from child, multiplex and send out to fd#1 */
+       close(fd1[1]); close(fd2[1]); /* we do not write to pipe */
+       packet_write(1, "ACK\n");
+       packet_flush(1);
+
+       while (1) {
+               struct pollfd pfd[2];
+               int status;
+
+               pfd[0].fd = fd1[0];
+               pfd[0].events = POLLIN;
+               pfd[1].fd = fd2[0];
+               pfd[1].events = POLLIN;
+               if (poll(pfd, 2, -1) < 0) {
+                       if (errno != EINTR) {
+                               error("poll failed resuming: %s",
+                                     strerror(errno));
+                               sleep(1);
+                       }
+                       continue;
+               }
+               if (pfd[1].revents & POLLIN)
+                       /* Status stream ready */
+                       if (process_input(pfd[1].fd, 2))
+                               continue;
+               if (pfd[0].revents & POLLIN)
+                       /* Data stream ready */
+                       if (process_input(pfd[0].fd, 1))
+                               continue;
+
+               if (waitpid(writer, &status, 0) < 0)
+                       error_clnt("%s", lostchild);
+               else if (!WIFEXITED(status) || WEXITSTATUS(status) > 0)
+                       error_clnt("%s", deadchild);
+               packet_flush(1);
+               break;
+       }
+       return 0;
+}
diff --git a/builtin/var.c b/builtin/var.c
new file mode 100644 (file)
index 0000000..0744bb8
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Eric Biederman, 2005
+ */
+#include "cache.h"
+#include "exec_cmd.h"
+
+static const char var_usage[] = "git var (-l | <variable>)";
+
+static const char *editor(int flag)
+{
+       const char *pgm = git_editor();
+
+       if (!pgm && flag & IDENT_ERROR_ON_NO_NAME)
+               die("Terminal is dumb, but EDITOR unset");
+
+       return pgm;
+}
+
+static const char *pager(int flag)
+{
+       const char *pgm = git_pager(1);
+
+       if (!pgm)
+               pgm = "cat";
+       return pgm;
+}
+
+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 },
+       { "GIT_EDITOR", editor },
+       { "GIT_PAGER", pager },
+       { "", NULL },
+};
+
+static void list_vars(void)
+{
+       struct git_var *ptr;
+       const char *val;
+
+       for (ptr = git_vars; ptr->read; ptr++)
+               if ((val = ptr->read(0)))
+                       printf("%s=%s\n", ptr->name, val);
+}
+
+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(IDENT_ERROR_ON_NO_NAME);
+                       break;
+               }
+       }
+       return val;
+}
+
+static int show_config(const char *var, const char *value, void *cb)
+{
+       if (value)
+               printf("%s=%s\n", var, value);
+       else
+               printf("%s\n", var);
+       return git_default_config(var, value, cb);
+}
+
+int cmd_var(int argc, const char **argv, const char *prefix)
+{
+       const char *val = NULL;
+       if (argc != 2)
+               usage(var_usage);
+
+       if (strcmp(argv[1], "-l") == 0) {
+               git_config(show_config, NULL);
+               list_vars();
+               return 0;
+       }
+       git_config(git_default_config, NULL);
+       val = read_var(argv[1]);
+       if (!val)
+               usage(var_usage);
+
+       printf("%s\n", val);
+
+       return 0;
+}
diff --git a/builtin/verify-pack.c b/builtin/verify-pack.c
new file mode 100644 (file)
index 0000000..b6079ae
--- /dev/null
@@ -0,0 +1,166 @@
+#include "builtin.h"
+#include "cache.h"
+#include "pack.h"
+#include "pack-revindex.h"
+#include "parse-options.h"
+
+#define MAX_CHAIN 50
+
+#define VERIFY_PACK_VERBOSE 01
+#define VERIFY_PACK_STAT_ONLY 02
+
+static void show_pack_info(struct packed_git *p, unsigned int flags)
+{
+       uint32_t nr_objects, i;
+       int cnt;
+       int stat_only = flags & VERIFY_PACK_STAT_ONLY;
+       unsigned long chain_histogram[MAX_CHAIN+1], baseobjects;
+
+       nr_objects = p->num_objects;
+       memset(chain_histogram, 0, sizeof(chain_histogram));
+       baseobjects = 0;
+
+       for (i = 0; i < nr_objects; i++) {
+               const unsigned char *sha1;
+               unsigned char base_sha1[20];
+               const char *type;
+               unsigned long size;
+               unsigned long store_size;
+               off_t offset;
+               unsigned int delta_chain_length;
+
+               sha1 = nth_packed_object_sha1(p, i);
+               if (!sha1)
+                       die("internal error pack-check nth-packed-object");
+               offset = nth_packed_object_offset(p, i);
+               type = packed_object_info_detail(p, offset, &size, &store_size,
+                                                &delta_chain_length,
+                                                base_sha1);
+               if (!stat_only)
+                       printf("%s ", sha1_to_hex(sha1));
+               if (!delta_chain_length) {
+                       if (!stat_only)
+                               printf("%-6s %lu %lu %"PRIuMAX"\n",
+                                      type, size, store_size, (uintmax_t)offset);
+                       baseobjects++;
+               }
+               else {
+                       if (!stat_only)
+                               printf("%-6s %lu %lu %"PRIuMAX" %u %s\n",
+                                      type, size, store_size, (uintmax_t)offset,
+                                      delta_chain_length, sha1_to_hex(base_sha1));
+                       if (delta_chain_length <= MAX_CHAIN)
+                               chain_histogram[delta_chain_length]++;
+                       else
+                               chain_histogram[0]++;
+               }
+       }
+
+       if (baseobjects)
+               printf("non delta: %lu object%s\n",
+                      baseobjects, baseobjects > 1 ? "s" : "");
+
+       for (cnt = 1; cnt <= MAX_CHAIN; cnt++) {
+               if (!chain_histogram[cnt])
+                       continue;
+               printf("chain length = %d: %lu object%s\n", cnt,
+                      chain_histogram[cnt],
+                      chain_histogram[cnt] > 1 ? "s" : "");
+       }
+       if (chain_histogram[0])
+               printf("chain length > %d: %lu object%s\n", MAX_CHAIN,
+                      chain_histogram[0],
+                      chain_histogram[0] > 1 ? "s" : "");
+}
+
+static int verify_one_pack(const char *path, unsigned int flags)
+{
+       char arg[PATH_MAX];
+       int len;
+       int verbose = flags & VERIFY_PACK_VERBOSE;
+       int stat_only = flags & VERIFY_PACK_STAT_ONLY;
+       struct packed_git *pack;
+       int err;
+
+       len = strlcpy(arg, path, PATH_MAX);
+       if (len >= PATH_MAX)
+               return error("name too long: %s", path);
+
+       /*
+        * In addition to "foo.idx" we accept "foo.pack" and "foo";
+        * normalize these forms to "foo.idx" for add_packed_git().
+        */
+       if (has_extension(arg, ".pack")) {
+               strcpy(arg + len - 5, ".idx");
+               len--;
+       } else if (!has_extension(arg, ".idx")) {
+               if (len + 4 >= PATH_MAX)
+                       return error("name too long: %s.idx", arg);
+               strcpy(arg + len, ".idx");
+               len += 4;
+       }
+
+       /*
+        * add_packed_git() uses our buffer (containing "foo.idx") to
+        * build the pack filename ("foo.pack").  Make sure it fits.
+        */
+       if (len + 1 >= PATH_MAX) {
+               arg[len - 4] = '\0';
+               return error("name too long: %s.pack", arg);
+       }
+
+       pack = add_packed_git(arg, len, 1);
+       if (!pack)
+               return error("packfile %s not found.", arg);
+
+       install_packed_git(pack);
+
+       if (!stat_only)
+               err = verify_pack(pack);
+       else
+               err = open_pack_index(pack);
+
+       if (verbose || stat_only) {
+               if (err)
+                       printf("%s: bad\n", pack->pack_name);
+               else {
+                       show_pack_info(pack, flags);
+                       if (!stat_only)
+                               printf("%s: ok\n", pack->pack_name);
+               }
+       }
+
+       return err;
+}
+
+static const char * const verify_pack_usage[] = {
+       "git verify-pack [-v|--verbose] [-s|--stat-only] <pack>...",
+       NULL
+};
+
+int cmd_verify_pack(int argc, const char **argv, const char *prefix)
+{
+       int err = 0;
+       unsigned int flags = 0;
+       int i;
+       const struct option verify_pack_options[] = {
+               OPT_BIT('v', "verbose", &flags, "verbose",
+                       VERIFY_PACK_VERBOSE),
+               OPT_BIT('s', "stat-only", &flags, "show statistics only",
+                       VERIFY_PACK_STAT_ONLY),
+               OPT_END()
+       };
+
+       git_config(git_default_config, NULL);
+       argc = parse_options(argc, argv, prefix, verify_pack_options,
+                            verify_pack_usage, 0);
+       if (argc < 1)
+               usage_with_options(verify_pack_usage, verify_pack_options);
+       for (i = 0; i < argc; i++) {
+               if (verify_one_pack(argv[i], flags))
+                       err = 1;
+               discard_revindex();
+       }
+
+       return err;
+}
diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c
new file mode 100644 (file)
index 0000000..9f482c2
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Builtin "git verify-tag"
+ *
+ * Copyright (c) 2007 Carlos Rica <jasampler@gmail.com>
+ *
+ * Based on git-verify-tag.sh
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "tag.h"
+#include "run-command.h"
+#include <signal.h>
+#include "parse-options.h"
+
+static const char * const verify_tag_usage[] = {
+               "git verify-tag [-v|--verbose] <tag>...",
+               NULL
+};
+
+#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
+
+static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
+{
+       struct child_process gpg;
+       const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL};
+       char path[PATH_MAX], *eol;
+       size_t len;
+       int fd, ret;
+
+       fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX");
+       if (fd < 0)
+               return error("could not create temporary file '%s': %s",
+                                               path, strerror(errno));
+       if (write_in_full(fd, buf, size) < 0)
+               return error("failed writing temporary file '%s': %s",
+                                               path, strerror(errno));
+       close(fd);
+
+       /* find the length without signature */
+       len = 0;
+       while (len < size && prefixcmp(buf + len, PGP_SIGNATURE)) {
+               eol = memchr(buf + len, '\n', size - len);
+               len += eol ? eol - (buf + len) + 1 : size - len;
+       }
+       if (verbose)
+               write_in_full(1, buf, len);
+
+       memset(&gpg, 0, sizeof(gpg));
+       gpg.argv = args_gpg;
+       gpg.in = -1;
+       args_gpg[2] = path;
+       if (start_command(&gpg)) {
+               unlink(path);
+               return error("could not run gpg.");
+       }
+
+       write_in_full(gpg.in, buf, len);
+       close(gpg.in);
+       ret = finish_command(&gpg);
+
+       unlink_or_warn(path);
+
+       return ret;
+}
+
+static int verify_tag(const char *name, int verbose)
+{
+       enum object_type type;
+       unsigned char sha1[20];
+       char *buf;
+       unsigned long size;
+       int ret;
+
+       if (get_sha1(name, sha1))
+               return error("tag '%s' not found.", name);
+
+       type = sha1_object_info(sha1, NULL);
+       if (type != OBJ_TAG)
+               return error("%s: cannot verify a non-tag object of type %s.",
+                               name, typename(type));
+
+       buf = read_sha1_file(sha1, &type, &size);
+       if (!buf)
+               return error("%s: unable to read file.", name);
+
+       ret = run_gpg_verify(buf, size, verbose);
+
+       free(buf);
+       return ret;
+}
+
+int cmd_verify_tag(int argc, const char **argv, const char *prefix)
+{
+       int i = 1, verbose = 0, had_error = 0;
+       const struct option verify_tag_options[] = {
+               OPT__VERBOSE(&verbose),
+               OPT_END()
+       };
+
+       git_config(git_default_config, NULL);
+
+       argc = parse_options(argc, argv, prefix, verify_tag_options,
+                            verify_tag_usage, PARSE_OPT_KEEP_ARGV0);
+       if (argc <= i)
+               usage_with_options(verify_tag_usage, verify_tag_options);
+
+       /* sometimes the program was terminated because this signal
+        * was received in the process of writing the gpg input: */
+       signal(SIGPIPE, SIG_IGN);
+       while (i < argc)
+               if (verify_tag(argv[i++], verbose))
+                       had_error = 1;
+       return had_error;
+}
diff --git a/builtin/write-tree.c b/builtin/write-tree.c
new file mode 100644 (file)
index 0000000..b223af4
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "builtin.h"
+#include "cache.h"
+#include "tree.h"
+#include "cache-tree.h"
+#include "parse-options.h"
+
+static const char * const write_tree_usage[] = {
+       "git write-tree [--missing-ok] [--prefix=<prefix>/]",
+       NULL
+};
+
+int cmd_write_tree(int argc, const char **argv, const char *unused_prefix)
+{
+       int flags = 0, ret;
+       const char *prefix = NULL;
+       unsigned char sha1[20];
+       const char *me = "git-write-tree";
+       struct option write_tree_options[] = {
+               OPT_BIT(0, "missing-ok", &flags, "allow missing objects",
+                       WRITE_TREE_MISSING_OK),
+               { OPTION_STRING, 0, "prefix", &prefix, "<prefix>/",
+                 "write tree object for a subdirectory <prefix>" ,
+                 PARSE_OPT_LITERAL_ARGHELP },
+               { OPTION_BIT, 0, "ignore-cache-tree", &flags, NULL,
+                 "only useful for debugging",
+                 PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, NULL,
+                 WRITE_TREE_IGNORE_CACHE_TREE },
+               OPT_END()
+       };
+
+       git_config(git_default_config, NULL);
+       argc = parse_options(argc, argv, unused_prefix, write_tree_options,
+                            write_tree_usage, 0);
+
+       ret = write_cache_as_tree(sha1, flags, prefix);
+       switch (ret) {
+       case 0:
+               printf("%s\n", sha1_to_hex(sha1));
+               break;
+       case WRITE_TREE_UNREADABLE_INDEX:
+               die("%s: error reading the index", me);
+               break;
+       case WRITE_TREE_UNMERGED_INDEX:
+               die("%s: error building trees", me);
+               break;
+       case WRITE_TREE_PREFIX_ERROR:
+               die("%s: prefix %s not found", me, prefix);
+               break;
+       }
+       return ret;
+}
index d0dd818b31faa04caa4d418a39ad3020a919aa2d..65ea26bdb8c2cef671030c3a62d87d84612e8144 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -98,7 +98,7 @@ int verify_bundle(struct bundle_header *header, int verbose)
         */
        struct ref_list *p = &header->prerequisites;
        struct rev_info revs;
-       const char *argv[] = {NULL, "--all"};
+       const char *argv[] = {NULL, "--all", NULL};
        struct object_array refs;
        struct commit *commit;
        int i, ret = 0, req_nr;
@@ -204,7 +204,6 @@ int create_bundle(struct bundle_header *header, const char *path,
        int i, ref_count = 0;
        char buffer[1024];
        struct rev_info revs;
-       int read_from_stdin = 0;
        struct child_process rls;
        FILE *rls_fout;
 
@@ -234,7 +233,7 @@ int create_bundle(struct bundle_header *header, const char *path,
        rls.git_cmd = 1;
        if (start_command(&rls))
                return -1;
-       rls_fout = fdopen(rls.out, "r");
+       rls_fout = xfdopen(rls.out, "r");
        while (fgets(buffer, sizeof(buffer), rls_fout)) {
                unsigned char sha1[20];
                if (buffer[0] == '-') {
@@ -256,15 +255,8 @@ int create_bundle(struct bundle_header *header, const char *path,
        /* write references */
        argc = setup_revisions(argc, argv, &revs, NULL);
 
-       for (i = 1; i < argc; i++) {
-               if (!strcmp(argv[i], "--stdin")) {
-                       if (read_from_stdin++)
-                               die("--stdin given twice?");
-                       read_revisions_from_stdin(&revs);
-                       continue;
-               }
-               return error("unrecognized argument: %s'", argv[i]);
-       }
+       if (argc > 1)
+               return error("unrecognized argument: %s'", argv[1]);
 
        object_array_remove_duplicates(&revs.pending);
 
@@ -351,7 +343,7 @@ int create_bundle(struct bundle_header *header, const char *path,
 
        /* write pack */
        argv_pack[0] = "pack-objects";
-       argv_pack[1] = "--all-progress";
+       argv_pack[1] = "--all-progress-implied";
        argv_pack[2] = "--stdout";
        argv_pack[3] = "--thin";
        argv_pack[4] = NULL;
@@ -380,8 +372,10 @@ int create_bundle(struct bundle_header *header, const char *path,
        close(rls.in);
        if (finish_command(&rls))
                return error ("pack-objects died");
-       if (!bundle_to_stdout)
-               commit_lock_file(&lock);
+       if (!bundle_to_stdout) {
+               if (commit_lock_file(&lock))
+                       die_errno("cannot create '%s'", path);
+       }
        return 0;
 }
 
index 37bf35e636f0966662416f0693dbea5c1cc73311..f755590da827234830d8b4359720cfbfd87a3dea 100644 (file)
@@ -22,8 +22,10 @@ void cache_tree_free(struct cache_tree **it_p)
        if (!it)
                return;
        for (i = 0; i < it->subtree_nr; i++)
-               if (it->down[i])
+               if (it->down[i]) {
                        cache_tree_free(&it->down[i]->cache_tree);
+                       free(it->down[i]);
+               }
        free(it->down);
        free(it);
        *it_p = NULL;
@@ -328,8 +330,11 @@ static int update_one(struct cache_tree *it,
                        mode = ce->ce_mode;
                        entlen = pathlen - baselen;
                }
-               if (mode != S_IFGITLINK && !missing_ok && !has_sha1_file(sha1))
-                       return error("invalid object %s", sha1_to_hex(sha1));
+               if (mode != S_IFGITLINK && !missing_ok && !has_sha1_file(sha1)) {
+                       strbuf_release(&buffer);
+                       return error("invalid object %06o %s for '%.*s'",
+                               mode, sha1_to_hex(sha1), entlen+baselen, path);
+               }
 
                if (ce->ce_flags & CE_REMOVE)
                        continue; /* entry being removed */
@@ -514,6 +519,8 @@ struct cache_tree *cache_tree_read(const char *buffer, unsigned long size)
 
 static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path)
 {
+       if (!it)
+               return NULL;
        while (*path) {
                const char *slash;
                struct cache_tree_sub *sub;
@@ -538,28 +545,32 @@ static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *pat
        return it;
 }
 
-int write_cache_as_tree(unsigned char *sha1, int missing_ok, const char *prefix)
+int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)
 {
        int entries, was_valid, newfd;
+       struct lock_file *lock_file;
 
        /*
         * We can't free this memory, it becomes part of a linked list
         * parsed atexit()
         */
-       struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+       lock_file = xcalloc(1, sizeof(struct lock_file));
 
        newfd = hold_locked_index(lock_file, 1);
 
        entries = read_cache();
        if (entries < 0)
                return WRITE_TREE_UNREADABLE_INDEX;
+       if (flags & WRITE_TREE_IGNORE_CACHE_TREE)
+               cache_tree_free(&(active_cache_tree));
 
        if (!active_cache_tree)
                active_cache_tree = cache_tree();
 
        was_valid = cache_tree_fully_valid(active_cache_tree);
-
        if (!was_valid) {
+               int missing_ok = flags & WRITE_TREE_MISSING_OK;
+
                if (cache_tree_update(active_cache_tree,
                                      active_cache, active_nr,
                                      missing_ok, 0) < 0)
@@ -625,3 +636,35 @@ void prime_cache_tree(struct cache_tree **it, struct tree *tree)
        *it = cache_tree();
        prime_cache_tree_rec(*it, tree);
 }
+
+/*
+ * find the cache_tree that corresponds to the current level without
+ * exploding the full path into textual form.  The root of the
+ * cache tree is given as "root", and our current level is "info".
+ * (1) When at root level, info->prev is NULL, so it is "root" itself.
+ * (2) Otherwise, find the cache_tree that corresponds to one level
+ *     above us, and find ourselves in there.
+ */
+static struct cache_tree *find_cache_tree_from_traversal(struct cache_tree *root,
+                                                        struct traverse_info *info)
+{
+       struct cache_tree *our_parent;
+
+       if (!info->prev)
+               return root;
+       our_parent = find_cache_tree_from_traversal(root, info->prev);
+       return cache_tree_find(our_parent, info->name.path);
+}
+
+int cache_tree_matches_traversal(struct cache_tree *root,
+                                struct name_entry *ent,
+                                struct traverse_info *info)
+{
+       struct cache_tree *it;
+
+       it = find_cache_tree_from_traversal(root, info);
+       it = cache_tree_find(it, ent->path);
+       if (it && it->entry_count > 0 && !hashcmp(ent->sha1, it->sha1))
+               return it->entry_count;
+       return 0;
+}
index e95883523633a51f833683e96af1738da2868238..3df641f59311f43aa951a2cdfa9f110b97b13a45 100644 (file)
@@ -2,6 +2,7 @@
 #define CACHE_TREE_H
 
 #include "tree.h"
+#include "tree-walk.h"
 
 struct cache_tree;
 struct cache_tree_sub {
@@ -30,11 +31,18 @@ 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);
 
+/* bitmasks to write_cache_as_tree flags */
+#define WRITE_TREE_MISSING_OK 1
+#define WRITE_TREE_IGNORE_CACHE_TREE 2
+
+/* error return codes */
 #define WRITE_TREE_UNREADABLE_INDEX (-1)
 #define WRITE_TREE_UNMERGED_INDEX (-2)
 #define WRITE_TREE_PREFIX_ERROR (-3)
 
-int write_cache_as_tree(unsigned char *sha1, int missing_ok, const char *prefix);
+int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix);
 void prime_cache_tree(struct cache_tree **, struct tree *);
 
+extern int cache_tree_matches_traversal(struct cache_tree *, struct name_entry *ent, struct traverse_info *info);
+
 #endif
diff --git a/cache.h b/cache.h
index b8503ad91c3b13ccaf87a6f596d13918f7ae114b..2ef2fa3a5e166d94e5e981ca7c76afad13236e7e 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -4,6 +4,7 @@
 #include "git-compat-util.h"
 #include "strbuf.h"
 #include "hash.h"
+#include "advice.h"
 
 #include SHA1_HEADER
 #ifndef git_SHA_CTX
@@ -176,15 +177,21 @@ struct cache_entry {
 
 #define CE_HASHED    (0x100000)
 #define CE_UNHASHED  (0x200000)
+#define CE_CONFLICTED (0x800000)
+
+#define CE_WT_REMOVE (0x400000) /* remove in work directory */
+
+#define CE_UNPACKED  (0x1000000)
 
 /*
  * Extended on-disk flags
  */
 #define CE_INTENT_TO_ADD 0x20000000
+#define CE_SKIP_WORKTREE 0x40000000
 /* CE_EXTENDED2 is for future extension */
 #define CE_EXTENDED2 0x80000000
 
-#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD)
+#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD | CE_SKIP_WORKTREE)
 
 /*
  * Safeguard to avoid saving wrong flags:
@@ -233,6 +240,7 @@ static inline size_t ce_namelen(const struct cache_entry *ce)
                            ondisk_cache_entry_size(ce_namelen(ce)))
 #define ce_stage(ce) ((CE_STAGEMASK & (ce)->ce_flags) >> CE_STAGESHIFT)
 #define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE)
+#define ce_skip_worktree(ce) ((ce)->ce_flags & CE_SKIP_WORKTREE)
 #define ce_mark_uptodate(ce) ((ce)->ce_flags |= CE_UPTODATE)
 
 #define ce_permissions(mode) (((mode) & 0100) ? 0755 : 0644)
@@ -281,6 +289,7 @@ static inline int ce_to_dtype(const struct cache_entry *ce)
 struct index_state {
        struct cache_entry **cache;
        unsigned int cache_nr, cache_alloc, cache_changed;
+       struct string_list *resolve_undo;
        struct cache_tree *cache_tree;
        struct cache_time timestamp;
        void *alloc;
@@ -330,11 +339,14 @@ static inline void remove_name_hash(struct cache_entry *ce)
 #define remove_file_from_cache(path) remove_file_from_index(&the_index, (path))
 #define add_to_cache(path, st, flags) add_to_index(&the_index, (path), (st), (flags))
 #define add_file_to_cache(path, flags) add_file_to_index(&the_index, (path), (flags))
-#define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL)
+#define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL, NULL)
 #define ce_match_stat(ce, st, options) ie_match_stat(&the_index, (ce), (st), (options))
 #define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options))
 #define cache_name_exists(name, namelen, igncase) index_name_exists(&the_index, (name), (namelen), (igncase))
 #define cache_name_is_other(name, namelen) index_name_is_other(&the_index, (name), (namelen))
+#define resolve_undo_clear() resolve_undo_clear_index(&the_index)
+#define unmerge_cache_entry_at(at) unmerge_index_entry_at(&the_index, at)
+#define unmerge_cache(pathspec) unmerge_index(&the_index, pathspec)
 #endif
 
 enum object_type {
@@ -348,7 +360,7 @@ enum object_type {
        OBJ_OFS_DELTA = 6,
        OBJ_REF_DELTA = 7,
        OBJ_ANY,
-       OBJ_MAX,
+       OBJ_MAX
 };
 
 static inline enum object_type object_type(unsigned int mode)
@@ -366,11 +378,27 @@ static inline enum object_type object_type(unsigned int mode)
 #define GRAFT_ENVIRONMENT "GIT_GRAFT_FILE"
 #define TEMPLATE_DIR_ENVIRONMENT "GIT_TEMPLATE_DIR"
 #define CONFIG_ENVIRONMENT "GIT_CONFIG"
+#define CONFIG_DATA_ENVIRONMENT "GIT_CONFIG_PARAMETERS"
 #define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH"
 #define CEILING_DIRECTORIES_ENVIRONMENT "GIT_CEILING_DIRECTORIES"
+#define NO_REPLACE_OBJECTS_ENVIRONMENT "GIT_NO_REPLACE_OBJECTS"
 #define GITATTRIBUTES_FILE ".gitattributes"
 #define INFOATTRIBUTES_FILE "info/attributes"
 #define ATTRIBUTE_MACRO_PREFIX "[attr]"
+#define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF"
+#define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
+#define GIT_NOTES_DISPLAY_REF_ENVIRONMENT "GIT_NOTES_DISPLAY_REF"
+#define GIT_NOTES_REWRITE_REF_ENVIRONMENT "GIT_NOTES_REWRITE_REF"
+#define GIT_NOTES_REWRITE_MODE_ENVIRONMENT "GIT_NOTES_REWRITE_MODE"
+
+/*
+ * Repository-local GIT_* environment variables
+ * The array is NULL-terminated to simplify its usage in contexts such
+ * environment creation or simple walk of the list.
+ * The number of non-NULL entries is available as a macro.
+ */
+#define LOCAL_REPO_ENV_SIZE 9
+extern const char *const local_repo_env[LOCAL_REPO_ENV_SIZE + 1];
 
 extern int is_bare_repository_cfg;
 extern int is_bare_repository(void);
@@ -395,6 +423,7 @@ 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 int check_filename(const char *prefix, const char *name);
 extern void verify_filename(const char *prefix, const char *name);
 extern void verify_non_filename(const char *prefix, const char *name);
 
@@ -420,7 +449,7 @@ extern int init_db(const char *template_dir, unsigned int flags);
                                alloc = alloc_nr(alloc); \
                        x = xrealloc((x), alloc * sizeof(*(x))); \
                } \
-       } while(0)
+       } while (0)
 
 /* Initialize and use the cache information */
 extern int read_index(struct index_state *);
@@ -440,7 +469,6 @@ extern int index_name_pos(const struct index_state *, const char *name, int name
 #define ADD_CACHE_JUST_APPEND 8                /* Append only; tree.c::read_tree() */
 #define ADD_CACHE_NEW_ONLY 16          /* Do not replace existing ones */
 extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option);
-extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
 extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name);
 extern int remove_index_entry_at(struct index_state *, int pos);
 extern void remove_marked_cache_entries(struct index_state *istate);
@@ -459,7 +487,9 @@ extern int index_name_is_other(const struct index_state *, const char *, int);
 /* do stat comparison even if CE_VALID is true */
 #define CE_MATCH_IGNORE_VALID          01
 /* do not check the contents but report dirty on racily-clean entries */
-#define CE_MATCH_RACY_IS_DIRTY 02
+#define CE_MATCH_RACY_IS_DIRTY         02
+/* do stat comparison even if CE_SKIP_WORKTREE is true */
+#define CE_MATCH_IGNORE_SKIP_WORKTREE  04
 extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 
@@ -473,8 +503,8 @@ extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
 #define REFRESH_QUIET          0x0004  /* be quiet about it */
 #define REFRESH_IGNORE_MISSING 0x0008  /* ignore non-existent */
 #define REFRESH_IGNORE_SUBMODULES      0x0010  /* ignore submodules */
-#define REFRESH_SAY_CHANGED    0x0020  /* say "changed" not "needs update" */
-extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen);
+#define REFRESH_IN_PORCELAIN   0x0020  /* user friendly output, not "needs update" */
+extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen, char *header_msg);
 
 struct lock_file {
        struct lock_file *next;
@@ -485,6 +515,7 @@ struct lock_file {
 };
 #define LOCK_DIE_ON_ERROR 1
 #define LOCK_NODEREF 2
+extern int unable_to_lock_error(const char *path, int err);
 extern NORETURN void unable_to_lock_index_die(const char *path, int err);
 extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
 extern int hold_lock_file_for_append(struct lock_file *, const char *path, int);
@@ -509,45 +540,68 @@ extern int log_all_ref_updates;
 extern int warn_ambiguous_refs;
 extern int shared_repository;
 extern const char *apply_default_whitespace;
+extern const char *apply_default_ignorewhitespace;
 extern int zlib_compression_level;
 extern int core_compression_level;
 extern int core_compression_seen;
 extern size_t packed_git_window_size;
 extern size_t packed_git_limit;
 extern size_t delta_base_cache_limit;
-extern int auto_crlf;
+extern int read_replace_refs;
 extern int fsync_object_files;
 extern int core_preload_index;
+extern int core_apply_sparse_checkout;
 
 enum safe_crlf {
        SAFE_CRLF_FALSE = 0,
        SAFE_CRLF_FAIL = 1,
-       SAFE_CRLF_WARN = 2,
+       SAFE_CRLF_WARN = 2
 };
 
 extern enum safe_crlf safe_crlf;
 
+enum auto_crlf {
+       AUTO_CRLF_FALSE = 0,
+       AUTO_CRLF_TRUE = 1,
+       AUTO_CRLF_INPUT = -1,
+};
+
+extern enum auto_crlf auto_crlf;
+
+enum eol {
+       EOL_UNSET,
+       EOL_CRLF,
+       EOL_LF,
+#ifdef NATIVE_CRLF
+       EOL_NATIVE = EOL_CRLF
+#else
+       EOL_NATIVE = EOL_LF
+#endif
+};
+
+extern enum eol eol;
+
 enum branch_track {
        BRANCH_TRACK_UNSPECIFIED = -1,
        BRANCH_TRACK_NEVER = 0,
        BRANCH_TRACK_REMOTE,
        BRANCH_TRACK_ALWAYS,
        BRANCH_TRACK_EXPLICIT,
+       BRANCH_TRACK_OVERRIDE
 };
 
 enum rebase_setup_type {
        AUTOREBASE_NEVER = 0,
        AUTOREBASE_LOCAL,
        AUTOREBASE_REMOTE,
-       AUTOREBASE_ALWAYS,
+       AUTOREBASE_ALWAYS
 };
 
 enum push_default_type {
-       PUSH_DEFAULT_UNSPECIFIED = -1,
        PUSH_DEFAULT_NOTHING = 0,
        PUSH_DEFAULT_MATCHING,
        PUSH_DEFAULT_TRACKING,
-       PUSH_DEFAULT_CURRENT,
+       PUSH_DEFAULT_CURRENT
 };
 
 extern enum branch_track git_branch_track;
@@ -556,11 +610,15 @@ extern enum push_default_type push_default;
 
 enum object_creation_mode {
        OBJECT_CREATION_USES_HARDLINKS = 0,
-       OBJECT_CREATION_USES_RENAMES = 1,
+       OBJECT_CREATION_USES_RENAMES = 1
 };
 
 extern enum object_creation_mode object_creation_mode;
 
+extern char *notes_ref_name;
+
+extern int grafts_replace_parents;
+
 #define GIT_REPO_VERSION 0
 extern int repository_format_version;
 extern int check_repository_format(void);
@@ -583,6 +641,9 @@ extern char *git_pathdup(const char *fmt, ...)
 /* 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 *git_path_submodule(const char *path, const char *fmt, ...)
+       __attribute__((format (printf, 2, 3)));
+
 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);
@@ -604,7 +665,6 @@ static inline void hashclr(unsigned char *hash)
 {
        memset(hash, 0, 20);
 }
-extern int is_empty_blob_sha1(const unsigned char *sha1);
 
 #define EMPTY_TREE_SHA1_HEX \
        "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
@@ -614,6 +674,12 @@ extern int is_empty_blob_sha1(const unsigned char *sha1);
 
 int git_mkstemp(char *path, size_t n, const char *template);
 
+int git_mkstemps(char *path, size_t n, const char *template, int suffix_len);
+
+/* set default permissions by passing mode arguments to open(2) */
+int git_mkstemps_mode(char *pattern, int suffix_len, int mode);
+int git_mkstemp_mode(char *pattern, int mode);
+
 /*
  * NOTE NOTE NOTE!!
  *
@@ -627,13 +693,14 @@ enum sharedrepo {
        OLD_PERM_GROUP      = 1,
        OLD_PERM_EVERYBODY  = 2,
        PERM_GROUP          = 0660,
-       PERM_EVERYBODY      = 0664,
+       PERM_EVERYBODY      = 0664
 };
 int git_config_perm(const char *var, const char *value);
 int set_shared_perm(const char *path, int mode);
 #define adjust_shared_perm(path) set_shared_perm((path), 0)
 int safe_create_leading_directories(char *path);
 int safe_create_leading_directories_const(const char *path);
+extern char *expand_user_path(const char *path);
 char *enter_repo(char *path, int strict);
 static inline int is_absolute_path(const char *path)
 {
@@ -646,12 +713,18 @@ const char *make_relative_path(const char *abs, const char *base);
 int normalize_path_copy(char *dst, const char *src);
 int longest_ancestor_length(const char *path, const char *prefix_list);
 char *strip_path_suffix(const char *path, const char *suffix);
+int daemon_avoid_alias(const char *path);
+int offset_1st_component(const char *path);
 
 /* Read and unpack a sha1 file into memory, write memory to a sha1 file */
 extern int sha1_object_info(const unsigned char *, unsigned long *);
-extern void * read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size);
+extern void *read_sha1_file_repl(const unsigned char *sha1, enum object_type *type, unsigned long *size, const unsigned char **replacement);
+static inline void *read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
+{
+       return read_sha1_file_repl(sha1, type, size, NULL);
+}
 extern int hash_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1);
-extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *return_sha1);
+extern int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *return_sha1);
 extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
 extern int force_object_loose(const unsigned char *sha1, time_t mtime);
 
@@ -666,9 +739,10 @@ extern int has_sha1_pack(const unsigned char *sha1);
 extern int has_sha1_file(const unsigned char *sha1);
 extern int has_loose_object_nonlocal(const unsigned char *sha1);
 
-extern int has_pack_file(const unsigned char *sha1);
 extern int has_pack_index(const unsigned char *sha1);
 
+extern void assert_sha1_type(const unsigned char *sha1, enum object_type expect);
+
 extern const signed char hexval_table[256];
 static inline unsigned int hexval(unsigned char c)
 {
@@ -679,8 +753,23 @@ static inline unsigned int hexval(unsigned char c)
 #define MINIMUM_ABBREV 4
 #define DEFAULT_ABBREV 7
 
+struct object_context {
+       unsigned char tree[20];
+       char path[PATH_MAX];
+       unsigned mode;
+};
+
 extern int get_sha1(const char *str, unsigned char *sha1);
-extern int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode);
+extern int get_sha1_with_mode_1(const char *str, unsigned char *sha1, unsigned *mode, int gently, const char *prefix);
+static inline int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode)
+{
+       return get_sha1_with_mode_1(str, sha1, mode, 1, NULL);
+}
+extern int get_sha1_with_context_1(const char *name, unsigned char *sha1, struct object_context *orc, int gently, const char *prefix);
+static inline int get_sha1_with_context(const char *str, unsigned char *sha1, struct object_context *orc)
+{
+       return get_sha1_with_context_1(str, sha1, orc, 1, NULL);
+}
 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);
@@ -688,6 +777,7 @@ extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *
 extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
 extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
 extern int interpret_branch_name(const char *str, struct strbuf *);
+extern int get_sha1_mb(const char *str, unsigned char *sha1);
 
 extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
 extern const char *ref_rev_parse_rules[];
@@ -719,9 +809,16 @@ enum date_mode {
 };
 
 const char *show_date(unsigned long time, int timezone, enum date_mode mode);
+const char *show_date_relative(unsigned long time, int tz,
+                              const struct timeval *now,
+                              char *timebuf,
+                              size_t timebuf_size);
 int parse_date(const char *date, char *buf, int bufsize);
+int parse_date_basic(const char *date, unsigned long *timestamp, int *offset);
 void datestamp(char *buf, int bufsize);
-unsigned long approxidate(const char *);
+#define approxidate(s) approxidate_careful((s), NULL)
+unsigned long approxidate_careful(const char *, int *);
+unsigned long approxidate_relative(const char *date, const struct timeval *now);
 enum date_mode parse_date_format(const char *format);
 
 #define IDENT_WARN_ON_NO_NAME  1
@@ -731,6 +828,8 @@ extern const char *git_author_info(int);
 extern const char *git_committer_info(int);
 extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int);
 extern const char *fmt_name(const char *name, const char *email);
+extern const char *git_editor(void);
+extern const char *git_pager(int stdout_is_tty);
 
 struct checkout {
        const char *base_dir;
@@ -742,11 +841,19 @@ struct checkout {
 };
 
 extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath);
+
+struct cache_def {
+       char path[PATH_MAX + 1];
+       int len;
+       int flags;
+       int track_flags;
+       int prefix_len_stat_func;
+};
+
 extern int has_symlink_leading_path(const char *name, int len);
+extern int threaded_has_symlink_leading_path(struct cache_def *, const char *, int);
 extern int has_symlink_or_noent_leading_path(const char *name, int len);
 extern int has_dirs_only_path(const char *name, int len, int prefix_len);
-extern void invalidate_lstat_cache(const char *name, int len);
-extern void clear_lstat_cache(void);
 extern void schedule_dir_for_removal(const char *name, int len);
 extern void remove_scheduled_dirs(void);
 
@@ -810,7 +917,7 @@ struct ref {
                REF_STATUS_REJECT_NODELETE,
                REF_STATUS_UPTODATE,
                REF_STATUS_REMOTE_REJECT,
-               REF_STATUS_EXPECTING_REPORT,
+               REF_STATUS_EXPECTING_REPORT
        } status;
        char *remote_status;
        struct ref *peer_ref; /* when renaming */
@@ -824,10 +931,10 @@ struct ref {
 extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
 
 #define CONNECT_VERBOSE       (1u << 0)
+extern char *git_getpass(const char *prompt);
 extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
 extern int finish_connect(struct child_process *conn);
 extern int path_match(const char *path, int nr, char **match);
-extern int get_ack(int fd, unsigned char *result_sha1);
 struct extra_have_objects {
        int nr, alloc;
        unsigned char (*array)[20];
@@ -835,7 +942,7 @@ struct extra_have_objects {
 extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags, struct extra_have_objects *);
 extern int server_supports(const char *feature);
 
-extern struct packed_git *parse_pack_index(unsigned char *sha1);
+extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);
 
 extern void prepare_packed_git(void);
 extern void reprepare_packed_git(void);
@@ -846,6 +953,7 @@ extern struct packed_git *find_sha1_pack(const unsigned char *sha1,
 
 extern void pack_report(void);
 extern int open_pack_index(struct packed_git *);
+extern void close_pack_index(struct packed_git *);
 extern unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned int *);
 extern void close_pack_windows(struct packed_git *);
 extern void unuse_pack(struct pack_window **);
@@ -866,18 +974,25 @@ extern int update_server_info(int);
 typedef int (*config_fn_t)(const char *, const char *, void *);
 extern int git_default_config(const char *, const char *, void *);
 extern int git_config_from_file(config_fn_t fn, const char *, void *);
+extern void git_config_push_parameter(const char *text);
+extern int git_config_parse_parameter(const char *text);
+extern int git_config_parse_environment(void);
+extern int git_config_from_parameters(config_fn_t fn, void *data);
 extern int git_config(config_fn_t fn, void *);
 extern int git_parse_ulong(const char *, unsigned long *);
 extern int git_config_int(const char *, const char *);
 extern unsigned long git_config_ulong(const char *, const char *);
 extern int git_config_bool_or_int(const char *, const char *, int *);
 extern int git_config_bool(const char *, const char *);
+extern int git_config_maybe_bool(const char *, const char *);
 extern int git_config_string(const char **, const char *, const char *);
+extern int git_config_pathname(const char **, const char *, const char *);
 extern int git_config_set(const char *, const char *);
 extern int git_config_set_multivar(const char *, const char *, const char *, int);
 extern int git_config_rename_section(const char *, const char *);
 extern const char *git_etc_gitconfig(void);
 extern int check_repository_format_version(const char *var, const char *value, void *cb);
+extern int git_env_bool(const char *, int);
 extern int git_config_system(void);
 extern int git_config_global(void);
 extern int config_error_nonbool(const char *);
@@ -886,7 +1001,11 @@ extern const char *config_exclusive_filename;
 #define MAX_GITNAME (1000)
 extern char git_default_email[MAX_GITNAME];
 extern char git_default_name[MAX_GITNAME];
+#define IDENT_NAME_GIVEN 01
+#define IDENT_MAIL_GIVEN 02
+#define IDENT_ALL_GIVEN (IDENT_NAME_GIVEN|IDENT_MAIL_GIVEN)
 extern int user_ident_explicitly_given;
+extern int user_ident_sufficiently_given(void);
 
 extern const char *git_commit_encoding;
 extern const char *git_log_output_encoding;
@@ -896,13 +1015,19 @@ extern const char *git_mailmap_file;
 extern void maybe_flush_or_die(FILE *, const char *);
 extern int copy_fd(int ifd, int ofd);
 extern int copy_file(const char *dst, const char *src, int mode);
-extern ssize_t read_in_full(int fd, void *buf, size_t count);
-extern ssize_t write_in_full(int fd, const void *buf, size_t count);
+extern int copy_file_with_time(const char *dst, const char *src, int mode);
 extern void write_or_die(int fd, const void *buf, size_t count);
 extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg);
 extern int write_or_whine_pipe(int fd, const void *buf, size_t count, const char *msg);
 extern void fsync_or_die(int fd, const char *);
 
+extern ssize_t read_in_full(int fd, void *buf, size_t count);
+extern ssize_t write_in_full(int fd, const void *buf, size_t count);
+static inline ssize_t write_str_in_full(int fd, const char *str)
+{
+       return write_in_full(fd, str, strlen(str));
+}
+
 /* pager.c */
 extern void setup_pager(void);
 extern const char *pager_program;
@@ -910,6 +1035,7 @@ extern int pager_in_use(void);
 extern int pager_use_color;
 
 extern const char *editor_program;
+extern const char *askpass_program;
 extern const char *excludes_file;
 
 /* base85 */
@@ -925,7 +1051,9 @@ extern void *alloc_object_node(void);
 extern void alloc_report(void);
 
 /* trace.c */
+__attribute__((format (printf, 1, 2)))
 extern void trace_printf(const char *format, ...);
+__attribute__((format (printf, 2, 3)))
 extern void trace_argv_printf(const char **argv, const char *format, ...);
 
 /* convert.c */
@@ -933,6 +1061,7 @@ extern void trace_argv_printf(const char **argv, const char *format, ...);
 extern int convert_to_git(const char *path, const char *src, size_t len,
                           struct strbuf *dst, enum safe_crlf checksafe);
 extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst);
+extern int renormalize_buffer(const char *path, const char *src, size_t len, struct strbuf *dst);
 
 /* add */
 /*
@@ -946,15 +1075,19 @@ extern int diff_auto_refresh_index;
 
 /* match-trees.c */
 void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int);
+void shift_tree_by(const unsigned char *, const unsigned char *, unsigned char *, const char *);
 
 /*
  * whitespace rules.
  * used by both diff and apply
  */
-#define WS_TRAILING_SPACE      01
+#define WS_BLANK_AT_EOL         01
 #define WS_SPACE_BEFORE_TAB    02
 #define WS_INDENT_WITH_NON_TAB 04
 #define WS_CR_AT_EOL           010
+#define WS_BLANK_AT_EOF        020
+#define WS_TAB_IN_INDENT       040
+#define WS_TRAILING_SPACE      (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF)
 #define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB)
 extern unsigned whitespace_rule_cfg;
 extern unsigned whitespace_rule(const char *);
@@ -962,7 +1095,7 @@ extern unsigned parse_whitespace_rule(const char *);
 extern unsigned ws_check(const char *line, int len, unsigned ws_rule);
 extern void ws_check_emit(const char *line, int len, unsigned ws_rule, FILE *stream, const char *set, const char *reset, const char *ws);
 extern char *whitespace_error_string(unsigned ws);
-extern int ws_fix_copy(char *, const char *, int, unsigned, int *);
+extern void ws_fix_copy(struct strbuf *, const char *, int, unsigned, int *);
 extern int ws_blank_line(const char *line, int len, unsigned ws_rule);
 
 /* ls-files */
@@ -971,5 +1104,16 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix);
 
 char *alias_lookup(const char *alias);
 int split_cmdline(char *cmdline, const char ***argv);
+/* Takes a negative value returned by split_cmdline */
+const char *split_cmdline_strerror(int cmdline_errno);
+
+/* git.c */
+struct startup_info {
+       int have_repository;
+};
+extern struct startup_info *startup_info;
+
+/* builtin/merge.c */
+int checkout_fast_forward(const unsigned char *from, const unsigned char *to);
 
 #endif /* CACHE_H */
diff --git a/color.c b/color.c
index 62977f4808ae339fdfe797e16b4eb28dc6abb85d..1b00554dd50d2960b4e66f229b0d2d0da8eb8c28 100644 (file)
--- a/color.c
+++ b/color.c
@@ -47,7 +47,7 @@ void color_parse_mem(const char *value, int value_len, const char *var,
 {
        const char *ptr = value;
        int len = value_len;
-       int attr = -1;
+       unsigned int attr = 0;
        int fg = -2;
        int bg = -2;
 
@@ -56,7 +56,7 @@ void color_parse_mem(const char *value, int value_len, const char *var,
                return;
        }
 
-       /* [fg [bg]] [attr] */
+       /* [fg [bg]] [attr]... */
        while (len > 0) {
                const char *word = ptr;
                int val, wordlen = 0;
@@ -85,19 +85,27 @@ void color_parse_mem(const char *value, int value_len, const char *var,
                        goto bad;
                }
                val = parse_attr(word, wordlen);
-               if (val < 0 || attr != -1)
+               if (0 <= val)
+                       attr |= (1 << val);
+               else
                        goto bad;
-               attr = val;
        }
 
-       if (attr >= 0 || fg >= 0 || bg >= 0) {
+       if (attr || fg >= 0 || bg >= 0) {
                int sep = 0;
+               int i;
 
                *dst++ = '\033';
                *dst++ = '[';
-               if (attr >= 0) {
-                       *dst++ = '0' + attr;
-                       sep++;
+
+               for (i = 0; attr; i++) {
+                       unsigned bit = (1 << i);
+                       if (!(attr & bit))
+                               continue;
+                       attr &= ~bit;
+                       if (sep++)
+                               *dst++ = ';';
+                       *dst++ = '0' + i;
                }
                if (fg >= 0) {
                        if (sep++)
@@ -138,6 +146,9 @@ int git_config_colorbool(const char *var, const char *value, int stdout_is_tty)
                        goto auto_color;
        }
 
+       if (!var)
+               return -1;
+
        /* Missing or explicit false to turn off colorization */
        if (!git_config_bool(var, value))
                return 0;
@@ -200,31 +211,3 @@ int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...)
        va_end(args);
        return r;
 }
-
-/*
- * This function splits the buffer by newlines and colors the lines individually.
- *
- * Returns 0 on success.
- */
-int color_fwrite_lines(FILE *fp, const char *color,
-               size_t count, const char *buf)
-{
-       if (!*color)
-               return fwrite(buf, count, 1, fp) != 1;
-       while (count) {
-               char *p = memchr(buf, '\n', count);
-               if (p != buf && (fputs(color, fp) < 0 ||
-                               fwrite(buf, p ? p - buf : count, 1, fp) != 1 ||
-                               fputs(GIT_COLOR_RESET, fp) < 0))
-                       return -1;
-               if (!p)
-                       return 0;
-               if (fputc('\n', fp) < 0)
-                       return -1;
-               count -= p + 1 - buf;
-               buf = p + 1;
-       }
-       return 0;
-}
-
-
diff --git a/color.h b/color.h
index 18abeb7c7dd47794d4f5fea4049b5719f03383fe..03ca0647485e8044d4131f2c61c37b4e9408cd2a 100644 (file)
--- a/color.h
+++ b/color.h
@@ -1,9 +1,26 @@
 #ifndef COLOR_H
 #define COLOR_H
 
-/* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */
-#define COLOR_MAXLEN 24
+/*  2 + (2 * num_attrs) + 8 + 1 + 8 + 'm' + NUL */
+/* "\033[1;2;4;5;7;38;5;2xx;48;5;2xxm\0" */
+/*
+ * The maximum length of ANSI color sequence we would generate:
+ * - leading ESC '['            2
+ * - attr + ';'                 2 * 8 (e.g. "1;")
+ * - fg color + ';'             9 (e.g. "38;5;2xx;")
+ * - fg color + ';'             9 (e.g. "48;5;2xx;")
+ * - terminating 'm' NUL        2
+ *
+ * The above overcounts attr (we only use 5 not 8) and one semicolon
+ * but it is close enough.
+ */
+#define COLOR_MAXLEN 40
 
+/*
+ * IMPORTANT: Due to the way these color codes are emulated on Windows,
+ * write them only using printf(), fprintf(), and fputs(). In particular,
+ * do not use puts() or write().
+ */
 #define GIT_COLOR_NORMAL       ""
 #define GIT_COLOR_RESET                "\033[m"
 #define GIT_COLOR_BOLD         "\033[1m"
 #define GIT_COLOR_BLUE         "\033[34m"
 #define GIT_COLOR_MAGENTA      "\033[35m"
 #define GIT_COLOR_CYAN         "\033[36m"
+#define GIT_COLOR_BOLD_RED     "\033[1;31m"
+#define GIT_COLOR_BOLD_GREEN   "\033[1;32m"
+#define GIT_COLOR_BOLD_YELLOW  "\033[1;33m"
+#define GIT_COLOR_BOLD_BLUE    "\033[1;34m"
+#define GIT_COLOR_BOLD_MAGENTA "\033[1;35m"
+#define GIT_COLOR_BOLD_CYAN    "\033[1;36m"
 #define GIT_COLOR_BG_RED       "\033[41m"
+#define GIT_COLOR_BG_GREEN     "\033[42m"
+#define GIT_COLOR_BG_YELLOW    "\033[43m"
+#define GIT_COLOR_BG_BLUE      "\033[44m"
+#define GIT_COLOR_BG_MAGENTA   "\033[45m"
+#define GIT_COLOR_BG_CYAN      "\033[46m"
 
 /*
  * This variable stores the value of color.ui
@@ -29,8 +57,9 @@ int git_color_default_config(const char *var, const char *value, void *cb);
 int git_config_colorbool(const char *var, const char *value, int stdout_is_tty);
 void color_parse(const char *value, const char *var, char *dst);
 void color_parse_mem(const char *value, int len, const char *var, char *dst);
+__attribute__((format (printf, 3, 4)))
 int color_fprintf(FILE *fp, const char *color, const char *fmt, ...);
+__attribute__((format (printf, 3, 4)))
 int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...);
-int color_fwrite_lines(FILE *fp, const char *color, size_t count, const char *buf);
 
 #endif /* COLOR_H */
index 60d03676bbd0dba7be79c7098d7cae553962ca98..655fa89d8a7ffe3c3823080f5af3e5e385e95440 100644 (file)
@@ -80,6 +80,7 @@ struct lline {
 /* Lines surviving in the merge result */
 struct sline {
        struct lline *lost_head, **lost_tail;
+       struct lline *next_lost;
        char *bol;
        int len;
        /* bit 0 up to (N-1) are on if the parent has this line (i.e.
@@ -121,18 +122,12 @@ static void append_lost(struct sline *sline, int n, const char *line, int 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;
+               lline = sline->next_lost;
                while (lline) {
                        if (lline->len == len &&
                            !memcmp(lline->line, line, len)) {
                                lline->parent_map |= this_mask;
+                               sline->next_lost = lline->next;
                                return;
                        }
                        lline = lline->next;
@@ -147,6 +142,7 @@ static void append_lost(struct sline *sline, int n, const char *line, int len)
        lline->line[len] = 0;
        *sline->lost_tail = lline;
        sline->lost_tail = &lline->next;
+       sline->next_lost = NULL;
 }
 
 struct combine_diff_state {
@@ -168,25 +164,28 @@ static void consume_line(void *state_, char *line, unsigned long len)
                                      &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)
+               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,
+                        *
+                        * Note that this is correct even when N == 0,
+                        * in which case the hunk removes the first
+                        * line in the file.
                         */
                        state->lost_bucket = &state->sline[state->nb];
-               else
+                       if (!state->nb)
+                               state->nb = 1;
+               } 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;
+               state->lost_bucket->next_lost = state->lost_bucket->lost_head;
                return;
        }
        if (!state->lost_bucket)
@@ -205,24 +204,23 @@ static void consume_line(void *state_, char *line, unsigned long len)
 static void combine_diff(const unsigned char *parent, unsigned int mode,
                         mmfile_t *result_file,
                         struct sline *sline, unsigned int cnt, int n,
-                        int num_parent)
+                        int num_parent, int result_deleted)
 {
        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)
+       if (result_deleted)
                return; /* result deleted */
 
        parent_file.ptr = grab_blob(parent, mode, &sz);
        parent_file.size = sz;
        memset(&xpp, 0, sizeof(xpp));
-       xpp.flags = XDF_NEED_MINIMAL;
+       xpp.flags = 0;
        memset(&xecfg, 0, sizeof(xecfg));
        memset(&state, 0, sizeof(state));
        state.nmask = nmask;
@@ -232,7 +230,7 @@ static void combine_diff(const unsigned char *parent, unsigned int mode,
        state.n = n;
 
        xdi_diff_outf(&parent_file, result_file, consume_line, &state,
-                     &xpp, &xecfg, &ecb);
+                     &xpp, &xecfg);
        free(parent_file.ptr);
 
        /* Assign line numbers for this parent.
@@ -518,19 +516,20 @@ static void show_line_to_eol(const char *line, int len, const char *reset)
 }
 
 static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
-                      int use_color)
+                      int use_color, int result_deleted)
 {
        unsigned long mark = (1UL<<num_parent);
        unsigned long no_pre_delete = (2UL<<num_parent);
        int i;
        unsigned long lno = 0;
        const char *c_frag = diff_get_color(use_color, DIFF_FRAGINFO);
+       const char *c_func = diff_get_color(use_color, DIFF_FUNCINFO);
        const char *c_new = diff_get_color(use_color, DIFF_FILE_NEW);
        const char *c_old = diff_get_color(use_color, DIFF_FILE_OLD);
        const char *c_plain = diff_get_color(use_color, DIFF_PLAIN);
        const char *c_reset = diff_get_color(use_color, DIFF_RESET);
 
-       if (!cnt)
+       if (result_deleted)
                return; /* result deleted */
 
        while (1) {
@@ -589,7 +588,9 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
                                    comment_end = i;
                        }
                        if (comment_end)
-                               putchar(' ');
+                               printf("%s%s %s%s", c_reset,
+                                                   c_plain, c_reset,
+                                                   c_func);
                        for (i = 0; i < comment_end; i++)
                                putchar(hunk_comment[i]);
                }
@@ -685,6 +686,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
 {
        struct diff_options *opt = &rev->diffopt;
        unsigned long result_size, cnt, lno;
+       int result_deleted = 0;
        char *result, *cp;
        struct sline *sline; /* survived lines */
        int mode_differs = 0;
@@ -746,7 +748,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
 
                        done = read_in_full(fd, result, len);
                        if (done < 0)
-                               die("read error '%s'", elem->path);
+                               die_errno("read error '%s'", elem->path);
                        else if (done < len)
                                die("early EOF '%s'", elem->path);
 
@@ -765,6 +767,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
                }
                else {
                deleted_file:
+                       result_deleted = 1;
                        result_size = 0;
                        elem->mode = 0;
                        result = xcalloc(1, 1);
@@ -821,7 +824,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
                        combine_diff(elem->parent[i].sha1,
                                     elem->parent[i].mode,
                                     &result_file, sline,
-                                    cnt, i, num_parent);
+                                    cnt, i, num_parent, result_deleted);
                if (elem->parent[i].mode != elem->mode)
                        mode_differs = 1;
        }
@@ -887,7 +890,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
                        dump_quoted_path("+++ ", b_prefix, elem->path,
                                         c_meta, c_reset);
                dump_sline(sline, cnt, num_parent,
-                          DIFF_OPT_TST(opt, COLOR_DIFF));
+                          DIFF_OPT_TST(opt, COLOR_DIFF), result_deleted);
        }
        free(result);
 
index fb03a2ebb5d51f46d00fad3b3f6b1794d4fdad2b..95bf18cf06b7c77789e68bd8ab0a312395fb5d86 100644 (file)
@@ -49,6 +49,7 @@ git-grep                                mainporcelain common
 git-gui                                 mainporcelain
 git-hash-object                         plumbingmanipulators
 git-help                               ancillaryinterrogators
+git-http-backend                        synchingrepositories
 git-http-fetch                          synchelpers
 git-http-push                           synchelpers
 git-imap-send                           foreignscminterface
@@ -74,6 +75,7 @@ git-mktag                               plumbingmanipulators
 git-mktree                              plumbingmanipulators
 git-mv                                  mainporcelain common
 git-name-rev                            plumbinginterrogators
+git-notes                               mainporcelain
 git-pack-objects                        plumbingmanipulators
 git-pack-redundant                      plumbinginterrogators
 git-pack-refs                           ancillarymanipulators
@@ -92,6 +94,7 @@ git-reflog                              ancillarymanipulators
 git-relink                              ancillarymanipulators
 git-remote                              ancillarymanipulators
 git-repack                              ancillarymanipulators
+git-replace                             ancillarymanipulators
 git-repo-config                         ancillarymanipulators  deprecated
 git-request-pull                        foreignscminterface
 git-rerere                              ancillaryinterrogators
index aa3b35b6a86891ac9d0628e20a6a46d506bf7700..0094ec1c9278332f736d2814c847b272788a7dd3 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -5,6 +5,7 @@
 #include "utf8.h"
 #include "diff.h"
 #include "revision.h"
+#include "notes.h"
 
 int save_commit_buffer = 1;
 
@@ -50,7 +51,6 @@ struct commit *lookup_commit(const unsigned char *sha1)
 
 static unsigned long parse_commit_date(const char *buf, const char *tail)
 {
-       unsigned long date;
        const char *dateptr;
 
        if (buf + 6 >= tail)
@@ -73,10 +73,7 @@ static unsigned long parse_commit_date(const char *buf, const char *tail)
        if (buf >= tail)
                return 0;
        /* dateptr < buf && buf[-1] == '\n', so strtoul will stop at buf-1 */
-       date = strtoul(dateptr, NULL, 10);
-       if (date == ULONG_MAX)
-               date = 0;
-       return date;
+       return strtoul(dateptr, NULL, 10);
 }
 
 static struct commit_graft **commit_graft;
@@ -136,8 +133,8 @@ struct commit_graft *read_graft_line(char *buf, int len)
        int i;
        struct commit_graft *graft = NULL;
 
-       if (buf[len-1] == '\n')
-               buf[--len] = 0;
+       while (len && isspace(buf[len-1]))
+               buf[--len] = '\0';
        if (buf[0] == '#' || buf[0] == '\0')
                return NULL;
        if ((len + 1) % 41) {
@@ -203,7 +200,7 @@ struct commit_graft *lookup_commit_graft(const unsigned char *sha1)
        return commit_graft[pos];
 }
 
-int write_shallow_commits(int fd, int use_pack_protocol)
+int write_shallow_commits(struct strbuf *out, int use_pack_protocol)
 {
        int i, count = 0;
        for (i = 0; i < commit_graft_nr; i++)
@@ -212,12 +209,10 @@ int write_shallow_commits(int fd, int use_pack_protocol)
                                sha1_to_hex(commit_graft[i]->sha1);
                        count++;
                        if (use_pack_protocol)
-                               packet_write(fd, "shallow %s", hex);
+                               packet_buf_write(out, "shallow %s", hex);
                        else {
-                               if (write_in_full(fd, hex,  40) != 40)
-                                       break;
-                               if (write_in_full(fd, "\n", 1) != 1)
-                                       break;
+                               strbuf_addstr(out, hex);
+                               strbuf_addch(out, '\n');
                        }
                }
        return count;
@@ -229,7 +224,7 @@ int unregister_shallow(const unsigned char *sha1)
        if (pos < 0)
                return -1;
        if (pos + 1 < commit_graft_nr)
-               memcpy(commit_graft + pos, commit_graft + pos + 1,
+               memmove(commit_graft + pos, commit_graft + pos + 1,
                                sizeof(struct commit_graft *)
                                * (commit_graft_nr - pos - 1));
        commit_graft_nr--;
@@ -266,7 +261,11 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)
                    bufptr[47] != '\n')
                        return error("bad parents in commit %s", sha1_to_hex(item->object.sha1));
                bufptr += 48;
-               if (graft)
+               /*
+                * The clone is shallow if nr_parent < 0, and we must
+                * not traverse its real parents even when we unhide them.
+                */
+               if (graft && (graft->nr_parent < 0 || grafts_replace_parents))
                        continue;
                new_parent = lookup_commit(parent);
                if (new_parent)
@@ -316,6 +315,25 @@ int parse_commit(struct commit *item)
        return ret;
 }
 
+int find_commit_subject(const char *commit_buffer, const char **subject)
+{
+       const char *eol;
+       const char *p = commit_buffer;
+
+       while (*p && (*p != '\n' || p[1] != '\n'))
+               p++;
+       if (*p) {
+               p += 2;
+               for (eol = p; *eol && *eol != '\n'; eol++)
+                       ; /* do nothing */
+       } else
+               eol = p;
+
+       *subject = p;
+
+       return eol - p;
+}
+
 struct commit_list *commit_list_insert(struct commit *item, struct commit_list **list_p)
 {
        struct commit_list *new_list = xmalloc(sizeof(struct commit_list));
@@ -564,13 +582,13 @@ static struct commit_list *merge_bases_many(struct commit *one, int n, struct co
        while (interesting(list)) {
                struct commit *commit;
                struct commit_list *parents;
-               struct commit_list *n;
+               struct commit_list *next;
                int flags;
 
                commit = list->item;
-               n = list->next;
+               next = list->next;
                free(list);
-               list = n;
+               list = next;
 
                flags = commit->object.flags & (PARENT1 | PARENT2 | STALE);
                if (flags == (PARENT1 | PARENT2)) {
@@ -598,11 +616,11 @@ static struct commit_list *merge_bases_many(struct commit *one, int n, struct co
        free_commit_list(list);
        list = result; result = NULL;
        while (list) {
-               struct commit_list *n = list->next;
+               struct commit_list *next = list->next;
                if (!(list->item->object.flags & STALE))
                        insert_by_date(list->item, &result);
                free(list);
-               list = n;
+               list = next;
        }
        return result;
 }
@@ -791,3 +809,58 @@ struct commit_list *reduce_heads(struct commit_list *heads)
        free(other);
        return result;
 }
+
+static const char commit_utf8_warn[] =
+"Warning: commit message does not conform to UTF-8.\n"
+"You may want to amend it after fixing the message, or set the config\n"
+"variable i18n.commitencoding to the encoding your project uses.\n";
+
+int commit_tree(const char *msg, unsigned char *tree,
+               struct commit_list *parents, unsigned char *ret,
+               const char *author)
+{
+       int result;
+       int encoding_is_utf8;
+       struct strbuf buffer;
+
+       assert_sha1_type(tree, OBJ_TREE);
+
+       /* Not having i18n.commitencoding is the same as having utf-8 */
+       encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
+
+       strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */
+       strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree));
+
+       /*
+        * 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.
+        */
+       while (parents) {
+               struct commit_list *next = parents->next;
+               strbuf_addf(&buffer, "parent %s\n",
+                       sha1_to_hex(parents->item->object.sha1));
+               free(parents);
+               parents = next;
+       }
+
+       /* Person/date information */
+       if (!author)
+               author = git_author_info(IDENT_ERROR_ON_NO_NAME);
+       strbuf_addf(&buffer, "author %s\n", author);
+       strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME));
+       if (!encoding_is_utf8)
+               strbuf_addf(&buffer, "encoding %s\n", git_commit_encoding);
+       strbuf_addch(&buffer, '\n');
+
+       /* And add the comment */
+       strbuf_addstr(&buffer, msg);
+
+       /* And check the encoding */
+       if (encoding_is_utf8 && !is_utf8(buffer.buf))
+               fprintf(stderr, commit_utf8_warn);
+
+       result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
+       strbuf_release(&buffer);
+       return result;
+}
index ba9f63813eba004ae409eba8741266a074161239..9113bbe4889d71e824348edcb920110598db18d2 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -28,6 +28,7 @@ extern const char *commit_type;
 extern struct decoration name_decoration;
 struct name_decoration {
        struct name_decoration *next;
+       int type;
        char name[1];
 };
 
@@ -40,6 +41,9 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size);
 
 int parse_commit(struct commit *item);
 
+/* Find beginning and length of commit subject. */
+int find_commit_subject(const char *commit_buffer, const char **subject);
+
 struct commit_list * commit_list_insert(struct commit *item, struct commit_list **list_p);
 unsigned commit_list_count(const struct commit_list *l);
 struct commit_list * insert_by_date(struct commit *item, struct commit_list **list);
@@ -60,22 +64,36 @@ enum cmit_fmt {
        CMIT_FMT_EMAIL,
        CMIT_FMT_USERFORMAT,
 
-       CMIT_FMT_UNSPECIFIED,
+       CMIT_FMT_UNSPECIFIED
+};
+
+struct pretty_print_context
+{
+       int abbrev;
+       const char *subject;
+       const char *after_subject;
+       enum date_mode date_mode;
+       int need_8bit_cte;
+       int show_notes;
+       struct reflog_walk_info *reflog_info;
+};
+
+struct userformat_want {
+       unsigned notes:1;
 };
 
-extern int non_ascii(int);
+extern int has_non_ascii(const char *text);
 struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
 extern char *reencode_commit_message(const struct commit *commit,
                                     const char **encoding_p);
 extern void get_commit_format(const char *arg, struct rev_info *);
+extern void userformat_find_requirements(const char *fmt, struct userformat_want *w);
 extern void format_commit_message(const struct commit *commit,
-                                 const void *format, struct strbuf *sb,
-                                 enum date_mode dmode);
-extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit*,
-                                struct strbuf *,
-                                int abbrev, const char *subject,
-                                const char *after_subject, enum date_mode,
-                               int need_8bit_cte);
+                                 const char *format, struct strbuf *sb,
+                                 const struct pretty_print_context *context);
+extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
+                               struct strbuf *sb,
+                               const struct pretty_print_context *context);
 void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
                   const char *line, enum date_mode dmode,
                   const char *encoding);
@@ -122,13 +140,15 @@ struct commit_graft *read_graft_line(char *buf, int len);
 int register_commit_graft(struct commit_graft *, int);
 struct commit_graft *lookup_commit_graft(const unsigned char *sha1);
 
+const unsigned char *lookup_replace_object(const unsigned char *sha1);
+
 extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup);
 extern struct commit_list *get_merge_bases_many(struct commit *one, int n, struct commit **twos, int cleanup);
 extern struct commit_list *get_octopus_merge_bases(struct commit_list *in);
 
 extern int register_shallow(const unsigned char *sha1);
 extern int unregister_shallow(const unsigned char *sha1);
-extern int write_shallow_commits(int fd, int use_pack_protocol);
+extern int write_shallow_commits(struct strbuf *out, int use_pack_protocol);
 extern int is_repository_shallow(void);
 extern struct commit_list *get_shallow_commits(struct object_array *heads,
                int depth, int shallow_flag, int not_shallow_flag);
@@ -137,6 +157,8 @@ int is_descendant_of(struct commit *, struct commit_list *);
 int in_merge_bases(struct commit *, struct commit **, int);
 
 extern int interactive_add(int argc, const char **argv, const char *prefix);
+extern int run_add_interactive(const char *revision, const char *patch_mode,
+                              const char **pathspec);
 
 static inline int single_parent(struct commit *commit)
 {
@@ -145,4 +167,8 @@ static inline int single_parent(struct commit *commit)
 
 struct commit_list *reduce_heads(struct commit_list *heads);
 
+extern int commit_tree(const char *msg, unsigned char *tree,
+               struct commit_list *parents, unsigned char *ret,
+               const char *author);
+
 #endif /* COMMIT_H */
diff --git a/compat/basename.c b/compat/basename.c
new file mode 100644 (file)
index 0000000..d8f8a3c
--- /dev/null
@@ -0,0 +1,15 @@
+#include "../git-compat-util.h"
+
+/* Adapted from libiberty's basename.c.  */
+char *gitbasename (char *path)
+{
+       const char *base;
+       /* Skip over the disk name in MSDOS pathnames. */
+       if (has_dos_drive_prefix(path))
+               path += 2;
+       for (base = path; *path; path++) {
+               if (is_dir_sep(*path))
+                       base = path + 1;
+       }
+       return (char *)base;
+}
diff --git a/compat/bswap.h b/compat/bswap.h
new file mode 100644 (file)
index 0000000..54756db
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Let's make sure we always have a sane definition for ntohl()/htonl().
+ * Some libraries define those as a function call, just to perform byte
+ * shifting, bringing significant overhead to what should be a simple
+ * operation.
+ */
+
+/*
+ * Default version that the compiler ought to optimize properly with
+ * constant values.
+ */
+static inline uint32_t default_swab32(uint32_t val)
+{
+       return (((val & 0xff000000) >> 24) |
+               ((val & 0x00ff0000) >>  8) |
+               ((val & 0x0000ff00) <<  8) |
+               ((val & 0x000000ff) << 24));
+}
+
+#undef bswap32
+
+#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
+
+#define bswap32(x) ({ \
+       uint32_t __res; \
+       if (__builtin_constant_p(x)) { \
+               __res = default_swab32(x); \
+       } else { \
+               __asm__("bswap %0" : "=r" (__res) : "0" ((uint32_t)(x))); \
+       } \
+       __res; })
+
+#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
+
+#include <stdlib.h>
+
+#define bswap32(x) _byteswap_ulong(x)
+
+#endif
+
+#ifdef bswap32
+
+#undef ntohl
+#undef htonl
+#define ntohl(x) bswap32(x)
+#define htonl(x) bswap32(x)
+
+#endif
index cdeda1d9859fd545950e0c39ee7de3c9dc09bb07..f2d9e1fd974b7271366da09370e10fafd2c50f08 100644 (file)
@@ -1,10 +1,9 @@
 #include "../git-compat-util.h"
 #include "win32.h"
+#include <conio.h>
 #include "../strbuf.h"
 
-unsigned int _CRT_fmode = _O_BINARY;
-
-static int err_win_to_posix(DWORD winerr)
+int err_win_to_posix(DWORD winerr)
 {
        int error = ENOSYS;
        switch(winerr) {
@@ -122,13 +121,17 @@ int mingw_open (const char *filename, int oflags, ...)
 {
        va_list args;
        unsigned mode;
+       int fd;
+
        va_start(args, oflags);
        mode = va_arg(args, int);
        va_end(args);
 
        if (!strcmp(filename, "/dev/null"))
                filename = "nul";
-       int fd = open(filename, oflags, mode);
+
+       fd = open(filename, oflags, mode);
+
        if (fd < 0 && (oflags & O_CREAT) && errno == EACCES) {
                DWORD attrs = GetFileAttributes(filename);
                if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY))
@@ -137,12 +140,53 @@ int mingw_open (const char *filename, int oflags, ...)
        return fd;
 }
 
-static inline time_t filetime_to_time_t(const FILETIME *ft)
+#undef write
+ssize_t mingw_write(int fd, const void *buf, size_t count)
+{
+       /*
+        * While write() calls to a file on a local disk are translated
+        * into WriteFile() calls with a maximum size of 64KB on Windows
+        * XP and 256KB on Vista, no such cap is placed on writes to
+        * files over the network on Windows XP.  Unfortunately, there
+        * seems to be a limit of 32MB-28KB on X64 and 64MB-32KB on x86;
+        * bigger writes fail on Windows XP.
+        * So we cap to a nice 31MB here to avoid write failures over
+        * the net without changing the number of WriteFile() calls in
+        * the local case.
+        */
+       return write(fd, buf, min(count, 31 * 1024 * 1024));
+}
+
+#undef fopen
+FILE *mingw_fopen (const char *filename, const char *otype)
+{
+       if (!strcmp(filename, "/dev/null"))
+               filename = "nul";
+       return fopen(filename, otype);
+}
+
+#undef freopen
+FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
+{
+       if (filename && !strcmp(filename, "/dev/null"))
+               filename = "nul";
+       return freopen(filename, otype, stream);
+}
+
+/*
+ * The unit of FILETIME is 100-nanoseconds since January 1, 1601, UTC.
+ * Returns the 100-nanoseconds ("hekto nanoseconds") since the epoch.
+ */
+static inline long long filetime_to_hnsec(const FILETIME *ft)
 {
        long long winTime = ((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime;
-       winTime -= 116444736000000000LL; /* Windows to Unix Epoch conversion */
-       winTime /= 10000000;             /* Nano to seconds resolution */
-       return (time_t)winTime;
+       /* Windows to Unix Epoch conversion */
+       return winTime - 116444736000000000LL;
+}
+
+static inline time_t filetime_to_time_t(const FILETIME *ft)
+{
+       return (time_t)(filetime_to_hnsec(ft) / 10000000);
 }
 
 /* We keep the do_lstat code in a separate function to avoid recursion.
@@ -248,17 +292,38 @@ int mingw_utime (const char *file_name, const struct utimbuf *times)
        int fh, rc;
 
        /* must have write permission */
-       if ((fh = open(file_name, O_RDWR | O_BINARY)) < 0)
-               return -1;
+       DWORD attrs = GetFileAttributes(file_name);
+       if (attrs != INVALID_FILE_ATTRIBUTES &&
+           (attrs & FILE_ATTRIBUTE_READONLY)) {
+               /* ignore errors here; open() will report them */
+               SetFileAttributes(file_name, attrs & ~FILE_ATTRIBUTE_READONLY);
+       }
 
-       time_t_to_filetime(times->modtime, &mft);
-       time_t_to_filetime(times->actime, &aft);
+       if ((fh = open(file_name, O_RDWR | O_BINARY)) < 0) {
+               rc = -1;
+               goto revert_attrs;
+       }
+
+       if (times) {
+               time_t_to_filetime(times->modtime, &mft);
+               time_t_to_filetime(times->actime, &aft);
+       } else {
+               GetSystemTimeAsFileTime(&mft);
+               aft = mft;
+       }
        if (!SetFileTime((HANDLE)_get_osfhandle(fh), NULL, &aft, &mft)) {
                errno = EINVAL;
                rc = -1;
        } else
                rc = 0;
        close(fh);
+
+revert_attrs:
+       if (attrs != INVALID_FILE_ATTRIBUTES &&
+           (attrs & FILE_ATTRIBUTE_READONLY)) {
+               /* ignore errors again */
+               SetFileAttributes(file_name, attrs);
+       }
        return rc;
 }
 
@@ -278,64 +343,37 @@ int mkstemp(char *template)
 
 int gettimeofday(struct timeval *tv, void *tz)
 {
-       SYSTEMTIME st;
-       struct tm tm;
-       GetSystemTime(&st);
-       tm.tm_year = st.wYear-1900;
-       tm.tm_mon = st.wMonth-1;
-       tm.tm_mday = st.wDay;
-       tm.tm_hour = st.wHour;
-       tm.tm_min = st.wMinute;
-       tm.tm_sec = st.wSecond;
-       tv->tv_sec = tm_to_time_t(&tm);
-       if (tv->tv_sec < 0)
-               return -1;
-       tv->tv_usec = st.wMilliseconds*1000;
+       FILETIME ft;
+       long long hnsec;
+
+       GetSystemTimeAsFileTime(&ft);
+       hnsec = filetime_to_hnsec(&ft);
+       tv->tv_sec = hnsec / 10000000;
+       tv->tv_usec = (hnsec % 10000000) / 10;
        return 0;
 }
 
 int pipe(int filedes[2])
 {
-       int fd;
-       HANDLE h[2], parent;
-
-       if (_pipe(filedes, 8192, 0) < 0)
-               return -1;
+       HANDLE h[2];
 
-       parent = GetCurrentProcess();
-
-       if (!DuplicateHandle (parent, (HANDLE)_get_osfhandle(filedes[0]),
-                       parent, &h[0], 0, FALSE, DUPLICATE_SAME_ACCESS)) {
-               close(filedes[0]);
-               close(filedes[1]);
+       /* this creates non-inheritable handles */
+       if (!CreatePipe(&h[0], &h[1], NULL, 8192)) {
+               errno = err_win_to_posix(GetLastError());
                return -1;
        }
-       if (!DuplicateHandle (parent, (HANDLE)_get_osfhandle(filedes[1]),
-                       parent, &h[1], 0, FALSE, DUPLICATE_SAME_ACCESS)) {
-               close(filedes[0]);
-               close(filedes[1]);
-               CloseHandle(h[0]);
-               return -1;
-       }
-       fd = _open_osfhandle((int)h[0], O_NOINHERIT);
-       if (fd < 0) {
-               close(filedes[0]);
-               close(filedes[1]);
+       filedes[0] = _open_osfhandle((int)h[0], O_NOINHERIT);
+       if (filedes[0] < 0) {
                CloseHandle(h[0]);
                CloseHandle(h[1]);
                return -1;
        }
-       close(filedes[0]);
-       filedes[0] = fd;
-       fd = _open_osfhandle((int)h[1], O_NOINHERIT);
-       if (fd < 0) {
+       filedes[1] = _open_osfhandle((int)h[1], O_NOINHERIT);
+       if (filedes[0] < 0) {
                close(filedes[0]);
-               close(filedes[1]);
                CloseHandle(h[1]);
                return -1;
        }
-       close(filedes[1]);
-       filedes[1] = fd;
        return 0;
 }
 
@@ -525,8 +563,8 @@ static const char *parse_interpreter(const char *cmd)
        if (buf[0] != '#' || buf[1] != '!')
                return NULL;
        buf[n] = '\0';
-       p = strchr(buf, '\n');
-       if (!p)
+       p = buf + strcspn(buf, "\r\n");
+       if (!*p)
                return NULL;
 
        *p = '\0';
@@ -579,10 +617,11 @@ static char **get_path_split(void)
 
 static void free_path_split(char **path)
 {
+       char **p = path;
+
        if (!path)
                return;
 
-       char **p = path;
        while (*p)
                free(*p++);
        free(path);
@@ -607,7 +646,7 @@ static char *lookup_prog(const char *dir, const char *cmd, int isexe, int exe_on
 }
 
 /*
- * Determines the absolute path of cmd using the the split path in path.
+ * Determines the absolute path of cmd using the split path in path.
  * If cmd contains a slash or backslash, no lookup is performed.
  */
 static char *path_lookup(const char *cmd, char **path, int exe_only)
@@ -632,8 +671,9 @@ static int env_compare(const void *a, const void *b)
        return strcasecmp(*ea, *eb);
 }
 
-static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env,
-                          int prepend_cmd)
+static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env,
+                             const char *dir,
+                             int prepend_cmd, int fhin, int fhout, int fherr)
 {
        STARTUPINFO si;
        PROCESS_INFORMATION pi;
@@ -669,9 +709,9 @@ static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env,
        memset(&si, 0, sizeof(si));
        si.cb = sizeof(si);
        si.dwFlags = STARTF_USESTDHANDLES;
-       si.hStdInput = (HANDLE) _get_osfhandle(0);
-       si.hStdOutput = (HANDLE) _get_osfhandle(1);
-       si.hStdError = (HANDLE) _get_osfhandle(2);
+       si.hStdInput = (HANDLE) _get_osfhandle(fhin);
+       si.hStdOutput = (HANDLE) _get_osfhandle(fhout);
+       si.hStdError = (HANDLE) _get_osfhandle(fherr);
 
        /* concatenate argv, quoting args as we go */
        strbuf_init(&args, 0);
@@ -712,7 +752,7 @@ static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env,
 
        memset(&pi, 0, sizeof(pi));
        ret = CreateProcess(cmd, args.buf, NULL, NULL, TRUE, flags,
-               env ? envblk.buf : NULL, NULL, &si, &pi);
+               env ? envblk.buf : NULL, dir, &si, &pi);
 
        if (env)
                strbuf_release(&envblk);
@@ -726,7 +766,15 @@ static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env,
        return (pid_t)pi.hProcess;
 }
 
-pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env)
+static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env,
+                          int prepend_cmd)
+{
+       return mingw_spawnve_fd(cmd, argv, env, NULL, prepend_cmd, 0, 1, 2);
+}
+
+pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env,
+                    const char *dir,
+                    int fhin, int fhout, int fherr)
 {
        pid_t pid;
        char **path = get_path_split();
@@ -748,13 +796,15 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env)
                                pid = -1;
                        }
                        else {
-                               pid = mingw_spawnve(iprog, argv, env, 1);
+                               pid = mingw_spawnve_fd(iprog, argv, env, dir, 1,
+                                                      fhin, fhout, fherr);
                                free(iprog);
                        }
                        argv[0] = argv0;
                }
                else
-                       pid = mingw_spawnve(prog, argv, env, 0);
+                       pid = mingw_spawnve_fd(prog, argv, env, dir, 0,
+                                              fhin, fhout, fherr);
                free(prog);
        }
        free_path_split(path);
@@ -823,7 +873,7 @@ void mingw_execvp(const char *cmd, char *const *argv)
        free_path_split(path);
 }
 
-char **copy_environ()
+static char **copy_environ(void)
 {
        char **env;
        int i = 0;
@@ -860,7 +910,7 @@ static int lookup_env(char **env, const char *name, size_t nmln)
 /*
  * If name contains '=', then sets the variable, otherwise it unsets it
  */
-char **env_setenv(char **env, const char *name)
+static char **env_setenv(char **env, const char *name)
 {
        char *eq = strchrnul(name, '=');
        int i = lookup_env(env, name, eq-name);
@@ -885,19 +935,207 @@ char **env_setenv(char **env, const char *name)
        return env;
 }
 
-/* this is the first function to call into WS_32; initialize it */
-#undef gethostbyname
-struct hostent *mingw_gethostbyname(const char *host)
+/*
+ * Copies global environ and adjusts variables as specified by vars.
+ */
+char **make_augmented_environ(const char *const *vars)
+{
+       char **env = copy_environ();
+
+       while (*vars)
+               env = env_setenv(env, *vars++);
+       return env;
+}
+
+/*
+ * Note, this isn't a complete replacement for getaddrinfo. It assumes
+ * that service contains a numerical port, or that it it is null. It
+ * does a simple search using gethostbyname, and returns one IPv4 host
+ * if one was found.
+ */
+static int WSAAPI getaddrinfo_stub(const char *node, const char *service,
+                                  const struct addrinfo *hints,
+                                  struct addrinfo **res)
+{
+       struct hostent *h = gethostbyname(node);
+       struct addrinfo *ai;
+       struct sockaddr_in *sin;
+
+       if (!h)
+               return WSAGetLastError();
+
+       ai = xmalloc(sizeof(struct addrinfo));
+       *res = ai;
+       ai->ai_flags = 0;
+       ai->ai_family = AF_INET;
+       ai->ai_socktype = hints->ai_socktype;
+       switch (hints->ai_socktype) {
+       case SOCK_STREAM:
+               ai->ai_protocol = IPPROTO_TCP;
+               break;
+       case SOCK_DGRAM:
+               ai->ai_protocol = IPPROTO_UDP;
+               break;
+       default:
+               ai->ai_protocol = 0;
+               break;
+       }
+       ai->ai_addrlen = sizeof(struct sockaddr_in);
+       ai->ai_canonname = strdup(h->h_name);
+
+       sin = xmalloc(ai->ai_addrlen);
+       memset(sin, 0, ai->ai_addrlen);
+       sin->sin_family = AF_INET;
+       if (service)
+               sin->sin_port = htons(atoi(service));
+       sin->sin_addr = *(struct in_addr *)h->h_addr;
+       ai->ai_addr = (struct sockaddr *)sin;
+       ai->ai_next = 0;
+       return 0;
+}
+
+static void WSAAPI freeaddrinfo_stub(struct addrinfo *res)
+{
+       free(res->ai_canonname);
+       free(res->ai_addr);
+       free(res);
+}
+
+static int WSAAPI getnameinfo_stub(const struct sockaddr *sa, socklen_t salen,
+                                  char *host, DWORD hostlen,
+                                  char *serv, DWORD servlen, int flags)
+{
+       const struct sockaddr_in *sin = (const struct sockaddr_in *)sa;
+       if (sa->sa_family != AF_INET)
+               return EAI_FAMILY;
+       if (!host && !serv)
+               return EAI_NONAME;
+
+       if (host && hostlen > 0) {
+               struct hostent *ent = NULL;
+               if (!(flags & NI_NUMERICHOST))
+                       ent = gethostbyaddr((const char *)&sin->sin_addr,
+                                           sizeof(sin->sin_addr), AF_INET);
+
+               if (ent)
+                       snprintf(host, hostlen, "%s", ent->h_name);
+               else if (flags & NI_NAMEREQD)
+                       return EAI_NONAME;
+               else
+                       snprintf(host, hostlen, "%s", inet_ntoa(sin->sin_addr));
+       }
+
+       if (serv && servlen > 0) {
+               struct servent *ent = NULL;
+               if (!(flags & NI_NUMERICSERV))
+                       ent = getservbyport(sin->sin_port,
+                                           flags & NI_DGRAM ? "udp" : "tcp");
+
+               if (ent)
+                       snprintf(serv, servlen, "%s", ent->s_name);
+               else
+                       snprintf(serv, servlen, "%d", ntohs(sin->sin_port));
+       }
+
+       return 0;
+}
+
+static HMODULE ipv6_dll = NULL;
+static void (WSAAPI *ipv6_freeaddrinfo)(struct addrinfo *res);
+static int (WSAAPI *ipv6_getaddrinfo)(const char *node, const char *service,
+                                     const struct addrinfo *hints,
+                                     struct addrinfo **res);
+static int (WSAAPI *ipv6_getnameinfo)(const struct sockaddr *sa, socklen_t salen,
+                                     char *host, DWORD hostlen,
+                                     char *serv, DWORD servlen, int flags);
+/*
+ * gai_strerror is an inline function in the ws2tcpip.h header, so we
+ * don't need to try to load that one dynamically.
+ */
+
+static void socket_cleanup(void)
+{
+       WSACleanup();
+       if (ipv6_dll)
+               FreeLibrary(ipv6_dll);
+       ipv6_dll = NULL;
+       ipv6_freeaddrinfo = freeaddrinfo_stub;
+       ipv6_getaddrinfo = getaddrinfo_stub;
+       ipv6_getnameinfo = getnameinfo_stub;
+}
+
+static void ensure_socket_initialization(void)
 {
        WSADATA wsa;
+       static int initialized = 0;
+       const char *libraries[] = { "ws2_32.dll", "wship6.dll", NULL };
+       const char **name;
+
+       if (initialized)
+               return;
 
        if (WSAStartup(MAKEWORD(2,2), &wsa))
                die("unable to initialize winsock subsystem, error %d",
                        WSAGetLastError());
-       atexit((void(*)(void)) WSACleanup);
+
+       for (name = libraries; *name; name++) {
+               ipv6_dll = LoadLibrary(*name);
+               if (!ipv6_dll)
+                       continue;
+
+               ipv6_freeaddrinfo = (void (WSAAPI *)(struct addrinfo *))
+                       GetProcAddress(ipv6_dll, "freeaddrinfo");
+               ipv6_getaddrinfo = (int (WSAAPI *)(const char *, const char *,
+                                                  const struct addrinfo *,
+                                                  struct addrinfo **))
+                       GetProcAddress(ipv6_dll, "getaddrinfo");
+               ipv6_getnameinfo = (int (WSAAPI *)(const struct sockaddr *,
+                                                  socklen_t, char *, DWORD,
+                                                  char *, DWORD, int))
+                       GetProcAddress(ipv6_dll, "getnameinfo");
+               if (!ipv6_freeaddrinfo || !ipv6_getaddrinfo || !ipv6_getnameinfo) {
+                       FreeLibrary(ipv6_dll);
+                       ipv6_dll = NULL;
+               } else
+                       break;
+       }
+       if (!ipv6_freeaddrinfo || !ipv6_getaddrinfo || !ipv6_getnameinfo) {
+               ipv6_freeaddrinfo = freeaddrinfo_stub;
+               ipv6_getaddrinfo = getaddrinfo_stub;
+               ipv6_getnameinfo = getnameinfo_stub;
+       }
+
+       atexit(socket_cleanup);
+       initialized = 1;
+}
+
+#undef gethostbyname
+struct hostent *mingw_gethostbyname(const char *host)
+{
+       ensure_socket_initialization();
        return gethostbyname(host);
 }
 
+void mingw_freeaddrinfo(struct addrinfo *res)
+{
+       ipv6_freeaddrinfo(res);
+}
+
+int mingw_getaddrinfo(const char *node, const char *service,
+                     const struct addrinfo *hints, struct addrinfo **res)
+{
+       ensure_socket_initialization();
+       return ipv6_getaddrinfo(node, service, hints, res);
+}
+
+int mingw_getnameinfo(const struct sockaddr *sa, socklen_t salen,
+                     char *host, DWORD hostlen, char *serv, DWORD servlen,
+                     int flags)
+{
+       ensure_socket_initialization();
+       return ipv6_getnameinfo(sa, salen, host, hostlen, serv, servlen, flags);
+}
+
 int mingw_socket(int domain, int type, int protocol)
 {
        int sockfd;
@@ -982,6 +1220,18 @@ repeat:
        return -1;
 }
 
+/*
+ * Note that this doesn't return the actual pagesize, but
+ * the allocation granularity. If future Windows specific git code
+ * needs the real getpagesize function, we need to find another solution.
+ */
+int mingw_getpagesize(void)
+{
+       SYSTEM_INFO si;
+       GetSystemInfo(&si);
+       return si.dwAllocationGranularity;
+}
+
 struct passwd *getpwuid(int uid)
 {
        static char user_name[100];
@@ -1011,7 +1261,7 @@ static sig_handler_t timer_fn = SIG_DFL;
  * length to call the signal handler.
  */
 
-static __stdcall unsigned ticktack(void *dummy)
+static unsigned __stdcall ticktack(void *dummy)
 {
        while (WaitForSingleObject(timer_event, timer_interval) == WAIT_TIMEOUT) {
                if (timer_fn == SIG_DFL)
@@ -1107,9 +1357,9 @@ int sigaction(int sig, struct sigaction *in, struct sigaction *out)
 #undef signal
 sig_handler_t mingw_signal(int sig, sig_handler_t handler)
 {
+       sig_handler_t old = timer_fn;
        if (sig != SIGALRM)
                return signal(sig, handler);
-       sig_handler_t old = timer_fn;
        timer_fn = handler;
        return old;
 }
@@ -1132,13 +1382,27 @@ static const char *make_backslash_path(const char *path)
 void mingw_open_html(const char *unixpath)
 {
        const char *htmlpath = make_backslash_path(unixpath);
+       typedef HINSTANCE (WINAPI *T)(HWND, const char *,
+                       const char *, const char *, const char *, INT);
+       T ShellExecute;
+       HMODULE shell32;
+
+       shell32 = LoadLibrary("shell32.dll");
+       if (!shell32)
+               die("cannot load shell32.dll");
+       ShellExecute = (T)GetProcAddress(shell32, "ShellExecuteA");
+       if (!ShellExecute)
+               die("cannot run browser");
+
        printf("Launching default browser to display HTML ...\n");
        ShellExecute(NULL, "open", htmlpath, NULL, "\\", 0);
+
+       FreeLibrary(shell32);
 }
 
 int link(const char *oldpath, const char *newpath)
 {
-       typedef BOOL WINAPI (*T)(const char*, const char*, LPSECURITY_ATTRIBUTES);
+       typedef BOOL (WINAPI *T)(const char*, const char*, LPSECURITY_ATTRIBUTES);
        static T create_hard_link = NULL;
        if (!create_hard_link) {
                create_hard_link = (T) GetProcAddress(
@@ -1156,3 +1420,78 @@ int link(const char *oldpath, const char *newpath)
        }
        return 0;
 }
+
+char *getpass(const char *prompt)
+{
+       struct strbuf buf = STRBUF_INIT;
+
+       fputs(prompt, stderr);
+       for (;;) {
+               char c = _getch();
+               if (c == '\r' || c == '\n')
+                       break;
+               strbuf_addch(&buf, c);
+       }
+       fputs("\n", stderr);
+       return strbuf_detach(&buf, NULL);
+}
+
+#ifndef NO_MINGW_REPLACE_READDIR
+/* MinGW readdir implementation to avoid extra lstats for Git */
+struct mingw_DIR
+{
+       struct _finddata_t      dd_dta;         /* disk transfer area for this dir */
+       struct mingw_dirent     dd_dir;         /* Our own implementation, including d_type */
+       long                    dd_handle;      /* _findnext handle */
+       int                     dd_stat;        /* 0 = next entry to read is first entry, -1 = off the end, positive = 0 based index of next entry */
+       char                    dd_name[1];     /* given path for dir with search pattern (struct is extended) */
+};
+
+struct dirent *mingw_readdir(DIR *dir)
+{
+       WIN32_FIND_DATAA buf;
+       HANDLE handle;
+       struct mingw_DIR *mdir = (struct mingw_DIR*)dir;
+
+       if (!dir->dd_handle) {
+               errno = EBADF; /* No set_errno for mingw */
+               return NULL;
+       }
+
+       if (dir->dd_handle == (long)INVALID_HANDLE_VALUE && dir->dd_stat == 0)
+       {
+               DWORD lasterr;
+               handle = FindFirstFileA(dir->dd_name, &buf);
+               lasterr = GetLastError();
+               dir->dd_handle = (long)handle;
+               if (handle == INVALID_HANDLE_VALUE && (lasterr != ERROR_NO_MORE_FILES)) {
+                       errno = err_win_to_posix(lasterr);
+                       return NULL;
+               }
+       } else if (dir->dd_handle == (long)INVALID_HANDLE_VALUE) {
+               return NULL;
+       } else if (!FindNextFileA((HANDLE)dir->dd_handle, &buf)) {
+               DWORD lasterr = GetLastError();
+               FindClose((HANDLE)dir->dd_handle);
+               dir->dd_handle = (long)INVALID_HANDLE_VALUE;
+               /* POSIX says you shouldn't set errno when readdir can't
+                  find any more files; so, if another error we leave it set. */
+               if (lasterr != ERROR_NO_MORE_FILES)
+                       errno = err_win_to_posix(lasterr);
+               return NULL;
+       }
+
+       /* We get here if `buf' contains valid data.  */
+       strcpy(dir->dd_dir.d_name, buf.cFileName);
+       ++dir->dd_stat;
+
+       /* Set file type, based on WIN32_FIND_DATA */
+       mdir->dd_dir.d_type = 0;
+       if (buf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+               mdir->dd_dir.d_type |= DT_DIR;
+       else
+               mdir->dd_dir.d_type |= DT_REG;
+
+       return (struct dirent*)&dir->dd_dir;
+}
+#endif // !NO_MINGW_REPLACE_READDIR
index 762eb143a7654480d8831a6258a0767c0f33900b..3b2477be5f658be665f19a12b48cc47fa07d1c6b 100644 (file)
@@ -1,4 +1,5 @@
 #include <winsock2.h>
+#include <ws2tcpip.h>
 
 /*
  * things that are not available in header files
@@ -17,9 +18,10 @@ typedef int pid_t;
 #define S_IROTH 0
 #define S_IXOTH 0
 
-#define WIFEXITED(x) ((unsigned)(x) < 259)     /* STILL_ACTIVE */
+#define WIFEXITED(x) 1
+#define WIFSIGNALED(x) 0
 #define WEXITSTATUS(x) ((x) & 0xff)
-#define WIFSIGNALED(x) ((unsigned)(x) > 259)
+#define WTERMSIG(x) SIGTERM
 
 #define SIGHUP 1
 #define SIGQUIT 3
@@ -38,6 +40,9 @@ struct passwd {
        char *pw_dir;
 };
 
+extern char *getpass(const char *prompt);
+
+#ifndef POLLIN
 struct pollfd {
        int fd;           /* file descriptor */
        short events;     /* requested events */
@@ -45,6 +50,7 @@ struct pollfd {
 };
 #define POLLIN 1
 #define POLLHUP 2
+#endif
 
 typedef void (__cdecl *sig_handler_t)(int);
 struct sigaction {
@@ -74,7 +80,7 @@ static inline int fork(void)
 static inline unsigned int alarm(unsigned int seconds)
 { return 0; }
 static inline int fsync(int fd)
-{ return 0; }
+{ return _commit(fd); }
 static inline int getppid(void)
 { return 1; }
 static inline void sync(void)
@@ -83,13 +89,15 @@ static inline int getuid()
 { return 1; }
 static inline struct passwd *getpwnam(const char *name)
 { return NULL; }
-static inline int fcntl(int fd, int cmd, long arg)
+static inline int fcntl(int fd, int cmd, ...)
 {
        if (cmd == F_GETFD || cmd == F_SETFD)
                return 0;
        errno = EINVAL;
        return -1;
 }
+/* bash cannot reliably detect negative return codes as failure */
+#define exit(code) exit((code) & 0xff)
 
 /*
  * simple adaptors
@@ -109,7 +117,7 @@ static inline int mingw_unlink(const char *pathname)
 }
 #define unlink mingw_unlink
 
-static inline int waitpid(pid_t pid, unsigned *status, unsigned options)
+static inline int waitpid(pid_t pid, int *status, unsigned options)
 {
        if (options == 0)
                return _cwait(status, pid, 0);
@@ -117,6 +125,27 @@ static inline int waitpid(pid_t pid, unsigned *status, unsigned options)
        return -1;
 }
 
+#ifndef NO_OPENSSL
+#include <openssl/ssl.h>
+static inline int mingw_SSL_set_fd(SSL *ssl, int fd)
+{
+       return SSL_set_fd(ssl, _get_osfhandle(fd));
+}
+#define SSL_set_fd mingw_SSL_set_fd
+
+static inline int mingw_SSL_set_rfd(SSL *ssl, int fd)
+{
+       return SSL_set_rfd(ssl, _get_osfhandle(fd));
+}
+#define SSL_set_rfd mingw_SSL_set_rfd
+
+static inline int mingw_SSL_set_wfd(SSL *ssl, int fd)
+{
+       return SSL_set_wfd(ssl, _get_osfhandle(fd));
+}
+#define SSL_set_wfd mingw_SSL_set_wfd
+#endif
+
 /*
  * implementations of missing functions
  */
@@ -141,6 +170,15 @@ int link(const char *oldpath, const char *newpath);
 int mingw_open (const char *filename, int oflags, ...);
 #define open mingw_open
 
+ssize_t mingw_write(int fd, const void *buf, size_t count);
+#define write mingw_write
+
+FILE *mingw_fopen (const char *filename, const char *otype);
+#define fopen mingw_fopen
+
+FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream);
+#define freopen mingw_freopen
+
 char *mingw_getcwd(char *pointer, int len);
 #define getcwd mingw_getcwd
 
@@ -150,6 +188,18 @@ char *mingw_getenv(const char *name);
 struct hostent *mingw_gethostbyname(const char *host);
 #define gethostbyname mingw_gethostbyname
 
+void mingw_freeaddrinfo(struct addrinfo *res);
+#define freeaddrinfo mingw_freeaddrinfo
+
+int mingw_getaddrinfo(const char *node, const char *service,
+                     const struct addrinfo *hints, struct addrinfo **res);
+#define getaddrinfo mingw_getaddrinfo
+
+int mingw_getnameinfo(const struct sockaddr *sa, socklen_t salen,
+                     char *host, DWORD hostlen, char *serv, DWORD servlen,
+                     int flags);
+#define getnameinfo mingw_getnameinfo
+
 int mingw_socket(int domain, int type, int protocol);
 #define socket mingw_socket
 
@@ -159,7 +209,7 @@ int mingw_connect(int sockfd, struct sockaddr *sa, size_t sz);
 int mingw_rename(const char*, const char*);
 #define rename mingw_rename
 
-#ifdef USE_WIN32_MMAP
+#if defined(USE_WIN32_MMAP) || defined(_MSC_VER)
 int mingw_getpagesize(void);
 #define getpagesize mingw_getpagesize
 #endif
@@ -168,18 +218,22 @@ int mingw_getpagesize(void);
  * mingw_fstat() instead of fstat() on Windows.
  */
 #define off_t off64_t
-#define stat _stati64
 #define lseek _lseeki64
+#ifndef ALREADY_DECLARED_STAT_FUNCS
+#define stat _stati64
 int mingw_lstat(const char *file_name, struct stat *buf);
 int mingw_fstat(int fd, struct stat *buf);
 #define fstat mingw_fstat
 #define lstat mingw_lstat
 #define _stati64(x,y) mingw_lstat(x,y)
+#endif
 
 int mingw_utime(const char *file_name, const struct utimbuf *times);
 #define utime mingw_utime
 
-pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env);
+pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env,
+                    const char *dir,
+                    int fhin, int fhout, int fherr);
 void mingw_execvp(const char *cmd, char *const *argv);
 #define execvp mingw_execvp
 
@@ -217,19 +271,57 @@ void mingw_open_html(const char *path);
  * helpers
  */
 
-char **copy_environ(void);
+char **make_augmented_environ(const char *const *vars);
 void free_environ(char **env);
-char **env_setenv(char **env, const char *name);
 
 /*
  * A replacement of main() that ensures that argv[0] has a path
+ * and that default fmode and std(in|out|err) are in binary mode
  */
 
 #define main(c,v) dummy_decl_mingw_main(); \
 static int mingw_main(); \
 int main(int argc, const char **argv) \
 { \
+       _fmode = _O_BINARY; \
+       _setmode(_fileno(stdin), _O_BINARY); \
+       _setmode(_fileno(stdout), _O_BINARY); \
+       _setmode(_fileno(stderr), _O_BINARY); \
        argv[0] = xstrdup(_pgmptr); \
        return mingw_main(argc, argv); \
 } \
 static int mingw_main(c,v)
+
+#ifndef NO_MINGW_REPLACE_READDIR
+/*
+ * A replacement of readdir, to ensure that it reads the file type at
+ * the same time. This avoid extra unneeded lstats in git on MinGW
+ */
+#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
+
+struct mingw_dirent
+{
+       long            d_ino;                  /* Always zero. */
+       union {
+               unsigned short  d_reclen;       /* Always zero. */
+               unsigned char   d_type;         /* Reimplementation adds this */
+       };
+       unsigned short  d_namlen;               /* Length of name in d_name. */
+       char            d_name[FILENAME_MAX];   /* File name. */
+};
+#define dirent mingw_dirent
+#define readdir(x) mingw_readdir(x)
+struct dirent *mingw_readdir(DIR *dir);
+#endif // !NO_MINGW_REPLACE_READDIR
+
+/*
+ * Used by Pthread API implementation for Windows
+ */
+extern int err_win_to_posix(DWORD winerr);
index 34d4b49818b0896b9db19b2b1387f142cbbbd42b..11361195925c674423309d40f343c88f58b7bc1e 100644 (file)
@@ -2,7 +2,7 @@
 
 char *gitmkdtemp(char *template)
 {
-       if (!mktemp(template) || mkdir(template, 0700))
+       if (!*mktemp(template) || mkdir(template, 0700))
                return NULL;
        return template;
 }
diff --git a/compat/msvc.c b/compat/msvc.c
new file mode 100644 (file)
index 0000000..ac04a4c
--- /dev/null
@@ -0,0 +1,35 @@
+#include "../git-compat-util.h"
+#include "win32.h"
+#include <conio.h>
+#include "../strbuf.h"
+
+DIR *opendir(const char *name)
+{
+       int len;
+       DIR *p;
+       p = (DIR*)malloc(sizeof(DIR));
+       memset(p, 0, sizeof(DIR));
+       strncpy(p->dd_name, name, PATH_MAX);
+       len = strlen(p->dd_name);
+       p->dd_name[len] = '/';
+       p->dd_name[len+1] = '*';
+
+       if (p == NULL)
+               return NULL;
+
+       p->dd_handle = _findfirst(p->dd_name, &p->dd_dta);
+
+       if (p->dd_handle == -1) {
+               free(p);
+               return NULL;
+       }
+       return p;
+}
+int closedir(DIR *dir)
+{
+       _findclose(dir->dd_handle);
+       free(dir);
+       return 0;
+}
+
+#include "mingw.c"
diff --git a/compat/msvc.h b/compat/msvc.h
new file mode 100644 (file)
index 0000000..023aba0
--- /dev/null
@@ -0,0 +1,42 @@
+#ifndef __MSVC__HEAD
+#define __MSVC__HEAD
+
+#include <direct.h>
+#include <process.h>
+#include <malloc.h>
+
+/* porting function */
+#define inline __inline
+#define __inline__ __inline
+#define __attribute__(x)
+#define va_copy(dst, src)     ((dst) = (src))
+#define strncasecmp  _strnicmp
+#define ftruncate    _chsize
+
+static __inline int strcasecmp (const char *s1, const char *s2)
+{
+       int size1 = strlen(s1);
+       int sisz2 = strlen(s2);
+       return _strnicmp(s1, s2, sisz2 > size1 ? sisz2 : size1);
+}
+
+#undef ERROR
+
+/* Use mingw_lstat() instead of lstat()/stat() and mingw_fstat() instead
+ * of fstat(). We add the declaration of these functions here, suppressing
+ * the corresponding declarations in mingw.h, so that we can use the
+ * appropriate structure type (and function) names from the msvc headers.
+ */
+#define stat _stat64
+int mingw_lstat(const char *file_name, struct stat *buf);
+int mingw_fstat(int fd, struct stat *buf);
+#define fstat mingw_fstat
+#define lstat mingw_lstat
+#define _stat64(x,y) mingw_lstat(x,y)
+#define ALREADY_DECLARED_STAT_FUNCS
+
+#include "compat/mingw.h"
+
+#undef ALREADY_DECLARED_STAT_FUNCS
+
+#endif
diff --git a/compat/nedmalloc/License.txt b/compat/nedmalloc/License.txt
new file mode 100644 (file)
index 0000000..36b7cd9
--- /dev/null
@@ -0,0 +1,23 @@
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/compat/nedmalloc/Readme.txt b/compat/nedmalloc/Readme.txt
new file mode 100644 (file)
index 0000000..8763656
--- /dev/null
@@ -0,0 +1,136 @@
+nedalloc v1.05 15th June 2008:
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+by Niall Douglas (http://www.nedprod.com/programs/portable/nedmalloc/)
+
+Enclosed is nedalloc, an alternative malloc implementation for multiple
+threads without lock contention based on dlmalloc v2.8.4. It is more
+or less a newer implementation of ptmalloc2, the standard allocator in
+Linux (which is based on dlmalloc v2.7.0) but also contains a per-thread
+cache for maximum CPU scalability.
+
+It is licensed under the Boost Software License which basically means
+you can do anything you like with it. This does not apply to the malloc.c.h
+file which remains copyright to others.
+
+It has been tested on win32 (x86), win64 (x64), Linux (x64), FreeBSD (x64)
+and Apple MacOS X (x86). It works very well on all of these and is very
+significantly faster than the system allocator on all of these platforms.
+
+By literally dropping in this allocator as a replacement for your system
+allocator, you can see real world improvements of up to three times in normal
+code!
+
+To use:
+-=-=-=-
+Drop in nedmalloc.h, nedmalloc.c and malloc.c.h into your project.
+Configure using the instructions in nedmalloc.h. Run and enjoy.
+
+To test, compile test.c. It will run a comparison between your system
+allocator and nedalloc and tell you how much faster nedalloc is. It also
+serves as an example of usage.
+
+Notes:
+-=-=-=
+If you want the very latest version of this allocator, get it from the
+TnFOX SVN repository at svn://svn.berlios.de/viewcvs/tnfox/trunk/src/nedmalloc
+
+Because of how nedalloc allocates an mspace per thread, it can cause
+severe bloating of memory usage under certain allocation patterns.
+You can substantially reduce this wastage by setting MAXTHREADSINPOOL
+or the threads parameter to nedcreatepool() to a fraction of the number of
+threads which would normally be in a pool at once. This will reduce
+bloating at the cost of an increase in lock contention. If allocated size
+is less than THREADCACHEMAX, locking is avoided 90-99% of the time and
+if most of your allocations are below this value, you can safely set
+MAXTHREADSINPOOL to one.
+
+You will suffer memory leakage unless you call neddisablethreadcache()
+per pool for every thread which exits. This is because nedalloc cannot
+portably know when a thread exits and thus when its thread cache can
+be returned for use by other code. Don't forget pool zero, the system pool.
+
+For C++ type allocation patterns (where the same sizes of memory are
+regularly allocated and deallocated as objects are created and destroyed),
+the threadcache always benefits performance. If however your allocation
+patterns are different, searching the threadcache may significantly slow
+down your code - as a rule of thumb, if cache utilisation is below 80%
+(see the source for neddisablethreadcache() for how to enable debug
+printing in release mode) then you should disable the thread cache for
+that thread. You can compile out the threadcache code by setting
+THREADCACHEMAX to zero.
+
+Speed comparisons:
+-=-=-=-=-=-=-=-=-=
+See Benchmarks.xls for details.
+
+The enclosed test.c can do two things: it can be a torture test or a speed
+test. The speed test is designed to be a representative synthetic
+memory allocator test. It works by randomly mixing allocations with frees
+with half of the allocation sizes being a two power multiple less than
+512 bytes (to mimic C++ stack instantiated objects) and the other half
+being a simple random value less than 16Kb.
+
+The real world code results are from Tn's TestIO benchmark. This is a
+heavily multithreaded and memory intensive benchmark with a lot of branching
+and other stuff modern processors don't like so much. As you'll note, the
+test doesn't show the benefits of the threadcache mostly due to the saturation
+of the memory bus being the limiting factor.
+
+ChangeLog:
+-=-=-=-=-=
+v1.05 15th June 2008:
+ * { 1042 } Added error check for TLSSET() and TLSFREE() macros. Thanks to
+Markus Elfring for reporting this.
+ * { 1043 } Fixed a segfault when freeing memory allocated using
+nedindependent_comalloc(). Thanks to Pavel Vozenilek for reporting this.
+
+v1.04 14th July 2007:
+ * Fixed a bug with the new optimised implementation that failed to lock
+on a realloc under certain conditions.
+ * Fixed lack of thread synchronisation in InitPool() causing pool corruption
+ * Fixed a memory leak of thread cache contents on disabling. Thanks to Earl
+Chew for reporting this.
+ * Added a sanity check for freed blocks being valid.
+ * Reworked test.c into being a torture test.
+ * Fixed GCC assembler optimisation misspecification
+
+v1.04alpha_svn915 7th October 2006:
+ * Fixed failure to unlock thread cache list if allocating a new list failed.
+Thanks to Dmitry Chichkov for reporting this. Futher thanks to Aleksey Sanin.
+ * Fixed realloc(0, <size>) segfaulting. Thanks to Dmitry Chichkov for
+reporting this.
+ * Made config defines #ifndef so they can be overriden by the build system.
+Thanks to Aleksey Sanin for suggesting this.
+ * Fixed deadlock in nedprealloc() due to unnecessary locking of preferred
+thread mspace when mspace_realloc() always uses the original block's mspace
+anyway. Thanks to Aleksey Sanin for reporting this.
+ * Made some speed improvements by hacking mspace_malloc() to no longer lock
+its mspace, thus allowing the recursive mutex implementation to be removed
+with an associated speed increase. Thanks to Aleksey Sanin for suggesting this.
+ * Fixed a bug where allocating mspaces overran its max limit. Thanks to
+Aleksey Sanin for reporting this.
+
+v1.03 10th July 2006:
+ * Fixed memory corruption bug in threadcache code which only appeared with >4
+threads and in heavy use of the threadcache.
+
+v1.02 15th May 2006:
+ * Integrated dlmalloc v2.8.4, fixing the win32 memory release problem and
+improving performance still further. Speed is now up to twice the speed of v1.01
+(average is 67% faster).
+ * Fixed win32 critical section implementation. Thanks to Pavel Kuznetsov
+for reporting this.
+ * Wasn't locking mspace if all mspaces were locked. Thanks to Pavel Kuznetsov
+for reporting this.
+ * Added Apple Mac OS X support.
+
+v1.01 24th February 2006:
+ * Fixed multiprocessor scaling problems by removing sources of cache sloshing
+ * Earl Chew <earl_chew <at> agilent <dot> com> sent patches for the following:
+   1. size2binidx() wasn't working for default code path (non x86)
+   2. Fixed failure to release mspace lock under certain circumstances which
+      caused a deadlock
+
+v1.00 1st January 2006:
+ * First release
diff --git a/compat/nedmalloc/malloc.c.h b/compat/nedmalloc/malloc.c.h
new file mode 100644 (file)
index 0000000..87260d2
--- /dev/null
@@ -0,0 +1,5752 @@
+/*
+  This is a version (aka dlmalloc) of malloc/free/realloc written by
+  Doug Lea and released to the public domain, as explained at
+  http://creativecommons.org/licenses/publicdomain.  Send questions,
+  comments, complaints, performance data, etc to dl@cs.oswego.edu
+
+* Version pre-2.8.4 Mon Nov 27 11:22:37 2006    (dl at gee)
+
+   Note: There may be an updated version of this malloc obtainable at
+          ftp://gee.cs.oswego.edu/pub/misc/malloc.c
+        Check before installing!
+
+* Quickstart
+
+  This library is all in one file to simplify the most common usage:
+  ftp it, compile it (-O3), and link it into another program. All of
+  the compile-time options default to reasonable values for use on
+  most platforms.  You might later want to step through various
+  compile-time and dynamic tuning options.
+
+  For convenience, an include file for code using this malloc is at:
+     ftp://gee.cs.oswego.edu/pub/misc/malloc-2.8.4.h
+  You don't really need this .h file unless you call functions not
+  defined in your system include files.  The .h file contains only the
+  excerpts from this file needed for using this malloc on ANSI C/C++
+  systems, so long as you haven't changed compile-time options about
+  naming and tuning parameters.  If you do, then you can create your
+  own malloc.h that does include all settings by cutting at the point
+  indicated below. Note that you may already by default be using a C
+  library containing a malloc that is based on some version of this
+  malloc (for example in linux). You might still want to use the one
+  in this file to customize settings or to avoid overheads associated
+  with library versions.
+
+* Vital statistics:
+
+  Supported pointer/size_t representation:       4 or 8 bytes
+       size_t MUST be an unsigned type of the same width as
+       pointers. (If you are using an ancient system that declares
+       size_t as a signed type, or need it to be a different width
+       than pointers, you can use a previous release of this malloc
+       (e.g. 2.7.2) supporting these.)
+
+  Alignment:                                     8 bytes (default)
+       This suffices for nearly all current machines and C compilers.
+       However, you can define MALLOC_ALIGNMENT to be wider than this
+       if necessary (up to 128bytes), at the expense of using more space.
+
+  Minimum overhead per allocated chunk:   4 or  8 bytes (if 4byte sizes)
+                                         8 or 16 bytes (if 8byte sizes)
+       Each malloced chunk has a hidden word of overhead holding size
+       and status information, and additional cross-check word
+       if FOOTERS is defined.
+
+  Minimum allocated size: 4-byte ptrs:  16 bytes    (including overhead)
+                         8-byte ptrs:  32 bytes    (including overhead)
+
+       Even a request for zero bytes (i.e., malloc(0)) returns a
+       pointer to something of the minimum allocatable size.
+       The maximum overhead wastage (i.e., number of extra bytes
+       allocated than were requested in malloc) is less than or equal
+       to the minimum size, except for requests >= mmap_threshold that
+       are serviced via mmap(), where the worst case wastage is about
+       32 bytes plus the remainder from a system page (the minimal
+       mmap unit); typically 4096 or 8192 bytes.
+
+  Security: static-safe; optionally more or less
+       The "security" of malloc refers to the ability of malicious
+       code to accentuate the effects of errors (for example, freeing
+       space that is not currently malloc'ed or overwriting past the
+       ends of chunks) in code that calls malloc.  This malloc
+       guarantees not to modify any memory locations below the base of
+       heap, i.e., static variables, even in the presence of usage
+       errors.  The routines additionally detect most improper frees
+       and reallocs.  All this holds as long as the static bookkeeping
+       for malloc itself is not corrupted by some other means.  This
+       is only one aspect of security -- these checks do not, and
+       cannot, detect all possible programming errors.
+
+       If FOOTERS is defined nonzero, then each allocated chunk
+       carries an additional check word to verify that it was malloced
+       from its space.  These check words are the same within each
+       execution of a program using malloc, but differ across
+       executions, so externally crafted fake chunks cannot be
+       freed. This improves security by rejecting frees/reallocs that
+       could corrupt heap memory, in addition to the checks preventing
+       writes to statics that are always on.  This may further improve
+       security at the expense of time and space overhead.  (Note that
+       FOOTERS may also be worth using with MSPACES.)
+
+       By default detected errors cause the program to abort (calling
+       "abort()"). You can override this to instead proceed past
+       errors by defining PROCEED_ON_ERROR.  In this case, a bad free
+       has no effect, and a malloc that encounters a bad address
+       caused by user overwrites will ignore the bad address by
+       dropping pointers and indices to all known memory. This may
+       be appropriate for programs that should continue if at all
+       possible in the face of programming errors, although they may
+       run out of memory because dropped memory is never reclaimed.
+
+       If you don't like either of these options, you can define
+       CORRUPTION_ERROR_ACTION and USAGE_ERROR_ACTION to do anything
+       else. And if if you are sure that your program using malloc has
+       no errors or vulnerabilities, you can define INSECURE to 1,
+       which might (or might not) provide a small performance improvement.
+
+  Thread-safety: NOT thread-safe unless USE_LOCKS defined
+       When USE_LOCKS is defined, each public call to malloc, free,
+       etc is surrounded with either a pthread mutex or a win32
+       spinlock (depending on WIN32). This is not especially fast, and
+       can be a major bottleneck.  It is designed only to provide
+       minimal protection in concurrent environments, and to provide a
+       basis for extensions.  If you are using malloc in a concurrent
+       program, consider instead using nedmalloc
+       (http://www.nedprod.com/programs/portable/nedmalloc/) or
+       ptmalloc (See http://www.malloc.de), which are derived
+       from versions of this malloc.
+
+  System requirements: Any combination of MORECORE and/or MMAP/MUNMAP
+       This malloc can use unix sbrk or any emulation (invoked using
+       the CALL_MORECORE macro) and/or mmap/munmap or any emulation
+       (invoked using CALL_MMAP/CALL_MUNMAP) to get and release system
+       memory.  On most unix systems, it tends to work best if both
+       MORECORE and MMAP are enabled.  On Win32, it uses emulations
+       based on VirtualAlloc. It also uses common C library functions
+       like memset.
+
+  Compliance: I believe it is compliant with the Single Unix Specification
+       (See http://www.unix.org). Also SVID/XPG, ANSI C, and probably
+       others as well.
+
+* Overview of algorithms
+
+  This is not the fastest, most space-conserving, most portable, or
+  most tunable malloc ever written. However it is among the fastest
+  while also being among the most space-conserving, portable and
+  tunable.  Consistent balance across these factors results in a good
+  general-purpose allocator for malloc-intensive programs.
+
+  In most ways, this malloc is a best-fit allocator. Generally, it
+  chooses the best-fitting existing chunk for a request, with ties
+  broken in approximately least-recently-used order. (This strategy
+  normally maintains low fragmentation.) However, for requests less
+  than 256bytes, it deviates from best-fit when there is not an
+  exactly fitting available chunk by preferring to use space adjacent
+  to that used for the previous small request, as well as by breaking
+  ties in approximately most-recently-used order. (These enhance
+  locality of series of small allocations.)  And for very large requests
+  (>= 256Kb by default), it relies on system memory mapping
+  facilities, if supported.  (This helps avoid carrying around and
+  possibly fragmenting memory used only for large chunks.)
+
+  All operations (except malloc_stats and mallinfo) have execution
+  times that are bounded by a constant factor of the number of bits in
+  a size_t, not counting any clearing in calloc or copying in realloc,
+  or actions surrounding MORECORE and MMAP that have times
+  proportional to the number of non-contiguous regions returned by
+  system allocation routines, which is often just 1. In real-time
+  applications, you can optionally suppress segment traversals using
+  NO_SEGMENT_TRAVERSAL, which assures bounded execution even when
+  system allocators return non-contiguous spaces, at the typical
+  expense of carrying around more memory and increased fragmentation.
+
+  The implementation is not very modular and seriously overuses
+  macros. Perhaps someday all C compilers will do as good a job
+  inlining modular code as can now be done by brute-force expansion,
+  but now, enough of them seem not to.
+
+  Some compilers issue a lot of warnings about code that is
+  dead/unreachable only on some platforms, and also about intentional
+  uses of negation on unsigned types. All known cases of each can be
+  ignored.
+
+  For a longer but out of date high-level description, see
+     http://gee.cs.oswego.edu/dl/html/malloc.html
+
+* MSPACES
+  If MSPACES is defined, then in addition to malloc, free, etc.,
+  this file also defines mspace_malloc, mspace_free, etc. These
+  are versions of malloc routines that take an "mspace" argument
+  obtained using create_mspace, to control all internal bookkeeping.
+  If ONLY_MSPACES is defined, only these versions are compiled.
+  So if you would like to use this allocator for only some allocations,
+  and your system malloc for others, you can compile with
+  ONLY_MSPACES and then do something like...
+    static mspace mymspace = create_mspace(0,0); // for example
+    #define mymalloc(bytes)  mspace_malloc(mymspace, bytes)
+
+  (Note: If you only need one instance of an mspace, you can instead
+  use "USE_DL_PREFIX" to relabel the global malloc.)
+
+  You can similarly create thread-local allocators by storing
+  mspaces as thread-locals. For example:
+    static __thread mspace tlms = 0;
+    void*  tlmalloc(size_t bytes) {
+      if (tlms == 0) tlms = create_mspace(0, 0);
+      return mspace_malloc(tlms, bytes);
+    }
+    void  tlfree(void* mem) { mspace_free(tlms, mem); }
+
+  Unless FOOTERS is defined, each mspace is completely independent.
+  You cannot allocate from one and free to another (although
+  conformance is only weakly checked, so usage errors are not always
+  caught). If FOOTERS is defined, then each chunk carries around a tag
+  indicating its originating mspace, and frees are directed to their
+  originating spaces.
+
+ -------------------------  Compile-time options ---------------------------
+
+Be careful in setting #define values for numerical constants of type
+size_t. On some systems, literal values are not automatically extended
+to size_t precision unless they are explicitly casted. You can also
+use the symbolic values MAX_SIZE_T, SIZE_T_ONE, etc below.
+
+WIN32                    default: defined if _WIN32 defined
+  Defining WIN32 sets up defaults for MS environment and compilers.
+  Otherwise defaults are for unix. Beware that there seem to be some
+  cases where this malloc might not be a pure drop-in replacement for
+  Win32 malloc: Random-looking failures from Win32 GDI API's (eg;
+  SetDIBits()) may be due to bugs in some video driver implementations
+  when pixel buffers are malloc()ed, and the region spans more than
+  one VirtualAlloc()ed region. Because dlmalloc uses a small (64Kb)
+  default granularity, pixel buffers may straddle virtual allocation
+  regions more often than when using the Microsoft allocator.  You can
+  avoid this by using VirtualAlloc() and VirtualFree() for all pixel
+  buffers rather than using malloc().  If this is not possible,
+  recompile this malloc with a larger DEFAULT_GRANULARITY.
+
+MALLOC_ALIGNMENT         default: (size_t)8
+  Controls the minimum alignment for malloc'ed chunks.  It must be a
+  power of two and at least 8, even on machines for which smaller
+  alignments would suffice. It may be defined as larger than this
+  though. Note however that code and data structures are optimized for
+  the case of 8-byte alignment.
+
+MSPACES                  default: 0 (false)
+  If true, compile in support for independent allocation spaces.
+  This is only supported if HAVE_MMAP is true.
+
+ONLY_MSPACES             default: 0 (false)
+  If true, only compile in mspace versions, not regular versions.
+
+USE_LOCKS                default: 0 (false)
+  Causes each call to each public routine to be surrounded with
+  pthread or WIN32 mutex lock/unlock. (If set true, this can be
+  overridden on a per-mspace basis for mspace versions.) If set to a
+  non-zero value other than 1, locks are used, but their
+  implementation is left out, so lock functions must be supplied manually.
+
+USE_SPIN_LOCKS           default: 1 iff USE_LOCKS and on x86 using gcc or MSC
+  If true, uses custom spin locks for locking. This is currently
+  supported only for x86 platforms using gcc or recent MS compilers.
+  Otherwise, posix locks or win32 critical sections are used.
+
+FOOTERS                  default: 0
+  If true, provide extra checking and dispatching by placing
+  information in the footers of allocated chunks. This adds
+  space and time overhead.
+
+INSECURE                 default: 0
+  If true, omit checks for usage errors and heap space overwrites.
+
+USE_DL_PREFIX            default: NOT defined
+  Causes compiler to prefix all public routines with the string 'dl'.
+  This can be useful when you only want to use this malloc in one part
+  of a program, using your regular system malloc elsewhere.
+
+ABORT                    default: defined as abort()
+  Defines how to abort on failed checks.  On most systems, a failed
+  check cannot die with an "assert" or even print an informative
+  message, because the underlying print routines in turn call malloc,
+  which will fail again.  Generally, the best policy is to simply call
+  abort(). It's not very useful to do more than this because many
+  errors due to overwriting will show up as address faults (null, odd
+  addresses etc) rather than malloc-triggered checks, so will also
+  abort.  Also, most compilers know that abort() does not return, so
+  can better optimize code conditionally calling it.
+
+PROCEED_ON_ERROR           default: defined as 0 (false)
+  Controls whether detected bad addresses cause them to bypassed
+  rather than aborting. If set, detected bad arguments to free and
+  realloc are ignored. And all bookkeeping information is zeroed out
+  upon a detected overwrite of freed heap space, thus losing the
+  ability to ever return it from malloc again, but enabling the
+  application to proceed. If PROCEED_ON_ERROR is defined, the
+  static variable malloc_corruption_error_count is compiled in
+  and can be examined to see if errors have occurred. This option
+  generates slower code than the default abort policy.
+
+DEBUG                    default: NOT defined
+  The DEBUG setting is mainly intended for people trying to modify
+  this code or diagnose problems when porting to new platforms.
+  However, it may also be able to better isolate user errors than just
+  using runtime checks.  The assertions in the check routines spell
+  out in more detail the assumptions and invariants underlying the
+  algorithms.  The checking is fairly extensive, and will slow down
+  execution noticeably. Calling malloc_stats or mallinfo with DEBUG
+  set will attempt to check every non-mmapped allocated and free chunk
+  in the course of computing the summaries.
+
+ABORT_ON_ASSERT_FAILURE   default: defined as 1 (true)
+  Debugging assertion failures can be nearly impossible if your
+  version of the assert macro causes malloc to be called, which will
+  lead to a cascade of further failures, blowing the runtime stack.
+  ABORT_ON_ASSERT_FAILURE cause assertions failures to call abort(),
+  which will usually make debugging easier.
+
+MALLOC_FAILURE_ACTION     default: sets errno to ENOMEM, or no-op on win32
+  The action to take before "return 0" when malloc fails to be able to
+  return memory because there is none available.
+
+HAVE_MORECORE             default: 1 (true) unless win32 or ONLY_MSPACES
+  True if this system supports sbrk or an emulation of it.
+
+MORECORE                  default: sbrk
+  The name of the sbrk-style system routine to call to obtain more
+  memory.  See below for guidance on writing custom MORECORE
+  functions. The type of the argument to sbrk/MORECORE varies across
+  systems.  It cannot be size_t, because it supports negative
+  arguments, so it is normally the signed type of the same width as
+  size_t (sometimes declared as "intptr_t").  It doesn't much matter
+  though. Internally, we only call it with arguments less than half
+  the max value of a size_t, which should work across all reasonable
+  possibilities, although sometimes generating compiler warnings.
+
+MORECORE_CONTIGUOUS       default: 1 (true) if HAVE_MORECORE
+  If true, take advantage of fact that consecutive calls to MORECORE
+  with positive arguments always return contiguous increasing
+  addresses.  This is true of unix sbrk. It does not hurt too much to
+  set it true anyway, since malloc copes with non-contiguities.
+  Setting it false when definitely non-contiguous saves time
+  and possibly wasted space it would take to discover this though.
+
+MORECORE_CANNOT_TRIM      default: NOT defined
+  True if MORECORE cannot release space back to the system when given
+  negative arguments. This is generally necessary only if you are
+  using a hand-crafted MORECORE function that cannot handle negative
+  arguments.
+
+NO_SEGMENT_TRAVERSAL       default: 0
+  If non-zero, suppresses traversals of memory segments
+  returned by either MORECORE or CALL_MMAP. This disables
+  merging of segments that are contiguous, and selectively
+  releasing them to the OS if unused, but bounds execution times.
+
+HAVE_MMAP                 default: 1 (true)
+  True if this system supports mmap or an emulation of it.  If so, and
+  HAVE_MORECORE is not true, MMAP is used for all system
+  allocation. If set and HAVE_MORECORE is true as well, MMAP is
+  primarily used to directly allocate very large blocks. It is also
+  used as a backup strategy in cases where MORECORE fails to provide
+  space from system. Note: A single call to MUNMAP is assumed to be
+  able to unmap memory that may have be allocated using multiple calls
+  to MMAP, so long as they are adjacent.
+
+HAVE_MREMAP               default: 1 on linux, else 0
+  If true realloc() uses mremap() to re-allocate large blocks and
+  extend or shrink allocation spaces.
+
+MMAP_CLEARS               default: 1 except on WINCE.
+  True if mmap clears memory so calloc doesn't need to. This is true
+  for standard unix mmap using /dev/zero and on WIN32 except for WINCE.
+
+USE_BUILTIN_FFS            default: 0 (i.e., not used)
+  Causes malloc to use the builtin ffs() function to compute indices.
+  Some compilers may recognize and intrinsify ffs to be faster than the
+  supplied C version. Also, the case of x86 using gcc is special-cased
+  to an asm instruction, so is already as fast as it can be, and so
+  this setting has no effect. Similarly for Win32 under recent MS compilers.
+  (On most x86s, the asm version is only slightly faster than the C version.)
+
+malloc_getpagesize         default: derive from system includes, or 4096.
+  The system page size. To the extent possible, this malloc manages
+  memory from the system in page-size units.  This may be (and
+  usually is) a function rather than a constant. This is ignored
+  if WIN32, where page size is determined using getSystemInfo during
+  initialization.
+
+USE_DEV_RANDOM             default: 0 (i.e., not used)
+  Causes malloc to use /dev/random to initialize secure magic seed for
+  stamping footers. Otherwise, the current time is used.
+
+NO_MALLINFO                default: 0
+  If defined, don't compile "mallinfo". This can be a simple way
+  of dealing with mismatches between system declarations and
+  those in this file.
+
+MALLINFO_FIELD_TYPE        default: size_t
+  The type of the fields in the mallinfo struct. This was originally
+  defined as "int" in SVID etc, but is more usefully defined as
+  size_t. The value is used only if  HAVE_USR_INCLUDE_MALLOC_H is not set
+
+REALLOC_ZERO_BYTES_FREES    default: not defined
+  This should be set if a call to realloc with zero bytes should
+  be the same as a call to free. Some people think it should. Otherwise,
+  since this malloc returns a unique pointer for malloc(0), so does
+  realloc(p, 0).
+
+LACKS_UNISTD_H, LACKS_FCNTL_H, LACKS_SYS_PARAM_H, LACKS_SYS_MMAN_H
+LACKS_STRINGS_H, LACKS_STRING_H, LACKS_SYS_TYPES_H,  LACKS_ERRNO_H
+LACKS_STDLIB_H                default: NOT defined unless on WIN32
+  Define these if your system does not have these header files.
+  You might need to manually insert some of the declarations they provide.
+
+DEFAULT_GRANULARITY        default: page size if MORECORE_CONTIGUOUS,
+                               system_info.dwAllocationGranularity in WIN32,
+                               otherwise 64K.
+      Also settable using mallopt(M_GRANULARITY, x)
+  The unit for allocating and deallocating memory from the system.  On
+  most systems with contiguous MORECORE, there is no reason to
+  make this more than a page. However, systems with MMAP tend to
+  either require or encourage larger granularities.  You can increase
+  this value to prevent system allocation functions to be called so
+  often, especially if they are slow.  The value must be at least one
+  page and must be a power of two.  Setting to 0 causes initialization
+  to either page size or win32 region size.  (Note: In previous
+  versions of malloc, the equivalent of this option was called
+  "TOP_PAD")
+
+DEFAULT_TRIM_THRESHOLD    default: 2MB
+      Also settable using mallopt(M_TRIM_THRESHOLD, x)
+  The maximum amount of unused top-most memory to keep before
+  releasing via malloc_trim in free().  Automatic trimming is mainly
+  useful in long-lived programs using contiguous MORECORE.  Because
+  trimming via sbrk can be slow on some systems, and can sometimes be
+  wasteful (in cases where programs immediately afterward allocate
+  more large chunks) the value should be high enough so that your
+  overall system performance would improve by releasing this much
+  memory.  As a rough guide, you might set to a value close to the
+  average size of a process (program) running on your system.
+  Releasing this much memory would allow such a process to run in
+  memory.  Generally, it is worth tuning trim thresholds when a
+  program undergoes phases where several large chunks are allocated
+  and released in ways that can reuse each other's storage, perhaps
+  mixed with phases where there are no such chunks at all. The trim
+  value must be greater than page size to have any useful effect.  To
+  disable trimming completely, you can set to MAX_SIZE_T. Note that the trick
+  some people use of mallocing a huge space and then freeing it at
+  program startup, in an attempt to reserve system memory, doesn't
+  have the intended effect under automatic trimming, since that memory
+  will immediately be returned to the system.
+
+DEFAULT_MMAP_THRESHOLD       default: 256K
+      Also settable using mallopt(M_MMAP_THRESHOLD, x)
+  The request size threshold for using MMAP to directly service a
+  request. Requests of at least this size that cannot be allocated
+  using already-existing space will be serviced via mmap.  (If enough
+  normal freed space already exists it is used instead.)  Using mmap
+  segregates relatively large chunks of memory so that they can be
+  individually obtained and released from the host system. A request
+  serviced through mmap is never reused by any other request (at least
+  not directly; the system may just so happen to remap successive
+  requests to the same locations).  Segregating space in this way has
+  the benefits that: Mmapped space can always be individually released
+  back to the system, which helps keep the system level memory demands
+  of a long-lived program low.  Also, mapped memory doesn't become
+  `locked' between other chunks, as can happen with normally allocated
+  chunks, which means that even trimming via malloc_trim would not
+  release them.  However, it has the disadvantage that the space
+  cannot be reclaimed, consolidated, and then used to service later
+  requests, as happens with normal chunks.  The advantages of mmap
+  nearly always outweigh disadvantages for "large" chunks, but the
+  value of "large" may vary across systems.  The default is an
+  empirically derived value that works well in most systems. You can
+  disable mmap by setting to MAX_SIZE_T.
+
+MAX_RELEASE_CHECK_RATE   default: 4095 unless not HAVE_MMAP
+  The number of consolidated frees between checks to release
+  unused segments when freeing. When using non-contiguous segments,
+  especially with multiple mspaces, checking only for topmost space
+  doesn't always suffice to trigger trimming. To compensate for this,
+  free() will, with a period of MAX_RELEASE_CHECK_RATE (or the
+  current number of segments, if greater) try to release unused
+  segments to the OS when freeing chunks that result in
+  consolidation. The best value for this parameter is a compromise
+  between slowing down frees with relatively costly checks that
+  rarely trigger versus holding on to unused memory. To effectively
+  disable, set to MAX_SIZE_T. This may lead to a very slight speed
+  improvement at the expense of carrying around more memory.
+*/
+
+/* Version identifier to allow people to support multiple versions */
+#ifndef DLMALLOC_VERSION
+#define DLMALLOC_VERSION 20804
+#endif /* DLMALLOC_VERSION */
+
+#ifndef WIN32
+#ifdef _WIN32
+#define WIN32 1
+#endif  /* _WIN32 */
+#ifdef _WIN32_WCE
+#define LACKS_FCNTL_H
+#define WIN32 1
+#endif /* _WIN32_WCE */
+#endif  /* WIN32 */
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#define _WIN32_WINNT 0x403
+#include <windows.h>
+#define HAVE_MMAP 1
+#define HAVE_MORECORE 0
+#define LACKS_UNISTD_H
+#define LACKS_SYS_PARAM_H
+#define LACKS_SYS_MMAN_H
+#define LACKS_STRING_H
+#define LACKS_STRINGS_H
+#define LACKS_SYS_TYPES_H
+#define LACKS_ERRNO_H
+#ifndef MALLOC_FAILURE_ACTION
+#define MALLOC_FAILURE_ACTION
+#endif /* MALLOC_FAILURE_ACTION */
+#ifdef _WIN32_WCE /* WINCE reportedly does not clear */
+#define MMAP_CLEARS 0
+#else
+#define MMAP_CLEARS 1
+#endif /* _WIN32_WCE */
+#endif  /* WIN32 */
+
+#if defined(DARWIN) || defined(_DARWIN)
+/* Mac OSX docs advise not to use sbrk; it seems better to use mmap */
+#ifndef HAVE_MORECORE
+#define HAVE_MORECORE 0
+#define HAVE_MMAP 1
+/* OSX allocators provide 16 byte alignment */
+#ifndef MALLOC_ALIGNMENT
+#define MALLOC_ALIGNMENT ((size_t)16U)
+#endif
+#endif  /* HAVE_MORECORE */
+#endif  /* DARWIN */
+
+#ifndef LACKS_SYS_TYPES_H
+#include <sys/types.h>  /* For size_t */
+#endif  /* LACKS_SYS_TYPES_H */
+
+/* The maximum possible size_t value has all bits set */
+#define MAX_SIZE_T           (~(size_t)0)
+
+#ifndef ONLY_MSPACES
+#define ONLY_MSPACES 0     /* define to a value */
+#else
+#define ONLY_MSPACES 1
+#endif  /* ONLY_MSPACES */
+#ifndef MSPACES
+#if ONLY_MSPACES
+#define MSPACES 1
+#else   /* ONLY_MSPACES */
+#define MSPACES 0
+#endif  /* ONLY_MSPACES */
+#endif  /* MSPACES */
+#ifndef MALLOC_ALIGNMENT
+#define MALLOC_ALIGNMENT ((size_t)8U)
+#endif  /* MALLOC_ALIGNMENT */
+#ifndef FOOTERS
+#define FOOTERS 0
+#endif  /* FOOTERS */
+#ifndef ABORT
+#define ABORT  abort()
+#endif  /* ABORT */
+#ifndef ABORT_ON_ASSERT_FAILURE
+#define ABORT_ON_ASSERT_FAILURE 1
+#endif  /* ABORT_ON_ASSERT_FAILURE */
+#ifndef PROCEED_ON_ERROR
+#define PROCEED_ON_ERROR 0
+#endif  /* PROCEED_ON_ERROR */
+#ifndef USE_LOCKS
+#define USE_LOCKS 0
+#endif  /* USE_LOCKS */
+#ifndef USE_SPIN_LOCKS
+#if USE_LOCKS && (defined(__GNUC__) && ((defined(__i386__) || defined(__x86_64__)))) || (defined(_MSC_VER) && _MSC_VER>=1310)
+#define USE_SPIN_LOCKS 1
+#else
+#define USE_SPIN_LOCKS 0
+#endif /* USE_LOCKS && ... */
+#endif /* USE_SPIN_LOCKS */
+#ifndef INSECURE
+#define INSECURE 0
+#endif  /* INSECURE */
+#ifndef HAVE_MMAP
+#define HAVE_MMAP 1
+#endif  /* HAVE_MMAP */
+#ifndef MMAP_CLEARS
+#define MMAP_CLEARS 1
+#endif  /* MMAP_CLEARS */
+#ifndef HAVE_MREMAP
+#ifdef linux
+#define HAVE_MREMAP 1
+#else   /* linux */
+#define HAVE_MREMAP 0
+#endif  /* linux */
+#endif  /* HAVE_MREMAP */
+#ifndef MALLOC_FAILURE_ACTION
+#define MALLOC_FAILURE_ACTION  errno = ENOMEM;
+#endif  /* MALLOC_FAILURE_ACTION */
+#ifndef HAVE_MORECORE
+#if ONLY_MSPACES
+#define HAVE_MORECORE 0
+#else   /* ONLY_MSPACES */
+#define HAVE_MORECORE 1
+#endif  /* ONLY_MSPACES */
+#endif  /* HAVE_MORECORE */
+#if !HAVE_MORECORE
+#define MORECORE_CONTIGUOUS 0
+#else   /* !HAVE_MORECORE */
+#define MORECORE_DEFAULT sbrk
+#ifndef MORECORE_CONTIGUOUS
+#define MORECORE_CONTIGUOUS 1
+#endif  /* MORECORE_CONTIGUOUS */
+#endif  /* HAVE_MORECORE */
+#ifndef DEFAULT_GRANULARITY
+#if (MORECORE_CONTIGUOUS || defined(WIN32))
+#define DEFAULT_GRANULARITY (0)  /* 0 means to compute in init_mparams */
+#else   /* MORECORE_CONTIGUOUS */
+#define DEFAULT_GRANULARITY ((size_t)64U * (size_t)1024U)
+#endif  /* MORECORE_CONTIGUOUS */
+#endif  /* DEFAULT_GRANULARITY */
+#ifndef DEFAULT_TRIM_THRESHOLD
+#ifndef MORECORE_CANNOT_TRIM
+#define DEFAULT_TRIM_THRESHOLD ((size_t)2U * (size_t)1024U * (size_t)1024U)
+#else   /* MORECORE_CANNOT_TRIM */
+#define DEFAULT_TRIM_THRESHOLD MAX_SIZE_T
+#endif  /* MORECORE_CANNOT_TRIM */
+#endif  /* DEFAULT_TRIM_THRESHOLD */
+#ifndef DEFAULT_MMAP_THRESHOLD
+#if HAVE_MMAP
+#define DEFAULT_MMAP_THRESHOLD ((size_t)256U * (size_t)1024U)
+#else   /* HAVE_MMAP */
+#define DEFAULT_MMAP_THRESHOLD MAX_SIZE_T
+#endif  /* HAVE_MMAP */
+#endif  /* DEFAULT_MMAP_THRESHOLD */
+#ifndef MAX_RELEASE_CHECK_RATE
+#if HAVE_MMAP
+#define MAX_RELEASE_CHECK_RATE 4095
+#else
+#define MAX_RELEASE_CHECK_RATE MAX_SIZE_T
+#endif /* HAVE_MMAP */
+#endif /* MAX_RELEASE_CHECK_RATE */
+#ifndef USE_BUILTIN_FFS
+#define USE_BUILTIN_FFS 0
+#endif  /* USE_BUILTIN_FFS */
+#ifndef USE_DEV_RANDOM
+#define USE_DEV_RANDOM 0
+#endif  /* USE_DEV_RANDOM */
+#ifndef NO_MALLINFO
+#define NO_MALLINFO 0
+#endif  /* NO_MALLINFO */
+#ifndef MALLINFO_FIELD_TYPE
+#define MALLINFO_FIELD_TYPE size_t
+#endif  /* MALLINFO_FIELD_TYPE */
+#ifndef NO_SEGMENT_TRAVERSAL
+#define NO_SEGMENT_TRAVERSAL 0
+#endif /* NO_SEGMENT_TRAVERSAL */
+
+/*
+  mallopt tuning options.  SVID/XPG defines four standard parameter
+  numbers for mallopt, normally defined in malloc.h.  None of these
+  are used in this malloc, so setting them has no effect. But this
+  malloc does support the following options.
+*/
+
+#define M_TRIM_THRESHOLD     (-1)
+#define M_GRANULARITY        (-2)
+#define M_MMAP_THRESHOLD     (-3)
+
+/* ------------------------ Mallinfo declarations ------------------------ */
+
+#if !NO_MALLINFO
+/*
+  This version of malloc supports the standard SVID/XPG mallinfo
+  routine that returns a struct containing usage properties and
+  statistics. It should work on any system that has a
+  /usr/include/malloc.h defining struct mallinfo.  The main
+  declaration needed is the mallinfo struct that is returned (by-copy)
+  by mallinfo().  The malloinfo struct contains a bunch of fields that
+  are not even meaningful in this version of malloc.  These fields are
+  are instead filled by mallinfo() with other numbers that might be of
+  interest.
+
+  HAVE_USR_INCLUDE_MALLOC_H should be set if you have a
+  /usr/include/malloc.h file that includes a declaration of struct
+  mallinfo.  If so, it is included; else a compliant version is
+  declared below.  These must be precisely the same for mallinfo() to
+  work.  The original SVID version of this struct, defined on most
+  systems with mallinfo, declares all fields as ints. But some others
+  define as unsigned long. If your system defines the fields using a
+  type of different width than listed here, you MUST #include your
+  system version and #define HAVE_USR_INCLUDE_MALLOC_H.
+*/
+
+/* #define HAVE_USR_INCLUDE_MALLOC_H */
+
+#ifdef HAVE_USR_INCLUDE_MALLOC_H
+#include "/usr/include/malloc.h"
+#else /* HAVE_USR_INCLUDE_MALLOC_H */
+#ifndef STRUCT_MALLINFO_DECLARED
+#define STRUCT_MALLINFO_DECLARED 1
+struct mallinfo {
+  MALLINFO_FIELD_TYPE arena;    /* non-mmapped space allocated from system */
+  MALLINFO_FIELD_TYPE ordblks;  /* number of free chunks */
+  MALLINFO_FIELD_TYPE smblks;   /* always 0 */
+  MALLINFO_FIELD_TYPE hblks;    /* always 0 */
+  MALLINFO_FIELD_TYPE hblkhd;   /* space in mmapped regions */
+  MALLINFO_FIELD_TYPE usmblks;  /* maximum total allocated space */
+  MALLINFO_FIELD_TYPE fsmblks;  /* always 0 */
+  MALLINFO_FIELD_TYPE uordblks; /* total allocated space */
+  MALLINFO_FIELD_TYPE fordblks; /* total free space */
+  MALLINFO_FIELD_TYPE keepcost; /* releasable (via malloc_trim) space */
+};
+#endif /* STRUCT_MALLINFO_DECLARED */
+#endif /* HAVE_USR_INCLUDE_MALLOC_H */
+#endif /* NO_MALLINFO */
+
+/*
+  Try to persuade compilers to inline. The most critical functions for
+  inlining are defined as macros, so these aren't used for them.
+*/
+
+#ifndef FORCEINLINE
+  #if defined(__GNUC__)
+#define FORCEINLINE __inline __attribute__ ((always_inline))
+  #elif defined(_MSC_VER)
+    #define FORCEINLINE __forceinline
+  #endif
+#endif
+#ifndef NOINLINE
+  #if defined(__GNUC__)
+    #define NOINLINE __attribute__ ((noinline))
+  #elif defined(_MSC_VER)
+    #define NOINLINE __declspec(noinline)
+  #else
+    #define NOINLINE
+  #endif
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#ifndef FORCEINLINE
+ #define FORCEINLINE inline
+#endif
+#endif /* __cplusplus */
+#ifndef FORCEINLINE
+ #define FORCEINLINE
+#endif
+
+#if !ONLY_MSPACES
+
+/* ------------------- Declarations of public routines ------------------- */
+
+#ifndef USE_DL_PREFIX
+#define dlcalloc               calloc
+#define dlfree                 free
+#define dlmalloc               malloc
+#define dlmemalign             memalign
+#define dlrealloc              realloc
+#define dlvalloc               valloc
+#define dlpvalloc              pvalloc
+#define dlmallinfo             mallinfo
+#define dlmallopt              mallopt
+#define dlmalloc_trim          malloc_trim
+#define dlmalloc_stats         malloc_stats
+#define dlmalloc_usable_size   malloc_usable_size
+#define dlmalloc_footprint     malloc_footprint
+#define dlmalloc_max_footprint malloc_max_footprint
+#define dlindependent_calloc   independent_calloc
+#define dlindependent_comalloc independent_comalloc
+#endif /* USE_DL_PREFIX */
+
+
+/*
+  malloc(size_t n)
+  Returns a pointer to a newly allocated chunk of at least n bytes, or
+  null if no space is available, in which case errno is set to ENOMEM
+  on ANSI C systems.
+
+  If n is zero, malloc returns a minimum-sized chunk. (The minimum
+  size is 16 bytes on most 32bit systems, and 32 bytes on 64bit
+  systems.)  Note that size_t is an unsigned type, so calls with
+  arguments that would be negative if signed are interpreted as
+  requests for huge amounts of space, which will often fail. The
+  maximum supported value of n differs across systems, but is in all
+  cases less than the maximum representable value of a size_t.
+*/
+void* dlmalloc(size_t);
+
+/*
+  free(void* p)
+  Releases the chunk of memory pointed to by p, that had been previously
+  allocated using malloc or a related routine such as realloc.
+  It has no effect if p is null. If p was not malloced or already
+  freed, free(p) will by default cause the current program to abort.
+*/
+void  dlfree(void*);
+
+/*
+  calloc(size_t n_elements, size_t element_size);
+  Returns a pointer to n_elements * element_size bytes, with all locations
+  set to zero.
+*/
+void* dlcalloc(size_t, size_t);
+
+/*
+  realloc(void* p, size_t n)
+  Returns a pointer to a chunk of size n that contains the same data
+  as does chunk p up to the minimum of (n, p's size) bytes, or null
+  if no space is available.
+
+  The returned pointer may or may not be the same as p. The algorithm
+  prefers extending p in most cases when possible, otherwise it
+  employs the equivalent of a malloc-copy-free sequence.
+
+  If p is null, realloc is equivalent to malloc.
+
+  If space is not available, realloc returns null, errno is set (if on
+  ANSI) and p is NOT freed.
+
+  if n is for fewer bytes than already held by p, the newly unused
+  space is lopped off and freed if possible.  realloc with a size
+  argument of zero (re)allocates a minimum-sized chunk.
+
+  The old unix realloc convention of allowing the last-free'd chunk
+  to be used as an argument to realloc is not supported.
+*/
+
+void* dlrealloc(void*, size_t);
+
+/*
+  memalign(size_t alignment, size_t n);
+  Returns a pointer to a newly allocated chunk of n bytes, aligned
+  in accord with the alignment argument.
+
+  The alignment argument should be a power of two. If the argument is
+  not a power of two, the nearest greater power is used.
+  8-byte alignment is guaranteed by normal malloc calls, so don't
+  bother calling memalign with an argument of 8 or less.
+
+  Overreliance on memalign is a sure way to fragment space.
+*/
+void* dlmemalign(size_t, size_t);
+
+/*
+  valloc(size_t n);
+  Equivalent to memalign(pagesize, n), where pagesize is the page
+  size of the system. If the pagesize is unknown, 4096 is used.
+*/
+void* dlvalloc(size_t);
+
+/*
+  mallopt(int parameter_number, int parameter_value)
+  Sets tunable parameters The format is to provide a
+  (parameter-number, parameter-value) pair.  mallopt then sets the
+  corresponding parameter to the argument value if it can (i.e., so
+  long as the value is meaningful), and returns 1 if successful else
+  0.  To workaround the fact that mallopt is specified to use int,
+  not size_t parameters, the value -1 is specially treated as the
+  maximum unsigned size_t value.
+
+  SVID/XPG/ANSI defines four standard param numbers for mallopt,
+  normally defined in malloc.h.  None of these are use in this malloc,
+  so setting them has no effect. But this malloc also supports other
+  options in mallopt. See below for details.  Briefly, supported
+  parameters are as follows (listed defaults are for "typical"
+  configurations).
+
+  Symbol            param #  default    allowed param values
+  M_TRIM_THRESHOLD     -1   2*1024*1024   any   (-1 disables)
+  M_GRANULARITY        -2     page size   any power of 2 >= page size
+  M_MMAP_THRESHOLD     -3      256*1024   any   (or 0 if no MMAP support)
+*/
+int dlmallopt(int, int);
+
+/*
+  malloc_footprint();
+  Returns the number of bytes obtained from the system.  The total
+  number of bytes allocated by malloc, realloc etc., is less than this
+  value. Unlike mallinfo, this function returns only a precomputed
+  result, so can be called frequently to monitor memory consumption.
+  Even if locks are otherwise defined, this function does not use them,
+  so results might not be up to date.
+*/
+size_t dlmalloc_footprint(void);
+
+/*
+  malloc_max_footprint();
+  Returns the maximum number of bytes obtained from the system. This
+  value will be greater than current footprint if deallocated space
+  has been reclaimed by the system. The peak number of bytes allocated
+  by malloc, realloc etc., is less than this value. Unlike mallinfo,
+  this function returns only a precomputed result, so can be called
+  frequently to monitor memory consumption.  Even if locks are
+  otherwise defined, this function does not use them, so results might
+  not be up to date.
+*/
+size_t dlmalloc_max_footprint(void);
+
+#if !NO_MALLINFO
+/*
+  mallinfo()
+  Returns (by copy) a struct containing various summary statistics:
+
+  arena:     current total non-mmapped bytes allocated from system
+  ordblks:   the number of free chunks
+  smblks:    always zero.
+  hblks:     current number of mmapped regions
+  hblkhd:    total bytes held in mmapped regions
+  usmblks:   the maximum total allocated space. This will be greater
+               than current total if trimming has occurred.
+  fsmblks:   always zero
+  uordblks:  current total allocated space (normal or mmapped)
+  fordblks:  total free space
+  keepcost:  the maximum number of bytes that could ideally be released
+              back to system via malloc_trim. ("ideally" means that
+              it ignores page restrictions etc.)
+
+  Because these fields are ints, but internal bookkeeping may
+  be kept as longs, the reported values may wrap around zero and
+  thus be inaccurate.
+*/
+struct mallinfo dlmallinfo(void);
+#endif /* NO_MALLINFO */
+
+/*
+  independent_calloc(size_t n_elements, size_t element_size, void* chunks[]);
+
+  independent_calloc is similar to calloc, but instead of returning a
+  single cleared space, it returns an array of pointers to n_elements
+  independent elements that can hold contents of size elem_size, each
+  of which starts out cleared, and can be independently freed,
+  realloc'ed etc. The elements are guaranteed to be adjacently
+  allocated (this is not guaranteed to occur with multiple callocs or
+  mallocs), which may also improve cache locality in some
+  applications.
+
+  The "chunks" argument is optional (i.e., may be null, which is
+  probably the most typical usage). If it is null, the returned array
+  is itself dynamically allocated and should also be freed when it is
+  no longer needed. Otherwise, the chunks array must be of at least
+  n_elements in length. It is filled in with the pointers to the
+  chunks.
+
+  In either case, independent_calloc returns this pointer array, or
+  null if the allocation failed.  If n_elements is zero and "chunks"
+  is null, it returns a chunk representing an array with zero elements
+  (which should be freed if not wanted).
+
+  Each element must be individually freed when it is no longer
+  needed. If you'd like to instead be able to free all at once, you
+  should instead use regular calloc and assign pointers into this
+  space to represent elements.  (In this case though, you cannot
+  independently free elements.)
+
+  independent_calloc simplifies and speeds up implementations of many
+  kinds of pools.  It may also be useful when constructing large data
+  structures that initially have a fixed number of fixed-sized nodes,
+  but the number is not known at compile time, and some of the nodes
+  may later need to be freed. For example:
+
+  struct Node { int item; struct Node* next; };
+
+  struct Node* build_list() {
+    struct Node** pool;
+    int n = read_number_of_nodes_needed();
+    if (n <= 0) return 0;
+    pool = (struct Node**)(independent_calloc(n, sizeof(struct Node), 0);
+    if (pool == 0) die();
+    // organize into a linked list...
+    struct Node* first = pool[0];
+    for (i = 0; i < n-1; ++i)
+      pool[i]->next = pool[i+1];
+    free(pool);     // Can now free the array (or not, if it is needed later)
+    return first;
+  }
+*/
+void** dlindependent_calloc(size_t, size_t, void**);
+
+/*
+  independent_comalloc(size_t n_elements, size_t sizes[], void* chunks[]);
+
+  independent_comalloc allocates, all at once, a set of n_elements
+  chunks with sizes indicated in the "sizes" array.    It returns
+  an array of pointers to these elements, each of which can be
+  independently freed, realloc'ed etc. The elements are guaranteed to
+  be adjacently allocated (this is not guaranteed to occur with
+  multiple callocs or mallocs), which may also improve cache locality
+  in some applications.
+
+  The "chunks" argument is optional (i.e., may be null). If it is null
+  the returned array is itself dynamically allocated and should also
+  be freed when it is no longer needed. Otherwise, the chunks array
+  must be of at least n_elements in length. It is filled in with the
+  pointers to the chunks.
+
+  In either case, independent_comalloc returns this pointer array, or
+  null if the allocation failed.  If n_elements is zero and chunks is
+  null, it returns a chunk representing an array with zero elements
+  (which should be freed if not wanted).
+
+  Each element must be individually freed when it is no longer
+  needed. If you'd like to instead be able to free all at once, you
+  should instead use a single regular malloc, and assign pointers at
+  particular offsets in the aggregate space. (In this case though, you
+  cannot independently free elements.)
+
+  independent_comallac differs from independent_calloc in that each
+  element may have a different size, and also that it does not
+  automatically clear elements.
+
+  independent_comalloc can be used to speed up allocation in cases
+  where several structs or objects must always be allocated at the
+  same time.  For example:
+
+  struct Head { ... }
+  struct Foot { ... }
+
+  void send_message(char* msg) {
+    int msglen = strlen(msg);
+    size_t sizes[3] = { sizeof(struct Head), msglen, sizeof(struct Foot) };
+    void* chunks[3];
+    if (independent_comalloc(3, sizes, chunks) == 0)
+      die();
+    struct Head* head = (struct Head*)(chunks[0]);
+    char*        body = (char*)(chunks[1]);
+    struct Foot* foot = (struct Foot*)(chunks[2]);
+    // ...
+  }
+
+  In general though, independent_comalloc is worth using only for
+  larger values of n_elements. For small values, you probably won't
+  detect enough difference from series of malloc calls to bother.
+
+  Overuse of independent_comalloc can increase overall memory usage,
+  since it cannot reuse existing noncontiguous small chunks that
+  might be available for some of the elements.
+*/
+void** dlindependent_comalloc(size_t, size_t*, void**);
+
+
+/*
+  pvalloc(size_t n);
+  Equivalent to valloc(minimum-page-that-holds(n)), that is,
+  round up n to nearest pagesize.
+ */
+void*  dlpvalloc(size_t);
+
+/*
+  malloc_trim(size_t pad);
+
+  If possible, gives memory back to the system (via negative arguments
+  to sbrk) if there is unused memory at the `high' end of the malloc
+  pool or in unused MMAP segments. You can call this after freeing
+  large blocks of memory to potentially reduce the system-level memory
+  requirements of a program. However, it cannot guarantee to reduce
+  memory. Under some allocation patterns, some large free blocks of
+  memory will be locked between two used chunks, so they cannot be
+  given back to the system.
+
+  The `pad' argument to malloc_trim represents the amount of free
+  trailing space to leave untrimmed. If this argument is zero, only
+  the minimum amount of memory to maintain internal data structures
+  will be left. Non-zero arguments can be supplied to maintain enough
+  trailing space to service future expected allocations without having
+  to re-obtain memory from the system.
+
+  Malloc_trim returns 1 if it actually released any memory, else 0.
+*/
+int  dlmalloc_trim(size_t);
+
+/*
+  malloc_stats();
+  Prints on stderr the amount of space obtained from the system (both
+  via sbrk and mmap), the maximum amount (which may be more than
+  current if malloc_trim and/or munmap got called), and the current
+  number of bytes allocated via malloc (or realloc, etc) but not yet
+  freed. Note that this is the number of bytes allocated, not the
+  number requested. It will be larger than the number requested
+  because of alignment and bookkeeping overhead. Because it includes
+  alignment wastage as being in use, this figure may be greater than
+  zero even when no user-level chunks are allocated.
+
+  The reported current and maximum system memory can be inaccurate if
+  a program makes other calls to system memory allocation functions
+  (normally sbrk) outside of malloc.
+
+  malloc_stats prints only the most commonly interesting statistics.
+  More information can be obtained by calling mallinfo.
+*/
+void  dlmalloc_stats(void);
+
+#endif /* ONLY_MSPACES */
+
+/*
+  malloc_usable_size(void* p);
+
+  Returns the number of bytes you can actually use in
+  an allocated chunk, which may be more than you requested (although
+  often not) due to alignment and minimum size constraints.
+  You can use this many bytes without worrying about
+  overwriting other allocated objects. This is not a particularly great
+  programming practice. malloc_usable_size can be more useful in
+  debugging and assertions, for example:
+
+  p = malloc(n);
+  assert(malloc_usable_size(p) >= 256);
+*/
+size_t dlmalloc_usable_size(void*);
+
+
+#if MSPACES
+
+/*
+  mspace is an opaque type representing an independent
+  region of space that supports mspace_malloc, etc.
+*/
+typedef void* mspace;
+
+/*
+  create_mspace creates and returns a new independent space with the
+  given initial capacity, or, if 0, the default granularity size.  It
+  returns null if there is no system memory available to create the
+  space.  If argument locked is non-zero, the space uses a separate
+  lock to control access. The capacity of the space will grow
+  dynamically as needed to service mspace_malloc requests.  You can
+  control the sizes of incremental increases of this space by
+  compiling with a different DEFAULT_GRANULARITY or dynamically
+  setting with mallopt(M_GRANULARITY, value).
+*/
+mspace create_mspace(size_t capacity, int locked);
+
+/*
+  destroy_mspace destroys the given space, and attempts to return all
+  of its memory back to the system, returning the total number of
+  bytes freed. After destruction, the results of access to all memory
+  used by the space become undefined.
+*/
+size_t destroy_mspace(mspace msp);
+
+/*
+  create_mspace_with_base uses the memory supplied as the initial base
+  of a new mspace. Part (less than 128*sizeof(size_t) bytes) of this
+  space is used for bookkeeping, so the capacity must be at least this
+  large. (Otherwise 0 is returned.) When this initial space is
+  exhausted, additional memory will be obtained from the system.
+  Destroying this space will deallocate all additionally allocated
+  space (if possible) but not the initial base.
+*/
+mspace create_mspace_with_base(void* base, size_t capacity, int locked);
+
+/*
+  mspace_mmap_large_chunks controls whether requests for large chunks
+  are allocated in their own mmapped regions, separate from others in
+  this mspace. By default this is enabled, which reduces
+  fragmentation. However, such chunks are not necessarily released to
+  the system upon destroy_mspace.  Disabling by setting to false may
+  increase fragmentation, but avoids leakage when relying on
+  destroy_mspace to release all memory allocated using this space.
+*/
+int mspace_mmap_large_chunks(mspace msp, int enable);
+
+
+/*
+  mspace_malloc behaves as malloc, but operates within
+  the given space.
+*/
+void* mspace_malloc(mspace msp, size_t bytes);
+
+/*
+  mspace_free behaves as free, but operates within
+  the given space.
+
+  If compiled with FOOTERS==1, mspace_free is not actually needed.
+  free may be called instead of mspace_free because freed chunks from
+  any space are handled by their originating spaces.
+*/
+void mspace_free(mspace msp, void* mem);
+
+/*
+  mspace_realloc behaves as realloc, but operates within
+  the given space.
+
+  If compiled with FOOTERS==1, mspace_realloc is not actually
+  needed.  realloc may be called instead of mspace_realloc because
+  realloced chunks from any space are handled by their originating
+  spaces.
+*/
+void* mspace_realloc(mspace msp, void* mem, size_t newsize);
+
+/*
+  mspace_calloc behaves as calloc, but operates within
+  the given space.
+*/
+void* mspace_calloc(mspace msp, size_t n_elements, size_t elem_size);
+
+/*
+  mspace_memalign behaves as memalign, but operates within
+  the given space.
+*/
+void* mspace_memalign(mspace msp, size_t alignment, size_t bytes);
+
+/*
+  mspace_independent_calloc behaves as independent_calloc, but
+  operates within the given space.
+*/
+void** mspace_independent_calloc(mspace msp, size_t n_elements,
+                                size_t elem_size, void* chunks[]);
+
+/*
+  mspace_independent_comalloc behaves as independent_comalloc, but
+  operates within the given space.
+*/
+void** mspace_independent_comalloc(mspace msp, size_t n_elements,
+                                  size_t sizes[], void* chunks[]);
+
+/*
+  mspace_footprint() returns the number of bytes obtained from the
+  system for this space.
+*/
+size_t mspace_footprint(mspace msp);
+
+/*
+  mspace_max_footprint() returns the peak number of bytes obtained from the
+  system for this space.
+*/
+size_t mspace_max_footprint(mspace msp);
+
+
+#if !NO_MALLINFO
+/*
+  mspace_mallinfo behaves as mallinfo, but reports properties of
+  the given space.
+*/
+struct mallinfo mspace_mallinfo(mspace msp);
+#endif /* NO_MALLINFO */
+
+/*
+  malloc_usable_size(void* p) behaves the same as malloc_usable_size;
+*/
+  size_t mspace_usable_size(void* mem);
+
+/*
+  mspace_malloc_stats behaves as malloc_stats, but reports
+  properties of the given space.
+*/
+void mspace_malloc_stats(mspace msp);
+
+/*
+  mspace_trim behaves as malloc_trim, but
+  operates within the given space.
+*/
+int mspace_trim(mspace msp, size_t pad);
+
+/*
+  An alias for mallopt.
+*/
+int mspace_mallopt(int, int);
+
+#endif /* MSPACES */
+
+#ifdef __cplusplus
+};  /* end of extern "C" */
+#endif /* __cplusplus */
+
+/*
+  ========================================================================
+  To make a fully customizable malloc.h header file, cut everything
+  above this line, put into file malloc.h, edit to suit, and #include it
+  on the next line, as well as in programs that use this malloc.
+  ========================================================================
+*/
+
+/* #include "malloc.h" */
+
+/*------------------------------ internal #includes ---------------------- */
+
+#ifdef WIN32
+#ifndef __GNUC__
+#pragma warning( disable : 4146 ) /* no "unsigned" warnings */
+#endif
+#endif /* WIN32 */
+
+#include <stdio.h>       /* for printing in malloc_stats */
+
+#ifndef LACKS_ERRNO_H
+#include <errno.h>       /* for MALLOC_FAILURE_ACTION */
+#endif /* LACKS_ERRNO_H */
+#if FOOTERS
+#include <time.h>        /* for magic initialization */
+#endif /* FOOTERS */
+#ifndef LACKS_STDLIB_H
+#include <stdlib.h>      /* for abort() */
+#endif /* LACKS_STDLIB_H */
+#ifdef DEBUG
+#if ABORT_ON_ASSERT_FAILURE
+#define assert(x) if(!(x)) ABORT
+#else /* ABORT_ON_ASSERT_FAILURE */
+#include <assert.h>
+#endif /* ABORT_ON_ASSERT_FAILURE */
+#else  /* DEBUG */
+#ifndef assert
+#define assert(x)
+#endif
+#define DEBUG 0
+#endif /* DEBUG */
+#ifndef LACKS_STRING_H
+#include <string.h>      /* for memset etc */
+#endif  /* LACKS_STRING_H */
+#if USE_BUILTIN_FFS
+#ifndef LACKS_STRINGS_H
+#include <strings.h>     /* for ffs */
+#endif /* LACKS_STRINGS_H */
+#endif /* USE_BUILTIN_FFS */
+#if HAVE_MMAP
+#ifndef LACKS_SYS_MMAN_H
+#include <sys/mman.h>    /* for mmap */
+#endif /* LACKS_SYS_MMAN_H */
+#ifndef LACKS_FCNTL_H
+#include <fcntl.h>
+#endif /* LACKS_FCNTL_H */
+#endif /* HAVE_MMAP */
+#ifndef LACKS_UNISTD_H
+#include <unistd.h>     /* for sbrk, sysconf */
+#else /* LACKS_UNISTD_H */
+#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__)
+extern void*     sbrk(ptrdiff_t);
+#endif /* FreeBSD etc */
+#endif /* LACKS_UNISTD_H */
+
+/* Declarations for locking */
+#if USE_LOCKS
+#ifndef WIN32
+#include <pthread.h>
+#if defined (__SVR4) && defined (__sun)  /* solaris */
+#include <thread.h>
+#endif /* solaris */
+#else
+#ifndef _M_AMD64
+/* These are already defined on AMD64 builds */
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+#ifndef __MINGW32__
+LONG __cdecl _InterlockedCompareExchange(LONG volatile *Dest, LONG Exchange, LONG Comp);
+LONG __cdecl _InterlockedExchange(LONG volatile *Target, LONG Value);
+#endif
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* _M_AMD64 */
+#ifndef __MINGW32__
+#pragma intrinsic (_InterlockedCompareExchange)
+#pragma intrinsic (_InterlockedExchange)
+#else
+  /* --[ start GCC compatibility ]----------------------------------------------
+   * Compatibility <intrin_x86.h> header for GCC -- GCC equivalents of intrinsic
+   * Microsoft Visual C++ functions. Originally developed for the ReactOS
+   * (<http://www.reactos.org/>) and TinyKrnl (<http://www.tinykrnl.org/>)
+   * projects.
+   *
+   * Copyright (c) 2006 KJK::Hyperion <hackbunny@reactos.com>
+   *
+   * Permission is hereby granted, free of charge, to any person obtaining a
+   * copy of this software and associated documentation files (the "Software"),
+   * to deal in the Software without restriction, including without limitation
+   * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+   * and/or sell copies of the Software, and to permit persons to whom the
+   * Software is furnished to do so, subject to the following conditions:
+   *
+   * The above copyright notice and this permission notice shall be included in
+   * all copies or substantial portions of the Software.
+   *
+   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+   * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+   * DEALINGS IN THE SOFTWARE.
+   */
+
+  /*** Atomic operations ***/
+  #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) > 40100
+    #define _ReadWriteBarrier() __sync_synchronize()
+  #else
+    static __inline__ __attribute__((always_inline)) long __sync_lock_test_and_set(volatile long * const Target, const long Value)
+    {
+      long res;
+      __asm__ __volatile__("xchg%z0 %2, %0" : "=g" (*(Target)), "=r" (res) : "1" (Value));
+      return res;
+    }
+    static void __inline__ __attribute__((always_inline)) _MemoryBarrier(void)
+    {
+      __asm__ __volatile__("" : : : "memory");
+    }
+    #define _ReadWriteBarrier() _MemoryBarrier()
+  #endif
+  /* BUGBUG: GCC only supports full barriers */
+  static __inline__ __attribute__((always_inline)) long _InterlockedExchange(volatile long * const Target, const long Value)
+  {
+    /* NOTE: __sync_lock_test_and_set would be an acquire barrier, so we force a full barrier */
+    _ReadWriteBarrier();
+    return __sync_lock_test_and_set(Target, Value);
+  }
+  /* --[ end GCC compatibility ]---------------------------------------------- */
+#endif
+#define interlockedcompareexchange _InterlockedCompareExchange
+#define interlockedexchange _InterlockedExchange
+#endif /* Win32 */
+#endif /* USE_LOCKS */
+
+/* Declarations for bit scanning on win32 */
+#if defined(_MSC_VER) && _MSC_VER>=1300
+#ifndef BitScanForward /* Try to avoid pulling in WinNT.h */
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+unsigned char _BitScanForward(unsigned long *index, unsigned long mask);
+unsigned char _BitScanReverse(unsigned long *index, unsigned long mask);
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#define BitScanForward _BitScanForward
+#define BitScanReverse _BitScanReverse
+#pragma intrinsic(_BitScanForward)
+#pragma intrinsic(_BitScanReverse)
+#endif /* BitScanForward */
+#endif /* defined(_MSC_VER) && _MSC_VER>=1300 */
+
+#ifndef WIN32
+#ifndef malloc_getpagesize
+#  ifdef _SC_PAGESIZE         /* some SVR4 systems omit an underscore */
+#    ifndef _SC_PAGE_SIZE
+#      define _SC_PAGE_SIZE _SC_PAGESIZE
+#    endif
+#  endif
+#  ifdef _SC_PAGE_SIZE
+#    define malloc_getpagesize sysconf(_SC_PAGE_SIZE)
+#  else
+#    if defined(BSD) || defined(DGUX) || defined(HAVE_GETPAGESIZE)
+       extern size_t getpagesize();
+#      define malloc_getpagesize getpagesize()
+#    else
+#      ifdef WIN32 /* use supplied emulation of getpagesize */
+#        define malloc_getpagesize getpagesize()
+#      else
+#        ifndef LACKS_SYS_PARAM_H
+#          include <sys/param.h>
+#        endif
+#        ifdef EXEC_PAGESIZE
+#          define malloc_getpagesize EXEC_PAGESIZE
+#        else
+#          ifdef NBPG
+#            ifndef CLSIZE
+#              define malloc_getpagesize NBPG
+#            else
+#              define malloc_getpagesize (NBPG * CLSIZE)
+#            endif
+#          else
+#            ifdef NBPC
+#              define malloc_getpagesize NBPC
+#            else
+#              ifdef PAGESIZE
+#                define malloc_getpagesize PAGESIZE
+#              else /* just guess */
+#                define malloc_getpagesize ((size_t)4096U)
+#              endif
+#            endif
+#          endif
+#        endif
+#      endif
+#    endif
+#  endif
+#endif
+#endif
+
+
+
+/* ------------------- size_t and alignment properties -------------------- */
+
+/* The byte and bit size of a size_t */
+#define SIZE_T_SIZE         (sizeof(size_t))
+#define SIZE_T_BITSIZE      (sizeof(size_t) << 3)
+
+/* Some constants coerced to size_t */
+/* Annoying but necessary to avoid errors on some platforms */
+#define SIZE_T_ZERO         ((size_t)0)
+#define SIZE_T_ONE          ((size_t)1)
+#define SIZE_T_TWO          ((size_t)2)
+#define SIZE_T_FOUR         ((size_t)4)
+#define TWO_SIZE_T_SIZES    (SIZE_T_SIZE<<1)
+#define FOUR_SIZE_T_SIZES   (SIZE_T_SIZE<<2)
+#define SIX_SIZE_T_SIZES    (FOUR_SIZE_T_SIZES+TWO_SIZE_T_SIZES)
+#define HALF_MAX_SIZE_T     (MAX_SIZE_T / 2U)
+
+/* The bit mask value corresponding to MALLOC_ALIGNMENT */
+#define CHUNK_ALIGN_MASK    (MALLOC_ALIGNMENT - SIZE_T_ONE)
+
+/* True if address a has acceptable alignment */
+#define is_aligned(A)       (((size_t)((A)) & (CHUNK_ALIGN_MASK)) == 0)
+
+/* the number of bytes to offset an address to align it */
+#define align_offset(A)\
+ ((((size_t)(A) & CHUNK_ALIGN_MASK) == 0)? 0 :\
+  ((MALLOC_ALIGNMENT - ((size_t)(A) & CHUNK_ALIGN_MASK)) & CHUNK_ALIGN_MASK))
+
+/* -------------------------- MMAP preliminaries ------------------------- */
+
+/*
+   If HAVE_MORECORE or HAVE_MMAP are false, we just define calls and
+   checks to fail so compiler optimizer can delete code rather than
+   using so many "#if"s.
+*/
+
+
+/* MORECORE and MMAP must return MFAIL on failure */
+#define MFAIL                ((void*)(MAX_SIZE_T))
+#define CMFAIL               ((char*)(MFAIL)) /* defined for convenience */
+
+#if HAVE_MMAP
+
+#ifndef WIN32
+#define MUNMAP_DEFAULT(a, s)  munmap((a), (s))
+#define MMAP_PROT            (PROT_READ|PROT_WRITE)
+#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
+#define MAP_ANONYMOUS        MAP_ANON
+#endif /* MAP_ANON */
+#ifdef MAP_ANONYMOUS
+#define MMAP_FLAGS           (MAP_PRIVATE|MAP_ANONYMOUS)
+#define MMAP_DEFAULT(s)       mmap(0, (s), MMAP_PROT, MMAP_FLAGS, -1, 0)
+#else /* MAP_ANONYMOUS */
+/*
+   Nearly all versions of mmap support MAP_ANONYMOUS, so the following
+   is unlikely to be needed, but is supplied just in case.
+*/
+#define MMAP_FLAGS           (MAP_PRIVATE)
+static int dev_zero_fd = -1; /* Cached file descriptor for /dev/zero. */
+#define MMAP_DEFAULT(s) ((dev_zero_fd < 0) ? \
+          (dev_zero_fd = open("/dev/zero", O_RDWR), \
+           mmap(0, (s), MMAP_PROT, MMAP_FLAGS, dev_zero_fd, 0)) : \
+           mmap(0, (s), MMAP_PROT, MMAP_FLAGS, dev_zero_fd, 0))
+#endif /* MAP_ANONYMOUS */
+
+#define DIRECT_MMAP_DEFAULT(s) MMAP_DEFAULT(s)
+
+#else /* WIN32 */
+
+/* Win32 MMAP via VirtualAlloc */
+static FORCEINLINE void* win32mmap(size_t size) {
+  void* ptr = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
+  return (ptr != 0)? ptr: MFAIL;
+}
+
+/* For direct MMAP, use MEM_TOP_DOWN to minimize interference */
+static FORCEINLINE void* win32direct_mmap(size_t size) {
+  void* ptr = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN,
+                          PAGE_READWRITE);
+  return (ptr != 0)? ptr: MFAIL;
+}
+
+/* This function supports releasing coalesed segments */
+static FORCEINLINE int win32munmap(void* ptr, size_t size) {
+  MEMORY_BASIC_INFORMATION minfo;
+  char* cptr = (char*)ptr;
+  while (size) {
+    if (VirtualQuery(cptr, &minfo, sizeof(minfo)) == 0)
+      return -1;
+    if (minfo.BaseAddress != cptr || minfo.AllocationBase != cptr ||
+       minfo.State != MEM_COMMIT || minfo.RegionSize > size)
+      return -1;
+    if (VirtualFree(cptr, 0, MEM_RELEASE) == 0)
+      return -1;
+    cptr += minfo.RegionSize;
+    size -= minfo.RegionSize;
+  }
+  return 0;
+}
+
+#define MMAP_DEFAULT(s)             win32mmap(s)
+#define MUNMAP_DEFAULT(a, s)        win32munmap((a), (s))
+#define DIRECT_MMAP_DEFAULT(s)      win32direct_mmap(s)
+#endif /* WIN32 */
+#endif /* HAVE_MMAP */
+
+#if HAVE_MREMAP
+#ifndef WIN32
+#define MREMAP_DEFAULT(addr, osz, nsz, mv) mremap((addr), (osz), (nsz), (mv))
+#endif /* WIN32 */
+#endif /* HAVE_MREMAP */
+
+
+/**
+ * Define CALL_MORECORE
+ */
+#if HAVE_MORECORE
+    #ifdef MORECORE
+       #define CALL_MORECORE(S)    MORECORE(S)
+    #else  /* MORECORE */
+       #define CALL_MORECORE(S)    MORECORE_DEFAULT(S)
+    #endif /* MORECORE */
+#else  /* HAVE_MORECORE */
+    #define CALL_MORECORE(S)        MFAIL
+#endif /* HAVE_MORECORE */
+
+/**
+ * Define CALL_MMAP/CALL_MUNMAP/CALL_DIRECT_MMAP
+ */
+#if HAVE_MMAP
+    #define IS_MMAPPED_BIT          (SIZE_T_ONE)
+    #define USE_MMAP_BIT            (SIZE_T_ONE)
+
+    #ifdef MMAP
+       #define CALL_MMAP(s)        MMAP(s)
+    #else /* MMAP */
+       #define CALL_MMAP(s)        MMAP_DEFAULT(s)
+    #endif /* MMAP */
+    #ifdef MUNMAP
+       #define CALL_MUNMAP(a, s)   MUNMAP((a), (s))
+    #else /* MUNMAP */
+       #define CALL_MUNMAP(a, s)   MUNMAP_DEFAULT((a), (s))
+    #endif /* MUNMAP */
+    #ifdef DIRECT_MMAP
+       #define CALL_DIRECT_MMAP(s) DIRECT_MMAP(s)
+    #else /* DIRECT_MMAP */
+       #define CALL_DIRECT_MMAP(s) DIRECT_MMAP_DEFAULT(s)
+    #endif /* DIRECT_MMAP */
+#else  /* HAVE_MMAP */
+    #define IS_MMAPPED_BIT          (SIZE_T_ZERO)
+    #define USE_MMAP_BIT            (SIZE_T_ZERO)
+
+    #define MMAP(s)                 MFAIL
+    #define MUNMAP(a, s)            (-1)
+    #define DIRECT_MMAP(s)          MFAIL
+    #define CALL_DIRECT_MMAP(s)     DIRECT_MMAP(s)
+    #define CALL_MMAP(s)            MMAP(s)
+    #define CALL_MUNMAP(a, s)       MUNMAP((a), (s))
+#endif /* HAVE_MMAP */
+
+/**
+ * Define CALL_MREMAP
+ */
+#if HAVE_MMAP && HAVE_MREMAP
+    #ifdef MREMAP
+       #define CALL_MREMAP(addr, osz, nsz, mv) MREMAP((addr), (osz), (nsz), (mv))
+    #else /* MREMAP */
+       #define CALL_MREMAP(addr, osz, nsz, mv) MREMAP_DEFAULT((addr), (osz), (nsz), (mv))
+    #endif /* MREMAP */
+#else  /* HAVE_MMAP && HAVE_MREMAP */
+    #define CALL_MREMAP(addr, osz, nsz, mv)     MFAIL
+#endif /* HAVE_MMAP && HAVE_MREMAP */
+
+/* mstate bit set if continguous morecore disabled or failed */
+#define USE_NONCONTIGUOUS_BIT (4U)
+
+/* segment bit set in create_mspace_with_base */
+#define EXTERN_BIT            (8U)
+
+
+/* --------------------------- Lock preliminaries ------------------------ */
+
+/*
+  When locks are defined, there is one global lock, plus
+  one per-mspace lock.
+
+  The global lock_ensures that mparams.magic and other unique
+  mparams values are initialized only once. It also protects
+  sequences of calls to MORECORE.  In many cases sys_alloc requires
+  two calls, that should not be interleaved with calls by other
+  threads.  This does not protect against direct calls to MORECORE
+  by other threads not using this lock, so there is still code to
+  cope the best we can on interference.
+
+  Per-mspace locks surround calls to malloc, free, etc.  To enable use
+  in layered extensions, per-mspace locks are reentrant.
+
+  Because lock-protected regions generally have bounded times, it is
+  OK to use the supplied simple spinlocks in the custom versions for
+  x86.
+
+  If USE_LOCKS is > 1, the definitions of lock routines here are
+  bypassed, in which case you will need to define at least
+  INITIAL_LOCK, ACQUIRE_LOCK, RELEASE_LOCK and possibly TRY_LOCK
+  (which is not used in this malloc, but commonly needed in
+  extensions.)
+*/
+
+#if USE_LOCKS == 1
+
+#if USE_SPIN_LOCKS
+#ifndef WIN32
+
+/* Custom pthread-style spin locks on x86 and x64 for gcc */
+struct pthread_mlock_t {
+  volatile unsigned int l;
+  volatile unsigned int c;
+  volatile pthread_t threadid;
+};
+#define MLOCK_T struct        pthread_mlock_t
+#define CURRENT_THREAD        pthread_self()
+#define INITIAL_LOCK(sl)      (memset(sl, 0, sizeof(MLOCK_T)), 0)
+#define ACQUIRE_LOCK(sl)      pthread_acquire_lock(sl)
+#define RELEASE_LOCK(sl)      pthread_release_lock(sl)
+#define TRY_LOCK(sl)          pthread_try_lock(sl)
+#define SPINS_PER_YIELD       63
+
+static MLOCK_T malloc_global_mutex = { 0, 0, 0};
+
+static FORCEINLINE int pthread_acquire_lock (MLOCK_T *sl) {
+  int spins = 0;
+  volatile unsigned int* lp = &sl->l;
+  for (;;) {
+    if (*lp != 0) {
+      if (sl->threadid == CURRENT_THREAD) {
+       ++sl->c;
+       return 0;
+      }
+    }
+    else {
+      /* place args to cmpxchgl in locals to evade oddities in some gccs */
+      int cmp = 0;
+      int val = 1;
+      int ret;
+      __asm__ __volatile__  ("lock; cmpxchgl %1, %2"
+                            : "=a" (ret)
+                            : "r" (val), "m" (*(lp)), "0"(cmp)
+                            : "memory", "cc");
+      if (!ret) {
+       assert(!sl->threadid);
+       sl->c = 1;
+       sl->threadid = CURRENT_THREAD;
+       return 0;
+      }
+      if ((++spins & SPINS_PER_YIELD) == 0) {
+#if defined (__SVR4) && defined (__sun) /* solaris */
+       thr_yield();
+#else
+#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__)
+       sched_yield();
+#else  /* no-op yield on unknown systems */
+       ;
+#endif /* __linux__ || __FreeBSD__ || __APPLE__ */
+#endif /* solaris */
+      }
+    }
+  }
+}
+
+static FORCEINLINE void pthread_release_lock (MLOCK_T *sl) {
+  assert(sl->l != 0);
+  assert(sl->threadid == CURRENT_THREAD);
+  if (--sl->c == 0) {
+    sl->threadid = 0;
+    volatile unsigned int* lp = &sl->l;
+    int prev = 0;
+    int ret;
+    __asm__ __volatile__ ("lock; xchgl %0, %1"
+                         : "=r" (ret)
+                         : "m" (*(lp)), "0"(prev)
+                         : "memory");
+  }
+}
+
+static FORCEINLINE int pthread_try_lock (MLOCK_T *sl) {
+  volatile unsigned int* lp = &sl->l;
+  if (*lp != 0) {
+      if (sl->threadid == CURRENT_THREAD) {
+       ++sl->c;
+       return 1;
+      }
+  }
+  else {
+    int cmp = 0;
+    int val = 1;
+    int ret;
+    __asm__ __volatile__  ("lock; cmpxchgl %1, %2"
+                          : "=a" (ret)
+                          : "r" (val), "m" (*(lp)), "0"(cmp)
+                          : "memory", "cc");
+    if (!ret) {
+      assert(!sl->threadid);
+      sl->c = 1;
+      sl->threadid = CURRENT_THREAD;
+      return 1;
+    }
+  }
+  return 0;
+}
+
+
+#else /* WIN32 */
+/* Custom win32-style spin locks on x86 and x64 for MSC */
+struct win32_mlock_t
+{
+  volatile long l;
+  volatile unsigned int c;
+  volatile long threadid;
+};
+
+#define MLOCK_T               struct win32_mlock_t
+#define CURRENT_THREAD        win32_getcurrentthreadid()
+#define INITIAL_LOCK(sl)      (memset(sl, 0, sizeof(MLOCK_T)), 0)
+#define ACQUIRE_LOCK(sl)      win32_acquire_lock(sl)
+#define RELEASE_LOCK(sl)      win32_release_lock(sl)
+#define TRY_LOCK(sl)          win32_try_lock(sl)
+#define SPINS_PER_YIELD       63
+
+static MLOCK_T malloc_global_mutex = { 0, 0, 0};
+
+static FORCEINLINE long win32_getcurrentthreadid() {
+#ifdef _MSC_VER
+#if defined(_M_IX86)
+  long *threadstruct=(long *)__readfsdword(0x18);
+  long threadid=threadstruct[0x24/sizeof(long)];
+  return threadid;
+#elif defined(_M_X64)
+  /* todo */
+  return GetCurrentThreadId();
+#else
+  return GetCurrentThreadId();
+#endif
+#else
+  return GetCurrentThreadId();
+#endif
+}
+
+static FORCEINLINE int win32_acquire_lock (MLOCK_T *sl) {
+  int spins = 0;
+  for (;;) {
+    if (sl->l != 0) {
+      if (sl->threadid == CURRENT_THREAD) {
+       ++sl->c;
+       return 0;
+      }
+    }
+    else {
+      if (!interlockedexchange(&sl->l, 1)) {
+       assert(!sl->threadid);
+               sl->c=CURRENT_THREAD;
+       sl->threadid = CURRENT_THREAD;
+       sl->c = 1;
+       return 0;
+      }
+    }
+    if ((++spins & SPINS_PER_YIELD) == 0)
+      SleepEx(0, FALSE);
+  }
+}
+
+static FORCEINLINE void win32_release_lock (MLOCK_T *sl) {
+  assert(sl->threadid == CURRENT_THREAD);
+  assert(sl->l != 0);
+  if (--sl->c == 0) {
+    sl->threadid = 0;
+    interlockedexchange (&sl->l, 0);
+  }
+}
+
+static FORCEINLINE int win32_try_lock (MLOCK_T *sl) {
+  if(sl->l != 0) {
+      if (sl->threadid == CURRENT_THREAD) {
+       ++sl->c;
+       return 1;
+      }
+  }
+  else {
+    if (!interlockedexchange(&sl->l, 1)){
+      assert(!sl->threadid);
+      sl->threadid = CURRENT_THREAD;
+      sl->c = 1;
+      return 1;
+    }
+  }
+  return 0;
+}
+
+#endif /* WIN32 */
+#else /* USE_SPIN_LOCKS */
+
+#ifndef WIN32
+/* pthreads-based locks */
+
+#define MLOCK_T               pthread_mutex_t
+#define CURRENT_THREAD        pthread_self()
+#define INITIAL_LOCK(sl)      pthread_init_lock(sl)
+#define ACQUIRE_LOCK(sl)      pthread_mutex_lock(sl)
+#define RELEASE_LOCK(sl)      pthread_mutex_unlock(sl)
+#define TRY_LOCK(sl)          (!pthread_mutex_trylock(sl))
+
+static MLOCK_T malloc_global_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/* Cope with old-style linux recursive lock initialization by adding */
+/* skipped internal declaration from pthread.h */
+#ifdef linux
+#ifndef PTHREAD_MUTEX_RECURSIVE
+extern int pthread_mutexattr_setkind_np __P ((pthread_mutexattr_t *__attr,
+                                          int __kind));
+#define PTHREAD_MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE_NP
+#define pthread_mutexattr_settype(x,y) pthread_mutexattr_setkind_np(x,y)
+#endif
+#endif
+
+static int pthread_init_lock (MLOCK_T *sl) {
+  pthread_mutexattr_t attr;
+  if (pthread_mutexattr_init(&attr)) return 1;
+  if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) return 1;
+  if (pthread_mutex_init(sl, &attr)) return 1;
+  if (pthread_mutexattr_destroy(&attr)) return 1;
+  return 0;
+}
+
+#else /* WIN32 */
+/* Win32 critical sections */
+#define MLOCK_T               CRITICAL_SECTION
+#define CURRENT_THREAD        GetCurrentThreadId()
+#define INITIAL_LOCK(s)       (!InitializeCriticalSectionAndSpinCount((s), 0x80000000|4000))
+#define ACQUIRE_LOCK(s)       (EnterCriticalSection(s), 0)
+#define RELEASE_LOCK(s)       LeaveCriticalSection(s)
+#define TRY_LOCK(s)           TryEnterCriticalSection(s)
+#define NEED_GLOBAL_LOCK_INIT
+
+static MLOCK_T malloc_global_mutex;
+static volatile long malloc_global_mutex_status;
+
+/* Use spin loop to initialize global lock */
+static void init_malloc_global_mutex() {
+  for (;;) {
+    long stat = malloc_global_mutex_status;
+    if (stat > 0)
+      return;
+    /* transition to < 0 while initializing, then to > 0) */
+    if (stat == 0 &&
+       interlockedcompareexchange(&malloc_global_mutex_status, -1, 0) == 0) {
+      InitializeCriticalSection(&malloc_global_mutex);
+      interlockedexchange(&malloc_global_mutex_status,1);
+      return;
+    }
+    SleepEx(0, FALSE);
+  }
+}
+
+#endif /* WIN32 */
+#endif /* USE_SPIN_LOCKS */
+#endif /* USE_LOCKS == 1 */
+
+/* -----------------------  User-defined locks ------------------------ */
+
+#if USE_LOCKS > 1
+/* Define your own lock implementation here */
+/* #define INITIAL_LOCK(sl)  ... */
+/* #define ACQUIRE_LOCK(sl)  ... */
+/* #define RELEASE_LOCK(sl)  ... */
+/* #define TRY_LOCK(sl) ... */
+/* static MLOCK_T malloc_global_mutex = ... */
+#endif /* USE_LOCKS > 1 */
+
+/* -----------------------  Lock-based state ------------------------ */
+
+#if USE_LOCKS
+#define USE_LOCK_BIT               (2U)
+#else  /* USE_LOCKS */
+#define USE_LOCK_BIT               (0U)
+#define INITIAL_LOCK(l)
+#endif /* USE_LOCKS */
+
+#if USE_LOCKS
+#define ACQUIRE_MALLOC_GLOBAL_LOCK()  ACQUIRE_LOCK(&malloc_global_mutex);
+#define RELEASE_MALLOC_GLOBAL_LOCK()  RELEASE_LOCK(&malloc_global_mutex);
+#else  /* USE_LOCKS */
+#define ACQUIRE_MALLOC_GLOBAL_LOCK()
+#define RELEASE_MALLOC_GLOBAL_LOCK()
+#endif /* USE_LOCKS */
+
+
+/* -----------------------  Chunk representations ------------------------ */
+
+/*
+  (The following includes lightly edited explanations by Colin Plumb.)
+
+  The malloc_chunk declaration below is misleading (but accurate and
+  necessary).  It declares a "view" into memory allowing access to
+  necessary fields at known offsets from a given base.
+
+  Chunks of memory are maintained using a `boundary tag' method as
+  originally described by Knuth.  (See the paper by Paul Wilson
+  ftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps for a survey of such
+  techniques.)  Sizes of free chunks are stored both in the front of
+  each chunk and at the end.  This makes consolidating fragmented
+  chunks into bigger chunks fast.  The head fields also hold bits
+  representing whether chunks are free or in use.
+
+  Here are some pictures to make it clearer.  They are "exploded" to
+  show that the state of a chunk can be thought of as extending from
+  the high 31 bits of the head field of its header through the
+  prev_foot and PINUSE_BIT bit of the following chunk header.
+
+  A chunk that's in use looks like:
+
+   chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+          | Size of previous chunk (if P = 0)                             |
+          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |P|
+        | Size of this chunk                                         1| +-+
+   mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+        |                                                               |
+        +-                                                             -+
+        |                                                               |
+        +-                                                             -+
+        |                                                               :
+        +-      size - sizeof(size_t) available payload bytes          -+
+        :                                                               |
+ chunk-> +-                                                             -+
+        |                                                               |
+        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |1|
+       | Size of next chunk (may or may not be in use)               | +-+
+ mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+    And if it's free, it looks like this:
+
+   chunk-> +-                                                             -+
+          | User payload (must be in use, or we would have merged!)       |
+          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |P|
+        | Size of this chunk                                         0| +-+
+   mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+        | Next pointer                                                  |
+        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+        | Prev pointer                                                  |
+        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+        |                                                               :
+        +-      size - sizeof(struct chunk) unused bytes               -+
+        :                                                               |
+ chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+        | Size of this chunk                                            |
+        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |0|
+       | Size of next chunk (must be in use, or we would have merged)| +-+
+ mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+       |                                                               :
+       +- User payload                                                -+
+       :                                                               |
+       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+                                                                    |0|
+                                                                    +-+
+  Note that since we always merge adjacent free chunks, the chunks
+  adjacent to a free chunk must be in use.
+
+  Given a pointer to a chunk (which can be derived trivially from the
+  payload pointer) we can, in O(1) time, find out whether the adjacent
+  chunks are free, and if so, unlink them from the lists that they
+  are on and merge them with the current chunk.
+
+  Chunks always begin on even word boundaries, so the mem portion
+  (which is returned to the user) is also on an even word boundary, and
+  thus at least double-word aligned.
+
+  The P (PINUSE_BIT) bit, stored in the unused low-order bit of the
+  chunk size (which is always a multiple of two words), is an in-use
+  bit for the *previous* chunk.  If that bit is *clear*, then the
+  word before the current chunk size contains the previous chunk
+  size, and can be used to find the front of the previous chunk.
+  The very first chunk allocated always has this bit set, preventing
+  access to non-existent (or non-owned) memory. If pinuse is set for
+  any given chunk, then you CANNOT determine the size of the
+  previous chunk, and might even get a memory addressing fault when
+  trying to do so.
+
+  The C (CINUSE_BIT) bit, stored in the unused second-lowest bit of
+  the chunk size redundantly records whether the current chunk is
+  inuse. This redundancy enables usage checks within free and realloc,
+  and reduces indirection when freeing and consolidating chunks.
+
+  Each freshly allocated chunk must have both cinuse and pinuse set.
+  That is, each allocated chunk borders either a previously allocated
+  and still in-use chunk, or the base of its memory arena. This is
+  ensured by making all allocations from the `lowest' part of any
+  found chunk.  Further, no free chunk physically borders another one,
+  so each free chunk is known to be preceded and followed by either
+  inuse chunks or the ends of memory.
+
+  Note that the `foot' of the current chunk is actually represented
+  as the prev_foot of the NEXT chunk. This makes it easier to
+  deal with alignments etc but can be very confusing when trying
+  to extend or adapt this code.
+
+  The exceptions to all this are
+
+     1. The special chunk `top' is the top-most available chunk (i.e.,
+       the one bordering the end of available memory). It is treated
+       specially.  Top is never included in any bin, is used only if
+       no other chunk is available, and is released back to the
+       system if it is very large (see M_TRIM_THRESHOLD).  In effect,
+       the top chunk is treated as larger (and thus less well
+       fitting) than any other available chunk.  The top chunk
+       doesn't update its trailing size field since there is no next
+       contiguous chunk that would have to index off it. However,
+       space is still allocated for it (TOP_FOOT_SIZE) to enable
+       separation or merging when space is extended.
+
+     3. Chunks allocated via mmap, which have the lowest-order bit
+       (IS_MMAPPED_BIT) set in their prev_foot fields, and do not set
+       PINUSE_BIT in their head fields.  Because they are allocated
+       one-by-one, each must carry its own prev_foot field, which is
+       also used to hold the offset this chunk has within its mmapped
+       region, which is needed to preserve alignment. Each mmapped
+       chunk is trailed by the first two fields of a fake next-chunk
+       for sake of usage checks.
+
+*/
+
+struct malloc_chunk {
+  size_t               prev_foot;  /* Size of previous chunk (if free).  */
+  size_t               head;       /* Size and inuse bits. */
+  struct malloc_chunk* fd;         /* double links -- used only if free. */
+  struct malloc_chunk* bk;
+};
+
+typedef struct malloc_chunk  mchunk;
+typedef struct malloc_chunk* mchunkptr;
+typedef struct malloc_chunk* sbinptr;  /* The type of bins of chunks */
+typedef unsigned int bindex_t;         /* Described below */
+typedef unsigned int binmap_t;         /* Described below */
+typedef unsigned int flag_t;           /* The type of various bit flag sets */
+
+/* ------------------- Chunks sizes and alignments ----------------------- */
+
+#define MCHUNK_SIZE         (sizeof(mchunk))
+
+#if FOOTERS
+#define CHUNK_OVERHEAD      (TWO_SIZE_T_SIZES)
+#else /* FOOTERS */
+#define CHUNK_OVERHEAD      (SIZE_T_SIZE)
+#endif /* FOOTERS */
+
+/* MMapped chunks need a second word of overhead ... */
+#define MMAP_CHUNK_OVERHEAD (TWO_SIZE_T_SIZES)
+/* ... and additional padding for fake next-chunk at foot */
+#define MMAP_FOOT_PAD       (FOUR_SIZE_T_SIZES)
+
+/* The smallest size we can malloc is an aligned minimal chunk */
+#define MIN_CHUNK_SIZE\
+  ((MCHUNK_SIZE + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK)
+
+/* conversion from malloc headers to user pointers, and back */
+#define chunk2mem(p)        ((void*)((char*)(p)       + TWO_SIZE_T_SIZES))
+#define mem2chunk(mem)      ((mchunkptr)((char*)(mem) - TWO_SIZE_T_SIZES))
+/* chunk associated with aligned address A */
+#define align_as_chunk(A)   (mchunkptr)((A) + align_offset(chunk2mem(A)))
+
+/* Bounds on request (not chunk) sizes. */
+#define MAX_REQUEST         ((-MIN_CHUNK_SIZE) << 2)
+#define MIN_REQUEST         (MIN_CHUNK_SIZE - CHUNK_OVERHEAD - SIZE_T_ONE)
+
+/* pad request bytes into a usable size */
+#define pad_request(req) \
+   (((req) + CHUNK_OVERHEAD + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK)
+
+/* pad request, checking for minimum (but not maximum) */
+#define request2size(req) \
+  (((req) < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(req))
+
+
+/* ------------------ Operations on head and foot fields ----------------- */
+
+/*
+  The head field of a chunk is or'ed with PINUSE_BIT when previous
+  adjacent chunk in use, and or'ed with CINUSE_BIT if this chunk is in
+  use. If the chunk was obtained with mmap, the prev_foot field has
+  IS_MMAPPED_BIT set, otherwise holding the offset of the base of the
+  mmapped region to the base of the chunk.
+
+  FLAG4_BIT is not used by this malloc, but might be useful in extensions.
+*/
+
+#define PINUSE_BIT          (SIZE_T_ONE)
+#define CINUSE_BIT          (SIZE_T_TWO)
+#define FLAG4_BIT           (SIZE_T_FOUR)
+#define INUSE_BITS          (PINUSE_BIT|CINUSE_BIT)
+#define FLAG_BITS           (PINUSE_BIT|CINUSE_BIT|FLAG4_BIT)
+
+/* Head value for fenceposts */
+#define FENCEPOST_HEAD      (INUSE_BITS|SIZE_T_SIZE)
+
+/* extraction of fields from head words */
+#define cinuse(p)           ((p)->head & CINUSE_BIT)
+#define pinuse(p)           ((p)->head & PINUSE_BIT)
+#define chunksize(p)        ((p)->head & ~(FLAG_BITS))
+
+#define clear_pinuse(p)     ((p)->head &= ~PINUSE_BIT)
+#define clear_cinuse(p)     ((p)->head &= ~CINUSE_BIT)
+
+/* Treat space at ptr +/- offset as a chunk */
+#define chunk_plus_offset(p, s)  ((mchunkptr)(((char*)(p)) + (s)))
+#define chunk_minus_offset(p, s) ((mchunkptr)(((char*)(p)) - (s)))
+
+/* Ptr to next or previous physical malloc_chunk. */
+#define next_chunk(p) ((mchunkptr)( ((char*)(p)) + ((p)->head & ~FLAG_BITS)))
+#define prev_chunk(p) ((mchunkptr)( ((char*)(p)) - ((p)->prev_foot) ))
+
+/* extract next chunk's pinuse bit */
+#define next_pinuse(p)  ((next_chunk(p)->head) & PINUSE_BIT)
+
+/* Get/set size at footer */
+#define get_foot(p, s)  (((mchunkptr)((char*)(p) + (s)))->prev_foot)
+#define set_foot(p, s)  (((mchunkptr)((char*)(p) + (s)))->prev_foot = (s))
+
+/* Set size, pinuse bit, and foot */
+#define set_size_and_pinuse_of_free_chunk(p, s)\
+  ((p)->head = (s|PINUSE_BIT), set_foot(p, s))
+
+/* Set size, pinuse bit, foot, and clear next pinuse */
+#define set_free_with_pinuse(p, s, n)\
+  (clear_pinuse(n), set_size_and_pinuse_of_free_chunk(p, s))
+
+#define is_mmapped(p)\
+  (!((p)->head & PINUSE_BIT) && ((p)->prev_foot & IS_MMAPPED_BIT))
+
+/* Get the internal overhead associated with chunk p */
+#define overhead_for(p)\
+ (is_mmapped(p)? MMAP_CHUNK_OVERHEAD : CHUNK_OVERHEAD)
+
+/* Return true if malloced space is not necessarily cleared */
+#if MMAP_CLEARS
+#define calloc_must_clear(p) (!is_mmapped(p))
+#else /* MMAP_CLEARS */
+#define calloc_must_clear(p) (1)
+#endif /* MMAP_CLEARS */
+
+/* ---------------------- Overlaid data structures ----------------------- */
+
+/*
+  When chunks are not in use, they are treated as nodes of either
+  lists or trees.
+
+  "Small"  chunks are stored in circular doubly-linked lists, and look
+  like this:
+
+    chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+           |             Size of previous chunk                            |
+           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+    `head:' |             Size of chunk, in bytes                         |P|
+      mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+           |             Forward pointer to next chunk in list             |
+           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+           |             Back pointer to previous chunk in list            |
+           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+           |             Unused space (may be 0 bytes long)                .
+           .                                                               .
+           .                                                               |
+nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+    `foot:' |             Size of chunk, in bytes                           |
+           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+  Larger chunks are kept in a form of bitwise digital trees (aka
+  tries) keyed on chunksizes.  Because malloc_tree_chunks are only for
+  free chunks greater than 256 bytes, their size doesn't impose any
+  constraints on user chunk sizes.  Each node looks like:
+
+    chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+           |             Size of previous chunk                            |
+           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+    `head:' |             Size of chunk, in bytes                         |P|
+      mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+           |             Forward pointer to next chunk of same size        |
+           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+           |             Back pointer to previous chunk of same size       |
+           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+           |             Pointer to left child (child[0])                  |
+           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+           |             Pointer to right child (child[1])                 |
+           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+           |             Pointer to parent                                 |
+           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+           |             bin index of this chunk                           |
+           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+           |             Unused space                                      .
+           .                                                               |
+nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+    `foot:' |             Size of chunk, in bytes                           |
+           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+  Each tree holding treenodes is a tree of unique chunk sizes.  Chunks
+  of the same size are arranged in a circularly-linked list, with only
+  the oldest chunk (the next to be used, in our FIFO ordering)
+  actually in the tree.  (Tree members are distinguished by a non-null
+  parent pointer.)  If a chunk with the same size an an existing node
+  is inserted, it is linked off the existing node using pointers that
+  work in the same way as fd/bk pointers of small chunks.
+
+  Each tree contains a power of 2 sized range of chunk sizes (the
+  smallest is 0x100 <= x < 0x180), which is is divided in half at each
+  tree level, with the chunks in the smaller half of the range (0x100
+  <= x < 0x140 for the top nose) in the left subtree and the larger
+  half (0x140 <= x < 0x180) in the right subtree.  This is, of course,
+  done by inspecting individual bits.
+
+  Using these rules, each node's left subtree contains all smaller
+  sizes than its right subtree.  However, the node at the root of each
+  subtree has no particular ordering relationship to either.  (The
+  dividing line between the subtree sizes is based on trie relation.)
+  If we remove the last chunk of a given size from the interior of the
+  tree, we need to replace it with a leaf node.  The tree ordering
+  rules permit a node to be replaced by any leaf below it.
+
+  The smallest chunk in a tree (a common operation in a best-fit
+  allocator) can be found by walking a path to the leftmost leaf in
+  the tree.  Unlike a usual binary tree, where we follow left child
+  pointers until we reach a null, here we follow the right child
+  pointer any time the left one is null, until we reach a leaf with
+  both child pointers null. The smallest chunk in the tree will be
+  somewhere along that path.
+
+  The worst case number of steps to add, find, or remove a node is
+  bounded by the number of bits differentiating chunks within
+  bins. Under current bin calculations, this ranges from 6 up to 21
+  (for 32 bit sizes) or up to 53 (for 64 bit sizes). The typical case
+  is of course much better.
+*/
+
+struct malloc_tree_chunk {
+  /* The first four fields must be compatible with malloc_chunk */
+  size_t                    prev_foot;
+  size_t                    head;
+  struct malloc_tree_chunk* fd;
+  struct malloc_tree_chunk* bk;
+
+  struct malloc_tree_chunk* child[2];
+  struct malloc_tree_chunk* parent;
+  bindex_t                  index;
+};
+
+typedef struct malloc_tree_chunk  tchunk;
+typedef struct malloc_tree_chunk* tchunkptr;
+typedef struct malloc_tree_chunk* tbinptr; /* The type of bins of trees */
+
+/* A little helper macro for trees */
+#define leftmost_child(t) ((t)->child[0] != 0? (t)->child[0] : (t)->child[1])
+
+/* ----------------------------- Segments -------------------------------- */
+
+/*
+  Each malloc space may include non-contiguous segments, held in a
+  list headed by an embedded malloc_segment record representing the
+  top-most space. Segments also include flags holding properties of
+  the space. Large chunks that are directly allocated by mmap are not
+  included in this list. They are instead independently created and
+  destroyed without otherwise keeping track of them.
+
+  Segment management mainly comes into play for spaces allocated by
+  MMAP.  Any call to MMAP might or might not return memory that is
+  adjacent to an existing segment.  MORECORE normally contiguously
+  extends the current space, so this space is almost always adjacent,
+  which is simpler and faster to deal with. (This is why MORECORE is
+  used preferentially to MMAP when both are available -- see
+  sys_alloc.)  When allocating using MMAP, we don't use any of the
+  hinting mechanisms (inconsistently) supported in various
+  implementations of unix mmap, or distinguish reserving from
+  committing memory. Instead, we just ask for space, and exploit
+  contiguity when we get it.  It is probably possible to do
+  better than this on some systems, but no general scheme seems
+  to be significantly better.
+
+  Management entails a simpler variant of the consolidation scheme
+  used for chunks to reduce fragmentation -- new adjacent memory is
+  normally prepended or appended to an existing segment. However,
+  there are limitations compared to chunk consolidation that mostly
+  reflect the fact that segment processing is relatively infrequent
+  (occurring only when getting memory from system) and that we
+  don't expect to have huge numbers of segments:
+
+  * Segments are not indexed, so traversal requires linear scans.  (It
+    would be possible to index these, but is not worth the extra
+    overhead and complexity for most programs on most platforms.)
+  * New segments are only appended to old ones when holding top-most
+    memory; if they cannot be prepended to others, they are held in
+    different segments.
+
+  Except for the top-most segment of an mstate, each segment record
+  is kept at the tail of its segment. Segments are added by pushing
+  segment records onto the list headed by &mstate.seg for the
+  containing mstate.
+
+  Segment flags control allocation/merge/deallocation policies:
+  * If EXTERN_BIT set, then we did not allocate this segment,
+    and so should not try to deallocate or merge with others.
+    (This currently holds only for the initial segment passed
+    into create_mspace_with_base.)
+  * If IS_MMAPPED_BIT set, the segment may be merged with
+    other surrounding mmapped segments and trimmed/de-allocated
+    using munmap.
+  * If neither bit is set, then the segment was obtained using
+    MORECORE so can be merged with surrounding MORECORE'd segments
+    and deallocated/trimmed using MORECORE with negative arguments.
+*/
+
+struct malloc_segment {
+  char*        base;             /* base address */
+  size_t       size;             /* allocated size */
+  struct malloc_segment* next;   /* ptr to next segment */
+  flag_t       sflags;           /* mmap and extern flag */
+};
+
+#define is_mmapped_segment(S)  ((S)->sflags & IS_MMAPPED_BIT)
+#define is_extern_segment(S)   ((S)->sflags & EXTERN_BIT)
+
+typedef struct malloc_segment  msegment;
+typedef struct malloc_segment* msegmentptr;
+
+/* ---------------------------- malloc_state ----------------------------- */
+
+/*
+   A malloc_state holds all of the bookkeeping for a space.
+   The main fields are:
+
+  Top
+    The topmost chunk of the currently active segment. Its size is
+    cached in topsize.  The actual size of topmost space is
+    topsize+TOP_FOOT_SIZE, which includes space reserved for adding
+    fenceposts and segment records if necessary when getting more
+    space from the system.  The size at which to autotrim top is
+    cached from mparams in trim_check, except that it is disabled if
+    an autotrim fails.
+
+  Designated victim (dv)
+    This is the preferred chunk for servicing small requests that
+    don't have exact fits.  It is normally the chunk split off most
+    recently to service another small request.  Its size is cached in
+    dvsize. The link fields of this chunk are not maintained since it
+    is not kept in a bin.
+
+  SmallBins
+    An array of bin headers for free chunks.  These bins hold chunks
+    with sizes less than MIN_LARGE_SIZE bytes. Each bin contains
+    chunks of all the same size, spaced 8 bytes apart.  To simplify
+    use in double-linked lists, each bin header acts as a malloc_chunk
+    pointing to the real first node, if it exists (else pointing to
+    itself).  This avoids special-casing for headers.  But to avoid
+    waste, we allocate only the fd/bk pointers of bins, and then use
+    repositioning tricks to treat these as the fields of a chunk.
+
+  TreeBins
+    Treebins are pointers to the roots of trees holding a range of
+    sizes. There are 2 equally spaced treebins for each power of two
+    from TREE_SHIFT to TREE_SHIFT+16. The last bin holds anything
+    larger.
+
+  Bin maps
+    There is one bit map for small bins ("smallmap") and one for
+    treebins ("treemap).  Each bin sets its bit when non-empty, and
+    clears the bit when empty.  Bit operations are then used to avoid
+    bin-by-bin searching -- nearly all "search" is done without ever
+    looking at bins that won't be selected.  The bit maps
+    conservatively use 32 bits per map word, even if on 64bit system.
+    For a good description of some of the bit-based techniques used
+    here, see Henry S. Warren Jr's book "Hacker's Delight" (and
+    supplement at http://hackersdelight.org/). Many of these are
+    intended to reduce the branchiness of paths through malloc etc, as
+    well as to reduce the number of memory locations read or written.
+
+  Segments
+    A list of segments headed by an embedded malloc_segment record
+    representing the initial space.
+
+  Address check support
+    The least_addr field is the least address ever obtained from
+    MORECORE or MMAP. Attempted frees and reallocs of any address less
+    than this are trapped (unless INSECURE is defined).
+
+  Magic tag
+    A cross-check field that should always hold same value as mparams.magic.
+
+  Flags
+    Bits recording whether to use MMAP, locks, or contiguous MORECORE
+
+  Statistics
+    Each space keeps track of current and maximum system memory
+    obtained via MORECORE or MMAP.
+
+  Trim support
+    Fields holding the amount of unused topmost memory that should trigger
+    timming, and a counter to force periodic scanning to release unused
+    non-topmost segments.
+
+  Locking
+    If USE_LOCKS is defined, the "mutex" lock is acquired and released
+    around every public call using this mspace.
+
+  Extension support
+    A void* pointer and a size_t field that can be used to help implement
+    extensions to this malloc.
+*/
+
+/* Bin types, widths and sizes */
+#define NSMALLBINS        (32U)
+#define NTREEBINS         (32U)
+#define SMALLBIN_SHIFT    (3U)
+#define SMALLBIN_WIDTH    (SIZE_T_ONE << SMALLBIN_SHIFT)
+#define TREEBIN_SHIFT     (8U)
+#define MIN_LARGE_SIZE    (SIZE_T_ONE << TREEBIN_SHIFT)
+#define MAX_SMALL_SIZE    (MIN_LARGE_SIZE - SIZE_T_ONE)
+#define MAX_SMALL_REQUEST (MAX_SMALL_SIZE - CHUNK_ALIGN_MASK - CHUNK_OVERHEAD)
+
+struct malloc_state {
+  binmap_t   smallmap;
+  binmap_t   treemap;
+  size_t     dvsize;
+  size_t     topsize;
+  char*      least_addr;
+  mchunkptr  dv;
+  mchunkptr  top;
+  size_t     trim_check;
+  size_t     release_checks;
+  size_t     magic;
+  mchunkptr  smallbins[(NSMALLBINS+1)*2];
+  tbinptr    treebins[NTREEBINS];
+  size_t     footprint;
+  size_t     max_footprint;
+  flag_t     mflags;
+#if USE_LOCKS
+  MLOCK_T    mutex;     /* locate lock among fields that rarely change */
+#endif /* USE_LOCKS */
+  msegment   seg;
+  void*      extp;      /* Unused but available for extensions */
+  size_t     exts;
+};
+
+typedef struct malloc_state*    mstate;
+
+/* ------------- Global malloc_state and malloc_params ------------------- */
+
+/*
+  malloc_params holds global properties, including those that can be
+  dynamically set using mallopt. There is a single instance, mparams,
+  initialized in init_mparams. Note that the non-zeroness of "magic"
+  also serves as an initialization flag.
+*/
+
+struct malloc_params {
+  volatile size_t magic;
+  size_t page_size;
+  size_t granularity;
+  size_t mmap_threshold;
+  size_t trim_threshold;
+  flag_t default_mflags;
+};
+
+static struct malloc_params mparams;
+
+/* Ensure mparams initialized */
+#define ensure_initialization() ((void)(mparams.magic != 0 || init_mparams()))
+
+#if !ONLY_MSPACES
+
+/* The global malloc_state used for all non-"mspace" calls */
+static struct malloc_state _gm_;
+#define gm                 (&_gm_)
+#define is_global(M)       ((M) == &_gm_)
+
+#endif /* !ONLY_MSPACES */
+
+#define is_initialized(M)  ((M)->top != 0)
+
+/* -------------------------- system alloc setup ------------------------- */
+
+/* Operations on mflags */
+
+#define use_lock(M)           ((M)->mflags &   USE_LOCK_BIT)
+#define enable_lock(M)        ((M)->mflags |=  USE_LOCK_BIT)
+#define disable_lock(M)       ((M)->mflags &= ~USE_LOCK_BIT)
+
+#define use_mmap(M)           ((M)->mflags &   USE_MMAP_BIT)
+#define enable_mmap(M)        ((M)->mflags |=  USE_MMAP_BIT)
+#define disable_mmap(M)       ((M)->mflags &= ~USE_MMAP_BIT)
+
+#define use_noncontiguous(M)  ((M)->mflags &   USE_NONCONTIGUOUS_BIT)
+#define disable_contiguous(M) ((M)->mflags |=  USE_NONCONTIGUOUS_BIT)
+
+#define set_lock(M,L)\
+ ((M)->mflags = (L)?\
+  ((M)->mflags | USE_LOCK_BIT) :\
+  ((M)->mflags & ~USE_LOCK_BIT))
+
+/* page-align a size */
+#define page_align(S)\
+ (((S) + (mparams.page_size - SIZE_T_ONE)) & ~(mparams.page_size - SIZE_T_ONE))
+
+/* granularity-align a size */
+#define granularity_align(S)\
+  (((S) + (mparams.granularity - SIZE_T_ONE))\
+   & ~(mparams.granularity - SIZE_T_ONE))
+
+
+/* For mmap, use granularity alignment on windows, else page-align */
+#ifdef WIN32
+#define mmap_align(S) granularity_align(S)
+#else
+#define mmap_align(S) page_align(S)
+#endif
+
+/* For sys_alloc, enough padding to ensure can malloc request on success */
+#define SYS_ALLOC_PADDING (TOP_FOOT_SIZE + MALLOC_ALIGNMENT)
+
+#define is_page_aligned(S)\
+   (((size_t)(S) & (mparams.page_size - SIZE_T_ONE)) == 0)
+#define is_granularity_aligned(S)\
+   (((size_t)(S) & (mparams.granularity - SIZE_T_ONE)) == 0)
+
+/*  True if segment S holds address A */
+#define segment_holds(S, A)\
+  ((char*)(A) >= S->base && (char*)(A) < S->base + S->size)
+
+/* Return segment holding given address */
+static msegmentptr segment_holding(mstate m, char* addr) {
+  msegmentptr sp = &m->seg;
+  for (;;) {
+    if (addr >= sp->base && addr < sp->base + sp->size)
+      return sp;
+    if ((sp = sp->next) == 0)
+      return 0;
+  }
+}
+
+/* Return true if segment contains a segment link */
+static int has_segment_link(mstate m, msegmentptr ss) {
+  msegmentptr sp = &m->seg;
+  for (;;) {
+    if ((char*)sp >= ss->base && (char*)sp < ss->base + ss->size)
+      return 1;
+    if ((sp = sp->next) == 0)
+      return 0;
+  }
+}
+
+#ifndef MORECORE_CANNOT_TRIM
+#define should_trim(M,s)  ((s) > (M)->trim_check)
+#else  /* MORECORE_CANNOT_TRIM */
+#define should_trim(M,s)  (0)
+#endif /* MORECORE_CANNOT_TRIM */
+
+/*
+  TOP_FOOT_SIZE is padding at the end of a segment, including space
+  that may be needed to place segment records and fenceposts when new
+  noncontiguous segments are added.
+*/
+#define TOP_FOOT_SIZE\
+  (align_offset(chunk2mem(0))+pad_request(sizeof(struct malloc_segment))+MIN_CHUNK_SIZE)
+
+
+/* -------------------------------  Hooks -------------------------------- */
+
+/*
+  PREACTION should be defined to return 0 on success, and nonzero on
+  failure. If you are not using locking, you can redefine these to do
+  anything you like.
+*/
+
+#if USE_LOCKS
+
+#define PREACTION(M)  ((use_lock(M))? ACQUIRE_LOCK(&(M)->mutex) : 0)
+#define POSTACTION(M) { if (use_lock(M)) RELEASE_LOCK(&(M)->mutex); }
+#else /* USE_LOCKS */
+
+#ifndef PREACTION
+#define PREACTION(M) (0)
+#endif  /* PREACTION */
+
+#ifndef POSTACTION
+#define POSTACTION(M)
+#endif  /* POSTACTION */
+
+#endif /* USE_LOCKS */
+
+/*
+  CORRUPTION_ERROR_ACTION is triggered upon detected bad addresses.
+  USAGE_ERROR_ACTION is triggered on detected bad frees and
+  reallocs. The argument p is an address that might have triggered the
+  fault. It is ignored by the two predefined actions, but might be
+  useful in custom actions that try to help diagnose errors.
+*/
+
+#if PROCEED_ON_ERROR
+
+/* A count of the number of corruption errors causing resets */
+int malloc_corruption_error_count;
+
+/* default corruption action */
+static void reset_on_error(mstate m);
+
+#define CORRUPTION_ERROR_ACTION(m)  reset_on_error(m)
+#define USAGE_ERROR_ACTION(m, p)
+
+#else /* PROCEED_ON_ERROR */
+
+#ifndef CORRUPTION_ERROR_ACTION
+#define CORRUPTION_ERROR_ACTION(m) ABORT
+#endif /* CORRUPTION_ERROR_ACTION */
+
+#ifndef USAGE_ERROR_ACTION
+#define USAGE_ERROR_ACTION(m,p) ABORT
+#endif /* USAGE_ERROR_ACTION */
+
+#endif /* PROCEED_ON_ERROR */
+
+/* -------------------------- Debugging setup ---------------------------- */
+
+#if ! DEBUG
+
+#define check_free_chunk(M,P)
+#define check_inuse_chunk(M,P)
+#define check_malloced_chunk(M,P,N)
+#define check_mmapped_chunk(M,P)
+#define check_malloc_state(M)
+#define check_top_chunk(M,P)
+
+#else /* DEBUG */
+#define check_free_chunk(M,P)       do_check_free_chunk(M,P)
+#define check_inuse_chunk(M,P)      do_check_inuse_chunk(M,P)
+#define check_top_chunk(M,P)        do_check_top_chunk(M,P)
+#define check_malloced_chunk(M,P,N) do_check_malloced_chunk(M,P,N)
+#define check_mmapped_chunk(M,P)    do_check_mmapped_chunk(M,P)
+#define check_malloc_state(M)       do_check_malloc_state(M)
+
+static void   do_check_any_chunk(mstate m, mchunkptr p);
+static void   do_check_top_chunk(mstate m, mchunkptr p);
+static void   do_check_mmapped_chunk(mstate m, mchunkptr p);
+static void   do_check_inuse_chunk(mstate m, mchunkptr p);
+static void   do_check_free_chunk(mstate m, mchunkptr p);
+static void   do_check_malloced_chunk(mstate m, void* mem, size_t s);
+static void   do_check_tree(mstate m, tchunkptr t);
+static void   do_check_treebin(mstate m, bindex_t i);
+static void   do_check_smallbin(mstate m, bindex_t i);
+static void   do_check_malloc_state(mstate m);
+static int    bin_find(mstate m, mchunkptr x);
+static size_t traverse_and_check(mstate m);
+#endif /* DEBUG */
+
+/* ---------------------------- Indexing Bins ---------------------------- */
+
+#define is_small(s)         (((s) >> SMALLBIN_SHIFT) < NSMALLBINS)
+#define small_index(s)      ((s)  >> SMALLBIN_SHIFT)
+#define small_index2size(i) ((i)  << SMALLBIN_SHIFT)
+#define MIN_SMALL_INDEX     (small_index(MIN_CHUNK_SIZE))
+
+/* addressing by index. See above about smallbin repositioning */
+#define smallbin_at(M, i)   ((sbinptr)((char*)&((M)->smallbins[(i)<<1])))
+#define treebin_at(M,i)     (&((M)->treebins[i]))
+
+/* assign tree index for size S to variable I. Use x86 asm if possible  */
+#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
+#define compute_tree_index(S, I)\
+{\
+  unsigned int X = S >> TREEBIN_SHIFT;\
+  if (X == 0)\
+    I = 0;\
+  else if (X > 0xFFFF)\
+    I = NTREEBINS-1;\
+  else {\
+    unsigned int K;\
+    __asm__("bsrl\t%1, %0\n\t" : "=r" (K) : "rm"  (X));\
+    I =  (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\
+  }\
+}
+
+#elif defined (__INTEL_COMPILER)
+#define compute_tree_index(S, I)\
+{\
+  size_t X = S >> TREEBIN_SHIFT;\
+  if (X == 0)\
+    I = 0;\
+  else if (X > 0xFFFF)\
+    I = NTREEBINS-1;\
+  else {\
+    unsigned int K = _bit_scan_reverse (X); \
+    I =  (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\
+  }\
+}
+
+#elif defined(_MSC_VER) && _MSC_VER>=1300
+#define compute_tree_index(S, I)\
+{\
+  size_t X = S >> TREEBIN_SHIFT;\
+  if (X == 0)\
+    I = 0;\
+  else if (X > 0xFFFF)\
+    I = NTREEBINS-1;\
+  else {\
+    unsigned int K;\
+    _BitScanReverse((DWORD *) &K, X);\
+    I =  (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\
+  }\
+}
+
+#else /* GNUC */
+#define compute_tree_index(S, I)\
+{\
+  size_t X = S >> TREEBIN_SHIFT;\
+  if (X == 0)\
+    I = 0;\
+  else if (X > 0xFFFF)\
+    I = NTREEBINS-1;\
+  else {\
+    unsigned int Y = (unsigned int)X;\
+    unsigned int N = ((Y - 0x100) >> 16) & 8;\
+    unsigned int K = (((Y <<= N) - 0x1000) >> 16) & 4;\
+    N += K;\
+    N += K = (((Y <<= K) - 0x4000) >> 16) & 2;\
+    K = 14 - N + ((Y <<= K) >> 15);\
+    I = (K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1));\
+  }\
+}
+#endif /* GNUC */
+
+/* Bit representing maximum resolved size in a treebin at i */
+#define bit_for_tree_index(i) \
+   (i == NTREEBINS-1)? (SIZE_T_BITSIZE-1) : (((i) >> 1) + TREEBIN_SHIFT - 2)
+
+/* Shift placing maximum resolved bit in a treebin at i as sign bit */
+#define leftshift_for_tree_index(i) \
+   ((i == NTREEBINS-1)? 0 : \
+    ((SIZE_T_BITSIZE-SIZE_T_ONE) - (((i) >> 1) + TREEBIN_SHIFT - 2)))
+
+/* The size of the smallest chunk held in bin with index i */
+#define minsize_for_tree_index(i) \
+   ((SIZE_T_ONE << (((i) >> 1) + TREEBIN_SHIFT)) |  \
+   (((size_t)((i) & SIZE_T_ONE)) << (((i) >> 1) + TREEBIN_SHIFT - 1)))
+
+
+/* ------------------------ Operations on bin maps ----------------------- */
+
+/* bit corresponding to given index */
+#define idx2bit(i)              ((binmap_t)(1) << (i))
+
+/* Mark/Clear bits with given index */
+#define mark_smallmap(M,i)      ((M)->smallmap |=  idx2bit(i))
+#define clear_smallmap(M,i)     ((M)->smallmap &= ~idx2bit(i))
+#define smallmap_is_marked(M,i) ((M)->smallmap &   idx2bit(i))
+
+#define mark_treemap(M,i)       ((M)->treemap  |=  idx2bit(i))
+#define clear_treemap(M,i)      ((M)->treemap  &= ~idx2bit(i))
+#define treemap_is_marked(M,i)  ((M)->treemap  &   idx2bit(i))
+
+/* isolate the least set bit of a bitmap */
+#define least_bit(x)         ((x) & -(x))
+
+/* mask with all bits to left of least bit of x on */
+#define left_bits(x)         ((x<<1) | -(x<<1))
+
+/* mask with all bits to left of or equal to least bit of x on */
+#define same_or_left_bits(x) ((x) | -(x))
+
+/* index corresponding to given bit. Use x86 asm if possible */
+
+#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
+#define compute_bit2idx(X, I)\
+{\
+  unsigned int J;\
+  __asm__("bsfl\t%1, %0\n\t" : "=r" (J) : "rm" (X));\
+  I = (bindex_t)J;\
+}
+
+#elif defined (__INTEL_COMPILER)
+#define compute_bit2idx(X, I)\
+{\
+  unsigned int J;\
+  J = _bit_scan_forward (X); \
+  I = (bindex_t)J;\
+}
+
+#elif defined(_MSC_VER) && _MSC_VER>=1300
+#define compute_bit2idx(X, I)\
+{\
+  unsigned int J;\
+  _BitScanForward((DWORD *) &J, X);\
+  I = (bindex_t)J;\
+}
+
+#elif USE_BUILTIN_FFS
+#define compute_bit2idx(X, I) I = ffs(X)-1
+
+#else
+#define compute_bit2idx(X, I)\
+{\
+  unsigned int Y = X - 1;\
+  unsigned int K = Y >> (16-4) & 16;\
+  unsigned int N = K;        Y >>= K;\
+  N += K = Y >> (8-3) &  8;  Y >>= K;\
+  N += K = Y >> (4-2) &  4;  Y >>= K;\
+  N += K = Y >> (2-1) &  2;  Y >>= K;\
+  N += K = Y >> (1-0) &  1;  Y >>= K;\
+  I = (bindex_t)(N + Y);\
+}
+#endif /* GNUC */
+
+
+/* ----------------------- Runtime Check Support ------------------------- */
+
+/*
+  For security, the main invariant is that malloc/free/etc never
+  writes to a static address other than malloc_state, unless static
+  malloc_state itself has been corrupted, which cannot occur via
+  malloc (because of these checks). In essence this means that we
+  believe all pointers, sizes, maps etc held in malloc_state, but
+  check all of those linked or offsetted from other embedded data
+  structures.  These checks are interspersed with main code in a way
+  that tends to minimize their run-time cost.
+
+  When FOOTERS is defined, in addition to range checking, we also
+  verify footer fields of inuse chunks, which can be used guarantee
+  that the mstate controlling malloc/free is intact.  This is a
+  streamlined version of the approach described by William Robertson
+  et al in "Run-time Detection of Heap-based Overflows" LISA'03
+  http://www.usenix.org/events/lisa03/tech/robertson.html The footer
+  of an inuse chunk holds the xor of its mstate and a random seed,
+  that is checked upon calls to free() and realloc().  This is
+  (probablistically) unguessable from outside the program, but can be
+  computed by any code successfully malloc'ing any chunk, so does not
+  itself provide protection against code that has already broken
+  security through some other means.  Unlike Robertson et al, we
+  always dynamically check addresses of all offset chunks (previous,
+  next, etc). This turns out to be cheaper than relying on hashes.
+*/
+
+#if !INSECURE
+/* Check if address a is at least as high as any from MORECORE or MMAP */
+#define ok_address(M, a) ((char*)(a) >= (M)->least_addr)
+/* Check if address of next chunk n is higher than base chunk p */
+#define ok_next(p, n)    ((char*)(p) < (char*)(n))
+/* Check if p has its cinuse bit on */
+#define ok_cinuse(p)     cinuse(p)
+/* Check if p has its pinuse bit on */
+#define ok_pinuse(p)     pinuse(p)
+
+#else /* !INSECURE */
+#define ok_address(M, a) (1)
+#define ok_next(b, n)    (1)
+#define ok_cinuse(p)     (1)
+#define ok_pinuse(p)     (1)
+#endif /* !INSECURE */
+
+#if (FOOTERS && !INSECURE)
+/* Check if (alleged) mstate m has expected magic field */
+#define ok_magic(M)      ((M)->magic == mparams.magic)
+#else  /* (FOOTERS && !INSECURE) */
+#define ok_magic(M)      (1)
+#endif /* (FOOTERS && !INSECURE) */
+
+
+/* In gcc, use __builtin_expect to minimize impact of checks */
+#if !INSECURE
+#if defined(__GNUC__) && __GNUC__ >= 3
+#define RTCHECK(e)  __builtin_expect(e, 1)
+#else /* GNUC */
+#define RTCHECK(e)  (e)
+#endif /* GNUC */
+#else /* !INSECURE */
+#define RTCHECK(e)  (1)
+#endif /* !INSECURE */
+
+/* macros to set up inuse chunks with or without footers */
+
+#if !FOOTERS
+
+#define mark_inuse_foot(M,p,s)
+
+/* Set cinuse bit and pinuse bit of next chunk */
+#define set_inuse(M,p,s)\
+  ((p)->head = (((p)->head & PINUSE_BIT)|s|CINUSE_BIT),\
+  ((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT)
+
+/* Set cinuse and pinuse of this chunk and pinuse of next chunk */
+#define set_inuse_and_pinuse(M,p,s)\
+  ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\
+  ((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT)
+
+/* Set size, cinuse and pinuse bit of this chunk */
+#define set_size_and_pinuse_of_inuse_chunk(M, p, s)\
+  ((p)->head = (s|PINUSE_BIT|CINUSE_BIT))
+
+#else /* FOOTERS */
+
+/* Set foot of inuse chunk to be xor of mstate and seed */
+#define mark_inuse_foot(M,p,s)\
+  (((mchunkptr)((char*)(p) + (s)))->prev_foot = ((size_t)(M) ^ mparams.magic))
+
+#define get_mstate_for(p)\
+  ((mstate)(((mchunkptr)((char*)(p) +\
+    (chunksize(p))))->prev_foot ^ mparams.magic))
+
+#define set_inuse(M,p,s)\
+  ((p)->head = (((p)->head & PINUSE_BIT)|s|CINUSE_BIT),\
+  (((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT), \
+  mark_inuse_foot(M,p,s))
+
+#define set_inuse_and_pinuse(M,p,s)\
+  ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\
+  (((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT),\
+ mark_inuse_foot(M,p,s))
+
+#define set_size_and_pinuse_of_inuse_chunk(M, p, s)\
+  ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\
+  mark_inuse_foot(M, p, s))
+
+#endif /* !FOOTERS */
+
+/* ---------------------------- setting mparams -------------------------- */
+
+/* Initialize mparams */
+static int init_mparams(void) {
+#ifdef NEED_GLOBAL_LOCK_INIT
+  if (malloc_global_mutex_status <= 0)
+    init_malloc_global_mutex();
+#endif
+
+  ACQUIRE_MALLOC_GLOBAL_LOCK();
+  if (mparams.magic == 0) {
+    size_t magic;
+    size_t psize;
+    size_t gsize;
+
+#ifndef WIN32
+    psize = malloc_getpagesize;
+    gsize = ((DEFAULT_GRANULARITY != 0)? DEFAULT_GRANULARITY : psize);
+#else /* WIN32 */
+    {
+      SYSTEM_INFO system_info;
+      GetSystemInfo(&system_info);
+      psize = system_info.dwPageSize;
+      gsize = ((DEFAULT_GRANULARITY != 0)?
+              DEFAULT_GRANULARITY : system_info.dwAllocationGranularity);
+    }
+#endif /* WIN32 */
+
+    /* Sanity-check configuration:
+       size_t must be unsigned and as wide as pointer type.
+       ints must be at least 4 bytes.
+       alignment must be at least 8.
+       Alignment, min chunk size, and page size must all be powers of 2.
+    */
+    if ((sizeof(size_t) != sizeof(char*)) ||
+       (MAX_SIZE_T < MIN_CHUNK_SIZE)  ||
+       (sizeof(int) < 4)  ||
+       (MALLOC_ALIGNMENT < (size_t)8U) ||
+       ((MALLOC_ALIGNMENT & (MALLOC_ALIGNMENT-SIZE_T_ONE)) != 0) ||
+       ((MCHUNK_SIZE      & (MCHUNK_SIZE-SIZE_T_ONE))      != 0) ||
+       ((gsize            & (gsize-SIZE_T_ONE))            != 0) ||
+       ((psize            & (psize-SIZE_T_ONE))            != 0))
+      ABORT;
+
+    mparams.granularity = gsize;
+    mparams.page_size = psize;
+    mparams.mmap_threshold = DEFAULT_MMAP_THRESHOLD;
+    mparams.trim_threshold = DEFAULT_TRIM_THRESHOLD;
+#if MORECORE_CONTIGUOUS
+    mparams.default_mflags = USE_LOCK_BIT|USE_MMAP_BIT;
+#else  /* MORECORE_CONTIGUOUS */
+    mparams.default_mflags = USE_LOCK_BIT|USE_MMAP_BIT|USE_NONCONTIGUOUS_BIT;
+#endif /* MORECORE_CONTIGUOUS */
+
+#if !ONLY_MSPACES
+    /* Set up lock for main malloc area */
+    gm->mflags = mparams.default_mflags;
+    INITIAL_LOCK(&gm->mutex);
+#endif
+
+#if (FOOTERS && !INSECURE)
+    {
+#if USE_DEV_RANDOM
+      int fd;
+      unsigned char buf[sizeof(size_t)];
+      /* Try to use /dev/urandom, else fall back on using time */
+      if ((fd = open("/dev/urandom", O_RDONLY)) >= 0 &&
+         read(fd, buf, sizeof(buf)) == sizeof(buf)) {
+       magic = *((size_t *) buf);
+       close(fd);
+      }
+      else
+#endif /* USE_DEV_RANDOM */
+#ifdef WIN32
+       magic = (size_t)(GetTickCount() ^ (size_t)0x55555555U);
+#else
+      magic = (size_t)(time(0) ^ (size_t)0x55555555U);
+#endif
+      magic |= (size_t)8U;    /* ensure nonzero */
+      magic &= ~(size_t)7U;   /* improve chances of fault for bad values */
+    }
+#else /* (FOOTERS && !INSECURE) */
+    magic = (size_t)0x58585858U;
+#endif /* (FOOTERS && !INSECURE) */
+
+    mparams.magic = magic;
+  }
+
+  RELEASE_MALLOC_GLOBAL_LOCK();
+  return 1;
+}
+
+/* support for mallopt */
+static int change_mparam(int param_number, int value) {
+  size_t val = (value == -1)? MAX_SIZE_T : (size_t)value;
+  ensure_initialization();
+  switch(param_number) {
+  case M_TRIM_THRESHOLD:
+    mparams.trim_threshold = val;
+    return 1;
+  case M_GRANULARITY:
+    if (val >= mparams.page_size && ((val & (val-1)) == 0)) {
+      mparams.granularity = val;
+      return 1;
+    }
+    else
+      return 0;
+  case M_MMAP_THRESHOLD:
+    mparams.mmap_threshold = val;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+#if DEBUG
+/* ------------------------- Debugging Support --------------------------- */
+
+/* Check properties of any chunk, whether free, inuse, mmapped etc  */
+static void do_check_any_chunk(mstate m, mchunkptr p) {
+  assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD));
+  assert(ok_address(m, p));
+}
+
+/* Check properties of top chunk */
+static void do_check_top_chunk(mstate m, mchunkptr p) {
+  msegmentptr sp = segment_holding(m, (char*)p);
+  size_t  sz = p->head & ~INUSE_BITS; /* third-lowest bit can be set! */
+  assert(sp != 0);
+  assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD));
+  assert(ok_address(m, p));
+  assert(sz == m->topsize);
+  assert(sz > 0);
+  assert(sz == ((sp->base + sp->size) - (char*)p) - TOP_FOOT_SIZE);
+  assert(pinuse(p));
+  assert(!pinuse(chunk_plus_offset(p, sz)));
+}
+
+/* Check properties of (inuse) mmapped chunks */
+static void do_check_mmapped_chunk(mstate m, mchunkptr p) {
+  size_t  sz = chunksize(p);
+  size_t len = (sz + (p->prev_foot & ~IS_MMAPPED_BIT) + MMAP_FOOT_PAD);
+  assert(is_mmapped(p));
+  assert(use_mmap(m));
+  assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD));
+  assert(ok_address(m, p));
+  assert(!is_small(sz));
+  assert((len & (mparams.page_size-SIZE_T_ONE)) == 0);
+  assert(chunk_plus_offset(p, sz)->head == FENCEPOST_HEAD);
+  assert(chunk_plus_offset(p, sz+SIZE_T_SIZE)->head == 0);
+}
+
+/* Check properties of inuse chunks */
+static void do_check_inuse_chunk(mstate m, mchunkptr p) {
+  do_check_any_chunk(m, p);
+  assert(cinuse(p));
+  assert(next_pinuse(p));
+  /* If not pinuse and not mmapped, previous chunk has OK offset */
+  assert(is_mmapped(p) || pinuse(p) || next_chunk(prev_chunk(p)) == p);
+  if (is_mmapped(p))
+    do_check_mmapped_chunk(m, p);
+}
+
+/* Check properties of free chunks */
+static void do_check_free_chunk(mstate m, mchunkptr p) {
+  size_t sz = chunksize(p);
+  mchunkptr next = chunk_plus_offset(p, sz);
+  do_check_any_chunk(m, p);
+  assert(!cinuse(p));
+  assert(!next_pinuse(p));
+  assert (!is_mmapped(p));
+  if (p != m->dv && p != m->top) {
+    if (sz >= MIN_CHUNK_SIZE) {
+      assert((sz & CHUNK_ALIGN_MASK) == 0);
+      assert(is_aligned(chunk2mem(p)));
+      assert(next->prev_foot == sz);
+      assert(pinuse(p));
+      assert (next == m->top || cinuse(next));
+      assert(p->fd->bk == p);
+      assert(p->bk->fd == p);
+    }
+    else  /* markers are always of size SIZE_T_SIZE */
+      assert(sz == SIZE_T_SIZE);
+  }
+}
+
+/* Check properties of malloced chunks at the point they are malloced */
+static void do_check_malloced_chunk(mstate m, void* mem, size_t s) {
+  if (mem != 0) {
+    mchunkptr p = mem2chunk(mem);
+    size_t sz = p->head & ~(PINUSE_BIT|CINUSE_BIT);
+    do_check_inuse_chunk(m, p);
+    assert((sz & CHUNK_ALIGN_MASK) == 0);
+    assert(sz >= MIN_CHUNK_SIZE);
+    assert(sz >= s);
+    /* unless mmapped, size is less than MIN_CHUNK_SIZE more than request */
+    assert(is_mmapped(p) || sz < (s + MIN_CHUNK_SIZE));
+  }
+}
+
+/* Check a tree and its subtrees.  */
+static void do_check_tree(mstate m, tchunkptr t) {
+  tchunkptr head = 0;
+  tchunkptr u = t;
+  bindex_t tindex = t->index;
+  size_t tsize = chunksize(t);
+  bindex_t idx;
+  compute_tree_index(tsize, idx);
+  assert(tindex == idx);
+  assert(tsize >= MIN_LARGE_SIZE);
+  assert(tsize >= minsize_for_tree_index(idx));
+  assert((idx == NTREEBINS-1) || (tsize < minsize_for_tree_index((idx+1))));
+
+  do { /* traverse through chain of same-sized nodes */
+    do_check_any_chunk(m, ((mchunkptr)u));
+    assert(u->index == tindex);
+    assert(chunksize(u) == tsize);
+    assert(!cinuse(u));
+    assert(!next_pinuse(u));
+    assert(u->fd->bk == u);
+    assert(u->bk->fd == u);
+    if (u->parent == 0) {
+      assert(u->child[0] == 0);
+      assert(u->child[1] == 0);
+    }
+    else {
+      assert(head == 0); /* only one node on chain has parent */
+      head = u;
+      assert(u->parent != u);
+      assert (u->parent->child[0] == u ||
+             u->parent->child[1] == u ||
+             *((tbinptr*)(u->parent)) == u);
+      if (u->child[0] != 0) {
+       assert(u->child[0]->parent == u);
+       assert(u->child[0] != u);
+       do_check_tree(m, u->child[0]);
+      }
+      if (u->child[1] != 0) {
+       assert(u->child[1]->parent == u);
+       assert(u->child[1] != u);
+       do_check_tree(m, u->child[1]);
+      }
+      if (u->child[0] != 0 && u->child[1] != 0) {
+       assert(chunksize(u->child[0]) < chunksize(u->child[1]));
+      }
+    }
+    u = u->fd;
+  } while (u != t);
+  assert(head != 0);
+}
+
+/*  Check all the chunks in a treebin.  */
+static void do_check_treebin(mstate m, bindex_t i) {
+  tbinptr* tb = treebin_at(m, i);
+  tchunkptr t = *tb;
+  int empty = (m->treemap & (1U << i)) == 0;
+  if (t == 0)
+    assert(empty);
+  if (!empty)
+    do_check_tree(m, t);
+}
+
+/*  Check all the chunks in a smallbin.  */
+static void do_check_smallbin(mstate m, bindex_t i) {
+  sbinptr b = smallbin_at(m, i);
+  mchunkptr p = b->bk;
+  unsigned int empty = (m->smallmap & (1U << i)) == 0;
+  if (p == b)
+    assert(empty);
+  if (!empty) {
+    for (; p != b; p = p->bk) {
+      size_t size = chunksize(p);
+      mchunkptr q;
+      /* each chunk claims to be free */
+      do_check_free_chunk(m, p);
+      /* chunk belongs in bin */
+      assert(small_index(size) == i);
+      assert(p->bk == b || chunksize(p->bk) == chunksize(p));
+      /* chunk is followed by an inuse chunk */
+      q = next_chunk(p);
+      if (q->head != FENCEPOST_HEAD)
+       do_check_inuse_chunk(m, q);
+    }
+  }
+}
+
+/* Find x in a bin. Used in other check functions. */
+static int bin_find(mstate m, mchunkptr x) {
+  size_t size = chunksize(x);
+  if (is_small(size)) {
+    bindex_t sidx = small_index(size);
+    sbinptr b = smallbin_at(m, sidx);
+    if (smallmap_is_marked(m, sidx)) {
+      mchunkptr p = b;
+      do {
+       if (p == x)
+         return 1;
+      } while ((p = p->fd) != b);
+    }
+  }
+  else {
+    bindex_t tidx;
+    compute_tree_index(size, tidx);
+    if (treemap_is_marked(m, tidx)) {
+      tchunkptr t = *treebin_at(m, tidx);
+      size_t sizebits = size << leftshift_for_tree_index(tidx);
+      while (t != 0 && chunksize(t) != size) {
+       t = t->child[(sizebits >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1];
+       sizebits <<= 1;
+      }
+      if (t != 0) {
+       tchunkptr u = t;
+       do {
+         if (u == (tchunkptr)x)
+           return 1;
+       } while ((u = u->fd) != t);
+      }
+    }
+  }
+  return 0;
+}
+
+/* Traverse each chunk and check it; return total */
+static size_t traverse_and_check(mstate m) {
+  size_t sum = 0;
+  if (is_initialized(m)) {
+    msegmentptr s = &m->seg;
+    sum += m->topsize + TOP_FOOT_SIZE;
+    while (s != 0) {
+      mchunkptr q = align_as_chunk(s->base);
+      mchunkptr lastq = 0;
+      assert(pinuse(q));
+      while (segment_holds(s, q) &&
+            q != m->top && q->head != FENCEPOST_HEAD) {
+       sum += chunksize(q);
+       if (cinuse(q)) {
+         assert(!bin_find(m, q));
+         do_check_inuse_chunk(m, q);
+       }
+       else {
+         assert(q == m->dv || bin_find(m, q));
+         assert(lastq == 0 || cinuse(lastq)); /* Not 2 consecutive free */
+         do_check_free_chunk(m, q);
+       }
+       lastq = q;
+       q = next_chunk(q);
+      }
+      s = s->next;
+    }
+  }
+  return sum;
+}
+
+/* Check all properties of malloc_state. */
+static void do_check_malloc_state(mstate m) {
+  bindex_t i;
+  size_t total;
+  /* check bins */
+  for (i = 0; i < NSMALLBINS; ++i)
+    do_check_smallbin(m, i);
+  for (i = 0; i < NTREEBINS; ++i)
+    do_check_treebin(m, i);
+
+  if (m->dvsize != 0) { /* check dv chunk */
+    do_check_any_chunk(m, m->dv);
+    assert(m->dvsize == chunksize(m->dv));
+    assert(m->dvsize >= MIN_CHUNK_SIZE);
+    assert(bin_find(m, m->dv) == 0);
+  }
+
+  if (m->top != 0) {   /* check top chunk */
+    do_check_top_chunk(m, m->top);
+    /*assert(m->topsize == chunksize(m->top)); redundant */
+    assert(m->topsize > 0);
+    assert(bin_find(m, m->top) == 0);
+  }
+
+  total = traverse_and_check(m);
+  assert(total <= m->footprint);
+  assert(m->footprint <= m->max_footprint);
+}
+#endif /* DEBUG */
+
+/* ----------------------------- statistics ------------------------------ */
+
+#if !NO_MALLINFO
+static struct mallinfo internal_mallinfo(mstate m) {
+  struct mallinfo nm = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+  ensure_initialization();
+  if (!PREACTION(m)) {
+    check_malloc_state(m);
+    if (is_initialized(m)) {
+      size_t nfree = SIZE_T_ONE; /* top always free */
+      size_t mfree = m->topsize + TOP_FOOT_SIZE;
+      size_t sum = mfree;
+      msegmentptr s = &m->seg;
+      while (s != 0) {
+       mchunkptr q = align_as_chunk(s->base);
+       while (segment_holds(s, q) &&
+              q != m->top && q->head != FENCEPOST_HEAD) {
+         size_t sz = chunksize(q);
+         sum += sz;
+         if (!cinuse(q)) {
+           mfree += sz;
+           ++nfree;
+         }
+         q = next_chunk(q);
+       }
+       s = s->next;
+      }
+
+      nm.arena    = sum;
+      nm.ordblks  = nfree;
+      nm.hblkhd   = m->footprint - sum;
+      nm.usmblks  = m->max_footprint;
+      nm.uordblks = m->footprint - mfree;
+      nm.fordblks = mfree;
+      nm.keepcost = m->topsize;
+    }
+
+    POSTACTION(m);
+  }
+  return nm;
+}
+#endif /* !NO_MALLINFO */
+
+static void internal_malloc_stats(mstate m) {
+  ensure_initialization();
+  if (!PREACTION(m)) {
+    size_t maxfp = 0;
+    size_t fp = 0;
+    size_t used = 0;
+    check_malloc_state(m);
+    if (is_initialized(m)) {
+      msegmentptr s = &m->seg;
+      maxfp = m->max_footprint;
+      fp = m->footprint;
+      used = fp - (m->topsize + TOP_FOOT_SIZE);
+
+      while (s != 0) {
+       mchunkptr q = align_as_chunk(s->base);
+       while (segment_holds(s, q) &&
+              q != m->top && q->head != FENCEPOST_HEAD) {
+         if (!cinuse(q))
+           used -= chunksize(q);
+         q = next_chunk(q);
+       }
+       s = s->next;
+      }
+    }
+
+    fprintf(stderr, "max system bytes = %10lu\n", (unsigned long)(maxfp));
+    fprintf(stderr, "system bytes     = %10lu\n", (unsigned long)(fp));
+    fprintf(stderr, "in use bytes     = %10lu\n", (unsigned long)(used));
+
+    POSTACTION(m);
+  }
+}
+
+/* ----------------------- Operations on smallbins ----------------------- */
+
+/*
+  Various forms of linking and unlinking are defined as macros.  Even
+  the ones for trees, which are very long but have very short typical
+  paths.  This is ugly but reduces reliance on inlining support of
+  compilers.
+*/
+
+/* Link a free chunk into a smallbin  */
+#define insert_small_chunk(M, P, S) {\
+  bindex_t I  = small_index(S);\
+  mchunkptr B = smallbin_at(M, I);\
+  mchunkptr F = B;\
+  assert(S >= MIN_CHUNK_SIZE);\
+  if (!smallmap_is_marked(M, I))\
+    mark_smallmap(M, I);\
+  else if (RTCHECK(ok_address(M, B->fd)))\
+    F = B->fd;\
+  else {\
+    CORRUPTION_ERROR_ACTION(M);\
+  }\
+  B->fd = P;\
+  F->bk = P;\
+  P->fd = F;\
+  P->bk = B;\
+}
+
+/* Unlink a chunk from a smallbin  */
+#define unlink_small_chunk(M, P, S) {\
+  mchunkptr F = P->fd;\
+  mchunkptr B = P->bk;\
+  bindex_t I = small_index(S);\
+  assert(P != B);\
+  assert(P != F);\
+  assert(chunksize(P) == small_index2size(I));\
+  if (F == B)\
+    clear_smallmap(M, I);\
+  else if (RTCHECK((F == smallbin_at(M,I) || ok_address(M, F)) &&\
+                  (B == smallbin_at(M,I) || ok_address(M, B)))) {\
+    F->bk = B;\
+    B->fd = F;\
+  }\
+  else {\
+    CORRUPTION_ERROR_ACTION(M);\
+  }\
+}
+
+/* Unlink the first chunk from a smallbin */
+#define unlink_first_small_chunk(M, B, P, I) {\
+  mchunkptr F = P->fd;\
+  assert(P != B);\
+  assert(P != F);\
+  assert(chunksize(P) == small_index2size(I));\
+  if (B == F)\
+    clear_smallmap(M, I);\
+  else if (RTCHECK(ok_address(M, F))) {\
+    B->fd = F;\
+    F->bk = B;\
+  }\
+  else {\
+    CORRUPTION_ERROR_ACTION(M);\
+  }\
+}
+
+
+
+/* Replace dv node, binning the old one */
+/* Used only when dvsize known to be small */
+#define replace_dv(M, P, S) {\
+  size_t DVS = M->dvsize;\
+  if (DVS != 0) {\
+    mchunkptr DV = M->dv;\
+    assert(is_small(DVS));\
+    insert_small_chunk(M, DV, DVS);\
+  }\
+  M->dvsize = S;\
+  M->dv = P;\
+}
+
+/* ------------------------- Operations on trees ------------------------- */
+
+/* Insert chunk into tree */
+#define insert_large_chunk(M, X, S) {\
+  tbinptr* H;\
+  bindex_t I;\
+  compute_tree_index(S, I);\
+  H = treebin_at(M, I);\
+  X->index = I;\
+  X->child[0] = X->child[1] = 0;\
+  if (!treemap_is_marked(M, I)) {\
+    mark_treemap(M, I);\
+    *H = X;\
+    X->parent = (tchunkptr)H;\
+    X->fd = X->bk = X;\
+  }\
+  else {\
+    tchunkptr T = *H;\
+    size_t K = S << leftshift_for_tree_index(I);\
+    for (;;) {\
+      if (chunksize(T) != S) {\
+       tchunkptr* C = &(T->child[(K >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]);\
+       K <<= 1;\
+       if (*C != 0)\
+         T = *C;\
+       else if (RTCHECK(ok_address(M, C))) {\
+         *C = X;\
+         X->parent = T;\
+         X->fd = X->bk = X;\
+         break;\
+       }\
+       else {\
+         CORRUPTION_ERROR_ACTION(M);\
+         break;\
+       }\
+      }\
+      else {\
+       tchunkptr F = T->fd;\
+       if (RTCHECK(ok_address(M, T) && ok_address(M, F))) {\
+         T->fd = F->bk = X;\
+         X->fd = F;\
+         X->bk = T;\
+         X->parent = 0;\
+         break;\
+       }\
+       else {\
+         CORRUPTION_ERROR_ACTION(M);\
+         break;\
+       }\
+      }\
+    }\
+  }\
+}
+
+/*
+  Unlink steps:
+
+  1. If x is a chained node, unlink it from its same-sized fd/bk links
+     and choose its bk node as its replacement.
+  2. If x was the last node of its size, but not a leaf node, it must
+     be replaced with a leaf node (not merely one with an open left or
+     right), to make sure that lefts and rights of descendents
+     correspond properly to bit masks.  We use the rightmost descendent
+     of x.  We could use any other leaf, but this is easy to locate and
+     tends to counteract removal of leftmosts elsewhere, and so keeps
+     paths shorter than minimally guaranteed.  This doesn't loop much
+     because on average a node in a tree is near the bottom.
+  3. If x is the base of a chain (i.e., has parent links) relink
+     x's parent and children to x's replacement (or null if none).
+*/
+
+#define unlink_large_chunk(M, X) {\
+  tchunkptr XP = X->parent;\
+  tchunkptr R;\
+  if (X->bk != X) {\
+    tchunkptr F = X->fd;\
+    R = X->bk;\
+    if (RTCHECK(ok_address(M, F))) {\
+      F->bk = R;\
+      R->fd = F;\
+    }\
+    else {\
+      CORRUPTION_ERROR_ACTION(M);\
+    }\
+  }\
+  else {\
+    tchunkptr* RP;\
+    if (((R = *(RP = &(X->child[1]))) != 0) ||\
+       ((R = *(RP = &(X->child[0]))) != 0)) {\
+      tchunkptr* CP;\
+      while ((*(CP = &(R->child[1])) != 0) ||\
+            (*(CP = &(R->child[0])) != 0)) {\
+       R = *(RP = CP);\
+      }\
+      if (RTCHECK(ok_address(M, RP)))\
+       *RP = 0;\
+      else {\
+       CORRUPTION_ERROR_ACTION(M);\
+      }\
+    }\
+  }\
+  if (XP != 0) {\
+    tbinptr* H = treebin_at(M, X->index);\
+    if (X == *H) {\
+      if ((*H = R) == 0) \
+       clear_treemap(M, X->index);\
+    }\
+    else if (RTCHECK(ok_address(M, XP))) {\
+      if (XP->child[0] == X) \
+       XP->child[0] = R;\
+      else \
+       XP->child[1] = R;\
+    }\
+    else\
+      CORRUPTION_ERROR_ACTION(M);\
+    if (R != 0) {\
+      if (RTCHECK(ok_address(M, R))) {\
+       tchunkptr C0, C1;\
+       R->parent = XP;\
+       if ((C0 = X->child[0]) != 0) {\
+         if (RTCHECK(ok_address(M, C0))) {\
+           R->child[0] = C0;\
+           C0->parent = R;\
+         }\
+         else\
+           CORRUPTION_ERROR_ACTION(M);\
+       }\
+       if ((C1 = X->child[1]) != 0) {\
+         if (RTCHECK(ok_address(M, C1))) {\
+           R->child[1] = C1;\
+           C1->parent = R;\
+         }\
+         else\
+           CORRUPTION_ERROR_ACTION(M);\
+       }\
+      }\
+      else\
+       CORRUPTION_ERROR_ACTION(M);\
+    }\
+  }\
+}
+
+/* Relays to large vs small bin operations */
+
+#define insert_chunk(M, P, S)\
+  if (is_small(S)) insert_small_chunk(M, P, S)\
+  else { tchunkptr TP = (tchunkptr)(P); insert_large_chunk(M, TP, S); }
+
+#define unlink_chunk(M, P, S)\
+  if (is_small(S)) unlink_small_chunk(M, P, S)\
+  else { tchunkptr TP = (tchunkptr)(P); unlink_large_chunk(M, TP); }
+
+
+/* Relays to internal calls to malloc/free from realloc, memalign etc */
+
+#if ONLY_MSPACES
+#define internal_malloc(m, b) mspace_malloc(m, b)
+#define internal_free(m, mem) mspace_free(m,mem);
+#else /* ONLY_MSPACES */
+#if MSPACES
+#define internal_malloc(m, b)\
+   (m == gm)? dlmalloc(b) : mspace_malloc(m, b)
+#define internal_free(m, mem)\
+   if (m == gm) dlfree(mem); else mspace_free(m,mem);
+#else /* MSPACES */
+#define internal_malloc(m, b) dlmalloc(b)
+#define internal_free(m, mem) dlfree(mem)
+#endif /* MSPACES */
+#endif /* ONLY_MSPACES */
+
+/* -----------------------  Direct-mmapping chunks ----------------------- */
+
+/*
+  Directly mmapped chunks are set up with an offset to the start of
+  the mmapped region stored in the prev_foot field of the chunk. This
+  allows reconstruction of the required argument to MUNMAP when freed,
+  and also allows adjustment of the returned chunk to meet alignment
+  requirements (especially in memalign).  There is also enough space
+  allocated to hold a fake next chunk of size SIZE_T_SIZE to maintain
+  the PINUSE bit so frees can be checked.
+*/
+
+/* Malloc using mmap */
+static void* mmap_alloc(mstate m, size_t nb) {
+  size_t mmsize = mmap_align(nb + SIX_SIZE_T_SIZES + CHUNK_ALIGN_MASK);
+  if (mmsize > nb) {     /* Check for wrap around 0 */
+    char* mm = (char*)(CALL_DIRECT_MMAP(mmsize));
+    if (mm != CMFAIL) {
+      size_t offset = align_offset(chunk2mem(mm));
+      size_t psize = mmsize - offset - MMAP_FOOT_PAD;
+      mchunkptr p = (mchunkptr)(mm + offset);
+      p->prev_foot = offset | IS_MMAPPED_BIT;
+      (p)->head = (psize|CINUSE_BIT);
+      mark_inuse_foot(m, p, psize);
+      chunk_plus_offset(p, psize)->head = FENCEPOST_HEAD;
+      chunk_plus_offset(p, psize+SIZE_T_SIZE)->head = 0;
+
+      if (mm < m->least_addr)
+       m->least_addr = mm;
+      if ((m->footprint += mmsize) > m->max_footprint)
+       m->max_footprint = m->footprint;
+      assert(is_aligned(chunk2mem(p)));
+      check_mmapped_chunk(m, p);
+      return chunk2mem(p);
+    }
+  }
+  return 0;
+}
+
+/* Realloc using mmap */
+static mchunkptr mmap_resize(mstate m, mchunkptr oldp, size_t nb) {
+  size_t oldsize = chunksize(oldp);
+  if (is_small(nb)) /* Can't shrink mmap regions below small size */
+    return 0;
+  /* Keep old chunk if big enough but not too big */
+  if (oldsize >= nb + SIZE_T_SIZE &&
+      (oldsize - nb) <= (mparams.granularity << 1))
+    return oldp;
+  else {
+    size_t offset = oldp->prev_foot & ~IS_MMAPPED_BIT;
+    size_t oldmmsize = oldsize + offset + MMAP_FOOT_PAD;
+    size_t newmmsize = mmap_align(nb + SIX_SIZE_T_SIZES + CHUNK_ALIGN_MASK);
+    char* cp = (char*)CALL_MREMAP((char*)oldp - offset,
+                                 oldmmsize, newmmsize, 1);
+    if (cp != CMFAIL) {
+      mchunkptr newp = (mchunkptr)(cp + offset);
+      size_t psize = newmmsize - offset - MMAP_FOOT_PAD;
+      newp->head = (psize|CINUSE_BIT);
+      mark_inuse_foot(m, newp, psize);
+      chunk_plus_offset(newp, psize)->head = FENCEPOST_HEAD;
+      chunk_plus_offset(newp, psize+SIZE_T_SIZE)->head = 0;
+
+      if (cp < m->least_addr)
+       m->least_addr = cp;
+      if ((m->footprint += newmmsize - oldmmsize) > m->max_footprint)
+       m->max_footprint = m->footprint;
+      check_mmapped_chunk(m, newp);
+      return newp;
+    }
+  }
+  return 0;
+}
+
+/* -------------------------- mspace management -------------------------- */
+
+/* Initialize top chunk and its size */
+static void init_top(mstate m, mchunkptr p, size_t psize) {
+  /* Ensure alignment */
+  size_t offset = align_offset(chunk2mem(p));
+  p = (mchunkptr)((char*)p + offset);
+  psize -= offset;
+
+  m->top = p;
+  m->topsize = psize;
+  p->head = psize | PINUSE_BIT;
+  /* set size of fake trailing chunk holding overhead space only once */
+  chunk_plus_offset(p, psize)->head = TOP_FOOT_SIZE;
+  m->trim_check = mparams.trim_threshold; /* reset on each update */
+}
+
+/* Initialize bins for a new mstate that is otherwise zeroed out */
+static void init_bins(mstate m) {
+  /* Establish circular links for smallbins */
+  bindex_t i;
+  for (i = 0; i < NSMALLBINS; ++i) {
+    sbinptr bin = smallbin_at(m,i);
+    bin->fd = bin->bk = bin;
+  }
+}
+
+#if PROCEED_ON_ERROR
+
+/* default corruption action */
+static void reset_on_error(mstate m) {
+  int i;
+  ++malloc_corruption_error_count;
+  /* Reinitialize fields to forget about all memory */
+  m->smallbins = m->treebins = 0;
+  m->dvsize = m->topsize = 0;
+  m->seg.base = 0;
+  m->seg.size = 0;
+  m->seg.next = 0;
+  m->top = m->dv = 0;
+  for (i = 0; i < NTREEBINS; ++i)
+    *treebin_at(m, i) = 0;
+  init_bins(m);
+}
+#endif /* PROCEED_ON_ERROR */
+
+/* Allocate chunk and prepend remainder with chunk in successor base. */
+static void* prepend_alloc(mstate m, char* newbase, char* oldbase,
+                          size_t nb) {
+  mchunkptr p = align_as_chunk(newbase);
+  mchunkptr oldfirst = align_as_chunk(oldbase);
+  size_t psize = (char*)oldfirst - (char*)p;
+  mchunkptr q = chunk_plus_offset(p, nb);
+  size_t qsize = psize - nb;
+  set_size_and_pinuse_of_inuse_chunk(m, p, nb);
+
+  assert((char*)oldfirst > (char*)q);
+  assert(pinuse(oldfirst));
+  assert(qsize >= MIN_CHUNK_SIZE);
+
+  /* consolidate remainder with first chunk of old base */
+  if (oldfirst == m->top) {
+    size_t tsize = m->topsize += qsize;
+    m->top = q;
+    q->head = tsize | PINUSE_BIT;
+    check_top_chunk(m, q);
+  }
+  else if (oldfirst == m->dv) {
+    size_t dsize = m->dvsize += qsize;
+    m->dv = q;
+    set_size_and_pinuse_of_free_chunk(q, dsize);
+  }
+  else {
+    if (!cinuse(oldfirst)) {
+      size_t nsize = chunksize(oldfirst);
+      unlink_chunk(m, oldfirst, nsize);
+      oldfirst = chunk_plus_offset(oldfirst, nsize);
+      qsize += nsize;
+    }
+    set_free_with_pinuse(q, qsize, oldfirst);
+    insert_chunk(m, q, qsize);
+    check_free_chunk(m, q);
+  }
+
+  check_malloced_chunk(m, chunk2mem(p), nb);
+  return chunk2mem(p);
+}
+
+/* Add a segment to hold a new noncontiguous region */
+static void add_segment(mstate m, char* tbase, size_t tsize, flag_t mmapped) {
+  /* Determine locations and sizes of segment, fenceposts, old top */
+  char* old_top = (char*)m->top;
+  msegmentptr oldsp = segment_holding(m, old_top);
+  char* old_end = oldsp->base + oldsp->size;
+  size_t ssize = pad_request(sizeof(struct malloc_segment));
+  char* rawsp = old_end - (ssize + FOUR_SIZE_T_SIZES + CHUNK_ALIGN_MASK);
+  size_t offset = align_offset(chunk2mem(rawsp));
+  char* asp = rawsp + offset;
+  char* csp = (asp < (old_top + MIN_CHUNK_SIZE))? old_top : asp;
+  mchunkptr sp = (mchunkptr)csp;
+  msegmentptr ss = (msegmentptr)(chunk2mem(sp));
+  mchunkptr tnext = chunk_plus_offset(sp, ssize);
+  mchunkptr p = tnext;
+  int nfences = 0;
+
+  /* reset top to new space */
+  init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE);
+
+  /* Set up segment record */
+  assert(is_aligned(ss));
+  set_size_and_pinuse_of_inuse_chunk(m, sp, ssize);
+  *ss = m->seg; /* Push current record */
+  m->seg.base = tbase;
+  m->seg.size = tsize;
+  m->seg.sflags = mmapped;
+  m->seg.next = ss;
+
+  /* Insert trailing fenceposts */
+  for (;;) {
+    mchunkptr nextp = chunk_plus_offset(p, SIZE_T_SIZE);
+    p->head = FENCEPOST_HEAD;
+    ++nfences;
+    if ((char*)(&(nextp->head)) < old_end)
+      p = nextp;
+    else
+      break;
+  }
+  assert(nfences >= 2);
+
+  /* Insert the rest of old top into a bin as an ordinary free chunk */
+  if (csp != old_top) {
+    mchunkptr q = (mchunkptr)old_top;
+    size_t psize = csp - old_top;
+    mchunkptr tn = chunk_plus_offset(q, psize);
+    set_free_with_pinuse(q, psize, tn);
+    insert_chunk(m, q, psize);
+  }
+
+  check_top_chunk(m, m->top);
+}
+
+/* -------------------------- System allocation -------------------------- */
+
+/* Get memory from system using MORECORE or MMAP */
+static void* sys_alloc(mstate m, size_t nb) {
+  char* tbase = CMFAIL;
+  size_t tsize = 0;
+  flag_t mmap_flag = 0;
+
+  ensure_initialization();
+
+  /* Directly map large chunks */
+  if (use_mmap(m) && nb >= mparams.mmap_threshold) {
+    void* mem = mmap_alloc(m, nb);
+    if (mem != 0)
+      return mem;
+  }
+
+  /*
+    Try getting memory in any of three ways (in most-preferred to
+    least-preferred order):
+    1. A call to MORECORE that can normally contiguously extend memory.
+       (disabled if not MORECORE_CONTIGUOUS or not HAVE_MORECORE or
+       or main space is mmapped or a previous contiguous call failed)
+    2. A call to MMAP new space (disabled if not HAVE_MMAP).
+       Note that under the default settings, if MORECORE is unable to
+       fulfill a request, and HAVE_MMAP is true, then mmap is
+       used as a noncontiguous system allocator. This is a useful backup
+       strategy for systems with holes in address spaces -- in this case
+       sbrk cannot contiguously expand the heap, but mmap may be able to
+       find space.
+    3. A call to MORECORE that cannot usually contiguously extend memory.
+       (disabled if not HAVE_MORECORE)
+
+   In all cases, we need to request enough bytes from system to ensure
+   we can malloc nb bytes upon success, so pad with enough space for
+   top_foot, plus alignment-pad to make sure we don't lose bytes if
+   not on boundary, and round this up to a granularity unit.
+  */
+
+  if (MORECORE_CONTIGUOUS && !use_noncontiguous(m)) {
+    char* br = CMFAIL;
+    msegmentptr ss = (m->top == 0)? 0 : segment_holding(m, (char*)m->top);
+    size_t asize = 0;
+    ACQUIRE_MALLOC_GLOBAL_LOCK();
+
+    if (ss == 0) {  /* First time through or recovery */
+      char* base = (char*)CALL_MORECORE(0);
+      if (base != CMFAIL) {
+       asize = granularity_align(nb + SYS_ALLOC_PADDING);
+       /* Adjust to end on a page boundary */
+       if (!is_page_aligned(base))
+         asize += (page_align((size_t)base) - (size_t)base);
+       /* Can't call MORECORE if size is negative when treated as signed */
+       if (asize < HALF_MAX_SIZE_T &&
+           (br = (char*)(CALL_MORECORE(asize))) == base) {
+         tbase = base;
+         tsize = asize;
+       }
+      }
+    }
+    else {
+      /* Subtract out existing available top space from MORECORE request. */
+      asize = granularity_align(nb - m->topsize + SYS_ALLOC_PADDING);
+      /* Use mem here only if it did continuously extend old space */
+      if (asize < HALF_MAX_SIZE_T &&
+         (br = (char*)(CALL_MORECORE(asize))) == ss->base+ss->size) {
+       tbase = br;
+       tsize = asize;
+      }
+    }
+
+    if (tbase == CMFAIL) {    /* Cope with partial failure */
+      if (br != CMFAIL) {    /* Try to use/extend the space we did get */
+       if (asize < HALF_MAX_SIZE_T &&
+           asize < nb + SYS_ALLOC_PADDING) {
+         size_t esize = granularity_align(nb + SYS_ALLOC_PADDING - asize);
+         if (esize < HALF_MAX_SIZE_T) {
+           char* end = (char*)CALL_MORECORE(esize);
+           if (end != CMFAIL)
+             asize += esize;
+           else {            /* Can't use; try to release */
+             (void) CALL_MORECORE(-asize);
+             br = CMFAIL;
+           }
+         }
+       }
+      }
+      if (br != CMFAIL) {    /* Use the space we did get */
+       tbase = br;
+       tsize = asize;
+      }
+      else
+       disable_contiguous(m); /* Don't try contiguous path in the future */
+    }
+
+    RELEASE_MALLOC_GLOBAL_LOCK();
+  }
+
+  if (HAVE_MMAP && tbase == CMFAIL) {  /* Try MMAP */
+    size_t rsize = granularity_align(nb + SYS_ALLOC_PADDING);
+    if (rsize > nb) { /* Fail if wraps around zero */
+      char* mp = (char*)(CALL_MMAP(rsize));
+      if (mp != CMFAIL) {
+       tbase = mp;
+       tsize = rsize;
+       mmap_flag = IS_MMAPPED_BIT;
+      }
+    }
+  }
+
+  if (HAVE_MORECORE && tbase == CMFAIL) { /* Try noncontiguous MORECORE */
+    size_t asize = granularity_align(nb + SYS_ALLOC_PADDING);
+    if (asize < HALF_MAX_SIZE_T) {
+      char* br = CMFAIL;
+      char* end = CMFAIL;
+      ACQUIRE_MALLOC_GLOBAL_LOCK();
+      br = (char*)(CALL_MORECORE(asize));
+      end = (char*)(CALL_MORECORE(0));
+      RELEASE_MALLOC_GLOBAL_LOCK();
+      if (br != CMFAIL && end != CMFAIL && br < end) {
+       size_t ssize = end - br;
+       if (ssize > nb + TOP_FOOT_SIZE) {
+         tbase = br;
+         tsize = ssize;
+       }
+      }
+    }
+  }
+
+  if (tbase != CMFAIL) {
+
+    if ((m->footprint += tsize) > m->max_footprint)
+      m->max_footprint = m->footprint;
+
+    if (!is_initialized(m)) { /* first-time initialization */
+      m->seg.base = m->least_addr = tbase;
+      m->seg.size = tsize;
+      m->seg.sflags = mmap_flag;
+      m->magic = mparams.magic;
+      m->release_checks = MAX_RELEASE_CHECK_RATE;
+      init_bins(m);
+#if !ONLY_MSPACES
+      if (is_global(m))
+       init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE);
+      else
+#endif
+      {
+       /* Offset top by embedded malloc_state */
+       mchunkptr mn = next_chunk(mem2chunk(m));
+       init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) -TOP_FOOT_SIZE);
+      }
+    }
+
+    else {
+      /* Try to merge with an existing segment */
+      msegmentptr sp = &m->seg;
+      /* Only consider most recent segment if traversal suppressed */
+      while (sp != 0 && tbase != sp->base + sp->size)
+       sp = (NO_SEGMENT_TRAVERSAL) ? 0 : sp->next;
+      if (sp != 0 &&
+         !is_extern_segment(sp) &&
+         (sp->sflags & IS_MMAPPED_BIT) == mmap_flag &&
+         segment_holds(sp, m->top)) { /* append */
+       sp->size += tsize;
+       init_top(m, m->top, m->topsize + tsize);
+      }
+      else {
+       if (tbase < m->least_addr)
+         m->least_addr = tbase;
+       sp = &m->seg;
+       while (sp != 0 && sp->base != tbase + tsize)
+         sp = (NO_SEGMENT_TRAVERSAL) ? 0 : sp->next;
+       if (sp != 0 &&
+           !is_extern_segment(sp) &&
+           (sp->sflags & IS_MMAPPED_BIT) == mmap_flag) {
+         char* oldbase = sp->base;
+         sp->base = tbase;
+         sp->size += tsize;
+         return prepend_alloc(m, tbase, oldbase, nb);
+       }
+       else
+         add_segment(m, tbase, tsize, mmap_flag);
+      }
+    }
+
+    if (nb < m->topsize) { /* Allocate from new or extended top space */
+      size_t rsize = m->topsize -= nb;
+      mchunkptr p = m->top;
+      mchunkptr r = m->top = chunk_plus_offset(p, nb);
+      r->head = rsize | PINUSE_BIT;
+      set_size_and_pinuse_of_inuse_chunk(m, p, nb);
+      check_top_chunk(m, m->top);
+      check_malloced_chunk(m, chunk2mem(p), nb);
+      return chunk2mem(p);
+    }
+  }
+
+  MALLOC_FAILURE_ACTION;
+  return 0;
+}
+
+/* -----------------------  system deallocation -------------------------- */
+
+/* Unmap and unlink any mmapped segments that don't contain used chunks */
+static size_t release_unused_segments(mstate m) {
+  size_t released = 0;
+  int nsegs = 0;
+  msegmentptr pred = &m->seg;
+  msegmentptr sp = pred->next;
+  while (sp != 0) {
+    char* base = sp->base;
+    size_t size = sp->size;
+    msegmentptr next = sp->next;
+    ++nsegs;
+    if (is_mmapped_segment(sp) && !is_extern_segment(sp)) {
+      mchunkptr p = align_as_chunk(base);
+      size_t psize = chunksize(p);
+      /* Can unmap if first chunk holds entire segment and not pinned */
+      if (!cinuse(p) && (char*)p + psize >= base + size - TOP_FOOT_SIZE) {
+       tchunkptr tp = (tchunkptr)p;
+       assert(segment_holds(sp, (char*)sp));
+       if (p == m->dv) {
+         m->dv = 0;
+         m->dvsize = 0;
+       }
+       else {
+         unlink_large_chunk(m, tp);
+       }
+       if (CALL_MUNMAP(base, size) == 0) {
+         released += size;
+         m->footprint -= size;
+         /* unlink obsoleted record */
+         sp = pred;
+         sp->next = next;
+       }
+       else { /* back out if cannot unmap */
+         insert_large_chunk(m, tp, psize);
+       }
+      }
+    }
+    if (NO_SEGMENT_TRAVERSAL) /* scan only first segment */
+      break;
+    pred = sp;
+    sp = next;
+  }
+  /* Reset check counter */
+  m->release_checks = ((nsegs > MAX_RELEASE_CHECK_RATE)?
+                      nsegs : MAX_RELEASE_CHECK_RATE);
+  return released;
+}
+
+static int sys_trim(mstate m, size_t pad) {
+  size_t released = 0;
+  ensure_initialization();
+  if (pad < MAX_REQUEST && is_initialized(m)) {
+    pad += TOP_FOOT_SIZE; /* ensure enough room for segment overhead */
+
+    if (m->topsize > pad) {
+      /* Shrink top space in granularity-size units, keeping at least one */
+      size_t unit = mparams.granularity;
+      size_t extra = ((m->topsize - pad + (unit - SIZE_T_ONE)) / unit -
+                     SIZE_T_ONE) * unit;
+      msegmentptr sp = segment_holding(m, (char*)m->top);
+
+      if (!is_extern_segment(sp)) {
+       if (is_mmapped_segment(sp)) {
+         if (HAVE_MMAP &&
+             sp->size >= extra &&
+             !has_segment_link(m, sp)) { /* can't shrink if pinned */
+           size_t newsize = sp->size - extra;
+           /* Prefer mremap, fall back to munmap */
+           if ((CALL_MREMAP(sp->base, sp->size, newsize, 0) != MFAIL) ||
+               (CALL_MUNMAP(sp->base + newsize, extra) == 0)) {
+             released = extra;
+           }
+         }
+       }
+       else if (HAVE_MORECORE) {
+         if (extra >= HALF_MAX_SIZE_T) /* Avoid wrapping negative */
+           extra = (HALF_MAX_SIZE_T) + SIZE_T_ONE - unit;
+         ACQUIRE_MALLOC_GLOBAL_LOCK();
+         {
+           /* Make sure end of memory is where we last set it. */
+           char* old_br = (char*)(CALL_MORECORE(0));
+           if (old_br == sp->base + sp->size) {
+             char* rel_br = (char*)(CALL_MORECORE(-extra));
+             char* new_br = (char*)(CALL_MORECORE(0));
+             if (rel_br != CMFAIL && new_br < old_br)
+               released = old_br - new_br;
+           }
+         }
+         RELEASE_MALLOC_GLOBAL_LOCK();
+       }
+      }
+
+      if (released != 0) {
+       sp->size -= released;
+       m->footprint -= released;
+       init_top(m, m->top, m->topsize - released);
+       check_top_chunk(m, m->top);
+      }
+    }
+
+    /* Unmap any unused mmapped segments */
+    if (HAVE_MMAP)
+      released += release_unused_segments(m);
+
+    /* On failure, disable autotrim to avoid repeated failed future calls */
+    if (released == 0 && m->topsize > m->trim_check)
+      m->trim_check = MAX_SIZE_T;
+  }
+
+  return (released != 0)? 1 : 0;
+}
+
+
+/* ---------------------------- malloc support --------------------------- */
+
+/* allocate a large request from the best fitting chunk in a treebin */
+static void* tmalloc_large(mstate m, size_t nb) {
+  tchunkptr v = 0;
+  size_t rsize = -nb; /* Unsigned negation */
+  tchunkptr t;
+  bindex_t idx;
+  compute_tree_index(nb, idx);
+  if ((t = *treebin_at(m, idx)) != 0) {
+    /* Traverse tree for this bin looking for node with size == nb */
+    size_t sizebits = nb << leftshift_for_tree_index(idx);
+    tchunkptr rst = 0;  /* The deepest untaken right subtree */
+    for (;;) {
+      tchunkptr rt;
+      size_t trem = chunksize(t) - nb;
+      if (trem < rsize) {
+       v = t;
+       if ((rsize = trem) == 0)
+         break;
+      }
+      rt = t->child[1];
+      t = t->child[(sizebits >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1];
+      if (rt != 0 && rt != t)
+       rst = rt;
+      if (t == 0) {
+       t = rst; /* set t to least subtree holding sizes > nb */
+       break;
+      }
+      sizebits <<= 1;
+    }
+  }
+  if (t == 0 && v == 0) { /* set t to root of next non-empty treebin */
+    binmap_t leftbits = left_bits(idx2bit(idx)) & m->treemap;
+    if (leftbits != 0) {
+      bindex_t i;
+      binmap_t leastbit = least_bit(leftbits);
+      compute_bit2idx(leastbit, i);
+      t = *treebin_at(m, i);
+    }
+  }
+
+  while (t != 0) { /* find smallest of tree or subtree */
+    size_t trem = chunksize(t) - nb;
+    if (trem < rsize) {
+      rsize = trem;
+      v = t;
+    }
+    t = leftmost_child(t);
+  }
+
+  /*  If dv is a better fit, return 0 so malloc will use it */
+  if (v != 0 && rsize < (size_t)(m->dvsize - nb)) {
+    if (RTCHECK(ok_address(m, v))) { /* split */
+      mchunkptr r = chunk_plus_offset(v, nb);
+      assert(chunksize(v) == rsize + nb);
+      if (RTCHECK(ok_next(v, r))) {
+       unlink_large_chunk(m, v);
+       if (rsize < MIN_CHUNK_SIZE)
+         set_inuse_and_pinuse(m, v, (rsize + nb));
+       else {
+         set_size_and_pinuse_of_inuse_chunk(m, v, nb);
+         set_size_and_pinuse_of_free_chunk(r, rsize);
+         insert_chunk(m, r, rsize);
+       }
+       return chunk2mem(v);
+      }
+    }
+    CORRUPTION_ERROR_ACTION(m);
+  }
+  return 0;
+}
+
+/* allocate a small request from the best fitting chunk in a treebin */
+static void* tmalloc_small(mstate m, size_t nb) {
+  tchunkptr t, v;
+  size_t rsize;
+  bindex_t i;
+  binmap_t leastbit = least_bit(m->treemap);
+  compute_bit2idx(leastbit, i);
+  v = t = *treebin_at(m, i);
+  rsize = chunksize(t) - nb;
+
+  while ((t = leftmost_child(t)) != 0) {
+    size_t trem = chunksize(t) - nb;
+    if (trem < rsize) {
+      rsize = trem;
+      v = t;
+    }
+  }
+
+  if (RTCHECK(ok_address(m, v))) {
+    mchunkptr r = chunk_plus_offset(v, nb);
+    assert(chunksize(v) == rsize + nb);
+    if (RTCHECK(ok_next(v, r))) {
+      unlink_large_chunk(m, v);
+      if (rsize < MIN_CHUNK_SIZE)
+       set_inuse_and_pinuse(m, v, (rsize + nb));
+      else {
+       set_size_and_pinuse_of_inuse_chunk(m, v, nb);
+       set_size_and_pinuse_of_free_chunk(r, rsize);
+       replace_dv(m, r, rsize);
+      }
+      return chunk2mem(v);
+    }
+  }
+
+  CORRUPTION_ERROR_ACTION(m);
+  return 0;
+}
+
+/* --------------------------- realloc support --------------------------- */
+
+static void* internal_realloc(mstate m, void* oldmem, size_t bytes) {
+  if (bytes >= MAX_REQUEST) {
+    MALLOC_FAILURE_ACTION;
+    return 0;
+  }
+  if (!PREACTION(m)) {
+    mchunkptr oldp = mem2chunk(oldmem);
+    size_t oldsize = chunksize(oldp);
+    mchunkptr next = chunk_plus_offset(oldp, oldsize);
+    mchunkptr newp = 0;
+    void* extra = 0;
+
+    /* Try to either shrink or extend into top. Else malloc-copy-free */
+
+    if (RTCHECK(ok_address(m, oldp) && ok_cinuse(oldp) &&
+               ok_next(oldp, next) && ok_pinuse(next))) {
+      size_t nb = request2size(bytes);
+      if (is_mmapped(oldp))
+       newp = mmap_resize(m, oldp, nb);
+      else if (oldsize >= nb) { /* already big enough */
+       size_t rsize = oldsize - nb;
+       newp = oldp;
+       if (rsize >= MIN_CHUNK_SIZE) {
+         mchunkptr remainder = chunk_plus_offset(newp, nb);
+         set_inuse(m, newp, nb);
+         set_inuse(m, remainder, rsize);
+         extra = chunk2mem(remainder);
+       }
+      }
+      else if (next == m->top && oldsize + m->topsize > nb) {
+       /* Expand into top */
+       size_t newsize = oldsize + m->topsize;
+       size_t newtopsize = newsize - nb;
+       mchunkptr newtop = chunk_plus_offset(oldp, nb);
+       set_inuse(m, oldp, nb);
+       newtop->head = newtopsize |PINUSE_BIT;
+       m->top = newtop;
+       m->topsize = newtopsize;
+       newp = oldp;
+      }
+    }
+    else {
+      USAGE_ERROR_ACTION(m, oldmem);
+      POSTACTION(m);
+      return 0;
+    }
+
+    POSTACTION(m);
+
+    if (newp != 0) {
+      if (extra != 0) {
+       internal_free(m, extra);
+      }
+      check_inuse_chunk(m, newp);
+      return chunk2mem(newp);
+    }
+    else {
+      void* newmem = internal_malloc(m, bytes);
+      if (newmem != 0) {
+       size_t oc = oldsize - overhead_for(oldp);
+       memcpy(newmem, oldmem, (oc < bytes)? oc : bytes);
+       internal_free(m, oldmem);
+      }
+      return newmem;
+    }
+  }
+  return 0;
+}
+
+/* --------------------------- memalign support -------------------------- */
+
+static void* internal_memalign(mstate m, size_t alignment, size_t bytes) {
+  if (alignment <= MALLOC_ALIGNMENT)    /* Can just use malloc */
+    return internal_malloc(m, bytes);
+  if (alignment <  MIN_CHUNK_SIZE) /* must be at least a minimum chunk size */
+    alignment = MIN_CHUNK_SIZE;
+  if ((alignment & (alignment-SIZE_T_ONE)) != 0) {/* Ensure a power of 2 */
+    size_t a = MALLOC_ALIGNMENT << 1;
+    while (a < alignment) a <<= 1;
+    alignment = a;
+  }
+
+  if (bytes >= MAX_REQUEST - alignment) {
+    if (m != 0)  { /* Test isn't needed but avoids compiler warning */
+      MALLOC_FAILURE_ACTION;
+    }
+  }
+  else {
+    size_t nb = request2size(bytes);
+    size_t req = nb + alignment + MIN_CHUNK_SIZE - CHUNK_OVERHEAD;
+    char* mem = (char*)internal_malloc(m, req);
+    if (mem != 0) {
+      void* leader = 0;
+      void* trailer = 0;
+      mchunkptr p = mem2chunk(mem);
+
+      if (PREACTION(m)) return 0;
+      if ((((size_t)(mem)) % alignment) != 0) { /* misaligned */
+       /*
+         Find an aligned spot inside chunk.  Since we need to give
+         back leading space in a chunk of at least MIN_CHUNK_SIZE, if
+         the first calculation places us at a spot with less than
+         MIN_CHUNK_SIZE leader, we can move to the next aligned spot.
+         We've allocated enough total room so that this is always
+         possible.
+       */
+       char* br = (char*)mem2chunk((size_t)(((size_t)(mem +
+                                                      alignment -
+                                                      SIZE_T_ONE)) &
+                                            -alignment));
+       char* pos = ((size_t)(br - (char*)(p)) >= MIN_CHUNK_SIZE)?
+         br : br+alignment;
+       mchunkptr newp = (mchunkptr)pos;
+       size_t leadsize = pos - (char*)(p);
+       size_t newsize = chunksize(p) - leadsize;
+
+       if (is_mmapped(p)) { /* For mmapped chunks, just adjust offset */
+         newp->prev_foot = p->prev_foot + leadsize;
+         newp->head = (newsize|CINUSE_BIT);
+       }
+       else { /* Otherwise, give back leader, use the rest */
+         set_inuse(m, newp, newsize);
+         set_inuse(m, p, leadsize);
+         leader = chunk2mem(p);
+       }
+       p = newp;
+      }
+
+      /* Give back spare room at the end */
+      if (!is_mmapped(p)) {
+       size_t size = chunksize(p);
+       if (size > nb + MIN_CHUNK_SIZE) {
+         size_t remainder_size = size - nb;
+         mchunkptr remainder = chunk_plus_offset(p, nb);
+         set_inuse(m, p, nb);
+         set_inuse(m, remainder, remainder_size);
+         trailer = chunk2mem(remainder);
+       }
+      }
+
+      assert (chunksize(p) >= nb);
+      assert((((size_t)(chunk2mem(p))) % alignment) == 0);
+      check_inuse_chunk(m, p);
+      POSTACTION(m);
+      if (leader != 0) {
+       internal_free(m, leader);
+      }
+      if (trailer != 0) {
+       internal_free(m, trailer);
+      }
+      return chunk2mem(p);
+    }
+  }
+  return 0;
+}
+
+/* ------------------------ comalloc/coalloc support --------------------- */
+
+static void** ialloc(mstate m,
+                    size_t n_elements,
+                    size_t* sizes,
+                    int opts,
+                    void* chunks[]) {
+  /*
+    This provides common support for independent_X routines, handling
+    all of the combinations that can result.
+
+    The opts arg has:
+    bit 0 set if all elements are same size (using sizes[0])
+    bit 1 set if elements should be zeroed
+  */
+
+  size_t    element_size;   /* chunksize of each element, if all same */
+  size_t    contents_size;  /* total size of elements */
+  size_t    array_size;     /* request size of pointer array */
+  void*     mem;            /* malloced aggregate space */
+  mchunkptr p;              /* corresponding chunk */
+  size_t    remainder_size; /* remaining bytes while splitting */
+  void**    marray;         /* either "chunks" or malloced ptr array */
+  mchunkptr array_chunk;    /* chunk for malloced ptr array */
+  flag_t    was_enabled;    /* to disable mmap */
+  size_t    size;
+  size_t    i;
+
+  ensure_initialization();
+  /* compute array length, if needed */
+  if (chunks != 0) {
+    if (n_elements == 0)
+      return chunks; /* nothing to do */
+    marray = chunks;
+    array_size = 0;
+  }
+  else {
+    /* if empty req, must still return chunk representing empty array */
+    if (n_elements == 0)
+      return (void**)internal_malloc(m, 0);
+    marray = 0;
+    array_size = request2size(n_elements * (sizeof(void*)));
+  }
+
+  /* compute total element size */
+  if (opts & 0x1) { /* all-same-size */
+    element_size = request2size(*sizes);
+    contents_size = n_elements * element_size;
+  }
+  else { /* add up all the sizes */
+    element_size = 0;
+    contents_size = 0;
+    for (i = 0; i != n_elements; ++i)
+      contents_size += request2size(sizes[i]);
+  }
+
+  size = contents_size + array_size;
+
+  /*
+     Allocate the aggregate chunk.  First disable direct-mmapping so
+     malloc won't use it, since we would not be able to later
+     free/realloc space internal to a segregated mmap region.
+  */
+  was_enabled = use_mmap(m);
+  disable_mmap(m);
+  mem = internal_malloc(m, size - CHUNK_OVERHEAD);
+  if (was_enabled)
+    enable_mmap(m);
+  if (mem == 0)
+    return 0;
+
+  if (PREACTION(m)) return 0;
+  p = mem2chunk(mem);
+  remainder_size = chunksize(p);
+
+  assert(!is_mmapped(p));
+
+  if (opts & 0x2) {       /* optionally clear the elements */
+    memset((size_t*)mem, 0, remainder_size - SIZE_T_SIZE - array_size);
+  }
+
+  /* If not provided, allocate the pointer array as final part of chunk */
+  if (marray == 0) {
+    size_t  array_chunk_size;
+    array_chunk = chunk_plus_offset(p, contents_size);
+    array_chunk_size = remainder_size - contents_size;
+    marray = (void**) (chunk2mem(array_chunk));
+    set_size_and_pinuse_of_inuse_chunk(m, array_chunk, array_chunk_size);
+    remainder_size = contents_size;
+  }
+
+  /* split out elements */
+  for (i = 0; ; ++i) {
+    marray[i] = chunk2mem(p);
+    if (i != n_elements-1) {
+      if (element_size != 0)
+       size = element_size;
+      else
+       size = request2size(sizes[i]);
+      remainder_size -= size;
+      set_size_and_pinuse_of_inuse_chunk(m, p, size);
+      p = chunk_plus_offset(p, size);
+    }
+    else { /* the final element absorbs any overallocation slop */
+      set_size_and_pinuse_of_inuse_chunk(m, p, remainder_size);
+      break;
+    }
+  }
+
+#if DEBUG
+  if (marray != chunks) {
+    /* final element must have exactly exhausted chunk */
+    if (element_size != 0) {
+      assert(remainder_size == element_size);
+    }
+    else {
+      assert(remainder_size == request2size(sizes[i]));
+    }
+    check_inuse_chunk(m, mem2chunk(marray));
+  }
+  for (i = 0; i != n_elements; ++i)
+    check_inuse_chunk(m, mem2chunk(marray[i]));
+
+#endif /* DEBUG */
+
+  POSTACTION(m);
+  return marray;
+}
+
+
+/* -------------------------- public routines ---------------------------- */
+
+#if !ONLY_MSPACES
+
+void* dlmalloc(size_t bytes) {
+  /*
+     Basic algorithm:
+     If a small request (< 256 bytes minus per-chunk overhead):
+       1. If one exists, use a remainderless chunk in associated smallbin.
+         (Remainderless means that there are too few excess bytes to
+         represent as a chunk.)
+       2. If it is big enough, use the dv chunk, which is normally the
+         chunk adjacent to the one used for the most recent small request.
+       3. If one exists, split the smallest available chunk in a bin,
+         saving remainder in dv.
+       4. If it is big enough, use the top chunk.
+       5. If available, get memory from system and use it
+     Otherwise, for a large request:
+       1. Find the smallest available binned chunk that fits, and use it
+         if it is better fitting than dv chunk, splitting if necessary.
+       2. If better fitting than any binned chunk, use the dv chunk.
+       3. If it is big enough, use the top chunk.
+       4. If request size >= mmap threshold, try to directly mmap this chunk.
+       5. If available, get memory from system and use it
+
+     The ugly goto's here ensure that postaction occurs along all paths.
+  */
+
+#if USE_LOCKS
+  ensure_initialization(); /* initialize in sys_alloc if not using locks */
+#endif
+
+  if (!PREACTION(gm)) {
+    void* mem;
+    size_t nb;
+    if (bytes <= MAX_SMALL_REQUEST) {
+      bindex_t idx;
+      binmap_t smallbits;
+      nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes);
+      idx = small_index(nb);
+      smallbits = gm->smallmap >> idx;
+
+      if ((smallbits & 0x3U) != 0) { /* Remainderless fit to a smallbin. */
+       mchunkptr b, p;
+       idx += ~smallbits & 1;       /* Uses next bin if idx empty */
+       b = smallbin_at(gm, idx);
+       p = b->fd;
+       assert(chunksize(p) == small_index2size(idx));
+       unlink_first_small_chunk(gm, b, p, idx);
+       set_inuse_and_pinuse(gm, p, small_index2size(idx));
+       mem = chunk2mem(p);
+       check_malloced_chunk(gm, mem, nb);
+       goto postaction;
+      }
+
+      else if (nb > gm->dvsize) {
+       if (smallbits != 0) { /* Use chunk in next nonempty smallbin */
+         mchunkptr b, p, r;
+         size_t rsize;
+         bindex_t i;
+         binmap_t leftbits = (smallbits << idx) & left_bits(idx2bit(idx));
+         binmap_t leastbit = least_bit(leftbits);
+         compute_bit2idx(leastbit, i);
+         b = smallbin_at(gm, i);
+         p = b->fd;
+         assert(chunksize(p) == small_index2size(i));
+         unlink_first_small_chunk(gm, b, p, i);
+         rsize = small_index2size(i) - nb;
+         /* Fit here cannot be remainderless if 4byte sizes */
+         if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE)
+           set_inuse_and_pinuse(gm, p, small_index2size(i));
+         else {
+           set_size_and_pinuse_of_inuse_chunk(gm, p, nb);
+           r = chunk_plus_offset(p, nb);
+           set_size_and_pinuse_of_free_chunk(r, rsize);
+           replace_dv(gm, r, rsize);
+         }
+         mem = chunk2mem(p);
+         check_malloced_chunk(gm, mem, nb);
+         goto postaction;
+       }
+
+       else if (gm->treemap != 0 && (mem = tmalloc_small(gm, nb)) != 0) {
+         check_malloced_chunk(gm, mem, nb);
+         goto postaction;
+       }
+      }
+    }
+    else if (bytes >= MAX_REQUEST)
+      nb = MAX_SIZE_T; /* Too big to allocate. Force failure (in sys alloc) */
+    else {
+      nb = pad_request(bytes);
+      if (gm->treemap != 0 && (mem = tmalloc_large(gm, nb)) != 0) {
+       check_malloced_chunk(gm, mem, nb);
+       goto postaction;
+      }
+    }
+
+    if (nb <= gm->dvsize) {
+      size_t rsize = gm->dvsize - nb;
+      mchunkptr p = gm->dv;
+      if (rsize >= MIN_CHUNK_SIZE) { /* split dv */
+       mchunkptr r = gm->dv = chunk_plus_offset(p, nb);
+       gm->dvsize = rsize;
+       set_size_and_pinuse_of_free_chunk(r, rsize);
+       set_size_and_pinuse_of_inuse_chunk(gm, p, nb);
+      }
+      else { /* exhaust dv */
+       size_t dvs = gm->dvsize;
+       gm->dvsize = 0;
+       gm->dv = 0;
+       set_inuse_and_pinuse(gm, p, dvs);
+      }
+      mem = chunk2mem(p);
+      check_malloced_chunk(gm, mem, nb);
+      goto postaction;
+    }
+
+    else if (nb < gm->topsize) { /* Split top */
+      size_t rsize = gm->topsize -= nb;
+      mchunkptr p = gm->top;
+      mchunkptr r = gm->top = chunk_plus_offset(p, nb);
+      r->head = rsize | PINUSE_BIT;
+      set_size_and_pinuse_of_inuse_chunk(gm, p, nb);
+      mem = chunk2mem(p);
+      check_top_chunk(gm, gm->top);
+      check_malloced_chunk(gm, mem, nb);
+      goto postaction;
+    }
+
+    mem = sys_alloc(gm, nb);
+
+  postaction:
+    POSTACTION(gm);
+    return mem;
+  }
+
+  return 0;
+}
+
+void dlfree(void* mem) {
+  /*
+     Consolidate freed chunks with preceeding or succeeding bordering
+     free chunks, if they exist, and then place in a bin.  Intermixed
+     with special cases for top, dv, mmapped chunks, and usage errors.
+  */
+
+  if (mem != 0) {
+    mchunkptr p  = mem2chunk(mem);
+#if FOOTERS
+    mstate fm = get_mstate_for(p);
+    if (!ok_magic(fm)) {
+      USAGE_ERROR_ACTION(fm, p);
+      return;
+    }
+#else /* FOOTERS */
+#define fm gm
+#endif /* FOOTERS */
+    if (!PREACTION(fm)) {
+      check_inuse_chunk(fm, p);
+      if (RTCHECK(ok_address(fm, p) && ok_cinuse(p))) {
+       size_t psize = chunksize(p);
+       mchunkptr next = chunk_plus_offset(p, psize);
+       if (!pinuse(p)) {
+         size_t prevsize = p->prev_foot;
+         if ((prevsize & IS_MMAPPED_BIT) != 0) {
+           prevsize &= ~IS_MMAPPED_BIT;
+           psize += prevsize + MMAP_FOOT_PAD;
+           if (CALL_MUNMAP((char*)p - prevsize, psize) == 0)
+             fm->footprint -= psize;
+           goto postaction;
+         }
+         else {
+           mchunkptr prev = chunk_minus_offset(p, prevsize);
+           psize += prevsize;
+           p = prev;
+           if (RTCHECK(ok_address(fm, prev))) { /* consolidate backward */
+             if (p != fm->dv) {
+               unlink_chunk(fm, p, prevsize);
+             }
+             else if ((next->head & INUSE_BITS) == INUSE_BITS) {
+               fm->dvsize = psize;
+               set_free_with_pinuse(p, psize, next);
+               goto postaction;
+             }
+           }
+           else
+             goto erroraction;
+         }
+       }
+
+       if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) {
+         if (!cinuse(next)) {  /* consolidate forward */
+           if (next == fm->top) {
+             size_t tsize = fm->topsize += psize;
+             fm->top = p;
+             p->head = tsize | PINUSE_BIT;
+             if (p == fm->dv) {
+               fm->dv = 0;
+               fm->dvsize = 0;
+             }
+             if (should_trim(fm, tsize))
+               sys_trim(fm, 0);
+             goto postaction;
+           }
+           else if (next == fm->dv) {
+             size_t dsize = fm->dvsize += psize;
+             fm->dv = p;
+             set_size_and_pinuse_of_free_chunk(p, dsize);
+             goto postaction;
+           }
+           else {
+             size_t nsize = chunksize(next);
+             psize += nsize;
+             unlink_chunk(fm, next, nsize);
+             set_size_and_pinuse_of_free_chunk(p, psize);
+             if (p == fm->dv) {
+               fm->dvsize = psize;
+               goto postaction;
+             }
+           }
+         }
+         else
+           set_free_with_pinuse(p, psize, next);
+
+         if (is_small(psize)) {
+           insert_small_chunk(fm, p, psize);
+           check_free_chunk(fm, p);
+         }
+         else {
+           tchunkptr tp = (tchunkptr)p;
+           insert_large_chunk(fm, tp, psize);
+           check_free_chunk(fm, p);
+           if (--fm->release_checks == 0)
+             release_unused_segments(fm);
+         }
+         goto postaction;
+       }
+      }
+    erroraction:
+      USAGE_ERROR_ACTION(fm, p);
+    postaction:
+      POSTACTION(fm);
+    }
+  }
+#if !FOOTERS
+#undef fm
+#endif /* FOOTERS */
+}
+
+void* dlcalloc(size_t n_elements, size_t elem_size) {
+  void* mem;
+  size_t req = 0;
+  if (n_elements != 0) {
+    req = n_elements * elem_size;
+    if (((n_elements | elem_size) & ~(size_t)0xffff) &&
+       (req / n_elements != elem_size))
+      req = MAX_SIZE_T; /* force downstream failure on overflow */
+  }
+  mem = dlmalloc(req);
+  if (mem != 0 && calloc_must_clear(mem2chunk(mem)))
+    memset(mem, 0, req);
+  return mem;
+}
+
+void* dlrealloc(void* oldmem, size_t bytes) {
+  if (oldmem == 0)
+    return dlmalloc(bytes);
+#ifdef REALLOC_ZERO_BYTES_FREES
+  if (bytes == 0) {
+    dlfree(oldmem);
+    return 0;
+  }
+#endif /* REALLOC_ZERO_BYTES_FREES */
+  else {
+#if ! FOOTERS
+    mstate m = gm;
+#else /* FOOTERS */
+    mstate m = get_mstate_for(mem2chunk(oldmem));
+    if (!ok_magic(m)) {
+      USAGE_ERROR_ACTION(m, oldmem);
+      return 0;
+    }
+#endif /* FOOTERS */
+    return internal_realloc(m, oldmem, bytes);
+  }
+}
+
+void* dlmemalign(size_t alignment, size_t bytes) {
+  return internal_memalign(gm, alignment, bytes);
+}
+
+void** dlindependent_calloc(size_t n_elements, size_t elem_size,
+                                void* chunks[]) {
+  size_t sz = elem_size; /* serves as 1-element array */
+  return ialloc(gm, n_elements, &sz, 3, chunks);
+}
+
+void** dlindependent_comalloc(size_t n_elements, size_t sizes[],
+                                  void* chunks[]) {
+  return ialloc(gm, n_elements, sizes, 0, chunks);
+}
+
+void* dlvalloc(size_t bytes) {
+  size_t pagesz;
+  ensure_initialization();
+  pagesz = mparams.page_size;
+  return dlmemalign(pagesz, bytes);
+}
+
+void* dlpvalloc(size_t bytes) {
+  size_t pagesz;
+  ensure_initialization();
+  pagesz = mparams.page_size;
+  return dlmemalign(pagesz, (bytes + pagesz - SIZE_T_ONE) & ~(pagesz - SIZE_T_ONE));
+}
+
+int dlmalloc_trim(size_t pad) {
+  ensure_initialization();
+  int result = 0;
+  if (!PREACTION(gm)) {
+    result = sys_trim(gm, pad);
+    POSTACTION(gm);
+  }
+  return result;
+}
+
+size_t dlmalloc_footprint(void) {
+  return gm->footprint;
+}
+
+size_t dlmalloc_max_footprint(void) {
+  return gm->max_footprint;
+}
+
+#if !NO_MALLINFO
+struct mallinfo dlmallinfo(void) {
+  return internal_mallinfo(gm);
+}
+#endif /* NO_MALLINFO */
+
+void dlmalloc_stats() {
+  internal_malloc_stats(gm);
+}
+
+int dlmallopt(int param_number, int value) {
+  return change_mparam(param_number, value);
+}
+
+#endif /* !ONLY_MSPACES */
+
+size_t dlmalloc_usable_size(void* mem) {
+  if (mem != 0) {
+    mchunkptr p = mem2chunk(mem);
+    if (cinuse(p))
+      return chunksize(p) - overhead_for(p);
+  }
+  return 0;
+}
+
+/* ----------------------------- user mspaces ---------------------------- */
+
+#if MSPACES
+
+static mstate init_user_mstate(char* tbase, size_t tsize) {
+  size_t msize = pad_request(sizeof(struct malloc_state));
+  mchunkptr mn;
+  mchunkptr msp = align_as_chunk(tbase);
+  mstate m = (mstate)(chunk2mem(msp));
+  memset(m, 0, msize);
+  INITIAL_LOCK(&m->mutex);
+  msp->head = (msize|PINUSE_BIT|CINUSE_BIT);
+  m->seg.base = m->least_addr = tbase;
+  m->seg.size = m->footprint = m->max_footprint = tsize;
+  m->magic = mparams.magic;
+  m->release_checks = MAX_RELEASE_CHECK_RATE;
+  m->mflags = mparams.default_mflags;
+  m->extp = 0;
+  m->exts = 0;
+  disable_contiguous(m);
+  init_bins(m);
+  mn = next_chunk(mem2chunk(m));
+  init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) - TOP_FOOT_SIZE);
+  check_top_chunk(m, m->top);
+  return m;
+}
+
+mspace create_mspace(size_t capacity, int locked) {
+  mstate m = 0;
+  size_t msize;
+  ensure_initialization();
+  msize = pad_request(sizeof(struct malloc_state));
+  if (capacity < (size_t) -(msize + TOP_FOOT_SIZE + mparams.page_size)) {
+    size_t rs = ((capacity == 0)? mparams.granularity :
+                (capacity + TOP_FOOT_SIZE + msize));
+    size_t tsize = granularity_align(rs);
+    char* tbase = (char*)(CALL_MMAP(tsize));
+    if (tbase != CMFAIL) {
+      m = init_user_mstate(tbase, tsize);
+      m->seg.sflags = IS_MMAPPED_BIT;
+      set_lock(m, locked);
+    }
+  }
+  return (mspace)m;
+}
+
+mspace create_mspace_with_base(void* base, size_t capacity, int locked) {
+  mstate m = 0;
+  size_t msize;
+  ensure_initialization();
+  msize = pad_request(sizeof(struct malloc_state));
+  if (capacity > msize + TOP_FOOT_SIZE &&
+      capacity < (size_t) -(msize + TOP_FOOT_SIZE + mparams.page_size)) {
+    m = init_user_mstate((char*)base, capacity);
+    m->seg.sflags = EXTERN_BIT;
+    set_lock(m, locked);
+  }
+  return (mspace)m;
+}
+
+int mspace_mmap_large_chunks(mspace msp, int enable) {
+  int ret = 0;
+  mstate ms = (mstate)msp;
+  if (!PREACTION(ms)) {
+    if (use_mmap(ms))
+      ret = 1;
+    if (enable)
+      enable_mmap(ms);
+    else
+      disable_mmap(ms);
+    POSTACTION(ms);
+  }
+  return ret;
+}
+
+size_t destroy_mspace(mspace msp) {
+  size_t freed = 0;
+  mstate ms = (mstate)msp;
+  if (ok_magic(ms)) {
+    msegmentptr sp = &ms->seg;
+    while (sp != 0) {
+      char* base = sp->base;
+      size_t size = sp->size;
+      flag_t flag = sp->sflags;
+      sp = sp->next;
+      if ((flag & IS_MMAPPED_BIT) && !(flag & EXTERN_BIT) &&
+         CALL_MUNMAP(base, size) == 0)
+       freed += size;
+    }
+  }
+  else {
+    USAGE_ERROR_ACTION(ms,ms);
+  }
+  return freed;
+}
+
+/*
+  mspace versions of routines are near-clones of the global
+  versions. This is not so nice but better than the alternatives.
+*/
+
+
+void* mspace_malloc(mspace msp, size_t bytes) {
+  mstate ms = (mstate)msp;
+  if (!ok_magic(ms)) {
+    USAGE_ERROR_ACTION(ms,ms);
+    return 0;
+  }
+  if (!PREACTION(ms)) {
+    void* mem;
+    size_t nb;
+    if (bytes <= MAX_SMALL_REQUEST) {
+      bindex_t idx;
+      binmap_t smallbits;
+      nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes);
+      idx = small_index(nb);
+      smallbits = ms->smallmap >> idx;
+
+      if ((smallbits & 0x3U) != 0) { /* Remainderless fit to a smallbin. */
+       mchunkptr b, p;
+       idx += ~smallbits & 1;       /* Uses next bin if idx empty */
+       b = smallbin_at(ms, idx);
+       p = b->fd;
+       assert(chunksize(p) == small_index2size(idx));
+       unlink_first_small_chunk(ms, b, p, idx);
+       set_inuse_and_pinuse(ms, p, small_index2size(idx));
+       mem = chunk2mem(p);
+       check_malloced_chunk(ms, mem, nb);
+       goto postaction;
+      }
+
+      else if (nb > ms->dvsize) {
+       if (smallbits != 0) { /* Use chunk in next nonempty smallbin */
+         mchunkptr b, p, r;
+         size_t rsize;
+         bindex_t i;
+         binmap_t leftbits = (smallbits << idx) & left_bits(idx2bit(idx));
+         binmap_t leastbit = least_bit(leftbits);
+         compute_bit2idx(leastbit, i);
+         b = smallbin_at(ms, i);
+         p = b->fd;
+         assert(chunksize(p) == small_index2size(i));
+         unlink_first_small_chunk(ms, b, p, i);
+         rsize = small_index2size(i) - nb;
+         /* Fit here cannot be remainderless if 4byte sizes */
+         if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE)
+           set_inuse_and_pinuse(ms, p, small_index2size(i));
+         else {
+           set_size_and_pinuse_of_inuse_chunk(ms, p, nb);
+           r = chunk_plus_offset(p, nb);
+           set_size_and_pinuse_of_free_chunk(r, rsize);
+           replace_dv(ms, r, rsize);
+         }
+         mem = chunk2mem(p);
+         check_malloced_chunk(ms, mem, nb);
+         goto postaction;
+       }
+
+       else if (ms->treemap != 0 && (mem = tmalloc_small(ms, nb)) != 0) {
+         check_malloced_chunk(ms, mem, nb);
+         goto postaction;
+       }
+      }
+    }
+    else if (bytes >= MAX_REQUEST)
+      nb = MAX_SIZE_T; /* Too big to allocate. Force failure (in sys alloc) */
+    else {
+      nb = pad_request(bytes);
+      if (ms->treemap != 0 && (mem = tmalloc_large(ms, nb)) != 0) {
+       check_malloced_chunk(ms, mem, nb);
+       goto postaction;
+      }
+    }
+
+    if (nb <= ms->dvsize) {
+      size_t rsize = ms->dvsize - nb;
+      mchunkptr p = ms->dv;
+      if (rsize >= MIN_CHUNK_SIZE) { /* split dv */
+       mchunkptr r = ms->dv = chunk_plus_offset(p, nb);
+       ms->dvsize = rsize;
+       set_size_and_pinuse_of_free_chunk(r, rsize);
+       set_size_and_pinuse_of_inuse_chunk(ms, p, nb);
+      }
+      else { /* exhaust dv */
+       size_t dvs = ms->dvsize;
+       ms->dvsize = 0;
+       ms->dv = 0;
+       set_inuse_and_pinuse(ms, p, dvs);
+      }
+      mem = chunk2mem(p);
+      check_malloced_chunk(ms, mem, nb);
+      goto postaction;
+    }
+
+    else if (nb < ms->topsize) { /* Split top */
+      size_t rsize = ms->topsize -= nb;
+      mchunkptr p = ms->top;
+      mchunkptr r = ms->top = chunk_plus_offset(p, nb);
+      r->head = rsize | PINUSE_BIT;
+      set_size_and_pinuse_of_inuse_chunk(ms, p, nb);
+      mem = chunk2mem(p);
+      check_top_chunk(ms, ms->top);
+      check_malloced_chunk(ms, mem, nb);
+      goto postaction;
+    }
+
+    mem = sys_alloc(ms, nb);
+
+  postaction:
+    POSTACTION(ms);
+    return mem;
+  }
+
+  return 0;
+}
+
+void mspace_free(mspace msp, void* mem) {
+  if (mem != 0) {
+    mchunkptr p  = mem2chunk(mem);
+#if FOOTERS
+    mstate fm = get_mstate_for(p);
+#else /* FOOTERS */
+    mstate fm = (mstate)msp;
+#endif /* FOOTERS */
+    if (!ok_magic(fm)) {
+      USAGE_ERROR_ACTION(fm, p);
+      return;
+    }
+    if (!PREACTION(fm)) {
+      check_inuse_chunk(fm, p);
+      if (RTCHECK(ok_address(fm, p) && ok_cinuse(p))) {
+       size_t psize = chunksize(p);
+       mchunkptr next = chunk_plus_offset(p, psize);
+       if (!pinuse(p)) {
+         size_t prevsize = p->prev_foot;
+         if ((prevsize & IS_MMAPPED_BIT) != 0) {
+           prevsize &= ~IS_MMAPPED_BIT;
+           psize += prevsize + MMAP_FOOT_PAD;
+           if (CALL_MUNMAP((char*)p - prevsize, psize) == 0)
+             fm->footprint -= psize;
+           goto postaction;
+         }
+         else {
+           mchunkptr prev = chunk_minus_offset(p, prevsize);
+           psize += prevsize;
+           p = prev;
+           if (RTCHECK(ok_address(fm, prev))) { /* consolidate backward */
+             if (p != fm->dv) {
+               unlink_chunk(fm, p, prevsize);
+             }
+             else if ((next->head & INUSE_BITS) == INUSE_BITS) {
+               fm->dvsize = psize;
+               set_free_with_pinuse(p, psize, next);
+               goto postaction;
+             }
+           }
+           else
+             goto erroraction;
+         }
+       }
+
+       if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) {
+         if (!cinuse(next)) {  /* consolidate forward */
+           if (next == fm->top) {
+             size_t tsize = fm->topsize += psize;
+             fm->top = p;
+             p->head = tsize | PINUSE_BIT;
+             if (p == fm->dv) {
+               fm->dv = 0;
+               fm->dvsize = 0;
+             }
+             if (should_trim(fm, tsize))
+               sys_trim(fm, 0);
+             goto postaction;
+           }
+           else if (next == fm->dv) {
+             size_t dsize = fm->dvsize += psize;
+             fm->dv = p;
+             set_size_and_pinuse_of_free_chunk(p, dsize);
+             goto postaction;
+           }
+           else {
+             size_t nsize = chunksize(next);
+             psize += nsize;
+             unlink_chunk(fm, next, nsize);
+             set_size_and_pinuse_of_free_chunk(p, psize);
+             if (p == fm->dv) {
+               fm->dvsize = psize;
+               goto postaction;
+             }
+           }
+         }
+         else
+           set_free_with_pinuse(p, psize, next);
+
+         if (is_small(psize)) {
+           insert_small_chunk(fm, p, psize);
+           check_free_chunk(fm, p);
+         }
+         else {
+           tchunkptr tp = (tchunkptr)p;
+           insert_large_chunk(fm, tp, psize);
+           check_free_chunk(fm, p);
+           if (--fm->release_checks == 0)
+             release_unused_segments(fm);
+         }
+         goto postaction;
+       }
+      }
+    erroraction:
+      USAGE_ERROR_ACTION(fm, p);
+    postaction:
+      POSTACTION(fm);
+    }
+  }
+}
+
+void* mspace_calloc(mspace msp, size_t n_elements, size_t elem_size) {
+  void* mem;
+  size_t req = 0;
+  mstate ms = (mstate)msp;
+  if (!ok_magic(ms)) {
+    USAGE_ERROR_ACTION(ms,ms);
+    return 0;
+  }
+  if (n_elements != 0) {
+    req = n_elements * elem_size;
+    if (((n_elements | elem_size) & ~(size_t)0xffff) &&
+       (req / n_elements != elem_size))
+      req = MAX_SIZE_T; /* force downstream failure on overflow */
+  }
+  mem = internal_malloc(ms, req);
+  if (mem != 0 && calloc_must_clear(mem2chunk(mem)))
+    memset(mem, 0, req);
+  return mem;
+}
+
+void* mspace_realloc(mspace msp, void* oldmem, size_t bytes) {
+  if (oldmem == 0)
+    return mspace_malloc(msp, bytes);
+#ifdef REALLOC_ZERO_BYTES_FREES
+  if (bytes == 0) {
+    mspace_free(msp, oldmem);
+    return 0;
+  }
+#endif /* REALLOC_ZERO_BYTES_FREES */
+  else {
+#if FOOTERS
+    mchunkptr p  = mem2chunk(oldmem);
+    mstate ms = get_mstate_for(p);
+#else /* FOOTERS */
+    mstate ms = (mstate)msp;
+#endif /* FOOTERS */
+    if (!ok_magic(ms)) {
+      USAGE_ERROR_ACTION(ms,ms);
+      return 0;
+    }
+    return internal_realloc(ms, oldmem, bytes);
+  }
+}
+
+void* mspace_memalign(mspace msp, size_t alignment, size_t bytes) {
+  mstate ms = (mstate)msp;
+  if (!ok_magic(ms)) {
+    USAGE_ERROR_ACTION(ms,ms);
+    return 0;
+  }
+  return internal_memalign(ms, alignment, bytes);
+}
+
+void** mspace_independent_calloc(mspace msp, size_t n_elements,
+                                size_t elem_size, void* chunks[]) {
+  size_t sz = elem_size; /* serves as 1-element array */
+  mstate ms = (mstate)msp;
+  if (!ok_magic(ms)) {
+    USAGE_ERROR_ACTION(ms,ms);
+    return 0;
+  }
+  return ialloc(ms, n_elements, &sz, 3, chunks);
+}
+
+void** mspace_independent_comalloc(mspace msp, size_t n_elements,
+                                  size_t sizes[], void* chunks[]) {
+  mstate ms = (mstate)msp;
+  if (!ok_magic(ms)) {
+    USAGE_ERROR_ACTION(ms,ms);
+    return 0;
+  }
+  return ialloc(ms, n_elements, sizes, 0, chunks);
+}
+
+int mspace_trim(mspace msp, size_t pad) {
+  int result = 0;
+  mstate ms = (mstate)msp;
+  if (ok_magic(ms)) {
+    if (!PREACTION(ms)) {
+      result = sys_trim(ms, pad);
+      POSTACTION(ms);
+    }
+  }
+  else {
+    USAGE_ERROR_ACTION(ms,ms);
+  }
+  return result;
+}
+
+void mspace_malloc_stats(mspace msp) {
+  mstate ms = (mstate)msp;
+  if (ok_magic(ms)) {
+    internal_malloc_stats(ms);
+  }
+  else {
+    USAGE_ERROR_ACTION(ms,ms);
+  }
+}
+
+size_t mspace_footprint(mspace msp) {
+  size_t result = 0;
+  mstate ms = (mstate)msp;
+  if (ok_magic(ms)) {
+    result = ms->footprint;
+  }
+  else {
+    USAGE_ERROR_ACTION(ms,ms);
+  }
+  return result;
+}
+
+
+size_t mspace_max_footprint(mspace msp) {
+  size_t result = 0;
+  mstate ms = (mstate)msp;
+  if (ok_magic(ms)) {
+    result = ms->max_footprint;
+  }
+  else {
+    USAGE_ERROR_ACTION(ms,ms);
+  }
+  return result;
+}
+
+
+#if !NO_MALLINFO
+struct mallinfo mspace_mallinfo(mspace msp) {
+  mstate ms = (mstate)msp;
+  if (!ok_magic(ms)) {
+    USAGE_ERROR_ACTION(ms,ms);
+  }
+  return internal_mallinfo(ms);
+}
+#endif /* NO_MALLINFO */
+
+size_t mspace_usable_size(void* mem) {
+  if (mem != 0) {
+    mchunkptr p = mem2chunk(mem);
+    if (cinuse(p))
+      return chunksize(p) - overhead_for(p);
+  }
+  return 0;
+}
+
+int mspace_mallopt(int param_number, int value) {
+  return change_mparam(param_number, value);
+}
+
+#endif /* MSPACES */
+
+/* -------------------- Alternative MORECORE functions ------------------- */
+
+/*
+  Guidelines for creating a custom version of MORECORE:
+
+  * For best performance, MORECORE should allocate in multiples of pagesize.
+  * MORECORE may allocate more memory than requested. (Or even less,
+      but this will usually result in a malloc failure.)
+  * MORECORE must not allocate memory when given argument zero, but
+      instead return one past the end address of memory from previous
+      nonzero call.
+  * For best performance, consecutive calls to MORECORE with positive
+      arguments should return increasing addresses, indicating that
+      space has been contiguously extended.
+  * Even though consecutive calls to MORECORE need not return contiguous
+      addresses, it must be OK for malloc'ed chunks to span multiple
+      regions in those cases where they do happen to be contiguous.
+  * MORECORE need not handle negative arguments -- it may instead
+      just return MFAIL when given negative arguments.
+      Negative arguments are always multiples of pagesize. MORECORE
+      must not misinterpret negative args as large positive unsigned
+      args. You can suppress all such calls from even occurring by defining
+      MORECORE_CANNOT_TRIM,
+
+  As an example alternative MORECORE, here is a custom allocator
+  kindly contributed for pre-OSX macOS.  It uses virtually but not
+  necessarily physically contiguous non-paged memory (locked in,
+  present and won't get swapped out).  You can use it by uncommenting
+  this section, adding some #includes, and setting up the appropriate
+  defines above:
+
+      #define MORECORE osMoreCore
+
+  There is also a shutdown routine that should somehow be called for
+  cleanup upon program exit.
+
+  #define MAX_POOL_ENTRIES 100
+  #define MINIMUM_MORECORE_SIZE  (64 * 1024U)
+  static int next_os_pool;
+  void *our_os_pools[MAX_POOL_ENTRIES];
+
+  void *osMoreCore(int size)
+  {
+    void *ptr = 0;
+    static void *sbrk_top = 0;
+
+    if (size > 0)
+    {
+      if (size < MINIMUM_MORECORE_SIZE)
+        size = MINIMUM_MORECORE_SIZE;
+      if (CurrentExecutionLevel() == kTaskLevel)
+        ptr = PoolAllocateResident(size + RM_PAGE_SIZE, 0);
+      if (ptr == 0)
+      {
+       return (void *) MFAIL;
+      }
+      // save ptrs so they can be freed during cleanup
+      our_os_pools[next_os_pool] = ptr;
+      next_os_pool++;
+      ptr = (void *) ((((size_t) ptr) + RM_PAGE_MASK) & ~RM_PAGE_MASK);
+      sbrk_top = (char *) ptr + size;
+      return ptr;
+    }
+    else if (size < 0)
+    {
+      // we don't currently support shrink behavior
+      return (void *) MFAIL;
+    }
+    else
+    {
+      return sbrk_top;
+    }
+  }
+
+  // cleanup any allocated memory pools
+  // called as last thing before shutting down driver
+
+  void osCleanupMem(void)
+  {
+    void **ptr;
+
+    for (ptr = our_os_pools; ptr < &our_os_pools[MAX_POOL_ENTRIES]; ptr++)
+      if (*ptr)
+      {
+        PoolDeallocate(*ptr);
+        *ptr = 0;
+      }
+  }
+
+*/
+
+
+/* -----------------------------------------------------------------------
+History:
+    V2.8.4 (not yet released)
+      * Add mspace_mmap_large_chunks; thanks to Jean Brouwers
+      * Fix insufficient sys_alloc padding when using 16byte alignment
+      * Fix bad error check in mspace_footprint
+      * Adaptations for ptmalloc, courtesy of Wolfram Gloger.
+      * Reentrant spin locks, courtesy of Earl Chew and others
+      * Win32 improvements, courtesy of Niall Douglas and Earl Chew
+      * Add NO_SEGMENT_TRAVERSAL and MAX_RELEASE_CHECK_RATE options
+      * Extension hook in malloc_state
+      * Various small adjustments to reduce warnings on some compilers
+      * Various configuration extensions/changes for more platforms. Thanks
+        to all who contributed these.
+
+    V2.8.3 Thu Sep 22 11:16:32 2005  Doug Lea  (dl at gee)
+      * Add max_footprint functions
+      * Ensure all appropriate literals are size_t
+      * Fix conditional compilation problem for some #define settings
+      * Avoid concatenating segments with the one provided
+       in create_mspace_with_base
+      * Rename some variables to avoid compiler shadowing warnings
+      * Use explicit lock initialization.
+      * Better handling of sbrk interference.
+      * Simplify and fix segment insertion, trimming and mspace_destroy
+      * Reinstate REALLOC_ZERO_BYTES_FREES option from 2.7.x
+      * Thanks especially to Dennis Flanagan for help on these.
+
+    V2.8.2 Sun Jun 12 16:01:10 2005  Doug Lea  (dl at gee)
+      * Fix memalign brace error.
+
+    V2.8.1 Wed Jun  8 16:11:46 2005  Doug Lea  (dl at gee)
+      * Fix improper #endif nesting in C++
+      * Add explicit casts needed for C++
+
+    V2.8.0 Mon May 30 14:09:02 2005  Doug Lea  (dl at gee)
+      * Use trees for large bins
+      * Support mspaces
+      * Use segments to unify sbrk-based and mmap-based system allocation,
+       removing need for emulation on most platforms without sbrk.
+      * Default safety checks
+      * Optional footer checks. Thanks to William Robertson for the idea.
+      * Internal code refactoring
+      * Incorporate suggestions and platform-specific changes.
+       Thanks to Dennis Flanagan, Colin Plumb, Niall Douglas,
+       Aaron Bachmann,  Emery Berger, and others.
+      * Speed up non-fastbin processing enough to remove fastbins.
+      * Remove useless cfree() to avoid conflicts with other apps.
+      * Remove internal memcpy, memset. Compilers handle builtins better.
+      * Remove some options that no one ever used and rename others.
+
+    V2.7.2 Sat Aug 17 09:07:30 2002  Doug Lea  (dl at gee)
+      * Fix malloc_state bitmap array misdeclaration
+
+    V2.7.1 Thu Jul 25 10:58:03 2002  Doug Lea  (dl at gee)
+      * Allow tuning of FIRST_SORTED_BIN_SIZE
+      * Use PTR_UINT as type for all ptr->int casts. Thanks to John Belmonte.
+      * Better detection and support for non-contiguousness of MORECORE.
+       Thanks to Andreas Mueller, Conal Walsh, and Wolfram Gloger
+      * Bypass most of malloc if no frees. Thanks To Emery Berger.
+      * Fix freeing of old top non-contiguous chunk im sysmalloc.
+      * Raised default trim and map thresholds to 256K.
+      * Fix mmap-related #defines. Thanks to Lubos Lunak.
+      * Fix copy macros; added LACKS_FCNTL_H. Thanks to Neal Walfield.
+      * Branch-free bin calculation
+      * Default trim and mmap thresholds now 256K.
+
+    V2.7.0 Sun Mar 11 14:14:06 2001  Doug Lea  (dl at gee)
+      * Introduce independent_comalloc and independent_calloc.
+       Thanks to Michael Pachos for motivation and help.
+      * Make optional .h file available
+      * Allow > 2GB requests on 32bit systems.
+      * new WIN32 sbrk, mmap, munmap, lock code from <Walter@GeNeSys-e.de>.
+       Thanks also to Andreas Mueller <a.mueller at paradatec.de>,
+       and Anonymous.
+      * Allow override of MALLOC_ALIGNMENT (Thanks to Ruud Waij for
+       helping test this.)
+      * memalign: check alignment arg
+      * realloc: don't try to shift chunks backwards, since this
+       leads to  more fragmentation in some programs and doesn't
+       seem to help in any others.
+      * Collect all cases in malloc requiring system memory into sysmalloc
+      * Use mmap as backup to sbrk
+      * Place all internal state in malloc_state
+      * Introduce fastbins (although similar to 2.5.1)
+      * Many minor tunings and cosmetic improvements
+      * Introduce USE_PUBLIC_MALLOC_WRAPPERS, USE_MALLOC_LOCK
+      * Introduce MALLOC_FAILURE_ACTION, MORECORE_CONTIGUOUS
+       Thanks to Tony E. Bennett <tbennett@nvidia.com> and others.
+      * Include errno.h to support default failure action.
+
+    V2.6.6 Sun Dec  5 07:42:19 1999  Doug Lea  (dl at gee)
+      * return null for negative arguments
+      * Added Several WIN32 cleanups from Martin C. Fong <mcfong at yahoo.com>
+        * Add 'LACKS_SYS_PARAM_H' for those systems without 'sys/param.h'
+         (e.g. WIN32 platforms)
+        * Cleanup header file inclusion for WIN32 platforms
+        * Cleanup code to avoid Microsoft Visual C++ compiler complaints
+        * Add 'USE_DL_PREFIX' to quickly allow co-existence with existing
+          memory allocation routines
+        * Set 'malloc_getpagesize' for WIN32 platforms (needs more work)
+        * Use 'assert' rather than 'ASSERT' in WIN32 code to conform to
+          usage of 'assert' in non-WIN32 code
+        * Improve WIN32 'sbrk()' emulation's 'findRegion()' routine to
+          avoid infinite loop
+      * Always call 'fREe()' rather than 'free()'
+
+    V2.6.5 Wed Jun 17 15:57:31 1998  Doug Lea  (dl at gee)
+      * Fixed ordering problem with boundary-stamping
+
+    V2.6.3 Sun May 19 08:17:58 1996  Doug Lea  (dl at gee)
+      * Added pvalloc, as recommended by H.J. Liu
+      * Added 64bit pointer support mainly from Wolfram Gloger
+      * Added anonymously donated WIN32 sbrk emulation
+      * Malloc, calloc, getpagesize: add optimizations from Raymond Nijssen
+      * malloc_extend_top: fix mask error that caused wastage after
+       foreign sbrks
+      * Add linux mremap support code from HJ Liu
+
+    V2.6.2 Tue Dec  5 06:52:55 1995  Doug Lea  (dl at gee)
+      * Integrated most documentation with the code.
+      * Add support for mmap, with help from
+       Wolfram Gloger (Gloger@lrz.uni-muenchen.de).
+      * Use last_remainder in more cases.
+      * Pack bins using idea from  colin@nyx10.cs.du.edu
+      * Use ordered bins instead of best-fit threshhold
+      * Eliminate block-local decls to simplify tracing and debugging.
+      * Support another case of realloc via move into top
+      * Fix error occuring when initial sbrk_base not word-aligned.
+      * Rely on page size for units instead of SBRK_UNIT to
+       avoid surprises about sbrk alignment conventions.
+      * Add mallinfo, mallopt. Thanks to Raymond Nijssen
+       (raymond@es.ele.tue.nl) for the suggestion.
+      * Add `pad' argument to malloc_trim and top_pad mallopt parameter.
+      * More precautions for cases where other routines call sbrk,
+       courtesy of Wolfram Gloger (Gloger@lrz.uni-muenchen.de).
+      * Added macros etc., allowing use in linux libc from
+       H.J. Lu (hjl@gnu.ai.mit.edu)
+      * Inverted this history list
+
+    V2.6.1 Sat Dec  2 14:10:57 1995  Doug Lea  (dl at gee)
+      * Re-tuned and fixed to behave more nicely with V2.6.0 changes.
+      * Removed all preallocation code since under current scheme
+       the work required to undo bad preallocations exceeds
+       the work saved in good cases for most test programs.
+      * No longer use return list or unconsolidated bins since
+       no scheme using them consistently outperforms those that don't
+       given above changes.
+      * Use best fit for very large chunks to prevent some worst-cases.
+      * Added some support for debugging
+
+    V2.6.0 Sat Nov  4 07:05:23 1995  Doug Lea  (dl at gee)
+      * Removed footers when chunks are in use. Thanks to
+       Paul Wilson (wilson@cs.texas.edu) for the suggestion.
+
+    V2.5.4 Wed Nov  1 07:54:51 1995  Doug Lea  (dl at gee)
+      * Added malloc_trim, with help from Wolfram Gloger
+       (wmglo@Dent.MED.Uni-Muenchen.DE).
+
+    V2.5.3 Tue Apr 26 10:16:01 1994  Doug Lea  (dl at g)
+
+    V2.5.2 Tue Apr  5 16:20:40 1994  Doug Lea  (dl at g)
+      * realloc: try to expand in both directions
+      * malloc: swap order of clean-bin strategy;
+      * realloc: only conditionally expand backwards
+      * Try not to scavenge used bins
+      * Use bin counts as a guide to preallocation
+      * Occasionally bin return list chunks in first scan
+      * Add a few optimizations from colin@nyx10.cs.du.edu
+
+    V2.5.1 Sat Aug 14 15:40:43 1993  Doug Lea  (dl at g)
+      * faster bin computation & slightly different binning
+      * merged all consolidations to one part of malloc proper
+        (eliminating old malloc_find_space & malloc_clean_bin)
+      * Scan 2 returns chunks (not just 1)
+      * Propagate failure in realloc if malloc returns 0
+      * Add stuff to allow compilation on non-ANSI compilers
+         from kpv@research.att.com
+
+    V2.5 Sat Aug  7 07:41:59 1993  Doug Lea  (dl at g.oswego.edu)
+      * removed potential for odd address access in prev_chunk
+      * removed dependency on getpagesize.h
+      * misc cosmetics and a bit more internal documentation
+      * anticosmetics: mangled names in macros to evade debugger strangeness
+      * tested on sparc, hp-700, dec-mips, rs6000
+         with gcc & native cc (hp, dec only) allowing
+         Detlefs & Zorn comparison study (in SIGPLAN Notices.)
+
+    Trial version Fri Aug 28 13:14:29 1992  Doug Lea  (dl at g.oswego.edu)
+      * Based loosely on libg++-1.2X malloc. (It retains some of the overall
+        structure of old version,  but most details differ.)
+
+*/
+
+
diff --git a/compat/nedmalloc/nedmalloc.c b/compat/nedmalloc/nedmalloc.c
new file mode 100644 (file)
index 0000000..d9a17a8
--- /dev/null
@@ -0,0 +1,966 @@
+/* Alternative malloc implementation for multiple threads without
+lock contention based on dlmalloc. (C) 2005-2006 Niall Douglas
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+*/
+
+#ifdef _MSC_VER
+/* Enable full aliasing on MSVC */
+/*#pragma optimize("a", on)*/
+#endif
+
+/*#define FULLSANITYCHECKS*/
+
+#include "nedmalloc.h"
+#if defined(WIN32)
+ #include <malloc.h>
+#endif
+#define MSPACES 1
+#define ONLY_MSPACES 1
+#ifndef USE_LOCKS
+ #define USE_LOCKS 1
+#endif
+#define FOOTERS 1           /* Need to enable footers so frees lock the right mspace */
+#undef DEBUG                           /* dlmalloc wants DEBUG either 0 or 1 */
+#ifdef _DEBUG
+ #define DEBUG 1
+#else
+ #define DEBUG 0
+#endif
+#ifdef NDEBUG               /* Disable assert checking on release builds */
+ #undef DEBUG
+#endif
+/* The default of 64Kb means we spend too much time kernel-side */
+#ifndef DEFAULT_GRANULARITY
+#define DEFAULT_GRANULARITY (1*1024*1024)
+#endif
+/*#define USE_SPIN_LOCKS 0*/
+
+
+/*#define FORCEINLINE*/
+#include "malloc.c.h"
+#ifdef NDEBUG               /* Disable assert checking on release builds */
+ #undef DEBUG
+#endif
+
+/* The maximum concurrent threads in a pool possible */
+#ifndef MAXTHREADSINPOOL
+#define MAXTHREADSINPOOL 16
+#endif
+/* The maximum number of threadcaches which can be allocated */
+#ifndef THREADCACHEMAXCACHES
+#define THREADCACHEMAXCACHES 256
+#endif
+/* The maximum size to be allocated from the thread cache */
+#ifndef THREADCACHEMAX
+#define THREADCACHEMAX 8192
+#endif
+#if 0
+/* The number of cache entries for finer grained bins. This is (topbitpos(THREADCACHEMAX)-4)*2 */
+#define THREADCACHEMAXBINS ((13-4)*2)
+#else
+/* The number of cache entries. This is (topbitpos(THREADCACHEMAX)-4) */
+#define THREADCACHEMAXBINS (13-4)
+#endif
+/* Point at which the free space in a thread cache is garbage collected */
+#ifndef THREADCACHEMAXFREESPACE
+#define THREADCACHEMAXFREESPACE (512*1024)
+#endif
+
+
+#ifdef WIN32
+ #define TLSVAR                        DWORD
+ #define TLSALLOC(k)   (*(k)=TlsAlloc(), TLS_OUT_OF_INDEXES==*(k))
+ #define TLSFREE(k)            (!TlsFree(k))
+ #define TLSGET(k)             TlsGetValue(k)
+ #define TLSSET(k, a)  (!TlsSetValue(k, a))
+ #ifdef DEBUG
+static LPVOID ChkedTlsGetValue(DWORD idx)
+{
+       LPVOID ret=TlsGetValue(idx);
+       assert(S_OK==GetLastError());
+       return ret;
+}
+  #undef TLSGET
+  #define TLSGET(k) ChkedTlsGetValue(k)
+ #endif
+#else
+ #define TLSVAR                        pthread_key_t
+ #define TLSALLOC(k)   pthread_key_create(k, 0)
+ #define TLSFREE(k)            pthread_key_delete(k)
+ #define TLSGET(k)             pthread_getspecific(k)
+ #define TLSSET(k, a)  pthread_setspecific(k, a)
+#endif
+
+#if 0
+/* Only enable if testing with valgrind. Causes misoperation */
+#define mspace_malloc(p, s) malloc(s)
+#define mspace_realloc(p, m, s) realloc(m, s)
+#define mspace_calloc(p, n, s) calloc(n, s)
+#define mspace_free(p, m) free(m)
+#endif
+
+
+#if defined(__cplusplus)
+#if !defined(NO_NED_NAMESPACE)
+namespace nedalloc {
+#else
+extern "C" {
+#endif
+#endif
+
+size_t nedblksize(void *mem) THROWSPEC
+{
+#if 0
+       /* Only enable if testing with valgrind. Causes misoperation */
+       return THREADCACHEMAX;
+#else
+       if(mem)
+       {
+               mchunkptr p=mem2chunk(mem);
+               assert(cinuse(p));      /* If this fails, someone tried to free a block twice */
+               if(cinuse(p))
+                       return chunksize(p)-overhead_for(p);
+       }
+       return 0;
+#endif
+}
+
+void nedsetvalue(void *v) THROWSPEC                                    { nedpsetvalue(0, v); }
+void * nedmalloc(size_t size) THROWSPEC                                { return nedpmalloc(0, size); }
+void * nedcalloc(size_t no, size_t size) THROWSPEC     { return nedpcalloc(0, no, size); }
+void * nedrealloc(void *mem, size_t size) THROWSPEC    { return nedprealloc(0, mem, size); }
+void   nedfree(void *mem) THROWSPEC                                    { nedpfree(0, mem); }
+void * nedmemalign(size_t alignment, size_t bytes) THROWSPEC { return nedpmemalign(0, alignment, bytes); }
+#if !NO_MALLINFO
+struct mallinfo nedmallinfo(void) THROWSPEC                    { return nedpmallinfo(0); }
+#endif
+int    nedmallopt(int parno, int value) THROWSPEC      { return nedpmallopt(0, parno, value); }
+int    nedmalloc_trim(size_t pad) THROWSPEC                    { return nedpmalloc_trim(0, pad); }
+void   nedmalloc_stats() THROWSPEC                                     { nedpmalloc_stats(0); }
+size_t nedmalloc_footprint() THROWSPEC                         { return nedpmalloc_footprint(0); }
+void **nedindependent_calloc(size_t elemsno, size_t elemsize, void **chunks) THROWSPEC { return nedpindependent_calloc(0, elemsno, elemsize, chunks); }
+void **nedindependent_comalloc(size_t elems, size_t *sizes, void **chunks) THROWSPEC   { return nedpindependent_comalloc(0, elems, sizes, chunks); }
+
+struct threadcacheblk_t;
+typedef struct threadcacheblk_t threadcacheblk;
+struct threadcacheblk_t
+{      /* Keep less than 16 bytes on 32 bit systems and 32 bytes on 64 bit systems */
+#ifdef FULLSANITYCHECKS
+       unsigned int magic;
+#endif
+       unsigned int lastUsed, size;
+       threadcacheblk *next, *prev;
+};
+typedef struct threadcache_t
+{
+#ifdef FULLSANITYCHECKS
+       unsigned int magic1;
+#endif
+       int mymspace;                                           /* Last mspace entry this thread used */
+       long threadid;
+       unsigned int mallocs, frees, successes;
+       size_t freeInCache;                                     /* How much free space is stored in this cache */
+       threadcacheblk *bins[(THREADCACHEMAXBINS+1)*2];
+#ifdef FULLSANITYCHECKS
+       unsigned int magic2;
+#endif
+} threadcache;
+struct nedpool_t
+{
+       MLOCK_T mutex;
+       void *uservalue;
+       int threads;                                            /* Max entries in m to use */
+       threadcache *caches[THREADCACHEMAXCACHES];
+       TLSVAR mycache;                                         /* Thread cache for this thread. 0 for unset, negative for use mspace-1 directly, otherwise is cache-1 */
+       mstate m[MAXTHREADSINPOOL+1];           /* mspace entries for this pool */
+};
+static nedpool syspool;
+
+static FORCEINLINE unsigned int size2binidx(size_t _size) THROWSPEC
+{      /* 8=1000       16=10000        20=10100        24=11000        32=100000       48=110000       4096=1000000000000 */
+       unsigned int topbit, size=(unsigned int)(_size>>4);
+       /* 16=1         20=1    24=1    32=10   48=11   64=100  96=110  128=1000        4096=100000000 */
+
+#if defined(__GNUC__)
+       topbit = sizeof(size)*__CHAR_BIT__ - 1 - __builtin_clz(size);
+#elif defined(_MSC_VER) && _MSC_VER>=1300
+       {
+           unsigned long bsrTopBit;
+
+           _BitScanReverse(&bsrTopBit, size);
+
+           topbit = bsrTopBit;
+       }
+#else
+#if 0
+       union {
+               unsigned asInt[2];
+               double asDouble;
+       };
+       int n;
+
+       asDouble = (double)size + 0.5;
+       topbit = (asInt[!FOX_BIGENDIAN] >> 20) - 1023;
+#else
+       {
+               unsigned int x=size;
+               x = x | (x >> 1);
+               x = x | (x >> 2);
+               x = x | (x >> 4);
+               x = x | (x >> 8);
+               x = x | (x >>16);
+               x = ~x;
+               x = x - ((x >> 1) & 0x55555555);
+               x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
+               x = (x + (x >> 4)) & 0x0F0F0F0F;
+               x = x + (x << 8);
+               x = x + (x << 16);
+               topbit=31 - (x >> 24);
+       }
+#endif
+#endif
+       return topbit;
+}
+
+
+#ifdef FULLSANITYCHECKS
+static void tcsanitycheck(threadcacheblk **ptr) THROWSPEC
+{
+       assert((ptr[0] && ptr[1]) || (!ptr[0] && !ptr[1]));
+       if(ptr[0] && ptr[1])
+       {
+               assert(nedblksize(ptr[0])>=sizeof(threadcacheblk));
+               assert(nedblksize(ptr[1])>=sizeof(threadcacheblk));
+               assert(*(unsigned int *) "NEDN"==ptr[0]->magic);
+               assert(*(unsigned int *) "NEDN"==ptr[1]->magic);
+               assert(!ptr[0]->prev);
+               assert(!ptr[1]->next);
+               if(ptr[0]==ptr[1])
+               {
+                       assert(!ptr[0]->next);
+                       assert(!ptr[1]->prev);
+               }
+       }
+}
+static void tcfullsanitycheck(threadcache *tc) THROWSPEC
+{
+       threadcacheblk **tcbptr=tc->bins;
+       int n;
+       for(n=0; n<=THREADCACHEMAXBINS; n++, tcbptr+=2)
+       {
+               threadcacheblk *b, *ob=0;
+               tcsanitycheck(tcbptr);
+               for(b=tcbptr[0]; b; ob=b, b=b->next)
+               {
+                       assert(*(unsigned int *) "NEDN"==b->magic);
+                       assert(!ob || ob->next==b);
+                       assert(!ob || b->prev==ob);
+               }
+       }
+}
+#endif
+
+static NOINLINE void RemoveCacheEntries(nedpool *p, threadcache *tc, unsigned int age) THROWSPEC
+{
+#ifdef FULLSANITYCHECKS
+       tcfullsanitycheck(tc);
+#endif
+       if(tc->freeInCache)
+       {
+               threadcacheblk **tcbptr=tc->bins;
+               int n;
+               for(n=0; n<=THREADCACHEMAXBINS; n++, tcbptr+=2)
+               {
+                       threadcacheblk **tcb=tcbptr+1;          /* come from oldest end of list */
+                       /*tcsanitycheck(tcbptr);*/
+                       for(; *tcb && tc->frees-(*tcb)->lastUsed>=age; )
+                       {
+                               threadcacheblk *f=*tcb;
+                               size_t blksize=f->size; /*nedblksize(f);*/
+                               assert(blksize<=nedblksize(f));
+                               assert(blksize);
+#ifdef FULLSANITYCHECKS
+                               assert(*(unsigned int *) "NEDN"==(*tcb)->magic);
+#endif
+                               *tcb=(*tcb)->prev;
+                               if(*tcb)
+                                       (*tcb)->next=0;
+                               else
+                                       *tcbptr=0;
+                               tc->freeInCache-=blksize;
+                               assert((long) tc->freeInCache>=0);
+                               mspace_free(0, f);
+                               /*tcsanitycheck(tcbptr);*/
+                       }
+               }
+       }
+#ifdef FULLSANITYCHECKS
+       tcfullsanitycheck(tc);
+#endif
+}
+static void DestroyCaches(nedpool *p) THROWSPEC
+{
+       if(p->caches)
+       {
+               threadcache *tc;
+               int n;
+               for(n=0; n<THREADCACHEMAXCACHES; n++)
+               {
+                       if((tc=p->caches[n]))
+                       {
+                               tc->frees++;
+                               RemoveCacheEntries(p, tc, 0);
+                               assert(!tc->freeInCache);
+                               tc->mymspace=-1;
+                               tc->threadid=0;
+                               mspace_free(0, tc);
+                               p->caches[n]=0;
+                       }
+               }
+       }
+}
+
+static NOINLINE threadcache *AllocCache(nedpool *p) THROWSPEC
+{
+       threadcache *tc=0;
+       int n, end;
+       ACQUIRE_LOCK(&p->mutex);
+       for(n=0; n<THREADCACHEMAXCACHES && p->caches[n]; n++);
+       if(THREADCACHEMAXCACHES==n)
+       {       /* List exhausted, so disable for this thread */
+               RELEASE_LOCK(&p->mutex);
+               return 0;
+       }
+       tc=p->caches[n]=(threadcache *) mspace_calloc(p->m[0], 1, sizeof(threadcache));
+       if(!tc)
+       {
+               RELEASE_LOCK(&p->mutex);
+               return 0;
+       }
+#ifdef FULLSANITYCHECKS
+       tc->magic1=*(unsigned int *)"NEDMALC1";
+       tc->magic2=*(unsigned int *)"NEDMALC2";
+#endif
+       tc->threadid=(long)(size_t)CURRENT_THREAD;
+       for(end=0; p->m[end]; end++);
+       tc->mymspace=tc->threadid % end;
+       RELEASE_LOCK(&p->mutex);
+       if(TLSSET(p->mycache, (void *)(size_t)(n+1))) abort();
+       return tc;
+}
+
+static void *threadcache_malloc(nedpool *p, threadcache *tc, size_t *size) THROWSPEC
+{
+       void *ret=0;
+       unsigned int bestsize;
+       unsigned int idx=size2binidx(*size);
+       size_t blksize=0;
+       threadcacheblk *blk, **binsptr;
+#ifdef FULLSANITYCHECKS
+       tcfullsanitycheck(tc);
+#endif
+       /* Calculate best fit bin size */
+       bestsize=1<<(idx+4);
+#if 0
+       /* Finer grained bin fit */
+       idx<<=1;
+       if(*size>bestsize)
+       {
+               idx++;
+               bestsize+=bestsize>>1;
+       }
+       if(*size>bestsize)
+       {
+               idx++;
+               bestsize=1<<(4+(idx>>1));
+       }
+#else
+       if(*size>bestsize)
+       {
+               idx++;
+               bestsize<<=1;
+       }
+#endif
+       assert(bestsize>=*size);
+       if(*size<bestsize) *size=bestsize;
+       assert(*size<=THREADCACHEMAX);
+       assert(idx<=THREADCACHEMAXBINS);
+       binsptr=&tc->bins[idx*2];
+       /* Try to match close, but move up a bin if necessary */
+       blk=*binsptr;
+       if(!blk || blk->size<*size)
+       {       /* Bump it up a bin */
+               if(idx<THREADCACHEMAXBINS)
+               {
+                       idx++;
+                       binsptr+=2;
+                       blk=*binsptr;
+               }
+       }
+       if(blk)
+       {
+               blksize=blk->size; /*nedblksize(blk);*/
+               assert(nedblksize(blk)>=blksize);
+               assert(blksize>=*size);
+               if(blk->next)
+                       blk->next->prev=0;
+               *binsptr=blk->next;
+               if(!*binsptr)
+                       binsptr[1]=0;
+#ifdef FULLSANITYCHECKS
+               blk->magic=0;
+#endif
+               assert(binsptr[0]!=blk && binsptr[1]!=blk);
+               assert(nedblksize(blk)>=sizeof(threadcacheblk) && nedblksize(blk)<=THREADCACHEMAX+CHUNK_OVERHEAD);
+               /*printf("malloc: %p, %p, %p, %lu\n", p, tc, blk, (long) size);*/
+               ret=(void *) blk;
+       }
+       ++tc->mallocs;
+       if(ret)
+       {
+               assert(blksize>=*size);
+               ++tc->successes;
+               tc->freeInCache-=blksize;
+               assert((long) tc->freeInCache>=0);
+       }
+#if defined(DEBUG) && 0
+       if(!(tc->mallocs & 0xfff))
+       {
+               printf("*** threadcache=%u, mallocs=%u (%f), free=%u (%f), freeInCache=%u\n", (unsigned int) tc->threadid, tc->mallocs,
+                       (float) tc->successes/tc->mallocs, tc->frees, (float) tc->successes/tc->frees, (unsigned int) tc->freeInCache);
+       }
+#endif
+#ifdef FULLSANITYCHECKS
+       tcfullsanitycheck(tc);
+#endif
+       return ret;
+}
+static NOINLINE void ReleaseFreeInCache(nedpool *p, threadcache *tc, int mymspace) THROWSPEC
+{
+       unsigned int age=THREADCACHEMAXFREESPACE/8192;
+       /*ACQUIRE_LOCK(&p->m[mymspace]->mutex);*/
+       while(age && tc->freeInCache>=THREADCACHEMAXFREESPACE)
+       {
+               RemoveCacheEntries(p, tc, age);
+               /*printf("*** Removing cache entries older than %u (%u)\n", age, (unsigned int) tc->freeInCache);*/
+               age>>=1;
+       }
+       /*RELEASE_LOCK(&p->m[mymspace]->mutex);*/
+}
+static void threadcache_free(nedpool *p, threadcache *tc, int mymspace, void *mem, size_t size) THROWSPEC
+{
+       unsigned int bestsize;
+       unsigned int idx=size2binidx(size);
+       threadcacheblk **binsptr, *tck=(threadcacheblk *) mem;
+       assert(size>=sizeof(threadcacheblk) && size<=THREADCACHEMAX+CHUNK_OVERHEAD);
+#ifdef DEBUG
+       {       /* Make sure this is a valid memory block */
+           mchunkptr p  = mem2chunk(mem);
+           mstate fm = get_mstate_for(p);
+           if (!ok_magic(fm)) {
+             USAGE_ERROR_ACTION(fm, p);
+             return;
+           }
+       }
+#endif
+#ifdef FULLSANITYCHECKS
+       tcfullsanitycheck(tc);
+#endif
+       /* Calculate best fit bin size */
+       bestsize=1<<(idx+4);
+#if 0
+       /* Finer grained bin fit */
+       idx<<=1;
+       if(size>bestsize)
+       {
+               unsigned int biggerbestsize=bestsize+bestsize<<1;
+               if(size>=biggerbestsize)
+               {
+                       idx++;
+                       bestsize=biggerbestsize;
+               }
+       }
+#endif
+       if(bestsize!=size)      /* dlmalloc can round up, so we round down to preserve indexing */
+               size=bestsize;
+       binsptr=&tc->bins[idx*2];
+       assert(idx<=THREADCACHEMAXBINS);
+       if(tck==*binsptr)
+       {
+               fprintf(stderr, "Attempt to free already freed memory block %p - aborting!\n", tck);
+               abort();
+       }
+#ifdef FULLSANITYCHECKS
+       tck->magic=*(unsigned int *) "NEDN";
+#endif
+       tck->lastUsed=++tc->frees;
+       tck->size=(unsigned int) size;
+       tck->next=*binsptr;
+       tck->prev=0;
+       if(tck->next)
+               tck->next->prev=tck;
+       else
+               binsptr[1]=tck;
+       assert(!*binsptr || (*binsptr)->size==tck->size);
+       *binsptr=tck;
+       assert(tck==tc->bins[idx*2]);
+       assert(tc->bins[idx*2+1]==tck || binsptr[0]->next->prev==tck);
+       /*printf("free: %p, %p, %p, %lu\n", p, tc, mem, (long) size);*/
+       tc->freeInCache+=size;
+#ifdef FULLSANITYCHECKS
+       tcfullsanitycheck(tc);
+#endif
+#if 1
+       if(tc->freeInCache>=THREADCACHEMAXFREESPACE)
+               ReleaseFreeInCache(p, tc, mymspace);
+#endif
+}
+
+
+
+
+static NOINLINE int InitPool(nedpool *p, size_t capacity, int threads) THROWSPEC
+{      /* threads is -1 for system pool */
+       ensure_initialization();
+       ACQUIRE_MALLOC_GLOBAL_LOCK();
+       if(p->threads) goto done;
+       if(INITIAL_LOCK(&p->mutex)) goto err;
+       if(TLSALLOC(&p->mycache)) goto err;
+       if(!(p->m[0]=(mstate) create_mspace(capacity, 1))) goto err;
+       p->m[0]->extp=p;
+       p->threads=(threads<1 || threads>MAXTHREADSINPOOL) ? MAXTHREADSINPOOL : threads;
+done:
+       RELEASE_MALLOC_GLOBAL_LOCK();
+       return 1;
+err:
+       if(threads<0)
+               abort();                        /* If you can't allocate for system pool, we're screwed */
+       DestroyCaches(p);
+       if(p->m[0])
+       {
+               destroy_mspace(p->m[0]);
+               p->m[0]=0;
+       }
+       if(p->mycache)
+       {
+               if(TLSFREE(p->mycache)) abort();
+               p->mycache=0;
+       }
+       RELEASE_MALLOC_GLOBAL_LOCK();
+       return 0;
+}
+static NOINLINE mstate FindMSpace(nedpool *p, threadcache *tc, int *lastUsed, size_t size) THROWSPEC
+{      /* Gets called when thread's last used mspace is in use. The strategy
+       is to run through the list of all available mspaces looking for an
+       unlocked one and if we fail, we create a new one so long as we don't
+       exceed p->threads */
+       int n, end;
+       for(n=end=*lastUsed+1; p->m[n]; end=++n)
+       {
+               if(TRY_LOCK(&p->m[n]->mutex)) goto found;
+       }
+       for(n=0; n<*lastUsed && p->m[n]; n++)
+       {
+               if(TRY_LOCK(&p->m[n]->mutex)) goto found;
+       }
+       if(end<p->threads)
+       {
+               mstate temp;
+               if(!(temp=(mstate) create_mspace(size, 1)))
+                       goto badexit;
+               /* Now we're ready to modify the lists, we lock */
+               ACQUIRE_LOCK(&p->mutex);
+               while(p->m[end] && end<p->threads)
+                       end++;
+               if(end>=p->threads)
+               {       /* Drat, must destroy it now */
+                       RELEASE_LOCK(&p->mutex);
+                       destroy_mspace((mspace) temp);
+                       goto badexit;
+               }
+               /* We really want to make sure this goes into memory now but we
+               have to be careful of breaking aliasing rules, so write it twice */
+               *((volatile struct malloc_state **) &p->m[end])=p->m[end]=temp;
+               ACQUIRE_LOCK(&p->m[end]->mutex);
+               /*printf("Created mspace idx %d\n", end);*/
+               RELEASE_LOCK(&p->mutex);
+               n=end;
+               goto found;
+       }
+       /* Let it lock on the last one it used */
+badexit:
+       ACQUIRE_LOCK(&p->m[*lastUsed]->mutex);
+       return p->m[*lastUsed];
+found:
+       *lastUsed=n;
+       if(tc)
+               tc->mymspace=n;
+       else
+       {
+               if(TLSSET(p->mycache, (void *)(size_t)(-(n+1)))) abort();
+       }
+       return p->m[n];
+}
+
+nedpool *nedcreatepool(size_t capacity, int threads) THROWSPEC
+{
+       nedpool *ret;
+       if(!(ret=(nedpool *) nedpcalloc(0, 1, sizeof(nedpool)))) return 0;
+       if(!InitPool(ret, capacity, threads))
+       {
+               nedpfree(0, ret);
+               return 0;
+       }
+       return ret;
+}
+void neddestroypool(nedpool *p) THROWSPEC
+{
+       int n;
+       ACQUIRE_LOCK(&p->mutex);
+       DestroyCaches(p);
+       for(n=0; p->m[n]; n++)
+       {
+               destroy_mspace(p->m[n]);
+               p->m[n]=0;
+       }
+       RELEASE_LOCK(&p->mutex);
+       if(TLSFREE(p->mycache)) abort();
+       nedpfree(0, p);
+}
+
+void nedpsetvalue(nedpool *p, void *v) THROWSPEC
+{
+       if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); }
+       p->uservalue=v;
+}
+void *nedgetvalue(nedpool **p, void *mem) THROWSPEC
+{
+       nedpool *np=0;
+       mchunkptr mcp=mem2chunk(mem);
+       mstate fm;
+       if(!(is_aligned(chunk2mem(mcp))) && mcp->head != FENCEPOST_HEAD) return 0;
+       if(!cinuse(mcp)) return 0;
+       if(!next_pinuse(mcp)) return 0;
+       if(!is_mmapped(mcp) && !pinuse(mcp))
+       {
+               if(next_chunk(prev_chunk(mcp))!=mcp) return 0;
+       }
+       fm=get_mstate_for(mcp);
+       if(!ok_magic(fm)) return 0;
+       if(!ok_address(fm, mcp)) return 0;
+       if(!fm->extp) return 0;
+       np=(nedpool *) fm->extp;
+       if(p) *p=np;
+       return np->uservalue;
+}
+
+void neddisablethreadcache(nedpool *p) THROWSPEC
+{
+       int mycache;
+       if(!p)
+       {
+               p=&syspool;
+               if(!syspool.threads) InitPool(&syspool, 0, -1);
+       }
+       mycache=(int)(size_t) TLSGET(p->mycache);
+       if(!mycache)
+       {       /* Set to mspace 0 */
+               if(TLSSET(p->mycache, (void *)-1)) abort();
+       }
+       else if(mycache>0)
+       {       /* Set to last used mspace */
+               threadcache *tc=p->caches[mycache-1];
+#if defined(DEBUG)
+               printf("Threadcache utilisation: %lf%% in cache with %lf%% lost to other threads\n",
+                       100.0*tc->successes/tc->mallocs, 100.0*((double) tc->mallocs-tc->frees)/tc->mallocs);
+#endif
+               if(TLSSET(p->mycache, (void *)(size_t)(-tc->mymspace))) abort();
+               tc->frees++;
+               RemoveCacheEntries(p, tc, 0);
+               assert(!tc->freeInCache);
+               tc->mymspace=-1;
+               tc->threadid=0;
+               mspace_free(0, p->caches[mycache-1]);
+               p->caches[mycache-1]=0;
+       }
+}
+
+#define GETMSPACE(m,p,tc,ms,s,action)           \
+  do                                            \
+  {                                             \
+    mstate m = GetMSpace((p),(tc),(ms),(s));    \
+    action;                                     \
+    RELEASE_LOCK(&m->mutex);                    \
+  } while (0)
+
+static FORCEINLINE mstate GetMSpace(nedpool *p, threadcache *tc, int mymspace, size_t size) THROWSPEC
+{      /* Returns a locked and ready for use mspace */
+       mstate m=p->m[mymspace];
+       assert(m);
+       if(!TRY_LOCK(&p->m[mymspace]->mutex)) m=FindMSpace(p, tc, &mymspace, size);\
+       /*assert(IS_LOCKED(&p->m[mymspace]->mutex));*/
+       return m;
+}
+static FORCEINLINE void GetThreadCache(nedpool **p, threadcache **tc, int *mymspace, size_t *size) THROWSPEC
+{
+       int mycache;
+       if(size && *size<sizeof(threadcacheblk)) *size=sizeof(threadcacheblk);
+       if(!*p)
+       {
+               *p=&syspool;
+               if(!syspool.threads) InitPool(&syspool, 0, -1);
+       }
+       mycache=(int)(size_t) TLSGET((*p)->mycache);
+       if(mycache>0)
+       {
+               *tc=(*p)->caches[mycache-1];
+               *mymspace=(*tc)->mymspace;
+       }
+       else if(!mycache)
+       {
+               *tc=AllocCache(*p);
+               if(!*tc)
+               {       /* Disable */
+                       if(TLSSET((*p)->mycache, (void *)-1)) abort();
+                       *mymspace=0;
+               }
+               else
+                       *mymspace=(*tc)->mymspace;
+       }
+       else
+       {
+               *tc=0;
+               *mymspace=-mycache-1;
+       }
+       assert(*mymspace>=0);
+       assert((long)(size_t)CURRENT_THREAD==(*tc)->threadid);
+#ifdef FULLSANITYCHECKS
+       if(*tc)
+       {
+               if(*(unsigned int *)"NEDMALC1"!=(*tc)->magic1 || *(unsigned int *)"NEDMALC2"!=(*tc)->magic2)
+               {
+                       abort();
+               }
+       }
+#endif
+}
+
+void * nedpmalloc(nedpool *p, size_t size) THROWSPEC
+{
+       void *ret=0;
+       threadcache *tc;
+       int mymspace;
+       GetThreadCache(&p, &tc, &mymspace, &size);
+#if THREADCACHEMAX
+       if(tc && size<=THREADCACHEMAX)
+       {       /* Use the thread cache */
+               ret=threadcache_malloc(p, tc, &size);
+       }
+#endif
+       if(!ret)
+       {       /* Use this thread's mspace */
+       GETMSPACE(m, p, tc, mymspace, size,
+                 ret=mspace_malloc(m, size));
+       }
+       return ret;
+}
+void * nedpcalloc(nedpool *p, size_t no, size_t size) THROWSPEC
+{
+       size_t rsize=size*no;
+       void *ret=0;
+       threadcache *tc;
+       int mymspace;
+       GetThreadCache(&p, &tc, &mymspace, &rsize);
+#if THREADCACHEMAX
+       if(tc && rsize<=THREADCACHEMAX)
+       {       /* Use the thread cache */
+               if((ret=threadcache_malloc(p, tc, &rsize)))
+                       memset(ret, 0, rsize);
+       }
+#endif
+       if(!ret)
+       {       /* Use this thread's mspace */
+       GETMSPACE(m, p, tc, mymspace, rsize,
+                 ret=mspace_calloc(m, 1, rsize));
+       }
+       return ret;
+}
+void * nedprealloc(nedpool *p, void *mem, size_t size) THROWSPEC
+{
+       void *ret=0;
+       threadcache *tc;
+       int mymspace;
+       if(!mem) return nedpmalloc(p, size);
+       GetThreadCache(&p, &tc, &mymspace, &size);
+#if THREADCACHEMAX
+       if(tc && size && size<=THREADCACHEMAX)
+       {       /* Use the thread cache */
+               size_t memsize=nedblksize(mem);
+               assert(memsize);
+               if((ret=threadcache_malloc(p, tc, &size)))
+               {
+                       memcpy(ret, mem, memsize<size ? memsize : size);
+                       if(memsize<=THREADCACHEMAX)
+                               threadcache_free(p, tc, mymspace, mem, memsize);
+                       else
+                               mspace_free(0, mem);
+               }
+       }
+#endif
+       if(!ret)
+       {       /* Reallocs always happen in the mspace they happened in, so skip
+               locking the preferred mspace for this thread */
+               ret=mspace_realloc(0, mem, size);
+       }
+       return ret;
+}
+void   nedpfree(nedpool *p, void *mem) THROWSPEC
+{      /* Frees always happen in the mspace they happened in, so skip
+       locking the preferred mspace for this thread */
+       threadcache *tc;
+       int mymspace;
+       size_t memsize;
+       assert(mem);
+       GetThreadCache(&p, &tc, &mymspace, 0);
+#if THREADCACHEMAX
+       memsize=nedblksize(mem);
+       assert(memsize);
+       if(mem && tc && memsize<=(THREADCACHEMAX+CHUNK_OVERHEAD))
+               threadcache_free(p, tc, mymspace, mem, memsize);
+       else
+#endif
+               mspace_free(0, mem);
+}
+void * nedpmemalign(nedpool *p, size_t alignment, size_t bytes) THROWSPEC
+{
+       void *ret;
+       threadcache *tc;
+       int mymspace;
+       GetThreadCache(&p, &tc, &mymspace, &bytes);
+       {       /* Use this thread's mspace */
+       GETMSPACE(m, p, tc, mymspace, bytes,
+                 ret=mspace_memalign(m, alignment, bytes));
+       }
+       return ret;
+}
+#if !NO_MALLINFO
+struct mallinfo nedpmallinfo(nedpool *p) THROWSPEC
+{
+       int n;
+       struct mallinfo ret={0};
+       if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); }
+       for(n=0; p->m[n]; n++)
+       {
+               struct mallinfo t=mspace_mallinfo(p->m[n]);
+               ret.arena+=t.arena;
+               ret.ordblks+=t.ordblks;
+               ret.hblkhd+=t.hblkhd;
+               ret.usmblks+=t.usmblks;
+               ret.uordblks+=t.uordblks;
+               ret.fordblks+=t.fordblks;
+               ret.keepcost+=t.keepcost;
+       }
+       return ret;
+}
+#endif
+int    nedpmallopt(nedpool *p, int parno, int value) THROWSPEC
+{
+       return mspace_mallopt(parno, value);
+}
+int    nedpmalloc_trim(nedpool *p, size_t pad) THROWSPEC
+{
+       int n, ret=0;
+       if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); }
+       for(n=0; p->m[n]; n++)
+       {
+               ret+=mspace_trim(p->m[n], pad);
+       }
+       return ret;
+}
+void   nedpmalloc_stats(nedpool *p) THROWSPEC
+{
+       int n;
+       if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); }
+       for(n=0; p->m[n]; n++)
+       {
+               mspace_malloc_stats(p->m[n]);
+       }
+}
+size_t nedpmalloc_footprint(nedpool *p) THROWSPEC
+{
+       size_t ret=0;
+       int n;
+       if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); }
+       for(n=0; p->m[n]; n++)
+       {
+               ret+=mspace_footprint(p->m[n]);
+       }
+       return ret;
+}
+void **nedpindependent_calloc(nedpool *p, size_t elemsno, size_t elemsize, void **chunks) THROWSPEC
+{
+       void **ret;
+       threadcache *tc;
+       int mymspace;
+       GetThreadCache(&p, &tc, &mymspace, &elemsize);
+    GETMSPACE(m, p, tc, mymspace, elemsno*elemsize,
+             ret=mspace_independent_calloc(m, elemsno, elemsize, chunks));
+       return ret;
+}
+void **nedpindependent_comalloc(nedpool *p, size_t elems, size_t *sizes, void **chunks) THROWSPEC
+{
+       void **ret;
+       threadcache *tc;
+       int mymspace;
+    size_t i, *adjustedsizes=(size_t *) alloca(elems*sizeof(size_t));
+    if(!adjustedsizes) return 0;
+    for(i=0; i<elems; i++)
+       adjustedsizes[i]=sizes[i]<sizeof(threadcacheblk) ? sizeof(threadcacheblk) : sizes[i];
+       GetThreadCache(&p, &tc, &mymspace, 0);
+       GETMSPACE(m, p, tc, mymspace, 0,
+             ret=mspace_independent_comalloc(m, elems, adjustedsizes, chunks));
+       return ret;
+}
+
+#ifdef OVERRIDE_STRDUP
+/*
+ * This implementation is purely there to override the libc version, to
+ * avoid a crash due to allocation and free on different 'heaps'.
+ */
+char *strdup(const char *s1)
+{
+       char *s2 = 0;
+       if (s1) {
+               s2 = malloc(strlen(s1) + 1);
+               strcpy(s2, s1);
+       }
+       return s2;
+}
+#endif
+
+#if defined(__cplusplus)
+}
+#endif
diff --git a/compat/nedmalloc/nedmalloc.h b/compat/nedmalloc/nedmalloc.h
new file mode 100644 (file)
index 0000000..f960e66
--- /dev/null
@@ -0,0 +1,180 @@
+/* nedalloc, an alternative malloc implementation for multiple threads without
+lock contention based on dlmalloc v2.8.3. (C) 2005 Niall Douglas
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef NEDMALLOC_H
+#define NEDMALLOC_H
+
+
+/* See malloc.c.h for what each function does.
+
+REPLACE_SYSTEM_ALLOCATOR causes nedalloc's functions to be called malloc,
+free etc. instead of nedmalloc, nedfree etc. You may or may not want this.
+
+NO_NED_NAMESPACE prevents the functions from being defined in the nedalloc
+namespace when in C++ (uses the global namespace instead).
+
+EXTSPEC can be defined to be __declspec(dllexport) or
+__attribute__ ((visibility("default"))) or whatever you like. It defaults
+to extern.
+
+USE_LOCKS can be 2 if you want to define your own MLOCK_T, INITIAL_LOCK,
+ACQUIRE_LOCK, RELEASE_LOCK, TRY_LOCK, IS_LOCKED and NULL_LOCK_INITIALIZER.
+
+*/
+
+#include <stddef.h>   /* for size_t */
+
+#ifndef EXTSPEC
+ #define EXTSPEC extern
+#endif
+
+#if defined(_MSC_VER) && _MSC_VER>=1400
+ #define MALLOCATTR __declspec(restrict)
+#endif
+#ifdef __GNUC__
+ #define MALLOCATTR __attribute__ ((malloc))
+#endif
+#ifndef MALLOCATTR
+ #define MALLOCATTR
+#endif
+
+#ifdef REPLACE_SYSTEM_ALLOCATOR
+ #define nedmalloc               malloc
+ #define nedcalloc               calloc
+ #define nedrealloc              realloc
+ #define nedfree                 free
+ #define nedmemalign             memalign
+ #define nedmallinfo             mallinfo
+ #define nedmallopt              mallopt
+ #define nedmalloc_trim          malloc_trim
+ #define nedmalloc_stats         malloc_stats
+ #define nedmalloc_footprint     malloc_footprint
+ #define nedindependent_calloc   independent_calloc
+ #define nedindependent_comalloc independent_comalloc
+ #ifdef _MSC_VER
+  #define nedblksize              _msize
+ #endif
+#endif
+
+#ifndef NO_MALLINFO
+#define NO_MALLINFO 0
+#endif
+
+#if !NO_MALLINFO
+struct mallinfo;
+#endif
+
+#if defined(__cplusplus)
+ #if !defined(NO_NED_NAMESPACE)
+namespace nedalloc {
+ #else
+extern "C" {
+ #endif
+ #define THROWSPEC throw()
+#else
+ #define THROWSPEC
+#endif
+
+/* These are the global functions */
+
+/* Gets the usable size of an allocated block. Note this will always be bigger than what was
+asked for due to rounding etc.
+*/
+EXTSPEC size_t nedblksize(void *mem) THROWSPEC;
+
+EXTSPEC void nedsetvalue(void *v) THROWSPEC;
+
+EXTSPEC MALLOCATTR void * nedmalloc(size_t size) THROWSPEC;
+EXTSPEC MALLOCATTR void * nedcalloc(size_t no, size_t size) THROWSPEC;
+EXTSPEC MALLOCATTR void * nedrealloc(void *mem, size_t size) THROWSPEC;
+EXTSPEC void   nedfree(void *mem) THROWSPEC;
+EXTSPEC MALLOCATTR void * nedmemalign(size_t alignment, size_t bytes) THROWSPEC;
+#if !NO_MALLINFO
+EXTSPEC struct mallinfo nedmallinfo(void) THROWSPEC;
+#endif
+EXTSPEC int    nedmallopt(int parno, int value) THROWSPEC;
+EXTSPEC int    nedmalloc_trim(size_t pad) THROWSPEC;
+EXTSPEC void   nedmalloc_stats(void) THROWSPEC;
+EXTSPEC size_t nedmalloc_footprint(void) THROWSPEC;
+EXTSPEC MALLOCATTR void **nedindependent_calloc(size_t elemsno, size_t elemsize, void **chunks) THROWSPEC;
+EXTSPEC MALLOCATTR void **nedindependent_comalloc(size_t elems, size_t *sizes, void **chunks) THROWSPEC;
+
+/* These are the pool functions */
+struct nedpool_t;
+typedef struct nedpool_t nedpool;
+
+/* Creates a memory pool for use with the nedp* functions below.
+Capacity is how much to allocate immediately (if you know you'll be allocating a lot
+of memory very soon) which you can leave at zero. Threads specifies how many threads
+will *normally* be accessing the pool concurrently. Setting this to zero means it
+extends on demand, but be careful of this as it can rapidly consume system resources
+where bursts of concurrent threads use a pool at once.
+*/
+EXTSPEC MALLOCATTR nedpool *nedcreatepool(size_t capacity, int threads) THROWSPEC;
+
+/* Destroys a memory pool previously created by nedcreatepool().
+*/
+EXTSPEC void neddestroypool(nedpool *p) THROWSPEC;
+
+/* Sets a value to be associated with a pool. You can retrieve this value by passing
+any memory block allocated from that pool.
+*/
+EXTSPEC void nedpsetvalue(nedpool *p, void *v) THROWSPEC;
+/* Gets a previously set value using nedpsetvalue() or zero if memory is unknown.
+Optionally can also retrieve pool.
+*/
+EXTSPEC void *nedgetvalue(nedpool **p, void *mem) THROWSPEC;
+
+/* Disables the thread cache for the calling thread, returning any existing cache
+data to the central pool.
+*/
+EXTSPEC void neddisablethreadcache(nedpool *p) THROWSPEC;
+
+EXTSPEC MALLOCATTR void * nedpmalloc(nedpool *p, size_t size) THROWSPEC;
+EXTSPEC MALLOCATTR void * nedpcalloc(nedpool *p, size_t no, size_t size) THROWSPEC;
+EXTSPEC MALLOCATTR void * nedprealloc(nedpool *p, void *mem, size_t size) THROWSPEC;
+EXTSPEC void   nedpfree(nedpool *p, void *mem) THROWSPEC;
+EXTSPEC MALLOCATTR void * nedpmemalign(nedpool *p, size_t alignment, size_t bytes) THROWSPEC;
+#if !NO_MALLINFO
+EXTSPEC struct mallinfo nedpmallinfo(nedpool *p) THROWSPEC;
+#endif
+EXTSPEC int    nedpmallopt(nedpool *p, int parno, int value) THROWSPEC;
+EXTSPEC int    nedpmalloc_trim(nedpool *p, size_t pad) THROWSPEC;
+EXTSPEC void   nedpmalloc_stats(nedpool *p) THROWSPEC;
+EXTSPEC size_t nedpmalloc_footprint(nedpool *p) THROWSPEC;
+EXTSPEC MALLOCATTR void **nedpindependent_calloc(nedpool *p, size_t elemsno, size_t elemsize, void **chunks) THROWSPEC;
+EXTSPEC MALLOCATTR void **nedpindependent_comalloc(nedpool *p, size_t elems, size_t *sizes, void **chunks) THROWSPEC;
+
+#if defined(__cplusplus)
+}
+#endif
+
+#undef MALLOCATTR
+#undef EXTSPEC
+
+#endif
diff --git a/compat/regex/regcomp.c b/compat/regex/regcomp.c
new file mode 100644 (file)
index 0000000..8c96ed9
--- /dev/null
@@ -0,0 +1,3884 @@
+/* Extended regular expression matching and search library.
+   Copyright (C) 2002-2007,2009,2010 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+   Contributed by Isamu Hasegawa <isamu@yamato.ibm.com>.
+
+   The GNU C 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.
+
+   The GNU C 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 the GNU C Library; if not, write to the Free
+   Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301 USA.  */
+
+static reg_errcode_t re_compile_internal (regex_t *preg, const char * pattern,
+                                         size_t length, reg_syntax_t syntax);
+static void re_compile_fastmap_iter (regex_t *bufp,
+                                    const re_dfastate_t *init_state,
+                                    char *fastmap);
+static reg_errcode_t init_dfa (re_dfa_t *dfa, size_t pat_len);
+#ifdef RE_ENABLE_I18N
+static void free_charset (re_charset_t *cset);
+#endif /* RE_ENABLE_I18N */
+static void free_workarea_compile (regex_t *preg);
+static reg_errcode_t create_initial_state (re_dfa_t *dfa);
+#ifdef RE_ENABLE_I18N
+static void optimize_utf8 (re_dfa_t *dfa);
+#endif
+static reg_errcode_t analyze (regex_t *preg);
+static reg_errcode_t preorder (bin_tree_t *root,
+                              reg_errcode_t (fn (void *, bin_tree_t *)),
+                              void *extra);
+static reg_errcode_t postorder (bin_tree_t *root,
+                               reg_errcode_t (fn (void *, bin_tree_t *)),
+                               void *extra);
+static reg_errcode_t optimize_subexps (void *extra, bin_tree_t *node);
+static reg_errcode_t lower_subexps (void *extra, bin_tree_t *node);
+static bin_tree_t *lower_subexp (reg_errcode_t *err, regex_t *preg,
+                                bin_tree_t *node);
+static reg_errcode_t calc_first (void *extra, bin_tree_t *node);
+static reg_errcode_t calc_next (void *extra, bin_tree_t *node);
+static reg_errcode_t link_nfa_nodes (void *extra, bin_tree_t *node);
+static int duplicate_node (re_dfa_t *dfa, int org_idx, unsigned int constraint);
+static int search_duplicated_node (const re_dfa_t *dfa, int org_node,
+                                  unsigned int constraint);
+static reg_errcode_t calc_eclosure (re_dfa_t *dfa);
+static reg_errcode_t calc_eclosure_iter (re_node_set *new_set, re_dfa_t *dfa,
+                                        int node, int root);
+static reg_errcode_t calc_inveclosure (re_dfa_t *dfa);
+static int fetch_number (re_string_t *input, re_token_t *token,
+                        reg_syntax_t syntax);
+static int peek_token (re_token_t *token, re_string_t *input,
+                       reg_syntax_t syntax) internal_function;
+static bin_tree_t *parse (re_string_t *regexp, regex_t *preg,
+                         reg_syntax_t syntax, reg_errcode_t *err);
+static bin_tree_t *parse_reg_exp (re_string_t *regexp, regex_t *preg,
+                                 re_token_t *token, reg_syntax_t syntax,
+                                 int nest, reg_errcode_t *err);
+static bin_tree_t *parse_branch (re_string_t *regexp, regex_t *preg,
+                                re_token_t *token, reg_syntax_t syntax,
+                                int nest, reg_errcode_t *err);
+static bin_tree_t *parse_expression (re_string_t *regexp, regex_t *preg,
+                                    re_token_t *token, reg_syntax_t syntax,
+                                    int nest, reg_errcode_t *err);
+static bin_tree_t *parse_sub_exp (re_string_t *regexp, regex_t *preg,
+                                 re_token_t *token, reg_syntax_t syntax,
+                                 int nest, reg_errcode_t *err);
+static bin_tree_t *parse_dup_op (bin_tree_t *dup_elem, re_string_t *regexp,
+                                re_dfa_t *dfa, re_token_t *token,
+                                reg_syntax_t syntax, reg_errcode_t *err);
+static bin_tree_t *parse_bracket_exp (re_string_t *regexp, re_dfa_t *dfa,
+                                     re_token_t *token, reg_syntax_t syntax,
+                                     reg_errcode_t *err);
+static reg_errcode_t parse_bracket_element (bracket_elem_t *elem,
+                                           re_string_t *regexp,
+                                           re_token_t *token, int token_len,
+                                           re_dfa_t *dfa,
+                                           reg_syntax_t syntax,
+                                           int accept_hyphen);
+static reg_errcode_t parse_bracket_symbol (bracket_elem_t *elem,
+                                         re_string_t *regexp,
+                                         re_token_t *token);
+#ifdef RE_ENABLE_I18N
+static reg_errcode_t build_equiv_class (bitset_t sbcset,
+                                       re_charset_t *mbcset,
+                                       int *equiv_class_alloc,
+                                       const unsigned char *name);
+static reg_errcode_t build_charclass (RE_TRANSLATE_TYPE trans,
+                                     bitset_t sbcset,
+                                     re_charset_t *mbcset,
+                                     int *char_class_alloc,
+                                     const char *class_name,
+                                     reg_syntax_t syntax);
+#else  /* not RE_ENABLE_I18N */
+static reg_errcode_t build_equiv_class (bitset_t sbcset,
+                                       const unsigned char *name);
+static reg_errcode_t build_charclass (RE_TRANSLATE_TYPE trans,
+                                     bitset_t sbcset,
+                                     const char *class_name,
+                                     reg_syntax_t syntax);
+#endif /* not RE_ENABLE_I18N */
+static bin_tree_t *build_charclass_op (re_dfa_t *dfa,
+                                      RE_TRANSLATE_TYPE trans,
+                                      const char *class_name,
+                                      const char *extra,
+                                      int non_match, reg_errcode_t *err);
+static bin_tree_t *create_tree (re_dfa_t *dfa,
+                               bin_tree_t *left, bin_tree_t *right,
+                               re_token_type_t type);
+static bin_tree_t *create_token_tree (re_dfa_t *dfa,
+                                     bin_tree_t *left, bin_tree_t *right,
+                                     const re_token_t *token);
+static bin_tree_t *duplicate_tree (const bin_tree_t *src, re_dfa_t *dfa);
+static void free_token (re_token_t *node);
+static reg_errcode_t free_tree (void *extra, bin_tree_t *node);
+static reg_errcode_t mark_opt_subexp (void *extra, bin_tree_t *node);
+\f
+/* This table gives an error message for each of the error codes listed
+   in regex.h.  Obviously the order here has to be same as there.
+   POSIX doesn't require that we do anything for REG_NOERROR,
+   but why not be nice?  */
+
+const char __re_error_msgid[] attribute_hidden =
+  {
+#define REG_NOERROR_IDX        0
+    gettext_noop ("Success")   /* REG_NOERROR */
+    "\0"
+#define REG_NOMATCH_IDX (REG_NOERROR_IDX + sizeof "Success")
+    gettext_noop ("No match")  /* REG_NOMATCH */
+    "\0"
+#define REG_BADPAT_IDX (REG_NOMATCH_IDX + sizeof "No match")
+    gettext_noop ("Invalid regular expression") /* REG_BADPAT */
+    "\0"
+#define REG_ECOLLATE_IDX (REG_BADPAT_IDX + sizeof "Invalid regular expression")
+    gettext_noop ("Invalid collation character") /* REG_ECOLLATE */
+    "\0"
+#define REG_ECTYPE_IDX (REG_ECOLLATE_IDX + sizeof "Invalid collation character")
+    gettext_noop ("Invalid character class name") /* REG_ECTYPE */
+    "\0"
+#define REG_EESCAPE_IDX        (REG_ECTYPE_IDX + sizeof "Invalid character class name")
+    gettext_noop ("Trailing backslash") /* REG_EESCAPE */
+    "\0"
+#define REG_ESUBREG_IDX        (REG_EESCAPE_IDX + sizeof "Trailing backslash")
+    gettext_noop ("Invalid back reference") /* REG_ESUBREG */
+    "\0"
+#define REG_EBRACK_IDX (REG_ESUBREG_IDX + sizeof "Invalid back reference")
+    gettext_noop ("Unmatched [ or [^") /* REG_EBRACK */
+    "\0"
+#define REG_EPAREN_IDX (REG_EBRACK_IDX + sizeof "Unmatched [ or [^")
+    gettext_noop ("Unmatched ( or \\(") /* REG_EPAREN */
+    "\0"
+#define REG_EBRACE_IDX (REG_EPAREN_IDX + sizeof "Unmatched ( or \\(")
+    gettext_noop ("Unmatched \\{") /* REG_EBRACE */
+    "\0"
+#define REG_BADBR_IDX  (REG_EBRACE_IDX + sizeof "Unmatched \\{")
+    gettext_noop ("Invalid content of \\{\\}") /* REG_BADBR */
+    "\0"
+#define REG_ERANGE_IDX (REG_BADBR_IDX + sizeof "Invalid content of \\{\\}")
+    gettext_noop ("Invalid range end") /* REG_ERANGE */
+    "\0"
+#define REG_ESPACE_IDX (REG_ERANGE_IDX + sizeof "Invalid range end")
+    gettext_noop ("Memory exhausted") /* REG_ESPACE */
+    "\0"
+#define REG_BADRPT_IDX (REG_ESPACE_IDX + sizeof "Memory exhausted")
+    gettext_noop ("Invalid preceding regular expression") /* REG_BADRPT */
+    "\0"
+#define REG_EEND_IDX   (REG_BADRPT_IDX + sizeof "Invalid preceding regular expression")
+    gettext_noop ("Premature end of regular expression") /* REG_EEND */
+    "\0"
+#define REG_ESIZE_IDX  (REG_EEND_IDX + sizeof "Premature end of regular expression")
+    gettext_noop ("Regular expression too big") /* REG_ESIZE */
+    "\0"
+#define REG_ERPAREN_IDX        (REG_ESIZE_IDX + sizeof "Regular expression too big")
+    gettext_noop ("Unmatched ) or \\)") /* REG_ERPAREN */
+  };
+
+const size_t __re_error_msgid_idx[] attribute_hidden =
+  {
+    REG_NOERROR_IDX,
+    REG_NOMATCH_IDX,
+    REG_BADPAT_IDX,
+    REG_ECOLLATE_IDX,
+    REG_ECTYPE_IDX,
+    REG_EESCAPE_IDX,
+    REG_ESUBREG_IDX,
+    REG_EBRACK_IDX,
+    REG_EPAREN_IDX,
+    REG_EBRACE_IDX,
+    REG_BADBR_IDX,
+    REG_ERANGE_IDX,
+    REG_ESPACE_IDX,
+    REG_BADRPT_IDX,
+    REG_EEND_IDX,
+    REG_ESIZE_IDX,
+    REG_ERPAREN_IDX
+  };
+\f
+/* Entry points for GNU code.  */
+
+
+#ifdef ZOS_USS
+
+/* For ZOS USS we must define btowc */
+
+wchar_t 
+btowc (int c)
+{
+   wchar_t wtmp[2];
+   char tmp[2];
+
+   tmp[0] = c;
+   tmp[1] = 0;
+
+   mbtowc (wtmp, tmp, 1);
+   return wtmp[0];
+}
+#endif
+
+/* re_compile_pattern is the GNU regular expression compiler: it
+   compiles PATTERN (of length LENGTH) and puts the result in BUFP.
+   Returns 0 if the pattern was valid, otherwise an error string.
+
+   Assumes the `allocated' (and perhaps `buffer') and `translate' fields
+   are set in BUFP on entry.  */
+
+const char *
+re_compile_pattern (const char *pattern,
+                   size_t length,
+                   struct re_pattern_buffer *bufp)
+{
+  reg_errcode_t ret;
+
+  /* And GNU code determines whether or not to get register information
+     by passing null for the REGS argument to re_match, etc., not by
+     setting no_sub, unless RE_NO_SUB is set.  */
+  bufp->no_sub = !!(re_syntax_options & RE_NO_SUB);
+
+  /* Match anchors at newline.  */
+  bufp->newline_anchor = 1;
+
+  ret = re_compile_internal (bufp, pattern, length, re_syntax_options);
+
+  if (!ret)
+    return NULL;
+  return gettext (__re_error_msgid + __re_error_msgid_idx[(int) ret]);
+}
+#ifdef _LIBC
+weak_alias (__re_compile_pattern, re_compile_pattern)
+#endif
+
+/* Set by `re_set_syntax' to the current regexp syntax to recognize.  Can
+   also be assigned to arbitrarily: each pattern buffer stores its own
+   syntax, so it can be changed between regex compilations.  */
+/* This has no initializer because initialized variables in Emacs
+   become read-only after dumping.  */
+reg_syntax_t re_syntax_options;
+
+
+/* Specify the precise syntax of regexps for compilation.  This provides
+   for compatibility for various utilities which historically have
+   different, incompatible syntaxes.
+
+   The argument SYNTAX is a bit mask comprised of the various bits
+   defined in regex.h.  We return the old syntax.  */
+
+reg_syntax_t
+re_set_syntax (reg_syntax_t syntax)
+{
+  reg_syntax_t ret = re_syntax_options;
+
+  re_syntax_options = syntax;
+  return ret;
+}
+#ifdef _LIBC
+weak_alias (__re_set_syntax, re_set_syntax)
+#endif
+
+int
+re_compile_fastmap (struct re_pattern_buffer *bufp)
+{
+  re_dfa_t *dfa = (re_dfa_t *) bufp->buffer;
+  char *fastmap = bufp->fastmap;
+
+  memset (fastmap, '\0', sizeof (char) * SBC_MAX);
+  re_compile_fastmap_iter (bufp, dfa->init_state, fastmap);
+  if (dfa->init_state != dfa->init_state_word)
+    re_compile_fastmap_iter (bufp, dfa->init_state_word, fastmap);
+  if (dfa->init_state != dfa->init_state_nl)
+    re_compile_fastmap_iter (bufp, dfa->init_state_nl, fastmap);
+  if (dfa->init_state != dfa->init_state_begbuf)
+    re_compile_fastmap_iter (bufp, dfa->init_state_begbuf, fastmap);
+  bufp->fastmap_accurate = 1;
+  return 0;
+}
+#ifdef _LIBC
+weak_alias (__re_compile_fastmap, re_compile_fastmap)
+#endif
+
+static inline void
+__attribute ((always_inline))
+re_set_fastmap (char *fastmap, int icase, int ch)
+{
+  fastmap[ch] = 1;
+  if (icase)
+    fastmap[tolower (ch)] = 1;
+}
+
+/* Helper function for re_compile_fastmap.
+   Compile fastmap for the initial_state INIT_STATE.  */
+
+static void
+re_compile_fastmap_iter (regex_t *bufp, const re_dfastate_t *init_state,
+                        char *fastmap)
+{
+  volatile re_dfa_t *dfa = (re_dfa_t *) bufp->buffer;
+  int node_cnt;
+  int icase = (dfa->mb_cur_max == 1 && (bufp->syntax & RE_ICASE));
+  for (node_cnt = 0; node_cnt < init_state->nodes.nelem; ++node_cnt)
+    {
+      int node = init_state->nodes.elems[node_cnt];
+      re_token_type_t type = dfa->nodes[node].type;
+
+      if (type == CHARACTER)
+       {
+         re_set_fastmap (fastmap, icase, dfa->nodes[node].opr.c);
+#ifdef RE_ENABLE_I18N
+         if ((bufp->syntax & RE_ICASE) && dfa->mb_cur_max > 1)
+           {
+             unsigned char *buf = re_malloc (unsigned char, dfa->mb_cur_max), *p;
+             wchar_t wc;
+             mbstate_t state;
+
+             p = buf;
+             *p++ = dfa->nodes[node].opr.c;
+             while (++node < dfa->nodes_len
+                    && dfa->nodes[node].type == CHARACTER
+                    && dfa->nodes[node].mb_partial)
+               *p++ = dfa->nodes[node].opr.c;
+             memset (&state, '\0', sizeof (state));
+             if (__mbrtowc (&wc, (const char *) buf, p - buf,
+                            &state) == p - buf
+                 && (__wcrtomb ((char *) buf, towlower (wc), &state)
+                     != (size_t) -1))
+               re_set_fastmap (fastmap, 0, buf[0]);
+             re_free (buf);
+           }
+#endif
+       }
+      else if (type == SIMPLE_BRACKET)
+       {
+         int i, ch;
+         for (i = 0, ch = 0; i < BITSET_WORDS; ++i)
+           {
+             int j;
+             bitset_word_t w = dfa->nodes[node].opr.sbcset[i];
+             for (j = 0; j < BITSET_WORD_BITS; ++j, ++ch)
+               if (w & ((bitset_word_t) 1 << j))
+                 re_set_fastmap (fastmap, icase, ch);
+           }
+       }
+#ifdef RE_ENABLE_I18N
+      else if (type == COMPLEX_BRACKET)
+       {
+         re_charset_t *cset = dfa->nodes[node].opr.mbcset;
+         int i;
+
+# ifdef _LIBC
+         /* See if we have to try all bytes which start multiple collation
+            elements.
+            e.g. In da_DK, we want to catch 'a' since "aa" is a valid
+                 collation element, and don't catch 'b' since 'b' is
+                 the only collation element which starts from 'b' (and
+                 it is caught by SIMPLE_BRACKET).  */
+             if (_NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES) != 0
+                 && (cset->ncoll_syms || cset->nranges))
+               {
+                 const int32_t *table = (const int32_t *)
+                   _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB);
+                 for (i = 0; i < SBC_MAX; ++i)
+                   if (table[i] < 0)
+                     re_set_fastmap (fastmap, icase, i);
+               }
+# endif /* _LIBC */
+
+         /* See if we have to start the match at all multibyte characters,
+            i.e. where we would not find an invalid sequence.  This only
+            applies to multibyte character sets; for single byte character
+            sets, the SIMPLE_BRACKET again suffices.  */
+         if (dfa->mb_cur_max > 1
+             && (cset->nchar_classes || cset->non_match || cset->nranges
+# ifdef _LIBC
+                 || cset->nequiv_classes
+# endif /* _LIBC */
+                ))
+           {
+             unsigned char c = 0;
+             do
+               {
+                 mbstate_t mbs;
+                 memset (&mbs, 0, sizeof (mbs));
+                 if (__mbrtowc (NULL, (char *) &c, 1, &mbs) == (size_t) -2)
+                   re_set_fastmap (fastmap, false, (int) c);
+               }
+             while (++c != 0);
+           }
+
+         else
+           {
+             /* ... Else catch all bytes which can start the mbchars.  */
+             for (i = 0; i < cset->nmbchars; ++i)
+               {
+                 char buf[256];
+                 mbstate_t state;
+                 memset (&state, '\0', sizeof (state));
+                 if (__wcrtomb (buf, cset->mbchars[i], &state) != (size_t) -1)
+                   re_set_fastmap (fastmap, icase, *(unsigned char *) buf);
+                 if ((bufp->syntax & RE_ICASE) && dfa->mb_cur_max > 1)
+                   {
+                     if (__wcrtomb (buf, towlower (cset->mbchars[i]), &state)
+                         != (size_t) -1)
+                       re_set_fastmap (fastmap, false, *(unsigned char *) buf);
+                   }
+               }
+           }
+       }
+#endif /* RE_ENABLE_I18N */
+      else if (type == OP_PERIOD
+#ifdef RE_ENABLE_I18N
+              || type == OP_UTF8_PERIOD
+#endif /* RE_ENABLE_I18N */
+              || type == END_OF_RE)
+       {
+         memset (fastmap, '\1', sizeof (char) * SBC_MAX);
+         if (type == END_OF_RE)
+           bufp->can_be_null = 1;
+         return;
+       }
+    }
+}
+\f
+/* Entry point for POSIX code.  */
+/* regcomp takes a regular expression as a string and compiles it.
+
+   PREG is a regex_t *.  We do not expect any fields to be initialized,
+   since POSIX says we shouldn't.  Thus, we set
+
+     `buffer' to the compiled pattern;
+     `used' to the length of the compiled pattern;
+     `syntax' to RE_SYNTAX_POSIX_EXTENDED if the
+       REG_EXTENDED bit in CFLAGS is set; otherwise, to
+       RE_SYNTAX_POSIX_BASIC;
+     `newline_anchor' to REG_NEWLINE being set in CFLAGS;
+     `fastmap' to an allocated space for the fastmap;
+     `fastmap_accurate' to zero;
+     `re_nsub' to the number of subexpressions in PATTERN.
+
+   PATTERN is the address of the pattern string.
+
+   CFLAGS is a series of bits which affect compilation.
+
+     If REG_EXTENDED is set, we use POSIX extended syntax; otherwise, we
+     use POSIX basic syntax.
+
+     If REG_NEWLINE is set, then . and [^...] don't match newline.
+     Also, regexec will try a match beginning after every newline.
+
+     If REG_ICASE is set, then we considers upper- and lowercase
+     versions of letters to be equivalent when matching.
+
+     If REG_NOSUB is set, then when PREG is passed to regexec, that
+     routine will report only success or failure, and nothing about the
+     registers.
+
+   It returns 0 if it succeeds, nonzero if it doesn't.  (See regex.h for
+   the return codes and their meanings.)  */
+
+int
+regcomp (regex_t *__restrict preg,
+        const char *__restrict pattern,
+        int cflags)
+{
+  reg_errcode_t ret;
+  reg_syntax_t syntax = ((cflags & REG_EXTENDED) ? RE_SYNTAX_POSIX_EXTENDED
+                        : RE_SYNTAX_POSIX_BASIC);
+
+  preg->buffer = NULL;
+  preg->allocated = 0;
+  preg->used = 0;
+
+  /* Try to allocate space for the fastmap.  */
+  preg->fastmap = re_malloc (char, SBC_MAX);
+  if (BE (preg->fastmap == NULL, 0))
+    return REG_ESPACE;
+
+  syntax |= (cflags & REG_ICASE) ? RE_ICASE : 0;
+
+  /* If REG_NEWLINE is set, newlines are treated differently.  */
+  if (cflags & REG_NEWLINE)
+    { /* REG_NEWLINE implies neither . nor [^...] match newline.  */
+      syntax &= ~RE_DOT_NEWLINE;
+      syntax |= RE_HAT_LISTS_NOT_NEWLINE;
+      /* It also changes the matching behavior.  */
+      preg->newline_anchor = 1;
+    }
+  else
+    preg->newline_anchor = 0;
+  preg->no_sub = !!(cflags & REG_NOSUB);
+  preg->translate = NULL;
+
+  ret = re_compile_internal (preg, pattern, strlen (pattern), syntax);
+
+  /* POSIX doesn't distinguish between an unmatched open-group and an
+     unmatched close-group: both are REG_EPAREN.  */
+  if (ret == REG_ERPAREN)
+    ret = REG_EPAREN;
+
+  /* We have already checked preg->fastmap != NULL.  */
+  if (BE (ret == REG_NOERROR, 1))
+    /* Compute the fastmap now, since regexec cannot modify the pattern
+       buffer.  This function never fails in this implementation.  */
+    (void) re_compile_fastmap (preg);
+  else
+    {
+      /* Some error occurred while compiling the expression.  */
+      re_free (preg->fastmap);
+      preg->fastmap = NULL;
+    }
+
+  return (int) ret;
+}
+#ifdef _LIBC
+weak_alias (__regcomp, regcomp)
+#endif
+
+/* Returns a message corresponding to an error code, ERRCODE, returned
+   from either regcomp or regexec.   We don't use PREG here.  */
+
+size_t
+regerror(int errcode, const regex_t *__restrict preg,
+        char *__restrict errbuf, size_t errbuf_size)
+{
+  const char *msg;
+  size_t msg_size;
+
+  if (BE (errcode < 0
+         || errcode >= (int) (sizeof (__re_error_msgid_idx)
+                              / sizeof (__re_error_msgid_idx[0])), 0))
+    /* Only error codes returned by the rest of the code should be passed
+       to this routine.  If we are given anything else, or if other regex
+       code generates an invalid error code, then the program has a bug.
+       Dump core so we can fix it.  */
+    abort ();
+
+  msg = gettext (__re_error_msgid + __re_error_msgid_idx[errcode]);
+
+  msg_size = strlen (msg) + 1; /* Includes the null.  */
+
+  if (BE (errbuf_size != 0, 1))
+    {
+      if (BE (msg_size > errbuf_size, 0))
+       {
+         memcpy (errbuf, msg, errbuf_size - 1);
+         errbuf[errbuf_size - 1] = 0;
+       }
+      else
+       memcpy (errbuf, msg, msg_size);
+    }
+
+  return msg_size;
+}
+#ifdef _LIBC
+weak_alias (__regerror, regerror)
+#endif
+
+
+#ifdef RE_ENABLE_I18N
+/* This static array is used for the map to single-byte characters when
+   UTF-8 is used.  Otherwise we would allocate memory just to initialize
+   it the same all the time.  UTF-8 is the preferred encoding so this is
+   a worthwhile optimization.  */
+#if __GNUC__ >= 3
+static const bitset_t utf8_sb_map = {
+  /* Set the first 128 bits.  */
+  [0 ... 0x80 / BITSET_WORD_BITS - 1] = BITSET_WORD_MAX
+};
+#else /* ! (__GNUC__ >= 3) */
+static bitset_t utf8_sb_map;
+#endif /* __GNUC__ >= 3 */
+#endif /* RE_ENABLE_I18N */
+
+
+static void
+free_dfa_content (re_dfa_t *dfa)
+{
+  int i, j;
+
+  if (dfa->nodes)
+    for (i = 0; i < dfa->nodes_len; ++i)
+      free_token (dfa->nodes + i);
+  re_free (dfa->nexts);
+  for (i = 0; i < dfa->nodes_len; ++i)
+    {
+      if (dfa->eclosures != NULL)
+       re_node_set_free (dfa->eclosures + i);
+      if (dfa->inveclosures != NULL)
+       re_node_set_free (dfa->inveclosures + i);
+      if (dfa->edests != NULL)
+       re_node_set_free (dfa->edests + i);
+    }
+  re_free (dfa->edests);
+  re_free (dfa->eclosures);
+  re_free (dfa->inveclosures);
+  re_free (dfa->nodes);
+
+  if (dfa->state_table)
+    for (i = 0; i <= dfa->state_hash_mask; ++i)
+      {
+       struct re_state_table_entry *entry = dfa->state_table + i;
+       for (j = 0; j < entry->num; ++j)
+         {
+           re_dfastate_t *state = entry->array[j];
+           free_state (state);
+         }
+       re_free (entry->array);
+      }
+  re_free (dfa->state_table);
+#ifdef RE_ENABLE_I18N
+  if (dfa->sb_char != utf8_sb_map)
+    re_free (dfa->sb_char);
+#endif
+  re_free (dfa->subexp_map);
+#ifdef DEBUG
+  re_free (dfa->re_str);
+#endif
+
+  re_free (dfa);
+}
+
+
+/* Free dynamically allocated space used by PREG.  */
+
+void
+regfree (regex_t *preg)
+{
+  re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+  if (BE (dfa != NULL, 1))
+    free_dfa_content (dfa);
+  preg->buffer = NULL;
+  preg->allocated = 0;
+
+  re_free (preg->fastmap);
+  preg->fastmap = NULL;
+
+  re_free (preg->translate);
+  preg->translate = NULL;
+}
+#ifdef _LIBC
+weak_alias (__regfree, regfree)
+#endif
+\f
+/* Entry points compatible with 4.2 BSD regex library.  We don't define
+   them unless specifically requested.  */
+
+#if defined _REGEX_RE_COMP || defined _LIBC
+
+/* BSD has one and only one pattern buffer.  */
+static struct re_pattern_buffer re_comp_buf;
+
+char *
+# ifdef _LIBC
+/* Make these definitions weak in libc, so POSIX programs can redefine
+   these names if they don't use our functions, and still use
+   regcomp/regexec above without link errors.  */
+weak_function
+# endif
+re_comp (s)
+     const char *s;
+{
+  reg_errcode_t ret;
+  char *fastmap;
+
+  if (!s)
+    {
+      if (!re_comp_buf.buffer)
+       return gettext ("No previous regular expression");
+      return 0;
+    }
+
+  if (re_comp_buf.buffer)
+    {
+      fastmap = re_comp_buf.fastmap;
+      re_comp_buf.fastmap = NULL;
+      __regfree (&re_comp_buf);
+      memset (&re_comp_buf, '\0', sizeof (re_comp_buf));
+      re_comp_buf.fastmap = fastmap;
+    }
+
+  if (re_comp_buf.fastmap == NULL)
+    {
+      re_comp_buf.fastmap = (char *) malloc (SBC_MAX);
+      if (re_comp_buf.fastmap == NULL)
+       return (char *) gettext (__re_error_msgid
+                                + __re_error_msgid_idx[(int) REG_ESPACE]);
+    }
+
+  /* Since `re_exec' always passes NULL for the `regs' argument, we
+     don't need to initialize the pattern buffer fields which affect it.  */
+
+  /* Match anchors at newlines.  */
+  re_comp_buf.newline_anchor = 1;
+
+  ret = re_compile_internal (&re_comp_buf, s, strlen (s), re_syntax_options);
+
+  if (!ret)
+    return NULL;
+
+  /* Yes, we're discarding `const' here if !HAVE_LIBINTL.  */
+  return (char *) gettext (__re_error_msgid + __re_error_msgid_idx[(int) ret]);
+}
+
+#ifdef _LIBC
+libc_freeres_fn (free_mem)
+{
+  __regfree (&re_comp_buf);
+}
+#endif
+
+#endif /* _REGEX_RE_COMP */
+\f
+/* Internal entry point.
+   Compile the regular expression PATTERN, whose length is LENGTH.
+   SYNTAX indicate regular expression's syntax.  */
+
+static reg_errcode_t
+re_compile_internal (regex_t *preg, const char * pattern, size_t length,
+                    reg_syntax_t syntax)
+{
+  reg_errcode_t err = REG_NOERROR;
+  re_dfa_t *dfa;
+  re_string_t regexp;
+
+  /* Initialize the pattern buffer.  */
+  preg->fastmap_accurate = 0;
+  preg->syntax = syntax;
+  preg->not_bol = preg->not_eol = 0;
+  preg->used = 0;
+  preg->re_nsub = 0;
+  preg->can_be_null = 0;
+  preg->regs_allocated = REGS_UNALLOCATED;
+
+  /* Initialize the dfa.  */
+  dfa = (re_dfa_t *) preg->buffer;
+  if (BE (preg->allocated < sizeof (re_dfa_t), 0))
+    {
+      /* If zero allocated, but buffer is non-null, try to realloc
+        enough space.  This loses if buffer's address is bogus, but
+        that is the user's responsibility.  If ->buffer is NULL this
+        is a simple allocation.  */
+      dfa = re_realloc (preg->buffer, re_dfa_t, 1);
+      if (dfa == NULL)
+       return REG_ESPACE;
+      preg->allocated = sizeof (re_dfa_t);
+      preg->buffer = (unsigned char *) dfa;
+    }
+  preg->used = sizeof (re_dfa_t);
+
+  err = init_dfa (dfa, length);
+  if (BE (err != REG_NOERROR, 0))
+    {
+      free_dfa_content (dfa);
+      preg->buffer = NULL;
+      preg->allocated = 0;
+      return err;
+    }
+#ifdef DEBUG
+  /* Note: length+1 will not overflow since it is checked in init_dfa.  */
+  dfa->re_str = re_malloc (char, length + 1);
+  strncpy (dfa->re_str, pattern, length + 1);
+#endif
+
+  __libc_lock_init (dfa->lock);
+
+  err = re_string_construct (&regexp, pattern, length, preg->translate,
+                            syntax & RE_ICASE, dfa);
+  if (BE (err != REG_NOERROR, 0))
+    {
+    re_compile_internal_free_return:
+      free_workarea_compile (preg);
+      re_string_destruct (&regexp);
+      free_dfa_content (dfa);
+      preg->buffer = NULL;
+      preg->allocated = 0;
+      return err;
+    }
+
+  /* Parse the regular expression, and build a structure tree.  */
+  preg->re_nsub = 0;
+  dfa->str_tree = parse (&regexp, preg, syntax, &err);
+  if (BE (dfa->str_tree == NULL, 0))
+    goto re_compile_internal_free_return;
+
+  /* Analyze the tree and create the nfa.  */
+  err = analyze (preg);
+  if (BE (err != REG_NOERROR, 0))
+    goto re_compile_internal_free_return;
+
+#ifdef RE_ENABLE_I18N
+  /* If possible, do searching in single byte encoding to speed things up.  */
+  if (dfa->is_utf8 && !(syntax & RE_ICASE) && preg->translate == NULL)
+    optimize_utf8 (dfa);
+#endif
+
+  /* Then create the initial state of the dfa.  */
+  err = create_initial_state (dfa);
+
+  /* Release work areas.  */
+  free_workarea_compile (preg);
+  re_string_destruct (&regexp);
+
+  if (BE (err != REG_NOERROR, 0))
+    {
+      free_dfa_content (dfa);
+      preg->buffer = NULL;
+      preg->allocated = 0;
+    }
+
+  return err;
+}
+
+/* Initialize DFA.  We use the length of the regular expression PAT_LEN
+   as the initial length of some arrays.  */
+
+static reg_errcode_t
+init_dfa (re_dfa_t *dfa, size_t pat_len)
+{
+  unsigned int table_size;
+#ifndef _LIBC
+  char *codeset_name;
+#endif
+
+  memset (dfa, '\0', sizeof (re_dfa_t));
+
+  /* Force allocation of str_tree_storage the first time.  */
+  dfa->str_tree_storage_idx = BIN_TREE_STORAGE_SIZE;
+
+  /* Avoid overflows.  */
+  if (pat_len == SIZE_MAX)
+    return REG_ESPACE;
+
+  dfa->nodes_alloc = pat_len + 1;
+  dfa->nodes = re_malloc (re_token_t, dfa->nodes_alloc);
+
+  /*  table_size = 2 ^ ceil(log pat_len) */
+  for (table_size = 1; ; table_size <<= 1)
+    if (table_size > pat_len)
+      break;
+
+  dfa->state_table = calloc (sizeof (struct re_state_table_entry), table_size);
+  dfa->state_hash_mask = table_size - 1;
+
+  dfa->mb_cur_max = MB_CUR_MAX;
+#ifdef _LIBC
+  if (dfa->mb_cur_max == 6
+      && strcmp (_NL_CURRENT (LC_CTYPE, _NL_CTYPE_CODESET_NAME), "UTF-8") == 0)
+    dfa->is_utf8 = 1;
+  dfa->map_notascii = (_NL_CURRENT_WORD (LC_CTYPE, _NL_CTYPE_MAP_TO_NONASCII)
+                      != 0);
+#else
+# ifdef HAVE_LANGINFO_CODESET
+  codeset_name = nl_langinfo (CODESET);
+# else
+  codeset_name = getenv ("LC_ALL");
+  if (codeset_name == NULL || codeset_name[0] == '\0')
+    codeset_name = getenv ("LC_CTYPE");
+  if (codeset_name == NULL || codeset_name[0] == '\0')
+    codeset_name = getenv ("LANG");
+  if (codeset_name == NULL)
+    codeset_name = "";
+  else if (strchr (codeset_name, '.') !=  NULL)
+    codeset_name = strchr (codeset_name, '.') + 1;
+# endif
+
+  /* strcasecmp isn't a standard interface. brute force check */
+#if 0
+  if (strcasecmp (codeset_name, "UTF-8") == 0
+      || strcasecmp (codeset_name, "UTF8") == 0)
+    dfa->is_utf8 = 1;
+#else
+  if (   (codeset_name[0] == 'U' || codeset_name[0] == 'u')
+      && (codeset_name[1] == 'T' || codeset_name[1] == 't')
+      && (codeset_name[2] == 'F' || codeset_name[2] == 'f')
+      && (codeset_name[3] == '-'
+          ? codeset_name[4] == '8' && codeset_name[5] == '\0'
+          : codeset_name[3] == '8' && codeset_name[4] == '\0'))
+    dfa->is_utf8 = 1;
+#endif
+
+  /* We check exhaustively in the loop below if this charset is a
+     superset of ASCII.  */
+  dfa->map_notascii = 0;
+#endif
+
+#ifdef RE_ENABLE_I18N
+  if (dfa->mb_cur_max > 1)
+    {
+      if (dfa->is_utf8)
+        {
+#if !defined(__GNUC__) || __GNUC__ < 3
+         static short utf8_sb_map_inited = 0;
+
+         if (! utf8_sb_map_inited)
+           {
+               int i;
+
+               utf8_sb_map_inited = 0;
+               for (i = 0; i <= 0x80 / BITSET_WORD_BITS - 1; i++)
+                 utf8_sb_map[i] = BITSET_WORD_MAX;
+           }
+#endif
+         dfa->sb_char = (re_bitset_ptr_t) utf8_sb_map;
+       }
+      else
+       {
+         int i, j, ch;
+
+         dfa->sb_char = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1);
+         if (BE (dfa->sb_char == NULL, 0))
+           return REG_ESPACE;
+
+         /* Set the bits corresponding to single byte chars.  */
+         for (i = 0, ch = 0; i < BITSET_WORDS; ++i)
+           for (j = 0; j < BITSET_WORD_BITS; ++j, ++ch)
+             {
+               wint_t wch = __btowc (ch);
+               if (wch != WEOF)
+                 dfa->sb_char[i] |= (bitset_word_t) 1 << j;
+# ifndef _LIBC
+               if (isascii (ch) && wch != ch)
+                 dfa->map_notascii = 1;
+# endif
+             }
+       }
+    }
+#endif
+
+  if (BE (dfa->nodes == NULL || dfa->state_table == NULL, 0))
+    return REG_ESPACE;
+  return REG_NOERROR;
+}
+
+/* Initialize WORD_CHAR table, which indicate which character is
+   "word".  In this case "word" means that it is the word construction
+   character used by some operators like "\<", "\>", etc.  */
+
+static void
+internal_function
+init_word_char (re_dfa_t *dfa)
+{
+  int i, j, ch;
+  dfa->word_ops_used = 1;
+  for (i = 0, ch = 0; i < BITSET_WORDS; ++i)
+    for (j = 0; j < BITSET_WORD_BITS; ++j, ++ch)
+      if (isalnum (ch) || ch == '_')
+       dfa->word_char[i] |= (bitset_word_t) 1 << j;
+}
+
+/* Free the work area which are only used while compiling.  */
+
+static void
+free_workarea_compile (regex_t *preg)
+{
+  re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+  bin_tree_storage_t *storage, *next;
+  for (storage = dfa->str_tree_storage; storage; storage = next)
+    {
+      next = storage->next;
+      re_free (storage);
+    }
+  dfa->str_tree_storage = NULL;
+  dfa->str_tree_storage_idx = BIN_TREE_STORAGE_SIZE;
+  dfa->str_tree = NULL;
+  re_free (dfa->org_indices);
+  dfa->org_indices = NULL;
+}
+
+/* Create initial states for all contexts.  */
+
+static reg_errcode_t
+create_initial_state (re_dfa_t *dfa)
+{
+  int first, i;
+  reg_errcode_t err;
+  re_node_set init_nodes;
+
+  /* Initial states have the epsilon closure of the node which is
+     the first node of the regular expression.  */
+  first = dfa->str_tree->first->node_idx;
+  dfa->init_node = first;
+  err = re_node_set_init_copy (&init_nodes, dfa->eclosures + first);
+  if (BE (err != REG_NOERROR, 0))
+    return err;
+
+  /* The back-references which are in initial states can epsilon transit,
+     since in this case all of the subexpressions can be null.
+     Then we add epsilon closures of the nodes which are the next nodes of
+     the back-references.  */
+  if (dfa->nbackref > 0)
+    for (i = 0; i < init_nodes.nelem; ++i)
+      {
+       int node_idx = init_nodes.elems[i];
+       re_token_type_t type = dfa->nodes[node_idx].type;
+
+       int clexp_idx;
+       if (type != OP_BACK_REF)
+         continue;
+       for (clexp_idx = 0; clexp_idx < init_nodes.nelem; ++clexp_idx)
+         {
+           re_token_t *clexp_node;
+           clexp_node = dfa->nodes + init_nodes.elems[clexp_idx];
+           if (clexp_node->type == OP_CLOSE_SUBEXP
+               && clexp_node->opr.idx == dfa->nodes[node_idx].opr.idx)
+             break;
+         }
+       if (clexp_idx == init_nodes.nelem)
+         continue;
+
+       if (type == OP_BACK_REF)
+         {
+           int dest_idx = dfa->edests[node_idx].elems[0];
+           if (!re_node_set_contains (&init_nodes, dest_idx))
+             {
+               reg_errcode_t err = re_node_set_merge (&init_nodes,
+                                                      dfa->eclosures
+                                                      + dest_idx);
+               if (err != REG_NOERROR)
+                 return err;
+               i = 0;
+             }
+         }
+      }
+
+  /* It must be the first time to invoke acquire_state.  */
+  dfa->init_state = re_acquire_state_context (&err, dfa, &init_nodes, 0);
+  /* We don't check ERR here, since the initial state must not be NULL.  */
+  if (BE (dfa->init_state == NULL, 0))
+    return err;
+  if (dfa->init_state->has_constraint)
+    {
+      dfa->init_state_word = re_acquire_state_context (&err, dfa, &init_nodes,
+                                                      CONTEXT_WORD);
+      dfa->init_state_nl = re_acquire_state_context (&err, dfa, &init_nodes,
+                                                    CONTEXT_NEWLINE);
+      dfa->init_state_begbuf = re_acquire_state_context (&err, dfa,
+                                                        &init_nodes,
+                                                        CONTEXT_NEWLINE
+                                                        | CONTEXT_BEGBUF);
+      if (BE (dfa->init_state_word == NULL || dfa->init_state_nl == NULL
+             || dfa->init_state_begbuf == NULL, 0))
+       return err;
+    }
+  else
+    dfa->init_state_word = dfa->init_state_nl
+      = dfa->init_state_begbuf = dfa->init_state;
+
+  re_node_set_free (&init_nodes);
+  return REG_NOERROR;
+}
+\f
+#ifdef RE_ENABLE_I18N
+/* If it is possible to do searching in single byte encoding instead of UTF-8
+   to speed things up, set dfa->mb_cur_max to 1, clear is_utf8 and change
+   DFA nodes where needed.  */
+
+static void
+optimize_utf8 (re_dfa_t *dfa)
+{
+  int node, i, mb_chars = 0, has_period = 0;
+
+  for (node = 0; node < dfa->nodes_len; ++node)
+    switch (dfa->nodes[node].type)
+      {
+      case CHARACTER:
+       if (dfa->nodes[node].opr.c >= 0x80)
+         mb_chars = 1;
+       break;
+      case ANCHOR:
+       switch (dfa->nodes[node].opr.ctx_type)
+         {
+         case LINE_FIRST:
+         case LINE_LAST:
+         case BUF_FIRST:
+         case BUF_LAST:
+           break;
+         default:
+           /* Word anchors etc. cannot be handled.  It's okay to test
+              opr.ctx_type since constraints (for all DFA nodes) are
+              created by ORing one or more opr.ctx_type values.  */
+           return;
+         }
+       break;
+      case OP_PERIOD:
+       has_period = 1;
+       break;
+      case OP_BACK_REF:
+      case OP_ALT:
+      case END_OF_RE:
+      case OP_DUP_ASTERISK:
+      case OP_OPEN_SUBEXP:
+      case OP_CLOSE_SUBEXP:
+       break;
+      case COMPLEX_BRACKET:
+       return;
+      case SIMPLE_BRACKET:
+       /* Just double check.  The non-ASCII range starts at 0x80.  */
+       assert (0x80 % BITSET_WORD_BITS == 0);
+       for (i = 0x80 / BITSET_WORD_BITS; i < BITSET_WORDS; ++i)
+         if (dfa->nodes[node].opr.sbcset[i])
+           return;
+       break;
+      default:
+       abort ();
+      }
+
+  if (mb_chars || has_period)
+    for (node = 0; node < dfa->nodes_len; ++node)
+      {
+       if (dfa->nodes[node].type == CHARACTER
+           && dfa->nodes[node].opr.c >= 0x80)
+         dfa->nodes[node].mb_partial = 0;
+       else if (dfa->nodes[node].type == OP_PERIOD)
+         dfa->nodes[node].type = OP_UTF8_PERIOD;
+      }
+
+  /* The search can be in single byte locale.  */
+  dfa->mb_cur_max = 1;
+  dfa->is_utf8 = 0;
+  dfa->has_mb_node = dfa->nbackref > 0 || has_period;
+}
+#endif
+\f
+/* Analyze the structure tree, and calculate "first", "next", "edest",
+   "eclosure", and "inveclosure".  */
+
+static reg_errcode_t
+analyze (regex_t *preg)
+{
+  re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+  reg_errcode_t ret;
+
+  /* Allocate arrays.  */
+  dfa->nexts = re_malloc (int, dfa->nodes_alloc);
+  dfa->org_indices = re_malloc (int, dfa->nodes_alloc);
+  dfa->edests = re_malloc (re_node_set, dfa->nodes_alloc);
+  dfa->eclosures = re_malloc (re_node_set, dfa->nodes_alloc);
+  if (BE (dfa->nexts == NULL || dfa->org_indices == NULL || dfa->edests == NULL
+         || dfa->eclosures == NULL, 0))
+    return REG_ESPACE;
+
+  dfa->subexp_map = re_malloc (int, preg->re_nsub);
+  if (dfa->subexp_map != NULL)
+    {
+      int i;
+      for (i = 0; i < preg->re_nsub; i++)
+       dfa->subexp_map[i] = i;
+      preorder (dfa->str_tree, optimize_subexps, dfa);
+      for (i = 0; i < preg->re_nsub; i++)
+       if (dfa->subexp_map[i] != i)
+         break;
+      if (i == preg->re_nsub)
+       {
+         free (dfa->subexp_map);
+         dfa->subexp_map = NULL;
+       }
+    }
+
+  ret = postorder (dfa->str_tree, lower_subexps, preg);
+  if (BE (ret != REG_NOERROR, 0))
+    return ret;
+  ret = postorder (dfa->str_tree, calc_first, dfa);
+  if (BE (ret != REG_NOERROR, 0))
+    return ret;
+  preorder (dfa->str_tree, calc_next, dfa);
+  ret = preorder (dfa->str_tree, link_nfa_nodes, dfa);
+  if (BE (ret != REG_NOERROR, 0))
+    return ret;
+  ret = calc_eclosure (dfa);
+  if (BE (ret != REG_NOERROR, 0))
+    return ret;
+
+  /* We only need this during the prune_impossible_nodes pass in regexec.c;
+     skip it if p_i_n will not run, as calc_inveclosure can be quadratic.  */
+  if ((!preg->no_sub && preg->re_nsub > 0 && dfa->has_plural_match)
+      || dfa->nbackref)
+    {
+      dfa->inveclosures = re_malloc (re_node_set, dfa->nodes_len);
+      if (BE (dfa->inveclosures == NULL, 0))
+       return REG_ESPACE;
+      ret = calc_inveclosure (dfa);
+    }
+
+  return ret;
+}
+
+/* Our parse trees are very unbalanced, so we cannot use a stack to
+   implement parse tree visits.  Instead, we use parent pointers and
+   some hairy code in these two functions.  */
+static reg_errcode_t
+postorder (bin_tree_t *root, reg_errcode_t (fn (void *, bin_tree_t *)),
+          void *extra)
+{
+  bin_tree_t *node, *prev;
+
+  for (node = root; ; )
+    {
+      /* Descend down the tree, preferably to the left (or to the right
+        if that's the only child).  */
+      while (node->left || node->right)
+       if (node->left)
+         node = node->left;
+       else
+         node = node->right;
+
+      do
+       {
+         reg_errcode_t err = fn (extra, node);
+         if (BE (err != REG_NOERROR, 0))
+           return err;
+         if (node->parent == NULL)
+           return REG_NOERROR;
+         prev = node;
+         node = node->parent;
+       }
+      /* Go up while we have a node that is reached from the right.  */
+      while (node->right == prev || node->right == NULL);
+      node = node->right;
+    }
+}
+
+static reg_errcode_t
+preorder (bin_tree_t *root, reg_errcode_t (fn (void *, bin_tree_t *)),
+         void *extra)
+{
+  bin_tree_t *node;
+
+  for (node = root; ; )
+    {
+      reg_errcode_t err = fn (extra, node);
+      if (BE (err != REG_NOERROR, 0))
+       return err;
+
+      /* Go to the left node, or up and to the right.  */
+      if (node->left)
+       node = node->left;
+      else
+       {
+         bin_tree_t *prev = NULL;
+         while (node->right == prev || node->right == NULL)
+           {
+             prev = node;
+             node = node->parent;
+             if (!node)
+               return REG_NOERROR;
+           }
+         node = node->right;
+       }
+    }
+}
+
+/* Optimization pass: if a SUBEXP is entirely contained, strip it and tell
+   re_search_internal to map the inner one's opr.idx to this one's.  Adjust
+   backreferences as well.  Requires a preorder visit.  */
+static reg_errcode_t
+optimize_subexps (void *extra, bin_tree_t *node)
+{
+  re_dfa_t *dfa = (re_dfa_t *) extra;
+
+  if (node->token.type == OP_BACK_REF && dfa->subexp_map)
+    {
+      int idx = node->token.opr.idx;
+      node->token.opr.idx = dfa->subexp_map[idx];
+      dfa->used_bkref_map |= 1 << node->token.opr.idx;
+    }
+
+  else if (node->token.type == SUBEXP
+          && node->left && node->left->token.type == SUBEXP)
+    {
+      int other_idx = node->left->token.opr.idx;
+
+      node->left = node->left->left;
+      if (node->left)
+       node->left->parent = node;
+
+      dfa->subexp_map[other_idx] = dfa->subexp_map[node->token.opr.idx];
+      if (other_idx < BITSET_WORD_BITS)
+         dfa->used_bkref_map &= ~((bitset_word_t) 1 << other_idx);
+    }
+
+  return REG_NOERROR;
+}
+
+/* Lowering pass: Turn each SUBEXP node into the appropriate concatenation
+   of OP_OPEN_SUBEXP, the body of the SUBEXP (if any) and OP_CLOSE_SUBEXP.  */
+static reg_errcode_t
+lower_subexps (void *extra, bin_tree_t *node)
+{
+  regex_t *preg = (regex_t *) extra;
+  reg_errcode_t err = REG_NOERROR;
+
+  if (node->left && node->left->token.type == SUBEXP)
+    {
+      node->left = lower_subexp (&err, preg, node->left);
+      if (node->left)
+       node->left->parent = node;
+    }
+  if (node->right && node->right->token.type == SUBEXP)
+    {
+      node->right = lower_subexp (&err, preg, node->right);
+      if (node->right)
+       node->right->parent = node;
+    }
+
+  return err;
+}
+
+static bin_tree_t *
+lower_subexp (reg_errcode_t *err, regex_t *preg, bin_tree_t *node)
+{
+  re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+  bin_tree_t *body = node->left;
+  bin_tree_t *op, *cls, *tree1, *tree;
+
+  if (preg->no_sub
+      /* We do not optimize empty subexpressions, because otherwise we may
+        have bad CONCAT nodes with NULL children.  This is obviously not
+        very common, so we do not lose much.  An example that triggers
+        this case is the sed "script" /\(\)/x.  */
+      && node->left != NULL
+      && (node->token.opr.idx >= BITSET_WORD_BITS
+         || !(dfa->used_bkref_map
+              & ((bitset_word_t) 1 << node->token.opr.idx))))
+    return node->left;
+
+  /* Convert the SUBEXP node to the concatenation of an
+     OP_OPEN_SUBEXP, the contents, and an OP_CLOSE_SUBEXP.  */
+  op = create_tree (dfa, NULL, NULL, OP_OPEN_SUBEXP);
+  cls = create_tree (dfa, NULL, NULL, OP_CLOSE_SUBEXP);
+  tree1 = body ? create_tree (dfa, body, cls, CONCAT) : cls;
+  tree = create_tree (dfa, op, tree1, CONCAT);
+  if (BE (tree == NULL || tree1 == NULL || op == NULL || cls == NULL, 0))
+    {
+      *err = REG_ESPACE;
+      return NULL;
+    }
+
+  op->token.opr.idx = cls->token.opr.idx = node->token.opr.idx;
+  op->token.opt_subexp = cls->token.opt_subexp = node->token.opt_subexp;
+  return tree;
+}
+
+/* Pass 1 in building the NFA: compute FIRST and create unlinked automaton
+   nodes.  Requires a postorder visit.  */
+static reg_errcode_t
+calc_first (void *extra, bin_tree_t *node)
+{
+  re_dfa_t *dfa = (re_dfa_t *) extra;
+  if (node->token.type == CONCAT)
+    {
+      node->first = node->left->first;
+      node->node_idx = node->left->node_idx;
+    }
+  else
+    {
+      node->first = node;
+      node->node_idx = re_dfa_add_node (dfa, node->token);
+      if (BE (node->node_idx == -1, 0))
+       return REG_ESPACE;
+      if (node->token.type == ANCHOR)
+       dfa->nodes[node->node_idx].constraint = node->token.opr.ctx_type;
+    }
+  return REG_NOERROR;
+}
+
+/* Pass 2: compute NEXT on the tree.  Preorder visit.  */
+static reg_errcode_t
+calc_next (void *extra, bin_tree_t *node)
+{
+  switch (node->token.type)
+    {
+    case OP_DUP_ASTERISK:
+      node->left->next = node;
+      break;
+    case CONCAT:
+      node->left->next = node->right->first;
+      node->right->next = node->next;
+      break;
+    default:
+      if (node->left)
+       node->left->next = node->next;
+      if (node->right)
+       node->right->next = node->next;
+      break;
+    }
+  return REG_NOERROR;
+}
+
+/* Pass 3: link all DFA nodes to their NEXT node (any order will do).  */
+static reg_errcode_t
+link_nfa_nodes (void *extra, bin_tree_t *node)
+{
+  re_dfa_t *dfa = (re_dfa_t *) extra;
+  int idx = node->node_idx;
+  reg_errcode_t err = REG_NOERROR;
+
+  switch (node->token.type)
+    {
+    case CONCAT:
+      break;
+
+    case END_OF_RE:
+      assert (node->next == NULL);
+      break;
+
+    case OP_DUP_ASTERISK:
+    case OP_ALT:
+      {
+       int left, right;
+       dfa->has_plural_match = 1;
+       if (node->left != NULL)
+         left = node->left->first->node_idx;
+       else
+         left = node->next->node_idx;
+       if (node->right != NULL)
+         right = node->right->first->node_idx;
+       else
+         right = node->next->node_idx;
+       assert (left > -1);
+       assert (right > -1);
+       err = re_node_set_init_2 (dfa->edests + idx, left, right);
+      }
+      break;
+
+    case ANCHOR:
+    case OP_OPEN_SUBEXP:
+    case OP_CLOSE_SUBEXP:
+      err = re_node_set_init_1 (dfa->edests + idx, node->next->node_idx);
+      break;
+
+    case OP_BACK_REF:
+      dfa->nexts[idx] = node->next->node_idx;
+      if (node->token.type == OP_BACK_REF)
+       err = re_node_set_init_1 (dfa->edests + idx, dfa->nexts[idx]);
+      break;
+
+    default:
+      assert (!IS_EPSILON_NODE (node->token.type));
+      dfa->nexts[idx] = node->next->node_idx;
+      break;
+    }
+
+  return err;
+}
+
+/* Duplicate the epsilon closure of the node ROOT_NODE.
+   Note that duplicated nodes have constraint INIT_CONSTRAINT in addition
+   to their own constraint.  */
+
+static reg_errcode_t
+internal_function
+duplicate_node_closure (re_dfa_t *dfa, int top_org_node, int top_clone_node,
+                       int root_node, unsigned int init_constraint)
+{
+  int org_node, clone_node, ret;
+  unsigned int constraint = init_constraint;
+  for (org_node = top_org_node, clone_node = top_clone_node;;)
+    {
+      int org_dest, clone_dest;
+      if (dfa->nodes[org_node].type == OP_BACK_REF)
+       {
+         /* If the back reference epsilon-transit, its destination must
+            also have the constraint.  Then duplicate the epsilon closure
+            of the destination of the back reference, and store it in
+            edests of the back reference.  */
+         org_dest = dfa->nexts[org_node];
+         re_node_set_empty (dfa->edests + clone_node);
+         clone_dest = duplicate_node (dfa, org_dest, constraint);
+         if (BE (clone_dest == -1, 0))
+           return REG_ESPACE;
+         dfa->nexts[clone_node] = dfa->nexts[org_node];
+         ret = re_node_set_insert (dfa->edests + clone_node, clone_dest);
+         if (BE (ret < 0, 0))
+           return REG_ESPACE;
+       }
+      else if (dfa->edests[org_node].nelem == 0)
+       {
+         /* In case of the node can't epsilon-transit, don't duplicate the
+            destination and store the original destination as the
+            destination of the node.  */
+         dfa->nexts[clone_node] = dfa->nexts[org_node];
+         break;
+       }
+      else if (dfa->edests[org_node].nelem == 1)
+       {
+         /* In case of the node can epsilon-transit, and it has only one
+            destination.  */
+         org_dest = dfa->edests[org_node].elems[0];
+         re_node_set_empty (dfa->edests + clone_node);
+         /* If the node is root_node itself, it means the epsilon clsoure
+            has a loop.   Then tie it to the destination of the root_node.  */
+         if (org_node == root_node && clone_node != org_node)
+           {
+             ret = re_node_set_insert (dfa->edests + clone_node, org_dest);
+             if (BE (ret < 0, 0))
+               return REG_ESPACE;
+             break;
+           }
+         /* In case of the node has another constraint, add it.  */
+         constraint |= dfa->nodes[org_node].constraint;
+         clone_dest = duplicate_node (dfa, org_dest, constraint);
+         if (BE (clone_dest == -1, 0))
+           return REG_ESPACE;
+         ret = re_node_set_insert (dfa->edests + clone_node, clone_dest);
+         if (BE (ret < 0, 0))
+           return REG_ESPACE;
+       }
+      else /* dfa->edests[org_node].nelem == 2 */
+       {
+         /* In case of the node can epsilon-transit, and it has two
+            destinations. In the bin_tree_t and DFA, that's '|' and '*'.   */
+         org_dest = dfa->edests[org_node].elems[0];
+         re_node_set_empty (dfa->edests + clone_node);
+         /* Search for a duplicated node which satisfies the constraint.  */
+         clone_dest = search_duplicated_node (dfa, org_dest, constraint);
+         if (clone_dest == -1)
+           {
+             /* There is no such duplicated node, create a new one.  */
+             reg_errcode_t err;
+             clone_dest = duplicate_node (dfa, org_dest, constraint);
+             if (BE (clone_dest == -1, 0))
+               return REG_ESPACE;
+             ret = re_node_set_insert (dfa->edests + clone_node, clone_dest);
+             if (BE (ret < 0, 0))
+               return REG_ESPACE;
+             err = duplicate_node_closure (dfa, org_dest, clone_dest,
+                                           root_node, constraint);
+             if (BE (err != REG_NOERROR, 0))
+               return err;
+           }
+         else
+           {
+             /* There is a duplicated node which satisfies the constraint,
+                use it to avoid infinite loop.  */
+             ret = re_node_set_insert (dfa->edests + clone_node, clone_dest);
+             if (BE (ret < 0, 0))
+               return REG_ESPACE;
+           }
+
+         org_dest = dfa->edests[org_node].elems[1];
+         clone_dest = duplicate_node (dfa, org_dest, constraint);
+         if (BE (clone_dest == -1, 0))
+           return REG_ESPACE;
+         ret = re_node_set_insert (dfa->edests + clone_node, clone_dest);
+         if (BE (ret < 0, 0))
+           return REG_ESPACE;
+       }
+      org_node = org_dest;
+      clone_node = clone_dest;
+    }
+  return REG_NOERROR;
+}
+
+/* Search for a node which is duplicated from the node ORG_NODE, and
+   satisfies the constraint CONSTRAINT.  */
+
+static int
+search_duplicated_node (const re_dfa_t *dfa, int org_node,
+                       unsigned int constraint)
+{
+  int idx;
+  for (idx = dfa->nodes_len - 1; dfa->nodes[idx].duplicated && idx > 0; --idx)
+    {
+      if (org_node == dfa->org_indices[idx]
+         && constraint == dfa->nodes[idx].constraint)
+       return idx; /* Found.  */
+    }
+  return -1; /* Not found.  */
+}
+
+/* Duplicate the node whose index is ORG_IDX and set the constraint CONSTRAINT.
+   Return the index of the new node, or -1 if insufficient storage is
+   available.  */
+
+static int
+duplicate_node (re_dfa_t *dfa, int org_idx, unsigned int constraint)
+{
+  int dup_idx = re_dfa_add_node (dfa, dfa->nodes[org_idx]);
+  if (BE (dup_idx != -1, 1))
+    {
+      dfa->nodes[dup_idx].constraint = constraint;
+      dfa->nodes[dup_idx].constraint |= dfa->nodes[org_idx].constraint;
+      dfa->nodes[dup_idx].duplicated = 1;
+
+      /* Store the index of the original node.  */
+      dfa->org_indices[dup_idx] = org_idx;
+    }
+  return dup_idx;
+}
+
+static reg_errcode_t
+calc_inveclosure (re_dfa_t *dfa)
+{
+  int src, idx, ret;
+  for (idx = 0; idx < dfa->nodes_len; ++idx)
+    re_node_set_init_empty (dfa->inveclosures + idx);
+
+  for (src = 0; src < dfa->nodes_len; ++src)
+    {
+      int *elems = dfa->eclosures[src].elems;
+      for (idx = 0; idx < dfa->eclosures[src].nelem; ++idx)
+       {
+         ret = re_node_set_insert_last (dfa->inveclosures + elems[idx], src);
+         if (BE (ret == -1, 0))
+           return REG_ESPACE;
+       }
+    }
+
+  return REG_NOERROR;
+}
+
+/* Calculate "eclosure" for all the node in DFA.  */
+
+static reg_errcode_t
+calc_eclosure (re_dfa_t *dfa)
+{
+  int node_idx, incomplete;
+#ifdef DEBUG
+  assert (dfa->nodes_len > 0);
+#endif
+  incomplete = 0;
+  /* For each nodes, calculate epsilon closure.  */
+  for (node_idx = 0; ; ++node_idx)
+    {
+      reg_errcode_t err;
+      re_node_set eclosure_elem;
+      if (node_idx == dfa->nodes_len)
+       {
+         if (!incomplete)
+           break;
+         incomplete = 0;
+         node_idx = 0;
+       }
+
+#ifdef DEBUG
+      assert (dfa->eclosures[node_idx].nelem != -1);
+#endif
+
+      /* If we have already calculated, skip it.  */
+      if (dfa->eclosures[node_idx].nelem != 0)
+       continue;
+      /* Calculate epsilon closure of `node_idx'.  */
+      err = calc_eclosure_iter (&eclosure_elem, dfa, node_idx, 1);
+      if (BE (err != REG_NOERROR, 0))
+       return err;
+
+      if (dfa->eclosures[node_idx].nelem == 0)
+       {
+         incomplete = 1;
+         re_node_set_free (&eclosure_elem);
+       }
+    }
+  return REG_NOERROR;
+}
+
+/* Calculate epsilon closure of NODE.  */
+
+static reg_errcode_t
+calc_eclosure_iter (re_node_set *new_set, re_dfa_t *dfa, int node, int root)
+{
+  reg_errcode_t err;
+  int i;
+  re_node_set eclosure;
+  int ret;
+  int incomplete = 0;
+  err = re_node_set_alloc (&eclosure, dfa->edests[node].nelem + 1);
+  if (BE (err != REG_NOERROR, 0))
+    return err;
+
+  /* This indicates that we are calculating this node now.
+     We reference this value to avoid infinite loop.  */
+  dfa->eclosures[node].nelem = -1;
+
+  /* If the current node has constraints, duplicate all nodes
+     since they must inherit the constraints.  */
+  if (dfa->nodes[node].constraint
+      && dfa->edests[node].nelem
+      && !dfa->nodes[dfa->edests[node].elems[0]].duplicated)
+    {
+      err = duplicate_node_closure (dfa, node, node, node,
+                                   dfa->nodes[node].constraint);
+      if (BE (err != REG_NOERROR, 0))
+       return err;
+    }
+
+  /* Expand each epsilon destination nodes.  */
+  if (IS_EPSILON_NODE(dfa->nodes[node].type))
+    for (i = 0; i < dfa->edests[node].nelem; ++i)
+      {
+       re_node_set eclosure_elem;
+       int edest = dfa->edests[node].elems[i];
+       /* If calculating the epsilon closure of `edest' is in progress,
+          return intermediate result.  */
+       if (dfa->eclosures[edest].nelem == -1)
+         {
+           incomplete = 1;
+           continue;
+         }
+       /* If we haven't calculated the epsilon closure of `edest' yet,
+          calculate now. Otherwise use calculated epsilon closure.  */
+       if (dfa->eclosures[edest].nelem == 0)
+         {
+           err = calc_eclosure_iter (&eclosure_elem, dfa, edest, 0);
+           if (BE (err != REG_NOERROR, 0))
+             return err;
+         }
+       else
+         eclosure_elem = dfa->eclosures[edest];
+       /* Merge the epsilon closure of `edest'.  */
+       err = re_node_set_merge (&eclosure, &eclosure_elem);
+       if (BE (err != REG_NOERROR, 0))
+         return err;
+       /* If the epsilon closure of `edest' is incomplete,
+          the epsilon closure of this node is also incomplete.  */
+       if (dfa->eclosures[edest].nelem == 0)
+         {
+           incomplete = 1;
+           re_node_set_free (&eclosure_elem);
+         }
+      }
+
+  /* An epsilon closure includes itself.  */
+  ret = re_node_set_insert (&eclosure, node);
+  if (BE (ret < 0, 0))
+    return REG_ESPACE;
+  if (incomplete && !root)
+    dfa->eclosures[node].nelem = 0;
+  else
+    dfa->eclosures[node] = eclosure;
+  *new_set = eclosure;
+  return REG_NOERROR;
+}
+\f
+/* Functions for token which are used in the parser.  */
+
+/* Fetch a token from INPUT.
+   We must not use this function inside bracket expressions.  */
+
+static void
+internal_function
+fetch_token (re_token_t *result, re_string_t *input, reg_syntax_t syntax)
+{
+  re_string_skip_bytes (input, peek_token (result, input, syntax));
+}
+
+/* Peek a token from INPUT, and return the length of the token.
+   We must not use this function inside bracket expressions.  */
+
+static int
+internal_function
+peek_token (re_token_t *token, re_string_t *input, reg_syntax_t syntax)
+{
+  unsigned char c;
+
+  if (re_string_eoi (input))
+    {
+      token->type = END_OF_RE;
+      return 0;
+    }
+
+  c = re_string_peek_byte (input, 0);
+  token->opr.c = c;
+
+  token->word_char = 0;
+#ifdef RE_ENABLE_I18N
+  token->mb_partial = 0;
+  if (input->mb_cur_max > 1 &&
+      !re_string_first_byte (input, re_string_cur_idx (input)))
+    {
+      token->type = CHARACTER;
+      token->mb_partial = 1;
+      return 1;
+    }
+#endif
+  if (c == '\\')
+    {
+      unsigned char c2;
+      if (re_string_cur_idx (input) + 1 >= re_string_length (input))
+       {
+         token->type = BACK_SLASH;
+         return 1;
+       }
+
+      c2 = re_string_peek_byte_case (input, 1);
+      token->opr.c = c2;
+      token->type = CHARACTER;
+#ifdef RE_ENABLE_I18N
+      if (input->mb_cur_max > 1)
+       {
+         wint_t wc = re_string_wchar_at (input,
+                                         re_string_cur_idx (input) + 1);
+         token->word_char = IS_WIDE_WORD_CHAR (wc) != 0;
+       }
+      else
+#endif
+       token->word_char = IS_WORD_CHAR (c2) != 0;
+
+      switch (c2)
+       {
+       case '|':
+         if (!(syntax & RE_LIMITED_OPS) && !(syntax & RE_NO_BK_VBAR))
+           token->type = OP_ALT;
+         break;
+       case '1': case '2': case '3': case '4': case '5':
+       case '6': case '7': case '8': case '9':
+         if (!(syntax & RE_NO_BK_REFS))
+           {
+             token->type = OP_BACK_REF;
+             token->opr.idx = c2 - '1';
+           }
+         break;
+       case '<':
+         if (!(syntax & RE_NO_GNU_OPS))
+           {
+             token->type = ANCHOR;
+             token->opr.ctx_type = WORD_FIRST;
+           }
+         break;
+       case '>':
+         if (!(syntax & RE_NO_GNU_OPS))
+           {
+             token->type = ANCHOR;
+             token->opr.ctx_type = WORD_LAST;
+           }
+         break;
+       case 'b':
+         if (!(syntax & RE_NO_GNU_OPS))
+           {
+             token->type = ANCHOR;
+             token->opr.ctx_type = WORD_DELIM;
+           }
+         break;
+       case 'B':
+         if (!(syntax & RE_NO_GNU_OPS))
+           {
+             token->type = ANCHOR;
+             token->opr.ctx_type = NOT_WORD_DELIM;
+           }
+         break;
+       case 'w':
+         if (!(syntax & RE_NO_GNU_OPS))
+           token->type = OP_WORD;
+         break;
+       case 'W':
+         if (!(syntax & RE_NO_GNU_OPS))
+           token->type = OP_NOTWORD;
+         break;
+       case 's':
+         if (!(syntax & RE_NO_GNU_OPS))
+           token->type = OP_SPACE;
+         break;
+       case 'S':
+         if (!(syntax & RE_NO_GNU_OPS))
+           token->type = OP_NOTSPACE;
+         break;
+       case '`':
+         if (!(syntax & RE_NO_GNU_OPS))
+           {
+             token->type = ANCHOR;
+             token->opr.ctx_type = BUF_FIRST;
+           }
+         break;
+       case '\'':
+         if (!(syntax & RE_NO_GNU_OPS))
+           {
+             token->type = ANCHOR;
+             token->opr.ctx_type = BUF_LAST;
+           }
+         break;
+       case '(':
+         if (!(syntax & RE_NO_BK_PARENS))
+           token->type = OP_OPEN_SUBEXP;
+         break;
+       case ')':
+         if (!(syntax & RE_NO_BK_PARENS))
+           token->type = OP_CLOSE_SUBEXP;
+         break;
+       case '+':
+         if (!(syntax & RE_LIMITED_OPS) && (syntax & RE_BK_PLUS_QM))
+           token->type = OP_DUP_PLUS;
+         break;
+       case '?':
+         if (!(syntax & RE_LIMITED_OPS) && (syntax & RE_BK_PLUS_QM))
+           token->type = OP_DUP_QUESTION;
+         break;
+       case '{':
+         if ((syntax & RE_INTERVALS) && (!(syntax & RE_NO_BK_BRACES)))
+           token->type = OP_OPEN_DUP_NUM;
+         break;
+       case '}':
+         if ((syntax & RE_INTERVALS) && (!(syntax & RE_NO_BK_BRACES)))
+           token->type = OP_CLOSE_DUP_NUM;
+         break;
+       default:
+         break;
+       }
+      return 2;
+    }
+
+  token->type = CHARACTER;
+#ifdef RE_ENABLE_I18N
+  if (input->mb_cur_max > 1)
+    {
+      wint_t wc = re_string_wchar_at (input, re_string_cur_idx (input));
+      token->word_char = IS_WIDE_WORD_CHAR (wc) != 0;
+    }
+  else
+#endif
+    token->word_char = IS_WORD_CHAR (token->opr.c);
+
+  switch (c)
+    {
+    case '\n':
+      if (syntax & RE_NEWLINE_ALT)
+       token->type = OP_ALT;
+      break;
+    case '|':
+      if (!(syntax & RE_LIMITED_OPS) && (syntax & RE_NO_BK_VBAR))
+       token->type = OP_ALT;
+      break;
+    case '*':
+      token->type = OP_DUP_ASTERISK;
+      break;
+    case '+':
+      if (!(syntax & RE_LIMITED_OPS) && !(syntax & RE_BK_PLUS_QM))
+       token->type = OP_DUP_PLUS;
+      break;
+    case '?':
+      if (!(syntax & RE_LIMITED_OPS) && !(syntax & RE_BK_PLUS_QM))
+       token->type = OP_DUP_QUESTION;
+      break;
+    case '{':
+      if ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES))
+       token->type = OP_OPEN_DUP_NUM;
+      break;
+    case '}':
+      if ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES))
+       token->type = OP_CLOSE_DUP_NUM;
+      break;
+    case '(':
+      if (syntax & RE_NO_BK_PARENS)
+       token->type = OP_OPEN_SUBEXP;
+      break;
+    case ')':
+      if (syntax & RE_NO_BK_PARENS)
+       token->type = OP_CLOSE_SUBEXP;
+      break;
+    case '[':
+      token->type = OP_OPEN_BRACKET;
+      break;
+    case '.':
+      token->type = OP_PERIOD;
+      break;
+    case '^':
+      if (!(syntax & (RE_CONTEXT_INDEP_ANCHORS | RE_CARET_ANCHORS_HERE)) &&
+         re_string_cur_idx (input) != 0)
+       {
+         char prev = re_string_peek_byte (input, -1);
+         if (!(syntax & RE_NEWLINE_ALT) || prev != '\n')
+           break;
+       }
+      token->type = ANCHOR;
+      token->opr.ctx_type = LINE_FIRST;
+      break;
+    case '$':
+      if (!(syntax & RE_CONTEXT_INDEP_ANCHORS) &&
+         re_string_cur_idx (input) + 1 != re_string_length (input))
+       {
+         re_token_t next;
+         re_string_skip_bytes (input, 1);
+         peek_token (&next, input, syntax);
+         re_string_skip_bytes (input, -1);
+         if (next.type != OP_ALT && next.type != OP_CLOSE_SUBEXP)
+           break;
+       }
+      token->type = ANCHOR;
+      token->opr.ctx_type = LINE_LAST;
+      break;
+    default:
+      break;
+    }
+  return 1;
+}
+
+/* Peek a token from INPUT, and return the length of the token.
+   We must not use this function out of bracket expressions.  */
+
+static int
+internal_function
+peek_token_bracket (re_token_t *token, re_string_t *input, reg_syntax_t syntax)
+{
+  unsigned char c;
+  if (re_string_eoi (input))
+    {
+      token->type = END_OF_RE;
+      return 0;
+    }
+  c = re_string_peek_byte (input, 0);
+  token->opr.c = c;
+
+#ifdef RE_ENABLE_I18N
+  if (input->mb_cur_max > 1 &&
+      !re_string_first_byte (input, re_string_cur_idx (input)))
+    {
+      token->type = CHARACTER;
+      return 1;
+    }
+#endif /* RE_ENABLE_I18N */
+
+  if (c == '\\' && (syntax & RE_BACKSLASH_ESCAPE_IN_LISTS)
+      && re_string_cur_idx (input) + 1 < re_string_length (input))
+    {
+      /* In this case, '\' escape a character.  */
+      unsigned char c2;
+      re_string_skip_bytes (input, 1);
+      c2 = re_string_peek_byte (input, 0);
+      token->opr.c = c2;
+      token->type = CHARACTER;
+      return 1;
+    }
+  if (c == '[') /* '[' is a special char in a bracket exps.  */
+    {
+      unsigned char c2;
+      int token_len;
+      if (re_string_cur_idx (input) + 1 < re_string_length (input))
+       c2 = re_string_peek_byte (input, 1);
+      else
+       c2 = 0;
+      token->opr.c = c2;
+      token_len = 2;
+      switch (c2)
+       {
+       case '.':
+         token->type = OP_OPEN_COLL_ELEM;
+         break;
+       case '=':
+         token->type = OP_OPEN_EQUIV_CLASS;
+         break;
+       case ':':
+         if (syntax & RE_CHAR_CLASSES)
+           {
+             token->type = OP_OPEN_CHAR_CLASS;
+             break;
+           }
+         /* else fall through.  */
+       default:
+         token->type = CHARACTER;
+         token->opr.c = c;
+         token_len = 1;
+         break;
+       }
+      return token_len;
+    }
+  switch (c)
+    {
+    case '-':
+      token->type = OP_CHARSET_RANGE;
+      break;
+    case ']':
+      token->type = OP_CLOSE_BRACKET;
+      break;
+    case '^':
+      token->type = OP_NON_MATCH_LIST;
+      break;
+    default:
+      token->type = CHARACTER;
+    }
+  return 1;
+}
+\f
+/* Functions for parser.  */
+
+/* Entry point of the parser.
+   Parse the regular expression REGEXP and return the structure tree.
+   If an error is occured, ERR is set by error code, and return NULL.
+   This function build the following tree, from regular expression <reg_exp>:
+          CAT
+          / \
+         /   \
+   <reg_exp>  EOR
+
+   CAT means concatenation.
+   EOR means end of regular expression.  */
+
+static bin_tree_t *
+parse (re_string_t *regexp, regex_t *preg, reg_syntax_t syntax,
+       reg_errcode_t *err)
+{
+  re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+  bin_tree_t *tree, *eor, *root;
+  re_token_t current_token;
+  dfa->syntax = syntax;
+  fetch_token (&current_token, regexp, syntax | RE_CARET_ANCHORS_HERE);
+  tree = parse_reg_exp (regexp, preg, &current_token, syntax, 0, err);
+  if (BE (*err != REG_NOERROR && tree == NULL, 0))
+    return NULL;
+  eor = create_tree (dfa, NULL, NULL, END_OF_RE);
+  if (tree != NULL)
+    root = create_tree (dfa, tree, eor, CONCAT);
+  else
+    root = eor;
+  if (BE (eor == NULL || root == NULL, 0))
+    {
+      *err = REG_ESPACE;
+      return NULL;
+    }
+  return root;
+}
+
+/* This function build the following tree, from regular expression
+   <branch1>|<branch2>:
+          ALT
+          / \
+         /   \
+   <branch1> <branch2>
+
+   ALT means alternative, which represents the operator `|'.  */
+
+static bin_tree_t *
+parse_reg_exp (re_string_t *regexp, regex_t *preg, re_token_t *token,
+              reg_syntax_t syntax, int nest, reg_errcode_t *err)
+{
+  re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+  bin_tree_t *tree, *branch = NULL;
+  tree = parse_branch (regexp, preg, token, syntax, nest, err);
+  if (BE (*err != REG_NOERROR && tree == NULL, 0))
+    return NULL;
+
+  while (token->type == OP_ALT)
+    {
+      fetch_token (token, regexp, syntax | RE_CARET_ANCHORS_HERE);
+      if (token->type != OP_ALT && token->type != END_OF_RE
+         && (nest == 0 || token->type != OP_CLOSE_SUBEXP))
+       {
+         branch = parse_branch (regexp, preg, token, syntax, nest, err);
+         if (BE (*err != REG_NOERROR && branch == NULL, 0))
+           return NULL;
+       }
+      else
+       branch = NULL;
+      tree = create_tree (dfa, tree, branch, OP_ALT);
+      if (BE (tree == NULL, 0))
+       {
+         *err = REG_ESPACE;
+         return NULL;
+       }
+    }
+  return tree;
+}
+
+/* This function build the following tree, from regular expression
+   <exp1><exp2>:
+       CAT
+       / \
+       /   \
+   <exp1> <exp2>
+
+   CAT means concatenation.  */
+
+static bin_tree_t *
+parse_branch (re_string_t *regexp, regex_t *preg, re_token_t *token,
+             reg_syntax_t syntax, int nest, reg_errcode_t *err)
+{
+  bin_tree_t *tree, *exp;
+  re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+  tree = parse_expression (regexp, preg, token, syntax, nest, err);
+  if (BE (*err != REG_NOERROR && tree == NULL, 0))
+    return NULL;
+
+  while (token->type != OP_ALT && token->type != END_OF_RE
+        && (nest == 0 || token->type != OP_CLOSE_SUBEXP))
+    {
+      exp = parse_expression (regexp, preg, token, syntax, nest, err);
+      if (BE (*err != REG_NOERROR && exp == NULL, 0))
+       {
+         return NULL;
+       }
+      if (tree != NULL && exp != NULL)
+       {
+         tree = create_tree (dfa, tree, exp, CONCAT);
+         if (tree == NULL)
+           {
+             *err = REG_ESPACE;
+             return NULL;
+           }
+       }
+      else if (tree == NULL)
+       tree = exp;
+      /* Otherwise exp == NULL, we don't need to create new tree.  */
+    }
+  return tree;
+}
+
+/* This function build the following tree, from regular expression a*:
+        *
+        |
+        a
+*/
+
+static bin_tree_t *
+parse_expression (re_string_t *regexp, regex_t *preg, re_token_t *token,
+                 reg_syntax_t syntax, int nest, reg_errcode_t *err)
+{
+  re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+  bin_tree_t *tree;
+  switch (token->type)
+    {
+    case CHARACTER:
+      tree = create_token_tree (dfa, NULL, NULL, token);
+      if (BE (tree == NULL, 0))
+       {
+         *err = REG_ESPACE;
+         return NULL;
+       }
+#ifdef RE_ENABLE_I18N
+      if (dfa->mb_cur_max > 1)
+       {
+         while (!re_string_eoi (regexp)
+                && !re_string_first_byte (regexp, re_string_cur_idx (regexp)))
+           {
+             bin_tree_t *mbc_remain;
+             fetch_token (token, regexp, syntax);
+             mbc_remain = create_token_tree (dfa, NULL, NULL, token);
+             tree = create_tree (dfa, tree, mbc_remain, CONCAT);
+             if (BE (mbc_remain == NULL || tree == NULL, 0))
+               {
+                 *err = REG_ESPACE;
+                 return NULL;
+               }
+           }
+       }
+#endif
+      break;
+    case OP_OPEN_SUBEXP:
+      tree = parse_sub_exp (regexp, preg, token, syntax, nest + 1, err);
+      if (BE (*err != REG_NOERROR && tree == NULL, 0))
+       return NULL;
+      break;
+    case OP_OPEN_BRACKET:
+      tree = parse_bracket_exp (regexp, dfa, token, syntax, err);
+      if (BE (*err != REG_NOERROR && tree == NULL, 0))
+       return NULL;
+      break;
+    case OP_BACK_REF:
+      if (!BE (dfa->completed_bkref_map & (1 << token->opr.idx), 1))
+       {
+         *err = REG_ESUBREG;
+         return NULL;
+       }
+      dfa->used_bkref_map |= 1 << token->opr.idx;
+      tree = create_token_tree (dfa, NULL, NULL, token);
+      if (BE (tree == NULL, 0))
+       {
+         *err = REG_ESPACE;
+         return NULL;
+       }
+      ++dfa->nbackref;
+      dfa->has_mb_node = 1;
+      break;
+    case OP_OPEN_DUP_NUM:
+      if (syntax & RE_CONTEXT_INVALID_DUP)
+       {
+         *err = REG_BADRPT;
+         return NULL;
+       }
+      /* FALLTHROUGH */
+    case OP_DUP_ASTERISK:
+    case OP_DUP_PLUS:
+    case OP_DUP_QUESTION:
+      if (syntax & RE_CONTEXT_INVALID_OPS)
+       {
+         *err = REG_BADRPT;
+         return NULL;
+       }
+      else if (syntax & RE_CONTEXT_INDEP_OPS)
+       {
+         fetch_token (token, regexp, syntax);
+         return parse_expression (regexp, preg, token, syntax, nest, err);
+       }
+      /* else fall through  */
+    case OP_CLOSE_SUBEXP:
+      if ((token->type == OP_CLOSE_SUBEXP) &&
+         !(syntax & RE_UNMATCHED_RIGHT_PAREN_ORD))
+       {
+         *err = REG_ERPAREN;
+         return NULL;
+       }
+      /* else fall through  */
+    case OP_CLOSE_DUP_NUM:
+      /* We treat it as a normal character.  */
+
+      /* Then we can these characters as normal characters.  */
+      token->type = CHARACTER;
+      /* mb_partial and word_char bits should be initialized already
+        by peek_token.  */
+      tree = create_token_tree (dfa, NULL, NULL, token);
+      if (BE (tree == NULL, 0))
+       {
+         *err = REG_ESPACE;
+         return NULL;
+       }
+      break;
+    case ANCHOR:
+      if ((token->opr.ctx_type
+          & (WORD_DELIM | NOT_WORD_DELIM | WORD_FIRST | WORD_LAST))
+         && dfa->word_ops_used == 0)
+       init_word_char (dfa);
+      if (token->opr.ctx_type == WORD_DELIM
+         || token->opr.ctx_type == NOT_WORD_DELIM)
+       {
+         bin_tree_t *tree_first, *tree_last;
+         if (token->opr.ctx_type == WORD_DELIM)
+           {
+             token->opr.ctx_type = WORD_FIRST;
+             tree_first = create_token_tree (dfa, NULL, NULL, token);
+             token->opr.ctx_type = WORD_LAST;
+           }
+         else
+           {
+             token->opr.ctx_type = INSIDE_WORD;
+             tree_first = create_token_tree (dfa, NULL, NULL, token);
+             token->opr.ctx_type = INSIDE_NOTWORD;
+           }
+         tree_last = create_token_tree (dfa, NULL, NULL, token);
+         tree = create_tree (dfa, tree_first, tree_last, OP_ALT);
+         if (BE (tree_first == NULL || tree_last == NULL || tree == NULL, 0))
+           {
+             *err = REG_ESPACE;
+             return NULL;
+           }
+       }
+      else
+       {
+         tree = create_token_tree (dfa, NULL, NULL, token);
+         if (BE (tree == NULL, 0))
+           {
+             *err = REG_ESPACE;
+             return NULL;
+           }
+       }
+      /* We must return here, since ANCHORs can't be followed
+        by repetition operators.
+        eg. RE"^*" is invalid or "<ANCHOR(^)><CHAR(*)>",
+            it must not be "<ANCHOR(^)><REPEAT(*)>".  */
+      fetch_token (token, regexp, syntax);
+      return tree;
+    case OP_PERIOD:
+      tree = create_token_tree (dfa, NULL, NULL, token);
+      if (BE (tree == NULL, 0))
+       {
+         *err = REG_ESPACE;
+         return NULL;
+       }
+      if (dfa->mb_cur_max > 1)
+       dfa->has_mb_node = 1;
+      break;
+    case OP_WORD:
+    case OP_NOTWORD:
+      tree = build_charclass_op (dfa, regexp->trans,
+                                "alnum",
+                                "_",
+                                token->type == OP_NOTWORD, err);
+      if (BE (*err != REG_NOERROR && tree == NULL, 0))
+       return NULL;
+      break;
+    case OP_SPACE:
+    case OP_NOTSPACE:
+      tree = build_charclass_op (dfa, regexp->trans,
+                                "space",
+                                "",
+                                token->type == OP_NOTSPACE, err);
+      if (BE (*err != REG_NOERROR && tree == NULL, 0))
+       return NULL;
+      break;
+    case OP_ALT:
+    case END_OF_RE:
+      return NULL;
+    case BACK_SLASH:
+      *err = REG_EESCAPE;
+      return NULL;
+    default:
+      /* Must not happen?  */
+#ifdef DEBUG
+      assert (0);
+#endif
+      return NULL;
+    }
+  fetch_token (token, regexp, syntax);
+
+  while (token->type == OP_DUP_ASTERISK || token->type == OP_DUP_PLUS
+        || token->type == OP_DUP_QUESTION || token->type == OP_OPEN_DUP_NUM)
+    {
+      tree = parse_dup_op (tree, regexp, dfa, token, syntax, err);
+      if (BE (*err != REG_NOERROR && tree == NULL, 0))
+       return NULL;
+      /* In BRE consecutive duplications are not allowed.  */
+      if ((syntax & RE_CONTEXT_INVALID_DUP)
+         && (token->type == OP_DUP_ASTERISK
+             || token->type == OP_OPEN_DUP_NUM))
+       {
+         *err = REG_BADRPT;
+         return NULL;
+       }
+    }
+
+  return tree;
+}
+
+/* This function build the following tree, from regular expression
+   (<reg_exp>):
+        SUBEXP
+           |
+       <reg_exp>
+*/
+
+static bin_tree_t *
+parse_sub_exp (re_string_t *regexp, regex_t *preg, re_token_t *token,
+              reg_syntax_t syntax, int nest, reg_errcode_t *err)
+{
+  re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+  bin_tree_t *tree;
+  size_t cur_nsub;
+  cur_nsub = preg->re_nsub++;
+
+  fetch_token (token, regexp, syntax | RE_CARET_ANCHORS_HERE);
+
+  /* The subexpression may be a null string.  */
+  if (token->type == OP_CLOSE_SUBEXP)
+    tree = NULL;
+  else
+    {
+      tree = parse_reg_exp (regexp, preg, token, syntax, nest, err);
+      if (BE (*err == REG_NOERROR && token->type != OP_CLOSE_SUBEXP, 0))
+       *err = REG_EPAREN;
+      if (BE (*err != REG_NOERROR, 0))
+       return NULL;
+    }
+
+  if (cur_nsub <= '9' - '1')
+    dfa->completed_bkref_map |= 1 << cur_nsub;
+
+  tree = create_tree (dfa, tree, NULL, SUBEXP);
+  if (BE (tree == NULL, 0))
+    {
+      *err = REG_ESPACE;
+      return NULL;
+    }
+  tree->token.opr.idx = cur_nsub;
+  return tree;
+}
+
+/* This function parse repetition operators like "*", "+", "{1,3}" etc.  */
+
+static bin_tree_t *
+parse_dup_op (bin_tree_t *elem, re_string_t *regexp, re_dfa_t *dfa,
+             re_token_t *token, reg_syntax_t syntax, reg_errcode_t *err)
+{
+  bin_tree_t *tree = NULL, *old_tree = NULL;
+  int i, start, end, start_idx = re_string_cur_idx (regexp);
+#ifndef RE_TOKEN_INIT_BUG
+  re_token_t start_token = *token;
+#else
+  re_token_t start_token;
+
+  memcpy ((void *) &start_token, (void *) token, sizeof start_token);
+#endif
+
+  if (token->type == OP_OPEN_DUP_NUM)
+    {
+      end = 0;
+      start = fetch_number (regexp, token, syntax);
+      if (start == -1)
+       {
+         if (token->type == CHARACTER && token->opr.c == ',')
+           start = 0; /* We treat "{,m}" as "{0,m}".  */
+         else
+           {
+             *err = REG_BADBR; /* <re>{} is invalid.  */
+             return NULL;
+           }
+       }
+      if (BE (start != -2, 1))
+       {
+         /* We treat "{n}" as "{n,n}".  */
+         end = ((token->type == OP_CLOSE_DUP_NUM) ? start
+                : ((token->type == CHARACTER && token->opr.c == ',')
+                   ? fetch_number (regexp, token, syntax) : -2));
+       }
+      if (BE (start == -2 || end == -2, 0))
+       {
+         /* Invalid sequence.  */
+         if (BE (!(syntax & RE_INVALID_INTERVAL_ORD), 0))
+           {
+             if (token->type == END_OF_RE)
+               *err = REG_EBRACE;
+             else
+               *err = REG_BADBR;
+
+             return NULL;
+           }
+
+         /* If the syntax bit is set, rollback.  */
+         re_string_set_index (regexp, start_idx);
+         *token = start_token;
+         token->type = CHARACTER;
+         /* mb_partial and word_char bits should be already initialized by
+            peek_token.  */
+         return elem;
+       }
+
+      if (BE ((end != -1 && start > end) || token->type != OP_CLOSE_DUP_NUM, 0))
+       {
+         /* First number greater than second.  */
+         *err = REG_BADBR;
+         return NULL;
+       }
+    }
+  else
+    {
+      start = (token->type == OP_DUP_PLUS) ? 1 : 0;
+      end = (token->type == OP_DUP_QUESTION) ? 1 : -1;
+    }
+
+  fetch_token (token, regexp, syntax);
+
+  if (BE (elem == NULL, 0))
+    return NULL;
+  if (BE (start == 0 && end == 0, 0))
+    {
+      postorder (elem, free_tree, NULL);
+      return NULL;
+    }
+
+  /* Extract "<re>{n,m}" to "<re><re>...<re><re>{0,<m-n>}".  */
+  if (BE (start > 0, 0))
+    {
+      tree = elem;
+      for (i = 2; i <= start; ++i)
+       {
+         elem = duplicate_tree (elem, dfa);
+         tree = create_tree (dfa, tree, elem, CONCAT);
+         if (BE (elem == NULL || tree == NULL, 0))
+           goto parse_dup_op_espace;
+       }
+
+      if (start == end)
+       return tree;
+
+      /* Duplicate ELEM before it is marked optional.  */
+      elem = duplicate_tree (elem, dfa);
+      old_tree = tree;
+    }
+  else
+    old_tree = NULL;
+
+  if (elem->token.type == SUBEXP)
+    postorder (elem, mark_opt_subexp, (void *) (long) elem->token.opr.idx);
+
+  tree = create_tree (dfa, elem, NULL, (end == -1 ? OP_DUP_ASTERISK : OP_ALT));
+  if (BE (tree == NULL, 0))
+    goto parse_dup_op_espace;
+
+  /* This loop is actually executed only when end != -1,
+     to rewrite <re>{0,n} as (<re>(<re>...<re>?)?)?...  We have
+     already created the start+1-th copy.  */
+  for (i = start + 2; i <= end; ++i)
+    {
+      elem = duplicate_tree (elem, dfa);
+      tree = create_tree (dfa, tree, elem, CONCAT);
+      if (BE (elem == NULL || tree == NULL, 0))
+       goto parse_dup_op_espace;
+
+      tree = create_tree (dfa, tree, NULL, OP_ALT);
+      if (BE (tree == NULL, 0))
+       goto parse_dup_op_espace;
+    }
+
+  if (old_tree)
+    tree = create_tree (dfa, old_tree, tree, CONCAT);
+
+  return tree;
+
+ parse_dup_op_espace:
+  *err = REG_ESPACE;
+  return NULL;
+}
+
+/* Size of the names for collating symbol/equivalence_class/character_class.
+   I'm not sure, but maybe enough.  */
+#define BRACKET_NAME_BUF_SIZE 32
+
+#ifndef _LIBC
+  /* Local function for parse_bracket_exp only used in case of NOT _LIBC.
+     Build the range expression which starts from START_ELEM, and ends
+     at END_ELEM.  The result are written to MBCSET and SBCSET.
+     RANGE_ALLOC is the allocated size of mbcset->range_starts, and
+     mbcset->range_ends, is a pointer argument sinse we may
+     update it.  */
+
+static reg_errcode_t
+internal_function
+# ifdef RE_ENABLE_I18N
+build_range_exp (bitset_t sbcset, re_charset_t *mbcset, int *range_alloc,
+                bracket_elem_t *start_elem, bracket_elem_t *end_elem)
+# else /* not RE_ENABLE_I18N */
+build_range_exp (bitset_t sbcset, bracket_elem_t *start_elem,
+                bracket_elem_t *end_elem)
+# endif /* not RE_ENABLE_I18N */
+{
+  unsigned int start_ch, end_ch;
+  /* Equivalence Classes and Character Classes can't be a range start/end.  */
+  if (BE (start_elem->type == EQUIV_CLASS || start_elem->type == CHAR_CLASS
+         || end_elem->type == EQUIV_CLASS || end_elem->type == CHAR_CLASS,
+         0))
+    return REG_ERANGE;
+
+  /* We can handle no multi character collating elements without libc
+     support.  */
+  if (BE ((start_elem->type == COLL_SYM
+          && strlen ((char *) start_elem->opr.name) > 1)
+         || (end_elem->type == COLL_SYM
+             && strlen ((char *) end_elem->opr.name) > 1), 0))
+    return REG_ECOLLATE;
+
+# ifdef RE_ENABLE_I18N
+  {
+    wchar_t wc;
+    wint_t start_wc;
+    wint_t end_wc;
+    wchar_t cmp_buf[6] = {L'\0', L'\0', L'\0', L'\0', L'\0', L'\0'};
+
+    start_ch = ((start_elem->type == SB_CHAR) ? start_elem->opr.ch
+               : ((start_elem->type == COLL_SYM) ? start_elem->opr.name[0]
+                  : 0));
+    end_ch = ((end_elem->type == SB_CHAR) ? end_elem->opr.ch
+             : ((end_elem->type == COLL_SYM) ? end_elem->opr.name[0]
+                : 0));
+#ifdef GAWK
+    /*
+     * Fedora Core 2, maybe others, have broken `btowc' that returns -1
+     * for any value > 127. Sigh. Note that `start_ch' and `end_ch' are
+     * unsigned, so we don't have sign extension problems.
+     */
+    start_wc = ((start_elem->type == SB_CHAR || start_elem->type == COLL_SYM)
+               ? start_ch : start_elem->opr.wch);
+    end_wc = ((end_elem->type == SB_CHAR || end_elem->type == COLL_SYM)
+             ? end_ch : end_elem->opr.wch);
+#else
+    start_wc = ((start_elem->type == SB_CHAR || start_elem->type == COLL_SYM)
+               ? __btowc (start_ch) : start_elem->opr.wch);
+    end_wc = ((end_elem->type == SB_CHAR || end_elem->type == COLL_SYM)
+             ? __btowc (end_ch) : end_elem->opr.wch);
+#endif
+    if (start_wc == WEOF || end_wc == WEOF)
+      return REG_ECOLLATE;
+    cmp_buf[0] = start_wc;
+    cmp_buf[4] = end_wc;
+    if (wcscoll (cmp_buf, cmp_buf + 4) > 0)
+      return REG_ERANGE;
+
+    /* Got valid collation sequence values, add them as a new entry.
+       However, for !_LIBC we have no collation elements: if the
+       character set is single byte, the single byte character set
+       that we build below suffices.  parse_bracket_exp passes
+       no MBCSET if dfa->mb_cur_max == 1.  */
+    if (mbcset)
+      {
+       /* Check the space of the arrays.  */
+       if (BE (*range_alloc == mbcset->nranges, 0))
+         {
+           /* There is not enough space, need realloc.  */
+           wchar_t *new_array_start, *new_array_end;
+           int new_nranges;
+
+           /* +1 in case of mbcset->nranges is 0.  */
+           new_nranges = 2 * mbcset->nranges + 1;
+           /* Use realloc since mbcset->range_starts and mbcset->range_ends
+              are NULL if *range_alloc == 0.  */
+           new_array_start = re_realloc (mbcset->range_starts, wchar_t,
+                                         new_nranges);
+           new_array_end = re_realloc (mbcset->range_ends, wchar_t,
+                                       new_nranges);
+
+           if (BE (new_array_start == NULL || new_array_end == NULL, 0))
+             return REG_ESPACE;
+
+           mbcset->range_starts = new_array_start;
+           mbcset->range_ends = new_array_end;
+           *range_alloc = new_nranges;
+         }
+
+       mbcset->range_starts[mbcset->nranges] = start_wc;
+       mbcset->range_ends[mbcset->nranges++] = end_wc;
+      }
+
+    /* Build the table for single byte characters.  */
+    for (wc = 0; wc < SBC_MAX; ++wc)
+      {
+       cmp_buf[2] = wc;
+       if (wcscoll (cmp_buf, cmp_buf + 2) <= 0
+           && wcscoll (cmp_buf + 2, cmp_buf + 4) <= 0)
+         bitset_set (sbcset, wc);
+      }
+  }
+# else /* not RE_ENABLE_I18N */
+  {
+    unsigned int ch;
+    start_ch = ((start_elem->type == SB_CHAR ) ? start_elem->opr.ch
+               : ((start_elem->type == COLL_SYM) ? start_elem->opr.name[0]
+                  : 0));
+    end_ch = ((end_elem->type == SB_CHAR ) ? end_elem->opr.ch
+             : ((end_elem->type == COLL_SYM) ? end_elem->opr.name[0]
+                : 0));
+    if (start_ch > end_ch)
+      return REG_ERANGE;
+    /* Build the table for single byte characters.  */
+    for (ch = 0; ch < SBC_MAX; ++ch)
+      if (start_ch <= ch  && ch <= end_ch)
+       bitset_set (sbcset, ch);
+  }
+# endif /* not RE_ENABLE_I18N */
+  return REG_NOERROR;
+}
+#endif /* not _LIBC */
+
+#ifndef _LIBC
+/* Helper function for parse_bracket_exp only used in case of NOT _LIBC..
+   Build the collating element which is represented by NAME.
+   The result are written to MBCSET and SBCSET.
+   COLL_SYM_ALLOC is the allocated size of mbcset->coll_sym, is a
+   pointer argument since we may update it.  */
+
+static reg_errcode_t
+internal_function
+# ifdef RE_ENABLE_I18N
+build_collating_symbol (bitset_t sbcset, re_charset_t *mbcset,
+                       int *coll_sym_alloc, const unsigned char *name)
+# else /* not RE_ENABLE_I18N */
+build_collating_symbol (bitset_t sbcset, const unsigned char *name)
+# endif /* not RE_ENABLE_I18N */
+{
+  size_t name_len = strlen ((const char *) name);
+  if (BE (name_len != 1, 0))
+    return REG_ECOLLATE;
+  else
+    {
+      bitset_set (sbcset, name[0]);
+      return REG_NOERROR;
+    }
+}
+#endif /* not _LIBC */
+
+/* This function parse bracket expression like "[abc]", "[a-c]",
+   "[[.a-a.]]" etc.  */
+
+static bin_tree_t *
+parse_bracket_exp (re_string_t *regexp, re_dfa_t *dfa, re_token_t *token,
+                  reg_syntax_t syntax, reg_errcode_t *err)
+{
+#ifdef _LIBC
+  const unsigned char *collseqmb;
+  const char *collseqwc;
+  uint32_t nrules;
+  int32_t table_size;
+  const int32_t *symb_table;
+  const unsigned char *extra;
+
+  /* Local function for parse_bracket_exp used in _LIBC environement.
+     Seek the collating symbol entry correspondings to NAME.
+     Return the index of the symbol in the SYMB_TABLE.  */
+
+  auto inline int32_t
+  __attribute ((always_inline))
+  seek_collating_symbol_entry (name, name_len)
+        const unsigned char *name;
+        size_t name_len;
+    {
+      int32_t hash = elem_hash ((const char *) name, name_len);
+      int32_t elem = hash % table_size;
+      if (symb_table[2 * elem] != 0)
+       {
+         int32_t second = hash % (table_size - 2) + 1;
+
+         do
+           {
+             /* First compare the hashing value.  */
+             if (symb_table[2 * elem] == hash
+                 /* Compare the length of the name.  */
+                 && name_len == extra[symb_table[2 * elem + 1]]
+                 /* Compare the name.  */
+                 && memcmp (name, &extra[symb_table[2 * elem + 1] + 1],
+                            name_len) == 0)
+               {
+                 /* Yep, this is the entry.  */
+                 break;
+               }
+
+             /* Next entry.  */
+             elem += second;
+           }
+         while (symb_table[2 * elem] != 0);
+       }
+      return elem;
+    }
+
+  /* Local function for parse_bracket_exp used in _LIBC environment.
+     Look up the collation sequence value of BR_ELEM.
+     Return the value if succeeded, UINT_MAX otherwise.  */
+
+  auto inline unsigned int
+  __attribute ((always_inline))
+  lookup_collation_sequence_value (br_elem)
+        bracket_elem_t *br_elem;
+    {
+      if (br_elem->type == SB_CHAR)
+       {
+         /*
+         if (MB_CUR_MAX == 1)
+         */
+         if (nrules == 0)
+           return collseqmb[br_elem->opr.ch];
+         else
+           {
+             wint_t wc = __btowc (br_elem->opr.ch);
+             return __collseq_table_lookup (collseqwc, wc);
+           }
+       }
+      else if (br_elem->type == MB_CHAR)
+       {
+         if (nrules != 0)
+           return __collseq_table_lookup (collseqwc, br_elem->opr.wch);
+       }
+      else if (br_elem->type == COLL_SYM)
+       {
+         size_t sym_name_len = strlen ((char *) br_elem->opr.name);
+         if (nrules != 0)
+           {
+             int32_t elem, idx;
+             elem = seek_collating_symbol_entry (br_elem->opr.name,
+                                                 sym_name_len);
+             if (symb_table[2 * elem] != 0)
+               {
+                 /* We found the entry.  */
+                 idx = symb_table[2 * elem + 1];
+                 /* Skip the name of collating element name.  */
+                 idx += 1 + extra[idx];
+                 /* Skip the byte sequence of the collating element.  */
+                 idx += 1 + extra[idx];
+                 /* Adjust for the alignment.  */
+                 idx = (idx + 3) & ~3;
+                 /* Skip the multibyte collation sequence value.  */
+                 idx += sizeof (unsigned int);
+                 /* Skip the wide char sequence of the collating element.  */
+                 idx += sizeof (unsigned int) *
+                   (1 + *(unsigned int *) (extra + idx));
+                 /* Return the collation sequence value.  */
+                 return *(unsigned int *) (extra + idx);
+               }
+             else if (symb_table[2 * elem] == 0 && sym_name_len == 1)
+               {
+                 /* No valid character.  Match it as a single byte
+                    character.  */
+                 return collseqmb[br_elem->opr.name[0]];
+               }
+           }
+         else if (sym_name_len == 1)
+           return collseqmb[br_elem->opr.name[0]];
+       }
+      return UINT_MAX;
+    }
+
+  /* Local function for parse_bracket_exp used in _LIBC environement.
+     Build the range expression which starts from START_ELEM, and ends
+     at END_ELEM.  The result are written to MBCSET and SBCSET.
+     RANGE_ALLOC is the allocated size of mbcset->range_starts, and
+     mbcset->range_ends, is a pointer argument sinse we may
+     update it.  */
+
+  auto inline reg_errcode_t
+  __attribute ((always_inline))
+  build_range_exp (sbcset, mbcset, range_alloc, start_elem, end_elem)
+        re_charset_t *mbcset;
+        int *range_alloc;
+        bitset_t sbcset;
+        bracket_elem_t *start_elem, *end_elem;
+    {
+      unsigned int ch;
+      uint32_t start_collseq;
+      uint32_t end_collseq;
+
+      /* Equivalence Classes and Character Classes can't be a range
+        start/end.  */
+      if (BE (start_elem->type == EQUIV_CLASS || start_elem->type == CHAR_CLASS
+             || end_elem->type == EQUIV_CLASS || end_elem->type == CHAR_CLASS,
+             0))
+       return REG_ERANGE;
+
+      start_collseq = lookup_collation_sequence_value (start_elem);
+      end_collseq = lookup_collation_sequence_value (end_elem);
+      /* Check start/end collation sequence values.  */
+      if (BE (start_collseq == UINT_MAX || end_collseq == UINT_MAX, 0))
+       return REG_ECOLLATE;
+      if (BE ((syntax & RE_NO_EMPTY_RANGES) && start_collseq > end_collseq, 0))
+       return REG_ERANGE;
+
+      /* Got valid collation sequence values, add them as a new entry.
+        However, if we have no collation elements, and the character set
+        is single byte, the single byte character set that we
+        build below suffices. */
+      if (nrules > 0 || dfa->mb_cur_max > 1)
+       {
+         /* Check the space of the arrays.  */
+         if (BE (*range_alloc == mbcset->nranges, 0))
+           {
+             /* There is not enough space, need realloc.  */
+             uint32_t *new_array_start;
+             uint32_t *new_array_end;
+             int new_nranges;
+
+             /* +1 in case of mbcset->nranges is 0.  */
+             new_nranges = 2 * mbcset->nranges + 1;
+             new_array_start = re_realloc (mbcset->range_starts, uint32_t,
+                                           new_nranges);
+             new_array_end = re_realloc (mbcset->range_ends, uint32_t,
+                                         new_nranges);
+
+             if (BE (new_array_start == NULL || new_array_end == NULL, 0))
+               return REG_ESPACE;
+
+             mbcset->range_starts = new_array_start;
+             mbcset->range_ends = new_array_end;
+             *range_alloc = new_nranges;
+           }
+
+         mbcset->range_starts[mbcset->nranges] = start_collseq;
+         mbcset->range_ends[mbcset->nranges++] = end_collseq;
+       }
+
+      /* Build the table for single byte characters.  */
+      for (ch = 0; ch < SBC_MAX; ch++)
+       {
+         uint32_t ch_collseq;
+         /*
+         if (MB_CUR_MAX == 1)
+         */
+         if (nrules == 0)
+           ch_collseq = collseqmb[ch];
+         else
+           ch_collseq = __collseq_table_lookup (collseqwc, __btowc (ch));
+         if (start_collseq <= ch_collseq && ch_collseq <= end_collseq)
+           bitset_set (sbcset, ch);
+       }
+      return REG_NOERROR;
+    }
+
+  /* Local function for parse_bracket_exp used in _LIBC environement.
+     Build the collating element which is represented by NAME.
+     The result are written to MBCSET and SBCSET.
+     COLL_SYM_ALLOC is the allocated size of mbcset->coll_sym, is a
+     pointer argument sinse we may update it.  */
+
+  auto inline reg_errcode_t
+  __attribute ((always_inline))
+  build_collating_symbol (sbcset, mbcset, coll_sym_alloc, name)
+        re_charset_t *mbcset;
+        int *coll_sym_alloc;
+        bitset_t sbcset;
+        const unsigned char *name;
+    {
+      int32_t elem, idx;
+      size_t name_len = strlen ((const char *) name);
+      if (nrules != 0)
+       {
+         elem = seek_collating_symbol_entry (name, name_len);
+         if (symb_table[2 * elem] != 0)
+           {
+             /* We found the entry.  */
+             idx = symb_table[2 * elem + 1];
+             /* Skip the name of collating element name.  */
+             idx += 1 + extra[idx];
+           }
+         else if (symb_table[2 * elem] == 0 && name_len == 1)
+           {
+             /* No valid character, treat it as a normal
+                character.  */
+             bitset_set (sbcset, name[0]);
+             return REG_NOERROR;
+           }
+         else
+           return REG_ECOLLATE;
+
+         /* Got valid collation sequence, add it as a new entry.  */
+         /* Check the space of the arrays.  */
+         if (BE (*coll_sym_alloc == mbcset->ncoll_syms, 0))
+           {
+             /* Not enough, realloc it.  */
+             /* +1 in case of mbcset->ncoll_syms is 0.  */
+             int new_coll_sym_alloc = 2 * mbcset->ncoll_syms + 1;
+             /* Use realloc since mbcset->coll_syms is NULL
+                if *alloc == 0.  */
+             int32_t *new_coll_syms = re_realloc (mbcset->coll_syms, int32_t,
+                                                  new_coll_sym_alloc);
+             if (BE (new_coll_syms == NULL, 0))
+               return REG_ESPACE;
+             mbcset->coll_syms = new_coll_syms;
+             *coll_sym_alloc = new_coll_sym_alloc;
+           }
+         mbcset->coll_syms[mbcset->ncoll_syms++] = idx;
+         return REG_NOERROR;
+       }
+      else
+       {
+         if (BE (name_len != 1, 0))
+           return REG_ECOLLATE;
+         else
+           {
+             bitset_set (sbcset, name[0]);
+             return REG_NOERROR;
+           }
+       }
+    }
+#endif
+
+  re_token_t br_token;
+  re_bitset_ptr_t sbcset;
+#ifdef RE_ENABLE_I18N
+  re_charset_t *mbcset;
+  int coll_sym_alloc = 0, range_alloc = 0, mbchar_alloc = 0;
+  int equiv_class_alloc = 0, char_class_alloc = 0;
+#endif /* not RE_ENABLE_I18N */
+  int non_match = 0;
+  bin_tree_t *work_tree;
+  int token_len;
+  int first_round = 1;
+#ifdef _LIBC
+  collseqmb = (const unsigned char *)
+    _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQMB);
+  nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES);
+  if (nrules)
+    {
+      /*
+      if (MB_CUR_MAX > 1)
+      */
+      collseqwc = _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQWC);
+      table_size = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_SYMB_HASH_SIZEMB);
+      symb_table = (const int32_t *) _NL_CURRENT (LC_COLLATE,
+                                                 _NL_COLLATE_SYMB_TABLEMB);
+      extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE,
+                                                  _NL_COLLATE_SYMB_EXTRAMB);
+    }
+#endif
+  sbcset = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1);
+#ifdef RE_ENABLE_I18N
+  mbcset = (re_charset_t *) calloc (sizeof (re_charset_t), 1);
+#endif /* RE_ENABLE_I18N */
+#ifdef RE_ENABLE_I18N
+  if (BE (sbcset == NULL || mbcset == NULL, 0))
+#else
+  if (BE (sbcset == NULL, 0))
+#endif /* RE_ENABLE_I18N */
+    {
+      *err = REG_ESPACE;
+      return NULL;
+    }
+
+  token_len = peek_token_bracket (token, regexp, syntax);
+  if (BE (token->type == END_OF_RE, 0))
+    {
+      *err = REG_BADPAT;
+      goto parse_bracket_exp_free_return;
+    }
+  if (token->type == OP_NON_MATCH_LIST)
+    {
+#ifdef RE_ENABLE_I18N
+      mbcset->non_match = 1;
+#endif /* not RE_ENABLE_I18N */
+      non_match = 1;
+      if (syntax & RE_HAT_LISTS_NOT_NEWLINE)
+       bitset_set (sbcset, '\n');
+      re_string_skip_bytes (regexp, token_len); /* Skip a token.  */
+      token_len = peek_token_bracket (token, regexp, syntax);
+      if (BE (token->type == END_OF_RE, 0))
+       {
+         *err = REG_BADPAT;
+         goto parse_bracket_exp_free_return;
+       }
+    }
+
+  /* We treat the first ']' as a normal character.  */
+  if (token->type == OP_CLOSE_BRACKET)
+    token->type = CHARACTER;
+
+  while (1)
+    {
+      bracket_elem_t start_elem, end_elem;
+      unsigned char start_name_buf[BRACKET_NAME_BUF_SIZE];
+      unsigned char end_name_buf[BRACKET_NAME_BUF_SIZE];
+      reg_errcode_t ret;
+      int token_len2 = 0, is_range_exp = 0;
+      re_token_t token2;
+
+      start_elem.opr.name = start_name_buf;
+      ret = parse_bracket_element (&start_elem, regexp, token, token_len, dfa,
+                                  syntax, first_round);
+      if (BE (ret != REG_NOERROR, 0))
+       {
+         *err = ret;
+         goto parse_bracket_exp_free_return;
+       }
+      first_round = 0;
+
+      /* Get information about the next token.  We need it in any case.  */
+      token_len = peek_token_bracket (token, regexp, syntax);
+
+      /* Do not check for ranges if we know they are not allowed.  */
+      if (start_elem.type != CHAR_CLASS && start_elem.type != EQUIV_CLASS)
+       {
+         if (BE (token->type == END_OF_RE, 0))
+           {
+             *err = REG_EBRACK;
+             goto parse_bracket_exp_free_return;
+           }
+         if (token->type == OP_CHARSET_RANGE)
+           {
+             re_string_skip_bytes (regexp, token_len); /* Skip '-'.  */
+             token_len2 = peek_token_bracket (&token2, regexp, syntax);
+             if (BE (token2.type == END_OF_RE, 0))
+               {
+                 *err = REG_EBRACK;
+                 goto parse_bracket_exp_free_return;
+               }
+             if (token2.type == OP_CLOSE_BRACKET)
+               {
+                 /* We treat the last '-' as a normal character.  */
+                 re_string_skip_bytes (regexp, -token_len);
+                 token->type = CHARACTER;
+               }
+             else
+               is_range_exp = 1;
+           }
+       }
+
+      if (is_range_exp == 1)
+       {
+         end_elem.opr.name = end_name_buf;
+         ret = parse_bracket_element (&end_elem, regexp, &token2, token_len2,
+                                      dfa, syntax, 1);
+         if (BE (ret != REG_NOERROR, 0))
+           {
+             *err = ret;
+             goto parse_bracket_exp_free_return;
+           }
+
+         token_len = peek_token_bracket (token, regexp, syntax);
+
+#ifdef _LIBC
+         *err = build_range_exp (sbcset, mbcset, &range_alloc,
+                                 &start_elem, &end_elem);
+#else
+# ifdef RE_ENABLE_I18N
+         *err = build_range_exp (sbcset,
+                                 dfa->mb_cur_max > 1 ? mbcset : NULL,
+                                 &range_alloc, &start_elem, &end_elem);
+# else
+         *err = build_range_exp (sbcset, &start_elem, &end_elem);
+# endif
+#endif /* RE_ENABLE_I18N */
+         if (BE (*err != REG_NOERROR, 0))
+           goto parse_bracket_exp_free_return;
+       }
+      else
+       {
+         switch (start_elem.type)
+           {
+           case SB_CHAR:
+             bitset_set (sbcset, start_elem.opr.ch);
+             break;
+#ifdef RE_ENABLE_I18N
+           case MB_CHAR:
+             /* Check whether the array has enough space.  */
+             if (BE (mbchar_alloc == mbcset->nmbchars, 0))
+               {
+                 wchar_t *new_mbchars;
+                 /* Not enough, realloc it.  */
+                 /* +1 in case of mbcset->nmbchars is 0.  */
+                 mbchar_alloc = 2 * mbcset->nmbchars + 1;
+                 /* Use realloc since array is NULL if *alloc == 0.  */
+                 new_mbchars = re_realloc (mbcset->mbchars, wchar_t,
+                                           mbchar_alloc);
+                 if (BE (new_mbchars == NULL, 0))
+                   goto parse_bracket_exp_espace;
+                 mbcset->mbchars = new_mbchars;
+               }
+             mbcset->mbchars[mbcset->nmbchars++] = start_elem.opr.wch;
+             break;
+#endif /* RE_ENABLE_I18N */
+           case EQUIV_CLASS:
+             *err = build_equiv_class (sbcset,
+#ifdef RE_ENABLE_I18N
+                                       mbcset, &equiv_class_alloc,
+#endif /* RE_ENABLE_I18N */
+                                       start_elem.opr.name);
+             if (BE (*err != REG_NOERROR, 0))
+               goto parse_bracket_exp_free_return;
+             break;
+           case COLL_SYM:
+             *err = build_collating_symbol (sbcset,
+#ifdef RE_ENABLE_I18N
+                                            mbcset, &coll_sym_alloc,
+#endif /* RE_ENABLE_I18N */
+                                            start_elem.opr.name);
+             if (BE (*err != REG_NOERROR, 0))
+               goto parse_bracket_exp_free_return;
+             break;
+           case CHAR_CLASS:
+             *err = build_charclass (regexp->trans, sbcset,
+#ifdef RE_ENABLE_I18N
+                                     mbcset, &char_class_alloc,
+#endif /* RE_ENABLE_I18N */
+                                     (const char *) start_elem.opr.name, syntax);
+             if (BE (*err != REG_NOERROR, 0))
+              goto parse_bracket_exp_free_return;
+             break;
+           default:
+             assert (0);
+             break;
+           }
+       }
+      if (BE (token->type == END_OF_RE, 0))
+       {
+         *err = REG_EBRACK;
+         goto parse_bracket_exp_free_return;
+       }
+      if (token->type == OP_CLOSE_BRACKET)
+       break;
+    }
+
+  re_string_skip_bytes (regexp, token_len); /* Skip a token.  */
+
+  /* If it is non-matching list.  */
+  if (non_match)
+    bitset_not (sbcset);
+
+#ifdef RE_ENABLE_I18N
+  /* Ensure only single byte characters are set.  */
+  if (dfa->mb_cur_max > 1)
+    bitset_mask (sbcset, dfa->sb_char);
+
+  if (mbcset->nmbchars || mbcset->ncoll_syms || mbcset->nequiv_classes
+      || mbcset->nranges || (dfa->mb_cur_max > 1 && (mbcset->nchar_classes
+                                                    || mbcset->non_match)))
+    {
+      bin_tree_t *mbc_tree;
+      int sbc_idx;
+      /* Build a tree for complex bracket.  */
+      dfa->has_mb_node = 1;
+      br_token.type = COMPLEX_BRACKET;
+      br_token.opr.mbcset = mbcset;
+      mbc_tree = create_token_tree (dfa, NULL, NULL, &br_token);
+      if (BE (mbc_tree == NULL, 0))
+       goto parse_bracket_exp_espace;
+      for (sbc_idx = 0; sbc_idx < BITSET_WORDS; ++sbc_idx)
+       if (sbcset[sbc_idx])
+         break;
+      /* If there are no bits set in sbcset, there is no point
+        of having both SIMPLE_BRACKET and COMPLEX_BRACKET.  */
+      if (sbc_idx < BITSET_WORDS)
+       {
+         /* Build a tree for simple bracket.  */
+         br_token.type = SIMPLE_BRACKET;
+         br_token.opr.sbcset = sbcset;
+         work_tree = create_token_tree (dfa, NULL, NULL, &br_token);
+         if (BE (work_tree == NULL, 0))
+           goto parse_bracket_exp_espace;
+
+         /* Then join them by ALT node.  */
+         work_tree = create_tree (dfa, work_tree, mbc_tree, OP_ALT);
+         if (BE (work_tree == NULL, 0))
+           goto parse_bracket_exp_espace;
+       }
+      else
+       {
+         re_free (sbcset);
+         work_tree = mbc_tree;
+       }
+    }
+  else
+#endif /* not RE_ENABLE_I18N */
+    {
+#ifdef RE_ENABLE_I18N
+      free_charset (mbcset);
+#endif
+      /* Build a tree for simple bracket.  */
+      br_token.type = SIMPLE_BRACKET;
+      br_token.opr.sbcset = sbcset;
+      work_tree = create_token_tree (dfa, NULL, NULL, &br_token);
+      if (BE (work_tree == NULL, 0))
+       goto parse_bracket_exp_espace;
+    }
+  return work_tree;
+
+ parse_bracket_exp_espace:
+  *err = REG_ESPACE;
+ parse_bracket_exp_free_return:
+  re_free (sbcset);
+#ifdef RE_ENABLE_I18N
+  free_charset (mbcset);
+#endif /* RE_ENABLE_I18N */
+  return NULL;
+}
+
+/* Parse an element in the bracket expression.  */
+
+static reg_errcode_t
+parse_bracket_element (bracket_elem_t *elem, re_string_t *regexp,
+                      re_token_t *token, int token_len, re_dfa_t *dfa,
+                      reg_syntax_t syntax, int accept_hyphen)
+{
+#ifdef RE_ENABLE_I18N
+  int cur_char_size;
+  cur_char_size = re_string_char_size_at (regexp, re_string_cur_idx (regexp));
+  if (cur_char_size > 1)
+    {
+      elem->type = MB_CHAR;
+      elem->opr.wch = re_string_wchar_at (regexp, re_string_cur_idx (regexp));
+      re_string_skip_bytes (regexp, cur_char_size);
+      return REG_NOERROR;
+    }
+#endif /* RE_ENABLE_I18N */
+  re_string_skip_bytes (regexp, token_len); /* Skip a token.  */
+  if (token->type == OP_OPEN_COLL_ELEM || token->type == OP_OPEN_CHAR_CLASS
+      || token->type == OP_OPEN_EQUIV_CLASS)
+    return parse_bracket_symbol (elem, regexp, token);
+  if (BE (token->type == OP_CHARSET_RANGE, 0) && !accept_hyphen)
+    {
+      /* A '-' must only appear as anything but a range indicator before
+        the closing bracket.  Everything else is an error.  */
+      re_token_t token2;
+      (void) peek_token_bracket (&token2, regexp, syntax);
+      if (token2.type != OP_CLOSE_BRACKET)
+       /* The actual error value is not standardized since this whole
+          case is undefined.  But ERANGE makes good sense.  */
+       return REG_ERANGE;
+    }
+  elem->type = SB_CHAR;
+  elem->opr.ch = token->opr.c;
+  return REG_NOERROR;
+}
+
+/* Parse a bracket symbol in the bracket expression.  Bracket symbols are
+   such as [:<character_class>:], [.<collating_element>.], and
+   [=<equivalent_class>=].  */
+
+static reg_errcode_t
+parse_bracket_symbol (bracket_elem_t *elem, re_string_t *regexp,
+                     re_token_t *token)
+{
+  unsigned char ch, delim = token->opr.c;
+  int i = 0;
+  if (re_string_eoi(regexp))
+    return REG_EBRACK;
+  for (;; ++i)
+    {
+      if (i >= BRACKET_NAME_BUF_SIZE)
+       return REG_EBRACK;
+      if (token->type == OP_OPEN_CHAR_CLASS)
+       ch = re_string_fetch_byte_case (regexp);
+      else
+       ch = re_string_fetch_byte (regexp);
+      if (re_string_eoi(regexp))
+       return REG_EBRACK;
+      if (ch == delim && re_string_peek_byte (regexp, 0) == ']')
+       break;
+      elem->opr.name[i] = ch;
+    }
+  re_string_skip_bytes (regexp, 1);
+  elem->opr.name[i] = '\0';
+  switch (token->type)
+    {
+    case OP_OPEN_COLL_ELEM:
+      elem->type = COLL_SYM;
+      break;
+    case OP_OPEN_EQUIV_CLASS:
+      elem->type = EQUIV_CLASS;
+      break;
+    case OP_OPEN_CHAR_CLASS:
+      elem->type = CHAR_CLASS;
+      break;
+    default:
+      break;
+    }
+  return REG_NOERROR;
+}
+
+  /* Helper function for parse_bracket_exp.
+     Build the equivalence class which is represented by NAME.
+     The result are written to MBCSET and SBCSET.
+     EQUIV_CLASS_ALLOC is the allocated size of mbcset->equiv_classes,
+     is a pointer argument sinse we may update it.  */
+
+static reg_errcode_t
+#ifdef RE_ENABLE_I18N
+build_equiv_class (bitset_t sbcset, re_charset_t *mbcset,
+                  int *equiv_class_alloc, const unsigned char *name)
+#else /* not RE_ENABLE_I18N */
+build_equiv_class (bitset_t sbcset, const unsigned char *name)
+#endif /* not RE_ENABLE_I18N */
+{
+#ifdef _LIBC
+  uint32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES);
+  if (nrules != 0)
+    {
+      const int32_t *table, *indirect;
+      const unsigned char *weights, *extra, *cp;
+      unsigned char char_buf[2];
+      int32_t idx1, idx2;
+      unsigned int ch;
+      size_t len;
+      /* This #include defines a local function!  */
+# include <locale/weight.h>
+      /* Calculate the index for equivalence class.  */
+      cp = name;
+      table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB);
+      weights = (const unsigned char *) _NL_CURRENT (LC_COLLATE,
+                                              _NL_COLLATE_WEIGHTMB);
+      extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE,
+                                                  _NL_COLLATE_EXTRAMB);
+      indirect = (const int32_t *) _NL_CURRENT (LC_COLLATE,
+                                               _NL_COLLATE_INDIRECTMB);
+      idx1 = findidx (&cp);
+      if (BE (idx1 == 0 || cp < name + strlen ((const char *) name), 0))
+       /* This isn't a valid character.  */
+       return REG_ECOLLATE;
+
+      /* Build single byte matcing table for this equivalence class.  */
+      char_buf[1] = (unsigned char) '\0';
+      len = weights[idx1 & 0xffffff];
+      for (ch = 0; ch < SBC_MAX; ++ch)
+       {
+         char_buf[0] = ch;
+         cp = char_buf;
+         idx2 = findidx (&cp);
+/*
+         idx2 = table[ch];
+*/
+         if (idx2 == 0)
+           /* This isn't a valid character.  */
+           continue;
+         /* Compare only if the length matches and the collation rule
+            index is the same.  */
+         if (len == weights[idx2 & 0xffffff] && (idx1 >> 24) == (idx2 >> 24))
+           {
+             int cnt = 0;
+
+             while (cnt <= len &&
+                    weights[(idx1 & 0xffffff) + 1 + cnt]
+                    == weights[(idx2 & 0xffffff) + 1 + cnt])
+               ++cnt;
+
+             if (cnt > len)
+               bitset_set (sbcset, ch);
+           }
+       }
+      /* Check whether the array has enough space.  */
+      if (BE (*equiv_class_alloc == mbcset->nequiv_classes, 0))
+       {
+         /* Not enough, realloc it.  */
+         /* +1 in case of mbcset->nequiv_classes is 0.  */
+         int new_equiv_class_alloc = 2 * mbcset->nequiv_classes + 1;
+         /* Use realloc since the array is NULL if *alloc == 0.  */
+         int32_t *new_equiv_classes = re_realloc (mbcset->equiv_classes,
+                                                  int32_t,
+                                                  new_equiv_class_alloc);
+         if (BE (new_equiv_classes == NULL, 0))
+           return REG_ESPACE;
+         mbcset->equiv_classes = new_equiv_classes;
+         *equiv_class_alloc = new_equiv_class_alloc;
+       }
+      mbcset->equiv_classes[mbcset->nequiv_classes++] = idx1;
+    }
+  else
+#endif /* _LIBC */
+    {
+      if (BE (strlen ((const char *) name) != 1, 0))
+       return REG_ECOLLATE;
+      bitset_set (sbcset, *name);
+    }
+  return REG_NOERROR;
+}
+
+  /* Helper function for parse_bracket_exp.
+     Build the character class which is represented by NAME.
+     The result are written to MBCSET and SBCSET.
+     CHAR_CLASS_ALLOC is the allocated size of mbcset->char_classes,
+     is a pointer argument sinse we may update it.  */
+
+static reg_errcode_t
+#ifdef RE_ENABLE_I18N
+build_charclass (RE_TRANSLATE_TYPE trans, bitset_t sbcset,
+                re_charset_t *mbcset, int *char_class_alloc,
+                const char *class_name, reg_syntax_t syntax)
+#else /* not RE_ENABLE_I18N */
+build_charclass (RE_TRANSLATE_TYPE trans, bitset_t sbcset,
+                const char *class_name, reg_syntax_t syntax)
+#endif /* not RE_ENABLE_I18N */
+{
+  int i;
+
+  /* In case of REG_ICASE "upper" and "lower" match the both of
+     upper and lower cases.  */
+  if ((syntax & RE_ICASE)
+      && (strcmp (class_name, "upper") == 0 || strcmp (class_name, "lower") == 0))
+    class_name = "alpha";
+
+#ifdef RE_ENABLE_I18N
+  /* Check the space of the arrays.  */
+  if (BE (*char_class_alloc == mbcset->nchar_classes, 0))
+    {
+      /* Not enough, realloc it.  */
+      /* +1 in case of mbcset->nchar_classes is 0.  */
+      int new_char_class_alloc = 2 * mbcset->nchar_classes + 1;
+      /* Use realloc since array is NULL if *alloc == 0.  */
+      wctype_t *new_char_classes = re_realloc (mbcset->char_classes, wctype_t,
+                                              new_char_class_alloc);
+      if (BE (new_char_classes == NULL, 0))
+       return REG_ESPACE;
+      mbcset->char_classes = new_char_classes;
+      *char_class_alloc = new_char_class_alloc;
+    }
+  mbcset->char_classes[mbcset->nchar_classes++] = __wctype (class_name);
+#endif /* RE_ENABLE_I18N */
+
+#define BUILD_CHARCLASS_LOOP(ctype_func)       \
+  do {                                         \
+    if (BE (trans != NULL, 0))                 \
+      {                                                \
+       for (i = 0; i < SBC_MAX; ++i)           \
+         if (ctype_func (i))                   \
+           bitset_set (sbcset, trans[i]);      \
+      }                                                \
+    else                                       \
+      {                                                \
+       for (i = 0; i < SBC_MAX; ++i)           \
+         if (ctype_func (i))                   \
+           bitset_set (sbcset, i);             \
+      }                                                \
+  } while (0)
+
+  if (strcmp (class_name, "alnum") == 0)
+    BUILD_CHARCLASS_LOOP (isalnum);
+  else if (strcmp (class_name, "cntrl") == 0)
+    BUILD_CHARCLASS_LOOP (iscntrl);
+  else if (strcmp (class_name, "lower") == 0)
+    BUILD_CHARCLASS_LOOP (islower);
+  else if (strcmp (class_name, "space") == 0)
+    BUILD_CHARCLASS_LOOP (isspace);
+  else if (strcmp (class_name, "alpha") == 0)
+    BUILD_CHARCLASS_LOOP (isalpha);
+  else if (strcmp (class_name, "digit") == 0)
+    BUILD_CHARCLASS_LOOP (isdigit);
+  else if (strcmp (class_name, "print") == 0)
+    BUILD_CHARCLASS_LOOP (isprint);
+  else if (strcmp (class_name, "upper") == 0)
+    BUILD_CHARCLASS_LOOP (isupper);
+  else if (strcmp (class_name, "blank") == 0)
+#ifndef GAWK
+    BUILD_CHARCLASS_LOOP (isblank);
+#else
+    /* see comments above */
+    BUILD_CHARCLASS_LOOP (is_blank);
+#endif
+  else if (strcmp (class_name, "graph") == 0)
+    BUILD_CHARCLASS_LOOP (isgraph);
+  else if (strcmp (class_name, "punct") == 0)
+    BUILD_CHARCLASS_LOOP (ispunct);
+  else if (strcmp (class_name, "xdigit") == 0)
+    BUILD_CHARCLASS_LOOP (isxdigit);
+  else
+    return REG_ECTYPE;
+
+  return REG_NOERROR;
+}
+
+static bin_tree_t *
+build_charclass_op (re_dfa_t *dfa, RE_TRANSLATE_TYPE trans,
+                   const char *class_name,
+                   const char *extra, int non_match,
+                   reg_errcode_t *err)
+{
+  re_bitset_ptr_t sbcset;
+#ifdef RE_ENABLE_I18N
+  re_charset_t *mbcset;
+  int alloc = 0;
+#endif /* not RE_ENABLE_I18N */
+  reg_errcode_t ret;
+  re_token_t br_token;
+  bin_tree_t *tree;
+
+  sbcset = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1);
+#ifdef RE_ENABLE_I18N
+  mbcset = (re_charset_t *) calloc (sizeof (re_charset_t), 1);
+#endif /* RE_ENABLE_I18N */
+
+#ifdef RE_ENABLE_I18N
+  if (BE (sbcset == NULL || mbcset == NULL, 0))
+#else /* not RE_ENABLE_I18N */
+  if (BE (sbcset == NULL, 0))
+#endif /* not RE_ENABLE_I18N */
+    {
+      *err = REG_ESPACE;
+      return NULL;
+    }
+
+  if (non_match)
+    {
+#ifdef RE_ENABLE_I18N
+      mbcset->non_match = 1;
+#endif /* not RE_ENABLE_I18N */
+    }
+
+  /* We don't care the syntax in this case.  */
+  ret = build_charclass (trans, sbcset,
+#ifdef RE_ENABLE_I18N
+                        mbcset, &alloc,
+#endif /* RE_ENABLE_I18N */
+                        class_name, 0);
+
+  if (BE (ret != REG_NOERROR, 0))
+    {
+      re_free (sbcset);
+#ifdef RE_ENABLE_I18N
+      free_charset (mbcset);
+#endif /* RE_ENABLE_I18N */
+      *err = ret;
+      return NULL;
+    }
+  /* \w match '_' also.  */
+  for (; *extra; extra++)
+    bitset_set (sbcset, *extra);
+
+  /* If it is non-matching list.  */
+  if (non_match)
+    bitset_not (sbcset);
+
+#ifdef RE_ENABLE_I18N
+  /* Ensure only single byte characters are set.  */
+  if (dfa->mb_cur_max > 1)
+    bitset_mask (sbcset, dfa->sb_char);
+#endif
+
+  /* Build a tree for simple bracket.  */
+  br_token.type = SIMPLE_BRACKET;
+  br_token.opr.sbcset = sbcset;
+  tree = create_token_tree (dfa, NULL, NULL, &br_token);
+  if (BE (tree == NULL, 0))
+    goto build_word_op_espace;
+
+#ifdef RE_ENABLE_I18N
+  if (dfa->mb_cur_max > 1)
+    {
+      bin_tree_t *mbc_tree;
+      /* Build a tree for complex bracket.  */
+      br_token.type = COMPLEX_BRACKET;
+      br_token.opr.mbcset = mbcset;
+      dfa->has_mb_node = 1;
+      mbc_tree = create_token_tree (dfa, NULL, NULL, &br_token);
+      if (BE (mbc_tree == NULL, 0))
+       goto build_word_op_espace;
+      /* Then join them by ALT node.  */
+      tree = create_tree (dfa, tree, mbc_tree, OP_ALT);
+      if (BE (mbc_tree != NULL, 1))
+       return tree;
+    }
+  else
+    {
+      free_charset (mbcset);
+      return tree;
+    }
+#else /* not RE_ENABLE_I18N */
+  return tree;
+#endif /* not RE_ENABLE_I18N */
+
+ build_word_op_espace:
+  re_free (sbcset);
+#ifdef RE_ENABLE_I18N
+  free_charset (mbcset);
+#endif /* RE_ENABLE_I18N */
+  *err = REG_ESPACE;
+  return NULL;
+}
+
+/* This is intended for the expressions like "a{1,3}".
+   Fetch a number from `input', and return the number.
+   Return -1, if the number field is empty like "{,1}".
+   Return -2, If an error is occured.  */
+
+static int
+fetch_number (re_string_t *input, re_token_t *token, reg_syntax_t syntax)
+{
+  int num = -1;
+  unsigned char c;
+  while (1)
+    {
+      fetch_token (token, input, syntax);
+      c = token->opr.c;
+      if (BE (token->type == END_OF_RE, 0))
+       return -2;
+      if (token->type == OP_CLOSE_DUP_NUM || c == ',')
+       break;
+      num = ((token->type != CHARACTER || c < '0' || '9' < c || num == -2)
+            ? -2 : ((num == -1) ? c - '0' : num * 10 + c - '0'));
+      num = (num > RE_DUP_MAX) ? -2 : num;
+    }
+  return num;
+}
+\f
+#ifdef RE_ENABLE_I18N
+static void
+free_charset (re_charset_t *cset)
+{
+  re_free (cset->mbchars);
+# ifdef _LIBC
+  re_free (cset->coll_syms);
+  re_free (cset->equiv_classes);
+  re_free (cset->range_starts);
+  re_free (cset->range_ends);
+# endif
+  re_free (cset->char_classes);
+  re_free (cset);
+}
+#endif /* RE_ENABLE_I18N */
+\f
+/* Functions for binary tree operation.  */
+
+/* Create a tree node.  */
+
+static bin_tree_t *
+create_tree (re_dfa_t *dfa, bin_tree_t *left, bin_tree_t *right,
+            re_token_type_t type)
+{
+  re_token_t t;
+  t.type = type;
+  return create_token_tree (dfa, left, right, &t);
+}
+
+static bin_tree_t *
+create_token_tree (re_dfa_t *dfa, bin_tree_t *left, bin_tree_t *right,
+                  const re_token_t *token)
+{
+  bin_tree_t *tree;
+  if (BE (dfa->str_tree_storage_idx == BIN_TREE_STORAGE_SIZE, 0))
+    {
+      bin_tree_storage_t *storage = re_malloc (bin_tree_storage_t, 1);
+
+      if (storage == NULL)
+       return NULL;
+      storage->next = dfa->str_tree_storage;
+      dfa->str_tree_storage = storage;
+      dfa->str_tree_storage_idx = 0;
+    }
+  tree = &dfa->str_tree_storage->data[dfa->str_tree_storage_idx++];
+
+  tree->parent = NULL;
+  tree->left = left;
+  tree->right = right;
+  tree->token = *token;
+  tree->token.duplicated = 0;
+  tree->token.opt_subexp = 0;
+  tree->first = NULL;
+  tree->next = NULL;
+  tree->node_idx = -1;
+
+  if (left != NULL)
+    left->parent = tree;
+  if (right != NULL)
+    right->parent = tree;
+  return tree;
+}
+
+/* Mark the tree SRC as an optional subexpression.
+   To be called from preorder or postorder.  */
+
+static reg_errcode_t
+mark_opt_subexp (void *extra, bin_tree_t *node)
+{
+  int idx = (int) (long) extra;
+  if (node->token.type == SUBEXP && node->token.opr.idx == idx)
+    node->token.opt_subexp = 1;
+
+  return REG_NOERROR;
+}
+
+/* Free the allocated memory inside NODE. */
+
+static void
+free_token (re_token_t *node)
+{
+#ifdef RE_ENABLE_I18N
+  if (node->type == COMPLEX_BRACKET && node->duplicated == 0)
+    free_charset (node->opr.mbcset);
+  else
+#endif /* RE_ENABLE_I18N */
+    if (node->type == SIMPLE_BRACKET && node->duplicated == 0)
+      re_free (node->opr.sbcset);
+}
+
+/* Worker function for tree walking.  Free the allocated memory inside NODE
+   and its children. */
+
+static reg_errcode_t
+free_tree (void *extra, bin_tree_t *node)
+{
+  free_token (&node->token);
+  return REG_NOERROR;
+}
+
+
+/* Duplicate the node SRC, and return new node.  This is a preorder
+   visit similar to the one implemented by the generic visitor, but
+   we need more infrastructure to maintain two parallel trees --- so,
+   it's easier to duplicate.  */
+
+static bin_tree_t *
+duplicate_tree (const bin_tree_t *root, re_dfa_t *dfa)
+{
+  const bin_tree_t *node;
+  bin_tree_t *dup_root;
+  bin_tree_t **p_new = &dup_root, *dup_node = root->parent;
+
+  for (node = root; ; )
+    {
+      /* Create a new tree and link it back to the current parent.  */
+      *p_new = create_token_tree (dfa, NULL, NULL, &node->token);
+      if (*p_new == NULL)
+       return NULL;
+      (*p_new)->parent = dup_node;
+      (*p_new)->token.duplicated = 1;
+      dup_node = *p_new;
+
+      /* Go to the left node, or up and to the right.  */
+      if (node->left)
+       {
+         node = node->left;
+         p_new = &dup_node->left;
+       }
+      else
+       {
+         const bin_tree_t *prev = NULL;
+         while (node->right == prev || node->right == NULL)
+           {
+             prev = node;
+             node = node->parent;
+             dup_node = dup_node->parent;
+             if (!node)
+               return dup_root;
+           }
+         node = node->right;
+         p_new = &dup_node->right;
+       }
+    }
+}
index 5ea007567d15c229685f571628d4afcce6783e8d..3dd8dfa01f2a6ab7a197ecddd481704150253c36 100644 (file)
-/* Extended regular expression matching and search library,
-   version 0.12.
-   (Implements POSIX draft P10003.2/D11.2, except for
-   internationalization features.)
+/* Extended regular expression matching and search library.
+   Copyright (C) 2002, 2003, 2005 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+   Contributed by Isamu Hasegawa <isamu@yamato.ibm.com>.
 
-   Copyright (C) 1993 Free Software Foundation, Inc.
+   The GNU C 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 program is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2, or (at your option)
-   any later version.
-
-   This program is distributed in the hope that it will be useful,
+   The GNU C 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 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.  */
-
-/* AIX requires this to be the first thing in the file. */
-#if defined (_AIX) && !defined (REGEX_MALLOC)
-  #pragma alloca
-#endif
-
-#define _GNU_SOURCE
-
-/* We need this for `regex.h', and perhaps for the Emacs include files.  */
-#include <sys/types.h>
-
-/* We used to test for `BSTRING' here, but only GCC and Emacs define
-   `BSTRING', as far as I know, and neither of them use this code.  */
-#include <string.h>
-#ifndef bcmp
-#define bcmp(s1, s2, n)        memcmp ((s1), (s2), (n))
-#endif
-#ifndef bcopy
-#define bcopy(s, d, n) memcpy ((d), (s), (n))
-#endif
-#ifndef bzero
-#define bzero(s, n)    memset ((s), 0, (n))
-#endif
-
-#include <stdlib.h>
-
-
-/* Define the syntax stuff for \<, \>, etc.  */
-
-/* This must be nonzero for the wordchar and notwordchar pattern
-   commands in re_match_2.  */
-#ifndef Sword
-#define Sword 1
-#endif
-
-#ifdef SYNTAX_TABLE
-
-extern char *re_syntax_table;
-
-#else /* not SYNTAX_TABLE */
-
-/* How many characters in the character set.  */
-#define CHAR_SET_SIZE 256
-
-static char re_syntax_table[CHAR_SET_SIZE];
-
-static void
-init_syntax_once ()
-{
-   register int c;
-   static int done = 0;
-
-   if (done)
-     return;
-
-   bzero (re_syntax_table, sizeof re_syntax_table);
-
-   for (c = 'a'; c <= 'z'; c++)
-     re_syntax_table[c] = Sword;
-
-   for (c = 'A'; c <= 'Z'; c++)
-     re_syntax_table[c] = Sword;
-
-   for (c = '0'; c <= '9'; c++)
-     re_syntax_table[c] = Sword;
-
-   re_syntax_table['_'] = Sword;
-
-   done = 1;
-}
-
-#endif /* not SYNTAX_TABLE */
-
-#define SYNTAX(c) re_syntax_table[c]
-
-\f
-/* Get the interface, including the syntax bits.  */
-#include "regex.h"
-
-/* isalpha etc. are used for the character classes.  */
-#include <ctype.h>
-
-#ifndef isascii
-#define isascii(c) 1
-#endif
-
-#ifdef isblank
-#define ISBLANK(c) (isascii (c) && isblank (c))
-#else
-#define ISBLANK(c) ((c) == ' ' || (c) == '\t')
-#endif
-#ifdef isgraph
-#define ISGRAPH(c) (isascii (c) && isgraph (c))
-#else
-#define ISGRAPH(c) (isascii (c) && isprint (c) && !isspace (c))
-#endif
-
-#define ISPRINT(c) (isascii (c) && isprint (c))
-#define ISDIGIT(c) (isascii (c) && isdigit (c))
-#define ISALNUM(c) (isascii (c) && isalnum (c))
-#define ISALPHA(c) (isascii (c) && isalpha (c))
-#define ISCNTRL(c) (isascii (c) && iscntrl (c))
-#define ISLOWER(c) (isascii (c) && islower (c))
-#define ISPUNCT(c) (isascii (c) && ispunct (c))
-#define ISSPACE(c) (isascii (c) && isspace (c))
-#define ISUPPER(c) (isascii (c) && isupper (c))
-#define ISXDIGIT(c) (isascii (c) && isxdigit (c))
-
-#ifndef NULL
-#define NULL 0
-#endif
-
-/* We remove any previous definition of `SIGN_EXTEND_CHAR',
-   since ours (we hope) works properly with all combinations of
-   machines, compilers, `char' and `unsigned char' argument types.
-   (Per Bothner suggested the basic approach.)  */
-#undef SIGN_EXTEND_CHAR
-#if __STDC__
-#define SIGN_EXTEND_CHAR(c) ((signed char) (c))
-#else  /* not __STDC__ */
-/* As in Harbison and Steele.  */
-#define SIGN_EXTEND_CHAR(c) ((((unsigned char) (c)) ^ 128) - 128)
-#endif
-\f
-/* Should we use malloc or alloca?  If REGEX_MALLOC is not defined, we
-   use `alloca' instead of `malloc'.  This is because using malloc in
-   re_search* or re_match* could cause memory leaks when C-g is used in
-   Emacs; also, malloc is slower and causes storage fragmentation.  On
-   the other hand, malloc is more portable, and easier to debug.
-
-   Because we sometimes use alloca, some routines have to be macros,
-   not functions -- `alloca'-allocated space disappears at the end of the
-   function it is called in.  */
-
-#ifdef REGEX_MALLOC
-
-#define REGEX_ALLOCATE malloc
-#define REGEX_REALLOCATE(source, osize, nsize) realloc (source, nsize)
-
-#else /* not REGEX_MALLOC  */
-
-/* Emacs already defines alloca, sometimes.  */
-#ifndef alloca
-
-/* Make alloca work the best possible way.  */
-#ifdef __GNUC__
-#define alloca __builtin_alloca
-#else /* not __GNUC__ */
-#if HAVE_ALLOCA_H
-#include <alloca.h>
-#else /* not __GNUC__ or HAVE_ALLOCA_H */
-#ifndef _AIX /* Already did AIX, up at the top.  */
-char *alloca ();
-#endif /* not _AIX */
-#endif /* not HAVE_ALLOCA_H */
-#endif /* not __GNUC__ */
-
-#endif /* not alloca */
-
-#define REGEX_ALLOCATE alloca
-
-/* Assumes a `char *destination' variable.  */
-#define REGEX_REALLOCATE(source, osize, nsize)                         \
-  (destination = (char *) alloca (nsize),                              \
-   bcopy (source, destination, osize),                                 \
-   destination)
-
-#endif /* not REGEX_MALLOC */
-
-
-/* True if `size1' is non-NULL and PTR is pointing anywhere inside
-   `string1' or just past its end.  This works if PTR is NULL, which is
-   a good thing.  */
-#define FIRST_STRING_P(ptr)                                    \
-  (size1 && string1 <= (ptr) && (ptr) <= string1 + size1)
-
-/* (Re)Allocate N items of type T using malloc, or fail.  */
-#define TALLOC(n, t) ((t *) malloc ((n) * sizeof (t)))
-#define RETALLOC(addr, n, t) ((addr) = (t *) realloc (addr, (n) * sizeof (t)))
-#define REGEX_TALLOC(n, t) ((t *) REGEX_ALLOCATE ((n) * sizeof (t)))
-
-#define BYTEWIDTH 8 /* In bits.  */
-
-#define STREQ(s1, s2) ((strcmp (s1, s2) == 0))
-
-#define MAX(a, b) ((a) > (b) ? (a) : (b))
-#define MIN(a, b) ((a) < (b) ? (a) : (b))
-
-typedef char boolean;
-#define false 0
-#define true 1
-\f
-/* These are the command codes that appear in compiled regular
-   expressions.  Some opcodes are followed by argument bytes.  A
-   command code can specify any interpretation whatsoever for its
-   arguments.  Zero bytes may appear in the compiled regular expression.
-
-   The value of `exactn' is needed in search.c (search_buffer) in Emacs.
-   So regex.h defines a symbol `RE_EXACTN_VALUE' to be 1; the value of
-   `exactn' we use here must also be 1.  */
-
-typedef enum
-{
-  no_op = 0,
-
-       /* Followed by one byte giving n, then by n literal bytes.  */
-  exactn = 1,
-
-       /* Matches any (more or less) character.  */
-  anychar,
-
-       /* Matches any one char belonging to specified set.  First
-          following byte is number of bitmap bytes.  Then come bytes
-          for a bitmap saying which chars are in.  Bits in each byte
-          are ordered low-bit-first.  A character is in the set if its
-          bit is 1.  A character too large to have a bit in the map is
-          automatically not in the set.  */
-  charset,
-
-       /* Same parameters as charset, but match any character that is
-          not one of those specified.  */
-  charset_not,
-
-       /* Start remembering the text that is matched, for storing in a
-          register.  Followed by one byte with the register number, in
-          the range 0 to one less than the pattern buffer's re_nsub
-          field.  Then followed by one byte with the number of groups
-          inner to this one.  (This last has to be part of the
-          start_memory only because we need it in the on_failure_jump
-          of re_match_2.)  */
-  start_memory,
-
-       /* Stop remembering the text that is matched and store it in a
-          memory register.  Followed by one byte with the register
-          number, in the range 0 to one less than `re_nsub' in the
-          pattern buffer, and one byte with the number of inner groups,
-          just like `start_memory'.  (We need the number of inner
-          groups here because we don't have any easy way of finding the
-          corresponding start_memory when we're at a stop_memory.)  */
-  stop_memory,
-
-       /* Match a duplicate of something remembered. Followed by one
-          byte containing the register number.  */
-  duplicate,
-
-       /* Fail unless at beginning of line.  */
-  begline,
-
-       /* Fail unless at end of line.  */
-  endline,
-
-       /* Succeeds if at beginning of buffer (if emacs) or at beginning
-          of string to be matched (if not).  */
-  begbuf,
-
-       /* Analogously, for end of buffer/string.  */
-  endbuf,
-
-       /* Followed by two byte relative address to which to jump.  */
-  jump,
-
-       /* Same as jump, but marks the end of an alternative.  */
-  jump_past_alt,
-
-       /* Followed by two-byte relative address of place to resume at
-          in case of failure.  */
-  on_failure_jump,
-
-       /* Like on_failure_jump, but pushes a placeholder instead of the
-          current string position when executed.  */
-  on_failure_keep_string_jump,
-
-       /* Throw away latest failure point and then jump to following
-          two-byte relative address.  */
-  pop_failure_jump,
-
-       /* Change to pop_failure_jump if know won't have to backtrack to
-          match; otherwise change to jump.  This is used to jump
-          back to the beginning of a repeat.  If what follows this jump
-          clearly won't match what the repeat does, such that we can be
-          sure that there is no use backtracking out of repetitions
-          already matched, then we change it to a pop_failure_jump.
-          Followed by two-byte address.  */
-  maybe_pop_jump,
-
-       /* Jump to following two-byte address, and push a dummy failure
-          point. This failure point will be thrown away if an attempt
-          is made to use it for a failure.  A `+' construct makes this
-          before the first repeat.  Also used as an intermediary kind
-          of jump when compiling an alternative.  */
-  dummy_failure_jump,
-
-       /* Push a dummy failure point and continue.  Used at the end of
-          alternatives.  */
-  push_dummy_failure,
-
-       /* Followed by two-byte relative address and two-byte number n.
-          After matching N times, jump to the address upon failure.  */
-  succeed_n,
-
-       /* Followed by two-byte relative address, and two-byte number n.
-          Jump to the address N times, then fail.  */
-  jump_n,
-
-       /* Set the following two-byte relative address to the
-          subsequent two-byte number.  The address *includes* the two
-          bytes of number.  */
-  set_number_at,
-
-  wordchar,    /* Matches any word-constituent character.  */
-  notwordchar, /* Matches any char that is not a word-constituent.  */
-
-  wordbeg,     /* Succeeds if at word beginning.  */
-  wordend,     /* Succeeds if at word end.  */
-
-  wordbound,   /* Succeeds if at a word boundary.  */
-  notwordbound /* Succeeds if not at a word boundary.  */
-
-#ifdef emacs
-  ,before_dot, /* Succeeds if before point.  */
-  at_dot,      /* Succeeds if at point.  */
-  after_dot,   /* Succeeds if after point.  */
-
-       /* Matches any character whose syntax is specified.  Followed by
-          a byte which contains a syntax code, e.g., Sword.  */
-  syntaxspec,
-
-       /* Matches any character whose syntax is not that specified.  */
-  notsyntaxspec
-#endif /* emacs */
-} re_opcode_t;
-\f
-/* Common operations on the compiled pattern.  */
-
-/* Store NUMBER in two contiguous bytes starting at DESTINATION.  */
-
-#define STORE_NUMBER(destination, number)                              \
-  do {                                                                 \
-    (destination)[0] = (number) & 0377;                                        \
-    (destination)[1] = (number) >> 8;                                  \
-  } while (0)
-
-/* Same as STORE_NUMBER, except increment DESTINATION to
-   the byte after where the number is stored.  Therefore, DESTINATION
-   must be an lvalue.  */
-
-#define STORE_NUMBER_AND_INCR(destination, number)                     \
-  do {                                                                 \
-    STORE_NUMBER (destination, number);                                        \
-    (destination) += 2;                                                        \
-  } while (0)
-
-/* Put into DESTINATION a number stored in two contiguous bytes starting
-   at SOURCE.  */
-
-#define EXTRACT_NUMBER(destination, source)                            \
-  do {                                                                 \
-    (destination) = *(source) & 0377;                                  \
-    (destination) += SIGN_EXTEND_CHAR (*((source) + 1)) << 8;          \
-  } while (0)
-
-#ifdef DEBUG
-static void
-extract_number (dest, source)
-    int *dest;
-    unsigned char *source;
-{
-  int temp = SIGN_EXTEND_CHAR (*(source + 1));
-  *dest = *source & 0377;
-  *dest += temp << 8;
-}
-
-#ifndef EXTRACT_MACROS /* To debug the macros.  */
-#undef EXTRACT_NUMBER
-#define EXTRACT_NUMBER(dest, src) extract_number (&dest, src)
-#endif /* not EXTRACT_MACROS */
-
-#endif /* DEBUG */
-
-/* Same as EXTRACT_NUMBER, except increment SOURCE to after the number.
-   SOURCE must be an lvalue.  */
-
-#define EXTRACT_NUMBER_AND_INCR(destination, source)                   \
-  do {                                                                 \
-    EXTRACT_NUMBER (destination, source);                              \
-    (source) += 2;                                                     \
-  } while (0)
-
-#ifdef DEBUG
-static void
-extract_number_and_incr (destination, source)
-    int *destination;
-    unsigned char **source;
-{
-  extract_number (destination, *source);
-  *source += 2;
-}
-
-#ifndef EXTRACT_MACROS
-#undef EXTRACT_NUMBER_AND_INCR
-#define EXTRACT_NUMBER_AND_INCR(dest, src) \
-  extract_number_and_incr (&dest, &src)
-#endif /* not EXTRACT_MACROS */
-
-#endif /* DEBUG */
-\f
-/* If DEBUG is defined, Regex prints many voluminous messages about what
-   it is doing (if the variable `debug' is nonzero).  If linked with the
-   main program in `iregex.c', you can enter patterns and strings
-   interactively.  And if linked with the main program in `main.c' and
-   the other test files, you can run the already-written tests.  */
-
-#ifdef DEBUG
-
-/* We use standard I/O for debugging.  */
-#include <stdio.h>
-
-/* It is useful to test things that ``must'' be true when debugging.  */
-#include <assert.h>
-
-static int debug = 0;
-
-#define DEBUG_STATEMENT(e) e
-#define DEBUG_PRINT1(x) if (debug) printf (x)
-#define DEBUG_PRINT2(x1, x2) if (debug) printf (x1, x2)
-#define DEBUG_PRINT3(x1, x2, x3) if (debug) printf (x1, x2, x3)
-#define DEBUG_PRINT4(x1, x2, x3, x4) if (debug) printf (x1, x2, x3, x4)
-#define DEBUG_PRINT_COMPILED_PATTERN(p, s, e)                          \
-  if (debug) print_partial_compiled_pattern (s, e)
-#define DEBUG_PRINT_DOUBLE_STRING(w, s1, sz1, s2, sz2)                 \
-  if (debug) print_double_string (w, s1, sz1, s2, sz2)
-
-
-extern void printchar ();
-
-/* Print the fastmap in human-readable form.  */
-
-void
-print_fastmap (fastmap)
-    char *fastmap;
-{
-  unsigned was_a_range = 0;
-  unsigned i = 0;
-
-  while (i < (1 << BYTEWIDTH))
-    {
-      if (fastmap[i++])
-       {
-         was_a_range = 0;
-         printchar (i - 1);
-         while (i < (1 << BYTEWIDTH)  &&  fastmap[i])
-           {
-             was_a_range = 1;
-             i++;
-           }
-         if (was_a_range)
-           {
-             printf ("-");
-             printchar (i - 1);
-           }
-       }
-    }
-  putchar ('\n');
-}
-
-
-/* Print a compiled pattern string in human-readable form, starting at
-   the START pointer into it and ending just before the pointer END.  */
-
-void
-print_partial_compiled_pattern (start, end)
-    unsigned char *start;
-    unsigned char *end;
-{
-  int mcnt, mcnt2;
-  unsigned char *p = start;
-  unsigned char *pend = end;
-
-  if (start == NULL)
-    {
-      printf ("(null)\n");
-      return;
-    }
-
-  /* Loop over pattern commands.  */
-  while (p < pend)
-    {
-      switch ((re_opcode_t) *p++)
-       {
-       case no_op:
-         printf ("/no_op");
-         break;
-
-       case exactn:
-         mcnt = *p++;
-         printf ("/exactn/%d", mcnt);
-         do
-           {
-             putchar ('/');
-             printchar (*p++);
-           }
-         while (--mcnt);
-         break;
-
-       case start_memory:
-         mcnt = *p++;
-         printf ("/start_memory/%d/%d", mcnt, *p++);
-         break;
-
-       case stop_memory:
-         mcnt = *p++;
-         printf ("/stop_memory/%d/%d", mcnt, *p++);
-         break;
-
-       case duplicate:
-         printf ("/duplicate/%d", *p++);
-         break;
-
-       case anychar:
-         printf ("/anychar");
-         break;
-
-       case charset:
-       case charset_not:
-         {
-           register int c;
-
-           printf ("/charset%s",
-                   (re_opcode_t) *(p - 1) == charset_not ? "_not" : "");
-
-           assert (p + *p < pend);
-
-           for (c = 0; c < *p; c++)
-             {
-               unsigned bit;
-               unsigned char map_byte = p[1 + c];
-
-               putchar ('/');
-
-               for (bit = 0; bit < BYTEWIDTH; bit++)
-                 if (map_byte & (1 << bit))
-                   printchar (c * BYTEWIDTH + bit);
-             }
-           p += 1 + *p;
-           break;
-         }
-
-       case begline:
-         printf ("/begline");
-         break;
-
-       case endline:
-         printf ("/endline");
-         break;
-
-       case on_failure_jump:
-         extract_number_and_incr (&mcnt, &p);
-         printf ("/on_failure_jump/0/%d", mcnt);
-         break;
-
-       case on_failure_keep_string_jump:
-         extract_number_and_incr (&mcnt, &p);
-         printf ("/on_failure_keep_string_jump/0/%d", mcnt);
-         break;
-
-       case dummy_failure_jump:
-         extract_number_and_incr (&mcnt, &p);
-         printf ("/dummy_failure_jump/0/%d", mcnt);
-         break;
-
-       case push_dummy_failure:
-         printf ("/push_dummy_failure");
-         break;
-
-       case maybe_pop_jump:
-         extract_number_and_incr (&mcnt, &p);
-         printf ("/maybe_pop_jump/0/%d", mcnt);
-         break;
-
-       case pop_failure_jump:
-         extract_number_and_incr (&mcnt, &p);
-         printf ("/pop_failure_jump/0/%d", mcnt);
-         break;
-
-       case jump_past_alt:
-         extract_number_and_incr (&mcnt, &p);
-         printf ("/jump_past_alt/0/%d", mcnt);
-         break;
-
-       case jump:
-         extract_number_and_incr (&mcnt, &p);
-         printf ("/jump/0/%d", mcnt);
-         break;
-
-       case succeed_n:
-         extract_number_and_incr (&mcnt, &p);
-         extract_number_and_incr (&mcnt2, &p);
-         printf ("/succeed_n/0/%d/0/%d", mcnt, mcnt2);
-         break;
-
-       case jump_n:
-         extract_number_and_incr (&mcnt, &p);
-         extract_number_and_incr (&mcnt2, &p);
-         printf ("/jump_n/0/%d/0/%d", mcnt, mcnt2);
-         break;
-
-       case set_number_at:
-         extract_number_and_incr (&mcnt, &p);
-         extract_number_and_incr (&mcnt2, &p);
-         printf ("/set_number_at/0/%d/0/%d", mcnt, mcnt2);
-         break;
-
-       case wordbound:
-         printf ("/wordbound");
-         break;
-
-       case notwordbound:
-         printf ("/notwordbound");
-         break;
-
-       case wordbeg:
-         printf ("/wordbeg");
-         break;
-
-       case wordend:
-         printf ("/wordend");
-
-#ifdef emacs
-       case before_dot:
-         printf ("/before_dot");
-         break;
-
-       case at_dot:
-         printf ("/at_dot");
-         break;
-
-       case after_dot:
-         printf ("/after_dot");
-         break;
-
-       case syntaxspec:
-         printf ("/syntaxspec");
-         mcnt = *p++;
-         printf ("/%d", mcnt);
-         break;
-
-       case notsyntaxspec:
-         printf ("/notsyntaxspec");
-         mcnt = *p++;
-         printf ("/%d", mcnt);
-         break;
-#endif /* emacs */
-
-       case wordchar:
-         printf ("/wordchar");
-         break;
-
-       case notwordchar:
-         printf ("/notwordchar");
-         break;
-
-       case begbuf:
-         printf ("/begbuf");
-         break;
-
-       case endbuf:
-         printf ("/endbuf");
-         break;
-
-       default:
-         printf ("?%d", *(p-1));
-       }
-    }
-  printf ("/\n");
-}
-
-
-void
-print_compiled_pattern (bufp)
-    struct re_pattern_buffer *bufp;
-{
-  unsigned char *buffer = bufp->buffer;
-
-  print_partial_compiled_pattern (buffer, buffer + bufp->used);
-  printf ("%d bytes used/%d bytes allocated.\n", bufp->used, bufp->allocated);
-
-  if (bufp->fastmap_accurate && bufp->fastmap)
-    {
-      printf ("fastmap: ");
-      print_fastmap (bufp->fastmap);
-    }
-
-  printf ("re_nsub: %d\t", bufp->re_nsub);
-  printf ("regs_alloc: %d\t", bufp->regs_allocated);
-  printf ("can_be_null: %d\t", bufp->can_be_null);
-  printf ("newline_anchor: %d\n", bufp->newline_anchor);
-  printf ("no_sub: %d\t", bufp->no_sub);
-  printf ("not_bol: %d\t", bufp->not_bol);
-  printf ("not_eol: %d\t", bufp->not_eol);
-  printf ("syntax: %d\n", bufp->syntax);
-  /* Perhaps we should print the translate table?  */
-}
-
-
-void
-print_double_string (where, string1, size1, string2, size2)
-    const char *where;
-    const char *string1;
-    const char *string2;
-    int size1;
-    int size2;
-{
-  unsigned this_char;
-
-  if (where == NULL)
-    printf ("(null)");
-  else
-    {
-      if (FIRST_STRING_P (where))
-       {
-         for (this_char = where - string1; this_char < size1; this_char++)
-           printchar (string1[this_char]);
-
-         where = string2;
-       }
-
-      for (this_char = where - string2; this_char < size2; this_char++)
-       printchar (string2[this_char]);
-    }
-}
-
-#else /* not DEBUG */
-
-#undef assert
-#define assert(e)
-
-#define DEBUG_STATEMENT(e)
-#define DEBUG_PRINT1(x)
-#define DEBUG_PRINT2(x1, x2)
-#define DEBUG_PRINT3(x1, x2, x3)
-#define DEBUG_PRINT4(x1, x2, x3, x4)
-#define DEBUG_PRINT_COMPILED_PATTERN(p, s, e)
-#define DEBUG_PRINT_DOUBLE_STRING(w, s1, sz1, s2, sz2)
-
-#endif /* not DEBUG */
-\f
-/* Set by `re_set_syntax' to the current regexp syntax to recognize.  Can
-   also be assigned to arbitrarily: each pattern buffer stores its own
-   syntax, so it can be changed between regex compilations.  */
-reg_syntax_t re_syntax_options = RE_SYNTAX_EMACS;
-
-
-/* Specify the precise syntax of regexps for compilation.  This provides
-   for compatibility for various utilities which historically have
-   different, incompatible syntaxes.
-
-   The argument SYNTAX is a bit mask comprised of the various bits
-   defined in regex.h.  We return the old syntax.  */
-
-reg_syntax_t
-re_set_syntax (syntax)
-    reg_syntax_t syntax;
-{
-  reg_syntax_t ret = re_syntax_options;
-
-  re_syntax_options = syntax;
-  return ret;
-}
-\f
-/* This table gives an error message for each of the error codes listed
-   in regex.h.  Obviously the order here has to be same as there.  */
-
-static const char *re_error_msg[] =
-  { NULL,                                      /* REG_NOERROR */
-    "No match",                                        /* REG_NOMATCH */
-    "Invalid regular expression",              /* REG_BADPAT */
-    "Invalid collation character",             /* REG_ECOLLATE */
-    "Invalid character class name",            /* REG_ECTYPE */
-    "Trailing backslash",                      /* REG_EESCAPE */
-    "Invalid back reference",                  /* REG_ESUBREG */
-    "Unmatched [ or [^",                       /* REG_EBRACK */
-    "Unmatched ( or \\(",                      /* REG_EPAREN */
-    "Unmatched \\{",                           /* REG_EBRACE */
-    "Invalid content of \\{\\}",               /* REG_BADBR */
-    "Invalid range end",                       /* REG_ERANGE */
-    "Memory exhausted",                                /* REG_ESPACE */
-    "Invalid preceding regular expression",    /* REG_BADRPT */
-    "Premature end of regular expression",     /* REG_EEND */
-    "Regular expression too big",              /* REG_ESIZE */
-    "Unmatched ) or \\)",                      /* REG_ERPAREN */
-  };
-\f
-/* Subroutine declarations and macros for regex_compile.  */
-
-static void store_op1 (), store_op2 ();
-static void insert_op1 (), insert_op2 ();
-static boolean at_begline_loc_p (), at_endline_loc_p ();
-static boolean group_in_compile_stack ();
-static reg_errcode_t compile_range ();
-
-/* Fetch the next character in the uncompiled pattern---translating it
-   if necessary.  Also cast from a signed character in the constant
-   string passed to us by the user to an unsigned char that we can use
-   as an array index (in, e.g., `translate').  */
-#define PATFETCH(c)                                                    \
-  do {if (p == pend) return REG_EEND;                                  \
-    c = (unsigned char) *p++;                                          \
-    if (translate) c = translate[c];                                   \
-  } while (0)
-
-/* Fetch the next character in the uncompiled pattern, with no
-   translation.  */
-#define PATFETCH_RAW(c)                                                        \
-  do {if (p == pend) return REG_EEND;                                  \
-    c = (unsigned char) *p++;                                          \
-  } while (0)
-
-/* Go backwards one character in the pattern.  */
-#define PATUNFETCH p--
-
-
-/* If `translate' is non-null, return translate[D], else just D.  We
-   cast the subscript to translate because some data is declared as
-   `char *', to avoid warnings when a string constant is passed.  But
-   when we use a character as a subscript we must make it unsigned.  */
-#define TRANSLATE(d) (translate ? translate[(unsigned char) (d)] : (d))
-
-
-/* Macros for outputting the compiled pattern into `buffer'.  */
-
-/* If the buffer isn't allocated when it comes in, use this.  */
-#define INIT_BUF_SIZE  32
-
-/* Make sure we have at least N more bytes of space in buffer.  */
-#define GET_BUFFER_SPACE(n)                                            \
-    while (b - bufp->buffer + (n) > bufp->allocated)                   \
-      EXTEND_BUFFER ()
-
-/* Make sure we have one more byte of buffer space and then add C to it.  */
-#define BUF_PUSH(c)                                                    \
-  do {                                                                 \
-    GET_BUFFER_SPACE (1);                                              \
-    *b++ = (unsigned char) (c);                                                \
-  } while (0)
-
-
-/* Ensure we have two more bytes of buffer space and then append C1 and C2.  */
-#define BUF_PUSH_2(c1, c2)                                             \
-  do {                                                                 \
-    GET_BUFFER_SPACE (2);                                              \
-    *b++ = (unsigned char) (c1);                                       \
-    *b++ = (unsigned char) (c2);                                       \
-  } while (0)
-
-
-/* As with BUF_PUSH_2, except for three bytes.  */
-#define BUF_PUSH_3(c1, c2, c3)                                         \
-  do {                                                                 \
-    GET_BUFFER_SPACE (3);                                              \
-    *b++ = (unsigned char) (c1);                                       \
-    *b++ = (unsigned char) (c2);                                       \
-    *b++ = (unsigned char) (c3);                                       \
-  } while (0)
-
-
-/* Store a jump with opcode OP at LOC to location TO.  We store a
-   relative address offset by the three bytes the jump itself occupies.  */
-#define STORE_JUMP(op, loc, to) \
-  store_op1 (op, loc, (to) - (loc) - 3)
-
-/* Likewise, for a two-argument jump.  */
-#define STORE_JUMP2(op, loc, to, arg) \
-  store_op2 (op, loc, (to) - (loc) - 3, arg)
-
-/* Like `STORE_JUMP', but for inserting.  Assume `b' is the buffer end.  */
-#define INSERT_JUMP(op, loc, to) \
-  insert_op1 (op, loc, (to) - (loc) - 3, b)
-
-/* Like `STORE_JUMP2', but for inserting.  Assume `b' is the buffer end.  */
-#define INSERT_JUMP2(op, loc, to, arg) \
-  insert_op2 (op, loc, (to) - (loc) - 3, arg, b)
-
-
-/* This is not an arbitrary limit: the arguments which represent offsets
-   into the pattern are two bytes long.  So if 2^16 bytes turns out to
-   be too small, many things would have to change.  */
-#define MAX_BUF_SIZE (1L << 16)
-
-
-/* Extend the buffer by twice its current size via realloc and
-   reset the pointers that pointed into the old block to point to the
-   correct places in the new one.  If extending the buffer results in it
-   being larger than MAX_BUF_SIZE, then flag memory exhausted.  */
-#define EXTEND_BUFFER()                                                        \
-  do {                                                                         \
-    unsigned char *old_buffer = bufp->buffer;                          \
-    if (bufp->allocated == MAX_BUF_SIZE)                               \
-      return REG_ESIZE;                                                        \
-    bufp->allocated <<= 1;                                             \
-    if (bufp->allocated > MAX_BUF_SIZE)                                        \
-      bufp->allocated = MAX_BUF_SIZE;                                  \
-    bufp->buffer = (unsigned char *) realloc (bufp->buffer, bufp->allocated);\
-    if (bufp->buffer == NULL)                                          \
-      return REG_ESPACE;                                               \
-    /* If the buffer moved, move all the pointers into it.  */         \
-    if (old_buffer != bufp->buffer)                                    \
-      {                                                                        \
-       b = (b - old_buffer) + bufp->buffer;                            \
-       begalt = (begalt - old_buffer) + bufp->buffer;                  \
-       if (fixup_alt_jump)                                             \
-         fixup_alt_jump = (fixup_alt_jump - old_buffer) + bufp->buffer;\
-       if (laststart)                                                  \
-         laststart = (laststart - old_buffer) + bufp->buffer;          \
-       if (pending_exact)                                              \
-         pending_exact = (pending_exact - old_buffer) + bufp->buffer;  \
-      }                                                                        \
-  } while (0)
-
-
-/* Since we have one byte reserved for the register number argument to
-   {start,stop}_memory, the maximum number of groups we can report
-   things about is what fits in that byte.  */
-#define MAX_REGNUM 255
-
-/* But patterns can have more than `MAX_REGNUM' registers.  We just
-   ignore the excess.  */
-typedef unsigned regnum_t;
-
-
-/* Macros for the compile stack.  */
-
-/* Since offsets can go either forwards or backwards, this type needs to
-   be able to hold values from -(MAX_BUF_SIZE - 1) to MAX_BUF_SIZE - 1.  */
-typedef int pattern_offset_t;
-
-typedef struct
-{
-  pattern_offset_t begalt_offset;
-  pattern_offset_t fixup_alt_jump;
-  pattern_offset_t inner_group_offset;
-  pattern_offset_t laststart_offset;
-  regnum_t regnum;
-} compile_stack_elt_t;
-
-
-typedef struct
-{
-  compile_stack_elt_t *stack;
-  unsigned size;
-  unsigned avail;                      /* Offset of next open position.  */
-} compile_stack_type;
-
-
-#define INIT_COMPILE_STACK_SIZE 32
-
-#define COMPILE_STACK_EMPTY  (compile_stack.avail == 0)
-#define COMPILE_STACK_FULL  (compile_stack.avail == compile_stack.size)
-
-/* The next available element.  */
-#define COMPILE_STACK_TOP (compile_stack.stack[compile_stack.avail])
-
-
-/* Set the bit for character C in a list.  */
-#define SET_LIST_BIT(c)                               \
-  (b[((unsigned char) (c)) / BYTEWIDTH]               \
-   |= 1 << (((unsigned char) c) % BYTEWIDTH))
-
-
-/* Get the next unsigned number in the uncompiled pattern.  */
-#define GET_UNSIGNED_NUMBER(num)                                       \
-  { if (p != pend)                                                     \
-     {                                                                 \
-       PATFETCH (c);                                                   \
-       while (ISDIGIT (c))                                             \
-        {                                                              \
-          if (num < 0)                                                 \
-             num = 0;                                                  \
-          num = num * 10 + c - '0';                                    \
-          if (p == pend)                                               \
-             break;                                                    \
-          PATFETCH (c);                                                \
-        }                                                              \
-       }                                                               \
-    }
-
-#define CHAR_CLASS_MAX_LENGTH  6 /* Namely, `xdigit'.  */
-
-#define IS_CHAR_CLASS(string)                                          \
-   (STREQ (string, "alpha") || STREQ (string, "upper")                 \
-    || STREQ (string, "lower") || STREQ (string, "digit")              \
-    || STREQ (string, "alnum") || STREQ (string, "xdigit")             \
-    || STREQ (string, "space") || STREQ (string, "print")              \
-    || STREQ (string, "punct") || STREQ (string, "graph")              \
-    || STREQ (string, "cntrl") || STREQ (string, "blank"))
-\f
-/* `regex_compile' compiles PATTERN (of length SIZE) according to SYNTAX.
-   Returns one of error codes defined in `regex.h', or zero for success.
-
-   Assumes the `allocated' (and perhaps `buffer') and `translate'
-   fields are set in BUFP on entry.
-
-   If it succeeds, results are put in BUFP (if it returns an error, the
-   contents of BUFP are undefined):
-     `buffer' is the compiled pattern;
-     `syntax' is set to SYNTAX;
-     `used' is set to the length of the compiled pattern;
-     `fastmap_accurate' is zero;
-     `re_nsub' is the number of subexpressions in PATTERN;
-     `not_bol' and `not_eol' are zero;
-
-   The `fastmap' and `newline_anchor' fields are neither
-   examined nor set.  */
-
-static reg_errcode_t
-regex_compile (pattern, size, syntax, bufp)
-     const char *pattern;
-     int size;
-     reg_syntax_t syntax;
-     struct re_pattern_buffer *bufp;
-{
-  /* We fetch characters from PATTERN here.  Even though PATTERN is
-     `char *' (i.e., signed), we declare these variables as unsigned, so
-     they can be reliably used as array indices.  */
-  register unsigned char c, c1;
-
-  /* A random temporary spot in PATTERN.  */
-  const char *p1;
-
-  /* Points to the end of the buffer, where we should append.  */
-  register unsigned char *b;
-
-  /* Keeps track of unclosed groups.  */
-  compile_stack_type compile_stack;
-
-  /* Points to the current (ending) position in the pattern.  */
-  const char *p = pattern;
-  const char *pend = pattern + size;
-
-  /* How to translate the characters in the pattern.  */
-  char *translate = bufp->translate;
-
-  /* Address of the count-byte of the most recently inserted `exactn'
-     command.  This makes it possible to tell if a new exact-match
-     character can be added to that command or if the character requires
-     a new `exactn' command.  */
-  unsigned char *pending_exact = 0;
-
-  /* Address of start of the most recently finished expression.
-     This tells, e.g., postfix * where to find the start of its
-     operand.  Reset at the beginning of groups and alternatives.  */
-  unsigned char *laststart = 0;
-
-  /* Address of beginning of regexp, or inside of last group.  */
-  unsigned char *begalt;
-
-  /* Place in the uncompiled pattern (i.e., the {) to
-     which to go back if the interval is invalid.  */
-  const char *beg_interval;
-
-  /* Address of the place where a forward jump should go to the end of
-     the containing expression.  Each alternative of an `or' -- except the
-     last -- ends with a forward jump of this sort.  */
-  unsigned char *fixup_alt_jump = 0;
-
-  /* Counts open-groups as they are encountered.  Remembered for the
-     matching close-group on the compile stack, so the same register
-     number is put in the stop_memory as the start_memory.  */
-  regnum_t regnum = 0;
-
-#ifdef DEBUG
-  DEBUG_PRINT1 ("\nCompiling pattern: ");
-  if (debug)
-    {
-      unsigned debug_count;
-
-      for (debug_count = 0; debug_count < size; debug_count++)
-       printchar (pattern[debug_count]);
-      putchar ('\n');
-    }
-#endif /* DEBUG */
-
-  /* Initialize the compile stack.  */
-  compile_stack.stack = TALLOC (INIT_COMPILE_STACK_SIZE, compile_stack_elt_t);
-  if (compile_stack.stack == NULL)
-    return REG_ESPACE;
-
-  compile_stack.size = INIT_COMPILE_STACK_SIZE;
-  compile_stack.avail = 0;
-
-  /* Initialize the pattern buffer.  */
-  bufp->syntax = syntax;
-  bufp->fastmap_accurate = 0;
-  bufp->not_bol = bufp->not_eol = 0;
-
-  /* Set `used' to zero, so that if we return an error, the pattern
-     printer (for debugging) will think there's no pattern.  We reset it
-     at the end.  */
-  bufp->used = 0;
-
-  /* Always count groups, whether or not bufp->no_sub is set.  */
-  bufp->re_nsub = 0;
-
-#if !defined (emacs) && !defined (SYNTAX_TABLE)
-  /* Initialize the syntax table.  */
-   init_syntax_once ();
+   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 the GNU C Library; if not, write to the Free
+   Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301 USA.  */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/* Make sure noone compiles this code with a C++ compiler.  */
+#ifdef __cplusplus
+# error "This is C code, use a C compiler"
+#endif
+
+#ifdef _LIBC
+/* We have to keep the namespace clean.  */
+# define regfree(preg) __regfree (preg)
+# define regexec(pr, st, nm, pm, ef) __regexec (pr, st, nm, pm, ef)
+# define regcomp(preg, pattern, cflags) __regcomp (preg, pattern, cflags)
+# define regerror(errcode, preg, errbuf, errbuf_size) \
+       __regerror(errcode, preg, errbuf, errbuf_size)
+# define re_set_registers(bu, re, nu, st, en) \
+       __re_set_registers (bu, re, nu, st, en)
+# define re_match_2(bufp, string1, size1, string2, size2, pos, regs, stop) \
+       __re_match_2 (bufp, string1, size1, string2, size2, pos, regs, stop)
+# define re_match(bufp, string, size, pos, regs) \
+       __re_match (bufp, string, size, pos, regs)
+# define re_search(bufp, string, size, startpos, range, regs) \
+       __re_search (bufp, string, size, startpos, range, regs)
+# define re_compile_pattern(pattern, length, bufp) \
+       __re_compile_pattern (pattern, length, bufp)
+# define re_set_syntax(syntax) __re_set_syntax (syntax)
+# define re_search_2(bufp, st1, s1, st2, s2, startpos, range, regs, stop) \
+       __re_search_2 (bufp, st1, s1, st2, s2, startpos, range, regs, stop)
+# define re_compile_fastmap(bufp) __re_compile_fastmap (bufp)
+
+# include "../locale/localeinfo.h"
+#endif
+
+#if defined (_MSC_VER)
+#include <stdio.h> /* for size_t */
+#endif
+
+/* On some systems, limits.h sets RE_DUP_MAX to a lower value than
+   GNU regex allows.  Include it before <regex.h>, which correctly
+   #undefs RE_DUP_MAX and sets it to the right value.  */
+#include <limits.h>
+
+#ifdef GAWK
+#undef alloca
+#define alloca alloca_is_bad_you_should_never_use_it
+#endif
+#include <regex.h>
+#include "regex_internal.h"
+
+#include "regex_internal.c"
+#ifdef GAWK
+#define bool int
+#define true (1)
+#define false (0)
+#endif
+#include "regcomp.c"
+#include "regexec.c"
+
+/* Binary backward compatibility.  */
+#if _LIBC
+# include <shlib-compat.h>
+# if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_3)
+link_warning (re_max_failures, "the 're_max_failures' variable is obsolete and will go away.")
+int re_max_failures = 2000;
+# endif
 #endif
-
-  if (bufp->allocated == 0)
-    {
-      if (bufp->buffer)
-       { /* If zero allocated, but buffer is non-null, try to realloc
-            enough space.  This loses if buffer's address is bogus, but
-            that is the user's responsibility.  */
-         RETALLOC (bufp->buffer, INIT_BUF_SIZE, unsigned char);
-       }
-      else
-       { /* Caller did not allocate a buffer.  Do it for them.  */
-         bufp->buffer = TALLOC (INIT_BUF_SIZE, unsigned char);
-       }
-      if (!bufp->buffer) return REG_ESPACE;
-
-      bufp->allocated = INIT_BUF_SIZE;
-    }
-
-  begalt = b = bufp->buffer;
-
-  /* Loop through the uncompiled pattern until we're at the end.  */
-  while (p != pend)
-    {
-      PATFETCH (c);
-
-      switch (c)
-       {
-       case '^':
-         {
-           if (   /* If at start of pattern, it's an operator.  */
-                  p == pattern + 1
-                  /* If context independent, it's an operator.  */
-               || syntax & RE_CONTEXT_INDEP_ANCHORS
-                  /* Otherwise, depends on what's come before.  */
-               || at_begline_loc_p (pattern, p, syntax))
-             BUF_PUSH (begline);
-           else
-             goto normal_char;
-         }
-         break;
-
-
-       case '$':
-         {
-           if (   /* If at end of pattern, it's an operator.  */
-                  p == pend
-                  /* If context independent, it's an operator.  */
-               || syntax & RE_CONTEXT_INDEP_ANCHORS
-                  /* Otherwise, depends on what's next.  */
-               || at_endline_loc_p (p, pend, syntax))
-              BUF_PUSH (endline);
-            else
-              goto normal_char;
-          }
-          break;
-
-
-       case '+':
-       case '?':
-         if ((syntax & RE_BK_PLUS_QM)
-             || (syntax & RE_LIMITED_OPS))
-           goto normal_char;
-       handle_plus:
-       case '*':
-         /* If there is no previous pattern... */
-         if (!laststart)
-           {
-             if (syntax & RE_CONTEXT_INVALID_OPS)
-               return REG_BADRPT;
-             else if (!(syntax & RE_CONTEXT_INDEP_OPS))
-               goto normal_char;
-           }
-
-         {
-           /* Are we optimizing this jump?  */
-           boolean keep_string_p = false;
-
-           /* 1 means zero (many) matches is allowed.  */
-           char zero_times_ok = 0, many_times_ok = 0;
-
-           /* If there is a sequence of repetition chars, collapse it
-              down to just one (the right one).  We can't combine
-              interval operators with these because of, e.g., `a{2}*',
-              which should only match an even number of `a's.  */
-
-           for (;;)
-             {
-               zero_times_ok |= c != '+';
-               many_times_ok |= c != '?';
-
-               if (p == pend)
-                 break;
-
-               PATFETCH (c);
-
-               if (c == '*'
-                   || (!(syntax & RE_BK_PLUS_QM) && (c == '+' || c == '?')))
-                 ;
-
-               else if (syntax & RE_BK_PLUS_QM  &&  c == '\\')
-                 {
-                   if (p == pend) return REG_EESCAPE;
-
-                   PATFETCH (c1);
-                   if (!(c1 == '+' || c1 == '?'))
-                     {
-                       PATUNFETCH;
-                       PATUNFETCH;
-                       break;
-                     }
-
-                   c = c1;
-                 }
-               else
-                 {
-                   PATUNFETCH;
-                   break;
-                 }
-
-               /* If we get here, we found another repeat character.  */
-              }
-
-           /* Star, etc. applied to an empty pattern is equivalent
-              to an empty pattern.  */
-           if (!laststart)
-             break;
-
-           /* Now we know whether or not zero matches is allowed
-              and also whether or not two or more matches is allowed.  */
-           if (many_times_ok)
-             { /* More than one repetition is allowed, so put in at the
-                  end a backward relative jump from `b' to before the next
-                  jump we're going to put in below (which jumps from
-                  laststart to after this jump).
-
-                  But if we are at the `*' in the exact sequence `.*\n',
-                  insert an unconditional jump backwards to the .,
-                  instead of the beginning of the loop.  This way we only
-                  push a failure point once, instead of every time
-                  through the loop.  */
-               assert (p - 1 > pattern);
-
-               /* Allocate the space for the jump.  */
-               GET_BUFFER_SPACE (3);
-
-               /* We know we are not at the first character of the pattern,
-                  because laststart was nonzero.  And we've already
-                  incremented `p', by the way, to be the character after
-                  the `*'.  Do we have to do something analogous here
-                  for null bytes, because of RE_DOT_NOT_NULL?  */
-               if (TRANSLATE (*(p - 2)) == TRANSLATE ('.')
-                   && zero_times_ok
-                   && p < pend && TRANSLATE (*p) == TRANSLATE ('\n')
-                   && !(syntax & RE_DOT_NEWLINE))
-                 { /* We have .*\n.  */
-                   STORE_JUMP (jump, b, laststart);
-                   keep_string_p = true;
-                 }
-               else
-                 /* Anything else.  */
-                 STORE_JUMP (maybe_pop_jump, b, laststart - 3);
-
-               /* We've added more stuff to the buffer.  */
-               b += 3;
-             }
-
-           /* On failure, jump from laststart to b + 3, which will be the
-              end of the buffer after this jump is inserted.  */
-           GET_BUFFER_SPACE (3);
-           INSERT_JUMP (keep_string_p ? on_failure_keep_string_jump
-                                      : on_failure_jump,
-                        laststart, b + 3);
-           pending_exact = 0;
-           b += 3;
-
-           if (!zero_times_ok)
-             {
-               /* At least one repetition is required, so insert a
-                  `dummy_failure_jump' before the initial
-                  `on_failure_jump' instruction of the loop. This
-                  effects a skip over that instruction the first time
-                  we hit that loop.  */
-               GET_BUFFER_SPACE (3);
-               INSERT_JUMP (dummy_failure_jump, laststart, laststart + 6);
-               b += 3;
-             }
-           }
-         break;
-
-
-       case '.':
-         laststart = b;
-         BUF_PUSH (anychar);
-         break;
-
-
-       case '[':
-         {
-           boolean had_char_class = false;
-
-           if (p == pend) return REG_EBRACK;
-
-           /* Ensure that we have enough space to push a charset: the
-              opcode, the length count, and the bitset; 34 bytes in all.  */
-           GET_BUFFER_SPACE (34);
-
-           laststart = b;
-
-           /* We test `*p == '^' twice, instead of using an if
-              statement, so we only need one BUF_PUSH.  */
-           BUF_PUSH (*p == '^' ? charset_not : charset);
-           if (*p == '^')
-             p++;
-
-           /* Remember the first position in the bracket expression.  */
-           p1 = p;
-
-           /* Push the number of bytes in the bitmap.  */
-           BUF_PUSH ((1 << BYTEWIDTH) / BYTEWIDTH);
-
-           /* Clear the whole map.  */
-           bzero (b, (1 << BYTEWIDTH) / BYTEWIDTH);
-
-           /* charset_not matches newline according to a syntax bit.  */
-           if ((re_opcode_t) b[-2] == charset_not
-               && (syntax & RE_HAT_LISTS_NOT_NEWLINE))
-             SET_LIST_BIT ('\n');
-
-           /* Read in characters and ranges, setting map bits.  */
-           for (;;)
-             {
-               if (p == pend) return REG_EBRACK;
-
-               PATFETCH (c);
-
-               /* \ might escape characters inside [...] and [^...].  */
-               if ((syntax & RE_BACKSLASH_ESCAPE_IN_LISTS) && c == '\\')
-                 {
-                   if (p == pend) return REG_EESCAPE;
-
-                   PATFETCH (c1);
-                   SET_LIST_BIT (c1);
-                   continue;
-                 }
-
-               /* Could be the end of the bracket expression.  If it's
-                  not (i.e., when the bracket expression is `[]' so
-                  far), the ']' character bit gets set way below.  */
-               if (c == ']' && p != p1 + 1)
-                 break;
-
-               /* Look ahead to see if it's a range when the last thing
-                  was a character class.  */
-               if (had_char_class && c == '-' && *p != ']')
-                 return REG_ERANGE;
-
-               /* Look ahead to see if it's a range when the last thing
-                  was a character: if this is a hyphen not at the
-                  beginning or the end of a list, then it's the range
-                  operator.  */
-               if (c == '-'
-                   && !(p - 2 >= pattern && p[-2] == '[')
-                   && !(p - 3 >= pattern && p[-3] == '[' && p[-2] == '^')
-                   && *p != ']')
-                 {
-                   reg_errcode_t ret
-                     = compile_range (&p, pend, translate, syntax, b);
-                   if (ret != REG_NOERROR) return ret;
-                 }
-
-               else if (p[0] == '-' && p[1] != ']')
-                 { /* This handles ranges made up of characters only.  */
-                   reg_errcode_t ret;
-
-                   /* Move past the `-'.  */
-                   PATFETCH (c1);
-
-                   ret = compile_range (&p, pend, translate, syntax, b);
-                   if (ret != REG_NOERROR) return ret;
-                 }
-
-               /* See if we're at the beginning of a possible character
-                  class.  */
-
-               else if (syntax & RE_CHAR_CLASSES && c == '[' && *p == ':')
-                 { /* Leave room for the null.  */
-                   char str[CHAR_CLASS_MAX_LENGTH + 1];
-
-                   PATFETCH (c);
-                   c1 = 0;
-
-                   /* If pattern is `[[:'.  */
-                   if (p == pend) return REG_EBRACK;
-
-                   for (;;)
-                     {
-                       PATFETCH (c);
-                       if (c == ':' || c == ']' || p == pend
-                           || c1 == CHAR_CLASS_MAX_LENGTH)
-                         break;
-                       str[c1++] = c;
-                     }
-                   str[c1] = '\0';
-
-                   /* If isn't a word bracketed by `[:' and:`]':
-                      undo the ending character, the letters, and leave
-                      the leading `:' and `[' (but set bits for them).  */
-                   if (c == ':' && *p == ']')
-                     {
-                       int ch;
-                       boolean is_alnum = STREQ (str, "alnum");
-                       boolean is_alpha = STREQ (str, "alpha");
-                       boolean is_blank = STREQ (str, "blank");
-                       boolean is_cntrl = STREQ (str, "cntrl");
-                       boolean is_digit = STREQ (str, "digit");
-                       boolean is_graph = STREQ (str, "graph");
-                       boolean is_lower = STREQ (str, "lower");
-                       boolean is_print = STREQ (str, "print");
-                       boolean is_punct = STREQ (str, "punct");
-                       boolean is_space = STREQ (str, "space");
-                       boolean is_upper = STREQ (str, "upper");
-                       boolean is_xdigit = STREQ (str, "xdigit");
-
-                       if (!IS_CHAR_CLASS (str)) return REG_ECTYPE;
-
-                       /* Throw away the ] at the end of the character
-                          class.  */
-                       PATFETCH (c);
-
-                       if (p == pend) return REG_EBRACK;
-
-                       for (ch = 0; ch < 1 << BYTEWIDTH; ch++)
-                         {
-                           if (   (is_alnum  && ISALNUM (ch))
-                               || (is_alpha  && ISALPHA (ch))
-                               || (is_blank  && ISBLANK (ch))
-                               || (is_cntrl  && ISCNTRL (ch))
-                               || (is_digit  && ISDIGIT (ch))
-                               || (is_graph  && ISGRAPH (ch))
-                               || (is_lower  && ISLOWER (ch))
-                               || (is_print  && ISPRINT (ch))
-                               || (is_punct  && ISPUNCT (ch))
-                               || (is_space  && ISSPACE (ch))
-                               || (is_upper  && ISUPPER (ch))
-                               || (is_xdigit && ISXDIGIT (ch)))
-                           SET_LIST_BIT (ch);
-                         }
-                       had_char_class = true;
-                     }
-                   else
-                     {
-                       c1++;
-                       while (c1--)
-                         PATUNFETCH;
-                       SET_LIST_BIT ('[');
-                       SET_LIST_BIT (':');
-                       had_char_class = false;
-                     }
-                 }
-               else
-                 {
-                   had_char_class = false;
-                   SET_LIST_BIT (c);
-                 }
-             }
-
-           /* Discard any (non)matching list bytes that are all 0 at the
-              end of the map.  Decrease the map-length byte too.  */
-           while ((int) b[-1] > 0 && b[b[-1] - 1] == 0)
-             b[-1]--;
-           b += b[-1];
-         }
-         break;
-
-
-       case '(':
-         if (syntax & RE_NO_BK_PARENS)
-           goto handle_open;
-         else
-           goto normal_char;
-
-
-       case ')':
-         if (syntax & RE_NO_BK_PARENS)
-           goto handle_close;
-         else
-           goto normal_char;
-
-
-       case '\n':
-         if (syntax & RE_NEWLINE_ALT)
-           goto handle_alt;
-         else
-           goto normal_char;
-
-
-       case '|':
-         if (syntax & RE_NO_BK_VBAR)
-           goto handle_alt;
-         else
-           goto normal_char;
-
-
-       case '{':
-          if (syntax & RE_INTERVALS && syntax & RE_NO_BK_BRACES)
-            goto handle_interval;
-          else
-            goto normal_char;
-
-
-       case '\\':
-         if (p == pend) return REG_EESCAPE;
-
-         /* Do not translate the character after the \, so that we can
-            distinguish, e.g., \B from \b, even if we normally would
-            translate, e.g., B to b.  */
-         PATFETCH_RAW (c);
-
-         switch (c)
-           {
-           case '(':
-             if (syntax & RE_NO_BK_PARENS)
-               goto normal_backslash;
-
-           handle_open:
-             bufp->re_nsub++;
-             regnum++;
-
-             if (COMPILE_STACK_FULL)
-               {
-                 RETALLOC (compile_stack.stack, compile_stack.size << 1,
-                           compile_stack_elt_t);
-                 if (compile_stack.stack == NULL) return REG_ESPACE;
-
-                 compile_stack.size <<= 1;
-               }
-
-             /* These are the values to restore when we hit end of this
-                group.  They are all relative offsets, so that if the
-                whole pattern moves because of realloc, they will still
-                be valid.  */
-             COMPILE_STACK_TOP.begalt_offset = begalt - bufp->buffer;
-             COMPILE_STACK_TOP.fixup_alt_jump
-               = fixup_alt_jump ? fixup_alt_jump - bufp->buffer + 1 : 0;
-             COMPILE_STACK_TOP.laststart_offset = b - bufp->buffer;
-             COMPILE_STACK_TOP.regnum = regnum;
-
-             /* We will eventually replace the 0 with the number of
-                groups inner to this one.  But do not push a
-                start_memory for groups beyond the last one we can
-                represent in the compiled pattern.  */
-             if (regnum <= MAX_REGNUM)
-               {
-                 COMPILE_STACK_TOP.inner_group_offset = b - bufp->buffer + 2;
-                 BUF_PUSH_3 (start_memory, regnum, 0);
-               }
-
-             compile_stack.avail++;
-
-             fixup_alt_jump = 0;
-             laststart = 0;
-             begalt = b;
-             /* If we've reached MAX_REGNUM groups, then this open
-                won't actually generate any code, so we'll have to
-                clear pending_exact explicitly.  */
-             pending_exact = 0;
-             break;
-
-
-           case ')':
-             if (syntax & RE_NO_BK_PARENS) goto normal_backslash;
-
-             if (COMPILE_STACK_EMPTY)
-             {
-               if (syntax & RE_UNMATCHED_RIGHT_PAREN_ORD)
-                 goto normal_backslash;
-               else
-                 return REG_ERPAREN;
-             }
-
-           handle_close:
-             if (fixup_alt_jump)
-               { /* Push a dummy failure point at the end of the
-                    alternative for a possible future
-                    `pop_failure_jump' to pop.  See comments at
-                    `push_dummy_failure' in `re_match_2'.  */
-                 BUF_PUSH (push_dummy_failure);
-
-                 /* We allocated space for this jump when we assigned
-                    to `fixup_alt_jump', in the `handle_alt' case below.  */
-                 STORE_JUMP (jump_past_alt, fixup_alt_jump, b - 1);
-               }
-
-             /* See similar code for backslashed left paren above.  */
-             if (COMPILE_STACK_EMPTY)
-             {
-               if (syntax & RE_UNMATCHED_RIGHT_PAREN_ORD)
-                 goto normal_char;
-               else
-                 return REG_ERPAREN;
-             }
-
-             /* Since we just checked for an empty stack above, this
-                ``can't happen''.  */
-             assert (compile_stack.avail != 0);
-             {
-               /* We don't just want to restore into `regnum', because
-                  later groups should continue to be numbered higher,
-                  as in `(ab)c(de)' -- the second group is #2.  */
-               regnum_t this_group_regnum;
-
-               compile_stack.avail--;
-               begalt = bufp->buffer + COMPILE_STACK_TOP.begalt_offset;
-               fixup_alt_jump
-                 = COMPILE_STACK_TOP.fixup_alt_jump
-                   ? bufp->buffer + COMPILE_STACK_TOP.fixup_alt_jump - 1
-                   : 0;
-               laststart = bufp->buffer + COMPILE_STACK_TOP.laststart_offset;
-               this_group_regnum = COMPILE_STACK_TOP.regnum;
-               /* If we've reached MAX_REGNUM groups, then this open
-                  won't actually generate any code, so we'll have to
-                  clear pending_exact explicitly.  */
-               pending_exact = 0;
-
-               /* We're at the end of the group, so now we know how many
-                  groups were inside this one.  */
-               if (this_group_regnum <= MAX_REGNUM)
-                 {
-                   unsigned char *inner_group_loc
-                     = bufp->buffer + COMPILE_STACK_TOP.inner_group_offset;
-
-                   *inner_group_loc = regnum - this_group_regnum;
-                   BUF_PUSH_3 (stop_memory, this_group_regnum,
-                               regnum - this_group_regnum);
-                 }
-             }
-             break;
-
-
-           case '|':                                   /* `\|'.  */
-             if (syntax & RE_LIMITED_OPS || syntax & RE_NO_BK_VBAR)
-               goto normal_backslash;
-           handle_alt:
-             if (syntax & RE_LIMITED_OPS)
-               goto normal_char;
-
-             /* Insert before the previous alternative a jump which
-                jumps to this alternative if the former fails.  */
-             GET_BUFFER_SPACE (3);
-             INSERT_JUMP (on_failure_jump, begalt, b + 6);
-             pending_exact = 0;
-             b += 3;
-
-             /* The alternative before this one has a jump after it
-                which gets executed if it gets matched.  Adjust that
-                jump so it will jump to this alternative's analogous
-                jump (put in below, which in turn will jump to the next
-                (if any) alternative's such jump, etc.).  The last such
-                jump jumps to the correct final destination.  A picture:
-                         _____ _____
-                         |   | |   |
-                         |   v |   v
-                        a | b   | c
-
-                If we are at `b', then fixup_alt_jump right now points to a
-                three-byte space after `a'.  We'll put in the jump, set
-                fixup_alt_jump to right after `b', and leave behind three
-                bytes which we'll fill in when we get to after `c'.  */
-
-             if (fixup_alt_jump)
-               STORE_JUMP (jump_past_alt, fixup_alt_jump, b);
-
-             /* Mark and leave space for a jump after this alternative,
-                to be filled in later either by next alternative or
-                when know we're at the end of a series of alternatives.  */
-             fixup_alt_jump = b;
-             GET_BUFFER_SPACE (3);
-             b += 3;
-
-             laststart = 0;
-             begalt = b;
-             break;
-
-
-           case '{':
-             /* If \{ is a literal.  */
-             if (!(syntax & RE_INTERVALS)
-                    /* If we're at `\{' and it's not the open-interval
-                       operator.  */
-                 || ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES))
-                 || (p - 2 == pattern  &&  p == pend))
-               goto normal_backslash;
-
-           handle_interval:
-             {
-               /* If got here, then the syntax allows intervals.  */
-
-               /* At least (most) this many matches must be made.  */
-               int lower_bound = -1, upper_bound = -1;
-
-               beg_interval = p - 1;
-
-               if (p == pend)
-                 {
-                   if (syntax & RE_NO_BK_BRACES)
-                     goto unfetch_interval;
-                   else
-                     return REG_EBRACE;
-                 }
-
-               GET_UNSIGNED_NUMBER (lower_bound);
-
-               if (c == ',')
-                 {
-                   GET_UNSIGNED_NUMBER (upper_bound);
-                   if (upper_bound < 0) upper_bound = RE_DUP_MAX;
-                 }
-               else
-                 /* Interval such as `{1}' => match exactly once. */
-                 upper_bound = lower_bound;
-
-               if (lower_bound < 0 || upper_bound > RE_DUP_MAX
-                   || lower_bound > upper_bound)
-                 {
-                   if (syntax & RE_NO_BK_BRACES)
-                     goto unfetch_interval;
-                   else
-                     return REG_BADBR;
-                 }
-
-               if (!(syntax & RE_NO_BK_BRACES))
-                 {
-                   if (c != '\\') return REG_EBRACE;
-
-                   PATFETCH (c);
-                 }
-
-               if (c != '}')
-                 {
-                   if (syntax & RE_NO_BK_BRACES)
-                     goto unfetch_interval;
-                   else
-                     return REG_BADBR;
-                 }
-
-               /* We just parsed a valid interval.  */
-
-               /* If it's invalid to have no preceding re.  */
-               if (!laststart)
-                 {
-                   if (syntax & RE_CONTEXT_INVALID_OPS)
-                     return REG_BADRPT;
-                   else if (syntax & RE_CONTEXT_INDEP_OPS)
-                     laststart = b;
-                   else
-                     goto unfetch_interval;
-                 }
-
-               /* If the upper bound is zero, don't want to succeed at
-                  all; jump from `laststart' to `b + 3', which will be
-                  the end of the buffer after we insert the jump.  */
-                if (upper_bound == 0)
-                  {
-                    GET_BUFFER_SPACE (3);
-                    INSERT_JUMP (jump, laststart, b + 3);
-                    b += 3;
-                  }
-
-                /* Otherwise, we have a nontrivial interval.  When
-                   we're all done, the pattern will look like:
-                     set_number_at <jump count> <upper bound>
-                     set_number_at <succeed_n count> <lower bound>
-                     succeed_n <after jump addr> <succeed_n count>
-                     <body of loop>
-                     jump_n <succeed_n addr> <jump count>
-                   (The upper bound and `jump_n' are omitted if
-                   `upper_bound' is 1, though.)  */
-                else
-                  { /* If the upper bound is > 1, we need to insert
-                       more at the end of the loop.  */
-                    unsigned nbytes = 10 + (upper_bound > 1) * 10;
-
-                    GET_BUFFER_SPACE (nbytes);
-
-                    /* Initialize lower bound of the `succeed_n', even
-                       though it will be set during matching by its
-                       attendant `set_number_at' (inserted next),
-                       because `re_compile_fastmap' needs to know.
-                       Jump to the `jump_n' we might insert below.  */
-                    INSERT_JUMP2 (succeed_n, laststart,
-                                  b + 5 + (upper_bound > 1) * 5,
-                                  lower_bound);
-                    b += 5;
-
-                    /* Code to initialize the lower bound.  Insert
-                       before the `succeed_n'.  The `5' is the last two
-                       bytes of this `set_number_at', plus 3 bytes of
-                       the following `succeed_n'.  */
-                    insert_op2 (set_number_at, laststart, 5, lower_bound, b);
-                    b += 5;
-
-                    if (upper_bound > 1)
-                      { /* More than one repetition is allowed, so
-                           append a backward jump to the `succeed_n'
-                           that starts this interval.
-
-                           When we've reached this during matching,
-                           we'll have matched the interval once, so
-                           jump back only `upper_bound - 1' times.  */
-                        STORE_JUMP2 (jump_n, b, laststart + 5,
-                                     upper_bound - 1);
-                        b += 5;
-
-                        /* The location we want to set is the second
-                           parameter of the `jump_n'; that is `b-2' as
-                           an absolute address.  `laststart' will be
-                           the `set_number_at' we're about to insert;
-                           `laststart+3' the number to set, the source
-                           for the relative address.  But we are
-                           inserting into the middle of the pattern --
-                           so everything is getting moved up by 5.
-                           Conclusion: (b - 2) - (laststart + 3) + 5,
-                           i.e., b - laststart.
-
-                           We insert this at the beginning of the loop
-                           so that if we fail during matching, we'll
-                           reinitialize the bounds.  */
-                        insert_op2 (set_number_at, laststart, b - laststart,
-                                    upper_bound - 1, b);
-                        b += 5;
-                      }
-                  }
-               pending_exact = 0;
-               beg_interval = NULL;
-             }
-             break;
-
-           unfetch_interval:
-             /* If an invalid interval, match the characters as literals.  */
-              assert (beg_interval);
-              p = beg_interval;
-              beg_interval = NULL;
-
-              /* normal_char and normal_backslash need `c'.  */
-              PATFETCH (c);
-
-              if (!(syntax & RE_NO_BK_BRACES))
-                {
-                  if (p > pattern  &&  p[-1] == '\\')
-                    goto normal_backslash;
-                }
-              goto normal_char;
-
-#ifdef emacs
-           /* There is no way to specify the before_dot and after_dot
-              operators.  rms says this is ok.  --karl  */
-           case '=':
-             BUF_PUSH (at_dot);
-             break;
-
-           case 's':
-             laststart = b;
-             PATFETCH (c);
-             BUF_PUSH_2 (syntaxspec, syntax_spec_code[c]);
-             break;
-
-           case 'S':
-             laststart = b;
-             PATFETCH (c);
-             BUF_PUSH_2 (notsyntaxspec, syntax_spec_code[c]);
-             break;
-#endif /* emacs */
-
-
-           case 'w':
-             laststart = b;
-             BUF_PUSH (wordchar);
-             break;
-
-
-           case 'W':
-             laststart = b;
-             BUF_PUSH (notwordchar);
-             break;
-
-
-           case '<':
-             BUF_PUSH (wordbeg);
-             break;
-
-           case '>':
-             BUF_PUSH (wordend);
-             break;
-
-           case 'b':
-             BUF_PUSH (wordbound);
-             break;
-
-           case 'B':
-             BUF_PUSH (notwordbound);
-             break;
-
-           case '`':
-             BUF_PUSH (begbuf);
-             break;
-
-           case '\'':
-             BUF_PUSH (endbuf);
-             break;
-
-           case '1': case '2': case '3': case '4': case '5':
-           case '6': case '7': case '8': case '9':
-             if (syntax & RE_NO_BK_REFS)
-               goto normal_char;
-
-             c1 = c - '0';
-
-             if (c1 > regnum)
-               return REG_ESUBREG;
-
-             /* Can't back reference to a subexpression if inside of it.  */
-             if (group_in_compile_stack (compile_stack, c1))
-               goto normal_char;
-
-             laststart = b;
-             BUF_PUSH_2 (duplicate, c1);
-             break;
-
-
-           case '+':
-           case '?':
-             if (syntax & RE_BK_PLUS_QM)
-               goto handle_plus;
-             else
-               goto normal_backslash;
-
-           default:
-           normal_backslash:
-             /* You might think it would be useful for \ to mean
-                not to translate; but if we don't translate it
-                it will never match anything.  */
-             c = TRANSLATE (c);
-             goto normal_char;
-           }
-         break;
-
-
-       default:
-       /* Expects the character in `c'.  */
-       normal_char:
-             /* If no exactn currently being built.  */
-         if (!pending_exact
-
-             /* If last exactn not at current position.  */
-             || pending_exact + *pending_exact + 1 != b
-
-             /* We have only one byte following the exactn for the count.  */
-             || *pending_exact == (1 << BYTEWIDTH) - 1
-
-             /* If followed by a repetition operator.  */
-             || *p == '*' || *p == '^'
-             || ((syntax & RE_BK_PLUS_QM)
-                 ? *p == '\\' && (p[1] == '+' || p[1] == '?')
-                 : (*p == '+' || *p == '?'))
-             || ((syntax & RE_INTERVALS)
-                 && ((syntax & RE_NO_BK_BRACES)
-                     ? *p == '{'
-                     : (p[0] == '\\' && p[1] == '{'))))
-           {
-             /* Start building a new exactn.  */
-
-             laststart = b;
-
-             BUF_PUSH_2 (exactn, 0);
-             pending_exact = b - 1;
-           }
-
-         BUF_PUSH (c);
-         (*pending_exact)++;
-         break;
-       } /* switch (c) */
-    } /* while p != pend */
-
-
-  /* Through the pattern now.  */
-
-  if (fixup_alt_jump)
-    STORE_JUMP (jump_past_alt, fixup_alt_jump, b);
-
-  if (!COMPILE_STACK_EMPTY)
-    return REG_EPAREN;
-
-  free (compile_stack.stack);
-
-  /* We have succeeded; set the length of the buffer.  */
-  bufp->used = b - bufp->buffer;
-
-#ifdef DEBUG
-  if (debug)
-    {
-      DEBUG_PRINT1 ("\nCompiled pattern: ");
-      print_compiled_pattern (bufp);
-    }
-#endif /* DEBUG */
-
-  return REG_NOERROR;
-} /* regex_compile */
-\f
-/* Subroutines for `regex_compile'.  */
-
-/* Store OP at LOC followed by two-byte integer parameter ARG.  */
-
-static void
-store_op1 (op, loc, arg)
-    re_opcode_t op;
-    unsigned char *loc;
-    int arg;
-{
-  *loc = (unsigned char) op;
-  STORE_NUMBER (loc + 1, arg);
-}
-
-
-/* Like `store_op1', but for two two-byte parameters ARG1 and ARG2.  */
-
-static void
-store_op2 (op, loc, arg1, arg2)
-    re_opcode_t op;
-    unsigned char *loc;
-    int arg1, arg2;
-{
-  *loc = (unsigned char) op;
-  STORE_NUMBER (loc + 1, arg1);
-  STORE_NUMBER (loc + 3, arg2);
-}
-
-
-/* Copy the bytes from LOC to END to open up three bytes of space at LOC
-   for OP followed by two-byte integer parameter ARG.  */
-
-static void
-insert_op1 (op, loc, arg, end)
-    re_opcode_t op;
-    unsigned char *loc;
-    int arg;
-    unsigned char *end;
-{
-  register unsigned char *pfrom = end;
-  register unsigned char *pto = end + 3;
-
-  while (pfrom != loc)
-    *--pto = *--pfrom;
-
-  store_op1 (op, loc, arg);
-}
-
-
-/* Like `insert_op1', but for two two-byte parameters ARG1 and ARG2.  */
-
-static void
-insert_op2 (op, loc, arg1, arg2, end)
-    re_opcode_t op;
-    unsigned char *loc;
-    int arg1, arg2;
-    unsigned char *end;
-{
-  register unsigned char *pfrom = end;
-  register unsigned char *pto = end + 5;
-
-  while (pfrom != loc)
-    *--pto = *--pfrom;
-
-  store_op2 (op, loc, arg1, arg2);
-}
-
-
-/* P points to just after a ^ in PATTERN.  Return true if that ^ comes
-   after an alternative or a begin-subexpression.  We assume there is at
-   least one character before the ^.  */
-
-static boolean
-at_begline_loc_p (pattern, p, syntax)
-    const char *pattern, *p;
-    reg_syntax_t syntax;
-{
-  const char *prev = p - 2;
-  boolean prev_prev_backslash = prev > pattern && prev[-1] == '\\';
-
-  return
-       /* After a subexpression?  */
-       (*prev == '(' && (syntax & RE_NO_BK_PARENS || prev_prev_backslash))
-       /* After an alternative?  */
-    || (*prev == '|' && (syntax & RE_NO_BK_VBAR || prev_prev_backslash));
-}
-
-
-/* The dual of at_begline_loc_p.  This one is for $.  We assume there is
-   at least one character after the $, i.e., `P < PEND'.  */
-
-static boolean
-at_endline_loc_p (p, pend, syntax)
-    const char *p, *pend;
-    int syntax;
-{
-  const char *next = p;
-  boolean next_backslash = *next == '\\';
-  const char *next_next = p + 1 < pend ? p + 1 : NULL;
-
-  return
-       /* Before a subexpression?  */
-       (syntax & RE_NO_BK_PARENS ? *next == ')'
-       : next_backslash && next_next && *next_next == ')')
-       /* Before an alternative?  */
-    || (syntax & RE_NO_BK_VBAR ? *next == '|'
-       : next_backslash && next_next && *next_next == '|');
-}
-
-
-/* Returns true if REGNUM is in one of COMPILE_STACK's elements and
-   false if it's not.  */
-
-static boolean
-group_in_compile_stack (compile_stack, regnum)
-    compile_stack_type compile_stack;
-    regnum_t regnum;
-{
-  int this_element;
-
-  for (this_element = compile_stack.avail - 1;
-       this_element >= 0;
-       this_element--)
-    if (compile_stack.stack[this_element].regnum == regnum)
-      return true;
-
-  return false;
-}
-
-
-/* Read the ending character of a range (in a bracket expression) from the
-   uncompiled pattern *P_PTR (which ends at PEND).  We assume the
-   starting character is in `P[-2]'.  (`P[-1]' is the character `-'.)
-   Then we set the translation of all bits between the starting and
-   ending characters (inclusive) in the compiled pattern B.
-
-   Return an error code.
-
-   We use these short variable names so we can use the same macros as
-   `regex_compile' itself.  */
-
-static reg_errcode_t
-compile_range (p_ptr, pend, translate, syntax, b)
-    const char **p_ptr, *pend;
-    char *translate;
-    reg_syntax_t syntax;
-    unsigned char *b;
-{
-  unsigned this_char;
-
-  const char *p = *p_ptr;
-  int range_start, range_end;
-
-  if (p == pend)
-    return REG_ERANGE;
-
-  /* Even though the pattern is a signed `char *', we need to fetch
-     with unsigned char *'s; if the high bit of the pattern character
-     is set, the range endpoints will be negative if we fetch using a
-     signed char *.
-
-     We also want to fetch the endpoints without translating them; the
-     appropriate translation is done in the bit-setting loop below.  */
-  range_start = ((unsigned char *) p)[-2];
-  range_end   = ((unsigned char *) p)[0];
-
-  /* Have to increment the pointer into the pattern string, so the
-     caller isn't still at the ending character.  */
-  (*p_ptr)++;
-
-  /* If the start is after the end, the range is empty.  */
-  if (range_start > range_end)
-    return syntax & RE_NO_EMPTY_RANGES ? REG_ERANGE : REG_NOERROR;
-
-  /* Here we see why `this_char' has to be larger than an `unsigned
-     char' -- the range is inclusive, so if `range_end' == 0xff
-     (assuming 8-bit characters), we would otherwise go into an infinite
-     loop, since all characters <= 0xff.  */
-  for (this_char = range_start; this_char <= range_end; this_char++)
-    {
-      SET_LIST_BIT (TRANSLATE (this_char));
-    }
-
-  return REG_NOERROR;
-}
-\f
-/* Failure stack declarations and macros; both re_compile_fastmap and
-   re_match_2 use a failure stack.  These have to be macros because of
-   REGEX_ALLOCATE.  */
-
-
-/* Number of failure points for which to initially allocate space
-   when matching.  If this number is exceeded, we allocate more
-   space, so it is not a hard limit.  */
-#ifndef INIT_FAILURE_ALLOC
-#define INIT_FAILURE_ALLOC 5
-#endif
-
-/* Roughly the maximum number of failure points on the stack.  Would be
-   exactly that if always used MAX_FAILURE_SPACE each time we failed.
-   This is a variable only so users of regex can assign to it; we never
-   change it ourselves.  */
-int re_max_failures = 2000;
-
-typedef const unsigned char *fail_stack_elt_t;
-
-typedef struct
-{
-  fail_stack_elt_t *stack;
-  unsigned size;
-  unsigned avail;                      /* Offset of next open position.  */
-} fail_stack_type;
-
-#define FAIL_STACK_EMPTY()     (fail_stack.avail == 0)
-#define FAIL_STACK_PTR_EMPTY() (fail_stack_ptr->avail == 0)
-#define FAIL_STACK_FULL()      (fail_stack.avail == fail_stack.size)
-#define FAIL_STACK_TOP()       (fail_stack.stack[fail_stack.avail])
-
-
-/* Initialize `fail_stack'.  Do `return -2' if the alloc fails.  */
-
-#define INIT_FAIL_STACK()                                              \
-  do {                                                                 \
-    fail_stack.stack = (fail_stack_elt_t *)                            \
-      REGEX_ALLOCATE (INIT_FAILURE_ALLOC * sizeof (fail_stack_elt_t)); \
-                                                                       \
-    if (fail_stack.stack == NULL)                                      \
-      return -2;                                                       \
-                                                                       \
-    fail_stack.size = INIT_FAILURE_ALLOC;                              \
-    fail_stack.avail = 0;                                              \
-  } while (0)
-
-
-/* Double the size of FAIL_STACK, up to approximately `re_max_failures' items.
-
-   Return 1 if succeeds, and 0 if either ran out of memory
-   allocating space for it or it was already too large.
-
-   REGEX_REALLOCATE requires `destination' be declared.   */
-
-#define DOUBLE_FAIL_STACK(fail_stack)                                  \
-  ((fail_stack).size > re_max_failures * MAX_FAILURE_ITEMS             \
-   ? 0                                                                 \
-   : ((fail_stack).stack = (fail_stack_elt_t *)                                \
-       REGEX_REALLOCATE ((fail_stack).stack,                           \
-         (fail_stack).size * sizeof (fail_stack_elt_t),                \
-         ((fail_stack).size << 1) * sizeof (fail_stack_elt_t)),        \
-                                                                       \
-      (fail_stack).stack == NULL                                       \
-      ? 0                                                              \
-      : ((fail_stack).size <<= 1,                                      \
-        1)))
-
-
-/* Push PATTERN_OP on FAIL_STACK.
-
-   Return 1 if was able to do so and 0 if ran out of memory allocating
-   space to do so.  */
-#define PUSH_PATTERN_OP(pattern_op, fail_stack)                                \
-  ((FAIL_STACK_FULL ()                                                 \
-    && !DOUBLE_FAIL_STACK (fail_stack))                                        \
-    ? 0                                                                        \
-    : ((fail_stack).stack[(fail_stack).avail++] = pattern_op,          \
-       1))
-
-/* This pushes an item onto the failure stack.  Must be a four-byte
-   value.  Assumes the variable `fail_stack'.  Probably should only
-   be called from within `PUSH_FAILURE_POINT'.  */
-#define PUSH_FAILURE_ITEM(item)                                                \
-  fail_stack.stack[fail_stack.avail++] = (fail_stack_elt_t) item
-
-/* The complement operation.  Assumes `fail_stack' is nonempty.  */
-#define POP_FAILURE_ITEM() fail_stack.stack[--fail_stack.avail]
-
-/* Used to omit pushing failure point id's when we're not debugging.  */
-#ifdef DEBUG
-#define DEBUG_PUSH PUSH_FAILURE_ITEM
-#define DEBUG_POP(item_addr) *(item_addr) = POP_FAILURE_ITEM ()
-#else
-#define DEBUG_PUSH(item)
-#define DEBUG_POP(item_addr)
-#endif
-
-
-/* Push the information about the state we will need
-   if we ever fail back to it.
-
-   Requires variables fail_stack, regstart, regend, reg_info, and
-   num_regs be declared.  DOUBLE_FAIL_STACK requires `destination' be
-   declared.
-
-   Does `return FAILURE_CODE' if runs out of memory.  */
-
-#define PUSH_FAILURE_POINT(pattern_place, string_place, failure_code)  \
-  do {                                                                 \
-    char *destination;                                                 \
-    /* Must be int, so when we don't save any registers, the arithmetic        \
-       of 0 + -1 isn't done as unsigned.  */                           \
-    int this_reg;                                                      \
-                                                                       \
-    DEBUG_STATEMENT (failure_id++);                                    \
-    DEBUG_STATEMENT (nfailure_points_pushed++);                                \
-    DEBUG_PRINT2 ("\nPUSH_FAILURE_POINT #%u:\n", failure_id);          \
-    DEBUG_PRINT2 ("  Before push, next avail: %d\n", (fail_stack).avail);\
-    DEBUG_PRINT2 ("                     size: %d\n", (fail_stack).size);\
-                                                                       \
-    DEBUG_PRINT2 ("  slots needed: %d\n", NUM_FAILURE_ITEMS);          \
-    DEBUG_PRINT2 ("     available: %d\n", REMAINING_AVAIL_SLOTS);      \
-                                                                       \
-    /* Ensure we have enough space allocated for what we will push.  */        \
-    while (REMAINING_AVAIL_SLOTS < NUM_FAILURE_ITEMS)                  \
-      {                                                                        \
-       if (!DOUBLE_FAIL_STACK (fail_stack))                    \
-         return failure_code;                                          \
-                                                                       \
-       DEBUG_PRINT2 ("\n  Doubled stack; size now: %d\n",              \
-                      (fail_stack).size);                              \
-       DEBUG_PRINT2 ("  slots available: %d\n", REMAINING_AVAIL_SLOTS);\
-      }                                                                        \
-                                                                       \
-    /* Push the info, starting with the registers.  */                 \
-    DEBUG_PRINT1 ("\n");                                               \
-                                                                       \
-    for (this_reg = lowest_active_reg; this_reg <= highest_active_reg; \
-        this_reg++)                                                    \
-      {                                                                        \
-       DEBUG_PRINT2 ("  Pushing reg: %d\n", this_reg);                 \
-       DEBUG_STATEMENT (num_regs_pushed++);                            \
-                                                                       \
-       DEBUG_PRINT2 ("    start: 0x%x\n", regstart[this_reg]);         \
-       PUSH_FAILURE_ITEM (regstart[this_reg]);                         \
-                                                                       \
-       DEBUG_PRINT2 ("    end: 0x%x\n", regend[this_reg]);             \
-       PUSH_FAILURE_ITEM (regend[this_reg]);                           \
-                                                                       \
-       DEBUG_PRINT2 ("    info: 0x%x\n      ", reg_info[this_reg]);    \
-       DEBUG_PRINT2 (" match_null=%d",                                 \
-                     REG_MATCH_NULL_STRING_P (reg_info[this_reg]));    \
-       DEBUG_PRINT2 (" active=%d", IS_ACTIVE (reg_info[this_reg]));    \
-       DEBUG_PRINT2 (" matched_something=%d",                          \
-                     MATCHED_SOMETHING (reg_info[this_reg]));          \
-       DEBUG_PRINT2 (" ever_matched=%d",                               \
-                     EVER_MATCHED_SOMETHING (reg_info[this_reg]));     \
-       DEBUG_PRINT1 ("\n");                                            \
-       PUSH_FAILURE_ITEM (reg_info[this_reg].word);                    \
-      }                                                                        \
-                                                                       \
-    DEBUG_PRINT2 ("  Pushing  low active reg: %d\n", lowest_active_reg);\
-    PUSH_FAILURE_ITEM (lowest_active_reg);                             \
-                                                                       \
-    DEBUG_PRINT2 ("  Pushing high active reg: %d\n", highest_active_reg);\
-    PUSH_FAILURE_ITEM (highest_active_reg);                            \
-                                                                       \
-    DEBUG_PRINT2 ("  Pushing pattern 0x%x: ", pattern_place);          \
-    DEBUG_PRINT_COMPILED_PATTERN (bufp, pattern_place, pend);          \
-    PUSH_FAILURE_ITEM (pattern_place);                                 \
-                                                                       \
-    DEBUG_PRINT2 ("  Pushing string 0x%x: `", string_place);           \
-    DEBUG_PRINT_DOUBLE_STRING (string_place, string1, size1, string2,   \
-                                size2);                                \
-    DEBUG_PRINT1 ("'\n");                                              \
-    PUSH_FAILURE_ITEM (string_place);                                  \
-                                                                       \
-    DEBUG_PRINT2 ("  Pushing failure id: %u\n", failure_id);           \
-    DEBUG_PUSH (failure_id);                                           \
-  } while (0)
-
-/* This is the number of items that are pushed and popped on the stack
-   for each register.  */
-#define NUM_REG_ITEMS  3
-
-/* Individual items aside from the registers.  */
-#ifdef DEBUG
-#define NUM_NONREG_ITEMS 5 /* Includes failure point id.  */
-#else
-#define NUM_NONREG_ITEMS 4
-#endif
-
-/* We push at most this many items on the stack.  */
-#define MAX_FAILURE_ITEMS ((num_regs - 1) * NUM_REG_ITEMS + NUM_NONREG_ITEMS)
-
-/* We actually push this many items.  */
-#define NUM_FAILURE_ITEMS                                              \
-  ((highest_active_reg - lowest_active_reg + 1) * NUM_REG_ITEMS        \
-    + NUM_NONREG_ITEMS)
-
-/* How many items can still be added to the stack without overflowing it.  */
-#define REMAINING_AVAIL_SLOTS ((fail_stack).size - (fail_stack).avail)
-
-
-/* Pops what PUSH_FAIL_STACK pushes.
-
-   We restore into the parameters, all of which should be lvalues:
-     STR -- the saved data position.
-     PAT -- the saved pattern position.
-     LOW_REG, HIGH_REG -- the highest and lowest active registers.
-     REGSTART, REGEND -- arrays of string positions.
-     REG_INFO -- array of information about each subexpression.
-
-   Also assumes the variables `fail_stack' and (if debugging), `bufp',
-   `pend', `string1', `size1', `string2', and `size2'.  */
-
-#define POP_FAILURE_POINT(str, pat, low_reg, high_reg, regstart, regend, reg_info)\
-{                                                                      \
-  DEBUG_STATEMENT (fail_stack_elt_t failure_id;)                       \
-  int this_reg;                                                                \
-  const unsigned char *string_temp;                                    \
-                                                                       \
-  assert (!FAIL_STACK_EMPTY ());                                       \
-                                                                       \
-  /* Remove failure points and point to how many regs pushed.  */      \
-  DEBUG_PRINT1 ("POP_FAILURE_POINT:\n");                               \
-  DEBUG_PRINT2 ("  Before pop, next avail: %d\n", fail_stack.avail);   \
-  DEBUG_PRINT2 ("                    size: %d\n", fail_stack.size);    \
-                                                                       \
-  assert (fail_stack.avail >= NUM_NONREG_ITEMS);                       \
-                                                                       \
-  DEBUG_POP (&failure_id);                                             \
-  DEBUG_PRINT2 ("  Popping failure id: %u\n", failure_id);             \
-                                                                       \
-  /* If the saved string location is NULL, it came from an             \
-     on_failure_keep_string_jump opcode, and we want to throw away the \
-     saved NULL, thus retaining our current position in the string.  */        \
-  string_temp = POP_FAILURE_ITEM ();                                   \
-  if (string_temp != NULL)                                             \
-    str = (const char *) string_temp;                                  \
-                                                                       \
-  DEBUG_PRINT2 ("  Popping string 0x%x: `", str);                      \
-  DEBUG_PRINT_DOUBLE_STRING (str, string1, size1, string2, size2);     \
-  DEBUG_PRINT1 ("'\n");                                                        \
-                                                                       \
-  pat = (unsigned char *) POP_FAILURE_ITEM ();                         \
-  DEBUG_PRINT2 ("  Popping pattern 0x%x: ", pat);                      \
-  DEBUG_PRINT_COMPILED_PATTERN (bufp, pat, pend);                      \
-                                                                       \
-  /* Restore register info.  */                                                \
-  high_reg = (unsigned) POP_FAILURE_ITEM ();                           \
-  DEBUG_PRINT2 ("  Popping high active reg: %d\n", high_reg);          \
-                                                                       \
-  low_reg = (unsigned) POP_FAILURE_ITEM ();                            \
-  DEBUG_PRINT2 ("  Popping  low active reg: %d\n", low_reg);           \
-                                                                       \
-  for (this_reg = high_reg; this_reg >= low_reg; this_reg--)           \
-    {                                                                  \
-      DEBUG_PRINT2 ("    Popping reg: %d\n", this_reg);                        \
-                                                                       \
-      reg_info[this_reg].word = POP_FAILURE_ITEM ();                   \
-      DEBUG_PRINT2 ("      info: 0x%x\n", reg_info[this_reg]);         \
-                                                                       \
-      regend[this_reg] = (const char *) POP_FAILURE_ITEM ();           \
-      DEBUG_PRINT2 ("      end: 0x%x\n", regend[this_reg]);            \
-                                                                       \
-      regstart[this_reg] = (const char *) POP_FAILURE_ITEM ();         \
-      DEBUG_PRINT2 ("      start: 0x%x\n", regstart[this_reg]);                \
-    }                                                                  \
-                                                                       \
-  DEBUG_STATEMENT (nfailure_points_popped++);                          \
-} /* POP_FAILURE_POINT */
-\f
-/* re_compile_fastmap computes a ``fastmap'' for the compiled pattern in
-   BUFP.  A fastmap records which of the (1 << BYTEWIDTH) possible
-   characters can start a string that matches the pattern.  This fastmap
-   is used by re_search to skip quickly over impossible starting points.
-
-   The caller must supply the address of a (1 << BYTEWIDTH)-byte data
-   area as BUFP->fastmap.
-
-   We set the `fastmap', `fastmap_accurate', and `can_be_null' fields in
-   the pattern buffer.
-
-   Returns 0 if we succeed, -2 if an internal error.   */
-
-int
-re_compile_fastmap (bufp)
-     struct re_pattern_buffer *bufp;
-{
-  int j, k;
-  fail_stack_type fail_stack;
-#ifndef REGEX_MALLOC
-  char *destination;
-#endif
-  /* We don't push any register information onto the failure stack.  */
-  unsigned num_regs = 0;
-
-  register char *fastmap = bufp->fastmap;
-  unsigned char *pattern = bufp->buffer;
-  unsigned long size = bufp->used;
-  const unsigned char *p = pattern;
-  register unsigned char *pend = pattern + size;
-
-  /* Assume that each path through the pattern can be null until
-     proven otherwise.  We set this false at the bottom of switch
-     statement, to which we get only if a particular path doesn't
-     match the empty string.  */
-  boolean path_can_be_null = true;
-
-  /* We aren't doing a `succeed_n' to begin with.  */
-  boolean succeed_n_p = false;
-
-  assert (fastmap != NULL && p != NULL);
-
-  INIT_FAIL_STACK ();
-  bzero (fastmap, 1 << BYTEWIDTH);  /* Assume nothing's valid.  */
-  bufp->fastmap_accurate = 1;      /* It will be when we're done.  */
-  bufp->can_be_null = 0;
-
-  while (p != pend || !FAIL_STACK_EMPTY ())
-    {
-      if (p == pend)
-       {
-         bufp->can_be_null |= path_can_be_null;
-
-         /* Reset for next path.  */
-         path_can_be_null = true;
-
-         p = fail_stack.stack[--fail_stack.avail];
-       }
-
-      /* We should never be about to go beyond the end of the pattern.  */
-      assert (p < pend);
-
-#ifdef SWITCH_ENUM_BUG
-      switch ((int) ((re_opcode_t) *p++))
-#else
-      switch ((re_opcode_t) *p++)
-#endif
-       {
-
-       /* I guess the idea here is to simply not bother with a fastmap
-          if a backreference is used, since it's too hard to figure out
-          the fastmap for the corresponding group.  Setting
-          `can_be_null' stops `re_search_2' from using the fastmap, so
-          that is all we do.  */
-       case duplicate:
-         bufp->can_be_null = 1;
-         return 0;
-
-
-      /* Following are the cases which match a character.  These end
-        with `break'.  */
-
-       case exactn:
-         fastmap[p[1]] = 1;
-         break;
-
-
-       case charset:
-         for (j = *p++ * BYTEWIDTH - 1; j >= 0; j--)
-           if (p[j / BYTEWIDTH] & (1 << (j % BYTEWIDTH)))
-             fastmap[j] = 1;
-         break;
-
-
-       case charset_not:
-         /* Chars beyond end of map must be allowed.  */
-         for (j = *p * BYTEWIDTH; j < (1 << BYTEWIDTH); j++)
-           fastmap[j] = 1;
-
-         for (j = *p++ * BYTEWIDTH - 1; j >= 0; j--)
-           if (!(p[j / BYTEWIDTH] & (1 << (j % BYTEWIDTH))))
-             fastmap[j] = 1;
-         break;
-
-
-       case wordchar:
-         for (j = 0; j < (1 << BYTEWIDTH); j++)
-           if (SYNTAX (j) == Sword)
-             fastmap[j] = 1;
-         break;
-
-
-       case notwordchar:
-         for (j = 0; j < (1 << BYTEWIDTH); j++)
-           if (SYNTAX (j) != Sword)
-             fastmap[j] = 1;
-         break;
-
-
-       case anychar:
-         /* `.' matches anything ...  */
-         for (j = 0; j < (1 << BYTEWIDTH); j++)
-           fastmap[j] = 1;
-
-         /* ... except perhaps newline.  */
-         if (!(bufp->syntax & RE_DOT_NEWLINE))
-           fastmap['\n'] = 0;
-
-         /* Return if we have already set `can_be_null'; if we have,
-            then the fastmap is irrelevant.  Something's wrong here.  */
-         else if (bufp->can_be_null)
-           return 0;
-
-         /* Otherwise, have to check alternative paths.  */
-         break;
-
-
-#ifdef emacs
-       case syntaxspec:
-         k = *p++;
-         for (j = 0; j < (1 << BYTEWIDTH); j++)
-           if (SYNTAX (j) == (enum syntaxcode) k)
-             fastmap[j] = 1;
-         break;
-
-
-       case notsyntaxspec:
-         k = *p++;
-         for (j = 0; j < (1 << BYTEWIDTH); j++)
-           if (SYNTAX (j) != (enum syntaxcode) k)
-             fastmap[j] = 1;
-         break;
-
-
-      /* All cases after this match the empty string.  These end with
-        `continue'.  */
-
-
-       case before_dot:
-       case at_dot:
-       case after_dot:
-         continue;
-#endif /* not emacs */
-
-
-       case no_op:
-       case begline:
-       case endline:
-       case begbuf:
-       case endbuf:
-       case wordbound:
-       case notwordbound:
-       case wordbeg:
-       case wordend:
-       case push_dummy_failure:
-         continue;
-
-
-       case jump_n:
-       case pop_failure_jump:
-       case maybe_pop_jump:
-       case jump:
-       case jump_past_alt:
-       case dummy_failure_jump:
-         EXTRACT_NUMBER_AND_INCR (j, p);
-         p += j;
-         if (j > 0)
-           continue;
-
-         /* Jump backward implies we just went through the body of a
-            loop and matched nothing.  Opcode jumped to should be
-            `on_failure_jump' or `succeed_n'.  Just treat it like an
-            ordinary jump.  For a * loop, it has pushed its failure
-            point already; if so, discard that as redundant.  */
-         if ((re_opcode_t) *p != on_failure_jump
-             && (re_opcode_t) *p != succeed_n)
-           continue;
-
-         p++;
-         EXTRACT_NUMBER_AND_INCR (j, p);
-         p += j;
-
-         /* If what's on the stack is where we are now, pop it.  */
-         if (!FAIL_STACK_EMPTY ()
-             && fail_stack.stack[fail_stack.avail - 1] == p)
-           fail_stack.avail--;
-
-         continue;
-
-
-       case on_failure_jump:
-       case on_failure_keep_string_jump:
-       handle_on_failure_jump:
-         EXTRACT_NUMBER_AND_INCR (j, p);
-
-         /* For some patterns, e.g., `(a?)?', `p+j' here points to the
-            end of the pattern.  We don't want to push such a point,
-            since when we restore it above, entering the switch will
-            increment `p' past the end of the pattern.  We don't need
-            to push such a point since we obviously won't find any more
-            fastmap entries beyond `pend'.  Such a pattern can match
-            the null string, though.  */
-         if (p + j < pend)
-           {
-             if (!PUSH_PATTERN_OP (p + j, fail_stack))
-               return -2;
-           }
-         else
-           bufp->can_be_null = 1;
-
-         if (succeed_n_p)
-           {
-             EXTRACT_NUMBER_AND_INCR (k, p);   /* Skip the n.  */
-             succeed_n_p = false;
-           }
-
-         continue;
-
-
-       case succeed_n:
-         /* Get to the number of times to succeed.  */
-         p += 2;
-
-         /* Increment p past the n for when k != 0.  */
-         EXTRACT_NUMBER_AND_INCR (k, p);
-         if (k == 0)
-           {
-             p -= 4;
-             succeed_n_p = true;  /* Spaghetti code alert.  */
-             goto handle_on_failure_jump;
-           }
-         continue;
-
-
-       case set_number_at:
-         p += 4;
-         continue;
-
-
-       case start_memory:
-       case stop_memory:
-         p += 2;
-         continue;
-
-
-       default:
-         abort (); /* We have listed all the cases.  */
-       } /* switch *p++ */
-
-      /* Getting here means we have found the possible starting
-        characters for one path of the pattern -- and that the empty
-        string does not match.  We need not follow this path further.
-        Instead, look at the next alternative (remembered on the
-        stack), or quit if no more.  The test at the top of the loop
-        does these things.  */
-      path_can_be_null = false;
-      p = pend;
-    } /* while p */
-
-  /* Set `can_be_null' for the last path (also the first path, if the
-     pattern is empty).  */
-  bufp->can_be_null |= path_can_be_null;
-  return 0;
-} /* re_compile_fastmap */
-\f
-/* Set REGS to hold NUM_REGS registers, storing them in STARTS and
-   ENDS.  Subsequent matches using PATTERN_BUFFER and REGS will use
-   this memory for recording register information.  STARTS and ENDS
-   must be allocated using the malloc library routine, and must each
-   be at least NUM_REGS * sizeof (regoff_t) bytes long.
-
-   If NUM_REGS == 0, then subsequent matches should allocate their own
-   register data.
-
-   Unless this function is called, the first search or match using
-   PATTERN_BUFFER will allocate its own register data, without
-   freeing the old data.  */
-
-void
-re_set_registers (bufp, regs, num_regs, starts, ends)
-    struct re_pattern_buffer *bufp;
-    struct re_registers *regs;
-    unsigned num_regs;
-    regoff_t *starts, *ends;
-{
-  if (num_regs)
-    {
-      bufp->regs_allocated = REGS_REALLOCATE;
-      regs->num_regs = num_regs;
-      regs->start = starts;
-      regs->end = ends;
-    }
-  else
-    {
-      bufp->regs_allocated = REGS_UNALLOCATED;
-      regs->num_regs = 0;
-      regs->start = regs->end = (regoff_t) 0;
-    }
-}
-\f
-/* Searching routines.  */
-
-/* Like re_search_2, below, but only one string is specified, and
-   doesn't let you say where to stop matching. */
-
-int
-re_search (bufp, string, size, startpos, range, regs)
-     struct re_pattern_buffer *bufp;
-     const char *string;
-     int size, startpos, range;
-     struct re_registers *regs;
-{
-  return re_search_2 (bufp, NULL, 0, string, size, startpos, range,
-                     regs, size);
-}
-
-
-/* Using the compiled pattern in BUFP->buffer, first tries to match the
-   virtual concatenation of STRING1 and STRING2, starting first at index
-   STARTPOS, then at STARTPOS + 1, and so on.
-
-   STRING1 and STRING2 have length SIZE1 and SIZE2, respectively.
-
-   RANGE is how far to scan while trying to match.  RANGE = 0 means try
-   only at STARTPOS; in general, the last start tried is STARTPOS +
-   RANGE.
-
-   In REGS, return the indices of the virtual concatenation of STRING1
-   and STRING2 that matched the entire BUFP->buffer and its contained
-   subexpressions.
-
-   Do not consider matching one past the index STOP in the virtual
-   concatenation of STRING1 and STRING2.
-
-   We return either the position in the strings at which the match was
-   found, -1 if no match, or -2 if error (such as failure
-   stack overflow).  */
-
-int
-re_search_2 (bufp, string1, size1, string2, size2, startpos, range, regs, stop)
-     struct re_pattern_buffer *bufp;
-     const char *string1, *string2;
-     int size1, size2;
-     int startpos;
-     int range;
-     struct re_registers *regs;
-     int stop;
-{
-  int val;
-  register char *fastmap = bufp->fastmap;
-  register char *translate = bufp->translate;
-  int total_size = size1 + size2;
-  int endpos = startpos + range;
-
-  /* Check for out-of-range STARTPOS.  */
-  if (startpos < 0 || startpos > total_size)
-    return -1;
-
-  /* Fix up RANGE if it might eventually take us outside
-     the virtual concatenation of STRING1 and STRING2.  */
-  if (endpos < -1)
-    range = -1 - startpos;
-  else if (endpos > total_size)
-    range = total_size - startpos;
-
-  /* If the search isn't to be a backwards one, don't waste time in a
-     search for a pattern that must be anchored.  */
-  if (bufp->used > 0 && (re_opcode_t) bufp->buffer[0] == begbuf && range > 0)
-    {
-      if (startpos > 0)
-       return -1;
-      else
-       range = 1;
-    }
-
-  /* Update the fastmap now if not correct already.  */
-  if (fastmap && !bufp->fastmap_accurate)
-    if (re_compile_fastmap (bufp) == -2)
-      return -2;
-
-  /* Loop through the string, looking for a place to start matching.  */
-  for (;;)
-    {
-      /* If a fastmap is supplied, skip quickly over characters that
-        cannot be the start of a match.  If the pattern can match the
-        null string, however, we don't need to skip characters; we want
-        the first null string.  */
-      if (fastmap && startpos < total_size && !bufp->can_be_null)
-       {
-         if (range > 0)        /* Searching forwards.  */
-           {
-             register const char *d;
-             register int lim = 0;
-             int irange = range;
-
-             if (startpos < size1 && startpos + range >= size1)
-               lim = range - (size1 - startpos);
-
-             d = (startpos >= size1 ? string2 - size1 : string1) + startpos;
-
-             /* Written out as an if-else to avoid testing `translate'
-                inside the loop.  */
-             if (translate)
-               while (range > lim
-                      && !fastmap[(unsigned char)
-                                  translate[(unsigned char) *d++]])
-                 range--;
-             else
-               while (range > lim && !fastmap[(unsigned char) *d++])
-                 range--;
-
-             startpos += irange - range;
-           }
-         else                          /* Searching backwards.  */
-           {
-             register char c = (size1 == 0 || startpos >= size1
-                                ? string2[startpos - size1]
-                                : string1[startpos]);
-
-             if (!fastmap[(unsigned char) TRANSLATE (c)])
-               goto advance;
-           }
-       }
-
-      /* If can't match the null string, and that's all we have left, fail.  */
-      if (range >= 0 && startpos == total_size && fastmap
-         && !bufp->can_be_null)
-       return -1;
-
-      val = re_match_2 (bufp, string1, size1, string2, size2,
-                       startpos, regs, stop);
-      if (val >= 0)
-       return startpos;
-
-      if (val == -2)
-       return -2;
-
-    advance:
-      if (!range)
-       break;
-      else if (range > 0)
-       {
-         range--;
-         startpos++;
-       }
-      else
-       {
-         range++;
-         startpos--;
-       }
-    }
-  return -1;
-} /* re_search_2 */
-\f
-/* Declarations and macros for re_match_2.  */
-
-static int bcmp_translate ();
-static boolean alt_match_null_string_p (),
-              common_op_match_null_string_p (),
-              group_match_null_string_p ();
-
-/* Structure for per-register (a.k.a. per-group) information.
-   This must not be longer than one word, because we push this value
-   onto the failure stack.  Other register information, such as the
-   starting and ending positions (which are addresses), and the list of
-   inner groups (which is a bits list) are maintained in separate
-   variables.
-
-   We are making a (strictly speaking) nonportable assumption here: that
-   the compiler will pack our bit fields into something that fits into
-   the type of `word', i.e., is something that fits into one item on the
-   failure stack.  */
-typedef union
-{
-  fail_stack_elt_t word;
-  struct
-  {
-      /* This field is one if this group can match the empty string,
-        zero if not.  If not yet determined,  `MATCH_NULL_UNSET_VALUE'.  */
-#define MATCH_NULL_UNSET_VALUE 3
-    unsigned match_null_string_p : 2;
-    unsigned is_active : 1;
-    unsigned matched_something : 1;
-    unsigned ever_matched_something : 1;
-  } bits;
-} register_info_type;
-
-#define REG_MATCH_NULL_STRING_P(R)  ((R).bits.match_null_string_p)
-#define IS_ACTIVE(R)  ((R).bits.is_active)
-#define MATCHED_SOMETHING(R)  ((R).bits.matched_something)
-#define EVER_MATCHED_SOMETHING(R)  ((R).bits.ever_matched_something)
-
-
-/* Call this when have matched a real character; it sets `matched' flags
-   for the subexpressions which we are currently inside.  Also records
-   that those subexprs have matched.  */
-#define SET_REGS_MATCHED()                                             \
-  do                                                                   \
-    {                                                                  \
-      unsigned r;                                                      \
-      for (r = lowest_active_reg; r <= highest_active_reg; r++)                \
-       {                                                               \
-         MATCHED_SOMETHING (reg_info[r])                               \
-           = EVER_MATCHED_SOMETHING (reg_info[r])                      \
-           = 1;                                                        \
-       }                                                               \
-    }                                                                  \
-  while (0)
-
-
-/* This converts PTR, a pointer into one of the search strings `string1'
-   and `string2' into an offset from the beginning of that string.  */
-#define POINTER_TO_OFFSET(ptr)                                         \
-  (FIRST_STRING_P (ptr) ? (ptr) - string1 : (ptr) - string2 + size1)
-
-/* Registers are set to a sentinel when they haven't yet matched.  */
-#define REG_UNSET_VALUE ((char *) -1)
-#define REG_UNSET(e) ((e) == REG_UNSET_VALUE)
-
-
-/* Macros for dealing with the split strings in re_match_2.  */
-
-#define MATCHING_IN_FIRST_STRING  (dend == end_match_1)
-
-/* Call before fetching a character with *d.  This switches over to
-   string2 if necessary.  */
-#define PREFETCH()                                                     \
-  while (d == dend)                                                    \
-    {                                                                  \
-      /* End of string2 => fail.  */                                   \
-      if (dend == end_match_2)                                                 \
-       goto fail;                                                      \
-      /* End of string1 => advance to string2.  */                     \
-      d = string2;                                                     \
-      dend = end_match_2;                                              \
-    }
-
-
-/* Test if at very beginning or at very end of the virtual concatenation
-   of `string1' and `string2'.  If only one string, it's `string2'.  */
-#define AT_STRINGS_BEG(d) ((d) == (size1 ? string1 : string2) || !size2)
-#define AT_STRINGS_END(d) ((d) == end2)
-
-
-/* Test if D points to a character which is word-constituent.  We have
-   two special cases to check for: if past the end of string1, look at
-   the first character in string2; and if before the beginning of
-   string2, look at the last character in string1.  */
-#define WORDCHAR_P(d)                                                  \
-  (SYNTAX ((d) == end1 ? *string2                                      \
-          : (d) == string2 - 1 ? *(end1 - 1) : *(d))                   \
-   == Sword)
-
-/* Test if the character before D and the one at D differ with respect
-   to being word-constituent.  */
-#define AT_WORD_BOUNDARY(d)                                            \
-  (AT_STRINGS_BEG (d) || AT_STRINGS_END (d)                            \
-   || WORDCHAR_P (d - 1) != WORDCHAR_P (d))
-
-
-/* Free everything we malloc.  */
-#ifdef REGEX_MALLOC
-#define FREE_VAR(var) if (var) free (var); var = NULL
-#define FREE_VARIABLES()                                               \
-  do {                                                                 \
-    FREE_VAR (fail_stack.stack);                                       \
-    FREE_VAR (regstart);                                               \
-    FREE_VAR (regend);                                                 \
-    FREE_VAR (old_regstart);                                           \
-    FREE_VAR (old_regend);                                             \
-    FREE_VAR (best_regstart);                                          \
-    FREE_VAR (best_regend);                                            \
-    FREE_VAR (reg_info);                                               \
-    FREE_VAR (reg_dummy);                                              \
-    FREE_VAR (reg_info_dummy);                                         \
-  } while (0)
-#else /* not REGEX_MALLOC */
-/* Some MIPS systems (at least) want this to free alloca'd storage.  */
-#define FREE_VARIABLES() alloca (0)
-#endif /* not REGEX_MALLOC */
-
-
-/* These values must meet several constraints.  They must not be valid
-   register values; since we have a limit of 255 registers (because
-   we use only one byte in the pattern for the register number), we can
-   use numbers larger than 255.  They must differ by 1, because of
-   NUM_FAILURE_ITEMS above.  And the value for the lowest register must
-   be larger than the value for the highest register, so we do not try
-   to actually save any registers when none are active.  */
-#define NO_HIGHEST_ACTIVE_REG (1 << BYTEWIDTH)
-#define NO_LOWEST_ACTIVE_REG (NO_HIGHEST_ACTIVE_REG + 1)
-\f
-/* Matching routines.  */
-
-#ifndef emacs   /* Emacs never uses this.  */
-/* re_match is like re_match_2 except it takes only a single string.  */
-
-int
-re_match (bufp, string, size, pos, regs)
-     struct re_pattern_buffer *bufp;
-     const char *string;
-     int size, pos;
-     struct re_registers *regs;
- {
-  return re_match_2 (bufp, NULL, 0, string, size, pos, regs, size);
-}
-#endif /* not emacs */
-
-
-/* re_match_2 matches the compiled pattern in BUFP against the
-   the (virtual) concatenation of STRING1 and STRING2 (of length SIZE1
-   and SIZE2, respectively).  We start matching at POS, and stop
-   matching at STOP.
-
-   If REGS is non-null and the `no_sub' field of BUFP is nonzero, we
-   store offsets for the substring each group matched in REGS.  See the
-   documentation for exactly how many groups we fill.
-
-   We return -1 if no match, -2 if an internal error (such as the
-   failure stack overflowing).  Otherwise, we return the length of the
-   matched substring.  */
-
-int
-re_match_2 (bufp, string1, size1, string2, size2, pos, regs, stop)
-     struct re_pattern_buffer *bufp;
-     const char *string1, *string2;
-     int size1, size2;
-     int pos;
-     struct re_registers *regs;
-     int stop;
-{
-  /* General temporaries.  */
-  int mcnt;
-  unsigned char *p1;
-
-  /* Just past the end of the corresponding string.  */
-  const char *end1, *end2;
-
-  /* Pointers into string1 and string2, just past the last characters in
-     each to consider matching.  */
-  const char *end_match_1, *end_match_2;
-
-  /* Where we are in the data, and the end of the current string.  */
-  const char *d, *dend;
-
-  /* Where we are in the pattern, and the end of the pattern.  */
-  unsigned char *p = bufp->buffer;
-  register unsigned char *pend = p + bufp->used;
-
-  /* We use this to map every character in the string.  */
-  char *translate = bufp->translate;
-
-  /* Failure point stack.  Each place that can handle a failure further
-     down the line pushes a failure point on this stack.  It consists of
-     restart, regend, and reg_info for all registers corresponding to
-     the subexpressions we're currently inside, plus the number of such
-     registers, and, finally, two char *'s.  The first char * is where
-     to resume scanning the pattern; the second one is where to resume
-     scanning the strings.  If the latter is zero, the failure point is
-     a ``dummy''; if a failure happens and the failure point is a dummy,
-     it gets discarded and the next next one is tried.  */
-  fail_stack_type fail_stack;
-#ifdef DEBUG
-  static unsigned failure_id = 0;
-  unsigned nfailure_points_pushed = 0, nfailure_points_popped = 0;
-#endif
-
-  /* We fill all the registers internally, independent of what we
-     return, for use in backreferences.  The number here includes
-     an element for register zero.  */
-  unsigned num_regs = bufp->re_nsub + 1;
-
-  /* The currently active registers.  */
-  unsigned lowest_active_reg = NO_LOWEST_ACTIVE_REG;
-  unsigned highest_active_reg = NO_HIGHEST_ACTIVE_REG;
-
-  /* Information on the contents of registers. These are pointers into
-     the input strings; they record just what was matched (on this
-     attempt) by a subexpression part of the pattern, that is, the
-     regnum-th regstart pointer points to where in the pattern we began
-     matching and the regnum-th regend points to right after where we
-     stopped matching the regnum-th subexpression.  (The zeroth register
-     keeps track of what the whole pattern matches.)  */
-  const char **regstart = NULL, **regend = NULL;
-
-  /* If a group that's operated upon by a repetition operator fails to
-     match anything, then the register for its start will need to be
-     restored because it will have been set to wherever in the string we
-     are when we last see its open-group operator.  Similarly for a
-     register's end.  */
-  const char **old_regstart = NULL, **old_regend = NULL;
-
-  /* The is_active field of reg_info helps us keep track of which (possibly
-     nested) subexpressions we are currently in. The matched_something
-     field of reg_info[reg_num] helps us tell whether or not we have
-     matched any of the pattern so far this time through the reg_num-th
-     subexpression.  These two fields get reset each time through any
-     loop their register is in.  */
-  register_info_type *reg_info = NULL;
-
-  /* The following record the register info as found in the above
-     variables when we find a match better than any we've seen before.
-     This happens as we backtrack through the failure points, which in
-     turn happens only if we have not yet matched the entire string. */
-  unsigned best_regs_set = false;
-  const char **best_regstart = NULL, **best_regend = NULL;
-
-  /* Logically, this is `best_regend[0]'.  But we don't want to have to
-     allocate space for that if we're not allocating space for anything
-     else (see below).  Also, we never need info about register 0 for
-     any of the other register vectors, and it seems rather a kludge to
-     treat `best_regend' differently than the rest.  So we keep track of
-     the end of the best match so far in a separate variable.  We
-     initialize this to NULL so that when we backtrack the first time
-     and need to test it, it's not garbage.  */
-  const char *match_end = NULL;
-
-  /* Used when we pop values we don't care about.  */
-  const char **reg_dummy = NULL;
-  register_info_type *reg_info_dummy = NULL;
-
-#ifdef DEBUG
-  /* Counts the total number of registers pushed.  */
-  unsigned num_regs_pushed = 0;
-#endif
-
-  DEBUG_PRINT1 ("\n\nEntering re_match_2.\n");
-
-  INIT_FAIL_STACK ();
-
-  /* Do not bother to initialize all the register variables if there are
-     no groups in the pattern, as it takes a fair amount of time.  If
-     there are groups, we include space for register 0 (the whole
-     pattern), even though we never use it, since it simplifies the
-     array indexing.  We should fix this.  */
-  if (bufp->re_nsub)
-    {
-      regstart = REGEX_TALLOC (num_regs, const char *);
-      regend = REGEX_TALLOC (num_regs, const char *);
-      old_regstart = REGEX_TALLOC (num_regs, const char *);
-      old_regend = REGEX_TALLOC (num_regs, const char *);
-      best_regstart = REGEX_TALLOC (num_regs, const char *);
-      best_regend = REGEX_TALLOC (num_regs, const char *);
-      reg_info = REGEX_TALLOC (num_regs, register_info_type);
-      reg_dummy = REGEX_TALLOC (num_regs, const char *);
-      reg_info_dummy = REGEX_TALLOC (num_regs, register_info_type);
-
-      if (!(regstart && regend && old_regstart && old_regend && reg_info
-           && best_regstart && best_regend && reg_dummy && reg_info_dummy))
-       {
-         FREE_VARIABLES ();
-         return -2;
-       }
-    }
-#ifdef REGEX_MALLOC
-  else
-    {
-      /* We must initialize all our variables to NULL, so that
-        `FREE_VARIABLES' doesn't try to free them.  */
-      regstart = regend = old_regstart = old_regend = best_regstart
-       = best_regend = reg_dummy = NULL;
-      reg_info = reg_info_dummy = (register_info_type *) NULL;
-    }
-#endif /* REGEX_MALLOC */
-
-  /* The starting position is bogus.  */
-  if (pos < 0 || pos > size1 + size2)
-    {
-      FREE_VARIABLES ();
-      return -1;
-    }
-
-  /* Initialize subexpression text positions to -1 to mark ones that no
-     start_memory/stop_memory has been seen for. Also initialize the
-     register information struct.  */
-  for (mcnt = 1; mcnt < num_regs; mcnt++)
-    {
-      regstart[mcnt] = regend[mcnt]
-       = old_regstart[mcnt] = old_regend[mcnt] = REG_UNSET_VALUE;
-
-      REG_MATCH_NULL_STRING_P (reg_info[mcnt]) = MATCH_NULL_UNSET_VALUE;
-      IS_ACTIVE (reg_info[mcnt]) = 0;
-      MATCHED_SOMETHING (reg_info[mcnt]) = 0;
-      EVER_MATCHED_SOMETHING (reg_info[mcnt]) = 0;
-    }
-
-  /* We move `string1' into `string2' if the latter's empty -- but not if
-     `string1' is null.  */
-  if (size2 == 0 && string1 != NULL)
-    {
-      string2 = string1;
-      size2 = size1;
-      string1 = 0;
-      size1 = 0;
-    }
-  end1 = string1 + size1;
-  end2 = string2 + size2;
-
-  /* Compute where to stop matching, within the two strings.  */
-  if (stop <= size1)
-    {
-      end_match_1 = string1 + stop;
-      end_match_2 = string2;
-    }
-  else
-    {
-      end_match_1 = end1;
-      end_match_2 = string2 + stop - size1;
-    }
-
-  /* `p' scans through the pattern as `d' scans through the data.
-     `dend' is the end of the input string that `d' points within.  `d'
-     is advanced into the following input string whenever necessary, but
-     this happens before fetching; therefore, at the beginning of the
-     loop, `d' can be pointing at the end of a string, but it cannot
-     equal `string2'.  */
-  if (size1 > 0 && pos <= size1)
-    {
-      d = string1 + pos;
-      dend = end_match_1;
-    }
-  else
-    {
-      d = string2 + pos - size1;
-      dend = end_match_2;
-    }
-
-  DEBUG_PRINT1 ("The compiled pattern is: ");
-  DEBUG_PRINT_COMPILED_PATTERN (bufp, p, pend);
-  DEBUG_PRINT1 ("The string to match is: `");
-  DEBUG_PRINT_DOUBLE_STRING (d, string1, size1, string2, size2);
-  DEBUG_PRINT1 ("'\n");
-
-  /* This loops over pattern commands.  It exits by returning from the
-     function if the match is complete, or it drops through if the match
-     fails at this starting point in the input data.  */
-  for (;;)
-    {
-      DEBUG_PRINT2 ("\n0x%x: ", p);
-
-      if (p == pend)
-       { /* End of pattern means we might have succeeded.  */
-         DEBUG_PRINT1 ("end of pattern ... ");
-
-         /* If we haven't matched the entire string, and we want the
-            longest match, try backtracking.  */
-         if (d != end_match_2)
-           {
-             DEBUG_PRINT1 ("backtracking.\n");
-
-             if (!FAIL_STACK_EMPTY ())
-               { /* More failure points to try.  */
-                 boolean same_str_p = (FIRST_STRING_P (match_end)
-                                       == MATCHING_IN_FIRST_STRING);
-
-                 /* If exceeds best match so far, save it.  */
-                 if (!best_regs_set
-                     || (same_str_p && d > match_end)
-                     || (!same_str_p && !MATCHING_IN_FIRST_STRING))
-                   {
-                     best_regs_set = true;
-                     match_end = d;
-
-                     DEBUG_PRINT1 ("\nSAVING match as best so far.\n");
-
-                     for (mcnt = 1; mcnt < num_regs; mcnt++)
-                       {
-                         best_regstart[mcnt] = regstart[mcnt];
-                         best_regend[mcnt] = regend[mcnt];
-                       }
-                   }
-                 goto fail;
-               }
-
-             /* If no failure points, don't restore garbage.  */
-             else if (best_regs_set)
-               {
-               restore_best_regs:
-                 /* Restore best match.  It may happen that `dend ==
-                    end_match_1' while the restored d is in string2.
-                    For example, the pattern `x.*y.*z' against the
-                    strings `x-' and `y-z-', if the two strings are
-                    not consecutive in memory.  */
-                 DEBUG_PRINT1 ("Restoring best registers.\n");
-
-                 d = match_end;
-                 dend = ((d >= string1 && d <= end1)
-                          ? end_match_1 : end_match_2);
-
-                 for (mcnt = 1; mcnt < num_regs; mcnt++)
-                   {
-                     regstart[mcnt] = best_regstart[mcnt];
-                     regend[mcnt] = best_regend[mcnt];
-                   }
-               }
-           } /* d != end_match_2 */
-
-         DEBUG_PRINT1 ("Accepting match.\n");
-
-         /* If caller wants register contents data back, do it.  */
-         if (regs && !bufp->no_sub)
-           {
-             /* Have the register data arrays been allocated?  */
-             if (bufp->regs_allocated == REGS_UNALLOCATED)
-               { /* No.  So allocate them with malloc.  We need one
-                    extra element beyond `num_regs' for the `-1' marker
-                    GNU code uses.  */
-                 regs->num_regs = MAX (RE_NREGS, num_regs + 1);
-                 regs->start = TALLOC (regs->num_regs, regoff_t);
-                 regs->end = TALLOC (regs->num_regs, regoff_t);
-                 if (regs->start == NULL || regs->end == NULL)
-                   return -2;
-                 bufp->regs_allocated = REGS_REALLOCATE;
-               }
-             else if (bufp->regs_allocated == REGS_REALLOCATE)
-               { /* Yes.  If we need more elements than were already
-                    allocated, reallocate them.  If we need fewer, just
-                    leave it alone.  */
-                 if (regs->num_regs < num_regs + 1)
-                   {
-                     regs->num_regs = num_regs + 1;
-                     RETALLOC (regs->start, regs->num_regs, regoff_t);
-                     RETALLOC (regs->end, regs->num_regs, regoff_t);
-                     if (regs->start == NULL || regs->end == NULL)
-                       return -2;
-                   }
-               }
-             else
-               assert (bufp->regs_allocated == REGS_FIXED);
-
-             /* Convert the pointer data in `regstart' and `regend' to
-                indices.  Register zero has to be set differently,
-                since we haven't kept track of any info for it.  */
-             if (regs->num_regs > 0)
-               {
-                 regs->start[0] = pos;
-                 regs->end[0] = (MATCHING_IN_FIRST_STRING ? d - string1
-                                 : d - string2 + size1);
-               }
-
-             /* Go through the first `min (num_regs, regs->num_regs)'
-                registers, since that is all we initialized.  */
-             for (mcnt = 1; mcnt < MIN (num_regs, regs->num_regs); mcnt++)
-               {
-                 if (REG_UNSET (regstart[mcnt]) || REG_UNSET (regend[mcnt]))
-                   regs->start[mcnt] = regs->end[mcnt] = -1;
-                 else
-                   {
-                     regs->start[mcnt] = POINTER_TO_OFFSET (regstart[mcnt]);
-                     regs->end[mcnt] = POINTER_TO_OFFSET (regend[mcnt]);
-                   }
-               }
-
-             /* If the regs structure we return has more elements than
-                were in the pattern, set the extra elements to -1.  If
-                we (re)allocated the registers, this is the case,
-                because we always allocate enough to have at least one
-                -1 at the end.  */
-             for (mcnt = num_regs; mcnt < regs->num_regs; mcnt++)
-               regs->start[mcnt] = regs->end[mcnt] = -1;
-           } /* regs && !bufp->no_sub */
-
-         FREE_VARIABLES ();
-         DEBUG_PRINT4 ("%u failure points pushed, %u popped (%u remain).\n",
-                       nfailure_points_pushed, nfailure_points_popped,
-                       nfailure_points_pushed - nfailure_points_popped);
-         DEBUG_PRINT2 ("%u registers pushed.\n", num_regs_pushed);
-
-         mcnt = d - pos - (MATCHING_IN_FIRST_STRING
-                           ? string1
-                           : string2 - size1);
-
-         DEBUG_PRINT2 ("Returning %d from re_match_2.\n", mcnt);
-
-         return mcnt;
-       }
-
-      /* Otherwise match next pattern command.  */
-#ifdef SWITCH_ENUM_BUG
-      switch ((int) ((re_opcode_t) *p++))
-#else
-      switch ((re_opcode_t) *p++)
-#endif
-       {
-       /* Ignore these.  Used to ignore the n of succeed_n's which
-          currently have n == 0.  */
-       case no_op:
-         DEBUG_PRINT1 ("EXECUTING no_op.\n");
-         break;
-
-
-       /* Match the next n pattern characters exactly.  The following
-          byte in the pattern defines n, and the n bytes after that
-          are the characters to match.  */
-       case exactn:
-         mcnt = *p++;
-         DEBUG_PRINT2 ("EXECUTING exactn %d.\n", mcnt);
-
-         /* This is written out as an if-else so we don't waste time
-            testing `translate' inside the loop.  */
-         if (translate)
-           {
-             do
-               {
-                 PREFETCH ();
-                 if (translate[(unsigned char) *d++] != (char) *p++)
-                   goto fail;
-               }
-             while (--mcnt);
-           }
-         else
-           {
-             do
-               {
-                 PREFETCH ();
-                 if (*d++ != (char) *p++) goto fail;
-               }
-             while (--mcnt);
-           }
-         SET_REGS_MATCHED ();
-         break;
-
-
-       /* Match any character except possibly a newline or a null.  */
-       case anychar:
-         DEBUG_PRINT1 ("EXECUTING anychar.\n");
-
-         PREFETCH ();
-
-         if ((!(bufp->syntax & RE_DOT_NEWLINE) && TRANSLATE (*d) == '\n')
-             || (bufp->syntax & RE_DOT_NOT_NULL && TRANSLATE (*d) == '\000'))
-           goto fail;
-
-         SET_REGS_MATCHED ();
-         DEBUG_PRINT2 ("  Matched `%d'.\n", *d);
-         d++;
-         break;
-
-
-       case charset:
-       case charset_not:
-         {
-           register unsigned char c;
-           boolean not = (re_opcode_t) *(p - 1) == charset_not;
-
-           DEBUG_PRINT2 ("EXECUTING charset%s.\n", not ? "_not" : "");
-
-           PREFETCH ();
-           c = TRANSLATE (*d); /* The character to match.  */
-
-           /* Cast to `unsigned' instead of `unsigned char' in case the
-              bit list is a full 32 bytes long.  */
-           if (c < (unsigned) (*p * BYTEWIDTH)
-               && p[1 + c / BYTEWIDTH] & (1 << (c % BYTEWIDTH)))
-             not = !not;
-
-           p += 1 + *p;
-
-           if (!not) goto fail;
-
-           SET_REGS_MATCHED ();
-           d++;
-           break;
-         }
-
-
-       /* The beginning of a group is represented by start_memory.
-          The arguments are the register number in the next byte, and the
-          number of groups inner to this one in the next.  The text
-          matched within the group is recorded (in the internal
-          registers data structure) under the register number.  */
-       case start_memory:
-         DEBUG_PRINT3 ("EXECUTING start_memory %d (%d):\n", *p, p[1]);
-
-         /* Find out if this group can match the empty string.  */
-         p1 = p;               /* To send to group_match_null_string_p.  */
-
-         if (REG_MATCH_NULL_STRING_P (reg_info[*p]) == MATCH_NULL_UNSET_VALUE)
-           REG_MATCH_NULL_STRING_P (reg_info[*p])
-             = group_match_null_string_p (&p1, pend, reg_info);
-
-         /* Save the position in the string where we were the last time
-            we were at this open-group operator in case the group is
-            operated upon by a repetition operator, e.g., with `(a*)*b'
-            against `ab'; then we want to ignore where we are now in
-            the string in case this attempt to match fails.  */
-         old_regstart[*p] = REG_MATCH_NULL_STRING_P (reg_info[*p])
-                            ? REG_UNSET (regstart[*p]) ? d : regstart[*p]
-                            : regstart[*p];
-         DEBUG_PRINT2 ("  old_regstart: %d\n",
-                        POINTER_TO_OFFSET (old_regstart[*p]));
-
-         regstart[*p] = d;
-         DEBUG_PRINT2 ("  regstart: %d\n", POINTER_TO_OFFSET (regstart[*p]));
-
-         IS_ACTIVE (reg_info[*p]) = 1;
-         MATCHED_SOMETHING (reg_info[*p]) = 0;
-
-         /* This is the new highest active register.  */
-         highest_active_reg = *p;
-
-         /* If nothing was active before, this is the new lowest active
-            register.  */
-         if (lowest_active_reg == NO_LOWEST_ACTIVE_REG)
-           lowest_active_reg = *p;
-
-         /* Move past the register number and inner group count.  */
-         p += 2;
-         break;
-
-
-       /* The stop_memory opcode represents the end of a group.  Its
-          arguments are the same as start_memory's: the register
-          number, and the number of inner groups.  */
-       case stop_memory:
-         DEBUG_PRINT3 ("EXECUTING stop_memory %d (%d):\n", *p, p[1]);
-
-         /* We need to save the string position the last time we were at
-            this close-group operator in case the group is operated
-            upon by a repetition operator, e.g., with `((a*)*(b*)*)*'
-            against `aba'; then we want to ignore where we are now in
-            the string in case this attempt to match fails.  */
-         old_regend[*p] = REG_MATCH_NULL_STRING_P (reg_info[*p])
-                          ? REG_UNSET (regend[*p]) ? d : regend[*p]
-                          : regend[*p];
-         DEBUG_PRINT2 ("      old_regend: %d\n",
-                        POINTER_TO_OFFSET (old_regend[*p]));
-
-         regend[*p] = d;
-         DEBUG_PRINT2 ("      regend: %d\n", POINTER_TO_OFFSET (regend[*p]));
-
-         /* This register isn't active anymore.  */
-         IS_ACTIVE (reg_info[*p]) = 0;
-
-         /* If this was the only register active, nothing is active
-            anymore.  */
-         if (lowest_active_reg == highest_active_reg)
-           {
-             lowest_active_reg = NO_LOWEST_ACTIVE_REG;
-             highest_active_reg = NO_HIGHEST_ACTIVE_REG;
-           }
-         else
-           { /* We must scan for the new highest active register, since
-                it isn't necessarily one less than now: consider
-                (a(b)c(d(e)f)g).  When group 3 ends, after the f), the
-                new highest active register is 1.  */
-             unsigned char r = *p - 1;
-             while (r > 0 && !IS_ACTIVE (reg_info[r]))
-               r--;
-
-             /* If we end up at register zero, that means that we saved
-                the registers as the result of an `on_failure_jump', not
-                a `start_memory', and we jumped to past the innermost
-                `stop_memory'.  For example, in ((.)*) we save
-                registers 1 and 2 as a result of the *, but when we pop
-                back to the second ), we are at the stop_memory 1.
-                Thus, nothing is active.  */
-             if (r == 0)
-               {
-                 lowest_active_reg = NO_LOWEST_ACTIVE_REG;
-                 highest_active_reg = NO_HIGHEST_ACTIVE_REG;
-               }
-             else
-               highest_active_reg = r;
-           }
-
-         /* If just failed to match something this time around with a
-            group that's operated on by a repetition operator, try to
-            force exit from the ``loop'', and restore the register
-            information for this group that we had before trying this
-            last match.  */
-         if ((!MATCHED_SOMETHING (reg_info[*p])
-              || (re_opcode_t) p[-3] == start_memory)
-             && (p + 2) < pend)
-           {
-             boolean is_a_jump_n = false;
-
-             p1 = p + 2;
-             mcnt = 0;
-             switch ((re_opcode_t) *p1++)
-               {
-                 case jump_n:
-                   is_a_jump_n = true;
-                 case pop_failure_jump:
-                 case maybe_pop_jump:
-                 case jump:
-                 case dummy_failure_jump:
-                   EXTRACT_NUMBER_AND_INCR (mcnt, p1);
-                   if (is_a_jump_n)
-                     p1 += 2;
-                   break;
-
-                 default:
-                   /* do nothing */ ;
-               }
-             p1 += mcnt;
-
-             /* If the next operation is a jump backwards in the pattern
-                to an on_failure_jump right before the start_memory
-                corresponding to this stop_memory, exit from the loop
-                by forcing a failure after pushing on the stack the
-                on_failure_jump's jump in the pattern, and d.  */
-             if (mcnt < 0 && (re_opcode_t) *p1 == on_failure_jump
-                 && (re_opcode_t) p1[3] == start_memory && p1[4] == *p)
-               {
-                 /* If this group ever matched anything, then restore
-                    what its registers were before trying this last
-                    failed match, e.g., with `(a*)*b' against `ab' for
-                    regstart[1], and, e.g., with `((a*)*(b*)*)*'
-                    against `aba' for regend[3].
-
-                    Also restore the registers for inner groups for,
-                    e.g., `((a*)(b*))*' against `aba' (register 3 would
-                    otherwise get trashed).  */
-
-                 if (EVER_MATCHED_SOMETHING (reg_info[*p]))
-                   {
-                     unsigned r;
-
-                     EVER_MATCHED_SOMETHING (reg_info[*p]) = 0;
-
-                     /* Restore this and inner groups' (if any) registers.  */
-                     for (r = *p; r < *p + *(p + 1); r++)
-                       {
-                         regstart[r] = old_regstart[r];
-
-                         /* xx why this test?  */
-                         if ((int) old_regend[r] >= (int) regstart[r])
-                           regend[r] = old_regend[r];
-                       }
-                   }
-                 p1++;
-                 EXTRACT_NUMBER_AND_INCR (mcnt, p1);
-                 PUSH_FAILURE_POINT (p1 + mcnt, d, -2);
-
-                 goto fail;
-               }
-           }
-
-         /* Move past the register number and the inner group count.  */
-         p += 2;
-         break;
-
-
-       /* \<digit> has been turned into a `duplicate' command which is
-          followed by the numeric value of <digit> as the register number.  */
-       case duplicate:
-         {
-           register const char *d2, *dend2;
-           int regno = *p++;   /* Get which register to match against.  */
-           DEBUG_PRINT2 ("EXECUTING duplicate %d.\n", regno);
-
-           /* Can't back reference a group which we've never matched.  */
-           if (REG_UNSET (regstart[regno]) || REG_UNSET (regend[regno]))
-             goto fail;
-
-           /* Where in input to try to start matching.  */
-           d2 = regstart[regno];
-
-           /* Where to stop matching; if both the place to start and
-              the place to stop matching are in the same string, then
-              set to the place to stop, otherwise, for now have to use
-              the end of the first string.  */
-
-           dend2 = ((FIRST_STRING_P (regstart[regno])
-                     == FIRST_STRING_P (regend[regno]))
-                    ? regend[regno] : end_match_1);
-           for (;;)
-             {
-               /* If necessary, advance to next segment in register
-                  contents.  */
-               while (d2 == dend2)
-                 {
-                   if (dend2 == end_match_2) break;
-                   if (dend2 == regend[regno]) break;
-
-                   /* End of string1 => advance to string2. */
-                   d2 = string2;
-                   dend2 = regend[regno];
-                 }
-               /* At end of register contents => success */
-               if (d2 == dend2) break;
-
-               /* If necessary, advance to next segment in data.  */
-               PREFETCH ();
-
-               /* How many characters left in this segment to match.  */
-               mcnt = dend - d;
-
-               /* Want how many consecutive characters we can match in
-                  one shot, so, if necessary, adjust the count.  */
-               if (mcnt > dend2 - d2)
-                 mcnt = dend2 - d2;
-
-               /* Compare that many; failure if mismatch, else move
-                  past them.  */
-               if (translate
-                   ? bcmp_translate (d, d2, mcnt, translate)
-                   : bcmp (d, d2, mcnt))
-                 goto fail;
-               d += mcnt, d2 += mcnt;
-             }
-         }
-         break;
-
-
-       /* begline matches the empty string at the beginning of the string
-          (unless `not_bol' is set in `bufp'), and, if
-          `newline_anchor' is set, after newlines.  */
-       case begline:
-         DEBUG_PRINT1 ("EXECUTING begline.\n");
-
-         if (AT_STRINGS_BEG (d))
-           {
-             if (!bufp->not_bol) break;
-           }
-         else if (d[-1] == '\n' && bufp->newline_anchor)
-           {
-             break;
-           }
-         /* In all other cases, we fail.  */
-         goto fail;
-
-
-       /* endline is the dual of begline.  */
-       case endline:
-         DEBUG_PRINT1 ("EXECUTING endline.\n");
-
-         if (AT_STRINGS_END (d))
-           {
-             if (!bufp->not_eol) break;
-           }
-
-         /* We have to ``prefetch'' the next character.  */
-         else if ((d == end1 ? *string2 : *d) == '\n'
-                  && bufp->newline_anchor)
-           {
-             break;
-           }
-         goto fail;
-
-
-       /* Match at the very beginning of the data.  */
-       case begbuf:
-         DEBUG_PRINT1 ("EXECUTING begbuf.\n");
-         if (AT_STRINGS_BEG (d))
-           break;
-         goto fail;
-
-
-       /* Match at the very end of the data.  */
-       case endbuf:
-         DEBUG_PRINT1 ("EXECUTING endbuf.\n");
-         if (AT_STRINGS_END (d))
-           break;
-         goto fail;
-
-
-       /* on_failure_keep_string_jump is used to optimize `.*\n'.  It
-          pushes NULL as the value for the string on the stack.  Then
-          `pop_failure_point' will keep the current value for the
-          string, instead of restoring it.  To see why, consider
-          matching `foo\nbar' against `.*\n'.  The .* matches the foo;
-          then the . fails against the \n.  But the next thing we want
-          to do is match the \n against the \n; if we restored the
-          string value, we would be back at the foo.
-
-          Because this is used only in specific cases, we don't need to
-          check all the things that `on_failure_jump' does, to make
-          sure the right things get saved on the stack.  Hence we don't
-          share its code.  The only reason to push anything on the
-          stack at all is that otherwise we would have to change
-          `anychar's code to do something besides goto fail in this
-          case; that seems worse than this.  */
-       case on_failure_keep_string_jump:
-         DEBUG_PRINT1 ("EXECUTING on_failure_keep_string_jump");
-
-         EXTRACT_NUMBER_AND_INCR (mcnt, p);
-         DEBUG_PRINT3 (" %d (to 0x%x):\n", mcnt, p + mcnt);
-
-         PUSH_FAILURE_POINT (p + mcnt, NULL, -2);
-         break;
-
-
-       /* Uses of on_failure_jump:
-
-          Each alternative starts with an on_failure_jump that points
-          to the beginning of the next alternative.  Each alternative
-          except the last ends with a jump that in effect jumps past
-          the rest of the alternatives.  (They really jump to the
-          ending jump of the following alternative, because tensioning
-          these jumps is a hassle.)
-
-          Repeats start with an on_failure_jump that points past both
-          the repetition text and either the following jump or
-          pop_failure_jump back to this on_failure_jump.  */
-       case on_failure_jump:
-       on_failure:
-         DEBUG_PRINT1 ("EXECUTING on_failure_jump");
-
-         EXTRACT_NUMBER_AND_INCR (mcnt, p);
-         DEBUG_PRINT3 (" %d (to 0x%x)", mcnt, p + mcnt);
-
-         /* If this on_failure_jump comes right before a group (i.e.,
-            the original * applied to a group), save the information
-            for that group and all inner ones, so that if we fail back
-            to this point, the group's information will be correct.
-            For example, in \(a*\)*\1, we need the preceding group,
-            and in \(\(a*\)b*\)\2, we need the inner group.  */
-
-         /* We can't use `p' to check ahead because we push
-            a failure point to `p + mcnt' after we do this.  */
-         p1 = p;
-
-         /* We need to skip no_op's before we look for the
-            start_memory in case this on_failure_jump is happening as
-            the result of a completed succeed_n, as in \(a\)\{1,3\}b\1
-            against aba.  */
-         while (p1 < pend && (re_opcode_t) *p1 == no_op)
-           p1++;
-
-         if (p1 < pend && (re_opcode_t) *p1 == start_memory)
-           {
-             /* We have a new highest active register now.  This will
-                get reset at the start_memory we are about to get to,
-                but we will have saved all the registers relevant to
-                this repetition op, as described above.  */
-             highest_active_reg = *(p1 + 1) + *(p1 + 2);
-             if (lowest_active_reg == NO_LOWEST_ACTIVE_REG)
-               lowest_active_reg = *(p1 + 1);
-           }
-
-         DEBUG_PRINT1 (":\n");
-         PUSH_FAILURE_POINT (p + mcnt, d, -2);
-         break;
-
-
-       /* A smart repeat ends with `maybe_pop_jump'.
-          We change it to either `pop_failure_jump' or `jump'.  */
-       case maybe_pop_jump:
-         EXTRACT_NUMBER_AND_INCR (mcnt, p);
-         DEBUG_PRINT2 ("EXECUTING maybe_pop_jump %d.\n", mcnt);
-         {
-           register unsigned char *p2 = p;
-
-           /* Compare the beginning of the repeat with what in the
-              pattern follows its end. If we can establish that there
-              is nothing that they would both match, i.e., that we
-              would have to backtrack because of (as in, e.g., `a*a')
-              then we can change to pop_failure_jump, because we'll
-              never have to backtrack.
-
-              This is not true in the case of alternatives: in
-              `(a|ab)*' we do need to backtrack to the `ab' alternative
-              (e.g., if the string was `ab').  But instead of trying to
-              detect that here, the alternative has put on a dummy
-              failure point which is what we will end up popping.  */
-
-           /* Skip over open/close-group commands.  */
-           while (p2 + 2 < pend
-                  && ((re_opcode_t) *p2 == stop_memory
-                      || (re_opcode_t) *p2 == start_memory))
-             p2 += 3;                  /* Skip over args, too.  */
-
-           /* If we're at the end of the pattern, we can change.  */
-           if (p2 == pend)
-             {
-               /* Consider what happens when matching ":\(.*\)"
-                  against ":/".  I don't really understand this code
-                  yet.  */
-               p[-3] = (unsigned char) pop_failure_jump;
-               DEBUG_PRINT1
-                 ("  End of pattern: change to `pop_failure_jump'.\n");
-             }
-
-           else if ((re_opcode_t) *p2 == exactn
-                    || (bufp->newline_anchor && (re_opcode_t) *p2 == endline))
-             {
-               register unsigned char c
-                 = *p2 == (unsigned char) endline ? '\n' : p2[2];
-               p1 = p + mcnt;
-
-               /* p1[0] ... p1[2] are the `on_failure_jump' corresponding
-                  to the `maybe_finalize_jump' of this case.  Examine what
-                  follows.  */
-               if ((re_opcode_t) p1[3] == exactn && p1[5] != c)
-                 {
-                   p[-3] = (unsigned char) pop_failure_jump;
-                   DEBUG_PRINT3 ("  %c != %c => pop_failure_jump.\n",
-                                 c, p1[5]);
-                 }
-
-               else if ((re_opcode_t) p1[3] == charset
-                        || (re_opcode_t) p1[3] == charset_not)
-                 {
-                   int not = (re_opcode_t) p1[3] == charset_not;
-
-                   if (c < (unsigned char) (p1[4] * BYTEWIDTH)
-                       && p1[5 + c / BYTEWIDTH] & (1 << (c % BYTEWIDTH)))
-                     not = !not;
-
-                   /* `not' is equal to 1 if c would match, which means
-                       that we can't change to pop_failure_jump.  */
-                   if (!not)
-                     {
-                       p[-3] = (unsigned char) pop_failure_jump;
-                       DEBUG_PRINT1 ("  No match => pop_failure_jump.\n");
-                     }
-                 }
-             }
-         }
-         p -= 2;               /* Point at relative address again.  */
-         if ((re_opcode_t) p[-1] != pop_failure_jump)
-           {
-             p[-1] = (unsigned char) jump;
-             DEBUG_PRINT1 ("  Match => jump.\n");
-             goto unconditional_jump;
-           }
-       /* Note fall through.  */
-
-
-       /* The end of a simple repeat has a pop_failure_jump back to
-          its matching on_failure_jump, where the latter will push a
-          failure point.  The pop_failure_jump takes off failure
-          points put on by this pop_failure_jump's matching
-          on_failure_jump; we got through the pattern to here from the
-          matching on_failure_jump, so didn't fail.  */
-       case pop_failure_jump:
-         {
-           /* We need to pass separate storage for the lowest and
-              highest registers, even though we don't care about the
-              actual values.  Otherwise, we will restore only one
-              register from the stack, since lowest will == highest in
-              `pop_failure_point'.  */
-           unsigned dummy_low_reg, dummy_high_reg;
-           unsigned char *pdummy;
-           const char *sdummy;
-
-           DEBUG_PRINT1 ("EXECUTING pop_failure_jump.\n");
-           POP_FAILURE_POINT (sdummy, pdummy,
-                              dummy_low_reg, dummy_high_reg,
-                              reg_dummy, reg_dummy, reg_info_dummy);
-         }
-         /* Note fall through.  */
-
-
-       /* Unconditionally jump (without popping any failure points).  */
-       case jump:
-       unconditional_jump:
-         EXTRACT_NUMBER_AND_INCR (mcnt, p);    /* Get the amount to jump.  */
-         DEBUG_PRINT2 ("EXECUTING jump %d ", mcnt);
-         p += mcnt;                            /* Do the jump.  */
-         DEBUG_PRINT2 ("(to 0x%x).\n", p);
-         break;
-
-
-       /* We need this opcode so we can detect where alternatives end
-          in `group_match_null_string_p' et al.  */
-       case jump_past_alt:
-         DEBUG_PRINT1 ("EXECUTING jump_past_alt.\n");
-         goto unconditional_jump;
-
-
-       /* Normally, the on_failure_jump pushes a failure point, which
-          then gets popped at pop_failure_jump.  We will end up at
-          pop_failure_jump, also, and with a pattern of, say, `a+', we
-          are skipping over the on_failure_jump, so we have to push
-          something meaningless for pop_failure_jump to pop.  */
-       case dummy_failure_jump:
-         DEBUG_PRINT1 ("EXECUTING dummy_failure_jump.\n");
-         /* It doesn't matter what we push for the string here.  What
-            the code at `fail' tests is the value for the pattern.  */
-         PUSH_FAILURE_POINT (0, 0, -2);
-         goto unconditional_jump;
-
-
-       /* At the end of an alternative, we need to push a dummy failure
-          point in case we are followed by a `pop_failure_jump', because
-          we don't want the failure point for the alternative to be
-          popped.  For example, matching `(a|ab)*' against `aab'
-          requires that we match the `ab' alternative.  */
-       case push_dummy_failure:
-         DEBUG_PRINT1 ("EXECUTING push_dummy_failure.\n");
-         /* See comments just above at `dummy_failure_jump' about the
-            two zeroes.  */
-         PUSH_FAILURE_POINT (0, 0, -2);
-         break;
-
-       /* Have to succeed matching what follows at least n times.
-          After that, handle like `on_failure_jump'.  */
-       case succeed_n:
-         EXTRACT_NUMBER (mcnt, p + 2);
-         DEBUG_PRINT2 ("EXECUTING succeed_n %d.\n", mcnt);
-
-         assert (mcnt >= 0);
-         /* Originally, this is how many times we HAVE to succeed.  */
-         if (mcnt > 0)
-           {
-              mcnt--;
-              p += 2;
-              STORE_NUMBER_AND_INCR (p, mcnt);
-              DEBUG_PRINT3 ("  Setting 0x%x to %d.\n", p, mcnt);
-           }
-         else if (mcnt == 0)
-           {
-             DEBUG_PRINT2 ("  Setting two bytes from 0x%x to no_op.\n", p+2);
-             p[2] = (unsigned char) no_op;
-             p[3] = (unsigned char) no_op;
-             goto on_failure;
-           }
-         break;
-
-       case jump_n:
-         EXTRACT_NUMBER (mcnt, p + 2);
-         DEBUG_PRINT2 ("EXECUTING jump_n %d.\n", mcnt);
-
-         /* Originally, this is how many times we CAN jump.  */
-         if (mcnt)
-           {
-              mcnt--;
-              STORE_NUMBER (p + 2, mcnt);
-              goto unconditional_jump;
-           }
-         /* If don't have to jump any more, skip over the rest of command.  */
-         else
-           p += 4;
-         break;
-
-       case set_number_at:
-         {
-           DEBUG_PRINT1 ("EXECUTING set_number_at.\n");
-
-           EXTRACT_NUMBER_AND_INCR (mcnt, p);
-           p1 = p + mcnt;
-           EXTRACT_NUMBER_AND_INCR (mcnt, p);
-           DEBUG_PRINT3 ("  Setting 0x%x to %d.\n", p1, mcnt);
-           STORE_NUMBER (p1, mcnt);
-           break;
-         }
-
-       case wordbound:
-         DEBUG_PRINT1 ("EXECUTING wordbound.\n");
-         if (AT_WORD_BOUNDARY (d))
-           break;
-         goto fail;
-
-       case notwordbound:
-         DEBUG_PRINT1 ("EXECUTING notwordbound.\n");
-         if (AT_WORD_BOUNDARY (d))
-           goto fail;
-         break;
-
-       case wordbeg:
-         DEBUG_PRINT1 ("EXECUTING wordbeg.\n");
-         if (WORDCHAR_P (d) && (AT_STRINGS_BEG (d) || !WORDCHAR_P (d - 1)))
-           break;
-         goto fail;
-
-       case wordend:
-         DEBUG_PRINT1 ("EXECUTING wordend.\n");
-         if (!AT_STRINGS_BEG (d) && WORDCHAR_P (d - 1)
-             && (!WORDCHAR_P (d) || AT_STRINGS_END (d)))
-           break;
-         goto fail;
-
-#ifdef emacs
-#ifdef emacs19
-       case before_dot:
-         DEBUG_PRINT1 ("EXECUTING before_dot.\n");
-         if (PTR_CHAR_POS ((unsigned char *) d) >= point)
-           goto fail;
-         break;
-
-       case at_dot:
-         DEBUG_PRINT1 ("EXECUTING at_dot.\n");
-         if (PTR_CHAR_POS ((unsigned char *) d) != point)
-           goto fail;
-         break;
-
-       case after_dot:
-         DEBUG_PRINT1 ("EXECUTING after_dot.\n");
-         if (PTR_CHAR_POS ((unsigned char *) d) <= point)
-           goto fail;
-         break;
-#else /* not emacs19 */
-       case at_dot:
-         DEBUG_PRINT1 ("EXECUTING at_dot.\n");
-         if (PTR_CHAR_POS ((unsigned char *) d) + 1 != point)
-           goto fail;
-         break;
-#endif /* not emacs19 */
-
-       case syntaxspec:
-         DEBUG_PRINT2 ("EXECUTING syntaxspec %d.\n", mcnt);
-         mcnt = *p++;
-         goto matchsyntax;
-
-       case wordchar:
-         DEBUG_PRINT1 ("EXECUTING Emacs wordchar.\n");
-         mcnt = (int) Sword;
-       matchsyntax:
-         PREFETCH ();
-         if (SYNTAX (*d++) != (enum syntaxcode) mcnt)
-           goto fail;
-         SET_REGS_MATCHED ();
-         break;
-
-       case notsyntaxspec:
-         DEBUG_PRINT2 ("EXECUTING notsyntaxspec %d.\n", mcnt);
-         mcnt = *p++;
-         goto matchnotsyntax;
-
-       case notwordchar:
-         DEBUG_PRINT1 ("EXECUTING Emacs notwordchar.\n");
-         mcnt = (int) Sword;
-       matchnotsyntax:
-         PREFETCH ();
-         if (SYNTAX (*d++) == (enum syntaxcode) mcnt)
-           goto fail;
-         SET_REGS_MATCHED ();
-         break;
-
-#else /* not emacs */
-       case wordchar:
-         DEBUG_PRINT1 ("EXECUTING non-Emacs wordchar.\n");
-         PREFETCH ();
-         if (!WORDCHAR_P (d))
-           goto fail;
-         SET_REGS_MATCHED ();
-         d++;
-         break;
-
-       case notwordchar:
-         DEBUG_PRINT1 ("EXECUTING non-Emacs notwordchar.\n");
-         PREFETCH ();
-         if (WORDCHAR_P (d))
-           goto fail;
-         SET_REGS_MATCHED ();
-         d++;
-         break;
-#endif /* not emacs */
-
-       default:
-         abort ();
-       }
-      continue;  /* Successfully executed one pattern command; keep going.  */
-
-
-    /* We goto here if a matching operation fails. */
-    fail:
-      if (!FAIL_STACK_EMPTY ())
-       { /* A restart point is known.  Restore to that state.  */
-         DEBUG_PRINT1 ("\nFAIL:\n");
-         POP_FAILURE_POINT (d, p,
-                            lowest_active_reg, highest_active_reg,
-                            regstart, regend, reg_info);
-
-         /* If this failure point is a dummy, try the next one.  */
-         if (!p)
-           goto fail;
-
-         /* If we failed to the end of the pattern, don't examine *p.  */
-         assert (p <= pend);
-         if (p < pend)
-           {
-             boolean is_a_jump_n = false;
-
-             /* If failed to a backwards jump that's part of a repetition
-                loop, need to pop this failure point and use the next one.  */
-             switch ((re_opcode_t) *p)
-               {
-               case jump_n:
-                 is_a_jump_n = true;
-               case maybe_pop_jump:
-               case pop_failure_jump:
-               case jump:
-                 p1 = p + 1;
-                 EXTRACT_NUMBER_AND_INCR (mcnt, p1);
-                 p1 += mcnt;
-
-                 if ((is_a_jump_n && (re_opcode_t) *p1 == succeed_n)
-                     || (!is_a_jump_n
-                         && (re_opcode_t) *p1 == on_failure_jump))
-                   goto fail;
-                 break;
-               default:
-                 /* do nothing */ ;
-               }
-           }
-
-         if (d >= string1 && d <= end1)
-           dend = end_match_1;
-       }
-      else
-       break;   /* Matching at this starting point really fails.  */
-    } /* for (;;) */
-
-  if (best_regs_set)
-    goto restore_best_regs;
-
-  FREE_VARIABLES ();
-
-  return -1;                           /* Failure to match.  */
-} /* re_match_2 */
-\f
-/* Subroutine definitions for re_match_2.  */
-
-
-/* We are passed P pointing to a register number after a start_memory.
-
-   Return true if the pattern up to the corresponding stop_memory can
-   match the empty string, and false otherwise.
-
-   If we find the matching stop_memory, sets P to point to one past its number.
-   Otherwise, sets P to an undefined byte less than or equal to END.
-
-   We don't handle duplicates properly (yet).  */
-
-static boolean
-group_match_null_string_p (p, end, reg_info)
-    unsigned char **p, *end;
-    register_info_type *reg_info;
-{
-  int mcnt;
-  /* Point to after the args to the start_memory.  */
-  unsigned char *p1 = *p + 2;
-
-  while (p1 < end)
-    {
-      /* Skip over opcodes that can match nothing, and return true or
-        false, as appropriate, when we get to one that can't, or to the
-        matching stop_memory.  */
-
-      switch ((re_opcode_t) *p1)
-       {
-       /* Could be either a loop or a series of alternatives.  */
-       case on_failure_jump:
-         p1++;
-         EXTRACT_NUMBER_AND_INCR (mcnt, p1);
-
-         /* If the next operation is not a jump backwards in the
-            pattern.  */
-
-         if (mcnt >= 0)
-           {
-             /* Go through the on_failure_jumps of the alternatives,
-                seeing if any of the alternatives cannot match nothing.
-                The last alternative starts with only a jump,
-                whereas the rest start with on_failure_jump and end
-                with a jump, e.g., here is the pattern for `a|b|c':
-
-                /on_failure_jump/0/6/exactn/1/a/jump_past_alt/0/6
-                /on_failure_jump/0/6/exactn/1/b/jump_past_alt/0/3
-                /exactn/1/c
-
-                So, we have to first go through the first (n-1)
-                alternatives and then deal with the last one separately.  */
-
-
-             /* Deal with the first (n-1) alternatives, which start
-                with an on_failure_jump (see above) that jumps to right
-                past a jump_past_alt.  */
-
-             while ((re_opcode_t) p1[mcnt-3] == jump_past_alt)
-               {
-                 /* `mcnt' holds how many bytes long the alternative
-                    is, including the ending `jump_past_alt' and
-                    its number.  */
-
-                 if (!alt_match_null_string_p (p1, p1 + mcnt - 3,
-                                                     reg_info))
-                   return false;
-
-                 /* Move to right after this alternative, including the
-                    jump_past_alt.  */
-                 p1 += mcnt;
-
-                 /* Break if it's the beginning of an n-th alternative
-                    that doesn't begin with an on_failure_jump.  */
-                 if ((re_opcode_t) *p1 != on_failure_jump)
-                   break;
-
-                 /* Still have to check that it's not an n-th
-                    alternative that starts with an on_failure_jump.  */
-                 p1++;
-                 EXTRACT_NUMBER_AND_INCR (mcnt, p1);
-                 if ((re_opcode_t) p1[mcnt-3] != jump_past_alt)
-                   {
-                     /* Get to the beginning of the n-th alternative.  */
-                     p1 -= 3;
-                     break;
-                   }
-               }
-
-             /* Deal with the last alternative: go back and get number
-                of the `jump_past_alt' just before it.  `mcnt' contains
-                the length of the alternative.  */
-             EXTRACT_NUMBER (mcnt, p1 - 2);
-
-             if (!alt_match_null_string_p (p1, p1 + mcnt, reg_info))
-               return false;
-
-             p1 += mcnt;       /* Get past the n-th alternative.  */
-           } /* if mcnt > 0 */
-         break;
-
-
-       case stop_memory:
-         assert (p1[1] == **p);
-         *p = p1 + 2;
-         return true;
-
-
-       default:
-         if (!common_op_match_null_string_p (&p1, end, reg_info))
-           return false;
-       }
-    } /* while p1 < end */
-
-  return false;
-} /* group_match_null_string_p */
-
-
-/* Similar to group_match_null_string_p, but doesn't deal with alternatives:
-   It expects P to be the first byte of a single alternative and END one
-   byte past the last. The alternative can contain groups.  */
-
-static boolean
-alt_match_null_string_p (p, end, reg_info)
-    unsigned char *p, *end;
-    register_info_type *reg_info;
-{
-  int mcnt;
-  unsigned char *p1 = p;
-
-  while (p1 < end)
-    {
-      /* Skip over opcodes that can match nothing, and break when we get
-        to one that can't.  */
-
-      switch ((re_opcode_t) *p1)
-       {
-       /* It's a loop.  */
-       case on_failure_jump:
-         p1++;
-         EXTRACT_NUMBER_AND_INCR (mcnt, p1);
-         p1 += mcnt;
-         break;
-
-       default:
-         if (!common_op_match_null_string_p (&p1, end, reg_info))
-           return false;
-       }
-    }  /* while p1 < end */
-
-  return true;
-} /* alt_match_null_string_p */
-
-
-/* Deals with the ops common to group_match_null_string_p and
-   alt_match_null_string_p.
-
-   Sets P to one after the op and its arguments, if any.  */
-
-static boolean
-common_op_match_null_string_p (p, end, reg_info)
-    unsigned char **p, *end;
-    register_info_type *reg_info;
-{
-  int mcnt;
-  boolean ret;
-  int reg_no;
-  unsigned char *p1 = *p;
-
-  switch ((re_opcode_t) *p1++)
-    {
-    case no_op:
-    case begline:
-    case endline:
-    case begbuf:
-    case endbuf:
-    case wordbeg:
-    case wordend:
-    case wordbound:
-    case notwordbound:
-#ifdef emacs
-    case before_dot:
-    case at_dot:
-    case after_dot:
-#endif
-      break;
-
-    case start_memory:
-      reg_no = *p1;
-      assert (reg_no > 0 && reg_no <= MAX_REGNUM);
-      ret = group_match_null_string_p (&p1, end, reg_info);
-
-      /* Have to set this here in case we're checking a group which
-        contains a group and a back reference to it.  */
-
-      if (REG_MATCH_NULL_STRING_P (reg_info[reg_no]) == MATCH_NULL_UNSET_VALUE)
-       REG_MATCH_NULL_STRING_P (reg_info[reg_no]) = ret;
-
-      if (!ret)
-       return false;
-      break;
-
-    /* If this is an optimized succeed_n for zero times, make the jump.  */
-    case jump:
-      EXTRACT_NUMBER_AND_INCR (mcnt, p1);
-      if (mcnt >= 0)
-       p1 += mcnt;
-      else
-       return false;
-      break;
-
-    case succeed_n:
-      /* Get to the number of times to succeed.  */
-      p1 += 2;
-      EXTRACT_NUMBER_AND_INCR (mcnt, p1);
-
-      if (mcnt == 0)
-       {
-         p1 -= 4;
-         EXTRACT_NUMBER_AND_INCR (mcnt, p1);
-         p1 += mcnt;
-       }
-      else
-       return false;
-      break;
-
-    case duplicate:
-      if (!REG_MATCH_NULL_STRING_P (reg_info[*p1]))
-       return false;
-      break;
-
-    case set_number_at:
-      p1 += 4;
-
-    default:
-      /* All other opcodes mean we cannot match the empty string.  */
-      return false;
-  }
-
-  *p = p1;
-  return true;
-} /* common_op_match_null_string_p */
-
-
-/* Return zero if TRANSLATE[S1] and TRANSLATE[S2] are identical for LEN
-   bytes; nonzero otherwise.  */
-
-static int
-bcmp_translate(
-     unsigned char *s1,
-     unsigned char *s2,
-     int len,
-     char *translate
-)
-{
-  register unsigned char *p1 = s1, *p2 = s2;
-  while (len)
-    {
-      if (translate[*p1++] != translate[*p2++]) return 1;
-      len--;
-    }
-  return 0;
-}
-\f
-/* Entry points for GNU code.  */
-
-/* re_compile_pattern is the GNU regular expression compiler: it
-   compiles PATTERN (of length SIZE) and puts the result in BUFP.
-   Returns 0 if the pattern was valid, otherwise an error string.
-
-   Assumes the `allocated' (and perhaps `buffer') and `translate' fields
-   are set in BUFP on entry.
-
-   We call regex_compile to do the actual compilation.  */
-
-const char *
-re_compile_pattern (pattern, length, bufp)
-     const char *pattern;
-     int length;
-     struct re_pattern_buffer *bufp;
-{
-  reg_errcode_t ret;
-
-  /* GNU code is written to assume at least RE_NREGS registers will be set
-     (and at least one extra will be -1).  */
-  bufp->regs_allocated = REGS_UNALLOCATED;
-
-  /* And GNU code determines whether or not to get register information
-     by passing null for the REGS argument to re_match, etc., not by
-     setting no_sub.  */
-  bufp->no_sub = 0;
-
-  /* Match anchors at newline.  */
-  bufp->newline_anchor = 1;
-
-  ret = regex_compile (pattern, length, re_syntax_options, bufp);
-
-  return re_error_msg[(int) ret];
-}
-\f
-/* Entry points compatible with 4.2 BSD regex library.  We don't define
-   them if this is an Emacs or POSIX compilation.  */
-
-#if !defined (emacs) && !defined (_POSIX_SOURCE)
-
-/* BSD has one and only one pattern buffer.  */
-static struct re_pattern_buffer re_comp_buf;
-
-char *
-re_comp (s)
-    const char *s;
-{
-  reg_errcode_t ret;
-
-  if (!s)
-    {
-      if (!re_comp_buf.buffer)
-       return "No previous regular expression";
-      return 0;
-    }
-
-  if (!re_comp_buf.buffer)
-    {
-      re_comp_buf.buffer = (unsigned char *) malloc (200);
-      if (re_comp_buf.buffer == NULL)
-       return "Memory exhausted";
-      re_comp_buf.allocated = 200;
-
-      re_comp_buf.fastmap = (char *) malloc (1 << BYTEWIDTH);
-      if (re_comp_buf.fastmap == NULL)
-       return "Memory exhausted";
-    }
-
-  /* Since `re_exec' always passes NULL for the `regs' argument, we
-     don't need to initialize the pattern buffer fields which affect it.  */
-
-  /* Match anchors at newlines.  */
-  re_comp_buf.newline_anchor = 1;
-
-  ret = regex_compile (s, strlen (s), re_syntax_options, &re_comp_buf);
-
-  /* Yes, we're discarding `const' here.  */
-  return (char *) re_error_msg[(int) ret];
-}
-
-
-int
-re_exec (s)
-    const char *s;
-{
-  const int len = strlen (s);
-  return
-    0 <= re_search (&re_comp_buf, s, len, 0, len, (struct re_registers *) 0);
-}
-#endif /* not emacs and not _POSIX_SOURCE */
-\f
-/* POSIX.2 functions.  Don't define these for Emacs.  */
-
-#ifndef emacs
-
-/* regcomp takes a regular expression as a string and compiles it.
-
-   PREG is a regex_t *.  We do not expect any fields to be initialized,
-   since POSIX says we shouldn't.  Thus, we set
-
-     `buffer' to the compiled pattern;
-     `used' to the length of the compiled pattern;
-     `syntax' to RE_SYNTAX_POSIX_EXTENDED if the
-       REG_EXTENDED bit in CFLAGS is set; otherwise, to
-       RE_SYNTAX_POSIX_BASIC;
-     `newline_anchor' to REG_NEWLINE being set in CFLAGS;
-     `fastmap' and `fastmap_accurate' to zero;
-     `re_nsub' to the number of subexpressions in PATTERN.
-
-   PATTERN is the address of the pattern string.
-
-   CFLAGS is a series of bits which affect compilation.
-
-     If REG_EXTENDED is set, we use POSIX extended syntax; otherwise, we
-     use POSIX basic syntax.
-
-     If REG_NEWLINE is set, then . and [^...] don't match newline.
-     Also, regexec will try a match beginning after every newline.
-
-     If REG_ICASE is set, then we considers upper- and lowercase
-     versions of letters to be equivalent when matching.
-
-     If REG_NOSUB is set, then when PREG is passed to regexec, that
-     routine will report only success or failure, and nothing about the
-     registers.
-
-   It returns 0 if it succeeds, nonzero if it doesn't.  (See regex.h for
-   the return codes and their meanings.)  */
-
-int
-regcomp (preg, pattern, cflags)
-    regex_t *preg;
-    const char *pattern;
-    int cflags;
-{
-  reg_errcode_t ret;
-  unsigned syntax
-    = (cflags & REG_EXTENDED) ?
-      RE_SYNTAX_POSIX_EXTENDED : RE_SYNTAX_POSIX_BASIC;
-
-  /* regex_compile will allocate the space for the compiled pattern.  */
-  preg->buffer = 0;
-  preg->allocated = 0;
-
-  /* Don't bother to use a fastmap when searching.  This simplifies the
-     REG_NEWLINE case: if we used a fastmap, we'd have to put all the
-     characters after newlines into the fastmap.  This way, we just try
-     every character.  */
-  preg->fastmap = 0;
-
-  if (cflags & REG_ICASE)
-    {
-      unsigned i;
-
-      preg->translate = (char *) malloc (CHAR_SET_SIZE);
-      if (preg->translate == NULL)
-       return (int) REG_ESPACE;
-
-      /* Map uppercase characters to corresponding lowercase ones.  */
-      for (i = 0; i < CHAR_SET_SIZE; i++)
-       preg->translate[i] = ISUPPER (i) ? tolower (i) : i;
-    }
-  else
-    preg->translate = NULL;
-
-  /* If REG_NEWLINE is set, newlines are treated differently.  */
-  if (cflags & REG_NEWLINE)
-    { /* REG_NEWLINE implies neither . nor [^...] match newline.  */
-      syntax &= ~RE_DOT_NEWLINE;
-      syntax |= RE_HAT_LISTS_NOT_NEWLINE;
-      /* It also changes the matching behavior.  */
-      preg->newline_anchor = 1;
-    }
-  else
-    preg->newline_anchor = 0;
-
-  preg->no_sub = !!(cflags & REG_NOSUB);
-
-  /* POSIX says a null character in the pattern terminates it, so we
-     can use strlen here in compiling the pattern.  */
-  ret = regex_compile (pattern, strlen (pattern), syntax, preg);
-
-  /* POSIX doesn't distinguish between an unmatched open-group and an
-     unmatched close-group: both are REG_EPAREN.  */
-  if (ret == REG_ERPAREN) ret = REG_EPAREN;
-
-  return (int) ret;
-}
-
-
-/* regexec searches for a given pattern, specified by PREG, in the
-   string STRING.
-
-   If NMATCH is zero or REG_NOSUB was set in the cflags argument to
-   `regcomp', we ignore PMATCH.  Otherwise, we assume PMATCH has at
-   least NMATCH elements, and we set them to the offsets of the
-   corresponding matched substrings.
-
-   EFLAGS specifies `execution flags' which affect matching: if
-   REG_NOTBOL is set, then ^ does not match at the beginning of the
-   string; if REG_NOTEOL is set, then $ does not match at the end.
-
-   We return 0 if we find a match and REG_NOMATCH if not.  */
-
-int
-regexec (preg, string, nmatch, pmatch, eflags)
-    const regex_t *preg;
-    const char *string;
-    size_t nmatch;
-    regmatch_t pmatch[];
-    int eflags;
-{
-  int ret;
-  struct re_registers regs;
-  regex_t private_preg;
-  int len = strlen (string);
-  boolean want_reg_info = !preg->no_sub && nmatch > 0;
-
-  private_preg = *preg;
-
-  private_preg.not_bol = !!(eflags & REG_NOTBOL);
-  private_preg.not_eol = !!(eflags & REG_NOTEOL);
-
-  /* The user has told us exactly how many registers to return
-     information about, via `nmatch'.  We have to pass that on to the
-     matching routines.  */
-  private_preg.regs_allocated = REGS_FIXED;
-
-  if (want_reg_info)
-    {
-      regs.num_regs = nmatch;
-      regs.start = TALLOC (nmatch, regoff_t);
-      regs.end = TALLOC (nmatch, regoff_t);
-      if (regs.start == NULL || regs.end == NULL)
-       return (int) REG_NOMATCH;
-    }
-
-  /* Perform the searching operation.  */
-  ret = re_search (&private_preg, string, len,
-                  /* start: */ 0, /* range: */ len,
-                  want_reg_info ? &regs : (struct re_registers *) 0);
-
-  /* Copy the register information to the POSIX structure.  */
-  if (want_reg_info)
-    {
-      if (ret >= 0)
-       {
-         unsigned r;
-
-         for (r = 0; r < nmatch; r++)
-           {
-             pmatch[r].rm_so = regs.start[r];
-             pmatch[r].rm_eo = regs.end[r];
-           }
-       }
-
-      /* If we needed the temporary register info, free the space now.  */
-      free (regs.start);
-      free (regs.end);
-    }
-
-  /* We want zero return to mean success, unlike `re_search'.  */
-  return ret >= 0 ? (int) REG_NOERROR : (int) REG_NOMATCH;
-}
-
-
-/* Returns a message corresponding to an error code, ERRCODE, returned
-   from either regcomp or regexec.   We don't use PREG here.  */
-
-size_t
-regerror (errcode, preg, errbuf, errbuf_size)
-    int errcode;
-    const regex_t *preg;
-    char *errbuf;
-    size_t errbuf_size;
-{
-  const char *msg;
-  size_t msg_size;
-
-  if (errcode < 0
-      || errcode >= (sizeof (re_error_msg) / sizeof (re_error_msg[0])))
-    /* Only error codes returned by the rest of the code should be passed
-       to this routine.  If we are given anything else, or if other regex
-       code generates an invalid error code, then the program has a bug.
-       Dump core so we can fix it.  */
-    abort ();
-
-  msg = re_error_msg[errcode];
-
-  /* POSIX doesn't require that we do anything in this case, but why
-     not be nice.  */
-  if (! msg)
-    msg = "Success";
-
-  msg_size = strlen (msg) + 1; /* Includes the null.  */
-
-  if (errbuf_size != 0)
-    {
-      if (msg_size > errbuf_size)
-       {
-         strncpy (errbuf, msg, errbuf_size - 1);
-         errbuf[errbuf_size - 1] = 0;
-       }
-      else
-       strcpy (errbuf, msg);
-    }
-
-  return msg_size;
-}
-
-
-/* Free dynamically allocated space used by PREG.  */
-
-void
-regfree (preg)
-    regex_t *preg;
-{
-  if (preg->buffer != NULL)
-    free (preg->buffer);
-  preg->buffer = NULL;
-
-  preg->allocated = 0;
-  preg->used = 0;
-
-  if (preg->fastmap != NULL)
-    free (preg->fastmap);
-  preg->fastmap = NULL;
-  preg->fastmap_accurate = 0;
-
-  if (preg->translate != NULL)
-    free (preg->translate);
-  preg->translate = NULL;
-}
-
-#endif /* not emacs  */
-\f
-/*
-Local variables:
-make-backup-files: t
-version-control: t
-trim-versions-without-asking: nil
-End:
-*/
index 6eb64f14020db0a20ba596de5b58b3c552157f16..61c96838721501031935dd560eb86d8dbcedd2b0 100644 (file)
@@ -1,70 +1,90 @@
-/* Definitions for data structures and routines for the regular
-   expression library, version 0.12.
+#include <stdio.h>
+#include <stddef.h>
 
-   Copyright (C) 1985, 1989, 1990, 1991, 1992, 1993 Free Software Foundation, Inc.
+/* Definitions for data structures and routines for the regular
+   expression library.
+   Copyright (C) 1985,1989-93,1995-98,2000,2001,2002,2003,2005,2006,2008
+   Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
 
-   This program is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2, or (at your option)
-   any later version.
+   The GNU C 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 program is distributed in the hope that it will be useful,
+   The GNU C 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 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.  */
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
 
-#ifndef __REGEXP_LIBRARY_H__
-#define __REGEXP_LIBRARY_H__
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, write to the Free
+   Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301 USA.  */
 
-/* POSIX says that <sys/types.h> must be included (by the caller) before
-   <regex.h>.  */
+#ifndef _REGEX_H
+#define _REGEX_H 1
 
-#ifdef VMS
-/* VMS doesn't have `size_t' in <sys/types.h>, even though POSIX says it
-   should be there.  */
+#ifdef HAVE_STDDEF_H
 #include <stddef.h>
 #endif
 
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#ifndef _LIBC
+#define __USE_GNU      1
+#endif
+
+/* Allow the use in C++ code.  */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The following two types have to be signed and unsigned integer type
+   wide enough to hold a value of a pointer.  For most ANSI compilers
+   ptrdiff_t and size_t should be likely OK.  Still size of these two
+   types is 2 for Microsoft C.  Ugh... */
+typedef long int s_reg_t;
+typedef unsigned long int active_reg_t;
 
 /* The following bits are used to determine the regexp syntax we
    recognize.  The set/not-set meanings are chosen so that Emacs syntax
    remains the value 0.  The bits are given in alphabetical order, and
    the definitions shifted by one from the previous bit; thus, when we
    add or remove a bit, only one other definition need change.  */
-typedef unsigned reg_syntax_t;
+typedef unsigned long int reg_syntax_t;
 
+#ifdef __USE_GNU
 /* If this bit is not set, then \ inside a bracket expression is literal.
    If set, then such a \ quotes the following character.  */
-#define RE_BACKSLASH_ESCAPE_IN_LISTS (1)
+# define RE_BACKSLASH_ESCAPE_IN_LISTS ((unsigned long int) 1)
 
 /* If this bit is not set, then + and ? are operators, and \+ and \? are
      literals.
    If set, then \+ and \? are operators and + and ? are literals.  */
-#define RE_BK_PLUS_QM (RE_BACKSLASH_ESCAPE_IN_LISTS << 1)
+# define RE_BK_PLUS_QM (RE_BACKSLASH_ESCAPE_IN_LISTS << 1)
 
 /* If this bit is set, then character classes are supported.  They are:
      [:alpha:], [:upper:], [:lower:],  [:digit:], [:alnum:], [:xdigit:],
      [:space:], [:print:], [:punct:], [:graph:], and [:cntrl:].
    If not set, then character classes are not supported.  */
-#define RE_CHAR_CLASSES (RE_BK_PLUS_QM << 1)
+# define RE_CHAR_CLASSES (RE_BK_PLUS_QM << 1)
 
 /* If this bit is set, then ^ and $ are always anchors (outside bracket
      expressions, of course).
    If this bit is not set, then it depends:
-       ^  is an anchor if it is at the beginning of a regular
-          expression or after an open-group or an alternation operator;
-       $  is an anchor if it is at the end of a regular expression, or
-          before a close-group or an alternation operator.
+        ^  is an anchor if it is at the beginning of a regular
+           expression or after an open-group or an alternation operator;
+        $  is an anchor if it is at the end of a regular expression, or
+           before a close-group or an alternation operator.
 
    This bit could be (re)combined with RE_CONTEXT_INDEP_OPS, because
    POSIX draft 11.2 says that * etc. in leading positions is undefined.
    We already implemented a previous draft which made those constructs
    invalid, though, so we haven't changed the code back.  */
-#define RE_CONTEXT_INDEP_ANCHORS (RE_CHAR_CLASSES << 1)
+# define RE_CONTEXT_INDEP_ANCHORS (RE_CHAR_CLASSES << 1)
 
 /* If this bit is set, then special characters are always special
      regardless of where they are in the pattern.
@@ -72,63 +92,94 @@ typedef unsigned reg_syntax_t;
      some contexts; otherwise they are ordinary.  Specifically,
      * + ? and intervals are only special when not after the beginning,
      open-group, or alternation operator.  */
-#define RE_CONTEXT_INDEP_OPS (RE_CONTEXT_INDEP_ANCHORS << 1)
+# define RE_CONTEXT_INDEP_OPS (RE_CONTEXT_INDEP_ANCHORS << 1)
 
 /* If this bit is set, then *, +, ?, and { cannot be first in an re or
      immediately after an alternation or begin-group operator.  */
-#define RE_CONTEXT_INVALID_OPS (RE_CONTEXT_INDEP_OPS << 1)
+# define RE_CONTEXT_INVALID_OPS (RE_CONTEXT_INDEP_OPS << 1)
 
 /* If this bit is set, then . matches newline.
    If not set, then it doesn't.  */
-#define RE_DOT_NEWLINE (RE_CONTEXT_INVALID_OPS << 1)
+# define RE_DOT_NEWLINE (RE_CONTEXT_INVALID_OPS << 1)
 
 /* If this bit is set, then . doesn't match NUL.
    If not set, then it does.  */
-#define RE_DOT_NOT_NULL (RE_DOT_NEWLINE << 1)
+# define RE_DOT_NOT_NULL (RE_DOT_NEWLINE << 1)
 
 /* If this bit is set, nonmatching lists [^...] do not match newline.
    If not set, they do.  */
-#define RE_HAT_LISTS_NOT_NEWLINE (RE_DOT_NOT_NULL << 1)
+# define RE_HAT_LISTS_NOT_NEWLINE (RE_DOT_NOT_NULL << 1)
 
 /* If this bit is set, either \{...\} or {...} defines an
      interval, depending on RE_NO_BK_BRACES.
    If not set, \{, \}, {, and } are literals.  */
-#define RE_INTERVALS (RE_HAT_LISTS_NOT_NEWLINE << 1)
+# define RE_INTERVALS (RE_HAT_LISTS_NOT_NEWLINE << 1)
 
 /* If this bit is set, +, ? and | aren't recognized as operators.
    If not set, they are.  */
-#define RE_LIMITED_OPS (RE_INTERVALS << 1)
+# define RE_LIMITED_OPS (RE_INTERVALS << 1)
 
 /* If this bit is set, newline is an alternation operator.
    If not set, newline is literal.  */
-#define RE_NEWLINE_ALT (RE_LIMITED_OPS << 1)
+# define RE_NEWLINE_ALT (RE_LIMITED_OPS << 1)
 
 /* If this bit is set, then `{...}' defines an interval, and \{ and \}
      are literals.
   If not set, then `\{...\}' defines an interval.  */
-#define RE_NO_BK_BRACES (RE_NEWLINE_ALT << 1)
+# define RE_NO_BK_BRACES (RE_NEWLINE_ALT << 1)
 
 /* If this bit is set, (...) defines a group, and \( and \) are literals.
    If not set, \(...\) defines a group, and ( and ) are literals.  */
-#define RE_NO_BK_PARENS (RE_NO_BK_BRACES << 1)
+# define RE_NO_BK_PARENS (RE_NO_BK_BRACES << 1)
 
 /* If this bit is set, then \<digit> matches <digit>.
    If not set, then \<digit> is a back-reference.  */
-#define RE_NO_BK_REFS (RE_NO_BK_PARENS << 1)
+# define RE_NO_BK_REFS (RE_NO_BK_PARENS << 1)
 
 /* If this bit is set, then | is an alternation operator, and \| is literal.
    If not set, then \| is an alternation operator, and | is literal.  */
-#define RE_NO_BK_VBAR (RE_NO_BK_REFS << 1)
+# define RE_NO_BK_VBAR (RE_NO_BK_REFS << 1)
 
 /* If this bit is set, then an ending range point collating higher
      than the starting range point, as in [z-a], is invalid.
    If not set, then when ending range point collates higher than the
      starting range point, the range is ignored.  */
-#define RE_NO_EMPTY_RANGES (RE_NO_BK_VBAR << 1)
+# define RE_NO_EMPTY_RANGES (RE_NO_BK_VBAR << 1)
 
 /* If this bit is set, then an unmatched ) is ordinary.
    If not set, then an unmatched ) is invalid.  */
-#define RE_UNMATCHED_RIGHT_PAREN_ORD (RE_NO_EMPTY_RANGES << 1)
+# define RE_UNMATCHED_RIGHT_PAREN_ORD (RE_NO_EMPTY_RANGES << 1)
+
+/* If this bit is set, succeed as soon as we match the whole pattern,
+   without further backtracking.  */
+# define RE_NO_POSIX_BACKTRACKING (RE_UNMATCHED_RIGHT_PAREN_ORD << 1)
+
+/* If this bit is set, do not process the GNU regex operators.
+   If not set, then the GNU regex operators are recognized. */
+# define RE_NO_GNU_OPS (RE_NO_POSIX_BACKTRACKING << 1)
+
+/* If this bit is set, a syntactically invalid interval is treated as
+   a string of ordinary characters.  For example, the ERE 'a{1' is
+   treated as 'a\{1'.  */
+# define RE_INVALID_INTERVAL_ORD (RE_NO_GNU_OPS << 1)
+
+/* If this bit is set, then ignore case when matching.
+   If not set, then case is significant.  */
+# define RE_ICASE (RE_INVALID_INTERVAL_ORD << 1)
+
+/* This bit is used internally like RE_CONTEXT_INDEP_ANCHORS but only
+   for ^, because it is difficult to scan the regex backwards to find
+   whether ^ should be special.  */
+# define RE_CARET_ANCHORS_HERE (RE_ICASE << 1)
+
+/* If this bit is set, then \{ cannot be first in an bre or
+   immediately after an alternation or begin-group operator.  */
+# define RE_CONTEXT_INVALID_DUP (RE_CARET_ANCHORS_HERE << 1)
+
+/* If this bit is set, then no_sub will be set to 1 during
+   re_compile_pattern.  */
+#define RE_NO_SUB (RE_CONTEXT_INVALID_DUP << 1)
+#endif
 
 /* This global variable defines the particular regexp syntax to use (for
    some interfaces).  When a regexp is compiled, the syntax used is
@@ -136,6 +187,7 @@ typedef unsigned reg_syntax_t;
    already-compiled regexps.  */
 extern reg_syntax_t re_syntax_options;
 \f
+#ifdef __USE_GNU
 /* Define combinations of the above bits for the standard possibilities.
    (The [[[ comments delimit what gets put into the Texinfo file, so
    don't delete them!)  */
@@ -143,13 +195,22 @@ extern reg_syntax_t re_syntax_options;
 #define RE_SYNTAX_EMACS 0
 
 #define RE_SYNTAX_AWK                                                  \
-  (RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DOT_NOT_NULL                      \
-   | RE_NO_BK_PARENS            | RE_NO_BK_REFS                                \
-   | RE_NO_BK_VBAR               | RE_NO_EMPTY_RANGES                  \
-   | RE_UNMATCHED_RIGHT_PAREN_ORD)
-
-#define RE_SYNTAX_POSIX_AWK                                            \
-  (RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS)
+  (RE_BACKSLASH_ESCAPE_IN_LISTS   | RE_DOT_NOT_NULL                    \
+   | RE_NO_BK_PARENS              | RE_NO_BK_REFS                      \
+   | RE_NO_BK_VBAR                | RE_NO_EMPTY_RANGES                 \
+   | RE_DOT_NEWLINE              | RE_CONTEXT_INDEP_ANCHORS            \
+   | RE_UNMATCHED_RIGHT_PAREN_ORD | RE_NO_GNU_OPS)
+
+#define RE_SYNTAX_GNU_AWK                                              \
+  ((RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS            \
+   | RE_INVALID_INTERVAL_ORD)                                          \
+   & ~(RE_DOT_NOT_NULL | RE_CONTEXT_INDEP_OPS                          \
+       | RE_CONTEXT_INVALID_OPS ))
+
+#define RE_SYNTAX_POSIX_AWK                                            \
+  (RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS             \
+   | RE_INTERVALS          | RE_NO_GNU_OPS                             \
+   | RE_INVALID_INTERVAL_ORD)
 
 #define RE_SYNTAX_GREP                                                 \
   (RE_BK_PLUS_QM              | RE_CHAR_CLASSES                                \
@@ -163,7 +224,8 @@ extern reg_syntax_t re_syntax_options;
    | RE_NO_BK_VBAR)
 
 #define RE_SYNTAX_POSIX_EGREP                                          \
-  (RE_SYNTAX_EGREP | RE_INTERVALS | RE_NO_BK_BRACES)
+  (RE_SYNTAX_EGREP | RE_INTERVALS | RE_NO_BK_BRACES                    \
+   | RE_INVALID_INTERVAL_ORD)
 
 /* P1003.2/D11.2, section 4.20.7.1, lines 5078ff.  */
 #define RE_SYNTAX_ED RE_SYNTAX_POSIX_BASIC
@@ -176,7 +238,7 @@ extern reg_syntax_t re_syntax_options;
    | RE_INTERVALS  | RE_NO_EMPTY_RANGES)
 
 #define RE_SYNTAX_POSIX_BASIC                                          \
-  (_RE_SYNTAX_POSIX_COMMON | RE_BK_PLUS_QM)
+  (_RE_SYNTAX_POSIX_COMMON | RE_BK_PLUS_QM | RE_CONTEXT_INVALID_DUP)
 
 /* Differs from ..._POSIX_BASIC only in that RE_BK_PLUS_QM becomes
    RE_LIMITED_OPS, i.e., \? \+ \| are not recognized.  Actually, this
@@ -185,13 +247,13 @@ extern reg_syntax_t re_syntax_options;
   (_RE_SYNTAX_POSIX_COMMON | RE_LIMITED_OPS)
 
 #define RE_SYNTAX_POSIX_EXTENDED                                       \
-  (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS                  \
-   | RE_CONTEXT_INDEP_OPS  | RE_NO_BK_BRACES                           \
-   | RE_NO_BK_PARENS       | RE_NO_BK_VBAR                             \
-   | RE_UNMATCHED_RIGHT_PAREN_ORD)
+  (_RE_SYNTAX_POSIX_COMMON  | RE_CONTEXT_INDEP_ANCHORS                 \
+   | RE_CONTEXT_INDEP_OPS   | RE_NO_BK_BRACES                          \
+   | RE_NO_BK_PARENS        | RE_NO_BK_VBAR                            \
+   | RE_CONTEXT_INVALID_OPS | RE_UNMATCHED_RIGHT_PAREN_ORD)
 
-/* Differs from ..._POSIX_EXTENDED in that RE_CONTEXT_INVALID_OPS
-   replaces RE_CONTEXT_INDEP_OPS and RE_NO_BK_REFS is added.  */
+/* Differs from ..._POSIX_EXTENDED in that RE_CONTEXT_INDEP_OPS is
+   removed and RE_NO_BK_REFS is added.  */
 #define RE_SYNTAX_POSIX_MINIMAL_EXTENDED                               \
   (_RE_SYNTAX_POSIX_COMMON  | RE_CONTEXT_INDEP_ANCHORS                 \
    | RE_CONTEXT_INVALID_OPS | RE_NO_BK_BRACES                          \
@@ -202,10 +264,12 @@ extern reg_syntax_t re_syntax_options;
 /* Maximum number of duplicates an interval can allow.  Some systems
    (erroneously) define this in other header files, but we want our
    value, so remove any previous define.  */
-#ifdef RE_DUP_MAX
-#undef RE_DUP_MAX
+# ifdef RE_DUP_MAX
+#  undef RE_DUP_MAX
+# endif
+/* If sizeof(int) == 2, then ((1 << 15) - 1) overflows.  */
+# define RE_DUP_MAX (0x7fff)
 #endif
-#define RE_DUP_MAX ((1 << 15) - 1)
 
 
 /* POSIX `cflags' bits (i.e., information for `regcomp').  */
@@ -240,18 +304,26 @@ extern reg_syntax_t re_syntax_options;
 /* Like REG_NOTBOL, except for the end-of-line.  */
 #define REG_NOTEOL (1 << 1)
 
+/* Use PMATCH[0] to delimit the start and end of the search in the
+   buffer.  */
+#define REG_STARTEND (1 << 2)
+
 
 /* If any error codes are removed, changed, or added, update the
    `re_error_msg' table in regex.c.  */
 typedef enum
 {
+#if defined _XOPEN_SOURCE || defined __USE_XOPEN2K
+  REG_ENOSYS = -1,     /* This will never happen for this implementation.  */
+#endif
+
   REG_NOERROR = 0,     /* Success.  */
   REG_NOMATCH,         /* Didn't find a match (for regexec).  */
 
   /* POSIX regcomp return error codes.  (In the order listed in the
      standard.)  */
   REG_BADPAT,          /* Invalid pattern.  */
-  REG_ECOLLATE,                /* Not implemented.  */
+  REG_ECOLLATE,                /* Inalid collating element.  */
   REG_ECTYPE,          /* Invalid character class name.  */
   REG_EESCAPE,         /* Trailing backslash.  */
   REG_ESUBREG,         /* Invalid back reference.  */
@@ -275,85 +347,92 @@ typedef enum
    compiled, the `re_nsub' field is available.  All other fields are
    private to the regex routines.  */
 
+#ifndef RE_TRANSLATE_TYPE
+# define __RE_TRANSLATE_TYPE unsigned char *
+# ifdef __USE_GNU
+#  define RE_TRANSLATE_TYPE __RE_TRANSLATE_TYPE
+# endif
+#endif
+
+#ifdef __USE_GNU
+# define __REPB_PREFIX(name) name
+#else
+# define __REPB_PREFIX(name) __##name
+#endif
+
 struct re_pattern_buffer
 {
-/* [[[begin pattern_buffer]]] */
-       /* Space that holds the compiled pattern.  It is declared as
-         `unsigned char *' because its elements are
-          sometimes used as array indexes.  */
-  unsigned char *buffer;
+  /* Space that holds the compiled pattern.  It is declared as
+     `unsigned char *' because its elements are sometimes used as
+     array indexes.  */
+  unsigned char *__REPB_PREFIX(buffer);
 
-       /* Number of bytes to which `buffer' points.  */
-  unsigned long allocated;
+  /* Number of bytes to which `buffer' points.  */
+  unsigned long int __REPB_PREFIX(allocated);
 
-       /* Number of bytes actually used in `buffer'.  */
-  unsigned long used;
+  /* Number of bytes actually used in `buffer'.  */
+  unsigned long int __REPB_PREFIX(used);
 
-       /* Syntax setting with which the pattern was compiled.  */
-  reg_syntax_t syntax;
+  /* Syntax setting with which the pattern was compiled.  */
+  reg_syntax_t __REPB_PREFIX(syntax);
 
-       /* Pointer to a fastmap, if any, otherwise zero.  re_search uses
-          the fastmap, if there is one, to skip over impossible
-          starting points for matches.  */
-  char *fastmap;
+  /* Pointer to a fastmap, if any, otherwise zero.  re_search uses the
+     fastmap, if there is one, to skip over impossible starting points
+     for matches.  */
+  char *__REPB_PREFIX(fastmap);
 
-       /* Either a translate table to apply to all characters before
-          comparing them, or zero for no translation.  The translation
-          is applied to a pattern when it is compiled and to a string
-          when it is matched.  */
-  char *translate;
+  /* Either a translate table to apply to all characters before
+     comparing them, or zero for no translation.  The translation is
+     applied to a pattern when it is compiled and to a string when it
+     is matched.  */
+  __RE_TRANSLATE_TYPE __REPB_PREFIX(translate);
 
-       /* Number of subexpressions found by the compiler.  */
+  /* Number of subexpressions found by the compiler.  */
   size_t re_nsub;
 
-       /* Zero if this pattern cannot match the empty string, one else.
-          Well, in truth it's used only in `re_search_2', to see
-          whether or not we should use the fastmap, so we don't set
-          this absolutely perfectly; see `re_compile_fastmap' (the
-          `duplicate' case).  */
-  unsigned can_be_null : 1;
-
-       /* If REGS_UNALLOCATED, allocate space in the `regs' structure
-            for `max (RE_NREGS, re_nsub + 1)' groups.
-          If REGS_REALLOCATE, reallocate space if necessary.
-          If REGS_FIXED, use what's there.  */
-#define REGS_UNALLOCATED 0
-#define REGS_REALLOCATE 1
-#define REGS_FIXED 2
-  unsigned regs_allocated : 2;
-
-       /* Set to zero when `regex_compile' compiles a pattern; set to one
-          by `re_compile_fastmap' if it updates the fastmap.  */
-  unsigned fastmap_accurate : 1;
-
-       /* If set, `re_match_2' does not return information about
-          subexpressions.  */
-  unsigned no_sub : 1;
-
-       /* If set, a beginning-of-line anchor doesn't match at the
-          beginning of the string.  */
-  unsigned not_bol : 1;
-
-       /* Similarly for an end-of-line anchor.  */
-  unsigned not_eol : 1;
-
-       /* If true, an anchor at a newline matches.  */
-  unsigned newline_anchor : 1;
-
-/* [[[end pattern_buffer]]] */
-};
+  /* Zero if this pattern cannot match the empty string, one else.
+     Well, in truth it's used only in `re_search_2', to see whether or
+     not we should use the fastmap, so we don't set this absolutely
+     perfectly; see `re_compile_fastmap' (the `duplicate' case).  */
+  unsigned __REPB_PREFIX(can_be_null) : 1;
+
+  /* If REGS_UNALLOCATED, allocate space in the `regs' structure
+     for `max (RE_NREGS, re_nsub + 1)' groups.
+     If REGS_REALLOCATE, reallocate space if necessary.
+     If REGS_FIXED, use what's there.  */
+#ifdef __USE_GNU
+# define REGS_UNALLOCATED 0
+# define REGS_REALLOCATE 1
+# define REGS_FIXED 2
+#endif
+  unsigned __REPB_PREFIX(regs_allocated) : 2;
 
-typedef struct re_pattern_buffer regex_t;
+  /* Set to zero when `regex_compile' compiles a pattern; set to one
+     by `re_compile_fastmap' if it updates the fastmap.  */
+  unsigned __REPB_PREFIX(fastmap_accurate) : 1;
+
+  /* If set, `re_match_2' does not return information about
+     subexpressions.  */
+  unsigned __REPB_PREFIX(no_sub) : 1;
+
+  /* If set, a beginning-of-line anchor doesn't match at the beginning
+     of the string.  */
+  unsigned __REPB_PREFIX(not_bol) : 1;
+
+  /* Similarly for an end-of-line anchor.  */
+  unsigned __REPB_PREFIX(not_eol) : 1;
 
+  /* If true, an anchor at a newline matches.  */
+  unsigned __REPB_PREFIX(newline_anchor) : 1;
+};
 
-/* search.c (search_buffer) in Emacs needs this one opcode value.  It is
-   defined both in `regex.c' and here.  */
-#define RE_EXACTN_VALUE 1
+typedef struct re_pattern_buffer regex_t;
 \f
 /* Type for byte offsets within the string.  POSIX mandates this.  */
 typedef int regoff_t;
 
 
+#ifdef __USE_GNU
 /* This is the structure we store register match data in.  See
    regex.texinfo for a full description of what registers match.  */
 struct re_registers
@@ -367,8 +446,9 @@ struct re_registers
 /* If `regs_allocated' is REGS_UNALLOCATED in the pattern buffer,
    `re_match_2' returns information about at least this many registers
    the first time a `regs' structure is passed.  */
-#ifndef RE_NREGS
-#define RE_NREGS 30
+# ifndef RE_NREGS
+#  define RE_NREGS 30
+# endif
 #endif
 
 
@@ -383,38 +463,22 @@ typedef struct
 \f
 /* Declarations for routines.  */
 
-/* To avoid duplicating every routine declaration -- once with a
-   prototype (if we are ANSI), and once without (if we aren't) -- we
-   use the following macro to declare argument types.  This
-   unfortunately clutters up the declarations a bit, but I think it's
-   worth it.  */
-
-#if __STDC__
-
-#define _RE_ARGS(args) args
-
-#else /* not __STDC__ */
-
-#define _RE_ARGS(args) ()
-
-#endif /* not __STDC__ */
-
+#ifdef __USE_GNU
 /* Sets the current default syntax to SYNTAX, and return the old syntax.
    You can also simply assign to the `re_syntax_options' variable.  */
-extern reg_syntax_t re_set_syntax _RE_ARGS ((reg_syntax_t syntax));
+extern reg_syntax_t re_set_syntax (reg_syntax_t __syntax);
 
 /* Compile the regular expression PATTERN, with length LENGTH
    and syntax given by the global `re_syntax_options', into the buffer
    BUFFER.  Return NULL if successful, and an error string if not.  */
-extern const char *re_compile_pattern
-  _RE_ARGS ((const char *pattern, int length,
-            struct re_pattern_buffer *buffer));
+extern const char *re_compile_pattern (const char *__pattern, size_t __length,
+                                      struct re_pattern_buffer *__buffer);
 
 
 /* Compile a fastmap for the compiled pattern in BUFFER; used to
    accelerate searches.  Return 0 if successful and -2 if was an
    internal error.  */
-extern int re_compile_fastmap _RE_ARGS ((struct re_pattern_buffer *buffer));
+extern int re_compile_fastmap (struct re_pattern_buffer *__buffer);
 
 
 /* Search in the string STRING (with length LENGTH) for the pattern
@@ -422,31 +486,30 @@ extern int re_compile_fastmap _RE_ARGS ((struct re_pattern_buffer *buffer));
    characters.  Return the starting position of the match, -1 for no
    match, or -2 for an internal error.  Also return register
    information in REGS (if REGS and BUFFER->no_sub are nonzero).  */
-extern int re_search
-  _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string,
-           int length, int start, int range, struct re_registers *regs));
+extern int re_search (struct re_pattern_buffer *__buffer, const char *__cstring,
+                     int __length, int __start, int __range,
+                     struct re_registers *__regs);
 
 
 /* Like `re_search', but search in the concatenation of STRING1 and
    STRING2.  Also, stop searching at index START + STOP.  */
-extern int re_search_2
-  _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1,
-            int length1, const char *string2, int length2,
-            int start, int range, struct re_registers *regs, int stop));
+extern int re_search_2 (struct re_pattern_buffer *__buffer,
+                       const char *__string1, int __length1,
+                       const char *__string2, int __length2, int __start,
+                       int __range, struct re_registers *__regs, int __stop);
 
 
 /* Like `re_search', but return how many characters in STRING the regexp
    in BUFFER matched, starting at position START.  */
-extern int re_match
-  _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string,
-            int length, int start, struct re_registers *regs));
+extern int re_match (struct re_pattern_buffer *__buffer, const char *__cstring,
+                    int __length, int __start, struct re_registers *__regs);
 
 
 /* Relates to `re_match' as `re_search_2' relates to `re_search'.  */
-extern int re_match_2
-  _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1,
-            int length1, const char *string2, int length2,
-            int start, struct re_registers *regs, int stop));
+extern int re_match_2 (struct re_pattern_buffer *__buffer,
+                      const char *__string1, int __length1,
+                      const char *__string2, int __length2, int __start,
+                      struct re_registers *__regs, int __stop);
 
 
 /* Set REGS to hold NUM_REGS registers, storing them in STARTS and
@@ -461,30 +524,59 @@ extern int re_match_2
    Unless this function is called, the first search or match using
    PATTERN_BUFFER will allocate its own register data, without
    freeing the old data.  */
-extern void re_set_registers
-  _RE_ARGS ((struct re_pattern_buffer *buffer, struct re_registers *regs,
-            unsigned num_regs, regoff_t *starts, regoff_t *ends));
-
+extern void re_set_registers (struct re_pattern_buffer *__buffer,
+                             struct re_registers *__regs,
+                             unsigned int __num_regs,
+                             regoff_t *__starts, regoff_t *__ends);
+#endif /* Use GNU */
+
+#if defined _REGEX_RE_COMP || (defined _LIBC && defined __USE_BSD)
+# ifndef _CRAY
 /* 4.2 bsd compatibility.  */
-extern char *re_comp _RE_ARGS ((const char *));
-extern int re_exec _RE_ARGS ((const char *));
+extern char *re_comp (const char *);
+extern int re_exec (const char *);
+# endif
+#endif
+
+/* GCC 2.95 and later have "__restrict"; C99 compilers have
+   "restrict", and "configure" may have defined "restrict".  */
+#ifndef __restrict
+# if ! (2 < __GNUC__ || (2 == __GNUC__ && 95 <= __GNUC_MINOR__))
+#  if defined restrict || 199901L <= __STDC_VERSION__
+#   define __restrict restrict
+#  else
+#   define __restrict
+#  endif
+# endif
+#endif
+/* gcc 3.1 and up support the [restrict] syntax.  */
+#ifndef __restrict_arr
+# if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) \
+     && !defined __GNUG__
+#  define __restrict_arr __restrict
+# else
+#  define __restrict_arr
+# endif
+#endif
 
 /* POSIX compatibility.  */
-extern int regcomp _RE_ARGS ((regex_t *preg, const char *pattern, int cflags));
-extern int regexec
-  _RE_ARGS ((const regex_t *preg, const char *string, size_t nmatch,
-            regmatch_t pmatch[], int eflags));
-extern size_t regerror
-  _RE_ARGS ((int errcode, const regex_t *preg, char *errbuf,
-            size_t errbuf_size));
-extern void regfree _RE_ARGS ((regex_t *preg));
-
-#endif /* not __REGEXP_LIBRARY_H__ */
-\f
-/*
-Local variables:
-make-backup-files: t
-version-control: t
-trim-versions-without-asking: nil
-End:
-*/
+extern int regcomp (regex_t *__restrict __preg,
+                   const char *__restrict __pattern,
+                   int __cflags);
+
+extern int regexec (const regex_t *__restrict __preg,
+                   const char *__restrict __cstring, size_t __nmatch,
+                   regmatch_t __pmatch[__restrict_arr],
+                   int __eflags);
+
+extern size_t regerror (int __errcode, const regex_t *__restrict __preg,
+                       char *__restrict __errbuf, size_t __errbuf_size);
+
+extern void regfree (regex_t *__preg);
+
+
+#ifdef __cplusplus
+}
+#endif /* C++ */
+
+#endif /* regex.h */
diff --git a/compat/regex/regex_internal.c b/compat/regex/regex_internal.c
new file mode 100644 (file)
index 0000000..193854c
--- /dev/null
@@ -0,0 +1,1744 @@
+/* Extended regular expression matching and search library.
+   Copyright (C) 2002-2006, 2010 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+   Contributed by Isamu Hasegawa <isamu@yamato.ibm.com>.
+
+   The GNU C 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.
+
+   The GNU C 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 the GNU C Library; if not, write to the Free
+   Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301 USA.  */
+
+static void re_string_construct_common (const char *str, int len,
+                                       re_string_t *pstr,
+                                       RE_TRANSLATE_TYPE trans, int icase,
+                                       const re_dfa_t *dfa) internal_function;
+static re_dfastate_t *create_ci_newstate (const re_dfa_t *dfa,
+                                         const re_node_set *nodes,
+                                         unsigned int hash) internal_function;
+static re_dfastate_t *create_cd_newstate (const re_dfa_t *dfa,
+                                         const re_node_set *nodes,
+                                         unsigned int context,
+                                         unsigned int hash) internal_function;
+
+#ifdef GAWK
+#undef MAX     /* safety */
+static int
+MAX(size_t a, size_t b)
+{
+       return (a > b ? a : b);
+}
+#endif
+\f
+/* Functions for string operation.  */
+
+/* This function allocate the buffers.  It is necessary to call
+   re_string_reconstruct before using the object.  */
+
+static reg_errcode_t
+internal_function
+re_string_allocate (re_string_t *pstr, const char *str, int len, int init_len,
+                   RE_TRANSLATE_TYPE trans, int icase, const re_dfa_t *dfa)
+{
+  reg_errcode_t ret;
+  int init_buf_len;
+
+  /* Ensure at least one character fits into the buffers.  */
+  if (init_len < dfa->mb_cur_max)
+    init_len = dfa->mb_cur_max;
+  init_buf_len = (len + 1 < init_len) ? len + 1: init_len;
+  re_string_construct_common (str, len, pstr, trans, icase, dfa);
+
+  ret = re_string_realloc_buffers (pstr, init_buf_len);
+  if (BE (ret != REG_NOERROR, 0))
+    return ret;
+
+  pstr->word_char = dfa->word_char;
+  pstr->word_ops_used = dfa->word_ops_used;
+  pstr->mbs = pstr->mbs_allocated ? pstr->mbs : (unsigned char *) str;
+  pstr->valid_len = (pstr->mbs_allocated || dfa->mb_cur_max > 1) ? 0 : len;
+  pstr->valid_raw_len = pstr->valid_len;
+  return REG_NOERROR;
+}
+
+/* This function allocate the buffers, and initialize them.  */
+
+static reg_errcode_t
+internal_function
+re_string_construct (re_string_t *pstr, const char *str, int len,
+                    RE_TRANSLATE_TYPE trans, int icase, const re_dfa_t *dfa)
+{
+  reg_errcode_t ret;
+  memset (pstr, '\0', sizeof (re_string_t));
+  re_string_construct_common (str, len, pstr, trans, icase, dfa);
+
+  if (len > 0)
+    {
+      ret = re_string_realloc_buffers (pstr, len + 1);
+      if (BE (ret != REG_NOERROR, 0))
+       return ret;
+    }
+  pstr->mbs = pstr->mbs_allocated ? pstr->mbs : (unsigned char *) str;
+
+  if (icase)
+    {
+#ifdef RE_ENABLE_I18N
+      if (dfa->mb_cur_max > 1)
+       {
+         while (1)
+           {
+             ret = build_wcs_upper_buffer (pstr);
+             if (BE (ret != REG_NOERROR, 0))
+               return ret;
+             if (pstr->valid_raw_len >= len)
+               break;
+             if (pstr->bufs_len > pstr->valid_len + dfa->mb_cur_max)
+               break;
+             ret = re_string_realloc_buffers (pstr, pstr->bufs_len * 2);
+             if (BE (ret != REG_NOERROR, 0))
+               return ret;
+           }
+       }
+      else
+#endif /* RE_ENABLE_I18N  */
+       build_upper_buffer (pstr);
+    }
+  else
+    {
+#ifdef RE_ENABLE_I18N
+      if (dfa->mb_cur_max > 1)
+       build_wcs_buffer (pstr);
+      else
+#endif /* RE_ENABLE_I18N  */
+       {
+         if (trans != NULL)
+           re_string_translate_buffer (pstr);
+         else
+           {
+             pstr->valid_len = pstr->bufs_len;
+             pstr->valid_raw_len = pstr->bufs_len;
+           }
+       }
+    }
+
+  return REG_NOERROR;
+}
+
+/* Helper functions for re_string_allocate, and re_string_construct.  */
+
+static reg_errcode_t
+internal_function
+re_string_realloc_buffers (re_string_t *pstr, int new_buf_len)
+{
+#ifdef RE_ENABLE_I18N
+  if (pstr->mb_cur_max > 1)
+    {
+      wint_t *new_wcs;
+
+      /* Avoid overflow in realloc.  */
+      const size_t max_object_size = MAX (sizeof (wint_t), sizeof (int));
+      if (BE (SIZE_MAX / max_object_size < new_buf_len, 0))
+       return REG_ESPACE;
+
+      new_wcs = re_realloc (pstr->wcs, wint_t, new_buf_len);
+      if (BE (new_wcs == NULL, 0))
+       return REG_ESPACE;
+      pstr->wcs = new_wcs;
+      if (pstr->offsets != NULL)
+       {
+         int *new_offsets = re_realloc (pstr->offsets, int, new_buf_len);
+         if (BE (new_offsets == NULL, 0))
+           return REG_ESPACE;
+         pstr->offsets = new_offsets;
+       }
+    }
+#endif /* RE_ENABLE_I18N  */
+  if (pstr->mbs_allocated)
+    {
+      unsigned char *new_mbs = re_realloc (pstr->mbs, unsigned char,
+                                          new_buf_len);
+      if (BE (new_mbs == NULL, 0))
+       return REG_ESPACE;
+      pstr->mbs = new_mbs;
+    }
+  pstr->bufs_len = new_buf_len;
+  return REG_NOERROR;
+}
+
+
+static void
+internal_function
+re_string_construct_common (const char *str, int len, re_string_t *pstr,
+                           RE_TRANSLATE_TYPE trans, int icase,
+                           const re_dfa_t *dfa)
+{
+  pstr->raw_mbs = (const unsigned char *) str;
+  pstr->len = len;
+  pstr->raw_len = len;
+  pstr->trans = trans;
+  pstr->icase = icase ? 1 : 0;
+  pstr->mbs_allocated = (trans != NULL || icase);
+  pstr->mb_cur_max = dfa->mb_cur_max;
+  pstr->is_utf8 = dfa->is_utf8;
+  pstr->map_notascii = dfa->map_notascii;
+  pstr->stop = pstr->len;
+  pstr->raw_stop = pstr->stop;
+}
+
+#ifdef RE_ENABLE_I18N
+
+/* Build wide character buffer PSTR->WCS.
+   If the byte sequence of the string are:
+     <mb1>(0), <mb1>(1), <mb2>(0), <mb2>(1), <sb3>
+   Then wide character buffer will be:
+     <wc1>   , WEOF    , <wc2>   , WEOF    , <wc3>
+   We use WEOF for padding, they indicate that the position isn't
+   a first byte of a multibyte character.
+
+   Note that this function assumes PSTR->VALID_LEN elements are already
+   built and starts from PSTR->VALID_LEN.  */
+
+static void
+internal_function
+build_wcs_buffer (re_string_t *pstr)
+{
+#ifdef _LIBC
+  unsigned char buf[MB_LEN_MAX];
+  assert (MB_LEN_MAX >= pstr->mb_cur_max);
+#else
+  unsigned char buf[64];
+#endif
+  mbstate_t prev_st;
+  int byte_idx, end_idx, remain_len;
+  size_t mbclen;
+
+  /* Build the buffers from pstr->valid_len to either pstr->len or
+     pstr->bufs_len.  */
+  end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len;
+  for (byte_idx = pstr->valid_len; byte_idx < end_idx;)
+    {
+      wchar_t wc;
+      const char *p;
+
+      remain_len = end_idx - byte_idx;
+      prev_st = pstr->cur_state;
+      /* Apply the translation if we need.  */
+      if (BE (pstr->trans != NULL, 0))
+       {
+         int i, ch;
+
+         for (i = 0; i < pstr->mb_cur_max && i < remain_len; ++i)
+           {
+             ch = pstr->raw_mbs [pstr->raw_mbs_idx + byte_idx + i];
+             buf[i] = pstr->mbs[byte_idx + i] = pstr->trans[ch];
+           }
+         p = (const char *) buf;
+       }
+      else
+       p = (const char *) pstr->raw_mbs + pstr->raw_mbs_idx + byte_idx;
+      mbclen = __mbrtowc (&wc, p, remain_len, &pstr->cur_state);
+      if (BE (mbclen == (size_t) -2, 0))
+       {
+         /* The buffer doesn't have enough space, finish to build.  */
+         pstr->cur_state = prev_st;
+         break;
+       }
+      else if (BE (mbclen == (size_t) -1 || mbclen == 0, 0))
+       {
+         /* We treat these cases as a singlebyte character.  */
+         mbclen = 1;
+         wc = (wchar_t) pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx];
+         if (BE (pstr->trans != NULL, 0))
+           wc = pstr->trans[wc];
+         pstr->cur_state = prev_st;
+       }
+
+      /* Write wide character and padding.  */
+      pstr->wcs[byte_idx++] = wc;
+      /* Write paddings.  */
+      for (remain_len = byte_idx + mbclen - 1; byte_idx < remain_len ;)
+       pstr->wcs[byte_idx++] = WEOF;
+    }
+  pstr->valid_len = byte_idx;
+  pstr->valid_raw_len = byte_idx;
+}
+
+/* Build wide character buffer PSTR->WCS like build_wcs_buffer,
+   but for REG_ICASE.  */
+
+static reg_errcode_t
+internal_function
+build_wcs_upper_buffer (re_string_t *pstr)
+{
+  mbstate_t prev_st;
+  int src_idx, byte_idx, end_idx, remain_len;
+  size_t mbclen;
+#ifdef _LIBC
+  char buf[MB_LEN_MAX];
+  assert (MB_LEN_MAX >= pstr->mb_cur_max);
+#else
+  char buf[64];
+#endif
+
+  byte_idx = pstr->valid_len;
+  end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len;
+
+  /* The following optimization assumes that ASCII characters can be
+     mapped to wide characters with a simple cast.  */
+  if (! pstr->map_notascii && pstr->trans == NULL && !pstr->offsets_needed)
+    {
+      while (byte_idx < end_idx)
+       {
+         wchar_t wc;
+
+         if (isascii (pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx])
+             && mbsinit (&pstr->cur_state))
+           {
+             /* In case of a singlebyte character.  */
+             pstr->mbs[byte_idx]
+               = toupper (pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx]);
+             /* The next step uses the assumption that wchar_t is encoded
+                ASCII-safe: all ASCII values can be converted like this.  */
+             pstr->wcs[byte_idx] = (wchar_t) pstr->mbs[byte_idx];
+             ++byte_idx;
+             continue;
+           }
+
+         remain_len = end_idx - byte_idx;
+         prev_st = pstr->cur_state;
+         mbclen = __mbrtowc (&wc,
+                             ((const char *) pstr->raw_mbs + pstr->raw_mbs_idx
+                              + byte_idx), remain_len, &pstr->cur_state);
+         if (BE (mbclen + 2 > 2, 1))
+           {
+             wchar_t wcu = wc;
+             if (iswlower (wc))
+               {
+                 size_t mbcdlen;
+
+                 wcu = towupper (wc);
+                 mbcdlen = wcrtomb (buf, wcu, &prev_st);
+                 if (BE (mbclen == mbcdlen, 1))
+                   memcpy (pstr->mbs + byte_idx, buf, mbclen);
+                 else
+                   {
+                     src_idx = byte_idx;
+                     goto offsets_needed;
+                   }
+               }
+             else
+               memcpy (pstr->mbs + byte_idx,
+                       pstr->raw_mbs + pstr->raw_mbs_idx + byte_idx, mbclen);
+             pstr->wcs[byte_idx++] = wcu;
+             /* Write paddings.  */
+             for (remain_len = byte_idx + mbclen - 1; byte_idx < remain_len ;)
+               pstr->wcs[byte_idx++] = WEOF;
+           }
+         else if (mbclen == (size_t) -1 || mbclen == 0)
+           {
+             /* It is an invalid character or '\0'.  Just use the byte.  */
+             int ch = pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx];
+             pstr->mbs[byte_idx] = ch;
+             /* And also cast it to wide char.  */
+             pstr->wcs[byte_idx++] = (wchar_t) ch;
+             if (BE (mbclen == (size_t) -1, 0))
+               pstr->cur_state = prev_st;
+           }
+         else
+           {
+             /* The buffer doesn't have enough space, finish to build.  */
+             pstr->cur_state = prev_st;
+             break;
+           }
+       }
+      pstr->valid_len = byte_idx;
+      pstr->valid_raw_len = byte_idx;
+      return REG_NOERROR;
+    }
+  else
+    for (src_idx = pstr->valid_raw_len; byte_idx < end_idx;)
+      {
+       wchar_t wc;
+       const char *p;
+      offsets_needed:
+       remain_len = end_idx - byte_idx;
+       prev_st = pstr->cur_state;
+       if (BE (pstr->trans != NULL, 0))
+         {
+           int i, ch;
+
+           for (i = 0; i < pstr->mb_cur_max && i < remain_len; ++i)
+             {
+               ch = pstr->raw_mbs [pstr->raw_mbs_idx + src_idx + i];
+               buf[i] = pstr->trans[ch];
+             }
+           p = (const char *) buf;
+         }
+       else
+         p = (const char *) pstr->raw_mbs + pstr->raw_mbs_idx + src_idx;
+       mbclen = __mbrtowc (&wc, p, remain_len, &pstr->cur_state);
+       if (BE (mbclen + 2 > 2, 1))
+         {
+           wchar_t wcu = wc;
+           if (iswlower (wc))
+             {
+               size_t mbcdlen;
+
+               wcu = towupper (wc);
+               mbcdlen = wcrtomb ((char *) buf, wcu, &prev_st);
+               if (BE (mbclen == mbcdlen, 1))
+                 memcpy (pstr->mbs + byte_idx, buf, mbclen);
+               else if (mbcdlen != (size_t) -1)
+                 {
+                   size_t i;
+
+                   if (byte_idx + mbcdlen > pstr->bufs_len)
+                     {
+                       pstr->cur_state = prev_st;
+                       break;
+                     }
+
+                   if (pstr->offsets == NULL)
+                     {
+                       pstr->offsets = re_malloc (int, pstr->bufs_len);
+
+                       if (pstr->offsets == NULL)
+                         return REG_ESPACE;
+                     }
+                   if (!pstr->offsets_needed)
+                     {
+                       for (i = 0; i < (size_t) byte_idx; ++i)
+                         pstr->offsets[i] = i;
+                       pstr->offsets_needed = 1;
+                     }
+
+                   memcpy (pstr->mbs + byte_idx, buf, mbcdlen);
+                   pstr->wcs[byte_idx] = wcu;
+                   pstr->offsets[byte_idx] = src_idx;
+                   for (i = 1; i < mbcdlen; ++i)
+                     {
+                       pstr->offsets[byte_idx + i]
+                         = src_idx + (i < mbclen ? i : mbclen - 1);
+                       pstr->wcs[byte_idx + i] = WEOF;
+                     }
+                   pstr->len += mbcdlen - mbclen;
+                   if (pstr->raw_stop > src_idx)
+                     pstr->stop += mbcdlen - mbclen;
+                   end_idx = (pstr->bufs_len > pstr->len)
+                             ? pstr->len : pstr->bufs_len;
+                   byte_idx += mbcdlen;
+                   src_idx += mbclen;
+                   continue;
+                 }
+               else
+                 memcpy (pstr->mbs + byte_idx, p, mbclen);
+             }
+           else
+             memcpy (pstr->mbs + byte_idx, p, mbclen);
+
+           if (BE (pstr->offsets_needed != 0, 0))
+             {
+               size_t i;
+               for (i = 0; i < mbclen; ++i)
+                 pstr->offsets[byte_idx + i] = src_idx + i;
+             }
+           src_idx += mbclen;
+
+           pstr->wcs[byte_idx++] = wcu;
+           /* Write paddings.  */
+           for (remain_len = byte_idx + mbclen - 1; byte_idx < remain_len ;)
+             pstr->wcs[byte_idx++] = WEOF;
+         }
+       else if (mbclen == (size_t) -1 || mbclen == 0)
+         {
+           /* It is an invalid character or '\0'.  Just use the byte.  */
+           int ch = pstr->raw_mbs[pstr->raw_mbs_idx + src_idx];
+
+           if (BE (pstr->trans != NULL, 0))
+             ch = pstr->trans [ch];
+           pstr->mbs[byte_idx] = ch;
+
+           if (BE (pstr->offsets_needed != 0, 0))
+             pstr->offsets[byte_idx] = src_idx;
+           ++src_idx;
+
+           /* And also cast it to wide char.  */
+           pstr->wcs[byte_idx++] = (wchar_t) ch;
+           if (BE (mbclen == (size_t) -1, 0))
+             pstr->cur_state = prev_st;
+         }
+       else
+         {
+           /* The buffer doesn't have enough space, finish to build.  */
+           pstr->cur_state = prev_st;
+           break;
+         }
+      }
+  pstr->valid_len = byte_idx;
+  pstr->valid_raw_len = src_idx;
+  return REG_NOERROR;
+}
+
+/* Skip characters until the index becomes greater than NEW_RAW_IDX.
+   Return the index.  */
+
+static int
+internal_function
+re_string_skip_chars (re_string_t *pstr, int new_raw_idx, wint_t *last_wc)
+{
+  mbstate_t prev_st;
+  int rawbuf_idx;
+  size_t mbclen;
+  wint_t wc = WEOF;
+
+  /* Skip the characters which are not necessary to check.  */
+  for (rawbuf_idx = pstr->raw_mbs_idx + pstr->valid_raw_len;
+       rawbuf_idx < new_raw_idx;)
+    {
+      wchar_t wc2;
+      int remain_len = pstr->len - rawbuf_idx;
+      prev_st = pstr->cur_state;
+      mbclen = __mbrtowc (&wc2, (const char *) pstr->raw_mbs + rawbuf_idx,
+                         remain_len, &pstr->cur_state);
+      if (BE (mbclen == (size_t) -2 || mbclen == (size_t) -1 || mbclen == 0, 0))
+       {
+         /* We treat these cases as a single byte character.  */
+         if (mbclen == 0 || remain_len == 0)
+           wc = L'\0';
+         else
+           wc = *(unsigned char *) (pstr->raw_mbs + rawbuf_idx);
+         mbclen = 1;
+         pstr->cur_state = prev_st;
+       }
+      else
+       wc = (wint_t) wc2;
+      /* Then proceed the next character.  */
+      rawbuf_idx += mbclen;
+    }
+  *last_wc = (wint_t) wc;
+  return rawbuf_idx;
+}
+#endif /* RE_ENABLE_I18N  */
+
+/* Build the buffer PSTR->MBS, and apply the translation if we need.
+   This function is used in case of REG_ICASE.  */
+
+static void
+internal_function
+build_upper_buffer (re_string_t *pstr)
+{
+  int char_idx, end_idx;
+  end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len;
+
+  for (char_idx = pstr->valid_len; char_idx < end_idx; ++char_idx)
+    {
+      int ch = pstr->raw_mbs[pstr->raw_mbs_idx + char_idx];
+      if (BE (pstr->trans != NULL, 0))
+       ch = pstr->trans[ch];
+      if (islower (ch))
+       pstr->mbs[char_idx] = toupper (ch);
+      else
+       pstr->mbs[char_idx] = ch;
+    }
+  pstr->valid_len = char_idx;
+  pstr->valid_raw_len = char_idx;
+}
+
+/* Apply TRANS to the buffer in PSTR.  */
+
+static void
+internal_function
+re_string_translate_buffer (re_string_t *pstr)
+{
+  int buf_idx, end_idx;
+  end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len;
+
+  for (buf_idx = pstr->valid_len; buf_idx < end_idx; ++buf_idx)
+    {
+      int ch = pstr->raw_mbs[pstr->raw_mbs_idx + buf_idx];
+      pstr->mbs[buf_idx] = pstr->trans[ch];
+    }
+
+  pstr->valid_len = buf_idx;
+  pstr->valid_raw_len = buf_idx;
+}
+
+/* This function re-construct the buffers.
+   Concretely, convert to wide character in case of pstr->mb_cur_max > 1,
+   convert to upper case in case of REG_ICASE, apply translation.  */
+
+static reg_errcode_t
+internal_function
+re_string_reconstruct (re_string_t *pstr, int idx, int eflags)
+{
+  int offset = idx - pstr->raw_mbs_idx;
+  if (BE (offset < 0, 0))
+    {
+      /* Reset buffer.  */
+#ifdef RE_ENABLE_I18N
+      if (pstr->mb_cur_max > 1)
+       memset (&pstr->cur_state, '\0', sizeof (mbstate_t));
+#endif /* RE_ENABLE_I18N */
+      pstr->len = pstr->raw_len;
+      pstr->stop = pstr->raw_stop;
+      pstr->valid_len = 0;
+      pstr->raw_mbs_idx = 0;
+      pstr->valid_raw_len = 0;
+      pstr->offsets_needed = 0;
+      pstr->tip_context = ((eflags & REG_NOTBOL) ? CONTEXT_BEGBUF
+                          : CONTEXT_NEWLINE | CONTEXT_BEGBUF);
+      if (!pstr->mbs_allocated)
+       pstr->mbs = (unsigned char *) pstr->raw_mbs;
+      offset = idx;
+    }
+
+  if (BE (offset != 0, 1))
+    {
+      /* Should the already checked characters be kept?  */
+      if (BE (offset < pstr->valid_raw_len, 1))
+       {
+         /* Yes, move them to the front of the buffer.  */
+#ifdef RE_ENABLE_I18N
+         if (BE (pstr->offsets_needed, 0))
+           {
+             int low = 0, high = pstr->valid_len, mid;
+             do
+               {
+                 mid = (high + low) / 2;
+                 if (pstr->offsets[mid] > offset)
+                   high = mid;
+                 else if (pstr->offsets[mid] < offset)
+                   low = mid + 1;
+                 else
+                   break;
+               }
+             while (low < high);
+             if (pstr->offsets[mid] < offset)
+               ++mid;
+             pstr->tip_context = re_string_context_at (pstr, mid - 1,
+                                                       eflags);
+             /* This can be quite complicated, so handle specially
+                only the common and easy case where the character with
+                different length representation of lower and upper
+                case is present at or after offset.  */
+             if (pstr->valid_len > offset
+                 && mid == offset && pstr->offsets[mid] == offset)
+               {
+                 memmove (pstr->wcs, pstr->wcs + offset,
+                          (pstr->valid_len - offset) * sizeof (wint_t));
+                 memmove (pstr->mbs, pstr->mbs + offset, pstr->valid_len - offset);
+                 pstr->valid_len -= offset;
+                 pstr->valid_raw_len -= offset;
+                 for (low = 0; low < pstr->valid_len; low++)
+                   pstr->offsets[low] = pstr->offsets[low + offset] - offset;
+               }
+             else
+               {
+                 /* Otherwise, just find out how long the partial multibyte
+                    character at offset is and fill it with WEOF/255.  */
+                 pstr->len = pstr->raw_len - idx + offset;
+                 pstr->stop = pstr->raw_stop - idx + offset;
+                 pstr->offsets_needed = 0;
+                 while (mid > 0 && pstr->offsets[mid - 1] == offset)
+                   --mid;
+                 while (mid < pstr->valid_len)
+                   if (pstr->wcs[mid] != WEOF)
+                     break;
+                   else
+                     ++mid;
+                 if (mid == pstr->valid_len)
+                   pstr->valid_len = 0;
+                 else
+                   {
+                     pstr->valid_len = pstr->offsets[mid] - offset;
+                     if (pstr->valid_len)
+                       {
+                         for (low = 0; low < pstr->valid_len; ++low)
+                           pstr->wcs[low] = WEOF;
+                         memset (pstr->mbs, 255, pstr->valid_len);
+                       }
+                   }
+                 pstr->valid_raw_len = pstr->valid_len;
+               }
+           }
+         else
+#endif
+           {
+             pstr->tip_context = re_string_context_at (pstr, offset - 1,
+                                                       eflags);
+#ifdef RE_ENABLE_I18N
+             if (pstr->mb_cur_max > 1)
+               memmove (pstr->wcs, pstr->wcs + offset,
+                        (pstr->valid_len - offset) * sizeof (wint_t));
+#endif /* RE_ENABLE_I18N */
+             if (BE (pstr->mbs_allocated, 0))
+               memmove (pstr->mbs, pstr->mbs + offset,
+                        pstr->valid_len - offset);
+             pstr->valid_len -= offset;
+             pstr->valid_raw_len -= offset;
+#if DEBUG
+             assert (pstr->valid_len > 0);
+#endif
+           }
+       }
+      else
+       {
+#ifdef RE_ENABLE_I18N
+         /* No, skip all characters until IDX.  */
+         int prev_valid_len = pstr->valid_len;
+
+         if (BE (pstr->offsets_needed, 0))
+           {
+             pstr->len = pstr->raw_len - idx + offset;
+             pstr->stop = pstr->raw_stop - idx + offset;
+             pstr->offsets_needed = 0;
+           }
+#endif
+         pstr->valid_len = 0;
+#ifdef RE_ENABLE_I18N
+         if (pstr->mb_cur_max > 1)
+           {
+             int wcs_idx;
+             wint_t wc = WEOF;
+
+             if (pstr->is_utf8)
+               {
+                 const unsigned char *raw, *p, *end;
+
+                 /* Special case UTF-8.  Multi-byte chars start with any
+                    byte other than 0x80 - 0xbf.  */
+                 raw = pstr->raw_mbs + pstr->raw_mbs_idx;
+                 end = raw + (offset - pstr->mb_cur_max);
+                 if (end < pstr->raw_mbs)
+                   end = pstr->raw_mbs;
+                 p = raw + offset - 1;
+#ifdef _LIBC
+                 /* We know the wchar_t encoding is UCS4, so for the simple
+                    case, ASCII characters, skip the conversion step.  */
+                 if (isascii (*p) && BE (pstr->trans == NULL, 1))
+                   {
+                     memset (&pstr->cur_state, '\0', sizeof (mbstate_t));
+                     /* pstr->valid_len = 0; */
+                     wc = (wchar_t) *p;
+                   }
+                 else
+#endif
+                   for (; p >= end; --p)
+                     if ((*p & 0xc0) != 0x80)
+                       {
+                         mbstate_t cur_state;
+                         wchar_t wc2;
+                         int mlen = raw + pstr->len - p;
+                         unsigned char buf[6];
+                         size_t mbclen;
+
+                         if (BE (pstr->trans != NULL, 0))
+                           {
+                             int i = mlen < 6 ? mlen : 6;
+                             while (--i >= 0)
+                               buf[i] = pstr->trans[p[i]];
+                           }
+                         /* XXX Don't use mbrtowc, we know which conversion
+                            to use (UTF-8 -> UCS4).  */
+                         memset (&cur_state, 0, sizeof (cur_state));
+                         mbclen = __mbrtowc (&wc2, (const char *) p, mlen,
+                                             &cur_state);
+                         if (raw + offset - p <= mbclen
+                             && mbclen < (size_t) -2)
+                           {
+                             memset (&pstr->cur_state, '\0',
+                                     sizeof (mbstate_t));
+                             pstr->valid_len = mbclen - (raw + offset - p);
+                             wc = wc2;
+                           }
+                         break;
+                       }
+               }
+
+             if (wc == WEOF)
+               pstr->valid_len = re_string_skip_chars (pstr, idx, &wc) - idx;
+             if (wc == WEOF)
+               pstr->tip_context
+                 = re_string_context_at (pstr, prev_valid_len - 1, eflags);
+             else
+               pstr->tip_context = ((BE (pstr->word_ops_used != 0, 0)
+                                     && IS_WIDE_WORD_CHAR (wc))
+                                    ? CONTEXT_WORD
+                                    : ((IS_WIDE_NEWLINE (wc)
+                                        && pstr->newline_anchor)
+                                       ? CONTEXT_NEWLINE : 0));
+             if (BE (pstr->valid_len, 0))
+               {
+                 for (wcs_idx = 0; wcs_idx < pstr->valid_len; ++wcs_idx)
+                   pstr->wcs[wcs_idx] = WEOF;
+                 if (pstr->mbs_allocated)
+                   memset (pstr->mbs, 255, pstr->valid_len);
+               }
+             pstr->valid_raw_len = pstr->valid_len;
+           }
+         else
+#endif /* RE_ENABLE_I18N */
+           {
+             int c = pstr->raw_mbs[pstr->raw_mbs_idx + offset - 1];
+             pstr->valid_raw_len = 0;
+             if (pstr->trans)
+               c = pstr->trans[c];
+             pstr->tip_context = (bitset_contain (pstr->word_char, c)
+                                  ? CONTEXT_WORD
+                                  : ((IS_NEWLINE (c) && pstr->newline_anchor)
+                                     ? CONTEXT_NEWLINE : 0));
+           }
+       }
+      if (!BE (pstr->mbs_allocated, 0))
+       pstr->mbs += offset;
+    }
+  pstr->raw_mbs_idx = idx;
+  pstr->len -= offset;
+  pstr->stop -= offset;
+
+  /* Then build the buffers.  */
+#ifdef RE_ENABLE_I18N
+  if (pstr->mb_cur_max > 1)
+    {
+      if (pstr->icase)
+       {
+         reg_errcode_t ret = build_wcs_upper_buffer (pstr);
+         if (BE (ret != REG_NOERROR, 0))
+           return ret;
+       }
+      else
+       build_wcs_buffer (pstr);
+    }
+  else
+#endif /* RE_ENABLE_I18N */
+    if (BE (pstr->mbs_allocated, 0))
+      {
+       if (pstr->icase)
+         build_upper_buffer (pstr);
+       else if (pstr->trans != NULL)
+         re_string_translate_buffer (pstr);
+      }
+    else
+      pstr->valid_len = pstr->len;
+
+  pstr->cur_idx = 0;
+  return REG_NOERROR;
+}
+
+static unsigned char
+internal_function __attribute ((pure))
+re_string_peek_byte_case (const re_string_t *pstr, int idx)
+{
+  int ch, off;
+
+  /* Handle the common (easiest) cases first.  */
+  if (BE (!pstr->mbs_allocated, 1))
+    return re_string_peek_byte (pstr, idx);
+
+#ifdef RE_ENABLE_I18N
+  if (pstr->mb_cur_max > 1
+      && ! re_string_is_single_byte_char (pstr, pstr->cur_idx + idx))
+    return re_string_peek_byte (pstr, idx);
+#endif
+
+  off = pstr->cur_idx + idx;
+#ifdef RE_ENABLE_I18N
+  if (pstr->offsets_needed)
+    off = pstr->offsets[off];
+#endif
+
+  ch = pstr->raw_mbs[pstr->raw_mbs_idx + off];
+
+#ifdef RE_ENABLE_I18N
+  /* Ensure that e.g. for tr_TR.UTF-8 BACKSLASH DOTLESS SMALL LETTER I
+     this function returns CAPITAL LETTER I instead of first byte of
+     DOTLESS SMALL LETTER I.  The latter would confuse the parser,
+     since peek_byte_case doesn't advance cur_idx in any way.  */
+  if (pstr->offsets_needed && !isascii (ch))
+    return re_string_peek_byte (pstr, idx);
+#endif
+
+  return ch;
+}
+
+static unsigned char
+internal_function __attribute ((pure))
+re_string_fetch_byte_case (re_string_t *pstr)
+{
+  if (BE (!pstr->mbs_allocated, 1))
+    return re_string_fetch_byte (pstr);
+
+#ifdef RE_ENABLE_I18N
+  if (pstr->offsets_needed)
+    {
+      int off, ch;
+
+      /* For tr_TR.UTF-8 [[:islower:]] there is
+        [[: CAPITAL LETTER I WITH DOT lower:]] in mbs.  Skip
+        in that case the whole multi-byte character and return
+        the original letter.  On the other side, with
+        [[: DOTLESS SMALL LETTER I return [[:I, as doing
+        anything else would complicate things too much.  */
+
+      if (!re_string_first_byte (pstr, pstr->cur_idx))
+       return re_string_fetch_byte (pstr);
+
+      off = pstr->offsets[pstr->cur_idx];
+      ch = pstr->raw_mbs[pstr->raw_mbs_idx + off];
+
+      if (! isascii (ch))
+       return re_string_fetch_byte (pstr);
+
+      re_string_skip_bytes (pstr,
+                           re_string_char_size_at (pstr, pstr->cur_idx));
+      return ch;
+    }
+#endif
+
+  return pstr->raw_mbs[pstr->raw_mbs_idx + pstr->cur_idx++];
+}
+
+static void
+internal_function
+re_string_destruct (re_string_t *pstr)
+{
+#ifdef RE_ENABLE_I18N
+  re_free (pstr->wcs);
+  re_free (pstr->offsets);
+#endif /* RE_ENABLE_I18N  */
+  if (pstr->mbs_allocated)
+    re_free (pstr->mbs);
+}
+
+/* Return the context at IDX in INPUT.  */
+
+static unsigned int
+internal_function
+re_string_context_at (const re_string_t *input, int idx, int eflags)
+{
+  int c;
+  if (BE (idx < 0, 0))
+    /* In this case, we use the value stored in input->tip_context,
+       since we can't know the character in input->mbs[-1] here.  */
+    return input->tip_context;
+  if (BE (idx == input->len, 0))
+    return ((eflags & REG_NOTEOL) ? CONTEXT_ENDBUF
+           : CONTEXT_NEWLINE | CONTEXT_ENDBUF);
+#ifdef RE_ENABLE_I18N
+  if (input->mb_cur_max > 1)
+    {
+      wint_t wc;
+      int wc_idx = idx;
+      while(input->wcs[wc_idx] == WEOF)
+       {
+#ifdef DEBUG
+         /* It must not happen.  */
+         assert (wc_idx >= 0);
+#endif
+         --wc_idx;
+         if (wc_idx < 0)
+           return input->tip_context;
+       }
+      wc = input->wcs[wc_idx];
+      if (BE (input->word_ops_used != 0, 0) && IS_WIDE_WORD_CHAR (wc))
+       return CONTEXT_WORD;
+      return (IS_WIDE_NEWLINE (wc) && input->newline_anchor
+             ? CONTEXT_NEWLINE : 0);
+    }
+  else
+#endif
+    {
+      c = re_string_byte_at (input, idx);
+      if (bitset_contain (input->word_char, c))
+       return CONTEXT_WORD;
+      return IS_NEWLINE (c) && input->newline_anchor ? CONTEXT_NEWLINE : 0;
+    }
+}
+\f
+/* Functions for set operation.  */
+
+static reg_errcode_t
+internal_function
+re_node_set_alloc (re_node_set *set, int size)
+{
+  /*
+   * ADR: valgrind says size can be 0, which then doesn't
+   * free the block of size 0.  Harumph. This seems
+   * to work ok, though.
+   */
+  if (size == 0)
+    {
+       memset(set, 0, sizeof(*set));
+       return REG_NOERROR;
+    }
+  set->alloc = size;
+  set->nelem = 0;
+  set->elems = re_malloc (int, size);
+  if (BE (set->elems == NULL, 0))
+    return REG_ESPACE;
+  return REG_NOERROR;
+}
+
+static reg_errcode_t
+internal_function
+re_node_set_init_1 (re_node_set *set, int elem)
+{
+  set->alloc = 1;
+  set->nelem = 1;
+  set->elems = re_malloc (int, 1);
+  if (BE (set->elems == NULL, 0))
+    {
+      set->alloc = set->nelem = 0;
+      return REG_ESPACE;
+    }
+  set->elems[0] = elem;
+  return REG_NOERROR;
+}
+
+static reg_errcode_t
+internal_function
+re_node_set_init_2 (re_node_set *set, int elem1, int elem2)
+{
+  set->alloc = 2;
+  set->elems = re_malloc (int, 2);
+  if (BE (set->elems == NULL, 0))
+    return REG_ESPACE;
+  if (elem1 == elem2)
+    {
+      set->nelem = 1;
+      set->elems[0] = elem1;
+    }
+  else
+    {
+      set->nelem = 2;
+      if (elem1 < elem2)
+       {
+         set->elems[0] = elem1;
+         set->elems[1] = elem2;
+       }
+      else
+       {
+         set->elems[0] = elem2;
+         set->elems[1] = elem1;
+       }
+    }
+  return REG_NOERROR;
+}
+
+static reg_errcode_t
+internal_function
+re_node_set_init_copy (re_node_set *dest, const re_node_set *src)
+{
+  dest->nelem = src->nelem;
+  if (src->nelem > 0)
+    {
+      dest->alloc = dest->nelem;
+      dest->elems = re_malloc (int, dest->alloc);
+      if (BE (dest->elems == NULL, 0))
+       {
+         dest->alloc = dest->nelem = 0;
+         return REG_ESPACE;
+       }
+      memcpy (dest->elems, src->elems, src->nelem * sizeof (int));
+    }
+  else
+    re_node_set_init_empty (dest);
+  return REG_NOERROR;
+}
+
+/* Calculate the intersection of the sets SRC1 and SRC2. And merge it to
+   DEST. Return value indicate the error code or REG_NOERROR if succeeded.
+   Note: We assume dest->elems is NULL, when dest->alloc is 0.  */
+
+static reg_errcode_t
+internal_function
+re_node_set_add_intersect (re_node_set *dest, const re_node_set *src1,
+                          const re_node_set *src2)
+{
+  int i1, i2, is, id, delta, sbase;
+  if (src1->nelem == 0 || src2->nelem == 0)
+    return REG_NOERROR;
+
+  /* We need dest->nelem + 2 * elems_in_intersection; this is a
+     conservative estimate.  */
+  if (src1->nelem + src2->nelem + dest->nelem > dest->alloc)
+    {
+      int new_alloc = src1->nelem + src2->nelem + dest->alloc;
+      int *new_elems = re_realloc (dest->elems, int, new_alloc);
+      if (BE (new_elems == NULL, 0))
+       return REG_ESPACE;
+      dest->elems = new_elems;
+      dest->alloc = new_alloc;
+    }
+
+  /* Find the items in the intersection of SRC1 and SRC2, and copy
+     into the top of DEST those that are not already in DEST itself.  */
+  sbase = dest->nelem + src1->nelem + src2->nelem;
+  i1 = src1->nelem - 1;
+  i2 = src2->nelem - 1;
+  id = dest->nelem - 1;
+  for (;;)
+    {
+      if (src1->elems[i1] == src2->elems[i2])
+       {
+         /* Try to find the item in DEST.  Maybe we could binary search?  */
+         while (id >= 0 && dest->elems[id] > src1->elems[i1])
+           --id;
+
+         if (id < 0 || dest->elems[id] != src1->elems[i1])
+           dest->elems[--sbase] = src1->elems[i1];
+
+         if (--i1 < 0 || --i2 < 0)
+           break;
+       }
+
+      /* Lower the highest of the two items.  */
+      else if (src1->elems[i1] < src2->elems[i2])
+       {
+         if (--i2 < 0)
+           break;
+       }
+      else
+       {
+         if (--i1 < 0)
+           break;
+       }
+    }
+
+  id = dest->nelem - 1;
+  is = dest->nelem + src1->nelem + src2->nelem - 1;
+  delta = is - sbase + 1;
+
+  /* Now copy.  When DELTA becomes zero, the remaining
+     DEST elements are already in place; this is more or
+     less the same loop that is in re_node_set_merge.  */
+  dest->nelem += delta;
+  if (delta > 0 && id >= 0)
+    for (;;)
+      {
+       if (dest->elems[is] > dest->elems[id])
+         {
+           /* Copy from the top.  */
+           dest->elems[id + delta--] = dest->elems[is--];
+           if (delta == 0)
+             break;
+         }
+       else
+         {
+           /* Slide from the bottom.  */
+           dest->elems[id + delta] = dest->elems[id];
+           if (--id < 0)
+             break;
+         }
+      }
+
+  /* Copy remaining SRC elements.  */
+  memcpy (dest->elems, dest->elems + sbase, delta * sizeof (int));
+
+  return REG_NOERROR;
+}
+
+/* Calculate the union set of the sets SRC1 and SRC2. And store it to
+   DEST. Return value indicate the error code or REG_NOERROR if succeeded.  */
+
+static reg_errcode_t
+internal_function
+re_node_set_init_union (re_node_set *dest, const re_node_set *src1,
+                       const re_node_set *src2)
+{
+  int i1, i2, id;
+  if (src1 != NULL && src1->nelem > 0 && src2 != NULL && src2->nelem > 0)
+    {
+      dest->alloc = src1->nelem + src2->nelem;
+      dest->elems = re_malloc (int, dest->alloc);
+      if (BE (dest->elems == NULL, 0))
+       return REG_ESPACE;
+    }
+  else
+    {
+      if (src1 != NULL && src1->nelem > 0)
+       return re_node_set_init_copy (dest, src1);
+      else if (src2 != NULL && src2->nelem > 0)
+       return re_node_set_init_copy (dest, src2);
+      else
+       re_node_set_init_empty (dest);
+      return REG_NOERROR;
+    }
+  for (i1 = i2 = id = 0 ; i1 < src1->nelem && i2 < src2->nelem ;)
+    {
+      if (src1->elems[i1] > src2->elems[i2])
+       {
+         dest->elems[id++] = src2->elems[i2++];
+         continue;
+       }
+      if (src1->elems[i1] == src2->elems[i2])
+       ++i2;
+      dest->elems[id++] = src1->elems[i1++];
+    }
+  if (i1 < src1->nelem)
+    {
+      memcpy (dest->elems + id, src1->elems + i1,
+            (src1->nelem - i1) * sizeof (int));
+      id += src1->nelem - i1;
+    }
+  else if (i2 < src2->nelem)
+    {
+      memcpy (dest->elems + id, src2->elems + i2,
+            (src2->nelem - i2) * sizeof (int));
+      id += src2->nelem - i2;
+    }
+  dest->nelem = id;
+  return REG_NOERROR;
+}
+
+/* Calculate the union set of the sets DEST and SRC. And store it to
+   DEST. Return value indicate the error code or REG_NOERROR if succeeded.  */
+
+static reg_errcode_t
+internal_function
+re_node_set_merge (re_node_set *dest, const re_node_set *src)
+{
+  int is, id, sbase, delta;
+  if (src == NULL || src->nelem == 0)
+    return REG_NOERROR;
+  if (dest->alloc < 2 * src->nelem + dest->nelem)
+    {
+      int new_alloc = 2 * (src->nelem + dest->alloc);
+      int *new_buffer = re_realloc (dest->elems, int, new_alloc);
+      if (BE (new_buffer == NULL, 0))
+       return REG_ESPACE;
+      dest->elems = new_buffer;
+      dest->alloc = new_alloc;
+    }
+
+  if (BE (dest->nelem == 0, 0))
+    {
+      dest->nelem = src->nelem;
+      memcpy (dest->elems, src->elems, src->nelem * sizeof (int));
+      return REG_NOERROR;
+    }
+
+  /* Copy into the top of DEST the items of SRC that are not
+     found in DEST.  Maybe we could binary search in DEST?  */
+  for (sbase = dest->nelem + 2 * src->nelem,
+       is = src->nelem - 1, id = dest->nelem - 1; is >= 0 && id >= 0; )
+    {
+      if (dest->elems[id] == src->elems[is])
+       is--, id--;
+      else if (dest->elems[id] < src->elems[is])
+       dest->elems[--sbase] = src->elems[is--];
+      else /* if (dest->elems[id] > src->elems[is]) */
+       --id;
+    }
+
+  if (is >= 0)
+    {
+      /* If DEST is exhausted, the remaining items of SRC must be unique.  */
+      sbase -= is + 1;
+      memcpy (dest->elems + sbase, src->elems, (is + 1) * sizeof (int));
+    }
+
+  id = dest->nelem - 1;
+  is = dest->nelem + 2 * src->nelem - 1;
+  delta = is - sbase + 1;
+  if (delta == 0)
+    return REG_NOERROR;
+
+  /* Now copy.  When DELTA becomes zero, the remaining
+     DEST elements are already in place.  */
+  dest->nelem += delta;
+  for (;;)
+    {
+      if (dest->elems[is] > dest->elems[id])
+       {
+         /* Copy from the top.  */
+         dest->elems[id + delta--] = dest->elems[is--];
+         if (delta == 0)
+           break;
+       }
+      else
+       {
+         /* Slide from the bottom.  */
+         dest->elems[id + delta] = dest->elems[id];
+         if (--id < 0)
+           {
+             /* Copy remaining SRC elements.  */
+             memcpy (dest->elems, dest->elems + sbase,
+                     delta * sizeof (int));
+             break;
+           }
+       }
+    }
+
+  return REG_NOERROR;
+}
+
+/* Insert the new element ELEM to the re_node_set* SET.
+   SET should not already have ELEM.
+   return -1 if an error is occured, return 1 otherwise.  */
+
+static int
+internal_function
+re_node_set_insert (re_node_set *set, int elem)
+{
+  int idx;
+  /* In case the set is empty.  */
+  if (set->alloc == 0)
+    {
+      if (BE (re_node_set_init_1 (set, elem) == REG_NOERROR, 1))
+       return 1;
+      else
+       return -1;
+    }
+
+  if (BE (set->nelem, 0) == 0)
+    {
+      /* We already guaranteed above that set->alloc != 0.  */
+      set->elems[0] = elem;
+      ++set->nelem;
+      return 1;
+    }
+
+  /* Realloc if we need.  */
+  if (set->alloc == set->nelem)
+    {
+      int *new_elems;
+      set->alloc = set->alloc * 2;
+      new_elems = re_realloc (set->elems, int, set->alloc);
+      if (BE (new_elems == NULL, 0))
+       return -1;
+      set->elems = new_elems;
+    }
+
+  /* Move the elements which follows the new element.  Test the
+     first element separately to skip a check in the inner loop.  */
+  if (elem < set->elems[0])
+    {
+      idx = 0;
+      for (idx = set->nelem; idx > 0; idx--)
+       set->elems[idx] = set->elems[idx - 1];
+    }
+  else
+    {
+      for (idx = set->nelem; set->elems[idx - 1] > elem; idx--)
+       set->elems[idx] = set->elems[idx - 1];
+    }
+
+  /* Insert the new element.  */
+  set->elems[idx] = elem;
+  ++set->nelem;
+  return 1;
+}
+
+/* Insert the new element ELEM to the re_node_set* SET.
+   SET should not already have any element greater than or equal to ELEM.
+   Return -1 if an error is occured, return 1 otherwise.  */
+
+static int
+internal_function
+re_node_set_insert_last (re_node_set *set, int elem)
+{
+  /* Realloc if we need.  */
+  if (set->alloc == set->nelem)
+    {
+      int *new_elems;
+      set->alloc = (set->alloc + 1) * 2;
+      new_elems = re_realloc (set->elems, int, set->alloc);
+      if (BE (new_elems == NULL, 0))
+       return -1;
+      set->elems = new_elems;
+    }
+
+  /* Insert the new element.  */
+  set->elems[set->nelem++] = elem;
+  return 1;
+}
+
+/* Compare two node sets SET1 and SET2.
+   return 1 if SET1 and SET2 are equivalent, return 0 otherwise.  */
+
+static int
+internal_function __attribute ((pure))
+re_node_set_compare (const re_node_set *set1, const re_node_set *set2)
+{
+  int i;
+  if (set1 == NULL || set2 == NULL || set1->nelem != set2->nelem)
+    return 0;
+  for (i = set1->nelem ; --i >= 0 ; )
+    if (set1->elems[i] != set2->elems[i])
+      return 0;
+  return 1;
+}
+
+/* Return (idx + 1) if SET contains the element ELEM, return 0 otherwise.  */
+
+static int
+internal_function __attribute ((pure))
+re_node_set_contains (const re_node_set *set, int elem)
+{
+  unsigned int idx, right, mid;
+  if (set->nelem <= 0)
+    return 0;
+
+  /* Binary search the element.  */
+  idx = 0;
+  right = set->nelem - 1;
+  while (idx < right)
+    {
+      mid = (idx + right) / 2;
+      if (set->elems[mid] < elem)
+       idx = mid + 1;
+      else
+       right = mid;
+    }
+  return set->elems[idx] == elem ? idx + 1 : 0;
+}
+
+static void
+internal_function
+re_node_set_remove_at (re_node_set *set, int idx)
+{
+  if (idx < 0 || idx >= set->nelem)
+    return;
+  --set->nelem;
+  for (; idx < set->nelem; idx++)
+    set->elems[idx] = set->elems[idx + 1];
+}
+\f
+
+/* Add the token TOKEN to dfa->nodes, and return the index of the token.
+   Or return -1, if an error will be occured.  */
+
+static int
+internal_function
+re_dfa_add_node (re_dfa_t *dfa, re_token_t token)
+{
+  if (BE (dfa->nodes_len >= dfa->nodes_alloc, 0))
+    {
+      size_t new_nodes_alloc = dfa->nodes_alloc * 2;
+      int *new_nexts, *new_indices;
+      re_node_set *new_edests, *new_eclosures;
+      re_token_t *new_nodes;
+
+      /* Avoid overflows in realloc.  */
+      const size_t max_object_size = MAX (sizeof (re_token_t),
+                                         MAX (sizeof (re_node_set),
+                                              sizeof (int)));
+      if (BE (SIZE_MAX / max_object_size < new_nodes_alloc, 0))
+       return -1;
+
+      new_nodes = re_realloc (dfa->nodes, re_token_t, new_nodes_alloc);
+      if (BE (new_nodes == NULL, 0))
+       return -1;
+      dfa->nodes = new_nodes;
+      new_nexts = re_realloc (dfa->nexts, int, new_nodes_alloc);
+      new_indices = re_realloc (dfa->org_indices, int, new_nodes_alloc);
+      new_edests = re_realloc (dfa->edests, re_node_set, new_nodes_alloc);
+      new_eclosures = re_realloc (dfa->eclosures, re_node_set, new_nodes_alloc);
+      if (BE (new_nexts == NULL || new_indices == NULL
+             || new_edests == NULL || new_eclosures == NULL, 0))
+       return -1;
+      dfa->nexts = new_nexts;
+      dfa->org_indices = new_indices;
+      dfa->edests = new_edests;
+      dfa->eclosures = new_eclosures;
+      dfa->nodes_alloc = new_nodes_alloc;
+    }
+  dfa->nodes[dfa->nodes_len] = token;
+  dfa->nodes[dfa->nodes_len].constraint = 0;
+#ifdef RE_ENABLE_I18N
+  dfa->nodes[dfa->nodes_len].accept_mb =
+    (token.type == OP_PERIOD && dfa->mb_cur_max > 1) || token.type == COMPLEX_BRACKET;
+#endif
+  dfa->nexts[dfa->nodes_len] = -1;
+  re_node_set_init_empty (dfa->edests + dfa->nodes_len);
+  re_node_set_init_empty (dfa->eclosures + dfa->nodes_len);
+  return dfa->nodes_len++;
+}
+
+static inline unsigned int
+internal_function
+calc_state_hash (const re_node_set *nodes, unsigned int context)
+{
+  unsigned int hash = nodes->nelem + context;
+  int i;
+  for (i = 0 ; i < nodes->nelem ; i++)
+    hash += nodes->elems[i];
+  return hash;
+}
+
+/* Search for the state whose node_set is equivalent to NODES.
+   Return the pointer to the state, if we found it in the DFA.
+   Otherwise create the new one and return it.  In case of an error
+   return NULL and set the error code in ERR.
+   Note: - We assume NULL as the invalid state, then it is possible that
+          return value is NULL and ERR is REG_NOERROR.
+        - We never return non-NULL value in case of any errors, it is for
+          optimization.  */
+
+static re_dfastate_t *
+internal_function
+re_acquire_state (reg_errcode_t *err, const re_dfa_t *dfa,
+                 const re_node_set *nodes)
+{
+  unsigned int hash;
+  re_dfastate_t *new_state;
+  struct re_state_table_entry *spot;
+  int i;
+  if (BE (nodes->nelem == 0, 0))
+    {
+      *err = REG_NOERROR;
+      return NULL;
+    }
+  hash = calc_state_hash (nodes, 0);
+  spot = dfa->state_table + (hash & dfa->state_hash_mask);
+
+  for (i = 0 ; i < spot->num ; i++)
+    {
+      re_dfastate_t *state = spot->array[i];
+      if (hash != state->hash)
+       continue;
+      if (re_node_set_compare (&state->nodes, nodes))
+       return state;
+    }
+
+  /* There are no appropriate state in the dfa, create the new one.  */
+  new_state = create_ci_newstate (dfa, nodes, hash);
+  if (BE (new_state == NULL, 0))
+    *err = REG_ESPACE;
+
+  return new_state;
+}
+
+/* Search for the state whose node_set is equivalent to NODES and
+   whose context is equivalent to CONTEXT.
+   Return the pointer to the state, if we found it in the DFA.
+   Otherwise create the new one and return it.  In case of an error
+   return NULL and set the error code in ERR.
+   Note: - We assume NULL as the invalid state, then it is possible that
+          return value is NULL and ERR is REG_NOERROR.
+        - We never return non-NULL value in case of any errors, it is for
+          optimization.  */
+
+static re_dfastate_t *
+internal_function
+re_acquire_state_context (reg_errcode_t *err, const re_dfa_t *dfa,
+                         const re_node_set *nodes, unsigned int context)
+{
+  unsigned int hash;
+  re_dfastate_t *new_state;
+  struct re_state_table_entry *spot;
+  int i;
+  if (nodes->nelem == 0)
+    {
+      *err = REG_NOERROR;
+      return NULL;
+    }
+  hash = calc_state_hash (nodes, context);
+  spot = dfa->state_table + (hash & dfa->state_hash_mask);
+
+  for (i = 0 ; i < spot->num ; i++)
+    {
+      re_dfastate_t *state = spot->array[i];
+      if (state->hash == hash
+         && state->context == context
+         && re_node_set_compare (state->entrance_nodes, nodes))
+       return state;
+    }
+  /* There are no appropriate state in `dfa', create the new one.  */
+  new_state = create_cd_newstate (dfa, nodes, context, hash);
+  if (BE (new_state == NULL, 0))
+    *err = REG_ESPACE;
+
+  return new_state;
+}
+
+/* Finish initialization of the new state NEWSTATE, and using its hash value
+   HASH put in the appropriate bucket of DFA's state table.  Return value
+   indicates the error code if failed.  */
+
+static reg_errcode_t
+register_state (const re_dfa_t *dfa, re_dfastate_t *newstate,
+               unsigned int hash)
+{
+  struct re_state_table_entry *spot;
+  reg_errcode_t err;
+  int i;
+
+  newstate->hash = hash;
+  err = re_node_set_alloc (&newstate->non_eps_nodes, newstate->nodes.nelem);
+  if (BE (err != REG_NOERROR, 0))
+    return REG_ESPACE;
+  for (i = 0; i < newstate->nodes.nelem; i++)
+    {
+      int elem = newstate->nodes.elems[i];
+      if (!IS_EPSILON_NODE (dfa->nodes[elem].type))
+       if (re_node_set_insert_last (&newstate->non_eps_nodes, elem) < 0)
+         return REG_ESPACE;
+    }
+
+  spot = dfa->state_table + (hash & dfa->state_hash_mask);
+  if (BE (spot->alloc <= spot->num, 0))
+    {
+      int new_alloc = 2 * spot->num + 2;
+      re_dfastate_t **new_array = re_realloc (spot->array, re_dfastate_t *,
+                                             new_alloc);
+      if (BE (new_array == NULL, 0))
+       return REG_ESPACE;
+      spot->array = new_array;
+      spot->alloc = new_alloc;
+    }
+  spot->array[spot->num++] = newstate;
+  return REG_NOERROR;
+}
+
+static void
+free_state (re_dfastate_t *state)
+{
+  re_node_set_free (&state->non_eps_nodes);
+  re_node_set_free (&state->inveclosure);
+  if (state->entrance_nodes != &state->nodes)
+    {
+      re_node_set_free (state->entrance_nodes);
+      re_free (state->entrance_nodes);
+    }
+  re_node_set_free (&state->nodes);
+  re_free (state->word_trtable);
+  re_free (state->trtable);
+  re_free (state);
+}
+
+/* Create the new state which is independ of contexts.
+   Return the new state if succeeded, otherwise return NULL.  */
+
+static re_dfastate_t *
+internal_function
+create_ci_newstate (const re_dfa_t *dfa, const re_node_set *nodes,
+                   unsigned int hash)
+{
+  int i;
+  reg_errcode_t err;
+  re_dfastate_t *newstate;
+
+  newstate = (re_dfastate_t *) calloc (sizeof (re_dfastate_t), 1);
+  if (BE (newstate == NULL, 0))
+    return NULL;
+  err = re_node_set_init_copy (&newstate->nodes, nodes);
+  if (BE (err != REG_NOERROR, 0))
+    {
+      re_free (newstate);
+      return NULL;
+    }
+
+  newstate->entrance_nodes = &newstate->nodes;
+  for (i = 0 ; i < nodes->nelem ; i++)
+    {
+      re_token_t *node = dfa->nodes + nodes->elems[i];
+      re_token_type_t type = node->type;
+      if (type == CHARACTER && !node->constraint)
+       continue;
+#ifdef RE_ENABLE_I18N
+      newstate->accept_mb |= node->accept_mb;
+#endif /* RE_ENABLE_I18N */
+
+      /* If the state has the halt node, the state is a halt state.  */
+      if (type == END_OF_RE)
+       newstate->halt = 1;
+      else if (type == OP_BACK_REF)
+       newstate->has_backref = 1;
+      else if (type == ANCHOR || node->constraint)
+       newstate->has_constraint = 1;
+    }
+  err = register_state (dfa, newstate, hash);
+  if (BE (err != REG_NOERROR, 0))
+    {
+      free_state (newstate);
+      newstate = NULL;
+    }
+  return newstate;
+}
+
+/* Create the new state which is depend on the context CONTEXT.
+   Return the new state if succeeded, otherwise return NULL.  */
+
+static re_dfastate_t *
+internal_function
+create_cd_newstate (const re_dfa_t *dfa, const re_node_set *nodes,
+                   unsigned int context, unsigned int hash)
+{
+  int i, nctx_nodes = 0;
+  reg_errcode_t err;
+  re_dfastate_t *newstate;
+
+  newstate = (re_dfastate_t *) calloc (sizeof (re_dfastate_t), 1);
+  if (BE (newstate == NULL, 0))
+    return NULL;
+  err = re_node_set_init_copy (&newstate->nodes, nodes);
+  if (BE (err != REG_NOERROR, 0))
+    {
+      re_free (newstate);
+      return NULL;
+    }
+
+  newstate->context = context;
+  newstate->entrance_nodes = &newstate->nodes;
+
+  for (i = 0 ; i < nodes->nelem ; i++)
+    {
+      re_token_t *node = dfa->nodes + nodes->elems[i];
+      re_token_type_t type = node->type;
+      unsigned int constraint = node->constraint;
+
+      if (type == CHARACTER && !constraint)
+       continue;
+#ifdef RE_ENABLE_I18N
+      newstate->accept_mb |= node->accept_mb;
+#endif /* RE_ENABLE_I18N */
+
+      /* If the state has the halt node, the state is a halt state.  */
+      if (type == END_OF_RE)
+       newstate->halt = 1;
+      else if (type == OP_BACK_REF)
+       newstate->has_backref = 1;
+
+      if (constraint)
+       {
+         if (newstate->entrance_nodes == &newstate->nodes)
+           {
+             newstate->entrance_nodes = re_malloc (re_node_set, 1);
+             if (BE (newstate->entrance_nodes == NULL, 0))
+               {
+                 free_state (newstate);
+                 return NULL;
+               }
+             if (re_node_set_init_copy (newstate->entrance_nodes, nodes)
+                 != REG_NOERROR)
+               return NULL;
+             nctx_nodes = 0;
+             newstate->has_constraint = 1;
+           }
+
+         if (NOT_SATISFY_PREV_CONSTRAINT (constraint,context))
+           {
+             re_node_set_remove_at (&newstate->nodes, i - nctx_nodes);
+             ++nctx_nodes;
+           }
+       }
+    }
+  err = register_state (dfa, newstate, hash);
+  if (BE (err != REG_NOERROR, 0))
+    {
+      free_state (newstate);
+      newstate = NULL;
+    }
+  return  newstate;
+}
diff --git a/compat/regex/regex_internal.h b/compat/regex/regex_internal.h
new file mode 100644 (file)
index 0000000..4184d7f
--- /dev/null
@@ -0,0 +1,810 @@
+/* Extended regular expression matching and search library.
+   Copyright (C) 2002-2005, 2007, 2008, 2010 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+   Contributed by Isamu Hasegawa <isamu@yamato.ibm.com>.
+
+   The GNU C 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.
+
+   The GNU C 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 the GNU C Library; if not, write to the Free
+   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+   02111-1307 USA.  */
+
+#ifndef _REGEX_INTERNAL_H
+#define _REGEX_INTERNAL_H 1
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined HAVE_LANGINFO_H || defined HAVE_LANGINFO_CODESET || defined _LIBC
+# include <langinfo.h>
+#endif
+#if defined HAVE_LOCALE_H || defined _LIBC
+# include <locale.h>
+#endif
+#if defined HAVE_WCHAR_H || defined _LIBC
+# include <wchar.h>
+#endif /* HAVE_WCHAR_H || _LIBC */
+#if defined HAVE_WCTYPE_H || defined _LIBC
+# include <wctype.h>
+#endif /* HAVE_WCTYPE_H || _LIBC */
+#if defined HAVE_STDBOOL_H || defined _LIBC
+# include <stdbool.h>
+#endif /* HAVE_STDBOOL_H || _LIBC */
+#if !defined(ZOS_USS)
+#if defined HAVE_STDINT_H || defined _LIBC
+# include <stdint.h>
+#endif /* HAVE_STDINT_H || _LIBC */
+#endif /* !ZOS_USS */
+#if defined _LIBC
+# include <bits/libc-lock.h>
+#else
+# define __libc_lock_define(CLASS,NAME)
+# define __libc_lock_init(NAME) do { } while (0)
+# define __libc_lock_lock(NAME) do { } while (0)
+# define __libc_lock_unlock(NAME) do { } while (0)
+#endif
+
+#ifndef GAWK
+/* In case that the system doesn't have isblank().  */
+#if !defined _LIBC && !defined HAVE_ISBLANK && !defined isblank
+# define isblank(ch) ((ch) == ' ' || (ch) == '\t')
+#endif
+#else /* GAWK */
+/*
+ * This is a freaking mess. On glibc systems you have to define
+ * a magic constant to get isblank() out of <ctype.h>, since it's
+ * a C99 function.  To heck with all that and borrow a page from
+ * dfa.c's book.
+ */
+
+static int
+is_blank (int c)
+{
+   return (c == ' ' || c == '\t');
+}
+#endif /* GAWK */
+
+#ifdef _LIBC
+# ifndef _RE_DEFINE_LOCALE_FUNCTIONS
+#  define _RE_DEFINE_LOCALE_FUNCTIONS 1
+#   include <locale/localeinfo.h>
+#   include <locale/elem-hash.h>
+#   include <locale/coll-lookup.h>
+# endif
+#endif
+
+/* This is for other GNU distributions with internationalized messages.  */
+#if (HAVE_LIBINTL_H && ENABLE_NLS) || defined _LIBC
+# include <libintl.h>
+# ifdef _LIBC
+#  undef gettext
+#  define gettext(msgid) \
+  INTUSE(__dcgettext) (_libc_intl_domainname, msgid, LC_MESSAGES)
+# endif
+#else
+# define gettext(msgid) (msgid)
+#endif
+
+#ifndef gettext_noop
+/* This define is so xgettext can find the internationalizable
+   strings.  */
+# define gettext_noop(String) String
+#endif
+
+/* For loser systems without the definition.  */
+#ifndef SIZE_MAX
+# define SIZE_MAX ((size_t) -1)
+#endif
+
+#ifndef NO_MBSUPPORT
+#include "mbsupport.h" /* gawk */
+#endif
+#ifndef MB_CUR_MAX
+#define MB_CUR_MAX 1
+#endif
+
+#if (defined MBS_SUPPORT) || _LIBC
+# define RE_ENABLE_I18N
+#endif
+
+#if __GNUC__ >= 3
+# define BE(expr, val) __builtin_expect (expr, val)
+#else
+# define BE(expr, val) (expr)
+# ifdef inline
+# undef inline
+# endif
+# define inline
+#endif
+
+/* Number of single byte character.  */
+#define SBC_MAX 256
+
+#define COLL_ELEM_LEN_MAX 8
+
+/* The character which represents newline.  */
+#define NEWLINE_CHAR '\n'
+#define WIDE_NEWLINE_CHAR L'\n'
+
+/* Rename to standard API for using out of glibc.  */
+#ifndef _LIBC
+# ifdef __wctype
+# undef __wctype
+# endif
+# define __wctype wctype
+# ifdef __iswctype
+# undef __iswctype
+# endif
+# define __iswctype iswctype
+# define __btowc btowc
+# define __mbrtowc mbrtowc
+#undef __mempcpy       /* GAWK */
+# define __mempcpy mempcpy
+# define __wcrtomb wcrtomb
+# define __regfree regfree
+# define attribute_hidden
+#endif /* not _LIBC */
+
+#ifdef __GNUC__
+# define __attribute(arg) __attribute__ (arg)
+#else
+# define __attribute(arg)
+#endif
+
+extern const char __re_error_msgid[] attribute_hidden;
+extern const size_t __re_error_msgid_idx[] attribute_hidden;
+
+/* An integer used to represent a set of bits.  It must be unsigned,
+   and must be at least as wide as unsigned int.  */
+typedef unsigned long int bitset_word_t;
+/* All bits set in a bitset_word_t.  */
+#define BITSET_WORD_MAX ULONG_MAX
+/* Number of bits in a bitset_word_t.  */
+#define BITSET_WORD_BITS (sizeof (bitset_word_t) * CHAR_BIT)
+/* Number of bitset_word_t in a bit_set.  */
+#define BITSET_WORDS (SBC_MAX / BITSET_WORD_BITS)
+typedef bitset_word_t bitset_t[BITSET_WORDS];
+typedef bitset_word_t *re_bitset_ptr_t;
+typedef const bitset_word_t *re_const_bitset_ptr_t;
+
+#define bitset_set(set,i) \
+  (set[i / BITSET_WORD_BITS] |= (bitset_word_t) 1 << i % BITSET_WORD_BITS)
+#define bitset_clear(set,i) \
+  (set[i / BITSET_WORD_BITS] &= ~((bitset_word_t) 1 << i % BITSET_WORD_BITS))
+#define bitset_contain(set,i) \
+  (set[i / BITSET_WORD_BITS] & ((bitset_word_t) 1 << i % BITSET_WORD_BITS))
+#define bitset_empty(set) memset (set, '\0', sizeof (bitset_t))
+#define bitset_set_all(set) memset (set, '\xff', sizeof (bitset_t))
+#define bitset_copy(dest,src) memcpy (dest, src, sizeof (bitset_t))
+
+#define PREV_WORD_CONSTRAINT 0x0001
+#define PREV_NOTWORD_CONSTRAINT 0x0002
+#define NEXT_WORD_CONSTRAINT 0x0004
+#define NEXT_NOTWORD_CONSTRAINT 0x0008
+#define PREV_NEWLINE_CONSTRAINT 0x0010
+#define NEXT_NEWLINE_CONSTRAINT 0x0020
+#define PREV_BEGBUF_CONSTRAINT 0x0040
+#define NEXT_ENDBUF_CONSTRAINT 0x0080
+#define WORD_DELIM_CONSTRAINT 0x0100
+#define NOT_WORD_DELIM_CONSTRAINT 0x0200
+
+typedef enum
+{
+  INSIDE_WORD = PREV_WORD_CONSTRAINT | NEXT_WORD_CONSTRAINT,
+  WORD_FIRST = PREV_NOTWORD_CONSTRAINT | NEXT_WORD_CONSTRAINT,
+  WORD_LAST = PREV_WORD_CONSTRAINT | NEXT_NOTWORD_CONSTRAINT,
+  INSIDE_NOTWORD = PREV_NOTWORD_CONSTRAINT | NEXT_NOTWORD_CONSTRAINT,
+  LINE_FIRST = PREV_NEWLINE_CONSTRAINT,
+  LINE_LAST = NEXT_NEWLINE_CONSTRAINT,
+  BUF_FIRST = PREV_BEGBUF_CONSTRAINT,
+  BUF_LAST = NEXT_ENDBUF_CONSTRAINT,
+  WORD_DELIM = WORD_DELIM_CONSTRAINT,
+  NOT_WORD_DELIM = NOT_WORD_DELIM_CONSTRAINT
+} re_context_type;
+
+typedef struct
+{
+  int alloc;
+  int nelem;
+  int *elems;
+} re_node_set;
+
+typedef enum
+{
+  NON_TYPE = 0,
+
+  /* Node type, These are used by token, node, tree.  */
+  CHARACTER = 1,
+  END_OF_RE = 2,
+  SIMPLE_BRACKET = 3,
+  OP_BACK_REF = 4,
+  OP_PERIOD = 5,
+#ifdef RE_ENABLE_I18N
+  COMPLEX_BRACKET = 6,
+  OP_UTF8_PERIOD = 7,
+#endif /* RE_ENABLE_I18N */
+
+  /* We define EPSILON_BIT as a macro so that OP_OPEN_SUBEXP is used
+     when the debugger shows values of this enum type.  */
+#define EPSILON_BIT 8
+  OP_OPEN_SUBEXP = EPSILON_BIT | 0,
+  OP_CLOSE_SUBEXP = EPSILON_BIT | 1,
+  OP_ALT = EPSILON_BIT | 2,
+  OP_DUP_ASTERISK = EPSILON_BIT | 3,
+  ANCHOR = EPSILON_BIT | 4,
+
+  /* Tree type, these are used only by tree. */
+  CONCAT = 16,
+  SUBEXP = 17,
+
+  /* Token type, these are used only by token.  */
+  OP_DUP_PLUS = 18,
+  OP_DUP_QUESTION,
+  OP_OPEN_BRACKET,
+  OP_CLOSE_BRACKET,
+  OP_CHARSET_RANGE,
+  OP_OPEN_DUP_NUM,
+  OP_CLOSE_DUP_NUM,
+  OP_NON_MATCH_LIST,
+  OP_OPEN_COLL_ELEM,
+  OP_CLOSE_COLL_ELEM,
+  OP_OPEN_EQUIV_CLASS,
+  OP_CLOSE_EQUIV_CLASS,
+  OP_OPEN_CHAR_CLASS,
+  OP_CLOSE_CHAR_CLASS,
+  OP_WORD,
+  OP_NOTWORD,
+  OP_SPACE,
+  OP_NOTSPACE,
+  BACK_SLASH
+
+} re_token_type_t;
+
+#ifdef RE_ENABLE_I18N
+typedef struct
+{
+  /* Multibyte characters.  */
+  wchar_t *mbchars;
+
+  /* Collating symbols.  */
+# ifdef _LIBC
+  int32_t *coll_syms;
+# endif
+
+  /* Equivalence classes. */
+# ifdef _LIBC
+  int32_t *equiv_classes;
+# endif
+
+  /* Range expressions. */
+# ifdef _LIBC
+  uint32_t *range_starts;
+  uint32_t *range_ends;
+# else /* not _LIBC */
+  wchar_t *range_starts;
+  wchar_t *range_ends;
+# endif /* not _LIBC */
+
+  /* Character classes. */
+  wctype_t *char_classes;
+
+  /* If this character set is the non-matching list.  */
+  unsigned int non_match : 1;
+
+  /* # of multibyte characters.  */
+  int nmbchars;
+
+  /* # of collating symbols.  */
+  int ncoll_syms;
+
+  /* # of equivalence classes. */
+  int nequiv_classes;
+
+  /* # of range expressions. */
+  int nranges;
+
+  /* # of character classes. */
+  int nchar_classes;
+} re_charset_t;
+#endif /* RE_ENABLE_I18N */
+
+typedef struct
+{
+  union
+  {
+    unsigned char c;           /* for CHARACTER */
+    re_bitset_ptr_t sbcset;    /* for SIMPLE_BRACKET */
+#ifdef RE_ENABLE_I18N
+    re_charset_t *mbcset;      /* for COMPLEX_BRACKET */
+#endif /* RE_ENABLE_I18N */
+    int idx;                   /* for BACK_REF */
+    re_context_type ctx_type;  /* for ANCHOR */
+  } opr;
+#if __GNUC__ >= 2
+  re_token_type_t type : 8;
+#else
+  re_token_type_t type;
+#endif
+  unsigned int constraint : 10;        /* context constraint */
+  unsigned int duplicated : 1;
+  unsigned int opt_subexp : 1;
+#ifdef RE_ENABLE_I18N
+  unsigned int accept_mb : 1;
+  /* These 2 bits can be moved into the union if needed (e.g. if running out
+     of bits; move opr.c to opr.c.c and move the flags to opr.c.flags).  */
+  unsigned int mb_partial : 1;
+#endif
+  unsigned int word_char : 1;
+} re_token_t;
+
+#define IS_EPSILON_NODE(type) ((type) & EPSILON_BIT)
+
+struct re_string_t
+{
+  /* Indicate the raw buffer which is the original string passed as an
+     argument of regexec(), re_search(), etc..  */
+  const unsigned char *raw_mbs;
+  /* Store the multibyte string.  In case of "case insensitive mode" like
+     REG_ICASE, upper cases of the string are stored, otherwise MBS points
+     the same address that RAW_MBS points.  */
+  unsigned char *mbs;
+#ifdef RE_ENABLE_I18N
+  /* Store the wide character string which is corresponding to MBS.  */
+  wint_t *wcs;
+  int *offsets;
+  mbstate_t cur_state;
+#endif
+  /* Index in RAW_MBS.  Each character mbs[i] corresponds to
+     raw_mbs[raw_mbs_idx + i].  */
+  int raw_mbs_idx;
+  /* The length of the valid characters in the buffers.  */
+  int valid_len;
+  /* The corresponding number of bytes in raw_mbs array.  */
+  int valid_raw_len;
+  /* The length of the buffers MBS and WCS.  */
+  int bufs_len;
+  /* The index in MBS, which is updated by re_string_fetch_byte.  */
+  int cur_idx;
+  /* length of RAW_MBS array.  */
+  int raw_len;
+  /* This is RAW_LEN - RAW_MBS_IDX + VALID_LEN - VALID_RAW_LEN.  */
+  int len;
+  /* End of the buffer may be shorter than its length in the cases such
+     as re_match_2, re_search_2.  Then, we use STOP for end of the buffer
+     instead of LEN.  */
+  int raw_stop;
+  /* This is RAW_STOP - RAW_MBS_IDX adjusted through OFFSETS.  */
+  int stop;
+
+  /* The context of mbs[0].  We store the context independently, since
+     the context of mbs[0] may be different from raw_mbs[0], which is
+     the beginning of the input string.  */
+  unsigned int tip_context;
+  /* The translation passed as a part of an argument of re_compile_pattern.  */
+  RE_TRANSLATE_TYPE trans;
+  /* Copy of re_dfa_t's word_char.  */
+  re_const_bitset_ptr_t word_char;
+  /* 1 if REG_ICASE.  */
+  unsigned char icase;
+  unsigned char is_utf8;
+  unsigned char map_notascii;
+  unsigned char mbs_allocated;
+  unsigned char offsets_needed;
+  unsigned char newline_anchor;
+  unsigned char word_ops_used;
+  int mb_cur_max;
+};
+typedef struct re_string_t re_string_t;
+
+
+struct re_dfa_t;
+typedef struct re_dfa_t re_dfa_t;
+
+#ifndef _LIBC
+# ifdef __i386__
+#  define internal_function   __attribute ((regparm (3), stdcall))
+# else
+#  define internal_function
+# endif
+#endif
+
+#ifndef NOT_IN_libc
+static reg_errcode_t re_string_realloc_buffers (re_string_t *pstr,
+                                               int new_buf_len)
+     internal_function;
+# ifdef RE_ENABLE_I18N
+static void build_wcs_buffer (re_string_t *pstr) internal_function;
+static reg_errcode_t build_wcs_upper_buffer (re_string_t *pstr)
+  internal_function;
+# endif /* RE_ENABLE_I18N */
+static void build_upper_buffer (re_string_t *pstr) internal_function;
+static void re_string_translate_buffer (re_string_t *pstr) internal_function;
+static unsigned int re_string_context_at (const re_string_t *input, int idx,
+                                         int eflags)
+     internal_function __attribute ((pure));
+#endif
+#define re_string_peek_byte(pstr, offset) \
+  ((pstr)->mbs[(pstr)->cur_idx + offset])
+#define re_string_fetch_byte(pstr) \
+  ((pstr)->mbs[(pstr)->cur_idx++])
+#define re_string_first_byte(pstr, idx) \
+  ((idx) == (pstr)->valid_len || (pstr)->wcs[idx] != WEOF)
+#define re_string_is_single_byte_char(pstr, idx) \
+  ((pstr)->wcs[idx] != WEOF && ((pstr)->valid_len == (idx) + 1 \
+                               || (pstr)->wcs[(idx) + 1] != WEOF))
+#define re_string_eoi(pstr) ((pstr)->stop <= (pstr)->cur_idx)
+#define re_string_cur_idx(pstr) ((pstr)->cur_idx)
+#define re_string_get_buffer(pstr) ((pstr)->mbs)
+#define re_string_length(pstr) ((pstr)->len)
+#define re_string_byte_at(pstr,idx) ((pstr)->mbs[idx])
+#define re_string_skip_bytes(pstr,idx) ((pstr)->cur_idx += (idx))
+#define re_string_set_index(pstr,idx) ((pstr)->cur_idx = (idx))
+
+#ifndef _LIBC
+# if HAVE_ALLOCA
+#  if (_MSC_VER)
+#   include <malloc.h>
+#   define __libc_use_alloca(n) 0
+#  else
+#   include <alloca.h>
+/* The OS usually guarantees only one guard page at the bottom of the stack,
+   and a page size can be as small as 4096 bytes.  So we cannot safely
+   allocate anything larger than 4096 bytes.  Also care for the possibility
+   of a few compiler-allocated temporary stack slots.  */
+#  define __libc_use_alloca(n) ((n) < 4032)
+#  endif
+# else
+/* alloca is implemented with malloc, so just use malloc.  */
+#  define __libc_use_alloca(n) 0
+# endif
+#endif
+
+#define re_malloc(t,n) ((t *) malloc ((n) * sizeof (t)))
+/* SunOS 4.1.x realloc doesn't accept null pointers: pre-Standard C. Sigh. */
+#define re_realloc(p,t,n) ((p != NULL) ? (t *) realloc (p,(n)*sizeof(t)) : (t *) calloc(n,sizeof(t)))
+#define re_free(p) free (p)
+
+struct bin_tree_t
+{
+  struct bin_tree_t *parent;
+  struct bin_tree_t *left;
+  struct bin_tree_t *right;
+  struct bin_tree_t *first;
+  struct bin_tree_t *next;
+
+  re_token_t token;
+
+  /* `node_idx' is the index in dfa->nodes, if `type' == 0.
+     Otherwise `type' indicate the type of this node.  */
+  int node_idx;
+};
+typedef struct bin_tree_t bin_tree_t;
+
+#define BIN_TREE_STORAGE_SIZE \
+  ((1024 - sizeof (void *)) / sizeof (bin_tree_t))
+
+struct bin_tree_storage_t
+{
+  struct bin_tree_storage_t *next;
+  bin_tree_t data[BIN_TREE_STORAGE_SIZE];
+};
+typedef struct bin_tree_storage_t bin_tree_storage_t;
+
+#define CONTEXT_WORD 1
+#define CONTEXT_NEWLINE (CONTEXT_WORD << 1)
+#define CONTEXT_BEGBUF (CONTEXT_NEWLINE << 1)
+#define CONTEXT_ENDBUF (CONTEXT_BEGBUF << 1)
+
+#define IS_WORD_CONTEXT(c) ((c) & CONTEXT_WORD)
+#define IS_NEWLINE_CONTEXT(c) ((c) & CONTEXT_NEWLINE)
+#define IS_BEGBUF_CONTEXT(c) ((c) & CONTEXT_BEGBUF)
+#define IS_ENDBUF_CONTEXT(c) ((c) & CONTEXT_ENDBUF)
+#define IS_ORDINARY_CONTEXT(c) ((c) == 0)
+
+#define IS_WORD_CHAR(ch) (isalnum (ch) || (ch) == '_')
+#define IS_NEWLINE(ch) ((ch) == NEWLINE_CHAR)
+#define IS_WIDE_WORD_CHAR(ch) (iswalnum (ch) || (ch) == L'_')
+#define IS_WIDE_NEWLINE(ch) ((ch) == WIDE_NEWLINE_CHAR)
+
+#define NOT_SATISFY_PREV_CONSTRAINT(constraint,context) \
+ ((((constraint) & PREV_WORD_CONSTRAINT) && !IS_WORD_CONTEXT (context)) \
+  || ((constraint & PREV_NOTWORD_CONSTRAINT) && IS_WORD_CONTEXT (context)) \
+  || ((constraint & PREV_NEWLINE_CONSTRAINT) && !IS_NEWLINE_CONTEXT (context))\
+  || ((constraint & PREV_BEGBUF_CONSTRAINT) && !IS_BEGBUF_CONTEXT (context)))
+
+#define NOT_SATISFY_NEXT_CONSTRAINT(constraint,context) \
+ ((((constraint) & NEXT_WORD_CONSTRAINT) && !IS_WORD_CONTEXT (context)) \
+  || (((constraint) & NEXT_NOTWORD_CONSTRAINT) && IS_WORD_CONTEXT (context)) \
+  || (((constraint) & NEXT_NEWLINE_CONSTRAINT) && !IS_NEWLINE_CONTEXT (context)) \
+  || (((constraint) & NEXT_ENDBUF_CONSTRAINT) && !IS_ENDBUF_CONTEXT (context)))
+
+struct re_dfastate_t
+{
+  unsigned int hash;
+  re_node_set nodes;
+  re_node_set non_eps_nodes;
+  re_node_set inveclosure;
+  re_node_set *entrance_nodes;
+  struct re_dfastate_t **trtable, **word_trtable;
+  unsigned int context : 4;
+  unsigned int halt : 1;
+  /* If this state can accept `multi byte'.
+     Note that we refer to multibyte characters, and multi character
+     collating elements as `multi byte'.  */
+  unsigned int accept_mb : 1;
+  /* If this state has backreference node(s).  */
+  unsigned int has_backref : 1;
+  unsigned int has_constraint : 1;
+};
+typedef struct re_dfastate_t re_dfastate_t;
+
+struct re_state_table_entry
+{
+  int num;
+  int alloc;
+  re_dfastate_t **array;
+};
+
+/* Array type used in re_sub_match_last_t and re_sub_match_top_t.  */
+
+typedef struct
+{
+  int next_idx;
+  int alloc;
+  re_dfastate_t **array;
+} state_array_t;
+
+/* Store information about the node NODE whose type is OP_CLOSE_SUBEXP.  */
+
+typedef struct
+{
+  int node;
+  int str_idx; /* The position NODE match at.  */
+  state_array_t path;
+} re_sub_match_last_t;
+
+/* Store information about the node NODE whose type is OP_OPEN_SUBEXP.
+   And information about the node, whose type is OP_CLOSE_SUBEXP,
+   corresponding to NODE is stored in LASTS.  */
+
+typedef struct
+{
+  int str_idx;
+  int node;
+  state_array_t *path;
+  int alasts; /* Allocation size of LASTS.  */
+  int nlasts; /* The number of LASTS.  */
+  re_sub_match_last_t **lasts;
+} re_sub_match_top_t;
+
+struct re_backref_cache_entry
+{
+  int node;
+  int str_idx;
+  int subexp_from;
+  int subexp_to;
+  char more;
+  char unused;
+  unsigned short int eps_reachable_subexps_map;
+};
+
+typedef struct
+{
+  /* The string object corresponding to the input string.  */
+  re_string_t input;
+#if defined _LIBC || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L)
+  const re_dfa_t *const dfa;
+#else
+  const re_dfa_t *dfa;
+#endif
+  /* EFLAGS of the argument of regexec.  */
+  int eflags;
+  /* Where the matching ends.  */
+  int match_last;
+  int last_node;
+  /* The state log used by the matcher.  */
+  re_dfastate_t **state_log;
+  int state_log_top;
+  /* Back reference cache.  */
+  int nbkref_ents;
+  int abkref_ents;
+  struct re_backref_cache_entry *bkref_ents;
+  int max_mb_elem_len;
+  int nsub_tops;
+  int asub_tops;
+  re_sub_match_top_t **sub_tops;
+} re_match_context_t;
+
+typedef struct
+{
+  re_dfastate_t **sifted_states;
+  re_dfastate_t **limited_states;
+  int last_node;
+  int last_str_idx;
+  re_node_set limits;
+} re_sift_context_t;
+
+struct re_fail_stack_ent_t
+{
+  int idx;
+  int node;
+  regmatch_t *regs;
+  re_node_set eps_via_nodes;
+};
+
+struct re_fail_stack_t
+{
+  int num;
+  int alloc;
+  struct re_fail_stack_ent_t *stack;
+};
+
+struct re_dfa_t
+{
+  re_token_t *nodes;
+  size_t nodes_alloc;
+  size_t nodes_len;
+  int *nexts;
+  int *org_indices;
+  re_node_set *edests;
+  re_node_set *eclosures;
+  re_node_set *inveclosures;
+  struct re_state_table_entry *state_table;
+  re_dfastate_t *init_state;
+  re_dfastate_t *init_state_word;
+  re_dfastate_t *init_state_nl;
+  re_dfastate_t *init_state_begbuf;
+  bin_tree_t *str_tree;
+  bin_tree_storage_t *str_tree_storage;
+  re_bitset_ptr_t sb_char;
+  int str_tree_storage_idx;
+
+  /* number of subexpressions `re_nsub' is in regex_t.  */
+  unsigned int state_hash_mask;
+  int init_node;
+  int nbackref; /* The number of backreference in this dfa.  */
+
+  /* Bitmap expressing which backreference is used.  */
+  bitset_word_t used_bkref_map;
+  bitset_word_t completed_bkref_map;
+
+  unsigned int has_plural_match : 1;
+  /* If this dfa has "multibyte node", which is a backreference or
+     a node which can accept multibyte character or multi character
+     collating element.  */
+  unsigned int has_mb_node : 1;
+  unsigned int is_utf8 : 1;
+  unsigned int map_notascii : 1;
+  unsigned int word_ops_used : 1;
+  int mb_cur_max;
+  bitset_t word_char;
+  reg_syntax_t syntax;
+  int *subexp_map;
+#ifdef DEBUG
+  char* re_str;
+#endif
+#if defined _LIBC
+  __libc_lock_define (, lock)
+#endif
+};
+
+#define re_node_set_init_empty(set) memset (set, '\0', sizeof (re_node_set))
+#define re_node_set_remove(set,id) \
+  (re_node_set_remove_at (set, re_node_set_contains (set, id) - 1))
+#define re_node_set_empty(p) ((p)->nelem = 0)
+#define re_node_set_free(set) re_free ((set)->elems)
+\f
+
+typedef enum
+{
+  SB_CHAR,
+  MB_CHAR,
+  EQUIV_CLASS,
+  COLL_SYM,
+  CHAR_CLASS
+} bracket_elem_type;
+
+typedef struct
+{
+  bracket_elem_type type;
+  union
+  {
+    unsigned char ch;
+    unsigned char *name;
+    wchar_t wch;
+  } opr;
+} bracket_elem_t;
+
+
+/* Inline functions for bitset operation.  */
+static inline void
+bitset_not (bitset_t set)
+{
+  int bitset_i;
+  for (bitset_i = 0; bitset_i < BITSET_WORDS; ++bitset_i)
+    set[bitset_i] = ~set[bitset_i];
+}
+
+static inline void
+bitset_merge (bitset_t dest, const bitset_t src)
+{
+  int bitset_i;
+  for (bitset_i = 0; bitset_i < BITSET_WORDS; ++bitset_i)
+    dest[bitset_i] |= src[bitset_i];
+}
+
+static inline void
+bitset_mask (bitset_t dest, const bitset_t src)
+{
+  int bitset_i;
+  for (bitset_i = 0; bitset_i < BITSET_WORDS; ++bitset_i)
+    dest[bitset_i] &= src[bitset_i];
+}
+
+#ifdef RE_ENABLE_I18N
+/* Inline functions for re_string.  */
+static inline int
+internal_function __attribute ((pure))
+re_string_char_size_at (const re_string_t *pstr, int idx)
+{
+  int byte_idx;
+  if (pstr->mb_cur_max == 1)
+    return 1;
+  for (byte_idx = 1; idx + byte_idx < pstr->valid_len; ++byte_idx)
+    if (pstr->wcs[idx + byte_idx] != WEOF)
+      break;
+  return byte_idx;
+}
+
+static inline wint_t
+internal_function __attribute ((pure))
+re_string_wchar_at (const re_string_t *pstr, int idx)
+{
+  if (pstr->mb_cur_max == 1)
+    return (wint_t) pstr->mbs[idx];
+  return (wint_t) pstr->wcs[idx];
+}
+
+# ifndef NOT_IN_libc
+static int
+internal_function __attribute ((pure))
+re_string_elem_size_at (const re_string_t *pstr, int idx)
+{
+#  ifdef _LIBC
+  const unsigned char *p, *extra;
+  const int32_t *table, *indirect;
+  int32_t tmp;
+#   include <locale/weight.h>
+  uint_fast32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES);
+
+  if (nrules != 0)
+    {
+      table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB);
+      extra = (const unsigned char *)
+       _NL_CURRENT (LC_COLLATE, _NL_COLLATE_EXTRAMB);
+      indirect = (const int32_t *) _NL_CURRENT (LC_COLLATE,
+                                               _NL_COLLATE_INDIRECTMB);
+      p = pstr->mbs + idx;
+      tmp = findidx (&p);
+      return p - pstr->mbs - idx;
+    }
+  else
+#  endif /* _LIBC */
+    return 1;
+}
+# endif
+#endif /* RE_ENABLE_I18N */
+
+#endif /*  _REGEX_INTERNAL_H */
diff --git a/compat/regex/regexec.c b/compat/regex/regexec.c
new file mode 100644 (file)
index 0000000..0194965
--- /dev/null
@@ -0,0 +1,4369 @@
+/* Extended regular expression matching and search library.
+   Copyright (C) 2002-2005, 2007, 2009, 2010 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+   Contributed by Isamu Hasegawa <isamu@yamato.ibm.com>.
+
+   The GNU C 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.
+
+   The GNU C 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 the GNU C Library; if not, write to the Free
+   Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301 USA.  */
+
+static reg_errcode_t match_ctx_init (re_match_context_t *cache, int eflags,
+                                    int n) internal_function;
+static void match_ctx_clean (re_match_context_t *mctx) internal_function;
+static void match_ctx_free (re_match_context_t *cache) internal_function;
+static reg_errcode_t match_ctx_add_entry (re_match_context_t *cache, int node,
+                                         int str_idx, int from, int to)
+     internal_function;
+static int search_cur_bkref_entry (const re_match_context_t *mctx, int str_idx)
+     internal_function;
+static reg_errcode_t match_ctx_add_subtop (re_match_context_t *mctx, int node,
+                                          int str_idx) internal_function;
+static re_sub_match_last_t * match_ctx_add_sublast (re_sub_match_top_t *subtop,
+                                                  int node, int str_idx)
+     internal_function;
+static void sift_ctx_init (re_sift_context_t *sctx, re_dfastate_t **sifted_sts,
+                          re_dfastate_t **limited_sts, int last_node,
+                          int last_str_idx)
+     internal_function;
+static reg_errcode_t re_search_internal (const regex_t *preg,
+                                        const char *string, int length,
+                                        int start, int range, int stop,
+                                        size_t nmatch, regmatch_t pmatch[],
+                                        int eflags);
+static int re_search_2_stub (struct re_pattern_buffer *bufp,
+                            const char *string1, int length1,
+                            const char *string2, int length2,
+                            int start, int range, struct re_registers *regs,
+                            int stop, int ret_len);
+static int re_search_stub (struct re_pattern_buffer *bufp,
+                          const char *string, int length, int start,
+                          int range, int stop, struct re_registers *regs,
+                          int ret_len);
+static unsigned re_copy_regs (struct re_registers *regs, regmatch_t *pmatch,
+                             int nregs, int regs_allocated);
+static reg_errcode_t prune_impossible_nodes (re_match_context_t *mctx);
+static int check_matching (re_match_context_t *mctx, int fl_longest_match,
+                          int *p_match_first) internal_function;
+static int check_halt_state_context (const re_match_context_t *mctx,
+                                    const re_dfastate_t *state, int idx)
+     internal_function;
+static void update_regs (const re_dfa_t *dfa, regmatch_t *pmatch,
+                        regmatch_t *prev_idx_match, int cur_node,
+                        int cur_idx, int nmatch) internal_function;
+static reg_errcode_t push_fail_stack (struct re_fail_stack_t *fs,
+                                     int str_idx, int dest_node, int nregs,
+                                     regmatch_t *regs,
+                                     re_node_set *eps_via_nodes)
+     internal_function;
+static reg_errcode_t set_regs (const regex_t *preg,
+                              const re_match_context_t *mctx,
+                              size_t nmatch, regmatch_t *pmatch,
+                              int fl_backtrack) internal_function;
+static reg_errcode_t free_fail_stack_return (struct re_fail_stack_t *fs)
+     internal_function;
+
+#ifdef RE_ENABLE_I18N
+static int sift_states_iter_mb (const re_match_context_t *mctx,
+                               re_sift_context_t *sctx,
+                               int node_idx, int str_idx, int max_str_idx)
+     internal_function;
+#endif /* RE_ENABLE_I18N */
+static reg_errcode_t sift_states_backward (const re_match_context_t *mctx,
+                                          re_sift_context_t *sctx)
+     internal_function;
+static reg_errcode_t build_sifted_states (const re_match_context_t *mctx,
+                                         re_sift_context_t *sctx, int str_idx,
+                                         re_node_set *cur_dest)
+     internal_function;
+static reg_errcode_t update_cur_sifted_state (const re_match_context_t *mctx,
+                                             re_sift_context_t *sctx,
+                                             int str_idx,
+                                             re_node_set *dest_nodes)
+     internal_function;
+static reg_errcode_t add_epsilon_src_nodes (const re_dfa_t *dfa,
+                                           re_node_set *dest_nodes,
+                                           const re_node_set *candidates)
+     internal_function;
+static int check_dst_limits (const re_match_context_t *mctx,
+                            re_node_set *limits,
+                            int dst_node, int dst_idx, int src_node,
+                            int src_idx) internal_function;
+static int check_dst_limits_calc_pos_1 (const re_match_context_t *mctx,
+                                       int boundaries, int subexp_idx,
+                                       int from_node, int bkref_idx)
+     internal_function;
+static int check_dst_limits_calc_pos (const re_match_context_t *mctx,
+                                     int limit, int subexp_idx,
+                                     int node, int str_idx,
+                                     int bkref_idx) internal_function;
+static reg_errcode_t check_subexp_limits (const re_dfa_t *dfa,
+                                         re_node_set *dest_nodes,
+                                         const re_node_set *candidates,
+                                         re_node_set *limits,
+                                         struct re_backref_cache_entry *bkref_ents,
+                                         int str_idx) internal_function;
+static reg_errcode_t sift_states_bkref (const re_match_context_t *mctx,
+                                       re_sift_context_t *sctx,
+                                       int str_idx, const re_node_set *candidates)
+     internal_function;
+static reg_errcode_t merge_state_array (const re_dfa_t *dfa,
+                                       re_dfastate_t **dst,
+                                       re_dfastate_t **src, int num)
+     internal_function;
+static re_dfastate_t *find_recover_state (reg_errcode_t *err,
+                                        re_match_context_t *mctx) internal_function;
+static re_dfastate_t *transit_state (reg_errcode_t *err,
+                                    re_match_context_t *mctx,
+                                    re_dfastate_t *state) internal_function;
+static re_dfastate_t *merge_state_with_log (reg_errcode_t *err,
+                                           re_match_context_t *mctx,
+                                           re_dfastate_t *next_state)
+     internal_function;
+static reg_errcode_t check_subexp_matching_top (re_match_context_t *mctx,
+                                               re_node_set *cur_nodes,
+                                               int str_idx) internal_function;
+#if 0
+static re_dfastate_t *transit_state_sb (reg_errcode_t *err,
+                                       re_match_context_t *mctx,
+                                       re_dfastate_t *pstate)
+     internal_function;
+#endif
+#ifdef RE_ENABLE_I18N
+static reg_errcode_t transit_state_mb (re_match_context_t *mctx,
+                                      re_dfastate_t *pstate)
+     internal_function;
+#endif /* RE_ENABLE_I18N */
+static reg_errcode_t transit_state_bkref (re_match_context_t *mctx,
+                                         const re_node_set *nodes)
+     internal_function;
+static reg_errcode_t get_subexp (re_match_context_t *mctx,
+                                int bkref_node, int bkref_str_idx)
+     internal_function;
+static reg_errcode_t get_subexp_sub (re_match_context_t *mctx,
+                                    const re_sub_match_top_t *sub_top,
+                                    re_sub_match_last_t *sub_last,
+                                    int bkref_node, int bkref_str)
+     internal_function;
+static int find_subexp_node (const re_dfa_t *dfa, const re_node_set *nodes,
+                            int subexp_idx, int type) internal_function;
+static reg_errcode_t check_arrival (re_match_context_t *mctx,
+                                   state_array_t *path, int top_node,
+                                   int top_str, int last_node, int last_str,
+                                   int type) internal_function;
+static reg_errcode_t check_arrival_add_next_nodes (re_match_context_t *mctx,
+                                                  int str_idx,
+                                                  re_node_set *cur_nodes,
+                                                  re_node_set *next_nodes)
+     internal_function;
+static reg_errcode_t check_arrival_expand_ecl (const re_dfa_t *dfa,
+                                              re_node_set *cur_nodes,
+                                              int ex_subexp, int type)
+     internal_function;
+static reg_errcode_t check_arrival_expand_ecl_sub (const re_dfa_t *dfa,
+                                                  re_node_set *dst_nodes,
+                                                  int target, int ex_subexp,
+                                                  int type) internal_function;
+static reg_errcode_t expand_bkref_cache (re_match_context_t *mctx,
+                                        re_node_set *cur_nodes, int cur_str,
+                                        int subexp_num, int type)
+     internal_function;
+static int build_trtable (const re_dfa_t *dfa,
+                         re_dfastate_t *state) internal_function;
+#ifdef RE_ENABLE_I18N
+static int check_node_accept_bytes (const re_dfa_t *dfa, int node_idx,
+                                   const re_string_t *input, int idx)
+     internal_function;
+# ifdef _LIBC
+static unsigned int find_collation_sequence_value (const unsigned char *mbs,
+                                                  size_t name_len)
+     internal_function;
+# endif /* _LIBC */
+#endif /* RE_ENABLE_I18N */
+static int group_nodes_into_DFAstates (const re_dfa_t *dfa,
+                                      const re_dfastate_t *state,
+                                      re_node_set *states_node,
+                                      bitset_t *states_ch) internal_function;
+static int check_node_accept (const re_match_context_t *mctx,
+                             const re_token_t *node, int idx)
+     internal_function;
+static reg_errcode_t extend_buffers (re_match_context_t *mctx)
+     internal_function;
+\f
+/* Entry point for POSIX code.  */
+
+/* regexec searches for a given pattern, specified by PREG, in the
+   string STRING.
+
+   If NMATCH is zero or REG_NOSUB was set in the cflags argument to
+   `regcomp', we ignore PMATCH.  Otherwise, we assume PMATCH has at
+   least NMATCH elements, and we set them to the offsets of the
+   corresponding matched substrings.
+
+   EFLAGS specifies `execution flags' which affect matching: if
+   REG_NOTBOL is set, then ^ does not match at the beginning of the
+   string; if REG_NOTEOL is set, then $ does not match at the end.
+
+   We return 0 if we find a match and REG_NOMATCH if not.  */
+
+int
+regexec (
+       const regex_t *__restrict preg,
+       const char *__restrict string,
+       size_t nmatch,
+       regmatch_t pmatch[],
+       int eflags)
+{
+  reg_errcode_t err;
+  int start, length;
+
+  if (eflags & ~(REG_NOTBOL | REG_NOTEOL | REG_STARTEND))
+    return REG_BADPAT;
+
+  if (eflags & REG_STARTEND)
+    {
+      start = pmatch[0].rm_so;
+      length = pmatch[0].rm_eo;
+    }
+  else
+    {
+      start = 0;
+      length = strlen (string);
+    }
+
+  __libc_lock_lock (dfa->lock);
+  if (preg->no_sub)
+    err = re_search_internal (preg, string, length, start, length - start,
+                             length, 0, NULL, eflags);
+  else
+    err = re_search_internal (preg, string, length, start, length - start,
+                             length, nmatch, pmatch, eflags);
+  __libc_lock_unlock (dfa->lock);
+  return err != REG_NOERROR;
+}
+
+#ifdef _LIBC
+# include <shlib-compat.h>
+versioned_symbol (libc, __regexec, regexec, GLIBC_2_3_4);
+
+# if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_3_4)
+__typeof__ (__regexec) __compat_regexec;
+
+int
+attribute_compat_text_section
+__compat_regexec (const regex_t *__restrict preg,
+                 const char *__restrict string, size_t nmatch,
+                 regmatch_t pmatch[], int eflags)
+{
+  return regexec (preg, string, nmatch, pmatch,
+                 eflags & (REG_NOTBOL | REG_NOTEOL));
+}
+compat_symbol (libc, __compat_regexec, regexec, GLIBC_2_0);
+# endif
+#endif
+
+/* Entry points for GNU code.  */
+
+/* re_match, re_search, re_match_2, re_search_2
+
+   The former two functions operate on STRING with length LENGTH,
+   while the later two operate on concatenation of STRING1 and STRING2
+   with lengths LENGTH1 and LENGTH2, respectively.
+
+   re_match() matches the compiled pattern in BUFP against the string,
+   starting at index START.
+
+   re_search() first tries matching at index START, then it tries to match
+   starting from index START + 1, and so on.  The last start position tried
+   is START + RANGE.  (Thus RANGE = 0 forces re_search to operate the same
+   way as re_match().)
+
+   The parameter STOP of re_{match,search}_2 specifies that no match exceeding
+   the first STOP characters of the concatenation of the strings should be
+   concerned.
+
+   If REGS is not NULL, and BUFP->no_sub is not set, the offsets of the match
+   and all groups is stroed in REGS.  (For the "_2" variants, the offsets are
+   computed relative to the concatenation, not relative to the individual
+   strings.)
+
+   On success, re_match* functions return the length of the match, re_search*
+   return the position of the start of the match.  Return value -1 means no
+   match was found and -2 indicates an internal error.  */
+
+int
+re_match (struct re_pattern_buffer *bufp,
+         const char *string,
+         int length,
+         int start,
+         struct re_registers *regs)
+{
+  return re_search_stub (bufp, string, length, start, 0, length, regs, 1);
+}
+#ifdef _LIBC
+weak_alias (__re_match, re_match)
+#endif
+
+int
+re_search (struct re_pattern_buffer *bufp,
+          const char *string,
+          int length, int start, int range,
+          struct re_registers *regs)
+{
+  return re_search_stub (bufp, string, length, start, range, length, regs, 0);
+}
+#ifdef _LIBC
+weak_alias (__re_search, re_search)
+#endif
+
+int
+re_match_2 (struct re_pattern_buffer *bufp,
+           const char *string1, int length1,
+           const char *string2, int length2, int start,
+           struct re_registers *regs, int stop)
+{
+  return re_search_2_stub (bufp, string1, length1, string2, length2,
+                          start, 0, regs, stop, 1);
+}
+#ifdef _LIBC
+weak_alias (__re_match_2, re_match_2)
+#endif
+
+int
+re_search_2 (struct re_pattern_buffer *bufp,
+            const char *string1, int length1,
+            const char *string2, int length2, int start,
+            int range, struct re_registers *regs,  int stop)
+{
+  return re_search_2_stub (bufp, string1, length1, string2, length2,
+                          start, range, regs, stop, 0);
+}
+#ifdef _LIBC
+weak_alias (__re_search_2, re_search_2)
+#endif
+
+static int
+re_search_2_stub (struct re_pattern_buffer *bufp,
+                 const char *string1, int length1,
+                 const char *string2, int length2, int start,
+                 int range, struct re_registers *regs,
+                 int stop, int ret_len)
+{
+  const char *str;
+  int rval;
+  int len = length1 + length2;
+  int free_str = 0;
+
+  if (BE (length1 < 0 || length2 < 0 || stop < 0, 0))
+    return -2;
+
+  /* Concatenate the strings.  */
+  if (length2 > 0)
+    if (length1 > 0)
+      {
+       char *s = re_malloc (char, len);
+
+       if (BE (s == NULL, 0))
+         return -2;
+       memcpy (s, string1, length1);
+       memcpy (s + length1, string2, length2);
+       str = s;
+       free_str = 1;
+      }
+    else
+      str = string2;
+  else
+    str = string1;
+
+  rval = re_search_stub (bufp, str, len, start, range, stop, regs, ret_len);
+  if (free_str)
+    re_free ((char *) str);
+  return rval;
+}
+
+/* The parameters have the same meaning as those of re_search.
+   Additional parameters:
+   If RET_LEN is nonzero the length of the match is returned (re_match style);
+   otherwise the position of the match is returned.  */
+
+static int
+re_search_stub (struct re_pattern_buffer *bufp,
+               const char *string, int length, int start,
+               int range, int stop,
+               struct re_registers *regs, int ret_len)
+{
+  reg_errcode_t result;
+  regmatch_t *pmatch;
+  int nregs, rval;
+  int eflags = 0;
+
+  /* Check for out-of-range.  */
+  if (BE (start < 0 || start > length, 0))
+    return -1;
+  if (BE (start + range > length, 0))
+    range = length - start;
+  else if (BE (start + range < 0, 0))
+    range = -start;
+
+  __libc_lock_lock (dfa->lock);
+
+  eflags |= (bufp->not_bol) ? REG_NOTBOL : 0;
+  eflags |= (bufp->not_eol) ? REG_NOTEOL : 0;
+
+  /* Compile fastmap if we haven't yet.  */
+  if (range > 0 && bufp->fastmap != NULL && !bufp->fastmap_accurate)
+    re_compile_fastmap (bufp);
+
+  if (BE (bufp->no_sub, 0))
+    regs = NULL;
+
+  /* We need at least 1 register.  */
+  if (regs == NULL)
+    nregs = 1;
+  else if (BE (bufp->regs_allocated == REGS_FIXED &&
+              regs->num_regs < bufp->re_nsub + 1, 0))
+    {
+      nregs = regs->num_regs;
+      if (BE (nregs < 1, 0))
+       {
+         /* Nothing can be copied to regs.  */
+         regs = NULL;
+         nregs = 1;
+       }
+    }
+  else
+    nregs = bufp->re_nsub + 1;
+  pmatch = re_malloc (regmatch_t, nregs);
+  if (BE (pmatch == NULL, 0))
+    {
+      rval = -2;
+      goto out;
+    }
+
+  result = re_search_internal (bufp, string, length, start, range, stop,
+                              nregs, pmatch, eflags);
+
+  rval = 0;
+
+  /* I hope we needn't fill ther regs with -1's when no match was found.  */
+  if (result != REG_NOERROR)
+    rval = -1;
+  else if (regs != NULL)
+    {
+      /* If caller wants register contents data back, copy them.  */
+      bufp->regs_allocated = re_copy_regs (regs, pmatch, nregs,
+                                          bufp->regs_allocated);
+      if (BE (bufp->regs_allocated == REGS_UNALLOCATED, 0))
+       rval = -2;
+    }
+
+  if (BE (rval == 0, 1))
+    {
+      if (ret_len)
+       {
+         assert (pmatch[0].rm_so == start);
+         rval = pmatch[0].rm_eo - start;
+       }
+      else
+       rval = pmatch[0].rm_so;
+    }
+  re_free (pmatch);
+ out:
+  __libc_lock_unlock (dfa->lock);
+  return rval;
+}
+
+static unsigned
+re_copy_regs (struct re_registers *regs,
+             regmatch_t *pmatch,
+             int nregs, int regs_allocated)
+{
+  int rval = REGS_REALLOCATE;
+  int i;
+  int need_regs = nregs + 1;
+  /* We need one extra element beyond `num_regs' for the `-1' marker GNU code
+     uses.  */
+
+  /* Have the register data arrays been allocated?  */
+  if (regs_allocated == REGS_UNALLOCATED)
+    { /* No.  So allocate them with malloc.  */
+      regs->start = re_malloc (regoff_t, need_regs);
+      if (BE (regs->start == NULL, 0))
+       return REGS_UNALLOCATED;
+      regs->end = re_malloc (regoff_t, need_regs);
+      if (BE (regs->end == NULL, 0))
+       {
+         re_free (regs->start);
+         return REGS_UNALLOCATED;
+       }
+      regs->num_regs = need_regs;
+    }
+  else if (regs_allocated == REGS_REALLOCATE)
+    { /* Yes.  If we need more elements than were already
+        allocated, reallocate them.  If we need fewer, just
+        leave it alone.  */
+      if (BE (need_regs > regs->num_regs, 0))
+       {
+         regoff_t *new_start = re_realloc (regs->start, regoff_t, need_regs);
+         regoff_t *new_end;
+         if (BE (new_start == NULL, 0))
+           return REGS_UNALLOCATED;
+         new_end = re_realloc (regs->end, regoff_t, need_regs);
+         if (BE (new_end == NULL, 0))
+           {
+             re_free (new_start);
+             return REGS_UNALLOCATED;
+           }
+         regs->start = new_start;
+         regs->end = new_end;
+         regs->num_regs = need_regs;
+       }
+    }
+  else
+    {
+      assert (regs_allocated == REGS_FIXED);
+      /* This function may not be called with REGS_FIXED and nregs too big.  */
+      assert (regs->num_regs >= nregs);
+      rval = REGS_FIXED;
+    }
+
+  /* Copy the regs.  */
+  for (i = 0; i < nregs; ++i)
+    {
+      regs->start[i] = pmatch[i].rm_so;
+      regs->end[i] = pmatch[i].rm_eo;
+    }
+  for ( ; i < regs->num_regs; ++i)
+    regs->start[i] = regs->end[i] = -1;
+
+  return rval;
+}
+
+/* Set REGS to hold NUM_REGS registers, storing them in STARTS and
+   ENDS.  Subsequent matches using PATTERN_BUFFER and REGS will use
+   this memory for recording register information.  STARTS and ENDS
+   must be allocated using the malloc library routine, and must each
+   be at least NUM_REGS * sizeof (regoff_t) bytes long.
+
+   If NUM_REGS == 0, then subsequent matches should allocate their own
+   register data.
+
+   Unless this function is called, the first search or match using
+   PATTERN_BUFFER will allocate its own register data, without
+   freeing the old data.  */
+
+void
+re_set_registers (struct re_pattern_buffer *bufp,
+                 struct re_registers *regs,
+                 unsigned num_regs,
+                 regoff_t *starts,
+                 regoff_t *ends)
+{
+  if (num_regs)
+    {
+      bufp->regs_allocated = REGS_REALLOCATE;
+      regs->num_regs = num_regs;
+      regs->start = starts;
+      regs->end = ends;
+    }
+  else
+    {
+      bufp->regs_allocated = REGS_UNALLOCATED;
+      regs->num_regs = 0;
+      regs->start = regs->end = (regoff_t *) 0;
+    }
+}
+#ifdef _LIBC
+weak_alias (__re_set_registers, re_set_registers)
+#endif
+\f
+/* Entry points compatible with 4.2 BSD regex library.  We don't define
+   them unless specifically requested.  */
+
+#if defined _REGEX_RE_COMP || defined _LIBC
+int
+# ifdef _LIBC
+weak_function
+# endif
+re_exec (s)
+     const char *s;
+{
+  return 0 == regexec (&re_comp_buf, s, 0, NULL, 0);
+}
+#endif /* _REGEX_RE_COMP */
+\f
+/* Internal entry point.  */
+
+/* Searches for a compiled pattern PREG in the string STRING, whose
+   length is LENGTH.  NMATCH, PMATCH, and EFLAGS have the same
+   mingings with regexec.  START, and RANGE have the same meanings
+   with re_search.
+   Return REG_NOERROR if we find a match, and REG_NOMATCH if not,
+   otherwise return the error code.
+   Note: We assume front end functions already check ranges.
+   (START + RANGE >= 0 && START + RANGE <= LENGTH)  */
+
+static reg_errcode_t
+re_search_internal (const regex_t *preg,
+                   const char *string,
+                   int length, int start, int range, int stop,
+                   size_t nmatch, regmatch_t pmatch[],
+                   int eflags)
+{
+  reg_errcode_t err;
+  const re_dfa_t *dfa = (const re_dfa_t *) preg->buffer;
+  int left_lim, right_lim, incr;
+  int fl_longest_match, match_first, match_kind, match_last = -1;
+  int extra_nmatch;
+  int sb, ch;
+#if defined _LIBC || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L)
+  re_match_context_t mctx = { .dfa = dfa };
+#else
+  re_match_context_t mctx;
+#endif
+  char *fastmap = (preg->fastmap != NULL && preg->fastmap_accurate
+                  && range && !preg->can_be_null) ? preg->fastmap : NULL;
+  RE_TRANSLATE_TYPE t = preg->translate;
+
+#if !(defined _LIBC || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L))
+  memset (&mctx, '\0', sizeof (re_match_context_t));
+  mctx.dfa = dfa;
+#endif
+
+  extra_nmatch = (nmatch > preg->re_nsub) ? nmatch - (preg->re_nsub + 1) : 0;
+  nmatch -= extra_nmatch;
+
+  /* Check if the DFA haven't been compiled.  */
+  if (BE (preg->used == 0 || dfa->init_state == NULL
+         || dfa->init_state_word == NULL || dfa->init_state_nl == NULL
+         || dfa->init_state_begbuf == NULL, 0))
+    return REG_NOMATCH;
+
+#ifdef DEBUG
+  /* We assume front-end functions already check them.  */
+  assert (start + range >= 0 && start + range <= length);
+#endif
+
+  /* If initial states with non-begbuf contexts have no elements,
+     the regex must be anchored.  If preg->newline_anchor is set,
+     we'll never use init_state_nl, so do not check it.  */
+  if (dfa->init_state->nodes.nelem == 0
+      && dfa->init_state_word->nodes.nelem == 0
+      && (dfa->init_state_nl->nodes.nelem == 0
+         || !preg->newline_anchor))
+    {
+      if (start != 0 && start + range != 0)
+       return REG_NOMATCH;
+      start = range = 0;
+    }
+
+  /* We must check the longest matching, if nmatch > 0.  */
+  fl_longest_match = (nmatch != 0 || dfa->nbackref);
+
+  err = re_string_allocate (&mctx.input, string, length, dfa->nodes_len + 1,
+                           preg->translate, preg->syntax & RE_ICASE, dfa);
+  if (BE (err != REG_NOERROR, 0))
+    goto free_return;
+  mctx.input.stop = stop;
+  mctx.input.raw_stop = stop;
+  mctx.input.newline_anchor = preg->newline_anchor;
+
+  err = match_ctx_init (&mctx, eflags, dfa->nbackref * 2);
+  if (BE (err != REG_NOERROR, 0))
+    goto free_return;
+
+  /* We will log all the DFA states through which the dfa pass,
+     if nmatch > 1, or this dfa has "multibyte node", which is a
+     back-reference or a node which can accept multibyte character or
+     multi character collating element.  */
+  if (nmatch > 1 || dfa->has_mb_node)
+    {
+      /* Avoid overflow.  */
+      if (BE (SIZE_MAX / sizeof (re_dfastate_t *) <= mctx.input.bufs_len, 0))
+       {
+         err = REG_ESPACE;
+         goto free_return;
+       }
+
+      mctx.state_log = re_malloc (re_dfastate_t *, mctx.input.bufs_len + 1);
+      if (BE (mctx.state_log == NULL, 0))
+       {
+         err = REG_ESPACE;
+         goto free_return;
+       }
+    }
+  else
+    mctx.state_log = NULL;
+
+  match_first = start;
+  mctx.input.tip_context = (eflags & REG_NOTBOL) ? CONTEXT_BEGBUF
+                          : CONTEXT_NEWLINE | CONTEXT_BEGBUF;
+
+  /* Check incrementally whether of not the input string match.  */
+  incr = (range < 0) ? -1 : 1;
+  left_lim = (range < 0) ? start + range : start;
+  right_lim = (range < 0) ? start : start + range;
+  sb = dfa->mb_cur_max == 1;
+  match_kind =
+    (fastmap
+     ? ((sb || !(preg->syntax & RE_ICASE || t) ? 4 : 0)
+       | (range >= 0 ? 2 : 0)
+       | (t != NULL ? 1 : 0))
+     : 8);
+
+  for (;; match_first += incr)
+    {
+      err = REG_NOMATCH;
+      if (match_first < left_lim || right_lim < match_first)
+       goto free_return;
+
+      /* Advance as rapidly as possible through the string, until we
+        find a plausible place to start matching.  This may be done
+        with varying efficiency, so there are various possibilities:
+        only the most common of them are specialized, in order to
+        save on code size.  We use a switch statement for speed.  */
+      switch (match_kind)
+       {
+       case 8:
+         /* No fastmap.  */
+         break;
+
+       case 7:
+         /* Fastmap with single-byte translation, match forward.  */
+         while (BE (match_first < right_lim, 1)
+                && !fastmap[t[(unsigned char) string[match_first]]])
+           ++match_first;
+         goto forward_match_found_start_or_reached_end;
+
+       case 6:
+         /* Fastmap without translation, match forward.  */
+         while (BE (match_first < right_lim, 1)
+                && !fastmap[(unsigned char) string[match_first]])
+           ++match_first;
+
+       forward_match_found_start_or_reached_end:
+         if (BE (match_first == right_lim, 0))
+           {
+             ch = match_first >= length
+                      ? 0 : (unsigned char) string[match_first];
+             if (!fastmap[t ? t[ch] : ch])
+               goto free_return;
+           }
+         break;
+
+       case 4:
+       case 5:
+         /* Fastmap without multi-byte translation, match backwards.  */
+         while (match_first >= left_lim)
+           {
+             ch = match_first >= length
+                      ? 0 : (unsigned char) string[match_first];
+             if (fastmap[t ? t[ch] : ch])
+               break;
+             --match_first;
+           }
+         if (match_first < left_lim)
+           goto free_return;
+         break;
+
+       default:
+         /* In this case, we can't determine easily the current byte,
+            since it might be a component byte of a multibyte
+            character.  Then we use the constructed buffer instead.  */
+         for (;;)
+           {
+             /* If MATCH_FIRST is out of the valid range, reconstruct the
+                buffers.  */
+             unsigned int offset = match_first - mctx.input.raw_mbs_idx;
+             if (BE (offset >= (unsigned int) mctx.input.valid_raw_len, 0))
+               {
+                 err = re_string_reconstruct (&mctx.input, match_first,
+                                              eflags);
+                 if (BE (err != REG_NOERROR, 0))
+                   goto free_return;
+
+                 offset = match_first - mctx.input.raw_mbs_idx;
+               }
+             /* If MATCH_FIRST is out of the buffer, leave it as '\0'.
+                Note that MATCH_FIRST must not be smaller than 0.  */
+             ch = (match_first >= length
+                   ? 0 : re_string_byte_at (&mctx.input, offset));
+             if (fastmap[ch])
+               break;
+             match_first += incr;
+             if (match_first < left_lim || match_first > right_lim)
+               {
+                 err = REG_NOMATCH;
+                 goto free_return;
+               }
+           }
+         break;
+       }
+
+      /* Reconstruct the buffers so that the matcher can assume that
+        the matching starts from the beginning of the buffer.  */
+      err = re_string_reconstruct (&mctx.input, match_first, eflags);
+      if (BE (err != REG_NOERROR, 0))
+       goto free_return;
+
+#ifdef RE_ENABLE_I18N
+     /* Don't consider this char as a possible match start if it part,
+       yet isn't the head, of a multibyte character.  */
+      if (!sb && !re_string_first_byte (&mctx.input, 0))
+       continue;
+#endif
+
+      /* It seems to be appropriate one, then use the matcher.  */
+      /* We assume that the matching starts from 0.  */
+      mctx.state_log_top = mctx.nbkref_ents = mctx.max_mb_elem_len = 0;
+      match_last = check_matching (&mctx, fl_longest_match,
+                                  range >= 0 ? &match_first : NULL);
+      if (match_last != -1)
+       {
+         if (BE (match_last == -2, 0))
+           {
+             err = REG_ESPACE;
+             goto free_return;
+           }
+         else
+           {
+             mctx.match_last = match_last;
+             if ((!preg->no_sub && nmatch > 1) || dfa->nbackref)
+               {
+                 re_dfastate_t *pstate = mctx.state_log[match_last];
+                 mctx.last_node = check_halt_state_context (&mctx, pstate,
+                                                            match_last);
+               }
+             if ((!preg->no_sub && nmatch > 1 && dfa->has_plural_match)
+                 || dfa->nbackref)
+               {
+                 err = prune_impossible_nodes (&mctx);
+                 if (err == REG_NOERROR)
+                   break;
+                 if (BE (err != REG_NOMATCH, 0))
+                   goto free_return;
+                 match_last = -1;
+               }
+             else
+               break; /* We found a match.  */
+           }
+       }
+
+      match_ctx_clean (&mctx);
+    }
+
+#ifdef DEBUG
+  assert (match_last != -1);
+  assert (err == REG_NOERROR);
+#endif
+
+  /* Set pmatch[] if we need.  */
+  if (nmatch > 0)
+    {
+      int reg_idx;
+
+      /* Initialize registers.  */
+      for (reg_idx = 1; reg_idx < nmatch; ++reg_idx)
+       pmatch[reg_idx].rm_so = pmatch[reg_idx].rm_eo = -1;
+
+      /* Set the points where matching start/end.  */
+      pmatch[0].rm_so = 0;
+      pmatch[0].rm_eo = mctx.match_last;
+
+      if (!preg->no_sub && nmatch > 1)
+       {
+         err = set_regs (preg, &mctx, nmatch, pmatch,
+                         dfa->has_plural_match && dfa->nbackref > 0);
+         if (BE (err != REG_NOERROR, 0))
+           goto free_return;
+       }
+
+      /* At last, add the offset to the each registers, since we slided
+        the buffers so that we could assume that the matching starts
+        from 0.  */
+      for (reg_idx = 0; reg_idx < nmatch; ++reg_idx)
+       if (pmatch[reg_idx].rm_so != -1)
+         {
+#ifdef RE_ENABLE_I18N
+           if (BE (mctx.input.offsets_needed != 0, 0))
+             {
+               pmatch[reg_idx].rm_so =
+                 (pmatch[reg_idx].rm_so == mctx.input.valid_len
+                  ? mctx.input.valid_raw_len
+                  : mctx.input.offsets[pmatch[reg_idx].rm_so]);
+               pmatch[reg_idx].rm_eo =
+                 (pmatch[reg_idx].rm_eo == mctx.input.valid_len
+                  ? mctx.input.valid_raw_len
+                  : mctx.input.offsets[pmatch[reg_idx].rm_eo]);
+             }
+#else
+           assert (mctx.input.offsets_needed == 0);
+#endif
+           pmatch[reg_idx].rm_so += match_first;
+           pmatch[reg_idx].rm_eo += match_first;
+         }
+      for (reg_idx = 0; reg_idx < extra_nmatch; ++reg_idx)
+       {
+         pmatch[nmatch + reg_idx].rm_so = -1;
+         pmatch[nmatch + reg_idx].rm_eo = -1;
+       }
+
+      if (dfa->subexp_map)
+       for (reg_idx = 0; reg_idx + 1 < nmatch; reg_idx++)
+         if (dfa->subexp_map[reg_idx] != reg_idx)
+           {
+             pmatch[reg_idx + 1].rm_so
+               = pmatch[dfa->subexp_map[reg_idx] + 1].rm_so;
+             pmatch[reg_idx + 1].rm_eo
+               = pmatch[dfa->subexp_map[reg_idx] + 1].rm_eo;
+           }
+    }
+
+ free_return:
+  re_free (mctx.state_log);
+  if (dfa->nbackref)
+    match_ctx_free (&mctx);
+  re_string_destruct (&mctx.input);
+  return err;
+}
+
+static reg_errcode_t
+prune_impossible_nodes (re_match_context_t *mctx)
+{
+  const re_dfa_t *const dfa = mctx->dfa;
+  int halt_node, match_last;
+  reg_errcode_t ret;
+  re_dfastate_t **sifted_states;
+  re_dfastate_t **lim_states = NULL;
+  re_sift_context_t sctx;
+#ifdef DEBUG
+  assert (mctx->state_log != NULL);
+#endif
+  match_last = mctx->match_last;
+  halt_node = mctx->last_node;
+
+  /* Avoid overflow.  */
+  if (BE (SIZE_MAX / sizeof (re_dfastate_t *) <= match_last, 0))
+    return REG_ESPACE;
+
+  sifted_states = re_malloc (re_dfastate_t *, match_last + 1);
+  if (BE (sifted_states == NULL, 0))
+    {
+      ret = REG_ESPACE;
+      goto free_return;
+    }
+  if (dfa->nbackref)
+    {
+      lim_states = re_malloc (re_dfastate_t *, match_last + 1);
+      if (BE (lim_states == NULL, 0))
+       {
+         ret = REG_ESPACE;
+         goto free_return;
+       }
+      while (1)
+       {
+         memset (lim_states, '\0',
+                 sizeof (re_dfastate_t *) * (match_last + 1));
+         sift_ctx_init (&sctx, sifted_states, lim_states, halt_node,
+                        match_last);
+         ret = sift_states_backward (mctx, &sctx);
+         re_node_set_free (&sctx.limits);
+         if (BE (ret != REG_NOERROR, 0))
+             goto free_return;
+         if (sifted_states[0] != NULL || lim_states[0] != NULL)
+           break;
+         do
+           {
+             --match_last;
+             if (match_last < 0)
+               {
+                 ret = REG_NOMATCH;
+                 goto free_return;
+               }
+           } while (mctx->state_log[match_last] == NULL
+                    || !mctx->state_log[match_last]->halt);
+         halt_node = check_halt_state_context (mctx,
+                                               mctx->state_log[match_last],
+                                               match_last);
+       }
+      ret = merge_state_array (dfa, sifted_states, lim_states,
+                              match_last + 1);
+      re_free (lim_states);
+      lim_states = NULL;
+      if (BE (ret != REG_NOERROR, 0))
+       goto free_return;
+    }
+  else
+    {
+      sift_ctx_init (&sctx, sifted_states, lim_states, halt_node, match_last);
+      ret = sift_states_backward (mctx, &sctx);
+      re_node_set_free (&sctx.limits);
+      if (BE (ret != REG_NOERROR, 0))
+       goto free_return;
+      if (sifted_states[0] == NULL)
+       {
+         ret = REG_NOMATCH;
+         goto free_return;
+       }
+    }
+  re_free (mctx->state_log);
+  mctx->state_log = sifted_states;
+  sifted_states = NULL;
+  mctx->last_node = halt_node;
+  mctx->match_last = match_last;
+  ret = REG_NOERROR;
+ free_return:
+  re_free (sifted_states);
+  re_free (lim_states);
+  return ret;
+}
+
+/* Acquire an initial state and return it.
+   We must select appropriate initial state depending on the context,
+   since initial states may have constraints like "\<", "^", etc..  */
+
+static inline re_dfastate_t *
+__attribute ((always_inline)) internal_function
+acquire_init_state_context (reg_errcode_t *err, const re_match_context_t *mctx,
+                           int idx)
+{
+  const re_dfa_t *const dfa = mctx->dfa;
+  if (dfa->init_state->has_constraint)
+    {
+      unsigned int context;
+      context = re_string_context_at (&mctx->input, idx - 1, mctx->eflags);
+      if (IS_WORD_CONTEXT (context))
+       return dfa->init_state_word;
+      else if (IS_ORDINARY_CONTEXT (context))
+       return dfa->init_state;
+      else if (IS_BEGBUF_CONTEXT (context) && IS_NEWLINE_CONTEXT (context))
+       return dfa->init_state_begbuf;
+      else if (IS_NEWLINE_CONTEXT (context))
+       return dfa->init_state_nl;
+      else if (IS_BEGBUF_CONTEXT (context))
+       {
+         /* It is relatively rare case, then calculate on demand.  */
+         return re_acquire_state_context (err, dfa,
+                                          dfa->init_state->entrance_nodes,
+                                          context);
+       }
+      else
+       /* Must not happen?  */
+       return dfa->init_state;
+    }
+  else
+    return dfa->init_state;
+}
+
+/* Check whether the regular expression match input string INPUT or not,
+   and return the index where the matching end, return -1 if not match,
+   or return -2 in case of an error.
+   FL_LONGEST_MATCH means we want the POSIX longest matching.
+   If P_MATCH_FIRST is not NULL, and the match fails, it is set to the
+   next place where we may want to try matching.
+   Note that the matcher assume that the maching starts from the current
+   index of the buffer.  */
+
+static int
+internal_function
+check_matching (re_match_context_t *mctx, int fl_longest_match,
+               int *p_match_first)
+{
+  const re_dfa_t *const dfa = mctx->dfa;
+  reg_errcode_t err;
+  int match = 0;
+  int match_last = -1;
+  int cur_str_idx = re_string_cur_idx (&mctx->input);
+  re_dfastate_t *cur_state;
+  int at_init_state = p_match_first != NULL;
+  int next_start_idx = cur_str_idx;
+
+  err = REG_NOERROR;
+  cur_state = acquire_init_state_context (&err, mctx, cur_str_idx);
+  /* An initial state must not be NULL (invalid).  */
+  if (BE (cur_state == NULL, 0))
+    {
+      assert (err == REG_ESPACE);
+      return -2;
+    }
+
+  if (mctx->state_log != NULL)
+    {
+      mctx->state_log[cur_str_idx] = cur_state;
+
+      /* Check OP_OPEN_SUBEXP in the initial state in case that we use them
+        later.  E.g. Processing back references.  */
+      if (BE (dfa->nbackref, 0))
+       {
+         at_init_state = 0;
+         err = check_subexp_matching_top (mctx, &cur_state->nodes, 0);
+         if (BE (err != REG_NOERROR, 0))
+           return err;
+
+         if (cur_state->has_backref)
+           {
+             err = transit_state_bkref (mctx, &cur_state->nodes);
+             if (BE (err != REG_NOERROR, 0))
+               return err;
+           }
+       }
+    }
+
+  /* If the RE accepts NULL string.  */
+  if (BE (cur_state->halt, 0))
+    {
+      if (!cur_state->has_constraint
+         || check_halt_state_context (mctx, cur_state, cur_str_idx))
+       {
+         if (!fl_longest_match)
+           return cur_str_idx;
+         else
+           {
+             match_last = cur_str_idx;
+             match = 1;
+           }
+       }
+    }
+
+  while (!re_string_eoi (&mctx->input))
+    {
+      re_dfastate_t *old_state = cur_state;
+      int next_char_idx = re_string_cur_idx (&mctx->input) + 1;
+
+      if (BE (next_char_idx >= mctx->input.bufs_len, 0)
+         || (BE (next_char_idx >= mctx->input.valid_len, 0)
+             && mctx->input.valid_len < mctx->input.len))
+       {
+         err = extend_buffers (mctx);
+         if (BE (err != REG_NOERROR, 0))
+           {
+             assert (err == REG_ESPACE);
+             return -2;
+           }
+       }
+
+      cur_state = transit_state (&err, mctx, cur_state);
+      if (mctx->state_log != NULL)
+       cur_state = merge_state_with_log (&err, mctx, cur_state);
+
+      if (cur_state == NULL)
+       {
+         /* Reached the invalid state or an error.  Try to recover a valid
+            state using the state log, if available and if we have not
+            already found a valid (even if not the longest) match.  */
+         if (BE (err != REG_NOERROR, 0))
+           return -2;
+
+         if (mctx->state_log == NULL
+             || (match && !fl_longest_match)
+             || (cur_state = find_recover_state (&err, mctx)) == NULL)
+           break;
+       }
+
+      if (BE (at_init_state, 0))
+       {
+         if (old_state == cur_state)
+           next_start_idx = next_char_idx;
+         else
+           at_init_state = 0;
+       }
+
+      if (cur_state->halt)
+       {
+         /* Reached a halt state.
+            Check the halt state can satisfy the current context.  */
+         if (!cur_state->has_constraint
+             || check_halt_state_context (mctx, cur_state,
+                                          re_string_cur_idx (&mctx->input)))
+           {
+             /* We found an appropriate halt state.  */
+             match_last = re_string_cur_idx (&mctx->input);
+             match = 1;
+
+             /* We found a match, do not modify match_first below.  */
+             p_match_first = NULL;
+             if (!fl_longest_match)
+               break;
+           }
+       }
+    }
+
+  if (p_match_first)
+    *p_match_first += next_start_idx;
+
+  return match_last;
+}
+
+/* Check NODE match the current context.  */
+
+static int
+internal_function
+check_halt_node_context (const re_dfa_t *dfa, int node, unsigned int context)
+{
+  re_token_type_t type = dfa->nodes[node].type;
+  unsigned int constraint = dfa->nodes[node].constraint;
+  if (type != END_OF_RE)
+    return 0;
+  if (!constraint)
+    return 1;
+  if (NOT_SATISFY_NEXT_CONSTRAINT (constraint, context))
+    return 0;
+  return 1;
+}
+
+/* Check the halt state STATE match the current context.
+   Return 0 if not match, if the node, STATE has, is a halt node and
+   match the context, return the node.  */
+
+static int
+internal_function
+check_halt_state_context (const re_match_context_t *mctx,
+                         const re_dfastate_t *state, int idx)
+{
+  int i;
+  unsigned int context;
+#ifdef DEBUG
+  assert (state->halt);
+#endif
+  context = re_string_context_at (&mctx->input, idx, mctx->eflags);
+  for (i = 0; i < state->nodes.nelem; ++i)
+    if (check_halt_node_context (mctx->dfa, state->nodes.elems[i], context))
+      return state->nodes.elems[i];
+  return 0;
+}
+
+/* Compute the next node to which "NFA" transit from NODE("NFA" is a NFA
+   corresponding to the DFA).
+   Return the destination node, and update EPS_VIA_NODES, return -1 in case
+   of errors.  */
+
+static int
+internal_function
+proceed_next_node (const re_match_context_t *mctx, int nregs, regmatch_t *regs,
+                  int *pidx, int node, re_node_set *eps_via_nodes,
+                  struct re_fail_stack_t *fs)
+{
+  const re_dfa_t *const dfa = mctx->dfa;
+  int i, err;
+  if (IS_EPSILON_NODE (dfa->nodes[node].type))
+    {
+      re_node_set *cur_nodes = &mctx->state_log[*pidx]->nodes;
+      re_node_set *edests = &dfa->edests[node];
+      int dest_node;
+      err = re_node_set_insert (eps_via_nodes, node);
+      if (BE (err < 0, 0))
+       return -2;
+      /* Pick up a valid destination, or return -1 if none is found.  */
+      for (dest_node = -1, i = 0; i < edests->nelem; ++i)
+       {
+         int candidate = edests->elems[i];
+         if (!re_node_set_contains (cur_nodes, candidate))
+           continue;
+         if (dest_node == -1)
+           dest_node = candidate;
+
+         else
+           {
+             /* In order to avoid infinite loop like "(a*)*", return the second
+                epsilon-transition if the first was already considered.  */
+             if (re_node_set_contains (eps_via_nodes, dest_node))
+               return candidate;
+
+             /* Otherwise, push the second epsilon-transition on the fail stack.  */
+             else if (fs != NULL
+                      && push_fail_stack (fs, *pidx, candidate, nregs, regs,
+                                          eps_via_nodes))
+               return -2;
+
+             /* We know we are going to exit.  */
+             break;
+           }
+       }
+      return dest_node;
+    }
+  else
+    {
+      int naccepted = 0;
+      re_token_type_t type = dfa->nodes[node].type;
+
+#ifdef RE_ENABLE_I18N
+      if (dfa->nodes[node].accept_mb)
+       naccepted = check_node_accept_bytes (dfa, node, &mctx->input, *pidx);
+      else
+#endif /* RE_ENABLE_I18N */
+      if (type == OP_BACK_REF)
+       {
+         int subexp_idx = dfa->nodes[node].opr.idx + 1;
+         naccepted = regs[subexp_idx].rm_eo - regs[subexp_idx].rm_so;
+         if (fs != NULL)
+           {
+             if (regs[subexp_idx].rm_so == -1 || regs[subexp_idx].rm_eo == -1)
+               return -1;
+             else if (naccepted)
+               {
+                 char *buf = (char *) re_string_get_buffer (&mctx->input);
+                 if (memcmp (buf + regs[subexp_idx].rm_so, buf + *pidx,
+                             naccepted) != 0)
+                   return -1;
+               }
+           }
+
+         if (naccepted == 0)
+           {
+             int dest_node;
+             err = re_node_set_insert (eps_via_nodes, node);
+             if (BE (err < 0, 0))
+               return -2;
+             dest_node = dfa->edests[node].elems[0];
+             if (re_node_set_contains (&mctx->state_log[*pidx]->nodes,
+                                       dest_node))
+               return dest_node;
+           }
+       }
+
+      if (naccepted != 0
+         || check_node_accept (mctx, dfa->nodes + node, *pidx))
+       {
+         int dest_node = dfa->nexts[node];
+         *pidx = (naccepted == 0) ? *pidx + 1 : *pidx + naccepted;
+         if (fs && (*pidx > mctx->match_last || mctx->state_log[*pidx] == NULL
+                    || !re_node_set_contains (&mctx->state_log[*pidx]->nodes,
+                                              dest_node)))
+           return -1;
+         re_node_set_empty (eps_via_nodes);
+         return dest_node;
+       }
+    }
+  return -1;
+}
+
+static reg_errcode_t
+internal_function
+push_fail_stack (struct re_fail_stack_t *fs, int str_idx, int dest_node,
+                int nregs, regmatch_t *regs, re_node_set *eps_via_nodes)
+{
+  reg_errcode_t err;
+  int num = fs->num++;
+  if (fs->num == fs->alloc)
+    {
+      struct re_fail_stack_ent_t *new_array;
+      new_array = realloc (fs->stack, (sizeof (struct re_fail_stack_ent_t)
+                                      * fs->alloc * 2));
+      if (new_array == NULL)
+       return REG_ESPACE;
+      fs->alloc *= 2;
+      fs->stack = new_array;
+    }
+  fs->stack[num].idx = str_idx;
+  fs->stack[num].node = dest_node;
+  fs->stack[num].regs = re_malloc (regmatch_t, nregs);
+  if (fs->stack[num].regs == NULL)
+    return REG_ESPACE;
+  memcpy (fs->stack[num].regs, regs, sizeof (regmatch_t) * nregs);
+  err = re_node_set_init_copy (&fs->stack[num].eps_via_nodes, eps_via_nodes);
+  return err;
+}
+
+static int
+internal_function
+pop_fail_stack (struct re_fail_stack_t *fs, int *pidx, int nregs,
+               regmatch_t *regs, re_node_set *eps_via_nodes)
+{
+  int num = --fs->num;
+  assert (num >= 0);
+  *pidx = fs->stack[num].idx;
+  memcpy (regs, fs->stack[num].regs, sizeof (regmatch_t) * nregs);
+  re_node_set_free (eps_via_nodes);
+  re_free (fs->stack[num].regs);
+  *eps_via_nodes = fs->stack[num].eps_via_nodes;
+  return fs->stack[num].node;
+}
+
+/* Set the positions where the subexpressions are starts/ends to registers
+   PMATCH.
+   Note: We assume that pmatch[0] is already set, and
+   pmatch[i].rm_so == pmatch[i].rm_eo == -1 for 0 < i < nmatch.  */
+
+static reg_errcode_t
+internal_function
+set_regs (const regex_t *preg, const re_match_context_t *mctx, size_t nmatch,
+         regmatch_t *pmatch, int fl_backtrack)
+{
+  const re_dfa_t *dfa = (const re_dfa_t *) preg->buffer;
+  int idx, cur_node;
+  re_node_set eps_via_nodes;
+  struct re_fail_stack_t *fs;
+  struct re_fail_stack_t fs_body = { 0, 2, NULL };
+  regmatch_t *prev_idx_match;
+  int prev_idx_match_malloced = 0;
+
+#ifdef DEBUG
+  assert (nmatch > 1);
+  assert (mctx->state_log != NULL);
+#endif
+  if (fl_backtrack)
+    {
+      fs = &fs_body;
+      fs->stack = re_malloc (struct re_fail_stack_ent_t, fs->alloc);
+      if (fs->stack == NULL)
+       return REG_ESPACE;
+    }
+  else
+    fs = NULL;
+
+  cur_node = dfa->init_node;
+  re_node_set_init_empty (&eps_via_nodes);
+
+#ifdef HAVE_ALLOCA
+  if (__libc_use_alloca (nmatch * sizeof (regmatch_t)))
+    prev_idx_match = (regmatch_t *) alloca (nmatch * sizeof (regmatch_t));
+  else
+#endif
+    {
+      prev_idx_match = re_malloc (regmatch_t, nmatch);
+      if (prev_idx_match == NULL)
+       {
+         free_fail_stack_return (fs);
+         return REG_ESPACE;
+       }
+      prev_idx_match_malloced = 1;
+    }
+  memcpy (prev_idx_match, pmatch, sizeof (regmatch_t) * nmatch);
+
+  for (idx = pmatch[0].rm_so; idx <= pmatch[0].rm_eo ;)
+    {
+      update_regs (dfa, pmatch, prev_idx_match, cur_node, idx, nmatch);
+
+      if (idx == pmatch[0].rm_eo && cur_node == mctx->last_node)
+       {
+         int reg_idx;
+         if (fs)
+           {
+             for (reg_idx = 0; reg_idx < nmatch; ++reg_idx)
+               if (pmatch[reg_idx].rm_so > -1 && pmatch[reg_idx].rm_eo == -1)
+                 break;
+             if (reg_idx == nmatch)
+               {
+                 re_node_set_free (&eps_via_nodes);
+                 if (prev_idx_match_malloced)
+                   re_free (prev_idx_match);
+                 return free_fail_stack_return (fs);
+               }
+             cur_node = pop_fail_stack (fs, &idx, nmatch, pmatch,
+                                        &eps_via_nodes);
+           }
+         else
+           {
+             re_node_set_free (&eps_via_nodes);
+             if (prev_idx_match_malloced)
+               re_free (prev_idx_match);
+             return REG_NOERROR;
+           }
+       }
+
+      /* Proceed to next node.  */
+      cur_node = proceed_next_node (mctx, nmatch, pmatch, &idx, cur_node,
+                                   &eps_via_nodes, fs);
+
+      if (BE (cur_node < 0, 0))
+       {
+         if (BE (cur_node == -2, 0))
+           {
+             re_node_set_free (&eps_via_nodes);
+             if (prev_idx_match_malloced)
+               re_free (prev_idx_match);
+             free_fail_stack_return (fs);
+             return REG_ESPACE;
+           }
+         if (fs)
+           cur_node = pop_fail_stack (fs, &idx, nmatch, pmatch,
+                                      &eps_via_nodes);
+         else
+           {
+             re_node_set_free (&eps_via_nodes);
+             if (prev_idx_match_malloced)
+               re_free (prev_idx_match);
+             return REG_NOMATCH;
+           }
+       }
+    }
+  re_node_set_free (&eps_via_nodes);
+  if (prev_idx_match_malloced)
+    re_free (prev_idx_match);
+  return free_fail_stack_return (fs);
+}
+
+static reg_errcode_t
+internal_function
+free_fail_stack_return (struct re_fail_stack_t *fs)
+{
+  if (fs)
+    {
+      int fs_idx;
+      for (fs_idx = 0; fs_idx < fs->num; ++fs_idx)
+       {
+         re_node_set_free (&fs->stack[fs_idx].eps_via_nodes);
+         re_free (fs->stack[fs_idx].regs);
+       }
+      re_free (fs->stack);
+    }
+  return REG_NOERROR;
+}
+
+static void
+internal_function
+update_regs (const re_dfa_t *dfa, regmatch_t *pmatch,
+            regmatch_t *prev_idx_match, int cur_node, int cur_idx, int nmatch)
+{
+  int type = dfa->nodes[cur_node].type;
+  if (type == OP_OPEN_SUBEXP)
+    {
+      int reg_num = dfa->nodes[cur_node].opr.idx + 1;
+
+      /* We are at the first node of this sub expression.  */
+      if (reg_num < nmatch)
+       {
+         pmatch[reg_num].rm_so = cur_idx;
+         pmatch[reg_num].rm_eo = -1;
+       }
+    }
+  else if (type == OP_CLOSE_SUBEXP)
+    {
+      int reg_num = dfa->nodes[cur_node].opr.idx + 1;
+      if (reg_num < nmatch)
+       {
+         /* We are at the last node of this sub expression.  */
+         if (pmatch[reg_num].rm_so < cur_idx)
+           {
+             pmatch[reg_num].rm_eo = cur_idx;
+             /* This is a non-empty match or we are not inside an optional
+                subexpression.  Accept this right away.  */
+             memcpy (prev_idx_match, pmatch, sizeof (regmatch_t) * nmatch);
+           }
+         else
+           {
+             if (dfa->nodes[cur_node].opt_subexp
+                 && prev_idx_match[reg_num].rm_so != -1)
+               /* We transited through an empty match for an optional
+                  subexpression, like (a?)*, and this is not the subexp's
+                  first match.  Copy back the old content of the registers
+                  so that matches of an inner subexpression are undone as
+                  well, like in ((a?))*.  */
+               memcpy (pmatch, prev_idx_match, sizeof (regmatch_t) * nmatch);
+             else
+               /* We completed a subexpression, but it may be part of
+                  an optional one, so do not update PREV_IDX_MATCH.  */
+               pmatch[reg_num].rm_eo = cur_idx;
+           }
+       }
+    }
+}
+
+/* This function checks the STATE_LOG from the SCTX->last_str_idx to 0
+   and sift the nodes in each states according to the following rules.
+   Updated state_log will be wrote to STATE_LOG.
+
+   Rules: We throw away the Node `a' in the STATE_LOG[STR_IDX] if...
+     1. When STR_IDX == MATCH_LAST(the last index in the state_log):
+       If `a' isn't the LAST_NODE and `a' can't epsilon transit to
+       the LAST_NODE, we throw away the node `a'.
+     2. When 0 <= STR_IDX < MATCH_LAST and `a' accepts
+       string `s' and transit to `b':
+       i. If 'b' isn't in the STATE_LOG[STR_IDX+strlen('s')], we throw
+          away the node `a'.
+       ii. If 'b' is in the STATE_LOG[STR_IDX+strlen('s')] but 'b' is
+           thrown away, we throw away the node `a'.
+     3. When 0 <= STR_IDX < MATCH_LAST and 'a' epsilon transit to 'b':
+       i. If 'b' isn't in the STATE_LOG[STR_IDX], we throw away the
+          node `a'.
+       ii. If 'b' is in the STATE_LOG[STR_IDX] but 'b' is thrown away,
+           we throw away the node `a'.  */
+
+#define STATE_NODE_CONTAINS(state,node) \
+  ((state) != NULL && re_node_set_contains (&(state)->nodes, node))
+
+static reg_errcode_t
+internal_function
+sift_states_backward (const re_match_context_t *mctx, re_sift_context_t *sctx)
+{
+  reg_errcode_t err;
+  int null_cnt = 0;
+  int str_idx = sctx->last_str_idx;
+  re_node_set cur_dest;
+
+#ifdef DEBUG
+  assert (mctx->state_log != NULL && mctx->state_log[str_idx] != NULL);
+#endif
+
+  /* Build sifted state_log[str_idx].  It has the nodes which can epsilon
+     transit to the last_node and the last_node itself.  */
+  err = re_node_set_init_1 (&cur_dest, sctx->last_node);
+  if (BE (err != REG_NOERROR, 0))
+    return err;
+  err = update_cur_sifted_state (mctx, sctx, str_idx, &cur_dest);
+  if (BE (err != REG_NOERROR, 0))
+    goto free_return;
+
+  /* Then check each states in the state_log.  */
+  while (str_idx > 0)
+    {
+      /* Update counters.  */
+      null_cnt = (sctx->sifted_states[str_idx] == NULL) ? null_cnt + 1 : 0;
+      if (null_cnt > mctx->max_mb_elem_len)
+       {
+         memset (sctx->sifted_states, '\0',
+                 sizeof (re_dfastate_t *) * str_idx);
+         re_node_set_free (&cur_dest);
+         return REG_NOERROR;
+       }
+      re_node_set_empty (&cur_dest);
+      --str_idx;
+
+      if (mctx->state_log[str_idx])
+       {
+         err = build_sifted_states (mctx, sctx, str_idx, &cur_dest);
+         if (BE (err != REG_NOERROR, 0))
+           goto free_return;
+       }
+
+      /* Add all the nodes which satisfy the following conditions:
+        - It can epsilon transit to a node in CUR_DEST.
+        - It is in CUR_SRC.
+        And update state_log.  */
+      err = update_cur_sifted_state (mctx, sctx, str_idx, &cur_dest);
+      if (BE (err != REG_NOERROR, 0))
+       goto free_return;
+    }
+  err = REG_NOERROR;
+ free_return:
+  re_node_set_free (&cur_dest);
+  return err;
+}
+
+static reg_errcode_t
+internal_function
+build_sifted_states (const re_match_context_t *mctx, re_sift_context_t *sctx,
+                    int str_idx, re_node_set *cur_dest)
+{
+  const re_dfa_t *const dfa = mctx->dfa;
+  const re_node_set *cur_src = &mctx->state_log[str_idx]->non_eps_nodes;
+  int i;
+
+  /* Then build the next sifted state.
+     We build the next sifted state on `cur_dest', and update
+     `sifted_states[str_idx]' with `cur_dest'.
+     Note:
+     `cur_dest' is the sifted state from `state_log[str_idx + 1]'.
+     `cur_src' points the node_set of the old `state_log[str_idx]'
+     (with the epsilon nodes pre-filtered out).  */
+  for (i = 0; i < cur_src->nelem; i++)
+    {
+      int prev_node = cur_src->elems[i];
+      int naccepted = 0;
+      int ret;
+
+#ifdef DEBUG
+      re_token_type_t type = dfa->nodes[prev_node].type;
+      assert (!IS_EPSILON_NODE (type));
+#endif
+#ifdef RE_ENABLE_I18N
+      /* If the node may accept `multi byte'.  */
+      if (dfa->nodes[prev_node].accept_mb)
+       naccepted = sift_states_iter_mb (mctx, sctx, prev_node,
+                                        str_idx, sctx->last_str_idx);
+#endif /* RE_ENABLE_I18N */
+
+      /* We don't check backreferences here.
+        See update_cur_sifted_state().  */
+      if (!naccepted
+         && check_node_accept (mctx, dfa->nodes + prev_node, str_idx)
+         && STATE_NODE_CONTAINS (sctx->sifted_states[str_idx + 1],
+                                 dfa->nexts[prev_node]))
+       naccepted = 1;
+
+      if (naccepted == 0)
+       continue;
+
+      if (sctx->limits.nelem)
+       {
+         int to_idx = str_idx + naccepted;
+         if (check_dst_limits (mctx, &sctx->limits,
+                               dfa->nexts[prev_node], to_idx,
+                               prev_node, str_idx))
+           continue;
+       }
+      ret = re_node_set_insert (cur_dest, prev_node);
+      if (BE (ret == -1, 0))
+       return REG_ESPACE;
+    }
+
+  return REG_NOERROR;
+}
+
+/* Helper functions.  */
+
+static reg_errcode_t
+internal_function
+clean_state_log_if_needed (re_match_context_t *mctx, int next_state_log_idx)
+{
+  int top = mctx->state_log_top;
+
+  if (next_state_log_idx >= mctx->input.bufs_len
+      || (next_state_log_idx >= mctx->input.valid_len
+         && mctx->input.valid_len < mctx->input.len))
+    {
+      reg_errcode_t err;
+      err = extend_buffers (mctx);
+      if (BE (err != REG_NOERROR, 0))
+       return err;
+    }
+
+  if (top < next_state_log_idx)
+    {
+      memset (mctx->state_log + top + 1, '\0',
+             sizeof (re_dfastate_t *) * (next_state_log_idx - top));
+      mctx->state_log_top = next_state_log_idx;
+    }
+  return REG_NOERROR;
+}
+
+static reg_errcode_t
+internal_function
+merge_state_array (const re_dfa_t *dfa, re_dfastate_t **dst,
+                  re_dfastate_t **src, int num)
+{
+  int st_idx;
+  reg_errcode_t err;
+  for (st_idx = 0; st_idx < num; ++st_idx)
+    {
+      if (dst[st_idx] == NULL)
+       dst[st_idx] = src[st_idx];
+      else if (src[st_idx] != NULL)
+       {
+         re_node_set merged_set;
+         err = re_node_set_init_union (&merged_set, &dst[st_idx]->nodes,
+                                       &src[st_idx]->nodes);
+         if (BE (err != REG_NOERROR, 0))
+           return err;
+         dst[st_idx] = re_acquire_state (&err, dfa, &merged_set);
+         re_node_set_free (&merged_set);
+         if (BE (err != REG_NOERROR, 0))
+           return err;
+       }
+    }
+  return REG_NOERROR;
+}
+
+static reg_errcode_t
+internal_function
+update_cur_sifted_state (const re_match_context_t *mctx,
+                        re_sift_context_t *sctx, int str_idx,
+                        re_node_set *dest_nodes)
+{
+  const re_dfa_t *const dfa = mctx->dfa;
+  reg_errcode_t err = REG_NOERROR;
+  const re_node_set *candidates;
+  candidates = ((mctx->state_log[str_idx] == NULL) ? NULL
+               : &mctx->state_log[str_idx]->nodes);
+
+  if (dest_nodes->nelem == 0)
+    sctx->sifted_states[str_idx] = NULL;
+  else
+    {
+      if (candidates)
+       {
+         /* At first, add the nodes which can epsilon transit to a node in
+            DEST_NODE.  */
+         err = add_epsilon_src_nodes (dfa, dest_nodes, candidates);
+         if (BE (err != REG_NOERROR, 0))
+           return err;
+
+         /* Then, check the limitations in the current sift_context.  */
+         if (sctx->limits.nelem)
+           {
+             err = check_subexp_limits (dfa, dest_nodes, candidates, &sctx->limits,
+                                        mctx->bkref_ents, str_idx);
+             if (BE (err != REG_NOERROR, 0))
+               return err;
+           }
+       }
+
+      sctx->sifted_states[str_idx] = re_acquire_state (&err, dfa, dest_nodes);
+      if (BE (err != REG_NOERROR, 0))
+       return err;
+    }
+
+  if (candidates && mctx->state_log[str_idx]->has_backref)
+    {
+      err = sift_states_bkref (mctx, sctx, str_idx, candidates);
+      if (BE (err != REG_NOERROR, 0))
+       return err;
+    }
+  return REG_NOERROR;
+}
+
+static reg_errcode_t
+internal_function
+add_epsilon_src_nodes (const re_dfa_t *dfa, re_node_set *dest_nodes,
+                      const re_node_set *candidates)
+{
+  reg_errcode_t err = REG_NOERROR;
+  int i;
+
+  re_dfastate_t *state = re_acquire_state (&err, dfa, dest_nodes);
+  if (BE (err != REG_NOERROR, 0))
+    return err;
+
+  if (!state->inveclosure.alloc)
+    {
+      err = re_node_set_alloc (&state->inveclosure, dest_nodes->nelem);
+      if (BE (err != REG_NOERROR, 0))
+       return REG_ESPACE;
+      for (i = 0; i < dest_nodes->nelem; i++)
+       {
+         err = re_node_set_merge (&state->inveclosure,
+                                  dfa->inveclosures + dest_nodes->elems[i]);
+         if (BE (err != REG_NOERROR, 0))
+           return REG_ESPACE;
+       }
+    }
+  return re_node_set_add_intersect (dest_nodes, candidates,
+                                   &state->inveclosure);
+}
+
+static reg_errcode_t
+internal_function
+sub_epsilon_src_nodes (const re_dfa_t *dfa, int node, re_node_set *dest_nodes,
+                      const re_node_set *candidates)
+{
+    int ecl_idx;
+    reg_errcode_t err;
+    re_node_set *inv_eclosure = dfa->inveclosures + node;
+    re_node_set except_nodes;
+    re_node_set_init_empty (&except_nodes);
+    for (ecl_idx = 0; ecl_idx < inv_eclosure->nelem; ++ecl_idx)
+      {
+       int cur_node = inv_eclosure->elems[ecl_idx];
+       if (cur_node == node)
+         continue;
+       if (IS_EPSILON_NODE (dfa->nodes[cur_node].type))
+         {
+           int edst1 = dfa->edests[cur_node].elems[0];
+           int edst2 = ((dfa->edests[cur_node].nelem > 1)
+                        ? dfa->edests[cur_node].elems[1] : -1);
+           if ((!re_node_set_contains (inv_eclosure, edst1)
+                && re_node_set_contains (dest_nodes, edst1))
+               || (edst2 > 0
+                   && !re_node_set_contains (inv_eclosure, edst2)
+                   && re_node_set_contains (dest_nodes, edst2)))
+             {
+               err = re_node_set_add_intersect (&except_nodes, candidates,
+                                                dfa->inveclosures + cur_node);
+               if (BE (err != REG_NOERROR, 0))
+                 {
+                   re_node_set_free (&except_nodes);
+                   return err;
+                 }
+             }
+         }
+      }
+    for (ecl_idx = 0; ecl_idx < inv_eclosure->nelem; ++ecl_idx)
+      {
+       int cur_node = inv_eclosure->elems[ecl_idx];
+       if (!re_node_set_contains (&except_nodes, cur_node))
+         {
+           int idx = re_node_set_contains (dest_nodes, cur_node) - 1;
+           re_node_set_remove_at (dest_nodes, idx);
+         }
+      }
+    re_node_set_free (&except_nodes);
+    return REG_NOERROR;
+}
+
+static int
+internal_function
+check_dst_limits (const re_match_context_t *mctx, re_node_set *limits,
+                 int dst_node, int dst_idx, int src_node, int src_idx)
+{
+  const re_dfa_t *const dfa = mctx->dfa;
+  int lim_idx, src_pos, dst_pos;
+
+  int dst_bkref_idx = search_cur_bkref_entry (mctx, dst_idx);
+  int src_bkref_idx = search_cur_bkref_entry (mctx, src_idx);
+  for (lim_idx = 0; lim_idx < limits->nelem; ++lim_idx)
+    {
+      int subexp_idx;
+      struct re_backref_cache_entry *ent;
+      ent = mctx->bkref_ents + limits->elems[lim_idx];
+      subexp_idx = dfa->nodes[ent->node].opr.idx;
+
+      dst_pos = check_dst_limits_calc_pos (mctx, limits->elems[lim_idx],
+                                          subexp_idx, dst_node, dst_idx,
+                                          dst_bkref_idx);
+      src_pos = check_dst_limits_calc_pos (mctx, limits->elems[lim_idx],
+                                          subexp_idx, src_node, src_idx,
+                                          src_bkref_idx);
+
+      /* In case of:
+        <src> <dst> ( <subexp> )
+        ( <subexp> ) <src> <dst>
+        ( <subexp1> <src> <subexp2> <dst> <subexp3> )  */
+      if (src_pos == dst_pos)
+       continue; /* This is unrelated limitation.  */
+      else
+       return 1;
+    }
+  return 0;
+}
+
+static int
+internal_function
+check_dst_limits_calc_pos_1 (const re_match_context_t *mctx, int boundaries,
+                            int subexp_idx, int from_node, int bkref_idx)
+{
+  const re_dfa_t *const dfa = mctx->dfa;
+  const re_node_set *eclosures = dfa->eclosures + from_node;
+  int node_idx;
+
+  /* Else, we are on the boundary: examine the nodes on the epsilon
+     closure.  */
+  for (node_idx = 0; node_idx < eclosures->nelem; ++node_idx)
+    {
+      int node = eclosures->elems[node_idx];
+      switch (dfa->nodes[node].type)
+       {
+       case OP_BACK_REF:
+         if (bkref_idx != -1)
+           {
+             struct re_backref_cache_entry *ent = mctx->bkref_ents + bkref_idx;
+             do
+               {
+                 int dst, cpos;
+
+                 if (ent->node != node)
+                   continue;
+
+                 if (subexp_idx < BITSET_WORD_BITS
+                     && !(ent->eps_reachable_subexps_map
+                          & ((bitset_word_t) 1 << subexp_idx)))
+                   continue;
+
+                 /* Recurse trying to reach the OP_OPEN_SUBEXP and
+                    OP_CLOSE_SUBEXP cases below.  But, if the
+                    destination node is the same node as the source
+                    node, don't recurse because it would cause an
+                    infinite loop: a regex that exhibits this behavior
+                    is ()\1*\1*  */
+                 dst = dfa->edests[node].elems[0];
+                 if (dst == from_node)
+                   {
+                     if (boundaries & 1)
+                       return -1;
+                     else /* if (boundaries & 2) */
+                       return 0;
+                   }
+
+                 cpos =
+                   check_dst_limits_calc_pos_1 (mctx, boundaries, subexp_idx,
+                                                dst, bkref_idx);
+                 if (cpos == -1 /* && (boundaries & 1) */)
+                   return -1;
+                 if (cpos == 0 && (boundaries & 2))
+                   return 0;
+
+                 if (subexp_idx < BITSET_WORD_BITS)
+                   ent->eps_reachable_subexps_map
+                     &= ~((bitset_word_t) 1 << subexp_idx);
+               }
+             while (ent++->more);
+           }
+         break;
+
+       case OP_OPEN_SUBEXP:
+         if ((boundaries & 1) && subexp_idx == dfa->nodes[node].opr.idx)
+           return -1;
+         break;
+
+       case OP_CLOSE_SUBEXP:
+         if ((boundaries & 2) && subexp_idx == dfa->nodes[node].opr.idx)
+           return 0;
+         break;
+
+       default:
+           break;
+       }
+    }
+
+  return (boundaries & 2) ? 1 : 0;
+}
+
+static int
+internal_function
+check_dst_limits_calc_pos (const re_match_context_t *mctx, int limit,
+                          int subexp_idx, int from_node, int str_idx,
+                          int bkref_idx)
+{
+  struct re_backref_cache_entry *lim = mctx->bkref_ents + limit;
+  int boundaries;
+
+  /* If we are outside the range of the subexpression, return -1 or 1.  */
+  if (str_idx < lim->subexp_from)
+    return -1;
+
+  if (lim->subexp_to < str_idx)
+    return 1;
+
+  /* If we are within the subexpression, return 0.  */
+  boundaries = (str_idx == lim->subexp_from);
+  boundaries |= (str_idx == lim->subexp_to) << 1;
+  if (boundaries == 0)
+    return 0;
+
+  /* Else, examine epsilon closure.  */
+  return check_dst_limits_calc_pos_1 (mctx, boundaries, subexp_idx,
+                                     from_node, bkref_idx);
+}
+
+/* Check the limitations of sub expressions LIMITS, and remove the nodes
+   which are against limitations from DEST_NODES. */
+
+static reg_errcode_t
+internal_function
+check_subexp_limits (const re_dfa_t *dfa, re_node_set *dest_nodes,
+                    const re_node_set *candidates, re_node_set *limits,
+                    struct re_backref_cache_entry *bkref_ents, int str_idx)
+{
+  reg_errcode_t err;
+  int node_idx, lim_idx;
+
+  for (lim_idx = 0; lim_idx < limits->nelem; ++lim_idx)
+    {
+      int subexp_idx;
+      struct re_backref_cache_entry *ent;
+      ent = bkref_ents + limits->elems[lim_idx];
+
+      if (str_idx <= ent->subexp_from || ent->str_idx < str_idx)
+       continue; /* This is unrelated limitation.  */
+
+      subexp_idx = dfa->nodes[ent->node].opr.idx;
+      if (ent->subexp_to == str_idx)
+       {
+         int ops_node = -1;
+         int cls_node = -1;
+         for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx)
+           {
+             int node = dest_nodes->elems[node_idx];
+             re_token_type_t type = dfa->nodes[node].type;
+             if (type == OP_OPEN_SUBEXP
+                 && subexp_idx == dfa->nodes[node].opr.idx)
+               ops_node = node;
+             else if (type == OP_CLOSE_SUBEXP
+                      && subexp_idx == dfa->nodes[node].opr.idx)
+               cls_node = node;
+           }
+
+         /* Check the limitation of the open subexpression.  */
+         /* Note that (ent->subexp_to = str_idx != ent->subexp_from).  */
+         if (ops_node >= 0)
+           {
+             err = sub_epsilon_src_nodes (dfa, ops_node, dest_nodes,
+                                          candidates);
+             if (BE (err != REG_NOERROR, 0))
+               return err;
+           }
+
+         /* Check the limitation of the close subexpression.  */
+         if (cls_node >= 0)
+           for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx)
+             {
+               int node = dest_nodes->elems[node_idx];
+               if (!re_node_set_contains (dfa->inveclosures + node,
+                                          cls_node)
+                   && !re_node_set_contains (dfa->eclosures + node,
+                                             cls_node))
+                 {
+                   /* It is against this limitation.
+                      Remove it form the current sifted state.  */
+                   err = sub_epsilon_src_nodes (dfa, node, dest_nodes,
+                                                candidates);
+                   if (BE (err != REG_NOERROR, 0))
+                     return err;
+                   --node_idx;
+                 }
+             }
+       }
+      else /* (ent->subexp_to != str_idx)  */
+       {
+         for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx)
+           {
+             int node = dest_nodes->elems[node_idx];
+             re_token_type_t type = dfa->nodes[node].type;
+             if (type == OP_CLOSE_SUBEXP || type == OP_OPEN_SUBEXP)
+               {
+                 if (subexp_idx != dfa->nodes[node].opr.idx)
+                   continue;
+                 /* It is against this limitation.
+                    Remove it form the current sifted state.  */
+                 err = sub_epsilon_src_nodes (dfa, node, dest_nodes,
+                                              candidates);
+                 if (BE (err != REG_NOERROR, 0))
+                   return err;
+               }
+           }
+       }
+    }
+  return REG_NOERROR;
+}
+
+static reg_errcode_t
+internal_function
+sift_states_bkref (const re_match_context_t *mctx, re_sift_context_t *sctx,
+                  int str_idx, const re_node_set *candidates)
+{
+  const re_dfa_t *const dfa = mctx->dfa;
+  reg_errcode_t err;
+  int node_idx, node;
+  re_sift_context_t local_sctx;
+  int first_idx = search_cur_bkref_entry (mctx, str_idx);
+
+  if (first_idx == -1)
+    return REG_NOERROR;
+
+  local_sctx.sifted_states = NULL; /* Mark that it hasn't been initialized.  */
+
+  for (node_idx = 0; node_idx < candidates->nelem; ++node_idx)
+    {
+      int enabled_idx;
+      re_token_type_t type;
+      struct re_backref_cache_entry *entry;
+      node = candidates->elems[node_idx];
+      type = dfa->nodes[node].type;
+      /* Avoid infinite loop for the REs like "()\1+".  */
+      if (node == sctx->last_node && str_idx == sctx->last_str_idx)
+       continue;
+      if (type != OP_BACK_REF)
+       continue;
+
+      entry = mctx->bkref_ents + first_idx;
+      enabled_idx = first_idx;
+      do
+       {
+         int subexp_len;
+         int to_idx;
+         int dst_node;
+         int ret;
+         re_dfastate_t *cur_state;
+
+         if (entry->node != node)
+           continue;
+         subexp_len = entry->subexp_to - entry->subexp_from;
+         to_idx = str_idx + subexp_len;
+         dst_node = (subexp_len ? dfa->nexts[node]
+                     : dfa->edests[node].elems[0]);
+
+         if (to_idx > sctx->last_str_idx
+             || sctx->sifted_states[to_idx] == NULL
+             || !STATE_NODE_CONTAINS (sctx->sifted_states[to_idx], dst_node)
+             || check_dst_limits (mctx, &sctx->limits, node,
+                                  str_idx, dst_node, to_idx))
+           continue;
+
+         if (local_sctx.sifted_states == NULL)
+           {
+             local_sctx = *sctx;
+             err = re_node_set_init_copy (&local_sctx.limits, &sctx->limits);
+             if (BE (err != REG_NOERROR, 0))
+               goto free_return;
+           }
+         local_sctx.last_node = node;
+         local_sctx.last_str_idx = str_idx;
+         ret = re_node_set_insert (&local_sctx.limits, enabled_idx);
+         if (BE (ret < 0, 0))
+           {
+             err = REG_ESPACE;
+             goto free_return;
+           }
+         cur_state = local_sctx.sifted_states[str_idx];
+         err = sift_states_backward (mctx, &local_sctx);
+         if (BE (err != REG_NOERROR, 0))
+           goto free_return;
+         if (sctx->limited_states != NULL)
+           {
+             err = merge_state_array (dfa, sctx->limited_states,
+                                      local_sctx.sifted_states,
+                                      str_idx + 1);
+             if (BE (err != REG_NOERROR, 0))
+               goto free_return;
+           }
+         local_sctx.sifted_states[str_idx] = cur_state;
+         re_node_set_remove (&local_sctx.limits, enabled_idx);
+
+         /* mctx->bkref_ents may have changed, reload the pointer.  */
+         entry = mctx->bkref_ents + enabled_idx;
+       }
+      while (enabled_idx++, entry++->more);
+    }
+  err = REG_NOERROR;
+ free_return:
+  if (local_sctx.sifted_states != NULL)
+    {
+      re_node_set_free (&local_sctx.limits);
+    }
+
+  return err;
+}
+
+
+#ifdef RE_ENABLE_I18N
+static int
+internal_function
+sift_states_iter_mb (const re_match_context_t *mctx, re_sift_context_t *sctx,
+                    int node_idx, int str_idx, int max_str_idx)
+{
+  const re_dfa_t *const dfa = mctx->dfa;
+  int naccepted;
+  /* Check the node can accept `multi byte'.  */
+  naccepted = check_node_accept_bytes (dfa, node_idx, &mctx->input, str_idx);
+  if (naccepted > 0 && str_idx + naccepted <= max_str_idx &&
+      !STATE_NODE_CONTAINS (sctx->sifted_states[str_idx + naccepted],
+                           dfa->nexts[node_idx]))
+    /* The node can't accept the `multi byte', or the
+       destination was already thrown away, then the node
+       could't accept the current input `multi byte'.   */
+    naccepted = 0;
+  /* Otherwise, it is sure that the node could accept
+     `naccepted' bytes input.  */
+  return naccepted;
+}
+#endif /* RE_ENABLE_I18N */
+
+\f
+/* Functions for state transition.  */
+
+/* Return the next state to which the current state STATE will transit by
+   accepting the current input byte, and update STATE_LOG if necessary.
+   If STATE can accept a multibyte char/collating element/back reference
+   update the destination of STATE_LOG.  */
+
+static re_dfastate_t *
+internal_function
+transit_state (reg_errcode_t *err, re_match_context_t *mctx,
+              re_dfastate_t *state)
+{
+  re_dfastate_t **trtable;
+  unsigned char ch;
+
+#ifdef RE_ENABLE_I18N
+  /* If the current state can accept multibyte.  */
+  if (BE (state->accept_mb, 0))
+    {
+      *err = transit_state_mb (mctx, state);
+      if (BE (*err != REG_NOERROR, 0))
+       return NULL;
+    }
+#endif /* RE_ENABLE_I18N */
+
+  /* Then decide the next state with the single byte.  */
+#if 0
+  if (0)
+    /* don't use transition table  */
+    return transit_state_sb (err, mctx, state);
+#endif
+
+  /* Use transition table  */
+  ch = re_string_fetch_byte (&mctx->input);
+  for (;;)
+    {
+      trtable = state->trtable;
+      if (BE (trtable != NULL, 1))
+       return trtable[ch];
+
+      trtable = state->word_trtable;
+      if (BE (trtable != NULL, 1))
+       {
+         unsigned int context;
+         context
+           = re_string_context_at (&mctx->input,
+                                   re_string_cur_idx (&mctx->input) - 1,
+                                   mctx->eflags);
+         if (IS_WORD_CONTEXT (context))
+           return trtable[ch + SBC_MAX];
+         else
+           return trtable[ch];
+       }
+
+      if (!build_trtable (mctx->dfa, state))
+       {
+         *err = REG_ESPACE;
+         return NULL;
+       }
+
+      /* Retry, we now have a transition table.  */
+    }
+}
+
+/* Update the state_log if we need */
+re_dfastate_t *
+internal_function
+merge_state_with_log (reg_errcode_t *err, re_match_context_t *mctx,
+                     re_dfastate_t *next_state)
+{
+  const re_dfa_t *const dfa = mctx->dfa;
+  int cur_idx = re_string_cur_idx (&mctx->input);
+
+  if (cur_idx > mctx->state_log_top)
+    {
+      mctx->state_log[cur_idx] = next_state;
+      mctx->state_log_top = cur_idx;
+    }
+  else if (mctx->state_log[cur_idx] == 0)
+    {
+      mctx->state_log[cur_idx] = next_state;
+    }
+  else
+    {
+      re_dfastate_t *pstate;
+      unsigned int context;
+      re_node_set next_nodes, *log_nodes, *table_nodes = NULL;
+      /* If (state_log[cur_idx] != 0), it implies that cur_idx is
+        the destination of a multibyte char/collating element/
+        back reference.  Then the next state is the union set of
+        these destinations and the results of the transition table.  */
+      pstate = mctx->state_log[cur_idx];
+      log_nodes = pstate->entrance_nodes;
+      if (next_state != NULL)
+       {
+         table_nodes = next_state->entrance_nodes;
+         *err = re_node_set_init_union (&next_nodes, table_nodes,
+                                            log_nodes);
+         if (BE (*err != REG_NOERROR, 0))
+           return NULL;
+       }
+      else
+       next_nodes = *log_nodes;
+      /* Note: We already add the nodes of the initial state,
+        then we don't need to add them here.  */
+
+      context = re_string_context_at (&mctx->input,
+                                     re_string_cur_idx (&mctx->input) - 1,
+                                     mctx->eflags);
+      next_state = mctx->state_log[cur_idx]
+       = re_acquire_state_context (err, dfa, &next_nodes, context);
+      /* We don't need to check errors here, since the return value of
+        this function is next_state and ERR is already set.  */
+
+      if (table_nodes != NULL)
+       re_node_set_free (&next_nodes);
+    }
+
+  if (BE (dfa->nbackref, 0) && next_state != NULL)
+    {
+      /* Check OP_OPEN_SUBEXP in the current state in case that we use them
+        later.  We must check them here, since the back references in the
+        next state might use them.  */
+      *err = check_subexp_matching_top (mctx, &next_state->nodes,
+                                       cur_idx);
+      if (BE (*err != REG_NOERROR, 0))
+       return NULL;
+
+      /* If the next state has back references.  */
+      if (next_state->has_backref)
+       {
+         *err = transit_state_bkref (mctx, &next_state->nodes);
+         if (BE (*err != REG_NOERROR, 0))
+           return NULL;
+         next_state = mctx->state_log[cur_idx];
+       }
+    }
+
+  return next_state;
+}
+
+/* Skip bytes in the input that correspond to part of a
+   multi-byte match, then look in the log for a state
+   from which to restart matching.  */
+re_dfastate_t *
+internal_function
+find_recover_state (reg_errcode_t *err, re_match_context_t *mctx)
+{
+  re_dfastate_t *cur_state;
+  do
+    {
+      int max = mctx->state_log_top;
+      int cur_str_idx = re_string_cur_idx (&mctx->input);
+
+      do
+       {
+         if (++cur_str_idx > max)
+           return NULL;
+         re_string_skip_bytes (&mctx->input, 1);
+       }
+      while (mctx->state_log[cur_str_idx] == NULL);
+
+      cur_state = merge_state_with_log (err, mctx, NULL);
+    }
+  while (*err == REG_NOERROR && cur_state == NULL);
+  return cur_state;
+}
+
+/* Helper functions for transit_state.  */
+
+/* From the node set CUR_NODES, pick up the nodes whose types are
+   OP_OPEN_SUBEXP and which have corresponding back references in the regular
+   expression. And register them to use them later for evaluating the
+   correspoding back references.  */
+
+static reg_errcode_t
+internal_function
+check_subexp_matching_top (re_match_context_t *mctx, re_node_set *cur_nodes,
+                          int str_idx)
+{
+  const re_dfa_t *const dfa = mctx->dfa;
+  int node_idx;
+  reg_errcode_t err;
+
+  /* TODO: This isn't efficient.
+          Because there might be more than one nodes whose types are
+          OP_OPEN_SUBEXP and whose index is SUBEXP_IDX, we must check all
+          nodes.
+          E.g. RE: (a){2}  */
+  for (node_idx = 0; node_idx < cur_nodes->nelem; ++node_idx)
+    {
+      int node = cur_nodes->elems[node_idx];
+      if (dfa->nodes[node].type == OP_OPEN_SUBEXP
+         && dfa->nodes[node].opr.idx < BITSET_WORD_BITS
+         && (dfa->used_bkref_map
+             & ((bitset_word_t) 1 << dfa->nodes[node].opr.idx)))
+       {
+         err = match_ctx_add_subtop (mctx, node, str_idx);
+         if (BE (err != REG_NOERROR, 0))
+           return err;
+       }
+    }
+  return REG_NOERROR;
+}
+
+#if 0
+/* Return the next state to which the current state STATE will transit by
+   accepting the current input byte.  */
+
+static re_dfastate_t *
+transit_state_sb (reg_errcode_t *err, re_match_context_t *mctx,
+                 re_dfastate_t *state)
+{
+  const re_dfa_t *const dfa = mctx->dfa;
+  re_node_set next_nodes;
+  re_dfastate_t *next_state;
+  int node_cnt, cur_str_idx = re_string_cur_idx (&mctx->input);
+  unsigned int context;
+
+  *err = re_node_set_alloc (&next_nodes, state->nodes.nelem + 1);
+  if (BE (*err != REG_NOERROR, 0))
+    return NULL;
+  for (node_cnt = 0; node_cnt < state->nodes.nelem; ++node_cnt)
+    {
+      int cur_node = state->nodes.elems[node_cnt];
+      if (check_node_accept (mctx, dfa->nodes + cur_node, cur_str_idx))
+       {
+         *err = re_node_set_merge (&next_nodes,
+                                   dfa->eclosures + dfa->nexts[cur_node]);
+         if (BE (*err != REG_NOERROR, 0))
+           {
+             re_node_set_free (&next_nodes);
+             return NULL;
+           }
+       }
+    }
+  context = re_string_context_at (&mctx->input, cur_str_idx, mctx->eflags);
+  next_state = re_acquire_state_context (err, dfa, &next_nodes, context);
+  /* We don't need to check errors here, since the return value of
+     this function is next_state and ERR is already set.  */
+
+  re_node_set_free (&next_nodes);
+  re_string_skip_bytes (&mctx->input, 1);
+  return next_state;
+}
+#endif
+
+#ifdef RE_ENABLE_I18N
+static reg_errcode_t
+internal_function
+transit_state_mb (re_match_context_t *mctx, re_dfastate_t *pstate)
+{
+  const re_dfa_t *const dfa = mctx->dfa;
+  reg_errcode_t err;
+  int i;
+
+  for (i = 0; i < pstate->nodes.nelem; ++i)
+    {
+      re_node_set dest_nodes, *new_nodes;
+      int cur_node_idx = pstate->nodes.elems[i];
+      int naccepted, dest_idx;
+      unsigned int context;
+      re_dfastate_t *dest_state;
+
+      if (!dfa->nodes[cur_node_idx].accept_mb)
+       continue;
+
+      if (dfa->nodes[cur_node_idx].constraint)
+       {
+         context = re_string_context_at (&mctx->input,
+                                         re_string_cur_idx (&mctx->input),
+                                         mctx->eflags);
+         if (NOT_SATISFY_NEXT_CONSTRAINT (dfa->nodes[cur_node_idx].constraint,
+                                          context))
+           continue;
+       }
+
+      /* How many bytes the node can accept?  */
+      naccepted = check_node_accept_bytes (dfa, cur_node_idx, &mctx->input,
+                                          re_string_cur_idx (&mctx->input));
+      if (naccepted == 0)
+       continue;
+
+      /* The node can accepts `naccepted' bytes.  */
+      dest_idx = re_string_cur_idx (&mctx->input) + naccepted;
+      mctx->max_mb_elem_len = ((mctx->max_mb_elem_len < naccepted) ? naccepted
+                              : mctx->max_mb_elem_len);
+      err = clean_state_log_if_needed (mctx, dest_idx);
+      if (BE (err != REG_NOERROR, 0))
+       return err;
+#ifdef DEBUG
+      assert (dfa->nexts[cur_node_idx] != -1);
+#endif
+      new_nodes = dfa->eclosures + dfa->nexts[cur_node_idx];
+
+      dest_state = mctx->state_log[dest_idx];
+      if (dest_state == NULL)
+       dest_nodes = *new_nodes;
+      else
+       {
+         err = re_node_set_init_union (&dest_nodes,
+                                       dest_state->entrance_nodes, new_nodes);
+         if (BE (err != REG_NOERROR, 0))
+           return err;
+       }
+      context = re_string_context_at (&mctx->input, dest_idx - 1,
+                                     mctx->eflags);
+      mctx->state_log[dest_idx]
+       = re_acquire_state_context (&err, dfa, &dest_nodes, context);
+      if (dest_state != NULL)
+       re_node_set_free (&dest_nodes);
+      if (BE (mctx->state_log[dest_idx] == NULL && err != REG_NOERROR, 0))
+       return err;
+    }
+  return REG_NOERROR;
+}
+#endif /* RE_ENABLE_I18N */
+
+static reg_errcode_t
+internal_function
+transit_state_bkref (re_match_context_t *mctx, const re_node_set *nodes)
+{
+  const re_dfa_t *const dfa = mctx->dfa;
+  reg_errcode_t err;
+  int i;
+  int cur_str_idx = re_string_cur_idx (&mctx->input);
+
+  for (i = 0; i < nodes->nelem; ++i)
+    {
+      int dest_str_idx, prev_nelem, bkc_idx;
+      int node_idx = nodes->elems[i];
+      unsigned int context;
+      const re_token_t *node = dfa->nodes + node_idx;
+      re_node_set *new_dest_nodes;
+
+      /* Check whether `node' is a backreference or not.  */
+      if (node->type != OP_BACK_REF)
+       continue;
+
+      if (node->constraint)
+       {
+         context = re_string_context_at (&mctx->input, cur_str_idx,
+                                         mctx->eflags);
+         if (NOT_SATISFY_NEXT_CONSTRAINT (node->constraint, context))
+           continue;
+       }
+
+      /* `node' is a backreference.
+        Check the substring which the substring matched.  */
+      bkc_idx = mctx->nbkref_ents;
+      err = get_subexp (mctx, node_idx, cur_str_idx);
+      if (BE (err != REG_NOERROR, 0))
+       goto free_return;
+
+      /* And add the epsilon closures (which is `new_dest_nodes') of
+        the backreference to appropriate state_log.  */
+#ifdef DEBUG
+      assert (dfa->nexts[node_idx] != -1);
+#endif
+      for (; bkc_idx < mctx->nbkref_ents; ++bkc_idx)
+       {
+         int subexp_len;
+         re_dfastate_t *dest_state;
+         struct re_backref_cache_entry *bkref_ent;
+         bkref_ent = mctx->bkref_ents + bkc_idx;
+         if (bkref_ent->node != node_idx || bkref_ent->str_idx != cur_str_idx)
+           continue;
+         subexp_len = bkref_ent->subexp_to - bkref_ent->subexp_from;
+         new_dest_nodes = (subexp_len == 0
+                           ? dfa->eclosures + dfa->edests[node_idx].elems[0]
+                           : dfa->eclosures + dfa->nexts[node_idx]);
+         dest_str_idx = (cur_str_idx + bkref_ent->subexp_to
+                         - bkref_ent->subexp_from);
+         context = re_string_context_at (&mctx->input, dest_str_idx - 1,
+                                         mctx->eflags);
+         dest_state = mctx->state_log[dest_str_idx];
+         prev_nelem = ((mctx->state_log[cur_str_idx] == NULL) ? 0
+                       : mctx->state_log[cur_str_idx]->nodes.nelem);
+         /* Add `new_dest_node' to state_log.  */
+         if (dest_state == NULL)
+           {
+             mctx->state_log[dest_str_idx]
+               = re_acquire_state_context (&err, dfa, new_dest_nodes,
+                                           context);
+             if (BE (mctx->state_log[dest_str_idx] == NULL
+                     && err != REG_NOERROR, 0))
+               goto free_return;
+           }
+         else
+           {
+             re_node_set dest_nodes;
+             err = re_node_set_init_union (&dest_nodes,
+                                           dest_state->entrance_nodes,
+                                           new_dest_nodes);
+             if (BE (err != REG_NOERROR, 0))
+               {
+                 re_node_set_free (&dest_nodes);
+                 goto free_return;
+               }
+             mctx->state_log[dest_str_idx]
+               = re_acquire_state_context (&err, dfa, &dest_nodes, context);
+             re_node_set_free (&dest_nodes);
+             if (BE (mctx->state_log[dest_str_idx] == NULL
+                     && err != REG_NOERROR, 0))
+               goto free_return;
+           }
+         /* We need to check recursively if the backreference can epsilon
+            transit.  */
+         if (subexp_len == 0
+             && mctx->state_log[cur_str_idx]->nodes.nelem > prev_nelem)
+           {
+             err = check_subexp_matching_top (mctx, new_dest_nodes,
+                                              cur_str_idx);
+             if (BE (err != REG_NOERROR, 0))
+               goto free_return;
+             err = transit_state_bkref (mctx, new_dest_nodes);
+             if (BE (err != REG_NOERROR, 0))
+               goto free_return;
+           }
+       }
+    }
+  err = REG_NOERROR;
+ free_return:
+  return err;
+}
+
+/* Enumerate all the candidates which the backreference BKREF_NODE can match
+   at BKREF_STR_IDX, and register them by match_ctx_add_entry().
+   Note that we might collect inappropriate candidates here.
+   However, the cost of checking them strictly here is too high, then we
+   delay these checking for prune_impossible_nodes().  */
+
+static reg_errcode_t
+internal_function
+get_subexp (re_match_context_t *mctx, int bkref_node, int bkref_str_idx)
+{
+  const re_dfa_t *const dfa = mctx->dfa;
+  int subexp_num, sub_top_idx;
+  const char *buf = (const char *) re_string_get_buffer (&mctx->input);
+  /* Return if we have already checked BKREF_NODE at BKREF_STR_IDX.  */
+  int cache_idx = search_cur_bkref_entry (mctx, bkref_str_idx);
+  if (cache_idx != -1)
+    {
+      const struct re_backref_cache_entry *entry
+       = mctx->bkref_ents + cache_idx;
+      do
+       if (entry->node == bkref_node)
+         return REG_NOERROR; /* We already checked it.  */
+      while (entry++->more);
+    }
+
+  subexp_num = dfa->nodes[bkref_node].opr.idx;
+
+  /* For each sub expression  */
+  for (sub_top_idx = 0; sub_top_idx < mctx->nsub_tops; ++sub_top_idx)
+    {
+      reg_errcode_t err;
+      re_sub_match_top_t *sub_top = mctx->sub_tops[sub_top_idx];
+      re_sub_match_last_t *sub_last;
+      int sub_last_idx, sl_str, bkref_str_off;
+
+      if (dfa->nodes[sub_top->node].opr.idx != subexp_num)
+       continue; /* It isn't related.  */
+
+      sl_str = sub_top->str_idx;
+      bkref_str_off = bkref_str_idx;
+      /* At first, check the last node of sub expressions we already
+        evaluated.  */
+      for (sub_last_idx = 0; sub_last_idx < sub_top->nlasts; ++sub_last_idx)
+       {
+         int sl_str_diff;
+         sub_last = sub_top->lasts[sub_last_idx];
+         sl_str_diff = sub_last->str_idx - sl_str;
+         /* The matched string by the sub expression match with the substring
+            at the back reference?  */
+         if (sl_str_diff > 0)
+           {
+             if (BE (bkref_str_off + sl_str_diff > mctx->input.valid_len, 0))
+               {
+                 /* Not enough chars for a successful match.  */
+                 if (bkref_str_off + sl_str_diff > mctx->input.len)
+                   break;
+
+                 err = clean_state_log_if_needed (mctx,
+                                                  bkref_str_off
+                                                  + sl_str_diff);
+                 if (BE (err != REG_NOERROR, 0))
+                   return err;
+                 buf = (const char *) re_string_get_buffer (&mctx->input);
+               }
+             if (memcmp (buf + bkref_str_off, buf + sl_str, sl_str_diff) != 0)
+               /* We don't need to search this sub expression any more.  */
+               break;
+           }
+         bkref_str_off += sl_str_diff;
+         sl_str += sl_str_diff;
+         err = get_subexp_sub (mctx, sub_top, sub_last, bkref_node,
+                               bkref_str_idx);
+
+         /* Reload buf, since the preceding call might have reallocated
+            the buffer.  */
+         buf = (const char *) re_string_get_buffer (&mctx->input);
+
+         if (err == REG_NOMATCH)
+           continue;
+         if (BE (err != REG_NOERROR, 0))
+           return err;
+       }
+
+      if (sub_last_idx < sub_top->nlasts)
+       continue;
+      if (sub_last_idx > 0)
+       ++sl_str;
+      /* Then, search for the other last nodes of the sub expression.  */
+      for (; sl_str <= bkref_str_idx; ++sl_str)
+       {
+         int cls_node, sl_str_off;
+         const re_node_set *nodes;
+         sl_str_off = sl_str - sub_top->str_idx;
+         /* The matched string by the sub expression match with the substring
+            at the back reference?  */
+         if (sl_str_off > 0)
+           {
+             if (BE (bkref_str_off >= mctx->input.valid_len, 0))
+               {
+                 /* If we are at the end of the input, we cannot match.  */
+                 if (bkref_str_off >= mctx->input.len)
+                   break;
+
+                 err = extend_buffers (mctx);
+                 if (BE (err != REG_NOERROR, 0))
+                   return err;
+
+                 buf = (const char *) re_string_get_buffer (&mctx->input);
+               }
+             if (buf [bkref_str_off++] != buf[sl_str - 1])
+               break; /* We don't need to search this sub expression
+                         any more.  */
+           }
+         if (mctx->state_log[sl_str] == NULL)
+           continue;
+         /* Does this state have a ')' of the sub expression?  */
+         nodes = &mctx->state_log[sl_str]->nodes;
+         cls_node = find_subexp_node (dfa, nodes, subexp_num,
+                                      OP_CLOSE_SUBEXP);
+         if (cls_node == -1)
+           continue; /* No.  */
+         if (sub_top->path == NULL)
+           {
+             sub_top->path = calloc (sizeof (state_array_t),
+                                     sl_str - sub_top->str_idx + 1);
+             if (sub_top->path == NULL)
+               return REG_ESPACE;
+           }
+         /* Can the OP_OPEN_SUBEXP node arrive the OP_CLOSE_SUBEXP node
+            in the current context?  */
+         err = check_arrival (mctx, sub_top->path, sub_top->node,
+                              sub_top->str_idx, cls_node, sl_str,
+                              OP_CLOSE_SUBEXP);
+         if (err == REG_NOMATCH)
+             continue;
+         if (BE (err != REG_NOERROR, 0))
+             return err;
+         sub_last = match_ctx_add_sublast (sub_top, cls_node, sl_str);
+         if (BE (sub_last == NULL, 0))
+           return REG_ESPACE;
+         err = get_subexp_sub (mctx, sub_top, sub_last, bkref_node,
+                               bkref_str_idx);
+         if (err == REG_NOMATCH)
+           continue;
+       }
+    }
+  return REG_NOERROR;
+}
+
+/* Helper functions for get_subexp().  */
+
+/* Check SUB_LAST can arrive to the back reference BKREF_NODE at BKREF_STR.
+   If it can arrive, register the sub expression expressed with SUB_TOP
+   and SUB_LAST.  */
+
+static reg_errcode_t
+internal_function
+get_subexp_sub (re_match_context_t *mctx, const re_sub_match_top_t *sub_top,
+               re_sub_match_last_t *sub_last, int bkref_node, int bkref_str)
+{
+  reg_errcode_t err;
+  int to_idx;
+  /* Can the subexpression arrive the back reference?  */
+  err = check_arrival (mctx, &sub_last->path, sub_last->node,
+                      sub_last->str_idx, bkref_node, bkref_str,
+                      OP_OPEN_SUBEXP);
+  if (err != REG_NOERROR)
+    return err;
+  err = match_ctx_add_entry (mctx, bkref_node, bkref_str, sub_top->str_idx,
+                            sub_last->str_idx);
+  if (BE (err != REG_NOERROR, 0))
+    return err;
+  to_idx = bkref_str + sub_last->str_idx - sub_top->str_idx;
+  return clean_state_log_if_needed (mctx, to_idx);
+}
+
+/* Find the first node which is '(' or ')' and whose index is SUBEXP_IDX.
+   Search '(' if FL_OPEN, or search ')' otherwise.
+   TODO: This function isn't efficient...
+        Because there might be more than one nodes whose types are
+        OP_OPEN_SUBEXP and whose index is SUBEXP_IDX, we must check all
+        nodes.
+        E.g. RE: (a){2}  */
+
+static int
+internal_function
+find_subexp_node (const re_dfa_t *dfa, const re_node_set *nodes,
+                 int subexp_idx, int type)
+{
+  int cls_idx;
+  for (cls_idx = 0; cls_idx < nodes->nelem; ++cls_idx)
+    {
+      int cls_node = nodes->elems[cls_idx];
+      const re_token_t *node = dfa->nodes + cls_node;
+      if (node->type == type
+         && node->opr.idx == subexp_idx)
+       return cls_node;
+    }
+  return -1;
+}
+
+/* Check whether the node TOP_NODE at TOP_STR can arrive to the node
+   LAST_NODE at LAST_STR.  We record the path onto PATH since it will be
+   heavily reused.
+   Return REG_NOERROR if it can arrive, or REG_NOMATCH otherwise.  */
+
+static reg_errcode_t
+internal_function
+check_arrival (re_match_context_t *mctx, state_array_t *path, int top_node,
+              int top_str, int last_node, int last_str, int type)
+{
+  const re_dfa_t *const dfa = mctx->dfa;
+  reg_errcode_t err = REG_NOERROR;
+  int subexp_num, backup_cur_idx, str_idx, null_cnt;
+  re_dfastate_t *cur_state = NULL;
+  re_node_set *cur_nodes, next_nodes;
+  re_dfastate_t **backup_state_log;
+  unsigned int context;
+
+  subexp_num = dfa->nodes[top_node].opr.idx;
+  /* Extend the buffer if we need.  */
+  if (BE (path->alloc < last_str + mctx->max_mb_elem_len + 1, 0))
+    {
+      re_dfastate_t **new_array;
+      int old_alloc = path->alloc;
+      path->alloc += last_str + mctx->max_mb_elem_len + 1;
+      new_array = re_realloc (path->array, re_dfastate_t *, path->alloc);
+      if (BE (new_array == NULL, 0))
+       {
+         path->alloc = old_alloc;
+         return REG_ESPACE;
+       }
+      path->array = new_array;
+      memset (new_array + old_alloc, '\0',
+             sizeof (re_dfastate_t *) * (path->alloc - old_alloc));
+    }
+
+  str_idx = path->next_idx ? path->next_idx : top_str;
+
+  /* Temporary modify MCTX.  */
+  backup_state_log = mctx->state_log;
+  backup_cur_idx = mctx->input.cur_idx;
+  mctx->state_log = path->array;
+  mctx->input.cur_idx = str_idx;
+
+  /* Setup initial node set.  */
+  context = re_string_context_at (&mctx->input, str_idx - 1, mctx->eflags);
+  if (str_idx == top_str)
+    {
+      err = re_node_set_init_1 (&next_nodes, top_node);
+      if (BE (err != REG_NOERROR, 0))
+       return err;
+      err = check_arrival_expand_ecl (dfa, &next_nodes, subexp_num, type);
+      if (BE (err != REG_NOERROR, 0))
+       {
+         re_node_set_free (&next_nodes);
+         return err;
+       }
+    }
+  else
+    {
+      cur_state = mctx->state_log[str_idx];
+      if (cur_state && cur_state->has_backref)
+       {
+         err = re_node_set_init_copy (&next_nodes, &cur_state->nodes);
+         if (BE (err != REG_NOERROR, 0))
+           return err;
+       }
+      else
+       re_node_set_init_empty (&next_nodes);
+    }
+  if (str_idx == top_str || (cur_state && cur_state->has_backref))
+    {
+      if (next_nodes.nelem)
+       {
+         err = expand_bkref_cache (mctx, &next_nodes, str_idx,
+                                   subexp_num, type);
+         if (BE (err != REG_NOERROR, 0))
+           {
+             re_node_set_free (&next_nodes);
+             return err;
+           }
+       }
+      cur_state = re_acquire_state_context (&err, dfa, &next_nodes, context);
+      if (BE (cur_state == NULL && err != REG_NOERROR, 0))
+       {
+         re_node_set_free (&next_nodes);
+         return err;
+       }
+      mctx->state_log[str_idx] = cur_state;
+    }
+
+  for (null_cnt = 0; str_idx < last_str && null_cnt <= mctx->max_mb_elem_len;)
+    {
+      re_node_set_empty (&next_nodes);
+      if (mctx->state_log[str_idx + 1])
+       {
+         err = re_node_set_merge (&next_nodes,
+                                  &mctx->state_log[str_idx + 1]->nodes);
+         if (BE (err != REG_NOERROR, 0))
+           {
+             re_node_set_free (&next_nodes);
+             return err;
+           }
+       }
+      if (cur_state)
+       {
+         err = check_arrival_add_next_nodes (mctx, str_idx,
+                                             &cur_state->non_eps_nodes,
+                                             &next_nodes);
+         if (BE (err != REG_NOERROR, 0))
+           {
+             re_node_set_free (&next_nodes);
+             return err;
+           }
+       }
+      ++str_idx;
+      if (next_nodes.nelem)
+       {
+         err = check_arrival_expand_ecl (dfa, &next_nodes, subexp_num, type);
+         if (BE (err != REG_NOERROR, 0))
+           {
+             re_node_set_free (&next_nodes);
+             return err;
+           }
+         err = expand_bkref_cache (mctx, &next_nodes, str_idx,
+                                   subexp_num, type);
+         if (BE (err != REG_NOERROR, 0))
+           {
+             re_node_set_free (&next_nodes);
+             return err;
+           }
+       }
+      context = re_string_context_at (&mctx->input, str_idx - 1, mctx->eflags);
+      cur_state = re_acquire_state_context (&err, dfa, &next_nodes, context);
+      if (BE (cur_state == NULL && err != REG_NOERROR, 0))
+       {
+         re_node_set_free (&next_nodes);
+         return err;
+       }
+      mctx->state_log[str_idx] = cur_state;
+      null_cnt = cur_state == NULL ? null_cnt + 1 : 0;
+    }
+  re_node_set_free (&next_nodes);
+  cur_nodes = (mctx->state_log[last_str] == NULL ? NULL
+              : &mctx->state_log[last_str]->nodes);
+  path->next_idx = str_idx;
+
+  /* Fix MCTX.  */
+  mctx->state_log = backup_state_log;
+  mctx->input.cur_idx = backup_cur_idx;
+
+  /* Then check the current node set has the node LAST_NODE.  */
+  if (cur_nodes != NULL && re_node_set_contains (cur_nodes, last_node))
+    return REG_NOERROR;
+
+  return REG_NOMATCH;
+}
+
+/* Helper functions for check_arrival.  */
+
+/* Calculate the destination nodes of CUR_NODES at STR_IDX, and append them
+   to NEXT_NODES.
+   TODO: This function is similar to the functions transit_state*(),
+        however this function has many additional works.
+        Can't we unify them?  */
+
+static reg_errcode_t
+internal_function
+check_arrival_add_next_nodes (re_match_context_t *mctx, int str_idx,
+                             re_node_set *cur_nodes, re_node_set *next_nodes)
+{
+  const re_dfa_t *const dfa = mctx->dfa;
+  int result;
+  int cur_idx;
+#ifdef RE_ENABLE_I18N
+  reg_errcode_t err = REG_NOERROR;
+#endif
+  re_node_set union_set;
+  re_node_set_init_empty (&union_set);
+  for (cur_idx = 0; cur_idx < cur_nodes->nelem; ++cur_idx)
+    {
+      int naccepted = 0;
+      int cur_node = cur_nodes->elems[cur_idx];
+#ifdef DEBUG
+      re_token_type_t type = dfa->nodes[cur_node].type;
+      assert (!IS_EPSILON_NODE (type));
+#endif
+#ifdef RE_ENABLE_I18N
+      /* If the node may accept `multi byte'.  */
+      if (dfa->nodes[cur_node].accept_mb)
+       {
+         naccepted = check_node_accept_bytes (dfa, cur_node, &mctx->input,
+                                              str_idx);
+         if (naccepted > 1)
+           {
+             re_dfastate_t *dest_state;
+             int next_node = dfa->nexts[cur_node];
+             int next_idx = str_idx + naccepted;
+             dest_state = mctx->state_log[next_idx];
+             re_node_set_empty (&union_set);
+             if (dest_state)
+               {
+                 err = re_node_set_merge (&union_set, &dest_state->nodes);
+                 if (BE (err != REG_NOERROR, 0))
+                   {
+                     re_node_set_free (&union_set);
+                     return err;
+                   }
+               }
+             result = re_node_set_insert (&union_set, next_node);
+             if (BE (result < 0, 0))
+               {
+                 re_node_set_free (&union_set);
+                 return REG_ESPACE;
+               }
+             mctx->state_log[next_idx] = re_acquire_state (&err, dfa,
+                                                           &union_set);
+             if (BE (mctx->state_log[next_idx] == NULL
+                     && err != REG_NOERROR, 0))
+               {
+                 re_node_set_free (&union_set);
+                 return err;
+               }
+           }
+       }
+#endif /* RE_ENABLE_I18N */
+      if (naccepted
+         || check_node_accept (mctx, dfa->nodes + cur_node, str_idx))
+       {
+         result = re_node_set_insert (next_nodes, dfa->nexts[cur_node]);
+         if (BE (result < 0, 0))
+           {
+             re_node_set_free (&union_set);
+             return REG_ESPACE;
+           }
+       }
+    }
+  re_node_set_free (&union_set);
+  return REG_NOERROR;
+}
+
+/* For all the nodes in CUR_NODES, add the epsilon closures of them to
+   CUR_NODES, however exclude the nodes which are:
+    - inside the sub expression whose number is EX_SUBEXP, if FL_OPEN.
+    - out of the sub expression whose number is EX_SUBEXP, if !FL_OPEN.
+*/
+
+static reg_errcode_t
+internal_function
+check_arrival_expand_ecl (const re_dfa_t *dfa, re_node_set *cur_nodes,
+                         int ex_subexp, int type)
+{
+  reg_errcode_t err;
+  int idx, outside_node;
+  re_node_set new_nodes;
+#ifdef DEBUG
+  assert (cur_nodes->nelem);
+#endif
+  err = re_node_set_alloc (&new_nodes, cur_nodes->nelem);
+  if (BE (err != REG_NOERROR, 0))
+    return err;
+  /* Create a new node set NEW_NODES with the nodes which are epsilon
+     closures of the node in CUR_NODES.  */
+
+  for (idx = 0; idx < cur_nodes->nelem; ++idx)
+    {
+      int cur_node = cur_nodes->elems[idx];
+      const re_node_set *eclosure = dfa->eclosures + cur_node;
+      outside_node = find_subexp_node (dfa, eclosure, ex_subexp, type);
+      if (outside_node == -1)
+       {
+         /* There are no problematic nodes, just merge them.  */
+         err = re_node_set_merge (&new_nodes, eclosure);
+         if (BE (err != REG_NOERROR, 0))
+           {
+             re_node_set_free (&new_nodes);
+             return err;
+           }
+       }
+      else
+       {
+         /* There are problematic nodes, re-calculate incrementally.  */
+         err = check_arrival_expand_ecl_sub (dfa, &new_nodes, cur_node,
+                                             ex_subexp, type);
+         if (BE (err != REG_NOERROR, 0))
+           {
+             re_node_set_free (&new_nodes);
+             return err;
+           }
+       }
+    }
+  re_node_set_free (cur_nodes);
+  *cur_nodes = new_nodes;
+  return REG_NOERROR;
+}
+
+/* Helper function for check_arrival_expand_ecl.
+   Check incrementally the epsilon closure of TARGET, and if it isn't
+   problematic append it to DST_NODES.  */
+
+static reg_errcode_t
+internal_function
+check_arrival_expand_ecl_sub (const re_dfa_t *dfa, re_node_set *dst_nodes,
+                             int target, int ex_subexp, int type)
+{
+  int cur_node;
+  for (cur_node = target; !re_node_set_contains (dst_nodes, cur_node);)
+    {
+      int err;
+
+      if (dfa->nodes[cur_node].type == type
+         && dfa->nodes[cur_node].opr.idx == ex_subexp)
+       {
+         if (type == OP_CLOSE_SUBEXP)
+           {
+             err = re_node_set_insert (dst_nodes, cur_node);
+             if (BE (err == -1, 0))
+               return REG_ESPACE;
+           }
+         break;
+       }
+      err = re_node_set_insert (dst_nodes, cur_node);
+      if (BE (err == -1, 0))
+       return REG_ESPACE;
+      if (dfa->edests[cur_node].nelem == 0)
+       break;
+      if (dfa->edests[cur_node].nelem == 2)
+       {
+         err = check_arrival_expand_ecl_sub (dfa, dst_nodes,
+                                             dfa->edests[cur_node].elems[1],
+                                             ex_subexp, type);
+         if (BE (err != REG_NOERROR, 0))
+           return err;
+       }
+      cur_node = dfa->edests[cur_node].elems[0];
+    }
+  return REG_NOERROR;
+}
+
+
+/* For all the back references in the current state, calculate the
+   destination of the back references by the appropriate entry
+   in MCTX->BKREF_ENTS.  */
+
+static reg_errcode_t
+internal_function
+expand_bkref_cache (re_match_context_t *mctx, re_node_set *cur_nodes,
+                   int cur_str, int subexp_num, int type)
+{
+  const re_dfa_t *const dfa = mctx->dfa;
+  reg_errcode_t err;
+  int cache_idx_start = search_cur_bkref_entry (mctx, cur_str);
+  struct re_backref_cache_entry *ent;
+
+  if (cache_idx_start == -1)
+    return REG_NOERROR;
+
+ restart:
+  ent = mctx->bkref_ents + cache_idx_start;
+  do
+    {
+      int to_idx, next_node;
+
+      /* Is this entry ENT is appropriate?  */
+      if (!re_node_set_contains (cur_nodes, ent->node))
+       continue; /* No.  */
+
+      to_idx = cur_str + ent->subexp_to - ent->subexp_from;
+      /* Calculate the destination of the back reference, and append it
+        to MCTX->STATE_LOG.  */
+      if (to_idx == cur_str)
+       {
+         /* The backreference did epsilon transit, we must re-check all the
+            node in the current state.  */
+         re_node_set new_dests;
+         reg_errcode_t err2, err3;
+         next_node = dfa->edests[ent->node].elems[0];
+         if (re_node_set_contains (cur_nodes, next_node))
+           continue;
+         err = re_node_set_init_1 (&new_dests, next_node);
+         err2 = check_arrival_expand_ecl (dfa, &new_dests, subexp_num, type);
+         err3 = re_node_set_merge (cur_nodes, &new_dests);
+         re_node_set_free (&new_dests);
+         if (BE (err != REG_NOERROR || err2 != REG_NOERROR
+                 || err3 != REG_NOERROR, 0))
+           {
+             err = (err != REG_NOERROR ? err
+                    : (err2 != REG_NOERROR ? err2 : err3));
+             return err;
+           }
+         /* TODO: It is still inefficient...  */
+         goto restart;
+       }
+      else
+       {
+         re_node_set union_set;
+         next_node = dfa->nexts[ent->node];
+         if (mctx->state_log[to_idx])
+           {
+             int ret;
+             if (re_node_set_contains (&mctx->state_log[to_idx]->nodes,
+                                       next_node))
+               continue;
+             err = re_node_set_init_copy (&union_set,
+                                          &mctx->state_log[to_idx]->nodes);
+             ret = re_node_set_insert (&union_set, next_node);
+             if (BE (err != REG_NOERROR || ret < 0, 0))
+               {
+                 re_node_set_free (&union_set);
+                 err = err != REG_NOERROR ? err : REG_ESPACE;
+                 return err;
+               }
+           }
+         else
+           {
+             err = re_node_set_init_1 (&union_set, next_node);
+             if (BE (err != REG_NOERROR, 0))
+               return err;
+           }
+         mctx->state_log[to_idx] = re_acquire_state (&err, dfa, &union_set);
+         re_node_set_free (&union_set);
+         if (BE (mctx->state_log[to_idx] == NULL
+                 && err != REG_NOERROR, 0))
+           return err;
+       }
+    }
+  while (ent++->more);
+  return REG_NOERROR;
+}
+
+/* Build transition table for the state.
+   Return 1 if succeeded, otherwise return NULL.  */
+
+static int
+internal_function
+build_trtable (const re_dfa_t *dfa, re_dfastate_t *state)
+{
+  reg_errcode_t err;
+  int i, j, ch, need_word_trtable = 0;
+  bitset_word_t elem, mask;
+  bool dests_node_malloced = false;
+  bool dest_states_malloced = false;
+  int ndests; /* Number of the destination states from `state'.  */
+  re_dfastate_t **trtable;
+  re_dfastate_t **dest_states = NULL, **dest_states_word, **dest_states_nl;
+  re_node_set follows, *dests_node;
+  bitset_t *dests_ch;
+  bitset_t acceptable;
+
+  struct dests_alloc
+  {
+    re_node_set dests_node[SBC_MAX];
+    bitset_t dests_ch[SBC_MAX];
+  } *dests_alloc;
+
+  /* We build DFA states which corresponds to the destination nodes
+     from `state'.  `dests_node[i]' represents the nodes which i-th
+     destination state contains, and `dests_ch[i]' represents the
+     characters which i-th destination state accepts.  */
+#ifdef HAVE_ALLOCA
+  if (__libc_use_alloca (sizeof (struct dests_alloc)))
+    dests_alloc = (struct dests_alloc *) alloca (sizeof (struct dests_alloc));
+  else
+#endif
+    {
+      dests_alloc = re_malloc (struct dests_alloc, 1);
+      if (BE (dests_alloc == NULL, 0))
+       return 0;
+      dests_node_malloced = true;
+    }
+  dests_node = dests_alloc->dests_node;
+  dests_ch = dests_alloc->dests_ch;
+
+  /* Initialize transiton table.  */
+  state->word_trtable = state->trtable = NULL;
+
+  /* At first, group all nodes belonging to `state' into several
+     destinations.  */
+  ndests = group_nodes_into_DFAstates (dfa, state, dests_node, dests_ch);
+  if (BE (ndests <= 0, 0))
+    {
+      if (dests_node_malloced)
+       free (dests_alloc);
+      /* Return 0 in case of an error, 1 otherwise.  */
+      if (ndests == 0)
+       {
+         state->trtable = (re_dfastate_t **)
+           calloc (sizeof (re_dfastate_t *), SBC_MAX);
+         return 1;
+       }
+      return 0;
+    }
+
+  err = re_node_set_alloc (&follows, ndests + 1);
+  if (BE (err != REG_NOERROR, 0))
+    goto out_free;
+
+  /* Avoid arithmetic overflow in size calculation.  */
+  if (BE ((((SIZE_MAX - (sizeof (re_node_set) + sizeof (bitset_t)) * SBC_MAX)
+           / (3 * sizeof (re_dfastate_t *)))
+          < ndests),
+         0))
+    goto out_free;
+
+#ifdef HAVE_ALLOCA
+  if (__libc_use_alloca ((sizeof (re_node_set) + sizeof (bitset_t)) * SBC_MAX
+                        + ndests * 3 * sizeof (re_dfastate_t *)))
+    dest_states = (re_dfastate_t **)
+      alloca (ndests * 3 * sizeof (re_dfastate_t *));
+  else
+#endif
+    {
+      dest_states = (re_dfastate_t **)
+       malloc (ndests * 3 * sizeof (re_dfastate_t *));
+      if (BE (dest_states == NULL, 0))
+       {
+out_free:
+         if (dest_states_malloced)
+           free (dest_states);
+         re_node_set_free (&follows);
+         for (i = 0; i < ndests; ++i)
+           re_node_set_free (dests_node + i);
+         if (dests_node_malloced)
+           free (dests_alloc);
+         return 0;
+       }
+      dest_states_malloced = true;
+    }
+  dest_states_word = dest_states + ndests;
+  dest_states_nl = dest_states_word + ndests;
+  bitset_empty (acceptable);
+
+  /* Then build the states for all destinations.  */
+  for (i = 0; i < ndests; ++i)
+    {
+      int next_node;
+      re_node_set_empty (&follows);
+      /* Merge the follows of this destination states.  */
+      for (j = 0; j < dests_node[i].nelem; ++j)
+       {
+         next_node = dfa->nexts[dests_node[i].elems[j]];
+         if (next_node != -1)
+           {
+             err = re_node_set_merge (&follows, dfa->eclosures + next_node);
+             if (BE (err != REG_NOERROR, 0))
+               goto out_free;
+           }
+       }
+      dest_states[i] = re_acquire_state_context (&err, dfa, &follows, 0);
+      if (BE (dest_states[i] == NULL && err != REG_NOERROR, 0))
+       goto out_free;
+      /* If the new state has context constraint,
+        build appropriate states for these contexts.  */
+      if (dest_states[i]->has_constraint)
+       {
+         dest_states_word[i] = re_acquire_state_context (&err, dfa, &follows,
+                                                         CONTEXT_WORD);
+         if (BE (dest_states_word[i] == NULL && err != REG_NOERROR, 0))
+           goto out_free;
+
+         if (dest_states[i] != dest_states_word[i] && dfa->mb_cur_max > 1)
+           need_word_trtable = 1;
+
+         dest_states_nl[i] = re_acquire_state_context (&err, dfa, &follows,
+                                                       CONTEXT_NEWLINE);
+         if (BE (dest_states_nl[i] == NULL && err != REG_NOERROR, 0))
+           goto out_free;
+       }
+      else
+       {
+         dest_states_word[i] = dest_states[i];
+         dest_states_nl[i] = dest_states[i];
+       }
+      bitset_merge (acceptable, dests_ch[i]);
+    }
+
+  if (!BE (need_word_trtable, 0))
+    {
+      /* We don't care about whether the following character is a word
+        character, or we are in a single-byte character set so we can
+        discern by looking at the character code: allocate a
+        256-entry transition table.  */
+      trtable = state->trtable =
+       (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), SBC_MAX);
+      if (BE (trtable == NULL, 0))
+       goto out_free;
+
+      /* For all characters ch...:  */
+      for (i = 0; i < BITSET_WORDS; ++i)
+       for (ch = i * BITSET_WORD_BITS, elem = acceptable[i], mask = 1;
+            elem;
+            mask <<= 1, elem >>= 1, ++ch)
+         if (BE (elem & 1, 0))
+           {
+             /* There must be exactly one destination which accepts
+                character ch.  See group_nodes_into_DFAstates.  */
+             for (j = 0; (dests_ch[j][i] & mask) == 0; ++j)
+               ;
+
+             /* j-th destination accepts the word character ch.  */
+             if (dfa->word_char[i] & mask)
+               trtable[ch] = dest_states_word[j];
+             else
+               trtable[ch] = dest_states[j];
+           }
+    }
+  else
+    {
+      /* We care about whether the following character is a word
+        character, and we are in a multi-byte character set: discern
+        by looking at the character code: build two 256-entry
+        transition tables, one starting at trtable[0] and one
+        starting at trtable[SBC_MAX].  */
+      trtable = state->word_trtable =
+       (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), 2 * SBC_MAX);
+      if (BE (trtable == NULL, 0))
+       goto out_free;
+
+      /* For all characters ch...:  */
+      for (i = 0; i < BITSET_WORDS; ++i)
+       for (ch = i * BITSET_WORD_BITS, elem = acceptable[i], mask = 1;
+            elem;
+            mask <<= 1, elem >>= 1, ++ch)
+         if (BE (elem & 1, 0))
+           {
+             /* There must be exactly one destination which accepts
+                character ch.  See group_nodes_into_DFAstates.  */
+             for (j = 0; (dests_ch[j][i] & mask) == 0; ++j)
+               ;
+
+             /* j-th destination accepts the word character ch.  */
+             trtable[ch] = dest_states[j];
+             trtable[ch + SBC_MAX] = dest_states_word[j];
+           }
+    }
+
+  /* new line */
+  if (bitset_contain (acceptable, NEWLINE_CHAR))
+    {
+      /* The current state accepts newline character.  */
+      for (j = 0; j < ndests; ++j)
+       if (bitset_contain (dests_ch[j], NEWLINE_CHAR))
+         {
+           /* k-th destination accepts newline character.  */
+           trtable[NEWLINE_CHAR] = dest_states_nl[j];
+           if (need_word_trtable)
+             trtable[NEWLINE_CHAR + SBC_MAX] = dest_states_nl[j];
+           /* There must be only one destination which accepts
+              newline.  See group_nodes_into_DFAstates.  */
+           break;
+         }
+    }
+
+  if (dest_states_malloced)
+    free (dest_states);
+
+  re_node_set_free (&follows);
+  for (i = 0; i < ndests; ++i)
+    re_node_set_free (dests_node + i);
+
+  if (dests_node_malloced)
+    free (dests_alloc);
+
+  return 1;
+}
+
+/* Group all nodes belonging to STATE into several destinations.
+   Then for all destinations, set the nodes belonging to the destination
+   to DESTS_NODE[i] and set the characters accepted by the destination
+   to DEST_CH[i].  This function return the number of destinations.  */
+
+static int
+internal_function
+group_nodes_into_DFAstates (const re_dfa_t *dfa, const re_dfastate_t *state,
+                           re_node_set *dests_node, bitset_t *dests_ch)
+{
+  reg_errcode_t err;
+  int result;
+  int i, j, k;
+  int ndests; /* Number of the destinations from `state'.  */
+  bitset_t accepts; /* Characters a node can accept.  */
+  const re_node_set *cur_nodes = &state->nodes;
+  bitset_empty (accepts);
+  ndests = 0;
+
+  /* For all the nodes belonging to `state',  */
+  for (i = 0; i < cur_nodes->nelem; ++i)
+    {
+      re_token_t *node = &dfa->nodes[cur_nodes->elems[i]];
+      re_token_type_t type = node->type;
+      unsigned int constraint = node->constraint;
+
+      /* Enumerate all single byte character this node can accept.  */
+      if (type == CHARACTER)
+       bitset_set (accepts, node->opr.c);
+      else if (type == SIMPLE_BRACKET)
+       {
+         bitset_merge (accepts, node->opr.sbcset);
+       }
+      else if (type == OP_PERIOD)
+       {
+#ifdef RE_ENABLE_I18N
+         if (dfa->mb_cur_max > 1)
+           bitset_merge (accepts, dfa->sb_char);
+         else
+#endif
+           bitset_set_all (accepts);
+         if (!(dfa->syntax & RE_DOT_NEWLINE))
+           bitset_clear (accepts, '\n');
+         if (dfa->syntax & RE_DOT_NOT_NULL)
+           bitset_clear (accepts, '\0');
+       }
+#ifdef RE_ENABLE_I18N
+      else if (type == OP_UTF8_PERIOD)
+       {
+         memset (accepts, '\xff', sizeof (bitset_t) / 2);
+         if (!(dfa->syntax & RE_DOT_NEWLINE))
+           bitset_clear (accepts, '\n');
+         if (dfa->syntax & RE_DOT_NOT_NULL)
+           bitset_clear (accepts, '\0');
+       }
+#endif
+      else
+       continue;
+
+      /* Check the `accepts' and sift the characters which are not
+        match it the context.  */
+      if (constraint)
+       {
+         if (constraint & NEXT_NEWLINE_CONSTRAINT)
+           {
+             bool accepts_newline = bitset_contain (accepts, NEWLINE_CHAR);
+             bitset_empty (accepts);
+             if (accepts_newline)
+               bitset_set (accepts, NEWLINE_CHAR);
+             else
+               continue;
+           }
+         if (constraint & NEXT_ENDBUF_CONSTRAINT)
+           {
+             bitset_empty (accepts);
+             continue;
+           }
+
+         if (constraint & NEXT_WORD_CONSTRAINT)
+           {
+             bitset_word_t any_set = 0;
+             if (type == CHARACTER && !node->word_char)
+               {
+                 bitset_empty (accepts);
+                 continue;
+               }
+#ifdef RE_ENABLE_I18N
+             if (dfa->mb_cur_max > 1)
+               for (j = 0; j < BITSET_WORDS; ++j)
+                 any_set |= (accepts[j] &= (dfa->word_char[j] | ~dfa->sb_char[j]));
+             else
+#endif
+               for (j = 0; j < BITSET_WORDS; ++j)
+                 any_set |= (accepts[j] &= dfa->word_char[j]);
+             if (!any_set)
+               continue;
+           }
+         if (constraint & NEXT_NOTWORD_CONSTRAINT)
+           {
+             bitset_word_t any_set = 0;
+             if (type == CHARACTER && node->word_char)
+               {
+                 bitset_empty (accepts);
+                 continue;
+               }
+#ifdef RE_ENABLE_I18N
+             if (dfa->mb_cur_max > 1)
+               for (j = 0; j < BITSET_WORDS; ++j)
+                 any_set |= (accepts[j] &= ~(dfa->word_char[j] & dfa->sb_char[j]));
+             else
+#endif
+               for (j = 0; j < BITSET_WORDS; ++j)
+                 any_set |= (accepts[j] &= ~dfa->word_char[j]);
+             if (!any_set)
+               continue;
+           }
+       }
+
+      /* Then divide `accepts' into DFA states, or create a new
+        state.  Above, we make sure that accepts is not empty.  */
+      for (j = 0; j < ndests; ++j)
+       {
+         bitset_t intersec; /* Intersection sets, see below.  */
+         bitset_t remains;
+         /* Flags, see below.  */
+         bitset_word_t has_intersec, not_subset, not_consumed;
+
+         /* Optimization, skip if this state doesn't accept the character.  */
+         if (type == CHARACTER && !bitset_contain (dests_ch[j], node->opr.c))
+           continue;
+
+         /* Enumerate the intersection set of this state and `accepts'.  */
+         has_intersec = 0;
+         for (k = 0; k < BITSET_WORDS; ++k)
+           has_intersec |= intersec[k] = accepts[k] & dests_ch[j][k];
+         /* And skip if the intersection set is empty.  */
+         if (!has_intersec)
+           continue;
+
+         /* Then check if this state is a subset of `accepts'.  */
+         not_subset = not_consumed = 0;
+         for (k = 0; k < BITSET_WORDS; ++k)
+           {
+             not_subset |= remains[k] = ~accepts[k] & dests_ch[j][k];
+             not_consumed |= accepts[k] = accepts[k] & ~dests_ch[j][k];
+           }
+
+         /* If this state isn't a subset of `accepts', create a
+            new group state, which has the `remains'. */
+         if (not_subset)
+           {
+             bitset_copy (dests_ch[ndests], remains);
+             bitset_copy (dests_ch[j], intersec);
+             err = re_node_set_init_copy (dests_node + ndests, &dests_node[j]);
+             if (BE (err != REG_NOERROR, 0))
+               goto error_return;
+             ++ndests;
+           }
+
+         /* Put the position in the current group. */
+         result = re_node_set_insert (&dests_node[j], cur_nodes->elems[i]);
+         if (BE (result < 0, 0))
+           goto error_return;
+
+         /* If all characters are consumed, go to next node. */
+         if (!not_consumed)
+           break;
+       }
+      /* Some characters remain, create a new group. */
+      if (j == ndests)
+       {
+         bitset_copy (dests_ch[ndests], accepts);
+         err = re_node_set_init_1 (dests_node + ndests, cur_nodes->elems[i]);
+         if (BE (err != REG_NOERROR, 0))
+           goto error_return;
+         ++ndests;
+         bitset_empty (accepts);
+       }
+    }
+  return ndests;
+ error_return:
+  for (j = 0; j < ndests; ++j)
+    re_node_set_free (dests_node + j);
+  return -1;
+}
+
+#ifdef RE_ENABLE_I18N
+/* Check how many bytes the node `dfa->nodes[node_idx]' accepts.
+   Return the number of the bytes the node accepts.
+   STR_IDX is the current index of the input string.
+
+   This function handles the nodes which can accept one character, or
+   one collating element like '.', '[a-z]', opposite to the other nodes
+   can only accept one byte.  */
+
+static int
+internal_function
+check_node_accept_bytes (const re_dfa_t *dfa, int node_idx,
+                        const re_string_t *input, int str_idx)
+{
+  const re_token_t *node = dfa->nodes + node_idx;
+  int char_len, elem_len;
+  int i;
+  wint_t wc;
+
+  if (BE (node->type == OP_UTF8_PERIOD, 0))
+    {
+      unsigned char c = re_string_byte_at (input, str_idx), d;
+      if (BE (c < 0xc2, 1))
+       return 0;
+
+      if (str_idx + 2 > input->len)
+       return 0;
+
+      d = re_string_byte_at (input, str_idx + 1);
+      if (c < 0xe0)
+       return (d < 0x80 || d > 0xbf) ? 0 : 2;
+      else if (c < 0xf0)
+       {
+         char_len = 3;
+         if (c == 0xe0 && d < 0xa0)
+           return 0;
+       }
+      else if (c < 0xf8)
+       {
+         char_len = 4;
+         if (c == 0xf0 && d < 0x90)
+           return 0;
+       }
+      else if (c < 0xfc)
+       {
+         char_len = 5;
+         if (c == 0xf8 && d < 0x88)
+           return 0;
+       }
+      else if (c < 0xfe)
+       {
+         char_len = 6;
+         if (c == 0xfc && d < 0x84)
+           return 0;
+       }
+      else
+       return 0;
+
+      if (str_idx + char_len > input->len)
+       return 0;
+
+      for (i = 1; i < char_len; ++i)
+       {
+         d = re_string_byte_at (input, str_idx + i);
+         if (d < 0x80 || d > 0xbf)
+           return 0;
+       }
+      return char_len;
+    }
+
+  char_len = re_string_char_size_at (input, str_idx);
+  if (node->type == OP_PERIOD)
+    {
+      if (char_len <= 1)
+       return 0;
+      /* FIXME: I don't think this if is needed, as both '\n'
+        and '\0' are char_len == 1.  */
+      /* '.' accepts any one character except the following two cases.  */
+      if ((!(dfa->syntax & RE_DOT_NEWLINE) &&
+          re_string_byte_at (input, str_idx) == '\n') ||
+         ((dfa->syntax & RE_DOT_NOT_NULL) &&
+          re_string_byte_at (input, str_idx) == '\0'))
+       return 0;
+      return char_len;
+    }
+
+  elem_len = re_string_elem_size_at (input, str_idx);
+  wc = __btowc(*(input->mbs+str_idx));
+  if (((elem_len <= 1 && char_len <= 1) || char_len == 0) && (wc != WEOF && wc < SBC_MAX))
+    return 0;
+
+  if (node->type == COMPLEX_BRACKET)
+    {
+      const re_charset_t *cset = node->opr.mbcset;
+# ifdef _LIBC
+      const unsigned char *pin
+       = ((const unsigned char *) re_string_get_buffer (input) + str_idx);
+      int j;
+      uint32_t nrules;
+# endif /* _LIBC */
+      int match_len = 0;
+      wchar_t wc = ((cset->nranges || cset->nchar_classes || cset->nmbchars)
+                   ? re_string_wchar_at (input, str_idx) : 0);
+
+      /* match with multibyte character?  */
+      for (i = 0; i < cset->nmbchars; ++i)
+       if (wc == cset->mbchars[i])
+         {
+           match_len = char_len;
+           goto check_node_accept_bytes_match;
+         }
+      /* match with character_class?  */
+      for (i = 0; i < cset->nchar_classes; ++i)
+       {
+         wctype_t wt = cset->char_classes[i];
+         if (__iswctype (wc, wt))
+           {
+             match_len = char_len;
+             goto check_node_accept_bytes_match;
+           }
+       }
+
+# ifdef _LIBC
+      nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES);
+      if (nrules != 0)
+       {
+         unsigned int in_collseq = 0;
+         const int32_t *table, *indirect;
+         const unsigned char *weights, *extra;
+         const char *collseqwc;
+         /* This #include defines a local function!  */
+#  include <locale/weight.h>
+
+         /* match with collating_symbol?  */
+         if (cset->ncoll_syms)
+           extra = (const unsigned char *)
+             _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB);
+         for (i = 0; i < cset->ncoll_syms; ++i)
+           {
+             const unsigned char *coll_sym = extra + cset->coll_syms[i];
+             /* Compare the length of input collating element and
+                the length of current collating element.  */
+             if (*coll_sym != elem_len)
+               continue;
+             /* Compare each bytes.  */
+             for (j = 0; j < *coll_sym; j++)
+               if (pin[j] != coll_sym[1 + j])
+                 break;
+             if (j == *coll_sym)
+               {
+                 /* Match if every bytes is equal.  */
+                 match_len = j;
+                 goto check_node_accept_bytes_match;
+               }
+           }
+
+         if (cset->nranges)
+           {
+             if (elem_len <= char_len)
+               {
+                 collseqwc = _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQWC);
+                 in_collseq = __collseq_table_lookup (collseqwc, wc);
+               }
+             else
+               in_collseq = find_collation_sequence_value (pin, elem_len);
+           }
+         /* match with range expression?  */
+         for (i = 0; i < cset->nranges; ++i)
+           if (cset->range_starts[i] <= in_collseq
+               && in_collseq <= cset->range_ends[i])
+             {
+               match_len = elem_len;
+               goto check_node_accept_bytes_match;
+             }
+
+         /* match with equivalence_class?  */
+         if (cset->nequiv_classes)
+           {
+             const unsigned char *cp = pin;
+             table = (const int32_t *)
+               _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB);
+             weights = (const unsigned char *)
+               _NL_CURRENT (LC_COLLATE, _NL_COLLATE_WEIGHTMB);
+             extra = (const unsigned char *)
+               _NL_CURRENT (LC_COLLATE, _NL_COLLATE_EXTRAMB);
+             indirect = (const int32_t *)
+               _NL_CURRENT (LC_COLLATE, _NL_COLLATE_INDIRECTMB);
+             int32_t idx = findidx (&cp);
+             if (idx > 0)
+               for (i = 0; i < cset->nequiv_classes; ++i)
+                 {
+                   int32_t equiv_class_idx = cset->equiv_classes[i];
+                   size_t weight_len = weights[idx & 0xffffff];
+                   if (weight_len == weights[equiv_class_idx & 0xffffff]
+                       && (idx >> 24) == (equiv_class_idx >> 24))
+                     {
+                       int cnt = 0;
+
+                       idx &= 0xffffff;
+                       equiv_class_idx &= 0xffffff;
+
+                       while (cnt <= weight_len
+                              && (weights[equiv_class_idx + 1 + cnt]
+                                  == weights[idx + 1 + cnt]))
+                         ++cnt;
+                       if (cnt > weight_len)
+                         {
+                           match_len = elem_len;
+                           goto check_node_accept_bytes_match;
+                         }
+                     }
+                 }
+           }
+       }
+      else
+# endif /* _LIBC */
+       {
+         /* match with range expression?  */
+#if __GNUC__ >= 2
+         wchar_t cmp_buf[] = {L'\0', L'\0', wc, L'\0', L'\0', L'\0'};
+#else
+         wchar_t cmp_buf[] = {L'\0', L'\0', L'\0', L'\0', L'\0', L'\0'};
+         cmp_buf[2] = wc;
+#endif
+         for (i = 0; i < cset->nranges; ++i)
+           {
+             cmp_buf[0] = cset->range_starts[i];
+             cmp_buf[4] = cset->range_ends[i];
+             if (wcscoll (cmp_buf, cmp_buf + 2) <= 0
+                 && wcscoll (cmp_buf + 2, cmp_buf + 4) <= 0)
+               {
+                 match_len = char_len;
+                 goto check_node_accept_bytes_match;
+               }
+           }
+       }
+    check_node_accept_bytes_match:
+      if (!cset->non_match)
+       return match_len;
+      else
+       {
+         if (match_len > 0)
+           return 0;
+         else
+           return (elem_len > char_len) ? elem_len : char_len;
+       }
+    }
+  return 0;
+}
+
+# ifdef _LIBC
+static unsigned int
+internal_function
+find_collation_sequence_value (const unsigned char *mbs, size_t mbs_len)
+{
+  uint32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES);
+  if (nrules == 0)
+    {
+      if (mbs_len == 1)
+       {
+         /* No valid character.  Match it as a single byte character.  */
+         const unsigned char *collseq = (const unsigned char *)
+           _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQMB);
+         return collseq[mbs[0]];
+       }
+      return UINT_MAX;
+    }
+  else
+    {
+      int32_t idx;
+      const unsigned char *extra = (const unsigned char *)
+       _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB);
+      int32_t extrasize = (const unsigned char *)
+       _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB + 1) - extra;
+
+      for (idx = 0; idx < extrasize;)
+       {
+         int mbs_cnt, found = 0;
+         int32_t elem_mbs_len;
+         /* Skip the name of collating element name.  */
+         idx = idx + extra[idx] + 1;
+         elem_mbs_len = extra[idx++];
+         if (mbs_len == elem_mbs_len)
+           {
+             for (mbs_cnt = 0; mbs_cnt < elem_mbs_len; ++mbs_cnt)
+               if (extra[idx + mbs_cnt] != mbs[mbs_cnt])
+                 break;
+             if (mbs_cnt == elem_mbs_len)
+               /* Found the entry.  */
+               found = 1;
+           }
+         /* Skip the byte sequence of the collating element.  */
+         idx += elem_mbs_len;
+         /* Adjust for the alignment.  */
+         idx = (idx + 3) & ~3;
+         /* Skip the collation sequence value.  */
+         idx += sizeof (uint32_t);
+         /* Skip the wide char sequence of the collating element.  */
+         idx = idx + sizeof (uint32_t) * (extra[idx] + 1);
+         /* If we found the entry, return the sequence value.  */
+         if (found)
+           return *(uint32_t *) (extra + idx);
+         /* Skip the collation sequence value.  */
+         idx += sizeof (uint32_t);
+       }
+      return UINT_MAX;
+    }
+}
+# endif /* _LIBC */
+#endif /* RE_ENABLE_I18N */
+
+/* Check whether the node accepts the byte which is IDX-th
+   byte of the INPUT.  */
+
+static int
+internal_function
+check_node_accept (const re_match_context_t *mctx, const re_token_t *node,
+                  int idx)
+{
+  unsigned char ch;
+  ch = re_string_byte_at (&mctx->input, idx);
+  switch (node->type)
+    {
+    case CHARACTER:
+      if (node->opr.c != ch)
+       return 0;
+      break;
+
+    case SIMPLE_BRACKET:
+      if (!bitset_contain (node->opr.sbcset, ch))
+       return 0;
+      break;
+
+#ifdef RE_ENABLE_I18N
+    case OP_UTF8_PERIOD:
+      if (ch >= 0x80)
+       return 0;
+      /* FALLTHROUGH */
+#endif
+    case OP_PERIOD:
+      if ((ch == '\n' && !(mctx->dfa->syntax & RE_DOT_NEWLINE))
+         || (ch == '\0' && (mctx->dfa->syntax & RE_DOT_NOT_NULL)))
+       return 0;
+      break;
+
+    default:
+      return 0;
+    }
+
+  if (node->constraint)
+    {
+      /* The node has constraints.  Check whether the current context
+        satisfies the constraints.  */
+      unsigned int context = re_string_context_at (&mctx->input, idx,
+                                                  mctx->eflags);
+      if (NOT_SATISFY_NEXT_CONSTRAINT (node->constraint, context))
+       return 0;
+    }
+
+  return 1;
+}
+
+/* Extend the buffers, if the buffers have run out.  */
+
+static reg_errcode_t
+internal_function
+extend_buffers (re_match_context_t *mctx)
+{
+  reg_errcode_t ret;
+  re_string_t *pstr = &mctx->input;
+
+  /* Avoid overflow.  */
+  if (BE (INT_MAX / 2 / sizeof (re_dfastate_t *) <= pstr->bufs_len, 0))
+    return REG_ESPACE;
+
+  /* Double the lengthes of the buffers.  */
+  ret = re_string_realloc_buffers (pstr, pstr->bufs_len * 2);
+  if (BE (ret != REG_NOERROR, 0))
+    return ret;
+
+  if (mctx->state_log != NULL)
+    {
+      /* And double the length of state_log.  */
+      /* XXX We have no indication of the size of this buffer.  If this
+        allocation fail we have no indication that the state_log array
+        does not have the right size.  */
+      re_dfastate_t **new_array = re_realloc (mctx->state_log, re_dfastate_t *,
+                                             pstr->bufs_len + 1);
+      if (BE (new_array == NULL, 0))
+       return REG_ESPACE;
+      mctx->state_log = new_array;
+    }
+
+  /* Then reconstruct the buffers.  */
+  if (pstr->icase)
+    {
+#ifdef RE_ENABLE_I18N
+      if (pstr->mb_cur_max > 1)
+       {
+         ret = build_wcs_upper_buffer (pstr);
+         if (BE (ret != REG_NOERROR, 0))
+           return ret;
+       }
+      else
+#endif /* RE_ENABLE_I18N  */
+       build_upper_buffer (pstr);
+    }
+  else
+    {
+#ifdef RE_ENABLE_I18N
+      if (pstr->mb_cur_max > 1)
+       build_wcs_buffer (pstr);
+      else
+#endif /* RE_ENABLE_I18N  */
+       {
+         if (pstr->trans != NULL)
+           re_string_translate_buffer (pstr);
+       }
+    }
+  return REG_NOERROR;
+}
+
+\f
+/* Functions for matching context.  */
+
+/* Initialize MCTX.  */
+
+static reg_errcode_t
+internal_function
+match_ctx_init (re_match_context_t *mctx, int eflags, int n)
+{
+  mctx->eflags = eflags;
+  mctx->match_last = -1;
+  if (n > 0)
+    {
+      mctx->bkref_ents = re_malloc (struct re_backref_cache_entry, n);
+      mctx->sub_tops = re_malloc (re_sub_match_top_t *, n);
+      if (BE (mctx->bkref_ents == NULL || mctx->sub_tops == NULL, 0))
+       return REG_ESPACE;
+    }
+  /* Already zero-ed by the caller.
+     else
+       mctx->bkref_ents = NULL;
+     mctx->nbkref_ents = 0;
+     mctx->nsub_tops = 0;  */
+  mctx->abkref_ents = n;
+  mctx->max_mb_elem_len = 1;
+  mctx->asub_tops = n;
+  return REG_NOERROR;
+}
+
+/* Clean the entries which depend on the current input in MCTX.
+   This function must be invoked when the matcher changes the start index
+   of the input, or changes the input string.  */
+
+static void
+internal_function
+match_ctx_clean (re_match_context_t *mctx)
+{
+  int st_idx;
+  for (st_idx = 0; st_idx < mctx->nsub_tops; ++st_idx)
+    {
+      int sl_idx;
+      re_sub_match_top_t *top = mctx->sub_tops[st_idx];
+      for (sl_idx = 0; sl_idx < top->nlasts; ++sl_idx)
+       {
+         re_sub_match_last_t *last = top->lasts[sl_idx];
+         re_free (last->path.array);
+         re_free (last);
+       }
+      re_free (top->lasts);
+      if (top->path)
+       {
+         re_free (top->path->array);
+         re_free (top->path);
+       }
+      free (top);
+    }
+
+  mctx->nsub_tops = 0;
+  mctx->nbkref_ents = 0;
+}
+
+/* Free all the memory associated with MCTX.  */
+
+static void
+internal_function
+match_ctx_free (re_match_context_t *mctx)
+{
+  /* First, free all the memory associated with MCTX->SUB_TOPS.  */
+  match_ctx_clean (mctx);
+  re_free (mctx->sub_tops);
+  re_free (mctx->bkref_ents);
+}
+
+/* Add a new backreference entry to MCTX.
+   Note that we assume that caller never call this function with duplicate
+   entry, and call with STR_IDX which isn't smaller than any existing entry.
+*/
+
+static reg_errcode_t
+internal_function
+match_ctx_add_entry (re_match_context_t *mctx, int node, int str_idx, int from,
+                    int to)
+{
+  if (mctx->nbkref_ents >= mctx->abkref_ents)
+    {
+      struct re_backref_cache_entry* new_entry;
+      new_entry = re_realloc (mctx->bkref_ents, struct re_backref_cache_entry,
+                             mctx->abkref_ents * 2);
+      if (BE (new_entry == NULL, 0))
+       {
+         re_free (mctx->bkref_ents);
+         return REG_ESPACE;
+       }
+      mctx->bkref_ents = new_entry;
+      memset (mctx->bkref_ents + mctx->nbkref_ents, '\0',
+             sizeof (struct re_backref_cache_entry) * mctx->abkref_ents);
+      mctx->abkref_ents *= 2;
+    }
+  if (mctx->nbkref_ents > 0
+      && mctx->bkref_ents[mctx->nbkref_ents - 1].str_idx == str_idx)
+    mctx->bkref_ents[mctx->nbkref_ents - 1].more = 1;
+
+  mctx->bkref_ents[mctx->nbkref_ents].node = node;
+  mctx->bkref_ents[mctx->nbkref_ents].str_idx = str_idx;
+  mctx->bkref_ents[mctx->nbkref_ents].subexp_from = from;
+  mctx->bkref_ents[mctx->nbkref_ents].subexp_to = to;
+
+  /* This is a cache that saves negative results of check_dst_limits_calc_pos.
+     If bit N is clear, means that this entry won't epsilon-transition to
+     an OP_OPEN_SUBEXP or OP_CLOSE_SUBEXP for the N+1-th subexpression.  If
+     it is set, check_dst_limits_calc_pos_1 will recurse and try to find one
+     such node.
+
+     A backreference does not epsilon-transition unless it is empty, so set
+     to all zeros if FROM != TO.  */
+  mctx->bkref_ents[mctx->nbkref_ents].eps_reachable_subexps_map
+    = (from == to ? ~0 : 0);
+
+  mctx->bkref_ents[mctx->nbkref_ents++].more = 0;
+  if (mctx->max_mb_elem_len < to - from)
+    mctx->max_mb_elem_len = to - from;
+  return REG_NOERROR;
+}
+
+/* Search for the first entry which has the same str_idx, or -1 if none is
+   found.  Note that MCTX->BKREF_ENTS is already sorted by MCTX->STR_IDX.  */
+
+static int
+internal_function
+search_cur_bkref_entry (const re_match_context_t *mctx, int str_idx)
+{
+  int left, right, mid, last;
+  last = right = mctx->nbkref_ents;
+  for (left = 0; left < right;)
+    {
+      mid = (left + right) / 2;
+      if (mctx->bkref_ents[mid].str_idx < str_idx)
+       left = mid + 1;
+      else
+       right = mid;
+    }
+  if (left < last && mctx->bkref_ents[left].str_idx == str_idx)
+    return left;
+  else
+    return -1;
+}
+
+/* Register the node NODE, whose type is OP_OPEN_SUBEXP, and which matches
+   at STR_IDX.  */
+
+static reg_errcode_t
+internal_function
+match_ctx_add_subtop (re_match_context_t *mctx, int node, int str_idx)
+{
+#ifdef DEBUG
+  assert (mctx->sub_tops != NULL);
+  assert (mctx->asub_tops > 0);
+#endif
+  if (BE (mctx->nsub_tops == mctx->asub_tops, 0))
+    {
+      int new_asub_tops = mctx->asub_tops * 2;
+      re_sub_match_top_t **new_array = re_realloc (mctx->sub_tops,
+                                                  re_sub_match_top_t *,
+                                                  new_asub_tops);
+      if (BE (new_array == NULL, 0))
+       return REG_ESPACE;
+      mctx->sub_tops = new_array;
+      mctx->asub_tops = new_asub_tops;
+    }
+  mctx->sub_tops[mctx->nsub_tops] = calloc (1, sizeof (re_sub_match_top_t));
+  if (BE (mctx->sub_tops[mctx->nsub_tops] == NULL, 0))
+    return REG_ESPACE;
+  mctx->sub_tops[mctx->nsub_tops]->node = node;
+  mctx->sub_tops[mctx->nsub_tops++]->str_idx = str_idx;
+  return REG_NOERROR;
+}
+
+/* Register the node NODE, whose type is OP_CLOSE_SUBEXP, and which matches
+   at STR_IDX, whose corresponding OP_OPEN_SUBEXP is SUB_TOP.  */
+
+static re_sub_match_last_t *
+internal_function
+match_ctx_add_sublast (re_sub_match_top_t *subtop, int node, int str_idx)
+{
+  re_sub_match_last_t *new_entry;
+  if (BE (subtop->nlasts == subtop->alasts, 0))
+    {
+      int new_alasts = 2 * subtop->alasts + 1;
+      re_sub_match_last_t **new_array = re_realloc (subtop->lasts,
+                                                   re_sub_match_last_t *,
+                                                   new_alasts);
+      if (BE (new_array == NULL, 0))
+       return NULL;
+      subtop->lasts = new_array;
+      subtop->alasts = new_alasts;
+    }
+  new_entry = calloc (1, sizeof (re_sub_match_last_t));
+  if (BE (new_entry != NULL, 1))
+    {
+      subtop->lasts[subtop->nlasts] = new_entry;
+      new_entry->node = node;
+      new_entry->str_idx = str_idx;
+      ++subtop->nlasts;
+    }
+  return new_entry;
+}
+
+static void
+internal_function
+sift_ctx_init (re_sift_context_t *sctx, re_dfastate_t **sifted_sts,
+              re_dfastate_t **limited_sts, int last_node, int last_str_idx)
+{
+  sctx->sifted_states = sifted_sts;
+  sctx->limited_states = limited_sts;
+  sctx->last_node = last_node;
+  sctx->last_str_idx = last_str_idx;
+  re_node_set_init_empty (&sctx->limits);
+}
index 357e733074ea7c85f880fa577ad65dfb3787fec7..e1e0e7543d9414726122c121b7909bf73809a81a 100644 (file)
@@ -2,12 +2,19 @@
 
 /*
  * The size parameter specifies the available space, i.e. includes
- * the trailing NUL byte; but Windows's vsnprintf expects the
- * number of characters to write without the trailing NUL.
+ * the trailing NUL byte; but Windows's vsnprintf uses the entire
+ * buffer and avoids the trailing NUL, should the buffer be exactly
+ * big enough for the result. Defining SNPRINTF_SIZE_CORR to 1 will
+ * therefore remove 1 byte from the reported buffer size, so we
+ * always have room for a trailing NUL byte.
  */
 #ifndef SNPRINTF_SIZE_CORR
+#if defined(WIN32) && (!defined(__GNUC__) || __GNUC__ < 4)
+#define SNPRINTF_SIZE_CORR 1
+#else
 #define SNPRINTF_SIZE_CORR 0
 #endif
+#endif
 
 #undef vsnprintf
 int git_vsnprintf(char *str, size_t maxsize, const char *format, va_list ap)
diff --git a/compat/strtok_r.c b/compat/strtok_r.c
new file mode 100644 (file)
index 0000000..7b5d568
--- /dev/null
@@ -0,0 +1,61 @@
+/* Reentrant string tokenizer.  Generic version.
+   Copyright (C) 1991,1996-1999,2001,2004 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C 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.
+
+   The GNU C 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 the GNU C Library; if not, write to the Free
+   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+   02111-1307 USA.  */
+
+#include "../git-compat-util.h"
+
+/* Parse S into tokens separated by characters in DELIM.
+   If S is NULL, the saved pointer in SAVE_PTR is used as
+   the next starting point.  For example:
+       char s[] = "-abc-=-def";
+       char *sp;
+       x = strtok_r(s, "-", &sp);      // x = "abc", sp = "=-def"
+       x = strtok_r(NULL, "-=", &sp);  // x = "def", sp = NULL
+       x = strtok_r(NULL, "=", &sp);   // x = NULL
+               // s = "abc\0-def\0"
+*/
+char *
+gitstrtok_r (char *s, const char *delim, char **save_ptr)
+{
+  char *token;
+
+  if (s == NULL)
+    s = *save_ptr;
+
+  /* Scan leading delimiters.  */
+  s += strspn (s, delim);
+  if (*s == '\0')
+    {
+      *save_ptr = s;
+      return NULL;
+    }
+
+  /* Find the end of the token.  */
+  token = s;
+  s = strpbrk (token, delim);
+  if (s == NULL)
+    /* This token finishes the string.  */
+    *save_ptr = token + strlen (token);
+  else
+    {
+      /* Terminate the token and make *SAVE_PTR point past it.  */
+      *s = '\0';
+      *save_ptr = s + 1;
+    }
+  return token;
+}
diff --git a/compat/vcbuild/README b/compat/vcbuild/README
new file mode 100644 (file)
index 0000000..df8a657
--- /dev/null
@@ -0,0 +1,50 @@
+The Steps of Build Git with VS2008
+
+1. You need the build environment, which contains the Git dependencies
+   to be able to compile, link and run Git with MSVC.
+
+   You can either use the binary repository:
+
+       WWW: http://repo.or.cz/w/msvcgit.git
+       Git: git clone git://repo.or.cz/msvcgit.git
+       Zip: http://repo.or.cz/w/msvcgit.git?a=snapshot;h=master;sf=zip
+
+   and call the setup_32bit_env.cmd batch script before compiling Git,
+   (see repo/package README for details), or the source repository:
+
+       WWW: http://repo.or.cz/w/gitbuild.git
+       Git: git clone git://repo.or.cz/gitbuild.git
+       Zip: (None, as it's a project with submodules)
+
+   and build the support libs as instructed in that repo/package.
+
+2. Ensure you have the msysgit environment in your path, so you have
+   GNU Make, bash and perl available.
+
+       WWW: http://repo.or.cz/w/msysgit.git
+       Git: git clone git://repo.or.cz/msysgit.git
+       Zip: http://repo.or.cz/w/msysgit.git?a=snapshot;h=master;sf=zip
+
+   This environment is also needed when you use the resulting
+   executables, since Git might need to run scripts which are part of
+   the git operations.
+
+3. Inside Git's directory run the command:
+       make common-cmds.h
+   to generate the common-cmds.h file needed to compile git.
+
+4. Then either build Git with the GNU Make Makefile in the Git projects
+   root
+       make MSVC=1
+   or generate Visual Studio solution/projects (.sln/.vcproj) with the
+   command
+       perl contrib/buildsystems/generate -g Vcproj
+   and open and build the solution with the IDE
+       devenv git.sln /useenv
+   or build with the IDE build engine directly from the command line
+       devenv git.sln /useenv /build "Release|Win32"
+   The /useenv option is required, so Visual Studio picks up the
+   environment variables for the support libraries required to build
+   Git, which you set up in step 1.
+
+Done!
diff --git a/compat/vcbuild/include/alloca.h b/compat/vcbuild/include/alloca.h
new file mode 100644 (file)
index 0000000..c0d7985
--- /dev/null
@@ -0,0 +1 @@
+#include <malloc.h>
diff --git a/compat/vcbuild/include/arpa/inet.h b/compat/vcbuild/include/arpa/inet.h
new file mode 100644 (file)
index 0000000..0d8552a
--- /dev/null
@@ -0,0 +1 @@
+/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/dirent.h b/compat/vcbuild/include/dirent.h
new file mode 100644 (file)
index 0000000..440618d
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * DIRENT.H (formerly DIRLIB.H)
+ * This file has no copyright assigned and is placed in the Public Domain.
+ * This file is a part of the mingw-runtime package.
+ *
+ * The mingw-runtime package and its code is distributed in the hope that it
+ * will be useful but WITHOUT ANY WARRANTY.  ALL WARRANTIES, EXPRESSED OR
+ * IMPLIED ARE HEREBY DISCLAIMED.  This includes but is not limited to
+ * warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * You are free to use this package and its code without limitation.
+ */
+#ifndef _DIRENT_H_
+#define _DIRENT_H_
+#include <io.h>
+
+#define PATH_MAX 512
+
+#define __MINGW_NOTHROW
+
+#ifndef RC_INVOKED
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct dirent
+{
+       long            d_ino;          /* Always zero. */
+       unsigned short  d_reclen;       /* Always zero. */
+       unsigned short  d_namlen;       /* Length of name in d_name. */
+       char            d_name[FILENAME_MAX]; /* File name. */
+};
+
+/*
+ * This is an internal data structure. Good programmers will not use it
+ * except as an argument to one of the functions below.
+ * dd_stat field is now int (was short in older versions).
+ */
+typedef struct
+{
+       /* disk transfer area for this dir */
+       struct _finddata_t      dd_dta;
+
+       /* dirent struct to return from dir (NOTE: this makes this thread
+        * safe as long as only one thread uses a particular DIR struct at
+        * a time) */
+       struct dirent           dd_dir;
+
+       /* _findnext handle */
+       long                    dd_handle;
+
+       /*
+        * Status of search:
+        *   0 = not started yet (next entry to read is first entry)
+        *  -1 = off the end
+        *   positive = 0 based index of next entry
+        */
+       int                     dd_stat;
+
+       /* given path for dir with search pattern (struct is extended) */
+       char                    dd_name[PATH_MAX+3];
+} DIR;
+
+DIR* __cdecl __MINGW_NOTHROW opendir (const char*);
+struct dirent* __cdecl __MINGW_NOTHROW readdir (DIR*);
+int __cdecl __MINGW_NOTHROW closedir (DIR*);
+void __cdecl __MINGW_NOTHROW rewinddir (DIR*);
+long __cdecl __MINGW_NOTHROW telldir (DIR*);
+void __cdecl __MINGW_NOTHROW seekdir (DIR*, long);
+
+
+/* wide char versions */
+
+struct _wdirent
+{
+       long            d_ino;          /* Always zero. */
+       unsigned short  d_reclen;       /* Always zero. */
+       unsigned short  d_namlen;       /* Length of name in d_name. */
+       wchar_t         d_name[FILENAME_MAX]; /* File name. */
+};
+
+/*
+ * This is an internal data structure. Good programmers will not use it
+ * except as an argument to one of the functions below.
+ */
+typedef struct
+{
+       /* disk transfer area for this dir */
+       //struct _wfinddata_t   dd_dta;
+
+       /* dirent struct to return from dir (NOTE: this makes this thread
+        * safe as long as only one thread uses a particular DIR struct at
+        * a time) */
+       struct _wdirent         dd_dir;
+
+       /* _findnext handle */
+       long                    dd_handle;
+
+       /*
+        * Status of search:
+        *   0 = not started yet (next entry to read is first entry)
+        *  -1 = off the end
+        *   positive = 0 based index of next entry
+        */
+       int                     dd_stat;
+
+       /* given path for dir with search pattern (struct is extended) */
+       wchar_t                 dd_name[1];
+} _WDIR;
+
+
+
+_WDIR* __cdecl __MINGW_NOTHROW _wopendir (const wchar_t*);
+struct _wdirent*  __cdecl __MINGW_NOTHROW _wreaddir (_WDIR*);
+int __cdecl __MINGW_NOTHROW _wclosedir (_WDIR*);
+void __cdecl __MINGW_NOTHROW _wrewinddir (_WDIR*);
+long __cdecl __MINGW_NOTHROW _wtelldir (_WDIR*);
+void __cdecl __MINGW_NOTHROW _wseekdir (_WDIR*, long);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* Not RC_INVOKED */
+
+#endif /* Not _DIRENT_H_ */
diff --git a/compat/vcbuild/include/grp.h b/compat/vcbuild/include/grp.h
new file mode 100644 (file)
index 0000000..0d8552a
--- /dev/null
@@ -0,0 +1 @@
+/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/inttypes.h b/compat/vcbuild/include/inttypes.h
new file mode 100644 (file)
index 0000000..0d8552a
--- /dev/null
@@ -0,0 +1 @@
+/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/netdb.h b/compat/vcbuild/include/netdb.h
new file mode 100644 (file)
index 0000000..0d8552a
--- /dev/null
@@ -0,0 +1 @@
+/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/netinet/in.h b/compat/vcbuild/include/netinet/in.h
new file mode 100644 (file)
index 0000000..0d8552a
--- /dev/null
@@ -0,0 +1 @@
+/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/netinet/tcp.h b/compat/vcbuild/include/netinet/tcp.h
new file mode 100644 (file)
index 0000000..0d8552a
--- /dev/null
@@ -0,0 +1 @@
+/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/pwd.h b/compat/vcbuild/include/pwd.h
new file mode 100644 (file)
index 0000000..0d8552a
--- /dev/null
@@ -0,0 +1 @@
+/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/sys/ioctl.h b/compat/vcbuild/include/sys/ioctl.h
new file mode 100644 (file)
index 0000000..0d8552a
--- /dev/null
@@ -0,0 +1 @@
+/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/sys/param.h b/compat/vcbuild/include/sys/param.h
new file mode 100644 (file)
index 0000000..0d8552a
--- /dev/null
@@ -0,0 +1 @@
+/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/sys/poll.h b/compat/vcbuild/include/sys/poll.h
new file mode 100644 (file)
index 0000000..0d8552a
--- /dev/null
@@ -0,0 +1 @@
+/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/sys/select.h b/compat/vcbuild/include/sys/select.h
new file mode 100644 (file)
index 0000000..0d8552a
--- /dev/null
@@ -0,0 +1 @@
+/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/sys/socket.h b/compat/vcbuild/include/sys/socket.h
new file mode 100644 (file)
index 0000000..0d8552a
--- /dev/null
@@ -0,0 +1 @@
+/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/sys/time.h b/compat/vcbuild/include/sys/time.h
new file mode 100644 (file)
index 0000000..0d8552a
--- /dev/null
@@ -0,0 +1 @@
+/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/sys/utime.h b/compat/vcbuild/include/sys/utime.h
new file mode 100644 (file)
index 0000000..582589c
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef        _UTIME_H_
+#define        _UTIME_H_
+/*
+ * UTIME.H
+ * This file has no copyright assigned and is placed in the Public Domain.
+ * This file is a part of the mingw-runtime package.
+ *
+ * The mingw-runtime package and its code is distributed in the hope that it
+ * will be useful but WITHOUT ANY WARRANTY.  ALL WARRANTIES, EXPRESSED OR
+ * IMPLIED ARE HEREBY DISCLAIMED.  This includes but is not limited to
+ * warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * You are free to use this package and its code without limitation.
+ */
+
+/*
+ * Structure used by _utime function.
+ */
+struct _utimbuf
+{
+       time_t  actime;         /* Access time */
+       time_t  modtime;        /* Modification time */
+};
+
+#ifndef        _NO_OLDNAMES
+/* NOTE: Must be the same as _utimbuf above. */
+struct utimbuf
+{
+       time_t  actime;
+       time_t  modtime;
+};
+#endif /* Not _NO_OLDNAMES */
+
+#endif
diff --git a/compat/vcbuild/include/sys/wait.h b/compat/vcbuild/include/sys/wait.h
new file mode 100644 (file)
index 0000000..0d8552a
--- /dev/null
@@ -0,0 +1 @@
+/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/termios.h b/compat/vcbuild/include/termios.h
new file mode 100644 (file)
index 0000000..0d8552a
--- /dev/null
@@ -0,0 +1 @@
+/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/unistd.h b/compat/vcbuild/include/unistd.h
new file mode 100644 (file)
index 0000000..2a4f276
--- /dev/null
@@ -0,0 +1,92 @@
+#ifndef _UNISTD_
+#define _UNISTD_
+
+/* Win32 define for porting git*/
+
+#ifndef _MODE_T_
+#define        _MODE_T_
+typedef unsigned short _mode_t;
+
+#ifndef        _NO_OLDNAMES
+typedef _mode_t        mode_t;
+#endif
+#endif /* Not _MODE_T_ */
+
+#ifndef _SSIZE_T_
+#define _SSIZE_T_
+typedef long _ssize_t;
+
+#ifndef        _OFF_T_
+#define        _OFF_T_
+typedef long _off_t;
+
+#ifndef        _NO_OLDNAMES
+typedef _off_t off_t;
+#endif
+#endif /* Not _OFF_T_ */
+
+
+#ifndef        _NO_OLDNAMES
+typedef _ssize_t ssize_t;
+#endif
+#endif /* Not _SSIZE_T_ */
+
+typedef signed char int8_t;
+typedef unsigned char   uint8_t;
+typedef short  int16_t;
+typedef unsigned short  uint16_t;
+typedef int  int32_t;
+typedef unsigned   uint32_t;
+typedef long long  int64_t;
+typedef unsigned long long   uint64_t;
+
+typedef long long  intmax_t;
+typedef unsigned long long uintmax_t;
+
+typedef int64_t off64_t;
+
+#define STDOUT_FILENO 1
+#define STDERR_FILENO 2
+
+/* Some defines for _access nAccessMode (MS doesn't define them, but
+ * it doesn't seem to hurt to add them). */
+#define        F_OK    0       /* Check for file existence */
+/* Well maybe it does hurt.  On newer versions of MSVCRT, an access mode
+   of 1 causes invalid parameter error. */
+#define        X_OK    0       /* MS access() doesn't check for execute permission. */
+#define        W_OK    2       /* Check for write permission */
+#define        R_OK    4       /* Check for read permission */
+
+#define        _S_IFIFO        0x1000  /* FIFO */
+#define        _S_IFCHR        0x2000  /* Character */
+#define        _S_IFBLK        0x3000  /* Block: Is this ever set under w32? */
+#define        _S_IFDIR        0x4000  /* Directory */
+#define        _S_IFREG        0x8000  /* Regular */
+
+#define        _S_IFMT         0xF000  /* File type mask */
+
+#define        _S_IXUSR        _S_IEXEC
+#define        _S_IWUSR        _S_IWRITE
+#define        _S_IRUSR        _S_IREAD
+#define        _S_ISDIR(m)     (((m) & _S_IFMT) == _S_IFDIR)
+
+#define        S_IFIFO         _S_IFIFO
+#define        S_IFCHR         _S_IFCHR
+#define        S_IFBLK         _S_IFBLK
+#define        S_IFDIR         _S_IFDIR
+#define        S_IFREG         _S_IFREG
+#define        S_IFMT          _S_IFMT
+#define        S_IEXEC         _S_IEXEC
+#define        S_IWRITE        _S_IWRITE
+#define        S_IREAD         _S_IREAD
+#define        S_IRWXU         _S_IRWXU
+#define        S_IXUSR         _S_IXUSR
+#define        S_IWUSR         _S_IWUSR
+#define        S_IRUSR         _S_IRUSR
+
+
+#define        S_ISDIR(m)      (((m) & S_IFMT) == S_IFDIR)
+#define        S_ISREG(m)      (((m) & S_IFMT) == S_IFREG)
+#define        S_ISFIFO(m)     (((m) & S_IFMT) == S_IFIFO)
+
+#endif
diff --git a/compat/vcbuild/include/utime.h b/compat/vcbuild/include/utime.h
new file mode 100644 (file)
index 0000000..8285f38
--- /dev/null
@@ -0,0 +1 @@
+#include <sys/utime.h>
diff --git a/compat/vcbuild/scripts/clink.pl b/compat/vcbuild/scripts/clink.pl
new file mode 100644 (file)
index 0000000..4374771
--- /dev/null
@@ -0,0 +1,52 @@
+#!/usr/bin/perl -w
+######################################################################
+# Compiles or links files
+#
+# This is a wrapper to facilitate the compilation of Git with MSVC
+# using GNU Make as the build system. So, instead of manipulating the
+# Makefile into something nasty, just to support non-space arguments
+# etc, we use this wrapper to fix the command line options
+#
+# Copyright (C) 2009 Marius Storm-Olsen <mstormo@gmail.com>
+######################################################################
+use strict;
+my @args = ();
+my @cflags = ();
+my $is_linking = 0;
+while (@ARGV) {
+       my $arg = shift @ARGV;
+       if ("$arg" =~ /^-[DIMGO]/) {
+               push(@cflags, $arg);
+       } elsif ("$arg" eq "-o") {
+               my $file_out = shift @ARGV;
+               if ("$file_out" =~ /exe$/) {
+                       $is_linking = 1;
+                       push(@args, "-OUT:$file_out");
+               } else {
+                       push(@args, "-Fo$file_out");
+               }
+       } elsif ("$arg" eq "-lz") {
+               push(@args, "zlib.lib");
+       } elsif ("$arg" eq "-liconv") {
+               push(@args, "iconv.lib");
+       } elsif ("$arg" eq "-lcrypto") {
+               push(@args, "libeay32.lib");
+       } elsif ("$arg" eq "-lssl") {
+               push(@args, "ssleay32.lib");
+       } elsif ("$arg" =~ /^-L/ && "$arg" ne "-LTCG") {
+               $arg =~ s/^-L/-LIBPATH:/;
+               push(@args, $arg);
+       } elsif ("$arg" =~ /^-R/) {
+               # eat
+       } else {
+               push(@args, $arg);
+       }
+}
+if ($is_linking) {
+       unshift(@args, "link.exe");
+} else {
+       unshift(@args, "cl.exe");
+       push(@args, @cflags);
+}
+#printf("**** @args\n");
+exit (system(@args) != 0);
diff --git a/compat/vcbuild/scripts/lib.pl b/compat/vcbuild/scripts/lib.pl
new file mode 100644 (file)
index 0000000..d8054e4
--- /dev/null
@@ -0,0 +1,26 @@
+#!/usr/bin/perl -w
+######################################################################
+# Libifies files on Windows
+#
+# This is a wrapper to facilitate the compilation of Git with MSVC
+# using GNU Make as the build system. So, instead of manipulating the
+# Makefile into something nasty, just to support non-space arguments
+# etc, we use this wrapper to fix the command line options
+#
+# Copyright (C) 2009 Marius Storm-Olsen <mstormo@gmail.com>
+######################################################################
+use strict;
+my @args = ();
+while (@ARGV) {
+       my $arg = shift @ARGV;
+       if ("$arg" eq "rcs") {
+               # Consume the rcs option
+       } elsif ("$arg" =~ /\.a$/) {
+               push(@args, "-OUT:$arg");
+       } else {
+               push(@args, $arg);
+       }
+}
+unshift(@args, "lib.exe");
+# printf("**** @args\n");
+exit (system(@args) != 0);
index c26384e595b4f23d5fef938b1136cc3a85469e56..8ce91048deffc17bdf8d51672ddaa0c867052ac5 100644 (file)
@@ -1,5 +1,10 @@
+#ifndef WIN32_H
+#define WIN32_H
+
 /* common Win32 functions for MinGW and Cygwin */
+#ifndef WIN32         /* Not defined by Cygwin */
 #include <windows.h>
+#endif
 
 static inline int file_attr_to_st_mode (DWORD attr)
 {
@@ -32,3 +37,5 @@ static inline int get_file_attr(const char *fname, WIN32_FILE_ATTRIBUTE_DATA *fd
                return ENOENT;
        }
 }
+
+#endif
diff --git a/compat/win32/pthread.c b/compat/win32/pthread.c
new file mode 100644 (file)
index 0000000..010e875
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2009 Andrzej K. Haczewski <ahaczewski@gmail.com>
+ *
+ * DISCLAIMER: The implementation is Git-specific, it is subset of original
+ * Pthreads API, without lots of other features that Git doesn't use.
+ * Git also makes sure that the passed arguments are valid, so there's
+ * no need for double-checking.
+ */
+
+#include "../../git-compat-util.h"
+#include "pthread.h"
+
+#include <errno.h>
+#include <limits.h>
+
+static unsigned __stdcall win32_start_routine(void *arg)
+{
+       pthread_t *thread = arg;
+       thread->tid = GetCurrentThreadId();
+       thread->arg = thread->start_routine(thread->arg);
+       return 0;
+}
+
+int pthread_create(pthread_t *thread, const void *unused,
+                  void *(*start_routine)(void*), void *arg)
+{
+       thread->arg = arg;
+       thread->start_routine = start_routine;
+       thread->handle = (HANDLE)
+               _beginthreadex(NULL, 0, win32_start_routine, thread, 0, NULL);
+
+       if (!thread->handle)
+               return errno;
+       else
+               return 0;
+}
+
+int win32_pthread_join(pthread_t *thread, void **value_ptr)
+{
+       DWORD result = WaitForSingleObject(thread->handle, INFINITE);
+       switch (result) {
+               case WAIT_OBJECT_0:
+                       if (value_ptr)
+                               *value_ptr = thread->arg;
+                       return 0;
+               case WAIT_ABANDONED:
+                       return EINVAL;
+               default:
+                       return err_win_to_posix(GetLastError());
+       }
+}
+
+pthread_t pthread_self(void)
+{
+       pthread_t t = { 0 };
+       t.tid = GetCurrentThreadId();
+       return t;
+}
+
+int pthread_cond_init(pthread_cond_t *cond, const void *unused)
+{
+       cond->waiters = 0;
+       cond->was_broadcast = 0;
+       InitializeCriticalSection(&cond->waiters_lock);
+
+       cond->sema = CreateSemaphore(NULL, 0, LONG_MAX, NULL);
+       if (!cond->sema)
+               die("CreateSemaphore() failed");
+
+       cond->continue_broadcast = CreateEvent(NULL,    /* security */
+                               FALSE,                  /* auto-reset */
+                               FALSE,                  /* not signaled */
+                               NULL);                  /* name */
+       if (!cond->continue_broadcast)
+               die("CreateEvent() failed");
+
+       return 0;
+}
+
+int pthread_cond_destroy(pthread_cond_t *cond)
+{
+       CloseHandle(cond->sema);
+       CloseHandle(cond->continue_broadcast);
+       DeleteCriticalSection(&cond->waiters_lock);
+       return 0;
+}
+
+int pthread_cond_wait(pthread_cond_t *cond, CRITICAL_SECTION *mutex)
+{
+       int last_waiter;
+
+       EnterCriticalSection(&cond->waiters_lock);
+       cond->waiters++;
+       LeaveCriticalSection(&cond->waiters_lock);
+
+       /*
+        * Unlock external mutex and wait for signal.
+        * NOTE: we've held mutex locked long enough to increment
+        * waiters count above, so there's no problem with
+        * leaving mutex unlocked before we wait on semaphore.
+        */
+       LeaveCriticalSection(mutex);
+
+       /* let's wait - ignore return value */
+       WaitForSingleObject(cond->sema, INFINITE);
+
+       /*
+        * Decrease waiters count. If we are the last waiter, then we must
+        * notify the broadcasting thread that it can continue.
+        * But if we continued due to cond_signal, we do not have to do that
+        * because the signaling thread knows that only one waiter continued.
+        */
+       EnterCriticalSection(&cond->waiters_lock);
+       cond->waiters--;
+       last_waiter = cond->was_broadcast && cond->waiters == 0;
+       LeaveCriticalSection(&cond->waiters_lock);
+
+       if (last_waiter) {
+               /*
+                * cond_broadcast was issued while mutex was held. This means
+                * that all other waiters have continued, but are contending
+                * for the mutex at the end of this function because the
+                * broadcasting thread did not leave cond_broadcast, yet.
+                * (This is so that it can be sure that each waiter has
+                * consumed exactly one slice of the semaphor.)
+                * The last waiter must tell the broadcasting thread that it
+                * can go on.
+                */
+               SetEvent(cond->continue_broadcast);
+               /*
+                * Now we go on to contend with all other waiters for
+                * the mutex. Auf in den Kampf!
+                */
+       }
+       /* lock external mutex again */
+       EnterCriticalSection(mutex);
+
+       return 0;
+}
+
+/*
+ * IMPORTANT: This implementation requires that pthread_cond_signal
+ * is called while the mutex is held that is used in the corresponding
+ * pthread_cond_wait calls!
+ */
+int pthread_cond_signal(pthread_cond_t *cond)
+{
+       int have_waiters;
+
+       EnterCriticalSection(&cond->waiters_lock);
+       have_waiters = cond->waiters > 0;
+       LeaveCriticalSection(&cond->waiters_lock);
+
+       /*
+        * Signal only when there are waiters
+        */
+       if (have_waiters)
+               return ReleaseSemaphore(cond->sema, 1, NULL) ?
+                       0 : err_win_to_posix(GetLastError());
+       else
+               return 0;
+}
+
+/*
+ * DOUBLY IMPORTANT: This implementation requires that pthread_cond_broadcast
+ * is called while the mutex is held that is used in the corresponding
+ * pthread_cond_wait calls!
+ */
+int pthread_cond_broadcast(pthread_cond_t *cond)
+{
+       EnterCriticalSection(&cond->waiters_lock);
+
+       if ((cond->was_broadcast = cond->waiters > 0)) {
+               /* wake up all waiters */
+               ReleaseSemaphore(cond->sema, cond->waiters, NULL);
+               LeaveCriticalSection(&cond->waiters_lock);
+               /*
+                * At this point all waiters continue. Each one takes its
+                * slice of the semaphor. Now it's our turn to wait: Since
+                * the external mutex is held, no thread can leave cond_wait,
+                * yet. For this reason, we can be sure that no thread gets
+                * a chance to eat *more* than one slice. OTOH, it means
+                * that the last waiter must send us a wake-up.
+                */
+               WaitForSingleObject(cond->continue_broadcast, INFINITE);
+               /*
+                * Since the external mutex is held, no thread can enter
+                * cond_wait, and, hence, it is safe to reset this flag
+                * without cond->waiters_lock held.
+                */
+               cond->was_broadcast = 0;
+       } else {
+               LeaveCriticalSection(&cond->waiters_lock);
+       }
+       return 0;
+}
diff --git a/compat/win32/pthread.h b/compat/win32/pthread.h
new file mode 100644 (file)
index 0000000..2e20548
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Header used to adapt pthread-based POSIX code to Windows API threads.
+ *
+ * Copyright (C) 2009 Andrzej K. Haczewski <ahaczewski@gmail.com>
+ */
+
+#ifndef PTHREAD_H
+#define PTHREAD_H
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+
+#include <windows.h>
+
+/*
+ * Defines that adapt Windows API threads to pthreads API
+ */
+#define pthread_mutex_t CRITICAL_SECTION
+
+#define pthread_mutex_init(a,b) (InitializeCriticalSection((a)), 0)
+#define pthread_mutex_destroy(a) DeleteCriticalSection((a))
+#define pthread_mutex_lock EnterCriticalSection
+#define pthread_mutex_unlock LeaveCriticalSection
+
+typedef int pthread_mutexattr_t;
+#define pthread_mutexattr_init(a) (*(a) = 0)
+#define pthread_mutexattr_destroy(a) do {} while (0)
+#define pthread_mutexattr_settype(a, t) 0
+#define PTHREAD_MUTEX_RECURSIVE 0
+
+/*
+ * Implement simple condition variable for Windows threads, based on ACE
+ * implementation.
+ *
+ * See original implementation: http://bit.ly/1vkDjo
+ * ACE homepage: http://www.cse.wustl.edu/~schmidt/ACE.html
+ * See also: http://www.cse.wustl.edu/~schmidt/win32-cv-1.html
+ */
+typedef struct {
+       LONG waiters;
+       int was_broadcast;
+       CRITICAL_SECTION waiters_lock;
+       HANDLE sema;
+       HANDLE continue_broadcast;
+} pthread_cond_t;
+
+extern int pthread_cond_init(pthread_cond_t *cond, const void *unused);
+extern int pthread_cond_destroy(pthread_cond_t *cond);
+extern int pthread_cond_wait(pthread_cond_t *cond, CRITICAL_SECTION *mutex);
+extern int pthread_cond_signal(pthread_cond_t *cond);
+extern int pthread_cond_broadcast(pthread_cond_t *cond);
+
+/*
+ * Simple thread creation implementation using pthread API
+ */
+typedef struct {
+       HANDLE handle;
+       void *(*start_routine)(void*);
+       void *arg;
+       DWORD tid;
+} pthread_t;
+
+extern int pthread_create(pthread_t *thread, const void *unused,
+                         void *(*start_routine)(void*), void *arg);
+
+/*
+ * To avoid the need of copying a struct, we use small macro wrapper to pass
+ * pointer to win32_pthread_join instead.
+ */
+#define pthread_join(a, b) win32_pthread_join(&(a), (b))
+
+extern int win32_pthread_join(pthread_t *thread, void **value_ptr);
+
+#define pthread_equal(t1, t2) ((t1).tid == (t2).tid)
+extern pthread_t pthread_self(void);
+
+static inline int pthread_exit(void *ret)
+{
+       ExitThread((DWORD)ret);
+}
+
+typedef DWORD pthread_key_t;
+static inline int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *value))
+{
+       return (*keyp = TlsAlloc()) == TLS_OUT_OF_INDEXES ? EAGAIN : 0;
+}
+
+static inline int pthread_setspecific(pthread_key_t key, const void *value)
+{
+       return TlsSetValue(key, (void *)value) ? 0 : EINVAL;
+}
+
+static inline void *pthread_getspecific(pthread_key_t key)
+{
+       return TlsGetValue(key);
+}
+
+#endif /* PTHREAD_H */
index 779d796cd5f017d4effbede360185a4690cf414e..b58aa69fa0609dad7f591024f9da31dfa58496fb 100644 (file)
@@ -1,34 +1,22 @@
 #include "../git-compat-util.h"
 
-/*
- * Note that this doesn't return the actual pagesize, but
- * the allocation granularity. If future Windows specific git code
- * needs the real getpagesize function, we need to find another solution.
- */
-int mingw_getpagesize(void)
-{
-       SYSTEM_INFO si;
-       GetSystemInfo(&si);
-       return si.dwAllocationGranularity;
-}
-
 void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)
 {
        HANDLE hmap;
        void *temp;
-       size_t len;
+       off_t len;
        struct stat st;
        uint64_t o = offset;
        uint32_t l = o & 0xFFFFFFFF;
        uint32_t h = (o >> 32) & 0xFFFFFFFF;
 
        if (!fstat(fd, &st))
-               len = xsize_t(st.st_size);
+               len = st.st_size;
        else
                die("mmap: could not determine filesize");
 
        if ((length + offset) > len)
-               length = len - offset;
+               length = xsize_t(len - offset);
 
        if (!(flags & MAP_PRIVATE))
                die("Invalid usage of mmap when built with USE_WIN32_MMAP");
index 44dc293ad314379d7835d9d96a5c3fd12ad2b27f..dedce2104eaf5cefbb1abef1b7921eb99c67a75e 100644 (file)
@@ -2,7 +2,6 @@
  * Copyright 2008 Peter Harris <git@peter.is-a-geek.org>
  */
 
-#include <windows.h>
 #include "../git-compat-util.h"
 
 /*
@@ -80,6 +79,7 @@ static void set_console_attr(void)
 static void erase_in_line(void)
 {
        CONSOLE_SCREEN_BUFFER_INFO sbi;
+       DWORD dummy; /* Needed for Windows 7 (or Vista) regression */
 
        if (!console)
                return;
@@ -87,7 +87,7 @@ static void erase_in_line(void)
        GetConsoleScreenBufferInfo(console, &sbi);
        FillConsoleOutputCharacterA(console, ' ',
                sbi.dwSize.X - sbi.dwCursorPosition.X, sbi.dwCursorPosition,
-               NULL);
+               &dummy);
 }
 
 
index 1682273c12ab042d73fa32caf30d18fb13ef85e3..4b0a82040e7982ab936ed52b2bb3405bc85b80f8 100644 (file)
--- a/config.c
+++ b/config.c
@@ -7,6 +7,8 @@
  */
 #include "cache.h"
 #include "exec_cmd.h"
+#include "strbuf.h"
+#include "quote.h"
 
 #define MAXNAME (256)
 
@@ -18,6 +20,92 @@ static int zlib_compression_seen;
 
 const char *config_exclusive_filename = NULL;
 
+struct config_item
+{
+       struct config_item *next;
+       char *name;
+       char *value;
+};
+static struct config_item *config_parameters;
+static struct config_item **config_parameters_tail = &config_parameters;
+
+static void lowercase(char *p)
+{
+       for (; *p; p++)
+               *p = tolower(*p);
+}
+
+void git_config_push_parameter(const char *text)
+{
+       struct strbuf env = STRBUF_INIT;
+       const char *old = getenv(CONFIG_DATA_ENVIRONMENT);
+       if (old) {
+               strbuf_addstr(&env, old);
+               strbuf_addch(&env, ' ');
+       }
+       sq_quote_buf(&env, text);
+       setenv(CONFIG_DATA_ENVIRONMENT, env.buf, 1);
+       strbuf_release(&env);
+}
+
+int git_config_parse_parameter(const char *text)
+{
+       struct config_item *ct;
+       struct strbuf tmp = STRBUF_INIT;
+       struct strbuf **pair;
+       strbuf_addstr(&tmp, text);
+       pair = strbuf_split(&tmp, '=');
+       if (pair[0]->len && pair[0]->buf[pair[0]->len - 1] == '=')
+               strbuf_setlen(pair[0], pair[0]->len - 1);
+       strbuf_trim(pair[0]);
+       if (!pair[0]->len) {
+               strbuf_list_free(pair);
+               return -1;
+       }
+       ct = xcalloc(1, sizeof(struct config_item));
+       ct->name = strbuf_detach(pair[0], NULL);
+       if (pair[1]) {
+               strbuf_trim(pair[1]);
+               ct->value = strbuf_detach(pair[1], NULL);
+       }
+       strbuf_list_free(pair);
+       lowercase(ct->name);
+       *config_parameters_tail = ct;
+       config_parameters_tail = &ct->next;
+       return 0;
+}
+
+int git_config_parse_environment(void) {
+       const char *env = getenv(CONFIG_DATA_ENVIRONMENT);
+       char *envw;
+       const char **argv = NULL;
+       int nr = 0, alloc = 0;
+       int i;
+
+       if (!env)
+               return 0;
+       /* sq_dequote will write over it */
+       envw = xstrdup(env);
+
+       if (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {
+               free(envw);
+               return error("bogus format in " CONFIG_DATA_ENVIRONMENT);
+       }
+
+       for (i = 0; i < nr; i++) {
+               if (git_config_parse_parameter(argv[i]) < 0) {
+                       error("bogus config parameter: %s", argv[i]);
+                       free(argv);
+                       free(envw);
+                       return -1;
+               }
+       }
+
+       free(argv);
+       free(envw);
+       return 0;
+}
+
 static int get_next_char(void)
 {
        int c;
@@ -62,7 +150,8 @@ static char *parse_value(void)
                if (comment)
                        continue;
                if (isspace(c) && !quote) {
-                       space = 1;
+                       if (len)
+                               space++;
                        continue;
                }
                if (!quote) {
@@ -71,11 +160,8 @@ static char *parse_value(void)
                                continue;
                        }
                }
-               if (space) {
-                       if (len)
-                               value[len++] = ' ';
-                       space = 0;
-               }
+               for (; space; space--)
+                       value[len++] = ' ';
                if (c == '\\') {
                        c = get_next_char();
                        switch (c) {
@@ -324,17 +410,30 @@ unsigned long git_config_ulong(const char *name, const char *value)
        return ret;
 }
 
-int git_config_bool_or_int(const char *name, const char *value, int *is_bool)
+int git_config_maybe_bool(const char *name, const char *value)
 {
-       *is_bool = 1;
        if (!value)
                return 1;
        if (!*value)
                return 0;
-       if (!strcasecmp(value, "true") || !strcasecmp(value, "yes") || !strcasecmp(value, "on"))
+       if (!strcasecmp(value, "true")
+           || !strcasecmp(value, "yes")
+           || !strcasecmp(value, "on"))
                return 1;
-       if (!strcasecmp(value, "false") || !strcasecmp(value, "no") || !strcasecmp(value, "off"))
+       if (!strcasecmp(value, "false")
+           || !strcasecmp(value, "no")
+           || !strcasecmp(value, "off"))
                return 0;
+       return -1;
+}
+
+int git_config_bool_or_int(const char *name, const char *value, int *is_bool)
+{
+       int v = git_config_maybe_bool(name, value);
+       if (0 <= v) {
+               *is_bool = 1;
+               return v;
+       }
        *is_bool = 0;
        return git_config_int(name, value);
 }
@@ -353,6 +452,16 @@ int git_config_string(const char **dest, const char *var, const char *value)
        return 0;
 }
 
+int git_config_pathname(const char **dest, const char *var, const char *value)
+{
+       if (!value)
+               return config_error_nonbool(var);
+       *dest = expand_user_path(value);
+       if (!*dest)
+               die("Failed to expand user dir in: '%s'", value);
+       return 0;
+}
+
 static int git_default_core_config(const char *var, const char *value)
 {
        /* This needs a better name */
@@ -453,7 +562,9 @@ static int git_default_core_config(const char *var, const char *value)
 
        if (!strcmp(var, "core.autocrlf")) {
                if (value && !strcasecmp(value, "input")) {
-                       auto_crlf = -1;
+                       if (eol == EOL_CRLF)
+                               return error("core.autocrlf=input conflicts with core.eol=crlf");
+                       auto_crlf = AUTO_CRLF_INPUT;
                        return 0;
                }
                auto_crlf = git_config_bool(var, value);
@@ -469,14 +580,36 @@ static int git_default_core_config(const char *var, const char *value)
                return 0;
        }
 
+       if (!strcmp(var, "core.eol")) {
+               if (value && !strcasecmp(value, "lf"))
+                       eol = EOL_LF;
+               else if (value && !strcasecmp(value, "crlf"))
+                       eol = EOL_CRLF;
+               else if (value && !strcasecmp(value, "native"))
+                       eol = EOL_NATIVE;
+               else
+                       eol = EOL_UNSET;
+               if (eol == EOL_CRLF && auto_crlf == AUTO_CRLF_INPUT)
+                       return error("core.autocrlf=input conflicts with core.eol=crlf");
+               return 0;
+       }
+
+       if (!strcmp(var, "core.notesref")) {
+               notes_ref_name = xstrdup(value);
+               return 0;
+       }
+
        if (!strcmp(var, "core.pager"))
                return git_config_string(&pager_program, var, value);
 
        if (!strcmp(var, "core.editor"))
                return git_config_string(&editor_program, var, value);
 
+       if (!strcmp(var, "core.askpass"))
+               return git_config_string(&askpass_program, var, value);
+
        if (!strcmp(var, "core.excludesfile"))
-               return git_config_string(&excludes_file, var, value);
+               return git_config_pathname(&excludes_file, var, value);
 
        if (!strcmp(var, "core.whitespace")) {
                if (!value)
@@ -505,6 +638,11 @@ static int git_default_core_config(const char *var, const char *value)
                return 0;
        }
 
+       if (!strcmp(var, "core.sparsecheckout")) {
+               core_apply_sparse_checkout = git_config_bool(var, value);
+               return 0;
+       }
+
        /* Add other config variables here and to Documentation/config.txt. */
        return 0;
 }
@@ -515,8 +653,7 @@ static int git_default_user_config(const char *var, const char *value)
                if (!value)
                        return config_error_nonbool(var);
                strlcpy(git_default_name, value, sizeof(git_default_name));
-               if (git_default_email[0])
-                       user_ident_explicitly_given = 1;
+               user_ident_explicitly_given |= IDENT_NAME_GIVEN;
                return 0;
        }
 
@@ -524,8 +661,7 @@ static int git_default_user_config(const char *var, const char *value)
                if (!value)
                        return config_error_nonbool(var);
                strlcpy(git_default_email, value, sizeof(git_default_email));
-               if (git_default_name[0])
-                       user_ident_explicitly_given = 1;
+               user_ident_explicitly_given |= IDENT_MAIL_GIVEN;
                return 0;
        }
 
@@ -629,6 +765,9 @@ int git_default_config(const char *var, const char *value, void *dummy)
        if (!prefixcmp(var, "mailmap."))
                return git_default_mailmap_config(var, value);
 
+       if (!prefixcmp(var, "advice."))
+               return git_default_advice_config(var, value);
+
        if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) {
                pager_use_color = git_config_bool(var,value);
                return 0;
@@ -664,7 +803,7 @@ const char *git_etc_gitconfig(void)
        return system_wide;
 }
 
-static int git_env_bool(const char *k, int def)
+int git_env_bool(const char *k, int def)
 {
        const char *v = getenv(k);
        return v ? git_config_bool(k, v) : def;
@@ -680,6 +819,22 @@ int git_config_global(void)
        return !git_env_bool("GIT_CONFIG_NOGLOBAL", 0);
 }
 
+int git_config_from_parameters(config_fn_t fn, void *data)
+{
+       static int loaded_environment;
+       const struct config_item *ct;
+
+       if (!loaded_environment) {
+               if (git_config_parse_environment() < 0)
+                       return -1;
+               loaded_environment = 1;
+       }
+       for (ct = config_parameters; ct; ct = ct->next)
+               if (fn(ct->name, ct->value, data) < 0)
+                       return -1;
+       return 0;
+}
+
 int git_config(config_fn_t fn, void *data)
 {
        int ret = 0, found = 0;
@@ -711,6 +866,11 @@ int git_config(config_fn_t fn, void *data)
                found += 1;
        }
        free(repo_config);
+
+       ret += git_config_from_parameters(fn, data);
+       if (config_parameters)
+               found += 1;
+
        if (found == 0)
                return -1;
        return ret;
@@ -1118,7 +1278,7 @@ int git_config_set_multivar(const char *key, const char *value,
                                    copy_end - copy_begin)
                                        goto write_err_out;
                                if (new_line &&
-                                   write_in_full(fd, "\n", 1) != 1)
+                                   write_str_in_full(fd, "\n") != 1)
                                        goto write_err_out;
                        }
                        copy_begin = store.offset[i];
@@ -1174,7 +1334,9 @@ write_err_out:
 static int section_name_match (const char *buf, const char *name)
 {
        int i = 0, j = 0, dot = 0;
-       for (; buf[i] && buf[i] != ']'; i++) {
+       if (buf[i] != '[')
+               return 0;
+       for (i = 1; buf[i] && buf[i] != ']'; i++) {
                if (!dot && isspace(buf[i])) {
                        dot = 1;
                        if (name[j++] != '.')
@@ -1195,7 +1357,17 @@ static int section_name_match (const char *buf, const char *name)
                if (buf[i] != name[j++])
                        break;
        }
-       return (buf[i] == ']' && name[j] == 0);
+       if (buf[i] == ']' && name[j] == 0) {
+               /*
+                * We match, now just find the right length offset by
+                * gobbling up any whitespace after it, as well
+                */
+               i++;
+               for (; buf[i] && isspace(buf[i]); i++)
+                       ; /* do nothing */
+               return i;
+       }
+       return 0;
 }
 
 /* if new_name == NULL, the section is removed instead */
@@ -1225,11 +1397,13 @@ int git_config_rename_section(const char *old_name, const char *new_name)
        while (fgets(buf, sizeof(buf), config_file)) {
                int i;
                int length;
+               char *output = buf;
                for (i = 0; buf[i] && isspace(buf[i]); i++)
                        ; /* do nothing */
                if (buf[i] == '[') {
                        /* it's a section */
-                       if (section_name_match (&buf[i+1], old_name)) {
+                       int offset = section_name_match(&buf[i], old_name);
+                       if (offset > 0) {
                                ret++;
                                if (new_name == NULL) {
                                        remove = 1;
@@ -1240,14 +1414,29 @@ int git_config_rename_section(const char *old_name, const char *new_name)
                                        ret = write_error(lock->filename);
                                        goto out;
                                }
-                               continue;
+                               /*
+                                * We wrote out the new section, with
+                                * a newline, now skip the old
+                                * section's length
+                                */
+                               output += offset + i;
+                               if (strlen(output) > 0) {
+                                       /*
+                                        * More content means there's
+                                        * a declaration to put on the
+                                        * next line; indent with a
+                                        * tab
+                                        */
+                                       output -= 1;
+                                       output[0] = '\t';
+                               }
                        }
                        remove = 0;
                }
                if (remove)
                        continue;
-               length = strlen(buf);
-               if (write_in_full(out_fd, buf, length) != length) {
+               length = strlen(output);
+               if (write_in_full(out_fd, output, length) != length) {
                        ret = write_error(lock->filename);
                        goto out;
                }
index 7cce0c12d507b222d47d8469abf59bf3ef9096b9..a0c34eec15773f0f3e4d2b4e12f0c76e722ede95 100644 (file)
@@ -3,10 +3,12 @@
 
 CC = @CC@
 CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
 LDFLAGS = @LDFLAGS@
 CC_LD_DYNPATH = @CC_LD_DYNPATH@
 AR = @AR@
 TAR = @TAR@
+DIFF = @DIFF@
 #INSTALL = @INSTALL@           # needs install-sh or install.sh in sources
 TCLTK_PATH = @TCLTK_PATH@
 
@@ -30,15 +32,21 @@ NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@
 NO_OPENSSL=@NO_OPENSSL@
 NO_CURL=@NO_CURL@
 NO_EXPAT=@NO_EXPAT@
+NO_LIBGEN_H=@NO_LIBGEN_H@
+HAVE_PATHS_H=@HAVE_PATHS_H@
 NEEDS_LIBICONV=@NEEDS_LIBICONV@
 NEEDS_SOCKET=@NEEDS_SOCKET@
+NEEDS_RESOLV=@NEEDS_RESOLV@
+NEEDS_LIBGEN=@NEEDS_LIBGEN@
 NO_SYS_SELECT_H=@NO_SYS_SELECT_H@
 NO_D_INO_IN_DIRENT=@NO_D_INO_IN_DIRENT@
 NO_D_TYPE_IN_DIRENT=@NO_D_TYPE_IN_DIRENT@
 NO_SOCKADDR_STORAGE=@NO_SOCKADDR_STORAGE@
 NO_IPV6=@NO_IPV6@
 NO_C99_FORMAT=@NO_C99_FORMAT@
+NO_HSTRERROR=@NO_HSTRERROR@
 NO_STRCASESTR=@NO_STRCASESTR@
+NO_STRTOK_R=@NO_STRTOK_R@
 NO_MEMMEM=@NO_MEMMEM@
 NO_STRLCPY=@NO_STRLCPY@
 NO_UINTMAX_T=@NO_UINTMAX_T@
@@ -46,11 +54,17 @@ NO_STRTOUMAX=@NO_STRTOUMAX@
 NO_SETENV=@NO_SETENV@
 NO_UNSETENV=@NO_UNSETENV@
 NO_MKDTEMP=@NO_MKDTEMP@
+NO_MKSTEMPS=@NO_MKSTEMPS@
+NO_INET_NTOP=@NO_INET_NTOP@
+NO_INET_PTON=@NO_INET_PTON@
 NO_ICONV=@NO_ICONV@
 OLD_ICONV=@OLD_ICONV@
+NO_REGEX=@NO_REGEX@
 NO_DEFLATE_BOUND=@NO_DEFLATE_BOUND@
+INLINE=@INLINE@
+SOCKLEN_T=@SOCKLEN_T@
 FREAD_READS_DIRECTORIES=@FREAD_READS_DIRECTORIES@
 SNPRINTF_RETURNS_BOGUS=@SNPRINTF_RETURNS_BOGUS@
 NO_PTHREADS=@NO_PTHREADS@
-THREADED_DELTA_SEARCH=@THREADED_DELTA_SEARCH@
+PTHREAD_CFLAGS=@PTHREAD_CFLAGS@
 PTHREAD_LIBS=@PTHREAD_LIBS@
index 4e728bca35f5f7b01188ef84df85b161266ce305..56731c35c9d1b5e2d2c82cdff51ab496dcbac7f5 100644 (file)
@@ -23,21 +23,32 @@ AC_DEFUN([GIT_CONF_APPEND_LINE],
 # GIT_ARG_SET_PATH(PROGRAM)
 # -------------------------
 # Provide --with-PROGRAM=PATH option to set PATH to PROGRAM
+# Optional second argument allows setting NO_PROGRAM=YesPlease if
+# --without-PROGRAM version used.
 AC_DEFUN([GIT_ARG_SET_PATH],
 [AC_ARG_WITH([$1],
  [AS_HELP_STRING([--with-$1=PATH],
                  [provide PATH to $1])],
- [GIT_CONF_APPEND_PATH($1)],[])
+ [GIT_CONF_APPEND_PATH($1,$2)],[])
 ])# GIT_ARG_SET_PATH
 #
 # GIT_CONF_APPEND_PATH(PROGRAM)
 # ------------------------------
 # Parse --with-PROGRAM=PATH option to set PROGRAM_PATH=PATH
 # Used by GIT_ARG_SET_PATH(PROGRAM)
+# Optional second argument allows setting NO_PROGRAM=YesPlease if
+# --without-PROGRAM is used.
 AC_DEFUN([GIT_CONF_APPEND_PATH],
 [PROGRAM=m4_toupper($1); \
 if test "$withval" = "no"; then \
-       AC_MSG_ERROR([You cannot use git without $1]); \
+       if test -n "$2"; then \
+               m4_toupper($1)_PATH=$withval; \
+               AC_MSG_NOTICE([Disabling use of ${PROGRAM}]); \
+               GIT_CONF_APPEND_LINE(NO_${PROGRAM}=YesPlease); \
+               GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=); \
+       else \
+               AC_MSG_ERROR([You cannot use git without $1]); \
+       fi; \
 else \
        if test "$withval" = "yes"; then \
                AC_MSG_WARN([You should provide path for --with-$1=PATH]); \
@@ -68,6 +79,26 @@ else \
        GIT_CONF_APPEND_LINE(${PACKAGE}DIR=$withval); \
 fi \
 ])# GIT_PARSE_WITH
+#
+# GIT_PARSE_WITH_SET_MAKE_VAR(WITHNAME, VAR, HELP_TEXT)
+# ---------------------
+# Set VAR to the value specied by --with-WITHNAME.
+# No verification of arguments is performed, but warnings are issued
+# if either 'yes' or 'no' is specified.
+# HELP_TEXT is presented when --help is called.
+# This is a direct way to allow setting variables in the Makefile.
+AC_DEFUN([GIT_PARSE_WITH_SET_MAKE_VAR],
+[AC_ARG_WITH([$1],
+ [AS_HELP_STRING([--with-$1=VALUE], $3)],
+ if test -n "$withval"; then \
+  if test "$withval" = "yes" -o "$withval" = "no"; then \
+    AC_MSG_WARN([You likely do not want either 'yes' or 'no' as]
+                    [a value for $1 ($2).  Maybe you do...?]); \
+  fi; \
+  \
+  AC_MSG_NOTICE([Setting $2 to $withval]); \
+  GIT_CONF_APPEND_LINE($2=$withval); \
+ fi)])# GIT_PARSE_WITH_SET_MAKE_VAR
 
 dnl
 dnl GIT_CHECK_FUNC(FUNCTION, IFTRUE, IFFALSE)
@@ -148,6 +179,26 @@ fi],
    AC_MSG_NOTICE([Will try -pthread then -lpthread to enable POSIX Threads.])
 ])
 
+# Define option to enable JavaScript minification
+AC_ARG_ENABLE([jsmin],
+[AS_HELP_STRING([--enable-jsmin=PATH],
+  [PATH is the name of a JavaScript minifier or the absolute path to one.])],
+[
+  JSMIN=$enableval;
+  AC_MSG_NOTICE([Setting JSMIN to '$JSMIN' to enable JavaScript minifying])
+  GIT_CONF_APPEND_LINE(JSMIN=$enableval);
+])
+
+# Define option to enable CSS minification
+AC_ARG_ENABLE([cssmin],
+[AS_HELP_STRING([--enable-cssmin=PATH],
+  [PATH is the name of a CSS minifier or the absolute path to one.])],
+[
+  CSSMIN=$enableval;
+  AC_MSG_NOTICE([Setting CSSMIN to '$CSSMIN' to enable CSS minifying])
+  GIT_CONF_APPEND_LINE(CSSMIN=$enableval);
+])
+
 ## Site configuration (override autodetection)
 ## --with-PACKAGE[=ARG] and --without-PACKAGE
 AC_MSG_NOTICE([CHECKS for site configuration])
@@ -156,19 +207,11 @@ AC_MSG_NOTICE([CHECKS for site configuration])
 # tests.  These tests take up a significant amount of the total test time
 # but are not needed unless you plan to talk to SVN repos.
 #
-# Define MOZILLA_SHA1 environment variable when running make to make use of
-# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast
-# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default
-# choice) has very fast version optimized for i586.
-#
 # Define PPC_SHA1 environment variable when running make to make use of
 # a bundled SHA1 routine optimized for PowerPC.
 #
-# Define ARM_SHA1 environment variable when running make to make use of
-# a bundled SHA1 routine optimized for ARM.
-#
 # Define NO_OPENSSL environment variable if you do not have OpenSSL.
-# This also implies MOZILLA_SHA1.
+# This also implies BLK_SHA1.
 #
 # Define OPENSSLDIR=/foo/bar if your openssl header and library files are in
 # /foo/bar/include and /foo/bar/lib directories.
@@ -234,6 +277,29 @@ GIT_PARSE_WITH(iconv))
 # Define USE_STDEV below if you want git to care about the underlying device
 # change being considered an inode change from the update-index perspective.
 
+#
+# Allow user to set ETC_GITCONFIG variable
+GIT_PARSE_WITH_SET_MAKE_VAR(gitconfig, ETC_GITCONFIG,
+                       Use VALUE instead of /etc/gitconfig as the
+                       global git configuration file.
+                       If VALUE is not fully qualified it will be interpretted
+                       as a path relative to the computed prefix at runtime.)
+
+#
+# Allow user to set the default pager
+GIT_PARSE_WITH_SET_MAKE_VAR(pager, DEFAULT_PAGER,
+                       Use VALUE as the fall-back pager instead of 'less'.
+                       This is used by things like 'git log' when the user
+                       does not specify a pager to use through alternate
+                       methods. eg: /usr/bin/pager)
+#
+# Allow user to set the default editor
+GIT_PARSE_WITH_SET_MAKE_VAR(editor, DEFAULT_EDITOR,
+                       Use VALUE as the fall-back editor instead of 'vi'.
+                       This is used by things like 'git commit' when the user
+                       does not specify a preferred editor through other
+                       methods. eg: /usr/bin/editor)
+
 #
 # Define SHELL_PATH to provide path to shell.
 GIT_ARG_SET_PATH(shell)
@@ -241,6 +307,9 @@ GIT_ARG_SET_PATH(shell)
 # Define PERL_PATH to provide path to Perl.
 GIT_ARG_SET_PATH(perl)
 #
+# Define PYTHON_PATH to provide path to Python.
+GIT_ARG_SET_PATH(python, allow-without)
+#
 # Define ZLIB_PATH to provide path to zlib.
 GIT_ARG_SET_PATH(zlib)
 #
@@ -258,6 +327,12 @@ GIT_PARSE_WITH(tcltk))
 AC_MSG_NOTICE([CHECKS for programs])
 #
 AC_PROG_CC([cc gcc])
+AC_C_INLINE
+case $ac_cv_c_inline in
+  inline | yes | no)   ;;
+  *)                   AC_SUBST([INLINE], [$ac_cv_c_inline]) ;;
+esac
+
 # which switch to pass runtime path to dynamic libraries to the linker
 AC_CACHE_CHECK([if linker supports -R], git_cv_ld_dashr, [
    SAVE_LDFLAGS="${LDFLAGS}"
@@ -293,6 +368,7 @@ fi
 #AC_PROG_INSTALL               # needs install-sh or install.sh in sources
 AC_CHECK_TOOLS(AR, [gar ar], :)
 AC_CHECK_PROGS(TAR, [gtar tar])
+AC_CHECK_PROGS(DIFF, [gnudiff gdiff diff])
 # TCLTK_PATH will be set to some value if we want Tcl/Tk
 # or will be empty otherwise.
 if test -z "$NO_TCLTK"; then
@@ -342,9 +418,8 @@ GIT_STASH_FLAGS($OPENSSLDIR)
 AC_CHECK_LIB([crypto], [SHA1_Init],
 [NEEDS_SSL_WITH_CRYPTO=],
 [AC_CHECK_LIB([ssl], [SHA1_Init],
- [NEEDS_SSL_WITH_CRYPTO=YesPlease
-  NEEDS_SSL_WITH_CRYPTO=],
- [NO_OPENSSL=YesPlease])])
+ [NEEDS_SSL_WITH_CRYPTO=YesPlease],
+ [NEEDS_SSL_WITH_CRYPTO= NO_OPENSSL=YesPlease])])
 
 GIT_UNSTASH_FLAGS($OPENSSLDIR)
 
@@ -385,6 +460,8 @@ AC_SUBST(NO_EXPAT)
 # some Solaris installations).
 # Define NO_ICONV if neither libc nor libiconv support iconv.
 
+if test -z "$NO_ICONV"; then
+
 GIT_STASH_FLAGS($ICONVDIR)
 
 AC_DEFUN([ICONVTEST_SRC], [
@@ -431,6 +508,12 @@ GIT_UNSTASH_FLAGS($ICONVDIR)
 AC_SUBST(NEEDS_LIBICONV)
 AC_SUBST(NO_ICONV)
 
+if test -n "$NO_ICONV"; then
+    NEEDS_LIBICONV=
+fi
+
+fi
+
 #
 # Define NO_DEFLATE_BOUND if deflateBound is missing from zlib.
 
@@ -467,6 +550,55 @@ AC_CHECK_LIB([c], [socket],
 AC_SUBST(NEEDS_SOCKET)
 test -n "$NEEDS_SOCKET" && LIBS="$LIBS -lsocket"
 
+#
+# The next few tests will define NEEDS_RESOLV if linking with
+# libresolv provides some of the functions we would normally get
+# from libc.
+NEEDS_RESOLV=
+AC_SUBST(NEEDS_RESOLV)
+#
+# Define NO_INET_NTOP if linking with -lresolv is not enough.
+# Solaris 2.7 in particular hos inet_ntop in -lresolv.
+NO_INET_NTOP=
+AC_SUBST(NO_INET_NTOP)
+AC_CHECK_FUNC([inet_ntop],
+       [],
+    [AC_CHECK_LIB([resolv], [inet_ntop],
+           [NEEDS_RESOLV=YesPlease],
+       [NO_INET_NTOP=YesPlease])
+])
+#
+# Define NO_INET_PTON if linking with -lresolv is not enough.
+# Solaris 2.7 in particular hos inet_pton in -lresolv.
+NO_INET_PTON=
+AC_SUBST(NO_INET_PTON)
+AC_CHECK_FUNC([inet_pton],
+       [],
+    [AC_CHECK_LIB([resolv], [inet_pton],
+           [NEEDS_RESOLV=YesPlease],
+       [NO_INET_PTON=YesPlease])
+])
+#
+# Define NO_HSTRERROR if linking with -lresolv is not enough.
+# Solaris 2.6 in particular has no hstrerror, even in -lresolv.
+NO_HSTRERROR=
+AC_CHECK_FUNC([hstrerror],
+       [],
+    [AC_CHECK_LIB([resolv], [hstrerror],
+           [NEEDS_RESOLV=YesPlease],
+       [NO_HSTRERROR=YesPlease])
+])
+AC_SUBST(NO_HSTRERROR)
+#
+# If any of the above tests determined that -lresolv is needed at
+# build-time, also set it here for remaining configure-time checks.
+test -n "$NEEDS_RESOLV" && LIBS="$LIBS -lresolv"
+
+AC_CHECK_LIB([c], [basename],
+[NEEDS_LIBGEN=],
+[NEEDS_LIBGEN=YesPlease])
+AC_SUBST(NEEDS_LIBGEN)
+test -n "$NEEDS_LIBGEN" && LIBS="$LIBS -lgen"
 
 ## Checks for header files.
 AC_MSG_NOTICE([CHECKS for header files])
@@ -507,6 +639,12 @@ AC_SUBST(OLD_ICONV)
 ## Checks for typedefs, structures, and compiler characteristics.
 AC_MSG_NOTICE([CHECKS for typedefs, structures, and compiler characteristics])
 #
+TYPE_SOCKLEN_T
+case $ac_cv_type_socklen_t in
+  yes) ;;
+  *)   AC_SUBST([SOCKLEN_T], [$git_cv_socklen_t_equiv]) ;;
+esac
+
 # Define NO_D_INO_IN_DIRENT if you don't have d_ino in your struct dirent.
 AC_CHECK_MEMBER(struct dirent.d_ino,
 [NO_D_INO_IN_DIRENT=],
@@ -568,6 +706,27 @@ else
 fi
 AC_SUBST(NO_C99_FORMAT)
 #
+# Define NO_REGEX if you have no or inferior regex support in your C library.
+AC_CACHE_CHECK([whether the platform regex can handle null bytes],
+ [ac_cv_c_excellent_regex], [
+AC_EGREP_CPP(yippeeyeswehaveit,
+       AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT
+#include <regex.h>
+],
+[#ifdef REG_STARTEND
+yippeeyeswehaveit
+#endif
+]),
+       [ac_cv_c_excellent_regex=yes],
+       [ac_cv_c_excellent_regex=no])
+])
+if test $ac_cv_c_excellent_regex = yes; then
+       NO_REGEX=
+else
+       NO_REGEX=YesPlease
+fi
+AC_SUBST(NO_REGEX)
+#
 # Define FREAD_READS_DIRECTORIES if your are on a system which succeeds
 # when attempting to read from an fopen'ed directory.
 AC_CACHE_CHECK([whether system succeeds to read fopen'ed directory],
@@ -627,12 +786,30 @@ AC_SUBST(SNPRINTF_RETURNS_BOGUS)
 ## (in default C library and libraries checked by AC_CHECK_LIB)
 AC_MSG_NOTICE([CHECKS for library functions])
 #
+# Define NO_LIBGEN_H if you don't have libgen.h.
+AC_CHECK_HEADER([libgen.h],
+[NO_LIBGEN_H=],
+[NO_LIBGEN_H=YesPlease])
+AC_SUBST(NO_LIBGEN_H)
+#
+# Define HAVE_PATHS_H if you have paths.h.
+AC_CHECK_HEADER([paths.h],
+[HAVE_PATHS_H=YesPlease],
+[HAVE_PATHS_H=])
+AC_SUBST(HAVE_PATHS_H)
+#
 # Define NO_STRCASESTR if you don't have strcasestr.
 GIT_CHECK_FUNC(strcasestr,
 [NO_STRCASESTR=],
 [NO_STRCASESTR=YesPlease])
 AC_SUBST(NO_STRCASESTR)
 #
+# Define NO_STRTOK_R if you don't have strtok_r
+GIT_CHECK_FUNC(strtok_r,
+[NO_STRTOK_R=],
+[NO_STRTOK_R=YesPlease])
+AC_SUBST(NO_STRTOK_R)
+#
 # Define NO_MEMMEM if you don't have memmem.
 GIT_CHECK_FUNC(memmem,
 [NO_MEMMEM=],
@@ -677,6 +854,13 @@ GIT_CHECK_FUNC(mkdtemp,
 [NO_MKDTEMP=YesPlease])
 AC_SUBST(NO_MKDTEMP)
 #
+# Define NO_MKSTEMPS if you don't have mkstemps in the C library.
+GIT_CHECK_FUNC(mkstemps,
+[NO_MKSTEMPS=],
+[NO_MKSTEMPS=YesPlease])
+AC_SUBST(NO_MKSTEMPS)
+#
+#
 # Define NO_MMAP if you want to avoid mmap.
 #
 # Define NO_ICONV if your libc does not properly support iconv.
@@ -689,17 +873,20 @@ AC_SUBST(NO_MKDTEMP)
 # 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 NO_PTHREADS if we do not have pthreads
+# Define NO_PTHREADS if we do not have pthreads.
 #
-# Define PTHREAD_LIBS to the linker flag used for Pthread support and define
-# THREADED_DELTA_SEARCH if Pthreads are available.
+# Define PTHREAD_LIBS to the linker flag used for Pthread support.
 AC_DEFUN([PTHREADTEST_SRC], [
 #include <pthread.h>
 
 int main(void)
 {
        pthread_mutex_t test_mutex;
-       return (0);
+       int retcode = 0;
+       retcode |= pthread_mutex_init(&test_mutex,(void *)0);
+       retcode |= pthread_mutex_lock(&test_mutex);
+       retcode |= pthread_mutex_unlock(&test_mutex);
+       return retcode;
 }
 ])
 
@@ -709,7 +896,6 @@ dnl   [[pthread_mutex_t test_mutex;]]
 dnl )])
 
 NO_PTHREADS=UnfortunatelyYes
-THREADED_DELTA_SEARCH=
 PTHREAD_LIBS=
 
 if test -n "$USER_NOPTHREAD"; then
@@ -717,7 +903,8 @@ if test -n "$USER_NOPTHREAD"; then
 # handle these separately since PTHREAD_CFLAGS could be '-lpthreads
 # -D_REENTRANT' or some such.
 elif test -z "$PTHREAD_CFLAGS"; then
-  for opt in -pthread -lpthread; do
+  threads_found=no
+  for opt in -mt -pthread -lpthread; do
      old_CFLAGS="$CFLAGS"
      CFLAGS="$opt $CFLAGS"
      AC_MSG_CHECKING([Checking for POSIX Threads with '$opt'])
@@ -725,12 +912,18 @@ elif test -z "$PTHREAD_CFLAGS"; then
        [AC_MSG_RESULT([yes])
                NO_PTHREADS=
                PTHREAD_LIBS="$opt"
-               THREADED_DELTA_SEARCH=YesPlease
+               PTHREAD_CFLAGS="$opt"
+               threads_found=yes
                break
        ],
        [AC_MSG_RESULT([no])])
       CFLAGS="$old_CFLAGS"
   done
+  if test $threads_found != yes; then
+    AC_CHECK_LIB([pthread], [pthread_create],
+       [PTHREAD_LIBS="-lpthread"],
+       [NO_PTHREADS=UnfortunatelyYes])
+  fi
 else
   old_CFLAGS="$CFLAGS"
   CFLAGS="$PTHREAD_CFLAGS $CFLAGS"
@@ -739,7 +932,6 @@ else
        [AC_MSG_RESULT([yes])
                NO_PTHREADS=
                PTHREAD_LIBS="$PTHREAD_CFLAGS"
-               THREADED_DELTA_SEARCH=YesPlease
        ],
        [AC_MSG_RESULT([no])])
 
@@ -748,9 +940,9 @@ fi
 
 CFLAGS="$old_CFLAGS"
 
+AC_SUBST(PTHREAD_CFLAGS)
 AC_SUBST(PTHREAD_LIBS)
 AC_SUBST(NO_PTHREADS)
-AC_SUBST(THREADED_DELTA_SEARCH)
 
 ## Output files
 AC_CONFIG_FILES(["${config_file}":"${config_in}":"${config_append}"])
index f6b8ba6fec16ed2ff2de4c66e2465e79d059a817..3450cabd0e3281e0eeedeb35d331b95bf95f5afb 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -5,6 +5,7 @@
 #include "refs.h"
 #include "run-command.h"
 #include "remote.h"
+#include "url.h"
 
 static char *server_capabilities;
 
@@ -107,27 +108,6 @@ int server_supports(const char *feature)
                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 (!prefixcmp(line, "ACK ")) {
-               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;
@@ -152,7 +132,7 @@ int path_match(const char *path, int nr, char **match)
 enum protocol {
        PROTO_LOCAL = 1,
        PROTO_SSH,
-       PROTO_GIT,
+       PROTO_GIT
 };
 
 static enum protocol get_protocol(const char *name)
@@ -173,6 +153,28 @@ static enum protocol get_protocol(const char *name)
 #define STR_(s)        # s
 #define STR(s) STR_(s)
 
+static void get_host_and_port(char **host, const char **port)
+{
+       char *colon, *end;
+
+       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;
+       }
+}
+
 #ifndef NO_IPV6
 
 static const char *ai_name(const struct addrinfo *ai)
@@ -191,30 +193,14 @@ static const char *ai_name(const struct addrinfo *ai)
 static int git_tcp_connect_sock(char *host, int flags)
 {
        int sockfd = -1, saved_errno = 0;
-       char *colon, *end;
        const char *port = STR(DEFAULT_GIT_PORT);
        struct addrinfo hints, *ai0, *ai;
        int gai;
        int cnt = 0;
 
-       if (host[0] == '[') {
-               end = strchr(host + 1, ']');
-               if (end) {
-                       *end = 0;
-                       end++;
-                       host++;
-               } else
-                       end = host;
-       } else
-               end = host;
-       colon = strchr(end, ':');
-
-       if (colon) {
-               *colon = 0;
-               port = colon + 1;
-               if (!*port)
-                       port = "<none>";
-       }
+       get_host_and_port(&host, &port);
+       if (!*port)
+               port = "<none>";
 
        memset(&hints, 0, sizeof(hints));
        hints.ai_socktype = SOCK_STREAM;
@@ -272,30 +258,15 @@ static int git_tcp_connect_sock(char *host, int flags)
 static int git_tcp_connect_sock(char *host, int flags)
 {
        int sockfd = -1, saved_errno = 0;
-       char *colon, *end;
-       char *port = STR(DEFAULT_GIT_PORT), *ep;
+       const char *port = STR(DEFAULT_GIT_PORT);
+       char *ep;
        struct hostent *he;
        struct sockaddr_in sa;
        char **ap;
        unsigned int nport;
        int cnt;
 
-       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;
-       }
+       get_host_and_port(&host, &port);
 
        if (flags & CONNECT_VERBOSE)
                fprintf(stderr, "Looking up %s ... ", host);
@@ -427,26 +398,10 @@ static int git_use_proxy(const char *host)
 static void git_proxy_connect(int fd[2], char *host)
 {
        const char *port = STR(DEFAULT_GIT_PORT);
-       char *colon, *end;
        const char *argv[4];
        struct child_process proxy;
 
-       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;
-       }
+       get_host_and_port(&host, &port);
 
        argv[0] = git_proxy_command;
        argv[1] = host;
@@ -464,7 +419,7 @@ static void git_proxy_connect(int fd[2], char *host)
 
 #define MAX_CMD_LEN 1024
 
-char *get_port(char *host)
+static char *get_port(char *host)
 {
        char *end;
        char *p = strchr(host, ':');
@@ -496,7 +451,7 @@ static struct child_process no_fork;
 struct child_process *git_connect(int fd[2], const char *url_orig,
                                  const char *prog, int flags)
 {
-       char *url = xstrdup(url_orig);
+       char *url;
        char *host, *path;
        char *end;
        int c;
@@ -512,8 +467,13 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
         */
        signal(SIGCHLD, SIG_DFL);
 
+       if (is_url(url_orig))
+               url = url_decode(url_orig);
+       else
+               url = xstrdup(url_orig);
+
        host = strstr(url, "://");
-       if(host) {
+       if (host) {
                *host = '\0';
                protocol = get_protocol(url);
                host += 3;
@@ -523,12 +483,18 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
                c = ':';
        }
 
+       /*
+        * Don't do destructive transforms with git:// as that
+        * protocol code does '[]' unwrapping of its own.
+        */
        if (host[0] == '[') {
                end = strchr(host + 1, ']');
                if (end) {
-                       *end = 0;
+                       if (protocol != PROTO_GIT) {
+                               *end = 0;
+                               host++;
+                       }
                        end++;
-                       host++;
                } else
                        end = host;
        } else
@@ -579,7 +545,10 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
                        git_tcp_connect(fd, host, flags);
                /*
                 * Separate original protocol components prog and path
-                * from extended components with a NUL byte.
+                * from extended host header with a NUL byte.
+                *
+                * Note: Do not add any other headers here!  Doing so
+                * will cause older git-daemon servers to crash.
                 */
                packet_write(fd[1],
                             "%s %s%chost=%s%c",
@@ -602,32 +571,26 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
                die("command line too long");
 
        conn->in = conn->out = -1;
-       conn->argv = arg = xcalloc(6, sizeof(*arg));
+       conn->argv = arg = xcalloc(7, sizeof(*arg));
        if (protocol == PROTO_SSH) {
                const char *ssh = getenv("GIT_SSH");
+               int putty = ssh && strcasestr(ssh, "plink");
                if (!ssh) ssh = "ssh";
 
                *arg++ = ssh;
+               if (putty && !strcasestr(ssh, "tortoiseplink"))
+                       *arg++ = "-batch";
                if (port) {
-                       *arg++ = "-p";
+                       /* P is for PuTTY, p is for OpenSSH */
+                       *arg++ = putty ? "-P" : "-p";
                        *arg++ = port;
                }
                *arg++ = host;
        }
        else {
-               /* remove these from the environment */
-               const char *env[] = {
-                       ALTERNATE_DB_ENVIRONMENT,
-                       DB_ENVIRONMENT,
-                       GIT_DIR_ENVIRONMENT,
-                       GIT_WORK_TREE_ENVIRONMENT,
-                       GRAFT_ENVIRONMENT,
-                       INDEX_ENVIRONMENT,
-                       NULL
-               };
-               conn->env = env;
-               *arg++ = "sh";
-               *arg++ = "-c";
+               /* remove repo-local variables from the environment */
+               conn->env = local_repo_env;
+               conn->use_shell = 1;
        }
        *arg++ = cmd.buf;
        *arg = NULL;
@@ -655,3 +618,43 @@ int finish_connect(struct child_process *conn)
        free(conn);
        return code;
 }
+
+char *git_getpass(const char *prompt)
+{
+       const char *askpass;
+       struct child_process pass;
+       const char *args[3];
+       static struct strbuf buffer = STRBUF_INIT;
+
+       askpass = getenv("GIT_ASKPASS");
+       if (!askpass)
+               askpass = askpass_program;
+       if (!askpass)
+               askpass = getenv("SSH_ASKPASS");
+       if (!askpass || !(*askpass))
+               return getpass(prompt);
+
+       args[0] = askpass;
+       args[1] = prompt;
+       args[2] = NULL;
+
+       memset(&pass, 0, sizeof(pass));
+       pass.argv = args;
+       pass.out = -1;
+
+       if (start_command(&pass))
+               exit(1);
+
+       strbuf_reset(&buffer);
+       if (strbuf_read(&buffer, pass.out, 20) < 0)
+               die("failed to read password from %s\n", askpass);
+
+       close(pass.out);
+
+       if (finish_command(&pass))
+               exit(1);
+
+       strbuf_setlen(&buffer, strcspn(buffer.buf, "\r\n"));
+
+       return buffer.buf;
+}
diff --git a/contrib/buildsystems/Generators.pm b/contrib/buildsystems/Generators.pm
new file mode 100644 (file)
index 0000000..408ef71
--- /dev/null
@@ -0,0 +1,42 @@
+package Generators;
+require Exporter;
+
+use strict;
+use File::Basename;
+no strict 'refs';
+use vars qw($VERSION @AVAILABLE);
+
+our $VERSION = '1.00';
+our(@ISA, @EXPORT, @EXPORT_OK, @AVAILABLE);
+@ISA = qw(Exporter);
+
+BEGIN {
+    local(*D);
+    my $me = $INC{"Generators.pm"};
+    die "Couldn't find myself in \@INC, which is required to load the generators!" if ("$me" eq "");
+    $me = dirname($me);
+    if (opendir(D,"$me/Generators")) {
+        foreach my $gen (readdir(D)) {
+            next if ($gen  =~ /^\.\.?$/);
+            require "${me}/Generators/$gen";
+            $gen =~ s,\.pm,,;
+            push(@AVAILABLE, $gen);
+        }
+        closedir(D);
+        my $gens = join(', ', @AVAILABLE);
+    }
+
+    push @EXPORT_OK, qw(available);
+}
+
+sub available {
+    return @AVAILABLE;
+}
+
+sub generate {
+    my ($gen, $git_dir, $out_dir, $rel_dir, %build_structure) = @_;
+    return eval("Generators::${gen}::generate(\$git_dir, \$out_dir, \$rel_dir, \%build_structure)") if grep(/^$gen$/, @AVAILABLE);
+    die "Generator \"${gen}\" is not available!\nAvailable generators are: @AVAILABLE\n";
+}
+
+1;
diff --git a/contrib/buildsystems/Generators/QMake.pm b/contrib/buildsystems/Generators/QMake.pm
new file mode 100644 (file)
index 0000000..ff3b657
--- /dev/null
@@ -0,0 +1,189 @@
+package Generators::QMake;
+require Exporter;
+
+use strict;
+use vars qw($VERSION);
+
+our $VERSION = '1.00';
+our(@ISA, @EXPORT, @EXPORT_OK, @AVAILABLE);
+@ISA = qw(Exporter);
+
+BEGIN {
+    push @EXPORT_OK, qw(generate);
+}
+
+sub generate {
+    my ($git_dir, $out_dir, $rel_dir, %build_structure) = @_;
+
+    my @libs = @{$build_structure{"LIBS"}};
+    foreach (@libs) {
+        createLibProject($_, $git_dir, $out_dir, $rel_dir, %build_structure);
+    }
+
+    my @apps = @{$build_structure{"APPS"}};
+    foreach (@apps) {
+        createAppProject($_, $git_dir, $out_dir, $rel_dir, %build_structure);
+    }
+
+    createGlueProject($git_dir, $out_dir, $rel_dir, %build_structure);
+    return 0;
+}
+
+sub createLibProject {
+    my ($libname, $git_dir, $out_dir, $rel_dir, %build_structure) = @_;
+    print "Generate $libname lib project\n";
+    $rel_dir = "../$rel_dir";
+
+    my $sources = join(" \\\n\t", sort(map("$rel_dir/$_", @{$build_structure{"LIBS_${libname}_SOURCES"}})));
+    my $defines = join(" \\\n\t", sort(@{$build_structure{"LIBS_${libname}_DEFINES"}}));
+    my $includes= join(" \\\n\t", sort(map("$rel_dir/$_", @{$build_structure{"LIBS_${libname}_INCLUDES"}})));
+    my $cflags  = join(" ", sort(@{$build_structure{"LIBS_${libname}_CFLAGS"}}));
+
+    my $cflags_debug = $cflags;
+    $cflags_debug =~ s/-MT/-MTd/;
+    $cflags_debug =~ s/-O.//;
+
+    my $cflags_release = $cflags;
+    $cflags_release =~ s/-MTd/-MT/;
+
+    my @tmp  = @{$build_structure{"LIBS_${libname}_LFLAGS"}};
+    my @tmp2 = ();
+    foreach (@tmp) {
+        if (/^-LTCG/) {
+        } elsif (/^-L/) {
+            $_ =~ s/^-L/-LIBPATH:$rel_dir\//;
+        }
+        push(@tmp2, $_);
+    }
+    my $lflags = join(" ", sort(@tmp));
+
+    my $target = $libname;
+    $target =~ s/\//_/g;
+    $defines =~ s/-D//g;
+    $defines =~ s/"/\\\\"/g;
+    $includes =~ s/-I//g;
+    mkdir "$target" || die "Could not create the directory $target for lib project!\n";
+    open F, ">$target/$target.pro" || die "Could not open $target/$target.pro for writing!\n";
+    print F << "EOM";
+TEMPLATE = lib
+TARGET = $target
+DESTDIR = $rel_dir
+
+CONFIG -= qt
+CONFIG += static
+
+QMAKE_CFLAGS =
+QMAKE_CFLAGS_RELEASE = $cflags_release
+QMAKE_CFLAGS_DEBUG = $cflags_debug
+QMAKE_LIBFLAGS = $lflags
+
+DEFINES += \\
+        $defines
+
+INCLUDEPATH += \\
+        $includes
+
+SOURCES += \\
+        $sources
+EOM
+    close F;
+}
+
+sub createAppProject {
+    my ($appname, $git_dir, $out_dir, $rel_dir, %build_structure) = @_;
+    print "Generate $appname app project\n";
+    $rel_dir = "../$rel_dir";
+
+    my $sources = join(" \\\n\t", sort(map("$rel_dir/$_", @{$build_structure{"APPS_${appname}_SOURCES"}})));
+    my $defines = join(" \\\n\t", sort(@{$build_structure{"APPS_${appname}_DEFINES"}}));
+    my $includes= join(" \\\n\t", sort(map("$rel_dir/$_", @{$build_structure{"APPS_${appname}_INCLUDES"}})));
+    my $cflags  = join(" ", sort(@{$build_structure{"APPS_${appname}_CFLAGS"}}));
+
+    my $cflags_debug = $cflags;
+    $cflags_debug =~ s/-MT/-MTd/;
+    $cflags_debug =~ s/-O.//;
+
+    my $cflags_release = $cflags;
+    $cflags_release =~ s/-MTd/-MT/;
+
+    my $libs;
+    foreach (sort(@{$build_structure{"APPS_${appname}_LIBS"}})) {
+        $_ =~ s/\//_/g;
+        $libs .= " $_";
+    }
+    my @tmp  = @{$build_structure{"APPS_${appname}_LFLAGS"}};
+    my @tmp2 = ();
+    foreach (@tmp) {
+        # next if ($_ eq "-NODEFAULTLIB:MSVCRT.lib");
+        if (/^-LTCG/) {
+        } elsif (/^-L/) {
+            $_ =~ s/^-L/-LIBPATH:$rel_dir\//;
+        }
+        push(@tmp2, $_);
+    }
+    my $lflags = join(" ", sort(@tmp));
+
+    my $target = $appname;
+    $target =~ s/\.exe//;
+    $target =~ s/\//_/g;
+    $defines =~ s/-D//g;
+    $defines =~ s/"/\\\\"/g;
+    $includes =~ s/-I//g;
+    mkdir "$target" || die "Could not create the directory $target for app project!\n";
+    open F, ">$target/$target.pro" || die "Could not open $target/$target.pro for writing!\n";
+    print F << "EOM";
+TEMPLATE = app
+TARGET = $target
+DESTDIR = $rel_dir
+
+CONFIG -= qt embed_manifest_exe
+CONFIG += console
+
+QMAKE_CFLAGS =
+QMAKE_CFLAGS_RELEASE = $cflags_release
+QMAKE_CFLAGS_DEBUG = $cflags_debug
+QMAKE_LFLAGS = $lflags
+LIBS   = $libs
+
+DEFINES += \\
+        $defines
+
+INCLUDEPATH += \\
+        $includes
+
+win32:QMAKE_LFLAGS += -LIBPATH:$rel_dir
+else: QMAKE_LFLAGS += -L$rel_dir
+
+SOURCES += \\
+        $sources
+EOM
+    close F;
+}
+
+sub createGlueProject {
+    my ($git_dir, $out_dir, $rel_dir, %build_structure) = @_;
+    my $libs = join(" \\ \n", map("\t$_|$_.pro", @{$build_structure{"LIBS"}}));
+    my $apps = join(" \\ \n", map("\t$_|$_.pro", @{$build_structure{"APPS"}}));
+    $libs =~ s/\.a//g;
+    $libs =~ s/\//_/g;
+    $libs =~ s/\|/\//g;
+    $apps =~ s/\.exe//g;
+    $apps =~ s/\//_/g;
+    $apps =~ s/\|/\//g;
+
+    my $filename = $out_dir;
+    $filename =~ s/.*\/([^\/]+)$/$1/;
+    $filename =~ s/\/$//;
+    print "Generate glue project $filename.pro\n";
+    open F, ">$filename.pro" || die "Could not open $filename.pro for writing!\n";
+    print F << "EOM";
+TEMPLATE = subdirs
+CONFIG += ordered
+SUBDIRS += \\
+$libs \\
+$apps
+EOM
+    close F;
+}
+
+1;
diff --git a/contrib/buildsystems/Generators/Vcproj.pm b/contrib/buildsystems/Generators/Vcproj.pm
new file mode 100644 (file)
index 0000000..cfa74ad
--- /dev/null
@@ -0,0 +1,626 @@
+package Generators::Vcproj;
+require Exporter;
+
+use strict;
+use vars qw($VERSION);
+
+our $VERSION = '1.00';
+our(@ISA, @EXPORT, @EXPORT_OK, @AVAILABLE);
+@ISA = qw(Exporter);
+
+BEGIN {
+    push @EXPORT_OK, qw(generate);
+}
+
+my $guid_index = 0;
+my @GUIDS = (
+    "{E07B9989-2BF7-4F21-8918-BE22BA467AC3}",
+    "{278FFB51-0296-4A44-A81A-22B87B7C3592}",
+    "{7346A2C4-F0FD-444F-9EBE-1AF23B2B5650}",
+    "{67F421AC-EB34-4D49-820B-3196807B423F}",
+    "{385DCFE1-CC8C-4211-A451-80FCFC31CA51}",
+    "{97CC46C5-D2CC-4D26-B634-E75792B79916}",
+    "{C7CE21FE-6EF8-4012-A5C7-A22BCEDFBA11}",
+    "{51575134-3FDF-42D1-BABD-3FB12669C6C9}",
+    "{0AE195E4-9823-4B87-8E6F-20C5614AF2FF}",
+    "{4B918255-67CA-43BB-A46C-26704B666E6B}",
+    "{18CCFEEF-C8EE-4CC1-A265-26F95C9F4649}",
+    "{5D5D90FA-01B7-4973-AFE5-CA88C53AC197}",
+    "{1F054320-036D-49E1-B384-FB5DF0BC8AC0}",
+    "{7CED65EE-F2D9-4171-825B-C7D561FE5786}",
+    "{8D341679-0F07-4664-9A56-3BA0DE88B9BC}",
+    "{C189FEDC-2957-4BD7-9FA4-7622241EA145}",
+    "{66844203-1B9F-4C53-9274-164FFF95B847}",
+    "{E4FEA145-DECC-440D-AEEA-598CF381FD43}",
+    "{73300A8E-C8AC-41B0-B555-4F596B681BA7}",
+    "{873FDEB1-D01D-40BF-A1BF-8BBC58EC0F51}",
+    "{7922C8BE-76C5-4AC6-8BF7-885C0F93B782}",
+    "{E245D370-308B-4A49-BFC1-1E527827975F}",
+    "{F6FA957B-66FC-4ED7-B260-E59BBE4FE813}",
+    "{E6055070-0198-431A-BC49-8DB6CEE770AE}",
+    "{54159234-C3EB-43DA-906B-CE5DA5C74654}",
+    "{594CFC35-0B60-46F6-B8EF-9983ACC1187D}",
+    "{D93FCAB7-1F01-48D2-B832-F761B83231A5}",
+    "{DBA5E6AC-E7BE-42D3-8703-4E787141526E}",
+    "{6171953F-DD26-44C7-A3BE-CC45F86FC11F}",
+    "{9E19DDBE-F5E4-4A26-A2FE-0616E04879B8}",
+    "{AE81A615-99E3-4885-9CE0-D9CAA193E867}",
+    "{FBF4067E-1855-4F6C-8BCD-4D62E801A04D}",
+    "{17007948-6593-4AEB-8106-F7884B4F2C19}",
+    "{199D4C8D-8639-4DA6-82EF-08668C35DEE0}",
+    "{E085E50E-C140-4CF3-BE4B-094B14F0DDD6}",
+    "{00785268-A9CC-4E40-AC29-BAC0019159CE}",
+    "{4C06F56A-DCDB-46A6-B67C-02339935CF12}",
+    "{3A62D3FD-519E-4EC9-8171-D2C1BFEA022F}",
+    "{3A62D3FD-519E-4EC9-8171-D2C1BFEA022F}",
+    "{9392EB58-D7BA-410B-B1F0-B2FAA6BC89A7}",
+    "{2ACAB2D5-E0CE-4027-BCA0-D78B2D7A6C66}",
+    "{86E216C3-43CE-481A-BCB2-BE5E62850635}",
+    "{FB631291-7923-4B91-9A57-7B18FDBB7A42}",
+    "{0A176EC9-E934-45B8-B87F-16C7F4C80039}",
+    "{DF55CA80-46E8-4C53-B65B-4990A23DD444}",
+    "{3A0F9895-55D2-4710-BE5E-AD7498B5BF44}",
+    "{294BDC5A-F448-48B6-8110-DD0A81820F8C}",
+    "{4B9F66E9-FAC9-47AB-B1EF-C16756FBFD06}",
+    "{72EA49C6-2806-48BD-B81B-D4905102E19C}",
+    "{5728EB7E-8929-486C-8CD5-3238D060E768}"
+);
+
+sub generate {
+    my ($git_dir, $out_dir, $rel_dir, %build_structure) = @_;
+    my @libs = @{$build_structure{"LIBS"}};
+    foreach (@libs) {
+        createLibProject($_, $git_dir, $out_dir, $rel_dir, \%build_structure);
+    }
+
+    my @apps = @{$build_structure{"APPS"}};
+    foreach (@apps) {
+        createAppProject($_, $git_dir, $out_dir, $rel_dir, \%build_structure);
+    }
+
+    createGlueProject($git_dir, $out_dir, $rel_dir, %build_structure);
+    return 0;
+}
+
+sub createLibProject {
+    my ($libname, $git_dir, $out_dir, $rel_dir, $build_structure) = @_;
+    print "Generate $libname vcproj lib project\n";
+    $rel_dir = "..\\$rel_dir";
+    $rel_dir =~ s/\//\\/g;
+
+    my $target = $libname;
+    $target =~ s/\//_/g;
+    $target =~ s/\.a//;
+
+    my $uuid = $GUIDS[$guid_index];
+    $$build_structure{"LIBS_${target}_GUID"} = $uuid;
+    $guid_index += 1;
+
+    my @srcs = sort(map("$rel_dir\\$_", @{$$build_structure{"LIBS_${libname}_SOURCES"}}));
+    my @sources;
+    foreach (@srcs) {
+        $_ =~ s/\//\\/g;
+        push(@sources, $_);
+    }
+    my $defines = join(",", sort(@{$$build_structure{"LIBS_${libname}_DEFINES"}}));
+    my $includes= join(";", sort(map("&quot;$rel_dir\\$_&quot;", @{$$build_structure{"LIBS_${libname}_INCLUDES"}})));
+    my $cflags  = join(" ", sort(@{$$build_structure{"LIBS_${libname}_CFLAGS"}}));
+    $cflags =~ s/\"/&quot;/g;
+
+    my $cflags_debug = $cflags;
+    $cflags_debug =~ s/-MT/-MTd/;
+    $cflags_debug =~ s/-O.//;
+
+    my $cflags_release = $cflags;
+    $cflags_release =~ s/-MTd/-MT/;
+
+    my @tmp  = @{$$build_structure{"LIBS_${libname}_LFLAGS"}};
+    my @tmp2 = ();
+    foreach (@tmp) {
+        if (/^-LTCG/) {
+        } elsif (/^-L/) {
+            $_ =~ s/^-L/-LIBPATH:$rel_dir\//;
+        }
+        push(@tmp2, $_);
+    }
+    my $lflags = join(" ", sort(@tmp));
+
+    $defines =~ s/-D//g;
+    $defines =~ s/\"/\\&quot;/g;
+    $defines =~ s/\'//g;
+    $includes =~ s/-I//g;
+    mkdir "$target" || die "Could not create the directory $target for lib project!\n";
+    open F, ">$target/$target.vcproj" || die "Could not open $target/$target.pro for writing!\n";
+    binmode F, ":crlf";
+    print F << "EOM";
+<?xml version="1.0" encoding = "Windows-1252"?>
+<VisualStudioProject
+       ProjectType="Visual C++"
+       Version="9,00"
+       Name="$target"
+       ProjectGUID="$uuid">
+       <Platforms>
+               <Platform
+                       Name="Win32"/>
+       </Platforms>
+       <ToolFiles>
+       </ToolFiles>
+       <Configurations>
+               <Configuration
+                       Name="Debug|Win32"
+                       OutputDirectory="$rel_dir"
+                       ConfigurationType="4"
+                       CharacterSet="0"
+                       IntermediateDirectory="\$(ProjectDir)\$(ConfigurationName)"
+                       >
+                       <Tool
+                               Name="VCPreBuildEventTool"
+                       />
+                       <Tool
+                               Name="VCCustomBuildTool"
+                       />
+                       <Tool
+                               Name="VCXMLDataGeneratorTool"
+                       />
+                       <Tool
+                               Name="VCWebServiceProxyGeneratorTool"
+                       />
+                       <Tool
+                               Name="VCMIDLTool"
+                       />
+                       <Tool
+                               Name="VCCLCompilerTool"
+                               AdditionalOptions="$cflags_debug"
+                               Optimization="0"
+                               InlineFunctionExpansion="1"
+                               AdditionalIncludeDirectories="$includes"
+                               PreprocessorDefinitions="WIN32,_DEBUG,$defines"
+                               MinimalRebuild="true"
+                               RuntimeLibrary="1"
+                               UsePrecompiledHeader="0"
+                               ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb"
+                               WarningLevel="3"
+                               DebugInformationFormat="3"
+                       />
+                       <Tool
+                               Name="VCManagedResourceCompilerTool"
+                       />
+                       <Tool
+                               Name="VCResourceCompilerTool"
+                       />
+                       <Tool
+                               Name="VCPreLinkEventTool"
+                       />
+                       <Tool
+                               Name="VCLibrarianTool"
+                               SuppressStartupBanner="true"
+                       />
+                       <Tool
+                               Name="VCALinkTool"
+                       />
+                       <Tool
+                               Name="VCXDCMakeTool"
+                       />
+                       <Tool
+                               Name="VCBscMakeTool"
+                       />
+                       <Tool
+                               Name="VCFxCopTool"
+                       />
+                       <Tool
+                               Name="VCPostBuildEventTool"
+                       />
+               </Configuration>
+               <Configuration
+                       Name="Release|Win32"
+                       OutputDirectory="$rel_dir"
+                       ConfigurationType="4"
+                       CharacterSet="0"
+                       WholeProgramOptimization="1"
+                       IntermediateDirectory="\$(ProjectDir)\$(ConfigurationName)"
+                       >
+                       <Tool
+                               Name="VCPreBuildEventTool"
+                       />
+                       <Tool
+                               Name="VCCustomBuildTool"
+                       />
+                       <Tool
+                               Name="VCXMLDataGeneratorTool"
+                       />
+                       <Tool
+                               Name="VCWebServiceProxyGeneratorTool"
+                       />
+                       <Tool
+                               Name="VCMIDLTool"
+                       />
+                       <Tool
+                               Name="VCCLCompilerTool"
+                               AdditionalOptions="$cflags_release"
+                               Optimization="2"
+                               InlineFunctionExpansion="1"
+                               EnableIntrinsicFunctions="true"
+                               AdditionalIncludeDirectories="$includes"
+                               PreprocessorDefinitions="WIN32,NDEBUG,$defines"
+                               RuntimeLibrary="0"
+                               EnableFunctionLevelLinking="true"
+                               UsePrecompiledHeader="0"
+                               ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb"
+                               WarningLevel="3"
+                               DebugInformationFormat="3"
+                       />
+                       <Tool
+                               Name="VCManagedResourceCompilerTool"
+                       />
+                       <Tool
+                               Name="VCResourceCompilerTool"
+                       />
+                       <Tool
+                               Name="VCPreLinkEventTool"
+                       />
+                       <Tool
+                               Name="VCLibrarianTool"
+                               SuppressStartupBanner="true"
+                       />
+                       <Tool
+                               Name="VCALinkTool"
+                       />
+                       <Tool
+                               Name="VCXDCMakeTool"
+                       />
+                       <Tool
+                               Name="VCBscMakeTool"
+                       />
+                       <Tool
+                               Name="VCFxCopTool"
+                       />
+                       <Tool
+                               Name="VCPostBuildEventTool"
+                       />
+               </Configuration>
+       </Configurations>
+       <Files>
+               <Filter
+                       Name="Source Files"
+                       Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx"
+                       UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}">
+EOM
+    foreach(@sources) {
+        print F << "EOM";
+                       <File
+                               RelativePath="$_"/>
+EOM
+    }
+    print F << "EOM";
+               </Filter>
+       </Files>
+       <Globals>
+       </Globals>
+</VisualStudioProject>
+EOM
+    close F;
+}
+
+sub createAppProject {
+    my ($appname, $git_dir, $out_dir, $rel_dir, $build_structure) = @_;
+    print "Generate $appname vcproj app project\n";
+    $rel_dir = "..\\$rel_dir";
+    $rel_dir =~ s/\//\\/g;
+
+    my $target = $appname;
+    $target =~ s/\//_/g;
+    $target =~ s/\.exe//;
+
+    my $uuid = $GUIDS[$guid_index];
+    $$build_structure{"APPS_${target}_GUID"} = $uuid;
+    $guid_index += 1;
+
+    my @srcs = sort(map("$rel_dir\\$_", @{$$build_structure{"APPS_${appname}_SOURCES"}}));
+    my @sources;
+    foreach (@srcs) {
+        $_ =~ s/\//\\/g;
+        push(@sources, $_);
+    }
+    my $defines = join(",", sort(@{$$build_structure{"APPS_${appname}_DEFINES"}}));
+    my $includes= join(";", sort(map("&quot;$rel_dir\\$_&quot;", @{$$build_structure{"APPS_${appname}_INCLUDES"}})));
+    my $cflags  = join(" ", sort(@{$$build_structure{"APPS_${appname}_CFLAGS"}}));
+    $cflags =~ s/\"/&quot;/g;
+
+    my $cflags_debug = $cflags;
+    $cflags_debug =~ s/-MT/-MTd/;
+    $cflags_debug =~ s/-O.//;
+
+    my $cflags_release = $cflags;
+    $cflags_release =~ s/-MTd/-MT/;
+
+    my $libs;
+    foreach (sort(@{$$build_structure{"APPS_${appname}_LIBS"}})) {
+        $_ =~ s/\//_/g;
+        $libs .= " $_";
+    }
+    my @tmp  = @{$$build_structure{"APPS_${appname}_LFLAGS"}};
+    my @tmp2 = ();
+    foreach (@tmp) {
+        if (/^-LTCG/) {
+        } elsif (/^-L/) {
+            $_ =~ s/^-L/-LIBPATH:$rel_dir\//;
+        }
+        push(@tmp2, $_);
+    }
+    my $lflags = join(" ", sort(@tmp)) . " -LIBPATH:$rel_dir";
+
+    $defines =~ s/-D//g;
+    $defines =~ s/\"/\\&quot;/g;
+    $defines =~ s/\'//g;
+    $defines =~ s/\\\\/\\/g;
+    $includes =~ s/-I//g;
+    mkdir "$target" || die "Could not create the directory $target for lib project!\n";
+    open F, ">$target/$target.vcproj" || die "Could not open $target/$target.pro for writing!\n";
+    binmode F, ":crlf";
+    print F << "EOM";
+<?xml version="1.0" encoding = "Windows-1252"?>
+<VisualStudioProject
+       ProjectType="Visual C++"
+       Version="9,00"
+       Name="$target"
+       ProjectGUID="$uuid">
+       <Platforms>
+               <Platform
+                       Name="Win32"/>
+       </Platforms>
+       <ToolFiles>
+       </ToolFiles>
+       <Configurations>
+               <Configuration
+                       Name="Debug|Win32"
+                       OutputDirectory="$rel_dir"
+                       ConfigurationType="1"
+                       CharacterSet="0"
+                       IntermediateDirectory="\$(ProjectDir)\$(ConfigurationName)"
+                       >
+                       <Tool
+                               Name="VCPreBuildEventTool"
+                       />
+                       <Tool
+                               Name="VCCustomBuildTool"
+                       />
+                       <Tool
+                               Name="VCXMLDataGeneratorTool"
+                       />
+                       <Tool
+                               Name="VCWebServiceProxyGeneratorTool"
+                       />
+                       <Tool
+                               Name="VCMIDLTool"
+                       />
+                       <Tool
+                               Name="VCCLCompilerTool"
+                               AdditionalOptions="$cflags_debug"
+                               Optimization="0"
+                               InlineFunctionExpansion="1"
+                               AdditionalIncludeDirectories="$includes"
+                               PreprocessorDefinitions="WIN32,_DEBUG,$defines"
+                               MinimalRebuild="true"
+                               RuntimeLibrary="1"
+                               UsePrecompiledHeader="0"
+                               ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb"
+                               WarningLevel="3"
+                               DebugInformationFormat="3"
+                       />
+                       <Tool
+                               Name="VCManagedResourceCompilerTool"
+                       />
+                       <Tool
+                               Name="VCResourceCompilerTool"
+                       />
+                       <Tool
+                               Name="VCPreLinkEventTool"
+                       />
+                       <Tool
+                               Name="VCLinkerTool"
+                               AdditionalDependencies="$libs"
+                               AdditionalOptions="$lflags"
+                               LinkIncremental="2"
+                               GenerateDebugInformation="true"
+                               SubSystem="1"
+                               TargetMachine="1"
+                       />
+                       <Tool
+                               Name="VCALinkTool"
+                       />
+                       <Tool
+                               Name="VCXDCMakeTool"
+                       />
+                       <Tool
+                               Name="VCBscMakeTool"
+                       />
+                       <Tool
+                               Name="VCFxCopTool"
+                       />
+                       <Tool
+                               Name="VCPostBuildEventTool"
+                       />
+               </Configuration>
+               <Configuration
+                       Name="Release|Win32"
+                       OutputDirectory="$rel_dir"
+                       ConfigurationType="1"
+                       CharacterSet="0"
+                       WholeProgramOptimization="1"
+                       IntermediateDirectory="\$(ProjectDir)\$(ConfigurationName)"
+                       >
+                       <Tool
+                               Name="VCPreBuildEventTool"
+                       />
+                       <Tool
+                               Name="VCCustomBuildTool"
+                       />
+                       <Tool
+                               Name="VCXMLDataGeneratorTool"
+                       />
+                       <Tool
+                               Name="VCWebServiceProxyGeneratorTool"
+                       />
+                       <Tool
+                               Name="VCMIDLTool"
+                       />
+                       <Tool
+                               Name="VCCLCompilerTool"
+                               AdditionalOptions="$cflags_release"
+                               Optimization="2"
+                               InlineFunctionExpansion="1"
+                               EnableIntrinsicFunctions="true"
+                               AdditionalIncludeDirectories="$includes"
+                               PreprocessorDefinitions="WIN32,NDEBUG,$defines"
+                               RuntimeLibrary="0"
+                               EnableFunctionLevelLinking="true"
+                               UsePrecompiledHeader="0"
+                               ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb"
+                               WarningLevel="3"
+                               DebugInformationFormat="3"
+                       />
+                       <Tool
+                               Name="VCManagedResourceCompilerTool"
+                       />
+                       <Tool
+                               Name="VCResourceCompilerTool"
+                       />
+                       <Tool
+                               Name="VCPreLinkEventTool"
+                       />
+                       <Tool
+                               Name="VCLinkerTool"
+                               AdditionalDependencies="$libs"
+                               AdditionalOptions="$lflags"
+                               LinkIncremental="1"
+                               GenerateDebugInformation="true"
+                               SubSystem="1"
+                               TargetMachine="1"
+                               OptimizeReferences="2"
+                               EnableCOMDATFolding="2"
+                       />
+                       <Tool
+                               Name="VCALinkTool"
+                       />
+                       <Tool
+                               Name="VCXDCMakeTool"
+                       />
+                       <Tool
+                               Name="VCBscMakeTool"
+                       />
+                       <Tool
+                               Name="VCFxCopTool"
+                       />
+                       <Tool
+                               Name="VCPostBuildEventTool"
+                       />
+               </Configuration>
+       </Configurations>
+       <Files>
+               <Filter
+                       Name="Source Files"
+                       Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx"
+                       UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}">
+EOM
+    foreach(@sources) {
+        print F << "EOM";
+                       <File
+                               RelativePath="$_"/>
+EOM
+    }
+    print F << "EOM";
+               </Filter>
+       </Files>
+       <Globals>
+       </Globals>
+</VisualStudioProject>
+EOM
+    close F;
+}
+
+sub createGlueProject {
+    my ($git_dir, $out_dir, $rel_dir, %build_structure) = @_;
+    print "Generate solutions file\n";
+    $rel_dir = "..\\$rel_dir";
+    $rel_dir =~ s/\//\\/g;
+    my $SLN_HEAD = "Microsoft Visual Studio Solution File, Format Version 10.00\n# Visual Studio 2008\n";
+    my $SLN_PRE  = "Project(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = ";
+    my $SLN_POST = "\nEndProject\n";
+
+    my @libs = @{$build_structure{"LIBS"}};
+    my @tmp;
+    foreach (@libs) {
+        $_ =~ s/\//_/g;
+        $_ =~ s/\.a//;
+        push(@tmp, $_);
+    }
+    @libs = @tmp;
+
+    my @apps = @{$build_structure{"APPS"}};
+    @tmp = ();
+    foreach (@apps) {
+        $_ =~ s/\//_/g;
+        $_ =~ s/\.exe//;
+        push(@tmp, $_);
+    }
+    @apps = @tmp;
+
+    open F, ">git.sln" || die "Could not open git.sln for writing!\n";
+    binmode F, ":crlf";
+    print F "$SLN_HEAD";
+    foreach (@libs) {
+        my $libname = $_;
+        my $uuid = $build_structure{"LIBS_${libname}_GUID"};
+        print F "$SLN_PRE";
+        print F "\"${libname}\", \"${libname}\\${libname}.vcproj\", \"${uuid}\"";
+        print F "$SLN_POST";
+    }
+    my $uuid_libgit = $build_structure{"LIBS_libgit_GUID"};
+    my $uuid_xdiff_lib = $build_structure{"LIBS_xdiff_lib_GUID"};
+    foreach (@apps) {
+        my $appname = $_;
+        my $uuid = $build_structure{"APPS_${appname}_GUID"};
+        print F "$SLN_PRE";
+        print F "\"${appname}\", \"${appname}\\${appname}.vcproj\", \"${uuid}\"\n";
+        print F "      ProjectSection(ProjectDependencies) = postProject\n";
+        print F "              ${uuid_libgit} = ${uuid_libgit}\n";
+        print F "              ${uuid_xdiff_lib} = ${uuid_xdiff_lib}\n";
+        print F "      EndProjectSection";
+        print F "$SLN_POST";
+    }
+
+    print F << "EOM";
+Global
+       GlobalSection(SolutionConfigurationPlatforms) = preSolution
+               Debug|Win32 = Debug|Win32
+               Release|Win32 = Release|Win32
+       EndGlobalSection
+EOM
+    print F << "EOM";
+       GlobalSection(ProjectConfigurationPlatforms) = postSolution
+EOM
+    foreach (@libs) {
+        my $libname = $_;
+        my $uuid = $build_structure{"LIBS_${libname}_GUID"};
+        print F "\t\t${uuid}.Debug|Win32.ActiveCfg = Debug|Win32\n";
+        print F "\t\t${uuid}.Debug|Win32.Build.0 = Debug|Win32\n";
+        print F "\t\t${uuid}.Release|Win32.ActiveCfg = Release|Win32\n";
+        print F "\t\t${uuid}.Release|Win32.Build.0 = Release|Win32\n";
+    }
+    foreach (@apps) {
+        my $appname = $_;
+        my $uuid = $build_structure{"APPS_${appname}_GUID"};
+        print F "\t\t${uuid}.Debug|Win32.ActiveCfg = Debug|Win32\n";
+        print F "\t\t${uuid}.Debug|Win32.Build.0 = Debug|Win32\n";
+        print F "\t\t${uuid}.Release|Win32.ActiveCfg = Release|Win32\n";
+        print F "\t\t${uuid}.Release|Win32.Build.0 = Release|Win32\n";
+    }
+
+    print F << "EOM";
+       EndGlobalSection
+EndGlobal
+EOM
+    close F;
+}
+
+1;
diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl
new file mode 100644 (file)
index 0000000..23da787
--- /dev/null
@@ -0,0 +1,359 @@
+#!/usr/bin/perl -w
+######################################################################
+# Do not call this script directly!
+#
+# The generate script ensures that @INC is correct before the engine
+# is executed.
+#
+# Copyright (C) 2009 Marius Storm-Olsen <mstormo@gmail.com>
+######################################################################
+use strict;
+use File::Basename;
+use File::Spec;
+use Cwd;
+use Generators;
+
+my (%build_structure, %compile_options, @makedry);
+my $out_dir = getcwd();
+my $git_dir = $out_dir;
+$git_dir =~ s=\\=/=g;
+$git_dir = dirname($git_dir) while (!-e "$git_dir/git.c" && "$git_dir" ne "");
+die "Couldn't find Git repo" if ("$git_dir" eq "");
+
+my @gens = Generators::available();
+my $gen = "Vcproj";
+
+sub showUsage
+{
+    my $genlist = join(', ', @gens);
+    print << "EOM";
+generate usage:
+  -g <GENERATOR>  --gen <GENERATOR> Specify the buildsystem generator    (default: $gen)
+                                    Available: $genlist
+  -o <PATH>       --out <PATH>      Specify output directory generation  (default: .)
+  -i <FILE>       --in <FILE>       Specify input file, instead of running GNU Make
+  -h,-?           --help            This help
+EOM
+    exit 0;
+}
+
+# Parse command-line options
+while (@ARGV) {
+    my $arg = shift @ARGV;
+    if ("$arg" eq "-h" || "$arg" eq "--help" || "$arg" eq "-?") {
+       showUsage();
+       exit(0);
+    } elsif("$arg" eq "--out" || "$arg" eq "-o") {
+       $out_dir = shift @ARGV;
+    } elsif("$arg" eq "--gen" || "$arg" eq "-g") {
+       $gen = shift @ARGV;
+    } elsif("$arg" eq "--in" || "$arg" eq "-i") {
+       my $infile = shift @ARGV;
+        open(F, "<$infile") || die "Couldn't open file $infile";
+        @makedry = <F>;
+        close(F);
+    }
+}
+
+# NOT using File::Spec->rel2abs($path, $base) here, as
+# it fails badly for me in the msysgit environment
+$git_dir = File::Spec->rel2abs($git_dir);
+$out_dir = File::Spec->rel2abs($out_dir);
+my $rel_dir = makeOutRel2Git($git_dir, $out_dir);
+
+# Print some information so the user feels informed
+print << "EOM";
+-----
+Generator: $gen
+Git dir:   $git_dir
+Out dir:   $out_dir
+-----
+Running GNU Make to figure out build structure...
+EOM
+
+# Pipe a make --dry-run into a variable, if not already loaded from file
+@makedry = `cd $git_dir && make -n MSVC=1 V=1 2>/dev/null` if !@makedry;
+
+# Parse the make output into usable info
+parseMakeOutput();
+
+# Finally, ask the generator to start generating..
+Generators::generate($gen, $git_dir, $out_dir, $rel_dir, %build_structure);
+
+# main flow ends here
+# -------------------------------------------------------------------------------------------------
+
+
+# 1) path: /foo/bar/baz        2) path: /foo/bar/baz   3) path: /foo/bar/baz
+#    base: /foo/bar/baz/temp      base: /foo/bar          base: /tmp
+#    rel:  ..                     rel:  baz               rel:  ../foo/bar/baz
+sub makeOutRel2Git
+{
+    my ($path, $base) = @_;
+    my $rel;
+    if ("$path" eq "$base") {
+        return ".";
+    } elsif ($base =~ /^$path/) {
+        # case 1
+        my $tmp = $base;
+        $tmp =~ s/^$path//;
+        foreach (split('/', $tmp)) {
+            $rel .= "../" if ("$_" ne "");
+        }
+    } elsif ($path =~ /^$base/) {
+        # case 2
+        $rel = $path;
+        $rel =~ s/^$base//;
+        $rel = "./$rel";
+    } else {
+        my $tmp = $base;
+        foreach (split('/', $tmp)) {
+            $rel .= "../" if ("$_" ne "");
+        }
+        $rel .= $path;
+    }
+    $rel =~ s/\/\//\//g; # simplify
+    $rel =~ s/\/$//;     # don't end with /
+    return $rel;
+}
+
+sub parseMakeOutput
+{
+    print "Parsing GNU Make output to figure out build structure...\n";
+    my $line = 0;
+    while (my $text = shift @makedry) {
+        my $ate_next;
+        do {
+            $ate_next = 0;
+            $line++;
+            chomp $text;
+            chop $text if ($text =~ /\r$/);
+            if ($text =~ /\\$/) {
+                $text =~ s/\\$//;
+                $text .= shift @makedry;
+                $ate_next = 1;
+            }
+        } while($ate_next);
+
+        if ($text =~ /^test /) {
+            # options to test (eg -o) may be mistaken for linker options
+            next;
+        }
+
+        if($text =~ / -c /) {
+            # compilation
+            handleCompileLine($text, $line);
+
+        } elsif ($text =~ / -o /) {
+            # linking executable
+            handleLinkLine($text, $line);
+
+        } elsif ($text =~ /\.o / && $text =~ /\.a /) {
+            # libifying
+            handleLibLine($text, $line);
+#
+#        } elsif ($text =~ /^cp /) {
+#            # copy file around
+#
+#        } elsif ($text =~ /^rm -f /) {
+#            # shell command
+#
+#        } elsif ($text =~ /^make[ \[]/) {
+#            # make output
+#
+#        } elsif ($text =~ /^echo /) {
+#            # echo to file
+#
+#        } elsif ($text =~ /^if /) {
+#            # shell conditional
+#
+#        } elsif ($text =~ /^tclsh /) {
+#            # translation stuff
+#
+#        } elsif ($text =~ /^umask /) {
+#            # handling boilerplates
+#
+#        } elsif ($text =~ /\$\(\:\)/) {
+#            # ignore
+#
+#        } elsif ($text =~ /^FLAGS=/) {
+#            # flags check for dependencies
+#
+#        } elsif ($text =~ /^'\/usr\/bin\/perl' -MError -e/) {
+#            # perl commands for copying files
+#
+#        } elsif ($text =~ /generate-cmdlist\.sh/) {
+#            # command for generating list of commands
+#
+#        } elsif ($text =~ /new locations or Tcl/) {
+#            # command for detecting Tcl/Tk changes
+#
+#        } elsif ($text =~ /mkdir -p/) {
+#            # command creating path
+#
+#        } elsif ($text =~ /: no custom templates yet/) {
+#            # whatever
+#
+#        } else {
+#            print "Unhandled (line: $line): $text\n";
+        }
+    }
+
+#    use Data::Dumper;
+#    print "Parsed build structure:\n";
+#    print Dumper(%build_structure);
+}
+
+# variables for the compilation part of each step
+my (@defines, @incpaths, @cflags, @sources);
+
+sub clearCompileStep
+{
+    @defines = ();
+    @incpaths = ();
+    @cflags = ();
+    @sources = ();
+}
+
+sub removeDuplicates
+{
+    my (%dupHash, $entry);
+    %dupHash = map { $_, 1 } @defines;
+    @defines = keys %dupHash;
+
+    %dupHash = map { $_, 1 } @incpaths;
+    @incpaths = keys %dupHash;
+
+    %dupHash = map { $_, 1 } @cflags;
+    @cflags = keys %dupHash;
+}
+
+sub handleCompileLine
+{
+    my ($line, $lineno) = @_;
+    my @parts = split(' ', $line);
+    my $sourcefile;
+    shift(@parts); # ignore cmd
+    while (my $part = shift @parts) {
+        if ("$part" eq "-o") {
+            # ignore object file
+            shift @parts;
+        } elsif ("$part" eq "-c") {
+            # ignore compile flag
+        } elsif ("$part" eq "-c") {
+        } elsif ($part =~ /^.?-I/) {
+            push(@incpaths, $part);
+        } elsif ($part =~ /^.?-D/) {
+            push(@defines, $part);
+        } elsif ($part =~ /^-/) {
+            push(@cflags, $part);
+        } elsif ($part =~ /\.(c|cc|cpp)$/) {
+            $sourcefile = $part;
+        } else {
+            die "Unhandled compiler option @ line $lineno: $part";
+        }
+    }
+    @{$compile_options{"${sourcefile}_CFLAGS"}} = @cflags;
+    @{$compile_options{"${sourcefile}_DEFINES"}} = @defines;
+    @{$compile_options{"${sourcefile}_INCPATHS"}} = @incpaths;
+    clearCompileStep();
+}
+
+sub handleLibLine
+{
+    my ($line, $lineno) = @_;
+    my (@objfiles, @lflags, $libout, $part);
+    # kill cmd and rm 'prefix'
+    $line =~ s/^rm -f .* && .* rcs //;
+    my @parts = split(' ', $line);
+    while ($part = shift @parts) {
+        if ($part =~ /^-/) {
+            push(@lflags, $part);
+        } elsif ($part =~ /\.(o|obj)$/) {
+            push(@objfiles, $part);
+        } elsif ($part =~ /\.(a|lib)$/) {
+            $libout = $part;
+            $libout =~ s/\.a$//;
+        } else {
+            die "Unhandled lib option @ line $lineno: $part";
+        }
+    }
+#    print "LibOut: '$libout'\nLFlags: @lflags\nOfiles: @objfiles\n";
+#    exit(1);
+    foreach (@objfiles) {
+        my $sourcefile = $_;
+        $sourcefile =~ s/\.o/.c/;
+        push(@sources, $sourcefile);
+        push(@cflags, @{$compile_options{"${sourcefile}_CFLAGS"}});
+        push(@defines, @{$compile_options{"${sourcefile}_DEFINES"}});
+        push(@incpaths, @{$compile_options{"${sourcefile}_INCPATHS"}});
+    }
+    removeDuplicates();
+
+    push(@{$build_structure{"LIBS"}}, $libout);
+    @{$build_structure{"LIBS_${libout}"}} = ("_DEFINES", "_INCLUDES", "_CFLAGS", "_SOURCES",
+                                             "_OBJECTS");
+    @{$build_structure{"LIBS_${libout}_DEFINES"}} = @defines;
+    @{$build_structure{"LIBS_${libout}_INCLUDES"}} = @incpaths;
+    @{$build_structure{"LIBS_${libout}_CFLAGS"}} = @cflags;
+    @{$build_structure{"LIBS_${libout}_LFLAGS"}} = @lflags;
+    @{$build_structure{"LIBS_${libout}_SOURCES"}} = @sources;
+    @{$build_structure{"LIBS_${libout}_OBJECTS"}} = @objfiles;
+    clearCompileStep();
+}
+
+sub handleLinkLine
+{
+    my ($line, $lineno) = @_;
+    my (@objfiles, @lflags, @libs, $appout, $part);
+    my @parts = split(' ', $line);
+    shift(@parts); # ignore cmd
+    while ($part = shift @parts) {
+        if ($part =~ /^-IGNORE/) {
+            push(@lflags, $part);
+        } elsif ($part =~ /^-[GRIMDO]/) {
+            # eat compiler flags
+        } elsif ("$part" eq "-o") {
+            $appout = shift @parts;
+        } elsif ("$part" eq "-lz") {
+            push(@libs, "zlib.lib");
+       } elsif ("$part" eq "-lcrypto") {
+            push(@libs, "libeay32.lib");
+        } elsif ("$part" eq "-lssl") {
+            push(@libs, "ssleay32.lib");
+        } elsif ($part =~ /^-/) {
+            push(@lflags, $part);
+        } elsif ($part =~ /\.(a|lib)$/) {
+            $part =~ s/\.a$/.lib/;
+            push(@libs, $part);
+        } elsif ($part =~ /\.(o|obj)$/) {
+            push(@objfiles, $part);
+        } else {
+            die "Unhandled lib option @ line $lineno: $part";
+        }
+    }
+#    print "AppOut: '$appout'\nLFlags: @lflags\nLibs  : @libs\nOfiles: @objfiles\n";
+#    exit(1);
+    foreach (@objfiles) {
+        my $sourcefile = $_;
+        $sourcefile =~ s/\.o/.c/;
+        push(@sources, $sourcefile);
+        push(@cflags, @{$compile_options{"${sourcefile}_CFLAGS"}});
+        push(@defines, @{$compile_options{"${sourcefile}_DEFINES"}});
+        push(@incpaths, @{$compile_options{"${sourcefile}_INCPATHS"}});
+    }
+    removeDuplicates();
+
+    removeDuplicates();
+    push(@{$build_structure{"APPS"}}, $appout);
+    @{$build_structure{"APPS_${appout}"}} = ("_DEFINES", "_INCLUDES", "_CFLAGS", "_LFLAGS",
+                                             "_SOURCES", "_OBJECTS", "_LIBS");
+    @{$build_structure{"APPS_${appout}_DEFINES"}} = @defines;
+    @{$build_structure{"APPS_${appout}_INCLUDES"}} = @incpaths;
+    @{$build_structure{"APPS_${appout}_CFLAGS"}} = @cflags;
+    @{$build_structure{"APPS_${appout}_LFLAGS"}} = @lflags;
+    @{$build_structure{"APPS_${appout}_SOURCES"}} = @sources;
+    @{$build_structure{"APPS_${appout}_OBJECTS"}} = @objfiles;
+    @{$build_structure{"APPS_${appout}_LIBS"}} = @libs;
+    clearCompileStep();
+}
diff --git a/contrib/buildsystems/generate b/contrib/buildsystems/generate
new file mode 100644 (file)
index 0000000..bc10f25
--- /dev/null
@@ -0,0 +1,29 @@
+#!/usr/bin/perl -w
+######################################################################
+# Generate buildsystem files
+#
+# This script generate buildsystem files based on the output of a
+# GNU Make --dry-run, enabling Windows users to develop Git with their
+# trusted IDE with native projects.
+#
+# Note:
+# It is not meant as *the* way of building Git with MSVC, but merely a
+# convenience. The correct way of building Git with MSVC is to use the
+# GNU Make tool to build with the maintained Makefile in the root of
+# the project. If you have the msysgit environment installed and
+# available in your current console, together with the Visual Studio
+# environment you wish to build for, all you have to do is run the
+# command:
+#     make MSVC=1
+#
+# Copyright (C) 2009 Marius Storm-Olsen <mstormo@gmail.com>
+######################################################################
+use strict;
+use File::Basename;
+use Cwd;
+
+my $git_dir = getcwd();
+$git_dir =~ s=\\=/=g;
+$git_dir = dirname($git_dir) while (!-e "$git_dir/git.c" && "$git_dir" ne "");
+die "Couldn't find Git repo" if ("$git_dir" eq "");
+exec join(" ", ("PERL5LIB=${git_dir}/contrib/buildsystems ${git_dir}/contrib/buildsystems/engine.pl", @ARGV));
diff --git a/contrib/buildsystems/parse.pl b/contrib/buildsystems/parse.pl
new file mode 100644 (file)
index 0000000..c9656ec
--- /dev/null
@@ -0,0 +1,228 @@
+#!/usr/bin/perl -w
+######################################################################
+# Do not call this script directly!
+#
+# The generate script ensures that @INC is correct before the engine
+# is executed.
+#
+# Copyright (C) 2009 Marius Storm-Olsen <mstormo@gmail.com>
+######################################################################
+use strict;
+use File::Basename;
+use Cwd;
+
+my $file = $ARGV[0];
+die "No file provided!" if !defined $file;
+
+my ($cflags, $target, $type, $line);
+
+open(F, "<$file") || die "Couldn't open file $file";
+my @data = <F>;
+close(F);
+
+while (my $text = shift @data) {
+    my $ate_next;
+    do {
+        $ate_next = 0;
+        $line++;
+        chomp $text;
+        chop $text if ($text =~ /\r$/);
+        if ($text =~ /\\$/) {
+            $text =~ s/\\$//;
+            $text .= shift @data;
+            $ate_next = 1;
+        }
+    } while($ate_next);
+
+    if($text =~ / -c /) {
+        # compilation
+        handleCompileLine($text, $line);
+
+    } elsif ($text =~ / -o /) {
+        # linking executable
+        handleLinkLine($text, $line);
+
+    } elsif ($text =~ /\.o / && $text =~ /\.a /) {
+        # libifying
+        handleLibLine($text, $line);
+
+#    } elsif ($text =~ /^cp /) {
+#        # copy file around
+#
+#    } elsif ($text =~ /^rm -f /) {
+#        # shell command
+#
+#    } elsif ($text =~ /^make[ \[]/) {
+#        # make output
+#
+#    } elsif ($text =~ /^echo /) {
+#        # echo to file
+#
+#    } elsif ($text =~ /^if /) {
+#        # shell conditional
+#
+#    } elsif ($text =~ /^tclsh /) {
+#        # translation stuff
+#
+#    } elsif ($text =~ /^umask /) {
+#        # handling boilerplates
+#
+#    } elsif ($text =~ /\$\(\:\)/) {
+#        # ignore
+#
+#    } elsif ($text =~ /^FLAGS=/) {
+#        # flags check for dependencies
+#
+#    } elsif ($text =~ /^'\/usr\/bin\/perl' -MError -e/) {
+#        # perl commands for copying files
+#
+#    } elsif ($text =~ /generate-cmdlist\.sh/) {
+#        # command for generating list of commands
+#
+#    } elsif ($text =~ /^test / && $text =~ /|| rm -f /) {
+#        # commands removing executables, if they exist
+#
+#    } elsif ($text =~ /new locations or Tcl/) {
+#        # command for detecting Tcl/Tk changes
+#
+#    } elsif ($text =~ /mkdir -p/) {
+#        # command creating path
+#
+#    } elsif ($text =~ /: no custom templates yet/) {
+#        # whatever
+
+    } else {
+#        print "Unhandled (line: $line): $text\n";
+    }
+}
+close(F);
+
+# use Data::Dumper;
+# print "Parsed build structure:\n";
+# print Dumper(%build_structure);
+
+# -------------------------------------------------------------------
+# Functions under here
+# -------------------------------------------------------------------
+my (%build_structure, @defines, @incpaths, @cflags, @sources);
+
+sub clearCompileStep
+{
+    @defines = ();
+    @incpaths = ();
+    @cflags = ();
+    @sources = ();
+}
+
+sub removeDuplicates
+{
+    my (%dupHash, $entry);
+    %dupHash = map { $_, 1 } @defines;
+    @defines = keys %dupHash;
+
+    %dupHash = map { $_, 1 } @incpaths;
+    @incpaths = keys %dupHash;
+
+    %dupHash = map { $_, 1 } @cflags;
+    @cflags = keys %dupHash;
+
+    %dupHash = map { $_, 1 } @sources;
+    @sources = keys %dupHash;
+}
+
+sub handleCompileLine
+{
+    my ($line, $lineno) = @_;
+    my @parts = split(' ', $line);
+    shift(@parts); # ignore cmd
+    while (my $part = shift @parts) {
+        if ("$part" eq "-o") {
+            # ignore object file
+            shift @parts;
+        } elsif ("$part" eq "-c") {
+            # ignore compile flag
+        } elsif ("$part" eq "-c") {
+        } elsif ($part =~ /^.?-I/) {
+            push(@incpaths, $part);
+        } elsif ($part =~ /^.?-D/) {
+            push(@defines, $part);
+        } elsif ($part =~ /^-/) {
+            push(@cflags, $part);
+        } elsif ($part =~ /\.(c|cc|cpp)$/) {
+            push(@sources, $part);
+        } else {
+            die "Unhandled compiler option @ line $lineno: $part";
+        }
+    }
+    #print "Sources: @sources\nCFlags: @cflags\nDefine: @defines\nIncpat: @incpaths\n";
+    #exit(1);
+}
+
+sub handleLibLine
+{
+    my ($line, $lineno) = @_;
+    my (@objfiles, @lflags, $libout, $part);
+    # kill cmd and rm 'prefix'
+    $line =~ s/^rm -f .* && .* rcs //;
+    my @parts = split(' ', $line);
+    while ($part = shift @parts) {
+        if ($part =~ /^-/) {
+            push(@lflags, $part);
+        } elsif ($part =~ /\.(o|obj)$/) {
+            push(@objfiles, $part);
+        } elsif ($part =~ /\.(a|lib)$/) {
+            $libout = $part;
+        } else {
+            die "Unhandled lib option @ line $lineno: $part";
+        }
+    }
+    #print "LibOut: '$libout'\nLFlags: @lflags\nOfiles: @objfiles\n";
+    #exit(1);
+    removeDuplicates();
+    push(@{$build_structure{"LIBS"}}, $libout);
+    @{$build_structure{"LIBS_${libout}"}} = ("_DEFINES", "_INCLUDES", "_CFLAGS", "_SOURCES",
+                                             "_OBJECTS");
+    @{$build_structure{"LIBS_${libout}_DEFINES"}} = @defines;
+    @{$build_structure{"LIBS_${libout}_INCLUDES"}} = @incpaths;
+    @{$build_structure{"LIBS_${libout}_CFLAGS"}} = @cflags;
+    @{$build_structure{"LIBS_${libout}_SOURCES"}} = @sources;
+    @{$build_structure{"LIBS_${libout}_OBJECTS"}} = @objfiles;
+    clearCompileStep();
+}
+
+sub handleLinkLine
+{
+    my ($line, $lineno) = @_;
+    my (@objfiles, @lflags, @libs, $appout, $part);
+    my @parts = split(' ', $line);
+    shift(@parts); # ignore cmd
+    while ($part = shift @parts) {
+        if ($part =~ /^-[GRIDO]/) {
+            # eat compiler flags
+        } elsif ("$part" eq "-o") {
+            $appout = shift @parts;
+        } elsif ($part =~ /^-/) {
+            push(@lflags, $part);
+        } elsif ($part =~ /\.(a|lib)$/) {
+            push(@libs, $part);
+        } elsif ($part =~ /\.(o|obj)$/) {
+            push(@objfiles, $part);
+        } else {
+            die "Unhandled lib option @ line $lineno: $part";
+        }
+    }
+    #print "AppOut: '$appout'\nLFlags: @lflags\nLibs  : @libs\nOfiles: @objfiles\n";
+    #exit(1);
+    removeDuplicates();
+    push(@{$build_structure{"APPS"}}, $appout);
+    @{$build_structure{"APPS_${appout}"}} = ("_DEFINES", "_INCLUDES", "_CFLAGS", "_LFLAGS",
+                                             "_SOURCES", "_OBJECTS", "_LIBS");
+    @{$build_structure{"APPS_${appout}_DEFINES"}} = @defines;
+    @{$build_structure{"APPS_${appout}_INCLUDES"}} = @incpaths;
+    @{$build_structure{"APPS_${appout}_CFLAGS"}} = @cflags;
+    @{$build_structure{"APPS_${appout}_LFLAGS"}} = @lflags;
+    @{$build_structure{"APPS_${appout}_SOURCES"}} = @sources;
+    @{$build_structure{"APPS_${appout}_OBJECTS"}} = @objfiles;
+    @{$build_structure{"APPS_${appout}_LIBS"}} = @libs;
+    clearCompileStep();
+}
diff --git a/contrib/ciabot/README b/contrib/ciabot/README
new file mode 100644 (file)
index 0000000..3b916ac
--- /dev/null
@@ -0,0 +1,12 @@
+These are hook scripts for the CIA notification service at <http://cia.vc/>
+
+They are maintained by Eric S. Raymond <esr@thyrsus.com>.  There is an
+upstream resource page for them at <http://www.catb.org/esr/ciabot/>,
+but they are unlikely to change rapidly.
+
+You probably want the Python version; it's faster, more capable, and
+better documented.  The shell version is maintained only as a fallback
+for use on hosting sites that don't permit Python hook scripts.
+
+You will find installation instructions for each script in its comment
+header.
diff --git a/contrib/ciabot/ciabot.py b/contrib/ciabot/ciabot.py
new file mode 100755 (executable)
index 0000000..d0627e0
--- /dev/null
@@ -0,0 +1,222 @@
+#!/usr/bin/env python
+# Copyright (c) 2010 Eric S. Raymond <esr@thyrsus.com>
+# Distributed under BSD terms.
+#
+# This script contains porcelain and porcelain byproducts.
+# It's Python because the Python standard libraries avoid portability/security
+# issues raised by callouts in the ancestral Perl and sh scripts.  It should
+# be compatible back to Python 2.1.5
+#
+# usage: ciabot.py [-V] [-n] [-p projectname]  [refname [commits...]]
+#
+# This script is meant to be run either in a post-commit hook or in an
+# update hook.  If there's nothing unusual about your hosting setup,
+# you can specify the project name with a -p option and avoid having
+# to modify this script.  Try it with -n to see the notification mail
+# dumped to stdout and verify that it looks sane. With -V it dumps its
+# version and exits.
+#
+# In post-commit, run it without arguments (other than possibly a -p
+# option). It will query for current HEAD and the latest commit ID to
+# get the information it needs.
+#
+# In update, call it with a refname followed by a list of commits:
+# You want to reverse the order git rev-list emits becxause it lists
+# from most recent to oldest.
+#
+# /path/to/ciabot.py ${refname} $(git rev-list ${oldhead}..${newhead} | tac)
+#
+# Note: this script uses mail, not XML-RPC, in order to avoid stalling
+# until timeout when the CIA XML-RPC server is down.
+#
+
+#
+# The project as known to CIA. You will either want to change this
+# or invoke the script with a -p option to set it.
+#
+project=None
+
+#
+# You may not need to change these:
+#
+import os, sys, commands, socket, urllib
+
+# Name of the repository.
+# You can hardwire this to make the script faster.
+repo = os.path.basename(os.getcwd())
+
+# Fully-qualified domain name of this host.
+# You can hardwire this to make the script faster.
+host = socket.getfqdn()
+
+# Changeset URL prefix for your repo: when the commit ID is appended
+# to this, it should point at a CGI that will display the commit
+# through gitweb or something similar. The defaults will probably
+# work if you have a typical gitweb/cgit setup.
+#
+#urlprefix="http://%(host)s/cgi-bin/gitweb.cgi?p=%(repo)s;a=commit;h="
+urlprefix="http://%(host)s/cgi-bin/cgit.cgi/%(repo)s/commit/?id="
+
+# The service used to turn your gitwebbish URL into a tinyurl so it
+# will take up less space on the IRC notification line.
+tinyifier = "http://tinyurl.com/api-create.php?url="
+
+# The template used to generate the XML messages to CIA.  You can make
+# visible changes to the IRC-bot notification lines by hacking this.
+# The default will produce a notfication line that looks like this:
+#
+# ${project}: ${author} ${repo}:${branch} * ${rev} ${files}: ${logmsg} ${url}
+#
+# By omitting $files you can collapse the files part to a single slash.
+xml = '''\
+<message>
+  <generator>
+    <name>CIA Python client for Git</name>
+    <version>%(gitver)s</version>
+    <url>%(generator)s</url>
+  </generator>
+  <source>
+    <project>%(project)s</project>
+    <branch>%(repo)s:%(branch)s</branch>
+  </source>
+  <timestamp>%(ts)s</timestamp>
+  <body>
+    <commit>
+      <author>%(author)s</author>
+      <revision>%(rev)s</revision>
+      <files>
+        %(files)s
+      </files>
+      <log>%(logmsg)s %(url)s</log>
+      <url>%(url)s</url>
+    </commit>
+  </body>
+</message>
+'''
+
+#
+# No user-serviceable parts below this line:
+#
+
+# Addresses for the e-mail. The from address is a dummy, since CIA
+# will never reply to this mail.
+fromaddr = "CIABOT-NOREPLY@" + host
+toaddr = "cia@cia.navi.cx"
+
+# Identify the generator script.
+# Should only change when the script itself gets a new home and maintainer.
+generator="http://www.catb.org/~esr/ciabot.py"
+
+def do(command):
+    return commands.getstatusoutput(command)[1]
+
+def report(refname, merged):
+    "Generate a commit notification to be reported to CIA"
+
+    # Try to tinyfy a reference to a web view for this commit.
+    try:
+        url = open(urllib.urlretrieve(tinyifier + urlprefix + merged)[0]).read()
+    except:
+        url = urlprefix + merged
+
+    branch = os.path.basename(refname)
+
+    # Compute a shortnane for the revision
+    rev = do("git describe ${merged} 2>/dev/null") or merged[:12]
+
+    # Extract the neta-information for the commit
+    rawcommit = do("git cat-file commit " + merged)
+    files=do("git diff-tree -r --name-only '"+ merged +"' | sed -e '1d' -e 's-.*-<file>&</file>-'")
+    inheader = True
+    headers = {}
+    logmsg = ""
+    for line in rawcommit.split("\n"):
+        if inheader:
+            if line:
+                fields = line.split()
+                headers[fields[0]] = " ".join(fields[1:])
+            else:
+                inheader = False
+        else:
+            logmsg = line
+            break
+    (author, ts) = headers["author"].split(">")
+
+    # This discards the part of the authors addrsss after @.
+    # Might be bnicece to ship the full email address, if not
+    # for spammers' address harvesters - getting this wrong
+    # would make the freenode #commits channel into harvester heaven.
+    author = author.replace("<", "").split("@")[0].split()[-1]
+
+    # This ignores the timezone.  Not clear what to do with it...
+    ts = ts.strip().split()[0]
+
+    context = locals()
+    context.update(globals())
+
+    out = xml % context
+
+    message = '''\
+Message-ID: <%(merged)s.%(author)s@%(project)s>
+From: %(fromaddr)s
+To: %(toaddr)s
+Content-type: text/xml
+Subject: DeliverXML
+
+%(out)s''' % locals()
+
+    return message
+
+if __name__ == "__main__":
+    import getopt
+
+    try:
+        (options, arguments) = getopt.getopt(sys.argv[1:], "np:V")
+    except getopt.GetoptError, msg:
+        print "ciabot.py: " + str(msg)
+        raise SystemExit, 1
+
+    mailit = True
+    for (switch, val) in options:
+        if switch == '-p':
+            project = val
+        elif switch == '-n':
+            mailit = False
+        elif switch == '-V':
+            print "ciabot.py: version 3.2"
+            sys.exit(0)
+
+    # Cough and die if user has not specified a project
+    if not project:
+        sys.stderr.write("ciabot.py: no project specified, bailing out.\n")
+        sys.exit(1)
+
+    # We'll need the git version number.
+    gitver = do("git --version").split()[0]
+
+    urlprefix = urlprefix % globals()
+
+    # The script wants a reference to head followed by the list of
+    # commit ID to report about.
+    if len(arguments) == 0:
+        refname = do("git symbolic-ref HEAD 2>/dev/null")
+        merges = [do("git rev-parse HEAD")]
+    else:
+        refname = arguments[0]
+        merges = arguments[1:]
+
+    if mailit:
+        import smtplib
+        server = smtplib.SMTP('localhost')
+
+    for merged in merges:
+        message = report(refname, merged)
+        if mailit:
+            server.sendmail(fromaddr, [toaddr], message)
+        else:
+            print message
+
+    if mailit:
+        server.quit()
+
+#End
diff --git a/contrib/ciabot/ciabot.sh b/contrib/ciabot/ciabot.sh
new file mode 100755 (executable)
index 0000000..eb87bba
--- /dev/null
@@ -0,0 +1,192 @@
+#!/bin/sh
+# Distributed under the terms of the GNU General Public License v2
+# Copyright (c) 2006 Fernando J. Pereda <ferdy@gentoo.org>
+# Copyright (c) 2008 Natanael Copa <natanael.copa@gmail.com>
+# Copyright (c) 2010 Eric S. Raymond <esr@thyrsus.com>
+#
+# This is a version 3.x of ciabot.sh; use -V to find the exact
+# version.  Versions 1 and 2 were shipped in 2006 and 2008 and are not
+# version-stamped.  The version 2 maintainer has passed the baton.
+#
+# Note: This script should be considered obsolete.
+# There is a faster, better-documented rewrite in Python: find it as ciabot.py
+# Use this only if your hosting site forbids Python hooks.
+#
+# Originally based on Git ciabot.pl by Petr Baudis.
+# This script contains porcelain and porcelain byproducts.
+#
+# usage: ciabot.sh [-V] [-n] [-p projectname] [refname commit]
+#
+# This script is meant to be run either in a post-commit hook or in an
+# update hook.  If there's nothing unusual about your hosting setup,
+# you can specify the project name with a -p option and avoid having
+# to modify this script.  Try it with -n first to see the notification
+# mail dumped to stdout and verify that it looks sane.  Use -V to dump
+# the version and exit.
+#
+# In post-commit, run it without arguments (other than possibly a -p
+# option). It will query for current HEAD and the latest commit ID to
+# get the information it needs.
+#
+# In update, you have to call it once per merged commit:
+#
+#       refname=$1
+#       oldhead=$2
+#       newhead=$3
+#       for merged in $(git rev-list ${oldhead}..${newhead} | tac) ; do
+#               /path/to/ciabot.bash ${refname} ${merged}
+#       done
+#
+# The reason for the tac call ids that git rev-list emits commits from
+# most recent to least - better to ship notifactions from oldest to newest.
+#
+# Note: this script uses mail, not XML-RPC, in order to avoid stalling
+# until timeout when the CIA XML-RPC server is down.
+#
+
+#
+# The project as known to CIA. You will either want to change this
+# or set the project name with a -p option.
+#
+project=
+
+#
+# You may not need to change these:
+#
+
+# Name of the repository.
+# You can hardwire this to make the script faster.
+repo="`basename ${PWD}`"
+
+# Fully qualified domain name of the repo host.
+# You can hardwire this to make the script faster.
+host=`hostname --fqdn`
+
+# Changeset URL prefix for your repo: when the commit ID is appended
+# to this, it should point at a CGI that will display the commit
+# through gitweb or something similar. The defaults will probably
+# work if you have a typical gitweb/cgit setup.
+#urlprefix="http://${host}/cgi-bin/gitweb.cgi?p=${repo};a=commit;h="
+urlprefix="http://${host}/cgi-bin/cgit.cgi/${repo}/commit/?id="
+
+#
+# You probably will not need to change the following:
+#
+
+# Identify the script. Should change only when the script itself
+# gets a new home and maintainer.
+generator="http://www.catb.org/~esr/ciabot/ciabot.sh"
+
+# Addresses for the e-mail
+from="CIABOT-NOREPLY@${host}"
+to="cia@cia.navi.cx"
+
+# SMTP client to use - may need to edit the absolute pathname for your system
+sendmail="sendmail -t -f ${from}"
+
+#
+# No user-serviceable parts below this line:
+#
+
+# Should include all places sendmail is likely to lurk.
+PATH="$PATH:/usr/sbin/"
+
+mode=mailit
+while getopts pnV opt
+do
+    case $opt in
+       p) project=$2; shift ; shift ;;
+       n) mode=dumpit; shift ;;
+       V) echo "ciabot.sh: version 3.2"; exit 0; shift ;;
+    esac
+done
+
+# Cough and die if user has not specified a project
+if [ -z "$project" ]
+then
+    echo "ciabot.sh: no project specified, bailing out." >&2
+    exit 1
+fi
+
+if [ $# -eq 0 ] ; then
+       refname=$(git symbolic-ref HEAD 2>/dev/null)
+       merged=$(git rev-parse HEAD)
+else
+       refname=$1
+       merged=$2
+fi
+
+# This tries to turn your gitwebbish URL into a tinyurl so it will take up
+# less space on the IRC notification line. Some repo sites (I'm looking at
+# you, berlios.de!) forbid wget calls for security reasons.  On these,
+# the code will fall back to the full un-tinyfied URL.
+longurl=${urlprefix}${merged}
+url=$(wget -O - -q http://tinyurl.com/api-create.php?url=${longurl} 2>/dev/null)
+if [ -z "$url" ]; then
+       url="${longurl}"
+fi
+
+refname=${refname##refs/heads/}
+
+gitver=$(git --version)
+gitver=${gitver##* }
+
+rev=$(git describe ${merged} 2>/dev/null)
+# ${merged:0:12} was the only bashism left in the 2008 version of this
+# script, according to checkbashisms.  Replace it with ${merged} here
+# because it was just a fallback anyway, and it's worth accepting a
+# longer fallback for faster execution and removing the bash
+# dependency.
+[ -z ${rev} ] && rev=${merged}
+
+# This discards the part of the author's address after @.
+# Might be nice to ship the full email address, if not
+# for spammers' address harvesters - getting this wrong
+# would make the freenode #commits channel into harvester heaven.
+rawcommit=$(git cat-file commit ${merged})
+author=$(echo "$rawcommit" | sed -n -e '/^author .*<\([^@]*\).*$/s--\1-p')
+logmessage=$(echo "$rawcommit" | sed -e '1,/^$/d' | head -n 1)
+logmessage=$(echo "$logmessage" | sed 's/\&/&amp\;/g; s/</&lt\;/g; s/>/&gt\;/g')
+ts=$(echo "$rawcommit" | sed -n -e '/^author .*> \([0-9]\+\).*$/s--\1-p')
+files=$(git diff-tree -r --name-only ${merged} | sed -e '1d' -e 's-.*-<file>&</file>-')
+
+out="
+<message>
+  <generator>
+    <name>CIA Shell client for Git</name>
+    <version>${gitver}</version>
+    <url>${generator}</url>
+  </generator>
+  <source>
+    <project>${project}</project>
+    <branch>$repo:${refname}</branch>
+  </source>
+  <timestamp>${ts}</timestamp>
+  <body>
+    <commit>
+      <author>${author}</author>
+      <revision>${rev}</revision>
+      <files>
+       ${files}
+      </files>
+      <log>${logmessage} ${url}</log>
+      <url>${url}</url>
+    </commit>
+  </body>
+</message>"
+
+if [ "$mode" = "dumpit" ]
+then
+    sendmail=cat
+fi
+
+${sendmail} << EOM
+Message-ID: <${merged}.${author}@${project}>
+From: ${from}
+To: ${to}
+Content-type: text/xml
+Subject: DeliverXML
+${out}
+EOM
+
+# vim: set tw=70 :
index 1683e6d7b8bc677469d52acf4d6ff187e084cf3a..67569901e71e5062199e48304afc424f15b57ba1 100755 (executable)
 #    2) Added the following line to your .bashrc:
 #        source ~/.git-completion.sh
 #
-#    3) You may want to make sure the git executable is available
-#       in your PATH before this script is sourced, as some caching
-#       is performed while the script loads.  If git isn't found
-#       at source time then all lookups will be done on demand,
-#       which may be slightly slower.
-#
-#    4) Consider changing your PS1 to also show the current branch:
+#    3) Consider changing your PS1 to also show the current branch:
 #        PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
 #
 #       The argument to __git_ps1 will be displayed only if you
 #       with the bash.showDirtyState variable, which defaults to true
 #       once GIT_PS1_SHOWDIRTYSTATE is enabled.
 #
+#       You can also see if currently something is stashed, by setting
+#       GIT_PS1_SHOWSTASHSTATE to a nonempty value. If something is stashed,
+#       then a '$' will be shown next to the branch name.
+#
+#       If you would like to see if there're untracked files, then you can
+#       set GIT_PS1_SHOWUNTRACKEDFILES to a nonempty value. If there're
+#       untracked files, then a '%' will be shown next to the branch name.
+#
+#       If you would like to see the difference between HEAD and its
+#       upstream, set GIT_PS1_SHOWUPSTREAM="auto".  A "<" indicates
+#       you are behind, ">" indicates you are ahead, and "<>"
+#       indicates you have diverged.  You can further control
+#       behaviour by setting GIT_PS1_SHOWUPSTREAM to a space-separated
+#       list of values:
+#           verbose       show number of commits ahead/behind (+/-) upstream
+#           legacy        don't use the '--count' option available in recent
+#                         versions of git-rev-list
+#           git           always compare HEAD to @{upstream}
+#           svn           always compare HEAD to your SVN upstream
+#       By default, __git_ps1 will compare HEAD to your SVN upstream
+#       if it can find one, or @{upstream} otherwise.  Once you have
+#       set GIT_PS1_SHOWUPSTREAM, you can override it on a
+#       per-repository basis by setting the bash.showUpstream config
+#       variable.
+#
+#
 # To submit patches:
 #
 #    *) Read Documentation/SubmittingPatches
@@ -76,51 +96,183 @@ __gitdir ()
        fi
 }
 
+# stores the divergence from upstream in $p
+# used by GIT_PS1_SHOWUPSTREAM
+__git_ps1_show_upstream ()
+{
+       local key value
+       local svn_remote=() svn_url_pattern count n
+       local upstream=git legacy="" verbose=""
+
+       # get some config options from git-config
+       while read key value; do
+               case "$key" in
+               bash.showupstream)
+                       GIT_PS1_SHOWUPSTREAM="$value"
+                       if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then
+                               p=""
+                               return
+                       fi
+                       ;;
+               svn-remote.*.url)
+                       svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value"
+                       svn_url_pattern+="\\|$value"
+                       upstream=svn+git # default upstream is SVN if available, else git
+                       ;;
+               esac
+       done < <(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')
+
+       # parse configuration values
+       for option in ${GIT_PS1_SHOWUPSTREAM}; do
+               case "$option" in
+               git|svn) upstream="$option" ;;
+               verbose) verbose=1 ;;
+               legacy)  legacy=1  ;;
+               esac
+       done
+
+       # Find our upstream
+       case "$upstream" in
+       git)    upstream="@{upstream}" ;;
+       svn*)
+               # get the upstream from the "git-svn-id: ..." in a commit message
+               # (git-svn uses essentially the same procedure internally)
+               local svn_upstream=($(git log --first-parent -1 \
+                                       --grep="^git-svn-id: \(${svn_url_pattern:2}\)" 2>/dev/null))
+               if [[ 0 -ne ${#svn_upstream[@]} ]]; then
+                       svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]}
+                       svn_upstream=${svn_upstream%@*}
+                       for ((n=1; "$n" <= "${#svn_remote[@]}"; ++n)); do
+                               svn_upstream=${svn_upstream#${svn_remote[$n]}}
+                       done
+
+                       if [[ -z "$svn_upstream" ]]; then
+                               # default branch name for checkouts with no layout:
+                               upstream=${GIT_SVN_ID:-git-svn}
+                       else
+                               upstream=${svn_upstream#/}
+                       fi
+               elif [[ "svn+git" = "$upstream" ]]; then
+                       upstream="@{upstream}"
+               fi
+               ;;
+       esac
+
+       # Find how many commits we are ahead/behind our upstream
+       if [[ -z "$legacy" ]]; then
+               count="$(git rev-list --count --left-right \
+                               "$upstream"...HEAD 2>/dev/null)"
+       else
+               # produce equivalent output to --count for older versions of git
+               local commits
+               if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)"
+               then
+                       local commit behind=0 ahead=0
+                       for commit in $commits
+                       do
+                               case "$commit" in
+                               "<"*) let ++behind
+                                       ;;
+                               *)    let ++ahead
+                                       ;;
+                               esac
+                       done
+                       count="$behind  $ahead"
+               else
+                       count=""
+               fi
+       fi
+
+       # calculate the result
+       if [[ -z "$verbose" ]]; then
+               case "$count" in
+               "") # no upstream
+                       p="" ;;
+               "0      0") # equal to upstream
+                       p="=" ;;
+               "0      "*) # ahead of upstream
+                       p=">" ;;
+               *"      0") # behind upstream
+                       p="<" ;;
+               *)          # diverged from upstream
+                       p="<>" ;;
+               esac
+       else
+               case "$count" in
+               "") # no upstream
+                       p="" ;;
+               "0      0") # equal to upstream
+                       p=" u=" ;;
+               "0      "*) # ahead of upstream
+                       p=" u+${count#0 }" ;;
+               *"      0") # behind upstream
+                       p=" u-${count%  0}" ;;
+               *)          # diverged from upstream
+                       p=" u+${count#* }-${count%      *}" ;;
+               esac
+       fi
+
+}
+
+
 # __git_ps1 accepts 0 or 1 arguments (i.e., format string)
 # returns text to add to bash PS1 prompt (includes branch name)
 __git_ps1 ()
 {
        local g="$(__gitdir)"
        if [ -n "$g" ]; then
-               local r
-               local b
-               if [ -d "$g/rebase-apply" ]; then
-                       if [ -f "$g/rebase-apply/rebasing" ]; then
-                               r="|REBASE"
-               elif [ -f "$g/rebase-apply/applying" ]; then
-                               r="|AM"
-                       else
-                               r="|AM/REBASE"
-                       fi
-                       b="$(git symbolic-ref HEAD 2>/dev/null)"
-               elif [ -f "$g/rebase-merge/interactive" ]; then
+               local r=""
+               local b=""
+               if [ -f "$g/rebase-merge/interactive" ]; then
                        r="|REBASE-i"
                        b="$(cat "$g/rebase-merge/head-name")"
                elif [ -d "$g/rebase-merge" ]; then
                        r="|REBASE-m"
                        b="$(cat "$g/rebase-merge/head-name")"
-               elif [ -f "$g/MERGE_HEAD" ]; then
-                       r="|MERGING"
-                       b="$(git symbolic-ref HEAD 2>/dev/null)"
                else
-                       if [ -f "$g/BISECT_LOG" ]; then
-                               r="|BISECTING"
-                       fi
-                       if ! b="$(git symbolic-ref HEAD 2>/dev/null)"; then
-                               if ! b="$(git describe --exact-match HEAD 2>/dev/null)"; then
-                                       if [ -r "$g/HEAD" ]; then
-                                               b="$(cut -c1-7 "$g/HEAD")..."
-                                       fi
+                       if [ -d "$g/rebase-apply" ]; then
+                               if [ -f "$g/rebase-apply/rebasing" ]; then
+                                       r="|REBASE"
+                               elif [ -f "$g/rebase-apply/applying" ]; then
+                                       r="|AM"
+                               else
+                                       r="|AM/REBASE"
                                fi
+                       elif [ -f "$g/MERGE_HEAD" ]; then
+                               r="|MERGING"
+                       elif [ -f "$g/BISECT_LOG" ]; then
+                               r="|BISECTING"
                        fi
+
+                       b="$(git symbolic-ref HEAD 2>/dev/null)" || {
+
+                               b="$(
+                               case "${GIT_PS1_DESCRIBE_STYLE-}" in
+                               (contains)
+                                       git describe --contains HEAD ;;
+                               (branch)
+                                       git describe --contains --all HEAD ;;
+                               (describe)
+                                       git describe HEAD ;;
+                               (* | default)
+                                       git describe --exact-match HEAD ;;
+                               esac 2>/dev/null)" ||
+
+                               b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." ||
+                               b="unknown"
+                               b="($b)"
+                       }
                fi
 
-               local w
-               local i
-               local c
+               local w=""
+               local i=""
+               local s=""
+               local u=""
+               local c=""
+               local p=""
 
                if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then
-                       if [ "true" = "$(git config --bool core.bare 2>/dev/null)" ]; then
+                       if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then
                                c="BARE:"
                        else
                                b="GIT_DIR!"
@@ -128,25 +280,31 @@ __git_ps1 ()
                elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then
                        if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then
                                if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then
-                                       git diff --no-ext-diff --ignore-submodules \
-                                               --quiet --exit-code || w="*"
+                                       git diff --no-ext-diff --quiet --exit-code || w="*"
                                        if git rev-parse --quiet --verify HEAD >/dev/null; then
-                                               git diff-index --cached --quiet \
-                                                       --ignore-submodules HEAD -- || i="+"
+                                               git diff-index --cached --quiet HEAD -- || i="+"
                                        else
                                                i="#"
                                        fi
                                fi
                        fi
-               fi
+                       if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then
+                               git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$"
+                       fi
 
-               if [ -n "$b" ]; then
-                       if [ -n "${1-}" ]; then
-                               printf "$1" "$c${b##refs/heads/}$w$i$r"
-                       else
-                               printf " (%s)" "$c${b##refs/heads/}$w$i$r"
+                       if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then
+                          if [ -n "$(git ls-files --others --exclude-standard)" ]; then
+                             u="%"
+                          fi
+                       fi
+
+                       if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
+                               __git_ps1_show_upstream
                        fi
                fi
+
+               local f="$w$i$s$u"
+               printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p"
        fi
 }
 
@@ -234,7 +392,9 @@ __git_refs ()
                        refs="${cur%/*}"
                        ;;
                *)
-                       if [ -e "$dir/HEAD" ]; then echo HEAD; fi
+                       for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD; do
+                               if [ -e "$dir/$i" ]; then echo $i; fi
+                       done
                        format="refname:short"
                        refs="refs/tags refs/heads refs/remotes"
                        ;;
@@ -291,22 +451,14 @@ __git_remotes ()
                echo ${i#$d/remotes/}
        done
        [ "$ngoff" ] && shopt -u nullglob
-       for i in $(git --git-dir="$d" config --list); do
-               case "$i" in
-               remote.*.url=*)
-                       i="${i#remote.}"
-                       echo "${i/.url=*/}"
-                       ;;
-               esac
+       for i in $(git --git-dir="$d" config --get-regexp 'remote\..*\.url' 2>/dev/null); do
+               i="${i#remote.}"
+               echo "${i/.url*/}"
        done
 }
 
-__git_merge_strategies ()
+__git_list_merge_strategies ()
 {
-       if [ -n "${__git_merge_strategylist-}" ]; then
-               echo "$__git_merge_strategylist"
-               return
-       fi
        git merge -s help 2>&1 |
        sed -n -e '/[Aa]vailable strategies are: /,/^$/{
                s/\.$//
@@ -316,8 +468,17 @@ __git_merge_strategies ()
                p
        }'
 }
-__git_merge_strategylist=
-__git_merge_strategylist=$(__git_merge_strategies 2>/dev/null)
+
+__git_merge_strategies=
+# 'git merge -s help' (and thus detection of the merge strategy
+# list) fails, unfortunately, if run outside of any git working
+# tree.  __git_merge_strategies is set to the empty string in
+# that case, and the detection will be repeated the next time it
+# is needed.
+__git_compute_merge_strategies ()
+{
+       : ${__git_merge_strategies:=$(__git_list_merge_strategies)}
+}
 
 __git_complete_file ()
 {
@@ -395,7 +556,17 @@ __git_complete_remote_or_refspec ()
        while [ $c -lt $COMP_CWORD ]; do
                i="${COMP_WORDS[c]}"
                case "$i" in
-               --all|--mirror) [ "$cmd" = "push" ] && no_complete_refspec=1 ;;
+               --mirror) [ "$cmd" = "push" ] && no_complete_refspec=1 ;;
+               --all)
+                       case "$cmd" in
+                       push) no_complete_refspec=1 ;;
+                       fetch)
+                               COMPREPLY=()
+                               return
+                               ;;
+                       *) ;;
+                       esac
+                       ;;
                -*) ;;
                *) remote="$i"; break ;;
                esac
@@ -451,29 +622,26 @@ __git_complete_remote_or_refspec ()
 
 __git_complete_strategy ()
 {
+       __git_compute_merge_strategies
        case "${COMP_WORDS[COMP_CWORD-1]}" in
        -s|--strategy)
-               __gitcomp "$(__git_merge_strategies)"
+               __gitcomp "$__git_merge_strategies"
                return 0
        esac
        local cur="${COMP_WORDS[COMP_CWORD]}"
        case "$cur" in
        --strategy=*)
-               __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}"
+               __gitcomp "$__git_merge_strategies" "" "${cur##--strategy=}"
                return 0
                ;;
        esac
        return 1
 }
 
-__git_all_commands ()
+__git_list_all_commands ()
 {
-       if [ -n "${__git_all_commandlist-}" ]; then
-               echo "$__git_all_commandlist"
-               return
-       fi
        local i IFS=" "$'\n'
-       for i in $(git help -a|egrep '^ ')
+       for i in $(git help -a|egrep '^  [a-zA-Z0-9]')
        do
                case $i in
                *--*)             : helper pattern;;
@@ -481,17 +649,18 @@ __git_all_commands ()
                esac
        done
 }
-__git_all_commandlist=
-__git_all_commandlist="$(__git_all_commands 2>/dev/null)"
 
-__git_porcelain_commands ()
+__git_all_commands=
+__git_compute_all_commands ()
+{
+       : ${__git_all_commands:=$(__git_list_all_commands)}
+}
+
+__git_list_porcelain_commands ()
 {
-       if [ -n "${__git_porcelain_commandlist-}" ]; then
-               echo "$__git_porcelain_commandlist"
-               return
-       fi
        local i IFS=" "$'\n'
-       for i in "help" $(__git_all_commands)
+       __git_compute_all_commands
+       for i in "help" $__git_all_commands
        do
                case $i in
                *--*)             : helper pattern;;
@@ -543,6 +712,7 @@ __git_porcelain_commands ()
                read-tree)        : plumbing;;
                receive-pack)     : plumbing;;
                reflog)           : plumbing;;
+               remote-*)         : transport;;
                repo-config)      : deprecated;;
                rerere)           : plumbing;;
                rev-list)         : plumbing;;
@@ -572,17 +742,22 @@ __git_porcelain_commands ()
                esac
        done
 }
-__git_porcelain_commandlist=
-__git_porcelain_commandlist="$(__git_porcelain_commands 2>/dev/null)"
+
+__git_porcelain_commands=
+__git_compute_porcelain_commands ()
+{
+       __git_compute_all_commands
+       : ${__git_porcelain_commands:=$(__git_list_porcelain_commands)}
+}
 
 __git_aliases ()
 {
        local i IFS=$'\n'
-       for i in $(git --git-dir="$(__gitdir)" config --list); do
+       for i in $(git --git-dir="$(__gitdir)" config --get-regexp "alias\..*" 2>/dev/null); do
                case "$i" in
                alias.*)
                        i="${i#alias.}"
-                       echo "${i/=*/}"
+                       echo "${i/ */}"
                        ;;
                esac
        done
@@ -594,15 +769,24 @@ __git_aliased_command ()
        local word cmdline=$(git --git-dir="$(__gitdir)" \
                config --get "alias.$1")
        for word in $cmdline; do
-               if [ "${word##-*}" ]; then
-                       echo $word
+               case "$word" in
+               \!gitk|gitk)
+                       echo "gitk"
                        return
-               fi
+                       ;;
+               \!*)    : shell command alias ;;
+               -*)     : option ;;
+               *=*)    : setting env ;;
+               git)    : git itself ;;
+               *)
+                       echo "$word"
+                       return
+               esac
        done
 }
 
-# __git_find_subcommand requires 1 argument
-__git_find_subcommand ()
+# __git_find_on_cmdline requires 1 argument
+__git_find_on_cmdline ()
 {
        local word subcommand c=1
 
@@ -636,7 +820,7 @@ _git_am ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)"
        if [ -d "$dir"/rebase-apply ]; then
-               __gitcomp "--skip --resolved --abort"
+               __gitcomp "--skip --continue --resolved --abort"
                return
        fi
        case "$cur" in
@@ -647,8 +831,9 @@ _git_am ()
        --*)
                __gitcomp "
                        --3way --committer-date-is-author-date --ignore-date
+                       --ignore-whitespace --ignore-space-change
                        --interactive --keep --no-utf8 --signoff --utf8
-                       --whitespace=
+                       --whitespace= --scissors
                        "
                return
        esac
@@ -668,6 +853,7 @@ _git_apply ()
                        --stat --numstat --summary --check --index
                        --cached --index-info --reverse --reject --unidiff-zero
                        --apply --no-add --exclude=
+                       --ignore-whitespace --ignore-space-change
                        --whitespace= --inaccurate-eof --verbose
                        "
                return
@@ -719,7 +905,7 @@ _git_bisect ()
        __git_has_doubledash && return
 
        local subcommands="start bad good skip reset visualize replay log run"
-       local subcommand="$(__git_find_subcommand "$subcommands")"
+       local subcommand="$(__git_find_on_cmdline "$subcommands")"
        if [ -z "$subcommand" ]; then
                __gitcomp "$subcommands"
                return
@@ -753,6 +939,7 @@ _git_branch ()
                __gitcomp "
                        --color --no-color --verbose --abbrev= --no-abbrev
                        --track --no-track --contains --merged --no-merged
+                       --set-upstream
                        "
                ;;
        *)
@@ -789,7 +976,21 @@ _git_checkout ()
 {
        __git_has_doubledash && return
 
-       __gitcomp "$(__git_refs)"
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       case "$cur" in
+       --conflict=*)
+               __gitcomp "diff3 merge" "" "${cur##--conflict=}"
+               ;;
+       --*)
+               __gitcomp "
+                       --quiet --ours --theirs --track --no-track --merge
+                       --conflict= --orphan --patch
+                       "
+               ;;
+       *)
+               __gitcomp "$(__git_refs)"
+               ;;
+       esac
 }
 
 _git_cherry ()
@@ -855,10 +1056,31 @@ _git_commit ()
 
        local cur="${COMP_WORDS[COMP_CWORD]}"
        case "$cur" in
+       --cleanup=*)
+               __gitcomp "default strip verbatim whitespace
+                       " "" "${cur##--cleanup=}"
+               return
+               ;;
+       --reuse-message=*)
+               __gitcomp "$(__git_refs)" "" "${cur##--reuse-message=}"
+               return
+               ;;
+       --reedit-message=*)
+               __gitcomp "$(__git_refs)" "" "${cur##--reedit-message=}"
+               return
+               ;;
+       --untracked-files=*)
+               __gitcomp "all no normal" "" "${cur##--untracked-files=}"
+               return
+               ;;
        --*)
                __gitcomp "
                        --all --author= --signoff --verify --no-verify
                        --edit --amend --include --only --interactive
+                       --dry-run --reuse-message= --reedit-message=
+                       --reset-author --file= --message= --template=
+                       --cleanup= --untracked-files --untracked-files=
+                       --verbose --quiet
                        "
                return
        esac
@@ -891,6 +1113,8 @@ __git_diff_common_options="--stat --numstat --shortstat --summary
                        --inter-hunk-context=
                        --patience
                        --raw
+                       --dirstat --dirstat= --dirstat-by-file
+                       --dirstat-by-file= --cumulative
 "
 
 _git_diff ()
@@ -911,11 +1135,13 @@ _git_diff ()
 }
 
 __git_mergetools_common="diffuse ecmerge emerge kdiff3 meld opendiff
-                       tkdiff vimdiff gvimdiff xxdiff
+                       tkdiff vimdiff gvimdiff xxdiff araxis p4merge
 "
 
 _git_difftool ()
 {
+       __git_has_doubledash && return
+
        local cur="${COMP_WORDS[COMP_CWORD]}"
        case "$cur" in
        --tool=*)
@@ -923,16 +1149,20 @@ _git_difftool ()
                return
                ;;
        --*)
-               __gitcomp "--tool="
+               __gitcomp "--cached --staged --pickaxe-all --pickaxe-regex
+                       --base --ours --theirs
+                       --no-renames --diff-filter= --find-copies-harder
+                       --relative --ignore-submodules
+                       --tool="
                return
                ;;
        esac
-       COMPREPLY=()
+       __git_complete_file
 }
 
 __git_fetch_options="
        --quiet --verbose --append --upload-pack --force --keep --depth=
-       --tags --no-tags
+       --tags --no-tags --all --prune --dry-run
 "
 
 _git_fetch ()
@@ -964,7 +1194,7 @@ _git_format_patch ()
                        --numbered --start-number
                        --numbered-files
                        --keep-subject
-                       --signoff
+                       --signoff --signature --no-signature
                        --in-reply-to= --cc=
                        --full-index --binary
                        --not --all
@@ -1006,6 +1236,11 @@ _git_gc ()
        COMPREPLY=()
 }
 
+_git_gitk ()
+{
+       _gitk
+}
+
 _git_grep ()
 {
        __git_has_doubledash && return
@@ -1020,13 +1255,15 @@ _git_grep ()
                        --extended-regexp --basic-regexp --fixed-strings
                        --files-with-matches --name-only
                        --files-without-match
+                       --max-depth
                        --count
                        --and --or --not --all-match
                        "
                return
                ;;
        esac
-       COMPREPLY=()
+
+       __gitcomp "$(__git_refs)"
 }
 
 _git_help ()
@@ -1038,7 +1275,8 @@ _git_help ()
                return
                ;;
        esac
-       __gitcomp "$(__git_all_commands)
+       __git_compute_all_commands
+       __gitcomp "$__git_all_commands
                attributes cli core-tutorial cvs-migration
                diffcore gitk glossary hooks ignore modules
                repository-layout tutorial tutorial-2
@@ -1098,7 +1336,7 @@ _git_ls_tree ()
 __git_log_common_options="
        --not --all
        --branches --tags --remotes
-       --first-parent --no-merges
+       --first-parent --merges --no-merges
        --max-count=
        --max-age= --since= --after=
        --min-age= --until= --before=
@@ -1143,19 +1381,23 @@ _git_log ()
                __gitcomp "$__git_log_date_formats" "" "${cur##--date=}"
                return
                ;;
+       --decorate=*)
+               __gitcomp "long short" "" "${cur##--decorate=}"
+               return
+               ;;
        --*)
                __gitcomp "
                        $__git_log_common_options
                        $__git_log_shortlog_options
                        $__git_log_gitk_options
                        --root --topo-order --date-order --reverse
-                       --follow
+                       --follow --full-diff
                        --abbrev-commit --abbrev=
                        --relative-date --date=
                        --pretty= --format= --oneline
                        --cherry-pick
                        --graph
-                       --decorate
+                       --decorate --decorate=
                        --walk-reflogs
                        --parents --children
                        $merge
@@ -1170,7 +1412,7 @@ _git_log ()
 
 __git_merge_options="
        --no-commit --no-stat --log --no-log --squash --strategy
-       --commit --stat --no-squash --ff --no-ff
+       --commit --stat --no-squash --ff --no-ff --ff-only
 "
 
 _git_merge ()
@@ -1224,6 +1466,24 @@ _git_name_rev ()
        __gitcomp "--tags --all --stdin"
 }
 
+_git_notes ()
+{
+       local subcommands="edit show"
+       if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then
+               __gitcomp "$subcommands"
+               return
+       fi
+
+       case "${COMP_WORDS[COMP_CWORD-1]}" in
+       -m|-F)
+               COMPREPLY=()
+               ;;
+       *)
+               __gitcomp "$(__git_refs)"
+               ;;
+       esac
+}
+
 _git_pull ()
 {
        __git_complete_strategy && return
@@ -1275,15 +1535,26 @@ _git_rebase ()
        fi
        __git_complete_strategy && return
        case "$cur" in
+       --whitespace=*)
+               __gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}"
+               return
+               ;;
        --*)
-               __gitcomp "--onto --merge --strategy --interactive"
+               __gitcomp "
+                       --onto --merge --strategy --interactive
+                       --preserve-merges --stat --no-stat
+                       --committer-date-is-author-date --ignore-date
+                       --ignore-whitespace --whitespace=
+                       --autosquash
+                       "
+
                return
        esac
        __gitcomp "$(__git_refs)"
 }
 
 __git_send_email_confirm_options="always never auto cc compose"
-__git_send_email_suppresscc_options="author self cc ccbody sob cccmd body all"
+__git_send_email_suppresscc_options="author self cc bodycc sob cccmd body all"
 
 _git_send_email ()
 {
@@ -1322,6 +1593,41 @@ _git_send_email ()
        COMPREPLY=()
 }
 
+_git_stage ()
+{
+       _git_add
+}
+
+__git_config_get_set_variables ()
+{
+       local prevword word config_file= c=$COMP_CWORD
+       while [ $c -gt 1 ]; do
+               word="${COMP_WORDS[c]}"
+               case "$word" in
+               --global|--system|--file=*)
+                       config_file="$word"
+                       break
+                       ;;
+               -f|--file)
+                       config_file="$word $prevword"
+                       break
+                       ;;
+               esac
+               prevword=$word
+               c=$((--c))
+       done
+
+       git --git-dir="$(__gitdir)" config $config_file --list 2>/dev/null |
+       while read line
+       do
+               case "$line" in
+               *.*=*)
+                       echo "${line/=*/}"
+                       ;;
+               esac
+       done
+}
+
 _git_config ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
@@ -1350,10 +1656,12 @@ _git_config ()
                return
                ;;
        pull.twohead|pull.octopus)
-               __gitcomp "$(__git_merge_strategies)"
+               __git_compute_merge_strategies
+               __gitcomp "$__git_merge_strategies"
                return
                ;;
-       color.branch|color.diff|color.interactive|color.status|color.ui)
+       color.branch|color.diff|color.interactive|\
+       color.showbranch|color.status|color.ui)
                __gitcomp "always never auto"
                return
                ;;
@@ -1388,6 +1696,10 @@ _git_config ()
                __gitcomp "$__git_send_email_suppresscc_options"
                return
                ;;
+       --get|--get-all|--unset|--unset-all)
+               __gitcomp "$(__git_config_get_set_variables)"
+               return
+               ;;
        *.*)
                COMPREPLY=()
                return
@@ -1407,7 +1719,7 @@ _git_config ()
        branch.*.*)
                local pfx="${cur%.*}."
                cur="${cur##*.}"
-               __gitcomp "remote merge mergeoptions" "$pfx" "$cur"
+               __gitcomp "remote merge mergeoptions rebase" "$pfx" "$cur"
                return
                ;;
        branch.*)
@@ -1446,7 +1758,8 @@ _git_config ()
        pager.*)
                local pfx="${cur%.*}."
                cur="${cur#*.}"
-               __gitcomp "$(__git_all_commands)" "$pfx" "$cur"
+               __git_compute_all_commands
+               __gitcomp "$__git_all_commands" "$pfx" "$cur"
                return
                ;;
        remote.*.*)
@@ -1454,7 +1767,7 @@ _git_config ()
                cur="${cur##*.}"
                __gitcomp "
                        url proxy fetch push mirror skipDefaultUpdate
-                       receivepack uploadpack tagopt
+                       receivepack uploadpack tagopt pushurl
                        " "$pfx" "$cur"
                return
                ;;
@@ -1467,12 +1780,14 @@ _git_config ()
        url.*.*)
                local pfx="${cur%.*}."
                cur="${cur##*.}"
-               __gitcomp "insteadof" "$pfx" "$cur"
+               __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur"
                return
                ;;
        esac
        __gitcomp "
+               add.ignore-errors
                alias.
+               apply.ignorewhitespace
                apply.whitespace
                branch.autosetupmerge
                branch.autosetuprebase
@@ -1498,6 +1813,7 @@ _git_config ()
                color.interactive.help
                color.interactive.prompt
                color.pager
+               color.showbranch
                color.status
                color.status.added
                color.status.changed
@@ -1552,6 +1868,7 @@ _git_config ()
                format.headers
                format.numbered
                format.pretty
+               format.signature
                format.signoff
                format.subjectprefix
                format.suffix
@@ -1689,7 +2006,7 @@ _git_config ()
 _git_remote ()
 {
        local subcommands="add rename rm show prune update set-head"
-       local subcommand="$(__git_find_subcommand "$subcommands")"
+       local subcommand="$(__git_find_on_cmdline "$subcommands")"
        if [ -z "$subcommand" ]; then
                __gitcomp "$subcommands"
                return
@@ -1701,13 +2018,9 @@ _git_remote ()
                ;;
        update)
                local i c='' IFS=$'\n'
-               for i in $(git --git-dir="$(__gitdir)" config --list); do
-                       case "$i" in
-                       remotes.*)
-                               i="${i#remotes.}"
-                               c="$c ${i/=*/}"
-                               ;;
-                       esac
+               for i in $(git --git-dir="$(__gitdir)" config --get-regexp "remotes\..*" 2>/dev/null); do
+                       i="${i#remotes.}"
+                       c="$c ${i/ */}"
                done
                __gitcomp "$c"
                ;;
@@ -1717,6 +2030,11 @@ _git_remote ()
        esac
 }
 
+_git_replace ()
+{
+       __gitcomp "$(__git_refs)"
+}
+
 _git_reset ()
 {
        __git_has_doubledash && return
@@ -1724,7 +2042,7 @@ _git_reset ()
        local cur="${COMP_WORDS[COMP_CWORD]}"
        case "$cur" in
        --*)
-               __gitcomp "--merge --mixed --hard --soft"
+               __gitcomp "--merge --mixed --hard --soft --patch"
                return
                ;;
        esac
@@ -1792,7 +2110,7 @@ _git_show ()
                return
                ;;
        --*)
-               __gitcomp "--pretty= --format=
+               __gitcomp "--pretty= --format= --abbrev-commit --oneline
                        $__git_diff_common_options
                        "
                return
@@ -1809,7 +2127,8 @@ _git_show_branch ()
                __gitcomp "
                        --all --remotes --topo-order --current --more=
                        --list --independent --merge-base --no-name
-                       --sha1-name --topics --reflog
+                       --color --no-color
+                       --sha1-name --sparse --topics --reflog
                        "
                return
                ;;
@@ -1819,20 +2138,32 @@ _git_show_branch ()
 
 _git_stash ()
 {
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       local save_opts='--keep-index --no-keep-index --quiet --patch'
        local subcommands='save list show apply clear drop pop create branch'
-       local subcommand="$(__git_find_subcommand "$subcommands")"
+       local subcommand="$(__git_find_on_cmdline "$subcommands")"
        if [ -z "$subcommand" ]; then
-               __gitcomp "$subcommands"
+               case "$cur" in
+               --*)
+                       __gitcomp "$save_opts"
+                       ;;
+               *)
+                       if [ -z "$(__git_find_on_cmdline "$save_opts")" ]; then
+                               __gitcomp "$subcommands"
+                       else
+                               COMPREPLY=()
+                       fi
+                       ;;
+               esac
        else
-               local cur="${COMP_WORDS[COMP_CWORD]}"
                case "$subcommand,$cur" in
                save,--*)
-                       __gitcomp "--keep-index"
+                       __gitcomp "$save_opts"
                        ;;
-               apply,--*)
-                       __gitcomp "--index"
+               apply,--*|pop,--*)
+                       __gitcomp "--index --quiet"
                        ;;
-               show,--*|drop,--*|pop,--*|branch,--*)
+               show,--*|drop,--*|branch,--*)
                        COMPREPLY=()
                        ;;
                show,*|apply,*|drop,*|pop,*|branch,*)
@@ -1851,7 +2182,7 @@ _git_submodule ()
        __git_has_doubledash && return
 
        local subcommands="add status init update summary foreach sync"
-       if [ -z "$(__git_find_subcommand "$subcommands")" ]; then
+       if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then
                local cur="${COMP_WORDS[COMP_CWORD]}"
                case "$cur" in
                --*)
@@ -1871,9 +2202,9 @@ _git_svn ()
                init fetch clone rebase dcommit log find-rev
                set-tree commit-diff info create-ignore propget
                proplist show-ignore show-externals branch tag blame
-               migrate
+               migrate mkdirs reset gc
                "
-       local subcommand="$(__git_find_subcommand "$subcommands")"
+       local subcommand="$(__git_find_on_cmdline "$subcommands")"
        if [ -z "$subcommand" ]; then
                __gitcomp "$subcommands"
        else
@@ -1918,7 +2249,7 @@ _git_svn ()
                        __gitcomp "--stdin $cmt_opts $fc_opts"
                        ;;
                create-ignore,--*|propget,--*|proplist,--*|show-ignore,--*|\
-               show-externals,--*)
+               show-externals,--*|mkdirs,--*)
                        __gitcomp "--revision="
                        ;;
                log,--*)
@@ -1955,6 +2286,9 @@ _git_svn ()
                                --no-auth-cache --username=
                                "
                        ;;
+               reset,--*)
+                       __gitcomp "--revision= --parent"
+                       ;;
                *)
                        COMPREPLY=()
                        ;;
@@ -1996,6 +2330,11 @@ _git_tag ()
        esac
 }
 
+_git_whatchanged ()
+{
+       _git_log
+}
+
 _git ()
 {
        local i c=1 command __git_dir
@@ -2026,67 +2365,20 @@ _git ()
                        --help
                        "
                        ;;
-               *)     __gitcomp "$(__git_porcelain_commands) $(__git_aliases)" ;;
+               *)     __git_compute_porcelain_commands
+                      __gitcomp "$__git_porcelain_commands $(__git_aliases)" ;;
                esac
                return
        fi
 
+       local completion_func="_git_${command//-/_}"
+       declare -F $completion_func >/dev/null && $completion_func && return
+
        local expansion=$(__git_aliased_command "$command")
-       [ "$expansion" ] && command="$expansion"
-
-       case "$command" in
-       am)          _git_am ;;
-       add)         _git_add ;;
-       apply)       _git_apply ;;
-       archive)     _git_archive ;;
-       bisect)      _git_bisect ;;
-       bundle)      _git_bundle ;;
-       branch)      _git_branch ;;
-       checkout)    _git_checkout ;;
-       cherry)      _git_cherry ;;
-       cherry-pick) _git_cherry_pick ;;
-       clean)       _git_clean ;;
-       clone)       _git_clone ;;
-       commit)      _git_commit ;;
-       config)      _git_config ;;
-       describe)    _git_describe ;;
-       diff)        _git_diff ;;
-       difftool)    _git_difftool ;;
-       fetch)       _git_fetch ;;
-       format-patch) _git_format_patch ;;
-       fsck)        _git_fsck ;;
-       gc)          _git_gc ;;
-       grep)        _git_grep ;;
-       help)        _git_help ;;
-       init)        _git_init ;;
-       log)         _git_log ;;
-       ls-files)    _git_ls_files ;;
-       ls-remote)   _git_ls_remote ;;
-       ls-tree)     _git_ls_tree ;;
-       merge)       _git_merge;;
-       mergetool)   _git_mergetool;;
-       merge-base)  _git_merge_base ;;
-       mv)          _git_mv ;;
-       name-rev)    _git_name_rev ;;
-       pull)        _git_pull ;;
-       push)        _git_push ;;
-       rebase)      _git_rebase ;;
-       remote)      _git_remote ;;
-       reset)       _git_reset ;;
-       revert)      _git_revert ;;
-       rm)          _git_rm ;;
-       send-email)  _git_send_email ;;
-       shortlog)    _git_shortlog ;;
-       show)        _git_show ;;
-       show-branch) _git_show_branch ;;
-       stash)       _git_stash ;;
-       stage)       _git_add ;;
-       submodule)   _git_submodule ;;
-       svn)         _git_svn ;;
-       tag)         _git_tag ;;
-       whatchanged) _git_log ;;
-       *)           COMPREPLY=() ;;
-       esac
+       if [ -n "$expansion" ]; then
+               completion_func="_git_${expansion//-/_}"
+               declare -F $completion_func >/dev/null && $completion_func
+       fi
 }
 
 _gitk ()
index 4fa70c5ad47fcd717d9cbdb23a8142f89227f630..7f4c7929784f8e2d8b24b0eaf9f05a2f10c1c6eb 100644 (file)
 
 (eval-when-compile (require 'cl))                            ; to use `push', `pop'
 
+(defface git-blame-prefix-face
+  '((((background dark)) (:foreground "gray"
+                          :background "black"))
+    (((background light)) (:foreground "gray"
+                           :background "white"))
+    (t (:weight bold)))
+  "The face used for the hash prefix."
+  :group 'git-blame)
+
+(defgroup git-blame nil
+  "A minor mode showing Git blame information."
+  :group 'git
+  :link '(function-link git-blame-mode))
+
+
+(defcustom git-blame-use-colors t
+  "Use colors to indicate commits in `git-blame-mode'."
+  :type 'boolean
+  :group 'git-blame)
+
+(defcustom git-blame-prefix-format
+  "%h %20A:"
+  "The format of the prefix added to each line in `git-blame'
+mode. The format is passed to `format-spec' with the following format keys:
+
+  %h - the abbreviated hash
+  %H - the full hash
+  %a - the author name
+  %A - the author email
+  %c - the committer name
+  %C - the committer email
+  %s - the commit summary
+"
+  :group 'git-blame)
+
+(defcustom git-blame-mouseover-format
+  "%h %a %A: %s"
+  "The format of the description shown when pointing at a line in
+`git-blame' mode. The format string is passed to `format-spec'
+with the following format keys:
+
+  %h - the abbreviated hash
+  %H - the full hash
+  %a - the author name
+  %A - the author email
+  %c - the committer name
+  %C - the committer email
+  %s - the commit summary
+"
+  :group 'git-blame)
+
 
 (defun git-blame-color-scale (&rest elements)
   "Given a list, returns a list of triples formed with each
@@ -302,72 +353,69 @@ See also function `git-blame-mode'."
                (src-line (string-to-number (match-string 2)))
                (res-line (string-to-number (match-string 3)))
                (num-lines (string-to-number (match-string 4))))
-           (setq git-blame-current
-                 (if (string= hash "0000000000000000000000000000000000000000")
-                     nil
-                   (git-blame-new-commit
-                    hash src-line res-line num-lines))))
-         (delete-region (point) (match-end 0))
-         t)
-        ((looking-at "filename \\(.+\\)\n")
-         (let ((filename (match-string 1)))
-           (git-blame-add-info "filename" filename))
-         (delete-region (point) (match-end 0))
+           (delete-region (point) (match-end 0))
+           (setq git-blame-current (list (git-blame-new-commit hash)
+                                         src-line res-line num-lines)))
          t)
         ((looking-at "\\([a-z-]+\\) \\(.+\\)\n")
          (let ((key (match-string 1))
                (value (match-string 2)))
-           (git-blame-add-info key value))
-         (delete-region (point) (match-end 0))
-         t)
-        ((looking-at "boundary\n")
-         (setq git-blame-current nil)
-         (delete-region (point) (match-end 0))
+           (delete-region (point) (match-end 0))
+           (git-blame-add-info (car git-blame-current) key value)
+           (when (string= key "filename")
+             (git-blame-create-overlay (car git-blame-current)
+                                       (caddr git-blame-current)
+                                       (cadddr git-blame-current))
+             (setq git-blame-current nil)))
          t)
         (t
          nil)))
 
-(defun git-blame-new-commit (hash src-line res-line num-lines)
+(defun git-blame-new-commit (hash)
+  (with-current-buffer git-blame-file
+    (or (gethash hash git-blame-cache)
+        ;; Assign a random color to each new commit info
+        ;; Take care not to select the same color multiple times
+        (let* ((color (if git-blame-colors
+                          (git-blame-random-pop git-blame-colors)
+                        git-blame-ancient-color))
+               (info `(,hash (color . ,color))))
+          (puthash hash info git-blame-cache)
+          info))))
+
+(defun git-blame-create-overlay (info start-line num-lines)
   (save-excursion
     (set-buffer git-blame-file)
-    (let ((info (gethash hash git-blame-cache))
-          (inhibit-point-motion-hooks t)
+    (let ((inhibit-point-motion-hooks t)
           (inhibit-modification-hooks t))
-      (when (not info)
-       ;; Assign a random color to each new commit info
-       ;; Take care not to select the same color multiple times
-       (let ((color (if git-blame-colors
-                        (git-blame-random-pop git-blame-colors)
-                      git-blame-ancient-color)))
-          (setq info (list hash src-line res-line num-lines
-                           (git-describe-commit hash)
-                           (cons 'color color))))
-        (puthash hash info git-blame-cache))
-      (goto-line res-line)
-      (while (> num-lines 0)
-        (if (get-text-property (point) 'git-blame)
-            (forward-line)
-          (let* ((start (point))
-                 (end (progn (forward-line 1) (point)))
-                 (ovl (make-overlay start end)))
-            (push ovl git-blame-overlays)
-            (overlay-put ovl 'git-blame info)
-            (overlay-put ovl 'help-echo hash)
+      (goto-line start-line)
+      (let* ((start (point))
+             (end (progn (forward-line num-lines) (point)))
+             (ovl (make-overlay start end))
+             (hash (car info))
+             (spec `((?h . ,(substring hash 0 6))
+                     (?H . ,hash)
+                     (?a . ,(git-blame-get-info info 'author))
+                     (?A . ,(git-blame-get-info info 'author-mail))
+                     (?c . ,(git-blame-get-info info 'committer))
+                     (?C . ,(git-blame-get-info info 'committer-mail))
+                     (?s . ,(git-blame-get-info info 'summary)))))
+        (push ovl git-blame-overlays)
+        (overlay-put ovl 'git-blame info)
+        (overlay-put ovl 'help-echo
+                     (format-spec git-blame-mouseover-format spec))
+        (if git-blame-use-colors
             (overlay-put ovl 'face (list :background
-                                         (cdr (assq 'color (nthcdr 5 info)))))
-            ;; the point-entered property doesn't seem to work in overlays
-            ;;(overlay-put ovl 'point-entered
-            ;;             `(lambda (x y) (git-blame-identify ,hash)))
-            (let ((modified (buffer-modified-p)))
-              (put-text-property (if (= start 1) start (1- start)) (1- end)
-                                 'point-entered
-                                 `(lambda (x y) (git-blame-identify ,hash)))
-              (set-buffer-modified-p modified))))
-        (setq num-lines (1- num-lines))))))
-
-(defun git-blame-add-info (key value)
-  (if git-blame-current
-      (nconc git-blame-current (list (cons (intern key) value)))))
+                                         (cdr (assq 'color (cdr info))))))
+        (overlay-put ovl 'line-prefix
+                     (propertize (format-spec git-blame-prefix-format spec)
+                                 'face 'git-blame-prefix-face))))))
+
+(defun git-blame-add-info (info key value)
+  (nconc info (list (cons (intern key) value))))
+
+(defun git-blame-get-info (info key)
+  (cdr (assq key (cdr info))))
 
 (defun git-blame-current-commit ()
   (let ((info (get-char-property (point) 'git-blame)))
index eace9c18eb1d17075836694ce664a009f3e02038..214930a021e6ab18f855b0b4ff4bd7d53912d06b 100644 (file)
@@ -429,16 +429,19 @@ Each entry is a cons of (SHORT-NAME . FULL-NAME)."
     (git-get-string-sha1
      (git-call-process-string-display-error "write-tree"))))
 
-(defun git-commit-tree (buffer tree head)
-  "Call git-commit-tree with buffer as input and return the resulting commit SHA1."
+(defun git-commit-tree (buffer tree parent)
+  "Create a commit and possibly update HEAD.
+Create a commit with the message in BUFFER using the tree with hash TREE.
+Use PARENT as the parent of the new commit. If PARENT is the current \"HEAD\",
+update the \"HEAD\" reference to the new commit."
   (let ((author-name (git-get-committer-name))
         (author-email (git-get-committer-email))
         (subject "commit (initial): ")
         author-date log-start log-end args coding-system-for-write)
-    (when head
+    (when parent
       (setq subject "commit: ")
       (push "-p" args)
-      (push head args))
+      (push parent args))
     (with-current-buffer buffer
       (goto-char (point-min))
       (if
@@ -474,7 +477,7 @@ Each entry is a cons of (SHORT-NAME . FULL-NAME)."
               (apply #'git-run-command-region
                      buffer log-start log-end env
                      "commit-tree" tree (nreverse args))))))
-      (when commit (git-update-ref "HEAD" commit head subject))
+      (when commit (git-update-ref "HEAD" commit parent subject))
       commit)))
 
 (defun git-empty-db-p ()
@@ -1043,7 +1046,7 @@ The FILES list must be sorted."
 (defun git-add-file ()
   "Add marked file(s) to the index cache."
   (interactive)
-  (let ((files (git-get-filenames (git-marked-files-state 'unknown 'ignored))))
+  (let ((files (git-get-filenames (git-marked-files-state 'unknown 'ignored 'unmerged))))
     ;; FIXME: add support for directories
     (unless files
       (push (file-relative-name (read-file-name "File to add: " nil nil t)) files))
@@ -1116,15 +1119,6 @@ The FILES list must be sorted."
               (when buffer (with-current-buffer buffer (revert-buffer t t t)))))
           (git-success-message "Reverted" names))))))
 
-(defun git-resolve-file ()
-  "Resolve conflicts in marked file(s)."
-  (interactive)
-  (let ((files (git-get-filenames (git-marked-files-state 'unmerged))))
-    (when files
-      (when (apply 'git-call-process-display-error "update-index" "--" files)
-        (git-update-status-files files)
-        (git-success-message "Resolved" files)))))
-
 (defun git-remove-handled ()
   "Remove handled files from the status list."
   (interactive)
@@ -1553,7 +1547,6 @@ amended version of it."
     (define-key map "P"   'git-prev-unmerged-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"    toggle-map)
     (define-key map "T"   'git-toggle-all-marks)
     (define-key map "u"   'git-unmark-file)
@@ -1595,7 +1588,6 @@ amended version of it."
       ("Merge"
        ["Next Unmerged File" git-next-unmerged-file t]
        ["Prev Unmerged File" git-prev-unmerged-file t]
-       ["Mark as Resolved" git-resolve-file t]
        ["Interactive Merge File" git-find-file-imerge t]
        ["Diff Against Common Base File" git-diff-file-base t]
        ["Diff Combined" git-diff-file-combined t]
diff --git a/contrib/examples/builtin-fetch--tool.c b/contrib/examples/builtin-fetch--tool.c
new file mode 100644 (file)
index 0000000..cd10dbc
--- /dev/null
@@ -0,0 +1,574 @@
+#include "builtin.h"
+#include "cache.h"
+#include "refs.h"
+#include "commit.h"
+#include "sigchain.h"
+
+static char *get_stdin(void)
+{
+       struct strbuf buf = STRBUF_INIT;
+       if (strbuf_read(&buf, 0, 1024) < 0) {
+               die_errno("error reading standard input");
+       }
+       return strbuf_detach(&buf, NULL);
+}
+
+static void show_new(enum object_type type, unsigned char *sha1_new)
+{
+       fprintf(stderr, "  %s: %s\n", typename(type),
+               find_unique_abbrev(sha1_new, DEFAULT_ABBREV));
+}
+
+static int update_ref_env(const char *action,
+                     const char *refname,
+                     unsigned char *sha1,
+                     unsigned char *oldval)
+{
+       char msg[1024];
+       const char *rla = getenv("GIT_REFLOG_ACTION");
+
+       if (!rla)
+               rla = "(reflog update)";
+       if (snprintf(msg, sizeof(msg), "%s: %s", rla, action) >= sizeof(msg))
+               warning("reflog message too long: %.*s...", 50, msg);
+       return update_ref(msg, refname, sha1, oldval, 0, QUIET_ON_ERR);
+}
+
+static int update_local_ref(const char *name,
+                           const char *new_head,
+                           const char *note,
+                           int verbose, int force)
+{
+       unsigned char sha1_old[20], sha1_new[20];
+       char oldh[41], newh[41];
+       struct commit *current, *updated;
+       enum object_type type;
+
+       if (get_sha1_hex(new_head, sha1_new))
+               die("malformed object name %s", new_head);
+
+       type = sha1_object_info(sha1_new, NULL);
+       if (type < 0)
+               die("object %s not found", new_head);
+
+       if (!*name) {
+               /* Not storing */
+               if (verbose) {
+                       fprintf(stderr, "* fetched %s\n", note);
+                       show_new(type, sha1_new);
+               }
+               return 0;
+       }
+
+       if (get_sha1(name, sha1_old)) {
+               const char *msg;
+       just_store:
+               /* new ref */
+               if (!strncmp(name, "refs/tags/", 10))
+                       msg = "storing tag";
+               else
+                       msg = "storing head";
+               fprintf(stderr, "* %s: storing %s\n",
+                       name, note);
+               show_new(type, sha1_new);
+               return update_ref_env(msg, name, sha1_new, NULL);
+       }
+
+       if (!hashcmp(sha1_old, sha1_new)) {
+               if (verbose) {
+                       fprintf(stderr, "* %s: same as %s\n", name, note);
+                       show_new(type, sha1_new);
+               }
+               return 0;
+       }
+
+       if (!strncmp(name, "refs/tags/", 10)) {
+               fprintf(stderr, "* %s: updating with %s\n", name, note);
+               show_new(type, sha1_new);
+               return update_ref_env("updating tag", name, sha1_new, NULL);
+       }
+
+       current = lookup_commit_reference(sha1_old);
+       updated = lookup_commit_reference(sha1_new);
+       if (!current || !updated)
+               goto just_store;
+
+       strcpy(oldh, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
+       strcpy(newh, find_unique_abbrev(sha1_new, DEFAULT_ABBREV));
+
+       if (in_merge_bases(current, &updated, 1)) {
+               fprintf(stderr, "* %s: fast-forward to %s\n",
+                       name, note);
+               fprintf(stderr, "  old..new: %s..%s\n", oldh, newh);
+               return update_ref_env("fast-forward", name, sha1_new, sha1_old);
+       }
+       if (!force) {
+               fprintf(stderr,
+                       "* %s: not updating to non-fast-forward %s\n",
+                       name, note);
+               fprintf(stderr,
+                       "  old...new: %s...%s\n", oldh, newh);
+               return 1;
+       }
+       fprintf(stderr,
+               "* %s: forcing update to non-fast-forward %s\n",
+               name, note);
+       fprintf(stderr, "  old...new: %s...%s\n", oldh, newh);
+       return update_ref_env("forced-update", name, sha1_new, sha1_old);
+}
+
+static int append_fetch_head(FILE *fp,
+                            const char *head, const char *remote,
+                            const char *remote_name, const char *remote_nick,
+                            const char *local_name, int not_for_merge,
+                            int verbose, int force)
+{
+       struct commit *commit;
+       int remote_len, i, note_len;
+       unsigned char sha1[20];
+       char note[1024];
+       const char *what, *kind;
+
+       if (get_sha1(head, sha1))
+               return error("Not a valid object name: %s", head);
+       commit = lookup_commit_reference_gently(sha1, 1);
+       if (!commit)
+               not_for_merge = 1;
+
+       if (!strcmp(remote_name, "HEAD")) {
+               kind = "";
+               what = "";
+       }
+       else if (!strncmp(remote_name, "refs/heads/", 11)) {
+               kind = "branch";
+               what = remote_name + 11;
+       }
+       else if (!strncmp(remote_name, "refs/tags/", 10)) {
+               kind = "tag";
+               what = remote_name + 10;
+       }
+       else if (!strncmp(remote_name, "refs/remotes/", 13)) {
+               kind = "remote branch";
+               what = remote_name + 13;
+       }
+       else {
+               kind = "";
+               what = remote_name;
+       }
+
+       remote_len = strlen(remote);
+       for (i = remote_len - 1; remote[i] == '/' && 0 <= i; i--)
+               ;
+       remote_len = i + 1;
+       if (4 < i && !strncmp(".git", remote + i - 3, 4))
+               remote_len = i - 3;
+
+       note_len = 0;
+       if (*what) {
+               if (*kind)
+                       note_len += sprintf(note + note_len, "%s ", kind);
+               note_len += sprintf(note + note_len, "'%s' of ", what);
+       }
+       note_len += sprintf(note + note_len, "%.*s", remote_len, remote);
+       fprintf(fp, "%s\t%s\t%s\n",
+               sha1_to_hex(commit ? commit->object.sha1 : sha1),
+               not_for_merge ? "not-for-merge" : "",
+               note);
+       return update_local_ref(local_name, head, note, verbose, force);
+}
+
+static char *keep;
+static void remove_keep(void)
+{
+       if (keep && *keep)
+               unlink(keep);
+}
+
+static void remove_keep_on_signal(int signo)
+{
+       remove_keep();
+       sigchain_pop(signo);
+       raise(signo);
+}
+
+static char *find_local_name(const char *remote_name, const char *refs,
+                            int *force_p, int *not_for_merge_p)
+{
+       const char *ref = refs;
+       int len = strlen(remote_name);
+
+       while (ref) {
+               const char *next;
+               int single_force, not_for_merge;
+
+               while (*ref == '\n')
+                       ref++;
+               if (!*ref)
+                       break;
+               next = strchr(ref, '\n');
+
+               single_force = not_for_merge = 0;
+               if (*ref == '+') {
+                       single_force = 1;
+                       ref++;
+               }
+               if (*ref == '.') {
+                       not_for_merge = 1;
+                       ref++;
+                       if (*ref == '+') {
+                               single_force = 1;
+                               ref++;
+                       }
+               }
+               if (!strncmp(remote_name, ref, len) && ref[len] == ':') {
+                       const char *local_part = ref + len + 1;
+                       int retlen;
+
+                       if (!next)
+                               retlen = strlen(local_part);
+                       else
+                               retlen = next - local_part;
+                       *force_p = single_force;
+                       *not_for_merge_p = not_for_merge;
+                       return xmemdupz(local_part, retlen);
+               }
+               ref = next;
+       }
+       return NULL;
+}
+
+static int fetch_native_store(FILE *fp,
+                             const char *remote,
+                             const char *remote_nick,
+                             const char *refs,
+                             int verbose, int force)
+{
+       char buffer[1024];
+       int err = 0;
+
+       sigchain_push_common(remove_keep_on_signal);
+       atexit(remove_keep);
+
+       while (fgets(buffer, sizeof(buffer), stdin)) {
+               int len;
+               char *cp;
+               char *local_name;
+               int single_force, not_for_merge;
+
+               for (cp = buffer; *cp && !isspace(*cp); cp++)
+                       ;
+               if (*cp)
+                       *cp++ = 0;
+               len = strlen(cp);
+               if (len && cp[len-1] == '\n')
+                       cp[--len] = 0;
+               if (!strcmp(buffer, "failed"))
+                       die("Fetch failure: %s", remote);
+               if (!strcmp(buffer, "pack"))
+                       continue;
+               if (!strcmp(buffer, "keep")) {
+                       char *od = get_object_directory();
+                       int len = strlen(od) + strlen(cp) + 50;
+                       keep = xmalloc(len);
+                       sprintf(keep, "%s/pack/pack-%s.keep", od, cp);
+                       continue;
+               }
+
+               local_name = find_local_name(cp, refs,
+                                            &single_force, &not_for_merge);
+               if (!local_name)
+                       continue;
+               err |= append_fetch_head(fp,
+                                        buffer, remote, cp, remote_nick,
+                                        local_name, not_for_merge,
+                                        verbose, force || single_force);
+       }
+       return err;
+}
+
+static int parse_reflist(const char *reflist)
+{
+       const char *ref;
+
+       printf("refs='");
+       for (ref = reflist; ref; ) {
+               const char *next;
+               while (*ref && isspace(*ref))
+                       ref++;
+               if (!*ref)
+                       break;
+               for (next = ref; *next && !isspace(*next); next++)
+                       ;
+               printf("\n%.*s", (int)(next - ref), ref);
+               ref = next;
+       }
+       printf("'\n");
+
+       printf("rref='");
+       for (ref = reflist; ref; ) {
+               const char *next, *colon;
+               while (*ref && isspace(*ref))
+                       ref++;
+               if (!*ref)
+                       break;
+               for (next = ref; *next && !isspace(*next); next++)
+                       ;
+               if (*ref == '.')
+                       ref++;
+               if (*ref == '+')
+                       ref++;
+               colon = strchr(ref, ':');
+               putchar('\n');
+               printf("%.*s", (int)((colon ? colon : next) - ref), ref);
+               ref = next;
+       }
+       printf("'\n");
+       return 0;
+}
+
+static int expand_refs_wildcard(const char *ls_remote_result, int numrefs,
+                               const char **refs)
+{
+       int i, matchlen, replacelen;
+       int found_one = 0;
+       const char *remote = *refs++;
+       numrefs--;
+
+       if (numrefs == 0) {
+               fprintf(stderr, "Nothing specified for fetching with remote.%s.fetch\n",
+                       remote);
+               printf("empty\n");
+       }
+
+       for (i = 0; i < numrefs; i++) {
+               const char *ref = refs[i];
+               const char *lref = ref;
+               const char *colon;
+               const char *tail;
+               const char *ls;
+               const char *next;
+
+               if (*lref == '+')
+                       lref++;
+               colon = strchr(lref, ':');
+               tail = lref + strlen(lref);
+               if (!(colon &&
+                     2 < colon - lref &&
+                     colon[-1] == '*' &&
+                     colon[-2] == '/' &&
+                     2 < tail - (colon + 1) &&
+                     tail[-1] == '*' &&
+                     tail[-2] == '/')) {
+                       /* not a glob */
+                       if (!found_one++)
+                               printf("explicit\n");
+                       printf("%s\n", ref);
+                       continue;
+               }
+
+               /* glob */
+               if (!found_one++)
+                       printf("glob\n");
+
+               /* lref to colon-2 is remote hierarchy name;
+                * colon+1 to tail-2 is local.
+                */
+               matchlen = (colon-1) - lref;
+               replacelen = (tail-1) - (colon+1);
+               for (ls = ls_remote_result; ls; ls = next) {
+                       const char *eol;
+                       unsigned char sha1[20];
+                       int namelen;
+
+                       while (*ls && isspace(*ls))
+                               ls++;
+                       next = strchr(ls, '\n');
+                       eol = !next ? (ls + strlen(ls)) : next;
+                       if (!memcmp("^{}", eol-3, 3))
+                               continue;
+                       if (eol - ls < 40)
+                               continue;
+                       if (get_sha1_hex(ls, sha1))
+                               continue;
+                       ls += 40;
+                       while (ls < eol && isspace(*ls))
+                               ls++;
+                       /* ls to next (or eol) is the name.
+                        * is it identical to lref to colon-2?
+                        */
+                       if ((eol - ls) <= matchlen ||
+                           strncmp(ls, lref, matchlen))
+                               continue;
+
+                       /* Yes, it is a match */
+                       namelen = eol - ls;
+                       if (lref != ref)
+                               putchar('+');
+                       printf("%.*s:%.*s%.*s\n",
+                              namelen, ls,
+                              replacelen, colon + 1,
+                              namelen - matchlen, ls + matchlen);
+               }
+       }
+       return 0;
+}
+
+static int pick_rref(int sha1_only, const char *rref, const char *ls_remote_result)
+{
+       int err = 0;
+       int lrr_count = lrr_count, i, pass;
+       const char *cp;
+       struct lrr {
+               const char *line;
+               const char *name;
+               int namelen;
+               int shown;
+       } *lrr_list = lrr_list;
+
+       for (pass = 0; pass < 2; pass++) {
+               /* pass 0 counts and allocates, pass 1 fills... */
+               cp = ls_remote_result;
+               i = 0;
+               while (1) {
+                       const char *np;
+                       while (*cp && isspace(*cp))
+                               cp++;
+                       if (!*cp)
+                               break;
+                       np = strchrnul(cp, '\n');
+                       if (pass) {
+                               lrr_list[i].line = cp;
+                               lrr_list[i].name = cp + 41;
+                               lrr_list[i].namelen = np - (cp + 41);
+                       }
+                       i++;
+                       cp = np;
+               }
+               if (!pass) {
+                       lrr_count = i;
+                       lrr_list = xcalloc(lrr_count, sizeof(*lrr_list));
+               }
+       }
+
+       while (1) {
+               const char *next;
+               int rreflen;
+               int i;
+
+               while (*rref && isspace(*rref))
+                       rref++;
+               if (!*rref)
+                       break;
+               next = strchrnul(rref, '\n');
+               rreflen = next - rref;
+
+               for (i = 0; i < lrr_count; i++) {
+                       struct lrr *lrr = &(lrr_list[i]);
+
+                       if (rreflen == lrr->namelen &&
+                           !memcmp(lrr->name, rref, rreflen)) {
+                               if (!lrr->shown)
+                                       printf("%.*s\n",
+                                              sha1_only ? 40 : lrr->namelen + 41,
+                                              lrr->line);
+                               lrr->shown = 1;
+                               break;
+                       }
+               }
+               if (lrr_count <= i) {
+                       error("pick-rref: %.*s not found", rreflen, rref);
+                       err = 1;
+               }
+               rref = next;
+       }
+       free(lrr_list);
+       return err;
+}
+
+int cmd_fetch__tool(int argc, const char **argv, const char *prefix)
+{
+       int verbose = 0;
+       int force = 0;
+       int sopt = 0;
+
+       while (1 < argc) {
+               const char *arg = argv[1];
+               if (!strcmp("-v", arg))
+                       verbose = 1;
+               else if (!strcmp("-f", arg))
+                       force = 1;
+               else if (!strcmp("-s", arg))
+                       sopt = 1;
+               else
+                       break;
+               argc--;
+               argv++;
+       }
+
+       if (argc <= 1)
+               return error("Missing subcommand");
+
+       if (!strcmp("append-fetch-head", argv[1])) {
+               int result;
+               FILE *fp;
+               char *filename;
+
+               if (argc != 8)
+                       return error("append-fetch-head takes 6 args");
+               filename = git_path("FETCH_HEAD");
+               fp = fopen(filename, "a");
+               if (!fp)
+                       return error("cannot open %s: %s\n", filename, strerror(errno));
+               result = append_fetch_head(fp, argv[2], argv[3],
+                                          argv[4], argv[5],
+                                          argv[6], !!argv[7][0],
+                                          verbose, force);
+               fclose(fp);
+               return result;
+       }
+       if (!strcmp("native-store", argv[1])) {
+               int result;
+               FILE *fp;
+               char *filename;
+
+               if (argc != 5)
+                       return error("fetch-native-store takes 3 args");
+               filename = git_path("FETCH_HEAD");
+               fp = fopen(filename, "a");
+               if (!fp)
+                       return error("cannot open %s: %s\n", filename, strerror(errno));
+               result = fetch_native_store(fp, argv[2], argv[3], argv[4],
+                                           verbose, force);
+               fclose(fp);
+               return result;
+       }
+       if (!strcmp("parse-reflist", argv[1])) {
+               const char *reflist;
+               if (argc != 3)
+                       return error("parse-reflist takes 1 arg");
+               reflist = argv[2];
+               if (!strcmp(reflist, "-"))
+                       reflist = get_stdin();
+               return parse_reflist(reflist);
+       }
+       if (!strcmp("pick-rref", argv[1])) {
+               const char *ls_remote_result;
+               if (argc != 4)
+                       return error("pick-rref takes 2 args");
+               ls_remote_result = argv[3];
+               if (!strcmp(ls_remote_result, "-"))
+                       ls_remote_result = get_stdin();
+               return pick_rref(sopt, argv[2], ls_remote_result);
+       }
+       if (!strcmp("expand-refs-wildcard", argv[1])) {
+               const char *reflist;
+               if (argc < 4)
+                       return error("expand-refs-wildcard takes at least 2 args");
+               reflist = argv[2];
+               if (!strcmp(reflist, "-"))
+                       reflist = get_stdin();
+               return expand_refs_wildcard(reflist, argc - 3, argv + 3);
+       }
+
+       return error("Unknown subcommand: %s", argv[1]);
+}
index 5c72f655c7e4fb1bd18e979d33bd94062fce8c1a..23ffb028d1ece96d8c363ddeacca83d2b20b628f 100755 (executable)
@@ -631,7 +631,7 @@ then
        if test -z "$quiet"
        then
                commit=`git diff-tree --always --shortstat --pretty="format:%h: %s"\
-                      --summary --root HEAD --`
+                      --abbrev --summary --root HEAD --`
                echo "Created${initial_commit:+ initial} commit $commit"
        fi
 fi
index e44af2c86d8e7e44bc79aafcc8ccef3806804720..a314273bd51a865d9d5fc5cd899a51ffa70388a5 100755 (executable)
@@ -127,10 +127,12 @@ then
        orig_head=$(git rev-parse --verify HEAD 2>/dev/null)
 fi
 
-# Allow --notags from remote.$1.tagopt
+# Allow --tags/--notags from remote.$1.tagopt
 case "$tags$no_tags" in
 '')
        case "$(git config --get "remote.$1.tagopt")" in
+       --tags)
+               tags=t ;;
        --no-tags)
                no_tags=t ;;
        esac
index e9588eec33ba5b64d186ff048bb040c18c57e6bc..7b922c39480c8b78d0687cbabac59672f9ae6ede 100755 (executable)
@@ -14,8 +14,11 @@ summary              (synonym to --stat)
 log                  add list of one-line log to merge commit message
 squash               create a single commit instead of doing a merge
 commit               perform a commit if the merge succeeds (default)
-ff                   allow fast forward (default)
+ff                   allow fast-forward (default)
+ff-only              abort if fast-forward is not possible
+rerere-autoupdate    update index with any reused conflict resolution
 s,strategy=          merge strategy to use
+X=                   option for selected merge strategy
 m,message=           message to be used for the merge commit (if any)
 "
 
@@ -25,25 +28,32 @@ require_work_tree
 cd_to_toplevel
 
 test -z "$(git ls-files -u)" ||
-       die "You are in the middle of a conflicted merge."
+       die "Merge is not possible because you have unmerged files."
+
+! test -e "$GIT_DIR/MERGE_HEAD" ||
+       die 'You have not concluded your merge (MERGE_HEAD exists).'
 
 LF='
 '
 
 all_strategies='recur recursive octopus resolve stupid ours subtree'
+all_strategies="$all_strategies recursive-ours recursive-theirs"
+not_strategies='base file index tree'
 default_twohead_strategies='recursive'
 default_octopus_strategies='octopus'
 no_fast_forward_strategies='subtree ours'
-no_trivial_strategies='recursive recur subtree ours'
+no_trivial_strategies='recursive recur subtree ours recursive-ours recursive-theirs'
 use_strategies=
+xopt=
 
 allow_fast_forward=t
+fast_forward_only=
 allow_trivial_merge=t
-squash= no_commit= log_arg=
+squash= no_commit= log_arg= rr_arg=
 
 dropsave() {
        rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \
-                "$GIT_DIR/MERGE_STASH" || exit 1
+                "$GIT_DIR/MERGE_STASH" "$GIT_DIR/MERGE_MODE" || exit 1
 }
 
 savestate() {
@@ -130,21 +140,34 @@ finish () {
 merge_name () {
        remote="$1"
        rh=$(git rev-parse --verify "$remote^0" 2>/dev/null) || return
-       bh=$(git show-ref -s --verify "refs/heads/$remote" 2>/dev/null)
-       if test "$rh" = "$bh"
-       then
-               echo "$rh               branch '$remote' of ."
-       elif truname=$(expr "$remote" : '\(.*\)~[1-9][0-9]*$') &&
+       if truname=$(expr "$remote" : '\(.*\)~[0-9]*$') &&
                git show-ref -q --verify "refs/heads/$truname" 2>/dev/null
        then
                echo "$rh               branch '$truname' (early part) of ."
-       elif test "$remote" = "FETCH_HEAD" -a -r "$GIT_DIR/FETCH_HEAD"
+               return
+       fi
+       if found_ref=$(git rev-parse --symbolic-full-name --verify \
+                                                       "$remote" 2>/dev/null)
+       then
+               expanded=$(git check-ref-format --branch "$remote") ||
+                       exit
+               if test "${found_ref#refs/heads/}" != "$found_ref"
+               then
+                       echo "$rh               branch '$expanded' of ."
+                       return
+               elif test "${found_ref#refs/remotes/}" != "$found_ref"
+               then
+                       echo "$rh               remote branch '$expanded' of ."
+                       return
+               fi
+       fi
+       if test "$remote" = "FETCH_HEAD" -a -r "$GIT_DIR/FETCH_HEAD"
        then
                sed -e 's/      not-for-merge   /               /' -e 1q \
                        "$GIT_DIR/FETCH_HEAD"
-       else
-               echo "$rh               commit '$remote'"
+               return
        fi
+       echo "$rh               commit '$remote'"
 }
 
 parse_config () {
@@ -171,16 +194,36 @@ parse_config () {
                --no-ff)
                        test "$squash" != t ||
                                die "You cannot combine --squash with --no-ff."
+                       test "$fast_forward_only" != t ||
+                               die "You cannot combine --ff-only with --no-ff."
                        allow_fast_forward=f ;;
+               --ff-only)
+                       test "$allow_fast_forward" != f ||
+                               die "You cannot combine --ff-only with --no-ff."
+                       fast_forward_only=t ;;
+               --rerere-autoupdate|--no-rerere-autoupdate)
+                       rr_arg=$1 ;;
                -s|--strategy)
                        shift
                        case " $all_strategies " in
                        *" $1 "*)
-                               use_strategies="$use_strategies$1 " ;;
+                               use_strategies="$use_strategies$1 "
+                               ;;
                        *)
-                               die "available strategies are: $all_strategies" ;;
+                               case " $not_strategies " in
+                               *" $1 "*)
+                                       false
+                               esac &&
+                               type "git-merge-$1" >/dev/null 2>&1 ||
+                                       die "available strategies are: $all_strategies"
+                               use_strategies="$use_strategies$1 "
+                               ;;
                        esac
                        ;;
+               -X)
+                       shift
+                       xopt="${xopt:+$xopt }$(git rev-parse --sq-quote "--$1")"
+                       ;;
                -m|--message)
                        shift
                        merge_msg="$1"
@@ -244,6 +287,10 @@ then
                exit 1
        fi
 
+       test "$squash" != t ||
+               die "Squash commit into empty head not supported yet"
+       test "$allow_fast_forward" = t ||
+               die "Non-fast-forward into an empty head does not make sense"
        rh=$(git rev-parse --verify "$1^0") ||
                die "$1 - not something we can merge"
 
@@ -260,12 +307,18 @@ else
        # the given message.  If remote is invalid we will die
        # later in the common codepath so we discard the error
        # in this loop.
-       merge_name=$(for remote
+       merge_msg="$(
+               for remote
                do
                        merge_name "$remote"
-               done | git fmt-merge-msg $log_arg
-       )
-       merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name"
+               done |
+               if test "$have_message" = t
+               then
+                       git fmt-merge-msg -m "$merge_msg" $log_arg
+               else
+                       git fmt-merge-msg $log_arg
+               fi
+       )"
 fi
 head=$(git rev-parse --verify "$head_arg"^0) || usage
 
@@ -334,7 +387,7 @@ case "$#" in
        common=$(git merge-base --all $head "$@")
        ;;
 *)
-       common=$(git show-branch --merge-base $head "$@")
+       common=$(git merge-base --all --octopus $head "$@")
        ;;
 esac
 echo "$head" >"$GIT_DIR/ORIG_HEAD"
@@ -353,7 +406,7 @@ t,1,"$head",*)
        # Again the most common case of merging one remote.
        echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $1)"
        git update-index --refresh 2>/dev/null
-       msg="Fast forward"
+       msg="Fast-forward"
        if test -n "$have_message"
        then
                msg="$msg (no commit created; -m option ignored)"
@@ -365,15 +418,15 @@ t,1,"$head",*)
        exit 0
        ;;
 ?,1,?*"$LF"?*,*)
-       # We are not doing octopus and not fast forward.  Need a
+       # 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
+       # We are not doing octopus, not fast-forward, and have only
        # one common.
        git update-index --refresh 2>/dev/null
-       case "$allow_trivial_merge" in
-       t)
+       case "$allow_trivial_merge,$fast_forward_only" in
+       t,)
                # See if it is really trivial.
                git var GIT_COMMITTER_IDENT >/dev/null || exit
                echo "Trying really trivial in-index merge..."
@@ -412,6 +465,11 @@ t,1,"$head",*)
        ;;
 esac
 
+if test "$fast_forward_only" = t
+then
+       die "Not possible to fast-forward, aborting."
+fi
+
 # We are going to make a new commit.
 git var GIT_COMMITTER_IDENT >/dev/null || exit
 
@@ -450,7 +508,7 @@ do
     # Remember which strategy left the state in the working tree
     wt_strategy=$strategy
 
-    git-merge-$strategy $common -- "$head_arg" "$@"
+    eval 'git-merge-$strategy '"$xopt"' $common -- "$head_arg" "$@"'
     exit=$?
     if test "$no_commit" = t && test "$exit" = 0
     then
@@ -488,9 +546,9 @@ if test '' != "$result_tree"
 then
     if test "$allow_fast_forward" = "t"
     then
-        parents=$(git show-branch --independent "$head" "$@")
+       parents=$(git merge-base --independent "$head" "$@")
     else
-        parents=$(git rev-parse "$head" "$@")
+       parents=$(git rev-parse "$head" "$@")
     fi
     parents=$(echo "$parents" | sed -e 's/^/-p /')
     result_commit=$(printf '%s\n' "$merge_msg" | git commit-tree $result_tree $parents) || exit
@@ -532,7 +590,15 @@ else
        do
                echo $remote
        done >"$GIT_DIR/MERGE_HEAD"
-       printf '%s\n' "$merge_msg" >"$GIT_DIR/MERGE_MSG"
+       printf '%s\n' "$merge_msg" >"$GIT_DIR/MERGE_MSG" ||
+               die "Could not write to $GIT_DIR/MERGE_MSG"
+       if test "$allow_fast_forward" != t
+       then
+               printf "%s" no-ff
+       else
+               :
+       fi >"$GIT_DIR/MERGE_MODE" ||
+               die "Could not write to $GIT_DIR/MERGE_MODE"
 fi
 
 if test "$merge_was_ok" = t
@@ -549,6 +615,6 @@ Conflicts:
                sed -e 's/^[^   ]*      /       /' |
                uniq
        } >>"$GIT_DIR/MERGE_MSG"
-       git rerere
+       git rerere $rr_arg
        die "Automatic merge failed; fix conflicts and then commit the result."
 fi
diff --git a/contrib/examples/git-notes.sh b/contrib/examples/git-notes.sh
new file mode 100755 (executable)
index 0000000..e642e47
--- /dev/null
@@ -0,0 +1,121 @@
+#!/bin/sh
+
+USAGE="(edit [-F <file> | -m <msg>] | show) [commit]"
+. git-sh-setup
+
+test -z "$1" && usage
+ACTION="$1"; shift
+
+test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="$(git config core.notesref)"
+test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="refs/notes/commits"
+
+MESSAGE=
+while test $# != 0
+do
+       case "$1" in
+       -m)
+               test "$ACTION" = "edit" || usage
+               shift
+               if test "$#" = "0"; then
+                       die "error: option -m needs an argument"
+               else
+                       if [ -z "$MESSAGE" ]; then
+                               MESSAGE="$1"
+                       else
+                               MESSAGE="$MESSAGE
+
+$1"
+                       fi
+                       shift
+               fi
+               ;;
+       -F)
+               test "$ACTION" = "edit" || usage
+               shift
+               if test "$#" = "0"; then
+                       die "error: option -F needs an argument"
+               else
+                       if [ -z "$MESSAGE" ]; then
+                               MESSAGE="$(cat "$1")"
+                       else
+                               MESSAGE="$MESSAGE
+
+$(cat "$1")"
+                       fi
+                       shift
+               fi
+               ;;
+       -*)
+               usage
+               ;;
+       *)
+               break
+               ;;
+       esac
+done
+
+COMMIT=$(git rev-parse --verify --default HEAD "$@") ||
+die "Invalid commit: $@"
+
+case "$ACTION" in
+edit)
+       if [ "${GIT_NOTES_REF#refs/notes/}" = "$GIT_NOTES_REF" ]; then
+               die "Refusing to edit notes in $GIT_NOTES_REF (outside of refs/notes/)"
+       fi
+
+       MSG_FILE="$GIT_DIR/new-notes-$COMMIT"
+       GIT_INDEX_FILE="$MSG_FILE.idx"
+       export GIT_INDEX_FILE
+
+       trap '
+               test -f "$MSG_FILE" && rm "$MSG_FILE"
+               test -f "$GIT_INDEX_FILE" && rm "$GIT_INDEX_FILE"
+       ' 0
+
+       CURRENT_HEAD=$(git show-ref "$GIT_NOTES_REF" | cut -f 1 -d ' ')
+       if [ -z "$CURRENT_HEAD" ]; then
+               PARENT=
+       else
+               PARENT="-p $CURRENT_HEAD"
+               git read-tree "$GIT_NOTES_REF" || die "Could not read index"
+       fi
+
+       if [ -z "$MESSAGE" ]; then
+               GIT_NOTES_REF= git log -1 $COMMIT | sed "s/^/#/" > "$MSG_FILE"
+               if [ ! -z "$CURRENT_HEAD" ]; then
+                       git cat-file blob :$COMMIT >> "$MSG_FILE" 2> /dev/null
+               fi
+               core_editor="$(git config core.editor)"
+               ${GIT_EDITOR:-${core_editor:-${VISUAL:-${EDITOR:-vi}}}} "$MSG_FILE"
+       else
+               echo "$MESSAGE" > "$MSG_FILE"
+       fi
+
+       grep -v ^# < "$MSG_FILE" | git stripspace > "$MSG_FILE".processed
+       mv "$MSG_FILE".processed "$MSG_FILE"
+       if [ -s "$MSG_FILE" ]; then
+               BLOB=$(git hash-object -w "$MSG_FILE") ||
+                       die "Could not write into object database"
+               git update-index --add --cacheinfo 0644 $BLOB $COMMIT ||
+                       die "Could not write index"
+       else
+               test -z "$CURRENT_HEAD" &&
+                       die "Will not initialise with empty tree"
+               git update-index --force-remove $COMMIT ||
+                       die "Could not update index"
+       fi
+
+       TREE=$(git write-tree) || die "Could not write tree"
+       NEW_HEAD=$(echo Annotate $COMMIT | git commit-tree $TREE $PARENT) ||
+               die "Could not annotate"
+       git update-ref -m "Annotate $COMMIT" \
+               "$GIT_NOTES_REF" $NEW_HEAD $CURRENT_HEAD
+;;
+show)
+       git rev-parse -q --verify "$GIT_NOTES_REF":$COMMIT > /dev/null ||
+               die "No note for commit $COMMIT."
+       git show "$GIT_NOTES_REF":$COMMIT
+;;
+*)
+       usage
+esac
index 0ee1bd898ecbb725d13385408b4ed4bcb413f503..8f98142f77b5d6eda44b301082da20604a893db0 100755 (executable)
@@ -48,7 +48,7 @@ case "$common" in
 "$head")
        echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $merge)"
        git read-tree -u -m $head $merge || exit 1
-       git update-ref -m "resolve $merge_name: Fast forward" \
+       git update-ref -m "resolve $merge_name: Fast-forward" \
                HEAD "$merge" "$head"
        git diff-tree -p $head $merge | git apply --stat
        dropheads
index 49f00321b28833c24ebb78ea2104f34091d43017..60a05a8b978345224c0313edb8060f5a7c2ccb54 100755 (executable)
@@ -181,7 +181,6 @@ Conflicts:
        esac
        exit 1
 }
-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
index 342529db309821f461e8f77d05bc5e01c76802ec..c1ea643ace920e83a5577948553d4c9d1046abec 100755 (executable)
@@ -8,12 +8,10 @@
 # License: MIT <http://www.opensource.org/licenses/mit-license.php>
 #
 
-import optparse, sys, os, marshal, popen2, subprocess, shelve
-import tempfile, getopt, sha, os.path, time, platform
+import optparse, sys, os, marshal, subprocess, shelve
+import tempfile, getopt, os.path, time, platform
 import re
 
-from sets import Set;
-
 verbose = False
 
 
@@ -201,7 +199,7 @@ def isModeExec(mode):
 def isModeExecChanged(src_mode, dst_mode):
     return isModeExec(src_mode) != isModeExec(dst_mode)
 
-def p4CmdList(cmd, stdin=None, stdin_mode='w+b'):
+def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None):
     cmd = p4_build_cmd("-G %s" % (cmd))
     if verbose:
         sys.stderr.write("Opening pipe: %s\n" % cmd)
@@ -224,7 +222,10 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b'):
     try:
         while True:
             entry = marshal.load(p4.stdout)
-            result.append(entry)
+           if cb is not None:
+               cb(entry)
+           else:
+               result.append(entry)
     except EOFError:
         pass
     exitCode = p4.wait()
@@ -728,13 +729,10 @@ class P4Submit(Command):
             tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
             tmpFile.close()
             mtime = os.stat(fileName).st_mtime
-            defaultEditor = "vi"
-            if platform.system() == "Windows":
-                defaultEditor = "notepad"
             if os.environ.has_key("P4EDITOR"):
                 editor = os.environ.get("P4EDITOR")
             else:
-                editor = os.environ.get("EDITOR", defaultEditor);
+                editor = read_pipe("git var GIT_EDITOR").strip()
             system(editor + " " + fileName)
 
             response = "y"
@@ -804,7 +802,7 @@ class P4Submit(Command):
         self.oldWorkingDirectory = os.getcwd()
 
         chdir(self.clientPath)
-        print "Syncronizing p4 checkout..."
+        print "Synchronizing p4 checkout..."
         p4_system("sync ...")
 
         self.check()
@@ -861,8 +859,8 @@ class P4Sync(Command):
 
         self.usage += " //depot/path[@revRange]"
         self.silent = False
-        self.createdBranches = Set()
-        self.committedChanges = Set()
+        self.createdBranches = set()
+        self.committedChanges = set()
         self.branch = ""
         self.detectBranches = False
         self.detectLabels = False
@@ -950,10 +948,83 @@ class P4Sync(Command):
 
         return branches
 
-    ## Should move this out, doesn't use SELF.
-    def readP4Files(self, files):
+    # output one file from the P4 stream
+    # - helper for streamP4Files
+
+    def streamOneP4File(self, file, contents):
+       if file["type"] == "apple":
+           print "\nfile %s is a strange apple file that forks. Ignoring" % \
+               file['depotFile']
+           return
+
+        relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
+        if verbose:
+            sys.stderr.write("%s\n" % relPath)
+
+        mode = "644"
+        if isP4Exec(file["type"]):
+            mode = "755"
+        elif file["type"] == "symlink":
+            mode = "120000"
+            # p4 print on a symlink contains "target\n", so strip it off
+            data = ''.join(contents)
+            contents = [data[:-1]]
+
+        if self.isWindows and file["type"].endswith("text"):
+            mangled = []
+            for data in contents:
+                data = data.replace("\r\n", "\n")
+                mangled.append(data)
+            contents = mangled
+
+        if file['type'] in ('text+ko', 'unicode+ko', 'binary+ko'):
+            contents = map(lambda text: re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', text), contents)
+        elif file['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'):
+            contents = map(lambda text: re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$\n]*\$',r'$\1$', text), contents)
+
+        self.gitStream.write("M %s inline %s\n" % (mode, relPath))
+
+        # total length...
+        length = 0
+        for d in contents:
+            length = length + len(d)
+
+        self.gitStream.write("data %d\n" % length)
+        for d in contents:
+            self.gitStream.write(d)
+        self.gitStream.write("\n")
+
+    def streamOneP4Deletion(self, file):
+        relPath = self.stripRepoPath(file['path'], self.branchPrefixes)
+        if verbose:
+            sys.stderr.write("delete %s\n" % relPath)
+        self.gitStream.write("D %s\n" % relPath)
+
+    # handle another chunk of streaming data
+    def streamP4FilesCb(self, marshalled):
+
+       if marshalled.has_key('depotFile') and self.stream_have_file_info:
+           # start of a new file - output the old one first
+           self.streamOneP4File(self.stream_file, self.stream_contents)
+           self.stream_file = {}
+           self.stream_contents = []
+           self.stream_have_file_info = False
+
+       # pick up the new file information... for the
+       # 'data' field we need to append to our array
+       for k in marshalled.keys():
+           if k == 'data':
+               self.stream_contents.append(marshalled['data'])
+           else:
+               self.stream_file[k] = marshalled[k]
+
+       self.stream_have_file_info = True
+
+    # Stream directly from "p4 files" into "git fast-import"
+    def streamP4Files(self, files):
         filesForCommit = []
         filesToRead = []
+        filesToDelete = []
 
         for f in files:
             includeFile = True
@@ -965,52 +1036,37 @@ class P4Sync(Command):
 
             if includeFile:
                 filesForCommit.append(f)
-                if f['action'] not in ('delete', 'purge'):
+                if f['action'] not in ('delete', 'move/delete', 'purge'):
                     filesToRead.append(f)
+                else:
+                    filesToDelete.append(f)
 
-        filedata = []
-        if len(filesToRead) > 0:
-            filedata = p4CmdList('-x - print',
-                                 stdin='\n'.join(['%s#%s' % (f['path'], f['rev'])
-                                                  for f in filesToRead]),
-                                 stdin_mode='w+')
-
-            if "p4ExitCode" in filedata[0]:
-                die("Problems executing p4. Error: [%d]."
-                    % (filedata[0]['p4ExitCode']));
-
-        j = 0;
-        contents = {}
-        while j < len(filedata):
-            stat = filedata[j]
-            j += 1
-            text = ''
-            while j < len(filedata) and filedata[j]['code'] in ('text', 'unicode', 'binary'):
-                text += filedata[j]['data']
-                del filedata[j]['data']
-                j += 1
-
-            if not stat.has_key('depotFile'):
-                sys.stderr.write("p4 print fails with: %s\n" % repr(stat))
-                continue
+        # deleted files...
+        for f in filesToDelete:
+            self.streamOneP4Deletion(f)
 
-            if stat['type'] in ('text+ko', 'unicode+ko', 'binary+ko'):
-                text = re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', text)
-            elif stat['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'):
-                text = re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$\n]*\$',r'$\1$', text)
+        if len(filesToRead) > 0:
+            self.stream_file = {}
+            self.stream_contents = []
+            self.stream_have_file_info = False
 
-            contents[stat['depotFile']] = text
+           # curry self argument
+           def streamP4FilesCbSelf(entry):
+               self.streamP4FilesCb(entry)
 
-        for f in filesForCommit:
-            path = f['path']
-            if contents.has_key(path):
-                f['data'] = contents[path]
+           p4CmdList("-x - print",
+               '\n'.join(['%s#%s' % (f['path'], f['rev'])
+                                                  for f in filesToRead]),
+               cb=streamP4FilesCbSelf)
 
-        return filesForCommit
+            # do the last chunk
+            if self.stream_file.has_key('depotFile'):
+                self.streamOneP4File(self.stream_file, self.stream_contents)
 
     def commit(self, details, files, branch, branchPrefixes, parent = ""):
         epoch = details["time"]
         author = details["user"]
+       self.branchPrefixes = branchPrefixes
 
         if self.verbose:
             print "commit into %s" % branch
@@ -1023,7 +1079,6 @@ class P4Sync(Command):
                 new_files.append (f)
             else:
                 sys.stderr.write("Ignoring file outside of prefix: %s\n" % path)
-        files = self.readP4Files(new_files)
 
         self.gitStream.write("commit %s\n" % branch)
 #        gitStream.write("mark :%s\n" % details["change"])
@@ -1051,33 +1106,7 @@ class P4Sync(Command):
                 print "parent %s" % parent
             self.gitStream.write("from %s\n" % parent)
 
-        for file in files:
-            if file["type"] == "apple":
-                print "\nfile %s is a strange apple file that forks. Ignoring!" % file['path']
-                continue
-
-            relPath = self.stripRepoPath(file['path'], branchPrefixes)
-            if file["action"] in ("delete", "purge"):
-                self.gitStream.write("D %s\n" % relPath)
-            else:
-                data = file['data']
-
-                mode = "644"
-                if isP4Exec(file["type"]):
-                    mode = "755"
-                elif file["type"] == "symlink":
-                    mode = "120000"
-                    # p4 print on a symlink contains "target\n", so strip it off
-                    data = data[:-1]
-
-                if self.isWindows and file["type"].endswith("text"):
-                    data = data.replace("\r\n", "\n")
-
-                self.gitStream.write("M %s inline %s\n" % (mode, relPath))
-                self.gitStream.write("data %s\n" % len(data))
-                self.gitStream.write(data)
-                self.gitStream.write("\n")
-
+        self.streamP4Files(new_files)
         self.gitStream.write("\n")
 
         change = int(details["change"])
@@ -1627,7 +1656,7 @@ class P4Sync(Command):
 
             if len(self.changesFile) > 0:
                 output = open(self.changesFile).readlines()
-                changeSet = Set()
+                changeSet = set()
                 for line in output:
                     changeSet.add(int(line))
 
diff --git a/contrib/fast-import/import-directories.perl b/contrib/fast-import/import-directories.perl
new file mode 100755 (executable)
index 0000000..3a5da4a
--- /dev/null
@@ -0,0 +1,416 @@
+#!/usr/bin/perl -w
+#
+# Copyright 2008-2009 Peter Krefting <peter@softwolves.pp.se>
+#
+# ------------------------------------------------------------------------
+#
+# 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.
+#
+# 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.
+#
+# ------------------------------------------------------------------------
+
+=pod
+
+=head1 NAME
+
+import-directories - Import bits and pieces to Git.
+
+=head1 SYNOPSIS
+
+B<import-directories.perl> F<configfile> F<outputfile>
+
+=head1 DESCRIPTION
+
+Script to import arbitrary projects version controlled by the "copy the
+source directory to a new location and edit it there"-version controlled
+projects into version control. Handles projects with arbitrary branching
+and version trees, taking a file describing the inputs and generating a
+file compatible with the L<git-fast-import(1)> format.
+
+=head1 CONFIGURATION FILE
+
+=head2 Format
+
+The configuration file is based on the standard I<.ini> format.
+
+ ; Comments start with semi-colons
+ [section]
+ key=value
+
+Please see below for information on how to escape special characters.
+
+=head2 Global configuration
+
+Global configuration is done in the B<[config]> section, which should be
+the first section in the file. Configuration can be changed by
+repeating configuration sections later on.
+
+ [config]
+ ; configure conversion of CRLFs. "convert" means that all CRLFs
+ ; should be converted into LFs (suitable for the core.autocrlf
+ ; setting set to true in Git). "none" means that all data is
+ ; treated as binary.
+ crlf=convert
+
+=head2 Revision configuration
+
+Each revision that is to be imported is described in three
+sections. Revisions should be defined in topological order, so
+that a revision's parent has always been defined when a new revision
+is introduced. All the sections for one revision must be defined
+before defining the next revision.
+
+Each revision is assigned a unique numerical identifier. The
+numbers do not need to be consecutive, nor monotonically
+increasing.
+
+For instance, if your configuration file contains only the two
+revisions 4711 and 42, where 4711 is the initial commit, the
+only requirement is that 4711 is completely defined before 42.
+
+=pod
+
+=head3 Revision description section
+
+A section whose section name is just an integer gives meta-data
+about the revision.
+
+ [3]
+ ; author sets the author of the revisions
+ author=Peter Krefting <peter@softwolves.pp.se>
+ ; branch sets the branch that the revision should be committed to
+ branch=master
+ ; parent describes the revision that is the parent of this commit
+ ; (optional)
+ parent=1
+ ; merges describes a revision that is merged into this commit
+ ; (optional; can be repeated)
+ merges=2
+ ; selects one file to take the timestamp from
+ ; (optional; if unspecified, the most recent file from the .files
+ ;  section is used)
+ timestamp=3/source.c
+
+=head3 Revision contents section
+
+A section whose section name is an integer followed by B<.files>
+describe all the files included in this revision. If a file that
+was available previously is not included in this revision, it will
+be removed.
+
+If an on-disk revision is incomplete, you can point to files from
+a previous revision. There are no restriction as to where the source
+files are located, nor to the names of them.
+
+ [3.files]
+ ; the key is the path inside the repository, the value is the path
+ ; as seen from the importer script.
+ source.c=ver-3.00/source.c
+ source.h=ver-2.99/source.h
+ readme.txt=ver-3.00/introduction to the project.txt
+
+File names are treated as byte strings (but please see below on
+quoting rules), and should be stored in the configuration file in
+the encoding that should be used in the generated repository.
+
+=head3 Revision commit message section
+
+A section whose section name is an integer followed by B<.message>
+gives the commit message. This section is read verbatim, up until
+the beginning of the next section. As such, a commit message may not
+contain a line that begins with an opening square bracket ("[") and
+ends with a closing square bracket ("]"), unless they are surrounded
+by whitespace or other characters.
+
+ [3.message]
+ Implement foobar.
+ ; trailing blank lines are ignored.
+
+=cut
+
+# Globals
+use strict;
+use integer;
+my $crlfmode = 0;
+my @revs;
+my (%revmap, %message, %files, %author, %branch, %parent, %merges, %time, %timesource);
+my $sectiontype = 0;
+my $rev = 0;
+my $mark = 1;
+
+# Check command line
+if ($#ARGV < 1 || $ARGV[0] =~ /^--?h/)
+{
+    exec('perldoc', $0);
+    exit 1;
+}
+
+# Open configuration
+my $config = $ARGV[0];
+open CFG, '<', $config or die "Cannot open configuration file \"$config\": ";
+
+# Open output
+my $output = $ARGV[1];
+open OUT, '>', $output or die "Cannot create output file \"$output\": ";
+binmode OUT;
+
+LINE: while (my $line = <CFG>)
+{
+       $line =~ s/\r?\n$//;
+       next LINE if $sectiontype != 4 && $line eq '';
+       next LINE if $line =~ /^;/;
+       my $oldsectiontype = $sectiontype;
+       my $oldrev = $rev;
+
+       # Sections
+       if ($line =~ m"^\[(config|(\d+)(|\.files|\.message))\]$")
+       {
+               if ($1 eq 'config')
+               {
+                       $sectiontype = 1;
+               }
+               elsif ($3 eq '')
+               {
+                       $sectiontype = 2;
+                       $rev = $2;
+                       # Create a new revision
+                       die "Duplicate rev: $line\n " if defined $revmap{$rev};
+                       print "Reading revision $rev\n";
+                       push @revs, $rev;
+                       $revmap{$rev} = $mark ++;
+                       $time{$revmap{$rev}} = 0;
+               }
+               elsif ($3 eq '.files')
+               {
+                       $sectiontype = 3;
+                       $rev = $2;
+                       die "Revision mismatch: $line\n " unless $rev == $oldrev;
+               }
+               elsif ($3 eq '.message')
+               {
+                       $sectiontype = 4;
+                       $rev = $2;
+                       die "Revision mismatch: $line\n " unless $rev == $oldrev;
+               }
+               else
+               {
+                       die "Internal parse error: $line\n ";
+               }
+               next LINE;
+       }
+
+       # Parse data
+       if ($sectiontype != 4)
+       {
+               # Key and value
+               if ($line =~ m"^\s*([^\s].*=.*[^\s])\s*$")
+               {
+                       my ($key, $value) = &parsekeyvaluepair($1);
+                       # Global configuration
+                       if (1 == $sectiontype)
+                       {
+                               if ($key eq 'crlf')
+                               {
+                                       $crlfmode = 1, next LINE if $value eq 'convert';
+                                       $crlfmode = 0, next LINE if $value eq 'none';
+                               }
+                               die "Unknown configuration option: $line\n ";
+                       }
+                       # Revision specification
+                       if (2 == $sectiontype)
+                       {
+                               my $current = $revmap{$rev};
+                               $author{$current} = $value, next LINE if $key eq 'author';
+                               $branch{$current} = $value, next LINE if $key eq 'branch';
+                               $parent{$current} = $value, next LINE if $key eq 'parent';
+                               $timesource{$current} = $value, next LINE if $key eq 'timestamp';
+                               push(@{$merges{$current}}, $value), next LINE if $key eq 'merges';
+                               die "Unknown revision option: $line\n ";
+                       }
+                       # Filespecs
+                       if (3 == $sectiontype)
+                       {
+                               # Add the file and create a marker
+                               die "File not found: $line\n " unless -f $value;
+                               my $current = $revmap{$rev};
+                               ${$files{$current}}{$key} = $mark;
+                               my $time = &fileblob($value, $crlfmode, $mark ++);
+
+                               # Update revision timestamp if more recent than other
+                               # files seen, or if this is the file we have selected
+                               # to take the time stamp from using the "timestamp"
+                               # directive.
+                               if ((defined $timesource{$current} && $timesource{$current} eq $value)
+                                   || $time > $time{$current})
+                               {
+                                       $time{$current} = $time;
+                               }
+                       }
+               }
+               else
+               {
+                       die "Parse error: $line\n ";
+               }
+       }
+       else
+       {
+               # Commit message
+               my $current = $revmap{$rev};
+               if (defined $message{$current})
+               {
+                       $message{$current} .= "\n";
+               }
+               $message{$current} .= $line;
+       }
+}
+close CFG;
+
+# Start spewing out data for git-fast-import
+foreach my $commit (@revs)
+{
+       # Progress
+       print OUT "progress Creating revision $commit\n";
+
+       # Create commit header
+       my $mark = $revmap{$commit};
+
+       # Branch and commit id
+       print OUT "commit refs/heads/", $branch{$mark}, "\nmark :", $mark, "\n";
+
+       # Author and timestamp
+       die "No timestamp defined for $commit (no files?)\n" unless defined $time{$mark};
+       print OUT "committer ", $author{$mark}, " ", $time{$mark}, " +0100\n";
+
+       # Commit message
+       die "No message defined for $commit\n" unless defined $message{$mark};
+       my $message = $message{$mark};
+       $message =~ s/\n$//; # Kill trailing empty line
+       print OUT "data ", length($message), "\n", $message, "\n";
+
+       # Parent and any merges
+       print OUT "from :", $revmap{$parent{$mark}}, "\n" if defined $parent{$mark};
+       if (defined $merges{$mark})
+       {
+               foreach my $merge (@{$merges{$mark}})
+               {
+                       print OUT "merge :", $revmap{$merge}, "\n";
+               }
+       }
+
+       # Output file marks
+       print OUT "deleteall\n"; # start from scratch
+       foreach my $file (sort keys %{$files{$mark}})
+       {
+               print OUT "M 644 :", ${$files{$mark}}{$file}, " $file\n";
+       }
+       print OUT "\n";
+}
+
+# Create one file blob
+sub fileblob
+{
+       my ($filename, $crlfmode, $mark) = @_;
+
+       # Import the file
+       print OUT "progress Importing $filename\nblob\nmark :$mark\n";
+       open FILE, '<', $filename or die "Cannot read $filename\n ";
+       binmode FILE;
+       my ($size, $mtime) = (stat(FILE))[7,9];
+       my $file;
+       read FILE, $file, $size;
+       close FILE;
+       $file =~ s/\r\n/\n/g if $crlfmode;
+       print OUT "data ", length($file), "\n", $file, "\n";
+
+       return $mtime;
+}
+
+# Parse a key=value pair
+sub parsekeyvaluepair
+{
+=pod
+
+=head2 Escaping special characters
+
+Key and value strings may be enclosed in quotes, in which case
+whitespace inside the quotes is preserved. Additionally, an equal
+sign may be included in the key by preceding it with a backslash.
+For example:
+
+ "key1 "=value1
+ key2=" value2"
+ key\=3=value3
+ key4=value=4
+ "key5""=value5
+
+Here the first key is "key1 " (note the trailing white-space) and the
+second value is " value2" (note the leading white-space). The third
+key contains an equal sign "key=3" and so does the fourth value, which
+does not need to be escaped. The fifth key contains a trailing quote,
+which does not need to be escaped since it is inside a surrounding
+quote.
+
+=cut
+       my $pair = shift;
+
+       # Separate key and value by the first non-quoted equal sign
+       my ($key, $value);
+       if ($pair =~ /^(.*[^\\])=(.*)$/)
+       {
+               ($key, $value) = ($1, $2)
+       }
+       else
+       {
+               die "Parse error: $pair\n ";
+       }
+
+       # Unquote and unescape the key and value separately
+       return (&unescape($key), &unescape($value));
+}
+
+# Unquote and unescape
+sub unescape
+{
+       my $string = shift;
+
+       # First remove enclosing quotes. Backslash before the trailing
+       # quote leaves both.
+       if ($string =~ /^"(.*[^\\])"$/)
+       {
+               $string = $1;
+       }
+
+       # Second remove any backslashes inside the unquoted string.
+       # For later: Handle special sequences like \t ?
+       $string =~ s/\\(.)/$1/g;
+
+       return $string;
+}
+
+__END__
+
+=pod
+
+=head1 EXAMPLES
+
+B<import-directories.perl> F<project.import>
+
+=head1 AUTHOR
+
+Copyright 2008-2009 Peter Krefting E<lt>peter@softwolves.pp.se>
+
+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.
+
+=cut
index 6309d146e74a428520d09cbff4dc8d9a429ec001..95438e1ed42f7289131a1b08654a2b23098c8e26 100755 (executable)
@@ -8,9 +8,20 @@
 ##  perl import-tars.perl *.tar.bz2
 ##  git whatchanged import-tars
 ##
+## Use --metainfo to specify the extension for a meta data file, where
+## import-tars can read the commit message and optionally author and
+## committer information.
+##
+##  echo 'This is the commit message' > myfile.tar.bz2.msg
+##  perl import-tars.perl --metainfo=msg myfile.tar.bz2
 
 use strict;
-die "usage: import-tars *.tar.{gz,bz2,Z}\n" unless @ARGV;
+use Getopt::Long;
+
+my $metaext = '';
+
+die "usage: import-tars [--metainfo=extension] *.tar.{gz,bz2,lzma,xz,Z}\n"
+       unless GetOptions('metainfo=s' => \$metaext) && @ARGV;
 
 my $branch_name = 'import-tars';
 my $branch_ref = "refs/heads/$branch_name";
@@ -38,6 +49,9 @@ foreach my $tar_file (@ARGV)
        } elsif ($tar_name =~ s/\.tar\.Z$//) {
                open(I, '-|', 'uncompress', '-c', $tar_file)
                        or die "Unable to uncompress -c $tar_file: $!\n";
+       } elsif ($tar_name =~ s/\.(tar\.(lzma|xz)|(tlz|txz))$//) {
+               open(I, '-|', 'xz', '-dc', $tar_file)
+                       or die "Unable to xz -dc $tar_file: $!\n";
        } elsif ($tar_name =~ s/\.tar$//) {
                open(I, $tar_file) or die "Unable to open $tar_file: $!\n";
        } else {
@@ -82,10 +96,16 @@ foreach my $tar_file (@ARGV)
                $mtime = oct $mtime;
                next if $typeflag == 5; # directory
 
-               print FI "blob\n", "mark :$next_mark\n", "data $size\n";
-               while ($size > 0 && read(I, $_, 512) == 512) {
-                       print FI substr($_, 0, $size);
-                       $size -= 512;
+               print FI "blob\n", "mark :$next_mark\n";
+               if ($typeflag == 2) { # symbolic link
+                       print FI "data ", length($linkname), "\n", $linkname;
+                       $mode = 0120000;
+               } else {
+                       print FI "data $size\n";
+                       while ($size > 0 && read(I, $_, 512) == 512) {
+                               print FI substr($_, 0, $size);
+                               $size -= 512;
+                       }
                }
                print FI "\n";
 
@@ -103,12 +123,43 @@ foreach my $tar_file (@ARGV)
                $have_top_dir = 0 if $top_dir ne $1;
        }
 
+       my $commit_msg = "Imported from $tar_file.";
+       my $this_committer_name = $committer_name;
+       my $this_committer_email = $committer_email;
+       my $this_author_name = $author_name;
+       my $this_author_email = $author_email;
+       if ($metaext ne '') {
+               # Optionally read a commit message from <filename.tar>.msg
+               # Add a line on the form "Committer: name <e-mail>" to override
+               # the committer and "Author: name <e-mail>" to override the
+               # author for this tar ball.
+               if (open MSG, '<', "${tar_file}.${metaext}") {
+                       my $header_done = 0;
+                       $commit_msg = '';
+                       while (<MSG>) {
+                               if (!$header_done && /^Committer:\s+([^<>]*)\s+<(.*)>\s*$/i) {
+                                       $this_committer_name = $1;
+                                       $this_committer_email = $2;
+                               } elsif (!$header_done && /^Author:\s+([^<>]*)\s+<(.*)>\s*$/i) {
+                                       $this_author_name = $1;
+                                       $this_author_email = $2;
+                               } elsif (!$header_done && /^$/) { # empty line ends header.
+                                       $header_done = 1;
+                               } else {
+                                       $commit_msg .= $_;
+                                       $header_done = 1;
+                               }
+                       }
+                       close MSG;
+               }
+       }
+
        print FI <<EOF;
 commit $branch_ref
-author $author_name <$author_email> $author_time +0000
-committer $committer_name <$committer_email> $commit_time +0000
+author $this_author_name <$this_author_email> $author_time +0000
+committer $this_committer_name <$this_committer_email> $commit_time +0000
 data <<END_OF_COMMIT_MESSAGE
-Imported from $tar_file.
+$commit_msg
 END_OF_COMMIT_MESSAGE
 
 deleteall
@@ -118,7 +169,8 @@ EOF
        {
                my ($mark, $mode) = @{$files{$path}};
                $path =~ s,^([^/]+)/,, if $have_top_dir;
-               printf FI "M %o :%i %s\n", $mode & 0111 ? 0755 : 0644, $mark, $path;
+               $mode = $mode & 0111 ? 0755 : 0644 unless $mode == 0120000;
+               printf FI "M %o :%i %s\n", $mode, $mark, $path;
        }
        print FI "\n";
 
index 7051a83a59758277dd60fe026dea730eb7b6b115..82f5ed3ddc8adb1b9b281c3912f4e67c53ef152f 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
 
 ## zip archive frontend for git-fast-import
 ##
index c364dda696912037cfafdb5e182b6e58f99e0a7e..a4ed4c3c62f0d5abcee36000a6c3a8f43dc02112 100755 (executable)
@@ -9,6 +9,7 @@ other/Merge <other> into <name> (respectively) commit subjects, which
 is rather slow but allows you to resurrect other people's topic
 branches."
 
+OPTIONS_KEEPDASHDASH=
 OPTIONS_SPEC="\
 git resurrect $USAGE
 --
index 7b03204ed18500756ba55818f0808b52db68d048..046cb2b268a82358630e86bb55cf8b4e58c730fb 100755 (executable)
@@ -1,4 +1,4 @@
-#! /usr/bin/python
+#!/usr/bin/env python
 
 """ hg-to-git.py - A Mercurial to GIT converter
 
@@ -20,7 +20,7 @@
 """
 
 import os, os.path, sys
-import tempfile, popen2, pickle, getopt
+import tempfile, pickle, getopt
 import re
 
 # Maps hg version -> git version
@@ -59,14 +59,14 @@ def getgitenv(user, date):
     elems = re.compile('(.*?)\s+<(.*)>').match(user)
     if elems:
         env += 'export GIT_AUTHOR_NAME="%s" ;' % elems.group(1)
-        env += 'export GIT_COMMITER_NAME="%s" ;' % elems.group(1)
+        env += 'export GIT_COMMITTER_NAME="%s" ;' % elems.group(1)
         env += 'export GIT_AUTHOR_EMAIL="%s" ;' % elems.group(2)
-        env += 'export GIT_COMMITER_EMAIL="%s" ;' % elems.group(2)
+        env += 'export GIT_COMMITTER_EMAIL="%s" ;' % elems.group(2)
     else:
         env += 'export GIT_AUTHOR_NAME="%s" ;' % user
-        env += 'export GIT_COMMITER_NAME="%s" ;' % user
+        env += 'export GIT_COMMITTER_NAME="%s" ;' % user
         env += 'export GIT_AUTHOR_EMAIL= ;'
-        env += 'export GIT_COMMITER_EMAIL= ;'
+        env += 'export GIT_COMMITTER_EMAIL= ;'
 
     env += 'export GIT_AUTHOR_DATE="%s" ;' % date
     env += 'export GIT_COMMITTER_DATE="%s" ;' % date
old mode 100644 (file)
new mode 100755 (executable)
index 60cbab6..0085086
 # possible for the email to be from someone other than the person doing the
 # push.
 #
+# To help with debugging and use on pre-v1.5.1 git servers, this script will
+# also obey the interface of hooks/update, taking its arguments on the
+# command line.  Unfortunately, hooks/update is called once for each ref.
+# To avoid firing one email per ref, this script just prints its output to
+# the screen when used in this mode.  The output can then be redirected if
+# wanted.
+#
 # Config
 # ------
 # hooks.mailinglist
 #   --pretty %s", displaying the commit id, author, date and log
 #   message.  To list full patches separated by a blank line, you
 #   could set this to "git show -C %s; echo".
+#   To list a gitweb/cgit URL *and* a full patch for each change set, use this:
+#     "t=%s; printf 'http://.../?id=%%s' \$t; echo;echo; git show -C \$t; echo"
+#   Be careful if "..." contains things that will be expanded by shell "eval"
+#   or printf.
+# hooks.emailmaxlines
+#   The maximum number of lines that should be included in the generated
+#   email body. If not specified, there is no limit.
+#   Lines beyond the limit are suppressed and counted, and a final
+#   line is added indicating the number of suppressed lines.
 #
 # Notes
 # -----
@@ -73,6 +89,7 @@ generate_email()
        oldrev=$(git rev-parse $1)
        newrev=$(git rev-parse $2)
        refname="$3"
+       maxlines=$4
 
        # --- Interpret
        # 0000->1234 (create)
@@ -181,7 +198,12 @@ generate_email()
                fn_name=atag
                ;;
        esac
-       generate_${change_type}_${fn_name}_email
+
+       if [ -z "$maxlines" ]; then
+               generate_${change_type}_${fn_name}_email
+       else
+               generate_${change_type}_${fn_name}_email | limit_lines $maxlines
+       fi
 
        generate_email_footer
 }
@@ -192,7 +214,7 @@ generate_email_header()
        # Generate header
        cat <<-EOF
        To: $recipients
-       Subject: ${emailprefix}$projectdesc $refname_type, $short_refname, ${change_type}d. $describe
+       Subject: ${emailprefix}$projectdesc $refname_type $short_refname ${change_type}d. $describe
        X-Git-Refname: $refname
        X-Git-Reftype: $refname_type
        X-Git-Oldrev: $oldrev
@@ -311,8 +333,8 @@ generate_update_branch_email()
        # "remotes/" will be ignored as well.
 
        # List all of the revisions that were removed by this update, in a
-       # fast forward update, this list will be empty, because rev-list O
-       # ^N is empty.  For a non fast forward, O ^N is the list of removed
+       # fast-forward update, this list will be empty, because rev-list O
+       # ^N is empty.  For a non-fast-forward, O ^N is the list of removed
        # revisions
        fast_forward=""
        rev=""
@@ -407,7 +429,7 @@ generate_update_branch_email()
        # revision because the base is effectively a random revision at this
        # point - the user will be interested in what this revision changed
        # - including the undoing of previous revisions in the case of
-       # non-fast forward updates.
+       # non-fast-forward updates.
        echo ""
        echo "Summary of changes:"
        git diff-tree --stat --summary --find-copies-harder $oldrev..$newrev
@@ -631,6 +653,24 @@ show_new_revisions()
 }
 
 
+limit_lines()
+{
+       lines=0
+       skipped=0
+       while IFS="" read -r line; do
+               lines=$((lines + 1))
+               if [ $lines -gt $1 ]; then
+                       skipped=$((skipped + 1))
+               else
+                       printf "%s\n" "$line"
+               fi
+       done
+       if [ $skipped -ne 0 ]; then
+               echo "... $skipped lines suppressed ..."
+       fi
+}
+
+
 send_mail()
 {
        if [ -n "$envelopesender" ]; then
@@ -668,6 +708,7 @@ announcerecipients=$(git config hooks.announcelist)
 envelopesender=$(git config hooks.envelopesender)
 emailprefix=$(git config hooks.emailprefix || echo '[SCM] ')
 custom_showrev=$(git config hooks.showrev)
+maxlines=$(git config hooks.emailmaxlines)
 
 # --- Main loop
 # Allow dual mode: run from the command line just like the update hook, or
@@ -680,6 +721,6 @@ if [ -n "$1" -a -n "$2" -a -n "$3" ]; then
 else
        while read oldrev newrev refname
        do
-               generate_email $oldrev $newrev $refname | send_mail
+               generate_email $oldrev $newrev $refname $maxlines | send_mail
        done
 fi
index 0f3d97b67eef3108728265e26f5d79c4526d11ac..b6e534b65b687d955a878d902b9bb46cfa2e42ce 100644 (file)
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
 #
 # This tool is copyright (c) 2006, Sean Estabrooks.
 # It is released under the Gnu Public License, version 2.
diff --git a/contrib/svn-fe/.gitignore b/contrib/svn-fe/.gitignore
new file mode 100644 (file)
index 0000000..02a7791
--- /dev/null
@@ -0,0 +1,4 @@
+/*.xml
+/*.1
+/*.html
+/svn-fe
diff --git a/contrib/svn-fe/Makefile b/contrib/svn-fe/Makefile
new file mode 100644 (file)
index 0000000..360d8da
--- /dev/null
@@ -0,0 +1,63 @@
+all:: svn-fe$X
+
+CC = gcc
+RM = rm -f
+MV = mv
+
+CFLAGS = -g -O2 -Wall
+LDFLAGS =
+ALL_CFLAGS = $(CFLAGS)
+ALL_LDFLAGS = $(LDFLAGS)
+EXTLIBS =
+
+GIT_LIB = ../../libgit.a
+VCSSVN_LIB = ../../vcs-svn/lib.a
+LIBS = $(VCSSVN_LIB) $(GIT_LIB) $(EXTLIBS)
+
+QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir
+QUIET_SUBDIR1 =
+
+ifneq ($(findstring $(MAKEFLAGS),w),w)
+PRINT_DIR = --no-print-directory
+else # "make -w"
+NO_SUBDIR = :
+endif
+
+ifneq ($(findstring $(MAKEFLAGS),s),s)
+ifndef V
+       QUIET_CC      = @echo '   ' CC $@;
+       QUIET_LINK    = @echo '   ' LINK $@;
+       QUIET_SUBDIR0 = +@subdir=
+       QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo '   ' SUBDIR $$subdir; \
+                       $(MAKE) $(PRINT_DIR) -C $$subdir
+endif
+endif
+
+svn-fe$X: svn-fe.o $(VCSSVN_LIB) $(GIT_LIB)
+       $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ svn-fe.o \
+               $(ALL_LDFLAGS) $(LIBS)
+
+svn-fe.o: svn-fe.c ../../vcs-svn/svndump.h
+       $(QUIET_CC)$(CC) -I../../vcs-svn -o $*.o -c $(ALL_CFLAGS) $<
+
+svn-fe.html: svn-fe.txt
+       $(QUIET_SUBDIR0)../../Documentation $(QUIET_SUBDIR1) \
+               MAN_TXT=../contrib/svn-fe/svn-fe.txt \
+               ../contrib/svn-fe/$@
+
+svn-fe.1: svn-fe.txt
+       $(QUIET_SUBDIR0)../../Documentation $(QUIET_SUBDIR1) \
+               MAN_TXT=../contrib/svn-fe/svn-fe.txt \
+               ../contrib/svn-fe/$@
+       $(MV) ../../Documentation/svn-fe.1 .
+
+../../vcs-svn/lib.a: FORCE
+       $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) vcs-svn/lib.a
+
+../../libgit.a: FORCE
+       $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) libgit.a
+
+clean:
+       $(RM) svn-fe$X svn-fe.o svn-fe.html svn-fe.xml svn-fe.1
+
+.PHONY: all clean FORCE
diff --git a/contrib/svn-fe/svn-fe.c b/contrib/svn-fe/svn-fe.c
new file mode 100644 (file)
index 0000000..a2677b0
--- /dev/null
@@ -0,0 +1,16 @@
+/*
+ * This file is in the public domain.
+ * You may freely use, modify, distribute, and relicense it.
+ */
+
+#include <stdlib.h>
+#include "svndump.h"
+
+int main(int argc, char **argv)
+{
+       svndump_init(NULL);
+       svndump_read((argc > 1) ? argv[1] : NULL);
+       svndump_deinit();
+       svndump_reset();
+       return 0;
+}
diff --git a/contrib/svn-fe/svn-fe.txt b/contrib/svn-fe/svn-fe.txt
new file mode 100644 (file)
index 0000000..35f84bd
--- /dev/null
@@ -0,0 +1,67 @@
+svn-fe(1)
+=========
+
+NAME
+----
+svn-fe - convert an SVN "dumpfile" to a fast-import stream
+
+SYNOPSIS
+--------
+svnadmin dump --incremental REPO | svn-fe [url] | git fast-import
+
+DESCRIPTION
+-----------
+
+Converts a Subversion dumpfile into input suitable for
+git-fast-import(1) and similar importers. REPO is a path to a
+Subversion repository mirrored on the local disk. Remote Subversion
+repositories can be mirrored on local disk using the `svnsync`
+command.
+
+INPUT FORMAT
+------------
+Subversion's repository dump format is documented in full in
+`notes/dump-load-format.txt` from the Subversion source tree.
+Files in this format can be generated using the 'svnadmin dump' or
+'svk admin dump' command.
+
+Dumps produced with 'svnadmin dump --deltas' (dumpfile format v3)
+are not supported.
+
+OUTPUT FORMAT
+-------------
+The fast-import format is documented by the git-fast-import(1)
+manual page.
+
+NOTES
+-----
+Subversion dumps do not record a separate author and committer for
+each revision, nor a separate display name and email address for
+each author.  Like git-svn(1), 'svn-fe' will use the name
+
+---------
+user <user@UUID>
+---------
+
+as committer, where 'user' is the value of the `svn:author` property
+and 'UUID' the repository's identifier.
+
+To support incremental imports, 'svn-fe' puts a `git-svn-id` line at
+the end of each commit log message if passed an url on the command
+line.  This line has the form `git-svn-id: URL@REVNO UUID`.
+
+The resulting repository will generally require further processing
+to put each project in its own repository and to separate the history
+of each branch.  The 'git filter-branch --subdirectory-filter' command
+may be useful for this purpose.
+
+BUGS
+----
+Empty directories and unknown properties are silently discarded.
+
+The exit status does not reflect whether an error was detected.
+
+SEE ALSO
+--------
+git-svn(1), svn2git(1), svk(1), git-filter-branch(1), git-fast-import(1),
+https://svn.apache.org/repos/asf/subversion/trunk/notes/dump-load-format.txt
index 993cacf324b8595e5be583ff372b25353c7af95c..3ad2c0cea56392fb941282ec6225c5f9a3ce05ea 100755 (executable)
@@ -54,13 +54,13 @@ then
        die "destination directory '$new_workdir' already exists."
 fi
 
-# make sure the the links use full paths
+# make sure the links use full paths
 git_dir=$(cd "$git_dir"; pwd)
 
 # create the workdir
 mkdir -p "$new_workdir/.git" || die "unable to create \"$new_workdir\"!"
 
-# create the links to the original repo.  explictly exclude index, HEAD and
+# create the links to the original repo.  explicitly exclude index, HEAD and
 # logs/HEAD from the list since they are purely related to the current working
 # directory, and should not be shared.
 for x in config refs logs/refs objects info hooks packed-refs remotes rr-cache svn
index 1816e977b7b13782003fc78a9de9b47cd3554a32..01de9a84c21b31a0120065a32a386f27321cdf7b 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -8,13 +8,17 @@
  * This should use the pathname to decide on whether it wants to do some
  * more interesting conversions (automatic gzip/unzip, general format
  * conversions etc etc), but by default it just does automatic CRLF<->LF
- * translation when the "auto_crlf" option is set.
+ * translation when the "text" attribute or "auto_crlf" option is set.
  */
 
-#define CRLF_GUESS     (-1)
-#define CRLF_BINARY    0
-#define CRLF_TEXT      1
-#define CRLF_INPUT     2
+enum action {
+       CRLF_GUESS = -1,
+       CRLF_BINARY = 0,
+       CRLF_TEXT,
+       CRLF_INPUT,
+       CRLF_CRLF,
+       CRLF_AUTO,
+};
 
 struct text_stat {
        /* NUL, CR, LF and CRLF counts */
@@ -89,49 +93,112 @@ static int is_binary(unsigned long size, struct text_stat *stats)
        return 0;
 }
 
-static void check_safe_crlf(const char *path, int action,
+static enum eol determine_output_conversion(enum action action)
+{
+       switch (action) {
+       case CRLF_BINARY:
+               return EOL_UNSET;
+       case CRLF_CRLF:
+               return EOL_CRLF;
+       case CRLF_INPUT:
+               return EOL_LF;
+       case CRLF_GUESS:
+               if (!auto_crlf)
+                       return EOL_UNSET;
+               /* fall through */
+       case CRLF_TEXT:
+       case CRLF_AUTO:
+               if (auto_crlf == AUTO_CRLF_TRUE)
+                       return EOL_CRLF;
+               else if (auto_crlf == AUTO_CRLF_INPUT)
+                       return EOL_LF;
+               else if (eol == EOL_UNSET)
+                       return EOL_NATIVE;
+       }
+       return eol;
+}
+
+static void check_safe_crlf(const char *path, enum action action,
                             struct text_stat *stats, enum safe_crlf checksafe)
 {
        if (!checksafe)
                return;
 
-       if (action == CRLF_INPUT || auto_crlf <= 0) {
+       if (determine_output_conversion(action) == EOL_LF) {
                /*
                 * CRLFs would not be restored by checkout:
                 * check if we'd remove CRLFs
                 */
                if (stats->crlf) {
                        if (checksafe == SAFE_CRLF_WARN)
-                               warning("CRLF will be replaced by LF in %s.", path);
+                               warning("CRLF will be replaced by LF in %s.\nThe file will have its original line endings in your working directory.", path);
                        else /* i.e. SAFE_CRLF_FAIL */
                                die("CRLF would be replaced by LF in %s.", path);
                }
-       } else if (auto_crlf > 0) {
+       } else if (determine_output_conversion(action) == EOL_CRLF) {
                /*
                 * CRLFs would be added by checkout:
                 * check if we have "naked" LFs
                 */
                if (stats->lf != stats->crlf) {
                        if (checksafe == SAFE_CRLF_WARN)
-                               warning("LF will be replaced by CRLF in %s", path);
+                               warning("LF will be replaced by CRLF in %s.\nThe file will have its original line endings in your working directory.", path);
                        else /* i.e. SAFE_CRLF_FAIL */
                                die("LF would be replaced by CRLF in %s", path);
                }
        }
 }
 
+static int has_cr_in_index(const char *path)
+{
+       int pos, len;
+       unsigned long sz;
+       enum object_type type;
+       void *data;
+       int has_cr;
+       struct index_state *istate = &the_index;
+
+       len = strlen(path);
+       pos = index_name_pos(istate, path, len);
+       if (pos < 0) {
+               /*
+                * We might be in the middle of a merge, in which
+                * case we would read stage #2 (ours).
+                */
+               int i;
+               for (i = -pos - 1;
+                    (pos < 0 && i < istate->cache_nr &&
+                     !strcmp(istate->cache[i]->name, path));
+                    i++)
+                       if (ce_stage(istate->cache[i]) == 2)
+                               pos = i;
+       }
+       if (pos < 0)
+               return 0;
+       data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz);
+       if (!data || type != OBJ_BLOB) {
+               free(data);
+               return 0;
+       }
+
+       has_cr = memchr(data, '\r', sz) != NULL;
+       free(data);
+       return has_cr;
+}
+
 static int crlf_to_git(const char *path, const char *src, size_t len,
-                       struct strbuf *buf, int action, enum safe_crlf checksafe)
+                      struct strbuf *buf, enum action action, enum safe_crlf checksafe)
 {
        struct text_stat stats;
        char *dst;
 
-       if ((action == CRLF_BINARY) || !auto_crlf || !len)
+       if (action == CRLF_BINARY ||
+           (action == CRLF_GUESS && auto_crlf == AUTO_CRLF_FALSE) || !len)
                return 0;
 
        gather_stats(src, len, &stats);
 
-       if (action == CRLF_GUESS) {
+       if (action == CRLF_AUTO || action == CRLF_GUESS) {
                /*
                 * We're currently not going to even try to convert stuff
                 * that has bare CR characters. Does anybody do that crazy
@@ -145,6 +212,15 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
                 */
                if (is_binary(len, &stats))
                        return 0;
+
+               if (action == CRLF_GUESS) {
+                       /*
+                        * If the file in the index has any CR in it, do not convert.
+                        * This is the new safer autocrlf handling.
+                        */
+                       if (has_cr_in_index(path))
+                               return 0;
+               }
        }
 
        check_safe_crlf(path, action, &stats, checksafe);
@@ -157,7 +233,7 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
        if (strbuf_avail(buf) + buf->len < len)
                strbuf_grow(buf, len - buf->len);
        dst = buf->buf;
-       if (action == CRLF_GUESS) {
+       if (action == CRLF_AUTO || action == CRLF_GUESS) {
                /*
                 * If we guessed, we already know we rejected a file with
                 * lone CR, and we can strip a CR without looking at what
@@ -180,16 +256,12 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
 }
 
 static int crlf_to_worktree(const char *path, const char *src, size_t len,
-                            struct strbuf *buf, int action)
+                           struct strbuf *buf, enum action action)
 {
        char *to_free = NULL;
        struct text_stat stats;
 
-       if ((action == CRLF_BINARY) || (action == CRLF_INPUT) ||
-           auto_crlf <= 0)
-               return 0;
-
-       if (!len)
+       if (!len || determine_output_conversion(action) != EOL_CRLF)
                return 0;
 
        gather_stats(src, len, &stats);
@@ -202,7 +274,14 @@ static int crlf_to_worktree(const char *path, const char *src, size_t len,
        if (stats.lf == stats.crlf)
                return 0;
 
-       if (action == CRLF_GUESS) {
+       if (action == CRLF_AUTO || action == CRLF_GUESS) {
+               if (action == CRLF_GUESS) {
+                       /* If we have any CR or CRLF line endings, we do not touch it */
+                       /* This is the new safer autocrlf-handling */
+                       if (stats.cr > 0 || stats.crlf > 0)
+                               return 0;
+               }
+
                /* If we have any bare CR characters, we're not going to touch it */
                if (stats.cr != stats.crlf)
                        return 0;
@@ -241,7 +320,7 @@ struct filter_params {
        const char *cmd;
 };
 
-static int filter_buffer(int fd, void *data)
+static int filter_buffer(int in, int out, void *data)
 {
        /*
         * Spawn cmd and feed the buffer contents through its stdin.
@@ -249,12 +328,15 @@ static int filter_buffer(int fd, void *data)
        struct child_process child_process;
        struct filter_params *params = (struct filter_params *)data;
        int write_err, status;
-       const char *argv[] = { "sh", "-c", params->cmd, NULL };
+       const char *argv[] = { NULL, NULL };
+
+       argv[0] = params->cmd;
 
        memset(&child_process, 0, sizeof(child_process));
        child_process.argv = argv;
+       child_process.use_shell = 1;
        child_process.in = -1;
-       child_process.out = fd;
+       child_process.out = out;
 
        if (start_command(&child_process))
                return error("cannot fork to run external filter %s", params->cmd);
@@ -267,7 +349,7 @@ static int filter_buffer(int fd, void *data)
 
        status = finish_command(&child_process);
        if (status)
-               error("external filter %s failed %d", params->cmd, -status);
+               error("external filter %s failed %d", params->cmd, status);
        return (write_err || status);
 }
 
@@ -291,6 +373,7 @@ static int apply_filter(const char *path, const char *src, size_t len,
        memset(&async, 0, sizeof(async));
        async.proc = filter_buffer;
        async.data = &params;
+       async.out = -1;
        params.src = src;
        params.size = len;
        params.cmd = cmd;
@@ -372,20 +455,26 @@ static int read_convert_config(const char *var, const char *value, void *cb)
 
 static void setup_convert_check(struct git_attr_check *check)
 {
+       static struct git_attr *attr_text;
        static struct git_attr *attr_crlf;
+       static struct git_attr *attr_eol;
        static struct git_attr *attr_ident;
        static struct git_attr *attr_filter;
 
-       if (!attr_crlf) {
-               attr_crlf = git_attr("crlf", 4);
-               attr_ident = git_attr("ident", 5);
-               attr_filter = git_attr("filter", 6);
+       if (!attr_text) {
+               attr_text = git_attr("text");
+               attr_crlf = git_attr("crlf");
+               attr_eol = git_attr("eol");
+               attr_ident = git_attr("ident");
+               attr_filter = git_attr("filter");
                user_convert_tail = &user_convert;
                git_config(read_convert_config, NULL);
        }
        check[0].attr = attr_crlf;
        check[1].attr = attr_ident;
        check[2].attr = attr_filter;
+       check[3].attr = attr_eol;
+       check[4].attr = attr_text;
 }
 
 static int count_ident(const char *cp, unsigned long size)
@@ -423,6 +512,8 @@ static int count_ident(const char *cp, unsigned long size)
                                cnt++;
                                break;
                        }
+                       if (ch == '\n')
+                               break;
                }
        }
        return cnt;
@@ -453,6 +544,11 @@ static int ident_to_git(const char *path, const char *src, size_t len,
                        dollar = memchr(src + 3, '$', len - 3);
                        if (!dollar)
                                break;
+                       if (memchr(src + 3, '\n', dollar - src - 3)) {
+                               /* Line break before the next dollar. */
+                               continue;
+                       }
+
                        memcpy(dst, "Id$", 3);
                        dst += 3;
                        len -= dollar + 1 - src;
@@ -468,7 +564,7 @@ static int ident_to_worktree(const char *path, const char *src, size_t len,
                              struct strbuf *buf, int ident)
 {
        unsigned char sha1[20];
-       char *to_free = NULL, *dollar;
+       char *to_free = NULL, *dollar, *spc;
        int cnt;
 
        if (!ident)
@@ -504,7 +600,10 @@ static int ident_to_worktree(const char *path, const char *src, size_t len,
                } else if (src[2] == ':') {
                        /*
                         * It's possible that an expanded Id has crept its way into the
-                        * repository, we cope with that by stripping the expansion out
+                        * repository, we cope with that by stripping the expansion out.
+                        * This is probably not a good idea, since it will cause changes
+                        * on checkout, which won't go away by stash, but let's keep it
+                        * for git-style ids.
                         */
                        dollar = memchr(src + 3, '$', len - 3);
                        if (!dollar) {
@@ -512,6 +611,20 @@ static int ident_to_worktree(const char *path, const char *src, size_t len,
                                break;
                        }
 
+                       if (memchr(src + 3, '\n', dollar - src - 3)) {
+                               /* Line break before the next dollar. */
+                               continue;
+                       }
+
+                       spc = memchr(src + 4, ' ', dollar - src - 4);
+                       if (spc && spc < dollar-1) {
+                               /* There are spaces in unexpected places.
+                                * This is probably an id from some other
+                                * versioning system. Keep it for now.
+                                */
+                               continue;
+                       }
+
                        len -= dollar + 1 - src;
                        src  = dollar + 1;
                } else {
@@ -542,9 +655,24 @@ static int git_path_check_crlf(const char *path, struct git_attr_check *check)
                ;
        else if (!strcmp(value, "input"))
                return CRLF_INPUT;
+       else if (!strcmp(value, "auto"))
+               return CRLF_AUTO;
        return CRLF_GUESS;
 }
 
+static int git_path_check_eol(const char *path, struct git_attr_check *check)
+{
+       const char *value = check->value;
+
+       if (ATTR_UNSET(value))
+               ;
+       else if (!strcmp(value, "lf"))
+               return EOL_LF;
+       else if (!strcmp(value, "crlf"))
+               return EOL_CRLF;
+       return EOL_UNSET;
+}
+
 static struct convert_driver *git_path_check_convert(const char *path,
                                             struct git_attr_check *check)
 {
@@ -566,20 +694,35 @@ static int git_path_check_ident(const char *path, struct git_attr_check *check)
        return !!ATTR_TRUE(value);
 }
 
+static enum action determine_action(enum action text_attr, enum eol eol_attr)
+{
+       if (text_attr == CRLF_BINARY)
+               return CRLF_BINARY;
+       if (eol_attr == EOL_LF)
+               return CRLF_INPUT;
+       if (eol_attr == EOL_CRLF)
+               return CRLF_CRLF;
+       return text_attr;
+}
+
 int convert_to_git(const char *path, const char *src, size_t len,
                    struct strbuf *dst, enum safe_crlf checksafe)
 {
-       struct git_attr_check check[3];
-       int crlf = CRLF_GUESS;
+       struct git_attr_check check[5];
+       enum action action = CRLF_GUESS;
+       enum eol eol_attr = EOL_UNSET;
        int ident = 0, ret = 0;
        const char *filter = NULL;
 
        setup_convert_check(check);
        if (!git_checkattr(path, ARRAY_SIZE(check), check)) {
                struct convert_driver *drv;
-               crlf = git_path_check_crlf(path, check + 0);
+               action = git_path_check_crlf(path, check + 4);
+               if (action == CRLF_GUESS)
+                       action = git_path_check_crlf(path, check + 0);
                ident = git_path_check_ident(path, check + 1);
                drv = git_path_check_convert(path, check + 2);
+               eol_attr = git_path_check_eol(path, check + 3);
                if (drv && drv->clean)
                        filter = drv->clean;
        }
@@ -589,7 +732,8 @@ int convert_to_git(const char *path, const char *src, size_t len,
                src = dst->buf;
                len = dst->len;
        }
-       ret |= crlf_to_git(path, src, len, dst, crlf, checksafe);
+       action = determine_action(action, eol_attr);
+       ret |= crlf_to_git(path, src, len, dst, action, checksafe);
        if (ret) {
                src = dst->buf;
                len = dst->len;
@@ -597,19 +741,25 @@ int convert_to_git(const char *path, const char *src, size_t len,
        return ret | ident_to_git(path, src, len, dst, ident);
 }
 
-int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst)
+static int convert_to_working_tree_internal(const char *path, const char *src,
+                                           size_t len, struct strbuf *dst,
+                                           int normalizing)
 {
-       struct git_attr_check check[3];
-       int crlf = CRLF_GUESS;
+       struct git_attr_check check[5];
+       enum action action = CRLF_GUESS;
+       enum eol eol_attr = EOL_UNSET;
        int ident = 0, ret = 0;
        const char *filter = NULL;
 
        setup_convert_check(check);
        if (!git_checkattr(path, ARRAY_SIZE(check), check)) {
                struct convert_driver *drv;
-               crlf = git_path_check_crlf(path, check + 0);
+               action = git_path_check_crlf(path, check + 4);
+               if (action == CRLF_GUESS)
+                       action = git_path_check_crlf(path, check + 0);
                ident = git_path_check_ident(path, check + 1);
                drv = git_path_check_convert(path, check + 2);
+               eol_attr = git_path_check_eol(path, check + 3);
                if (drv && drv->smudge)
                        filter = drv->smudge;
        }
@@ -619,10 +769,32 @@ int convert_to_working_tree(const char *path, const char *src, size_t len, struc
                src = dst->buf;
                len = dst->len;
        }
-       ret |= crlf_to_worktree(path, src, len, dst, crlf);
+       /*
+        * CRLF conversion can be skipped if normalizing, unless there
+        * is a smudge filter.  The filter might expect CRLFs.
+        */
+       if (filter || !normalizing) {
+               action = determine_action(action, eol_attr);
+               ret |= crlf_to_worktree(path, src, len, dst, action);
+               if (ret) {
+                       src = dst->buf;
+                       len = dst->len;
+               }
+       }
+       return ret | apply_filter(path, src, len, dst, filter);
+}
+
+int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst)
+{
+       return convert_to_working_tree_internal(path, src, len, dst, 0);
+}
+
+int renormalize_buffer(const char *path, const char *src, size_t len, struct strbuf *dst)
+{
+       int ret = convert_to_working_tree_internal(path, src, len, dst, 1);
        if (ret) {
                src = dst->buf;
                len = dst->len;
        }
-       return ret | apply_filter(path, src, len, dst, filter);
+       return ret | convert_to_git(path, src, len, dst, 0);
 }
diff --git a/copy.c b/copy.c
index e54d15aced7595ccb11423b0de121db9051ad1f3..a7f58fd905b31b2634f74580090ec664a640e279 100644 (file)
--- a/copy.c
+++ b/copy.c
@@ -35,6 +35,19 @@ int copy_fd(int ifd, int ofd)
        return 0;
 }
 
+static int copy_times(const char *dst, const char *src)
+{
+       struct stat st;
+       struct utimbuf times;
+       if (stat(src, &st) < 0)
+               return -1;
+       times.actime = st.st_atime;
+       times.modtime = st.st_mtime;
+       if (utime(dst, &times) < 0)
+               return -1;
+       return 0;
+}
+
 int copy_file(const char *dst, const char *src, int mode)
 {
        int fdi, fdo, status;
@@ -55,3 +68,11 @@ int copy_file(const char *dst, const char *src, int mode)
 
        return status;
 }
+
+int copy_file_with_time(const char *dst, const char *src, int mode)
+{
+       int status = copy_file(dst, src, mode);
+       if (!status)
+               return copy_times(dst, src);
+       return status;
+}
index 2ddb12a0b70da87afe6fa8a33dce08c6c8ae7f71..4d50cc5ce18c24a1dc853d3050062b864fe0b943 100644 (file)
@@ -26,7 +26,7 @@ static void flush(struct sha1file *f, void * buf, unsigned int count)
                }
                if (!ret)
                        die("sha1 file '%s' write error. Out of diskspace", f->name);
-               die("sha1 file '%s' write error (%s)", f->name, strerror(errno));
+               die_errno("sha1 file '%s' write error", f->name);
        }
 }
 
@@ -55,8 +55,7 @@ int sha1close(struct sha1file *f, unsigned char *result, unsigned int flags)
                if (flags & CSUM_FSYNC)
                        fsync_or_die(f->fd, f->name);
                if (close(f->fd))
-                       die("%s: sha1 file error on close (%s)",
-                           f->name, strerror(errno));
+                       die_errno("%s: sha1 file error on close", f->name);
                fd = 0;
        } else
                fd = f->fd;
diff --git a/ctype.c b/ctype.c
index 7ee64c7d77dd4a5665f70d80ffba1bcdecb9a408..de600279eef4765db497599e6654c2bedd978129 100644 (file)
--- a/ctype.c
+++ b/ctype.c
@@ -10,7 +10,7 @@ enum {
        A = GIT_ALPHA,
        D = GIT_DIGIT,
        G = GIT_GLOB_SPECIAL,   /* *, ?, [, \\ */
-       R = GIT_REGEX_SPECIAL,  /* $, (, ), +, ., ^, {, | */
+       R = GIT_REGEX_SPECIAL   /* $, (, ), +, ., ^, {, | */
 };
 
 unsigned char sane_ctype[256] = {
index daa4c8e8c95924c6fb446884b3ed44c454425b54..e22a2b7fa5f6fcfc5b26541a21c10d3c8b7eda81 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -1,6 +1,8 @@
 #include "cache.h"
 #include "pkt-line.h"
 #include "exec_cmd.h"
+#include "run-command.h"
+#include "strbuf.h"
 
 #include <syslog.h>
 
@@ -75,6 +77,7 @@ static void logreport(int priority, const char *err, va_list params)
        }
 }
 
+__attribute__((format (printf, 1, 2)))
 static void logerror(const char *err, ...)
 {
        va_list params;
@@ -83,6 +86,7 @@ static void logerror(const char *err, ...)
        va_end(params);
 }
 
+__attribute__((format (printf, 1, 2)))
 static void loginfo(const char *err, ...)
 {
        va_list params;
@@ -99,53 +103,6 @@ static void NORETURN daemon_die(const char *err, va_list params)
        exit(1);
 }
 
-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 *directory)
 {
        static char rpath[PATH_MAX];
@@ -155,7 +112,7 @@ static char *path_ok(char *directory)
 
        dir = directory;
 
-       if (avoid_alias(dir)) {
+       if (daemon_avoid_alias(dir)) {
                logerror("'%s': aliased", dir);
                return NULL;
        }
@@ -184,16 +141,14 @@ static char *path_ok(char *directory)
        }
        else if (interpolated_path && saw_extended_args) {
                struct strbuf expanded_path = STRBUF_INIT;
-               struct strbuf_expand_dict_entry dict[] = {
-                       { "H", hostname },
-                       { "CH", canon_hostname },
-                       { "IP", ip_address },
-                       { "P", tcp_port },
-                       { "D", directory },
-                       { "%", "%" },
-                       { NULL }
-               };
-
+               struct strbuf_expand_dict_entry dict[6];
+
+               dict[0].placeholder = "H"; dict[0].value = hostname;
+               dict[1].placeholder = "CH"; dict[1].value = canon_hostname;
+               dict[2].placeholder = "IP"; dict[2].value = ip_address;
+               dict[3].placeholder = "P"; dict[3].value = tcp_port;
+               dict[4].placeholder = "D"; dict[4].value = directory;
+               dict[5].placeholder = NULL; dict[5].value = NULL;
                if (*dir != '/') {
                        /* Allow only absolute */
                        logerror("'%s': Non-absolute path denied (interpolated-path active)", dir);
@@ -343,28 +298,68 @@ static int run_service(char *dir, struct daemon_service *service)
        return service->fn();
 }
 
+static void copy_to_log(int fd)
+{
+       struct strbuf line = STRBUF_INIT;
+       FILE *fp;
+
+       fp = fdopen(fd, "r");
+       if (fp == NULL) {
+               logerror("fdopen of error channel failed");
+               close(fd);
+               return;
+       }
+
+       while (strbuf_getline(&line, fp, '\n') != EOF) {
+               logerror("%s", line.buf);
+               strbuf_setlen(&line, 0);
+       }
+
+       strbuf_release(&line);
+       fclose(fp);
+}
+
+static int run_service_command(const char **argv)
+{
+       struct child_process cld;
+
+       memset(&cld, 0, sizeof(cld));
+       cld.argv = argv;
+       cld.git_cmd = 1;
+       cld.err = -1;
+       if (start_command(&cld))
+               return -1;
+
+       close(0);
+       close(1);
+
+       copy_to_log(cld.err);
+
+       return finish_command(&cld);
+}
+
 static int upload_pack(void)
 {
        /* Timeout as string */
        char timeout_buf[64];
+       const char *argv[] = { "upload-pack", "--strict", NULL, ".", NULL };
 
-       snprintf(timeout_buf, sizeof timeout_buf, "--timeout=%u", timeout);
+       argv[2] = timeout_buf;
 
-       /* git-upload-pack only ever reads stuff, so this is safe */
-       execl_git_cmd("upload-pack", "--strict", timeout_buf, ".", NULL);
-       return -1;
+       snprintf(timeout_buf, sizeof timeout_buf, "--timeout=%u", timeout);
+       return run_service_command(argv);
 }
 
 static int upload_archive(void)
 {
-       execl_git_cmd("upload-archive", ".", NULL);
-       return -1;
+       static const char *argv[] = { "upload-archive", ".", NULL };
+       return run_service_command(argv);
 }
 
 static int receive_pack(void)
 {
-       execl_git_cmd("receive-pack", ".", NULL);
-       return -1;
+       static const char *argv[] = { "receive-pack", ".", NULL };
+       return run_service_command(argv);
 }
 
 static struct daemon_service daemon_service[] = {
@@ -405,27 +400,53 @@ static char *xstrdup_tolower(const char *str)
        return dup;
 }
 
+static void parse_host_and_port(char *hostport, char **host,
+       char **port)
+{
+       if (*hostport == '[') {
+               char *end;
+
+               end = strchr(hostport, ']');
+               if (!end)
+                       die("Invalid request ('[' without ']')");
+               *end = '\0';
+               *host = hostport + 1;
+               if (!end[1])
+                       *port = NULL;
+               else if (end[1] == ':')
+                       *port = end + 2;
+               else
+                       die("Garbage after end of host part");
+       } else {
+               *host = hostport;
+               *port = strrchr(hostport, ':');
+               if (*port) {
+                       **port = '\0';
+                       ++*port;
+               }
+       }
+}
+
 /*
- * Separate the "extra args" information as supplied by the client connection.
+ * Read the host as supplied by the client connection.
  */
-static void parse_extra_args(char *extra_args, int buflen)
+static void parse_host_arg(char *extra_args, int buflen)
 {
        char *val;
        int vallen;
        char *end = extra_args + buflen;
 
-       while (extra_args < end && *extra_args) {
+       if (extra_args < end && *extra_args) {
                saw_extended_args = 1;
                if (strncasecmp("host=", extra_args, 5) == 0) {
                        val = extra_args + 5;
                        vallen = strlen(val) + 1;
                        if (*val) {
                                /* Split <host>:<port> at colon. */
-                               char *host = val;
-                               char *port = strrchr(host, ':');
+                               char *host;
+                               char *port;
+                               parse_host_and_port(val, &host, &port);
                                if (port) {
-                                       *port = 0;
-                                       port++;
                                        free(tcp_port);
                                        tcp_port = xstrdup(port);
                                }
@@ -436,6 +457,8 @@ static void parse_extra_args(char *extra_args, int buflen)
                        /* On to the next one */
                        extra_args = val + vallen;
                }
+               if (extra_args < end && *extra_args)
+                       die("Invalid request");
        }
 
        /*
@@ -451,7 +474,7 @@ static void parse_extra_args(char *extra_args, int buflen)
                memset(&hints, 0, sizeof(hints));
                hints.ai_flags = AI_CANONNAME;
 
-               gai = getaddrinfo(hostname, 0, &hints, &ai);
+               gai = getaddrinfo(hostname, NULL, &hints, &ai);
                if (!gai) {
                        struct sockaddr_in *sin_addr = (void *)ai->ai_addr;
 
@@ -545,7 +568,7 @@ static int execute(struct sockaddr *addr)
        hostname = canon_hostname = ip_address = tcp_port = NULL;
 
        if (len != pktlen)
-               parse_extra_args(line + len + 1, pktlen - len - 1);
+               parse_host_arg(line + len + 1, pktlen - len - 1);
 
        for (i = 0; i < ARRAY_SIZE(daemon_service); i++) {
                struct daemon_service *s = &(daemon_service[i]);
@@ -565,6 +588,27 @@ static int execute(struct sockaddr *addr)
        return -1;
 }
 
+static int addrcmp(const struct sockaddr_storage *s1,
+    const struct sockaddr_storage *s2)
+{
+       const struct sockaddr *sa1 = (const struct sockaddr*) s1;
+       const struct sockaddr *sa2 = (const struct sockaddr*) s2;
+
+       if (sa1->sa_family != sa2->sa_family)
+               return sa1->sa_family - sa2->sa_family;
+       if (sa1->sa_family == AF_INET)
+               return memcmp(&((struct sockaddr_in *)s1)->sin_addr,
+                   &((struct sockaddr_in *)s2)->sin_addr,
+                   sizeof(struct in_addr));
+#ifndef NO_IPV6
+       if (sa1->sa_family == AF_INET6)
+               return memcmp(&((struct sockaddr_in6 *)s1)->sin6_addr,
+                   &((struct sockaddr_in6 *)s2)->sin6_addr,
+                   sizeof(struct in6_addr));
+#endif
+       return 0;
+}
+
 static int max_connections = 32;
 
 static unsigned int live_children;
@@ -579,17 +623,12 @@ static void add_child(pid_t pid, struct sockaddr *addr, int addrlen)
 {
        struct child *newborn, **cradle;
 
-       /*
-        * This must be xcalloc() -- we'll compare the whole sockaddr_storage
-        * but individual address may be shorter.
-        */
        newborn = xcalloc(1, sizeof(*newborn));
        live_children++;
        newborn->pid = pid;
        memcpy(&newborn->address, addr, addrlen);
        for (cradle = &firstborn; *cradle; cradle = &(*cradle)->next)
-               if (!memcmp(&(*cradle)->address, &newborn->address,
-                           sizeof(newborn->address)))
+               if (!addrcmp(&(*cradle)->address, &newborn->address))
                        break;
        newborn->next = *cradle;
        *cradle = newborn;
@@ -622,8 +661,7 @@ static void kill_some_child(void)
                return;
 
        for (; (next = blanket->next); blanket = next)
-               if (!memcmp(&blanket->address, &next->address,
-                           sizeof(next->address))) {
+               if (!addrcmp(&blanket->address, &next->address)) {
                        kill(blanket->pid, SIGTERM);
                        break;
                }
@@ -860,7 +898,7 @@ static int service_loop(int socknum, int *socklist)
                                        case ECONNABORTED:
                                                continue;
                                        default:
-                                               die("accept returned %s", strerror(errno));
+                                               die_errno("accept returned");
                                        }
                                }
                                handle(incoming, (struct sockaddr *)&ss, sslen);
@@ -876,7 +914,7 @@ static void sanitize_stdfds(void)
        while (fd != -1 && fd < 2)
                fd = dup(fd);
        if (fd == -1)
-               die("open /dev/null or dup failed: %s", strerror(errno));
+               die_errno("open /dev/null or dup failed");
        if (fd > 2)
                close(fd);
 }
@@ -887,12 +925,12 @@ static void daemonize(void)
                case 0:
                        break;
                case -1:
-                       die("fork failed: %s", strerror(errno));
+                       die_errno("fork failed");
                default:
                        exit(0);
        }
        if (setsid() == -1)
-               die("setsid failed: %s", strerror(errno));
+               die_errno("setsid failed");
        close(0);
        close(1);
        close(2);
@@ -903,9 +941,9 @@ static void store_pid(const char *path)
 {
        FILE *f = fopen(path, "w");
        if (!f)
-               die("cannot open pid file %s: %s", path, strerror(errno));
+               die_errno("cannot open pid file '%s'", path);
        if (fprintf(f, "%"PRIuMAX"\n", (uintmax_t) getpid()) < 0 || fclose(f) != 0)
-               die("failed to write pid file %s: %s", path, strerror(errno));
+               die_errno("failed to write pid file '%s'", path);
 }
 
 static int serve(char *listen_addr, int listen_port, struct passwd *pass, gid_t gid)
@@ -1105,8 +1143,7 @@ int main(int argc, char **argv)
                socklen_t slen = sizeof(ss);
 
                if (!freopen("/dev/null", "w", stderr))
-                       die("failed to redirect stderr to /dev/null: %s",
-                           strerror(errno));
+                       die_errno("failed to redirect stderr to /dev/null");
 
                if (getpeername(0, peer, &slen))
                        peer = NULL;
diff --git a/date.c b/date.c
index 409a17d46424083b62f21e7e278393cee7bfa080..00f9eb5d0b9730107a8e08e92eb04d4cc9233595 100644 (file)
--- a/date.c
+++ b/date.c
@@ -9,7 +9,7 @@
 /*
  * This is like mktime, but without normalization of tm_wday and tm_yday.
  */
-time_t tm_to_time_t(const struct tm *tm)
+static time_t tm_to_time_t(const struct tm *tm)
 {
        static const int mdays[] = {
            0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
@@ -24,6 +24,8 @@ time_t tm_to_time_t(const struct tm *tm)
                return -1;
        if (month < 2 || (year + 2) % 4)
                day--;
+       if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0)
+               return -1;
        return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL +
                tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec;
 }
@@ -84,6 +86,67 @@ static int local_tzoffset(unsigned long time)
        return offset * eastwest;
 }
 
+const char *show_date_relative(unsigned long time, int tz,
+                              const struct timeval *now,
+                              char *timebuf,
+                              size_t timebuf_size)
+{
+       unsigned long diff;
+       if (now->tv_sec < time)
+               return "in the future";
+       diff = now->tv_sec - time;
+       if (diff < 90) {
+               snprintf(timebuf, timebuf_size, "%lu seconds ago", diff);
+               return timebuf;
+       }
+       /* Turn it into minutes */
+       diff = (diff + 30) / 60;
+       if (diff < 90) {
+               snprintf(timebuf, timebuf_size, "%lu minutes ago", diff);
+               return timebuf;
+       }
+       /* Turn it into hours */
+       diff = (diff + 30) / 60;
+       if (diff < 36) {
+               snprintf(timebuf, timebuf_size, "%lu hours ago", diff);
+               return timebuf;
+       }
+       /* We deal with number of days from here on */
+       diff = (diff + 12) / 24;
+       if (diff < 14) {
+               snprintf(timebuf, timebuf_size, "%lu days ago", diff);
+               return timebuf;
+       }
+       /* Say weeks for the past 10 weeks or so */
+       if (diff < 70) {
+               snprintf(timebuf, timebuf_size, "%lu weeks ago", (diff + 3) / 7);
+               return timebuf;
+       }
+       /* Say months for the past 12 months or so */
+       if (diff < 365) {
+               snprintf(timebuf, timebuf_size, "%lu months ago", (diff + 15) / 30);
+               return timebuf;
+       }
+       /* Give years and months for 5 years or so */
+       if (diff < 1825) {
+               unsigned long years = diff / 365;
+               unsigned long months = (diff % 365 + 15) / 30;
+               int n;
+               n = snprintf(timebuf, timebuf_size, "%lu year%s",
+                               years, (years > 1 ? "s" : ""));
+               if (months)
+                       snprintf(timebuf + n, timebuf_size - n,
+                                       ", %lu month%s ago",
+                                       months, (months > 1 ? "s" : ""));
+               else
+                       snprintf(timebuf + n, timebuf_size - n, " ago");
+               return timebuf;
+       }
+       /* Otherwise, just years. Centuries is probably overkill. */
+       snprintf(timebuf, timebuf_size, "%lu years ago", (diff + 183) / 365);
+       return timebuf;
+}
+
 const char *show_date(unsigned long time, int tz, enum date_mode mode)
 {
        struct tm *tm;
@@ -95,63 +158,10 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)
        }
 
        if (mode == DATE_RELATIVE) {
-               unsigned long diff;
                struct timeval now;
                gettimeofday(&now, NULL);
-               if (now.tv_sec < time)
-                       return "in the future";
-               diff = now.tv_sec - time;
-               if (diff < 90) {
-                       snprintf(timebuf, sizeof(timebuf), "%lu seconds ago", diff);
-                       return timebuf;
-               }
-               /* Turn it into minutes */
-               diff = (diff + 30) / 60;
-               if (diff < 90) {
-                       snprintf(timebuf, sizeof(timebuf), "%lu minutes ago", diff);
-                       return timebuf;
-               }
-               /* Turn it into hours */
-               diff = (diff + 30) / 60;
-               if (diff < 36) {
-                       snprintf(timebuf, sizeof(timebuf), "%lu hours ago", diff);
-                       return timebuf;
-               }
-               /* We deal with number of days from here on */
-               diff = (diff + 12) / 24;
-               if (diff < 14) {
-                       snprintf(timebuf, sizeof(timebuf), "%lu days ago", diff);
-                       return timebuf;
-               }
-               /* Say weeks for the past 10 weeks or so */
-               if (diff < 70) {
-                       snprintf(timebuf, sizeof(timebuf), "%lu weeks ago", (diff + 3) / 7);
-                       return timebuf;
-               }
-               /* Say months for the past 12 months or so */
-               if (diff < 360) {
-                       snprintf(timebuf, sizeof(timebuf), "%lu months ago", (diff + 15) / 30);
-                       return timebuf;
-               }
-               /* Give years and months for 5 years or so */
-               if (diff < 1825) {
-                       unsigned long years = (diff + 183) / 365;
-                       unsigned long months = (diff % 365 + 15) / 30;
-                       int n;
-                       n = snprintf(timebuf, sizeof(timebuf), "%lu year%s",
-                                       years, (years > 1 ? "s" : ""));
-                       if (months)
-                               snprintf(timebuf + n, sizeof(timebuf) - n,
-                                       ", %lu month%s ago",
-                                       months, (months > 1 ? "s" : ""));
-                       else
-                               snprintf(timebuf + n, sizeof(timebuf) - n,
-                                       " ago");
-                       return timebuf;
-               }
-               /* Otherwise, just years. Centuries is probably overkill. */
-               snprintf(timebuf, sizeof(timebuf), "%lu years ago", (diff + 183) / 365);
-               return timebuf;
+               return show_date_relative(time, tz, &now,
+                                         timebuf, sizeof(timebuf));
        }
 
        if (mode == DATE_LOCAL)
@@ -219,6 +229,7 @@ static const struct {
 
        { "GMT",    0, 0, },    /* Greenwich Mean */
        { "UTC",    0, 0, },    /* Universal (Coordinated) */
+       { "Z",      0, 0, },    /* Zulu, alias for UTC */
 
        { "WET",    0, 0, },    /* Western European */
        { "BST",    0, 1, },    /* British Summer */
@@ -295,7 +306,7 @@ static int match_alpha(const char *date, struct tm *tm, int *offset)
 
        for (i = 0; i < ARRAY_SIZE(timezone_names); i++) {
                int match = match_string(date, timezone_names[i].name);
-               if (match >= 3) {
+               if (match >= 3 || match == strlen(timezone_names[i].name)) {
                        int off = timezone_names[i].offset;
 
                        /* This is bogus, but we like summer */
@@ -425,13 +436,19 @@ static int match_multi_number(unsigned long num, char c, const char *date, char
        return end - date;
 }
 
-/* Have we filled in any part of the time/date yet? */
+/*
+ * Have we filled in any part of the time/date yet?
+ * We just do a binary 'and' to see if the sign bit
+ * is set in all the values.
+ */
 static inline int nodate(struct tm *tm)
 {
-       return tm->tm_year < 0 &&
-               tm->tm_mon < 0 &&
-               tm->tm_mday < 0 &&
-               !(tm->tm_hour | tm->tm_min | tm->tm_sec);
+       return (tm->tm_year &
+               tm->tm_mon &
+               tm->tm_mday &
+               tm->tm_hour &
+               tm->tm_min &
+               tm->tm_sec) < 0;
 }
 
 /*
@@ -525,11 +542,8 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt
                }
        }
 
-       if (num > 0 && num < 32) {
-               tm->tm_mday = num;
-       } else if (num > 0 && num < 13) {
+       if (num > 0 && num < 13 && tm->tm_mon < 0)
                tm->tm_mon = num-1;
-       }
 
        return n;
 }
@@ -572,18 +586,27 @@ static int date_string(unsigned long date, int offset, char *buf, int len)
 
 /* 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)
+int parse_date_basic(const char *date, unsigned long *timestamp, int *offset)
 {
        struct tm tm;
-       int offset, tm_gmt;
-       time_t then;
+       int tm_gmt;
+       unsigned long dummy_timestamp;
+       int dummy_offset;
+
+       if (!timestamp)
+               timestamp = &dummy_timestamp;
+       if (!offset)
+               offset = &dummy_offset;
 
        memset(&tm, 0, sizeof(tm));
        tm.tm_year = -1;
        tm.tm_mon = -1;
        tm.tm_mday = -1;
        tm.tm_isdst = -1;
-       offset = -1;
+       tm.tm_hour = -1;
+       tm.tm_min = -1;
+       tm.tm_sec = -1;
+       *offset = -1;
        tm_gmt = 0;
 
        for (;;) {
@@ -595,11 +618,11 @@ int parse_date(const char *date, char *result, int maxlen)
                        break;
 
                if (isalpha(c))
-                       match = match_alpha(date, &tm, &offset);
+                       match = match_alpha(date, &tm, offset);
                else if (isdigit(c))
-                       match = match_digit(date, &tm, &offset, &tm_gmt);
+                       match = match_digit(date, &tm, offset, &tm_gmt);
                else if ((c == '-' || c == '+') && isdigit(date[1]))
-                       match = match_tz(date, &offset);
+                       match = match_tz(date, offset);
 
                if (!match) {
                        /* BAD CRAP */
@@ -610,16 +633,25 @@ int parse_date(const char *date, char *result, int maxlen)
        }
 
        /* mktime uses local timezone */
-       then = tm_to_time_t(&tm);
-       if (offset == -1)
-               offset = (then - mktime(&tm)) / 60;
+       *timestamp = tm_to_time_t(&tm);
+       if (*offset == -1)
+               *offset = ((time_t)*timestamp - mktime(&tm)) / 60;
 
-       if (then == -1)
+       if (*timestamp == -1)
                return -1;
 
        if (!tm_gmt)
-               then -= offset * 60;
-       return date_string(then, offset, result, maxlen);
+               *timestamp -= *offset * 60;
+       return 0; /* success */
+}
+
+int parse_date(const char *date, char *result, int maxlen)
+{
+       unsigned long timestamp;
+       int offset;
+       if (parse_date_basic(date, &timestamp, &offset))
+               return -1;
+       return date_string(timestamp, offset, result, maxlen);
 }
 
 enum date_mode parse_date_format(const char *format)
@@ -657,42 +689,64 @@ void datestamp(char *buf, int bufsize)
        date_string(now, offset, buf, bufsize);
 }
 
-static void update_tm(struct tm *tm, unsigned long sec)
+/*
+ * Relative time update (eg "2 days ago").  If we haven't set the time
+ * yet, we need to set it from current time.
+ */
+static unsigned long update_tm(struct tm *tm, struct tm *now, unsigned long sec)
 {
-       time_t n = mktime(tm) - sec;
+       time_t n;
+
+       if (tm->tm_mday < 0)
+               tm->tm_mday = now->tm_mday;
+       if (tm->tm_mon < 0)
+               tm->tm_mon = now->tm_mon;
+       if (tm->tm_year < 0) {
+               tm->tm_year = now->tm_year;
+               if (tm->tm_mon > now->tm_mon)
+                       tm->tm_year--;
+       }
+
+       n = mktime(tm) - sec;
        localtime_r(&n, tm);
+       return n;
+}
+
+static void date_now(struct tm *tm, struct tm *now, int *num)
+{
+       update_tm(tm, now, 0);
 }
 
-static void date_yesterday(struct tm *tm, int *num)
+static void date_yesterday(struct tm *tm, struct tm *now, int *num)
 {
-       update_tm(tm, 24*60*60);
+       update_tm(tm, now, 24*60*60);
 }
 
-static void date_time(struct tm *tm, int hour)
+static void date_time(struct tm *tm, struct tm *now, int hour)
 {
        if (tm->tm_hour < hour)
-               date_yesterday(tm, NULL);
+               date_yesterday(tm, now, NULL);
        tm->tm_hour = hour;
        tm->tm_min = 0;
        tm->tm_sec = 0;
 }
 
-static void date_midnight(struct tm *tm, int *num)
+static void date_midnight(struct tm *tm, struct tm *now, int *num)
 {
-       date_time(tm, 0);
+       date_time(tm, now, 0);
 }
 
-static void date_noon(struct tm *tm, int *num)
+static void date_noon(struct tm *tm, struct tm *now, int *num)
 {
-       date_time(tm, 12);
+       date_time(tm, now, 12);
 }
 
-static void date_tea(struct tm *tm, int *num)
+static void date_tea(struct tm *tm, struct tm *now, int *num)
 {
-       date_time(tm, 17);
+       date_time(tm, now, 17);
 }
 
-static void date_pm(struct tm *tm, int *num)
+static void date_pm(struct tm *tm, struct tm *now, int *num)
 {
        int hour, n = *num;
        *num = 0;
@@ -706,7 +760,7 @@ static void date_pm(struct tm *tm, int *num)
        tm->tm_hour = (hour % 12) + 12;
 }
 
-static void date_am(struct tm *tm, int *num)
+static void date_am(struct tm *tm, struct tm *now, int *num)
 {
        int hour, n = *num;
        *num = 0;
@@ -720,7 +774,7 @@ static void date_am(struct tm *tm, int *num)
        tm->tm_hour = (hour % 12);
 }
 
-static void date_never(struct tm *tm, int *num)
+static void date_never(struct tm *tm, struct tm *now, int *num)
 {
        time_t n = 0;
        localtime_r(&n, tm);
@@ -728,7 +782,7 @@ static void date_never(struct tm *tm, int *num)
 
 static const struct special {
        const char *name;
-       void (*fn)(struct tm *, int *);
+       void (*fn)(struct tm *, struct tm *, int *);
 } special[] = {
        { "yesterday", date_yesterday },
        { "noon", date_noon },
@@ -737,6 +791,7 @@ static const struct special {
        { "PM", date_pm },
        { "AM", date_am },
        { "never", date_never },
+       { "now", date_now },
        { NULL }
 };
 
@@ -757,7 +812,7 @@ static const struct typelen {
        { NULL }
 };
 
-static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
+static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num, int *touched)
 {
        const struct typelen *tl;
        const struct special *s;
@@ -771,6 +826,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
                int match = match_string(date, month_names[i]);
                if (match >= 3) {
                        tm->tm_mon = i;
+                       *touched = 1;
                        return end;
                }
        }
@@ -778,7 +834,8 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
        for (s = special; s->name; s++) {
                int len = strlen(s->name);
                if (match_string(date, s->name) == len) {
-                       s->fn(tm, num);
+                       s->fn(tm, now, num);
+                       *touched = 1;
                        return end;
                }
        }
@@ -788,11 +845,14 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
                        int len = strlen(number_name[i]);
                        if (match_string(date, number_name[i]) == len) {
                                *num = i;
+                               *touched = 1;
                                return end;
                        }
                }
-               if (match_string(date, "last") == 4)
+               if (match_string(date, "last") == 4) {
                        *num = 1;
+                       *touched = 1;
+               }
                return end;
        }
 
@@ -800,8 +860,9 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
        while (tl->type) {
                int len = strlen(tl->type);
                if (match_string(date, tl->type) >= len-1) {
-                       update_tm(tm, tl->length * *num);
+                       update_tm(tm, now, tl->length * *num);
                        *num = 0;
+                       *touched = 1;
                        return end;
                }
                tl++;
@@ -818,25 +879,31 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
                                n++;
                        diff += 7*n;
 
-                       update_tm(tm, diff * 24 * 60 * 60);
+                       update_tm(tm, now, diff * 24 * 60 * 60);
+                       *touched = 1;
                        return end;
                }
        }
 
        if (match_string(date, "months") >= 5) {
-               int n = tm->tm_mon - *num;
+               int n;
+               update_tm(tm, now, 0); /* fill in date fields if needed */
+               n = tm->tm_mon - *num;
                *num = 0;
                while (n < 0) {
                        n += 12;
                        tm->tm_year--;
                }
                tm->tm_mon = n;
+               *touched = 1;
                return end;
        }
 
        if (match_string(date, "years") >= 4) {
+               update_tm(tm, now, 0); /* fill in date fields if needed */
                tm->tm_year -= *num;
                *num = 0;
+               *touched = 1;
                return end;
        }
 
@@ -866,36 +933,95 @@ static const char *approxidate_digit(const char *date, struct tm *tm, int *num)
        return end;
 }
 
-unsigned long approxidate(const char *date)
+/*
+ * Do we have a pending number at the end, or when
+ * we see a new one? Let's assume it's a month day,
+ * as in "Dec 6, 1992"
+ */
+static void pending_number(struct tm *tm, int *num)
+{
+       int number = *num;
+
+       if (number) {
+               *num = 0;
+               if (tm->tm_mday < 0 && number < 32)
+                       tm->tm_mday = number;
+               else if (tm->tm_mon < 0 && number < 13)
+                       tm->tm_mon = number-1;
+               else if (tm->tm_year < 0) {
+                       if (number > 1969 && number < 2100)
+                               tm->tm_year = number - 1900;
+                       else if (number > 69 && number < 100)
+                               tm->tm_year = number;
+                       else if (number < 38)
+                               tm->tm_year = 100 + number;
+                       /* We screw up for number = 00 ? */
+               }
+       }
+}
+
+static unsigned long approxidate_str(const char *date,
+                                    const struct timeval *tv,
+                                    int *error_ret)
 {
        int number = 0;
+       int touched = 0;
        struct tm tm, now;
-       struct timeval tv;
        time_t time_sec;
-       char buffer[50];
 
-       if (parse_date(date, buffer, sizeof(buffer)) > 0)
-               return strtoul(buffer, NULL, 10);
-
-       gettimeofday(&tv, NULL);
-       time_sec = tv.tv_sec;
+       time_sec = tv->tv_sec;
        localtime_r(&time_sec, &tm);
        now = tm;
+
+       tm.tm_year = -1;
+       tm.tm_mon = -1;
+       tm.tm_mday = -1;
+
        for (;;) {
                unsigned char c = *date;
                if (!c)
                        break;
                date++;
                if (isdigit(c)) {
+                       pending_number(&tm, &number);
                        date = approxidate_digit(date-1, &tm, &number);
+                       touched = 1;
                        continue;
                }
                if (isalpha(c))
-                       date = approxidate_alpha(date-1, &tm, &number);
+                       date = approxidate_alpha(date-1, &tm, &now, &number, &touched);
        }
-       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);
+       pending_number(&tm, &number);
+       if (!touched)
+               *error_ret = 1;
+       return update_tm(&tm, &now, 0);
+}
+
+unsigned long approxidate_relative(const char *date, const struct timeval *tv)
+{
+       unsigned long timestamp;
+       int offset;
+       int errors = 0;
+
+       if (!parse_date_basic(date, &timestamp, &offset))
+               return timestamp;
+       return approxidate_str(date, tv, &errors);
+}
+
+unsigned long approxidate_careful(const char *date, int *error_ret)
+{
+       struct timeval tv;
+       unsigned long timestamp;
+       int offset;
+       int dummy = 0;
+       if (!error_ret)
+               error_ret = &dummy;
+
+       if (!parse_date_basic(date, &timestamp, &offset)) {
+               *error_ret = 0;
+               return timestamp;
+       }
+
+       gettimeofday(&tv, NULL);
+       return approxidate_str(date, &tv, error_ret);
 }
index 82d9e221eabab53acc418d0db6327e480836a5ed..2f8a63e38881587fe29fcb72a5272ef54b9efa6e 100644 (file)
@@ -8,7 +8,9 @@
 
 static unsigned int hash_obj(const struct object *obj, unsigned int n)
 {
-       unsigned int hash = *(unsigned int *)obj->sha1;
+       unsigned int hash;
+
+       memcpy(&hash, obj->sha1, sizeof(unsigned int));
        return hash % n;
 }
 
@@ -16,7 +18,7 @@ static void *insert_decoration(struct decoration *n, const struct object *base,
 {
        int size = n->size;
        struct object_decoration *hash = n->hash;
-       int j = hash_obj(base, size);
+       unsigned int j = hash_obj(base, size);
 
        while (hash[j].base) {
                if (hash[j].base == base) {
@@ -68,7 +70,7 @@ void *add_decoration(struct decoration *n, const struct object *obj,
 /* Lookup a decoration pointer */
 void *lookup_decoration(struct decoration *n, const struct object *obj)
 {
-       int j;
+       unsigned int j;
 
        /* nothing to lookup */
        if (!n->size)
diff --git a/delta.h b/delta.h
index 40ccf5a1e95f62d840a006274f7024fa43208b1c..b9d333dd5a1c64ab35159ed608cf942951504f84 100644 (file)
--- a/delta.h
+++ b/delta.h
@@ -90,12 +90,11 @@ 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;
+       unsigned long cmd, size = 0;
        int i = 0;
        do {
                cmd = *data++;
-               size |= (cmd & ~0x80) << i;
+               size |= (cmd & 0x7f) << i;
                i += 7;
        } while (cmd & 0x80 && data < top);
        *datap = data;
index a4e28df714b4834e5efe42fa3abb647711913d71..93385e12baa0d90ae475bd02500edf5ddcce320c 100644 (file)
@@ -4,7 +4,7 @@
  * This code was greatly inspired by parts of LibXDiff from Davide Libenzi
  * http://www.xmailserver.org/xdiff-lib.html
  *
- * Rewritten for GIT by Nicolas Pitre <nico@cam.org>, (C) 2005-2007
+ * Rewritten for GIT by Nicolas Pitre <nico@fluxnic.net>, (C) 2005-2007
  *
  * 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
@@ -146,7 +146,14 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize)
        /* Determine index hash size.  Note that indexing skips the
           first byte to allow for optimizing the Rabin's polynomial
           initialization in create_delta(). */
-       entries = (bufsize - 1)  / RABIN_WINDOW;
+       entries = (bufsize - 1) / RABIN_WINDOW;
+       if (bufsize >= 0xffffffffUL) {
+               /*
+                * Current delta format can't encode offsets into
+                * reference buffer with more than 32 bits.
+                */
+               entries = 0xfffffffeU / RABIN_WINDOW;
+       }
        hsize = entries / 4;
        for (i = 4; (1u << i) < hsize && i < 31; i++);
        hsize = 1 << i;
index a310fb2ad08cfb5fac01f5519e8b95b2338b07db..392ce2bef05746cea7922d39da67bf25d1d3d192 100644 (file)
@@ -10,6 +10,7 @@
 #include "cache-tree.h"
 #include "unpack-trees.h"
 #include "refs.h"
+#include "submodule.h"
 
 /*
  * diff-files
@@ -54,6 +55,33 @@ static int check_removed(const struct cache_entry *ce, struct stat *st)
        return 0;
 }
 
+/*
+ * Has a file changed or has a submodule new commits or a dirty work tree?
+ *
+ * Return 1 when changes are detected, 0 otherwise. If the DIRTY_SUBMODULES
+ * option is set, the caller does not only want to know if a submodule is
+ * modified at all but wants to know all the conditions that are met (new
+ * commits, untracked content and/or modified content).
+ */
+static int match_stat_with_submodule(struct diff_options *diffopt,
+                                     struct cache_entry *ce, struct stat *st,
+                                     unsigned ce_option, unsigned *dirty_submodule)
+{
+       int changed = ce_match_stat(ce, st, ce_option);
+       if (S_ISGITLINK(ce->ce_mode)) {
+               unsigned orig_flags = diffopt->flags;
+               if (!DIFF_OPT_TST(diffopt, OVERRIDE_SUBMODULE_CONFIG))
+                       set_diffopt_flags_from_submodule_config(diffopt, ce->name);
+               if (DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES))
+                       changed = 0;
+               else if (!DIFF_OPT_TST(diffopt, IGNORE_DIRTY_SUBMODULES)
+                   && (!changed || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES)))
+                       *dirty_submodule = is_submodule_modified(ce->name, DIFF_OPT_TST(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES));
+               diffopt->flags = orig_flags;
+       }
+       return changed;
+}
+
 int run_diff_files(struct rev_info *revs, unsigned int option)
 {
        int entries, i;
@@ -72,8 +100,9 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                unsigned int oldmode, newmode;
                struct cache_entry *ce = active_cache[i];
                int changed;
+               unsigned dirty_submodule = 0;
 
-               if (DIFF_OPT_TST(&revs->diffopt, QUIET) &&
+               if (DIFF_OPT_TST(&revs->diffopt, QUICK) &&
                        DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES))
                        break;
 
@@ -159,10 +188,11 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                                continue;
                }
 
-               if (ce_uptodate(ce))
+               if (ce_uptodate(ce) || ce_skip_worktree(ce))
                        continue;
 
-               changed = check_removed(ce, &st);
+               /* If CE_VALID is set, don't look at workdir for file removal */
+               changed = (ce->ce_flags & CE_VALID) ? 0 : check_removed(ce, &st);
                if (changed) {
                        if (changed < 0) {
                                perror(ce->name);
@@ -171,11 +201,12 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                        if (silent_on_removed)
                                continue;
                        diff_addremove(&revs->diffopt, '-', ce->ce_mode,
-                                      ce->sha1, ce->name);
+                                      ce->sha1, ce->name, 0);
                        continue;
                }
-               changed = ce_match_stat(ce, &st, ce_option);
-               if (!changed) {
+               changed = match_stat_with_submodule(&revs->diffopt, ce, &st,
+                                                   ce_option, &dirty_submodule);
+               if (!changed && !dirty_submodule) {
                        ce_mark_uptodate(ce);
                        if (!DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER))
                                continue;
@@ -184,7 +215,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                newmode = ce_mode_from_stat(ce, st.st_mode);
                diff_change(&revs->diffopt, oldmode, newmode,
                            ce->sha1, (changed ? null_sha1 : ce->sha1),
-                           ce->name);
+                           ce->name, 0, dirty_submodule);
 
        }
        diffcore_std(&revs->diffopt);
@@ -200,21 +231,23 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
 static void diff_index_show_file(struct rev_info *revs,
                                 const char *prefix,
                                 struct cache_entry *ce,
-                                const unsigned char *sha1, unsigned int mode)
+                                const unsigned char *sha1, unsigned int mode,
+                                unsigned dirty_submodule)
 {
        diff_addremove(&revs->diffopt, prefix[0], mode,
-                      sha1, ce->name);
+                      sha1, ce->name, dirty_submodule);
 }
 
 static int get_stat_data(struct cache_entry *ce,
                         const unsigned char **sha1p,
                         unsigned int *modep,
-                        int cached, int match_missing)
+                        int cached, int match_missing,
+                        unsigned *dirty_submodule, struct diff_options *diffopt)
 {
        const unsigned char *sha1 = ce->sha1;
        unsigned int mode = ce->ce_mode;
 
-       if (!cached) {
+       if (!cached && !ce_uptodate(ce)) {
                int changed;
                struct stat st;
                changed = check_removed(ce, &st);
@@ -228,7 +261,8 @@ static int get_stat_data(struct cache_entry *ce,
                        }
                        return -1;
                }
-               changed = ce_match_stat(ce, &st, 0);
+               changed = match_stat_with_submodule(diffopt, ce, &st,
+                                                   0, dirty_submodule);
                if (changed) {
                        mode = ce_mode_from_stat(ce, st.st_mode);
                        sha1 = null_sha1;
@@ -246,15 +280,17 @@ static void show_new_file(struct rev_info *revs,
 {
        const unsigned char *sha1;
        unsigned int mode;
+       unsigned dirty_submodule = 0;
 
        /*
         * 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)
+       if (get_stat_data(new, &sha1, &mode, cached, match_missing,
+           &dirty_submodule, &revs->diffopt) < 0)
                return;
 
-       diff_index_show_file(revs, "+", new, sha1, mode);
+       diff_index_show_file(revs, "+", new, sha1, mode, dirty_submodule);
 }
 
 static int show_modified(struct rev_info *revs,
@@ -265,11 +301,13 @@ static int show_modified(struct rev_info *revs,
 {
        unsigned int mode, oldmode;
        const unsigned char *sha1;
+       unsigned dirty_submodule = 0;
 
-       if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) {
+       if (get_stat_data(new, &sha1, &mode, cached, match_missing,
+                         &dirty_submodule, &revs->diffopt) < 0) {
                if (report_missing)
                        diff_index_show_file(revs, "-", old,
-                                            old->sha1, old->ce_mode);
+                                            old->sha1, old->ce_mode, 0);
                return -1;
        }
 
@@ -299,31 +337,15 @@ static int show_modified(struct rev_info *revs,
        }
 
        oldmode = old->ce_mode;
-       if (mode == oldmode && !hashcmp(sha1, old->sha1) &&
+       if (mode == oldmode && !hashcmp(sha1, old->sha1) && !dirty_submodule &&
            !DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER))
                return 0;
 
        diff_change(&revs->diffopt, oldmode, mode,
-                   old->sha1, sha1, old->name);
+                   old->sha1, sha1, old->name, 0, dirty_submodule);
        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 |= CE_STAGEMASK;
-       }
-}
-
 /*
  * This gets a mix of an existing index and a tree, one pathname entry
  * at a time. The index entry may be a single stage-0 one, but it could
@@ -337,6 +359,9 @@ static void do_oneway_diff(struct unpack_trees_options *o,
        struct rev_info *revs = o->unpack_data;
        int match_missing, cached;
 
+       /* if the entry is not checked out, don't examine work tree */
+       cached = o->index_only ||
+               (idx && ((idx->ce_flags & CE_VALID) || ce_skip_worktree(idx)));
        /*
         * Backward compatibility wart - "diff-index -m" does
         * not mean "do not ignore merges", but "match_missing".
@@ -344,12 +369,11 @@ static void do_oneway_diff(struct unpack_trees_options *o,
         * But with the revision flag parsing, that's found in
         * "!revs->ignore_merges".
         */
-       cached = o->index_only;
        match_missing = !revs->ignore_merges;
 
        if (cached && idx && ce_stage(idx)) {
-               if (tree)
-                       diff_unmerge(&revs->diffopt, idx->name, idx->ce_mode, idx->sha1);
+               diff_unmerge(&revs->diffopt, idx->name, idx->ce_mode,
+                            idx->sha1);
                return;
        }
 
@@ -365,7 +389,7 @@ static void do_oneway_diff(struct unpack_trees_options *o,
         * Something removed from the tree?
         */
        if (!idx) {
-               diff_index_show_file(revs, "-", tree, tree->sha1, tree->ce_mode);
+               diff_index_show_file(revs, "-", tree, tree->sha1, tree->ce_mode, 0);
                return;
        }
 
@@ -373,21 +397,6 @@ static void do_oneway_diff(struct unpack_trees_options *o,
        show_modified(revs, tree, idx, 1, cached, match_missing);
 }
 
-static inline void skip_same_name(struct cache_entry *ce, struct unpack_trees_options *o)
-{
-       int len = ce_namelen(ce);
-       const struct index_state *index = o->src_index;
-
-       while (o->pos < index->cache_nr) {
-               struct cache_entry *next = index->cache[o->pos];
-               if (len != ce_namelen(next))
-                       break;
-               if (memcmp(ce->name, next->name, len))
-                       break;
-               o->pos++;
-       }
-}
-
 /*
  * The unpack_trees() interface is designed for merging, so
  * the different source entries are designed primarily for
@@ -397,7 +406,7 @@ static inline void skip_same_name(struct cache_entry *ce, struct unpack_trees_op
  * For diffing, the index is more important, and we only have a
  * single tree.
  *
- * We're supposed to return how many index entries we want to skip.
+ * We're supposed to advance o->pos to skip what we have already processed.
  *
  * This wrapper makes it all more readable, and takes care of all
  * the fairly complex unpack_trees() semantic requirements, including
@@ -409,9 +418,6 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o)
        struct cache_entry *tree = src[1];
        struct rev_info *revs = o->unpack_data;
 
-       if (idx && ce_stage(idx))
-               skip_same_name(idx, o);
-
        /*
         * Unpack-trees generates a DF/conflict entry if
         * there was a directory in the index and a tree
@@ -435,8 +441,6 @@ int run_diff_index(struct rev_info *revs, int cached)
        struct unpack_trees_options opts;
        struct tree_desc t;
 
-       mark_merge_entries();
-
        ent = revs->pending.objects[0].item;
        tree_name = revs->pending.objects[0].name;
        tree = parse_tree_indirect(ent->sha1);
@@ -446,6 +450,8 @@ int run_diff_index(struct rev_info *revs, int cached)
        memset(&opts, 0, sizeof(opts));
        opts.head_idx = 1;
        opts.index_only = cached;
+       opts.diff_index_cached = (cached &&
+                                 !DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER));
        opts.merge = 1;
        opts.fn = oneway_diff;
        opts.unpack_data = revs;
@@ -457,6 +463,7 @@ int run_diff_index(struct rev_info *revs, int cached)
                exit(128);
 
        diff_set_mnemonic_prefix(&revs->diffopt, "c/", cached ? "i/" : "w/");
+       diffcore_fix_diff_index(&revs->diffopt);
        diffcore_std(&revs->diffopt);
        diff_flush(&revs->diffopt);
        return 0;
@@ -502,6 +509,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
        memset(&opts, 0, sizeof(opts));
        opts.head_idx = 1;
        opts.index_only = 1;
+       opts.diff_index_cached = !DIFF_OPT_TST(opt, FIND_COPIES_HARDER);
        opts.merge = 1;
        opts.fn = oneway_diff;
        opts.unpack_data = &revs;
@@ -517,10 +525,13 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
 int index_differs_from(const char *def, int diff_flags)
 {
        struct rev_info rev;
+       struct setup_revision_opt opt;
 
        init_revisions(&rev, NULL);
-       setup_revisions(0, NULL, &rev, def);
-       DIFF_OPT_SET(&rev.diffopt, QUIET);
+       memset(&opt, 0, sizeof(opt));
+       opt.def = def;
+       setup_revisions(0, NULL, &rev, &opt);
+       DIFF_OPT_SET(&rev.diffopt, QUICK);
        DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
        rev.diffopt.flags |= diff_flags;
        run_diff_index(&rev, 1);
index 4ebc1dbd87d9b90ad2aaa5c7d4601761e2d1d724..ce9e783407437bb1e0efc6d5bc2392af26da5a41 100644 (file)
@@ -26,7 +26,7 @@ static int read_directory(const char *path, struct string_list *list)
 
        while ((e = readdir(dir)))
                if (strcmp(".", e->d_name) && strcmp("..", e->d_name))
-                       string_list_insert(e->d_name, list);
+                       string_list_insert(list, e->d_name);
 
        closedir(dir);
        return 0;
@@ -64,7 +64,8 @@ static int queue_diff(struct diff_options *o,
 
        if (S_ISDIR(mode1) || S_ISDIR(mode2)) {
                char buffer1[PATH_MAX], buffer2[PATH_MAX];
-               struct string_list p1 = {NULL, 0, 0, 1}, p2 = {NULL, 0, 0, 1};
+               struct string_list p1 = STRING_LIST_INIT_DUP;
+               struct string_list p2 = STRING_LIST_INIT_DUP;
                int len1 = 0, len2 = 0, i1, i2, ret = 0;
 
                if (name1 && read_directory(name1, &p1))
@@ -150,16 +151,14 @@ static int queue_diff(struct diff_options *o,
 
 static int path_outside_repo(const char *path)
 {
-       /*
-        * We have already done setup_git_directory_gently() so we
-        * know we are inside a git work tree already.
-        */
        const char *work_tree;
        size_t len;
 
        if (!is_absolute_path(path))
                return 0;
        work_tree = get_git_work_tree();
+       if (!work_tree)
+               return 1;
        len = strlen(work_tree);
        if (strncmp(path, work_tree, len) ||
            (path[len] != '\0' && path[len] != '/'))
@@ -201,8 +200,8 @@ void diff_no_index(struct rev_info *revs,
                        return;
        }
        if (argc != i + 2)
-               die("git diff %s takes two paths",
-                   no_index ? "--no-index" : "[--no-index]");
+               usagef("git diff %s <path> <path>",
+                      no_index ? "--no-index" : "[--no-index]");
 
        diff_setup(&revs->diffopt);
        for (i = 1; i < argc - 2; ) {
diff --git a/diff.c b/diff.c
index 363dcb9613f5f57a2ae31ea6f803caf44954475a..9a5c77c13ff3406a85e8a94f309a639529f2d88b 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -13,6 +13,8 @@
 #include "utf8.h"
 #include "userdiff.h"
 #include "sigchain.h"
+#include "submodule.h"
+#include "ll-merge.h"
 
 #ifdef NO_FAST_WORKING_DIRECTORY
 #define FAST_WORKING_DIRECTORY 0
@@ -28,6 +30,8 @@ static const char *diff_word_regex_cfg;
 static const char *external_diff_cmd_cfg;
 int diff_auto_refresh_index = 1;
 static int diff_mnemonic_prefix;
+static int diff_no_prefix;
+static struct diff_options default_diff_options;
 
 static char diff_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_RESET,
@@ -38,11 +42,9 @@ static char diff_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_GREEN,        /* NEW */
        GIT_COLOR_YELLOW,       /* COMMIT */
        GIT_COLOR_BG_RED,       /* WHITESPACE */
+       GIT_COLOR_NORMAL,       /* FUNCINFO */
 };
 
-static void diff_filespec_load_driver(struct diff_filespec *one);
-static char *run_textconv(const char *, struct diff_filespec *, size_t *);
-
 static int parse_diff_color_slot(const char *var, int ofs)
 {
        if (!strcasecmp(var+ofs, "plain"))
@@ -59,7 +61,9 @@ static int parse_diff_color_slot(const char *var, int ofs)
                return DIFF_COMMIT;
        if (!strcasecmp(var+ofs, "whitespace"))
                return DIFF_WHITESPACE;
-       die("bad config variable '%s'", var);
+       if (!strcasecmp(var+ofs, "func"))
+               return DIFF_FUNCINFO;
+       return -1;
 }
 
 static int git_config_rename(const char *var, const char *value)
@@ -95,11 +99,18 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
                diff_mnemonic_prefix = git_config_bool(var, value);
                return 0;
        }
+       if (!strcmp(var, "diff.noprefix")) {
+               diff_no_prefix = git_config_bool(var, value);
+               return 0;
+       }
        if (!strcmp(var, "diff.external"))
                return git_config_string(&external_diff_cmd_cfg, var, value);
        if (!strcmp(var, "diff.wordregex"))
                return git_config_string(&diff_word_regex_cfg, var, value);
 
+       if (!strcmp(var, "diff.ignoresubmodules"))
+               handle_ignore_submodules_arg(&default_diff_options, value);
+
        return git_diff_basic_config(var, value, cb);
 }
 
@@ -118,6 +129,8 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
 
        if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
                int slot = parse_diff_color_slot(var, 11);
+               if (slot < 0)
+                       return 0;
                if (!value)
                        return config_error_nonbool(var);
                color_parse(value, var, diff_colors[slot]);
@@ -132,6 +145,9 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
+       if (!prefixcmp(var, "submodule."))
+               return parse_submodule_config_option(var, value);
+
        return git_color_default_config(var, value, cb);
 }
 
@@ -174,6 +190,243 @@ static struct diff_tempfile {
        char tmp_path[PATH_MAX];
 } diff_temp[2];
 
+typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
+
+struct emit_callback {
+       int color_diff;
+       unsigned ws_rule;
+       int blank_at_eof_in_preimage;
+       int blank_at_eof_in_postimage;
+       int lno_in_preimage;
+       int lno_in_postimage;
+       sane_truncate_fn truncate;
+       const char **label_path;
+       struct diff_words_data *diff_words;
+       struct diff_options *opt;
+       int *found_changesp;
+       struct strbuf *header;
+};
+
+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 int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
+{
+       if (!DIFF_FILE_VALID(one)) {
+               mf->ptr = (char *)""; /* 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;
+}
+
+static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule)
+{
+       char *ptr = mf->ptr;
+       long size = mf->size;
+       int cnt = 0;
+
+       if (!size)
+               return cnt;
+       ptr += size - 1; /* pointing at the very end */
+       if (*ptr != '\n')
+               ; /* incomplete line */
+       else
+               ptr--; /* skip the last LF */
+       while (mf->ptr < ptr) {
+               char *prev_eol;
+               for (prev_eol = ptr; mf->ptr <= prev_eol; prev_eol--)
+                       if (*prev_eol == '\n')
+                               break;
+               if (!ws_blank_line(prev_eol + 1, ptr - prev_eol, ws_rule))
+                       break;
+               cnt++;
+               ptr = prev_eol - 1;
+       }
+       return cnt;
+}
+
+static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,
+                              struct emit_callback *ecbdata)
+{
+       int l1, l2, at;
+       unsigned ws_rule = ecbdata->ws_rule;
+       l1 = count_trailing_blank(mf1, ws_rule);
+       l2 = count_trailing_blank(mf2, ws_rule);
+       if (l2 <= l1) {
+               ecbdata->blank_at_eof_in_preimage = 0;
+               ecbdata->blank_at_eof_in_postimage = 0;
+               return;
+       }
+       at = count_lines(mf1->ptr, mf1->size);
+       ecbdata->blank_at_eof_in_preimage = (at - l1) + 1;
+
+       at = count_lines(mf2->ptr, mf2->size);
+       ecbdata->blank_at_eof_in_postimage = (at - l2) + 1;
+}
+
+static void emit_line_0(struct diff_options *o, const char *set, const char *reset,
+                       int first, const char *line, int len)
+{
+       int has_trailing_newline, has_trailing_carriage_return;
+       int nofirst;
+       FILE *file = o->file;
+
+       if (o->output_prefix) {
+               struct strbuf *msg = NULL;
+               msg = o->output_prefix(o, o->output_prefix_data);
+               assert(msg);
+               fwrite(msg->buf, msg->len, 1, file);
+       }
+
+       if (len == 0) {
+               has_trailing_newline = (first == '\n');
+               has_trailing_carriage_return = (!has_trailing_newline &&
+                                               (first == '\r'));
+               nofirst = has_trailing_newline || has_trailing_carriage_return;
+       } else {
+               has_trailing_newline = (len > 0 && line[len-1] == '\n');
+               if (has_trailing_newline)
+                       len--;
+               has_trailing_carriage_return = (len > 0 && line[len-1] == '\r');
+               if (has_trailing_carriage_return)
+                       len--;
+               nofirst = 0;
+       }
+
+       if (len || !nofirst) {
+               fputs(set, file);
+               if (!nofirst)
+                       fputc(first, file);
+               fwrite(line, len, 1, file);
+               fputs(reset, file);
+       }
+       if (has_trailing_carriage_return)
+               fputc('\r', file);
+       if (has_trailing_newline)
+               fputc('\n', file);
+}
+
+static void emit_line(struct diff_options *o, const char *set, const char *reset,
+                     const char *line, int len)
+{
+       emit_line_0(o, set, reset, line[0], line+1, len-1);
+}
+
+static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len)
+{
+       if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) &&
+             ecbdata->blank_at_eof_in_preimage &&
+             ecbdata->blank_at_eof_in_postimage &&
+             ecbdata->blank_at_eof_in_preimage <= ecbdata->lno_in_preimage &&
+             ecbdata->blank_at_eof_in_postimage <= ecbdata->lno_in_postimage))
+               return 0;
+       return ws_blank_line(line, len, ecbdata->ws_rule);
+}
+
+static void emit_add_line(const char *reset,
+                         struct emit_callback *ecbdata,
+                         const char *line, int len)
+{
+       const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
+       const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
+
+       if (!*ws)
+               emit_line_0(ecbdata->opt, set, reset, '+', line, len);
+       else if (new_blank_line_at_eof(ecbdata, line, len))
+               /* Blank line at EOF - paint '+' as well */
+               emit_line_0(ecbdata->opt, ws, reset, '+', line, len);
+       else {
+               /* Emit just the prefix, then the rest. */
+               emit_line_0(ecbdata->opt, set, reset, '+', "", 0);
+               ws_check_emit(line, len, ecbdata->ws_rule,
+                             ecbdata->opt->file, set, reset, ws);
+       }
+}
+
+static void emit_hunk_header(struct emit_callback *ecbdata,
+                            const char *line, int len)
+{
+       const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
+       const char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
+       const char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);
+       const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
+       static const char atat[2] = { '@', '@' };
+       const char *cp, *ep;
+       struct strbuf msgbuf = STRBUF_INIT;
+       int org_len = len;
+       int i = 1;
+
+       /*
+        * As a hunk header must begin with "@@ -<old>, +<new> @@",
+        * it always is at least 10 bytes long.
+        */
+       if (len < 10 ||
+           memcmp(line, atat, 2) ||
+           !(ep = memmem(line + 2, len - 2, atat, 2))) {
+               emit_line(ecbdata->opt, plain, reset, line, len);
+               return;
+       }
+       ep += 2; /* skip over @@ */
+
+       /* The hunk header in fraginfo color */
+       strbuf_add(&msgbuf, frag, strlen(frag));
+       strbuf_add(&msgbuf, line, ep - line);
+       strbuf_add(&msgbuf, reset, strlen(reset));
+
+       /*
+        * trailing "\r\n"
+        */
+       for ( ; i < 3; i++)
+               if (line[len - i] == '\r' || line[len - i] == '\n')
+                       len--;
+
+       /* blank before the func header */
+       for (cp = ep; ep - line < len; ep++)
+               if (*ep != ' ' && *ep != '\t')
+                       break;
+       if (ep != cp) {
+               strbuf_add(&msgbuf, plain, strlen(plain));
+               strbuf_add(&msgbuf, cp, ep - cp);
+               strbuf_add(&msgbuf, reset, strlen(reset));
+       }
+
+       if (ep < line + len) {
+               strbuf_add(&msgbuf, func, strlen(func));
+               strbuf_add(&msgbuf, ep, line + len - ep);
+               strbuf_add(&msgbuf, reset, strlen(reset));
+       }
+
+       strbuf_add(&msgbuf, line + len, org_len - len);
+       emit_line(ecbdata->opt, "", "", msgbuf.buf, msgbuf.len);
+       strbuf_release(&msgbuf);
+}
+
 static struct diff_tempfile *claim_diff_tempfile(void) {
        int i;
        for (i = 0; i < ARRAY_SIZE(diff_temp); i++)
@@ -189,7 +442,7 @@ static void remove_tempfile(void)
        int i;
        for (i = 0; i < ARRAY_SIZE(diff_temp); i++) {
                if (diff_temp[i].name == diff_temp[i].tmp_path)
-                       unlink(diff_temp[i].name);
+                       unlink_or_warn(diff_temp[i].name);
                diff_temp[i].name = NULL;
        }
 }
@@ -201,29 +454,6 @@ static void remove_tempfile_on_signal(int signo)
        raise(signo);
 }
 
-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(FILE *file, int count)
 {
        switch (count) {
@@ -239,34 +469,44 @@ static void print_line_count(FILE *file, int count)
        }
 }
 
-static void copy_file_with_prefix(FILE *file,
-                                 int prefix, const char *data, int size,
-                                 const char *set, const char *reset)
+static void emit_rewrite_lines(struct emit_callback *ecb,
+                              int prefix, const char *data, int size)
 {
-       int ch, nl_just_seen = 1;
-       while (0 < size--) {
-               ch = *data++;
-               if (nl_just_seen) {
-                       fputs(set, file);
-                       putc(prefix, file);
+       const char *endp = NULL;
+       static const char *nneof = " No newline at end of file\n";
+       const char *old = diff_get_color(ecb->color_diff, DIFF_FILE_OLD);
+       const char *reset = diff_get_color(ecb->color_diff, DIFF_RESET);
+
+       while (0 < size) {
+               int len;
+
+               endp = memchr(data, '\n', size);
+               len = endp ? (endp - data + 1) : size;
+               if (prefix != '+') {
+                       ecb->lno_in_preimage++;
+                       emit_line_0(ecb->opt, old, reset, '-',
+                                   data, len);
+               } else {
+                       ecb->lno_in_postimage++;
+                       emit_add_line(reset, ecb, data, len);
                }
-               if (ch == '\n') {
-                       nl_just_seen = 1;
-                       fputs(reset, file);
-               } else
-                       nl_just_seen = 0;
-               putc(ch, file);
+               size -= len;
+               data += len;
+       }
+       if (!endp) {
+               const char *plain = diff_get_color(ecb->color_diff,
+                                                  DIFF_PLAIN);
+               emit_line_0(ecb->opt, plain, reset, '\\',
+                           nneof, strlen(nneof));
        }
-       if (!nl_just_seen)
-               fprintf(file, "%s\n\\ No newline at end of file\n", reset);
 }
 
 static void emit_rewrite_diff(const char *name_a,
                              const char *name_b,
                              struct diff_filespec *one,
                              struct diff_filespec *two,
-                             const char *textconv_one,
-                             const char *textconv_two,
+                             struct userdiff_driver *textconv_one,
+                             struct userdiff_driver *textconv_two,
                              struct diff_options *o)
 {
        int lc_a, lc_b;
@@ -274,13 +514,19 @@ static void emit_rewrite_diff(const char *name_a,
        const char *name_a_tab, *name_b_tab;
        const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO);
        const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO);
-       const char *old = diff_get_color(color_diff, DIFF_FILE_OLD);
-       const char *new = diff_get_color(color_diff, DIFF_FILE_NEW);
        const char *reset = diff_get_color(color_diff, DIFF_RESET);
        static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT;
        const char *a_prefix, *b_prefix;
-       const char *data_one, *data_two;
+       char *data_one, *data_two;
        size_t size_one, size_two;
+       struct emit_callback ecbdata;
+       char *line_prefix = "";
+       struct strbuf *msgbuf;
+
+       if (o && o->output_prefix) {
+               msgbuf = o->output_prefix(o, o->output_prefix_data);
+               line_prefix = msgbuf->buf;
+       }
 
        if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) {
                a_prefix = o->b_prefix;
@@ -300,56 +546,44 @@ static void emit_rewrite_diff(const char *name_a,
        quote_two_c_style(&a_name, a_prefix, name_a, 0);
        quote_two_c_style(&b_name, b_prefix, name_b, 0);
 
-       diff_populate_filespec(one, 0);
-       diff_populate_filespec(two, 0);
-       if (textconv_one) {
-               data_one = run_textconv(textconv_one, one, &size_one);
-               if (!data_one)
-                       die("unable to read files to diff");
-       }
-       else {
-               data_one = one->data;
-               size_one = one->size;
-       }
-       if (textconv_two) {
-               data_two = run_textconv(textconv_two, two, &size_two);
-               if (!data_two)
-                       die("unable to read files to diff");
-       }
-       else {
-               data_two = two->data;
-               size_two = two->size;
+       size_one = fill_textconv(textconv_one, one, &data_one);
+       size_two = fill_textconv(textconv_two, two, &data_two);
+
+       memset(&ecbdata, 0, sizeof(ecbdata));
+       ecbdata.color_diff = color_diff;
+       ecbdata.found_changesp = &o->found_changes;
+       ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
+       ecbdata.opt = o;
+       if (ecbdata.ws_rule & WS_BLANK_AT_EOF) {
+               mmfile_t mf1, mf2;
+               mf1.ptr = (char *)data_one;
+               mf2.ptr = (char *)data_two;
+               mf1.size = size_one;
+               mf2.size = size_two;
+               check_blank_at_eof(&mf1, &mf2, &ecbdata);
        }
+       ecbdata.lno_in_preimage = 1;
+       ecbdata.lno_in_postimage = 1;
 
        lc_a = count_lines(data_one, size_one);
        lc_b = count_lines(data_two, size_two);
        fprintf(o->file,
-               "%s--- %s%s%s\n%s+++ %s%s%s\n%s@@ -",
-               metainfo, a_name.buf, name_a_tab, reset,
-               metainfo, b_name.buf, name_b_tab, reset, fraginfo);
+               "%s%s--- %s%s%s\n%s%s+++ %s%s%s\n%s%s@@ -",
+               line_prefix, metainfo, a_name.buf, name_a_tab, reset,
+               line_prefix, metainfo, b_name.buf, name_b_tab, reset,
+               line_prefix, fraginfo);
        print_line_count(o->file, lc_a);
        fprintf(o->file, " +");
        print_line_count(o->file, lc_b);
        fprintf(o->file, " @@%s\n", reset);
        if (lc_a)
-               copy_file_with_prefix(o->file, '-', data_one, size_one, old, reset);
+               emit_rewrite_lines(&ecbdata, '-', data_one, size_one);
        if (lc_b)
-               copy_file_with_prefix(o->file, '+', data_two, size_two, new, reset);
-}
-
-static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
-{
-       if (!DIFF_FILE_VALID(one)) {
-               mf->ptr = (char *)""; /* 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;
+               emit_rewrite_lines(&ecbdata, '+', data_two, size_two);
+       if (textconv_one)
+               free((char *)data_one);
+       if (textconv_two)
+               free((char *)data_two);
 }
 
 struct diff_words_buffer {
@@ -372,23 +606,134 @@ static void diff_words_append(char *line, unsigned long len,
        buffer->text.ptr[buffer->text.size] = '\0';
 }
 
+struct diff_words_style_elem
+{
+       const char *prefix;
+       const char *suffix;
+       const char *color; /* NULL; filled in by the setup code if
+                           * color is enabled */
+};
+
+struct diff_words_style
+{
+       enum diff_words_type type;
+       struct diff_words_style_elem new, old, ctx;
+       const char *newline;
+};
+
+struct diff_words_style diff_words_styles[] = {
+       { DIFF_WORDS_PORCELAIN, {"+", "\n"}, {"-", "\n"}, {" ", "\n"}, "~\n" },
+       { DIFF_WORDS_PLAIN, {"{+", "+}"}, {"[-", "-]"}, {"", ""}, "\n" },
+       { DIFF_WORDS_COLOR, {"", ""}, {"", ""}, {"", ""}, "\n" }
+};
+
 struct diff_words_data {
        struct diff_words_buffer minus, plus;
        const char *current_plus;
-       FILE *file;
+       int last_minus;
+       struct diff_options *opt;
        regex_t *word_regex;
+       enum diff_words_type type;
+       struct diff_words_style *style;
 };
 
+static int fn_out_diff_words_write_helper(FILE *fp,
+                                         struct diff_words_style_elem *st_el,
+                                         const char *newline,
+                                         size_t count, const char *buf,
+                                         const char *line_prefix)
+{
+       int print = 0;
+
+       while (count) {
+               char *p = memchr(buf, '\n', count);
+               if (print)
+                       fputs(line_prefix, fp);
+               if (p != buf) {
+                       if (st_el->color && fputs(st_el->color, fp) < 0)
+                               return -1;
+                       if (fputs(st_el->prefix, fp) < 0 ||
+                           fwrite(buf, p ? p - buf : count, 1, fp) != 1 ||
+                           fputs(st_el->suffix, fp) < 0)
+                               return -1;
+                       if (st_el->color && *st_el->color
+                           && fputs(GIT_COLOR_RESET, fp) < 0)
+                               return -1;
+               }
+               if (!p)
+                       return 0;
+               if (fputs(newline, fp) < 0)
+                       return -1;
+               count -= p + 1 - buf;
+               buf = p + 1;
+               print = 1;
+       }
+       return 0;
+}
+
+/*
+ * '--color-words' algorithm can be described as:
+ *
+ *   1. collect a the minus/plus lines of a diff hunk, divided into
+ *      minus-lines and plus-lines;
+ *
+ *   2. break both minus-lines and plus-lines into words and
+ *      place them into two mmfile_t with one word for each line;
+ *
+ *   3. use xdiff to run diff on the two mmfile_t to get the words level diff;
+ *
+ * And for the common parts of the both file, we output the plus side text.
+ * diff_words->current_plus is used to trace the current position of the plus file
+ * which printed. diff_words->last_minus is used to trace the last minus word
+ * printed.
+ *
+ * For '--graph' to work with '--color-words', we need to output the graph prefix
+ * on each line of color words output. Generally, there are two conditions on
+ * which we should output the prefix.
+ *
+ *   1. diff_words->last_minus == 0 &&
+ *      diff_words->current_plus == diff_words->plus.text.ptr
+ *
+ *      that is: the plus text must start as a new line, and if there is no minus
+ *      word printed, a graph prefix must be printed.
+ *
+ *   2. diff_words->current_plus > diff_words->plus.text.ptr &&
+ *      *(diff_words->current_plus - 1) == '\n'
+ *
+ *      that is: a graph prefix must be printed following a '\n'
+ */
+static int color_words_output_graph_prefix(struct diff_words_data *diff_words)
+{
+       if ((diff_words->last_minus == 0 &&
+               diff_words->current_plus == diff_words->plus.text.ptr) ||
+               (diff_words->current_plus > diff_words->plus.text.ptr &&
+               *(diff_words->current_plus - 1) == '\n')) {
+               return 1;
+       } else {
+               return 0;
+       }
+}
+
 static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len)
 {
        struct diff_words_data *diff_words = priv;
+       struct diff_words_style *style = diff_words->style;
        int minus_first, minus_len, plus_first, plus_len;
        const char *minus_begin, *minus_end, *plus_begin, *plus_end;
+       struct diff_options *opt = diff_words->opt;
+       struct strbuf *msgbuf;
+       char *line_prefix = "";
 
        if (line[0] != '@' || parse_hunk_header(line, len,
                        &minus_first, &minus_len, &plus_first, &plus_len))
                return;
 
+       assert(opt);
+       if (opt->output_prefix) {
+               msgbuf = opt->output_prefix(opt, opt->output_prefix_data);
+               line_prefix = msgbuf->buf;
+       }
+
        /* POSIX requires that first be decremented by one if len == 0... */
        if (minus_len) {
                minus_begin = diff_words->minus.orig[minus_first].begin;
@@ -404,20 +749,32 @@ static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len)
        } else
                plus_begin = plus_end = diff_words->plus.orig[plus_first].end;
 
-       if (diff_words->current_plus != plus_begin)
-               fwrite(diff_words->current_plus,
-                               plus_begin - diff_words->current_plus, 1,
-                               diff_words->file);
-       if (minus_begin != minus_end)
-               color_fwrite_lines(diff_words->file,
-                               diff_get_color(1, DIFF_FILE_OLD),
-                               minus_end - minus_begin, minus_begin);
-       if (plus_begin != plus_end)
-               color_fwrite_lines(diff_words->file,
-                               diff_get_color(1, DIFF_FILE_NEW),
-                               plus_end - plus_begin, plus_begin);
+       if (color_words_output_graph_prefix(diff_words)) {
+               fputs(line_prefix, diff_words->opt->file);
+       }
+       if (diff_words->current_plus != plus_begin) {
+               fn_out_diff_words_write_helper(diff_words->opt->file,
+                               &style->ctx, style->newline,
+                               plus_begin - diff_words->current_plus,
+                               diff_words->current_plus, line_prefix);
+               if (*(plus_begin - 1) == '\n')
+                       fputs(line_prefix, diff_words->opt->file);
+       }
+       if (minus_begin != minus_end) {
+               fn_out_diff_words_write_helper(diff_words->opt->file,
+                               &style->old, style->newline,
+                               minus_end - minus_begin, minus_begin,
+                               line_prefix);
+       }
+       if (plus_begin != plus_end) {
+               fn_out_diff_words_write_helper(diff_words->opt->file,
+                               &style->new, style->newline,
+                               plus_end - plus_begin, plus_begin,
+                               line_prefix);
+       }
 
        diff_words->current_plus = plus_end;
+       diff_words->last_minus = minus_first;
 }
 
 /* This function starts looking at *begin, and returns 0 iff a word was found. */
@@ -495,65 +852,77 @@ static void diff_words_show(struct diff_words_data *diff_words)
 {
        xpparam_t xpp;
        xdemitconf_t xecfg;
-       xdemitcb_t ecb;
        mmfile_t minus, plus;
+       struct diff_words_style *style = diff_words->style;
+
+       struct diff_options *opt = diff_words->opt;
+       struct strbuf *msgbuf;
+       char *line_prefix = "";
+
+       assert(opt);
+       if (opt->output_prefix) {
+               msgbuf = opt->output_prefix(opt, opt->output_prefix_data);
+               line_prefix = msgbuf->buf;
+       }
 
        /* special case: only removal */
        if (!diff_words->plus.text.size) {
-               color_fwrite_lines(diff_words->file,
-                       diff_get_color(1, DIFF_FILE_OLD),
-                       diff_words->minus.text.size, diff_words->minus.text.ptr);
+               fputs(line_prefix, diff_words->opt->file);
+               fn_out_diff_words_write_helper(diff_words->opt->file,
+                       &style->old, style->newline,
+                       diff_words->minus.text.size,
+                       diff_words->minus.text.ptr, line_prefix);
                diff_words->minus.text.size = 0;
                return;
        }
 
        diff_words->current_plus = diff_words->plus.text.ptr;
+       diff_words->last_minus = 0;
 
        memset(&xpp, 0, sizeof(xpp));
        memset(&xecfg, 0, sizeof(xecfg));
        diff_words_fill(&diff_words->minus, &minus, diff_words->word_regex);
        diff_words_fill(&diff_words->plus, &plus, diff_words->word_regex);
-       xpp.flags = XDF_NEED_MINIMAL;
+       xpp.flags = 0;
        /* as only the hunk header will be parsed, we need a 0-context */
        xecfg.ctxlen = 0;
        xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words,
-                     &xpp, &xecfg, &ecb);
+                     &xpp, &xecfg);
        free(minus.ptr);
        free(plus.ptr);
        if (diff_words->current_plus != diff_words->plus.text.ptr +
-                       diff_words->plus.text.size)
-               fwrite(diff_words->current_plus,
+                       diff_words->plus.text.size) {
+               if (color_words_output_graph_prefix(diff_words))
+                       fputs(line_prefix, diff_words->opt->file);
+               fn_out_diff_words_write_helper(diff_words->opt->file,
+                       &style->ctx, style->newline,
                        diff_words->plus.text.ptr + diff_words->plus.text.size
-                       - diff_words->current_plus, 1,
-                       diff_words->file);
+                       - diff_words->current_plus, diff_words->current_plus,
+                       line_prefix);
+       }
        diff_words->minus.text.size = diff_words->plus.text.size = 0;
 }
 
-typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
-
-struct emit_callback {
-       int nparents, color_diff;
-       unsigned ws_rule;
-       sane_truncate_fn truncate;
-       const char **label_path;
-       struct diff_words_data *diff_words;
-       int *found_changesp;
-       FILE *file;
-};
+/* In "color-words" mode, show word-diff of words accumulated in the buffer */
+static void diff_words_flush(struct emit_callback *ecbdata)
+{
+       if (ecbdata->diff_words->minus.text.size ||
+           ecbdata->diff_words->plus.text.size)
+               diff_words_show(ecbdata->diff_words);
+}
 
 static void free_diff_words_data(struct emit_callback *ecbdata)
 {
        if (ecbdata->diff_words) {
-               /* flush buffers */
-               if (ecbdata->diff_words->minus.text.size ||
-                               ecbdata->diff_words->plus.text.size)
-                       diff_words_show(ecbdata->diff_words);
-
+               diff_words_flush(ecbdata);
                free (ecbdata->diff_words->minus.text.ptr);
                free (ecbdata->diff_words->minus.orig);
                free (ecbdata->diff_words->plus.text.ptr);
                free (ecbdata->diff_words->plus.orig);
-               free(ecbdata->diff_words->word_regex);
+               if (ecbdata->diff_words->word_regex) {
+                       regfree(ecbdata->diff_words->word_regex);
+                       free(ecbdata->diff_words->word_regex);
+               }
                free(ecbdata->diff_words);
                ecbdata->diff_words = NULL;
        }
@@ -566,42 +935,6 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix)
        return "";
 }
 
-static void emit_line(FILE *file, const char *set, const char *reset, const char *line, int len)
-{
-       int has_trailing_newline, has_trailing_carriage_return;
-
-       has_trailing_newline = (len > 0 && line[len-1] == '\n');
-       if (has_trailing_newline)
-               len--;
-       has_trailing_carriage_return = (len > 0 && line[len-1] == '\r');
-       if (has_trailing_carriage_return)
-               len--;
-
-       fputs(set, file);
-       fwrite(line, len, 1, file);
-       fputs(reset, file);
-       if (has_trailing_carriage_return)
-               fputc('\r', file);
-       if (has_trailing_newline)
-               fputc('\n', file);
-}
-
-static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len)
-{
-       const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
-       const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
-
-       if (!*ws)
-               emit_line(ecbdata->file, set, reset, line, len);
-       else {
-               /* Emit just the prefix, then the rest. */
-               emit_line(ecbdata->file, set, reset, line, ecbdata->nparents);
-               ws_check_emit(line + ecbdata->nparents,
-                             len - ecbdata->nparents, ecbdata->ws_rule,
-                             ecbdata->file, set, reset, ws);
-       }
-}
-
 static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, unsigned long len)
 {
        const char *cp;
@@ -620,15 +953,41 @@ static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, u
        return allot - l;
 }
 
+static void find_lno(const char *line, struct emit_callback *ecbdata)
+{
+       const char *p;
+       ecbdata->lno_in_preimage = 0;
+       ecbdata->lno_in_postimage = 0;
+       p = strchr(line, '-');
+       if (!p)
+               return; /* cannot happen */
+       ecbdata->lno_in_preimage = strtol(p + 1, NULL, 10);
+       p = strchr(p, '+');
+       if (!p)
+               return; /* cannot happen */
+       ecbdata->lno_in_postimage = strtol(p + 1, NULL, 10);
+}
+
 static void fn_out_consume(void *priv, char *line, unsigned long len)
 {
-       int i;
-       int color;
        struct emit_callback *ecbdata = priv;
        const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
        const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
        const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
+       struct diff_options *o = ecbdata->opt;
+       char *line_prefix = "";
+       struct strbuf *msgbuf;
 
+       if (o && o->output_prefix) {
+               msgbuf = o->output_prefix(o, o->output_prefix_data);
+               line_prefix = msgbuf->buf;
+       }
+
+       if (ecbdata->header) {
+               fprintf(ecbdata->opt->file, "%s", ecbdata->header->buf);
+               strbuf_reset(ecbdata->header);
+               ecbdata->header = NULL;
+       }
        *(ecbdata->found_changesp) = 1;
 
        if (ecbdata->label_path[0]) {
@@ -637,10 +996,10 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
                name_a_tab = strchr(ecbdata->label_path[0], ' ') ? "\t" : "";
                name_b_tab = strchr(ecbdata->label_path[1], ' ') ? "\t" : "";
 
-               fprintf(ecbdata->file, "%s--- %s%s%s\n",
-                       meta, ecbdata->label_path[0], reset, name_a_tab);
-               fprintf(ecbdata->file, "%s+++ %s%s%s\n",
-                       meta, ecbdata->label_path[1], reset, name_b_tab);
+               fprintf(ecbdata->opt->file, "%s%s--- %s%s%s\n",
+                       line_prefix, meta, ecbdata->label_path[0], reset, name_a_tab);
+               fprintf(ecbdata->opt->file, "%s%s+++ %s%s%s\n",
+                       line_prefix, meta, ecbdata->label_path[1], reset, name_b_tab);
                ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
        }
 
@@ -650,31 +1009,25 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
                len = 1;
        }
 
-       /* This is not really necessary for now because
-        * this codepath only deals with two-way diffs.
-        */
-       for (i = 0; i < len && line[i] == '@'; i++)
-               ;
-       if (2 <= i && i < len && line[i] == ' ') {
-               ecbdata->nparents = i - 1;
+       if (line[0] == '@') {
+               if (ecbdata->diff_words)
+                       diff_words_flush(ecbdata);
                len = sane_truncate_line(ecbdata, line, len);
-               emit_line(ecbdata->file,
-                         diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO),
-                         reset, line, len);
+               find_lno(line, ecbdata);
+               emit_hunk_header(ecbdata, line, len);
                if (line[len-1] != '\n')
-                       putc('\n', ecbdata->file);
+                       putc('\n', ecbdata->opt->file);
                return;
        }
 
-       if (len < ecbdata->nparents) {
-               emit_line(ecbdata->file, reset, reset, line, len);
+       if (len < 1) {
+               emit_line(ecbdata->opt, reset, reset, line, len);
+               if (ecbdata->diff_words
+                   && ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN)
+                       fputs("~\n", ecbdata->opt->file);
                return;
        }
 
-       color = DIFF_PLAIN;
-       if (ecbdata->diff_words && ecbdata->nparents != 1)
-               /* fall back to normal diff */
-               free_diff_words_data(ecbdata);
        if (ecbdata->diff_words) {
                if (line[0] == '-') {
                        diff_words_append(line, len,
@@ -685,28 +1038,29 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
                                          &ecbdata->diff_words->plus);
                        return;
                }
-               if (ecbdata->diff_words->minus.text.size ||
-                   ecbdata->diff_words->plus.text.size)
-                       diff_words_show(ecbdata->diff_words);
-               line++;
-               len--;
-               emit_line(ecbdata->file, plain, reset, line, len);
+               diff_words_flush(ecbdata);
+               if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) {
+                       emit_line(ecbdata->opt, plain, reset, line, len);
+                       fputs("~\n", ecbdata->opt->file);
+               } else {
+                       /* don't print the prefix character */
+                       emit_line(ecbdata->opt, plain, reset, line+1, len-1);
+               }
                return;
        }
-       for (i = 0; i < ecbdata->nparents && len; i++) {
-               if (line[i] == '-')
-                       color = DIFF_FILE_OLD;
-               else if (line[i] == '+')
-                       color = DIFF_FILE_NEW;
-       }
 
-       if (color != DIFF_FILE_NEW) {
-               emit_line(ecbdata->file,
-                         diff_get_color(ecbdata->color_diff, color),
-                         reset, line, len);
-               return;
+       if (line[0] != '+') {
+               const char *color =
+                       diff_get_color(ecbdata->color_diff,
+                                      line[0] == '-' ? DIFF_FILE_OLD : DIFF_PLAIN);
+               ecbdata->lno_in_preimage++;
+               if (line[0] == ' ')
+                       ecbdata->lno_in_postimage++;
+               emit_line(ecbdata->opt, color, reset, line, len);
+       } else {
+               ecbdata->lno_in_postimage++;
+               emit_add_line(reset, ecbdata, line + 1, len - 1);
        }
-       emit_add_line(reset, ecbdata, line, len);
 }
 
 static char *pprint_rename(const char *a, const char *b)
@@ -786,7 +1140,7 @@ struct diffstat_t {
                unsigned is_unmerged:1;
                unsigned is_binary:1;
                unsigned is_renamed:1;
-               unsigned int added, deleted;
+               uintmax_t added, deleted;
        } **files;
 };
 
@@ -839,10 +1193,9 @@ static int scale_linear(int it, int width, int max_change)
 }
 
 static void show_name(FILE *file,
-                     const char *prefix, const char *name, int len,
-                     const char *reset, const char *set)
+                     const char *prefix, const char *name, int len)
 {
-       fprintf(file, " %s%s%-*s%s |", set, prefix, len, name, reset);
+       fprintf(file, " %s%-*s |", prefix, len, name);
 }
 
 static void show_graph(FILE *file, char ch, int cnt, const char *set, const char *reset)
@@ -879,14 +1232,21 @@ static void fill_print_name(struct diffstat_file *file)
 static void show_stats(struct diffstat_t *data, struct diff_options *options)
 {
        int i, len, add, del, adds = 0, dels = 0;
-       int max_change = 0, max_len = 0;
+       uintmax_t max_change = 0, max_len = 0;
        int total_files = data->nr;
        int width, name_width;
        const char *reset, *set, *add_c, *del_c;
+       const char *line_prefix = "";
+       struct strbuf *msg = NULL;
 
        if (data->nr == 0)
                return;
 
+       if (options->output_prefix) {
+               msg = options->output_prefix(options, options->output_prefix_data);
+               line_prefix = msg->buf;
+       }
+
        width = options->stat_width ? options->stat_width : 80;
        name_width = options->stat_name_width ? options->stat_name_width : 50;
 
@@ -908,7 +1268,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
 
        for (i = 0; i < data->nr; i++) {
                struct diffstat_file *file = data->files[i];
-               int change = file->added + file->deleted;
+               uintmax_t change = file->added + file->deleted;
                fill_print_name(file);
                len = strlen(file->print_name);
                if (max_len < len)
@@ -936,8 +1296,8 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
        for (i = 0; i < data->nr; i++) {
                const char *prefix = "";
                char *name = data->files[i]->print_name;
-               int added = data->files[i]->added;
-               int deleted = data->files[i]->deleted;
+               uintmax_t added = data->files[i]->added;
+               uintmax_t deleted = data->files[i]->deleted;
                int name_len;
 
                /*
@@ -956,17 +1316,21 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                }
 
                if (data->files[i]->is_binary) {
-                       show_name(options->file, prefix, name, len, reset, set);
+                       fprintf(options->file, "%s", line_prefix);
+                       show_name(options->file, prefix, name, len);
                        fprintf(options->file, "  Bin ");
-                       fprintf(options->file, "%s%d%s", del_c, deleted, reset);
+                       fprintf(options->file, "%s%"PRIuMAX"%s",
+                               del_c, deleted, reset);
                        fprintf(options->file, " -> ");
-                       fprintf(options->file, "%s%d%s", add_c, added, reset);
+                       fprintf(options->file, "%s%"PRIuMAX"%s",
+                               add_c, added, reset);
                        fprintf(options->file, " bytes");
                        fprintf(options->file, "\n");
                        continue;
                }
                else if (data->files[i]->is_unmerged) {
-                       show_name(options->file, prefix, name, len, reset, set);
+                       fprintf(options->file, "%s", line_prefix);
+                       show_name(options->file, prefix, name, len);
                        fprintf(options->file, "  Unmerged\n");
                        continue;
                }
@@ -988,19 +1352,21 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                        add = scale_linear(add, width, max_change);
                        del = scale_linear(del, width, max_change);
                }
-               show_name(options->file, prefix, name, len, reset, set);
-               fprintf(options->file, "%5d%s", added + deleted,
+               fprintf(options->file, "%s", line_prefix);
+               show_name(options->file, prefix, name, len);
+               fprintf(options->file, "%5"PRIuMAX"%s", added + deleted,
                                added + deleted ? " " : "");
                show_graph(options->file, '+', add, add_c, reset);
                show_graph(options->file, '-', del, del_c, reset);
                fprintf(options->file, "\n");
        }
+       fprintf(options->file, "%s", line_prefix);
        fprintf(options->file,
-              "%s %d files changed, %d insertions(+), %d deletions(-)%s\n",
-              set, total_files, adds, dels, reset);
+              " %d files changed, %d insertions(+), %d deletions(-)\n",
+              total_files, adds, dels);
 }
 
-static void show_shortstats(struct diffstat_tdata, struct diff_options *options)
+static void show_shortstats(struct diffstat_t *data, struct diff_options *options)
 {
        int i, adds = 0, dels = 0, total_files = data->nr;
 
@@ -1021,6 +1387,12 @@ static void show_shortstats(struct diffstat_t* data, struct diff_options *option
                        }
                }
        }
+       if (options->output_prefix) {
+               struct strbuf *msg = NULL;
+               msg = options->output_prefix(options,
+                               options->output_prefix_data);
+               fprintf(options->file, "%s", msg->buf);
+       }
        fprintf(options->file, " %d files changed, %d insertions(+), %d deletions(-)\n",
               total_files, adds, dels);
 }
@@ -1035,11 +1407,19 @@ static void show_numstat(struct diffstat_t *data, struct diff_options *options)
        for (i = 0; i < data->nr; i++) {
                struct diffstat_file *file = data->files[i];
 
+               if (options->output_prefix) {
+                       struct strbuf *msg = NULL;
+                       msg = options->output_prefix(options,
+                                       options->output_prefix_data);
+                       fprintf(options->file, "%s", msg->buf);
+               }
+
                if (file->is_binary)
                        fprintf(options->file, "-\t-\t");
                else
                        fprintf(options->file,
-                               "%d\t%d\t", file->added, file->deleted);
+                               "%"PRIuMAX"\t%"PRIuMAX"\t",
+                               file->added, file->deleted);
                if (options->line_termination) {
                        fill_print_name(file);
                        if (!file->is_renamed)
@@ -1069,10 +1449,18 @@ struct dirstat_dir {
        int alloc, nr, percent, cumulative;
 };
 
-static long gather_dirstat(FILE *file, struct dirstat_dir *dir, unsigned long changed, const char *base, int baselen)
+static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir,
+               unsigned long changed, const char *base, int baselen)
 {
        unsigned long this_dir = 0;
        unsigned int sources = 0;
+       const char *line_prefix = "";
+       struct strbuf *msg = NULL;
+
+       if (opt->output_prefix) {
+               msg = opt->output_prefix(opt, opt->output_prefix_data);
+               line_prefix = msg->buf;
+       }
 
        while (dir->nr) {
                struct dirstat_file *f = dir->files;
@@ -1087,7 +1475,7 @@ static long gather_dirstat(FILE *file, struct dirstat_dir *dir, unsigned long ch
                slash = strchr(f->name + baselen, '/');
                if (slash) {
                        int newbaselen = slash + 1 - f->name;
-                       this = gather_dirstat(file, dir, changed, f->name, newbaselen);
+                       this = gather_dirstat(opt, dir, changed, f->name, newbaselen);
                        sources++;
                } else {
                        this = f->changed;
@@ -1109,7 +1497,8 @@ static long gather_dirstat(FILE *file, struct dirstat_dir *dir, unsigned long ch
                if (permille) {
                        int percent = permille / 10;
                        if (percent >= dir->percent) {
-                               fprintf(file, "%4d.%01d%% %.*s\n", percent, permille % 10, baselen, base);
+                               fprintf(opt->file, "%s%4d.%01d%% %.*s\n", line_prefix,
+                                       percent, permille % 10, baselen, base);
                                if (!dir->cumulative)
                                        return 0;
                        }
@@ -1189,7 +1578,7 @@ static void show_dirstat(struct diff_options *options)
 
        /* Show all directories with more than x% of the changes */
        qsort(dir.files, dir.nr, sizeof(dir.files[0]), dirstat_compare);
-       gather_dirstat(options->file, &dir, changed, "", 0);
+       gather_dirstat(options, &dir, changed, "", 0);
 }
 
 static void free_diffstat_info(struct diffstat_t *diffstat)
@@ -1209,38 +1598,32 @@ static void free_diffstat_info(struct diffstat_t *diffstat)
 struct checkdiff_t {
        const char *filename;
        int lineno;
+       int conflict_marker_size;
        struct diff_options *o;
        unsigned ws_rule;
        unsigned status;
-       int trailing_blanks_start;
 };
 
-static int is_conflict_marker(const char *line, unsigned long len)
+static int is_conflict_marker(const char *line, int marker_size, unsigned long len)
 {
        char firstchar;
        int cnt;
 
-       if (len < 8)
+       if (len < marker_size + 1)
                return 0;
        firstchar = line[0];
        switch (firstchar) {
-       case '=': case '>': case '<':
+       case '=': case '>': case '<': case '|':
                break;
        default:
                return 0;
        }
-       for (cnt = 1; cnt < 7; cnt++)
+       for (cnt = 1; cnt < marker_size; cnt++)
                if (line[cnt] != firstchar)
                        return 0;
-       /* line[0] thru line[6] are same as firstchar */
-       if (firstchar == '=') {
-               /* divider between ours and theirs? */
-               if (len != 8 || line[7] != '\n')
-                       return 0;
-       } else if (len < 8 || !isspace(line[7])) {
-               /* not divider before ours nor after theirs */
+       /* line[1] thru line[marker_size-1] are same as firstchar */
+       if (len < marker_size + 1 || !isspace(line[marker_size]))
                return 0;
-       }
        return 1;
 }
 
@@ -1248,45 +1631,49 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len)
 {
        struct checkdiff_t *data = priv;
        int color_diff = DIFF_OPT_TST(data->o, COLOR_DIFF);
+       int marker_size = data->conflict_marker_size;
        const char *ws = diff_get_color(color_diff, DIFF_WHITESPACE);
        const char *reset = diff_get_color(color_diff, DIFF_RESET);
        const char *set = diff_get_color(color_diff, DIFF_FILE_NEW);
        char *err;
+       char *line_prefix = "";
+       struct strbuf *msgbuf;
+
+       assert(data->o);
+       if (data->o->output_prefix) {
+               msgbuf = data->o->output_prefix(data->o,
+                       data->o->output_prefix_data);
+               line_prefix = msgbuf->buf;
+       }
 
        if (line[0] == '+') {
                unsigned bad;
                data->lineno++;
-               if (!ws_blank_line(line + 1, len - 1, data->ws_rule))
-                       data->trailing_blanks_start = 0;
-               else if (!data->trailing_blanks_start)
-                       data->trailing_blanks_start = data->lineno;
-               if (is_conflict_marker(line + 1, len - 1)) {
+               if (is_conflict_marker(line + 1, marker_size, len - 1)) {
                        data->status |= 1;
                        fprintf(data->o->file,
-                               "%s:%d: leftover conflict marker\n",
-                               data->filename, data->lineno);
+                               "%s%s:%d: leftover conflict marker\n",
+                               line_prefix, data->filename, data->lineno);
                }
                bad = ws_check(line + 1, len - 1, data->ws_rule);
                if (!bad)
                        return;
                data->status |= bad;
                err = whitespace_error_string(bad);
-               fprintf(data->o->file, "%s:%d: %s.\n",
-                       data->filename, data->lineno, err);
+               fprintf(data->o->file, "%s%s:%d: %s.\n",
+                       line_prefix, data->filename, data->lineno, err);
                free(err);
-               emit_line(data->o->file, set, reset, line, 1);
+               emit_line(data->o, set, reset, line, 1);
                ws_check_emit(line + 1, len - 1, data->ws_rule,
                              data->o->file, set, reset, ws);
        } else if (line[0] == ' ') {
                data->lineno++;
-               data->trailing_blanks_start = 0;
        } else if (line[0] == '@') {
                char *plus = strchr(line, '+');
                if (plus)
                        data->lineno = strtol(plus, NULL, 10) - 1;
                else
                        die("invalid diff");
-               data->trailing_blanks_start = 0;
        }
 }
 
@@ -1314,7 +1701,7 @@ static unsigned char *deflate_it(char *data,
        return deflated;
 }
 
-static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two)
+static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, char *prefix)
 {
        void *cp;
        void *delta;
@@ -1343,13 +1730,13 @@ static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two)
        }
 
        if (delta && delta_size < deflate_size) {
-               fprintf(file, "delta %lu\n", orig_size);
+               fprintf(file, "%sdelta %lu\n", prefix, orig_size);
                free(deflated);
                data = delta;
                data_size = delta_size;
        }
        else {
-               fprintf(file, "literal %lu\n", two->size);
+               fprintf(file, "%sliteral %lu\n", prefix, two->size);
                free(delta);
                data = deflated;
                data_size = deflate_size;
@@ -1367,18 +1754,19 @@ static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two)
                        line[0] = bytes - 26 + 'a' - 1;
                encode_85(line + 1, cp, bytes);
                cp = (char *) cp + bytes;
+               fprintf(file, "%s", prefix);
                fputs(line, file);
                fputc('\n', file);
        }
-       fprintf(file, "\n");
+       fprintf(file, "%s\n", prefix);
        free(data);
 }
 
-static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two)
+static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two, char *prefix)
 {
-       fprintf(file, "GIT binary patch\n");
-       emit_binary_diff_body(file, one, two);
-       emit_binary_diff_body(file, two, one);
+       fprintf(file, "%sGIT binary patch\n", prefix);
+       emit_binary_diff_body(file, one, two, prefix);
+       emit_binary_diff_body(file, two, one, prefix);
 }
 
 static void diff_filespec_load_driver(struct diff_filespec *one)
@@ -1428,14 +1816,26 @@ void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const
                options->b_prefix = b;
 }
 
-static const char *get_textconv(struct diff_filespec *one)
+struct userdiff_driver *get_textconv(struct diff_filespec *one)
 {
        if (!DIFF_FILE_VALID(one))
                return NULL;
        if (!S_ISREG(one->mode))
                return NULL;
        diff_filespec_load_driver(one);
-       return one->driver->textconv;
+       if (!one->driver->textconv)
+               return NULL;
+
+       if (one->driver->textconv_want_cache && !one->driver->textconv_cache) {
+               struct notes_cache *c = xmalloc(sizeof(*c));
+               struct strbuf name = STRBUF_INIT;
+
+               strbuf_addf(&name, "textconv/%s", one->driver->name);
+               notes_cache_init(c, name.buf, one->driver->textconv);
+               one->driver->textconv_cache = c;
+       }
+
+       return one->driver;
 }
 
 static void builtin_diff(const char *name_a,
@@ -1443,6 +1843,7 @@ static void builtin_diff(const char *name_a,
                         struct diff_filespec *one,
                         struct diff_filespec *two,
                         const char *xfrm_msg,
+                        int must_show_header,
                         struct diff_options *o,
                         int complete_rewrite)
 {
@@ -1452,7 +1853,27 @@ static void builtin_diff(const char *name_a,
        const char *set = diff_get_color_opt(o, DIFF_METAINFO);
        const char *reset = diff_get_color_opt(o, DIFF_RESET);
        const char *a_prefix, *b_prefix;
-       const char *textconv_one = NULL, *textconv_two = NULL;
+       struct userdiff_driver *textconv_one = NULL;
+       struct userdiff_driver *textconv_two = NULL;
+       struct strbuf header = STRBUF_INIT;
+       struct strbuf *msgbuf;
+       char *line_prefix = "";
+
+       if (o->output_prefix) {
+               msgbuf = o->output_prefix(o, o->output_prefix_data);
+               line_prefix = msgbuf->buf;
+       }
+
+       if (DIFF_OPT_TST(o, SUBMODULE_LOG) &&
+                       (!one->mode || S_ISGITLINK(one->mode)) &&
+                       (!two->mode || S_ISGITLINK(two->mode))) {
+               const char *del = diff_get_color_opt(o, DIFF_FILE_OLD);
+               const char *add = diff_get_color_opt(o, DIFF_FILE_NEW);
+               show_submodule_summary(o->file, one ? one->path : two->path,
+                               one->sha1, two->sha1, two->dirty_submodule,
+                               del, add, reset);
+               return;
+       }
 
        if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) {
                textconv_one = get_textconv(one);
@@ -1476,25 +1897,29 @@ static void builtin_diff(const char *name_a,
        b_two = quote_two(b_prefix, name_b + (*name_b == '/'));
        lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
        lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
-       fprintf(o->file, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
+       strbuf_addf(&header, "%s%sdiff --git %s %s%s\n", line_prefix, set, a_one, b_two, reset);
        if (lbl[0][0] == '/') {
                /* /dev/null */
-               fprintf(o->file, "%snew file mode %06o%s\n", set, two->mode, reset);
-               if (xfrm_msg && xfrm_msg[0])
-                       fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
+               strbuf_addf(&header, "%s%snew file mode %06o%s\n", line_prefix, set, two->mode, reset);
+               if (xfrm_msg)
+                       strbuf_addstr(&header, xfrm_msg);
+               must_show_header = 1;
        }
        else if (lbl[1][0] == '/') {
-               fprintf(o->file, "%sdeleted file mode %06o%s\n", set, one->mode, reset);
-               if (xfrm_msg && xfrm_msg[0])
-                       fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
+               strbuf_addf(&header, "%s%sdeleted file mode %06o%s\n", line_prefix, set, one->mode, reset);
+               if (xfrm_msg)
+                       strbuf_addstr(&header, xfrm_msg);
+               must_show_header = 1;
        }
        else {
                if (one->mode != two->mode) {
-                       fprintf(o->file, "%sold mode %06o%s\n", set, one->mode, reset);
-                       fprintf(o->file, "%snew mode %06o%s\n", set, two->mode, reset);
+                       strbuf_addf(&header, "%s%sold mode %06o%s\n", line_prefix, set, one->mode, reset);
+                       strbuf_addf(&header, "%s%snew mode %06o%s\n", line_prefix, set, two->mode, reset);
+                       must_show_header = 1;
                }
-               if (xfrm_msg && xfrm_msg[0])
-                       fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
+               if (xfrm_msg)
+                       strbuf_addstr(&header, xfrm_msg);
+
                /*
                 * we do not run diff between different kind
                 * of objects.
@@ -1504,6 +1929,8 @@ static void builtin_diff(const char *name_a,
                if (complete_rewrite &&
                    (textconv_one || !diff_filespec_is_binary(one)) &&
                    (textconv_two || !diff_filespec_is_binary(two))) {
+                       fprintf(o->file, "%s", header.buf);
+                       strbuf_reset(&header);
                        emit_rewrite_diff(name_a, name_b, one, two,
                                                textconv_one, textconv_two, o);
                        o->found_changes = 1;
@@ -1511,21 +1938,25 @@ static void builtin_diff(const char *name_a,
                }
        }
 
-       if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
-               die("unable to read files to diff");
-
        if (!DIFF_OPT_TST(o, TEXT) &&
-           ( (diff_filespec_is_binary(one) && !textconv_one) ||
-             (diff_filespec_is_binary(two) && !textconv_two) )) {
+           ( (!textconv_one && diff_filespec_is_binary(one)) ||
+             (!textconv_two && diff_filespec_is_binary(two)) )) {
+               if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
+                       die("unable to read files to diff");
                /* Quite common confusing case */
                if (mf1.size == mf2.size &&
-                   !memcmp(mf1.ptr, mf2.ptr, mf1.size))
+                   !memcmp(mf1.ptr, mf2.ptr, mf1.size)) {
+                       if (must_show_header)
+                               fprintf(o->file, "%s", header.buf);
                        goto free_ab_and_return;
+               }
+               fprintf(o->file, "%s", header.buf);
+               strbuf_reset(&header);
                if (DIFF_OPT_TST(o, BINARY))
-                       emit_binary_diff(o->file, &mf1, &mf2);
+                       emit_binary_diff(o->file, &mf1, &mf2, line_prefix);
                else
-                       fprintf(o->file, "Binary files %s and %s differ\n",
-                               lbl[0], lbl[1]);
+                       fprintf(o->file, "%sBinary files %s and %s differ\n",
+                               line_prefix, lbl[0], lbl[1]);
                o->found_changes = 1;
        }
        else {
@@ -1533,25 +1964,17 @@ static void builtin_diff(const char *name_a,
                const char *diffopts = getenv("GIT_DIFF_OPTS");
                xpparam_t xpp;
                xdemitconf_t xecfg;
-               xdemitcb_t ecb;
                struct emit_callback ecbdata;
                const struct userdiff_funcname *pe;
 
-               if (textconv_one) {
-                       size_t size;
-                       mf1.ptr = run_textconv(textconv_one, one, &size);
-                       if (!mf1.ptr)
-                               die("unable to read files to diff");
-                       mf1.size = size;
-               }
-               if (textconv_two) {
-                       size_t size;
-                       mf2.ptr = run_textconv(textconv_two, two, &size);
-                       if (!mf2.ptr)
-                               die("unable to read files to diff");
-                       mf2.size = size;
+               if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS) || must_show_header) {
+                       fprintf(o->file, "%s", header.buf);
+                       strbuf_reset(&header);
                }
 
+               mf1.size = fill_textconv(textconv_one, one, &mf1.ptr);
+               mf2.size = fill_textconv(textconv_two, two, &mf2.ptr);
+
                pe = diff_funcname_pattern(one);
                if (!pe)
                        pe = diff_funcname_pattern(two);
@@ -1563,8 +1986,11 @@ static void builtin_diff(const char *name_a,
                ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
                ecbdata.found_changesp = &o->found_changes;
                ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
-               ecbdata.file = o->file;
-               xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
+               if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
+                       check_blank_at_eof(&mf1, &mf2, &ecbdata);
+               ecbdata.opt = o;
+               ecbdata.header = header.len ? &header : NULL;
+               xpp.flags = o->xdl_opts;
                xecfg.ctxlen = o->context;
                xecfg.interhunkctxlen = o->interhunkcontext;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
@@ -1576,10 +2002,13 @@ static void builtin_diff(const char *name_a,
                        xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
                else if (!prefixcmp(diffopts, "-u"))
                        xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
-               if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) {
+               if (o->word_diff) {
+                       int i;
+
                        ecbdata.diff_words =
                                xcalloc(1, sizeof(struct diff_words_data));
-                       ecbdata.diff_words->file = o->file;
+                       ecbdata.diff_words->type = o->word_diff;
+                       ecbdata.diff_words->opt = o;
                        if (!o->word_regex)
                                o->word_regex = userdiff_word_regex(one);
                        if (!o->word_regex)
@@ -1595,18 +2024,33 @@ static void builtin_diff(const char *name_a,
                                        die ("Invalid regular expression: %s",
                                                        o->word_regex);
                        }
+                       for (i = 0; i < ARRAY_SIZE(diff_words_styles); i++) {
+                               if (o->word_diff == diff_words_styles[i].type) {
+                                       ecbdata.diff_words->style =
+                                               &diff_words_styles[i];
+                                       break;
+                               }
+                       }
+                       if (DIFF_OPT_TST(o, COLOR_DIFF)) {
+                               struct diff_words_style *st = ecbdata.diff_words->style;
+                               st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
+                               st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
+                               st->ctx.color = diff_get_color_opt(o, DIFF_PLAIN);
+                       }
                }
                xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
-                             &xpp, &xecfg, &ecb);
-               if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS))
+                             &xpp, &xecfg);
+               if (o->word_diff)
                        free_diff_words_data(&ecbdata);
                if (textconv_one)
                        free(mf1.ptr);
                if (textconv_two)
                        free(mf2.ptr);
+               xdiff_clear_find_func(&xecfg);
        }
 
  free_ab_and_return:
+       strbuf_release(&header);
        diff_free_filespec_data(one);
        diff_free_filespec_data(two);
        free(a_one);
@@ -1648,13 +2092,12 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
                /* Crazy xdl interfaces.. */
                xpparam_t xpp;
                xdemitconf_t xecfg;
-               xdemitcb_t ecb;
 
                memset(&xpp, 0, sizeof(xpp));
                memset(&xecfg, 0, sizeof(xecfg));
-               xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
+               xpp.flags = o->xdl_opts;
                xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat,
-                             &xpp, &xecfg, &ecb);
+                             &xpp, &xecfg);
        }
 
  free_and_return:
@@ -1679,6 +2122,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
        data.lineno = 0;
        data.o = o;
        data.ws_rule = whitespace_rule(attr_path);
+       data.conflict_marker_size = ll_merge_marker_size(attr_path);
 
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
@@ -1695,20 +2139,30 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
                /* Crazy xdl interfaces.. */
                xpparam_t xpp;
                xdemitconf_t xecfg;
-               xdemitcb_t ecb;
 
                memset(&xpp, 0, sizeof(xpp));
                memset(&xecfg, 0, sizeof(xecfg));
                xecfg.ctxlen = 1; /* at least one context line */
-               xpp.flags = XDF_NEED_MINIMAL;
+               xpp.flags = 0;
                xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data,
-                             &xpp, &xecfg, &ecb);
-
-               if ((data.ws_rule & WS_TRAILING_SPACE) &&
-                   data.trailing_blanks_start) {
-                       fprintf(o->file, "%s:%d: ends with blank lines.\n",
-                               data.filename, data.trailing_blanks_start);
-                       data.status = 1; /* report errors */
+                             &xpp, &xecfg);
+
+               if (data.ws_rule & WS_BLANK_AT_EOF) {
+                       struct emit_callback ecbdata;
+                       int blank_at_eof;
+
+                       ecbdata.ws_rule = data.ws_rule;
+                       check_blank_at_eof(&mf1, &mf2, &ecbdata);
+                       blank_at_eof = ecbdata.blank_at_eof_in_preimage;
+
+                       if (blank_at_eof) {
+                               static char *err;
+                               if (!err)
+                                       err = whitespace_error_string(WS_BLANK_AT_EOF);
+                               fprintf(o->file, "%s:%d: %s.\n",
+                                       data.filename, blank_at_eof, err);
+                               data.status = 1; /* report errors */
+                       }
                }
        }
  free_and_return:
@@ -1805,7 +2259,7 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int
         * If ce is marked as "assume unchanged", there is no
         * guarantee that work tree matches what we are looking for.
         */
-       if (ce->ce_flags & CE_VALID)
+       if ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce))
                return 0;
 
        /*
@@ -1837,9 +2291,14 @@ static int populate_from_stdin(struct diff_filespec *s)
 static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
 {
        int len;
-       char *data = xmalloc(100);
+       char *data = xmalloc(100), *dirty = "";
+
+       /* Are we looking at the work tree? */
+       if (s->dirty_submodule)
+               dirty = "-dirty";
+
        len = snprintf(data, 100,
-               "Subproject commit %s\n", sha1_to_hex(s->sha1));
+                      "Subproject commit %s%s\n", sha1_to_hex(s->sha1), dirty);
        s->data = data;
        s->size = len;
        s->should_free = 1;
@@ -1965,23 +2424,33 @@ static void prep_temp_blob(const char *path, struct diff_tempfile *temp,
 {
        int fd;
        struct strbuf buf = STRBUF_INIT;
+       struct strbuf template = STRBUF_INIT;
+       char *path_dup = xstrdup(path);
+       const char *base = basename(path_dup);
+
+       /* Generate "XXXXXX_basename.ext" */
+       strbuf_addstr(&template, "XXXXXX_");
+       strbuf_addstr(&template, base);
 
-       fd = git_mkstemp(temp->tmp_path, PATH_MAX, ".diff_XXXXXX");
+       fd = git_mkstemps(temp->tmp_path, PATH_MAX, template.buf,
+                       strlen(base) + 1);
        if (fd < 0)
-               die("unable to create temp-file: %s", strerror(errno));
+               die_errno("unable to create temp-file");
        if (convert_to_working_tree(path,
                        (const char *)blob, (size_t)size, &buf)) {
                blob = buf.buf;
                size = buf.len;
        }
        if (write_in_full(fd, blob, size) != size)
-               die("unable to write temp-file");
+               die_errno("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);
        strbuf_release(&buf);
+       strbuf_release(&template);
+       free(path_dup);
 }
 
 static struct diff_tempfile *prepare_temp_file(const char *name,
@@ -2012,21 +2481,18 @@ static struct diff_tempfile *prepare_temp_file(const char *name,
                if (lstat(name, &st) < 0) {
                        if (errno == ENOENT)
                                goto not_a_valid_file;
-                       die("stat(%s): %s", name, strerror(errno));
+                       die_errno("stat(%s)", name);
                }
                if (S_ISLNK(st.st_mode)) {
-                       int ret;
-                       char buf[PATH_MAX + 1]; /* ought to be SYMLINK_MAX */
-                       ret = readlink(name, buf, sizeof(buf));
-                       if (ret < 0)
-                               die("readlink(%s)", name);
-                       if (ret == sizeof(buf))
-                               die("symlink too long: %s", name);
-                       prep_temp_blob(name, temp, buf, ret,
+                       struct strbuf sb = STRBUF_INIT;
+                       if (strbuf_readlink(&sb, name, st.st_size) < 0)
+                               die_errno("readlink(%s)", name);
+                       prep_temp_blob(name, temp, sb.buf, sb.len,
                                       (one->sha1_valid ?
                                        one->sha1 : null_sha1),
                                       (one->sha1_valid ?
                                        one->mode : S_IFLNK));
+                       strbuf_release(&sb);
                }
                else {
                        /* we can borrow from the file in the work tree */
@@ -2095,7 +2561,7 @@ static void run_external_diff(const char *pgm,
        }
        *arg = NULL;
        fflush(NULL);
-       retval = run_command_v_opt(spawn_arg, 0);
+       retval = run_command_v_opt(spawn_arg, RUN_USING_SHELL);
        remove_tempfile();
        if (retval) {
                fprintf(stderr, "external diff died, stopping at %s.\n", name);
@@ -2114,36 +2580,53 @@ static void fill_metainfo(struct strbuf *msg,
                          struct diff_filespec *one,
                          struct diff_filespec *two,
                          struct diff_options *o,
-                         struct diff_filepair *p)
+                         struct diff_filepair *p,
+                         int *must_show_header,
+                         int use_color)
 {
+       const char *set = diff_get_color(use_color, DIFF_METAINFO);
+       const char *reset = diff_get_color(use_color, DIFF_RESET);
+       struct strbuf *msgbuf;
+       char *line_prefix = "";
+
+       *must_show_header = 1;
+       if (o->output_prefix) {
+               msgbuf = o->output_prefix(o, o->output_prefix_data);
+               line_prefix = msgbuf->buf;
+       }
        strbuf_init(msg, PATH_MAX * 2 + 300);
        switch (p->status) {
        case DIFF_STATUS_COPIED:
-               strbuf_addf(msg, "similarity index %d%%", similarity_index(p));
-               strbuf_addstr(msg, "\ncopy from ");
+               strbuf_addf(msg, "%s%ssimilarity index %d%%",
+                           line_prefix, set, similarity_index(p));
+               strbuf_addf(msg, "%s\n%s%scopy from ",
+                           reset,  line_prefix, set);
                quote_c_style(name, msg, NULL, 0);
-               strbuf_addstr(msg, "\ncopy to ");
+               strbuf_addf(msg, "%s\n%s%scopy to ", reset, line_prefix, set);
                quote_c_style(other, msg, NULL, 0);
-               strbuf_addch(msg, '\n');
+               strbuf_addf(msg, "%s\n", reset);
                break;
        case DIFF_STATUS_RENAMED:
-               strbuf_addf(msg, "similarity index %d%%", similarity_index(p));
-               strbuf_addstr(msg, "\nrename from ");
+               strbuf_addf(msg, "%s%ssimilarity index %d%%",
+                           line_prefix, set, similarity_index(p));
+               strbuf_addf(msg, "%s\n%s%srename from ",
+                           reset, line_prefix, set);
                quote_c_style(name, msg, NULL, 0);
-               strbuf_addstr(msg, "\nrename to ");
+               strbuf_addf(msg, "%s\n%s%srename to ",
+                           reset, line_prefix, set);
                quote_c_style(other, msg, NULL, 0);
-               strbuf_addch(msg, '\n');
+               strbuf_addf(msg, "%s\n", reset);
                break;
        case DIFF_STATUS_MODIFIED:
                if (p->score) {
-                       strbuf_addf(msg, "dissimilarity index %d%%\n",
-                                   similarity_index(p));
+                       strbuf_addf(msg, "%s%sdissimilarity index %d%%%s\n",
+                                   line_prefix,
+                                   set, similarity_index(p), reset);
                        break;
                }
                /* fallthru */
        default:
-               /* nothing */
-               ;
+               *must_show_header = 0;
        }
        if (one && two && hashcmp(one->sha1, two->sha1)) {
                int abbrev = DIFF_OPT_TST(o, FULL_INDEX) ? 40 : DEFAULT_ABBREV;
@@ -2154,15 +2637,13 @@ static void fill_metainfo(struct strbuf *msg,
                            (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two)))
                                abbrev = 40;
                }
-               strbuf_addf(msg, "index %.*s..%.*s",
-                           abbrev, sha1_to_hex(one->sha1),
-                           abbrev, sha1_to_hex(two->sha1));
+               strbuf_addf(msg, "%s%sindex %s..", line_prefix, set,
+                           find_unique_abbrev(one->sha1, abbrev));
+               strbuf_addstr(msg, find_unique_abbrev(two->sha1, abbrev));
                if (one->mode == two->mode)
                        strbuf_addf(msg, " %06o", one->mode);
-               strbuf_addch(msg, '\n');
+               strbuf_addf(msg, "%s\n", reset);
        }
-       if (msg->len)
-               strbuf_setlen(msg, msg->len - 1);
 }
 
 static void run_diff_cmd(const char *pgm,
@@ -2177,11 +2658,7 @@ static void run_diff_cmd(const char *pgm,
 {
        const char *xfrm_msg = NULL;
        int complete_rewrite = (p->status == DIFF_STATUS_MODIFIED) && p->score;
-
-       if (msg) {
-               fill_metainfo(msg, name, other, one, two, o, p);
-               xfrm_msg = msg->len ? msg->buf : NULL;
-       }
+       int must_show_header = 0;
 
        if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL))
                pgm = NULL;
@@ -2191,6 +2668,17 @@ static void run_diff_cmd(const char *pgm,
                        pgm = drv->external;
        }
 
+       if (msg) {
+               /*
+                * don't use colors when the header is intended for an
+                * external diff driver
+                */
+               fill_metainfo(msg, name, other, one, two, o, p,
+                             &must_show_header,
+                             DIFF_OPT_TST(o, COLOR_DIFF) && !pgm);
+               xfrm_msg = msg->len ? msg->buf : NULL;
+       }
+
        if (pgm) {
                run_external_diff(pgm, name, other, one, two, xfrm_msg,
                                  complete_rewrite);
@@ -2198,7 +2686,8 @@ static void run_diff_cmd(const char *pgm,
        }
        if (one && two)
                builtin_diff(name, other ? other : name,
-                            one, two, xfrm_msg, o, complete_rewrite);
+                            one, two, xfrm_msg, must_show_header,
+                            o, complete_rewrite);
        else
                fprintf(o->file, "* Unmerged path %s\n", name);
 }
@@ -2213,7 +2702,7 @@ static void diff_fill_sha1_info(struct diff_filespec *one)
                                return;
                        }
                        if (lstat(one->path, &st) < 0)
-                               die("stat %s", one->path);
+                               die_errno("stat '%s'", one->path);
                        if (index_path(one->sha1, one->path, &st, 0))
                                die("cannot hash %s", one->path);
                }
@@ -2225,10 +2714,16 @@ static void diff_fill_sha1_info(struct diff_filespec *one)
 static void strip_prefix(int prefix_length, const char **namep, const char **otherp)
 {
        /* Strip the prefix but do not molest /dev/null and absolute paths */
-       if (*namep && **namep != '/')
+       if (*namep && **namep != '/') {
                *namep += prefix_length;
-       if (*otherp && **otherp != '/')
+               if (**namep == '/')
+                       ++*namep;
+       }
+       if (*otherp && **otherp != '/') {
                *otherp += prefix_length;
+               if (**otherp == '/')
+                       ++*otherp;
+       }
 }
 
 static void run_diff(struct diff_filepair *p, struct diff_options *o)
@@ -2334,7 +2829,7 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
 
 void diff_setup(struct diff_options *options)
 {
-       memset(options, 0, sizeof(*options));
+       memcpy(options, &default_diff_options, sizeof(*options));
 
        options->file = stdout;
 
@@ -2350,7 +2845,9 @@ void diff_setup(struct diff_options *options)
                DIFF_OPT_SET(options, COLOR_DIFF);
        options->detect_rename = diff_detect_rename_default;
 
-       if (!diff_mnemonic_prefix) {
+       if (diff_no_prefix) {
+               options->a_prefix = options->b_prefix = "";
+       } else if (!diff_mnemonic_prefix) {
                options->a_prefix = "a/";
                options->b_prefix = "b/";
        }
@@ -2371,6 +2868,20 @@ int diff_setup_done(struct diff_options *options)
        if (count > 1)
                die("--name-only, --name-status, --check and -s are mutually exclusive");
 
+       /*
+        * Most of the time we can say "there are changes"
+        * only by checking if there are changed paths, but
+        * --ignore-whitespace* options force us to look
+        * inside contents.
+        */
+
+       if (DIFF_XDL_TST(options, IGNORE_WHITESPACE) ||
+           DIFF_XDL_TST(options, IGNORE_WHITESPACE_CHANGE) ||
+           DIFF_XDL_TST(options, IGNORE_WHITESPACE_AT_EOL))
+               DIFF_OPT_SET(options, DIFF_FROM_CONTENTS);
+       else
+               DIFF_OPT_CLR(options, DIFF_FROM_CONTENTS);
+
        if (DIFF_OPT_TST(options, FIND_COPIES_HARDER))
                options->detect_rename = DIFF_DETECT_COPY;
 
@@ -2410,6 +2921,12 @@ int diff_setup_done(struct diff_options *options)
         */
        if (options->pickaxe)
                DIFF_OPT_SET(options, RECURSIVE);
+       /*
+        * When patches are generated, submodules diffed against the work tree
+        * must be checked for dirtiness too so it can be shown in the output
+        */
+       if (options->output_format & DIFF_FORMAT_PATCH)
+               DIFF_OPT_SET(options, DIRTY_SUBMODULES);
 
        if (options->detect_rename && options->rename_limit < 0)
                options->rename_limit = diff_rename_limit_default;
@@ -2431,7 +2948,7 @@ int diff_setup_done(struct diff_options *options)
         * to have found.  It does not make sense not to return with
         * exit code in such a case either.
         */
-       if (DIFF_OPT_TST(options, QUIET)) {
+       if (DIFF_OPT_TST(options, QUICK)) {
                options->output_format = DIFF_FORMAT_NO_OUTPUT;
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        }
@@ -2488,12 +3005,103 @@ static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *va
 
 static int diff_scoreopt_parse(const char *opt);
 
+static inline int short_opt(char opt, const char **argv,
+                           const char **optarg)
+{
+       const char *arg = argv[0];
+       if (arg[0] != '-' || arg[1] != opt)
+               return 0;
+       if (arg[2] != '\0') {
+               *optarg = arg + 2;
+               return 1;
+       }
+       if (!argv[1])
+               die("Option '%c' requires a value", opt);
+       *optarg = argv[1];
+       return 2;
+}
+
+int parse_long_opt(const char *opt, const char **argv,
+                  const char **optarg)
+{
+       const char *arg = argv[0];
+       if (arg[0] != '-' || arg[1] != '-')
+               return 0;
+       arg += strlen("--");
+       if (prefixcmp(arg, opt))
+               return 0;
+       arg += strlen(opt);
+       if (*arg == '=') { /* sticked form: --option=value */
+               *optarg = arg + 1;
+               return 1;
+       }
+       if (*arg != '\0')
+               return 0;
+       /* separate form: --option value */
+       if (!argv[1])
+               die("Option '--%s' requires a value", opt);
+       *optarg = argv[1];
+       return 2;
+}
+
+static int stat_opt(struct diff_options *options, const char **av)
+{
+       const char *arg = av[0];
+       char *end;
+       int width = options->stat_width;
+       int name_width = options->stat_name_width;
+       int argcount = 1;
+
+       arg += strlen("--stat");
+       end = (char *)arg;
+
+       switch (*arg) {
+       case '-':
+               if (!prefixcmp(arg, "-width")) {
+                       arg += strlen("-width");
+                       if (*arg == '=')
+                               width = strtoul(arg + 1, &end, 10);
+                       else if (!*arg && !av[1])
+                               die("Option '--stat-width' requires a value");
+                       else if (!*arg) {
+                               width = strtoul(av[1], &end, 10);
+                               argcount = 2;
+                       }
+               } else if (!prefixcmp(arg, "-name-width")) {
+                       arg += strlen("-name-width");
+                       if (*arg == '=')
+                               name_width = strtoul(arg + 1, &end, 10);
+                       else if (!*arg && !av[1])
+                               die("Option '--stat-name-width' requires a value");
+                       else if (!*arg) {
+                               name_width = strtoul(av[1], &end, 10);
+                               argcount = 2;
+                       }
+               }
+               break;
+       case '=':
+               width = strtoul(arg+1, &end, 10);
+               if (*end == ',')
+                       name_width = strtoul(end+1, &end, 10);
+       }
+
+       /* Important! This checks all the error cases! */
+       if (*end)
+               return 0;
+       options->output_format |= DIFF_FORMAT_DIFFSTAT;
+       options->stat_name_width = name_width;
+       options->stat_width = width;
+       return argcount;
+}
+
 int diff_opt_parse(struct diff_options *options, const char **av, int ac)
 {
        const char *arg = av[0];
+       const char *optarg;
+       int argcount;
 
        /* Output format options */
-       if (!strcmp(arg, "-p") || !strcmp(arg, "-u"))
+       if (!strcmp(arg, "-p") || !strcmp(arg, "-u") || !strcmp(arg, "--patch"))
                options->output_format |= DIFF_FORMAT_PATCH;
        else if (opt_arg(arg, 'U', "unified", &options->context))
                options->output_format |= DIFF_FORMAT_PATCH;
@@ -2527,33 +3135,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                options->output_format |= DIFF_FORMAT_NAME_STATUS;
        else if (!strcmp(arg, "-s"))
                options->output_format |= DIFF_FORMAT_NO_OUTPUT;
-       else if (!prefixcmp(arg, "--stat")) {
-               char *end;
-               int width = options->stat_width;
-               int name_width = options->stat_name_width;
-               arg += 6;
-               end = (char *)arg;
-
-               switch (*arg) {
-               case '-':
-                       if (!prefixcmp(arg, "-width="))
-                               width = strtoul(arg + 7, &end, 10);
-                       else if (!prefixcmp(arg, "-name-width="))
-                               name_width = strtoul(arg + 12, &end, 10);
-                       break;
-               case '=':
-                       width = strtoul(arg+1, &end, 10);
-                       if (*end == ',')
-                               name_width = strtoul(end+1, &end, 10);
-               }
-
-               /* Important! This checks all the error cases! */
-               if (*end)
-                       return 0;
-               options->output_format |= DIFF_FORMAT_DIFFSTAT;
-               options->stat_name_width = name_width;
-               options->stat_width = width;
-       }
+       else if (!prefixcmp(arg, "--stat"))
+               /* --stat, --stat-width, or --stat-name-width */
+               return stat_opt(options, av);
 
        /* renames options */
        else if (!prefixcmp(arg, "-B")) {
@@ -2608,21 +3192,55 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                DIFF_OPT_SET(options, FOLLOW_RENAMES);
        else if (!strcmp(arg, "--color"))
                DIFF_OPT_SET(options, COLOR_DIFF);
+       else if (!prefixcmp(arg, "--color=")) {
+               int value = git_config_colorbool(NULL, arg+8, -1);
+               if (value == 0)
+                       DIFF_OPT_CLR(options, COLOR_DIFF);
+               else if (value > 0)
+                       DIFF_OPT_SET(options, COLOR_DIFF);
+               else
+                       return error("option `color' expects \"always\", \"auto\", or \"never\"");
+       }
        else if (!strcmp(arg, "--no-color"))
                DIFF_OPT_CLR(options, COLOR_DIFF);
        else if (!strcmp(arg, "--color-words")) {
                DIFF_OPT_SET(options, COLOR_DIFF);
-               DIFF_OPT_SET(options, COLOR_DIFF_WORDS);
+               options->word_diff = DIFF_WORDS_COLOR;
        }
        else if (!prefixcmp(arg, "--color-words=")) {
                DIFF_OPT_SET(options, COLOR_DIFF);
-               DIFF_OPT_SET(options, COLOR_DIFF_WORDS);
+               options->word_diff = DIFF_WORDS_COLOR;
                options->word_regex = arg + 14;
        }
+       else if (!strcmp(arg, "--word-diff")) {
+               if (options->word_diff == DIFF_WORDS_NONE)
+                       options->word_diff = DIFF_WORDS_PLAIN;
+       }
+       else if (!prefixcmp(arg, "--word-diff=")) {
+               const char *type = arg + 12;
+               if (!strcmp(type, "plain"))
+                       options->word_diff = DIFF_WORDS_PLAIN;
+               else if (!strcmp(type, "color")) {
+                       DIFF_OPT_SET(options, COLOR_DIFF);
+                       options->word_diff = DIFF_WORDS_COLOR;
+               }
+               else if (!strcmp(type, "porcelain"))
+                       options->word_diff = DIFF_WORDS_PORCELAIN;
+               else if (!strcmp(type, "none"))
+                       options->word_diff = DIFF_WORDS_NONE;
+               else
+                       die("bad --word-diff argument: %s", type);
+       }
+       else if ((argcount = parse_long_opt("word-diff-regex", av, &optarg))) {
+               if (options->word_diff == DIFF_WORDS_NONE)
+                       options->word_diff = DIFF_WORDS_PLAIN;
+               options->word_regex = optarg;
+               return argcount;
+       }
        else if (!strcmp(arg, "--exit-code"))
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        else if (!strcmp(arg, "--quiet"))
-               DIFF_OPT_SET(options, QUIET);
+               DIFF_OPT_SET(options, QUICK);
        else if (!strcmp(arg, "--ext-diff"))
                DIFF_OPT_SET(options, ALLOW_EXTERNAL);
        else if (!strcmp(arg, "--no-ext-diff"))
@@ -2631,24 +3249,42 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                DIFF_OPT_SET(options, ALLOW_TEXTCONV);
        else if (!strcmp(arg, "--no-textconv"))
                DIFF_OPT_CLR(options, ALLOW_TEXTCONV);
-       else if (!strcmp(arg, "--ignore-submodules"))
-               DIFF_OPT_SET(options, IGNORE_SUBMODULES);
+       else if (!strcmp(arg, "--ignore-submodules")) {
+               DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG);
+               handle_ignore_submodules_arg(options, "all");
+       } else if (!prefixcmp(arg, "--ignore-submodules=")) {
+               DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG);
+               handle_ignore_submodules_arg(options, arg + 20);
+       } else if (!strcmp(arg, "--submodule"))
+               DIFF_OPT_SET(options, SUBMODULE_LOG);
+       else if (!prefixcmp(arg, "--submodule=")) {
+               if (!strcmp(arg + 12, "log"))
+                       DIFF_OPT_SET(options, SUBMODULE_LOG);
+       }
 
        /* misc options */
        else if (!strcmp(arg, "-z"))
                options->line_termination = 0;
-       else if (!prefixcmp(arg, "-l"))
-               options->rename_limit = strtoul(arg+2, NULL, 10);
-       else if (!prefixcmp(arg, "-S"))
-               options->pickaxe = arg + 2;
+       else if ((argcount = short_opt('l', av, &optarg))) {
+               options->rename_limit = strtoul(optarg, NULL, 10);
+               return argcount;
+       }
+       else if ((argcount = short_opt('S', av, &optarg))) {
+               options->pickaxe = optarg;
+               return argcount;
+       }
        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 (!prefixcmp(arg, "-O"))
-               options->orderfile = arg + 2;
-       else if (!prefixcmp(arg, "--diff-filter="))
-               options->filter = arg + 14;
+       else if ((argcount = short_opt('O', av, &optarg))) {
+               options->orderfile = optarg;
+               return argcount;
+       }
+       else if ((argcount = parse_long_opt("diff-filter", av, &optarg))) {
+               options->filter = optarg;
+               return argcount;
+       }
        else if (!strcmp(arg, "--abbrev"))
                options->abbrev = DEFAULT_ABBREV;
        else if (!prefixcmp(arg, "--abbrev=")) {
@@ -2658,18 +3294,25 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                else if (40 < options->abbrev)
                        options->abbrev = 40;
        }
-       else if (!prefixcmp(arg, "--src-prefix="))
-               options->a_prefix = arg + 13;
-       else if (!prefixcmp(arg, "--dst-prefix="))
-               options->b_prefix = arg + 13;
+       else if ((argcount = parse_long_opt("src-prefix", av, &optarg))) {
+               options->a_prefix = optarg;
+               return argcount;
+       }
+       else if ((argcount = parse_long_opt("dst-prefix", av, &optarg))) {
+               options->b_prefix = optarg;
+               return argcount;
+       }
        else if (!strcmp(arg, "--no-prefix"))
                options->a_prefix = options->b_prefix = "";
        else if (opt_arg(arg, '\0', "inter-hunk-context",
                         &options->interhunkcontext))
                ;
-       else if (!prefixcmp(arg, "--output=")) {
-               options->file = fopen(arg + strlen("--output="), "w");
+       else if ((argcount = parse_long_opt("output", av, &optarg))) {
+               options->file = fopen(optarg, "w");
+               if (!options->file)
+                       die_errno("Could not open '%s'", arg + strlen("--output="));
                options->close_file = 1;
+               return argcount;
        } else
                return 0;
        return 1;
@@ -2684,7 +3327,7 @@ static int parse_num(const char **cp_p)
        num = 0;
        scale = 1;
        dot = 0;
-       for(;;) {
+       for (;;) {
                ch = *cp;
                if ( !dot && ch == '.' ) {
                        scale = 1;
@@ -2797,6 +3440,11 @@ static void diff_flush_raw(struct diff_filepair *p, struct diff_options *opt)
 {
        int line_termination = opt->line_termination;
        int inter_name_termination = line_termination ? '\t' : '\0';
+       if (opt->output_prefix) {
+               struct strbuf *msg = NULL;
+               msg = opt->output_prefix(opt, opt->output_prefix_data);
+               fprintf(opt->file, "%s", msg->buf);
+       }
 
        if (!(opt->output_format & DIFF_FORMAT_NAME_STATUS)) {
                fprintf(opt->file, ":%06o %06o %s ", p->one->mode, p->two->mode,
@@ -2851,7 +3499,8 @@ int diff_unmodified_pair(struct diff_filepair *p)
         * dealing with a change.
         */
        if (one->sha1_valid && two->sha1_valid &&
-           !hashcmp(one->sha1, two->sha1))
+           !hashcmp(one->sha1, two->sha1) &&
+           !one->dirty_submodule && !two->dirty_submodule)
                return 1; /* no change */
        if (!one->sha1_valid && !two->sha1_valid)
                return 1; /* both look at the same file on the filesystem. */
@@ -2986,6 +3635,8 @@ static void diff_resolve_rename_copy(void)
                }
                else if (hashcmp(p->one->sha1, p->two->sha1) ||
                         p->one->mode != p->two->mode ||
+                        p->one->dirty_submodule ||
+                        p->two->dirty_submodule ||
                         is_null_sha1(p->one->sha1))
                        p->status = DIFF_STATUS_MODIFIED;
                else {
@@ -3039,48 +3690,62 @@ static void show_file_mode_name(FILE *file, const char *newdelete, struct diff_f
 }
 
 
-static void show_mode_change(FILE *file, struct diff_filepair *p, int show_name)
+static void show_mode_change(FILE *file, struct diff_filepair *p, int show_name,
+               const char *line_prefix)
 {
        if (p->one->mode && p->two->mode && p->one->mode != p->two->mode) {
-               fprintf(file, " mode change %06o => %06o%c", p->one->mode, p->two->mode,
-                       show_name ? ' ' : '\n');
+               fprintf(file, "%s mode change %06o => %06o%c", line_prefix, p->one->mode,
+                       p->two->mode, show_name ? ' ' : '\n');
                if (show_name) {
                        write_name_quoted(p->two->path, file, '\n');
                }
        }
 }
 
-static void show_rename_copy(FILE *file, const char *renamecopy, struct diff_filepair *p)
+static void show_rename_copy(FILE *file, const char *renamecopy, struct diff_filepair *p,
+                       const char *line_prefix)
 {
        char *names = pprint_rename(p->one->path, p->two->path);
 
        fprintf(file, " %s %s (%d%%)\n", renamecopy, names, similarity_index(p));
        free(names);
-       show_mode_change(file, p, 0);
+       show_mode_change(file, p, 0, line_prefix);
 }
 
-static void diff_summary(FILE *file, struct diff_filepair *p)
+static void diff_summary(struct diff_options *opt, struct diff_filepair *p)
 {
+       FILE *file = opt->file;
+       char *line_prefix = "";
+
+       if (opt->output_prefix) {
+               struct strbuf *buf = opt->output_prefix(opt, opt->output_prefix_data);
+               line_prefix = buf->buf;
+       }
+
        switch(p->status) {
        case DIFF_STATUS_DELETED:
+               fputs(line_prefix, file);
                show_file_mode_name(file, "delete", p->one);
                break;
        case DIFF_STATUS_ADDED:
+               fputs(line_prefix, file);
                show_file_mode_name(file, "create", p->two);
                break;
        case DIFF_STATUS_COPIED:
-               show_rename_copy(file, "copy", p);
+               fputs(line_prefix, file);
+               show_rename_copy(file, "copy", p, line_prefix);
                break;
        case DIFF_STATUS_RENAMED:
-               show_rename_copy(file, "rename", p);
+               fputs(line_prefix, file);
+               show_rename_copy(file, "rename", p, line_prefix);
                break;
        default:
                if (p->score) {
-                       fputs(" rewrite ", file);
+                       fprintf(file, "%s rewrite ", line_prefix);
                        write_name_quoted(p->two->path, file, ' ');
                        fprintf(file, "(%d%%)\n", similarity_index(p));
                }
-               show_mode_change(file, p, !p->score);
+               show_mode_change(file, p, !p->score, line_prefix);
                break;
        }
 }
@@ -3134,7 +3799,6 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
        for (i = 0; i < q->nr; i++) {
                xpparam_t xpp;
                xdemitconf_t xecfg;
-               xdemitcb_t ecb;
                mmfile_t mf1, mf2;
                struct diff_filepair *p = q->queue[i];
                int len1, len2;
@@ -3192,11 +3856,18 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
                                        len2, p->two->path);
                git_SHA1_Update(&ctx, buffer, len1);
 
-               xpp.flags = XDF_NEED_MINIMAL;
+               if (diff_filespec_is_binary(p->one) ||
+                   diff_filespec_is_binary(p->two)) {
+                       git_SHA1_Update(&ctx, sha1_to_hex(p->one->sha1), 40);
+                       git_SHA1_Update(&ctx, sha1_to_hex(p->two->sha1), 40);
+                       continue;
+               }
+
+               xpp.flags = 0;
                xecfg.ctxlen = 3;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
                xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data,
-                             &xpp, &xecfg, &ecb);
+                             &xpp, &xecfg);
        }
 
        git_SHA1_Final(sha1, &ctx);
@@ -3213,8 +3884,7 @@ int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1)
                diff_free_filepair(q->queue[i]);
 
        free(q->queue);
-       q->queue = NULL;
-       q->nr = q->alloc = 0;
+       DIFF_QUEUE_CLEAR(q);
 
        return result;
 }
@@ -3291,11 +3961,35 @@ void diff_flush(struct diff_options *options)
                show_dirstat(options);
 
        if (output_format & DIFF_FORMAT_SUMMARY && !is_summary_empty(q)) {
-               for (i = 0; i < q->nr; i++)
-                       diff_summary(options->file, q->queue[i]);
+               for (i = 0; i < q->nr; i++) {
+                       diff_summary(options, q->queue[i]);
+               }
                separator++;
        }
 
+       if (output_format & DIFF_FORMAT_NO_OUTPUT &&
+           DIFF_OPT_TST(options, EXIT_WITH_STATUS) &&
+           DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) {
+               /*
+                * run diff_flush_patch for the exit status. setting
+                * options->file to /dev/null should be safe, becaue we
+                * aren't supposed to produce any output anyway.
+                */
+               if (options->close_file)
+                       fclose(options->file);
+               options->file = fopen("/dev/null", "w");
+               if (!options->file)
+                       die_errno("Could not open /dev/null");
+               options->close_file = 1;
+               for (i = 0; i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+                       if (check_pair_status(p))
+                               diff_flush_patch(p, options);
+                       if (options->found_changes)
+                               break;
+               }
+       }
+
        if (output_format & DIFF_FORMAT_PATCH) {
                if (separator) {
                        putc(options->line_termination, options->file);
@@ -3319,10 +4013,21 @@ void diff_flush(struct diff_options *options)
                diff_free_filepair(q->queue[i]);
 free_queue:
        free(q->queue);
-       q->queue = NULL;
-       q->nr = q->alloc = 0;
+       DIFF_QUEUE_CLEAR(q);
        if (options->close_file)
                fclose(options->file);
+
+       /*
+        * Report the content-level differences with HAS_CHANGES;
+        * diff_addremove/diff_change does not set the bit when
+        * DIFF_FROM_CONTENTS is in effect (e.g. with -w).
+        */
+       if (DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) {
+               if (options->found_changes)
+                       DIFF_OPT_SET(options, HAS_CHANGES);
+               else
+                       DIFF_OPT_CLR(options, HAS_CHANGES);
+       }
 }
 
 static void diffcore_apply_filter(const char *filter)
@@ -3330,8 +4035,7 @@ 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;
+       DIFF_QUEUE_CLEAR(&outq);
 
        if (!filter)
                return;
@@ -3399,14 +4103,13 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt)
        int i;
        struct diff_queue_struct *q = &diff_queued_diff;
        struct diff_queue_struct outq;
-       outq.queue = NULL;
-       outq.nr = outq.alloc = 0;
+       DIFF_QUEUE_CLEAR(&outq);
 
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
 
                /*
-                * 1. Entries that come from stat info dirtyness
+                * 1. Entries that come from stat info dirtiness
                 *    always have both sides (iow, not create/delete),
                 *    one side of the object name is unknown, with
                 *    the same mode and size.  Keep the ones that
@@ -3442,27 +4145,51 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt)
        *q = outq;
 }
 
+static int diffnamecmp(const void *a_, const void *b_)
+{
+       const struct diff_filepair *a = *((const struct diff_filepair **)a_);
+       const struct diff_filepair *b = *((const struct diff_filepair **)b_);
+       const char *name_a, *name_b;
+
+       name_a = a->one ? a->one->path : a->two->path;
+       name_b = b->one ? b->one->path : b->two->path;
+       return strcmp(name_a, name_b);
+}
+
+void diffcore_fix_diff_index(struct diff_options *options)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       qsort(q->queue, q->nr, sizeof(q->queue[0]), diffnamecmp);
+}
+
 void diffcore_std(struct diff_options *options)
 {
        if (options->skip_stat_unmatch)
                diffcore_skip_stat_unmatch(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->found_follow) {
+               /* See try_to_follow_renames() in tree-diff.c */
+               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();
+       if (!options->found_follow)
+               /* See try_to_follow_renames() in tree-diff.c */
+               diff_resolve_rename_copy();
        diffcore_apply_filter(options->filter);
 
-       if (diff_queued_diff.nr)
+       if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
                DIFF_OPT_SET(options, HAS_CHANGES);
        else
                DIFF_OPT_CLR(options, HAS_CHANGES);
+
+       options->found_follow = 0;
 }
 
 int diff_result_code(struct diff_options *opt, int status)
@@ -3480,14 +4207,32 @@ int diff_result_code(struct diff_options *opt, int status)
        return result;
 }
 
+/*
+ * Shall changes to this submodule be ignored?
+ *
+ * Submodule changes can be configured to be ignored separately for each path,
+ * but that configuration can be overridden from the command line.
+ */
+static int is_submodule_ignored(const char *path, struct diff_options *options)
+{
+       int ignored = 0;
+       unsigned orig_flags = options->flags;
+       if (!DIFF_OPT_TST(options, OVERRIDE_SUBMODULE_CONFIG))
+               set_diffopt_flags_from_submodule_config(options, path);
+       if (DIFF_OPT_TST(options, IGNORE_SUBMODULES))
+               ignored = 1;
+       options->flags = orig_flags;
+       return ignored;
+}
+
 void diff_addremove(struct diff_options *options,
                    int addremove, unsigned mode,
                    const unsigned char *sha1,
-                   const char *concatpath)
+                   const char *concatpath, unsigned dirty_submodule)
 {
        struct diff_filespec *one, *two;
 
-       if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(mode))
+       if (S_ISGITLINK(mode) && is_submodule_ignored(concatpath, options))
                return;
 
        /* This may look odd, but it is a preparation for
@@ -3515,23 +4260,27 @@ void diff_addremove(struct diff_options *options,
 
        if (addremove != '+')
                fill_filespec(one, sha1, mode);
-       if (addremove != '-')
+       if (addremove != '-') {
                fill_filespec(two, sha1, mode);
+               two->dirty_submodule = dirty_submodule;
+       }
 
        diff_queue(&diff_queued_diff, one, two);
-       DIFF_OPT_SET(options, HAS_CHANGES);
+       if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
+               DIFF_OPT_SET(options, HAS_CHANGES);
 }
 
 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 *concatpath)
+                const char *concatpath,
+                unsigned old_dirty_submodule, unsigned new_dirty_submodule)
 {
        struct diff_filespec *one, *two;
 
-       if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(old_mode)
-                       && S_ISGITLINK(new_mode))
+       if (S_ISGITLINK(old_mode) && S_ISGITLINK(new_mode) &&
+           is_submodule_ignored(concatpath, options))
                return;
 
        if (DIFF_OPT_TST(options, REVERSE_DIFF)) {
@@ -3539,6 +4288,8 @@ void diff_change(struct diff_options *options,
                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;
+               tmp = old_dirty_submodule; old_dirty_submodule = new_dirty_submodule;
+                       new_dirty_submodule = tmp;
        }
 
        if (options->prefix &&
@@ -3549,9 +4300,12 @@ void diff_change(struct diff_options *options,
        two = alloc_filespec(concatpath);
        fill_filespec(one, old_sha1, old_mode);
        fill_filespec(two, new_sha1, new_mode);
+       one->dirty_submodule = old_dirty_submodule;
+       two->dirty_submodule = new_dirty_submodule;
 
        diff_queue(&diff_queued_diff, one, two);
-       DIFF_OPT_SET(options, HAS_CHANGES);
+       if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
+               DIFF_OPT_SET(options, HAS_CHANGES);
 }
 
 void diff_unmerge(struct diff_options *options,
@@ -3578,6 +4332,7 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec,
        const char **arg = argv;
        struct child_process child;
        struct strbuf buf = STRBUF_INIT;
+       int err = 0;
 
        temp = prepare_temp_file(spec->path, spec);
        *arg++ = pgm;
@@ -3585,16 +4340,68 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec,
        *arg = NULL;
 
        memset(&child, 0, sizeof(child));
+       child.use_shell = 1;
        child.argv = argv;
        child.out = -1;
-       if (start_command(&child) != 0 ||
-           strbuf_read(&buf, child.out, 0) < 0 ||
-           finish_command(&child) != 0) {
+       if (start_command(&child)) {
+               remove_tempfile();
+               return NULL;
+       }
+
+       if (strbuf_read(&buf, child.out, 0) < 0)
+               err = error("error reading from textconv command '%s'", pgm);
+       close(child.out);
+
+       if (finish_command(&child) || err) {
+               strbuf_release(&buf);
                remove_tempfile();
-               error("error running textconv command '%s'", pgm);
                return NULL;
        }
        remove_tempfile();
 
        return strbuf_detach(&buf, outsize);
 }
+
+size_t fill_textconv(struct userdiff_driver *driver,
+                    struct diff_filespec *df,
+                    char **outbuf)
+{
+       size_t size;
+
+       if (!driver || !driver->textconv) {
+               if (!DIFF_FILE_VALID(df)) {
+                       *outbuf = "";
+                       return 0;
+               }
+               if (diff_populate_filespec(df, 0))
+                       die("unable to read files to diff");
+               *outbuf = df->data;
+               return df->size;
+       }
+
+       if (driver->textconv_cache) {
+               *outbuf = notes_cache_get(driver->textconv_cache, df->sha1,
+                                         &size);
+               if (*outbuf)
+                       return size;
+       }
+
+       *outbuf = run_textconv(driver->textconv, df, &size);
+       if (!*outbuf)
+               die("unable to read files to diff");
+
+       if (driver->textconv_cache) {
+               /* ignore errors, as we might be in a readonly repository */
+               notes_cache_put(driver->textconv_cache, df->sha1, *outbuf,
+                               size);
+               /*
+                * we could save up changes and flush them all at the end,
+                * but we would need an extra call after all diffing is done.
+                * Since generating a cache entry is the slow path anyway,
+                * this extra overhead probably isn't a big deal.
+                */
+               notes_cache_write(driver->textconv_cache);
+       }
+
+       return size;
+}
diff --git a/diff.h b/diff.h
index 6616877ee5d15a8101cbdea0271cc18f8837d2f1..bf2f44d840735684c89f654ee6f3b3a935af2c55 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -9,21 +9,27 @@
 struct rev_info;
 struct diff_options;
 struct diff_queue_struct;
+struct strbuf;
+struct diff_filespec;
+struct userdiff_driver;
 
 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 *fullpath);
+                const char *fullpath,
+                unsigned old_dirty_submodule, unsigned new_dirty_submodule);
 
 typedef void (*add_remove_fn_t)(struct diff_options *options,
                    int addremove, unsigned mode,
                    const unsigned char *sha1,
-                   const char *fullpath);
+                   const char *fullpath, unsigned dirty_submodule);
 
 typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
                struct diff_options *options, void *data);
 
+typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data);
+
 #define DIFF_FORMAT_RAW                0x0001
 #define DIFF_FORMAT_DIFFSTAT   0x0002
 #define DIFF_FORMAT_NUMSTAT    0x0004
@@ -53,9 +59,9 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
 #define DIFF_OPT_FIND_COPIES_HARDER  (1 <<  6)
 #define DIFF_OPT_FOLLOW_RENAMES      (1 <<  7)
 #define DIFF_OPT_COLOR_DIFF          (1 <<  8)
-#define DIFF_OPT_COLOR_DIFF_WORDS    (1 <<  9)
+/* (1 <<  9) unused */
 #define DIFF_OPT_HAS_CHANGES         (1 << 10)
-#define DIFF_OPT_QUIET               (1 << 11)
+#define DIFF_OPT_QUICK               (1 << 11)
 #define DIFF_OPT_NO_INDEX            (1 << 12)
 #define DIFF_OPT_ALLOW_EXTERNAL      (1 << 13)
 #define DIFF_OPT_EXIT_WITH_STATUS    (1 << 14)
@@ -66,6 +72,13 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
 #define DIFF_OPT_DIRSTAT_CUMULATIVE  (1 << 19)
 #define DIFF_OPT_DIRSTAT_BY_FILE     (1 << 20)
 #define DIFF_OPT_ALLOW_TEXTCONV      (1 << 21)
+#define DIFF_OPT_DIFF_FROM_CONTENTS  (1 << 22)
+#define DIFF_OPT_SUBMODULE_LOG       (1 << 23)
+#define DIFF_OPT_DIRTY_SUBMODULES    (1 << 24)
+#define DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES (1 << 25)
+#define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26)
+#define DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG (1 << 27)
+
 #define DIFF_OPT_TST(opts, flag)    ((opts)->flags & DIFF_OPT_##flag)
 #define DIFF_OPT_SET(opts, flag)    ((opts)->flags |= DIFF_OPT_##flag)
 #define DIFF_OPT_CLR(opts, flag)    ((opts)->flags &= ~DIFF_OPT_##flag)
@@ -73,6 +86,13 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
 #define DIFF_XDL_SET(opts, flag)    ((opts)->xdl_opts |= XDF_##flag)
 #define DIFF_XDL_CLR(opts, flag)    ((opts)->xdl_opts &= ~XDF_##flag)
 
+enum diff_words_type {
+       DIFF_WORDS_NONE = 0,
+       DIFF_WORDS_PORCELAIN,
+       DIFF_WORDS_PLAIN,
+       DIFF_WORDS_COLOR
+};
+
 struct diff_options {
        const char *filter;
        const char *orderfile;
@@ -102,10 +122,14 @@ struct diff_options {
        int stat_width;
        int stat_name_width;
        const char *word_regex;
+       enum diff_words_type word_diff;
 
        /* this is set by diffcore for DIFF_FORMAT_PATCH */
        int found_changes;
 
+       /* to support internal diff recursion by --follow hack*/
+       int found_follow;
+
        FILE *file;
        int close_file;
 
@@ -116,6 +140,8 @@ struct diff_options {
        add_remove_fn_t add_remove;
        diff_format_fn_t format_callback;
        void *format_callback_data;
+       diff_prefix_fn_t output_prefix;
+       void *output_prefix_data;
 };
 
 enum color_diff {
@@ -127,6 +153,7 @@ enum color_diff {
        DIFF_FILE_NEW = 5,
        DIFF_COMMIT = 6,
        DIFF_WHITESPACE = 7,
+       DIFF_FUNCINFO = 8
 };
 const char *diff_get_color(int diff_use_color, enum color_diff ix);
 #define diff_get_color_opt(o, ix) \
@@ -173,13 +200,14 @@ extern void diff_addremove(struct diff_options *,
                           int addremove,
                           unsigned mode,
                           const unsigned char *sha1,
-                          const char *fullpath);
+                          const char *fullpath, unsigned dirty_submodule);
 
 extern void diff_change(struct diff_options *,
                        unsigned mode1, unsigned mode2,
                        const unsigned char *sha1,
                        const unsigned char *sha2,
-                       const char *fullpath);
+                       const char *fullpath,
+                       unsigned dirty_submodule1, unsigned dirty_submodule2);
 
 extern void diff_unmerge(struct diff_options *,
                         const char *path,
@@ -190,6 +218,13 @@ extern void diff_unmerge(struct diff_options *,
 #define DIFF_SETUP_USE_CACHE           2
 #define DIFF_SETUP_USE_SIZE_CACHE      4
 
+/*
+ * Poor man's alternative to parse-option, to allow both sticked form
+ * (--option=value) and separate form (--option value).
+ */
+extern int parse_long_opt(const char *opt, const char **argv,
+                        const char **optarg);
+
 extern int git_diff_basic_config(const char *var, const char *value, void *cb);
 extern int git_diff_ui_config(const char *var, const char *value, void *cb);
 extern int diff_use_color_default;
@@ -204,6 +239,7 @@ extern int diff_setup_done(struct diff_options *);
 #define DIFF_PICKAXE_REGEX     2
 
 extern void diffcore_std(struct diff_options *);
+extern void diffcore_fix_diff_index(struct diff_options *);
 
 #define COMMON_DIFF_OPTIONS_HELP \
 "\ncommon diff options:\n" \
@@ -270,4 +306,10 @@ extern void diff_no_index(struct rev_info *, int, const char **, int, const char
 
 extern int index_differs_from(const char *def, int diff_flags);
 
+extern size_t fill_textconv(struct userdiff_driver *driver,
+                           struct diff_filespec *df,
+                           char **outbuf);
+
+extern struct userdiff_driver *get_textconv(struct diff_filespec *one);
+
 #endif /* DIFF_H */
index d7097bb576cef8900e8e0218ba82e1cc7a87a567..44f8678d22ea466b0867591429bbcc3285cdaf91 100644 (file)
@@ -69,7 +69,7 @@ static int should_break(struct diff_filespec *src,
                return 0; /* we do not break too small filepair */
 
        if (diffcore_count_changes(src, dst,
-                                  NULL, NULL,
+                                  &src->cnt_data, &dst->cnt_data,
                                   0,
                                   &src_copied, &literal_added))
                return 0;
@@ -162,8 +162,7 @@ void diffcore_break(int break_score)
        if (!merge_score)
                merge_score = DEFAULT_MERGE_SCORE;
 
-       outq.nr = outq.alloc = 0;
-       outq.queue = NULL;
+       DIFF_QUEUE_CLEAR(&outq);
 
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
@@ -204,12 +203,16 @@ void diffcore_break(int break_score)
                                dp->score = score;
                                dp->broken_pair = 1;
 
+                               diff_free_filespec_blob(p->one);
+                               diff_free_filespec_blob(p->two);
                                free(p); /* not diff_free_filepair(), we are
                                          * reusing one and two here.
                                          */
                                continue;
                        }
                }
+               diff_free_filespec_data(p->one);
+               diff_free_filespec_data(p->two);
                diff_q(&outq, p);
        }
        free(q->queue);
@@ -252,8 +255,7 @@ void diffcore_merge_broken(void)
        struct diff_queue_struct outq;
        int i, j;
 
-       outq.nr = outq.alloc = 0;
-       outq.queue = NULL;
+       DIFF_QUEUE_CLEAR(&outq);
 
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
index e670f8512558c38d9a9d6e754cfc609b042b1195..7cf431d261f9a35679ead7c8acda15aecdb8720d 100644 (file)
@@ -201,10 +201,15 @@ int diffcore_count_changes(struct diff_filespec *src,
                while (d->cnt) {
                        if (d->hashval >= s->hashval)
                                break;
+                       la += d->cnt;
                        d++;
                }
                src_cnt = s->cnt;
-               dst_cnt = d->hashval == s->hashval ? d->cnt : 0;
+               dst_cnt = 0;
+               if (d->cnt && d->hashval == s->hashval) {
+                       dst_cnt = d->cnt;
+                       d++;
+               }
                if (src_cnt < dst_cnt) {
                        la += dst_cnt - src_cnt;
                        sc += src_cnt;
@@ -213,6 +218,10 @@ int diffcore_count_changes(struct diff_filespec *src,
                        sc += dst_cnt;
                s++;
        }
+       while (d->cnt) {
+               la += d->cnt;
+               d++;
+       }
 
        if (!src_count_p)
                free(src_count);
index d0ef8397008824fb5139680856e3229ecf2c4eb1..929de15aa9228099b3a772be788d746e440a9e8c 100644 (file)
@@ -55,8 +55,7 @@ void diffcore_pickaxe(const char *needle, int opts)
        int i, has_changes;
        regex_t regex, *regexp = NULL;
        struct diff_queue_struct outq;
-       outq.queue = NULL;
-       outq.nr = outq.alloc = 0;
+       DIFF_QUEUE_CLEAR(&outq);
 
        if (opts & DIFF_PICKAXE_REGEX) {
                int err;
index 63ac998bfaf64da807bec0ae0bd57c391fb651fe..df41be56deab60d4d39a45920a1e62b05d0474f6 100644 (file)
@@ -523,10 +523,13 @@ void diffcore_rename(struct diff_options *options)
                        this_src.dst = i;
                        this_src.src = j;
                        record_if_better(m, &this_src);
+                       /*
+                        * Once we run estimate_similarity,
+                        * We do not need the text anymore.
+                        */
                        diff_free_filespec_blob(one);
+                       diff_free_filespec_blob(two);
                }
-               /* We do not need the text anymore */
-               diff_free_filespec_blob(two);
                dst_cnt++;
        }
 
@@ -566,8 +569,7 @@ void diffcore_rename(struct diff_options *options)
        /* 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;
+       DIFF_QUEUE_CLEAR(&outq);
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
                struct diff_filepair *pair_to_free = NULL;
index 5b634585e8970de81067b9807d8813c083b86b0a..8b3241ad137f5934e32336cd1caf8d99ca11d1f5 100644 (file)
@@ -18,7 +18,7 @@
 #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 DEFAULT_MERGE_SCORE  36000 /* maximum for break-merge to happen (60%) */
 
 #define MINIMUM_BREAK_SIZE     400 /* do not break a file smaller than this */
 
@@ -42,6 +42,9 @@ struct diff_filespec {
 #define DIFF_FILE_VALID(spec) (((spec)->mode) != 0)
        unsigned should_free : 1; /* data should be free()'ed */
        unsigned should_munmap : 1; /* data should be munmap()'ed */
+       unsigned dirty_submodule : 2;  /* For submodules: its work tree is dirty */
+#define DIRTY_SUBMODULE_UNTRACKED 1
+#define DIRTY_SUBMODULE_MODIFIED  2
 
        struct userdiff_driver *driver;
        /* data should be considered "binary"; -1 means "don't know yet" */
@@ -89,6 +92,11 @@ struct diff_queue_struct {
        int alloc;
        int nr;
 };
+#define DIFF_QUEUE_CLEAR(q) \
+       do { \
+               (q)->queue = NULL; \
+               (q)->nr = (q)->alloc = 0; \
+       } while (0)
 
 extern struct diff_queue_struct diff_queued_diff;
 extern struct diff_filepair *diff_queue(struct diff_queue_struct *,
@@ -108,9 +116,9 @@ 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)
+#define diff_debug_filespec(a,b,c) do { /* nothing */ } while (0)
+#define diff_debug_filepair(a,b) do { /* nothing */ } while (0)
+#define diff_debug_queue(a,b) do { /* nothing */ } while (0)
 #endif
 
 extern int diffcore_count_changes(struct diff_filespec *src,
diff --git a/dir.c b/dir.c
index 6aae09a22e54a7b9291ed1b6acf79529c6029a49..133f472a1e73786e781c1021eea17e543858937f 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -14,12 +14,11 @@ struct path_simplify {
        const char *path;
 };
 
-static int read_directory_recursive(struct dir_struct *dir,
-       const char *path, const char *base, int baselen,
+static int read_directory_recursive(struct dir_struct *dir, const char *path, int len,
        int check_only, const struct path_simplify *simplify);
-static int get_dtype(struct dirent *de, const char *path);
+static int get_dtype(struct dirent *de, const char *path, int len);
 
-int common_prefix(const char **pathspec)
+static int common_prefix(const char **pathspec)
 {
        const char *path, *slash, *next;
        int prefix;
@@ -32,26 +31,46 @@ int common_prefix(const char **pathspec)
        if (!slash)
                return 0;
 
+       /*
+        * The first 'prefix' characters of 'path' are common leading
+        * path components among the pathspecs we have seen so far,
+        * including the trailing slash.
+        */
        prefix = slash - path + 1;
        while ((next = *++pathspec) != NULL) {
-               int len = strlen(next);
-               if (len >= prefix && !memcmp(path, next, prefix))
+               int len, last_matching_slash = -1;
+               for (len = 0; len < prefix && next[len] == path[len]; len++)
+                       if (next[len] == '/')
+                               last_matching_slash = len;
+               if (len == prefix)
                        continue;
-               len = prefix - 1;
-               for (;;) {
-                       if (!len)
-                               return 0;
-                       if (next[--len] != '/')
-                               continue;
-                       if (memcmp(path, next, len+1))
-                               continue;
-                       prefix = len + 1;
-                       break;
-               }
+               if (last_matching_slash < 0)
+                       return 0;
+               prefix = last_matching_slash + 1;
        }
        return prefix;
 }
 
+int fill_directory(struct dir_struct *dir, const char **pathspec)
+{
+       const char *path;
+       int len;
+
+       /*
+        * Calculate common prefix for the pathspec, and
+        * use that to optimize the directory walk
+        */
+       len = common_prefix(pathspec);
+       path = "";
+
+       if (len)
+               path = xmemdupz(*pathspec, len);
+
+       /* Read the directory and prune it */
+       read_directory(dir, path, len, pathspec);
+       return len;
+}
+
 /*
  * Does 'match' match the given name?
  * A match is found if
@@ -181,11 +200,35 @@ void add_exclude(const char *string, const char *base,
        which->excludes[which->nr++] = x;
 }
 
-static int add_excludes_from_file_1(const char *fname,
-                                   const char *base,
-                                   int baselen,
-                                   char **buf_p,
-                                   struct exclude_list *which)
+static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
+{
+       int pos, len;
+       unsigned long sz;
+       enum object_type type;
+       void *data;
+       struct index_state *istate = &the_index;
+
+       len = strlen(path);
+       pos = index_name_pos(istate, path, len);
+       if (pos < 0)
+               return NULL;
+       if (!ce_skip_worktree(istate->cache[pos]))
+               return NULL;
+       data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz);
+       if (!data || type != OBJ_BLOB) {
+               free(data);
+               return NULL;
+       }
+       *size = xsize_t(sz);
+       return data;
+}
+
+int add_excludes_from_file_to_list(const char *fname,
+                                  const char *base,
+                                  int baselen,
+                                  char **buf_p,
+                                  struct exclude_list *which,
+                                  int check_index)
 {
        struct stat st;
        int fd, i;
@@ -193,24 +236,39 @@ static int add_excludes_from_file_1(const char *fname,
        char *buf, *entry;
 
        fd = open(fname, O_RDONLY);
-       if (fd < 0 || fstat(fd, &st) < 0)
-               goto err;
-       size = xsize_t(st.st_size);
-       if (size == 0) {
-               close(fd);
-               return 0;
+       if (fd < 0 || fstat(fd, &st) < 0) {
+               if (0 <= fd)
+                       close(fd);
+               if (!check_index ||
+                   (buf = read_skip_worktree_file_from_index(fname, &size)) == NULL)
+                       return -1;
+               if (size == 0) {
+                       free(buf);
+                       return 0;
+               }
+               if (buf[size-1] != '\n') {
+                       buf = xrealloc(buf, size+1);
+                       buf[size++] = '\n';
+               }
        }
-       buf = xmalloc(size+1);
-       if (read_in_full(fd, buf, size) != size)
-       {
-               free(buf);
-               goto err;
+       else {
+               size = xsize_t(st.st_size);
+               if (size == 0) {
+                       close(fd);
+                       return 0;
+               }
+               buf = xmalloc(size+1);
+               if (read_in_full(fd, buf, size) != size) {
+                       free(buf);
+                       close(fd);
+                       return -1;
+               }
+               buf[size++] = '\n';
+               close(fd);
        }
-       close(fd);
 
        if (buf_p)
                *buf_p = buf;
-       buf[size++] = '\n';
        entry = buf;
        for (i = 0; i < size; i++) {
                if (buf[i] == '\n') {
@@ -222,17 +280,12 @@ static int add_excludes_from_file_1(const char *fname,
                }
        }
        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, NULL,
-                                    &dir->exclude_list[EXC_FILE]) < 0)
+       if (add_excludes_from_file_to_list(fname, "", 0, NULL,
+                                          &dir->exclude_list[EXC_FILE], 0) < 0)
                die("cannot use %s as an exclude file", fname);
 }
 
@@ -281,9 +334,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
                memcpy(dir->basebuf + current, base + current,
                       stk->baselen - current);
                strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir);
-               add_excludes_from_file_1(dir->basebuf,
-                                        dir->basebuf, stk->baselen,
-                                        &stk->filebuf, el);
+               add_excludes_from_file_to_list(dir->basebuf,
+                                              dir->basebuf, stk->baselen,
+                                              &stk->filebuf, el, 1);
                dir->exclude_stack = stk;
                current = stk->baselen;
        }
@@ -293,9 +346,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
 /* Scan the list and let the last match determine the fate.
  * Return 1 for exclude, 0 for include and -1 for undecided.
  */
-static int excluded_1(const char *pathname,
-                     int pathlen, const char *basename, int *dtype,
-                     struct exclude_list *el)
+int excluded_from_list(const char *pathname,
+                      int pathlen, const char *basename, int *dtype,
+                      struct exclude_list *el)
 {
        int i;
 
@@ -306,8 +359,14 @@ static int excluded_1(const char *pathname,
                        int to_exclude = x->to_exclude;
 
                        if (x->flags & EXC_FLAG_MUSTBEDIR) {
+                               if (!dtype) {
+                                       if (!prefixcmp(pathname, exclude))
+                                               return to_exclude;
+                                       else
+                                               continue;
+                               }
                                if (*dtype == DT_UNKNOWN)
-                                       *dtype = get_dtype(NULL, pathname);
+                                       *dtype = get_dtype(NULL, pathname, pathlen);
                                if (*dtype != DT_DIR)
                                        continue;
                        }
@@ -363,8 +422,8 @@ int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
 
        prep_exclude(dir, pathname, basename-pathname);
        for (st = EXC_CMDL; st <= EXC_FILE; st++) {
-               switch (excluded_1(pathname, pathlen, basename,
-                                  dtype_p, &dir->exclude_list[st])) {
+               switch (excluded_from_list(pathname, pathlen, basename,
+                                          dtype_p, &dir->exclude_list[st])) {
                case 0:
                        return 0;
                case 1:
@@ -394,9 +453,9 @@ static struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathna
        return dir->entries[dir->nr++] = dir_entry_new(pathname, len);
 }
 
-static struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len)
+struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len)
 {
-       if (cache_name_pos(pathname, len) >= 0)
+       if (!cache_name_is_other(pathname, len))
                return NULL;
 
        ALLOC_GROW(dir->ignored, dir->ignored_nr+1, dir->ignored_alloc);
@@ -406,7 +465,7 @@ static struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pat
 enum exist_status {
        index_nonexistent = 0,
        index_directory,
-       index_gitdir,
+       index_gitdir
 };
 
 /*
@@ -474,7 +533,7 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)
 enum directory_treatment {
        show_directory,
        ignore_directory,
-       recurse_into_directory,
+       recurse_into_directory
 };
 
 static enum directory_treatment treat_directory(struct dir_struct *dir,
@@ -505,7 +564,7 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,
        /* This is the "show_other_directories" case */
        if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
                return show_directory;
-       if (!read_directory_recursive(dir, dirname, dirname, len, 1, simplify))
+       if (!read_directory_recursive(dir, dirname, len, 1, simplify))
                return ignore_directory;
        return show_directory;
 }
@@ -535,23 +594,80 @@ static int simplify_away(const char *path, int pathlen, const struct path_simpli
        return 0;
 }
 
-static int in_pathspec(const char *path, int len, const struct path_simplify *simplify)
+/*
+ * This function tells us whether an excluded path matches a
+ * list of "interesting" pathspecs. That is, whether a path matched
+ * by any of the pathspecs could possibly be ignored by excluding
+ * the specified path. This can happen if:
+ *
+ *   1. the path is mentioned explicitly in the pathspec
+ *
+ *   2. the path is a directory prefix of some element in the
+ *      pathspec
+ */
+static int exclude_matches_pathspec(const char *path, int len,
+               const struct path_simplify *simplify)
 {
        if (simplify) {
                for (; simplify->path; simplify++) {
                        if (len == simplify->len
                            && !memcmp(path, simplify->path, len))
                                return 1;
+                       if (len < simplify->len
+                           && simplify->path[len] == '/'
+                           && !memcmp(path, simplify->path, len))
+                               return 1;
                }
        }
        return 0;
 }
 
-static int get_dtype(struct dirent *de, const char *path)
+static int get_index_dtype(const char *path, int len)
+{
+       int pos;
+       struct cache_entry *ce;
+
+       ce = cache_name_exists(path, len, 0);
+       if (ce) {
+               if (!ce_uptodate(ce))
+                       return DT_UNKNOWN;
+               if (S_ISGITLINK(ce->ce_mode))
+                       return DT_DIR;
+               /*
+                * Nobody actually cares about the
+                * difference between DT_LNK and DT_REG
+                */
+               return DT_REG;
+       }
+
+       /* Try to look it up as a directory */
+       pos = cache_name_pos(path, len);
+       if (pos >= 0)
+               return DT_UNKNOWN;
+       pos = -pos-1;
+       while (pos < active_nr) {
+               ce = active_cache[pos++];
+               if (strncmp(ce->name, path, len))
+                       break;
+               if (ce->name[len] > '/')
+                       break;
+               if (ce->name[len] < '/')
+                       continue;
+               if (!ce_uptodate(ce))
+                       break;  /* continue? */
+               return DT_DIR;
+       }
+       return DT_UNKNOWN;
+}
+
+static int get_dtype(struct dirent *de, const char *path, int len)
 {
        int dtype = de ? DTYPE(de) : DT_UNKNOWN;
        struct stat st;
 
+       if (dtype != DT_UNKNOWN)
+               return dtype;
+       dtype = get_index_dtype(path, len);
        if (dtype != DT_UNKNOWN)
                return dtype;
        if (lstat(path, &st))
@@ -565,6 +681,92 @@ static int get_dtype(struct dirent *de, const char *path)
        return dtype;
 }
 
+enum path_treatment {
+       path_ignored,
+       path_handled,
+       path_recurse
+};
+
+static enum path_treatment treat_one_path(struct dir_struct *dir,
+                                         char *path, int *len,
+                                         const struct path_simplify *simplify,
+                                         int dtype, struct dirent *de)
+{
+       int exclude = excluded(dir, path, &dtype);
+       if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
+           && exclude_matches_pathspec(path, *len, simplify))
+               dir_add_ignored(dir, path, *len);
+
+       /*
+        * Excluded? If we don't explicitly want to show
+        * ignored files, ignore it
+        */
+       if (exclude && !(dir->flags & DIR_SHOW_IGNORED))
+               return path_ignored;
+
+       if (dtype == DT_UNKNOWN)
+               dtype = get_dtype(de, path, *len);
+
+       /*
+        * Do we want to see just the ignored files?
+        * We still need to recurse into directories,
+        * even if we don't ignore them, since the
+        * directory may contain files that we do..
+        */
+       if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) {
+               if (dtype != DT_DIR)
+                       return path_ignored;
+       }
+
+       switch (dtype) {
+       default:
+               return path_ignored;
+       case DT_DIR:
+               memcpy(path + *len, "/", 2);
+               (*len)++;
+               switch (treat_directory(dir, path, *len, simplify)) {
+               case show_directory:
+                       if (exclude != !!(dir->flags
+                                         & DIR_SHOW_IGNORED))
+                               return path_ignored;
+                       break;
+               case recurse_into_directory:
+                       return path_recurse;
+               case ignore_directory:
+                       return path_ignored;
+               }
+               break;
+       case DT_REG:
+       case DT_LNK:
+               break;
+       }
+       return path_handled;
+}
+
+static enum path_treatment treat_path(struct dir_struct *dir,
+                                     struct dirent *de,
+                                     char *path, int path_max,
+                                     int baselen,
+                                     const struct path_simplify *simplify,
+                                     int *len)
+{
+       int dtype;
+
+       if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
+               return path_ignored;
+       *len = strlen(de->d_name);
+       /* Ignore overly long pathnames! */
+       if (*len + baselen + 8 > path_max)
+               return path_ignored;
+       memcpy(path + baselen, de->d_name, *len + 1);
+       *len += baselen;
+       if (simplify_away(path, *len, simplify))
+               return path_ignored;
+
+       dtype = DTYPE(de);
+       return treat_one_path(dir, path, len, simplify, dtype, de);
+}
+
 /*
  * Read a directory tree. We currently ignore anything but
  * directories, regular files and symlinks. That's because git
@@ -574,87 +776,37 @@ static int get_dtype(struct dirent *de, const char *path)
  * 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, int check_only, const struct path_simplify *simplify)
+static int read_directory_recursive(struct dir_struct *dir,
+                                   const char *base, int baselen,
+                                   int check_only,
+                                   const struct path_simplify *simplify)
 {
-       DIR *fdir = opendir(path);
+       DIR *fdir = opendir(*base ? base : ".");
        int contents = 0;
 
        if (fdir) {
                struct dirent *de;
-               char fullname[PATH_MAX + 1];
-               memcpy(fullname, base, baselen);
+               char path[PATH_MAX + 1];
+               memcpy(path, base, baselen);
 
                while ((de = readdir(fdir)) != NULL) {
-                       int len, dtype;
-                       int exclude;
-
-                       if (is_dot_or_dotdot(de->d_name) ||
-                            !strcmp(de->d_name, ".git"))
-                               continue;
-                       len = strlen(de->d_name);
-                       /* Ignore overly long pathnames! */
-                       if (len + baselen + 8 > sizeof(fullname))
-                               continue;
-                       memcpy(fullname + baselen, de->d_name, len+1);
-                       if (simplify_away(fullname, baselen + len, simplify))
+                       int len;
+                       switch (treat_path(dir, de, path, sizeof(path),
+                                          baselen, simplify, &len)) {
+                       case path_recurse:
+                               contents += read_directory_recursive
+                                       (dir, path, len, 0, simplify);
                                continue;
-
-                       dtype = DTYPE(de);
-                       exclude = excluded(dir, fullname, &dtype);
-                       if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
-                           && in_pathspec(fullname, baselen + len, simplify))
-                               dir_add_ignored(dir, fullname, baselen + len);
-
-                       /*
-                        * Excluded? If we don't explicitly want to show
-                        * ignored files, ignore it
-                        */
-                       if (exclude && !(dir->flags & DIR_SHOW_IGNORED))
-                               continue;
-
-                       if (dtype == DT_UNKNOWN)
-                               dtype = get_dtype(de, fullname);
-
-                       /*
-                        * Do we want to see just the ignored files?
-                        * We still need to recurse into directories,
-                        * even if we don't ignore them, since the
-                        * directory may contain files that we do..
-                        */
-                       if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) {
-                               if (dtype != DT_DIR)
-                                       continue;
-                       }
-
-                       switch (dtype) {
-                       default:
+                       case path_ignored:
                                continue;
-                       case DT_DIR:
-                               memcpy(fullname + baselen + len, "/", 2);
-                               len++;
-                               switch (treat_directory(dir, fullname, baselen + len, simplify)) {
-                               case show_directory:
-                                       if (exclude != !!(dir->flags
-                                                       & DIR_SHOW_IGNORED))
-                                               continue;
-                                       break;
-                               case recurse_into_directory:
-                                       contents += read_directory_recursive(dir,
-                                               fullname, fullname, baselen + len, 0, simplify);
-                                       continue;
-                               case ignore_directory:
-                                       continue;
-                               }
-                               break;
-                       case DT_REG:
-                       case DT_LNK:
+                       case path_handled:
                                break;
                        }
                        contents++;
                        if (check_only)
                                goto exit_early;
                        else
-                               dir_add_name(dir, fullname, baselen + len);
+                               dir_add_name(dir, path, len);
                }
 exit_early:
                closedir(fdir);
@@ -717,15 +869,51 @@ static void free_simplify(struct path_simplify *simplify)
        free(simplify);
 }
 
-int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen, const char **pathspec)
+static int treat_leading_path(struct dir_struct *dir,
+                             const char *path, int len,
+                             const struct path_simplify *simplify)
+{
+       char pathbuf[PATH_MAX];
+       int baselen, blen;
+       const char *cp;
+
+       while (len && path[len - 1] == '/')
+               len--;
+       if (!len)
+               return 1;
+       baselen = 0;
+       while (1) {
+               cp = path + baselen + !!baselen;
+               cp = memchr(cp, '/', path + len - cp);
+               if (!cp)
+                       baselen = len;
+               else
+                       baselen = cp - path;
+               memcpy(pathbuf, path, baselen);
+               pathbuf[baselen] = '\0';
+               if (!is_directory(pathbuf))
+                       return 0;
+               if (simplify_away(pathbuf, baselen, simplify))
+                       return 0;
+               blen = baselen;
+               if (treat_one_path(dir, pathbuf, &blen, simplify,
+                                  DT_DIR, NULL) == path_ignored)
+                       return 0; /* do not recurse into it */
+               if (len <= baselen)
+                       return 1; /* finished checking */
+       }
+}
+
+int read_directory(struct dir_struct *dir, const char *path, int len, const char **pathspec)
 {
        struct path_simplify *simplify;
 
-       if (has_symlink_leading_path(path, strlen(path)))
+       if (has_symlink_leading_path(path, len))
                return dir->nr;
 
        simplify = create_simplify(pathspec);
-       read_directory_recursive(dir, path, base, baselen, 0, simplify);
+       if (!len || treat_leading_path(dir, path, len, simplify))
+               read_directory_recursive(dir, path, len, 0, simplify);
        free_simplify(simplify);
        qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
        qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);
@@ -759,7 +947,7 @@ char *get_relative_cwd(char *buffer, int size, const char *dir)
        if (!dir)
                return NULL;
        if (!getcwd(buffer, size))
-               die("can't find the current directory: %s", strerror(errno));
+               die_errno("can't find the current directory");
 
        if (!is_absolute_path(dir))
                dir = make_absolute_path(dir);
@@ -770,9 +958,14 @@ char *get_relative_cwd(char *buffer, int size, const char *dir)
        }
        if (*dir)
                return NULL;
-       if (*cwd == '/')
+       switch (*cwd) {
+       case '\0':
+               return cwd;
+       case '/':
                return cwd + 1;
-       return cwd;
+       default:
+               return NULL;
+       }
 }
 
 int is_inside_dir(const char *dir)
@@ -800,12 +993,20 @@ int is_empty_dir(const char *path)
        return ret;
 }
 
-int remove_dir_recursively(struct strbuf *path, int only_empty)
+int remove_dir_recursively(struct strbuf *path, int flag)
 {
-       DIR *dir = opendir(path->buf);
+       DIR *dir;
        struct dirent *e;
        int ret = 0, original_len = path->len, len;
+       int only_empty = (flag & REMOVE_DIR_EMPTY_ONLY);
+       unsigned char submodule_head[20];
+
+       if ((flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
+           !resolve_gitlink_ref(path->buf, "HEAD", submodule_head))
+               /* Do not descend and nuke a nested git work tree. */
+               return 0;
 
+       dir = opendir(path->buf);
        if (!dir)
                return -1;
        if (path->buf[original_len - 1] != '/')
@@ -864,7 +1065,7 @@ int remove_path(const char *name)
                slash = dirs + (slash - name);
                do {
                        *slash = '\0';
-               } while (rmdir(dirs) && (slash = strrchr(dirs, '/')));
+               } while (rmdir(dirs) == 0 && (slash = strrchr(dirs, '/')));
                free(dirs);
        }
        return 0;
diff --git a/dir.h b/dir.h
index 541286ad1daeb66a9963288d275534ee963bd5cd..278d84cdf7df01c33a45e6dc9c20592cecff9d85 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -61,16 +61,20 @@ struct dir_struct {
        char basebuf[PATH_MAX];
 };
 
-extern int common_prefix(const char **pathspec);
-
 #define MATCHED_RECURSIVELY 1
 #define MATCHED_FNMATCH 2
 #define MATCHED_EXACTLY 3
 extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen);
 
-extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen, const char **pathspec);
+extern int fill_directory(struct dir_struct *dir, const char **pathspec);
+extern int read_directory(struct dir_struct *, const char *path, int len, const char **pathspec);
 
+extern int excluded_from_list(const char *pathname, int pathlen, const char *basename,
+                             int *dtype, struct exclude_list *el);
 extern int excluded(struct dir_struct *, const char *, int *);
+struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len);
+extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
+                                         char **buf_p, struct exclude_list *which, int check_index);
 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);
@@ -89,7 +93,10 @@ static inline int is_dot_or_dotdot(const char *name)
 extern int is_empty_dir(const char *dir);
 
 extern void setup_standard_excludes(struct dir_struct *dir);
-extern int remove_dir_recursively(struct strbuf *path, int only_empty);
+
+#define REMOVE_DIR_EMPTY_ONLY 01
+#define REMOVE_DIR_KEEP_NESTED_GIT 02
+extern int remove_dir_recursively(struct strbuf *path, int flag);
 
 /* tries to remove the path with empty directories along it, ignores ENOENT */
 extern int remove_path(const char *path);
index 4d469d076bcd58df3af16d98adc9120e34765944..d8340031d24828483697c10257806efedfe041f5 100644 (file)
--- a/editor.c
+++ b/editor.c
@@ -2,46 +2,43 @@
 #include "strbuf.h"
 #include "run-command.h"
 
-int launch_editor(const char *path, struct strbuf *buffer, const char *const *env)
+#ifndef DEFAULT_EDITOR
+#define DEFAULT_EDITOR "vi"
+#endif
+
+const char *git_editor(void)
 {
-       const char *editor, *terminal;
+       const char *editor = getenv("GIT_EDITOR");
+       const char *terminal = getenv("TERM");
+       int terminal_is_dumb = !terminal || !strcmp(terminal, "dumb");
 
-       editor = getenv("GIT_EDITOR");
        if (!editor && editor_program)
                editor = editor_program;
-       if (!editor)
+       if (!editor && !terminal_is_dumb)
                editor = getenv("VISUAL");
        if (!editor)
                editor = getenv("EDITOR");
 
-       terminal = getenv("TERM");
-       if (!editor && (!terminal || !strcmp(terminal, "dumb")))
-               return error("Terminal is dumb but no VISUAL nor EDITOR defined.");
+       if (!editor && terminal_is_dumb)
+               return NULL;
 
        if (!editor)
-               editor = "vi";
+               editor = DEFAULT_EDITOR;
+
+       return editor;
+}
+
+int launch_editor(const char *path, struct strbuf *buffer, const char *const *env)
+{
+       const char *editor = git_editor();
+
+       if (!editor)
+               return error("Terminal is dumb, but EDITOR unset");
 
        if (strcmp(editor, ":")) {
-               size_t len = strlen(editor);
-               int i = 0;
-               int failed;
-               const char *args[6];
-               struct strbuf arg0 = STRBUF_INIT;
-
-               if (strcspn(editor, "$ \t'") != len) {
-                       /* there are specials */
-                       strbuf_addf(&arg0, "%s \"$@\"", editor);
-                       args[i++] = "sh";
-                       args[i++] = "-c";
-                       args[i++] = arg0.buf;
-               }
-               args[i++] = editor;
-               args[i++] = path;
-               args[i] = NULL;
-
-               failed = run_command_v_opt_cd_env(args, 0, NULL, env);
-               strbuf_release(&arg0);
-               if (failed)
+               const char *args[] = { editor, path, NULL };
+
+               if (run_command_v_opt_cd_env(args, RUN_USING_SHELL, NULL, env))
                        return error("There was a problem with the editor '%s'.",
                                        editor);
        }
diff --git a/entry.c b/entry.c
index 915514aa5c43d3fd39f077a79f63ac27b841d255..004182c99d27a6a5825d2429f9104fc7a8f1dc80 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -35,9 +35,9 @@ static void create_directories(const char *path, int path_len,
                 */
                if (mkdir(buf, 0777)) {
                        if (errno == EEXIST && state->force &&
-                           !unlink(buf) && !mkdir(buf, 0777))
+                           !unlink_or_warn(buf) && !mkdir(buf, 0777))
                                continue;
-                       die("cannot create directory at %s", buf);
+                       die_errno("cannot create directory at '%s'", buf);
                }
        }
        free(buf);
@@ -51,7 +51,7 @@ static void remove_subtree(const char *path)
        char *name;
 
        if (!dir)
-               die("cannot opendir %s (%s)", path, strerror(errno));
+               die_errno("cannot opendir '%s'", path);
        strcpy(pathbuf, path);
        name = pathbuf + strlen(path);
        *name++ = '/';
@@ -61,15 +61,15 @@ static void remove_subtree(const char *path)
                        continue;
                strcpy(name, de->d_name);
                if (lstat(pathbuf, &st))
-                       die("cannot lstat %s (%s)", pathbuf, strerror(errno));
+                       die_errno("cannot lstat '%s'", pathbuf);
                if (S_ISDIR(st.st_mode))
                        remove_subtree(pathbuf);
                else if (unlink(pathbuf))
-                       die("cannot unlink %s (%s)", pathbuf, strerror(errno));
+                       die_errno("cannot unlink '%s'", pathbuf);
        }
        closedir(dir);
        if (rmdir(path))
-               die("cannot rmdir %s (%s)", path, strerror(errno));
+               die_errno("cannot rmdir '%s'", path);
 }
 
 static int create_file(const char *path, unsigned int mode)
@@ -175,6 +175,23 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
        return 0;
 }
 
+/*
+ * This is like 'lstat()', except it refuses to follow symlinks
+ * in the path, after skipping "skiplen".
+ */
+static int check_path(const char *path, int len, struct stat *st, int skiplen)
+{
+       const char *slash = path + len;
+
+       while (path < slash && *slash != '/')
+               slash--;
+       if (!has_dirs_only_path(path, slash - path, skiplen)) {
+               errno = ENOENT;
+               return -1;
+       }
+       return lstat(path, st);
+}
+
 int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath)
 {
        static char path[PATH_MAX + 1];
@@ -188,8 +205,8 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t
        strcpy(path + len, ce->name);
        len += ce_namelen(ce);
 
-       if (!lstat(path, &st)) {
-               unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID);
+       if (!check_path(path, len, &st, state->base_dir_len)) {
+               unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
                if (!changed)
                        return 0;
                if (!state->force) {
index 801a005ef1b23ef13cfa9ece676c550fe35dedc0..2d0c3153791d7cceffb12f50dd5a7716b8c0a8c0 100644 (file)
@@ -26,6 +26,7 @@ const char *git_commit_encoding;
 const char *git_log_output_encoding;
 int shared_repository = PERM_UMASK;
 const char *apply_default_whitespace;
+const char *apply_default_ignorewhitespace;
 int zlib_compression_level = Z_BEST_SPEED;
 int core_compression_level;
 int core_compression_seen;
@@ -36,17 +37,24 @@ size_t delta_base_cache_limit = 16 * 1024 * 1024;
 const char *pager_program;
 int pager_use_color = 1;
 const char *editor_program;
+const char *askpass_program;
 const char *excludes_file;
-int auto_crlf = 0;     /* 1: both ways, -1: only when adding git objects */
+enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;
+int read_replace_refs = 1;
+enum eol eol = EOL_UNSET;
 enum safe_crlf safe_crlf = SAFE_CRLF_WARN;
 unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;
 enum branch_track git_branch_track = BRANCH_TRACK_REMOTE;
 enum rebase_setup_type autorebase = AUTOREBASE_NEVER;
-enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED;
+enum push_default_type push_default = PUSH_DEFAULT_MATCHING;
 #ifndef OBJECT_CREATION_MODE
 #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS
 #endif
 enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE;
+char *notes_ref_name;
+int grafts_replace_parents = 1;
+int core_apply_sparse_checkout;
+struct startup_info *startup_info;
 
 /* Parallel index stat data preload? */
 int core_preload_index = 0;
@@ -58,6 +66,24 @@ static char *work_tree;
 static const char *git_dir;
 static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file;
 
+/*
+ * Repository-local GIT_* environment variables
+ * Remember to update local_repo_env_size in cache.h when
+ * the size of the list changes
+ */
+const char * const local_repo_env[LOCAL_REPO_ENV_SIZE + 1] = {
+       ALTERNATE_DB_ENVIRONMENT,
+       CONFIG_ENVIRONMENT,
+       CONFIG_DATA_ENVIRONMENT,
+       DB_ENVIRONMENT,
+       GIT_DIR_ENVIRONMENT,
+       GIT_WORK_TREE_ENVIRONMENT,
+       GRAFT_ENVIRONMENT,
+       INDEX_ENVIRONMENT,
+       NO_REPLACE_OBJECTS_ENVIRONMENT,
+       NULL
+};
+
 static void setup_git_env(void)
 {
        git_dir = getenv(GIT_DIR_ENVIRONMENT);
@@ -80,6 +106,8 @@ static void setup_git_env(void)
        git_graft_file = getenv(GRAFT_ENVIRONMENT);
        if (!git_graft_file)
                git_graft_file = git_pathdup("info/grafts");
+       if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
+               read_replace_refs = 0;
 }
 
 int is_bare_repository(void)
index 408e4e55e1c58931444c772d35d23b505bf3e2ea..bf225706ee377b89035eb21f76f9957cfaf6363b 100644 (file)
@@ -28,7 +28,7 @@ const char *system_path(const char *path)
            !(prefix = strip_path_suffix(argv0_path, BINDIR)) &&
            !(prefix = strip_path_suffix(argv0_path, "git"))) {
                prefix = PREFIX;
-               fprintf(stderr, "RUNTIME_PREFIX requested, "
+               trace_printf("RUNTIME_PREFIX requested, "
                                "but prefix computation failed.  "
                                "Using static fallback '%s'.\n", prefix);
        }
@@ -107,7 +107,7 @@ void setup_path(void)
        if (old_path)
                strbuf_addstr(&new_path, old_path);
        else
-               strbuf_addstr(&new_path, "/usr/local/bin:/usr/bin:/bin");
+               strbuf_addstr(&new_path, _PATH_DEFPATH);
 
        setenv("PATH", new_path.buf, 1);
 
index e9d23ffb2fb3f6df141944dca7ddab0edbd1e5ee..2317b0fe7509b957577234890135509143c0872b 100644 (file)
@@ -19,11 +19,11 @@ Format of STDIN stream:
 
   new_commit ::= 'commit' sp ref_str lf
     mark?
-    ('author' sp name sp '<' email '>' sp when lf)?
-    'committer' sp name sp '<' email '>' sp when lf
+    ('author' (sp name)? sp '<' email '>' sp when lf)?
+    'committer' (sp name)? sp '<' email '>' sp when lf
     commit_msg
-    ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)?
-    ('merge' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)*
+    ('from' sp committish lf)?
+    ('merge' sp committish lf)*
     file_change*
     lf?;
   commit_msg ::= data;
@@ -41,15 +41,18 @@ Format of STDIN stream:
   file_obm ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf;
   file_inm ::= 'M' sp mode sp 'inline' sp path_str lf
     data;
+  note_obm ::= 'N' sp (hexsha1 | idnum) sp committish lf;
+  note_inm ::= 'N' sp 'inline' sp committish lf
+    data;
 
   new_tag ::= 'tag' sp tag_str lf
-    'from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf
-    ('tagger' sp name sp '<' email '>' sp when lf)?
+    'from' sp committish lf
+    ('tagger' (sp name)? sp '<' email '>' sp when lf)?
     tag_msg;
   tag_msg ::= data;
 
   reset_branch ::= 'reset' sp ref_str lf
-    ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)?
+    ('from' sp committish lf)?
     lf?;
 
   checkpoint ::= 'checkpoint' lf
@@ -88,6 +91,7 @@ Format of STDIN stream:
      # stream formatting is: \, " and LF.  Otherwise these values
      # are UTF8.
      #
+  committish  ::= (ref_str | hexsha1 | sha1exp_str | idnum);
   ref_str     ::= ref;
   sha1exp_str ::= sha1exp;
   tag_str     ::= tag;
@@ -160,12 +164,11 @@ Format of STDIN stream:
 
 struct object_entry
 {
+       struct pack_idx_entry idx;
        struct object_entry *next;
-       uint32_t offset;
        uint32_t type : TYPE_BITS,
                pack_id : PACK_ID_BITS,
                depth : DEPTH_BITS;
-       unsigned char sha1[20];
 };
 
 struct object_entry_pool
@@ -188,7 +191,7 @@ struct mark_set
 struct last_object
 {
        struct strbuf data;
-       uint32_t offset;
+       off_t offset;
        unsigned int depth;
        unsigned no_swap : 1;
 };
@@ -241,6 +244,7 @@ struct branch
        const char *name;
        struct tree_entry branch_tree;
        uintmax_t last_commit;
+       uintmax_t num_notes;
        unsigned active : 1;
        unsigned pack_id : PACK_ID_BITS;
        unsigned char sha1[20];
@@ -263,7 +267,7 @@ struct hash_list
 typedef enum {
        WHENSPEC_RAW = 1,
        WHENSPEC_RFC2822,
-       WHENSPEC_NOW,
+       WHENSPEC_NOW
 } whenspec_type;
 
 struct recent_command
@@ -275,7 +279,8 @@ struct recent_command
 
 /* Configured limits on output */
 static unsigned long max_depth = 10;
-static off_t max_packsize = (1LL << 32) - 1;
+static off_t max_packsize;
+static uintmax_t big_file_threshold = 512 * 1024 * 1024;
 static int force_update;
 static int pack_compression_level = Z_DEFAULT_COMPRESSION;
 static int pack_compression_seen;
@@ -291,6 +296,9 @@ static unsigned long branch_count;
 static unsigned long branch_load_count;
 static int failure;
 static FILE *pack_edges;
+static unsigned int show_stats = 1;
+static int global_argc;
+static const char **global_argv;
 
 /* Memory pools */
 static size_t mem_pool_alloc = 2*1024*1024 - sizeof(struct mem_pool);
@@ -304,16 +312,20 @@ static struct atom_str **atom_table;
 
 /* The .pack file being generated */
 static unsigned int pack_id;
+static struct sha1file *pack_file;
 static struct packed_git *pack_data;
 static struct packed_git **all_packs;
-static unsigned long pack_size;
+static off_t pack_size;
 
 /* Table of objects we've written. */
 static unsigned int object_entry_alloc = 5000;
 static struct object_entry_pool *blocks;
 static struct object_entry *object_table[1 << 16];
 static struct mark_set *marks;
-static const char *mark_file;
+static const char *export_marks_file;
+static const char *import_marks_file;
+static int import_marks_file_from_stream;
+static int relative_marks_paths;
 
 /* Our last blob */
 static struct last_object last_blob = { STRBUF_INIT, 0, 0, 0 };
@@ -347,6 +359,9 @@ static struct recent_command *rc_free;
 static unsigned int cmd_save = 100;
 static uintmax_t next_mark;
 static struct strbuf new_data = STRBUF_INIT;
+static int seen_data_command;
+
+static void parse_argv(void);
 
 static void write_branch_report(FILE *rpt, struct branch *b)
 {
@@ -450,8 +465,8 @@ static void write_crash_report(const char *err)
        fputc('\n', rpt);
        fputs("Marks\n", rpt);
        fputs("-----\n", rpt);
-       if (mark_file)
-               fprintf(rpt, "  exported to %s\n", mark_file);
+       if (export_marks_file)
+               fprintf(rpt, "  exported to %s\n", export_marks_file);
        else
                dump_marks_helper(rpt, 0, marks);
 
@@ -506,7 +521,7 @@ static struct object_entry *new_object(unsigned char *sha1)
                alloc_objects(object_entry_alloc);
 
        e = blocks->next_free++;
-       hashcpy(e->sha1, sha1);
+       hashcpy(e->idx.sha1, sha1);
        return e;
 }
 
@@ -515,7 +530,7 @@ static struct object_entry *find_object(unsigned char *sha1)
        unsigned int h = sha1[0] << 8 | sha1[1];
        struct object_entry *e;
        for (e = object_table[h]; e; e = e->next)
-               if (!hashcmp(sha1, e->sha1))
+               if (!hashcmp(sha1, e->idx.sha1))
                        return e;
        return NULL;
 }
@@ -527,7 +542,7 @@ static struct object_entry *insert_object(unsigned char *sha1)
        struct object_entry *p = NULL;
 
        while (e) {
-               if (!hashcmp(sha1, e->sha1))
+               if (!hashcmp(sha1, e->idx.sha1))
                        return e;
                p = e;
                e = e->next;
@@ -535,7 +550,7 @@ static struct object_entry *insert_object(unsigned char *sha1)
 
        e = new_object(sha1);
        e->next = NULL;
-       e->offset = 0;
+       e->idx.offset = 0;
        if (p)
                p->next = e;
        else
@@ -689,6 +704,7 @@ static struct branch *new_branch(const char *name)
        b->table_next_branch = branch_table[hc];
        b->branch_tree.versions[0].mode = S_IFDIR;
        b->branch_tree.versions[1].mode = S_IFDIR;
+       b->num_notes = 0;
        b->active = 0;
        b->pack_id = MAX_PACK_ID;
        branch_table[hc] = b;
@@ -823,11 +839,12 @@ static void start_packfile(void)
        p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2);
        strcpy(p->pack_name, tmpfile);
        p->pack_fd = pack_fd;
+       pack_file = sha1fd(pack_fd, p->pack_name);
 
        hdr.hdr_signature = htonl(PACK_SIGNATURE);
        hdr.hdr_version = htonl(2);
        hdr.hdr_entries = 0;
-       write_or_die(p->pack_fd, &hdr, sizeof(hdr));
+       sha1write(pack_file, &hdr, sizeof(hdr));
 
        pack_data = p;
        pack_size = sizeof(hdr);
@@ -837,67 +854,30 @@ static void start_packfile(void)
        all_packs[pack_id] = p;
 }
 
-static int oecmp (const void *a_, const void *b_)
-{
-       struct object_entry *a = *((struct object_entry**)a_);
-       struct object_entry *b = *((struct object_entry**)b_);
-       return hashcmp(a->sha1, b->sha1);
-}
-
-static char *create_index(void)
+static const char *create_index(void)
 {
-       static char tmpfile[PATH_MAX];
-       git_SHA_CTX ctx;
-       struct sha1file *f;
-       struct object_entry **idx, **c, **last, *e;
+       const char *tmpfile;
+       struct pack_idx_entry **idx, **c, **last;
+       struct object_entry *e;
        struct object_entry_pool *o;
-       uint32_t array[256];
-       int i, idx_fd;
 
-       /* Build the sorted table of object IDs. */
-       idx = xmalloc(object_count * sizeof(struct object_entry*));
+       /* Build the table of object IDs. */
+       idx = xmalloc(object_count * sizeof(*idx));
        c = idx;
        for (o = blocks; o; o = o->next_pool)
                for (e = o->next_free; e-- != o->entries;)
                        if (pack_id == e->pack_id)
-                               *c++ = e;
+                               *c++ = &e->idx;
        last = idx + object_count;
        if (c != last)
                die("internal consistency error creating the index");
-       qsort(idx, object_count, sizeof(struct object_entry*), oecmp);
 
-       /* Generate the fan-out array. */
-       c = idx;
-       for (i = 0; i < 256; i++) {
-               struct object_entry **next = c;
-               while (next < last) {
-                       if ((*next)->sha1[0] != i)
-                               break;
-                       next++;
-               }
-               array[i] = htonl(next - idx);
-               c = next;
-       }
-
-       idx_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
-                            "pack/tmp_idx_XXXXXX");
-       f = sha1fd(idx_fd, tmpfile);
-       sha1write(f, array, 256 * sizeof(int));
-       git_SHA1_Init(&ctx);
-       for (c = idx; c != last; c++) {
-               uint32_t offset = htonl((*c)->offset);
-               sha1write(f, &offset, 4);
-               sha1write(f, (*c)->sha1, sizeof((*c)->sha1));
-               git_SHA1_Update(&ctx, (*c)->sha1, 20);
-       }
-       sha1write(f, pack_data->sha1, sizeof(pack_data->sha1));
-       sha1close(f, NULL, CSUM_FSYNC);
+       tmpfile = write_idx_file(NULL, idx, object_count, pack_data->sha1);
        free(idx);
-       git_SHA1_Final(pack_data->sha1, &ctx);
        return tmpfile;
 }
 
-static char *keep_pack(char *curr_index_name)
+static char *keep_pack(const char *curr_index_name)
 {
        static char name[PATH_MAX];
        static const char *keep_msg = "fast-import";
@@ -905,10 +885,10 @@ static char *keep_pack(char *curr_index_name)
 
        keep_fd = odb_pack_keep(name, sizeof(name), pack_data->sha1);
        if (keep_fd < 0)
-               die("cannot create keep file");
+               die_errno("cannot create keep file");
        write_or_die(keep_fd, keep_msg, strlen(keep_msg));
        if (close(keep_fd))
-               die("failed to write keep file");
+               die_errno("failed to write keep file");
 
        snprintf(name, sizeof(name), "%s/pack/pack-%s.pack",
                 get_object_directory(), sha1_to_hex(pack_data->sha1));
@@ -919,6 +899,7 @@ static char *keep_pack(char *curr_index_name)
                 get_object_directory(), sha1_to_hex(pack_data->sha1));
        if (move_temp_to_file(curr_index_name, name))
                die("cannot store index file");
+       free((void *)curr_index_name);
        return name;
 }
 
@@ -931,7 +912,7 @@ static void unkeep_all_packs(void)
                struct packed_git *p = all_packs[k];
                snprintf(name, sizeof(name), "%s/pack/pack-%s.keep",
                         get_object_directory(), sha1_to_hex(p->sha1));
-               unlink(name);
+               unlink_or_warn(name);
        }
 }
 
@@ -941,15 +922,17 @@ static void end_packfile(void)
 
        clear_delta_base_cache();
        if (object_count) {
+               unsigned char cur_pack_sha1[20];
                char *idx_name;
                int i;
                struct branch *b;
                struct tag *t;
 
                close_pack_windows(pack_data);
+               sha1close(pack_file, cur_pack_sha1, 0);
                fixup_pack_header_footer(pack_data->pack_fd, pack_data->sha1,
                                    pack_data->pack_name, object_count,
-                                   NULL, 0);
+                                   cur_pack_sha1, pack_size);
                close(pack_data->pack_fd);
                idx_name = keep_pack(create_index());
 
@@ -981,7 +964,7 @@ static void end_packfile(void)
        }
        else {
                close(old_p->pack_fd);
-               unlink(old_p->pack_name);
+               unlink_or_warn(old_p->pack_name);
        }
        free(old_p);
 
@@ -997,29 +980,6 @@ static void cycle_packfile(void)
        start_packfile();
 }
 
-static size_t encode_header(
-       enum object_type type,
-       size_t size,
-       unsigned char *hdr)
-{
-       int n = 1;
-       unsigned char c;
-
-       if (type < OBJ_COMMIT || type > OBJ_REF_DELTA)
-               die("bad type %d", type);
-
-       c = (type << 4) | (size & 15);
-       size >>= 4;
-       while (size) {
-               *hdr++ = c | 0x80;
-               c = size & 0x7f;
-               size >>= 7;
-               n++;
-       }
-       *hdr = c;
-       return n;
-}
-
 static int store_object(
        enum object_type type,
        struct strbuf *dat,
@@ -1047,25 +1007,21 @@ static int store_object(
        e = insert_object(sha1);
        if (mark)
                insert_mark(mark, e);
-       if (e->offset) {
+       if (e->idx.offset) {
                duplicate_count_by_type[type]++;
                return 1;
        } else if (find_sha1_pack(sha1, packed_git)) {
                e->type = type;
                e->pack_id = MAX_PACK_ID;
-               e->offset = 1; /* just not zero! */
+               e->idx.offset = 1; /* just not zero! */
                duplicate_count_by_type[type]++;
                return 1;
        }
 
-       if (last && last->data.buf && last->depth < max_depth) {
+       if (last && last->data.buf && last->depth < max_depth && dat->len > 20) {
                delta = diff_delta(last->data.buf, last->data.len,
                        dat->buf, dat->len,
-                       &deltalen, 0);
-               if (delta && deltalen >= dat->len) {
-                       free(delta);
-                       delta = NULL;
-               }
+                       &deltalen, dat->len - 20);
        } else
                delta = NULL;
 
@@ -1085,7 +1041,7 @@ static int store_object(
        deflateEnd(&s);
 
        /* Determine if we should auto-checkpoint. */
-       if ((pack_size + 60 + s.total_out) > max_packsize
+       if ((max_packsize && (pack_size + 60 + s.total_out) > max_packsize)
                || (pack_size + 60 + s.total_out) < pack_size) {
 
                /* This new object needs to *not* have the current pack_id. */
@@ -1111,36 +1067,40 @@ static int store_object(
 
        e->type = type;
        e->pack_id = pack_id;
-       e->offset = pack_size;
+       e->idx.offset = pack_size;
        object_count++;
        object_count_by_type[type]++;
 
+       crc32_begin(pack_file);
+
        if (delta) {
-               unsigned long ofs = e->offset - last->offset;
+               off_t ofs = e->idx.offset - last->offset;
                unsigned pos = sizeof(hdr) - 1;
 
                delta_count_by_type[type]++;
                e->depth = last->depth + 1;
 
-               hdrlen = encode_header(OBJ_OFS_DELTA, deltalen, hdr);
-               write_or_die(pack_data->pack_fd, hdr, hdrlen);
+               hdrlen = encode_in_pack_object_header(OBJ_OFS_DELTA, deltalen, hdr);
+               sha1write(pack_file, hdr, hdrlen);
                pack_size += hdrlen;
 
                hdr[pos] = ofs & 127;
                while (ofs >>= 7)
                        hdr[--pos] = 128 | (--ofs & 127);
-               write_or_die(pack_data->pack_fd, hdr + pos, sizeof(hdr) - pos);
+               sha1write(pack_file, hdr + pos, sizeof(hdr) - pos);
                pack_size += sizeof(hdr) - pos;
        } else {
                e->depth = 0;
-               hdrlen = encode_header(type, dat->len, hdr);
-               write_or_die(pack_data->pack_fd, hdr, hdrlen);
+               hdrlen = encode_in_pack_object_header(type, dat->len, hdr);
+               sha1write(pack_file, hdr, hdrlen);
                pack_size += hdrlen;
        }
 
-       write_or_die(pack_data->pack_fd, out, s.total_out);
+       sha1write(pack_file, out, s.total_out);
        pack_size += s.total_out;
 
+       e->idx.crc32 = crc32_end(pack_file);
+
        free(out);
        free(delta);
        if (last) {
@@ -1149,12 +1109,137 @@ static int store_object(
                } else {
                        strbuf_swap(&last->data, dat);
                }
-               last->offset = e->offset;
+               last->offset = e->idx.offset;
                last->depth = e->depth;
        }
        return 0;
 }
 
+static void truncate_pack(off_t to, git_SHA_CTX *ctx)
+{
+       if (ftruncate(pack_data->pack_fd, to)
+        || lseek(pack_data->pack_fd, to, SEEK_SET) != to)
+               die_errno("cannot truncate pack to skip duplicate");
+       pack_size = to;
+
+       /* yes this is a layering violation */
+       pack_file->total = to;
+       pack_file->offset = 0;
+       pack_file->ctx = *ctx;
+}
+
+static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
+{
+       size_t in_sz = 64 * 1024, out_sz = 64 * 1024;
+       unsigned char *in_buf = xmalloc(in_sz);
+       unsigned char *out_buf = xmalloc(out_sz);
+       struct object_entry *e;
+       unsigned char sha1[20];
+       unsigned long hdrlen;
+       off_t offset;
+       git_SHA_CTX c;
+       git_SHA_CTX pack_file_ctx;
+       z_stream s;
+       int status = Z_OK;
+
+       /* Determine if we should auto-checkpoint. */
+       if ((max_packsize && (pack_size + 60 + len) > max_packsize)
+               || (pack_size + 60 + len) < pack_size)
+               cycle_packfile();
+
+       offset = pack_size;
+
+       /* preserve the pack_file SHA1 ctx in case we have to truncate later */
+       sha1flush(pack_file);
+       pack_file_ctx = pack_file->ctx;
+
+       hdrlen = snprintf((char *)out_buf, out_sz, "blob %" PRIuMAX, len) + 1;
+       if (out_sz <= hdrlen)
+               die("impossibly large object header");
+
+       git_SHA1_Init(&c);
+       git_SHA1_Update(&c, out_buf, hdrlen);
+
+       crc32_begin(pack_file);
+
+       memset(&s, 0, sizeof(s));
+       deflateInit(&s, pack_compression_level);
+
+       hdrlen = encode_in_pack_object_header(OBJ_BLOB, len, out_buf);
+       if (out_sz <= hdrlen)
+               die("impossibly large object header");
+
+       s.next_out = out_buf + hdrlen;
+       s.avail_out = out_sz - hdrlen;
+
+       while (status != Z_STREAM_END) {
+               if (0 < len && !s.avail_in) {
+                       size_t cnt = in_sz < len ? in_sz : (size_t)len;
+                       size_t n = fread(in_buf, 1, cnt, stdin);
+                       if (!n && feof(stdin))
+                               die("EOF in data (%" PRIuMAX " bytes remaining)", len);
+
+                       git_SHA1_Update(&c, in_buf, n);
+                       s.next_in = in_buf;
+                       s.avail_in = n;
+                       len -= n;
+               }
+
+               status = deflate(&s, len ? 0 : Z_FINISH);
+
+               if (!s.avail_out || status == Z_STREAM_END) {
+                       size_t n = s.next_out - out_buf;
+                       sha1write(pack_file, out_buf, n);
+                       pack_size += n;
+                       s.next_out = out_buf;
+                       s.avail_out = out_sz;
+               }
+
+               switch (status) {
+               case Z_OK:
+               case Z_BUF_ERROR:
+               case Z_STREAM_END:
+                       continue;
+               default:
+                       die("unexpected deflate failure: %d", status);
+               }
+       }
+       deflateEnd(&s);
+       git_SHA1_Final(sha1, &c);
+
+       if (sha1out)
+               hashcpy(sha1out, sha1);
+
+       e = insert_object(sha1);
+
+       if (mark)
+               insert_mark(mark, e);
+
+       if (e->idx.offset) {
+               duplicate_count_by_type[OBJ_BLOB]++;
+               truncate_pack(offset, &pack_file_ctx);
+
+       } else if (find_sha1_pack(sha1, packed_git)) {
+               e->type = OBJ_BLOB;
+               e->pack_id = MAX_PACK_ID;
+               e->idx.offset = 1; /* just not zero! */
+               duplicate_count_by_type[OBJ_BLOB]++;
+               truncate_pack(offset, &pack_file_ctx);
+
+       } else {
+               e->depth = 0;
+               e->type = OBJ_BLOB;
+               e->pack_id = pack_id;
+               e->idx.offset = offset;
+               e->idx.crc32 = crc32_end(pack_file);
+               object_count++;
+               object_count_by_type[OBJ_BLOB]++;
+       }
+
+       free(in_buf);
+       free(out_buf);
+}
+
 /* All calls must be guarded by find_object() or find_mark() to
  * ensure the 'struct object_entry' passed was written by this
  * process instance.  We unpack the entry by the offset, avoiding
@@ -1189,6 +1274,7 @@ static void *gfi_unpack_entry(
                 * the newly written data.
                 */
                close_pack_windows(p);
+               sha1flush(pack_file);
 
                /* We have to offer 20 bytes additional on the end of
                 * the packfile as the core unpacker code assumes the
@@ -1198,7 +1284,7 @@ static void *gfi_unpack_entry(
                 */
                p->pack_size = pack_size + 20;
        }
-       return unpack_entry(p, oe->offset, &type, sizep);
+       return unpack_entry(p, oe->idx.offset, &type, sizep);
 }
 
 static const char *get_mode(const char *str, uint16_t *modep)
@@ -1329,7 +1415,7 @@ static void store_tree(struct tree_entry *root)
        if (S_ISDIR(root->versions[0].mode) && le && le->pack_id == pack_id) {
                mktree(t, 0, &old_tree);
                lo.data = old_tree;
-               lo.offset = le->offset;
+               lo.offset = le->idx.offset;
                lo.depth = t->delta_depth;
        }
 
@@ -1442,6 +1528,14 @@ static int tree_content_remove(
        for (i = 0; i < t->entry_count; i++) {
                e = t->entries[i];
                if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {
+                       if (slash1 && !S_ISDIR(e->versions[1].mode))
+                               /*
+                                * If p names a file in some subdirectory, and a
+                                * file or symlink matching the name of the
+                                * parent directory of p exists, then p cannot
+                                * exist and need not be deleted.
+                                */
+                               return 1;
                        if (!slash1 || !S_ISDIR(e->versions[1].mode))
                                goto del_entry;
                        if (!e->tree)
@@ -1580,14 +1674,14 @@ static void dump_marks_helper(FILE *f,
        if (m->shift) {
                for (k = 0; k < 1024; k++) {
                        if (m->data.sets[k])
-                               dump_marks_helper(f, (base + k) << m->shift,
+                               dump_marks_helper(f, base + (k << m->shift),
                                        m->data.sets[k]);
                }
        } else {
                for (k = 0; k < 1024; k++) {
                        if (m->data.marked[k])
                                fprintf(f, ":%" PRIuMAX " %s\n", base + k,
-                                       sha1_to_hex(m->data.marked[k]->sha1));
+                                       sha1_to_hex(m->data.marked[k]->idx.sha1));
                }
        }
 }
@@ -1598,13 +1692,13 @@ static void dump_marks(void)
        int mark_fd;
        FILE *f;
 
-       if (!mark_file)
+       if (!export_marks_file)
                return;
 
-       mark_fd = hold_lock_file_for_update(&mark_lock, mark_file, 0);
+       mark_fd = hold_lock_file_for_update(&mark_lock, export_marks_file, 0);
        if (mark_fd < 0) {
                failure |= error("Unable to write marks file %s: %s",
-                       mark_file, strerror(errno));
+                       export_marks_file, strerror(errno));
                return;
        }
 
@@ -1613,7 +1707,7 @@ static void dump_marks(void)
                int saved_errno = errno;
                rollback_lock_file(&mark_lock);
                failure |= error("Unable to write marks file %s: %s",
-                       mark_file, strerror(saved_errno));
+                       export_marks_file, strerror(saved_errno));
                return;
        }
 
@@ -1629,7 +1723,7 @@ static void dump_marks(void)
                int saved_errno = errno;
                rollback_lock_file(&mark_lock);
                failure |= error("Unable to write marks file %s: %s",
-                       mark_file, strerror(saved_errno));
+                       export_marks_file, strerror(saved_errno));
                return;
        }
 
@@ -1637,11 +1731,47 @@ static void dump_marks(void)
                int saved_errno = errno;
                rollback_lock_file(&mark_lock);
                failure |= error("Unable to commit marks file %s: %s",
-                       mark_file, strerror(saved_errno));
+                       export_marks_file, strerror(saved_errno));
                return;
        }
 }
 
+static void read_marks(void)
+{
+       char line[512];
+       FILE *f = fopen(import_marks_file, "r");
+       if (!f)
+               die_errno("cannot read '%s'", import_marks_file);
+       while (fgets(line, sizeof(line), f)) {
+               uintmax_t mark;
+               char *end;
+               unsigned char sha1[20];
+               struct object_entry *e;
+
+               end = strchr(line, '\n');
+               if (line[0] != ':' || !end)
+                       die("corrupt mark line: %s", line);
+               *end = 0;
+               mark = strtoumax(line + 1, &end, 10);
+               if (!mark || end == line + 1
+                       || *end != ' ' || get_sha1(end + 1, sha1))
+                       die("corrupt mark line: %s", line);
+               e = find_object(sha1);
+               if (!e) {
+                       enum object_type type = sha1_object_info(sha1, NULL);
+                       if (type < 0)
+                               die("object not found: %s", sha1_to_hex(sha1));
+                       e = insert_object(sha1);
+                       e->type = type;
+                       e->pack_id = MAX_PACK_ID;
+                       e->idx.offset = 1; /* just not zero! */
+               }
+               insert_mark(mark, e);
+       }
+       fclose(f);
+}
+
+
 static int read_next_command(void)
 {
        static int stdin_eof = 0;
@@ -1662,6 +1792,12 @@ static int read_next_command(void)
                        if (stdin_eof)
                                return EOF;
 
+                       if (!seen_data_command
+                               && prefixcmp(command_buf.buf, "feature ")
+                               && prefixcmp(command_buf.buf, "option ")) {
+                               parse_argv();
+                       }
+
                        rc = rc_free;
                        if (rc)
                                rc_free = rc->next;
@@ -1700,7 +1836,7 @@ static void parse_mark(void)
                next_mark = 0;
 }
 
-static void parse_data(struct strbuf *sb)
+static int parse_data(struct strbuf *sb, uintmax_t limit, uintmax_t *len_res)
 {
        strbuf_reset(sb);
 
@@ -1724,9 +1860,15 @@ static void parse_data(struct strbuf *sb)
                free(term);
        }
        else {
-               size_t n = 0, length;
+               uintmax_t len = strtoumax(command_buf.buf + 5, NULL, 10);
+               size_t n = 0, length = (size_t)len;
 
-               length = strtoul(command_buf.buf + 5, NULL, 10);
+               if (limit && limit < len) {
+                       *len_res = len;
+                       return 0;
+               }
+               if (length < len)
+                       die("data is too large to use in this context");
 
                while (n < length) {
                        size_t s = strbuf_fread(sb, length - n, stdin);
@@ -1738,16 +1880,19 @@ static void parse_data(struct strbuf *sb)
        }
 
        skip_optional_lf();
+       return 1;
 }
 
 static int validate_raw_date(const char *src, char *result, int maxlen)
 {
        const char *orig_src = src;
        char *endp;
+       unsigned long num;
 
        errno = 0;
 
-       strtoul(src, &endp, 10);
+       num = strtoul(src, &endp, 10);
+       /* NEEDSWORK: perhaps check for reasonable values? */
        if (errno || endp == src || *endp != ' ')
                return -1;
 
@@ -1755,8 +1900,9 @@ static int validate_raw_date(const char *src, char *result, int maxlen)
        if (*src != '-' && *src != '+')
                return -1;
 
-       strtoul(src + 1, &endp, 10);
-       if (errno || endp == src || *endp || (endp - orig_src) >= maxlen)
+       num = strtoul(src + 1, &endp, 10);
+       if (errno || endp == src + 1 || *endp || (endp - orig_src) >= maxlen ||
+           1400 < num)
                return -1;
 
        strcpy(result, orig_src);
@@ -1799,14 +1945,32 @@ static char *parse_ident(const char *buf)
        return ident;
 }
 
-static void parse_new_blob(void)
+static void parse_and_store_blob(
+       struct last_object *last,
+       unsigned char *sha1out,
+       uintmax_t mark)
 {
        static struct strbuf buf = STRBUF_INIT;
+       uintmax_t len;
+
+       if (parse_data(&buf, big_file_threshold, &len))
+               store_object(OBJ_BLOB, &buf, last, sha1out, mark);
+       else {
+               if (last) {
+                       strbuf_release(&last->data);
+                       last->offset = 0;
+                       last->depth = 0;
+               }
+               stream_blob(len, sha1out, mark);
+               skip_optional_lf();
+       }
+}
 
+static void parse_new_blob(void)
+{
        read_next_command();
        parse_mark();
-       parse_data(&buf);
-       store_object(OBJ_BLOB, &buf, &last_blob, NULL, next_mark);
+       parse_and_store_blob(&last_blob, NULL, next_mark);
 }
 
 static void unload_one_branch(void)
@@ -1853,6 +2017,109 @@ static void load_branch(struct branch *b)
        }
 }
 
+static unsigned char convert_num_notes_to_fanout(uintmax_t num_notes)
+{
+       unsigned char fanout = 0;
+       while ((num_notes >>= 8))
+               fanout++;
+       return fanout;
+}
+
+static void construct_path_with_fanout(const char *hex_sha1,
+               unsigned char fanout, char *path)
+{
+       unsigned int i = 0, j = 0;
+       if (fanout >= 20)
+               die("Too large fanout (%u)", fanout);
+       while (fanout) {
+               path[i++] = hex_sha1[j++];
+               path[i++] = hex_sha1[j++];
+               path[i++] = '/';
+               fanout--;
+       }
+       memcpy(path + i, hex_sha1 + j, 40 - j);
+       path[i + 40 - j] = '\0';
+}
+
+static uintmax_t do_change_note_fanout(
+               struct tree_entry *orig_root, struct tree_entry *root,
+               char *hex_sha1, unsigned int hex_sha1_len,
+               char *fullpath, unsigned int fullpath_len,
+               unsigned char fanout)
+{
+       struct tree_content *t = root->tree;
+       struct tree_entry *e, leaf;
+       unsigned int i, tmp_hex_sha1_len, tmp_fullpath_len;
+       uintmax_t num_notes = 0;
+       unsigned char sha1[20];
+       char realpath[60];
+
+       for (i = 0; t && i < t->entry_count; i++) {
+               e = t->entries[i];
+               tmp_hex_sha1_len = hex_sha1_len + e->name->str_len;
+               tmp_fullpath_len = fullpath_len;
+
+               /*
+                * We're interested in EITHER existing note entries (entries
+                * with exactly 40 hex chars in path, not including directory
+                * separators), OR directory entries that may contain note
+                * entries (with < 40 hex chars in path).
+                * Also, each path component in a note entry must be a multiple
+                * of 2 chars.
+                */
+               if (!e->versions[1].mode ||
+                   tmp_hex_sha1_len > 40 ||
+                   e->name->str_len % 2)
+                       continue;
+
+               /* This _may_ be a note entry, or a subdir containing notes */
+               memcpy(hex_sha1 + hex_sha1_len, e->name->str_dat,
+                      e->name->str_len);
+               if (tmp_fullpath_len)
+                       fullpath[tmp_fullpath_len++] = '/';
+               memcpy(fullpath + tmp_fullpath_len, e->name->str_dat,
+                      e->name->str_len);
+               tmp_fullpath_len += e->name->str_len;
+               fullpath[tmp_fullpath_len] = '\0';
+
+               if (tmp_hex_sha1_len == 40 && !get_sha1_hex(hex_sha1, sha1)) {
+                       /* This is a note entry */
+                       construct_path_with_fanout(hex_sha1, fanout, realpath);
+                       if (!strcmp(fullpath, realpath)) {
+                               /* Note entry is in correct location */
+                               num_notes++;
+                               continue;
+                       }
+
+                       /* Rename fullpath to realpath */
+                       if (!tree_content_remove(orig_root, fullpath, &leaf))
+                               die("Failed to remove path %s", fullpath);
+                       tree_content_set(orig_root, realpath,
+                               leaf.versions[1].sha1,
+                               leaf.versions[1].mode,
+                               leaf.tree);
+               } else if (S_ISDIR(e->versions[1].mode)) {
+                       /* This is a subdir that may contain note entries */
+                       if (!e->tree)
+                               load_tree(e);
+                       num_notes += do_change_note_fanout(orig_root, e,
+                               hex_sha1, tmp_hex_sha1_len,
+                               fullpath, tmp_fullpath_len, fanout);
+               }
+
+               /* The above may have reallocated the current tree_content */
+               t = root->tree;
+       }
+       return num_notes;
+}
+
+static uintmax_t change_note_fanout(struct tree_entry *root,
+               unsigned char fanout)
+{
+       char hex_sha1[40], path[60];
+       return do_change_note_fanout(root, root, hex_sha1, 0, path, 0, fanout);
+}
+
 static void file_change_m(struct branch *b)
 {
        const char *p = command_buf.buf + 2;
@@ -1872,6 +2139,7 @@ static void file_change_m(struct branch *b)
        case S_IFREG | 0644:
        case S_IFREG | 0755:
        case S_IFLNK:
+       case S_IFDIR:
        case S_IFGITLINK:
                /* ok */
                break;
@@ -1882,7 +2150,7 @@ static void file_change_m(struct branch *b)
        if (*p == ':') {
                char *x;
                oe = find_mark(strtoumax(p + 1, &x, 10));
-               hashcpy(sha1, oe->sha1);
+               hashcpy(sha1, oe->idx.sha1);
                p = x;
        } else if (!prefixcmp(p, "inline")) {
                inline_data = 1;
@@ -1917,26 +2185,28 @@ static void file_change_m(struct branch *b)
                 * another repository.
                 */
        } else if (inline_data) {
-               static struct strbuf buf = STRBUF_INIT;
-
+               if (S_ISDIR(mode))
+                       die("Directories cannot be specified 'inline': %s",
+                               command_buf.buf);
                if (p != uq.buf) {
                        strbuf_addstr(&uq, p);
                        p = uq.buf;
                }
                read_next_command();
-               parse_data(&buf);
-               store_object(OBJ_BLOB, &buf, &last_blob, sha1, 0);
-       } else if (oe) {
-               if (oe->type != OBJ_BLOB)
-                       die("Not a blob (actually a %s): %s",
-                               typename(oe->type), command_buf.buf);
+               parse_and_store_blob(&last_blob, sha1, 0);
        } else {
-               enum object_type type = sha1_object_info(sha1, NULL);
+               enum object_type expected = S_ISDIR(mode) ?
+                                               OBJ_TREE: OBJ_BLOB;
+               enum object_type type = oe ? oe->type :
+                                       sha1_object_info(sha1, NULL);
                if (type < 0)
-                       die("Blob not found: %s", command_buf.buf);
-               if (type != OBJ_BLOB)
-                       die("Not a blob (actually a %s): %s",
-                           typename(type), command_buf.buf);
+                       die("%s not found: %s",
+                                       S_ISDIR(mode) ?  "Tree" : "Blob",
+                                       command_buf.buf);
+               if (type != expected)
+                       die("Not a %s (actually a %s): %s",
+                               typename(expected), typename(type),
+                               command_buf.buf);
        }
 
        tree_content_set(&b->branch_tree, p, sha1, mode, NULL);
@@ -2003,12 +2273,95 @@ static void file_change_cr(struct branch *b, int rename)
                leaf.tree);
 }
 
+static void note_change_n(struct branch *b, unsigned char old_fanout)
+{
+       const char *p = command_buf.buf + 2;
+       static struct strbuf uq = STRBUF_INIT;
+       struct object_entry *oe = oe;
+       struct branch *s;
+       unsigned char sha1[20], commit_sha1[20];
+       char path[60];
+       uint16_t inline_data = 0;
+       unsigned char new_fanout;
+
+       /* <dataref> or 'inline' */
+       if (*p == ':') {
+               char *x;
+               oe = find_mark(strtoumax(p + 1, &x, 10));
+               hashcpy(sha1, oe->idx.sha1);
+               p = x;
+       } else if (!prefixcmp(p, "inline")) {
+               inline_data = 1;
+               p += 6;
+       } else {
+               if (get_sha1_hex(p, sha1))
+                       die("Invalid SHA1: %s", command_buf.buf);
+               oe = find_object(sha1);
+               p += 40;
+       }
+       if (*p++ != ' ')
+               die("Missing space after SHA1: %s", command_buf.buf);
+
+       /* <committish> */
+       s = lookup_branch(p);
+       if (s) {
+               hashcpy(commit_sha1, s->sha1);
+       } else if (*p == ':') {
+               uintmax_t commit_mark = strtoumax(p + 1, NULL, 10);
+               struct object_entry *commit_oe = find_mark(commit_mark);
+               if (commit_oe->type != OBJ_COMMIT)
+                       die("Mark :%" PRIuMAX " not a commit", commit_mark);
+               hashcpy(commit_sha1, commit_oe->idx.sha1);
+       } else if (!get_sha1(p, commit_sha1)) {
+               unsigned long size;
+               char *buf = read_object_with_reference(commit_sha1,
+                       commit_type, &size, commit_sha1);
+               if (!buf || size < 46)
+                       die("Not a valid commit: %s", p);
+               free(buf);
+       } else
+               die("Invalid ref name or SHA1 expression: %s", p);
+
+       if (inline_data) {
+               if (p != uq.buf) {
+                       strbuf_addstr(&uq, p);
+                       p = uq.buf;
+               }
+               read_next_command();
+               parse_and_store_blob(&last_blob, sha1, 0);
+       } else if (oe) {
+               if (oe->type != OBJ_BLOB)
+                       die("Not a blob (actually a %s): %s",
+                               typename(oe->type), command_buf.buf);
+       } else if (!is_null_sha1(sha1)) {
+               enum object_type type = sha1_object_info(sha1, NULL);
+               if (type < 0)
+                       die("Blob not found: %s", command_buf.buf);
+               if (type != OBJ_BLOB)
+                       die("Not a blob (actually a %s): %s",
+                           typename(type), command_buf.buf);
+       }
+
+       construct_path_with_fanout(sha1_to_hex(commit_sha1), old_fanout, path);
+       if (tree_content_remove(&b->branch_tree, path, NULL))
+               b->num_notes--;
+
+       if (is_null_sha1(sha1))
+               return; /* nothing to insert */
+
+       b->num_notes++;
+       new_fanout = convert_num_notes_to_fanout(b->num_notes);
+       construct_path_with_fanout(sha1_to_hex(commit_sha1), new_fanout, path);
+       tree_content_set(&b->branch_tree, path, sha1, S_IFREG | 0644, NULL);
+}
+
 static void file_change_deleteall(struct branch *b)
 {
        release_tree_content_recursive(b->branch_tree.tree);
        hashclr(b->branch_tree.versions[0].sha1);
        hashclr(b->branch_tree.versions[1].sha1);
        load_tree(&b->branch_tree);
+       b->num_notes = 0;
 }
 
 static void parse_from_commit(struct branch *b, char *buf, unsigned long size)
@@ -2065,7 +2418,7 @@ static int parse_from(struct branch *b)
                struct object_entry *oe = find_mark(idnum);
                if (oe->type != OBJ_COMMIT)
                        die("Mark :%" PRIuMAX " not a commit", idnum);
-               hashcpy(b->sha1, oe->sha1);
+               hashcpy(b->sha1, oe->idx.sha1);
                if (oe->pack_id != MAX_PACK_ID) {
                        unsigned long size;
                        char *buf = gfi_unpack_entry(oe, &size);
@@ -2100,7 +2453,7 @@ static struct hash_list *parse_merge(unsigned int *count)
                        struct object_entry *oe = find_mark(idnum);
                        if (oe->type != OBJ_COMMIT)
                                die("Mark :%" PRIuMAX " not a commit", idnum);
-                       hashcpy(n->sha1, oe->sha1);
+                       hashcpy(n->sha1, oe->idx.sha1);
                } else if (!get_sha1(from, n->sha1)) {
                        unsigned long size;
                        char *buf = read_object_with_reference(n->sha1,
@@ -2132,6 +2485,7 @@ static void parse_new_commit(void)
        char *committer = NULL;
        struct hash_list *merge_list = NULL;
        unsigned int merge_count;
+       unsigned char prev_fanout, new_fanout;
 
        /* Obtain the branch name from the rest of our command */
        sp = strchr(command_buf.buf, ' ') + 1;
@@ -2151,7 +2505,7 @@ static void parse_new_commit(void)
        }
        if (!committer)
                die("Expected committer but didn't get one");
-       parse_data(&msg);
+       parse_data(&msg, 0, NULL);
        read_next_command();
        parse_from(b);
        merge_list = parse_merge(&merge_count);
@@ -2162,6 +2516,8 @@ static void parse_new_commit(void)
                load_branch(b);
        }
 
+       prev_fanout = convert_num_notes_to_fanout(b->num_notes);
+
        /* file_change* */
        while (command_buf.len > 0) {
                if (!prefixcmp(command_buf.buf, "M "))
@@ -2172,6 +2528,8 @@ static void parse_new_commit(void)
                        file_change_cr(b, 1);
                else if (!prefixcmp(command_buf.buf, "C "))
                        file_change_cr(b, 0);
+               else if (!prefixcmp(command_buf.buf, "N "))
+                       note_change_n(b, prev_fanout);
                else if (!strcmp("deleteall", command_buf.buf))
                        file_change_deleteall(b);
                else {
@@ -2182,6 +2540,10 @@ static void parse_new_commit(void)
                        break;
        }
 
+       new_fanout = convert_num_notes_to_fanout(b->num_notes);
+       if (new_fanout != prev_fanout)
+               b->num_notes = change_note_fanout(&b->branch_tree, new_fanout);
+
        /* build the tree and the commit */
        store_tree(&b->branch_tree);
        hashcpy(b->branch_tree.versions[0].sha1,
@@ -2222,6 +2584,7 @@ static void parse_new_tag(void)
        struct tag *t;
        uintmax_t from_mark = 0;
        unsigned char sha1[20];
+       enum object_type type;
 
        /* Obtain the new tag name from the rest of our command */
        sp = strchr(command_buf.buf, ' ') + 1;
@@ -2242,19 +2605,18 @@ static void parse_new_tag(void)
        s = lookup_branch(from);
        if (s) {
                hashcpy(sha1, s->sha1);
+               type = OBJ_COMMIT;
        } else if (*from == ':') {
                struct object_entry *oe;
                from_mark = strtoumax(from + 1, NULL, 10);
                oe = find_mark(from_mark);
-               if (oe->type != OBJ_COMMIT)
-                       die("Mark :%" PRIuMAX " not a commit", from_mark);
-               hashcpy(sha1, oe->sha1);
+               type = oe->type;
+               hashcpy(sha1, oe->idx.sha1);
        } else if (!get_sha1(from, sha1)) {
                unsigned long size;
                char *buf;
 
-               buf = read_object_with_reference(sha1,
-                       commit_type, &size, sha1);
+               buf = read_sha1_file(sha1, &type, &size);
                if (!buf || size < 46)
                        die("Not a valid commit: %s", from);
                free(buf);
@@ -2270,7 +2632,7 @@ static void parse_new_tag(void)
                tagger = NULL;
 
        /* tag payload/message */
-       parse_data(&msg);
+       parse_data(&msg, 0, NULL);
 
        /* build the tag object */
        strbuf_reset(&new_data);
@@ -2279,7 +2641,7 @@ static void parse_new_tag(void)
                    "object %s\n"
                    "type %s\n"
                    "tag %s\n",
-                   sha1_to_hex(sha1), commit_type, t->name);
+                   sha1_to_hex(sha1), typename(type), t->name);
        if (tagger)
                strbuf_addf(&new_data,
                            "tagger %s\n", tagger);
@@ -2337,39 +2699,152 @@ static void parse_progress(void)
        skip_optional_lf();
 }
 
-static void import_marks(const char *input_file)
+static char* make_fast_import_path(const char *path)
 {
-       char line[512];
-       FILE *f = fopen(input_file, "r");
-       if (!f)
-               die("cannot read %s: %s", input_file, strerror(errno));
-       while (fgets(line, sizeof(line), f)) {
-               uintmax_t mark;
-               char *end;
-               unsigned char sha1[20];
-               struct object_entry *e;
+       struct strbuf abs_path = STRBUF_INIT;
 
-               end = strchr(line, '\n');
-               if (line[0] != ':' || !end)
-                       die("corrupt mark line: %s", line);
-               *end = 0;
-               mark = strtoumax(line + 1, &end, 10);
-               if (!mark || end == line + 1
-                       || *end != ' ' || get_sha1(end + 1, sha1))
-                       die("corrupt mark line: %s", line);
-               e = find_object(sha1);
-               if (!e) {
-                       enum object_type type = sha1_object_info(sha1, NULL);
-                       if (type < 0)
-                               die("object not found: %s", sha1_to_hex(sha1));
-                       e = insert_object(sha1);
-                       e->type = type;
-                       e->pack_id = MAX_PACK_ID;
-                       e->offset = 1; /* just not zero! */
+       if (!relative_marks_paths || is_absolute_path(path))
+               return xstrdup(path);
+       strbuf_addf(&abs_path, "%s/info/fast-import/%s", get_git_dir(), path);
+       return strbuf_detach(&abs_path, NULL);
+}
+
+static void option_import_marks(const char *marks, int from_stream)
+{
+       if (import_marks_file) {
+               if (from_stream)
+                       die("Only one import-marks command allowed per stream");
+
+               /* read previous mark file */
+               if(!import_marks_file_from_stream)
+                       read_marks();
+       }
+
+       import_marks_file = make_fast_import_path(marks);
+       safe_create_leading_directories_const(import_marks_file);
+       import_marks_file_from_stream = from_stream;
+}
+
+static void option_date_format(const char *fmt)
+{
+       if (!strcmp(fmt, "raw"))
+               whenspec = WHENSPEC_RAW;
+       else if (!strcmp(fmt, "rfc2822"))
+               whenspec = WHENSPEC_RFC2822;
+       else if (!strcmp(fmt, "now"))
+               whenspec = WHENSPEC_NOW;
+       else
+               die("unknown --date-format argument %s", fmt);
+}
+
+static void option_depth(const char *depth)
+{
+       max_depth = strtoul(depth, NULL, 0);
+       if (max_depth > MAX_DEPTH)
+               die("--depth cannot exceed %u", MAX_DEPTH);
+}
+
+static void option_active_branches(const char *branches)
+{
+       max_active_branches = strtoul(branches, NULL, 0);
+}
+
+static void option_export_marks(const char *marks)
+{
+       export_marks_file = make_fast_import_path(marks);
+       safe_create_leading_directories_const(export_marks_file);
+}
+
+static void option_export_pack_edges(const char *edges)
+{
+       if (pack_edges)
+               fclose(pack_edges);
+       pack_edges = fopen(edges, "a");
+       if (!pack_edges)
+               die_errno("Cannot open '%s'", edges);
+}
+
+static int parse_one_option(const char *option)
+{
+       if (!prefixcmp(option, "max-pack-size=")) {
+               unsigned long v;
+               if (!git_parse_ulong(option + 14, &v))
+                       return 0;
+               if (v < 8192) {
+                       warning("max-pack-size is now in bytes, assuming --max-pack-size=%lum", v);
+                       v *= 1024 * 1024;
+               } else if (v < 1024 * 1024) {
+                       warning("minimum max-pack-size is 1 MiB");
+                       v = 1024 * 1024;
                }
-               insert_mark(mark, e);
+               max_packsize = v;
+       } else if (!prefixcmp(option, "big-file-threshold=")) {
+               unsigned long v;
+               if (!git_parse_ulong(option + 19, &v))
+                       return 0;
+               big_file_threshold = v;
+       } else if (!prefixcmp(option, "depth=")) {
+               option_depth(option + 6);
+       } else if (!prefixcmp(option, "active-branches=")) {
+               option_active_branches(option + 16);
+       } else if (!prefixcmp(option, "export-pack-edges=")) {
+               option_export_pack_edges(option + 18);
+       } else if (!prefixcmp(option, "quiet")) {
+               show_stats = 0;
+       } else if (!prefixcmp(option, "stats")) {
+               show_stats = 1;
+       } else {
+               return 0;
        }
-       fclose(f);
+
+       return 1;
+}
+
+static int parse_one_feature(const char *feature, int from_stream)
+{
+       if (!prefixcmp(feature, "date-format=")) {
+               option_date_format(feature + 12);
+       } else if (!prefixcmp(feature, "import-marks=")) {
+               option_import_marks(feature + 13, from_stream);
+       } else if (!prefixcmp(feature, "export-marks=")) {
+               option_export_marks(feature + 13);
+       } else if (!prefixcmp(feature, "relative-marks")) {
+               relative_marks_paths = 1;
+       } else if (!prefixcmp(feature, "no-relative-marks")) {
+               relative_marks_paths = 0;
+       } else if (!prefixcmp(feature, "force")) {
+               force_update = 1;
+       } else {
+               return 0;
+       }
+
+       return 1;
+}
+
+static void parse_feature(void)
+{
+       char *feature = command_buf.buf + 8;
+
+       if (seen_data_command)
+               die("Got feature command '%s' after data command", feature);
+
+       if (parse_one_feature(feature, 1))
+               return;
+
+       die("This version of fast-import does not support feature %s.", feature);
+}
+
+static void parse_option(void)
+{
+       char *option = command_buf.buf + 11;
+
+       if (seen_data_command)
+               die("Got option command '%s' after data command", option);
+
+       if (parse_one_option(option))
+               return;
+
+       die("This version of fast-import does not support option: %s", option);
 }
 
 static int git_pack_config(const char *k, const char *v, void *cb)
@@ -2390,18 +2865,62 @@ static int git_pack_config(const char *k, const char *v, void *cb)
                pack_compression_seen = 1;
                return 0;
        }
+       if (!strcmp(k, "pack.indexversion")) {
+               pack_idx_default_version = git_config_int(k, v);
+               if (pack_idx_default_version > 2)
+                       die("bad pack.indexversion=%"PRIu32,
+                           pack_idx_default_version);
+               return 0;
+       }
+       if (!strcmp(k, "pack.packsizelimit")) {
+               max_packsize = git_config_ulong(k, v);
+               return 0;
+       }
+       if (!strcmp(k, "core.bigfilethreshold")) {
+               long n = git_config_int(k, v);
+               big_file_threshold = 0 < n ? n : 0;
+       }
        return git_default_config(k, v, cb);
 }
 
 static const char fast_import_usage[] =
-"git fast-import [--date-format=f] [--max-pack-size=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file]";
+"git fast-import [--date-format=f] [--max-pack-size=n] [--big-file-threshold=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file]";
+
+static void parse_argv(void)
+{
+       unsigned int i;
+
+       for (i = 1; i < global_argc; i++) {
+               const char *a = global_argv[i];
+
+               if (*a != '-' || !strcmp(a, "--"))
+                       break;
+
+               if (parse_one_option(a + 2))
+                       continue;
+
+               if (parse_one_feature(a + 2, 0))
+                       continue;
+
+               die("unknown option %s", a);
+       }
+       if (i != global_argc)
+               usage(fast_import_usage);
+
+       seen_data_command = 1;
+       if (import_marks_file)
+               read_marks();
+}
 
 int main(int argc, const char **argv)
 {
-       unsigned int i, show_stats = 1;
+       unsigned int i;
 
        git_extract_argv0_path(argv[0]);
 
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage(fast_import_usage);
+
        setup_git_directory();
        git_config(git_pack_config, NULL);
        if (!pack_compression_seen && core_compression_seen)
@@ -2414,52 +2933,8 @@ int main(int argc, const char **argv)
        avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*));
        marks = pool_calloc(1, sizeof(struct mark_set));
 
-       for (i = 1; i < argc; i++) {
-               const char *a = argv[i];
-
-               if (*a != '-' || !strcmp(a, "--"))
-                       break;
-               else if (!prefixcmp(a, "--date-format=")) {
-                       const char *fmt = a + 14;
-                       if (!strcmp(fmt, "raw"))
-                               whenspec = WHENSPEC_RAW;
-                       else if (!strcmp(fmt, "rfc2822"))
-                               whenspec = WHENSPEC_RFC2822;
-                       else if (!strcmp(fmt, "now"))
-                               whenspec = WHENSPEC_NOW;
-                       else
-                               die("unknown --date-format argument %s", fmt);
-               }
-               else if (!prefixcmp(a, "--max-pack-size="))
-                       max_packsize = strtoumax(a + 16, NULL, 0) * 1024 * 1024;
-               else if (!prefixcmp(a, "--depth=")) {
-                       max_depth = strtoul(a + 8, NULL, 0);
-                       if (max_depth > MAX_DEPTH)
-                               die("--depth cannot exceed %u", MAX_DEPTH);
-               }
-               else if (!prefixcmp(a, "--active-branches="))
-                       max_active_branches = strtoul(a + 18, NULL, 0);
-               else if (!prefixcmp(a, "--import-marks="))
-                       import_marks(a + 15);
-               else if (!prefixcmp(a, "--export-marks="))
-                       mark_file = a + 15;
-               else if (!prefixcmp(a, "--export-pack-edges=")) {
-                       if (pack_edges)
-                               fclose(pack_edges);
-                       pack_edges = fopen(a + 20, "a");
-                       if (!pack_edges)
-                               die("Cannot open %s: %s", a + 20, strerror(errno));
-               } else if (!strcmp(a, "--force"))
-                       force_update = 1;
-               else if (!strcmp(a, "--quiet"))
-                       show_stats = 0;
-               else if (!strcmp(a, "--stats"))
-                       show_stats = 1;
-               else
-                       die("unknown option %s", a);
-       }
-       if (i != argc)
-               usage(fast_import_usage);
+       global_argc = argc;
+       global_argv = argv;
 
        rc_free = pool_alloc(cmd_save * sizeof(*rc_free));
        for (i = 0; i < (cmd_save - 1); i++)
@@ -2482,9 +2957,20 @@ int main(int argc, const char **argv)
                        parse_checkpoint();
                else if (!prefixcmp(command_buf.buf, "progress "))
                        parse_progress();
+               else if (!prefixcmp(command_buf.buf, "feature "))
+                       parse_feature();
+               else if (!prefixcmp(command_buf.buf, "option git "))
+                       parse_option();
+               else if (!prefixcmp(command_buf.buf, "option "))
+                       /* ignore non-git options*/;
                else
                        die("Unsupported command: %s", command_buf.buf);
        }
+
+       /* argv hasn't been parsed yet, do so */
+       if (!seen_data_command)
+               parse_argv();
+
        end_packfile();
 
        dump_branches();
index 8bd9c32561e79d194d27fa10cc98a26aa2cb673c..fbe85ac05fd2d945d645365dc086460003fa7f27 100644 (file)
@@ -13,7 +13,8 @@ struct fetch_pack_args
                fetch_all:1,
                verbose:1,
                no_progress:1,
-               include_tag:1;
+               include_tag:1,
+               stateless_rpc:1;
 };
 
 struct ref *fetch_pack(struct fetch_pack_args *args,
diff --git a/fsck.c b/fsck.c
index 511b82cba9977c14d6dcba5efbd6d38591d3aacc..3d05d4a794a4158a421ce1265422d241e28a5278 100644 (file)
--- a/fsck.c
+++ b/fsck.c
@@ -222,14 +222,49 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func)
        return retval;
 }
 
+static int fsck_ident(char **ident, struct object *obj, fsck_error error_func)
+{
+       if (**ident == '<' || **ident == '\n')
+               return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email");
+       *ident += strcspn(*ident, "<\n");
+       if ((*ident)[-1] != ' ')
+               return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email");
+       if (**ident != '<')
+               return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing email");
+       (*ident)++;
+       *ident += strcspn(*ident, "<>\n");
+       if (**ident != '>')
+               return error_func(obj, FSCK_ERROR, "invalid author/committer line - bad email");
+       (*ident)++;
+       if (**ident != ' ')
+               return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before date");
+       (*ident)++;
+       if (**ident == '0' && (*ident)[1] != ' ')
+               return error_func(obj, FSCK_ERROR, "invalid author/committer line - zero-padded date");
+       *ident += strspn(*ident, "0123456789");
+       if (**ident != ' ')
+               return error_func(obj, FSCK_ERROR, "invalid author/committer line - bad date");
+       (*ident)++;
+       if ((**ident != '+' && **ident != '-') ||
+           !isdigit((*ident)[1]) ||
+           !isdigit((*ident)[2]) ||
+           !isdigit((*ident)[3]) ||
+           !isdigit((*ident)[4]) ||
+           ((*ident)[5] != '\n'))
+               return error_func(obj, FSCK_ERROR, "invalid author/committer line - bad time zone");
+       (*ident) += 6;
+       return 0;
+}
+
 static int fsck_commit(struct commit *commit, fsck_error error_func)
 {
        char *buffer = commit->buffer;
        unsigned char tree_sha1[20], sha1[20];
        struct commit_graft *graft;
        int parents = 0;
+       int err;
 
-       if (!commit->date)
+       if (commit->date == ULONG_MAX)
                return error_func(&commit->object, FSCK_ERROR, "invalid author/committer line");
 
        if (memcmp(buffer, "tree ", 5))
@@ -266,6 +301,16 @@ static int fsck_commit(struct commit *commit, fsck_error error_func)
        }
        if (memcmp(buffer, "author ", 7))
                return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'author' line");
+       buffer += 7;
+       err = fsck_ident(&buffer, &commit->object, error_func);
+       if (err)
+               return err;
+       if (memcmp(buffer, "committer ", strlen("committer ")))
+               return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'committer' line");
+       buffer += strlen("committer ");
+       err = fsck_ident(&buffer, &commit->object, error_func);
+       if (err)
+               return err;
        if (!commit->tree)
                return error_func(&commit->object, FSCK_ERROR, "could not load commit's tree %s", sha1_to_hex(tree_sha1));
 
diff --git a/fsck.h b/fsck.h
index 008456b675b6e5c8a5a07e0b8628a41e3acc733a..1e4f527318ea4b4d701a27e08d68f3551173f176 100644 (file)
--- a/fsck.h
+++ b/fsck.h
@@ -17,6 +17,7 @@ typedef int (*fsck_walk_func)(struct object *obj, int type, void *data);
 /* callback for fsck_object, type is FSCK_ERROR or FSCK_WARN */
 typedef int (*fsck_error)(struct object *obj, int type, const char *err, ...);
 
+__attribute__((format (printf, 3, 4)))
 int fsck_error_function(struct object *obj, int type, const char *fmt, ...);
 
 /* descend in all linked child objects
index f6e536ece314316ebb281721ed27d7519577202f..27fc79347af428dd39daa5b550814cc6cf980510 100755 (executable)
@@ -72,6 +72,79 @@ sub colored {
 
 # command line options
 my $patch_mode;
+my $patch_mode_revision;
+
+sub apply_patch;
+sub apply_patch_for_checkout_commit;
+sub apply_patch_for_stash;
+
+my %patch_modes = (
+       'stage' => {
+               DIFF => 'diff-files -p',
+               APPLY => sub { apply_patch 'apply --cached', @_; },
+               APPLY_CHECK => 'apply --cached',
+               VERB => 'Stage',
+               TARGET => '',
+               PARTICIPLE => 'staging',
+               FILTER => 'file-only',
+       },
+       'stash' => {
+               DIFF => 'diff-index -p HEAD',
+               APPLY => sub { apply_patch 'apply --cached', @_; },
+               APPLY_CHECK => 'apply --cached',
+               VERB => 'Stash',
+               TARGET => '',
+               PARTICIPLE => 'stashing',
+               FILTER => undef,
+       },
+       'reset_head' => {
+               DIFF => 'diff-index -p --cached',
+               APPLY => sub { apply_patch 'apply -R --cached', @_; },
+               APPLY_CHECK => 'apply -R --cached',
+               VERB => 'Unstage',
+               TARGET => '',
+               PARTICIPLE => 'unstaging',
+               FILTER => 'index-only',
+       },
+       'reset_nothead' => {
+               DIFF => 'diff-index -R -p --cached',
+               APPLY => sub { apply_patch 'apply --cached', @_; },
+               APPLY_CHECK => 'apply --cached',
+               VERB => 'Apply',
+               TARGET => ' to index',
+               PARTICIPLE => 'applying',
+               FILTER => 'index-only',
+       },
+       'checkout_index' => {
+               DIFF => 'diff-files -p',
+               APPLY => sub { apply_patch 'apply -R', @_; },
+               APPLY_CHECK => 'apply -R',
+               VERB => 'Discard',
+               TARGET => ' from worktree',
+               PARTICIPLE => 'discarding',
+               FILTER => 'file-only',
+       },
+       'checkout_head' => {
+               DIFF => 'diff-index -p',
+               APPLY => sub { apply_patch_for_checkout_commit '-R', @_ },
+               APPLY_CHECK => 'apply -R',
+               VERB => 'Discard',
+               TARGET => ' from index and worktree',
+               PARTICIPLE => 'discarding',
+               FILTER => undef,
+       },
+       'checkout_nothead' => {
+               DIFF => 'diff-index -R -p',
+               APPLY => sub { apply_patch_for_checkout_commit '', @_ },
+               APPLY_CHECK => 'apply',
+               VERB => 'Apply',
+               TARGET => ' to index and worktree',
+               PARTICIPLE => 'applying',
+               FILTER => undef,
+       },
+);
+
+my %patch_mode_flavour = %{$patch_modes{stage}};
 
 sub run_cmd_pipe {
        if ($^O eq 'MSWin32' || $^O eq 'msys') {
@@ -186,11 +259,18 @@ sub list_modified {
                @tracked = map {
                        chomp $_;
                        unquote_path($_);
-               } run_cmd_pipe(qw(git ls-files --exclude-standard --), @ARGV);
+               } run_cmd_pipe(qw(git ls-files --), @ARGV);
                return if (!@tracked);
        }
 
-       my $reference = is_initial_commit() ? get_empty_tree() : 'HEAD';
+       my $reference;
+       if (defined $patch_mode_revision and $patch_mode_revision ne 'HEAD') {
+               $reference = $patch_mode_revision;
+       } elsif (is_initial_commit()) {
+               $reference = get_empty_tree();
+       } else {
+               $reference = 'HEAD';
+       }
        for (run_cmd_pipe(qw(git diff-index --cached
                             --numstat --summary), $reference,
                             '--', @tracked)) {
@@ -613,12 +693,24 @@ sub add_untracked_cmd {
        print "\n";
 }
 
+sub run_git_apply {
+       my $cmd = shift;
+       my $fh;
+       open $fh, '| git ' . $cmd;
+       print $fh @_;
+       return close $fh;
+}
+
 sub parse_diff {
        my ($path) = @_;
-       my @diff = run_cmd_pipe(qw(git diff-files -p --), $path);
+       my @diff_cmd = split(" ", $patch_mode_flavour{DIFF});
+       if (defined $patch_mode_revision) {
+               push @diff_cmd, $patch_mode_revision;
+       }
+       my @diff = run_cmd_pipe("git", @diff_cmd, "--", $path);
        my @colored = ();
        if ($diff_use_color) {
-               @colored = run_cmd_pipe(qw(git diff-files -p --color --), $path);
+               @colored = run_cmd_pipe("git", @diff_cmd, qw(--color --), $path);
        }
        my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' };
 
@@ -639,14 +731,17 @@ sub parse_diff_header {
 
        my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' };
        my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' };
+       my $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' };
 
        for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
-               my $dest = $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ?
-                       $mode : $head;
+               my $dest =
+                  $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode :
+                  $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :
+                  $head;
                push @{$dest->{TEXT}}, $src->{TEXT}->[$i];
                push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];
        }
-       return ($head, $mode);
+       return ($head, $mode, $deletion);
 }
 
 sub hunk_splittable {
@@ -767,6 +862,122 @@ sub split_hunk {
        return @split;
 }
 
+sub find_last_o_ctx {
+       my ($it) = @_;
+       my $text = $it->{TEXT};
+       my ($o_ofs, $o_cnt) = parse_hunk_header($text->[0]);
+       my $i = @{$text};
+       my $last_o_ctx = $o_ofs + $o_cnt;
+       while (0 < --$i) {
+               my $line = $text->[$i];
+               if ($line =~ /^ /) {
+                       $last_o_ctx--;
+                       next;
+               }
+               last;
+       }
+       return $last_o_ctx;
+}
+
+sub merge_hunk {
+       my ($prev, $this) = @_;
+       my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
+           parse_hunk_header($prev->{TEXT}[0]);
+       my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
+           parse_hunk_header($this->{TEXT}[0]);
+
+       my (@line, $i, $ofs, $o_cnt, $n_cnt);
+       $ofs = $o0_ofs;
+       $o_cnt = $n_cnt = 0;
+       for ($i = 1; $i < @{$prev->{TEXT}}; $i++) {
+               my $line = $prev->{TEXT}[$i];
+               if ($line =~ /^\+/) {
+                       $n_cnt++;
+                       push @line, $line;
+                       next;
+               }
+
+               last if ($o1_ofs <= $ofs);
+
+               $o_cnt++;
+               $ofs++;
+               if ($line =~ /^ /) {
+                       $n_cnt++;
+               }
+               push @line, $line;
+       }
+
+       for ($i = 1; $i < @{$this->{TEXT}}; $i++) {
+               my $line = $this->{TEXT}[$i];
+               if ($line =~ /^\+/) {
+                       $n_cnt++;
+                       push @line, $line;
+                       next;
+               }
+               $ofs++;
+               $o_cnt++;
+               if ($line =~ /^ /) {
+                       $n_cnt++;
+               }
+               push @line, $line;
+       }
+       my $head = ("@@ -$o0_ofs" .
+                   (($o_cnt != 1) ? ",$o_cnt" : '') .
+                   " +$n0_ofs" .
+                   (($n_cnt != 1) ? ",$n_cnt" : '') .
+                   " @@\n");
+       @{$prev->{TEXT}} = ($head, @line);
+}
+
+sub coalesce_overlapping_hunks {
+       my (@in) = @_;
+       my @out = ();
+
+       my ($last_o_ctx, $last_was_dirty);
+
+       for (grep { $_->{USE} } @in) {
+               if ($_->{TYPE} ne 'hunk') {
+                       push @out, $_;
+                       next;
+               }
+               my $text = $_->{TEXT};
+               my ($o_ofs) = parse_hunk_header($text->[0]);
+               if (defined $last_o_ctx &&
+                   $o_ofs <= $last_o_ctx &&
+                   !$_->{DIRTY} &&
+                   !$last_was_dirty) {
+                       merge_hunk($out[-1], $_);
+               }
+               else {
+                       push @out, $_;
+               }
+               $last_o_ctx = find_last_o_ctx($out[-1]);
+               $last_was_dirty = $_->{DIRTY};
+       }
+       return @out;
+}
+
+sub reassemble_patch {
+       my $head = shift;
+       my @patch;
+
+       # Include everything in the header except the beginning of the diff.
+       push @patch, (grep { !/^[-+]{3}/ } @$head);
+
+       # Then include any headers from the hunk lines, which must
+       # come before any actual hunk.
+       while (@_ && $_[0] !~ /^@/) {
+               push @patch, shift;
+       }
+
+       # Then begin the diff.
+       push @patch, grep { /^[-+]{3}/ } @$head;
+
+       # And then the actual hunks.
+       push @patch, @_;
+
+       return @patch;
+}
 
 sub color_diff {
        return map {
@@ -787,6 +998,7 @@ sub edit_hunk_manually {
                or die "failed to open hunk edit file for writing: " . $!;
        print $fh "# Manual hunk edit mode -- see bottom for a quick guide\n";
        print $fh @$oldtext;
+       my $participle = $patch_mode_flavour{PARTICIPLE};
        print $fh <<EOF;
 # ---
 # To remove '-' lines, make them ' ' lines (context).
@@ -794,14 +1006,13 @@ sub edit_hunk_manually {
 # Lines starting with # will be removed.
 #
 # If the patch applies cleanly, the edited hunk will immediately be
-# marked for staging. If it does not apply cleanly, you will be given
+# marked for $participle. If it does not apply cleanly, you will be given
 # an opportunity to edit again. If all lines of the hunk are removed,
 # then the edit is aborted and the hunk is left unchanged.
 EOF
        close $fh;
 
-       my $editor = $ENV{GIT_EDITOR} || $repo->config("core.editor")
-               || $ENV{VISUAL} || $ENV{EDITOR} || "vi";
+       chomp(my $editor = run_cmd_pipe(qw(git var GIT_EDITOR)));
        system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
 
        if ($? != 0) {
@@ -828,11 +1039,8 @@ EOF
 
 sub diff_applies {
        my $fh;
-       open $fh, '| git apply --recount --cached --check';
-       for my $h (@_) {
-               print $fh @{$h->{TEXT}};
-       }
-       return close $fh;
+       return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --recount --check',
+                            map { @{$_->{TEXT}} } @_);
 }
 
 sub _restore_terminal_and_die {
@@ -878,7 +1086,8 @@ sub edit_hunk_loop {
                my $newhunk = {
                        TEXT => $text,
                        TYPE => $hunk->[$ix]->{TYPE},
-                       USE => 1
+                       USE => 1,
+                       DIRTY => 1,
                };
                if (diff_applies($head,
                                 @{$hunk}[0..$ix-1],
@@ -897,12 +1106,14 @@ sub edit_hunk_loop {
 }
 
 sub help_patch_cmd {
-       print colored $help_color, <<\EOF ;
-y - stage this hunk
-n - do not stage this hunk
-q - quit, do not stage this hunk nor any of the remaining ones
-a - stage this and all the remaining hunks in the file
-d - do not stage this hunk nor any of the remaining hunks in the file
+       my $verb = lc $patch_mode_flavour{VERB};
+       my $target = $patch_mode_flavour{TARGET};
+       print colored $help_color, <<EOF ;
+y - $verb this hunk$target
+n - do not $verb this hunk$target
+q - quit; do not $verb this hunk nor any of the remaining ones
+a - $verb this hunk and all later hunks in the file
+d - do not $verb this hunk nor any of the later hunks in the file
 g - select a hunk to go to
 / - search for a hunk matching the given regex
 j - leave this hunk undecided, see next undecided hunk
@@ -915,8 +1126,40 @@ e - manually edit the current hunk
 EOF
 }
 
+sub apply_patch {
+       my $cmd = shift;
+       my $ret = run_git_apply $cmd . ' --recount', @_;
+       if (!$ret) {
+               print STDERR @_;
+       }
+       return $ret;
+}
+
+sub apply_patch_for_checkout_commit {
+       my $reverse = shift;
+       my $applies_index = run_git_apply 'apply '.$reverse.' --cached --recount --check', @_;
+       my $applies_worktree = run_git_apply 'apply '.$reverse.' --recount --check', @_;
+
+       if ($applies_worktree && $applies_index) {
+               run_git_apply 'apply '.$reverse.' --cached --recount', @_;
+               run_git_apply 'apply '.$reverse.' --recount', @_;
+               return 1;
+       } elsif (!$applies_index) {
+               print colored $error_color, "The selected hunks do not apply to the index!\n";
+               if (prompt_yesno "Apply them to the worktree anyway? ") {
+                       return run_git_apply 'apply '.$reverse.' --recount', @_;
+               } else {
+                       print colored $error_color, "Nothing was applied.\n";
+                       return 0;
+               }
+       } else {
+               print STDERR @_;
+               return 0;
+       }
+}
+
 sub patch_update_cmd {
-       my @all_mods = list_modified('file-only');
+       my @all_mods = list_modified($patch_mode_flavour{FILTER});
        my @mods = grep { !($_->{BINARY}) } @all_mods;
        my @them;
 
@@ -987,7 +1230,7 @@ sub patch_update_file {
        my ($ix, $num);
        my $path = shift;
        my ($head, @hunk) = parse_diff($path);
-       ($head, my $mode) = parse_diff_header($head);
+       ($head, my $mode, my $deletion) = parse_diff_header($head);
        for (@{$head->{DISPLAY}}) {
                print;
        }
@@ -995,6 +1238,13 @@ sub patch_update_file {
        if (@{$mode->{TEXT}}) {
                unshift @hunk, $mode;
        }
+       if (@{$deletion->{TEXT}}) {
+               foreach my $hunk (@hunk) {
+                       push @{$deletion->{TEXT}}, @{$hunk->{TEXT}};
+                       push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};
+               }
+               @hunk = ($deletion);
+       }
 
        $num = scalar @hunk;
        $ix = 0;
@@ -1047,8 +1297,11 @@ sub patch_update_file {
                for (@{$hunk[$ix]{DISPLAY}}) {
                        print;
                }
-               print colored $prompt_color, 'Stage ',
-                 ($hunk[$ix]{TYPE} eq 'mode' ? 'mode change' : 'this hunk'),
+               print colored $prompt_color, $patch_mode_flavour{VERB},
+                 ($hunk[$ix]{TYPE} eq 'mode' ? ' mode change' :
+                  $hunk[$ix]{TYPE} eq 'deletion' ? ' deletion' :
+                  ' this hunk'),
+                 $patch_mode_flavour{TARGET},
                  " [y,n,q,a,d,/$other,?]? ";
                my $line = prompt_single_character;
                if ($line) {
@@ -1210,6 +1463,8 @@ sub patch_update_file {
                }
        }
 
+       @hunk = coalesce_overlapping_hunks(@hunk);
+
        my $n_lofs = 0;
        my @result = ();
        for (@hunk) {
@@ -1220,16 +1475,9 @@ sub patch_update_file {
 
        if (@result) {
                my $fh;
-
-               open $fh, '| git apply --cached --recount';
-               for (@{$head->{TEXT}}, @result) {
-                       print $fh $_;
-               }
-               if (!close $fh) {
-                       for (@{$head->{TEXT}}, @result) {
-                               print STDERR $_;
-                       }
-               }
+               my @patch = reassemble_patch($head->{TEXT}, @result);
+               my $apply_routine = $patch_mode_flavour{APPLY};
+               &$apply_routine(@patch);
                refresh();
        }
 
@@ -1270,11 +1518,41 @@ EOF
 sub process_args {
        return unless @ARGV;
        my $arg = shift @ARGV;
-       if ($arg eq "--patch") {
-               $patch_mode = 1;
-               $arg = shift @ARGV or die "missing --";
+       if ($arg =~ /--patch(?:=(.*))?/) {
+               if (defined $1) {
+                       if ($1 eq 'reset') {
+                               $patch_mode = 'reset_head';
+                               $patch_mode_revision = 'HEAD';
+                               $arg = shift @ARGV or die "missing --";
+                               if ($arg ne '--') {
+                                       $patch_mode_revision = $arg;
+                                       $patch_mode = ($arg eq 'HEAD' ?
+                                                      'reset_head' : 'reset_nothead');
+                                       $arg = shift @ARGV or die "missing --";
+                               }
+                       } elsif ($1 eq 'checkout') {
+                               $arg = shift @ARGV or die "missing --";
+                               if ($arg eq '--') {
+                                       $patch_mode = 'checkout_index';
+                               } else {
+                                       $patch_mode_revision = $arg;
+                                       $patch_mode = ($arg eq 'HEAD' ?
+                                                      'checkout_head' : 'checkout_nothead');
+                                       $arg = shift @ARGV or die "missing --";
+                               }
+                       } elsif ($1 eq 'stage' or $1 eq 'stash') {
+                               $patch_mode = $1;
+                               $arg = shift @ARGV or die "missing --";
+                       } else {
+                               die "unknown --patch mode: $1";
+                       }
+               } else {
+                       $patch_mode = 'stage';
+                       $arg = shift @ARGV or die "missing --";
+               }
                die "invalid argument $arg, expecting --"
                    unless $arg eq "--";
+               %patch_mode_flavour = %{$patch_modes{$patch_mode}};
        }
        elsif ($arg ne "--") {
                die "invalid argument $arg, expecting --";
index 6d1848b6cce89e4953a3ca6e1b2e6e1611277a4a..e7f008c7baae2ff484e16882e199b6b9d75195aa 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -11,20 +11,29 @@ git am [options] (--resolved | --skip | --abort)
 i,interactive   run interactively
 b,binary*       (historical option -- no-op)
 3,3way          allow fall back on 3way merging if needed
+q,quiet         be quiet
 s,signoff       add a Signed-off-by line to the commit message
 u,utf8          recode into utf8 (default)
 k,keep          pass -k flag to git-mailinfo
+keep-cr         pass --keep-cr flag to git-mailsplit for mbox format
+no-keep-cr      do not pass --keep-cr flag to git-mailsplit independent of am.keepcr
+c,scissors      strip everything before a scissors line
 whitespace=     pass it through git-apply
+ignore-space-change pass it through git-apply
+ignore-whitespace pass it through git-apply
 directory=      pass it through git-apply
 C=              pass it through git-apply
 p=              pass it through git-apply
+patch-format=   format the patch(es) are in
 reject          pass it through git-apply
 resolvemsg=     override error message when patch failure occurs
-r,resolved      to be used after a patch failure
+continue        continue applying patches after resolving a conflict
+r,resolved      synonyms for --continue
 skip            skip the current patch
 abort           restore the original branch and abort the patching operation.
 committer-date-is-author-date    lie about committer date
 ignore-date     use current timestamp for author date
+rerere-autoupdate update the index with reused conflict resolution if possible
 rebasing*       (internal use for git-rebase)"
 
 . git-sh-setup
@@ -43,12 +52,18 @@ else
        HAS_HEAD=
 fi
 
+cmdline="git am"
+if test '' != "$interactive"
+then
+       cmdline="$cmdline -i"
+fi
+if test '' != "$threeway"
+then
+       cmdline="$cmdline -3"
+fi
+
 sq () {
-       for sqarg
-       do
-               printf "%s" "$sqarg" |
-               sed -e 's/'\''/'\''\\'\'''\''/g' -e 's/.*/ '\''&'\''/'
-       done
+       git rev-parse --sq-quote "$@"
 }
 
 stop_here () {
@@ -61,15 +76,6 @@ stop_here_user_resolve () {
            printf '%s\n' "$resolvemsg"
            stop_here $1
     fi
-    cmdline="git am"
-    if test '' != "$interactive"
-    then
-        cmdline="$cmdline -i"
-    fi
-    if test '' != "$threeway"
-    then
-        cmdline="$cmdline -3"
-    fi
     echo "When you have resolved this problem run \"$cmdline --resolved\"."
     echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
     echo "To restore the original branch and stop patching run \"$cmdline --abort\"."
@@ -103,7 +109,7 @@ fall_back_3way () {
     git write-tree >"$dotest/patch-merge-base+" ||
     cannot_fallback "Repository lacks necessary blobs to fall back on 3-way merge."
 
-    echo Using index info to reconstruct a base tree...
+    say Using index info to reconstruct a base tree...
     if GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
        git apply --cached <"$dotest/patch"
     then
@@ -119,7 +125,7 @@ It does not apply to blobs recorded in its index."
     orig_tree=$(cat "$dotest/patch-merge-base") &&
     rm -fr "$dotest"/patch-merge-* || exit 1
 
-    echo Falling back to patching base and 3-way merge...
+    say 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
@@ -129,21 +135,176 @@ It does not apply to blobs recorded in its index."
 
     eval GITHEAD_$his_tree='"$FIRSTLINE"'
     export GITHEAD_$his_tree
+    if test -n "$GIT_QUIET"
+    then
+           export GIT_MERGE_VERBOSITY=0
+    fi
     git-merge-recursive $orig_tree -- HEAD $his_tree || {
-           git rerere
+           git rerere $allow_rerere_autoupdate
            echo Failed to merge in the changes.
            exit 1
     }
     unset GITHEAD_$his_tree
 }
 
+clean_abort () {
+       test $# = 0 || echo >&2 "$@"
+       rm -fr "$dotest"
+       exit 1
+}
+
+patch_format=
+
+check_patch_format () {
+       # early return if patch_format was set from the command line
+       if test -n "$patch_format"
+       then
+               return 0
+       fi
+
+       # we default to mbox format if input is from stdin and for
+       # directories
+       if test $# = 0 || test "x$1" = "x-" || test -d "$1"
+       then
+               patch_format=mbox
+               return 0
+       fi
+
+       # otherwise, check the first few lines of the first patch to try
+       # to detect its format
+       {
+               read l1
+               read l2
+               read l3
+               case "$l1" in
+               "From "* | "From: "*)
+                       patch_format=mbox
+                       ;;
+               '# This series applies on GIT commit'*)
+                       patch_format=stgit-series
+                       ;;
+               "# HG changeset patch")
+                       patch_format=hg
+                       ;;
+               *)
+                       # if the second line is empty and the third is
+                       # a From, Author or Date entry, this is very
+                       # likely an StGIT patch
+                       case "$l2,$l3" in
+                       ,"From: "* | ,"Author: "* | ,"Date: "*)
+                               patch_format=stgit
+                               ;;
+                       *)
+                               ;;
+                       esac
+                       ;;
+               esac
+               if test -z "$patch_format" &&
+                       test -n "$l1" &&
+                       test -n "$l2" &&
+                       test -n "$l3"
+               then
+                       # This begins with three non-empty lines.  Is this a
+                       # piece of e-mail a-la RFC2822?  Grab all the headers,
+                       # discarding the indented remainder of folded lines,
+                       # and see if it looks like that they all begin with the
+                       # header field names...
+                       tr -d '\015' <"$1" |
+                       sed -n -e '/^$/q' -e '/^[       ]/d' -e p |
+                       sane_egrep -v '^[!-9;-~]+:' >/dev/null ||
+                       patch_format=mbox
+               fi
+       } < "$1" || clean_abort
+}
+
+split_patches () {
+       case "$patch_format" in
+       mbox)
+               if test -n "$rebasing" || test t = "$keepcr"
+               then
+                   keep_cr=--keep-cr
+               else
+                   keep_cr=
+               fi
+               git mailsplit -d"$prec" -o"$dotest" -b $keep_cr -- "$@" > "$dotest/last" ||
+               clean_abort
+               ;;
+       stgit-series)
+               if test $# -ne 1
+               then
+                       clean_abort "Only one StGIT patch series can be applied at once"
+               fi
+               series_dir=`dirname "$1"`
+               series_file="$1"
+               shift
+               {
+                       set x
+                       while read filename
+                       do
+                               set "$@" "$series_dir/$filename"
+                       done
+                       # remove the safety x
+                       shift
+                       # remove the arg coming from the first-line comment
+                       shift
+               } < "$series_file" || clean_abort
+               # set the patch format appropriately
+               patch_format=stgit
+               # now handle the actual StGIT patches
+               split_patches "$@"
+               ;;
+       stgit)
+               this=0
+               for stgit in "$@"
+               do
+                       this=`expr "$this" + 1`
+                       msgnum=`printf "%0${prec}d" $this`
+                       # Perl version of StGIT parse_patch. The first nonemptyline
+                       # not starting with Author, From or Date is the
+                       # subject, and the body starts with the next nonempty
+                       # line not starting with Author, From or Date
+                       perl -ne 'BEGIN { $subject = 0 }
+                               if ($subject > 1) { print ; }
+                               elsif (/^\s+$/) { next ; }
+                               elsif (/^Author:/) { print s/Author/From/ ; }
+                               elsif (/^(From|Date)/) { print ; }
+                               elsif ($subject) {
+                                       $subject = 2 ;
+                                       print "\n" ;
+                                       print ;
+                               } else {
+                                       print "Subject: ", $_ ;
+                                       $subject = 1;
+                               }
+                       ' < "$stgit" > "$dotest/$msgnum" || clean_abort
+               done
+               echo "$this" > "$dotest/last"
+               this=
+               msgnum=
+               ;;
+       *)
+               if test -n "$parse_patch" ; then
+                       clean_abort "Patch format $patch_format is not supported."
+               else
+                       clean_abort "Patch format detection failed."
+               fi
+               ;;
+       esac
+}
+
 prec=4
 dotest="$GIT_DIR/rebase-apply"
-sign= utf8=t keep= skip= interactive= resolved= rebasing= abort=
-resolvemsg= resume=
+sign= utf8=t keep= keepcr= skip= interactive= resolved= rebasing= abort=
+resolvemsg= resume= scissors= no_inbody_headers=
 git_apply_opt=
 committer_date_is_author_date=
 ignore_date=
+allow_rerere_autoupdate=
+
+if test "$(git config --bool --get am.keepcr)" = true
+then
+    keepcr=t
+fi
 
 while test $# != 0
 do
@@ -162,14 +323,18 @@ do
                utf8= ;;
        -k|--keep)
                keep=t ;;
-       -r|--resolved)
+       -c|--scissors)
+               scissors=t ;;
+       --no-scissors)
+               scissors=f ;;
+       -r|--resolved|--continue)
                resolved=t ;;
        --skip)
                skip=t ;;
        --abort)
                abort=t ;;
        --rebasing)
-               rebasing=t threeway=t keep=t ;;
+               rebasing=t threeway=t keep=t scissors=f no_inbody_headers=t ;;
        -d|--dotest)
                die "-d option is no longer supported.  Do not use."
                ;;
@@ -179,12 +344,22 @@ do
                git_apply_opt="$git_apply_opt $(sq "$1=$2")"; shift ;;
        -C|-p)
                git_apply_opt="$git_apply_opt $(sq "$1$2")"; shift ;;
-       --reject)
+       --patch-format)
+               shift ; patch_format="$1" ;;
+       --reject|--ignore-whitespace|--ignore-space-change)
                git_apply_opt="$git_apply_opt $1" ;;
        --committer-date-is-author-date)
                committer_date_is_author_date=t ;;
        --ignore-date)
                ignore_date=t ;;
+       --rerere-autoupdate|--no-rerere-autoupdate)
+               allow_rerere_autoupdate="$1" ;;
+       -q|--quiet)
+               GIT_QUIET=t ;;
+       --keep-cr)
+               keepcr=t ;;
+       --no-keep-cr)
+               keepcr=f ;;
        --)
                shift; break ;;
        *)
@@ -278,19 +453,22 @@ else
                done
                shift
        fi
-       git mailsplit -d"$prec" -o"$dotest" -b -- "$@" > "$dotest/last" ||  {
-               rm -fr "$dotest"
-               exit 1
-       }
 
-       # -s, -u, -k, --whitespace, -3, -C and -p flags are kept
-       # for the resuming session after a patch failure.
-       # -i can and must be given when resuming.
+       check_patch_format "$@"
+
+       split_patches "$@"
+
+       # -i can and must be given when resuming; everything
+       # else is kept
        echo " $git_apply_opt" >"$dotest/apply-opt"
        echo "$threeway" >"$dotest/threeway"
        echo "$sign" >"$dotest/sign"
        echo "$utf8" >"$dotest/utf8"
        echo "$keep" >"$dotest/keep"
+       echo "$keepcr" >"$dotest/keepcr"
+       echo "$scissors" >"$dotest/scissors"
+       echo "$no_inbody_headers" >"$dotest/no_inbody_headers"
+       echo "$GIT_QUIET" >"$dotest/quiet"
        echo 1 >"$dotest/next"
        if test -n "$rebasing"
        then
@@ -331,6 +509,28 @@ if test "$(cat "$dotest/keep")" = t
 then
        keep=-k
 fi
+case "$(cat "$dotest/keepcr")" in
+t)
+       keepcr=--keep-cr ;;
+f)
+       keepcr=--no-keep-cr ;;
+esac
+case "$(cat "$dotest/scissors")" in
+t)
+       scissors=--scissors ;;
+f)
+       scissors=--no-scissors ;;
+esac
+if test "$(cat "$dotest/no_inbody_headers")" = t
+then
+       no_inbody_headers=--no-inbody-headers
+else
+       no_inbody_headers=
+fi
+if test "$(cat "$dotest/quiet")" = t
+then
+       GIT_QUIET=t
+fi
 if test "$(cat "$dotest/threeway")" = t
 then
        threeway=t
@@ -356,7 +556,7 @@ fi
 
 if test "$this" -gt "$last"
 then
-       echo Nothing to do.
+       say Nothing to do.
        rm -fr "$dotest"
        exit
 fi
@@ -381,19 +581,22 @@ do
        # by the user, or the user can tell us to do so by --resolved flag.
        case "$resume" in
        '')
-               git mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \
+               git mailinfo $keep $no_inbody_headers $scissors $utf8 "$dotest/msg" "$dotest/patch" \
                        <"$dotest/$msgnum" >"$dotest/info" ||
                        stop_here $this
 
                # skip pine's internal folder data
-               grep '^Author: Mail System Internal Data$' \
+               sane_grep '^Author: Mail System Internal Data$' \
                        <"$dotest"/info >/dev/null &&
                        go_next && continue
 
                test -s "$dotest/patch" || {
                        echo "Patch is empty.  Was it split wrong?"
+                       echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
+                       echo "To restore the original branch and stop patching run \"$cmdline --abort\"."
                        stop_here $this
                }
+               rm -f "$dotest/original-commit" "$dotest/author-script"
                if test -f "$dotest/rebasing" &&
                        commit=$(sed -e 's/^From \([0-9a-f]*\) .*/\1/' \
                                -e q "$dotest/$msgnum") &&
@@ -401,19 +604,27 @@ do
                then
                        git cat-file commit "$commit" |
                        sed -e '1,/^$/d' >"$dotest/msg-clean"
+                       echo "$commit" > "$dotest/original-commit"
+                       get_author_ident_from_commit "$commit" > "$dotest/author-script"
                else
-                       SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$dotest/info")"
-                       case "$keep_subject" in -k)  SUBJECT="[PATCH] $SUBJECT" ;; esac
-
-                       (printf '%s\n\n' "$SUBJECT"; cat "$dotest/msg") |
-                               git stripspace > "$dotest/msg-clean"
+                       {
+                               sed -n '/^Subject/ s/Subject: //p' "$dotest/info"
+                               echo
+                               cat "$dotest/msg"
+                       } |
+                       git stripspace > "$dotest/msg-clean"
                fi
                ;;
        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 -f "$dotest/author-script"
+       then
+               eval $(cat "$dotest/author-script")
+       else
+               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")"
+       fi
 
        if test -z "$GIT_AUTHOR_EMAIL"
        then
@@ -481,14 +692,20 @@ do
                [eE]*) git_editor "$dotest/final-commit"
                       action=again ;;
                [vV]*) action=again
-                      LESS=-S ${PAGER:-less} "$dotest/patch" ;;
+                      git_pager "$dotest/patch" ;;
                *)     action=again ;;
                esac
            done
        else
            action=yes
        fi
-       FIRSTLINE=$(sed 1q "$dotest/final-commit")
+
+       if test -f "$dotest/final-commit"
+       then
+               FIRSTLINE=$(sed 1q "$dotest/final-commit")
+       else
+               FIRSTLINE=""
+       fi
 
        if test $action = skip
        then
@@ -502,11 +719,18 @@ do
                stop_here $this
        fi
 
-       printf 'Applying: %s\n' "$FIRSTLINE"
+       say "Applying: $FIRSTLINE"
 
        case "$resolved" in
        '')
-               eval 'git apply '"$git_apply_opt"' --index "$dotest/patch"'
+               # When we are allowed to fall back to 3-way later, don't give
+               # false errors during the initial attempt.
+               squelch=
+               if test "$threeway" = t
+               then
+                       squelch='>/dev/null 2>&1 '
+               fi
+               eval "git apply $squelch$git_apply_opt"' --index "$dotest/patch"'
                apply_status=$?
                ;;
        t)
@@ -517,6 +741,8 @@ do
                resolved=
                git diff-index --quiet --cached HEAD -- && {
                        echo "No changes - did you forget to use 'git add'?"
+                       echo "If there is nothing left to stage, chances are that something else"
+                       echo "already introduced the same changes; you might want to skip this patch."
                        stop_here_user_resolve $this
                }
                unmerged=$(git ls-files -u)
@@ -531,14 +757,14 @@ do
                ;;
        esac
 
-       if test $apply_status = 1 && test "$threeway" = t
+       if test $apply_status != 0 && 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.
                    git diff-index --quiet --cached HEAD -- && {
-                       echo No changes -- Patch already applied.
+                       say No changes -- Patch already applied.
                        go_next
                        continue
                    }
@@ -564,7 +790,7 @@ do
                        GIT_AUTHOR_DATE=
                fi
                parent=$(git rev-parse --verify -q HEAD) ||
-               echo >&2 "applying to an empty history"
+               say >&2 "applying to an empty history"
 
                if test -n "$committer_date_is_author_date"
                then
@@ -576,6 +802,10 @@ do
        git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent ||
        stop_here $this
 
+       if test -f "$dotest/original-commit"; then
+               echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten"
+       fi
+
        if test -x "$GIT_DIR"/hooks/post-applypatch
        then
                "$GIT_DIR"/hooks/post-applypatch
@@ -584,6 +814,12 @@ do
        go_next
 done
 
-git gc --auto
+if test -s "$dotest"/rewritten; then
+    git notes copy --for-rewrite=rebase < "$dotest"/rewritten
+    if test -x "$GIT_DIR"/hooks/post-rewrite; then
+       "$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+    fi
+fi
 
 rm -fr "$dotest"
+git gc --auto
index 24712ff304af89317793fa4c54d39f4c579bb345..6e2acb8ef29e5003945bed17014a68b141ada454 100755 (executable)
@@ -13,8 +13,8 @@ git bisect skip [(<rev>|<range>)...]
         mark <rev>... untestable 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 reset [<commit>]
+        finish bisection search and go back to commit.
 git bisect visualize
         show bisect status in gitk.
 git bisect replay <logfile>
@@ -33,16 +33,6 @@ require_work_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"
 
-sq() {
-       @@PERL@@ -e '
-               for (@ARGV) {
-                       s/'\''/'\'\\\\\'\''/g;
-                       print " '\''$_'\''";
-               }
-               print "\n";
-       ' "$@"
-}
-
 bisect_autostart() {
        test -s "$GIT_DIR/BISECT_START" || {
                echo >&2 'You need to start by "git bisect start"'
@@ -107,7 +97,7 @@ bisect_start() {
        for arg; do
            case "$arg" in --) has_double_dash=1; break ;; esac
        done
-       orig_args=$(sq "$@")
+       orig_args=$(git rev-parse --sq-quote "$@")
        bad_seen=0
        eval=''
        while [ $# -gt 0 ]; do
@@ -147,7 +137,7 @@ bisect_start() {
        # Write new start state.
        #
        echo "$start_head" >"$GIT_DIR/BISECT_START" &&
-       sq "$@" >"$GIT_DIR/BISECT_NAMES" &&
+       git rev-parse --sq-quote "$@" >"$GIT_DIR/BISECT_NAMES" &&
        eval "$eval" &&
        echo "git bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" || exit
        #
@@ -177,10 +167,6 @@ is_expected_rev() {
        test "$1" = $(cat "$GIT_DIR/BISECT_EXPECTED_REV")
 }
 
-mark_expected_rev() {
-       echo "$1" > "$GIT_DIR/BISECT_EXPECTED_REV"
-}
-
 check_expected_revs() {
        for _rev in "$@"; do
                if ! is_expected_rev "$_rev"; then
@@ -199,7 +185,7 @@ bisect_skip() {
             *..*)
                 revs=$(git rev-list "$arg") || die "Bad rev input: $arg" ;;
             *)
-                revs=$(sq "$arg") ;;
+                revs=$(git rev-parse --sq-quote "$arg") ;;
            esac
             all="$all $revs"
         done
@@ -279,162 +265,22 @@ bisect_auto_next() {
        bisect_next_check && bisect_next || :
 }
 
-exit_if_skipped_commits () {
-       _tried=$1
-       _bad=$2
-       if test -n "$_tried" ; then
-               echo "There are only 'skip'ped commit left to test."
-               echo "The first bad commit could be any of:"
-               echo "$_tried" | tr '[|]' '[\012]'
-               test -n "$_bad" && echo "$_bad"
-               echo "We cannot bisect more!"
-               exit 2
-       fi
-}
-
-bisect_checkout() {
-       _rev="$1"
-       _msg="$2"
-       echo "Bisecting: $_msg"
-       mark_expected_rev "$_rev"
-       git checkout -q "$_rev" -- || exit
-       git show-branch "$_rev"
-}
-
-is_among() {
-       _rev="$1"
-       _list="$2"
-       case "$_list" in *$_rev*) return 0 ;; esac
-       return 1
-}
-
-handle_bad_merge_base() {
-       _badmb="$1"
-       _good="$2"
-       if is_expected_rev "$_badmb"; then
-               cat >&2 <<EOF
-The merge base $_badmb is bad.
-This means the bug has been fixed between $_badmb and [$_good].
-EOF
-               exit 3
-       else
-               cat >&2 <<EOF
-Some good revs are not ancestor of the bad rev.
-git bisect cannot work properly in this case.
-Maybe you mistake good and bad revs?
-EOF
-               exit 1
-       fi
-}
-
-handle_skipped_merge_base() {
-       _mb="$1"
-       _bad="$2"
-       _good="$3"
-       cat >&2 <<EOF
-Warning: the merge base between $_bad and [$_good] must be skipped.
-So we cannot be sure the first bad commit is between $_mb and $_bad.
-We continue anyway.
-EOF
-}
-
-#
-# "check_merge_bases" checks that merge bases are not "bad".
-#
-# - If one is "good", that's good, we have nothing to do.
-# - If one is "bad", it means the user assumed something wrong
-# and we must exit.
-# - If one is "skipped", we can't know but we should warn.
-# - If we don't know, we should check it out and ask the user to test.
-#
-# In the last case we will return 1, and otherwise 0.
-#
-check_merge_bases() {
-       _bad="$1"
-       _good="$2"
-       _skip="$3"
-       for _mb in $(git merge-base --all $_bad $_good)
-       do
-               if is_among "$_mb" "$_good"; then
-                       continue
-               elif test "$_mb" = "$_bad"; then
-                       handle_bad_merge_base "$_bad" "$_good"
-               elif is_among "$_mb" "$_skip"; then
-                       handle_skipped_merge_base "$_mb" "$_bad" "$_good"
-               else
-                       bisect_checkout "$_mb" "a merge base must be tested"
-                       return 1
-               fi
-       done
-       return 0
-}
-
-#
-# "check_good_are_ancestors_of_bad" checks that all "good" revs are
-# ancestor of the "bad" rev.
-#
-# If that's not the case, we need to check the merge bases.
-# If a merge base must be tested by the user we return 1 and
-# otherwise 0.
-#
-check_good_are_ancestors_of_bad() {
-       test -f "$GIT_DIR/BISECT_ANCESTORS_OK" &&
-               return
-
-       _bad="$1"
-       _good=$(echo $2 | sed -e 's/\^//g')
-       _skip="$3"
-
-       # Bisecting with no good rev is ok
-       test -z "$_good" && return
-
-       _side=$(git rev-list $_good ^$_bad)
-       if test -n "$_side"; then
-               # Return if a checkout was done
-               check_merge_bases "$_bad" "$_good" "$_skip" || return
-       fi
-
-       : > "$GIT_DIR/BISECT_ANCESTORS_OK"
-
-       return 0
-}
-
 bisect_next() {
        case "$#" in 0) ;; *) usage ;; esac
        bisect_autostart
        bisect_next_check good
 
-       # Get bad, good and skipped revs
-       bad=$(git rev-parse --verify refs/bisect/bad) &&
-       good=$(git for-each-ref --format='^%(objectname)' \
-               "refs/bisect/good-*" | tr '\012' ' ') &&
-       skip=$(git for-each-ref --format='%(objectname)' \
-               "refs/bisect/skip-*" | tr '\012' ' ') || exit
-
-       # Maybe some merge bases must be tested first
-       check_good_are_ancestors_of_bad "$bad" "$good" "$skip"
-       # Return now if a checkout has already been done
-       test "$?" -eq "1" && return
-
-       # Get bisection information
-       eval=$(eval "git bisect--helper --next-vars") &&
-       eval "$eval" || exit
-
-       if [ -z "$bisect_rev" ]; then
-               # We should exit here only if the "bad"
-               # commit is also a "skip" commit (see above).
-               exit_if_skipped_commits "$bisect_tried"
-               echo "$bad was both good and bad"
-               exit 1
-       fi
-       if [ "$bisect_rev" = "$bad" ]; then
-               exit_if_skipped_commits "$bisect_tried" "$bad"
-               echo "$bisect_rev is first bad commit"
-               git diff-tree --pretty $bisect_rev
-               exit 0
-       fi
+       # Perform all bisection computation, display and checkout
+       git bisect--helper --next-all
+       res=$?
+
+        # Check if we should exit because bisection is finished
+       test $res -eq 10 && exit 0
 
-       bisect_checkout "$bisect_rev" "$bisect_nr revisions left to test after this (roughly $bisect_steps steps)"
+       # Check for an error in the bisection process
+       test $res -ne 0 && exit $res
+
+       return 0
 }
 
 bisect_visualize() {
@@ -454,8 +300,7 @@ bisect_visualize() {
                esac
        fi
 
-       not=$(git for-each-ref --format='%(refname)' "refs/bisect/good-*")
-       eval '"$@"' refs/bisect/bad --not $not -- $(cat "$GIT_DIR/BISECT_NAMES")
+       eval '"$@"' --bisect -- $(cat "$GIT_DIR/BISECT_NAMES")
 }
 
 bisect_reset() {
@@ -465,8 +310,8 @@ bisect_reset() {
        }
        case "$#" in
        0) branch=$(cat "$GIT_DIR/BISECT_START") ;;
-       1) git show-ref --verify --quiet -- "refs/heads/$1" ||
-              die "$1 does not seem to be a valid branch"
+       1) git rev-parse --quiet --verify "$1^{commit}" > /dev/null ||
+              die "'$1' is not a valid commit"
           branch="$1" ;;
        *)
            usage ;;
@@ -547,7 +392,7 @@ bisect_run () {
 
       cat "$GIT_DIR/BISECT_RUN"
 
-      if grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
+      if sane_grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
                > /dev/null; then
          echo >&2 "bisect run cannot continue any more"
          exit $res
@@ -559,7 +404,7 @@ bisect_run () {
          exit $res
       fi
 
-      if grep "is first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
+      if sane_grep "is the first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
          echo "bisect run success"
          exit 0;
       fi
index 1ac16bde5ab853098f857a64c6f0fea9f4c3437f..81883e7270daadafb4d827e894fa4645ff2e2b66 100644 (file)
@@ -7,7 +7,7 @@
 /*
  * See if our compiler is known to support flexible array members.
  */
-#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && (!defined(__SUNPRO_C) || (__SUNPRO_C > 0x580))
 # define FLEX_ARRAY /* empty */
 #elif defined(__GNUC__)
 # if (__GNUC__ >= 3)
@@ -26,6 +26,7 @@
 #endif
 
 #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+#define bitsizeof(x)  (CHAR_BIT * sizeof(x))
 
 #ifdef __GNUC__
 #define TYPEOF(x) (__typeof__(x))
 #define TYPEOF(x)
 #endif
 
-#define MSB(x, bits) ((x) & TYPEOF(x)(~0ULL << (sizeof(x) * 8 - (bits))))
+#define MSB(x, bits) ((x) & TYPEOF(x)(~0ULL << (bitsizeof(x) - (bits))))
 #define HAS_MULTI_BITS(i)  ((i) & ((i) - 1))  /* checks if an integer has more than 1 bit set */
 
+#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))
+
 /* Approximation of the length of the decimal representation of this type. */
 #define decimal_length(x)      ((int)(sizeof(x) * 2.56 + 0.5) + 1)
 
-#if !defined(__APPLE__) && !defined(__FreeBSD__)  && !defined(__USLC__) && !defined(_M_UNIX)
+#if defined(__sun__)
+ /*
+  * On Solaris, when _XOPEN_EXTENDED is set, its header file
+  * forces the programs to be XPG4v2, defeating any _XOPEN_SOURCE
+  * setting to say we are XPG5 or XPG6.  Also on Solaris,
+  * XPG6 programs must be compiled with a c99 compiler, while
+  * non XPG6 programs must be compiled with a pre-c99 compiler.
+  */
+# if __STDC_VERSION__ - 0 >= 199901L
+# define _XOPEN_SOURCE 600
+# else
+# define _XOPEN_SOURCE 500
+# endif
+#elif !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__USLC__) && \
+      !defined(_M_UNIX) && !defined(__sgi) && !defined(__DragonFly__)
 #define _XOPEN_SOURCE 600 /* glibc2 and AIX 5.3L need 500, OpenBSD needs 600 for S_ISLNK() */
 #define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */
 #endif
 #define _GNU_SOURCE 1
 #define _BSD_SOURCE 1
 #define _NETBSD_SOURCE 1
+#define _SGI_SOURCE 1
+
+#ifdef WIN32 /* Both MinGW and MSVC */
+#define WIN32_LEAN_AND_MEAN  /* stops windows.h including winsock.h */
+#include <winsock2.h>
+#include <windows.h>
+#endif
 
 #include <unistd.h>
 #include <stdio.h>
@@ -73,6 +97,7 @@
 #include <sys/poll.h>
 #include <sys/socket.h>
 #include <sys/ioctl.h>
+#include <termios.h>
 #ifndef NO_SYS_SELECT_H
 #include <sys/select.h>
 #endif
 /* pull in Windows compatibility stuff */
 #include "compat/mingw.h"
 #endif /* __MINGW32__ */
+#ifdef _MSC_VER
+#include "compat/msvc.h"
+#endif
+
+#ifndef NO_LIBGEN_H
+#include <libgen.h>
+#else
+#define basename gitbasename
+extern char *gitbasename(char *);
+#endif
 
 #ifndef NO_ICONV
 #include <iconv.h>
 #define PRIx32 "x"
 #endif
 
+#ifndef PRIo32
+#define PRIo32 "o"
+#endif
+
 #ifndef PATH_SEP
 #define PATH_SEP ':'
 #endif
 
+#ifdef HAVE_PATHS_H
+#include <paths.h>
+#endif
+#ifndef _PATH_DEFPATH
+#define _PATH_DEFPATH "/usr/local/bin:/usr/bin:/bin"
+#endif
+
 #ifndef STRIP_EXTENSION
 #define STRIP_EXTENSION ""
 #endif
 
 #ifdef __GNUC__
 #define NORETURN __attribute__((__noreturn__))
+#define NORETURN_PTR __attribute__((__noreturn__))
+#elif defined(_MSC_VER)
+#define NORETURN __declspec(noreturn)
+#define NORETURN_PTR
 #else
 #define NORETURN
+#define NORETURN_PTR
 #ifndef __attribute__
 #define __attribute__(x)
 #endif
 #endif
 
+#include "compat/bswap.h"
+
 /* General helper functions */
-extern void usage(const char *err) NORETURN;
-extern void die(const char *err, ...) NORETURN __attribute__((format (printf, 1, 2)));
+extern void vreportf(const char *prefix, const char *err, va_list params);
+extern NORETURN void usage(const char *err);
+extern NORETURN void usagef(const char *err, ...) __attribute__((format (printf, 1, 2)));
+extern NORETURN void die(const char *err, ...) __attribute__((format (printf, 1, 2)));
+extern NORETURN void die_errno(const char *err, ...) __attribute__((format (printf, 1, 2)));
 extern int error(const char *err, ...) __attribute__((format (printf, 1, 2)));
 extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
 
-extern void set_die_routine(void (*routine)(const char *err, va_list params) NORETURN);
+extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params));
 
 extern int prefixcmp(const char *str, const char *prefix);
-extern time_t tm_to_time_t(const struct tm *tm);
+extern int suffixcmp(const char *str, const char *suffix);
 
 static inline const char *skip_prefix(const char *str, const char *prefix)
 {
@@ -173,7 +229,6 @@ static inline const char *skip_prefix(const char *str, const char *prefix)
 #define PROT_READ 1
 #define PROT_WRITE 2
 #define MAP_PRIVATE 1
-#define MAP_FAILED ((void*)-1)
 #endif
 
 #define mmap git_mmap
@@ -202,6 +257,10 @@ extern int git_munmap(void *start, size_t length);
 
 #endif /* NO_MMAP */
 
+#ifndef MAP_FAILED
+#define MAP_FAILED ((void *)-1)
+#endif
+
 #ifdef NO_ST_BLOCKS_IN_STRUCT_STAT
 #define on_disk_bytes(st) ((st).st_size)
 #else
@@ -232,6 +291,11 @@ extern int gitsetenv(const char *, const char *, int);
 extern char *gitmkdtemp(char *);
 #endif
 
+#ifdef NO_MKSTEMPS
+#define mkstemps gitmkstemps
+extern int gitmkstemps(char *, int);
+#endif
+
 #ifdef NO_UNSETENV
 #define unsetenv gitunsetenv
 extern void gitunsetenv(const char *);
@@ -252,6 +316,11 @@ extern size_t gitstrlcpy(char *, const char *, size_t);
 extern uintmax_t gitstrtoumax(const char *, char **, int);
 #endif
 
+#ifdef NO_STRTOK_R
+#define strtok_r gitstrtok_r
+extern char *gitstrtok_r(char *s, const char *delim, char **save_ptr);
+#endif
+
 #ifdef NO_HSTRERROR
 #define hstrerror githstrerror
 extern const char *githstrerror(int herror);
@@ -283,6 +352,7 @@ extern int git_vsnprintf(char *str, size_t maxsize,
 #ifdef __GLIBC_PREREQ
 #if __GLIBC_PREREQ(2, 1)
 #define HAVE_STRCHRNUL
+#define HAVE_MEMPCPY
 #endif
 #endif
 
@@ -296,10 +366,22 @@ static inline char *gitstrchrnul(const char *s, int c)
 }
 #endif
 
+#ifndef HAVE_MEMPCPY
+#define mempcpy gitmempcpy
+static inline void *gitmempcpy(void *dest, const void *src, size_t n)
+{
+       return (char *)memcpy(dest, src, n) + n;
+}
+#endif
+
 extern void release_pack_memory(size_t, int);
 
+typedef void (*try_to_free_t)(size_t);
+extern try_to_free_t set_try_to_free_routine(try_to_free_t);
+
 extern char *xstrdup(const char *str);
 extern void *xmalloc(size_t size);
+extern void *xmallocz(size_t size);
 extern void *xmemdupz(const void *data, size_t len);
 extern char *xstrndup(const char *str, size_t len);
 extern void *xrealloc(void *ptr, size_t size);
@@ -315,6 +397,8 @@ extern int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1);
 
 static inline size_t xsize_t(off_t len)
 {
+       if (len > (size_t) len)
+               die("Cannot handle files this big");
        return (size_t)len;
 }
 
@@ -415,4 +499,19 @@ void git_qsort(void *base, size_t nmemb, size_t size,
 #define fstat_is_reliable() 1
 #endif
 
+/*
+ * Preserves errno, prints a message, but gives no warning for ENOENT.
+ * Always returns the return value of unlink(2).
+ */
+int unlink_or_warn(const char *path);
+/*
+ * Likewise for rmdir(2).
+ */
+int rmdir_or_warn(const char *path);
+/*
+ * Calls the correct function out of {unlink,rmdir}_or_warn based on
+ * the supplied file mode.
+ */
+int remove_or_warn(unsigned int mode, const char *path);
+
 #endif
index 6d9f0ef0f989133422cf8c0302e63dab15a999d5..59b672213bfc36f95db089f0e13bafc1c2f2ed71 100755 (executable)
@@ -8,9 +8,9 @@ use File::Basename qw(basename dirname);
 use File::Spec;
 use Git;
 
-our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m, $opt_d, $opt_u, $opt_w, $opt_W);
+our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m, $opt_d, $opt_u, $opt_w, $opt_W, $opt_k);
 
-getopts('uhPpvcfam:d:w:W');
+getopts('uhPpvcfkam:d:w:W');
 
 $opt_h && usage();
 
@@ -225,7 +225,14 @@ if (@canstatusfiles) {
       foreach my $name (keys %todo) {
        my $basename = basename($name);
 
-       $basename = "no file " . $basename if (exists($added{$basename}));
+       # CVS reports files that don't exist in the current revision as
+       # "no file $basename" in its "status" output, so we should
+       # anticipate that.  Totally unknown files will have a status
+       # "Unknown". However, if they exist in the Attic, their status
+       # will be "Up-to-date" (this means they were added once but have
+       # been removed).
+       $basename = "no file $basename" if $added{$basename};
+
        $basename =~ s/^\s+//;
        $basename =~ s/\s+$//;
 
@@ -233,31 +240,46 @@ if (@canstatusfiles) {
          $fullname{$basename} = $name;
          push (@canstatusfiles2, $name);
          delete($todo{$name});
-        }
+       }
       }
       my @cvsoutput;
       @cvsoutput = xargs_safe_pipe_capture([@cvs, 'status'], @canstatusfiles2);
       foreach my $l (@cvsoutput) {
-        chomp $l;
-        if ($l =~ /^File:\s+(.*\S)\s+Status: (.*)$/) {
-         if (!exists($fullname{$1})) {
-           print STDERR "Huh? Status reported for unexpected file '$1'\n";
-         } else {
-           $cvsstat{$fullname{$1}} = $2;
-         }
-       }
+       chomp $l;
+       next unless
+           my ($file, $status) = $l =~ /^File:\s+(.*\S)\s+Status: (.*)$/;
+
+       my $fullname = $fullname{$file};
+       print STDERR "Huh? Status '$status' reported for unexpected file '$file'\n"
+           unless defined $fullname;
+
+       # This response means the file does not exist except in
+       # CVS's attic, so set the status accordingly
+       $status = "In-attic"
+           if $file =~ /^no file /
+               && $status eq 'Up-to-date';
+
+       $cvsstat{$fullname{$file}} = $status
+           if defined $fullname{$file};
       }
     }
 }
 
-# ... validate new files,
+# ... Validate that new files have the correct status
 foreach my $f (@afiles) {
-    if (defined ($cvsstat{$f}) and $cvsstat{$f} ne "Unknown") {
-       $dirty = 1;
+    next unless defined(my $stat = $cvsstat{$f});
+
+    # This means the file has never been seen before
+    next if $stat eq 'Unknown';
+
+    # This means the file has been seen before but was removed
+    next if $stat eq 'In-attic';
+
+    $dirty = 1;
        warn "File $f is already known in your CVS checkout -- perhaps it has been added by another user. Or this may indicate that it exists on a different branch. If this is the case, use -f to force the merge.\n";
        warn "Status was: $cvsstat{$f}\n";
-    }
 }
+
 # ... validate known files.
 foreach my $f (@files) {
     next if grep { $_ eq $f } @afiles;
@@ -266,7 +288,26 @@ foreach my $f (@files) {
        $dirty = 1;
        warn "File $f not up to date but has status '$cvsstat{$f}' in your CVS checkout!\n";
     }
+
+    # Depending on how your GIT tree got imported from CVS you may
+    # have a conflict between expanded keywords in your CVS tree and
+    # unexpanded keywords in the patch about to be applied.
+    if ($opt_k) {
+       my $orig_file ="$f.orig";
+       rename $f, $orig_file;
+       open(FILTER_IN, "<$orig_file") or die "Cannot open $orig_file\n";
+       open(FILTER_OUT, ">$f") or die "Cannot open $f\n";
+       while (<FILTER_IN>)
+       {
+           my $line = $_;
+           $line =~ s/\$([A-Z][a-z]+):[^\$]+\$/\$$1\$/g;
+           print FILTER_OUT $line;
+       }
+       close FILTER_IN;
+       close FILTER_OUT;
+    }
 }
+
 if ($dirty) {
     if ($opt_f) {      warn "The tree is not clean -- forced merge\n";
        $dirty = 0;
@@ -370,7 +411,7 @@ sleep(1);
 
 sub usage {
        print STDERR <<END;
-Usage: GIT_DIR=/path/to/.git git cvsexportcommit [-h] [-p] [-v] [-c] [-f] [-u] [-w cvsworkdir] [-m msgprefix] [ parent ] commit
+Usage: GIT_DIR=/path/to/.git git cvsexportcommit [-h] [-p] [-v] [-c] [-f] [-u] [-k] [-w cvsworkdir] [-m msgprefix] [ parent ] commit
 END
        exit(1);
 }
index e43920296182f320dac31b5832a30844ffaef38f..9e03eee4586ca3b7476b56f66e9dcf6ffe3088cf 100755 (executable)
@@ -29,7 +29,7 @@ use IPC::Open2;
 $SIG{'PIPE'}="IGNORE";
 $ENV{'TZ'}="UTC";
 
-our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,@opt_M,$opt_A,$opt_S,$opt_L, $opt_a, $opt_r);
+our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,@opt_M,$opt_A,$opt_S,$opt_L, $opt_a, $opt_r, $opt_R);
 my (%conv_author_name, %conv_author_email);
 
 sub usage(;$) {
@@ -40,7 +40,7 @@ Usage: git cvsimport     # fetch/update GIT from CVS
        [-o branch-for-HEAD] [-h] [-v] [-d CVSROOT] [-A author-conv-file]
        [-p opts-for-cvsps] [-P file] [-C GIT_repository] [-z fuzz] [-i] [-k]
        [-u] [-s subst] [-a] [-m] [-M regex] [-S regex] [-L commitlimit]
-       [-r remote] [CVS_module]
+       [-r remote] [-R] [CVS_module]
 END
        exit(1);
 }
@@ -110,7 +110,7 @@ sub read_repo_config {
        }
 }
 
-my $opts = "haivmkuo:d:p:r:C:z:s:M:P:A:S:L:";
+my $opts = "haivmkuo:d:p:r:C:z:s:M:P:A:S:L:R";
 read_repo_config($opts);
 Getopt::Long::Configure( 'no_ignore_case', 'bundling' );
 
@@ -238,7 +238,9 @@ sub conn {
                }
                my $rr = ":pserver:$user\@$serv:$port$repo";
 
-               unless ($pass) {
+               if ($pass) {
+                       $pass = $self->_scramble($pass);
+               } else {
                        open(H,$ENV{'HOME'}."/.cvspass") and do {
                                # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
                                while (<H>) {
@@ -251,8 +253,8 @@ sub conn {
                                        }
                                }
                        };
+                       $pass = "A" unless $pass;
                }
-               $pass="A" unless $pass;
 
                my ($s, $rep);
                if ($proxyhost) {
@@ -484,6 +486,42 @@ sub _fetchfile {
        return $res;
 }
 
+sub _scramble {
+       my ($self, $pass) = @_;
+       my $scrambled = "A";
+
+       return $scrambled unless $pass;
+
+       my $pass_len = length($pass);
+       my @pass_arr = split("", $pass);
+       my $i;
+
+       # from cvs/src/scramble.c
+       my @shifts = (
+                 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
+                16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+               114,120, 53, 79, 96,109, 72,108, 70, 64, 76, 67,116, 74, 68, 87,
+               111, 52, 75,119, 49, 34, 82, 81, 95, 65,112, 86,118,110,122,105,
+                41, 57, 83, 43, 46,102, 40, 89, 38,103, 45, 50, 42,123, 91, 35,
+               125, 55, 54, 66,124,126, 59, 47, 92, 71,115, 78, 88,107,106, 56,
+                36,121,117,104,101,100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48,
+                58,113, 32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85,223,
+               225,216,187,166,229,189,222,188,141,249,148,200,184,136,248,190,
+               199,170,181,204,138,232,218,183,255,234,220,247,213,203,226,193,
+               174,172,228,252,217,201,131,230,197,211,145,238,161,179,160,212,
+               207,221,254,173,202,146,224,151,140,196,205,130,135,133,143,246,
+               192,159,244,239,185,168,215,144,139,165,180,157,147,186,214,176,
+               227,231,219,169,175,156,206,198,129,164,150,210,154,177,134,127,
+               182,128,158,208,162,132,167,209,149,241,153,251,237,236,171,195,
+               243,233,253,240,194,250,191,155,142,137,245,235,163,242,178,152
+       );
+
+       for ($i = 0; $i < $pass_len; $i++) {
+               $scrambled .= pack("C", $shifts[ord($pass_arr[$i])]);
+       }
+
+       return $scrambled;
+}
 
 package main;
 
@@ -541,10 +579,21 @@ sub get_headref ($) {
        return $r;
 }
 
+my $user_filename_prepend = '';
+sub munge_user_filename {
+       my $name = shift;
+       return File::Spec->file_name_is_absolute($name) ?
+               $name :
+               $user_filename_prepend . $name;
+}
+
 -d $git_tree
        or mkdir($git_tree,0777)
        or die "Could not create $git_tree: $!";
-chdir($git_tree);
+if ($git_tree ne '.') {
+       $user_filename_prepend = getwd() . '/';
+       chdir($git_tree);
+}
 
 my $last_branch = "";
 my $orig_branch = "";
@@ -560,16 +609,16 @@ $orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
 my %index; # holds filenames of one index per branch
 
 unless (-d $git_dir) {
-       system("git-init");
+       system(qw(git init));
        die "Cannot init the GIT db at $git_tree: $?\n" if $?;
-       system("git-read-tree");
+       system(qw(git read-tree));
        die "Cannot init an empty tree: $?\n" if $?;
 
        $last_branch = $opt_o;
        $orig_branch = "";
 } else {
-       open(F, "git-symbolic-ref HEAD |") or
-               die "Cannot run git-symbolic-ref: $!\n";
+       open(F, "-|", qw(git symbolic-ref HEAD)) or
+               die "Cannot run git symbolic-ref: $!\n";
        chomp ($last_branch = <F>);
        $last_branch = basename($last_branch);
        close(F);
@@ -578,12 +627,12 @@ unless (-d $git_dir) {
                $last_branch = "master";
        }
        $orig_branch = $last_branch;
-       $tip_at_start = `git-rev-parse --verify HEAD`;
+       $tip_at_start = `git rev-parse --verify HEAD`;
 
        # Get the last import timestamps
        my $fmt = '($ref, $author) = (%(refname), %(author));';
-       open(H, "git-for-each-ref --perl --format='$fmt' $remote |") or
-               die "Cannot run git-for-each-ref: $!\n";
+       my @cmd = ('git', 'for-each-ref', '--perl', "--format=$fmt", $remote);
+       open(H, "-|", @cmd) or die "Cannot run git for-each-ref: $!\n";
        while (defined(my $entry = <H>)) {
                my ($ref, $author);
                eval($entry) || die "cannot eval refs list: $@";
@@ -606,10 +655,15 @@ unless (-d $git_dir) {
 -f "$git_dir/cvs-authors" and
   read_author_info("$git_dir/cvs-authors");
 if ($opt_A) {
-       read_author_info($opt_A);
+       read_author_info(munge_user_filename($opt_A));
        write_author_info("$git_dir/cvs-authors");
 }
 
+# open .git/cvs-revisions, if requested
+open my $revision_map, '>>', "$git_dir/cvs-revisions"
+    or die "Can't open $git_dir/cvs-revisions for appending: $!\n"
+       if defined $opt_R;
+
 
 #
 # run cvsps into a file unless we are getting
@@ -638,10 +692,10 @@ unless ($opt_P) {
            print $cvspsfh $_;
        }
        close CVSPS;
-       $? == 0 or die "git-cvsimport: fatal: cvsps reported error\n";
+       $? == 0 or die "git cvsimport: fatal: cvsps reported error\n";
        close $cvspsfh;
 } else {
-       $cvspsfile = $opt_P;
+       $cvspsfile = munge_user_filename($opt_P);
 }
 
 open(CVS, "<$cvspsfile") or die $!;
@@ -667,33 +721,33 @@ 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: $!";
+       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: $!";
+               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: $?";
+               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: $!";
+       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";
+               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,%ignorebranch);
+my (@old,@new,@skipped,%ignorebranch,@commit_revisions);
 
 # commits that cvsps cannot place anywhere...
 $ignorebranch{'#CVSPS_NO_BRANCH'} = 1;
@@ -702,7 +756,7 @@ sub commit {
        if ($branch eq $opt_o && !$index{branch} &&
                !get_headref("$remote/$branch")) {
            # looks like an initial commit
-           # use the index primed by git-init
+           # use the index primed by git init
            $ENV{GIT_INDEX_FILE} = "$git_dir/index";
            $index{$branch} = "$git_dir/index";
        } else {
@@ -712,9 +766,9 @@ sub commit {
                $index{$branch} = tmpnam();
                $ENV{GIT_INDEX_FILE} = $index{$branch};
                if ($ancestor) {
-                   system("git-read-tree", "$remote/$ancestor");
+                   system("git", "read-tree", "$remote/$ancestor");
                } else {
-                   system("git-read-tree", "$remote/$branch");
+                   system("git", "read-tree", "$remote/$branch");
                }
                die "read-tree failed: $?\n" if $?;
            }
@@ -749,7 +803,7 @@ sub commit {
        $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);
+               'git', 'commit-tree', $tree, @commit_args);
 
        # compatibility with git2cvs
        substr($logmsg,32767) = "" if length($logmsg) > 32767;
@@ -762,7 +816,7 @@ sub commit {
        }
 
        print($commit_write "$logmsg\n") && close($commit_write)
-               or die "Error writing to git-commit-tree: $!\n";
+               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>);
@@ -771,11 +825,16 @@ sub commit {
        close($commit_read);
 
        waitpid($pid,0);
-       die "Error running git-commit-tree: $?\n" if $?;
+       die "Error running git commit-tree: $?\n" if $?;
 
-       system('git-update-ref', "$remote/$branch", $cid) == 0
+       system('git' , 'update-ref', "$remote/$branch", $cid) == 0
                or die "Cannot write branch $branch for update: $!\n";
 
+       if ($revision_map) {
+               print $revision_map "@$_ $cid\n" for @commit_revisions;
+       }
+       @commit_revisions = ();
+
        if ($tag) {
                my ($xtag) = $tag;
                $xtag =~ s/\s+\*\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY **
@@ -783,7 +842,7 @@ sub commit {
                $xtag =~ s/[\/]/$opt_s/g;
                $xtag =~ s/\[//g;
 
-               system('git-tag', '-f', $xtag, $cid) == 0
+               system('git' , 'tag', '-f', $xtag, $cid) == 0
                        or die "Cannot create tag $xtag: $!\n";
 
                print "Created tag '$xtag' on '$branch'\n" if $opt_v;
@@ -910,6 +969,7 @@ while (<CVS>) {
                    push(@skipped, $fn);
                    next;
                }
+               push @commit_revisions, [$fn, $rev];
                print "Fetching $fn   v $rev\n" if $opt_v;
                my ($tmpname, $size) = $cvs->file($fn,$rev);
                if ($size == -1) {
@@ -920,7 +980,7 @@ while (<CVS>) {
                        my $pid = open(my $F, '-|');
                        die $! unless defined $pid;
                        if (!$pid) {
-                           exec("git-hash-object", "-w", $tmpname)
+                           exec("git", "hash-object", "-w", $tmpname)
                                or die "Cannot create object: $!\n";
                        }
                        my $sha = <$F>;
@@ -932,7 +992,9 @@ while (<CVS>) {
                unlink($tmpname);
        } elsif ($state == 9 and /^\s+(.+?):\d+(?:\.\d+)+->(\d+(?:\.\d+)+)\(DEAD\)\s*$/) {
                my $fn = $1;
+               my $rev = $2;
                $fn =~ s#^/+##;
+               push @commit_revisions, [$fn, $rev];
                push(@old,$fn);
                print "Delete $fn\n" if $opt_v;
        } elsif ($state == 9 and /^\s*$/) {
@@ -944,7 +1006,7 @@ while (<CVS>) {
                }
                commit();
                if (($commitcount & 1023) == 0) {
-                       system("git repack -a -d");
+                       system(qw(git repack -a -d));
                }
                $state = 1;
        } elsif ($state == 11 and /^-+$/) {
@@ -964,11 +1026,11 @@ unless ($opt_P) {
 # The heuristic of repacking every 1024 commits can leave a
 # lot of unpacked data.  If there is more than 1MB worth of
 # not-packed objects, repack once more.
-my $line = `git-count-objects`;
+my $line = `git count-objects`;
 if ($line =~ /^(\d+) objects, (\d+) kilobytes$/) {
   my ($n_objects, $kb) = ($1, $2);
   1024 < $kb
-    and system("git repack -a -d");
+    and system(qw(git repack -a -d));
 }
 
 foreach my $git_index (values %index) {
@@ -989,28 +1051,28 @@ if ($orig_branch) {
        if ($opt_i) {
                exit 0;
        }
-       my $tip_at_end = `git-rev-parse --verify HEAD`;
+       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),
+               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), "$remote/$opt_o");
+               system(qw(git merge cvsimport HEAD), "$remote/$opt_o");
                die "Could not merge $opt_o into the current branch.\n" if $?;
        }
 } else {
        $orig_branch = "master";
        print "DONE; creating $orig_branch branch\n" if $opt_v;
-       system("git-update-ref", "refs/heads/master", "$remote/$opt_o")
+       system("git", "update-ref", "refs/heads/master", "$remote/$opt_o")
                unless defined get_headref('refs/heads/master');
-       system("git-symbolic-ref", "$remote/HEAD", "$remote/$opt_o")
+       system("git", "symbolic-ref", "$remote/HEAD", "$remote/$opt_o")
                if ($opt_r && $opt_o ne 'HEAD');
-       system('git-update-ref', 'HEAD', "$orig_branch");
+       system('git', 'update-ref', 'HEAD', "$orig_branch");
        unless ($opt_i) {
-               system('git checkout -f');
+               system(qw(git checkout -f));
                die "checkout failed: $?\n" if $?;
        }
 }
index ab6cea3e538047c30a5d728c7a16ee3c935c070b..e9f3037df351ceed0262a8995f19903464163af8 100755 (executable)
@@ -104,6 +104,7 @@ $log->info("--------------- STARTING -----------------");
 my $usage =
     "Usage: git cvsserver [options] [pserver|server] [<directory> ...]\n".
     "    --base-path <path>  : Prepend to requested CVSROOT\n".
+    "                          Can be read from GIT_CVSSERVER_BASE_PATH\n".
     "    --strict-paths      : Don't allow recursing into subdirectories\n".
     "    --export-all        : Don't check for gitcvs.enabled in config\n".
     "    --version, -V       : Print version information and exit\n".
@@ -111,7 +112,8 @@ my $usage =
     "\n".
     "<directory> ... is a list of allowed directories. If no directories\n".
     "are given, all are allowed. This is an additional restriction, gitcvs\n".
-    "access still needs to be enabled by the gitcvs.enabled config option.\n";
+    "access still needs to be enabled by the gitcvs.enabled config option.\n".
+    "Alternately, one directory may be specified in GIT_CVSSERVER_ROOT.\n";
 
 my @opts = ( 'help|h|H', 'version|V',
             'base-path=s', 'strict-paths', 'export-all' );
@@ -148,6 +150,24 @@ if ($state->{'export-all'} && !@{$state->{allowed_roots}}) {
     die "--export-all can only be used together with an explicit whitelist\n";
 }
 
+# Environment handling for running under git-shell
+if (exists $ENV{GIT_CVSSERVER_BASE_PATH}) {
+    if ($state->{'base-path'}) {
+       die "Cannot specify base path both ways.\n";
+    }
+    my $base_path = $ENV{GIT_CVSSERVER_BASE_PATH};
+    $state->{'base-path'} = $base_path;
+    $log->debug("Picked up base path '$base_path' from environment.\n");
+}
+if (exists $ENV{GIT_CVSSERVER_ROOT}) {
+    if (@{$state->{allowed_roots}}) {
+       die "Cannot specify roots both ways: @ARGV\n";
+    }
+    my $allowed_root = $ENV{GIT_CVSSERVER_ROOT};
+    $state->{allowed_roots} = [ $allowed_root ];
+    $log->debug("Picked up allowed root '$allowed_root' from environment.\n");
+}
+
 # if we are called with a pserver argument,
 # deal with the authentication cat before entering the
 # main loop
@@ -163,12 +183,58 @@ if ($state->{method} eq 'pserver') {
        exit 1;
     }
     $line = <STDIN>; chomp $line;
-    unless ($line eq 'anonymous') {
-       print "E Only anonymous user allowed via pserver\n";
-       print "I HATE YOU\n";
-       exit 1;
+    my $user = $line;
+    $line = <STDIN>; chomp $line;
+    my $password = $line;
+
+    if ($user eq 'anonymous') {
+        # "A" will be 1 byte, use length instead in case the
+        # encryption method ever changes (yeah, right!)
+        if (length($password) > 1 ) {
+            print "E Don't supply a password for the `anonymous' user\n";
+            print "I HATE YOU\n";
+            exit 1;
+        }
+
+        # Fall through to LOVE
+    } else {
+        # Trying to authenticate a user
+        if (not exists $cfg->{gitcvs}->{authdb}) {
+            print "E the repo config file needs a [gitcvs] section with an 'authdb' parameter set to the filename of the authentication database\n";
+            print "I HATE YOU\n";
+            exit 1;
+        }
+
+        my $authdb = $cfg->{gitcvs}->{authdb};
+
+        unless (-e $authdb) {
+            print "E The authentication database specified in [gitcvs.authdb] does not exist\n";
+            print "I HATE YOU\n";
+            exit 1;
+        }
+
+        my $auth_ok;
+        open my $passwd, "<", $authdb or die $!;
+        while (<$passwd>) {
+            if (m{^\Q$user\E:(.*)}) {
+                if (crypt($user, descramble($password)) eq $1) {
+                    $auth_ok = 1;
+                }
+            };
+        }
+        close $passwd;
+
+        unless ($auth_ok) {
+            print "I HATE YOU\n";
+            exit 1;
+        }
+
+        # Fall through to LOVE
     }
-    $line = <STDIN>; chomp $line;    # validate the password?
+
+    # For checking whether the user is anonymous on commit
+    $state->{user} = $user;
+
     $line = <STDIN>; chomp $line;
     unless ($line eq "END $request REQUEST") {
        die "E Do not understand $line -- expecting END $request REQUEST\n";
@@ -285,7 +351,7 @@ sub req_Root
        return 0;
     }
 
-    my @gitvars = `git-config -l`;
+    my @gitvars = `git config -l`;
     if ($?) {
        print "E problems executing git-config on the server -- this is not a git repository or the PATH is not set correctly.\n";
         print "E \n";
@@ -388,7 +454,7 @@ sub req_Directory
     $state->{localdir} = $data;
     $state->{repository} = $repository;
     $state->{path} = $repository;
-    $state->{path} =~ s/^$state->{CVSROOT}\///;
+    $state->{path} =~ s/^\Q$state->{CVSROOT}\E\///;
     $state->{module} = $1 if ($state->{path} =~ s/^(.*?)(\/|$)//);
     $state->{path} .= "/" if ( $state->{path} =~ /\S/ );
 
@@ -702,7 +768,7 @@ sub req_Modified
     # 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} = `git hash-object $filename`;
     $state->{entries}{$state->{directory}.$data}{modified_hash} =~ s/\s.*$//s;
 
     #$log->debug("req_Modified : file=$data mode=$mode size=$size");
@@ -981,6 +1047,8 @@ sub req_update
 
     #$log->debug("update state : " . Dumper($state));
 
+    my $last_dirname = "///";
+
     # foreach file specified on the command line ...
     foreach my $filename ( @{$state->{args}} )
     {
@@ -988,6 +1056,20 @@ sub req_update
 
         $log->debug("Processing file $filename");
 
+        unless ( $state->{globaloptions}{-Q} || $state->{globaloptions}{-q} )
+        {
+            my $cur_dirname = dirname($filename);
+            if ( $cur_dirname ne $last_dirname )
+            {
+                $last_dirname = $cur_dirname;
+                if ( $cur_dirname eq "" )
+                {
+                    $cur_dirname = ".";
+                }
+                print "E cvs update: Updating $cur_dirname\n";
+            }
+        }
+
         # if we have a -C we should pretend we never saw modified stuff
         if ( exists ( $state->{opt}{C} ) )
         {
@@ -1235,9 +1317,9 @@ sub req_ci
 
     $log->info("req_ci : " . ( defined($data) ? $data : "[NULL]" ));
 
-    if ( $state->{method} eq 'pserver')
+    if ( $state->{method} eq 'pserver' and $state->{user} eq 'anonymous' )
     {
-        print "error 1 pserver access cannot commit\n";
+        print "error 1 anonymous user cannot commit via pserver\n";
         cleanupWorkTree();
         exit;
     }
@@ -1289,7 +1371,7 @@ sub req_ci
 
        # do a checkout of the file if it is part of this tree
         if ($wrev) {
-            system('git-checkout-index', '-f', '-u', $filename);
+            system('git', 'checkout-index', '-f', '-u', $filename);
             unless ($? == 0) {
                 die "Error running git-checkout-index -f -u $filename : $!";
             }
@@ -1331,15 +1413,15 @@ sub req_ci
         {
             $log->info("Removing file '$filename'");
             unlink($filename);
-            system("git-update-index", "--remove", $filename);
+            system("git", "update-index", "--remove", $filename);
         }
         elsif ( $addflag )
         {
             $log->info("Adding file '$filename'");
-            system("git-update-index", "--add", $filename);
+            system("git", "update-index", "--add", $filename);
         } else {
             $log->info("Updating file '$filename'");
-            system("git-update-index", $filename);
+            system("git", "update-index", $filename);
         }
     }
 
@@ -1351,7 +1433,7 @@ sub req_ci
         return;
     }
 
-    my $treehash = `git-write-tree`;
+    my $treehash = `git write-tree`;
     chomp $treehash;
 
     $log->debug("Treehash : $treehash, Parenthash : $parenthash");
@@ -1368,7 +1450,7 @@ sub req_ci
     }
     close $msg_fh;
 
-    my $commithash = `git-commit-tree $treehash -p $parenthash < $msg_filename`;
+    my $commithash = `git commit-tree $treehash -p $parenthash < $msg_filename`;
     chomp($commithash);
     $log->info("Commit hash : $commithash");
 
@@ -1821,7 +1903,7 @@ sub req_annotate
        # 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);
+       system("git", "read-tree", $lastseenin);
        unless ($? == 0)
        {
            print "E error running git-read-tree $lastseenin $ENV{GIT_INDEX_FILE} $!\n";
@@ -1830,7 +1912,7 @@ sub req_annotate
        $log->info("Created index '$ENV{GIT_INDEX_FILE}' with commit $lastseenin - exit status $?");
 
         # do a checkout of the file
-        system('git-checkout-index', '-f', '-u', $filename);
+        system('git', 'checkout-index', '-f', '-u', $filename);
         unless ($? == 0) {
             print "E error running git-checkout-index -f -u $filename : $!\n";
             return;
@@ -1861,7 +1943,7 @@ sub req_annotate
         close ANNOTATEHINTS
             or (print "E failed to write $a_hints: $!\n"), return;
 
-        my @cmd = (qw(git-annotate -l -S), $a_hints, $filename);
+        my @cmd = (qw(git annotate -l -S), $a_hints, $filename);
         if (!open(ANNOTATE, "-|", @cmd)) {
             print "E error invoking ". join(' ',@cmd) .": $!\n";
             return;
@@ -2078,17 +2160,17 @@ sub transmitfile
 
     die "Need filehash" unless ( defined ( $filehash ) and $filehash =~ /^[a-zA-Z0-9]{40}$/ );
 
-    my $type = `git-cat-file -t $filehash`;
+    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`;
+    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 ( open my $fh, '-|', "git", "cat-file", "blob", $filehash )
     {
         if ( defined ( $options->{targetfile} ) )
         {
@@ -2333,15 +2415,20 @@ sub kopts_from_path
     if ( defined ( $cfg->{gitcvs}{usecrlfattr} ) and
          $cfg->{gitcvs}{usecrlfattr} =~ /\s*(1|true|yes)\s*$/i )
     {
-        my ($val) = check_attr( "crlf", $path );
-        if ( $val eq "set" )
+        my ($val) = check_attr( "text", $path );
+        if ( $val eq "unspecified" )
         {
-            return "";
+            $val = check_attr( "crlf", $path );
         }
-        elsif ( $val eq "unset" )
+        if ( $val eq "unset" )
         {
             return "-kb"
         }
+        elsif ( check_attr( "eol", $path ) ne "unspecified" ||
+                $val eq "set" || $val eq "input" )
+        {
+            return "";
+        }
         else
         {
             $log->info("Unrecognized check_attr crlf $path : $val");
@@ -2550,6 +2637,43 @@ sub cvs_author
     $author;
 }
 
+
+sub descramble
+{
+    # This table is from src/scramble.c in the CVS source
+    my @SHIFTS = (
+        0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
+        16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+        114,120, 53, 79, 96,109, 72,108, 70, 64, 76, 67,116, 74, 68, 87,
+        111, 52, 75,119, 49, 34, 82, 81, 95, 65,112, 86,118,110,122,105,
+        41, 57, 83, 43, 46,102, 40, 89, 38,103, 45, 50, 42,123, 91, 35,
+        125, 55, 54, 66,124,126, 59, 47, 92, 71,115, 78, 88,107,106, 56,
+        36,121,117,104,101,100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48,
+        58,113, 32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85,223,
+        225,216,187,166,229,189,222,188,141,249,148,200,184,136,248,190,
+        199,170,181,204,138,232,218,183,255,234,220,247,213,203,226,193,
+        174,172,228,252,217,201,131,230,197,211,145,238,161,179,160,212,
+        207,221,254,173,202,146,224,151,140,196,205,130,135,133,143,246,
+        192,159,244,239,185,168,215,144,139,165,180,157,147,186,214,176,
+        227,231,219,169,175,156,206,198,129,164,150,210,154,177,134,127,
+        182,128,158,208,162,132,167,209,149,241,153,251,237,236,171,195,
+        243,233,253,240,194,250,191,155,142,137,245,235,163,242,178,152
+    );
+    my ($str) = @_;
+
+    # This should never happen, the same password format (A) has been
+    # used by CVS since the beginning of time
+    {
+        my $fmt = substr($str, 0, 1);
+        die "invalid password format `$fmt'" unless $fmt eq 'A';
+    }
+
+    my @str = unpack "C*", substr($str, 1);
+    my $ret = join '', map { chr $SHIFTS[$_] } @str;
+    return $ret;
+}
+
+
 package GITCVS::log;
 
 ####
@@ -2935,7 +3059,7 @@ sub update
         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: $!";
+    open(GITLOG, '-|', 'git', 'rev-list', @git_log_params) or die "Cannot call git-rev-list: $!";
 
     my @commits;
 
@@ -3021,7 +3145,7 @@ sub update
                         next;
                     }
                    my $base = eval {
-                           safe_pipe_capture('git-merge-base',
+                           safe_pipe_capture('git', 'merge-base',
                                                 $lastpicked, $parent);
                    };
                    # The two branches may not be related at all,
@@ -3033,7 +3157,7 @@ sub update
                     if ($base) {
                         my @merged;
                         # print "want to log between  $base $parent \n";
-                        open(GITLOG, '-|', 'git-log', '--pretty=medium', "$base..$parent")
+                        open(GITLOG, '-|', 'git', 'log', '--pretty=medium', "$base..$parent")
                          or die "Cannot call git-log: $!";
                         my $mergedhash;
                         while (<GITLOG>) {
@@ -3075,7 +3199,7 @@ sub update
 
         if ( defined ( $lastpicked ) )
         {
-            my $filepipe = open(FILELIST, '-|', 'git-diff-tree', '-z', '-r', $lastpicked, $commit->{hash}) or die("Cannot call git-diff-tree : $!");
+            my $filepipe = open(FILELIST, '-|', 'git', 'diff-tree', '-z', '-r', $lastpicked, $commit->{hash}) or die("Cannot call git-diff-tree : $!");
            local ($/) = "\0";
             while ( <FILELIST> )
             {
@@ -3149,7 +3273,7 @@ sub update
             # this is used to detect files removed from the repo
             my $seen_files = {};
 
-            my $filepipe = open(FILELIST, '-|', 'git-ls-tree', '-z', '-r', $commit->{hash}) or die("Cannot call git-ls-tree : $!");
+            my $filepipe = open(FILELIST, '-|', 'git', 'ls-tree', '-z', '-r', $commit->{hash}) or die("Cannot call git-ls-tree : $!");
            local $/ = "\0";
             while ( <FILELIST> )
             {
@@ -3451,7 +3575,7 @@ sub commitmessage
         return $message;
     }
 
-    my @lines = safe_pipe_capture("git-cat-file", "commit", $commithash);
+    my @lines = safe_pipe_capture("git", "cat-file", "commit", $commithash);
     shift @lines while ( $lines[0] =~ /\S/ );
     $message = join("",@lines);
     $message .= " " if ( $message =~ /\n$/ );
index 57e8e3256d7b6578cda37815163d8945431aa6db..524f5ea8ab14bb44e5a8e6393e40c0ec5c1ddaa2 100755 (executable)
@@ -3,16 +3,16 @@
 # This script is typically launched by using the 'git difftool'
 # convenience command.
 #
-# Copyright (c) 2009 David Aguilar
+# Copyright (c) 2009, 2010 David Aguilar
 
-# Load common functions from git-mergetool--lib
 TOOL_MODE=diff
 . git-mergetool--lib
 
 # difftool.prompt controls the default prompt/no-prompt behavior
 # and is overridden with $GIT_DIFFTOOL*_PROMPT.
 should_prompt () {
-       prompt=$(git config --bool difftool.prompt || echo true)
+       prompt_merge=$(git config --bool mergetool.prompt || echo true)
+       prompt=$(git config --bool difftool.prompt || echo $prompt_merge)
        if test "$prompt" = true; then
                test -z "$GIT_DIFFTOOL_NO_PROMPT"
        else
@@ -20,7 +20,11 @@ should_prompt () {
        fi
 }
 
-# Sets up shell variables and runs a merge tool
+# Indicates that --extcmd=... was specified
+use_ext_cmd () {
+       test -n "$GIT_DIFFTOOL_EXTCMD"
+}
+
 launch_merge_tool () {
        # Merged is the filename as it appears in the work tree
        # Local is the contents of a/filename
@@ -35,20 +39,28 @@ launch_merge_tool () {
        # the user with the real $MERGED name before launching $merge_tool.
        if should_prompt; then
                printf "\nViewing: '$MERGED'\n"
-               printf "Hit return to launch '%s': " "$merge_tool"
+               if use_ext_cmd; then
+                       printf "Hit return to launch '%s': " \
+                               "$GIT_DIFFTOOL_EXTCMD"
+               else
+                       printf "Hit return to launch '%s': " "$merge_tool"
+               fi
                read ans
        fi
 
-       # Run the appropriate merge tool command
-       run_merge_tool "$merge_tool"
+       if use_ext_cmd; then
+               eval $GIT_DIFFTOOL_EXTCMD '"$LOCAL"' '"$REMOTE"'
+       else
+               run_merge_tool "$merge_tool"
+       fi
 }
 
-# Allow GIT_DIFF_TOOL and GIT_MERGE_TOOL to provide default values
-test -n "$GIT_MERGE_TOOL" && merge_tool="$GIT_MERGE_TOOL"
-test -n "$GIT_DIFF_TOOL" && merge_tool="$GIT_DIFF_TOOL"
-
-if test -z "$merge_tool"; then
-       merge_tool="$(get_merge_tool)" || exit
+if ! use_ext_cmd; then
+       if test -n "$GIT_DIFF_TOOL"; then
+               merge_tool="$GIT_DIFF_TOOL"
+       else
+               merge_tool="$(get_merge_tool)" || exit
+       fi
 fi
 
 # Launch the merge tool on each path provided by 'git diff'
index ba5e60a45e2c4a8b54e6a58e656b3978dcb45633..adc42de8752fd395707f1e395c61991b3146cebb 100755 (executable)
@@ -1,5 +1,5 @@
 #!/usr/bin/env perl
-# Copyright (c) 2009 David Aguilar
+# Copyright (c) 2009, 2010 David Aguilar
 #
 # This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
 # git-difftool--helper script.
@@ -15,13 +15,17 @@ use warnings;
 use Cwd qw(abs_path);
 use File::Basename qw(dirname);
 
+require Git;
+
 my $DIR = abs_path(dirname($0));
 
 
 sub usage
 {
        print << 'USAGE';
-usage: git difftool [--tool=<tool>] [-y|--no-prompt] ["git diff" options]
+usage: git difftool [-t|--tool=<tool>] [-x|--extcmd=<cmd>]
+                    [-y|--no-prompt]   [-g|--gui]
+                    ['git diff' options]
 USAGE
        exit 1;
 }
@@ -63,6 +67,26 @@ sub generate_command
                        $ENV{GIT_DIFF_TOOL} = substr($arg, 7);
                        next;
                }
+               if ($arg eq '-x' || $arg eq '--extcmd') {
+                       usage() if $#ARGV <= $idx;
+                       $ENV{GIT_DIFFTOOL_EXTCMD} = $ARGV[$idx + 1];
+                       $skip_next = 1;
+                       next;
+               }
+               if ($arg =~ /^--extcmd=/) {
+                       $ENV{GIT_DIFFTOOL_EXTCMD} = substr($arg, 9);
+                       next;
+               }
+               if ($arg eq '-g' || $arg eq '--gui') {
+                       eval {
+                               my $tool = Git::command_oneline('config',
+                                                               'diff.guitool');
+                               if (length($tool)) {
+                                       $ENV{GIT_DIFF_TOOL} = $tool;
+                               }
+                       };
+                       next;
+               }
                if ($arg eq '-y' || $arg eq '--no-prompt') {
                        $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true';
                        delete $ENV{GIT_DIFFTOOL_PROMPT};
index 37e044db40bb89470bb64a4108c86d5173e03b5a..962a93b586571eb6fc60aae53c77f6e6b9fb281f 100755 (executable)
@@ -97,12 +97,12 @@ set_ident () {
        echo "case \"\$GIT_${uid}_NAME\" in \"\") GIT_${uid}_NAME=\"\${GIT_${uid}_EMAIL%%@*}\" && export GIT_${uid}_NAME;; esac"
 }
 
-USAGE="[--env-filter <command>] [--tree-filter <command>] \
-[--index-filter <command>] [--parent-filter <command>] \
-[--msg-filter <command>] [--commit-filter <command>] \
-[--tag-name-filter <command>] [--subdirectory-filter <directory>] \
-[--original <namespace>] [-d <directory>] [-f | --force] \
-[<rev-list options>...]"
+USAGE="[--env-filter <command>] [--tree-filter <command>]
+            [--index-filter <command>] [--parent-filter <command>]
+            [--msg-filter <command>] [--commit-filter <command>]
+            [--tag-name-filter <command>] [--subdirectory-filter <directory>]
+            [--original <namespace>] [-d <directory>] [-f | --force]
+            [<rev-list options>...]"
 
 OPTIONS_SPEC=
 . git-sh-setup
@@ -125,6 +125,7 @@ filter_subdir=
 orig_namespace=refs/original/
 force=
 prune_empty=
+remap_to_ancestor=
 while :
 do
        case "$1" in
@@ -137,6 +138,12 @@ do
                force=t
                continue
                ;;
+       --remap-to-ancestor)
+               # deprecated ($remap_to_ancestor is set now automatically)
+               shift
+               remap_to_ancestor=t
+               continue
+               ;;
        --prune-empty)
                shift
                prune_empty=t
@@ -182,6 +189,7 @@ do
                ;;
        --subdirectory-filter)
                filter_subdir="$OPTARG"
+               remap_to_ancestor=t
                ;;
        --original)
                orig_namespace=$(expr "$OPTARG/" : '\(.*[^/]\)/*$')/
@@ -200,7 +208,7 @@ t,)
 ,*)
        ;;
 *)
-       die "Cannot set --prune-empty and --filter-commit at the same time"
+       die "Cannot set --prune-empty and --commit-filter at the same time"
 esac
 
 case "$force" in
@@ -252,20 +260,35 @@ test -s "$tempdir"/heads ||
 
 GIT_INDEX_FILE="$(pwd)/../index"
 export GIT_INDEX_FILE
-git read-tree || die "Could not seed the index"
 
 # map old->new commit ids for rewriting parents
 mkdir ../map || die "Could not create map/ directory"
 
+# we need "--" only if there are no path arguments in $@
+nonrevs=$(git rev-parse --no-revs "$@") || exit
+if test -z "$nonrevs"
+then
+       dashdash=--
+else
+       dashdash=
+       remap_to_ancestor=t
+fi
+
+rev_args=$(git rev-parse --revs-only "$@")
+
 case "$filter_subdir" in
 "")
-       git rev-list --reverse --topo-order --default HEAD \
-               --parents --simplify-merges "$@"
+       eval set -- "$(git rev-parse --sq --no-revs "$@")"
        ;;
 *)
-       git rev-list --reverse --topo-order --default HEAD \
-               --parents --simplify-merges "$@" -- "$filter_subdir"
-esac > ../revs || die "Could not get the commits"
+       eval set -- "$(git rev-parse --sq --no-revs "$@" $dashdash \
+               "$filter_subdir")"
+       ;;
+esac
+
+git rev-list --reverse --topo-order --default HEAD \
+       --parents --simplify-merges $rev_args "$@" > ../revs ||
+       die "Could not get the commits"
 commits=$(wc -l <../revs | tr -d " ")
 
 test $commits -eq 0 && die "Found nothing to rewrite"
@@ -316,7 +339,7 @@ while read commit parents; do
                        die "tree filter failed: $filter_tree"
 
                (
-                       git diff-index -r --name-only $commit &&
+                       git diff-index -r --name-only --ignore-submodules $commit &&
                        git ls-files --others
                ) > "$tempdir"/tree-state || exit
                git update-index --add --replace --remove --stdin \
@@ -345,19 +368,19 @@ while read commit parents; do
                        die "could not write rewritten commit"
 done <../revs
 
-# In case of a subdirectory filter, it is possible that a specified head
-# is not in the set of rewritten commits, because it was pruned by the
-# revision walker.  Fix it by mapping these heads to the unique nearest
-# ancestor that survived the pruning.
+# If we are filtering for paths, as in the case of a subdirectory
+# filter, it is possible that a specified head is not in the set of
+# rewritten commits, because it was pruned by the revision walker.
+# Ancestor remapping fixes this by mapping these heads to the unique
+# nearest ancestor that survived the pruning.
 
-if test "$filter_subdir"
+if test "$remap_to_ancestor" = t
 then
        while read ref
        do
                sha1=$(git rev-parse "$ref"^0)
                test -f "$workdir"/../map/$sha1 && continue
-               ancestor=$(git rev-list --simplify-merges -1 \
-                               $ref -- "$filter_subdir")
+               ancestor=$(git rev-list --simplify-merges -1 "$ref" "$@")
                test "$ancestor" && echo $(map $ancestor) >> "$workdir"/../map/$sha1
        done < "$tempdir"/heads
 fi
@@ -447,17 +470,17 @@ if [ "$filter_tag_name" ]; then
                                                "$new_sha1" "$new_ref"
                                git cat-file tag "$ref" |
                                sed -n \
-                                   -e "1,/^$/{
+                                   -e '1,/^$/{
                                          /^object /d
                                          /^type /d
                                          /^tag /d
-                                       }" \
+                                       }' \
                                    -e '/^-----BEGIN PGP SIGNATURE-----/q' \
                                    -e 'p' ) |
                                git mktag) ||
                                die "Could not create new tag object for $ref"
                        if git cat-file tag "$ref" | \
-                          grep '^-----BEGIN PGP SIGNATURE-----' >/dev/null 2>&1
+                          sane_grep '^-----BEGIN PGP SIGNATURE-----' >/dev/null 2>&1
                        then
                                warn "gpg signature stripped from tag object $sha1t"
                        fi
index b3f937eace99ce1e25afbdbea678e9f6926dbc75..1fb4d9b4b7393d2c803457365127692fb093338f 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=0.12.GITGUI
+DEF_VER=0.13.GITGUI
 
 LF='
 '
index b3580e9e48b6ac5d1a7fbd010f032445702f140f..e22ba5c321dc47fdf512f30c5e6c741c41fb99eb 100644 (file)
@@ -7,7 +7,7 @@ all::
 # TCL_PATH must be vaild for this to work.
 #
 
-GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
+GIT-VERSION-FILE: FORCE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
 -include GIT-VERSION-FILE
 
@@ -215,6 +215,7 @@ endif
 $(GITGUI_MAIN): git-gui.sh GIT-VERSION-FILE GIT-GUI-VARS
        $(QUIET_GEN)rm -f $@ $@+ && \
        sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
+               -e 's|@@SHELL_PATH@@|$(SHELL_PATH_SQ)|' \
                -e '1,30s|^ argv0=$$0| argv0=$(GITGUI_SCRIPT)|' \
                -e '1,30s|^ exec wish | exec '\''$(TCLTK_PATH_SED)'\'' |' \
                -e 's/@@GITGUI_VERSION@@/$(GITGUI_VERSION)/g' \
@@ -270,7 +271,7 @@ TRACK_VARS = \
        GITGUI_MACOSXAPP=$(GITGUI_MACOSXAPP) \
 #end TRACK_VARS
 
-GIT-GUI-VARS: .FORCE-GIT-GUI-VARS
+GIT-GUI-VARS: FORCE
        @VARS='$(TRACK_VARS)'; \
        if test x"$$VARS" != x"`cat $@ 2>/dev/null`" ; then \
                echo 1>&2 "    * new locations or Tcl/Tk interpreter"; \
@@ -340,5 +341,4 @@ ifdef GITGUI_WINDOWS_WRAPPER
 endif
 
 .PHONY: all install uninstall dist-version clean
-.PHONY: .FORCE-GIT-VERSION-FILE
-.PHONY: .FORCE-GIT-GUI-VARS
+.PHONY: FORCE
index 12e117ecb11afabef1d0d751ebee442ce515dc1e..4277f30c4116faf2788243af4ec23f1d077698e8 100755 (executable)
@@ -5,6 +5,8 @@ exec wish "$0" -- "$@"
 # This is a trivial implementation of an SSH_ASKPASS handler.
 # Git-gui uses this script if none are already configured.
 
+package require Tk
+
 set answer {}
 set yesno  0
 set rc     255
@@ -30,16 +32,20 @@ if {!$yesno} {
 
 frame .b
 button .b.ok     -text OK     -command finish
-button .b.cancel -text Cancel -command {destroy .}
+button .b.cancel -text Cancel -command cancel
 
 pack .b.ok -side left -expand 1
 pack .b.cancel -side right -expand 1
 pack .b -side bottom -fill x -padx 10 -pady 10
 
 bind . <Visibility> {focus -force .e}
-bind . <Key-Return> finish
-bind . <Key-Escape> {destroy .}
-bind . <Destroy>    {exit $rc}
+bind . <Key-Return> [list .b.ok invoke]
+bind . <Key-Escape> [list .b.cancel invoke]
+bind . <Destroy>    {set rc $rc}
+
+proc cancel {} {
+       set ::rc 255
+}
 
 proc finish {} {
        if {$::yesno} {
@@ -50,10 +56,11 @@ proc finish {} {
                }
        }
 
-       set ::rc 0
        puts $::answer
-       destroy .
+       set ::rc 0
 }
 
 wm title . "OpenSSH"
 tk::PlaceWindow .
+vwait rc
+exit $rc
index 14b92ba786554f4e714c89191e5584a825964ab2..4617f29c26726c2cd438f160be21a8422e66b352 100755 (executable)
@@ -10,8 +10,8 @@
  exec wish "$argv0" -- "$@"
 
 set appvers {@@GITGUI_VERSION@@}
-set copyright [encoding convertfrom utf-8 {
-Copyright © 2006, 2007 Shawn Pearce, et. al.
+set copyright [string map [list (c) \u00a9] {
+Copyright (c) 2006-2010 Shawn Pearce, et. al.
 
 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
@@ -38,7 +38,7 @@ if {[catch {package require Tcl 8.4} err]
        tk_messageBox \
                -icon error \
                -type ok \
-               -title [mc "git-gui: fatal error"] \
+               -title "git-gui: fatal error" \
                -message $err
        exit 1
 }
@@ -121,11 +121,14 @@ unset oguimsg
 
 set _appname {Git Gui}
 set _gitdir {}
+set _gitworktree {}
+set _isbare {}
 set _gitexec {}
 set _githtmldir {}
 set _reponame {}
 set _iscygwin {}
 set _search_path {}
+set _shellpath {@@SHELL_PATH@@}
 
 set _trace [lsearch -exact $argv --trace]
 if {$_trace >= 0} {
@@ -135,6 +138,18 @@ if {$_trace >= 0} {
        set _trace 0
 }
 
+proc shellpath {} {
+       global _shellpath env
+       if {[string match @@* $_shellpath]} {
+               if {[info exists env(SHELL)]} {
+                       return $env(SHELL)
+               } else {
+                       return /bin/sh
+               }
+       }
+       return $_shellpath
+}
+
 proc appname {} {
        global _appname
        return $_appname
@@ -267,6 +282,17 @@ proc is_config_true {name} {
        }
 }
 
+proc is_config_false {name} {
+       global repo_config
+       if {[catch {set v $repo_config($name)}]} {
+               return 0
+       } elseif {$v eq {false} || $v eq {0} || $v eq {no}} {
+               return 1
+       } else {
+               return 0
+       }
+}
+
 proc get_config {name} {
        global repo_config
        if {[catch {set v $repo_config($name)}]} {
@@ -276,6 +302,32 @@ proc get_config {name} {
        }
 }
 
+proc is_bare {} {
+       global _isbare
+       global _gitdir
+       global _gitworktree
+
+       if {$_isbare eq {}} {
+               if {[catch {
+                       set _bare [git rev-parse --is-bare-repository]
+                       switch  -- $_bare {
+                       true { set _isbare 1 }
+                       false { set _isbare 0}
+                       default { throw }
+                       }
+               }]} {
+                       if {[is_config_true core.bare]
+                               || ($_gitworktree eq {}
+                                       && [lindex [file split $_gitdir] end] ne {.git})} {
+                               set _isbare 1
+                       } else {
+                               set _isbare 0
+                       }
+               }
+       }
+       return $_isbare
+}
+
 ######################################################################
 ##
 ## handy utils
@@ -295,6 +347,8 @@ proc _trace_exec {cmd} {
        puts stderr $d
 }
 
+#'"  fix poor old emacs font-lock mode
+
 proc _git_cmd {name} {
        global _git_cmd_path
 
@@ -388,6 +442,9 @@ proc _lappend_nice {cmd_var} {
 
        if {![info exists _nice]} {
                set _nice [_which nice]
+               if {[catch {exec $_nice git version}]} {
+                       set _nice {}
+               }
        }
        if {$_nice ne {}} {
                lappend cmd $_nice
@@ -606,6 +663,7 @@ proc rmsel_tag {text} {
        return $text
 }
 
+wm withdraw .
 set root_exists 0
 bind . <Visibility> {
        bind . <Visibility> {}
@@ -649,12 +707,17 @@ if {[is_Windows]} {
 ## config defaults
 
 set cursor_ptr arrow
-font create font_diff -family Courier -size 10
 font create font_ui
-catch {
-       label .dummy
-       eval font configure font_ui [font actual [.dummy cget -font]]
-       destroy .dummy
+if {[lsearch -exact [font names] TkDefaultFont] != -1} {
+       eval [linsert [font actual TkDefaultFont] 0 font configure font_ui]
+       eval [linsert [font actual TkFixedFont] 0 font create font_diff]
+} else {
+       font create font_diff -family Courier -size 10
+       catch {
+               label .dummy
+               eval font configure font_ui [font actual [.dummy cget -font]]
+               destroy .dummy
+       }
 }
 
 font create font_uiitalic
@@ -669,6 +732,9 @@ foreach class {Button Checkbutton Entry Label
 }
 if {![is_MacOSX]} {
        option add *Menu.font font_ui
+       option add *Entry.borderWidth 1 startupFile
+       option add *Entry.relief sunken startupFile
+       option add *RadioButton.anchor w startupFile
 }
 unset class
 
@@ -721,6 +787,18 @@ proc apply_config {} {
                font configure ${font}bold -weight bold
                font configure ${font}italic -slant italic
        }
+
+       global use_ttk NS
+       set use_ttk 0
+       set NS {}
+       if {$repo_config(gui.usettk)} {
+               set use_ttk [package vsatisfies [package provide Tk] 8.5]
+               if {$use_ttk} {
+                       set NS ttk
+                       bind [winfo class .] <<ThemeChanged>> [list InitTheme]
+                       pave_toplevel .
+               }
+       }
 }
 
 set default_config(branch.autosetupmerge) true
@@ -734,6 +812,7 @@ set default_config(user.email) {}
 
 set default_config(gui.encoding) [encoding system]
 set default_config(gui.matchtrackingbranch) false
+set default_config(gui.textconv) true
 set default_config(gui.pruneduringfetch) false
 set default_config(gui.trustmtime) false
 set default_config(gui.fastcopyblame) false
@@ -745,6 +824,9 @@ set default_config(gui.newbranchtemplate) {}
 set default_config(gui.spellingdictionary) {}
 set default_config(gui.fontui) [font configure font_ui]
 set default_config(gui.fontdiff) [font configure font_diff]
+# TODO: this option should be added to the git-config documentation
+set default_config(gui.maxfilesdisplayed) 5000
+set default_config(gui.usettk) 1
 set font_descs {
        {fontui   font_ui   {mc "Main Font"}}
        {fontdiff font_diff {mc "Diff/Console Font"}}
@@ -1072,6 +1154,8 @@ if {[catch {
                set _prefix {}
                }]
        && [catch {
+               # beware that from the .git dir this sets _gitdir to .
+               # and _prefix to the empty string
                set _gitdir [git rev-parse --git-dir]
                set _prefix [git rev-parse --show-prefix]
        } err]} {
@@ -1080,6 +1164,14 @@ if {[catch {
        choose_repository::pick
        set picked 1
 }
+
+# we expand the _gitdir when it's just a single dot (i.e. when we're being
+# run from the .git dir itself) lest the routines to find the worktree
+# get confused
+if {$_gitdir eq "."} {
+       set _gitdir [pwd]
+}
+
 if {![file isdirectory $_gitdir] && [is_Cygwin]} {
        catch {set _gitdir [exec cygpath --windows $_gitdir]}
 }
@@ -1088,25 +1180,44 @@ if {![file isdirectory $_gitdir]} {
        error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"]
        exit 1
 }
+# _gitdir exists, so try loading the config
+load_config 0
+apply_config
+# try to set work tree from environment, falling back to core.worktree
+if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} {
+       set _gitworktree [get_config core.worktree]
+       if {$_gitworktree eq ""} {
+               set _gitworktree [file dirname [file normalize $_gitdir]]
+       }
+}
 if {$_prefix ne {}} {
-       regsub -all {[^/]+/} $_prefix ../ cdup
+       if {$_gitworktree eq {}} {
+               regsub -all {[^/]+/} $_prefix ../ cdup
+       } else {
+               set cdup $_gitworktree
+       }
        if {[catch {cd $cdup} err]} {
                catch {wm withdraw .}
                error_popup [strcat [mc "Cannot move to top of working directory:"] "\n\n$err"]
                exit 1
        }
+       set _gitworktree [pwd]
        unset cdup
 } elseif {![is_enabled bare]} {
-       if {[lindex [file split $_gitdir] end] ne {.git}} {
+       if {[is_bare]} {
                catch {wm withdraw .}
-               error_popup [strcat [mc "Cannot use funny .git directory:"] "\n\n$_gitdir"]
+               error_popup [strcat [mc "Cannot use bare repository:"] "\n\n$_gitdir"]
                exit 1
        }
-       if {[catch {cd [file dirname $_gitdir]} err]} {
+       if {$_gitworktree eq {}} {
+               set _gitworktree [file dirname $_gitdir]
+       }
+       if {[catch {cd $_gitworktree} err]} {
                catch {wm withdraw .}
-               error_popup [strcat [mc "No working directory"] " [file dirname $_gitdir]:\n\n$err"]
+               error_popup [strcat [mc "No working directory"] " $_gitworktree:\n\n$err"]
                exit 1
        }
+       set _gitworktree [pwd]
 }
 set _reponame [file split [file normalize $_gitdir]]
 if {[lindex $_reponame end] eq {.git}} {
@@ -1115,6 +1226,9 @@ if {[lindex $_reponame end] eq {.git}} {
        set _reponame [lindex $_reponame end]
 }
 
+set env(GIT_DIR) $_gitdir
+set env(GIT_WORK_TREE) $_gitworktree
+
 ######################################################################
 ##
 ## global init
@@ -1132,6 +1246,7 @@ set current_branch {}
 set is_detached 0
 set current_diff_path {}
 set is_3way_diff 0
+set is_submodule_diff 0
 set is_conflict_diff 0
 set selected_commit_type new
 set diff_empty_count 0
@@ -1610,6 +1725,9 @@ proc merge_state {path new_state {head_info {}} {index_info {}}} {
        } elseif {$s0 ne {_} && [string index $state 0] eq {_}
                && $head_info eq {}} {
                set head_info $index_info
+       } elseif {$s0 eq {_} && [string index $state 0] ne {_}} {
+               set index_info $head_info
+               set head_info {}
        }
 
        set file_states($path) [list $s0$s1 $icon \
@@ -1698,10 +1816,12 @@ proc display_all_files_helper {w path icon_name m} {
        $w insert end "[escape_path $path]\n"
 }
 
+set files_warning 0
 proc display_all_files {} {
        global ui_index ui_workdir
        global file_states file_lists
        global last_clicked
+       global files_warning
 
        $ui_index conf -state normal
        $ui_workdir conf -state normal
@@ -1713,7 +1833,18 @@ proc display_all_files {} {
        set file_lists($ui_index) [list]
        set file_lists($ui_workdir) [list]
 
-       foreach path [lsort [array names file_states]] {
+       set to_display [lsort [array names file_states]]
+       set display_limit [get_config gui.maxfilesdisplayed]
+       if {[llength $to_display] > $display_limit} {
+               if {!$files_warning} {
+                       # do not repeatedly warn:
+                       set files_warning 1
+                       info_popup [mc "Displaying only %s of %s files." \
+                               $display_limit [llength $to_display]]
+               }
+               set to_display [lrange $to_display 0 [expr {$display_limit-1}]]
+       }
+       foreach path $to_display {
                set s $file_states($path)
                set m [lindex $s 0]
                set icon_name [lindex $s 1]
@@ -1779,15 +1910,6 @@ static unsigned char file_fulltick_bits[] = {
    0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 } -maskdata $filemask
 
-image create bitmap file_parttick -background white -foreground "#005050" -data {
-#define parttick_width 14
-#define parttick_height 15
-static unsigned char parttick_bits[] = {
-   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
-   0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
-   0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
-} -maskdata $filemask
-
 image create bitmap file_question -background white -foreground black -data {
 #define file_question_width 14
 #define file_question_height 15
@@ -1828,7 +1950,7 @@ set ui_index .vpane.files.index.list
 set ui_workdir .vpane.files.workdir.list
 
 set all_icons(_$ui_index)   file_plain
-set all_icons(A$ui_index)   file_fulltick
+set all_icons(A$ui_index)   file_plain
 set all_icons(M$ui_index)   file_fulltick
 set all_icons(D$ui_index)   file_removed
 set all_icons(U$ui_index)   file_merge
@@ -1904,7 +2026,10 @@ proc incr_font_size {font {amt 1}} {
 
 set starting_gitk_msg [mc "Starting gitk... please wait..."]
 
-proc do_gitk {revs} {
+proc do_gitk {revs {is_submodule false}} {
+       global current_diff_path file_states current_diff_side ui_index
+       global _gitdir _gitworktree
+
        # -- Always start gitk through whatever we were loaded with.  This
        #    lets us bypass using shell process on Windows systems.
        #
@@ -1915,23 +2040,78 @@ proc do_gitk {revs} {
        } else {
                global env
 
-               if {[info exists env(GIT_DIR)]} {
-                       set old_GIT_DIR $env(GIT_DIR)
+               set pwd [pwd]
+
+               if {!$is_submodule} {
+                       if {![is_bare]} {
+                               cd $_gitworktree
+                       }
                } else {
-                       set old_GIT_DIR {}
+                       cd $current_diff_path
+                       if {$revs eq {--}} {
+                               set s $file_states($current_diff_path)
+                               set old_sha1 {}
+                               set new_sha1 {}
+                               switch -glob -- [lindex $s 0] {
+                               M_ { set old_sha1 [lindex [lindex $s 2] 1] }
+                               _M { set old_sha1 [lindex [lindex $s 3] 1] }
+                               MM {
+                                       if {$current_diff_side eq $ui_index} {
+                                               set old_sha1 [lindex [lindex $s 2] 1]
+                                               set new_sha1 [lindex [lindex $s 3] 1]
+                                       } else {
+                                               set old_sha1 [lindex [lindex $s 3] 1]
+                                       }
+                               }
+                               }
+                               set revs $old_sha1...$new_sha1
+                       }
+                       # GIT_DIR and GIT_WORK_TREE for the submodule are not the ones
+                       # we've been using for the main repository, so unset them.
+                       # TODO we could make life easier (start up faster?) for gitk
+                       # by setting these to the appropriate values to allow gitk
+                       # to skip the heuristics to find their proper value
+                       unset env(GIT_DIR)
+                       unset env(GIT_WORK_TREE)
                }
+               eval exec $cmd $revs "--" "--" &
+
+               set env(GIT_DIR) $_gitdir
+               set env(GIT_WORK_TREE) $_gitworktree
+               cd $pwd
+
+               ui_status $::starting_gitk_msg
+               after 10000 {
+                       ui_ready $starting_gitk_msg
+               }
+       }
+}
+
+proc do_git_gui {} {
+       global current_diff_path
+
+       # -- Always start git gui through whatever we were loaded with.  This
+       #    lets us bypass using shell process on Windows systems.
+       #
+       set exe [list [_which git]]
+       if {$exe eq {}} {
+               error_popup [mc "Couldn't find git gui in PATH"]
+       } else {
+               global env
+               global _gitdir _gitworktree
+
+               # see note in do_gitk about unsetting these vars when
+               # running tools in a submodule
+               unset env(GIT_DIR)
+               unset env(GIT_WORK_TREE)
 
                set pwd [pwd]
-               cd [file dirname [gitdir]]
-               set env(GIT_DIR) [file tail [gitdir]]
+               cd $current_diff_path
 
-               eval exec $cmd $revs &
+               eval exec $exe gui &
 
-               if {$old_GIT_DIR eq {}} {
-                       unset env(GIT_DIR)
-               } else {
-                       set env(GIT_DIR) $old_GIT_DIR
-               }
+               set env(GIT_DIR) $_gitdir
+               set env(GIT_WORK_TREE) $_gitworktree
                cd $pwd
 
                ui_status $::starting_gitk_msg
@@ -1942,6 +2122,7 @@ proc do_gitk {revs} {
 }
 
 proc do_explore {} {
+       global _gitworktree
        set explorer {}
        if {[is_Cygwin] || [is_Windows]} {
                set explorer "explorer.exe"
@@ -1951,7 +2132,7 @@ proc do_explore {} {
                # freedesktop.org-conforming system is our best shot
                set explorer "xdg-open"
        }
-       eval exec $explorer [list [file nativename [file dirname [gitdir]]]] &
+       eval exec $explorer [list [file nativename $_gitworktree]] &
 }
 
 set is_quitting 0
@@ -1967,7 +2148,7 @@ proc do_quit {{rc {1}}} {
        global ui_comm is_quitting repo_config commit_type
        global GITGUI_BCK_exists GITGUI_BCK_i
        global ui_comm_spell
-       global ret_code
+       global ret_code use_ttk
 
        if {$is_quitting} return
        set is_quitting 1
@@ -2010,10 +2191,28 @@ proc do_quit {{rc {1}}} {
 
                # -- Stash our current window geometry into this repository.
                #
+               set cfg_wmstate [wm state .]
+               if {[catch {set rc_wmstate $repo_config(gui.wmstate)}]} {
+                       set rc_wmstate {}
+               }
+               if {$cfg_wmstate ne $rc_wmstate} {
+                       catch {git config gui.wmstate $cfg_wmstate}
+               }
+               if {$cfg_wmstate eq {zoomed}} {
+                       # on Windows wm geometry will lie about window
+                       # position (but not size) when window is zoomed
+                       # restore the window before querying wm geometry
+                       wm state . normal
+               }
                set cfg_geometry [list]
                lappend cfg_geometry [wm geometry .]
-               lappend cfg_geometry [lindex [.vpane sash coord 0] 0]
-               lappend cfg_geometry [lindex [.vpane.files sash coord 0] 1]
+               if {$use_ttk} {
+                       lappend cfg_geometry [.vpane sashpos 0]
+                       lappend cfg_geometry [.vpane.files sashpos 0]
+               } else {
+                       lappend cfg_geometry [lindex [.vpane sash coord 0] 0]
+                       lappend cfg_geometry [lindex [.vpane.files sash coord 0] 1]
+               }
                if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
                        set rc_geometry {}
                }
@@ -2023,6 +2222,11 @@ proc do_quit {{rc {1}}} {
        }
 
        set ret_code $rc
+
+       # Briefly enable send again, working around Tk bug
+       # http://sourceforge.net/tracker/?func=detail&atid=112997&aid=1821174&group_id=12997
+       tk appname [appname]
+
        destroy .
 }
 
@@ -2297,8 +2501,6 @@ proc show_less_context {} {
 ##
 ## ui construction
 
-load_config 0
-apply_config
 set ui_comm {}
 
 # -- Menu Bar
@@ -2330,10 +2532,12 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
 #
 menu .mbar.repository
 
-.mbar.repository add command \
-       -label [mc "Explore Working Copy"] \
-       -command {do_explore}
-.mbar.repository add separator
+if {![is_bare]} {
+       .mbar.repository add command \
+               -label [mc "Explore Working Copy"] \
+               -command {do_explore}
+       .mbar.repository add separator
+}
 
 .mbar.repository add command \
        -label [mc "Browse Current Branch's Files"] \
@@ -2509,12 +2713,14 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
                [list .mbar.commit entryconf [.mbar.commit index last] -state]
 
        .mbar.commit add command -label [mc "Unstage From Commit"] \
-               -command do_unstage_selection
+               -command do_unstage_selection \
+               -accelerator $M1T-U
        lappend disable_on_lock \
                [list .mbar.commit entryconf [.mbar.commit index last] -state]
 
        .mbar.commit add command -label [mc "Revert Changes"] \
-               -command do_revert_selection
+               -command do_revert_selection \
+               -accelerator $M1T-J
        lappend disable_on_lock \
                [list .mbar.commit entryconf [.mbar.commit index last] -state]
 
@@ -2652,7 +2858,13 @@ bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
 
 set subcommand_args {}
 proc usage {} {
-       puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
+       set s "usage: $::argv0 $::subcommand $::subcommand_args"
+       if {[tk windowingsystem] eq "win32"} {
+               wm withdraw .
+               tk_messageBox -icon info -title "Usage" -message $s
+       } else {
+               puts stderr $s
+       }
        exit 1
 }
 
@@ -2729,6 +2941,7 @@ blame {
                set current_branch $head
        }
 
+       wm deiconify .
        switch -- $subcommand {
        browser {
                if {$jump_spec ne {}} usage
@@ -2744,7 +2957,12 @@ blame {
        }
        blame   {
                if {$head eq {} && ![file exists $path]} {
-                       puts stderr [mc "fatal: cannot stat path %s: No such file or directory" $path]
+                       catch {wm withdraw .}
+                       tk_messageBox \
+                               -icon error \
+                               -type ok \
+                               -title [mc "git-gui: fatal error"] \
+                               -message [mc "fatal: cannot stat path %s: No such file or directory" $path]
                        exit 1
                }
                blame::new $head $path $jump_spec
@@ -2773,14 +2991,13 @@ default {
 
 # -- Branch Control
 #
-frame .branch \
-       -borderwidth 1 \
-       -relief sunken
-label .branch.l1 \
+${NS}::frame .branch
+if {!$use_ttk} {.branch configure -borderwidth 1 -relief sunken}
+${NS}::label .branch.l1 \
        -text [mc "Current Branch:"] \
        -anchor w \
        -justify left
-label .branch.cb \
+${NS}::label .branch.cb \
        -textvariable current_branch \
        -anchor w \
        -justify left
@@ -2790,15 +3007,20 @@ pack .branch -side top -fill x
 
 # -- Main Window Layout
 #
-panedwindow .vpane -orient horizontal
-panedwindow .vpane.files -orient vertical
-.vpane add .vpane.files -sticky nsew -height 100 -width 200
+${NS}::panedwindow .vpane -orient horizontal
+${NS}::panedwindow .vpane.files -orient vertical
+if {$use_ttk} {
+       .vpane add .vpane.files
+} else {
+       .vpane add .vpane.files -sticky nsew -height 100 -width 200
+}
 pack .vpane -anchor n -side top -fill both -expand 1
 
 # -- Index File List
 #
-frame .vpane.files.index -height 100 -width 200
-label .vpane.files.index.title -text [mc "Staged Changes (Will Commit)"] \
+${NS}::frame .vpane.files.index -height 100 -width 200
+tlabel .vpane.files.index.title \
+       -text [mc "Staged Changes (Will Commit)"] \
        -background lightgreen -foreground black
 text $ui_index -background white -foreground black \
        -borderwidth 0 \
@@ -2808,8 +3030,8 @@ text $ui_index -background white -foreground black \
        -xscrollcommand {.vpane.files.index.sx set} \
        -yscrollcommand {.vpane.files.index.sy set} \
        -state disabled
-scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
-scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
+${NS}::scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
+${NS}::scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
 pack .vpane.files.index.title -side top -fill x
 pack .vpane.files.index.sx -side bottom -fill x
 pack .vpane.files.index.sy -side right -fill y
@@ -2817,8 +3039,8 @@ pack $ui_index -side left -fill both -expand 1
 
 # -- Working Directory File List
 #
-frame .vpane.files.workdir -height 100 -width 200
-label .vpane.files.workdir.title -text [mc "Unstaged Changes"] \
+${NS}::frame .vpane.files.workdir -height 100 -width 200
+tlabel .vpane.files.workdir.title -text [mc "Unstaged Changes"] \
        -background lightsalmon -foreground black
 text $ui_workdir -background white -foreground black \
        -borderwidth 0 \
@@ -2828,15 +3050,19 @@ text $ui_workdir -background white -foreground black \
        -xscrollcommand {.vpane.files.workdir.sx set} \
        -yscrollcommand {.vpane.files.workdir.sy set} \
        -state disabled
-scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
-scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
+${NS}::scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
+${NS}::scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
 pack .vpane.files.workdir.title -side top -fill x
 pack .vpane.files.workdir.sx -side bottom -fill x
 pack .vpane.files.workdir.sy -side right -fill y
 pack $ui_workdir -side left -fill both -expand 1
 
-.vpane.files add .vpane.files.workdir -sticky nsew
-.vpane.files add .vpane.files.index -sticky nsew
+.vpane.files add .vpane.files.workdir
+.vpane.files add .vpane.files.index
+if {!$use_ttk} {
+       .vpane.files paneconfigure .vpane.files.workdir -sticky news
+       .vpane.files paneconfigure .vpane.files.index -sticky news
+}
 
 foreach i [list $ui_index $ui_workdir] {
        rmsel_tag $i
@@ -2846,68 +3072,69 @@ unset i
 
 # -- Diff and Commit Area
 #
-frame .vpane.lower -height 300 -width 400
-frame .vpane.lower.commarea
-frame .vpane.lower.diff -relief sunken -borderwidth 1
+${NS}::frame .vpane.lower -height 300 -width 400
+${NS}::frame .vpane.lower.commarea
+${NS}::frame .vpane.lower.diff -relief sunken -borderwidth 1
 pack .vpane.lower.diff -fill both -expand 1
 pack .vpane.lower.commarea -side bottom -fill x
-.vpane add .vpane.lower -sticky nsew
+.vpane add .vpane.lower
+if {!$use_ttk} {.vpane paneconfigure .vpane.lower -sticky nsew}
 
 # -- Commit Area Buttons
 #
-frame .vpane.lower.commarea.buttons
-label .vpane.lower.commarea.buttons.l -text {} \
+${NS}::frame .vpane.lower.commarea.buttons
+${NS}::label .vpane.lower.commarea.buttons.l -text {} \
        -anchor w \
        -justify left
 pack .vpane.lower.commarea.buttons.l -side top -fill x
 pack .vpane.lower.commarea.buttons -side left -fill y
 
-button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \
+${NS}::button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \
        -command ui_do_rescan
 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
 lappend disable_on_lock \
        {.vpane.lower.commarea.buttons.rescan conf -state}
 
-button .vpane.lower.commarea.buttons.incall -text [mc "Stage Changed"] \
+${NS}::button .vpane.lower.commarea.buttons.incall -text [mc "Stage Changed"] \
        -command do_add_all
 pack .vpane.lower.commarea.buttons.incall -side top -fill x
 lappend disable_on_lock \
        {.vpane.lower.commarea.buttons.incall conf -state}
 
 if {![is_enabled nocommitmsg]} {
-       button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \
+       ${NS}::button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \
                -command do_signoff
        pack .vpane.lower.commarea.buttons.signoff -side top -fill x
 }
 
-button .vpane.lower.commarea.buttons.commit -text [commit_btn_caption] \
+${NS}::button .vpane.lower.commarea.buttons.commit -text [commit_btn_caption] \
        -command do_commit
 pack .vpane.lower.commarea.buttons.commit -side top -fill x
 lappend disable_on_lock \
        {.vpane.lower.commarea.buttons.commit conf -state}
 
 if {![is_enabled nocommit]} {
-       button .vpane.lower.commarea.buttons.push -text [mc Push] \
+       ${NS}::button .vpane.lower.commarea.buttons.push -text [mc Push] \
                -command do_push_anywhere
        pack .vpane.lower.commarea.buttons.push -side top -fill x
 }
 
 # -- Commit Message Buffer
 #
-frame .vpane.lower.commarea.buffer
-frame .vpane.lower.commarea.buffer.header
+${NS}::frame .vpane.lower.commarea.buffer
+${NS}::frame .vpane.lower.commarea.buffer.header
 set ui_comm .vpane.lower.commarea.buffer.t
 set ui_coml .vpane.lower.commarea.buffer.header.l
 
 if {![is_enabled nocommit]} {
-       radiobutton .vpane.lower.commarea.buffer.header.new \
+       ${NS}::radiobutton .vpane.lower.commarea.buffer.header.new \
                -text [mc "New Commit"] \
                -command do_select_commit_type \
                -variable selected_commit_type \
                -value new
        lappend disable_on_lock \
                [list .vpane.lower.commarea.buffer.header.new conf -state]
-       radiobutton .vpane.lower.commarea.buffer.header.amend \
+       ${NS}::radiobutton .vpane.lower.commarea.buffer.header.amend \
                -text [mc "Amend Last Commit"] \
                -command do_select_commit_type \
                -variable selected_commit_type \
@@ -2916,7 +3143,7 @@ if {![is_enabled nocommit]} {
                [list .vpane.lower.commarea.buffer.header.amend conf -state]
 }
 
-label $ui_coml \
+${NS}::label $ui_coml \
        -anchor w \
        -justify left
 proc trace_commit_type {varname args} {
@@ -2948,7 +3175,7 @@ text $ui_comm -background white -foreground black \
        -width $repo_config(gui.commitmsgwidth) -height 9 -wrap none \
        -font font_diff \
        -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
-scrollbar .vpane.lower.commarea.buffer.sby \
+${NS}::scrollbar .vpane.lower.commarea.buffer.sby \
        -command [list $ui_comm yview]
 pack .vpane.lower.commarea.buffer.header -side top -fill x
 pack .vpane.lower.commarea.buffer.sby -side right -fill y
@@ -3014,19 +3241,19 @@ proc trace_current_diff_path {varname args} {
 }
 trace add variable current_diff_path write trace_current_diff_path
 
-frame .vpane.lower.diff.header -background gold
-label .vpane.lower.diff.header.status \
+gold_frame .vpane.lower.diff.header
+tlabel .vpane.lower.diff.header.status \
        -background gold \
        -foreground black \
        -width $max_status_desc \
        -anchor w \
        -justify left
-label .vpane.lower.diff.header.file \
+tlabel .vpane.lower.diff.header.file \
        -background gold \
        -foreground black \
        -anchor w \
        -justify left
-label .vpane.lower.diff.header.path \
+tlabel .vpane.lower.diff.header.path \
        -background gold \
        -foreground black \
        -anchor w \
@@ -3050,18 +3277,18 @@ bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
 
 # -- Diff Body
 #
-frame .vpane.lower.diff.body
+${NS}::frame .vpane.lower.diff.body
 set ui_diff .vpane.lower.diff.body.t
 text $ui_diff -background white -foreground black \
        -borderwidth 0 \
-       -width 80 -height 15 -wrap none \
+       -width 80 -height 5 -wrap none \
        -font font_diff \
        -xscrollcommand {.vpane.lower.diff.body.sbx set} \
        -yscrollcommand {.vpane.lower.diff.body.sby set} \
        -state disabled
-scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
+${NS}::scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
        -command [list $ui_diff xview]
-scrollbar .vpane.lower.diff.body.sby -orient vertical \
+${NS}::scrollbar .vpane.lower.diff.body.sby -orient vertical \
        -command [list $ui_diff yview]
 pack .vpane.lower.diff.body.sbx -side bottom -fill x
 pack .vpane.lower.diff.body.sby -side right -fill y
@@ -3105,15 +3332,6 @@ $ui_diff tag raise sel
 #
 
 proc create_common_diff_popup {ctxm} {
-       $ctxm add command \
-               -label [mc "Show Less Context"] \
-               -command show_less_context
-       lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
-       $ctxm add command \
-               -label [mc "Show More Context"] \
-               -command show_more_context
-       lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
-       $ctxm add separator
        $ctxm add command \
                -label [mc Refresh] \
                -command reshow_diff
@@ -3165,10 +3383,19 @@ set ui_diff_applyhunk [$ctxm index last]
 lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
 $ctxm add command \
        -label [mc "Apply/Reverse Line"] \
-       -command {apply_line $cursorX $cursorY; do_rescan}
+       -command {apply_range_or_line $cursorX $cursorY; do_rescan}
 set ui_diff_applyline [$ctxm index last]
 lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state]
 $ctxm add separator
+$ctxm add command \
+       -label [mc "Show Less Context"] \
+       -command show_less_context
+lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+$ctxm add command \
+       -label [mc "Show More Context"] \
+       -command show_more_context
+lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+$ctxm add separator
 create_common_diff_popup $ctxm
 
 set ctxmmg .vpane.lower.diff.body.ctxmmg
@@ -3191,9 +3418,53 @@ $ctxmmg add command \
        -command {merge_resolve_one 1}
 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
 $ctxmmg add separator
+$ctxmmg add command \
+       -label [mc "Show Less Context"] \
+       -command show_less_context
+lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
+$ctxmmg add command \
+       -label [mc "Show More Context"] \
+       -command show_more_context
+lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
+$ctxmmg add separator
 create_common_diff_popup $ctxmmg
 
-proc popup_diff_menu {ctxm ctxmmg x y X Y} {
+set ctxmsm .vpane.lower.diff.body.ctxmsm
+menu $ctxmsm -tearoff 0
+$ctxmsm add command \
+       -label [mc "Visualize These Changes In The Submodule"] \
+       -command {do_gitk -- true}
+lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
+$ctxmsm add command \
+       -label [mc "Visualize Current Branch History In The Submodule"] \
+       -command {do_gitk {} true}
+lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
+$ctxmsm add command \
+       -label [mc "Visualize All Branch History In The Submodule"] \
+       -command {do_gitk --all true}
+lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
+$ctxmsm add separator
+$ctxmsm add command \
+       -label [mc "Start git gui In The Submodule"] \
+       -command {do_git_gui}
+lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
+$ctxmsm add separator
+create_common_diff_popup $ctxmsm
+
+proc has_textconv {path} {
+       if {[is_config_false gui.textconv]} {
+               return 0
+       }
+       set filter [gitattr $path diff set]
+       set textconv [get_config [join [list diff $filter textconv] .]]
+       if {$filter ne {set} && $textconv ne {}} {
+               return 1
+       } else {
+               return 0
+       }
+}
+
+proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} {
        global current_diff_path file_states
        set ::cursorX $x
        set ::cursorY $y
@@ -3204,20 +3475,32 @@ proc popup_diff_menu {ctxm ctxmmg x y X Y} {
        }
        if {[string first {U} $state] >= 0} {
                tk_popup $ctxmmg $X $Y
+       } elseif {$::is_submodule_diff} {
+               tk_popup $ctxmsm $X $Y
        } else {
+               set has_range [expr {[$::ui_diff tag nextrange sel 0.0] != {}}]
                if {$::ui_index eq $::current_diff_side} {
                        set l [mc "Unstage Hunk From Commit"]
-                       set t [mc "Unstage Line From Commit"]
+                       if {$has_range} {
+                               set t [mc "Unstage Lines From Commit"]
+                       } else {
+                               set t [mc "Unstage Line From Commit"]
+                       }
                } else {
                        set l [mc "Stage Hunk For Commit"]
-                       set t [mc "Stage Line For Commit"]
+                       if {$has_range} {
+                               set t [mc "Stage Lines For Commit"]
+                       } else {
+                               set t [mc "Stage Line For Commit"]
+                       }
                }
                if {$::is_3way_diff
                        || $current_diff_path eq {}
                        || {__} eq $state
                        || {_O} eq $state
                        || {_T} eq $state
-                       || {T_} eq $state} {
+                       || {T_} eq $state
+                       || [has_textconv $current_diff_path]} {
                        set s disabled
                } else {
                        set s normal
@@ -3227,7 +3510,7 @@ proc popup_diff_menu {ctxm ctxmmg x y X Y} {
                tk_popup $ctxm $X $Y
        }
 }
-bind_button3 $ui_diff [list popup_diff_menu $ctxm $ctxmmg %x %y %X %Y]
+bind_button3 $ui_diff [list popup_diff_menu $ctxm $ctxmmg $ctxmsm %x %y %X %Y]
 
 # -- Status Bar
 #
@@ -3237,16 +3520,44 @@ $main_status show [mc "Initializing..."]
 
 # -- Load geometry
 #
-catch {
-set gm $repo_config(gui.geometry)
-wm geometry . [lindex $gm 0]
-.vpane sash place 0 \
-       [lindex $gm 1] \
-       [lindex [.vpane sash coord 0] 1]
-.vpane.files sash place 0 \
-       [lindex [.vpane.files sash coord 0] 0] \
-       [lindex $gm 2]
-unset gm
+proc on_ttk_pane_mapped {w pane pos} {
+       bind $w <Map> {}
+       after 0 [list after idle [list $w sashpos $pane $pos]]
+}
+proc on_tk_pane_mapped {w pane x y} {
+       bind $w <Map> {}
+       after 0 [list after idle [list $w sash place $pane $x $y]]
+}
+proc on_application_mapped {} {
+       global repo_config use_ttk
+       bind . <Map> {}
+       set gm $repo_config(gui.geometry)
+       if {$use_ttk} {
+               bind .vpane <Map> \
+                   [list on_ttk_pane_mapped %W 0 [lindex $gm 1]]
+               bind .vpane.files <Map> \
+                   [list on_ttk_pane_mapped %W 0 [lindex $gm 2]]
+       } else {
+               bind .vpane <Map> \
+                   [list on_tk_pane_mapped %W 0 \
+                        [lindex $gm 1] \
+                        [lindex [.vpane sash coord 0] 1]]
+               bind .vpane.files <Map> \
+                   [list on_tk_pane_mapped %W 0 \
+                        [lindex [.vpane.files sash coord 0] 0] \
+                        [lindex $gm 2]]
+       }
+       wm geometry . [lindex $gm 0]
+}
+if {[info exists repo_config(gui.geometry)]} {
+       bind . <Map> [list on_application_mapped]
+       wm geometry . [lindex $repo_config(gui.geometry) 0]
+}
+
+# -- Load window state
+#
+if {[info exists repo_config(gui.wmstate)]} {
+       catch {wm state . $repo_config(gui.wmstate)}
 }
 
 # -- Key Bindings
@@ -3254,6 +3565,10 @@ unset gm
 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
 bind $ui_comm <$M1B-Key-t> {do_add_selection;break}
 bind $ui_comm <$M1B-Key-T> {do_add_selection;break}
+bind $ui_comm <$M1B-Key-u> {do_unstage_selection;break}
+bind $ui_comm <$M1B-Key-U> {do_unstage_selection;break}
+bind $ui_comm <$M1B-Key-j> {do_revert_selection;break}
+bind $ui_comm <$M1B-Key-J> {do_revert_selection;break}
 bind $ui_comm <$M1B-Key-i> {do_add_all;break}
 bind $ui_comm <$M1B-Key-I> {do_add_all;break}
 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
@@ -3310,6 +3625,8 @@ bind .   <$M1B-Key-s> do_signoff
 bind .   <$M1B-Key-S> do_signoff
 bind .   <$M1B-Key-t> do_add_selection
 bind .   <$M1B-Key-T> do_add_selection
+bind .   <$M1B-Key-j> do_revert_selection
+bind .   <$M1B-Key-J> do_revert_selection
 bind .   <$M1B-Key-i> do_add_all
 bind .   <$M1B-Key-I> do_add_all
 bind .   <$M1B-Key-minus> {show_less_context;break}
@@ -3328,7 +3645,7 @@ unset i
 set file_lists($ui_index) [list]
 set file_lists($ui_workdir) [list]
 
-wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]"
+wm title . "[appname] ([reponame]) [file normalize $_gitworktree]"
 focus -force $ui_comm
 
 # -- Warn the user about environmental problems.  Cygwin's Tcl
@@ -3501,3 +3818,9 @@ if {[is_enabled retcode]} {
 if {$picked && [is_config_true gui.autoexplore]} {
        do_explore
 }
+
+# Local variables:
+# mode: tcl
+# indent-tabs-mode: t
+# tab-width: 4
+# End:
index 241ab892cd5b731f07571acf7a0ca3150a763f4f..cfa50fca87827f09b2fc93509e0f249cc03ca6cd 100644 (file)
@@ -4,31 +4,26 @@
 proc do_about {} {
        global appvers copyright oguilib
        global tcl_patchLevel tk_patchLevel
-       global ui_comm_spell
+       global ui_comm_spell NS use_ttk
 
        set w .about_dialog
-       toplevel $w
+       Dialog $w
        wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
 
        pack [git_logo $w.git_logo] -side left -fill y -padx 10 -pady 10
-       label $w.header -text [mc "About %s" [appname]] \
-               -font font_uibold
+       ${NS}::label $w.header -text [mc "About %s" [appname]] \
+               -font font_uibold -anchor center
        pack $w.header -side top -fill x
 
-       frame $w.buttons
-       button $w.buttons.close -text {Close} \
+       ${NS}::frame $w.buttons
+       ${NS}::button $w.buttons.close -text {Close} \
                -default active \
                -command [list destroy $w]
        pack $w.buttons.close -side right
        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
 
-       label $w.desc \
-               -text "[mc "git-gui - a graphical user interface for Git."]\n$copyright" \
-               -padx 5 -pady 5 \
-               -justify left \
-               -anchor w \
-               -borderwidth 1 \
-               -relief solid
+       paddedlabel $w.desc \
+               -text "[mc "git-gui - a graphical user interface for Git."]\n$copyright"
        pack $w.desc -side top -fill x -padx 5 -pady 5
 
        set v {}
@@ -52,22 +47,10 @@ proc do_about {} {
        append d "git exec dir: [gitexec]\n"
        append d "git-gui lib: $oguilib"
 
-       label $w.vers \
-               -text $v \
-               -padx 5 -pady 5 \
-               -justify left \
-               -anchor w \
-               -borderwidth 1 \
-               -relief solid
+       paddedlabel $w.vers -text $v
        pack $w.vers -side top -fill x -padx 5 -pady 5
 
-       label $w.dirs \
-               -text $d \
-               -padx 5 -pady 5 \
-               -justify left \
-               -anchor w \
-               -borderwidth 1 \
-               -relief solid
+       paddedlabel $w.dirs -text $d
        pack $w.dirs -side top -fill x -padx 5 -pady 5
 
        menu $w.ctxm -tearoff 0
index 1f3b08f9efff873631d73e128ee97c1b9dad1822..61e358f960ca949cac5664c67442b82d0afca65f 100644 (file)
@@ -61,7 +61,7 @@ field tooltip_timer     {} ; # Current timer event for our tooltip
 field tooltip_commit    {} ; # Commit(s) in tooltip
 
 constructor new {i_commit i_path i_jump} {
-       global cursor_ptr M1B M1T have_tk85
+       global cursor_ptr M1B M1T have_tk85 use_ttk NS
        variable active_color
        variable group_colors
 
@@ -73,15 +73,15 @@ constructor new {i_commit i_path i_jump} {
 
        set font_w [font measure font_diff "0"]
 
-       frame $w.header -background gold
-       label $w.header.commit_l \
+       gold_frame $w.header
+       tlabel $w.header.commit_l \
                -text [mc "Commit:"] \
                -background gold \
                -foreground black \
                -anchor w \
                -justify left
        set w_back $w.header.commit_b
-       label $w_back \
+       tlabel $w_back \
                -image ::blame::img_back_arrow \
                -borderwidth 0 \
                -relief flat \
@@ -94,20 +94,20 @@ constructor new {i_commit i_path i_jump} {
                        [cb _history_menu]
                }
                "
-       label $w.header.commit \
+       tlabel $w.header.commit \
                -textvariable @commit \
                -background gold \
                -foreground black \
                -anchor w \
                -justify left
-       label $w.header.path_l \
+       tlabel $w.header.path_l \
                -text [mc "File:"] \
                -background gold \
                -foreground black \
                -anchor w \
                -justify left
        set w_path $w.header.path
-       label $w_path \
+       tlabel $w_path \
                -background gold \
                -foreground black \
                -anchor w \
@@ -209,10 +209,10 @@ constructor new {i_commit i_path i_jump} {
 
        set w_columns [list $w_amov $w_asim $w_line $w_file]
 
-       scrollbar $w.file_pane.out.sbx \
+       ${NS}::scrollbar $w.file_pane.out.sbx \
                -orient h \
                -command [list $w_file xview]
-       scrollbar $w.file_pane.out.sby \
+       ${NS}::scrollbar $w.file_pane.out.sby \
                -orient v \
                -command [list scrollbar2many $w_columns yview]
        eval grid $w_columns $w.file_pane.out.sby -sticky nsew
@@ -254,10 +254,10 @@ constructor new {i_commit i_path i_jump} {
                -background $active_color \
                -font font_ui
        $w_cviewer tag raise sel
-       scrollbar $w.file_pane.cm.sbx \
+       ${NS}::scrollbar $w.file_pane.cm.sbx \
                -orient h \
                -command [list $w_cviewer xview]
-       scrollbar $w.file_pane.cm.sby \
+       ${NS}::scrollbar $w.file_pane.cm.sby \
                -orient v \
                -command [list $w_cviewer yview]
        pack $w.file_pane.cm.sby -side right -fill y
@@ -449,11 +449,35 @@ method _load {jump} {
 
        $status show [mc "Reading %s..." "$commit:[escape_path $path]"]
        $w_path conf -text [escape_path $path]
+
+       set do_textconv 0
+       if {![is_config_false gui.textconv] && [git-version >= 1.7.2]} {
+               set filter [gitattr $path diff set]
+               set textconv [get_config [join [list diff $filter textconv] .]]
+               if {$filter ne {set} && $textconv ne {}} {
+                       set do_textconv 1
+               }
+       }
        if {$commit eq {}} {
-               set fd [open $path r]
+               if {$do_textconv ne 0} {
+                       # Run textconv with sh -c "..." to allow it to
+                       # contain command + arguments. On windows, just
+                       # call the filter command.
+                       if {![file executable [shellpath]]} {
+                               set fd [open |[linsert $textconv end $path] r]
+                       } else {
+                               set fd [open |[list [shellpath] -c "$textconv \"\$0\"" $path] r]
+                       }
+               } else {
+                       set fd [open $path r]
+               }
                fconfigure $fd -eofchar {}
        } else {
-               set fd [git_read cat-file blob "$commit:$path"]
+               if {$do_textconv ne 0} {
+                       set fd [git_read cat-file --textconv "$commit:$path"]
+               } else {
+                       set fd [git_read cat-file blob "$commit:$path"]
+               }
        }
        fconfigure $fd \
                -blocking 0 \
@@ -1245,6 +1269,18 @@ method _open_tooltip {cur_w} {
 
        $tooltip_t conf -state disabled
        _position_tooltip $this
+
+       # On MacOS raising a window causes it to acquire focus.
+       # Tk 8.5 on MacOS seems to properly support wm transient,
+       # so we can safely counter the effect there.
+       if {$::have_tk85 && [is_MacOSX]} {
+               update
+               if {$w eq {}} {
+                       raise .
+               } else {
+                       raise $w
+               }
+       }
 }
 
 method _position_tooltip {} {
@@ -1268,7 +1304,9 @@ method _position_tooltip {} {
        append g $pos_y
 
        wm geometry $tooltip_wm $g
-       raise $tooltip_wm
+       if {![is_MacOSX]} {
+               raise $tooltip_wm
+       }
 }
 
 method _hide_tooltip {} {
index 6603703ea163d830c7de1478aa2dd737c4d9d499..2e459a82976c6bf77cf1e9f2f1edeae8872a3364 100644 (file)
@@ -10,21 +10,24 @@ field opt_fetch     1; # refetch tracking branch if used?
 field opt_detach    0; # force a detached head case?
 
 constructor dialog {} {
-       make_toplevel top w
+       global use_ttk NS
+       make_dialog top w
+       wm withdraw $w
        wm title $top [append "[appname] ([reponame]): " [mc "Checkout Branch"]]
        if {$top ne {.}} {
                wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
        }
 
-       label $w.header -text [mc "Checkout Branch"] -font font_uibold
+       ${NS}::label $w.header -text [mc "Checkout Branch"] \
+               -font font_uibold -anchor center
        pack $w.header -side top -fill x
 
-       frame $w.buttons
-       button $w.buttons.create -text [mc Checkout] \
+       ${NS}::frame $w.buttons
+       ${NS}::button $w.buttons.create -text [mc Checkout] \
                -default active \
                -command [cb _checkout]
        pack $w.buttons.create -side right
-       button $w.buttons.cancel -text [mc Cancel] \
+       ${NS}::button $w.buttons.cancel -text [mc Cancel] \
                -command [list destroy $w]
        pack $w.buttons.cancel -side right -padx 5
        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
@@ -33,14 +36,14 @@ constructor dialog {} {
        $w_rev bind_listbox <Double-Button-1> [cb _checkout]
        pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5
 
-       labelframe $w.options -text [mc Options]
+       ${NS}::labelframe $w.options -text [mc Options]
 
-       checkbutton $w.options.fetch \
+       ${NS}::checkbutton $w.options.fetch \
                -text [mc "Fetch Tracking Branch"] \
                -variable @opt_fetch
        pack $w.options.fetch -anchor nw
 
-       checkbutton $w.options.detach \
+       ${NS}::checkbutton $w.options.detach \
                -text [mc "Detach From Local Branch"] \
                -variable @opt_detach
        pack $w.options.detach -anchor nw
@@ -50,6 +53,7 @@ constructor dialog {} {
        bind $w <Visibility> [cb _visible]
        bind $w <Key-Escape> [list destroy $w]
        bind $w <Key-Return> [cb _checkout]\;break
+       wm deiconify $w
        tkwait window $w
 }
 
index 3817771b944cc363205b86c91f7b4801c1d568f9..4bb907705c5f1ea5a33596a9bbbb2ef5ccf66b9a 100644 (file)
@@ -16,48 +16,48 @@ field opt_fetch     1; # refetch tracking branch if used?
 field reset_ok      0; # did the user agree to reset?
 
 constructor dialog {} {
-       global repo_config
+       global repo_config use_ttk NS
 
-       make_toplevel top w
+       make_dialog top w
+       wm withdraw $w
        wm title $top [append "[appname] ([reponame]): " [mc "Create Branch"]]
        if {$top ne {.}} {
                wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
        }
 
-       label $w.header -text [mc "Create New Branch"] -font font_uibold
+       ${NS}::label $w.header -text [mc "Create New Branch"] \
+               -font font_uibold -anchor center
        pack $w.header -side top -fill x
 
-       frame $w.buttons
-       button $w.buttons.create -text [mc Create] \
+       ${NS}::frame $w.buttons
+       ${NS}::button $w.buttons.create -text [mc Create] \
                -default active \
                -command [cb _create]
        pack $w.buttons.create -side right
-       button $w.buttons.cancel -text [mc Cancel] \
+       ${NS}::button $w.buttons.cancel -text [mc Cancel] \
                -command [list destroy $w]
        pack $w.buttons.cancel -side right -padx 5
        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
 
-       labelframe $w.desc -text [mc "Branch Name"]
-       radiobutton $w.desc.name_r \
-               -anchor w \
+       ${NS}::labelframe $w.desc -text [mc "Branch Name"]
+       ${NS}::radiobutton $w.desc.name_r \
                -text [mc "Name:"] \
                -value user \
                -variable @name_type
+       if {!$use_ttk} {$w.desc.name_r configure -anchor w}
        set w_name $w.desc.name_t
-       entry $w_name \
-               -borderwidth 1 \
-               -relief sunken \
+       ${NS}::entry $w_name \
                -width 40 \
                -textvariable @name \
                -validate key \
                -validatecommand [cb _validate %d %S]
        grid $w.desc.name_r $w_name -sticky we -padx {0 5}
 
-       radiobutton $w.desc.match_r \
-               -anchor w \
+       ${NS}::radiobutton $w.desc.match_r \
                -text [mc "Match Tracking Branch Name"] \
                -value match \
                -variable @name_type
+       if {!$use_ttk} {$w.desc.match_r configure -anchor w}
        grid $w.desc.match_r -sticky we -padx {0 5} -columnspan 2
 
        grid columnconfigure $w.desc 1 -weight 1
@@ -66,34 +66,34 @@ constructor dialog {} {
        set w_rev [::choose_rev::new $w.rev [mc "Starting Revision"]]
        pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5
 
-       labelframe $w.options -text [mc Options]
+       ${NS}::labelframe $w.options -text [mc Options]
 
-       frame $w.options.merge
-       label $w.options.merge.l -text [mc "Update Existing Branch:"]
+       ${NS}::frame $w.options.merge
+       ${NS}::label $w.options.merge.l -text [mc "Update Existing Branch:"]
        pack $w.options.merge.l -side left
-       radiobutton $w.options.merge.no \
+       ${NS}::radiobutton $w.options.merge.no \
                -text [mc No] \
                -value none \
                -variable @opt_merge
        pack $w.options.merge.no -side left
-       radiobutton $w.options.merge.ff \
+       ${NS}::radiobutton $w.options.merge.ff \
                -text [mc "Fast Forward Only"] \
                -value ff \
                -variable @opt_merge
        pack $w.options.merge.ff -side left
-       radiobutton $w.options.merge.reset \
+       ${NS}::radiobutton $w.options.merge.reset \
                -text [mc Reset] \
                -value reset \
                -variable @opt_merge
        pack $w.options.merge.reset -side left
        pack $w.options.merge -anchor nw
 
-       checkbutton $w.options.fetch \
+       ${NS}::checkbutton $w.options.fetch \
                -text [mc "Fetch Tracking Branch"] \
                -variable @opt_fetch
        pack $w.options.fetch -anchor nw
 
-       checkbutton $w.options.checkout \
+       ${NS}::checkbutton $w.options.checkout \
                -text [mc "Checkout After Creation"] \
                -variable @opt_checkout
        pack $w.options.checkout -anchor nw
@@ -109,6 +109,7 @@ constructor dialog {} {
        bind $w <Visibility> [cb _visible]
        bind $w <Key-Escape> [list destroy $w]
        bind $w <Key-Return> [cb _create]\;break
+       wm deiconify $w
        tkwait window $w
 }
 
index 20d5e42307c70a976e56f922f787610cde5bac9f..867938ec6a63ba77cc6efd700080e194d23d620f 100644 (file)
@@ -9,41 +9,40 @@ field w_check         ; # revision picker for merge test
 field w_delete        ; # delete button
 
 constructor dialog {} {
-       global current_branch
+       global current_branch use_ttk NS
 
-       make_toplevel top w
+       make_dialog top w
+       wm withdraw $w
        wm title $top [append "[appname] ([reponame]): " [mc "Delete Branch"]]
        if {$top ne {.}} {
                wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
        }
 
-       label $w.header -text [mc "Delete Local Branch"] -font font_uibold
+       ${NS}::label $w.header -text [mc "Delete Local Branch"] \
+               -font font_uibold -anchor center
        pack $w.header -side top -fill x
 
-       frame $w.buttons
+       ${NS}::frame $w.buttons
        set w_delete $w.buttons.delete
-       button $w_delete \
+       ${NS}::button $w_delete \
                -text [mc Delete] \
                -default active \
                -state disabled \
                -command [cb _delete]
        pack $w_delete -side right
-       button $w.buttons.cancel \
+       ${NS}::button $w.buttons.cancel \
                -text [mc Cancel] \
                -command [list destroy $w]
        pack $w.buttons.cancel -side right -padx 5
        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
 
-       labelframe $w.list -text [mc "Local Branches"]
+       ${NS}::labelframe $w.list -text [mc "Local Branches"]
        set w_heads $w.list.l
-       listbox $w_heads \
+       slistbox $w_heads \
                -height 10 \
                -width 70 \
                -selectmode extended \
-               -exportselection false \
-               -yscrollcommand [list $w.list.sby set]
-       scrollbar $w.list.sby -command [list $w.list.l yview]
-       pack $w.list.sby -side right -fill y
+               -exportselection false
        pack $w.list.l -side left -fill both -expand 1
        pack $w.list -fill both -expand 1 -pady 5 -padx 5
 
@@ -67,6 +66,7 @@ constructor dialog {} {
        "
        bind $w <Key-Escape> [list destroy $w]
        bind $w <Key-Return> [cb _delete]\;break
+       wm deiconify $w
        tkwait window $w
 }
 
index 166538808f461275075e2b03c56ddc15b5813e1a..63988773ba0d02af3dc91474b4a1669ecda27c5c 100644 (file)
@@ -8,9 +8,10 @@ field oldname
 field newname
 
 constructor dialog {} {
-       global current_branch
+       global current_branch use_ttk NS
 
-       make_toplevel top w
+       make_dialog top w
+       wm withdraw $w
        wm title $top [append "[appname] ([reponame]): " [mc "Rename Branch"]]
        if {$top ne {.}} {
                wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
@@ -19,27 +20,31 @@ constructor dialog {} {
        set oldname $current_branch
        set newname [get_config gui.newbranchtemplate]
 
-       label $w.header -text [mc "Rename Branch"] -font font_uibold
+       ${NS}::label $w.header -text [mc "Rename Branch"]\
+               -font font_uibold -anchor center
        pack $w.header -side top -fill x
 
-       frame $w.buttons
-       button $w.buttons.rename -text [mc Rename] \
+       ${NS}::frame $w.buttons
+       ${NS}::button $w.buttons.rename -text [mc Rename] \
                -default active \
                -command [cb _rename]
        pack $w.buttons.rename -side right
-       button $w.buttons.cancel -text [mc Cancel] \
+       ${NS}::button $w.buttons.cancel -text [mc Cancel] \
                -command [list destroy $w]
        pack $w.buttons.cancel -side right -padx 5
        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
 
-       frame $w.rename
-       label $w.rename.oldname_l -text [mc "Branch:"]
-       eval tk_optionMenu $w.rename.oldname_m @oldname [load_all_heads]
+       ${NS}::frame $w.rename
+       ${NS}::label $w.rename.oldname_l -text [mc "Branch:"]
+       if {$use_ttk} {
+               ttk::combobox $w.rename.oldname_m -textvariable @oldname \
+                       -values [load_all_heads] -state readonly
+       } else {
+               eval tk_optionMenu $w.rename.oldname_m @oldname [load_all_heads]
+       }
 
-       label $w.rename.newname_l -text [mc "New Name:"]
-       entry $w.rename.newname_t \
-               -borderwidth 1 \
-               -relief sunken \
+       ${NS}::label $w.rename.newname_l -text [mc "New Name:"]
+       ${NS}::entry $w.rename.newname_t \
                -width 40 \
                -textvariable @newname \
                -validate key \
@@ -60,6 +65,7 @@ constructor dialog {} {
                $w.rename.newname_t icursor end
                focus $w.rename.newname_t
        "
+       wm deiconify $w
        tkwait window $w
 }
 
index 0410cc68df186d6e8e0080058be94126243fd4d3..c2415729e036d87bc892b53901bd45d7e71b5ec2 100644 (file)
@@ -21,23 +21,23 @@ field browser_busy   1
 field ls_buf     {}; # Buffered record output from ls-tree
 
 constructor new {commit {path {}}} {
-       global cursor_ptr M1B
-       make_toplevel top w
+       global cursor_ptr M1B use_ttk NS
+       make_dialog top w
+       wm withdraw $top
        wm title $top [append "[appname] ([reponame]): " [mc "File Browser"]]
 
        set browser_commit $commit
        set browser_path $browser_commit:$path
 
-       label $w.path \
+       ${NS}::label $w.path \
                -textvariable @browser_path \
                -anchor w \
                -justify left \
-               -borderwidth 1 \
-               -relief sunken \
                -font font_uibold
+       if {!$use_ttk} { $w.path configure -borderwidth 1 -relief sunken}
        pack $w.path -anchor w -side top -fill x
 
-       frame $w.list
+       ${NS}::frame $w.list
        set w_list $w.list.l
        text $w_list -background white -foreground black \
                -borderwidth 0 \
@@ -49,19 +49,18 @@ constructor new {commit {path {}}} {
                -xscrollcommand [list $w.list.sbx set] \
                -yscrollcommand [list $w.list.sby set]
        rmsel_tag $w_list
-       scrollbar $w.list.sbx -orient h -command [list $w_list xview]
-       scrollbar $w.list.sby -orient v -command [list $w_list yview]
+       ${NS}::scrollbar $w.list.sbx -orient h -command [list $w_list xview]
+       ${NS}::scrollbar $w.list.sby -orient v -command [list $w_list yview]
        pack $w.list.sbx -side bottom -fill x
        pack $w.list.sby -side right -fill y
        pack $w_list -side left -fill both -expand 1
        pack $w.list -side top -fill both -expand 1
 
-       label $w.status \
+       ${NS}::label $w.status \
                -textvariable @browser_status \
                -anchor w \
-               -justify left \
-               -borderwidth 1 \
-               -relief sunken
+               -justify left
+       if {!$use_ttk} { $w.status configure -borderwidth 1 -relief sunken}
        pack $w.status -anchor w -side bottom -fill x
 
        bind $w_list <Button-1>        "[cb _click 0 @%x,%y];break"
@@ -78,6 +77,7 @@ constructor new {commit {path {}}} {
        bind $w_list <Right>           break
 
        bind $w_list <Visibility> [list focus $w_list]
+       wm deiconify $top
        set w $w_list
        if {$path ne {}} {
                _ls $this $browser_commit:$path $path
@@ -263,23 +263,27 @@ field w              ; # widget path
 field w_rev          ; # mega-widget to pick the initial revision
 
 constructor dialog {} {
-       make_toplevel top w
+       global use_ttk NS
+       make_dialog top w
+       wm withdraw $top
        wm title $top [append "[appname] ([reponame]): " [mc "Browse Branch Files"]]
        if {$top ne {.}} {
                wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
+               wm transient $top .
        }
 
-       label $w.header \
+       ${NS}::label $w.header \
                -text [mc "Browse Branch Files"] \
-               -font font_uibold
+               -font font_uibold \
+               -anchor center
        pack $w.header -side top -fill x
 
-       frame $w.buttons
-       button $w.buttons.browse -text [mc Browse] \
+       ${NS}::frame $w.buttons
+       ${NS}::button $w.buttons.browse -text [mc Browse] \
                -default active \
                -command [cb _open]
        pack $w.buttons.browse -side right
-       button $w.buttons.cancel -text [mc Cancel] \
+       ${NS}::button $w.buttons.cancel -text [mc Cancel] \
                -command [list destroy $w]
        pack $w.buttons.cancel -side right -padx 5
        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
@@ -291,6 +295,7 @@ constructor dialog {} {
        bind $w <Visibility> [cb _visible]
        bind $w <Key-Escape> [list destroy $w]
        bind $w <Key-Return> [cb _open]\;break
+       wm deiconify $top
        tkwait window $w
 }
 
index 56443b042c62bc10765aaf6484ad1077a843cb30..ebe50bd7d07e8a430096dc8f5813c781839bd0f6 100644 (file)
@@ -17,6 +17,7 @@ variable all_families [list]  ; # All fonts known to Tk
 
 constructor pick {path title a_family a_size} {
        variable all_families
+       global use_ttk NS
 
        set v_family $a_family
        set v_size $a_size
@@ -27,29 +28,30 @@ constructor pick {path title a_family a_size} {
        set f_family $pv_family
        set f_size $pv_size
 
-       make_toplevel top w
+       make_dialog top w
+       wm withdraw $top
        wm title $top "[appname] ([reponame]): $title"
        wm geometry $top "+[winfo rootx $path]+[winfo rooty $path]"
 
-       label $w.header -text $title -font font_uibold
+       ${NS}::label $w.header -text $title -font font_uibold -anchor center
        pack $w.header -side top -fill x
 
-       frame $w.buttons
-       button $w.buttons.select \
+       ${NS}::frame $w.buttons
+       ${NS}::button $w.buttons.select \
                -text [mc Select] \
                -default active \
                -command [cb _select]
-       button $w.buttons.cancel \
+       ${NS}::button $w.buttons.cancel \
                -text [mc Cancel] \
                -command [list destroy $w]
        pack $w.buttons.select -side right
        pack $w.buttons.cancel -side right -padx 5
        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
 
-       frame $w.inner
+       ${NS}::frame $w.inner
 
-       frame $w.inner.family
-       label $w.inner.family.l \
+       ${NS}::frame $w.inner.family
+       ${NS}::label $w.inner.family.l \
                -text [mc "Font Family"] \
                -anchor w
        set w_family $w.inner.family.v
@@ -64,16 +66,16 @@ constructor pick {path title a_family a_size} {
                -height 10 \
                -yscrollcommand [list $w.inner.family.sby set]
        rmsel_tag $w_family
-       scrollbar $w.inner.family.sby -command [list $w_family yview]
+       ${NS}::scrollbar $w.inner.family.sby -command [list $w_family yview]
        pack $w.inner.family.l -side top -fill x
        pack $w.inner.family.sby -side right -fill y
        pack $w_family -fill both -expand 1
 
-       frame $w.inner.size
-       label $w.inner.size.l \
+       ${NS}::frame $w.inner.size
+       ${NS}::label $w.inner.size.l \
                -text [mc "Font Size"] \
                -anchor w
-       spinbox $w.inner.size.v \
+       tspinbox $w.inner.size.v \
                -textvariable @f_size \
                -from 2 -to 80 -increment 1 \
                -width 3
@@ -86,8 +88,8 @@ constructor pick {path title a_family a_size} {
        grid columnconfigure $w.inner 0 -weight 1
        pack $w.inner -fill both -expand 1 -padx 5 -pady 5
 
-       frame $w.example
-       label $w.example.l \
+       ${NS}::frame $w.example
+       ${NS}::label $w.example.l \
                -text [mc "Font Example"] \
                -anchor w
        set w_example $w.example.t
@@ -129,6 +131,7 @@ constructor pick {path title a_family a_size} {
                grab $w
                focus $w
        "
+       wm deiconify $w
        tkwait window $w
 }
 
index 633cc572bbd076ff957c0b8194b6645e899e53ad..fae119286d3d4511d1b362fb05a8fab775e80a40 100644 (file)
@@ -22,9 +22,9 @@ field readtree_err        ; # Error output from read-tree (if any)
 field sorted_recent       ; # recent repositories (sorted)
 
 constructor pick {} {
-       global M1T M1B
+       global M1T M1B use_ttk NS
 
-       make_toplevel top w
+       make_dialog top w
        wm title $top [mc "Git Gui"]
 
        if {$top eq {.}} {
@@ -71,11 +71,11 @@ constructor pick {} {
 
        set w_body $w.body
        set opts $w_body.options
-       frame $w_body
+       ${NS}::frame $w_body
        text $opts \
                -cursor $::cursor_ptr \
                -relief flat \
-               -background [$w_body cget -background] \
+               -background [get_bg_color $w_body] \
                -wrap none \
                -spacing1 5 \
                -width 50 \
@@ -100,12 +100,17 @@ constructor pick {} {
        $opts insert end [mc "Clone Existing Repository"] link_clone
        $opts insert end "\n"
        if {$m_repo ne {}} {
+               if {[tk windowingsystem] eq "win32"} {
+                       set key L
+               } else {
+                       set key C
+               }
                $m_repo add command \
                        -command [cb _next clone] \
-                       -accelerator $M1T-C \
+                       -accelerator $M1T-$key \
                        -label [mc "Clone..."]
-               bind $top <$M1B-c> [cb _next clone]
-               bind $top <$M1B-C> [cb _next clone]
+               bind $top <$M1B-[string tolower $key]> [cb _next clone]
+               bind $top <$M1B-[string toupper $key]> [cb _next clone]
        }
 
        $opts tag conf link_open -foreground blue -underline 1
@@ -132,15 +137,15 @@ constructor pick {} {
                                -label [mc "Recent Repositories"]
                }
 
-               label $w_body.space
-               label $w_body.recentlabel \
+               ${NS}::label $w_body.space
+               ${NS}::label $w_body.recentlabel \
                        -anchor w \
                        -text [mc "Open Recent Repository:"]
                set w_recentlist $w_body.recentlist
                text $w_recentlist \
                        -cursor $::cursor_ptr \
                        -relief flat \
-                       -background [$w_body.recentlabel cget -background] \
+                       -background [get_bg_color $w_body.recentlabel] \
                        -wrap none \
                        -width 50 \
                        -height 10
@@ -176,10 +181,10 @@ constructor pick {} {
        }
        pack $w_body -fill x -padx 10 -pady 10
 
-       frame $w.buttons
+       ${NS}::frame $w.buttons
        set w_next $w.buttons.next
        set w_quit $w.buttons.quit
-       button $w_quit \
+       ${NS}::button $w_quit \
                -text [mc "Quit"] \
                -command exit
        pack $w_quit -side right -padx 5
@@ -203,6 +208,7 @@ constructor pick {} {
        wm deiconify $top
        tkwait variable @done
 
+       grab release $top
        if {$top eq {.}} {
                eval destroy [winfo children $top]
        }
@@ -235,6 +241,8 @@ proc _get_recentrepos {} {
        foreach p [get_config gui.recentrepo] {
                if {[_is_git [file join $p .git]]} {
                        lappend recent $p
+               } else {
+                       _unset_recentrepo $p
                }
        }
        return [lsort $recent]
@@ -243,6 +251,7 @@ proc _get_recentrepos {} {
 proc _unset_recentrepo {p} {
        regsub -all -- {([()\[\]{}\.^$+*?\\])} $p {\\\1} p
        git config --global --unset gui.recentrepo "^$p\$"
+       load_config 1
 }
 
 proc _append_recentrepos {path} {
@@ -261,6 +270,7 @@ proc _append_recentrepos {path} {
 
        lappend recent $path
        git config --global --add gui.recentrepo $path
+       load_config 1
 
        while {[llength $recent] > 10} {
                _unset_recentrepo [lindex $recent 0]
@@ -280,9 +290,10 @@ method _open_recent_path {p} {
 }
 
 method _next {action} {
+       global NS
        destroy $w_body
        if {![winfo exists $w_next]} {
-               button $w_next -default active
+               ${NS}::button $w_next -default active
                pack $w_next -side right -padx 5 -before $w_quit
        }
        _do_$action $this
@@ -371,26 +382,25 @@ proc _objdir {path} {
 ## Create New Repository
 
 method _do_new {} {
+       global use_ttk NS
        $w_next conf \
                -state disabled \
                -command [cb _do_new2] \
                -text [mc "Create"]
 
-       frame $w_body
-       label $w_body.h \
-               -font font_uibold \
+       ${NS}::frame $w_body
+       ${NS}::label $w_body.h \
+               -font font_uibold -anchor center \
                -text [mc "Create New Repository"]
        pack $w_body.h -side top -fill x -pady 10
        pack $w_body -fill x -padx 10
 
-       frame $w_body.where
-       label $w_body.where.l -text [mc "Directory:"]
-       entry $w_body.where.t \
+       ${NS}::frame $w_body.where
+       ${NS}::label $w_body.where.l -text [mc "Directory:"]
+       ${NS}::entry $w_body.where.t \
                -textvariable @local_path \
-               -borderwidth 1 \
-               -relief sunken \
                -width 50
-       button $w_body.where.b \
+       ${NS}::button $w_body.where.b \
                -text [mc "Browse"] \
                -command [cb _new_local_path]
        set w_localpath $w_body.where.t
@@ -456,63 +466,57 @@ proc _new_ok {p} {
 ## Clone Existing Repository
 
 method _do_clone {} {
+       global use_ttk NS
        $w_next conf \
                -state disabled \
                -command [cb _do_clone2] \
                -text [mc "Clone"]
 
-       frame $w_body
-       label $w_body.h \
-               -font font_uibold \
+       ${NS}::frame $w_body
+       ${NS}::label $w_body.h \
+               -font font_uibold -anchor center \
                -text [mc "Clone Existing Repository"]
        pack $w_body.h -side top -fill x -pady 10
        pack $w_body -fill x -padx 10
 
        set args $w_body.args
-       frame $w_body.args
+       ${NS}::frame $w_body.args
        pack $args -fill both
 
-       label $args.origin_l -text [mc "Source Location:"]
-       entry $args.origin_t \
+       ${NS}::label $args.origin_l -text [mc "Source Location:"]
+       ${NS}::entry $args.origin_t \
                -textvariable @origin_url \
-               -borderwidth 1 \
-               -relief sunken \
                -width 50
-       button $args.origin_b \
+       ${NS}::button $args.origin_b \
                -text [mc "Browse"] \
                -command [cb _open_origin]
        grid $args.origin_l $args.origin_t $args.origin_b -sticky ew
 
-       label $args.where_l -text [mc "Target Directory:"]
-       entry $args.where_t \
+       ${NS}::label $args.where_l -text [mc "Target Directory:"]
+       ${NS}::entry $args.where_t \
                -textvariable @local_path \
-               -borderwidth 1 \
-               -relief sunken \
                -width 50
-       button $args.where_b \
+       ${NS}::button $args.where_b \
                -text [mc "Browse"] \
                -command [cb _new_local_path]
        grid $args.where_l $args.where_t $args.where_b -sticky ew
        set w_localpath $args.where_t
 
-       label $args.type_l -text [mc "Clone Type:"]
-       frame $args.type_f
+       ${NS}::label $args.type_l -text [mc "Clone Type:"]
+       ${NS}::frame $args.type_f
        set w_types [list]
-       lappend w_types [radiobutton $args.type_f.hardlink \
+       lappend w_types [${NS}::radiobutton $args.type_f.hardlink \
                -state disabled \
-               -anchor w \
                -text [mc "Standard (Fast, Semi-Redundant, Hardlinks)"] \
                -variable @clone_type \
                -value hardlink]
-       lappend w_types [radiobutton $args.type_f.full \
+       lappend w_types [${NS}::radiobutton $args.type_f.full \
                -state disabled \
-               -anchor w \
                -text [mc "Full Copy (Slower, Redundant Backup)"] \
                -variable @clone_type \
                -value full]
-       lappend w_types [radiobutton $args.type_f.shared \
+       lappend w_types [${NS}::radiobutton $args.type_f.shared \
                -state disabled \
-               -anchor w \
                -text [mc "Shared (Fastest, Not Recommended, No Backup)"] \
                -variable @clone_type \
                -value shared]
@@ -1001,26 +1005,25 @@ method _postcheckout_wait {fd_ph} {
 ## Open Existing Repository
 
 method _do_open {} {
+       global NS
        $w_next conf \
                -state disabled \
                -command [cb _do_open2] \
                -text [mc "Open"]
 
-       frame $w_body
-       label $w_body.h \
-               -font font_uibold \
+       ${NS}::frame $w_body
+       ${NS}::label $w_body.h \
+               -font font_uibold -anchor center \
                -text [mc "Open Existing Repository"]
        pack $w_body.h -side top -fill x -pady 10
        pack $w_body -fill x -padx 10
 
-       frame $w_body.where
-       label $w_body.where.l -text [mc "Repository:"]
-       entry $w_body.where.t \
+       ${NS}::frame $w_body.where
+       ${NS}::label $w_body.where.l -text [mc "Repository:"]
+       ${NS}::entry $w_body.where.t \
                -textvariable @local_path \
-               -borderwidth 1 \
-               -relief sunken \
                -width 50
-       button $w_body.where.b \
+       ${NS}::button $w_body.where.b \
                -text [mc "Browse"] \
                -command [cb _open_local_path]
 
index c8821c146386f850c0794df70f605cd9f18dcff3..c12d5e1698a1fcdcfa39bb69b475633b2f6c34c9 100644 (file)
@@ -10,7 +10,7 @@ field w_list          ; # list of currently filtered specs
 field w_filter        ; # filter entry for $w_list
 
 field c_expr        {}; # current revision expression
-field filter          ; # current filter string
+field filter        ""; # current filter string
 field revtype     head; # type of revision chosen
 field cur_specs [list]; # list of specs for $revtype
 field spec_head       ; # list of all head specs
@@ -32,7 +32,7 @@ proc new_unmerged {path {title {}}} {
 }
 
 constructor _new {path unmerged_only title} {
-       global current_branch is_detached
+       global current_branch is_detached use_ttk NS
 
        if {![info exists ::all_remotes]} {
                load_all_remotes
@@ -41,65 +41,65 @@ constructor _new {path unmerged_only title} {
        set w $path
 
        if {$title ne {}} {
-               labelframe $w -text $title
+               ${NS}::labelframe $w -text $title
        } else {
-               frame $w
+               ${NS}::frame $w
        }
        bind $w <Destroy> [cb _delete %W]
 
        if {$is_detached} {
-               radiobutton $w.detachedhead_r \
-                       -anchor w \
+               ${NS}::radiobutton $w.detachedhead_r \
                        -text [mc "This Detached Checkout"] \
                        -value HEAD \
                        -variable @revtype
+               if {!$use_ttk} {$w.detachedhead_r configure -anchor w}
                grid $w.detachedhead_r -sticky we -padx {0 5} -columnspan 2
        }
 
-       radiobutton $w.expr_r \
+       ${NS}::radiobutton $w.expr_r \
                -text [mc "Revision Expression:"] \
                -value expr \
                -variable @revtype
-       entry $w.expr_t \
-               -borderwidth 1 \
-               -relief sunken \
+       ${NS}::entry $w.expr_t \
                -width 50 \
                -textvariable @c_expr \
                -validate key \
                -validatecommand [cb _validate %d %S]
        grid $w.expr_r $w.expr_t -sticky we -padx {0 5}
 
-       frame $w.types
-       radiobutton $w.types.head_r \
+       ${NS}::frame $w.types
+       ${NS}::radiobutton $w.types.head_r \
                -text [mc "Local Branch"] \
                -value head \
                -variable @revtype
        pack $w.types.head_r -side left
-       radiobutton $w.types.trck_r \
+       ${NS}::radiobutton $w.types.trck_r \
                -text [mc "Tracking Branch"] \
                -value trck \
                -variable @revtype
        pack $w.types.trck_r -side left
-       radiobutton $w.types.tag_r \
+       ${NS}::radiobutton $w.types.tag_r \
                -text [mc "Tag"] \
                -value tag \
                -variable @revtype
        pack $w.types.tag_r -side left
        set w_filter $w.types.filter
-       entry $w_filter \
-               -borderwidth 1 \
-               -relief sunken \
+       ${NS}::entry $w_filter \
                -width 12 \
                -textvariable @filter \
                -validate key \
                -validatecommand [cb _filter %P]
        pack $w_filter -side right
-       pack [label $w.types.filter_icon \
+       pack [${NS}::label $w.types.filter_icon \
                -image ::choose_rev::img_find \
                ] -side right
        grid $w.types -sticky we -padx {0 5} -columnspan 2
 
-       frame $w.list
+       if {$use_ttk} {
+               ttk::frame $w.list -style SListbox.TFrame -padding 2
+       } else {
+               frame $w.list
+       }
        set w_list $w.list.l
        listbox $w_list \
                -font font_diff \
@@ -109,6 +109,9 @@ constructor _new {path unmerged_only title} {
                -exportselection false \
                -xscrollcommand [cb _sb_set $w.list.sbx h] \
                -yscrollcommand [cb _sb_set $w.list.sby v]
+       if {$use_ttk} {
+               $w_list configure -relief flat -highlightthickness 0 -borderwidth 0
+       }
        pack $w_list -fill both -expand 1
        grid $w.list -sticky nswe -padx {20 5} -columnspan 2
        bind $w_list <Any-Motion>  [cb _show_tooltip @%x,%y]
@@ -235,11 +238,12 @@ constructor _new {path unmerged_only title} {
 }
 
 method none {text} {
+       global NS use_ttk
        if {![winfo exists $w.none_r]} {
-               radiobutton $w.none_r \
-                       -anchor w \
+               ${NS}::radiobutton $w.none_r \
                        -value none \
                        -variable @revtype
+               if {!$use_ttk} {$w.none_r configure -anchor w}
                grid $w.none_r -sticky we -padx {0 5} -columnspan 2
        }
        $w.none_r configure -text $text
@@ -425,6 +429,7 @@ method _delete {current} {
 }
 
 method _sb_set {sb orient first last} {
+       global NS
        set old_focus [focus -lastfor $w]
 
        if {$first == 0 && $last == 1} {
@@ -440,10 +445,10 @@ method _sb_set {sb orient first last} {
 
        if {![winfo exists $sb]} {
                if {$orient eq {h}} {
-                       scrollbar $sb -orient h -command [list $w_list xview]
+                       ${NS}::scrollbar $sb -orient h -command [list $w_list xview]
                        pack $sb -fill x -side bottom -before $w_list
                } else {
-                       scrollbar $sb -orient v -command [list $w_list yview]
+                       ${NS}::scrollbar $sb -orient v -command [list $w_list yview]
                        pack $sb -fill y -side right -before $w_list
                }
                if {$old_focus ne {}} {
index dc2141192a21e7416268cc94beda78d6ceb8f86f..c27b71476ac35dbad02b727f28aba5d7c0777e75 100644 (file)
@@ -134,6 +134,13 @@ proc delete_this {{t {}}} {
        if {[namespace exists $t]} {namespace delete $t}
 }
 
+proc make_dialog {t w args} {
+       upvar $t top $w pfx this this
+       global use_ttk
+       uplevel [linsert $args 0 make_toplevel $t $w]
+       pave_toplevel $pfx
+}
+
 proc make_toplevel {t w args} {
        upvar $t top $w pfx this this
 
index c112464ec367a2db707a3f28ff6c588aefe7985f..1f3248ffd11353200c64bdbde630b4147dc96c0a 100644 (file)
@@ -27,20 +27,20 @@ constructor embed {path title} {
 }
 
 method _init {} {
-       global M1B
+       global M1B use_ttk NS
 
        if {$is_toplevel} {
-               make_toplevel top w -autodelete 0
+               make_dialog top w -autodelete 0
                wm title $top "[appname] ([reponame]): $t_short"
        } else {
-               frame $w
+               ${NS}::frame $w
        }
 
        set console_cr 1.0
        set w_t $w.m.t
 
-       frame $w.m
-       label $w.m.l1 \
+       ${NS}::frame $w.m
+       ${NS}::label $w.m.l1 \
                -textvariable @t_long  \
                -anchor w \
                -justify left \
@@ -78,7 +78,7 @@ method _init {} {
                "
 
        if {$is_toplevel} {
-               button $w.ok -text [mc "Close"] \
+               ${NS}::button $w.ok -text [mc "Close"] \
                        -state disabled \
                        -command [list destroy $w]
                pack $w.ok -side bottom -anchor e -pady 10 -padx 10
@@ -206,13 +206,14 @@ method done {ok} {
 }
 
 method _sb_set {sb orient first last} {
+       global NS
        if {![winfo exists $sb]} {
                if {$first == $last || ($first == 0 && $last == 1)} return
                if {$orient eq {h}} {
-                       scrollbar $sb -orient h -command [list $w_t xview]
+                       ${NS}::scrollbar $sb -orient h -command [list $w_t xview]
                        pack $sb -fill x -side bottom -before $w_t
                } else {
-                       scrollbar $sb -orient v -command [list $w_t yview]
+                       ${NS}::scrollbar $sb -orient v -command [list $w_t yview]
                        pack $sb -fill y -side right -before $w_t
                }
        }
index a18ac8b4308d8263a0688058524282b72bafe77a..1f187ed286f877eab84ea2bc13ae210de9660e21 100644 (file)
@@ -2,6 +2,7 @@
 # Copyright (C) 2006, 2007 Shawn Pearce
 
 proc do_stats {} {
+       global use_ttk NS
        set fd [git_read count-objects -v]
        while {[gets $fd line] > 0} {
                if {[regexp {^([^:]+): (\d+)$} $line _ name value]} {
@@ -21,24 +22,22 @@ proc do_stats {} {
        }
 
        set w .stats_view
-       toplevel $w
+       Dialog $w
+       wm withdraw $w
        wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
 
-       label $w.header -text [mc "Database Statistics"]
-       pack $w.header -side top -fill x
-
-       frame $w.buttons -border 1
-       button $w.buttons.close -text [mc Close] \
+       ${NS}::frame $w.buttons
+       ${NS}::button $w.buttons.close -text [mc Close] \
                -default active \
                -command [list destroy $w]
-       button $w.buttons.gc -text [mc "Compress Database"] \
+       ${NS}::button $w.buttons.gc -text [mc "Compress Database"] \
                -default normal \
                -command "destroy $w;do_gc"
        pack $w.buttons.close -side right
        pack $w.buttons.gc -side left
        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
 
-       frame $w.stat -borderwidth 1 -relief solid
+       ${NS}::labelframe $w.stat -text [mc "Database Statistics"]
        foreach s {
                {count           {mc "Number of loose objects"}}
                {size            {mc "Disk space used by loose objects"} { KiB}}
@@ -55,8 +54,8 @@ proc do_stats {} {
                        set value "$value[lindex $s 2]"
                }
 
-               label $w.stat.l_$name -text "$label:" -anchor w
-               label $w.stat.v_$name -text $value -anchor w
+               ${NS}::label $w.stat.l_$name -text "$label:" -anchor w
+               ${NS}::label $w.stat.v_$name -text $value -anchor w
                grid $w.stat.l_$name $w.stat.v_$name -sticky we -padx {0 5}
        }
        pack $w.stat -pady 10 -padx 10
@@ -65,6 +64,7 @@ proc do_stats {} {
        bind $w <Key-Escape> [list destroy $w]
        bind $w <Key-Return> [list destroy $w]
        wm title $w [append "[appname] ([reponame]): " [mc "Database Statistics"]]
+       wm deiconify $w
        tkwait window $w
 }
 
@@ -89,27 +89,26 @@ proc do_fsck_objects {} {
 }
 
 proc hint_gc {} {
-       set object_limit 8
+       set ndirs 1
+       set limit 8
        if {[is_Windows]} {
-               set object_limit 1
+               set ndirs 4
+               set limit 1
        }
 
-       set objects_current [llength [glob \
-               -directory [gitdir objects 42] \
+       set count [llength [glob \
                -nocomplain \
-               -tails \
                -- \
-               *]]
+               [gitdir objects 4\[0-[expr {$ndirs-1}]\]/*]]]
 
-       if {$objects_current >= $object_limit} {
-               set objects_current [expr {$objects_current * 250}]
-               set object_limit    [expr {$object_limit    * 250}]
+       if {$count >= $limit * $ndirs} {
+               set objects_current [expr {$count * 256/$ndirs}]
                if {[ask_popup \
                        [mc "This repository currently has approximately %i loose objects.
 
-To maintain optimal performance it is strongly recommended that you compress the database when more than %i loose objects exist.
+To maintain optimal performance it is strongly recommended that you compress the database.
 
-Compress the database now?" $objects_current $object_limit]] eq yes} {
+Compress the database now?" $objects_current]] eq yes} {
                        do_gc
                }
        }
index 925b3f56c1c23cc5e0c2b270b9f7e160013c9009..c628750276303fca620c9292463c4c59690c764f 100644 (file)
@@ -55,7 +55,7 @@ proc handle_empty_diff {} {
 
        set path $current_diff_path
        set s $file_states($path)
-       if {[lindex $s 0] ne {_M}} return
+       if {[lindex $s 0] ne {_M} || [has_textconv $path]} return
 
        # Prevent infinite rescan loops
        incr diff_empty_count
@@ -255,7 +255,7 @@ proc show_other_diff {path w m cont_info} {
 
 proc start_show_diff {cont_info {add_opts {}}} {
        global file_states file_lists
-       global is_3way_diff diff_active repo_config
+       global is_3way_diff is_submodule_diff diff_active repo_config
        global ui_diff ui_index ui_workdir
        global current_diff_path current_diff_side current_diff_header
 
@@ -265,6 +265,7 @@ proc start_show_diff {cont_info {add_opts {}}} {
        set s $file_states($path)
        set m [lindex $s 0]
        set is_3way_diff 0
+       set is_submodule_diff 0
        set diff_active 1
        set current_diff_header {}
 
@@ -279,6 +280,18 @@ proc start_show_diff {cont_info {add_opts {}}} {
                        lappend cmd diff-files
                }
        }
+       if {![is_config_false gui.textconv] && [git-version >= 1.6.1]} {
+               lappend cmd --textconv
+       }
+
+       if {[string match {160000 *} [lindex $s 2]]
+        || [string match {160000 *} [lindex $s 3]]} {
+               set is_submodule_diff 1
+
+               if {[git-version >= "1.6.6"]} {
+                       lappend cmd --submodule
+               }
+       }
 
        lappend cmd -p
        lappend cmd --no-color
@@ -295,6 +308,14 @@ proc start_show_diff {cont_info {add_opts {}}} {
                lappend cmd $path
        }
 
+       if {$is_submodule_diff && [git-version < "1.6.6"]} {
+               if {$w eq $ui_index} {
+                       set cmd [list submodule summary --cached -- $path]
+               } else {
+                       set cmd [list submodule summary --files -- $path]
+               }
+       }
+
        if {[catch {set fd [eval git_read --nice $cmd]} err]} {
                set diff_active 0
                unlock_index
@@ -312,7 +333,7 @@ proc start_show_diff {cont_info {add_opts {}}} {
 }
 
 proc read_diff {fd cont_info} {
-       global ui_diff diff_active
+       global ui_diff diff_active is_submodule_diff
        global is_3way_diff is_conflict_diff current_diff_header
        global current_diff_queue
        global diff_empty_count
@@ -374,6 +395,25 @@ proc read_diff {fd cont_info} {
                                set tags {}
                        }
                        }
+               } elseif {$is_submodule_diff} {
+                       if {$line == ""} continue
+                       if {[regexp {^Submodule } $line]} {
+                               set tags d_@
+                       } elseif {[regexp {^\* } $line]} {
+                               set line [string replace $line 0 1 {Submodule }]
+                               set tags d_@
+                       } else {
+                               set op [string range $line 0 2]
+                               switch -- $op {
+                               {  <} {set tags d_-}
+                               {  >} {set tags d_+}
+                               {  W} {set tags {}}
+                               default {
+                                       puts "error: Unhandled submodule diff marker: {$op}"
+                                       set tags {}
+                               }
+                               }
+                       }
                } else {
                        set op [string index $line 0]
                        switch -- $op {
@@ -505,10 +545,23 @@ proc apply_hunk {x y} {
        }
 }
 
-proc apply_line {x y} {
+proc apply_range_or_line {x y} {
        global current_diff_path current_diff_header current_diff_side
        global ui_diff ui_index file_states
 
+       set selected [$ui_diff tag nextrange sel 0.0]
+
+       if {$selected == {}} {
+               set first [$ui_diff index "@$x,$y"]
+               set last $first
+       } else {
+               set first [lindex $selected 0]
+               set last [lindex $selected 1]
+       }
+
+       set first_l [$ui_diff index "$first linestart"]
+       set last_l [$ui_diff index "$last lineend"]
+
        if {$current_diff_path eq {} || $current_diff_header eq {}} return
        if {![lock_index apply_hunk]} return
 
@@ -531,119 +584,147 @@ proc apply_line {x y} {
                }
        }
 
-       set the_l [$ui_diff index @$x,$y]
-
-       # operate only on change lines
-       set c1 [$ui_diff get "$the_l linestart"]
-       if {$c1 ne {+} && $c1 ne {-}} {
-               unlock_index
-               return
-       }
-       set sign $c1
+       set wholepatch {}
 
-       set i_l [$ui_diff search -backwards -regexp ^@@ $the_l 0.0]
-       if {$i_l eq {}} {
-               unlock_index
-               return
-       }
-       # $i_l is now at the beginning of a line
+       while {$first_l < $last_l} {
+               set i_l [$ui_diff search -backwards -regexp ^@@ $first_l 0.0]
+               if {$i_l eq {}} {
+                       # If there's not a @@ above, then the selected range
+                       # must have come before the first_l @@
+                       set i_l [$ui_diff search -regexp ^@@ $first_l $last_l]
+               }
+               if {$i_l eq {}} {
+                       unlock_index
+                       return
+               }
+               # $i_l is now at the beginning of a line
 
-       # pick start line number from hunk header
-       set hh [$ui_diff get $i_l "$i_l + 1 lines"]
-       set hh [lindex [split $hh ,] 0]
-       set hln [lindex [split $hh -] 1]
+               # pick start line number from hunk header
+               set hh [$ui_diff get $i_l "$i_l + 1 lines"]
+               set hh [lindex [split $hh ,] 0]
+               set hln [lindex [split $hh -] 1]
 
-       # There is a special situation to take care of. Consider this hunk:
-       #
-       #    @@ -10,4 +10,4 @@
-       #     context before
-       #    -old 1
-       #    -old 2
-       #    +new 1
-       #    +new 2
-       #     context after
-       #
-       # We used to keep the context lines in the order they appear in the
-       # hunk. But then it is not possible to correctly stage only
-       # "-old 1" and "+new 1" - it would result in this staged text:
-       #
-       #    context before
-       #    old 2
-       #    new 1
-       #    context after
-       #
-       # (By symmetry it is not possible to *un*stage "old 2" and "new 2".)
-       #
-       # We resolve the problem by introducing an asymmetry, namely, when
-       # a "+" line is *staged*, it is moved in front of the context lines
-       # that are generated from the "-" lines that are immediately before
-       # the "+" block. That is, we construct this patch:
-       #
-       #    @@ -10,4 +10,5 @@
-       #     context before
-       #    +new 1
-       #     old 1
-       #     old 2
-       #     context after
-       #
-       # But we do *not* treat "-" lines that are *un*staged in a special
-       # way.
-       #
-       # With this asymmetry it is possible to stage the change
-       # "old 1" -> "new 1" directly, and to stage the change
-       # "old 2" -> "new 2" by first staging the entire hunk and
-       # then unstaging the change "old 1" -> "new 1".
-
-       # This is non-empty if and only if we are _staging_ changes;
-       # then it accumulates the consecutive "-" lines (after converting
-       # them to context lines) in order to be moved after the "+" change
-       # line.
-       set pre_context {}
-
-       set n 0
-       set i_l [$ui_diff index "$i_l + 1 lines"]
-       set patch {}
-       while {[$ui_diff compare $i_l < "end - 1 chars"] &&
-              [$ui_diff get $i_l "$i_l + 2 chars"] ne {@@}} {
-               set next_l [$ui_diff index "$i_l + 1 lines"]
-               set c1 [$ui_diff get $i_l]
-               if {[$ui_diff compare $i_l <= $the_l] &&
-                   [$ui_diff compare $the_l < $next_l]} {
-                       # the line to stage/unstage
-                       set ln [$ui_diff get $i_l $next_l]
-                       if {$c1 eq {-}} {
-                               set n [expr $n+1]
+               # There is a special situation to take care of. Consider this
+               # hunk:
+               #
+               #    @@ -10,4 +10,4 @@
+               #     context before
+               #    -old 1
+               #    -old 2
+               #    +new 1
+               #    +new 2
+               #     context after
+               #
+               # We used to keep the context lines in the order they appear in
+               # the hunk. But then it is not possible to correctly stage only
+               # "-old 1" and "+new 1" - it would result in this staged text:
+               #
+               #    context before
+               #    old 2
+               #    new 1
+               #    context after
+               #
+               # (By symmetry it is not possible to *un*stage "old 2" and "new
+               # 2".)
+               #
+               # We resolve the problem by introducing an asymmetry, namely,
+               # when a "+" line is *staged*, it is moved in front of the
+               # context lines that are generated from the "-" lines that are
+               # immediately before the "+" block. That is, we construct this
+               # patch:
+               #
+               #    @@ -10,4 +10,5 @@
+               #     context before
+               #    +new 1
+               #     old 1
+               #     old 2
+               #     context after
+               #
+               # But we do *not* treat "-" lines that are *un*staged in a
+               # special way.
+               #
+               # With this asymmetry it is possible to stage the change "old
+               # 1" -> "new 1" directly, and to stage the change "old 2" ->
+               # "new 2" by first staging the entire hunk and then unstaging
+               # the change "old 1" -> "new 1".
+               #
+               # Applying multiple lines adds complexity to the special
+               # situation.  The pre_context must be moved after the entire
+               # first block of consecutive staged "+" lines, so that
+               # staging both additions gives the following patch:
+               #
+               #    @@ -10,4 +10,6 @@
+               #     context before
+               #    +new 1
+               #    +new 2
+               #     old 1
+               #     old 2
+               #     context after
+
+               # This is non-empty if and only if we are _staging_ changes;
+               # then it accumulates the consecutive "-" lines (after
+               # converting them to context lines) in order to be moved after
+               # "+" change lines.
+               set pre_context {}
+
+               set n 0
+               set m 0
+               set i_l [$ui_diff index "$i_l + 1 lines"]
+               set patch {}
+               while {[$ui_diff compare $i_l < "end - 1 chars"] &&
+                      [$ui_diff get $i_l "$i_l + 2 chars"] ne {@@}} {
+                       set next_l [$ui_diff index "$i_l + 1 lines"]
+                       set c1 [$ui_diff get $i_l]
+                       if {[$ui_diff compare $first_l <= $i_l] &&
+                           [$ui_diff compare $i_l < $last_l] &&
+                           ($c1 eq {-} || $c1 eq {+})} {
+                               # a line to stage/unstage
+                               set ln [$ui_diff get $i_l $next_l]
+                               if {$c1 eq {-}} {
+                                       set n [expr $n+1]
+                                       set patch "$patch$pre_context$ln"
+                                       set pre_context {}
+                               } else {
+                                       set m [expr $m+1]
+                                       set patch "$patch$ln"
+                               }
+                       } elseif {$c1 ne {-} && $c1 ne {+}} {
+                               # context line
+                               set ln [$ui_diff get $i_l $next_l]
                                set patch "$patch$pre_context$ln"
+                               set n [expr $n+1]
+                               set m [expr $m+1]
+                               set pre_context {}
+                       } elseif {$c1 eq $to_context} {
+                               # turn change line into context line
+                               set ln [$ui_diff get "$i_l + 1 chars" $next_l]
+                               if {$c1 eq {-}} {
+                                       set pre_context "$pre_context $ln"
+                               } else {
+                                       set patch "$patch $ln"
+                               }
+                               set n [expr $n+1]
+                               set m [expr $m+1]
                        } else {
-                               set patch "$patch$ln$pre_context"
-                       }
-                       set pre_context {}
-               } elseif {$c1 ne {-} && $c1 ne {+}} {
-                       # context line
-                       set ln [$ui_diff get $i_l $next_l]
-                       set patch "$patch$pre_context$ln"
-                       set n [expr $n+1]
-                       set pre_context {}
-               } elseif {$c1 eq $to_context} {
-                       # turn change line into context line
-                       set ln [$ui_diff get "$i_l + 1 chars" $next_l]
-                       if {$c1 eq {-}} {
-                               set pre_context "$pre_context $ln"
-                       } else {
-                               set patch "$patch $ln"
+                               # a change in the opposite direction of
+                               # to_context which is outside the range of
+                               # lines to apply.
+                               set patch "$patch$pre_context"
+                               set pre_context {}
                        }
-                       set n [expr $n+1]
+                       set i_l $next_l
                }
-               set i_l $next_l
+               set patch "$patch$pre_context"
+               set wholepatch "$wholepatch@@ -$hln,$n +$hln,$m @@\n$patch"
+               set first_l [$ui_diff index "$next_l + 1 lines"]
        }
-       set patch "@@ -$hln,$n +$hln,[eval expr $n $sign 1] @@\n$patch"
 
        if {[catch {
                set enc [get_path_encoding $current_diff_path]
                set p [eval git_write $apply_cmd]
                fconfigure $p -translation binary -encoding $enc
                puts -nonewline $p $current_diff_header
-               puts -nonewline $p $patch
+               puts -nonewline $p $wholepatch
                close $p} err]} {
                error_popup [append $failed_msg "\n\n$err"]
        }
index 75650157e551e34dab650d89f3fa6d25afc91d6a..c0fa69af561b7789cdcfea46b14bf6d2646f5962 100644 (file)
@@ -71,11 +71,13 @@ proc ask_popup {msg} {
 }
 
 proc hook_failed_popup {hook msg {is_fatal 1}} {
+       global use_ttk NS
        set w .hookfail
-       toplevel $w
+       Dialog $w
+       wm withdraw $w
 
-       frame $w.m
-       label $w.m.l1 -text "$hook hook failed:" \
+       ${NS}::frame $w.m
+       ${NS}::label $w.m.l1 -text "$hook hook failed:" \
                -anchor w \
                -justify left \
                -font font_uibold
@@ -87,10 +89,10 @@ proc hook_failed_popup {hook msg {is_fatal 1}} {
                -width 80 -height 10 \
                -font font_diff \
                -yscrollcommand [list $w.m.sby set]
-       scrollbar $w.m.sby -command [list $w.m.t yview]
+       ${NS}::scrollbar $w.m.sby -command [list $w.m.t yview]
        pack $w.m.l1 -side top -fill x
        if {$is_fatal} {
-               label $w.m.l2 \
+               ${NS}::label $w.m.l2 \
                        -text [mc "You must correct the above errors before committing."] \
                        -anchor w \
                        -justify left \
@@ -104,7 +106,7 @@ proc hook_failed_popup {hook msg {is_fatal 1}} {
        $w.m.t insert 1.0 $msg
        $w.m.t conf -state disabled
 
-       button $w.ok -text OK \
+       ${NS}::button $w.ok -text OK \
                -width 15 \
                -command "destroy $w"
        pack $w.ok -side bottom -anchor e -pady 10 -padx 10
@@ -112,5 +114,6 @@ proc hook_failed_popup {hook msg {is_fatal 1}} {
        bind $w <Visibility> "grab $w; focus $w"
        bind $w <Key-Return> "destroy $w"
        wm title $w [strcat "[appname] ([reponame]): " [mc "error"]]
+       wm deiconify $w
        tkwait window $w
 }
index d33896a0ce26dd34c8024e21c71123e62832b8c0..e9db0c49895fbebdf8dbe7c0472546a05fea26a0 100644 (file)
@@ -8,36 +8,41 @@ proc _delete_indexlock {} {
 }
 
 proc _close_updateindex {fd after} {
+       global use_ttk NS
        fconfigure $fd -blocking 1
        if {[catch {close $fd} err]} {
                set w .indexfried
-               toplevel $w
+               Dialog $w
+               wm withdraw $w
                wm title $w [strcat "[appname] ([reponame]): " [mc "Index Error"]]
                wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
-               pack [label $w.msg \
-                       -justify left \
-                       -anchor w \
-                       -text [strcat \
-                               [mc "Updating the Git index failed.  A rescan will be automatically started to resynchronize git-gui."] \
-                               "\n\n$err"] \
-                       ] -anchor w
-
-               frame $w.buttons
-               button $w.buttons.continue \
+               set s [mc "Updating the Git index failed.  A rescan will be automatically started to resynchronize git-gui."]
+               text $w.msg -yscrollcommand [list $w.vs set] \
+                       -width [string length $s] -relief flat \
+                       -borderwidth 0 -highlightthickness 0 \
+                       -background [get_bg_color $w]
+               $w.msg tag configure bold -font font_uibold -justify center
+               ${NS}::scrollbar $w.vs -command [list $w.msg yview]
+               $w.msg insert end $s bold \n\n$err {}
+               $w.msg configure -state disabled
+
+               ${NS}::button $w.continue \
                        -text [mc "Continue"] \
                        -command [list destroy $w]
-               pack $w.buttons.continue -side right -padx 5
-               button $w.buttons.unlock \
+               ${NS}::button $w.unlock \
                        -text [mc "Unlock Index"] \
                        -command "destroy $w; _delete_indexlock"
-               pack $w.buttons.unlock -side right
-               pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+               grid $w.msg - $w.vs -sticky news
+               grid $w.unlock $w.continue - -sticky se -padx 2 -pady 2
+               grid columnconfigure $w 0 -weight 1
+               grid rowconfigure $w 0 -weight 1
 
                wm protocol $w WM_DELETE_WINDOW update
-               bind $w.buttons.continue <Visibility> "
+               bind $w.continue <Visibility> "
                        grab $w
-                       focus $w.buttons.continue
+                       focus %W
                "
+               wm deiconify $w
                tkwait window $w
 
                $::main_status stop
index 283e4915e928df0c188d8ed2ec49e49eeed3b519..5cded2341c541b5688adbac35d3d2e1216bc2d0b 100644 (file)
@@ -139,14 +139,14 @@ method _finish {cons ok} {
 
 constructor dialog {} {
        global current_branch
-       global M1B
+       global M1B use_ttk NS
 
        if {![_can_merge $this]} {
                delete_this
                return
        }
 
-       make_toplevel top w
+       make_dialog top w
        wm title $top [append "[appname] ([reponame]): " [mc "Merge"]]
        if {$top ne {.}} {
                wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
@@ -154,21 +154,21 @@ constructor dialog {} {
 
        set _start [cb _start]
 
-       label $w.header \
+       ${NS}::label $w.header \
                -text [mc "Merge Into %s" $current_branch] \
                -font font_uibold
        pack $w.header -side top -fill x
 
-       frame $w.buttons
-       button $w.buttons.visualize \
+       ${NS}::frame $w.buttons
+       ${NS}::button $w.buttons.visualize \
                -text [mc Visualize] \
                -command [cb _visualize]
        pack $w.buttons.visualize -side left
-       button $w.buttons.merge \
+       ${NS}::button $w.buttons.merge \
                -text [mc Merge] \
                -command $_start
        pack $w.buttons.merge -side right
-       button $w.buttons.cancel \
+       ${NS}::button $w.buttons.cancel \
                -text [mc "Cancel"] \
                -command [cb _cancel]
        pack $w.buttons.cancel -side right -padx 5
index 1d55b49c9bd8182cad2066ecdb81630ca6ad24a6..3807c8d28324a277204db9191e99ddb856041c22 100644 (file)
@@ -91,7 +91,7 @@ proc save_config {} {
 proc do_options {} {
        global repo_config global_config font_descs
        global repo_config_new global_config_new
-       global ui_comm_spell
+       global ui_comm_spell use_ttk NS
 
        array unset repo_config_new
        array unset global_config_new
@@ -110,26 +110,28 @@ proc do_options {} {
        }
 
        set w .options_editor
-       toplevel $w
+       Dialog $w
+       wm withdraw $w
+       wm transient $w [winfo parent $w]
        wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
 
-       frame $w.buttons
-       button $w.buttons.restore -text [mc "Restore Defaults"] \
+       ${NS}::frame $w.buttons
+       ${NS}::button $w.buttons.restore -text [mc "Restore Defaults"] \
                -default normal \
                -command do_restore_defaults
        pack $w.buttons.restore -side left
-       button $w.buttons.save -text [mc Save] \
+       ${NS}::button $w.buttons.save -text [mc Save] \
                -default active \
                -command [list do_save_config $w]
        pack $w.buttons.save -side right
-       button $w.buttons.cancel -text [mc "Cancel"] \
+       ${NS}::button $w.buttons.cancel -text [mc "Cancel"] \
                -default normal \
                -command [list destroy $w]
        pack $w.buttons.cancel -side right -padx 5
        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
 
-       labelframe $w.repo -text [mc "%s Repository" [reponame]]
-       labelframe $w.global -text [mc "Global (All Repositories)"]
+       ${NS}::labelframe $w.repo -text [mc "%s Repository" [reponame]]
+       ${NS}::labelframe $w.global -text [mc "Global (All Repositories)"]
        pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5
        pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5
 
@@ -146,6 +148,7 @@ proc do_options {} {
                {b gui.trustmtime  {mc "Trust File Modification Timestamps"}}
                {b gui.pruneduringfetch {mc "Prune Tracking Branches During Fetch"}}
                {b gui.matchtrackingbranch {mc "Match Tracking Branches"}}
+               {b gui.textconv {mc "Use Textconv For Diffs and Blames"}}
                {b gui.fastcopyblame {mc "Blame Copy Only On Changed Files"}}
                {i-20..200 gui.copyblamethreshold {mc "Minimum Letters To Blame Copy On"}}
                {i-0..300 gui.blamehistoryctx {mc "Blame History Context Radius (days)"}}
@@ -161,7 +164,7 @@ proc do_options {} {
                foreach f {repo global} {
                        switch -glob -- $type {
                        b {
-                               checkbutton $w.$f.$optid -text $text \
+                               ${NS}::checkbutton $w.$f.$optid -text $text \
                                        -variable ${f}_config_new($name) \
                                        -onvalue true \
                                        -offvalue false
@@ -169,10 +172,10 @@ proc do_options {} {
                        }
                        i-* {
                                regexp -- {-(\d+)\.\.(\d+)$} $type _junk min max
-                               frame $w.$f.$optid
-                               label $w.$f.$optid.l -text "$text:"
+                               ${NS}::frame $w.$f.$optid
+                               ${NS}::label $w.$f.$optid.l -text "$text:"
                                pack $w.$f.$optid.l -side left -anchor w -fill x
-                               spinbox $w.$f.$optid.v \
+                               tspinbox $w.$f.$optid.v \
                                        -textvariable ${f}_config_new($name) \
                                        -from $min \
                                        -to $max \
@@ -184,11 +187,9 @@ proc do_options {} {
                        }
                        c -
                        t {
-                               frame $w.$f.$optid
-                               label $w.$f.$optid.l -text "$text:"
-                               entry $w.$f.$optid.v \
-                                       -borderwidth 1 \
-                                       -relief sunken \
+                               ${NS}::frame $w.$f.$optid
+                               ${NS}::label $w.$f.$optid.l -text "$text:"
+                               ${NS}::entry $w.$f.$optid.v \
                                        -width 20 \
                                        -textvariable ${f}_config_new($name)
                                pack $w.$f.$optid.l -side left -anchor w
@@ -199,7 +200,7 @@ proc do_options {} {
                                        menu $w.$f.$optid.m
                                        build_encoding_menu $w.$f.$optid.m \
                                                [list set ${f}_config_new($name)] 1
-                                       button $w.$f.$optid.b \
+                                       ${NS}::button $w.$f.$optid.b \
                                                -text [mc "Change"] \
                                                -command [list popup_btn_menu \
                                                        $w.$f.$optid.m $w.$f.$optid.b]
@@ -226,11 +227,17 @@ proc do_options {} {
                        set ${f}_config_new(gui.spellingdictionary) $value
                }
 
-               frame $w.$f.$optid
-               label $w.$f.$optid.l -text [mc "Spelling Dictionary:"]
-               eval tk_optionMenu $w.$f.$optid.v \
-                       ${f}_config_new(gui.spellingdictionary) \
-                       $all_dicts
+               ${NS}::frame $w.$f.$optid
+               ${NS}::label $w.$f.$optid.l -text [mc "Spelling Dictionary:"]
+               if {$use_ttk} {
+                       ttk::combobox $w.$f.$optid.v \
+                               -textvariable ${f}_config_new(gui.spellingdictionary) \
+                               -values $all_dicts -state readonly
+               } else {
+                       eval tk_optionMenu $w.$f.$optid.v \
+                               ${f}_config_new(gui.spellingdictionary) \
+                               $all_dicts
+               }
                pack $w.$f.$optid.l -side left -anchor w -fill x
                pack $w.$f.$optid.v -side right -anchor e -padx 5
                pack $w.$f.$optid -side top -anchor w -fill x
@@ -248,20 +255,20 @@ proc do_options {} {
                set global_config_new(gui.$font^^size) \
                        [font configure $font -size]
 
-               frame $w.global.$name
-               label $w.global.$name.l -text "$text:"
-               button $w.global.$name.b \
+               ${NS}::frame $w.global.$name
+               ${NS}::label $w.global.$name.l -text "$text:"
+               ${NS}::button $w.global.$name.b \
                        -text [mc "Change Font"] \
                        -command [list \
-                               choose_font::pick \
+                               tchoosefont \
                                $w \
                                [mc "Choose %s" $text] \
                                global_config_new(gui.$font^^family) \
                                global_config_new(gui.$font^^size) \
                                ]
-               label $w.global.$name.f -textvariable global_config_new(gui.$font^^family)
-               label $w.global.$name.s -textvariable global_config_new(gui.$font^^size)
-               label $w.global.$name.pt -text [mc "pt."]
+               ${NS}::label $w.global.$name.f -textvariable global_config_new(gui.$font^^family)
+               ${NS}::label $w.global.$name.s -textvariable global_config_new(gui.$font^^size)
+               ${NS}::label $w.global.$name.pt -text [mc "pt."]
                pack $w.global.$name.l -side left -anchor w
                pack $w.global.$name.b -side right -anchor e
                pack $w.global.$name.pt -side right -anchor w
@@ -280,6 +287,7 @@ proc do_options {} {
                set t [mc "Options"]
        }
        wm title $w "[appname] ([reponame]): $t"
+       wm deiconify $w
        tkwait window $w
 }
 
index fb29422aa7b9bacb78d33026b825d19ab3399e95..50029d0ceeee0fdab884754022e3015b3925f6f7 100644 (file)
@@ -13,45 +13,43 @@ field location     {}; # location of the remote the user has chosen
 field opt_action fetch; # action to do after registering the remote locally
 
 constructor dialog {} {
-       global repo_config
+       global repo_config use_ttk NS
 
-       make_toplevel top w
+       make_dialog top w
+       wm withdraw $top
        wm title $top [append "[appname] ([reponame]): " [mc "Add Remote"]]
        if {$top ne {.}} {
                wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
        }
 
-       label $w.header -text [mc "Add New Remote"] -font font_uibold
+       ${NS}::label $w.header -text [mc "Add New Remote"] \
+               -font font_uibold -anchor center
        pack $w.header -side top -fill x
 
-       frame $w.buttons
-       button $w.buttons.create -text [mc Add] \
+       ${NS}::frame $w.buttons
+       ${NS}::button $w.buttons.create -text [mc Add] \
                -default active \
                -command [cb _add]
        pack $w.buttons.create -side right
-       button $w.buttons.cancel -text [mc Cancel] \
+       ${NS}::button $w.buttons.cancel -text [mc Cancel] \
                -command [list destroy $w]
        pack $w.buttons.cancel -side right -padx 5
        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
 
-       labelframe $w.desc -text [mc "Remote Details"]
+       ${NS}::labelframe $w.desc -text [mc "Remote Details"]
 
-       label $w.desc.name_l -text [mc "Name:"]
+       ${NS}::label $w.desc.name_l -text [mc "Name:"]
        set w_name $w.desc.name_t
-       entry $w_name \
-               -borderwidth 1 \
-               -relief sunken \
+       ${NS}::entry $w_name \
                -width 40 \
                -textvariable @name \
                -validate key \
                -validatecommand [cb _validate_name %d %S]
        grid $w.desc.name_l $w_name -sticky we -padx {0 5}
 
-       label $w.desc.loc_l -text [mc "Location:"]
+       ${NS}::label $w.desc.loc_l -text [mc "Location:"]
        set w_loc $w.desc.loc_t
-       entry $w_loc \
-               -borderwidth 1 \
-               -relief sunken \
+       ${NS}::entry $w_loc \
                -width 40 \
                -textvariable @location
        grid $w.desc.loc_l $w_loc -sticky we -padx {0 5}
@@ -59,21 +57,21 @@ constructor dialog {} {
        grid columnconfigure $w.desc 1 -weight 1
        pack $w.desc -anchor nw -fill x -pady 5 -padx 5
 
-       labelframe $w.action -text [mc "Further Action"]
+       ${NS}::labelframe $w.action -text [mc "Further Action"]
 
-       radiobutton $w.action.fetch \
+       ${NS}::radiobutton $w.action.fetch \
                -text [mc "Fetch Immediately"] \
                -value fetch \
                -variable @opt_action
        pack $w.action.fetch -anchor nw
 
-       radiobutton $w.action.push \
+       ${NS}::radiobutton $w.action.push \
                -text [mc "Initialize Remote Repository and Push"] \
                -value push \
                -variable @opt_action
        pack $w.action.push -anchor nw
 
-       radiobutton $w.action.none \
+       ${NS}::radiobutton $w.action.none \
                -text [mc "Do Nothing Else Now"] \
                -value none \
                -variable @opt_action
@@ -85,6 +83,7 @@ constructor dialog {} {
        bind $w <Visibility> [cb _visible]
        bind $w <Key-Escape> [list destroy $w]
        bind $w <Key-Return> [cb _add]\;break
+       wm deiconify $top
        tkwait window $w
 }
 
index 4e02fc0d393ff9fe8d2983fec99a03db7f36273a..f872a3d89d89bc861ea3729ec7974833bdac277f 100644 (file)
@@ -23,34 +23,40 @@ field full_cache
 field cached
 
 constructor dialog {} {
-       global all_remotes M1B
+       global all_remotes M1B use_ttk NS
 
-       make_toplevel top w
+       make_dialog top w
        wm title $top [append "[appname] ([reponame]): " [mc "Delete Branch Remotely"]]
        if {$top ne {.}} {
                wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
        }
 
-       label $w.header -text [mc "Delete Branch Remotely"] -font font_uibold
+       ${NS}::label $w.header -text [mc "Delete Branch Remotely"] \
+               -font font_uibold -anchor center
        pack $w.header -side top -fill x
 
-       frame $w.buttons
-       button $w.buttons.delete -text [mc Delete] \
+       ${NS}::frame $w.buttons
+       ${NS}::button $w.buttons.delete -text [mc Delete] \
                -default active \
                -command [cb _delete]
        pack $w.buttons.delete -side right
-       button $w.buttons.cancel -text [mc "Cancel"] \
+       ${NS}::button $w.buttons.cancel -text [mc "Cancel"] \
                -command [list destroy $w]
        pack $w.buttons.cancel -side right -padx 5
        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
 
-       labelframe $w.dest -text [mc "From Repository"]
+       ${NS}::labelframe $w.dest -text [mc "From Repository"]
        if {$all_remotes ne {}} {
-               radiobutton $w.dest.remote_r \
+               ${NS}::radiobutton $w.dest.remote_r \
                        -text [mc "Remote:"] \
                        -value remote \
                        -variable @urltype
-               eval tk_optionMenu $w.dest.remote_m @remote $all_remotes
+               if {$use_ttk} {
+                       ttk::combobox $w.dest.remote_m -textvariable @remote \
+                               -values $all_remotes -state readonly
+               } else {
+                       eval tk_optionMenu $w.dest.remote_m @remote $all_remotes
+               }
                grid $w.dest.remote_r $w.dest.remote_m -sticky w
                if {[lsearch -sorted -exact $all_remotes origin] != -1} {
                        set remote origin
@@ -62,13 +68,11 @@ constructor dialog {} {
        } else {
                set urltype url
        }
-       radiobutton $w.dest.url_r \
+       ${NS}::radiobutton $w.dest.url_r \
                -text [mc "Arbitrary Location:"] \
                -value url \
                -variable @urltype
-       entry $w.dest.url_t \
-               -borderwidth 1 \
-               -relief sunken \
+       ${NS}::entry $w.dest.url_t \
                -width 50 \
                -textvariable @url \
                -validate key \
@@ -81,33 +85,30 @@ constructor dialog {} {
        grid columnconfigure $w.dest 1 -weight 1
        pack $w.dest -anchor nw -fill x -pady 5 -padx 5
 
-       labelframe $w.heads -text [mc "Branches"]
-       listbox $w.heads.l \
+       ${NS}::labelframe $w.heads -text [mc "Branches"]
+       slistbox $w.heads.l \
                -height 10 \
                -width 70 \
                -listvariable @head_list \
-               -selectmode extended \
-               -yscrollcommand [list $w.heads.sby set]
-       scrollbar $w.heads.sby -command [list $w.heads.l yview]
+               -selectmode extended
 
-       frame $w.heads.footer
-       label $w.heads.footer.status \
+       ${NS}::frame $w.heads.footer
+       ${NS}::label $w.heads.footer.status \
                -textvariable @status \
                -anchor w \
                -justify left
-       button $w.heads.footer.rescan \
+       ${NS}::button $w.heads.footer.rescan \
                -text [mc "Rescan"] \
                -command [cb _rescan]
        pack $w.heads.footer.status -side left -fill x
        pack $w.heads.footer.rescan -side right
 
        pack $w.heads.footer -side bottom -fill x
-       pack $w.heads.sby -side right -fill y
        pack $w.heads.l -side left -fill both -expand 1
        pack $w.heads -fill both -expand 1 -pady 5 -padx 5
 
-       labelframe $w.validate -text [mc "Delete Only If"]
-       radiobutton $w.validate.head_r \
+       ${NS}::labelframe $w.validate -text [mc "Delete Only If"]
+       ${NS}::radiobutton $w.validate.head_r \
                -text [mc "Merged Into:"] \
                -value head \
                -variable @checktype
@@ -115,7 +116,7 @@ constructor dialog {} {
        trace add variable @head_list write [cb _write_head_list]
        trace add variable @check_head write [cb _write_check_head]
        grid $w.validate.head_r $w.validate.head_m -sticky w
-       radiobutton $w.validate.always_r \
+       ${NS}::radiobutton $w.validate.always_r \
                -text [mc "Always (Do not perform merge checks)"] \
                -value always \
                -variable @checktype
@@ -208,13 +209,15 @@ method _delete {} {
                return
        }
 
-       if {[tk_messageBox \
-               -icon warning \
-               -type yesno \
-               -title [wm title $w] \
-               -parent $w \
-               -message [mc "Recovering deleted branches is difficult.\n\nDelete the selected branches?"]] ne yes} {
-               return
+       if {$checktype ne {head}} {
+               if {[tk_messageBox \
+                       -icon warning \
+                       -type yesno \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message [mc "Recovering deleted branches is difficult.\n\nDelete the selected branches?"]] ne yes} {
+                       return
+               }
        }
 
        destroy $w
@@ -248,6 +251,8 @@ method _write_url        {args} { set urltype url    }
 method _write_check_head {args} { set checktype head }
 
 method _write_head_list {args} {
+       global current_branch
+
        $head_m delete 0 end
        foreach abr $head_list {
                $head_m insert end radiobutton \
@@ -256,7 +261,11 @@ method _write_head_list {args} {
                        -variable @check_head
        }
        if {[lsearch -exact -sorted $head_list $check_head] < 0} {
-               set check_head {}
+               if {[lsearch -exact -sorted $head_list $current_branch] < 0} {
+                       set check_head {}
+               } else {
+                       set check_head $current_branch
+               }
        }
 }
 
index b371e9a30a00fda0ced979c03a1a5d7856654baa..7fdbf87bcdcf3e6d5a928dd4ac8a323509ea7953 100644 (file)
@@ -14,15 +14,16 @@ field smarktop
 field smarkbot
 
 constructor new {i_w i_text args} {
+       global use_ttk NS
        set w      $i_w
        set ctext  $i_text
 
-       frame  $w
-       label  $w.l       -text [mc Find:]
+       ${NS}::frame  $w
+       ${NS}::label  $w.l       -text [mc Find:]
        entry  $w.ent -textvariable ${__this}::searchstring -background lightgreen
-       button $w.bn      -text [mc Next] -command [cb find_next]
-       button $w.bp      -text [mc Prev] -command [cb find_prev]
-       checkbutton $w.cs -text [mc Case-Sensitive] \
+       ${NS}::button $w.bn      -text [mc Next] -command [cb find_next]
+       ${NS}::button $w.bp      -text [mc Prev] -command [cb find_prev]
+       ${NS}::checkbutton $w.cs -text [mc Case-Sensitive] \
                -variable ${__this}::casesensitive -command [cb _incrsearch]
        pack   $w.l   -side left
        pack   $w.cs  -side right
index 2f20eb39c0e25d68e4e6b46fe3b14a46b84ae83e..78878ef89d11210d614fc8b3d2957705611bdaa3 100644 (file)
@@ -2,6 +2,7 @@
 # Copyright (C) 2006, 2007 Shawn Pearce
 
 proc do_windows_shortcut {} {
+       global _gitworktree
        set fn [tk_getSaveFile \
                -parent . \
                -title [append "[appname] ([reponame]): " [mc "Create Desktop Icon"]] \
@@ -15,7 +16,7 @@ proc do_windows_shortcut {} {
                                        [info nameofexecutable] \
                                        [file normalize $::argv0] \
                                        ] \
-                                       [file dirname [file normalize [gitdir]]]
+                                       [file normalize $_gitworktree]
                        } err]} {
                        error_popup [strcat [mc "Cannot write shortcut:"] "\n\n$err"]
                }
@@ -23,7 +24,7 @@ proc do_windows_shortcut {} {
 }
 
 proc do_cygwin_shortcut {} {
-       global argv0
+       global argv0 _gitworktree
 
        if {[catch {
                set desktop [exec cygpath \
@@ -56,7 +57,7 @@ proc do_cygwin_shortcut {} {
                                        $sh -c \
                                        "CHERE_INVOKING=1 source /etc/profile;[sq $me] &" \
                                        ] \
-                                       [file dirname [file normalize [gitdir]]]
+                                       [file normalize $_gitworktree]
                        } err]} {
                        error_popup [strcat [mc "Cannot write shortcut:"] "\n\n$err"]
                }
index 82a1a80ff4cf4e9f89d916c3052d731637acb6c9..5f75bc96b3504bdd67ae3500cfdd30f7d4fcb32a 100644 (file)
@@ -15,7 +15,7 @@ proc find_ssh_key {} {
 }
 
 proc do_ssh_key {} {
-       global sshkey_title have_tk85 sshkey_fd
+       global sshkey_title have_tk85 sshkey_fd use_ttk NS
 
        set w .sshkey_dialog
        if {[winfo exists $w]} {
@@ -23,7 +23,7 @@ proc do_ssh_key {} {
                return
        }
 
-       toplevel $w
+       Dialog $w
        wm transient $w .
 
        set finfo [find_ssh_key]
@@ -35,9 +35,9 @@ proc do_ssh_key {} {
                set gen_state   disabled
        }
 
-       frame $w.header -relief flat
-       label $w.header.lbl -textvariable sshkey_title -anchor w
-       button $w.header.gen -text [mc "Generate Key"] \
+       ${NS}::frame $w.header
+       ${NS}::label $w.header.lbl -textvariable sshkey_title -anchor w
+       ${NS}::button $w.header.gen -text [mc "Generate Key"] \
                -command [list make_ssh_key $w] -state $gen_state
        pack $w.header.lbl -side left -expand 1 -fill x
        pack $w.header.gen -side right
@@ -46,14 +46,16 @@ proc do_ssh_key {} {
        text $w.contents -width 60 -height 10 -wrap char -relief sunken
        pack $w.contents -fill both -expand 1
        if {$have_tk85} {
-               $w.contents configure -inactiveselectbackground darkblue
+               set clr darkblue
+               if {$use_ttk} { set clr [ttk::style lookup . -selectbackground] }
+               $w.contents configure -inactiveselectbackground $clr
        }
 
-       frame $w.buttons
-       button $w.buttons.close -text [mc Close] \
+       ${NS}::frame $w.buttons
+       ${NS}::button $w.buttons.close -text [mc Close] \
                -default active -command [list destroy $w]
        pack $w.buttons.close -side right
-       button $w.buttons.copy -text [mc "Copy To Clipboard"] \
+       ${NS}::button $w.buttons.copy -text [mc "Copy To Clipboard"] \
                -command [list tk_textCopy $w.contents]
        pack $w.buttons.copy -side left
        pack $w.buttons -side bottom -fill x -pady 5 -padx 5
index 51d4177551b911c35cfd8004a36fca4259367d24..95cb44991fc5b018805d6091c4f98ce7ae0ccf52 100644 (file)
@@ -13,14 +13,16 @@ field units   {}; # unit of progress
 field meter   {}; # current core git progress meter (if active)
 
 constructor new {path} {
+       global use_ttk NS
        set w $path
        set w_l $w.l
        set w_c $w.c
 
-       frame $w \
-               -borderwidth 1 \
-               -relief sunken
-       label $w_l \
+       ${NS}::frame $w
+       if {!$use_ttk} {
+               $w configure -borderwidth 1 -relief sunken
+       }
+       ${NS}::label $w_l \
                -textvariable @status \
                -anchor w \
                -justify left
@@ -37,12 +39,13 @@ method _oneline_pack {} {
 }
 
 constructor two_line {path} {
+       global NS
        set w $path
        set w_l $w.l
        set w_c $w.c
 
-       frame $w
-       label $w_l \
+       ${NS}::frame $w
+       ${NS}::label $w_l \
                -textvariable @status \
                -anchor w \
                -justify left
diff --git a/git-gui/lib/themed.tcl b/git-gui/lib/themed.tcl
new file mode 100644 (file)
index 0000000..1da4586
--- /dev/null
@@ -0,0 +1,174 @@
+# Functions for supporting the use of themed Tk widgets in git-gui.
+# Copyright (C) 2009 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+proc InitTheme {} {
+       # Create a color label style (bg can be overridden by widget option)
+       ttk::style layout Color.TLabel {
+               Color.Label.border -sticky news -children {
+                       Color.label.fill -sticky news -children {
+                               Color.Label.padding -sticky news -children {
+                                       Color.Label.label -sticky news}}}}
+       eval [linsert [ttk::style configure TLabel] 0 \
+                         ttk::style configure Color.TLabel]
+       ttk::style configure Color.TLabel \
+               -borderwidth 0 -relief flat -padding 2
+       ttk::style map Color.TLabel -background {{} gold}
+       # We also need a padded label.
+       ttk::style configure Padded.TLabel \
+               -padding {5 5} -borderwidth 1 -relief solid
+       # We need a gold frame.
+       ttk::style layout Gold.TFrame {
+               Gold.Frame.border -sticky nswe -children {
+                       Gold.Frame.fill -sticky nswe}}
+       ttk::style configure Gold.TFrame -background gold -relief flat
+       # listboxes should have a theme border so embed in ttk::frame
+       ttk::style layout SListbox.TFrame {
+        SListbox.Frame.Entry.field -sticky news -border true -children {
+            SListbox.Frame.padding -sticky news
+        }
+    }
+}
+
+proc gold_frame {w args} {
+       global use_ttk
+       if {$use_ttk} {
+               eval [linsert $args 0 ttk::frame $w -style Gold.TFrame]
+       } else {
+               eval [linsert $args 0 frame $w -background gold]
+       }
+}
+
+proc tlabel {w args} {
+       global use_ttk
+       if {$use_ttk} {
+               set cmd [list ttk::label $w -style Color.TLabel]
+               foreach {k v} $args {
+                       switch -glob -- $k {
+                               -activebackground {}
+                               default { lappend cmd $k $v }
+                       }
+               }
+               eval $cmd
+       } else {
+               eval [linsert $args 0 label $w]
+       }
+}
+
+# The padded label gets used in the about class.
+proc paddedlabel {w args} {
+       global use_ttk
+       if {$use_ttk} {
+               eval [linsert $args 0 ttk::label $w -style Padded.TLabel]
+       } else {
+               eval [linsert $args 0 label $w \
+                                 -padx 5 -pady 5 \
+                                 -justify left \
+                                 -anchor w \
+                                 -borderwidth 1 \
+                                 -relief solid]
+       }
+}
+
+# Create a toplevel for use as a dialog.
+# If available, sets the EWMH dialog hint and if ttk is enabled
+# place a themed frame over the surface.
+proc Dialog {w args} {
+       eval [linsert $args 0 toplevel $w -class Dialog]
+       pave_toplevel $w
+       return $w
+}
+
+# Tk toplevels are not themed - so pave it over with a themed frame to get
+# the base color correct per theme.
+proc pave_toplevel {w} {
+       global use_ttk
+       if {$use_ttk && ![winfo exists $w.!paving]} {
+               set paving [ttk::frame $w.!paving]
+               place $paving -x 0 -y 0 -relwidth 1 -relheight 1
+               lower $paving
+       }
+}
+
+# Create a scrolled listbox with appropriate border for the current theme.
+# On many themes the border for a scrolled listbox needs to go around the
+# listbox and the scrollbar.
+proc slistbox {w args} {
+       global use_ttk NS
+       if {$use_ttk} {
+               set f [ttk::frame $w -style SListbox.TFrame -padding 2]
+       } else {
+               set f [frame $w -relief flat]
+       }
+    if {[catch {
+               if {$use_ttk} {
+                       eval [linsert $args 0 listbox $f.list -relief flat \
+                                         -highlightthickness 0 -borderwidth 0]
+               } else {
+                       eval [linsert $args 0 listbox $f.list]
+               }
+        ${NS}::scrollbar $f.vs -command [list $f.list yview]
+        $f.list configure -yscrollcommand [list $f.vs set]
+        grid $f.list $f.vs -sticky news
+        grid rowconfigure $f 0 -weight 1
+        grid columnconfigure $f 0 -weight 1
+               bind $f.list <<ListboxSelect>> \
+                       [list event generate $w <<ListboxSelect>>]
+        interp hide {} $w
+        interp alias {} $w {} $f.list
+    } err]} {
+        destroy $f
+        return -code error $err
+    }
+    return $w
+}
+
+# fetch the background color from a widget.
+proc get_bg_color {w} {
+       global use_ttk
+       if {$use_ttk} {
+               set bg [ttk::style lookup [winfo class $w] -background]
+       } else {
+               set bg [$w cget -background]
+       }
+       return $bg
+}
+
+# ttk::spinbox didn't get added until 8.6
+proc tspinbox {w args} {
+       global use_ttk
+       if {$use_ttk && [llength [info commands ttk::spinbox]] > 0} {
+               eval [linsert $args 0 ttk::spinbox $w]
+       } else {
+               eval [linsert $args 0 spinbox $w]
+       }
+}
+
+# Tk 8.6 provides a standard font selection dialog. This uses the native
+# dialogs on Windows and MacOSX or a standard Tk dialog on X11.
+proc tchoosefont {w title familyvar sizevar} {
+       if {[package vsatisfies [package provide Tk] 8.6]} {
+               upvar #0 $familyvar family
+               upvar #0 $sizevar size
+               tk fontchooser configure -parent $w -title $title \
+                       -font [list $family $size] \
+                       -command [list on_choosefont $familyvar $sizevar]
+               tk fontchooser show
+       } else {
+               choose_font::pick $w $title $familyvar $sizevar
+       }
+}
+
+# Called when the Tk 8.6 fontchooser selects a font.
+proc on_choosefont {familyvar sizevar font} {
+       upvar #0 $familyvar family
+       upvar #0 $sizevar size
+       set font [font actual $font]
+       set family [dict get $font -family]
+       set size [dict get $font -size]
+}
+
+# Local variables:
+# mode: tcl
+# indent-tabs-mode: t
+# tab-width: 4
+# End:
index 5f7f08e239038e47da7bb0383fd840276d9caf59..7eeda9daf23161a0abbe4abcc857ebfb0e18f9de 100644 (file)
@@ -16,53 +16,50 @@ field ask_branch    0; # ask for a revision
 field ask_args      0; # ask for additional args
 
 constructor dialog {} {
-       global repo_config
+       global repo_config use_ttk NS
 
-       make_toplevel top w
+       make_dialog top w
        wm title $top [append "[appname] ([reponame]): " [mc "Add Tool"]]
        if {$top ne {.}} {
                wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
                wm transient $top .
        }
 
-       label $w.header -text [mc "Add New Tool Command"] -font font_uibold
+       ${NS}::label $w.header -text [mc "Add New Tool Command"] \
+               -font font_uibold -anchor center
        pack $w.header -side top -fill x
 
-       frame $w.buttons
-       checkbutton $w.buttons.global \
+       ${NS}::frame $w.buttons
+       ${NS}::checkbutton $w.buttons.global \
                -text [mc "Add globally"] \
                -variable @add_global
        pack $w.buttons.global -side left -padx 5
-       button $w.buttons.create -text [mc Add] \
+       ${NS}::button $w.buttons.create -text [mc Add] \
                -default active \
                -command [cb _add]
        pack $w.buttons.create -side right
-       button $w.buttons.cancel -text [mc Cancel] \
+       ${NS}::button $w.buttons.cancel -text [mc Cancel] \
                -command [list destroy $w]
        pack $w.buttons.cancel -side right -padx 5
        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
 
-       labelframe $w.desc -text [mc "Tool Details"]
+       ${NS}::labelframe $w.desc -text [mc "Tool Details"]
 
-       label $w.desc.name_cmnt -anchor w\
+       ${NS}::label $w.desc.name_cmnt -anchor w\
                -text [mc "Use '/' separators to create a submenu tree:"]
        grid x $w.desc.name_cmnt -sticky we -padx {0 5} -pady {0 2}
-       label $w.desc.name_l -text [mc "Name:"]
+       ${NS}::label $w.desc.name_l -text [mc "Name:"]
        set w_name $w.desc.name_t
-       entry $w_name \
-               -borderwidth 1 \
-               -relief sunken \
+       ${NS}::entry $w_name \
                -width 40 \
                -textvariable @name \
                -validate key \
                -validatecommand [cb _validate_name %d %S]
        grid $w.desc.name_l $w_name -sticky we -padx {0 5}
 
-       label $w.desc.cmd_l -text [mc "Command:"]
+       ${NS}::label $w.desc.cmd_l -text [mc "Command:"]
        set w_cmd $w.desc.cmd_t
-       entry $w_cmd \
-               -borderwidth 1 \
-               -relief sunken \
+       ${NS}::entry $w_cmd \
                -width 40 \
                -textvariable @command
        grid $w.desc.cmd_l $w_cmd -sticky we -padx {0 5} -pady {0 3}
@@ -70,30 +67,30 @@ constructor dialog {} {
        grid columnconfigure $w.desc 1 -weight 1
        pack $w.desc -anchor nw -fill x -pady 5 -padx 5
 
-       checkbutton $w.confirm \
+       ${NS}::checkbutton $w.confirm \
                -text [mc "Show a dialog before running"] \
                -variable @confirm -command [cb _check_enable_dlg]
 
-       labelframe $w.dlg -labelwidget $w.confirm
+       ${NS}::labelframe $w.dlg -labelwidget $w.confirm
 
-       checkbutton $w.dlg.askbranch \
+       ${NS}::checkbutton $w.dlg.askbranch \
                -text [mc "Ask the user to select a revision (sets \$REVISION)"] \
                -variable @ask_branch -state disabled
        pack $w.dlg.askbranch -anchor w -padx 15
 
-       checkbutton $w.dlg.askargs \
+       ${NS}::checkbutton $w.dlg.askargs \
                -text [mc "Ask the user for additional arguments (sets \$ARGS)"] \
                -variable @ask_args -state disabled
        pack $w.dlg.askargs -anchor w -padx 15
 
        pack $w.dlg -anchor nw -fill x -pady {0 8} -padx 5
 
-       checkbutton $w.noconsole \
+       ${NS}::checkbutton $w.noconsole \
                -text [mc "Don't show the command output window"] \
                -variable @no_console
        pack $w.noconsole -anchor w -padx 5
 
-       checkbutton $w.needsfile \
+       ${NS}::checkbutton $w.needsfile \
                -text [mc "Run only if a diff is selected (\$FILENAME not empty)"] \
                -variable @needs_file
        pack $w.needsfile -anchor w -padx 5
@@ -182,40 +179,38 @@ field w              ; # widget path
 field w_names        ; # name list
 
 constructor dialog {} {
-       global repo_config global_config system_config
+       global repo_config global_config system_config use_ttk NS
 
        load_config 1
 
-       make_toplevel top w
+       make_dialog top w
        wm title $top [append "[appname] ([reponame]): " [mc "Remove Tool"]]
        if {$top ne {.}} {
                wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
                wm transient $top .
        }
 
-       label $w.header -text [mc "Remove Tool Commands"] -font font_uibold
+       ${NS}::label $w.header -text [mc "Remove Tool Commands"] \
+               -font font_uibold -anchor center
        pack $w.header -side top -fill x
 
-       frame $w.buttons
-       button $w.buttons.create -text [mc Remove] \
+       ${NS}::frame $w.buttons
+       ${NS}::button $w.buttons.create -text [mc Remove] \
                -default active \
                -command [cb _remove]
        pack $w.buttons.create -side right
-       button $w.buttons.cancel -text [mc Cancel] \
+       ${NS}::button $w.buttons.cancel -text [mc Cancel] \
                -command [list destroy $w]
        pack $w.buttons.cancel -side right -padx 5
        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
 
-       frame $w.list
+       ${NS}::frame $w.list
        set w_names $w.list.l
-       listbox $w_names \
+       slistbox $w_names \
                -height 10 \
                -width 30 \
                -selectmode extended \
-               -exportselection false \
-               -yscrollcommand [list $w.list.sby set]
-       scrollbar $w.list.sby -command [list $w.list.l yview]
-       pack $w.list.sby -side right -fill y
+               -exportselection false
        pack $w.list.l -side left -fill both -expand 1
        pack $w.list -fill both -expand 1 -pady 5 -padx 5
 
@@ -232,7 +227,7 @@ constructor dialog {} {
        }
 
        if {$local_cnt > 0} {
-               label $w.colorlbl -foreground blue \
+               ${NS}::label $w.colorlbl -foreground blue \
                        -text [mc "(Blue denotes repository-local tools)"]
                pack $w.colorlbl -fill x -pady 5 -padx 5
        }
@@ -277,14 +272,14 @@ field is_ok         0; # ok to start
 field argstr       {}; # arguments
 
 constructor dialog {fullname} {
-       global M1B
+       global M1B use_ttk NS
 
        set title [get_config "guitool.$fullname.title"]
        if {$title eq {}} {
                regsub {/} $fullname { / } title
        }
 
-       make_toplevel top w -autodelete 0
+       make_dialog top w -autodelete 0
        wm title $top [append "[appname] ([reponame]): " $title]
        if {$top ne {.}} {
                wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
@@ -297,7 +292,7 @@ constructor dialog {fullname} {
                set prompt [mc "Run Command: %s" $command]
        }
 
-       label $w.header -text $prompt -font font_uibold
+       ${NS}::label $w.header -text $prompt -font font_uibold -anchor center
        pack $w.header -side top -fill x
 
        set argprompt [get_config "guitool.$fullname.argprompt"]
@@ -311,12 +306,10 @@ constructor dialog {fullname} {
                        set argprompt [mc "Arguments"]
                }
 
-               labelframe $w.arg -text $argprompt
+               ${NS}::labelframe $w.arg -text $argprompt
 
                set w_args $w.arg.txt
-               entry $w_args \
-                       -borderwidth 1 \
-                       -relief sunken \
+               ${NS}::entry $w_args \
                        -width 40 \
                        -textvariable @argstr
                pack $w_args -padx 5 -pady 5 -fill both
@@ -337,18 +330,18 @@ constructor dialog {fullname} {
                pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5
        }
 
-       frame $w.buttons
+       ${NS}::frame $w.buttons
        if {$is_ask_revs} {
-               button $w.buttons.visualize \
+               ${NS}::button $w.buttons.visualize \
                        -text [mc Visualize] \
                        -command [cb _visualize]
                pack $w.buttons.visualize -side left
        }
-       button $w.buttons.ok \
+       ${NS}::button $w.buttons.ok \
                -text [mc OK] \
                -command [cb _start]
        pack $w.buttons.ok -side right
-       button $w.buttons.cancel \
+       ${NS}::button $w.buttons.cancel \
                -text [mc "Cancel"] \
                -command [cb _cancel]
        pack $w.buttons.cancel -side right -padx 5
index b18d9c7a1b0b2d8f370ecd8c4cdf6ac1a4586c51..60e3a642c5fb7f1526c9feb1a1d3a495db7728f8 100644 (file)
@@ -91,50 +91,55 @@ trace add variable push_remote write \
 proc do_push_anywhere {} {
        global all_remotes current_branch
        global push_urltype push_remote push_url push_thin push_tags
-       global push_force
+       global push_force use_ttk NS
 
        set w .push_setup
        toplevel $w
+       wm withdraw $w
        wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
+       pave_toplevel $w
 
-       label $w.header -text [mc "Push Branches"] -font font_uibold
+       ${NS}::label $w.header -text [mc "Push Branches"] \
+               -font font_uibold -anchor center
        pack $w.header -side top -fill x
 
-       frame $w.buttons
-       button $w.buttons.create -text [mc Push] \
+       ${NS}::frame $w.buttons
+       ${NS}::button $w.buttons.create -text [mc Push] \
                -default active \
                -command [list start_push_anywhere_action $w]
        pack $w.buttons.create -side right
-       button $w.buttons.cancel -text [mc "Cancel"] \
+       ${NS}::button $w.buttons.cancel -text [mc "Cancel"] \
                -default normal \
                -command [list destroy $w]
        pack $w.buttons.cancel -side right -padx 5
        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
 
-       labelframe $w.source -text [mc "Source Branches"]
-       listbox $w.source.l \
+       ${NS}::labelframe $w.source -text [mc "Source Branches"]
+       slistbox $w.source.l \
                -height 10 \
                -width 70 \
-               -selectmode extended \
-               -yscrollcommand [list $w.source.sby set]
+               -selectmode extended
        foreach h [load_all_heads] {
                $w.source.l insert end $h
                if {$h eq $current_branch} {
                        $w.source.l select set end
                }
        }
-       scrollbar $w.source.sby -command [list $w.source.l yview]
-       pack $w.source.sby -side right -fill y
        pack $w.source.l -side left -fill both -expand 1
        pack $w.source -fill both -expand 1 -pady 5 -padx 5
 
-       labelframe $w.dest -text [mc "Destination Repository"]
+       ${NS}::labelframe $w.dest -text [mc "Destination Repository"]
        if {$all_remotes ne {}} {
-               radiobutton $w.dest.remote_r \
+               ${NS}::radiobutton $w.dest.remote_r \
                        -text [mc "Remote:"] \
                        -value remote \
                        -variable push_urltype
-               eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes
+               if {$use_ttk} {
+                       ttk::combobox $w.dest.remote_m -textvariable push_remote \
+                               -values $all_remotes
+               } else {
+                       eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes
+               }
                grid $w.dest.remote_r $w.dest.remote_m -sticky w
                if {[lsearch -sorted -exact $all_remotes origin] != -1} {
                        set push_remote origin
@@ -145,13 +150,11 @@ proc do_push_anywhere {} {
        } else {
                set push_urltype url
        }
-       radiobutton $w.dest.url_r \
+       ${NS}::radiobutton $w.dest.url_r \
                -text [mc "Arbitrary Location:"] \
                -value url \
                -variable push_urltype
-       entry $w.dest.url_t \
-               -borderwidth 1 \
-               -relief sunken \
+       ${NS}::entry $w.dest.url_t \
                -width 50 \
                -textvariable push_url \
                -validate key \
@@ -166,16 +169,16 @@ proc do_push_anywhere {} {
        grid columnconfigure $w.dest 1 -weight 1
        pack $w.dest -anchor nw -fill x -pady 5 -padx 5
 
-       labelframe $w.options -text [mc "Transfer Options"]
-       checkbutton $w.options.force \
+       ${NS}::labelframe $w.options -text [mc "Transfer Options"]
+       ${NS}::checkbutton $w.options.force \
                -text [mc "Force overwrite existing branch (may discard changes)"] \
                -variable push_force
        grid $w.options.force -columnspan 2 -sticky w
-       checkbutton $w.options.thin \
+       ${NS}::checkbutton $w.options.thin \
                -text [mc "Use thin pack (for slow network connections)"] \
                -variable push_thin
        grid $w.options.thin -columnspan 2 -sticky w
-       checkbutton $w.options.tags \
+       ${NS}::checkbutton $w.options.tags \
                -text [mc "Include tags"] \
                -variable push_tags
        grid $w.options.tags -columnspan 2 -sticky w
@@ -191,5 +194,6 @@ proc do_push_anywhere {} {
        bind $w <Key-Escape> "destroy $w"
        bind $w <Key-Return> [list start_push_anywhere_action $w]
        wm title $w [append "[appname] ([reponame]): " [mc "Push"]]
+       wm deiconify $w
        tkwait window $w
 }
index d7f93d045d1a2b3a14d2fdb4907697622b5973a8..db91ab84a56d79be6a5497f885a9181f368b9cf2 100644 (file)
@@ -18,9 +18,9 @@ proc win32_create_lnk {lnk_path lnk_exec lnk_dir} {
        eval [list exec wscript.exe \
                /E:jscript \
                /nologo \
-               [file join $oguilib win32_shortcut.js] \
+               [file nativename [file join $oguilib win32_shortcut.js]] \
                $lnk_path \
-               [file join $oguilib git-gui.ico] \
+               [file nativename [file join $oguilib git-gui.ico]] \
                $lnk_dir \
                $lnk_exec] $lnk_args
 }
index 51abb50bb627d05d702de2d794c47b874c9667ca..44c5ddc8b67cbdb5dbec19e779394745d8f2e6ad 100644 (file)
@@ -7,41 +7,41 @@ msgid ""
 msgstr ""
 "Project-Id-Version: git-gui\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-12-06 20:51+0100\n"
-"PO-Revision-Date: 2008-12-06 21:22+0100\n"
+"POT-Creation-Date: 2010-01-26 22:22+0100\n"
+"PO-Revision-Date: 2010-01-26 22:25+0100\n"
 "Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
 "Language-Team: German\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: git-gui.sh:41 git-gui.sh:634 git-gui.sh:648 git-gui.sh:661 git-gui.sh:744
-#: git-gui.sh:763
+#: git-gui.sh:41 git-gui.sh:793 git-gui.sh:807 git-gui.sh:820 git-gui.sh:903
+#: git-gui.sh:922
 msgid "git-gui: fatal error"
 msgstr "git-gui: Programmfehler"
 
-#: git-gui.sh:593
+#: git-gui.sh:743
 #, tcl-format
 msgid "Invalid font specified in %s:"
 msgstr "Ungültige Zeichensatz-Angabe in %s:"
 
-#: git-gui.sh:620
+#: git-gui.sh:779
 msgid "Main Font"
 msgstr "Programmschriftart"
 
-#: git-gui.sh:621
+#: git-gui.sh:780
 msgid "Diff/Console Font"
 msgstr "Vergleich-Schriftart"
 
-#: git-gui.sh:635
+#: git-gui.sh:794
 msgid "Cannot find git in PATH."
 msgstr "Git kann im PATH nicht gefunden werden."
 
-#: git-gui.sh:662
+#: git-gui.sh:821
 msgid "Cannot parse Git version string:"
 msgstr "Git Versionsangabe kann nicht erkannt werden:"
 
-#: git-gui.sh:680
+#: git-gui.sh:839
 #, tcl-format
 msgid ""
 "Git version cannot be determined.\n"
@@ -60,447 +60,479 @@ msgstr ""
 "\n"
 "Soll angenommen werden, »%s« sei Version 1.5.0?\n"
 
-#: git-gui.sh:918
+#: git-gui.sh:1128
 msgid "Git directory not found:"
 msgstr "Git-Verzeichnis nicht gefunden:"
 
-#: git-gui.sh:925
+#: git-gui.sh:1146
 msgid "Cannot move to top of working directory:"
 msgstr ""
 "Es konnte nicht in das oberste Verzeichnis der Arbeitskopie gewechselt "
 "werden:"
 
-#: git-gui.sh:932
-msgid "Cannot use funny .git directory:"
-msgstr "Unerwartete Struktur des .git Verzeichnis:"
+#: git-gui.sh:1154
+msgid "Cannot use bare repository:"
+msgstr "Leeres Projektarchiv kann nicht benutzt werden:"
 
-#: git-gui.sh:937
+#: git-gui.sh:1162
 msgid "No working directory"
 msgstr "Kein Arbeitsverzeichnis"
 
-#: git-gui.sh:1084 lib/checkout_op.tcl:283
+#: git-gui.sh:1334 lib/checkout_op.tcl:306
 msgid "Refreshing file status..."
 msgstr "Dateistatus aktualisieren..."
 
-#: git-gui.sh:1149
+#: git-gui.sh:1390
 msgid "Scanning for modified files ..."
 msgstr "Nach geänderten Dateien suchen..."
 
-#: git-gui.sh:1367
+#: git-gui.sh:1454
 msgid "Calling prepare-commit-msg hook..."
 msgstr "Aufrufen der Eintragen-Vorbereiten-Kontrolle..."
 
-#: git-gui.sh:1384
+#: git-gui.sh:1471
 msgid "Commit declined by prepare-commit-msg hook."
-msgstr "Eintragen abgelehnt durch Eintragen-Vorbereiten-Kontrolle (»prepare-commit hook«)."
+msgstr ""
+"Eintragen abgelehnt durch Eintragen-Vorbereiten-Kontrolle (»prepare-commit "
+"hook«)."
 
-#: git-gui.sh:1542 lib/browser.tcl:246
+#: git-gui.sh:1629 lib/browser.tcl:246
 msgid "Ready."
 msgstr "Bereit."
 
-#: git-gui.sh:1590
+#: git-gui.sh:1787
+#, tcl-format
+msgid "Displaying only %s of %s files."
+msgstr "Nur %s von %s Dateien werden angezeigt."
+
+#: git-gui.sh:1913
 msgid "Unmodified"
 msgstr "Unverändert"
 
-#: git-gui.sh:1592
+#: git-gui.sh:1915
 msgid "Modified, not staged"
 msgstr "Verändert, nicht bereitgestellt"
 
-#: git-gui.sh:1593 git-gui.sh:1598
+#: git-gui.sh:1916 git-gui.sh:1924
 msgid "Staged for commit"
 msgstr "Bereitgestellt zum Eintragen"
 
-#: git-gui.sh:1594 git-gui.sh:1599
+#: git-gui.sh:1917 git-gui.sh:1925
 msgid "Portions staged for commit"
 msgstr "Teilweise bereitgestellt zum Eintragen"
 
-#: git-gui.sh:1595 git-gui.sh:1600
+#: git-gui.sh:1918 git-gui.sh:1926
 msgid "Staged for commit, missing"
 msgstr "Bereitgestellt zum Eintragen, fehlend"
 
-#: git-gui.sh:1658
+#: git-gui.sh:1920
 msgid "File type changed, not staged"
 msgstr "Dateityp geändert, nicht bereitgestellt"
 
-#: git-gui.sh:1659
+#: git-gui.sh:1921
 msgid "File type changed, staged"
 msgstr "Dateityp geändert, bereitgestellt"
 
-#: git-gui.sh:1661
+#: git-gui.sh:1923
 msgid "Untracked, not staged"
 msgstr "Nicht unter Versionskontrolle, nicht bereitgestellt"
 
-#: git-gui.sh:1602
+#: git-gui.sh:1928
 msgid "Missing"
 msgstr "Fehlend"
 
-#: git-gui.sh:1603
+#: git-gui.sh:1929
 msgid "Staged for removal"
 msgstr "Bereitgestellt zum Löschen"
 
-#: git-gui.sh:1604
+#: git-gui.sh:1930
 msgid "Staged for removal, still present"
 msgstr "Bereitgestellt zum Löschen, trotzdem vorhanden"
 
-#: git-gui.sh:1606 git-gui.sh:1607 git-gui.sh:1608 git-gui.sh:1609
+#: git-gui.sh:1932 git-gui.sh:1933 git-gui.sh:1934 git-gui.sh:1935
+#: git-gui.sh:1936 git-gui.sh:1937
 msgid "Requires merge resolution"
 msgstr "Konfliktauflösung nötig"
 
-#: git-gui.sh:1644
+#: git-gui.sh:1972
 msgid "Starting gitk... please wait..."
 msgstr "Gitk wird gestartet... bitte warten."
 
-#: git-gui.sh:1698
+#: git-gui.sh:1984
 msgid "Couldn't find gitk in PATH"
 msgstr "Gitk kann im PATH nicht gefunden werden."
 
-#: git-gui.sh:1948 lib/choose_repository.tcl:36
+#: git-gui.sh:2043
+msgid "Couldn't find git gui in PATH"
+msgstr "»Git gui« kann im PATH nicht gefunden werden."
+
+#: git-gui.sh:2455 lib/choose_repository.tcl:36
 msgid "Repository"
 msgstr "Projektarchiv"
 
-#: git-gui.sh:1861
+#: git-gui.sh:2456
 msgid "Edit"
 msgstr "Bearbeiten"
 
-#: git-gui.sh:1863 lib/choose_rev.tcl:561
+#: git-gui.sh:2458 lib/choose_rev.tcl:561
 msgid "Branch"
 msgstr "Zweig"
 
-#: git-gui.sh:1866 lib/choose_rev.tcl:548
+#: git-gui.sh:2461 lib/choose_rev.tcl:548
 msgid "Commit@@noun"
 msgstr "Version"
 
-#: git-gui.sh:1869 lib/merge.tcl:120 lib/merge.tcl:149 lib/merge.tcl:167
+#: git-gui.sh:2464 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
 msgid "Merge"
 msgstr "Zusammenführen"
 
-#: git-gui.sh:1870 lib/choose_rev.tcl:557
+#: git-gui.sh:2465 lib/choose_rev.tcl:557
 msgid "Remote"
 msgstr "Andere Archive"
 
-#: git-gui.sh:2293
+#: git-gui.sh:2468
 msgid "Tools"
 msgstr "Werkzeuge"
 
-#: git-gui.sh:2302
+#: git-gui.sh:2477
 msgid "Explore Working Copy"
 msgstr "Arbeitskopie im Dateimanager"
 
-#: git-gui.sh:2247
+#: git-gui.sh:2483
 msgid "Browse Current Branch's Files"
 msgstr "Aktuellen Zweig durchblättern"
 
-#: git-gui.sh:1883
+#: git-gui.sh:2487
 msgid "Browse Branch Files..."
 msgstr "Einen Zweig durchblättern..."
 
-#: git-gui.sh:1888
+#: git-gui.sh:2492
 msgid "Visualize Current Branch's History"
 msgstr "Aktuellen Zweig darstellen"
 
-#: git-gui.sh:1892
+#: git-gui.sh:2496
 msgid "Visualize All Branch History"
 msgstr "Alle Zweige darstellen"
 
-#: git-gui.sh:1899
+#: git-gui.sh:2503
 #, tcl-format
 msgid "Browse %s's Files"
 msgstr "Zweig »%s« durchblättern"
 
-#: git-gui.sh:1901
+#: git-gui.sh:2505
 #, tcl-format
 msgid "Visualize %s's History"
 msgstr "Historie von »%s« darstellen"
 
-#: git-gui.sh:1906 lib/database.tcl:27 lib/database.tcl:67
+#: git-gui.sh:2510 lib/database.tcl:27 lib/database.tcl:67
 msgid "Database Statistics"
 msgstr "Datenbankstatistik"
 
-#: git-gui.sh:1909 lib/database.tcl:34
+#: git-gui.sh:2513 lib/database.tcl:34
 msgid "Compress Database"
 msgstr "Datenbank komprimieren"
 
-#: git-gui.sh:1912
+#: git-gui.sh:2516
 msgid "Verify Database"
 msgstr "Datenbank überprüfen"
 
-#: git-gui.sh:1919 git-gui.sh:1923 git-gui.sh:1927 lib/shortcut.tcl:7
-#: lib/shortcut.tcl:39 lib/shortcut.tcl:71
+#: git-gui.sh:2523 git-gui.sh:2527 git-gui.sh:2531 lib/shortcut.tcl:8
+#: lib/shortcut.tcl:40 lib/shortcut.tcl:72
 msgid "Create Desktop Icon"
 msgstr "Desktop-Icon erstellen"
 
-#: git-gui.sh:1932 lib/choose_repository.tcl:177 lib/choose_repository.tcl:185
+#: git-gui.sh:2539 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191
 msgid "Quit"
 msgstr "Beenden"
 
-#: git-gui.sh:1939
+#: git-gui.sh:2547
 msgid "Undo"
 msgstr "Rückgängig"
 
-#: git-gui.sh:1942
+#: git-gui.sh:2550
 msgid "Redo"
 msgstr "Wiederholen"
 
-#: git-gui.sh:1946 git-gui.sh:2443
+#: git-gui.sh:2554 git-gui.sh:3109
 msgid "Cut"
 msgstr "Ausschneiden"
 
-#: git-gui.sh:1949 git-gui.sh:2446 git-gui.sh:2520 git-gui.sh:2614
+#: git-gui.sh:2557 git-gui.sh:3112 git-gui.sh:3186 git-gui.sh:3259
 #: lib/console.tcl:69
 msgid "Copy"
 msgstr "Kopieren"
 
-#: git-gui.sh:1952 git-gui.sh:2449
+#: git-gui.sh:2560 git-gui.sh:3115
 msgid "Paste"
 msgstr "Einfügen"
 
-#: git-gui.sh:1955 git-gui.sh:2452 lib/branch_delete.tcl:26
+#: git-gui.sh:2563 git-gui.sh:3118 lib/branch_delete.tcl:26
 #: lib/remote_branch_delete.tcl:38
 msgid "Delete"
 msgstr "Löschen"
 
-#: git-gui.sh:1959 git-gui.sh:2456 git-gui.sh:2618 lib/console.tcl:71
+#: git-gui.sh:2567 git-gui.sh:3122 git-gui.sh:3263 lib/console.tcl:71
 msgid "Select All"
 msgstr "Alle auswählen"
 
-#: git-gui.sh:1968
+#: git-gui.sh:2576
 msgid "Create..."
 msgstr "Erstellen..."
 
-#: git-gui.sh:1974
+#: git-gui.sh:2582
 msgid "Checkout..."
 msgstr "Umstellen..."
 
-#: git-gui.sh:1980
+#: git-gui.sh:2588
 msgid "Rename..."
 msgstr "Umbenennen..."
 
-#: git-gui.sh:1985 git-gui.sh:2085
+#: git-gui.sh:2593
 msgid "Delete..."
 msgstr "Löschen..."
 
-#: git-gui.sh:1990
+#: git-gui.sh:2598
 msgid "Reset..."
 msgstr "Zurücksetzen..."
 
-#: git-gui.sh:2372
+#: git-gui.sh:2608
 msgid "Done"
 msgstr "Fertig"
 
-#: git-gui.sh:2374
+#: git-gui.sh:2610
 msgid "Commit@@verb"
 msgstr "Eintragen"
 
-#: git-gui.sh:2383 git-gui.sh:2786
+#: git-gui.sh:2619 git-gui.sh:3050
 msgid "New Commit"
 msgstr "Neue Version"
 
-#: git-gui.sh:2010 git-gui.sh:2396
+#: git-gui.sh:2627 git-gui.sh:3057
 msgid "Amend Last Commit"
 msgstr "Letzte nachbessern"
 
-#: git-gui.sh:2019 git-gui.sh:2356 lib/remote_branch_delete.tcl:99
+#: git-gui.sh:2637 git-gui.sh:3011 lib/remote_branch_delete.tcl:99
 msgid "Rescan"
 msgstr "Neu laden"
 
-#: git-gui.sh:2025
+#: git-gui.sh:2643
 msgid "Stage To Commit"
 msgstr "Zum Eintragen bereitstellen"
 
-#: git-gui.sh:2031
+#: git-gui.sh:2649
 msgid "Stage Changed Files To Commit"
 msgstr "Geänderte Dateien bereitstellen"
 
-#: git-gui.sh:2037
+#: git-gui.sh:2655
 msgid "Unstage From Commit"
 msgstr "Aus der Bereitstellung herausnehmen"
 
-#: git-gui.sh:2042 lib/index.tcl:395
+#: git-gui.sh:2661 lib/index.tcl:412
 msgid "Revert Changes"
 msgstr "Änderungen verwerfen"
 
-#: git-gui.sh:2141 git-gui.sh:2702
+#: git-gui.sh:2669 git-gui.sh:3310 git-gui.sh:3341
 msgid "Show Less Context"
 msgstr "Weniger Zeilen anzeigen"
 
-#: git-gui.sh:2145 git-gui.sh:2706
+#: git-gui.sh:2673 git-gui.sh:3314 git-gui.sh:3345
 msgid "Show More Context"
 msgstr "Mehr Zeilen anzeigen"
 
-#: git-gui.sh:2151 git-gui.sh:2470 git-gui.sh:2569
+#: git-gui.sh:2680 git-gui.sh:3024 git-gui.sh:3133
 msgid "Sign Off"
 msgstr "Abzeichnen"
 
-#: git-gui.sh:2458
+#: git-gui.sh:2696
 msgid "Local Merge..."
 msgstr "Lokales Zusammenführen..."
 
-#: git-gui.sh:2069
+#: git-gui.sh:2701
 msgid "Abort Merge..."
 msgstr "Zusammenführen abbrechen..."
 
-#: git-gui.sh:2475
+#: git-gui.sh:2713 git-gui.sh:2741
 msgid "Add..."
 msgstr "Hinzufügen..."
 
-#: git-gui.sh:2479
+#: git-gui.sh:2717
 msgid "Push..."
 msgstr "Versenden..."
 
-#: git-gui.sh:2483
+#: git-gui.sh:2721
 msgid "Delete Branch..."
 msgstr "Zweig löschen..."
 
-#: git-gui.sh:2493 git-gui.sh:2515 lib/about.tcl:14
-#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:50
-#, tcl-format
-msgid "About %s"
-msgstr "Über %s"
-
-#: git-gui.sh:2099
-msgid "Preferences..."
-msgstr "Einstellungen..."
-
-#: git-gui.sh:2107 git-gui.sh:2639
+#: git-gui.sh:2731 git-gui.sh:3292
 msgid "Options..."
 msgstr "Optionen..."
 
-#: git-gui.sh:2576
+#: git-gui.sh:2742
 msgid "Remove..."
 msgstr "Entfernen..."
 
-#: git-gui.sh:2585 lib/choose_repository.tcl:50
+#: git-gui.sh:2751 lib/choose_repository.tcl:50
 msgid "Help"
 msgstr "Hilfe"
 
-#: git-gui.sh:2154
+#: git-gui.sh:2755 git-gui.sh:2759 lib/about.tcl:14
+#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53
+#, tcl-format
+msgid "About %s"
+msgstr "Über %s"
+
+#: git-gui.sh:2783
 msgid "Online Documentation"
 msgstr "Online-Dokumentation"
 
-#: git-gui.sh:2614 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
+#: git-gui.sh:2786 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
 msgid "Show SSH Key"
 msgstr "SSH-Schlüssel anzeigen"
 
-#: git-gui.sh:2707
+#: git-gui.sh:2893
 #, tcl-format
 msgid "fatal: cannot stat path %s: No such file or directory"
 msgstr ""
 "Fehler: Verzeichnis »%s« kann nicht gelesen werden: Datei oder Verzeichnis "
 "nicht gefunden"
 
-#: git-gui.sh:2271
+#: git-gui.sh:2926
 msgid "Current Branch:"
 msgstr "Aktueller Zweig:"
 
-#: git-gui.sh:2292
+#: git-gui.sh:2947
 msgid "Staged Changes (Will Commit)"
 msgstr "Bereitstellung (zum Eintragen)"
 
-#: git-gui.sh:2312
+#: git-gui.sh:2967
 msgid "Unstaged Changes"
 msgstr "Nicht bereitgestellte Änderungen"
 
-#: git-gui.sh:2362
+#: git-gui.sh:3017
 msgid "Stage Changed"
 msgstr "Alles bereitstellen"
 
-#: git-gui.sh:2378 lib/transport.tcl:93 lib/transport.tcl:182
+#: git-gui.sh:3036 lib/transport.tcl:104 lib/transport.tcl:193
 msgid "Push"
 msgstr "Versenden"
 
-#: git-gui.sh:2408
+#: git-gui.sh:3071
 msgid "Initial Commit Message:"
 msgstr "Erste Versionsbeschreibung:"
 
-#: git-gui.sh:2409
+#: git-gui.sh:3072
 msgid "Amended Commit Message:"
 msgstr "Nachgebesserte Beschreibung:"
 
-#: git-gui.sh:2410
+#: git-gui.sh:3073
 msgid "Amended Initial Commit Message:"
 msgstr "Nachgebesserte erste Beschreibung:"
 
-#: git-gui.sh:2411
+#: git-gui.sh:3074
 msgid "Amended Merge Commit Message:"
 msgstr "Nachgebesserte Zusammenführungs-Beschreibung:"
 
-#: git-gui.sh:2412
+#: git-gui.sh:3075
 msgid "Merge Commit Message:"
 msgstr "Zusammenführungs-Beschreibung:"
 
-#: git-gui.sh:2413
+#: git-gui.sh:3076
 msgid "Commit Message:"
 msgstr "Versionsbeschreibung:"
 
-#: git-gui.sh:2459 git-gui.sh:2622 lib/console.tcl:73
+#: git-gui.sh:3125 git-gui.sh:3267 lib/console.tcl:73
 msgid "Copy All"
 msgstr "Alle kopieren"
 
-#: git-gui.sh:2483 lib/blame.tcl:107
+#: git-gui.sh:3149 lib/blame.tcl:104
 msgid "File:"
 msgstr "Datei:"
 
-#: git-gui.sh:2834
+#: git-gui.sh:3255
 msgid "Refresh"
 msgstr "Aktualisieren"
 
-#: git-gui.sh:2631
+#: git-gui.sh:3276
 msgid "Decrease Font Size"
 msgstr "Schriftgröße verkleinern"
 
-#: git-gui.sh:2635
+#: git-gui.sh:3280
 msgid "Increase Font Size"
 msgstr "Schriftgröße vergrößern"
 
-#: git-gui.sh:3033 lib/blame.tcl:281
+#: git-gui.sh:3288 lib/blame.tcl:281
 msgid "Encoding"
 msgstr "Zeichenkodierung"
 
-#: git-gui.sh:3044
+#: git-gui.sh:3299
 msgid "Apply/Reverse Hunk"
 msgstr "Kontext anwenden/umkehren"
 
-#: git-gui.sh:2875
+#: git-gui.sh:3304
 msgid "Apply/Reverse Line"
 msgstr "Zeile anwenden/umkehren"
 
-#: git-gui.sh:2885
+#: git-gui.sh:3323
 msgid "Run Merge Tool"
 msgstr "Zusammenführungswerkzeug"
 
-#: git-gui.sh:2890
+#: git-gui.sh:3328
 msgid "Use Remote Version"
 msgstr "Entfernte Version benutzen"
 
-#: git-gui.sh:2894
+#: git-gui.sh:3332
 msgid "Use Local Version"
 msgstr "Lokale Version benutzen"
 
-#: git-gui.sh:2898
+#: git-gui.sh:3336
 msgid "Revert To Base"
 msgstr "Ursprüngliche Version benutzen"
 
-#: git-gui.sh:3091
+#: git-gui.sh:3354
+msgid "Visualize These Changes In The Submodule"
+msgstr "Diese Änderungen im Untermodul darstellen"
+
+#: git-gui.sh:3358
+msgid "Visualize Current Branch History In The Submodule"
+msgstr "Aktuellen Zweig im Untermodul darstellen"
+
+#: git-gui.sh:3362
+msgid "Visualize All Branch History In The Submodule"
+msgstr "Alle Zweige im Untermodul darstellen"
+
+#: git-gui.sh:3367
+msgid "Start git gui In The Submodule"
+msgstr "Git gui im Untermodul starten"
+
+#: git-gui.sh:3389
 msgid "Unstage Hunk From Commit"
 msgstr "Kontext aus Bereitstellung herausnehmen"
 
-#: git-gui.sh:2748
+#: git-gui.sh:3391
+msgid "Unstage Lines From Commit"
+msgstr "Zeilen aus der Bereitstellung herausnehmen"
+
+#: git-gui.sh:3393
 msgid "Unstage Line From Commit"
 msgstr "Zeile aus der Bereitstellung herausnehmen"
 
-#: git-gui.sh:2750
+#: git-gui.sh:3396
 msgid "Stage Hunk For Commit"
 msgstr "Kontext zur Bereitstellung hinzufügen"
 
-#: git-gui.sh:2751
+#: git-gui.sh:3398
+msgid "Stage Lines For Commit"
+msgstr "Zeilen zur Bereitstellung hinzufügen"
+
+#: git-gui.sh:3400
 msgid "Stage Line For Commit"
 msgstr "Zeile zur Bereitstellung hinzufügen"
 
-#: git-gui.sh:2771
+#: git-gui.sh:3424
 msgid "Initializing..."
 msgstr "Initialisieren..."
 
-#: git-gui.sh:2762
+#: git-gui.sh:3541
 #, tcl-format
 msgid ""
 "Possible environment issues exist.\n"
@@ -516,7 +548,7 @@ msgstr ""
 "von %s an Git weitergegeben werden:\n"
 "\n"
 
-#: git-gui.sh:2792
+#: git-gui.sh:3570
 msgid ""
 "\n"
 "This is due to a known issue with the\n"
@@ -526,7 +558,7 @@ msgstr ""
 "Dies ist ein bekanntes Problem der Tcl-Version, die\n"
 "in Cygwin mitgeliefert wird."
 
-#: git-gui.sh:2797
+#: git-gui.sh:3575
 #, tcl-format
 msgid ""
 "\n"
@@ -546,15 +578,15 @@ msgstr ""
 msgid "git-gui - a graphical user interface for Git."
 msgstr "git-gui - eine grafische Oberfläche für Git."
 
-#: lib/blame.tcl:77
+#: lib/blame.tcl:72
 msgid "File Viewer"
 msgstr "Datei-Browser"
 
-#: lib/blame.tcl:81
+#: lib/blame.tcl:78
 msgid "Commit:"
 msgstr "Version:"
 
-#: lib/blame.tcl:264
+#: lib/blame.tcl:271
 msgid "Copy Commit"
 msgstr "Version kopieren"
 
@@ -566,88 +598,88 @@ msgstr "Text suchen..."
 msgid "Do Full Copy Detection"
 msgstr "Volle Kopie-Erkennung"
 
-#: lib/blame.tcl:263
+#: lib/blame.tcl:288
 msgid "Show History Context"
 msgstr "Historien-Kontext anzeigen"
 
-#: lib/blame.tcl:266
+#: lib/blame.tcl:291
 msgid "Blame Parent Commit"
 msgstr "Elternversion annotieren"
 
-#: lib/blame.tcl:394
+#: lib/blame.tcl:450
 #, tcl-format
 msgid "Reading %s..."
 msgstr "%s lesen..."
 
-#: lib/blame.tcl:488
+#: lib/blame.tcl:557
 msgid "Loading copy/move tracking annotations..."
 msgstr "Annotierungen für Kopieren/Verschieben werden geladen..."
 
-#: lib/blame.tcl:508
+#: lib/blame.tcl:577
 msgid "lines annotated"
 msgstr "Zeilen annotiert"
 
-#: lib/blame.tcl:689
+#: lib/blame.tcl:769
 msgid "Loading original location annotations..."
 msgstr "Annotierungen für ursprünglichen Ort werden geladen..."
 
-#: lib/blame.tcl:692
+#: lib/blame.tcl:772
 msgid "Annotation complete."
 msgstr "Annotierung vollständig."
 
-#: lib/blame.tcl:737
+#: lib/blame.tcl:802
 msgid "Busy"
 msgstr "Verarbeitung läuft"
 
-#: lib/blame.tcl:738
+#: lib/blame.tcl:803
 msgid "Annotation process is already running."
 msgstr "Annotierung läuft bereits."
 
-#: lib/blame.tcl:777
+#: lib/blame.tcl:842
 msgid "Running thorough copy detection..."
 msgstr "Intensive Kopie-Erkennung läuft..."
 
-#: lib/blame.tcl:827
+#: lib/blame.tcl:910
 msgid "Loading annotation..."
 msgstr "Annotierung laden..."
 
-#: lib/blame.tcl:802
+#: lib/blame.tcl:963
 msgid "Author:"
 msgstr "Autor:"
 
-#: lib/blame.tcl:806
+#: lib/blame.tcl:967
 msgid "Committer:"
 msgstr "Eintragender:"
 
-#: lib/blame.tcl:811
+#: lib/blame.tcl:972
 msgid "Original File:"
 msgstr "Ursprüngliche Datei:"
 
-#: lib/blame.tcl:1021
+#: lib/blame.tcl:1020
 msgid "Cannot find HEAD commit:"
 msgstr "Zweigspitze (»HEAD«) kann nicht gefunden werden:"
 
-#: lib/blame.tcl:1076
+#: lib/blame.tcl:1075
 msgid "Cannot find parent commit:"
 msgstr "Elternversion kann nicht gefunden werden:"
 
-#: lib/blame.tcl:1001
+#: lib/blame.tcl:1090
 msgid "Unable to display parent"
 msgstr "Elternversion kann nicht angezeigt werden"
 
-#: lib/blame.tcl:1002 lib/diff.tcl:191
+#: lib/blame.tcl:1091 lib/diff.tcl:320
 msgid "Error loading diff:"
 msgstr "Fehler beim Laden des Vergleichs:"
 
-#: lib/blame.tcl:1142
+#: lib/blame.tcl:1231
 msgid "Originally By:"
 msgstr "Ursprünglich von:"
 
-#: lib/blame.tcl:931
+#: lib/blame.tcl:1237
 msgid "In File:"
 msgstr "In Datei:"
 
-#: lib/blame.tcl:936
+#: lib/blame.tcl:1242
 msgid "Copied Or Moved Here By:"
 msgstr "Kopiert oder verschoben durch:"
 
@@ -661,16 +693,18 @@ msgstr "Umstellen"
 
 #: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
 #: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282
-#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:171
-#: lib/option.tcl:103 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97
+#: lib/checkout_op.tcl:579 lib/choose_font.tcl:43 lib/merge.tcl:172
+#: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42
+#: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352
+#: lib/transport.tcl:108
 msgid "Cancel"
 msgstr "Abbrechen"
 
-#: lib/branch_checkout.tcl:32 lib/browser.tcl:287
+#: lib/branch_checkout.tcl:32 lib/browser.tcl:287 lib/tools_dlg.tcl:328
 msgid "Revision"
 msgstr "Version"
 
-#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:242
+#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:280
 msgid "Options"
 msgstr "Optionen"
 
@@ -690,7 +724,7 @@ msgstr "Zweig erstellen"
 msgid "Create New Branch"
 msgstr "Neuen Zweig erstellen"
 
-#: lib/branch_create.tcl:31 lib/choose_repository.tcl:371
+#: lib/branch_create.tcl:31 lib/choose_repository.tcl:381
 msgid "Create"
 msgstr "Erstellen"
 
@@ -698,7 +732,7 @@ msgstr "Erstellen"
 msgid "Branch Name"
 msgstr "Zweigname"
 
-#: lib/branch_create.tcl:43
+#: lib/branch_create.tcl:43 lib/remote_add.tcl:39 lib/tools_dlg.tcl:50
 msgid "Name:"
 msgstr "Name:"
 
@@ -722,7 +756,7 @@ msgstr "Nein"
 msgid "Fast Forward Only"
 msgstr "Nur Schnellzusammenführung"
 
-#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514
+#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571
 msgid "Reset"
 msgstr "Zurücksetzen"
 
@@ -764,15 +798,26 @@ msgstr "Lokale Zweige"
 msgid "Delete Only If Merged Into"
 msgstr "Nur löschen, wenn zusammengeführt nach"
 
-#: lib/branch_delete.tcl:54
-msgid "Always (Do not perform merge test.)"
-msgstr "Immer (ohne Zusammenführungstest)"
+#: lib/branch_delete.tcl:54 lib/remote_branch_delete.tcl:119
+msgid "Always (Do not perform merge checks)"
+msgstr "Immer (Keine Zusammenführungsprüfung)"
 
 #: lib/branch_delete.tcl:103
 #, tcl-format
 msgid "The following branches are not completely merged into %s:"
 msgstr "Folgende Zweige sind noch nicht mit »%s« zusammengeführt:"
 
+#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:217
+msgid ""
+"Recovering deleted branches is difficult.\n"
+"\n"
+"Delete the selected branches?"
+msgstr ""
+"Das Wiederherstellen von gelöschten Zweigen ist nur mit größerem Aufwand "
+"möglich.\n"
+"\n"
+"Sollen die ausgewählten Zweige gelöscht werden?"
+
 #: lib/branch_delete.tcl:141
 #, tcl-format
 msgid ""
@@ -802,7 +847,7 @@ msgstr "Neuer Name:"
 msgid "Please select a branch to rename."
 msgstr "Bitte wählen Sie einen Zweig zum umbenennen."
 
-#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179
+#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202
 #, tcl-format
 msgid "Branch '%s' already exists."
 msgstr "Zweig »%s« existiert bereits."
@@ -833,37 +878,38 @@ msgstr "[Nach oben]"
 msgid "Browse Branch Files"
 msgstr "Dateien des Zweigs durchblättern"
 
-#: lib/browser.tcl:278 lib/choose_repository.tcl:387
-#: lib/choose_repository.tcl:474 lib/choose_repository.tcl:484
-#: lib/choose_repository.tcl:987
+#: lib/browser.tcl:278 lib/choose_repository.tcl:398
+#: lib/choose_repository.tcl:486 lib/choose_repository.tcl:497
+#: lib/choose_repository.tcl:1028
 msgid "Browse"
 msgstr "Blättern"
 
-#: lib/checkout_op.tcl:79
+#: lib/checkout_op.tcl:85
 #, tcl-format
 msgid "Fetching %s from %s"
 msgstr "Änderungen »%s« von »%s« anfordern"
 
-#: lib/checkout_op.tcl:127
+#: lib/checkout_op.tcl:133
 #, tcl-format
 msgid "fatal: Cannot resolve %s"
 msgstr "Fehler: »%s« kann nicht als Zweig oder Version erkannt werden"
 
-#: lib/checkout_op.tcl:140 lib/console.tcl:81 lib/database.tcl:31
+#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:31
+#: lib/sshkey.tcl:53
 msgid "Close"
 msgstr "Schließen"
 
-#: lib/checkout_op.tcl:169
+#: lib/checkout_op.tcl:175
 #, tcl-format
 msgid "Branch '%s' does not exist."
 msgstr "Zweig »%s« existiert nicht."
 
-#: lib/checkout_op.tcl:193
+#: lib/checkout_op.tcl:194
 #, tcl-format
 msgid "Failed to configure simplified git-pull for '%s'."
 msgstr "Fehler beim Einrichten der vereinfachten git-pull für »%s«."
 
-#: lib/checkout_op.tcl:228
+#: lib/checkout_op.tcl:229
 #, tcl-format
 msgid ""
 "Branch '%s' already exists.\n"
@@ -876,21 +922,21 @@ msgstr ""
 "Zweig kann nicht mit »%s« schnellzusammengeführt werden. Reguläres "
 "Zusammenführen ist notwendig."
 
-#: lib/checkout_op.tcl:220
+#: lib/checkout_op.tcl:243
 #, tcl-format
 msgid "Merge strategy '%s' not supported."
 msgstr "Zusammenführungsmethode »%s« nicht unterstützt."
 
-#: lib/checkout_op.tcl:239
+#: lib/checkout_op.tcl:262
 #, tcl-format
 msgid "Failed to update '%s'."
 msgstr "Aktualisieren von »%s« fehlgeschlagen."
 
-#: lib/checkout_op.tcl:251
+#: lib/checkout_op.tcl:274
 msgid "Staging area (index) is already locked."
 msgstr "Bereitstellung (»index«) ist zur Bearbeitung gesperrt (»locked«)."
 
-#: lib/checkout_op.tcl:266
+#: lib/checkout_op.tcl:289
 msgid ""
 "Last scanned state does not match repository state.\n"
 "\n"
@@ -906,32 +952,32 @@ msgstr ""
 "\n"
 "Es wird gleich neu geladen.\n"
 
-#: lib/checkout_op.tcl:322
+#: lib/checkout_op.tcl:345
 #, tcl-format
 msgid "Updating working directory to '%s'..."
 msgstr "Arbeitskopie umstellen auf »%s«..."
 
-#: lib/checkout_op.tcl:323
+#: lib/checkout_op.tcl:346
 msgid "files checked out"
 msgstr "Dateien aktualisiert"
 
-#: lib/checkout_op.tcl:353
+#: lib/checkout_op.tcl:376
 #, tcl-format
 msgid "Aborted checkout of '%s' (file level merging is required)."
 msgstr ""
 "Auf Zweig »%s« umstellen abgebrochen (Zusammenführen der Dateien ist "
 "notwendig)."
 
-#: lib/checkout_op.tcl:354
+#: lib/checkout_op.tcl:377
 msgid "File level merge required."
 msgstr "Zusammenführen der Dateien ist notwendig."
 
-#: lib/checkout_op.tcl:358
+#: lib/checkout_op.tcl:381
 #, tcl-format
 msgid "Staying on branch '%s'."
 msgstr "Es wird auf Zweig »%s« verblieben."
 
-#: lib/checkout_op.tcl:429
+#: lib/checkout_op.tcl:452
 msgid ""
 "You are no longer on a local branch.\n"
 "\n"
@@ -943,32 +989,32 @@ msgstr ""
 "Wenn Sie auf einem Zweig arbeiten möchten, erstellen Sie bitte jetzt einen "
 "Zweig mit der Auswahl »Abgetrennte Arbeitskopie-Version«."
 
-#: lib/checkout_op.tcl:446 lib/checkout_op.tcl:450
+#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507
 #, tcl-format
 msgid "Checked out '%s'."
 msgstr "Umgestellt auf »%s«."
 
-#: lib/checkout_op.tcl:478
+#: lib/checkout_op.tcl:535
 #, tcl-format
 msgid "Resetting '%s' to '%s' will lose the following commits:"
 msgstr "Zurücksetzen von »%s« nach »%s« wird folgende Versionen verwerfen:"
 
-#: lib/checkout_op.tcl:500
+#: lib/checkout_op.tcl:557
 msgid "Recovering lost commits may not be easy."
 msgstr ""
 "Verworfene Versionen können nur mit größerem Aufwand wiederhergestellt "
 "werden."
 
-#: lib/checkout_op.tcl:505
+#: lib/checkout_op.tcl:562
 #, tcl-format
 msgid "Reset '%s'?"
 msgstr "»%s« zurücksetzen?"
 
-#: lib/checkout_op.tcl:510 lib/merge.tcl:163
+#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:343
 msgid "Visualize"
 msgstr "Darstellen"
 
-#: lib/checkout_op.tcl:578
+#: lib/checkout_op.tcl:635
 #, tcl-format
 msgid ""
 "Failed to set current branch.\n"
@@ -1014,231 +1060,231 @@ msgstr ""
 msgid "Git Gui"
 msgstr "Git Gui"
 
-#: lib/choose_repository.tcl:81 lib/choose_repository.tcl:376
+#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:386
 msgid "Create New Repository"
 msgstr "Neues Projektarchiv"
 
-#: lib/choose_repository.tcl:87
+#: lib/choose_repository.tcl:93
 msgid "New..."
 msgstr "Neu..."
 
-#: lib/choose_repository.tcl:94 lib/choose_repository.tcl:460
+#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:471
 msgid "Clone Existing Repository"
 msgstr "Projektarchiv klonen"
 
-#: lib/choose_repository.tcl:100
+#: lib/choose_repository.tcl:106
 msgid "Clone..."
 msgstr "Klonen..."
 
-#: lib/choose_repository.tcl:107 lib/choose_repository.tcl:976
+#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:1016
 msgid "Open Existing Repository"
 msgstr "Projektarchiv öffnen"
 
-#: lib/choose_repository.tcl:113
+#: lib/choose_repository.tcl:119
 msgid "Open..."
 msgstr "Öffnen..."
 
-#: lib/choose_repository.tcl:126
+#: lib/choose_repository.tcl:132
 msgid "Recent Repositories"
 msgstr "Zuletzt benutzte Projektarchive"
 
-#: lib/choose_repository.tcl:132
+#: lib/choose_repository.tcl:138
 msgid "Open Recent Repository:"
 msgstr "Zuletzt benutztes Projektarchiv öffnen:"
 
-#: lib/choose_repository.tcl:296 lib/choose_repository.tcl:303
-#: lib/choose_repository.tcl:310
+#: lib/choose_repository.tcl:306 lib/choose_repository.tcl:313
+#: lib/choose_repository.tcl:320
 #, tcl-format
 msgid "Failed to create repository %s:"
 msgstr "Projektarchiv »%s« konnte nicht erstellt werden:"
 
-#: lib/choose_repository.tcl:387
+#: lib/choose_repository.tcl:391
 msgid "Directory:"
 msgstr "Verzeichnis:"
 
-#: lib/choose_repository.tcl:412 lib/choose_repository.tcl:537
-#: lib/choose_repository.tcl:1011
+#: lib/choose_repository.tcl:423 lib/choose_repository.tcl:550
+#: lib/choose_repository.tcl:1052
 msgid "Git Repository"
 msgstr "Git Projektarchiv"
 
-#: lib/choose_repository.tcl:442
+#: lib/choose_repository.tcl:448
 #, tcl-format
 msgid "Directory %s already exists."
 msgstr "Verzeichnis »%s« existiert bereits."
 
-#: lib/choose_repository.tcl:446
+#: lib/choose_repository.tcl:452
 #, tcl-format
 msgid "File %s already exists."
 msgstr "Datei »%s« existiert bereits."
 
-#: lib/choose_repository.tcl:455
+#: lib/choose_repository.tcl:466
 msgid "Clone"
 msgstr "Klonen"
 
-#: lib/choose_repository.tcl:473
+#: lib/choose_repository.tcl:479
 msgid "Source Location:"
 msgstr "Herkunft:"
 
-#: lib/choose_repository.tcl:484
+#: lib/choose_repository.tcl:490
 msgid "Target Directory:"
 msgstr "Zielverzeichnis:"
 
-#: lib/choose_repository.tcl:490
+#: lib/choose_repository.tcl:502
 msgid "Clone Type:"
 msgstr "Art des Klonens:"
 
-#: lib/choose_repository.tcl:495
+#: lib/choose_repository.tcl:508
 msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
 msgstr "Standard (schnell, teilweise redundant, Hardlinks)"
 
-#: lib/choose_repository.tcl:501
+#: lib/choose_repository.tcl:514
 msgid "Full Copy (Slower, Redundant Backup)"
 msgstr "Alles kopieren (langsamer, volle Redundanz)"
 
-#: lib/choose_repository.tcl:507
+#: lib/choose_repository.tcl:520
 msgid "Shared (Fastest, Not Recommended, No Backup)"
 msgstr "Verknüpft (schnell, nicht empfohlen, kein Backup)"
 
-#: lib/choose_repository.tcl:543 lib/choose_repository.tcl:590
-#: lib/choose_repository.tcl:736 lib/choose_repository.tcl:806
-#: lib/choose_repository.tcl:1017 lib/choose_repository.tcl:1025
+#: lib/choose_repository.tcl:556 lib/choose_repository.tcl:603
+#: lib/choose_repository.tcl:749 lib/choose_repository.tcl:819
+#: lib/choose_repository.tcl:1058 lib/choose_repository.tcl:1066
 #, tcl-format
 msgid "Not a Git repository: %s"
 msgstr "Kein Git-Projektarchiv in »%s« gefunden."
 
-#: lib/choose_repository.tcl:579
+#: lib/choose_repository.tcl:592
 msgid "Standard only available for local repository."
 msgstr "Standard ist nur für lokale Projektarchive verfügbar."
 
-#: lib/choose_repository.tcl:583
+#: lib/choose_repository.tcl:596
 msgid "Shared only available for local repository."
 msgstr "Verknüpft ist nur für lokale Projektarchive verfügbar."
 
-#: lib/choose_repository.tcl:604
+#: lib/choose_repository.tcl:617
 #, tcl-format
 msgid "Location %s already exists."
 msgstr "Projektarchiv »%s« existiert bereits."
 
-#: lib/choose_repository.tcl:615
+#: lib/choose_repository.tcl:628
 msgid "Failed to configure origin"
 msgstr "Der Ursprungsort konnte nicht eingerichtet werden"
 
-#: lib/choose_repository.tcl:627
+#: lib/choose_repository.tcl:640
 msgid "Counting objects"
 msgstr "Objekte werden gezählt"
 
-#: lib/choose_repository.tcl:628
+#: lib/choose_repository.tcl:641
 msgid "buckets"
 msgstr "Buckets"
 
-#: lib/choose_repository.tcl:652
+#: lib/choose_repository.tcl:665
 #, tcl-format
 msgid "Unable to copy objects/info/alternates: %s"
 msgstr "Kopien von Objekten/Info/Alternates konnten nicht erstellt werden: %s"
 
-#: lib/choose_repository.tcl:688
+#: lib/choose_repository.tcl:701
 #, tcl-format
 msgid "Nothing to clone from %s."
 msgstr "Von »%s« konnte nichts geklont werden."
 
-#: lib/choose_repository.tcl:690 lib/choose_repository.tcl:904
-#: lib/choose_repository.tcl:916
+#: lib/choose_repository.tcl:703 lib/choose_repository.tcl:917
+#: lib/choose_repository.tcl:929
 msgid "The 'master' branch has not been initialized."
 msgstr "Der »master«-Zweig wurde noch nicht initialisiert."
 
-#: lib/choose_repository.tcl:703
+#: lib/choose_repository.tcl:716
 msgid "Hardlinks are unavailable.  Falling back to copying."
 msgstr "Hardlinks nicht verfügbar. Stattdessen wird kopiert."
 
-#: lib/choose_repository.tcl:715
+#: lib/choose_repository.tcl:728
 #, tcl-format
 msgid "Cloning from %s"
 msgstr "Kopieren von »%s«"
 
-#: lib/choose_repository.tcl:746
+#: lib/choose_repository.tcl:759
 msgid "Copying objects"
 msgstr "Objektdatenbank kopieren"
 
-#: lib/choose_repository.tcl:747
+#: lib/choose_repository.tcl:760
 msgid "KiB"
 msgstr "KB"
 
-#: lib/choose_repository.tcl:771
+#: lib/choose_repository.tcl:784
 #, tcl-format
 msgid "Unable to copy object: %s"
 msgstr "Objekt kann nicht kopiert werden: %s"
 
-#: lib/choose_repository.tcl:781
+#: lib/choose_repository.tcl:794
 msgid "Linking objects"
 msgstr "Objekte verlinken"
 
-#: lib/choose_repository.tcl:782
+#: lib/choose_repository.tcl:795
 msgid "objects"
 msgstr "Objekte"
 
-#: lib/choose_repository.tcl:790
+#: lib/choose_repository.tcl:803
 #, tcl-format
 msgid "Unable to hardlink object: %s"
 msgstr "Für Objekt konnte kein Hardlink erstellt werden: %s"
 
-#: lib/choose_repository.tcl:845
+#: lib/choose_repository.tcl:858
 msgid "Cannot fetch branches and objects.  See console output for details."
 msgstr ""
 "Zweige und Objekte konnten nicht angefordert werden.  Kontrollieren Sie die "
 "Ausgaben auf der Konsole für weitere Angaben."
 
-#: lib/choose_repository.tcl:856
+#: lib/choose_repository.tcl:869
 msgid "Cannot fetch tags.  See console output for details."
 msgstr ""
 "Markierungen konnten nicht angefordert werden.  Kontrollieren Sie die "
 "Ausgaben auf der Konsole für weitere Angaben."
 
-#: lib/choose_repository.tcl:880
+#: lib/choose_repository.tcl:893
 msgid "Cannot determine HEAD.  See console output for details."
 msgstr ""
 "Die Zweigspitze (HEAD) konnte nicht gefunden werden.  Kontrollieren Sie die "
 "Ausgaben auf der Konsole für weitere Angaben."
 
-#: lib/choose_repository.tcl:889
+#: lib/choose_repository.tcl:902
 #, tcl-format
 msgid "Unable to cleanup %s"
 msgstr "Verzeichnis »%s« kann nicht aufgeräumt werden."
 
-#: lib/choose_repository.tcl:895
+#: lib/choose_repository.tcl:908
 msgid "Clone failed."
 msgstr "Klonen fehlgeschlagen."
 
-#: lib/choose_repository.tcl:902
+#: lib/choose_repository.tcl:915
 msgid "No default branch obtained."
 msgstr "Kein voreingestellter Zweig gefunden."
 
-#: lib/choose_repository.tcl:913
+#: lib/choose_repository.tcl:926
 #, tcl-format
 msgid "Cannot resolve %s as a commit."
 msgstr "»%s« wurde nicht als Version gefunden."
 
-#: lib/choose_repository.tcl:925
+#: lib/choose_repository.tcl:938
 msgid "Creating working directory"
 msgstr "Arbeitskopie erstellen"
 
-#: lib/choose_repository.tcl:926 lib/index.tcl:65 lib/index.tcl:127
-#: lib/index.tcl:193
+#: lib/choose_repository.tcl:939 lib/index.tcl:67 lib/index.tcl:130
+#: lib/index.tcl:198
 msgid "files"
 msgstr "Dateien"
 
-#: lib/choose_repository.tcl:955
+#: lib/choose_repository.tcl:968
 msgid "Initial file checkout failed."
 msgstr "Erstellen der Arbeitskopie fehlgeschlagen."
 
-#: lib/choose_repository.tcl:971
+#: lib/choose_repository.tcl:1011
 msgid "Open"
 msgstr "Öffnen"
 
-#: lib/choose_repository.tcl:981
+#: lib/choose_repository.tcl:1021
 msgid "Repository:"
 msgstr "Projektarchiv:"
 
-#: lib/choose_repository.tcl:1031
+#: lib/choose_repository.tcl:1072
 #, tcl-format
 msgid "Failed to open repository %s:"
 msgstr "Projektarchiv »%s« konnte nicht geöffnet werden."
@@ -1311,19 +1357,24 @@ msgstr ""
 "unfertige Zusammenführung existiert. Dazu müssen Sie die Zusammenführung "
 "beenden oder abbrechen.\n"
 
-#: lib/commit.tcl:49
+#: lib/commit.tcl:48
 msgid "Error loading commit data for amend:"
 msgstr "Fehler beim Laden der Versionsdaten für Nachbessern:"
 
-#: lib/commit.tcl:76
+#: lib/commit.tcl:75
 msgid "Unable to obtain your identity:"
 msgstr "Benutzername konnte nicht bestimmt werden:"
 
-#: lib/commit.tcl:81
+#: lib/commit.tcl:80
 msgid "Invalid GIT_COMMITTER_IDENT:"
 msgstr "Ungültiger Wert von GIT_COMMITTER_INDENT:"
 
-#: lib/commit.tcl:133
+#: lib/commit.tcl:129
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr "Warning: Tcl/Tk unterstützt die Zeichencodierung »%s« nicht."
+
+#: lib/commit.tcl:149
 msgid ""
 "Last scanned state does not match repository state.\n"
 "\n"
@@ -1339,7 +1390,7 @@ msgstr ""
 "\n"
 "Es wird gleich neu geladen.\n"
 
-#: lib/commit.tcl:154
+#: lib/commit.tcl:172
 #, tcl-format
 msgid ""
 "Unmerged files cannot be committed.\n"
@@ -1352,7 +1403,7 @@ msgstr ""
 "Die Datei »%s« hat noch nicht aufgelöste Zusammenführungs-Konflikte. Sie "
 "müssen diese Konflikte auflösen, bevor Sie eintragen können.\n"
 
-#: lib/commit.tcl:162
+#: lib/commit.tcl:180
 #, tcl-format
 msgid ""
 "Unknown file state %s detected.\n"
@@ -1363,7 +1414,7 @@ msgstr ""
 "\n"
 "Datei »%s« kann nicht eingetragen werden.\n"
 
-#: lib/commit.tcl:170
+#: lib/commit.tcl:188
 msgid ""
 "No changes to commit.\n"
 "\n"
@@ -1373,7 +1424,7 @@ msgstr ""
 "\n"
 "Sie müssen mindestens eine Datei bereitstellen, bevor Sie eintragen können.\n"
 
-#: lib/commit.tcl:183
+#: lib/commit.tcl:203
 msgid ""
 "Please supply a commit message.\n"
 "\n"
@@ -1393,47 +1444,42 @@ msgstr ""
 "\n"
 "- Rest: Eine ausführliche Beschreibung, warum diese Änderung hilfreich ist.\n"
 
-#: lib/commit.tcl:207
-#, tcl-format
-msgid "warning: Tcl does not support encoding '%s'."
-msgstr "Warning: Tcl/Tk unterstützt die Zeichencodierung »%s« nicht."
-
-#: lib/commit.tcl:221
+#: lib/commit.tcl:234
 msgid "Calling pre-commit hook..."
 msgstr "Aufrufen der Vor-Eintragen-Kontrolle..."
 
-#: lib/commit.tcl:236
+#: lib/commit.tcl:249
 msgid "Commit declined by pre-commit hook."
 msgstr "Eintragen abgelehnt durch Vor-Eintragen-Kontrolle (»pre-commit hook«)."
 
-#: lib/commit.tcl:259
+#: lib/commit.tcl:272
 msgid "Calling commit-msg hook..."
 msgstr "Aufrufen der Versionsbeschreibungs-Kontrolle..."
 
-#: lib/commit.tcl:274
+#: lib/commit.tcl:287
 msgid "Commit declined by commit-msg hook."
 msgstr ""
 "Eintragen abgelehnt durch Versionsbeschreibungs-Kontrolle (»commit-message "
 "hook«)."
 
-#: lib/commit.tcl:287
+#: lib/commit.tcl:300
 msgid "Committing changes..."
 msgstr "Änderungen eintragen..."
 
-#: lib/commit.tcl:303
+#: lib/commit.tcl:316
 msgid "write-tree failed:"
 msgstr "write-tree fehlgeschlagen:"
 
-#: lib/commit.tcl:304 lib/commit.tcl:348 lib/commit.tcl:368
+#: lib/commit.tcl:317 lib/commit.tcl:361 lib/commit.tcl:382
 msgid "Commit failed."
 msgstr "Eintragen fehlgeschlagen."
 
-#: lib/commit.tcl:321
+#: lib/commit.tcl:334
 #, tcl-format
 msgid "Commit %s appears to be corrupt"
 msgstr "Version »%s« scheint beschädigt zu sein"
 
-#: lib/commit.tcl:326
+#: lib/commit.tcl:339
 msgid ""
 "No changes to commit.\n"
 "\n"
@@ -1448,19 +1494,19 @@ msgstr ""
 "\n"
 "Das Arbeitsverzeichnis wird daher jetzt neu geladen.\n"
 
-#: lib/commit.tcl:333
+#: lib/commit.tcl:346
 msgid "No changes to commit."
 msgstr "Keine Änderungen, die eingetragen werden können."
 
-#: lib/commit.tcl:347
+#: lib/commit.tcl:360
 msgid "commit-tree failed:"
 msgstr "commit-tree fehlgeschlagen:"
 
-#: lib/commit.tcl:367
+#: lib/commit.tcl:381
 msgid "update-ref failed:"
 msgstr "update-ref fehlgeschlagen:"
 
-#: lib/commit.tcl:454
+#: lib/commit.tcl:469
 #, tcl-format
 msgid "Created commit %s: %s"
 msgstr "Version %s übertragen: %s"
@@ -1513,21 +1559,19 @@ msgstr "Objektdatenbank komprimieren"
 msgid "Verifying the object database with fsck-objects"
 msgstr "Die Objektdatenbank durch »fsck-objects« überprüfen lassen"
 
-#: lib/database.tcl:108
+#: lib/database.tcl:107
 #, tcl-format
 msgid ""
 "This repository currently has approximately %i loose objects.\n"
 "\n"
 "To maintain optimal performance it is strongly recommended that you compress "
-"the database when more than %i loose objects exist.\n"
+"the database.\n"
 "\n"
 "Compress the database now?"
 msgstr ""
 "Dieses Projektarchiv enthält ungefähr %i nicht verknüpfte Objekte.\n"
 "\n"
-"Für eine optimale Performance wird empfohlen, die Datenbank des "
-"Projektarchivs zu komprimieren, sobald mehr als %i nicht verknüpfte Objekte "
-"vorliegen.\n"
+"Für eine optimale Performance wird empfohlen, die Datenbank des Projektarchivs zu komprimieren.\n"
 "\n"
 "Soll die Datenbank jetzt komprimiert werden?"
 
@@ -1536,7 +1580,7 @@ msgstr ""
 msgid "Invalid date from Git: %s"
 msgstr "Ungültiges Datum von Git: %s"
 
-#: lib/diff.tcl:42
+#: lib/diff.tcl:64
 #, tcl-format
 msgid ""
 "No differences detected.\n"
@@ -1551,19 +1595,19 @@ msgid ""
 msgstr ""
 "Keine Änderungen feststellbar.\n"
 "\n"
-"»%s« enthält keine Änderungen. Zwar wurde das Änderungsdatum dieser Datei "
-"von einem anderen Programm modifiziert, aber der Inhalt der Datei ist "
+"»%s« enthält keine Änderungen. Zwar wurde das Änderungsdatum dieser Datei von "
+"einem anderen Programm modifiziert, aber der Inhalt der Datei ist "
 "unverändert.\n"
 "\n"
 "Das Arbeitsverzeichnis wird jetzt neu geladen, um diese Änderung bei allen "
 "Dateien zu prüfen."
 
-#: lib/diff.tcl:81
+#: lib/diff.tcl:104
 #, tcl-format
 msgid "Loading diff of %s..."
 msgstr "Vergleich von »%s« laden..."
 
-#: lib/diff.tcl:120
+#: lib/diff.tcl:125
 msgid ""
 "LOCAL: deleted\n"
 "REMOTE:\n"
@@ -1571,7 +1615,7 @@ msgstr ""
 "LOKAL: gelöscht\n"
 "ANDERES:\n"
 
-#: lib/diff.tcl:125
+#: lib/diff.tcl:130
 msgid ""
 "REMOTE: deleted\n"
 "LOCAL:\n"
@@ -1579,32 +1623,32 @@ msgstr ""
 "ANDERES: gelöscht\n"
 "LOKAL:\n"
 
-#: lib/diff.tcl:132
+#: lib/diff.tcl:137
 msgid "LOCAL:\n"
 msgstr "LOKAL:\n"
 
-#: lib/diff.tcl:135
+#: lib/diff.tcl:140
 msgid "REMOTE:\n"
 msgstr "ANDERES:\n"
 
-#: lib/diff.tcl:197 lib/diff.tcl:296
+#: lib/diff.tcl:202 lib/diff.tcl:319
 #, tcl-format
 msgid "Unable to display %s"
 msgstr "Datei »%s« kann nicht angezeigt werden"
 
-#: lib/diff.tcl:115
+#: lib/diff.tcl:203
 msgid "Error loading file:"
 msgstr "Fehler beim Laden der Datei:"
 
-#: lib/diff.tcl:122
+#: lib/diff.tcl:210
 msgid "Git Repository (subproject)"
 msgstr "Git-Projektarchiv (Unterprojekt)"
 
-#: lib/diff.tcl:134
+#: lib/diff.tcl:222
 msgid "* Binary file (not showing content)."
 msgstr "* Binärdatei (Inhalt wird nicht angezeigt)"
 
-#: lib/diff.tcl:222
+#: lib/diff.tcl:227
 #, tcl-format
 msgid ""
 "* Untracked file is %d bytes.\n"
@@ -1613,7 +1657,7 @@ msgstr ""
 "* Datei nicht unter Versionskontrolle, Dateigröße %d Bytes.\n"
 "* Nur erste %d Bytes werden angezeigt.\n"
 
-#: lib/diff.tcl:228
+#: lib/diff.tcl:233
 #, tcl-format
 msgid ""
 "\n"
@@ -1624,20 +1668,20 @@ msgstr ""
 "* Datei nicht unter Versionskontrolle, hier abgeschnitten durch %s.\n"
 "* Zum Ansehen der vollständigen Datei externen Editor benutzen.\n"
 
-#: lib/diff.tcl:436
+#: lib/diff.tcl:482
 msgid "Failed to unstage selected hunk."
 msgstr ""
 "Fehler beim Herausnehmen des gewählten Kontexts aus der Bereitstellung."
 
-#: lib/diff.tcl:310
+#: lib/diff.tcl:489
 msgid "Failed to stage selected hunk."
 msgstr "Fehler beim Bereitstellen des gewählten Kontexts."
 
-#: lib/diff.tcl:386
+#: lib/diff.tcl:568
 msgid "Failed to unstage selected line."
 msgstr "Fehler beim Herausnehmen der gewählten Zeile aus der Bereitstellung."
 
-#: lib/diff.tcl:394
+#: lib/diff.tcl:576
 msgid "Failed to stage selected line."
 msgstr "Fehler beim Bereitstellen der gewählten Zeile."
 
@@ -1675,7 +1719,7 @@ msgstr "Bereitstellung kann nicht wieder freigegeben werden."
 msgid "Index Error"
 msgstr "Fehler in Bereitstellung"
 
-#: lib/index.tcl:21
+#: lib/index.tcl:17
 msgid ""
 "Updating the Git index failed.  A rescan will be automatically started to "
 "resynchronize git-gui."
@@ -1684,7 +1728,7 @@ msgstr ""
 "Git-Aktualisierung wird jetzt gestartet, um git-gui wieder mit git zu "
 "synchronisieren."
 
-#: lib/index.tcl:27
+#: lib/index.tcl:28
 msgid "Continue"
 msgstr "Fortsetzen"
 
@@ -1692,44 +1736,44 @@ msgstr "Fortsetzen"
 msgid "Unlock Index"
 msgstr "Bereitstellung freigeben"
 
-#: lib/index.tcl:282
+#: lib/index.tcl:289
 #, tcl-format
 msgid "Unstaging %s from commit"
 msgstr "Datei »%s« aus der Bereitstellung herausnehmen"
 
-#: lib/index.tcl:313
+#: lib/index.tcl:328
 msgid "Ready to commit."
 msgstr "Bereit zum Eintragen."
 
-#: lib/index.tcl:326
+#: lib/index.tcl:341
 #, tcl-format
 msgid "Adding %s"
 msgstr "»%s« hinzufügen..."
 
-#: lib/index.tcl:381
+#: lib/index.tcl:398
 #, tcl-format
 msgid "Revert changes in file %s?"
 msgstr "Änderungen in Datei »%s« verwerfen?"
 
-#: lib/index.tcl:383
+#: lib/index.tcl:400
 #, tcl-format
 msgid "Revert changes in these %i files?"
 msgstr "Änderungen in den gewählten %i Dateien verwerfen?"
 
-#: lib/index.tcl:391
+#: lib/index.tcl:408
 msgid "Any unstaged changes will be permanently lost by the revert."
 msgstr ""
 "Alle nicht bereitgestellten Änderungen werden beim Verwerfen verloren gehen."
 
-#: lib/index.tcl:394
+#: lib/index.tcl:411
 msgid "Do Nothing"
 msgstr "Nichts tun"
 
-#: lib/index.tcl:419
+#: lib/index.tcl:429
 msgid "Reverting selected files"
 msgstr "Änderungen in gewählten Dateien verwerfen"
 
-#: lib/index.tcl:423
+#: lib/index.tcl:433
 #, tcl-format
 msgid "Reverting %s"
 msgstr "Änderungen in %s verwerfen"
@@ -1761,7 +1805,7 @@ msgstr ""
 "\n"
 "Es wird gleich neu geladen.\n"
 
-#: lib/merge.tcl:44
+#: lib/merge.tcl:45
 #, tcl-format
 msgid ""
 "You are in the middle of a conflicted merge.\n"
@@ -1778,7 +1822,7 @@ msgstr ""
 "bereitstellen und eintragen, um die Zusammenführung abzuschließen. Erst "
 "danach kann eine neue Zusammenführung begonnen werden.\n"
 
-#: lib/merge.tcl:54
+#: lib/merge.tcl:55
 #, tcl-format
 msgid ""
 "You are in the middle of a change.\n"
@@ -1795,34 +1839,34 @@ msgstr ""
 "Reihenfolge können Sie mögliche Konflikte beim Zusammenführen wesentlich "
 "einfacher beheben oder abbrechen.\n"
 
-#: lib/merge.tcl:106
+#: lib/merge.tcl:107
 #, tcl-format
 msgid "%s of %s"
 msgstr "%s von %s"
 
-#: lib/merge.tcl:119
+#: lib/merge.tcl:120
 #, tcl-format
 msgid "Merging %s and %s..."
 msgstr "Zusammenführen von %s und %s..."
 
-#: lib/merge.tcl:130
+#: lib/merge.tcl:131
 msgid "Merge completed successfully."
 msgstr "Zusammenführen erfolgreich abgeschlossen."
 
-#: lib/merge.tcl:132
+#: lib/merge.tcl:133
 msgid "Merge failed.  Conflict resolution is required."
 msgstr "Zusammenführen fehlgeschlagen. Konfliktauflösung ist notwendig."
 
-#: lib/merge.tcl:157
+#: lib/merge.tcl:158
 #, tcl-format
 msgid "Merge Into %s"
 msgstr "Zusammenführen in »%s«"
 
-#: lib/merge.tcl:176
+#: lib/merge.tcl:177
 msgid "Revision To Merge"
 msgstr "Zusammenzuführende Version"
 
-#: lib/merge.tcl:211
+#: lib/merge.tcl:212
 msgid ""
 "Cannot abort while amending.\n"
 "\n"
@@ -1832,7 +1876,7 @@ msgstr ""
 "\n"
 "Sie müssen die Nachbesserung der Version abschließen.\n"
 
-#: lib/merge.tcl:221
+#: lib/merge.tcl:222
 msgid ""
 "Abort merge?\n"
 "\n"
@@ -1847,7 +1891,7 @@ msgstr ""
 "\n"
 "Zusammenführen jetzt abbrechen?"
 
-#: lib/merge.tcl:227
+#: lib/merge.tcl:228
 msgid ""
 "Reset changes?\n"
 "\n"
@@ -1862,35 +1906,35 @@ msgstr ""
 "\n"
 "Änderungen jetzt zurücksetzen?"
 
-#: lib/merge.tcl:238
+#: lib/merge.tcl:239
 msgid "Aborting"
 msgstr "Abbruch"
 
-#: lib/merge.tcl:238
+#: lib/merge.tcl:239
 msgid "files reset"
 msgstr "Dateien zurückgesetzt"
 
-#: lib/merge.tcl:265
+#: lib/merge.tcl:267
 msgid "Abort failed."
 msgstr "Abbruch fehlgeschlagen."
 
-#: lib/merge.tcl:267
+#: lib/merge.tcl:269
 msgid "Abort completed.  Ready."
 msgstr "Abbruch durchgeführt. Bereit."
 
-#: lib/mergetool.tcl:14
+#: lib/mergetool.tcl:8
 msgid "Force resolution to the base version?"
 msgstr "Konflikt durch Basisversion ersetzen?"
 
-#: lib/mergetool.tcl:15
+#: lib/mergetool.tcl:9
 msgid "Force resolution to this branch?"
 msgstr "Konflikt durch diesen Zweig ersetzen?"
 
-#: lib/mergetool.tcl:16
+#: lib/mergetool.tcl:10
 msgid "Force resolution to the other branch?"
 msgstr "Konflikt durch anderen Zweig ersetzen?"
 
-#: lib/mergetool.tcl:20
+#: lib/mergetool.tcl:14
 #, tcl-format
 msgid ""
 "Note that the diff shows only conflicting changes.\n"
@@ -1916,31 +1960,31 @@ msgstr "Datei »%s« hat nicht aufgelöste Konflikte. Trotzdem bereitstellen?"
 msgid "Adding resolution for %s"
 msgstr "Auflösung hinzugefügt für %s"
 
-#: lib/mergetool.tcl:119
+#: lib/mergetool.tcl:141
 msgid "Cannot resolve deletion or link conflicts using a tool"
 msgstr ""
 "Konflikte durch gelöschte Dateien oder symbolische Links können nicht durch "
 "das Zusamenführungswerkzeug gelöst werden."
 
-#: lib/mergetool.tcl:124
+#: lib/mergetool.tcl:146
 msgid "Conflict file does not exist"
 msgstr "Konflikt-Datei existiert nicht"
 
-#: lib/mergetool.tcl:236
+#: lib/mergetool.tcl:264
 #, tcl-format
 msgid "Not a GUI merge tool: '%s'"
 msgstr "Kein GUI Zusammenführungswerkzeug: »%s«"
 
-#: lib/mergetool.tcl:240
+#: lib/mergetool.tcl:268
 #, tcl-format
 msgid "Unsupported merge tool '%s'"
 msgstr "Unbekanntes Zusammenführungswerkzeug: »%s«"
 
-#: lib/mergetool.tcl:275
+#: lib/mergetool.tcl:303
 msgid "Merge tool is already running, terminate it?"
 msgstr "Zusammenführungswerkzeug läuft bereits. Soll es abgebrochen werden?"
 
-#: lib/mergetool.tcl:295
+#: lib/mergetool.tcl:323
 #, tcl-format
 msgid ""
 "Error retrieving versions:\n"
@@ -1949,7 +1993,7 @@ msgstr ""
 "Fehler beim Abrufen der Dateiversionen:\n"
 "%s"
 
-#: lib/mergetool.tcl:315
+#: lib/mergetool.tcl:343
 #, tcl-format
 msgid ""
 "Could not start the merge tool:\n"
@@ -1960,11 +2004,11 @@ msgstr ""
 "\n"
 "%s"
 
-#: lib/mergetool.tcl:319
+#: lib/mergetool.tcl:347
 msgid "Running merge tool..."
 msgstr "Zusammenführungswerkzeug starten..."
 
-#: lib/mergetool.tcl:347 lib/mergetool.tcl:363
+#: lib/mergetool.tcl:375 lib/mergetool.tcl:383
 msgid "Merge tool failed."
 msgstr "Zusammenführungswerkzeug fehlgeschlagen."
 
@@ -1982,76 +2026,76 @@ msgstr "Ungültige Archiv-Zeichenkodierung »%s«"
 msgid "Restore Defaults"
 msgstr "Voreinstellungen wiederherstellen"
 
-#: lib/option.tcl:99
+#: lib/option.tcl:121
 msgid "Save"
 msgstr "Speichern"
 
-#: lib/option.tcl:109
+#: lib/option.tcl:131
 #, tcl-format
 msgid "%s Repository"
 msgstr "Projektarchiv %s"
 
-#: lib/option.tcl:110
+#: lib/option.tcl:132
 msgid "Global (All Repositories)"
 msgstr "Global (Alle Projektarchive)"
 
-#: lib/option.tcl:116
+#: lib/option.tcl:138
 msgid "User Name"
 msgstr "Benutzername"
 
-#: lib/option.tcl:117
+#: lib/option.tcl:139
 msgid "Email Address"
 msgstr "E-Mail-Adresse"
 
-#: lib/option.tcl:119
+#: lib/option.tcl:141
 msgid "Summarize Merge Commits"
 msgstr "Zusammenführungs-Versionen zusammenfassen"
 
-#: lib/option.tcl:120
+#: lib/option.tcl:142
 msgid "Merge Verbosity"
 msgstr "Ausführlichkeit der Zusammenführen-Meldungen"
 
-#: lib/option.tcl:121
+#: lib/option.tcl:143
 msgid "Show Diffstat After Merge"
 msgstr "Vergleichsstatistik nach Zusammenführen anzeigen"
 
-#: lib/option.tcl:122
+#: lib/option.tcl:144
 msgid "Use Merge Tool"
 msgstr "Zusammenführungswerkzeug"
 
-#: lib/option.tcl:124
+#: lib/option.tcl:146
 msgid "Trust File Modification Timestamps"
 msgstr "Auf Dateiänderungsdatum verlassen"
 
-#: lib/option.tcl:124
+#: lib/option.tcl:147
 msgid "Prune Tracking Branches During Fetch"
 msgstr "Übernahmezweige aufräumen während Anforderung"
 
-#: lib/option.tcl:125
+#: lib/option.tcl:148
 msgid "Match Tracking Branches"
 msgstr "Passend zu Übernahmezweig"
 
-#: lib/option.tcl:126
+#: lib/option.tcl:149
 msgid "Blame Copy Only On Changed Files"
 msgstr "Kopie-Annotieren nur bei geänderten Dateien"
 
-#: lib/option.tcl:127
+#: lib/option.tcl:150
 msgid "Minimum Letters To Blame Copy On"
 msgstr "Mindestzahl Zeichen für Kopie-Annotieren"
 
-#: lib/option.tcl:128
+#: lib/option.tcl:151
 msgid "Blame History Context Radius (days)"
 msgstr "Anzahl Tage für Historien-Kontext"
 
-#: lib/option.tcl:129
+#: lib/option.tcl:152
 msgid "Number of Diff Context Lines"
 msgstr "Anzahl der Kontextzeilen beim Vergleich"
 
-#: lib/option.tcl:127
+#: lib/option.tcl:153
 msgid "Commit Message Text Width"
 msgstr "Textbreite der Versionsbeschreibung"
 
-#: lib/option.tcl:128
+#: lib/option.tcl:154
 msgid "New Branch Name Template"
 msgstr "Namensvorschlag für neue Zweige"
 
@@ -2067,24 +2111,24 @@ msgstr "Ändern"
 msgid "Spelling Dictionary:"
 msgstr "Wörterbuch Rechtschreibprüfung:"
 
-#: lib/option.tcl:216
+#: lib/option.tcl:254
 msgid "Change Font"
 msgstr "Schriftart ändern"
 
-#: lib/option.tcl:220
+#: lib/option.tcl:258
 #, tcl-format
 msgid "Choose %s"
 msgstr "%s wählen"
 
-#: lib/option.tcl:226
+#: lib/option.tcl:264
 msgid "pt."
 msgstr "pt."
 
-#: lib/option.tcl:240
+#: lib/option.tcl:278
 msgid "Preferences"
 msgstr "Einstellungen"
 
-#: lib/option.tcl:275
+#: lib/option.tcl:314
 msgid "Failed to completely save options:"
 msgstr "Optionen konnten nicht gespeichert werden:"
 
@@ -2096,7 +2140,7 @@ msgstr "Anderes Archiv hinzufügen"
 msgid "Add New Remote"
 msgstr "Neues anderes Archiv hinzufügen"
 
-#: lib/remote_add.tcl:28
+#: lib/remote_add.tcl:28 lib/tools_dlg.tcl:36
 msgid "Add"
 msgstr "Hinzufügen"
 
@@ -2151,8 +2195,7 @@ msgstr "»%s« anfordern"
 #: lib/remote_add.tcl:157
 #, tcl-format
 msgid "Do not know how to initialize repository at location '%s'."
-msgstr ""
-"Initialisieren eines anderen Archivs an Adresse »%s« ist nicht möglich."
+msgstr "Initialisieren eines anderen Archivs an Adresse »%s« ist nicht möglich."
 
 #: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:63
 #: lib/transport.tcl:81
@@ -2173,11 +2216,11 @@ msgstr "Zweig in anderem Archiv löschen"
 msgid "From Repository"
 msgstr "In Projektarchiv"
 
-#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123
+#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:134
 msgid "Remote:"
 msgstr "Anderes Archiv:"
 
-#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138
+#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:149
 msgid "Arbitrary Location:"
 msgstr "Adresse:"
 
@@ -2193,10 +2236,6 @@ msgstr "Nur löschen, wenn"
 msgid "Merged Into:"
 msgstr "Zusammengeführt mit:"
 
-#: lib/remote_branch_delete.tcl:119
-msgid "Always (Do not perform merge checks)"
-msgstr "Immer (Keine Zusammenführungsprüfung)"
-
 #: lib/remote_branch_delete.tcl:152
 msgid "A branch is required for 'Merged Into'."
 msgstr "Für »Zusammenführen mit« muss ein Zweig angegeben werden."
@@ -2226,27 +2265,16 @@ msgstr ""
 msgid "Please select one or more branches to delete."
 msgstr "Bitte wählen Sie mindestens einen Zweig, der gelöscht werden soll."
 
-#: lib/remote_branch_delete.tcl:216
-msgid ""
-"Recovering deleted branches is difficult.\n"
-"\n"
-"Delete the selected branches?"
-msgstr ""
-"Das Wiederherstellen von gelöschten Zweigen ist nur mit größerem Aufwand "
-"möglich.\n"
-"\n"
-"Sollen die ausgewählten Zweige gelöscht werden?"
-
 #: lib/remote_branch_delete.tcl:226
 #, tcl-format
 msgid "Deleting branches from %s"
 msgstr "Zweige auf »%s« werden gelöscht"
 
-#: lib/remote_branch_delete.tcl:286
+#: lib/remote_branch_delete.tcl:292
 msgid "No repository selected."
 msgstr "Kein Projektarchiv ausgewählt."
 
-#: lib/remote_branch_delete.tcl:291
+#: lib/remote_branch_delete.tcl:297
 #, tcl-format
 msgid "Scanning %s..."
 msgstr "»%s« laden..."
@@ -2259,11 +2287,11 @@ msgstr "Anderes Archiv entfernen"
 msgid "Prune from"
 msgstr "Aufräumen von"
 
-#: lib/remote.tcl:170
+#: lib/remote.tcl:173
 msgid "Fetch from"
 msgstr "Anfordern von"
 
-#: lib/remote.tcl:213
+#: lib/remote.tcl:215
 msgid "Push to"
 msgstr "Versenden nach"
 
@@ -2271,11 +2299,11 @@ msgstr "Versenden nach"
 msgid "Find:"
 msgstr "Suchen:"
 
-#: lib/search.tcl:22
+#: lib/search.tcl:23
 msgid "Next"
 msgstr "Nächster"
 
-#: lib/search.tcl:23
+#: lib/search.tcl:24
 msgid "Prev"
 msgstr "Voriger"
 
@@ -2283,11 +2311,11 @@ msgstr "Voriger"
 msgid "Case-Sensitive"
 msgstr "Groß-/Kleinschreibung unterscheiden"
 
-#: lib/shortcut.tcl:20 lib/shortcut.tcl:61
+#: lib/shortcut.tcl:21 lib/shortcut.tcl:62
 msgid "Cannot write shortcut:"
 msgstr "Fehler beim Schreiben der Verknüpfung:"
 
-#: lib/shortcut.tcl:136
+#: lib/shortcut.tcl:137
 msgid "Cannot write icon:"
 msgstr "Fehler beim Erstellen des Icons:"
 
@@ -2316,15 +2344,15 @@ msgstr "Rechtschreibprüfungsprogramm mit Fehler abgebrochen"
 msgid "Unrecognized spell checker"
 msgstr "Unbekanntes Rechtschreibprüfungsprogramm"
 
-#: lib/spellcheck.tcl:180
+#: lib/spellcheck.tcl:186
 msgid "No Suggestions"
 msgstr "Keine Vorschläge"
 
-#: lib/spellcheck.tcl:381
+#: lib/spellcheck.tcl:388
 msgid "Unexpected EOF from spell checker"
 msgstr "Unerwartetes EOF vom Rechtschreibprüfungsprogramm"
 
-#: lib/spellcheck.tcl:385
+#: lib/spellcheck.tcl:392
 msgid "Spell Checker Failed"
 msgstr "Rechtschreibprüfung fehlgeschlagen"
 
@@ -2534,31 +2562,31 @@ msgstr "Spiegeln nach %s"
 msgid "Pushing %s %s to %s"
 msgstr "%s %s nach %s versenden"
 
-#: lib/transport.tcl:89
+#: lib/transport.tcl:100
 msgid "Push Branches"
 msgstr "Zweige versenden"
 
-#: lib/transport.tcl:103
+#: lib/transport.tcl:114
 msgid "Source Branches"
 msgstr "Lokale Zweige"
 
-#: lib/transport.tcl:120
+#: lib/transport.tcl:131
 msgid "Destination Repository"
 msgstr "Ziel-Projektarchiv"
 
-#: lib/transport.tcl:158
+#: lib/transport.tcl:169
 msgid "Transfer Options"
 msgstr "Netzwerk-Einstellungen"
 
-#: lib/transport.tcl:160
+#: lib/transport.tcl:171
 msgid "Force overwrite existing branch (may discard changes)"
 msgstr ""
 "Überschreiben von existierenden Zweigen erzwingen (könnte Änderungen löschen)"
 
-#: lib/transport.tcl:164
+#: lib/transport.tcl:175
 msgid "Use thin pack (for slow network connections)"
 msgstr "Kompaktes Datenformat benutzen (für langsame Netzverbindungen)"
 
-#: lib/transport.tcl:168
+#: lib/transport.tcl:179
 msgid "Include tags"
 msgstr "Mit Markierungen übertragen"
diff --git a/git-gui/po/el.po b/git-gui/po/el.po
new file mode 100644 (file)
index 0000000..3634ba4
--- /dev/null
@@ -0,0 +1,2005 @@
+# Translation of git-gui to Greek
+# Copyright (C) 2009 Jimmy Angelakos
+# This file is distributed under the same license as the git-gui package.
+# Jimmy Angelakos <vyruss@hellug.gr>, 2009.
+msgid ""
+msgstr ""
+"Project-Id-Version: el\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-03-14 07:18+0100\n"
+"PO-Revision-Date: 2009-06-23 21:33+0300\n"
+"Last-Translator: Jimmy Angelakos <vyruss@hellug.gr>\n"
+"Language-Team: Greek <i18n@lists.hellug.gr>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Lokalize 0.3\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: git-gui.sh:41 git-gui.sh:634 git-gui.sh:648 git-gui.sh:661 git-gui.sh:744
+#: git-gui.sh:763
+msgid "git-gui: fatal error"
+msgstr "git-gui: κρίσιμο σφάλμα"
+
+#: git-gui.sh:593
+#, tcl-format
+msgid "Invalid font specified in %s:"
+msgstr "Μη έγκυρη γραμματοσειρά στο %s:"
+
+#: git-gui.sh:620
+msgid "Main Font"
+msgstr "Κύρια Γραμματοσειρά"
+
+#: git-gui.sh:621
+msgid "Diff/Console Font"
+msgstr "Γραμματοσειρά Διαφοράς/Κονσόλας"
+
+#: git-gui.sh:635
+msgid "Cannot find git in PATH."
+msgstr "Δε βρέθηκε το git στο PATH."
+
+#: git-gui.sh:662
+msgid "Cannot parse Git version string:"
+msgstr "Αδύνατη η ανάγνωση στοιχειοσειράς έκδοσης Git:"
+
+#: git-gui.sh:680
+#, tcl-format
+msgid ""
+"Git version cannot be determined.\n"
+"\n"
+"%s claims it is version '%s'.\n"
+"\n"
+"%s requires at least Git 1.5.0 or later.\n"
+"\n"
+"Assume '%s' is version 1.5.0?\n"
+msgstr ""
+"Δε μπορεί να ανιχνευτεί η έκδοση του Git. \n"
+"\n"
+"Το %s υποστηρίζει πως είναι η έκδοση '%s'.\n"
+"\n"
+"Το %s  απαιτεί τουλάχιστον Git 1.5.0 ή πιό πρόσφατη.\n"
+"\n"
+"Να υποτεθεί πως το '%s' είναι η έκδοση 1.5.0;\n"
+
+#: git-gui.sh:918
+msgid "Git directory not found:"
+msgstr "Δε βρέθηκε φάκελος Git:"
+
+#: git-gui.sh:925
+msgid "Cannot move to top of working directory:"
+msgstr "Δεν είναι δυνατή η μετακίνηση στην κορυφή του φακέλου εργασίας:"
+
+#: git-gui.sh:932
+msgid "Cannot use funny .git directory:"
+msgstr "Δεν είναι δυνατή η χρήση περίεργου φακέλου .git:"
+
+#: git-gui.sh:937
+msgid "No working directory"
+msgstr "Δεν υπάρχει φάκελος εργασίας"
+
+#: git-gui.sh:1084 lib/checkout_op.tcl:283
+msgid "Refreshing file status..."
+msgstr "Ανανέωση κατάστασης αρχείου..."
+
+#: git-gui.sh:1149
+msgid "Scanning for modified files ..."
+msgstr "Ανίχνευση για τροποποιημένα αρχεία..."
+
+#: git-gui.sh:1324 lib/browser.tcl:246
+msgid "Ready."
+msgstr "Έτοιμο."
+
+#: git-gui.sh:1590
+msgid "Unmodified"
+msgstr "Μη τροποποιημένο"
+
+#: git-gui.sh:1592
+msgid "Modified, not staged"
+msgstr "Τροποποιημένο, μη σταδιοποιημένο"
+
+#: git-gui.sh:1593 git-gui.sh:1598
+msgid "Staged for commit"
+msgstr "Σταδιοποιημένο προς υποβολή"
+
+#: git-gui.sh:1594 git-gui.sh:1599
+msgid "Portions staged for commit"
+msgstr "Μέρη σταδιοποιημένα προς υποβολή"
+
+#: git-gui.sh:1595 git-gui.sh:1600
+msgid "Staged for commit, missing"
+msgstr "Σταδιοποιημένο προς υποβολή, λείπει"
+
+#: git-gui.sh:1597
+msgid "Untracked, not staged"
+msgstr "Μη παρακολουθούμενο, μη σταδιοποιημένο"
+
+#: git-gui.sh:1602
+msgid "Missing"
+msgstr "Λείπει"
+
+#: git-gui.sh:1603
+msgid "Staged for removal"
+msgstr "Σταδιοποιημένο προς αφαίρεση"
+
+#: git-gui.sh:1604
+msgid "Staged for removal, still present"
+msgstr "Σταδιοποιημένο προς αφαίρεση, ακόμα παρόν"
+
+#: git-gui.sh:1606 git-gui.sh:1607 git-gui.sh:1608 git-gui.sh:1609
+msgid "Requires merge resolution"
+msgstr "Απαιτεί επίλυση συγχώνευσης"
+
+#: git-gui.sh:1644
+msgid "Starting gitk... please wait..."
+msgstr "Γίνεται εκκίνηση του gitk... παρακαλώ περιμένετε..."
+
+#: git-gui.sh:1653
+#, tcl-format
+msgid ""
+"Unable to start gitk:\n"
+"\n"
+"%s does not exist"
+msgstr ""
+"Αδυναμία εκκίνησης του gitk:\n"
+"\n"
+"Το %s δεν υπάρχει"
+
+#: git-gui.sh:1860 lib/choose_repository.tcl:36
+msgid "Repository"
+msgstr "Αποθετήριο"
+
+#: git-gui.sh:1861
+msgid "Edit"
+msgstr "Επεξεργασία"
+
+#: git-gui.sh:1863 lib/choose_rev.tcl:561
+msgid "Branch"
+msgstr "Κλάδος"
+
+#: git-gui.sh:1866 lib/choose_rev.tcl:548
+msgid "Commit@@noun"
+msgstr "Υποβολή@@noun"
+
+#: git-gui.sh:1869 lib/merge.tcl:120 lib/merge.tcl:149 lib/merge.tcl:167
+msgid "Merge"
+msgstr "Συγχώνευση"
+
+#: git-gui.sh:1870 lib/choose_rev.tcl:557
+msgid "Remote"
+msgstr "Απομακρυσμένο"
+
+#: git-gui.sh:1879
+msgid "Browse Current Branch's Files"
+msgstr "Περιήγηση Αρχείων Τρέχοντα Κλάδου"
+
+#: git-gui.sh:1883
+msgid "Browse Branch Files..."
+msgstr "Περιήγηση Αρχείων Κλάδου..."
+
+#: git-gui.sh:1888
+msgid "Visualize Current Branch's History"
+msgstr "Απεικόνιση Ιστορικού Τρέχοντα Κλάδου"
+
+#: git-gui.sh:1892
+msgid "Visualize All Branch History"
+msgstr "Απεικόνιση Ιστορικού Όλων των Κλάδων"
+
+#: git-gui.sh:1899
+#, tcl-format
+msgid "Browse %s's Files"
+msgstr "Περιήγηση Αρχείων του %s"
+
+#: git-gui.sh:1901
+#, tcl-format
+msgid "Visualize %s's History"
+msgstr "Απεικόνιση Ιστορικού του %s"
+
+#: git-gui.sh:1906 lib/database.tcl:27 lib/database.tcl:67
+msgid "Database Statistics"
+msgstr "Στατιστικά Βάσης Δεδομένων"
+
+#: git-gui.sh:1909 lib/database.tcl:34
+msgid "Compress Database"
+msgstr "Συμπίεση Βάσης Δεδομένων"
+
+#: git-gui.sh:1912
+msgid "Verify Database"
+msgstr "Επαλήθευση Βάσης Δεδομένων"
+
+#: git-gui.sh:1919 git-gui.sh:1923 git-gui.sh:1927 lib/shortcut.tcl:7
+#: lib/shortcut.tcl:39 lib/shortcut.tcl:71
+msgid "Create Desktop Icon"
+msgstr "Δημιουργία Εικονιδίου Επιφάνειας Εργασίας"
+
+#: git-gui.sh:1932 lib/choose_repository.tcl:177 lib/choose_repository.tcl:185
+msgid "Quit"
+msgstr "Έξοδος"
+
+#: git-gui.sh:1939
+msgid "Undo"
+msgstr "Αναίρεση"
+
+#: git-gui.sh:1942
+msgid "Redo"
+msgstr "Ξανά"
+
+#: git-gui.sh:1946 git-gui.sh:2443
+msgid "Cut"
+msgstr "Αποκοπή"
+
+#: git-gui.sh:1949 git-gui.sh:2446 git-gui.sh:2520 git-gui.sh:2614
+#: lib/console.tcl:69
+msgid "Copy"
+msgstr "Αντιγραφή"
+
+#: git-gui.sh:1952 git-gui.sh:2449
+msgid "Paste"
+msgstr "Επικόλληση"
+
+#: git-gui.sh:1955 git-gui.sh:2452 lib/branch_delete.tcl:26
+#: lib/remote_branch_delete.tcl:38
+msgid "Delete"
+msgstr "Διαγραφή"
+
+#: git-gui.sh:1959 git-gui.sh:2456 git-gui.sh:2618 lib/console.tcl:71
+msgid "Select All"
+msgstr "Επιλογή Όλων"
+
+#: git-gui.sh:1968
+msgid "Create..."
+msgstr "Δημιουργία..."
+
+#: git-gui.sh:1974
+msgid "Checkout..."
+msgstr "Εξαγωγή..."
+
+#: git-gui.sh:1980
+msgid "Rename..."
+msgstr "Μετονομασία..."
+
+#: git-gui.sh:1985 git-gui.sh:2085
+msgid "Delete..."
+msgstr "Διαγραφή..."
+
+#: git-gui.sh:1990
+msgid "Reset..."
+msgstr "Επαναφορά..."
+
+#: git-gui.sh:2002 git-gui.sh:2389
+msgid "New Commit"
+msgstr "Νέα Υποβολή"
+
+#: git-gui.sh:2010 git-gui.sh:2396
+msgid "Amend Last Commit"
+msgstr "Διόρθωση Τελευταίας Υποβολής"
+
+#: git-gui.sh:2019 git-gui.sh:2356 lib/remote_branch_delete.tcl:99
+msgid "Rescan"
+msgstr "Επανανίχνευση"
+
+#: git-gui.sh:2025
+msgid "Stage To Commit"
+msgstr "Σταδιοποίηση Προς Υποβολή"
+
+#: git-gui.sh:2031
+msgid "Stage Changed Files To Commit"
+msgstr "Σταδιοποίηση Αλλαγμένων Αρχείων Προς Υποβολή"
+
+#: git-gui.sh:2037
+msgid "Unstage From Commit"
+msgstr "Αποσταδιοποίηση Από Υποβολή"
+
+#: git-gui.sh:2042 lib/index.tcl:395
+msgid "Revert Changes"
+msgstr "Αναίρεση Αλλαγών"
+
+#: git-gui.sh:2049 git-gui.sh:2368 git-gui.sh:2467
+msgid "Sign Off"
+msgstr "Αποσύνδεση"
+
+#: git-gui.sh:2053 git-gui.sh:2372
+msgid "Commit@@verb"
+msgstr "Υποβολή@@verb"
+
+#: git-gui.sh:2064
+msgid "Local Merge..."
+msgstr "Τοπική Συγχώνευση..."
+
+#: git-gui.sh:2069
+msgid "Abort Merge..."
+msgstr "Ακύρωση Συγχώνευσης..."
+
+#: git-gui.sh:2081
+msgid "Push..."
+msgstr "Ώθηση..."
+
+#: git-gui.sh:2092 lib/choose_repository.tcl:41
+#, fuzzy
+msgid "Apple"
+msgstr ""
+
+#: git-gui.sh:2095 git-gui.sh:2117 lib/about.tcl:14
+#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:50
+#, tcl-format
+msgid "About %s"
+msgstr "Περί %s"
+
+#: git-gui.sh:2099
+msgid "Preferences..."
+msgstr "Προτιμήσεις..."
+
+#: git-gui.sh:2107 git-gui.sh:2639
+msgid "Options..."
+msgstr "Επιλογές..."
+
+#: git-gui.sh:2113 lib/choose_repository.tcl:47
+msgid "Help"
+msgstr "Βοήθεια"
+
+#: git-gui.sh:2154
+msgid "Online Documentation"
+msgstr "Διαδικτυακή Τεκμηρίωση"
+
+#: git-gui.sh:2238
+#, tcl-format
+msgid "fatal: cannot stat path %s: No such file or directory"
+msgstr "κρίσιμο: δε βρέθηκε η διαδρομή: %s: Δεν υπάρχει το αρχείο ή ο φάκελος"
+
+#: git-gui.sh:2271
+msgid "Current Branch:"
+msgstr "Τρέχων Κλάδος:"
+
+#: git-gui.sh:2292
+msgid "Staged Changes (Will Commit)"
+msgstr "Σταδιοποιημένες Αλλαγές (Θα Υποβληθούν)"
+
+#: git-gui.sh:2312
+msgid "Unstaged Changes"
+msgstr "Μη Σταδιοποιημένες Αλλαγές"
+
+#: git-gui.sh:2362
+msgid "Stage Changed"
+msgstr "Σταδιοποίηση Αλλαγών"
+
+#: git-gui.sh:2378 lib/transport.tcl:93 lib/transport.tcl:182
+msgid "Push"
+msgstr "Ώθηση"
+
+#: git-gui.sh:2408
+msgid "Initial Commit Message:"
+msgstr "Αρχικό Μήνυμα Υποβολής:"
+
+#: git-gui.sh:2409
+msgid "Amended Commit Message:"
+msgstr "Διορθωμένο Μήνυμα Υποβολής:"
+
+#: git-gui.sh:2410
+msgid "Amended Initial Commit Message:"
+msgstr "Διορθωμένο Αρχικό Μήνυμα Υποβολής:"
+
+#: git-gui.sh:2411
+msgid "Amended Merge Commit Message:"
+msgstr "Διορθωμένο Μήνυμα Υποβολής Συγχώνευσης:"
+
+#: git-gui.sh:2412
+msgid "Merge Commit Message:"
+msgstr "Μήνυμα Υποβολής Συγχώνευσης:"
+
+#: git-gui.sh:2413
+msgid "Commit Message:"
+msgstr "Μήνυμα Υποβολής:"
+
+#: git-gui.sh:2459 git-gui.sh:2622 lib/console.tcl:73
+msgid "Copy All"
+msgstr "Αντιγραφή Όλων"
+
+#: git-gui.sh:2483 lib/blame.tcl:107
+msgid "File:"
+msgstr "Αρχείο:"
+
+#: git-gui.sh:2589
+msgid "Apply/Reverse Hunk"
+msgstr "Εφαρμογή/Αντιστροφή Κομματιού"
+
+#: git-gui.sh:2595
+msgid "Show Less Context"
+msgstr "Προβολή Στενότερου Πλαισίου"
+
+#: git-gui.sh:2602
+msgid "Show More Context"
+msgstr "Προβολή Ευρύτερου Πλαισίου"
+
+#: git-gui.sh:2610
+msgid "Refresh"
+msgstr "Ανανέωση"
+
+#: git-gui.sh:2631
+msgid "Decrease Font Size"
+msgstr "Μείωση Μεγέθους Γραμματοσειράς"
+
+#: git-gui.sh:2635
+msgid "Increase Font Size"
+msgstr "Αύξηση Μεγέθους Γραμματοσειράς"
+
+#: git-gui.sh:2646
+msgid "Unstage Hunk From Commit"
+msgstr "Αποσταδιοποίηση Κομματιού Από Υποβολή"
+
+#: git-gui.sh:2648
+msgid "Stage Hunk For Commit"
+msgstr "Σταδιοποίηση Κομματιού Προς Υποβολή"
+
+#: git-gui.sh:2667
+msgid "Initializing..."
+msgstr "Γίνεται αρχικοποίηση..."
+
+#: git-gui.sh:2762
+#, tcl-format
+msgid ""
+"Possible environment issues exist.\n"
+"\n"
+"The following environment variables are probably\n"
+"going to be ignored by any Git subprocess run\n"
+"by %s:\n"
+"\n"
+msgstr ""
+"Υπάρχουν πιθανά θέματα με το περιβάλλον.\n"
+"\n"
+"Οι εξής μεταβλητές περιβάλλοντος μάλλον θα\n"
+"αγνοηθούν από πιθανή εκτέλεση υποδιεργασίας Git\n"
+"από το %s:\n"
+"\n"
+
+#: git-gui.sh:2792
+msgid ""
+"\n"
+"This is due to a known issue with the\n"
+"Tcl binary distributed by Cygwin."
+msgstr ""
+"\n"
+"Αυτό οφείλεται σε ένα γνωστό θέμα με το\n"
+"εκτελέσιμο Tcl που διανέμεται με το Cygwin."
+
+#: git-gui.sh:2797
+#, tcl-format
+msgid ""
+"\n"
+"\n"
+"A good replacement for %s\n"
+"is placing values for the user.name and\n"
+"user.email settings into your personal\n"
+"~/.gitconfig file.\n"
+msgstr ""
+"\n"
+"\n"
+"Ένα καλό υποκατάστατο για το %s\n"
+"είναι η τοποθέτηση τιμών για τις ρυθμίσεις\n"
+"user.name και user.email στο προσωπικό σας\n"
+"αρχείο ~/.gitconfig .\n"
+
+#: lib/about.tcl:26
+msgid "git-gui - a graphical user interface for Git."
+msgstr "git-gui - ένα γραφικό περιβάλλον για το Git."
+
+#: lib/blame.tcl:77
+msgid "File Viewer"
+msgstr "Εφαρμογή Προβολής Αρχείου"
+
+#: lib/blame.tcl:81
+msgid "Commit:"
+msgstr "Υποβολή:"
+
+#: lib/blame.tcl:264
+msgid "Copy Commit"
+msgstr "Αντιγραφή Υποβολής"
+
+#: lib/blame.tcl:384
+#, tcl-format
+msgid "Reading %s..."
+msgstr "Ανάγνωση %s..."
+
+#: lib/blame.tcl:488
+msgid "Loading copy/move tracking annotations..."
+msgstr "Γίνεται φόρτωση σχολίων παρακολούθησης αντιγραφής/μετακίνησης..."
+
+#: lib/blame.tcl:508
+msgid "lines annotated"
+msgstr "γραμμές σχολιασμένες"
+
+#: lib/blame.tcl:689
+msgid "Loading original location annotations..."
+msgstr "Γίνεται φόρτωση σχολίων αρχικής τοποθεσίας..."
+
+#: lib/blame.tcl:692
+msgid "Annotation complete."
+msgstr "Έγινε ολοκλήρωση του σχολιασμού."
+
+#: lib/blame.tcl:746
+msgid "Loading annotation..."
+msgstr "Φόρτωση σχολίου..."
+
+#: lib/blame.tcl:802
+msgid "Author:"
+msgstr "Δημιουργός:"
+
+#: lib/blame.tcl:806
+msgid "Committer:"
+msgstr "Υποβολέας:"
+
+#: lib/blame.tcl:811
+msgid "Original File:"
+msgstr "Αρχικό Αρχείο:"
+
+#: lib/blame.tcl:925
+msgid "Originally By:"
+msgstr "Αρχικά Από:"
+
+#: lib/blame.tcl:931
+msgid "In File:"
+msgstr "Στο Αρχείο:"
+
+#: lib/blame.tcl:936
+msgid "Copied Or Moved Here By:"
+msgstr "Αντιγράφηκε ή Μετακινήθηκε Εδώ Από:"
+
+#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19
+msgid "Checkout Branch"
+msgstr "Εξαγωγή Κλάδου"
+
+#: lib/branch_checkout.tcl:23
+msgid "Checkout"
+msgstr "Εξαγωγή"
+
+#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
+#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282
+#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:171
+#: lib/option.tcl:103 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97
+msgid "Cancel"
+msgstr "Ακύρωση"
+
+#: lib/branch_checkout.tcl:32 lib/browser.tcl:287
+msgid "Revision"
+msgstr "Αναθεώρηση"
+
+#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:242
+msgid "Options"
+msgstr "Επιλογές"
+
+#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92
+msgid "Fetch Tracking Branch"
+msgstr "Ανάκτηση Κλάδου Παρακολούθησης"
+
+#: lib/branch_checkout.tcl:44
+msgid "Detach From Local Branch"
+msgstr "Αποκόλληση Από Τοπικό Κλάδο"
+
+#: lib/branch_create.tcl:22
+msgid "Create Branch"
+msgstr "Δημιουργία Κλάδου"
+
+#: lib/branch_create.tcl:27
+msgid "Create New Branch"
+msgstr "Δημιουργία Νέου Κλάδου"
+
+#: lib/branch_create.tcl:31 lib/choose_repository.tcl:371
+msgid "Create"
+msgstr "Δημιουργία"
+
+#: lib/branch_create.tcl:40
+msgid "Branch Name"
+msgstr "Όνομα Κλάδου"
+
+#: lib/branch_create.tcl:43
+msgid "Name:"
+msgstr "Όνομα:"
+
+#: lib/branch_create.tcl:58
+msgid "Match Tracking Branch Name"
+msgstr "Συμφωνία Ονόματος Κλάδου Παρακολούθησης"
+
+#: lib/branch_create.tcl:66
+msgid "Starting Revision"
+msgstr "Αρχική Αναθεώρηση"
+
+#: lib/branch_create.tcl:72
+msgid "Update Existing Branch:"
+msgstr "Ενημέρωση Υπάρχοντα Κλάδου:"
+
+#: lib/branch_create.tcl:75
+#, fuzzy
+msgid "No"
+msgstr "Όχι"
+
+#: lib/branch_create.tcl:80
+msgid "Fast Forward Only"
+msgstr "Συγχώνευση Επιτάχυνσης Μόνο"
+
+#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514
+msgid "Reset"
+msgstr "Επαναφορά"
+
+#: lib/branch_create.tcl:97
+msgid "Checkout After Creation"
+msgstr "Εξαγωγή Μετά τη Δημιουργία"
+
+#: lib/branch_create.tcl:131
+msgid "Please select a tracking branch."
+msgstr "Παρακαλώ επιλέξτε έναν κλάδο παρακολούθησης."
+
+#: lib/branch_create.tcl:140
+#, tcl-format
+msgid "Tracking branch %s is not a branch in the remote repository."
+msgstr ""
+"Ο κλάδος παρακολούθησης %s δεν είναι κλάδος που βρίσκεται στο απομακρυσμένο "
+"αποθετήριο."
+
+#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86
+msgid "Please supply a branch name."
+msgstr "Παρακαλώ δώστε ένα όνομα κλάδου."
+
+#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106
+#, tcl-format
+msgid "'%s' is not an acceptable branch name."
+msgstr "'%s' δεν είναι αποδεκτό όνομα κλάδου."
+
+#: lib/branch_delete.tcl:15
+msgid "Delete Branch"
+msgstr "Διαγραφή Κλάδου"
+
+#: lib/branch_delete.tcl:20
+msgid "Delete Local Branch"
+msgstr "Διαγραφή Τοπικού Κλάδου"
+
+#: lib/branch_delete.tcl:37
+msgid "Local Branches"
+msgstr "Τοπικοί Κλάδοι"
+
+#: lib/branch_delete.tcl:52
+msgid "Delete Only If Merged Into"
+msgstr "Διαγραφή Μόνο Εάν Είναι Συγχωνευμένο Με"
+
+#: lib/branch_delete.tcl:54
+msgid "Always (Do not perform merge test.)"
+msgstr "Πάντα (Μη διενεργηθεί δοκιμή συγχώνευσης.)"
+
+#: lib/branch_delete.tcl:103
+#, tcl-format
+msgid "The following branches are not completely merged into %s:"
+msgstr "Οι εξής κλάδοι δεν είναι πλήρως συγχωνευμένοι με το %s:"
+
+#: lib/branch_delete.tcl:115
+msgid ""
+"Recovering deleted branches is difficult. \n"
+"\n"
+" Delete the selected branches?"
+msgstr ""
+"Η ανάκτηση διεγραμμένων κλάδων είναι δύσκολη.\n"
+"\n"
+"Διαγραφή των επιλεγμένων κλάδων;"
+
+#: lib/branch_delete.tcl:141
+#, tcl-format
+msgid ""
+"Failed to delete branches:\n"
+"%s"
+msgstr ""
+"Αποτυχία διαγραφής κλάδων:\n"
+"%s"
+
+#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22
+msgid "Rename Branch"
+msgstr "Μετονομασία Κλάδου"
+
+#: lib/branch_rename.tcl:26
+msgid "Rename"
+msgstr "Μετονομασία"
+
+#: lib/branch_rename.tcl:36
+msgid "Branch:"
+msgstr "Κλάδος:"
+
+#: lib/branch_rename.tcl:39
+msgid "New Name:"
+msgstr "Νέο Όνομα:"
+
+#: lib/branch_rename.tcl:75
+msgid "Please select a branch to rename."
+msgstr "Παρακαλώ επιλέξτε κλάδο προς μετονομασία:"
+
+#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179
+#, tcl-format
+msgid "Branch '%s' already exists."
+msgstr "Ο Κλάδος '%s' υπάρχει ήδη."
+
+#: lib/branch_rename.tcl:117
+#, tcl-format
+msgid "Failed to rename '%s'."
+msgstr "Αποτυχία μετονομασίας '%s'."
+
+#: lib/browser.tcl:17
+msgid "Starting..."
+msgstr "Γίνεται Εκκίνηση..."
+
+#: lib/browser.tcl:26
+msgid "File Browser"
+msgstr "Περιηγητής Αρχείων"
+
+#: lib/browser.tcl:126 lib/browser.tcl:143
+#, tcl-format
+msgid "Loading %s..."
+msgstr "Γίνεται φόρτωση %s..."
+
+#: lib/browser.tcl:187
+#, fuzzy
+msgid "[Up To Parent]"
+msgstr "[Πάνω Προς Γονέα]"
+
+#: lib/browser.tcl:267 lib/browser.tcl:273
+msgid "Browse Branch Files"
+msgstr "Περιήγηση Αρχείων Κλάδου"
+
+#: lib/browser.tcl:278 lib/choose_repository.tcl:387
+#: lib/choose_repository.tcl:474 lib/choose_repository.tcl:484
+#: lib/choose_repository.tcl:987
+msgid "Browse"
+msgstr "Περιήγηση"
+
+#: lib/checkout_op.tcl:79
+#, tcl-format
+msgid "Fetching %s from %s"
+msgstr "Ανάκτηση %s από το %s"
+
+#: lib/checkout_op.tcl:127
+#, tcl-format
+msgid "fatal: Cannot resolve %s"
+msgstr "κρίσιμο: Δε μπόρεσε να επιλυθεί το %s"
+
+#: lib/checkout_op.tcl:140 lib/console.tcl:81 lib/database.tcl:31
+msgid "Close"
+msgstr "Κλείσιμο"
+
+#: lib/checkout_op.tcl:169
+#, tcl-format
+msgid "Branch '%s' does not exist."
+msgstr "Ο Κλάδος '%s' δεν υπάρχει."
+
+#: lib/checkout_op.tcl:206
+#, tcl-format
+msgid ""
+"Branch '%s' already exists.\n"
+"\n"
+"It cannot fast-forward to %s.\n"
+"A merge is required."
+msgstr ""
+"Ο Κλάδος '%s' υπάρχει ήδη.\n"
+"\n"
+"Δε γίνεται συγχώνευση επιτάχυνσής του στο %s.\n"
+"Απαιτείται συγχώνευση."
+
+#: lib/checkout_op.tcl:220
+#, tcl-format
+msgid "Merge strategy '%s' not supported."
+msgstr "Η στρατηγική Συγχώνευσης %s δεν υποστηρίζεται."
+
+#: lib/checkout_op.tcl:239
+#, tcl-format
+msgid "Failed to update '%s'."
+msgstr "Αποτυχία ενημέρωσης '%s'."
+
+#: lib/checkout_op.tcl:251
+msgid "Staging area (index) is already locked."
+msgstr "Η περιοχή σταδιοποίησης (το ευρετήριο) είναι ήδη κλειδωμένη."
+
+#: lib/checkout_op.tcl:266
+msgid ""
+"Last scanned state does not match repository state.\n"
+"\n"
+"Another Git program has modified this repository since the last scan.  A "
+"rescan must be performed before the current branch can be changed.\n"
+"\n"
+"The rescan will be automatically started now.\n"
+msgstr ""
+"Η τελευταία κατάσταση που ανιχνεύθηκε δε συμφωνεί με την κατάσταση του "
+"αποθετηρίου.\n"
+"\n"
+"Κάποιο άλλο πρόγραμμα Git τροποποίησε το αποθετήριο από την τελευταία "
+"ανίχνευση. Πρέπει να γίνει επανανίχνευση πριν να αλλαχθεί ο τρέχων κλάδος.\n"
+"\n"
+"Η επανανίχνευση θα ξεκινήσει αυτόματα τώρα.\n"
+
+#: lib/checkout_op.tcl:322
+#, tcl-format
+msgid "Updating working directory to '%s'..."
+msgstr "Ενημέρωση φακέλου εργασίας σε '%s'..."
+
+#: lib/checkout_op.tcl:323
+msgid "files checked out"
+msgstr "αρχεία έχουν εξαχθεί"
+
+#: lib/checkout_op.tcl:353
+#, tcl-format
+msgid "Aborted checkout of '%s' (file level merging is required)."
+msgstr ""
+"Έγινε ακύρωση εξαγωγής του '%s' (απαιτείται συγχώνευση επιπέδου αρχείου)."
+
+#: lib/checkout_op.tcl:354
+msgid "File level merge required."
+msgstr "Απαιτείται συγχώνευση επιπέδου αρχείου."
+
+#: lib/checkout_op.tcl:358
+#, tcl-format
+msgid "Staying on branch '%s'."
+msgstr "Παραμονή στον κλάδο '%s'."
+
+#: lib/checkout_op.tcl:429
+msgid ""
+"You are no longer on a local branch.\n"
+"\n"
+"If you wanted to be on a branch, create one now starting from 'This Detached "
+"Checkout'."
+msgstr ""
+"Δε βρίσκεστε πια σε τοπικό κλάδο.\n"
+"\n"
+"Αν θέλατε να βρίσκεστε σε κλάδο, δημιουργήστε έναν τώρα αρχίζοντας από 'This "
+"Detached Checkout'."
+
+#: lib/checkout_op.tcl:446 lib/checkout_op.tcl:450
+#, tcl-format
+msgid "Checked out '%s'."
+msgstr "Έγινε εξαγωγή του '%s'."
+
+#: lib/checkout_op.tcl:478
+#, tcl-format
+msgid "Resetting '%s' to '%s' will lose the following commits:"
+msgstr "Η επαναφορά '%s' στο '%s' θα χάσει τις εξής υποβολές:"
+
+#: lib/checkout_op.tcl:500
+msgid "Recovering lost commits may not be easy."
+msgstr "Η ανάκτηση χαμένων υποβολών μπορεί να είναι δύσκολη."
+
+#: lib/checkout_op.tcl:505
+#, tcl-format
+msgid "Reset '%s'?"
+msgstr "Επαναφορά '%s';"
+
+#: lib/checkout_op.tcl:510 lib/merge.tcl:163
+msgid "Visualize"
+msgstr "Απεικόνιση"
+
+#: lib/checkout_op.tcl:578
+#, tcl-format
+msgid ""
+"Failed to set current branch.\n"
+"\n"
+"This working directory is only partially switched.  We successfully updated "
+"your files, but failed to update an internal Git file.\n"
+"\n"
+"This should not have occurred.  %s will now close and give up."
+msgstr ""
+"Αποτυχία ορισμού τρέχοντος κλάδου.\n"
+"\n"
+"Αυτός ο φάκελος εργασίας είναι μόνο εν μέρει επιλεγμένος. 'Εγινε επιτυχής "
+"ενημέρωση των αρχείων σας, αλλά απέτυχε η ενημέρωση ενός εσωτερικού αρχείου "
+"του Git.\n"
+"\n"
+"Αυτό δε θα έπρεπε να συμβεί. Το %s θα κλείσει και θα εγκαταλείψει τώρα."
+
+#: lib/choose_font.tcl:39
+msgid "Select"
+msgstr "Επιλογή"
+
+#: lib/choose_font.tcl:53
+msgid "Font Family"
+msgstr "Οικογένεια Γραμματοσειράς"
+
+#: lib/choose_font.tcl:74
+msgid "Font Size"
+msgstr "Μέγεθος Γραμματοσειράς"
+
+#: lib/choose_font.tcl:91
+msgid "Font Example"
+msgstr "Παράδειγμα Γραμματοσειράς"
+
+#: lib/choose_font.tcl:103
+msgid ""
+"This is example text.\n"
+"If you like this text, it can be your font."
+msgstr ""
+"Αυτό είναι ένα παράδειγμα κειμένου.\n"
+"Αν σας αρέσει αυτό το κείμενο, μπορεί να γίνει δικό σας."
+
+#: lib/choose_repository.tcl:28
+msgid "Git Gui"
+msgstr "Γραφικό Περιβάλλον Git"
+
+#: lib/choose_repository.tcl:81 lib/choose_repository.tcl:376
+msgid "Create New Repository"
+msgstr "Δημιουργία Νέου Αποθετηρίου"
+
+#: lib/choose_repository.tcl:87
+msgid "New..."
+msgstr "Νέο..."
+
+#: lib/choose_repository.tcl:94 lib/choose_repository.tcl:460
+msgid "Clone Existing Repository"
+msgstr "Κλωνοποίηση Υπάρχοντος Αποθετηρίου"
+
+#: lib/choose_repository.tcl:100
+msgid "Clone..."
+msgstr "Κλωνοποίηση..."
+
+#: lib/choose_repository.tcl:107 lib/choose_repository.tcl:976
+msgid "Open Existing Repository"
+msgstr "Άνοιγμα Υπάρχοντος Αποθετηρίου"
+
+#: lib/choose_repository.tcl:113
+msgid "Open..."
+msgstr "Άνοιγμα..."
+
+#: lib/choose_repository.tcl:126
+msgid "Recent Repositories"
+msgstr "Πρόσφατα Αποθετήρια"
+
+#: lib/choose_repository.tcl:132
+msgid "Open Recent Repository:"
+msgstr "Άνοιγμα Πρόσφατου Αποθετηρίου:"
+
+#: lib/choose_repository.tcl:296 lib/choose_repository.tcl:303
+#: lib/choose_repository.tcl:310
+#, tcl-format
+msgid "Failed to create repository %s:"
+msgstr "Αποτυχία δημιουργίας αποθετηρίου %s:"
+
+#: lib/choose_repository.tcl:381 lib/choose_repository.tcl:478
+msgid "Directory:"
+msgstr "Φάκελος:"
+
+#: lib/choose_repository.tcl:412 lib/choose_repository.tcl:537
+#: lib/choose_repository.tcl:1011
+msgid "Git Repository"
+msgstr "Αποθετήριο Git"
+
+#: lib/choose_repository.tcl:437
+#, tcl-format
+msgid "Directory %s already exists."
+msgstr "Ο Φάκελος '%s' υπάρχει ήδη."
+
+#: lib/choose_repository.tcl:441
+#, tcl-format
+msgid "File %s already exists."
+msgstr "Το αρχείο %s υπάρχει ήδη."
+
+#: lib/choose_repository.tcl:455
+msgid "Clone"
+msgstr "Κλώνος"
+
+#: lib/choose_repository.tcl:468
+#, fuzzy
+msgid "URL:"
+msgstr ""
+
+#: lib/choose_repository.tcl:489
+msgid "Clone Type:"
+msgstr "Τύπος Κλώνου:"
+
+#: lib/choose_repository.tcl:495
+msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
+msgstr "Τυπικό (Ταχύ, Ημι-Πλεονάζον, Hardlinks)"
+
+#: lib/choose_repository.tcl:501
+msgid "Full Copy (Slower, Redundant Backup)"
+msgstr "Πλήρες Αντίγραφο (Πιο αργό, Πλεονάζον Αντίγραφο Ασφαλείας)"
+
+#: lib/choose_repository.tcl:507
+msgid "Shared (Fastest, Not Recommended, No Backup)"
+msgstr "Κοινή Χρήση (Ταχύτατο, Δε Συνιστάται, Κανένα Αντίγραφο Ασφαλείας)"
+
+#: lib/choose_repository.tcl:543 lib/choose_repository.tcl:590
+#: lib/choose_repository.tcl:736 lib/choose_repository.tcl:806
+#: lib/choose_repository.tcl:1017 lib/choose_repository.tcl:1025
+#, tcl-format
+msgid "Not a Git repository: %s"
+msgstr "Δεν είναι αποθετήριο Git: %s"
+
+#: lib/choose_repository.tcl:579
+msgid "Standard only available for local repository."
+msgstr "\"Τυπικό\" διαθέσιμο μόνο για τοπικό αποθετήριο."
+
+#: lib/choose_repository.tcl:583
+msgid "Shared only available for local repository."
+msgstr "\"Κοινή Χρήση\" διαθέσιμη μόνο για τοπικό αποθετήριο."
+
+#: lib/choose_repository.tcl:604
+#, tcl-format
+msgid "Location %s already exists."
+msgstr "Η Τοποθεσία %s υπάρχει ήδη."
+
+#: lib/choose_repository.tcl:615
+msgid "Failed to configure origin"
+msgstr "Αποτυχία ρύθμισης πηγής"
+
+#: lib/choose_repository.tcl:627
+msgid "Counting objects"
+msgstr "Γίνεται καταμέτρηση αντικειμένων"
+
+#: lib/choose_repository.tcl:628
+#, fuzzy
+msgid "buckets"
+msgstr ""
+
+#: lib/choose_repository.tcl:652
+#, tcl-format
+msgid "Unable to copy objects/info/alternates: %s"
+msgstr "Αδυναμία αντιγραφής αντικειμένων/πληροφοριών/ενναλακτικών: %s"
+
+#: lib/choose_repository.tcl:688
+#, tcl-format
+msgid "Nothing to clone from %s."
+msgstr "Τίποτα προς κλωνοποίηση από το %s."
+
+#: lib/choose_repository.tcl:690 lib/choose_repository.tcl:904
+#: lib/choose_repository.tcl:916
+msgid "The 'master' branch has not been initialized."
+msgstr "Ο κλάδος 'master' δεν έχει αρχικοποιηθεί."
+
+#: lib/choose_repository.tcl:703
+msgid "Hardlinks are unavailable.  Falling back to copying."
+msgstr "Hardlinks μη διαθέσιμα. Μετάπτωση σε αντιγραφή."
+
+#: lib/choose_repository.tcl:715
+#, tcl-format
+msgid "Cloning from %s"
+msgstr "Γίνεται κλωνοποίηση από το %s"
+
+#: lib/choose_repository.tcl:746
+msgid "Copying objects"
+msgstr "Γίνεται αντιγραφή αντικειμένων"
+
+#: lib/choose_repository.tcl:747
+#, fuzzy
+msgid "KiB"
+msgstr ""
+
+#: lib/choose_repository.tcl:771
+#, tcl-format
+msgid "Unable to copy object: %s"
+msgstr "Αδυναμία αντιγραφής αντικειμένου: %s"
+
+#: lib/choose_repository.tcl:781
+msgid "Linking objects"
+msgstr "Γίνεται σύνδεση αντικειμένων"
+
+#: lib/choose_repository.tcl:782
+msgid "objects"
+msgstr "αντικείμενα"
+
+#: lib/choose_repository.tcl:790
+#, tcl-format
+msgid "Unable to hardlink object: %s"
+msgstr "Αδυναμία hardlink αντικειμένου: %s"
+
+#: lib/choose_repository.tcl:845
+msgid "Cannot fetch branches and objects.  See console output for details."
+msgstr ""
+"Δε μπόρεσε να γίνει ανάκτηση κλάδων και αντικειμένων. Δείτε την έξοδο "
+"κονσόλας για λεπτομέρειες."
+
+#: lib/choose_repository.tcl:856
+msgid "Cannot fetch tags.  See console output for details."
+msgstr ""
+"Δε μπόρεσε να γίνει ανάκτηση ετικετών. Δείτε την έξοδο κονσόλας για "
+"λεπτομέρειες."
+
+#: lib/choose_repository.tcl:880
+msgid "Cannot determine HEAD.  See console output for details."
+msgstr ""
+"Δε μπόρεσε να γίνει καθορισμός του HEAD (παρακλαδιού). Δείτε την έξοδο "
+"κονσόλας για "
+"λεπτομέρειες."
+
+#: lib/choose_repository.tcl:889
+#, tcl-format
+msgid "Unable to cleanup %s"
+msgstr "Αδυναμία εκκαθάρισης %s"
+
+#: lib/choose_repository.tcl:895
+msgid "Clone failed."
+msgstr "Αποτυχία κλωνοποίησης."
+
+#: lib/choose_repository.tcl:902
+msgid "No default branch obtained."
+msgstr "Δεν έγινε ανάκτηση προεπιλεγμένου κλάδου."
+
+#: lib/choose_repository.tcl:913
+#, tcl-format
+msgid "Cannot resolve %s as a commit."
+msgstr "Δε μπόρεσε να επιλυθεί το %s ως υποβολή."
+
+#: lib/choose_repository.tcl:925
+msgid "Creating working directory"
+msgstr "Δημιουργία φακέλου εργασίας"
+
+#: lib/choose_repository.tcl:926 lib/index.tcl:65 lib/index.tcl:127
+#: lib/index.tcl:193
+msgid "files"
+msgstr "αρχεία"
+
+#: lib/choose_repository.tcl:955
+msgid "Initial file checkout failed."
+msgstr "Η αρχική εξαγωγή αρχείου απέτυχε."
+
+#: lib/choose_repository.tcl:971
+msgid "Open"
+msgstr "Άνοιγμα"
+
+#: lib/choose_repository.tcl:981
+msgid "Repository:"
+msgstr "Αποθετήριο:"
+
+#: lib/choose_repository.tcl:1031
+#, tcl-format
+msgid "Failed to open repository %s:"
+msgstr "Αποτυχία ανοίγματος αποθετηρίου %s:"
+
+#: lib/choose_rev.tcl:53
+#, fuzzy
+msgid "This Detached Checkout"
+msgstr "Αποσυνδεδεμένη Εξαγωγή"
+
+#: lib/choose_rev.tcl:60
+msgid "Revision Expression:"
+msgstr "Έκφραση Αναθεώρησης:"
+
+#: lib/choose_rev.tcl:74
+msgid "Local Branch"
+msgstr "Τοπικός Κλάδος"
+
+#: lib/choose_rev.tcl:79
+msgid "Tracking Branch"
+msgstr "Κλάδος Παρακολούθησης"
+
+#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:538
+msgid "Tag"
+msgstr "Ετικέτα"
+
+#: lib/choose_rev.tcl:317
+#, tcl-format
+msgid "Invalid revision: %s"
+msgstr "Μη έγκυρη αναθεώρηση: %s"
+
+#: lib/choose_rev.tcl:338
+msgid "No revision selected."
+msgstr "Δεν έχει επιλεγεί αναθεώρηση."
+
+#: lib/choose_rev.tcl:346
+msgid "Revision expression is empty."
+msgstr "Η έκφραση αναθεώρησης είναι κενή."
+
+#: lib/choose_rev.tcl:531
+msgid "Updated"
+msgstr "Ενημερωμένο"
+
+#: lib/choose_rev.tcl:559
+#, fuzzy
+msgid "URL"
+msgstr ""
+
+#: lib/commit.tcl:9
+msgid ""
+"There is nothing to amend.\n"
+"\n"
+"You are about to create the initial commit.  There is no commit before this "
+"to amend.\n"
+msgstr ""
+"Δεν υπάρχει κάτι προς διόρθωση.\n"
+"\n"
+"Πρόκειται να δημιουργήσετε την αρχική υποβολή. Δεν υπάρχει υποβολή πριν από "
+"αυτή για να διορθώσετε.\n"
+
+#: lib/commit.tcl:18
+msgid ""
+"Cannot amend while merging.\n"
+"\n"
+"You are currently in the middle of a merge that has not been fully "
+"completed.  You cannot amend the prior commit unless you first abort the "
+"current merge activity.\n"
+msgstr ""
+"Δε γίνεται διόρθωση καθώς συγχωνεύετε.\n"
+"\n"
+"Βρίσκεστε στο μέσο μιας συγχώνευσης που δεν έχει ολοκληρωθεί. Δε μπορείτε να "
+"διορθώσετε την προηγούμενη υποβολή εκτός εάν ακυρώσετε την τρέχουσα ενέργεια "
+"συγχώνευσης.\n"
+
+#: lib/commit.tcl:49
+msgid "Error loading commit data for amend:"
+msgstr "Σφάλμα φόρτωσης δεδομένων υποβολής προς διόρθωση:"
+
+#: lib/commit.tcl:76
+msgid "Unable to obtain your identity:"
+msgstr "Αδυναμία ανάκτησης της ταυτότητάς σας:"
+
+#: lib/commit.tcl:81
+msgid "Invalid GIT_COMMITTER_IDENT:"
+msgstr "Μη έγκυρο GIT_COMMITTER_IDENT:"
+
+#: lib/commit.tcl:133
+msgid ""
+"Last scanned state does not match repository state.\n"
+"\n"
+"Another Git program has modified this repository since the last scan.  A "
+"rescan must be performed before another commit can be created.\n"
+"\n"
+"The rescan will be automatically started now.\n"
+msgstr ""
+"Η τελευταία κατάσταση που ανιχνεύθηκε δε συμφωνεί με την κατάσταση του "
+"αποθετηρίου.\n"
+"\n"
+"Κάποιο άλλο πρόγραμμα Git τροποποίησε το αποθετήριο από την τελευταία "
+"ανίχνευση. Πρέπει να γίνει επανανίχνευση πριν τη δημιουργία νέας υποβολής.\n"
+"\n"
+"Η επανανίχνευση θα ξεκινήσει αυτόματα τώρα.\n"
+
+#: lib/commit.tcl:154
+#, tcl-format
+msgid ""
+"Unmerged files cannot be committed.\n"
+"\n"
+"File %s has merge conflicts.  You must resolve them and stage the file "
+"before committing.\n"
+msgstr ""
+"Τα μη συγχωνευμένα αρχεία δε μπορούν να υποβληθούν.\n"
+"\n"
+"Το αρχείο %s έχει συγκρούσεις συγχώνευσης. Πρέπει να τις επιλύσετε και να "
+"σταδιοποιήσετε το αρχείο πριν την υποβολή.\n"
+
+#: lib/commit.tcl:162
+#, tcl-format
+msgid ""
+"Unknown file state %s detected.\n"
+"\n"
+"File %s cannot be committed by this program.\n"
+msgstr ""
+"Άγνωστη κατάσταση αρχείου %s ανιχνεύθηκε.\n"
+"\n"
+"Το αρχείο %s δε μπορεί να υποβληθεί από αυτό το πρόγραμμα.\n"
+
+#: lib/commit.tcl:170
+msgid ""
+"No changes to commit.\n"
+"\n"
+"You must stage at least 1 file before you can commit.\n"
+msgstr ""
+"Δεν υπάρχουν αλλαγές προς υποβολή.\n"
+"\n "
+"Πρέπει να σταδιοποιήσετε τουλάχιστον 1 αρχείο πριν να κάνετε υποβολή.\n"
+
+#: lib/commit.tcl:183
+msgid ""
+"Please supply a commit message.\n"
+"\n"
+"A good commit message has the following format:\n"
+"\n"
+"- First line: Describe in one sentence what you did.\n"
+"- Second line: Blank\n"
+"- Remaining lines: Describe why this change is good.\n"
+msgstr ""
+"Παρακαλώ δώστε ένα μήνυμα υποβολής.\n"
+"\n"
+"Ένα σωστό μήνυμα υποβολής έχει την εξής μορφή:\n"
+"\n"
+"- Πρώτη γραμμή: Περιγραφή σε μία πρόταση του τι κάνατε.\n"
+"- Δεύτερη γραμμή: Κενή\n"
+"- Υπόλοιπες γραμμές: Περιγραφή του γιατί αυτή η αλλαγή είναι σωστή.\n"
+
+#: lib/commit.tcl:207
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr "προειδοποίηση: H Tcl δεν υποστηρίζει την κωδικοποίηση '%s'."
+
+#: lib/commit.tcl:221
+msgid "Calling pre-commit hook..."
+msgstr "Γίνεται κλήση του pre-commit hook..."
+
+#: lib/commit.tcl:236
+msgid "Commit declined by pre-commit hook."
+msgstr "Η υποβολή απορρίφθηκε από το pre-commit hook."
+
+#: lib/commit.tcl:259
+msgid "Calling commit-msg hook..."
+msgstr "Γίνεται κλήση του commit-msg hook..."
+
+#: lib/commit.tcl:274
+msgid "Commit declined by commit-msg hook."
+msgstr "Η υποβολή απορρίφθηκε από το commit-msg hook."
+
+#: lib/commit.tcl:287
+msgid "Committing changes..."
+msgstr "Γίνεται υποβολή των αλλαγών..."
+
+#: lib/commit.tcl:303
+msgid "write-tree failed:"
+msgstr "το write-tree απέτυχε:"
+
+#: lib/commit.tcl:304 lib/commit.tcl:348 lib/commit.tcl:368
+msgid "Commit failed."
+msgstr "Η υποβολή απέτυχε."
+
+#: lib/commit.tcl:321
+#, tcl-format
+msgid "Commit %s appears to be corrupt"
+msgstr "Η υποβολή %s δείχνει κατεστραμμένη"
+
+#: lib/commit.tcl:326
+msgid ""
+"No changes to commit.\n"
+"\n"
+"No files were modified by this commit and it was not a merge commit.\n"
+"\n"
+"A rescan will be automatically started now.\n"
+msgstr ""
+"Δεν υπάρχουν αλλαγές προς υποβολή.\n"
+"\n"
+"Δεν τροποποιήθηκαν αρχεία από αυτή την υποβολή και δεν ήταν υποβολή "
+"συγχώνευσης.\n"
+"\n"
+"Θα ξεκινήσει αυτόματα επανανίχνευση τώρα.\n"
+
+#: lib/commit.tcl:333
+msgid "No changes to commit."
+msgstr "Δεν υπάρχουν αλλαγές προς υποβολή."
+
+#: lib/commit.tcl:347
+msgid "commit-tree failed:"
+msgstr "το commit-tree απέτυχε:"
+
+#: lib/commit.tcl:367
+msgid "update-ref failed:"
+msgstr "το update-ref απέτυχε:"
+
+#: lib/commit.tcl:454
+#, tcl-format
+msgid "Created commit %s: %s"
+msgstr "Δημιουργήθηκε υποβολή %s: %s"
+
+#: lib/console.tcl:59
+msgid "Working... please wait..."
+msgstr "Γίνεται εργασία... Παρακαλώ περιμένετε..."
+
+#: lib/console.tcl:186
+msgid "Success"
+msgstr "Επιτυχία"
+
+#: lib/console.tcl:200
+msgid "Error: Command Failed"
+msgstr "Σφάλμα: Η Εντολή Απέτυχε"
+
+#: lib/database.tcl:43
+msgid "Number of loose objects"
+msgstr "Αριθμός ελεύθερων αντικειμένων"
+
+#: lib/database.tcl:44
+msgid "Disk space used by loose objects"
+msgstr "Χώρος κατειλλημένος από ελεύθερα αντικείμενα"
+
+#: lib/database.tcl:45
+msgid "Number of packed objects"
+msgstr "Αριθμός πακεταρισμένων αντικειμένων"
+
+#: lib/database.tcl:46
+msgid "Number of packs"
+msgstr "Αριθμός πακέτων"
+
+#: lib/database.tcl:47
+msgid "Disk space used by packed objects"
+msgstr "Χώρος κατειλλημένος από πακεταρισμένα αντικείμενα"
+
+#: lib/database.tcl:48
+msgid "Packed objects waiting for pruning"
+msgstr "Πακεταρισμένα αντικείμενα έτοιμα για κλάδεμα"
+
+#: lib/database.tcl:49
+msgid "Garbage files"
+msgstr "Άχρηστα αρχεία"
+
+#: lib/database.tcl:72
+msgid "Compressing the object database"
+msgstr "Γίνεται συμπίεση της βάσης δεδομένων αντικειμένων"
+
+#: lib/database.tcl:83
+msgid "Verifying the object database with fsck-objects"
+msgstr ""
+"Γίνεται επαλήθευση της βάσης δεδομένων αντικειμένων με αντικείμενα fsck"
+
+#: lib/database.tcl:108
+#, tcl-format
+msgid ""
+"This repository currently has approximately %i loose objects.\n"
+"\n"
+"To maintain optimal performance it is strongly recommended that you compress "
+"the database when more than %i loose objects exist.\n"
+"\n"
+"Compress the database now?"
+msgstr ""
+"Αυτό το αποθετήριο έχει αυτή τη στιγμή περίπου %i ελεύθερα αντικείμενα.\n"
+"\n"
+"Για τη διατήρηση βέλτιστων επιδόσεων συνιστάται να συμπιέσετε τη βάση "
+"δεδομένων όταν υπάρχουν περισσότερα από %i ελεύθερα αντικείμενα.\n"
+"\n"
+"Συμπίεση της βάσης δεδομένων τώρα;"
+
+#: lib/date.tcl:25
+#, tcl-format
+msgid "Invalid date from Git: %s"
+msgstr "Μη έγκυρη ημερομηνία από το Git: %s"
+
+#: lib/diff.tcl:42
+#, tcl-format
+msgid ""
+"No differences detected.\n"
+"\n"
+"%s has no changes.\n"
+"\n"
+"The modification date of this file was updated by another application, but "
+"the content within the file was not changed.\n"
+"\n"
+"A rescan will be automatically started to find other files which may have "
+"the same state."
+msgstr ""
+"Δεν ανιχνεύθηκαν διαφορές.\n"
+"\n"
+"Το %s δεν έχει αλλαγές."
+"\n"
+"Η ημερομηνία τροποποίησης αυτού του αρχείου ενημερώθηκε από άλλη εφαρμογή, "
+"αλλά το περιεχόμενο του αρχείου δεν άλλαξε.\n"
+"\n"
+"Θα ξεκινήσει αυτόματα επανανίχνευση για να βρεθούν άλλα αρχεία που μπορεί να "
+"βρίσκονται σε ίδια κατάσταση."
+
+#: lib/diff.tcl:81
+#, tcl-format
+msgid "Loading diff of %s..."
+msgstr "Γίνεται φόρτωση διαφοράς του %s..."
+
+#: lib/diff.tcl:114 lib/diff.tcl:184
+#, tcl-format
+msgid "Unable to display %s"
+msgstr "Δεν είναι δυνατή η προβολή του %s"
+
+#: lib/diff.tcl:115
+msgid "Error loading file:"
+msgstr "Σφάλμα φόρτωσης αρχείου:"
+
+#: lib/diff.tcl:122
+msgid "Git Repository (subproject)"
+msgstr "Αποθετήριο Git (θυγατρικό έργο)"
+
+#: lib/diff.tcl:134
+msgid "* Binary file (not showing content)."
+msgstr "* Δυαδικό αρχείο (μη εμφάνιση περιεχομένου)."
+
+#: lib/diff.tcl:185
+msgid "Error loading diff:"
+msgstr "Σφάλμα φόρτωσης διαφοράς:"
+
+#: lib/diff.tcl:303
+msgid "Failed to unstage selected hunk."
+msgstr "Αποτυχία αποσταδιοποίησης επιλεγμένου κομματιού."
+
+#: lib/diff.tcl:310
+msgid "Failed to stage selected hunk."
+msgstr "Αποτυχία σταδιοποίησης επιλεγμένου κομματιού."
+
+#: lib/error.tcl:20 lib/error.tcl:114
+msgid "error"
+msgstr "σφάλμα"
+
+#: lib/error.tcl:36
+msgid "warning"
+msgstr "προειδοποίηση"
+
+#: lib/error.tcl:94
+msgid "You must correct the above errors before committing."
+msgstr "Πρέπει να διορθώσετε τα παραπάνω λάθη πριν την υποβολή."
+
+#: lib/index.tcl:6
+msgid "Unable to unlock the index."
+msgstr "Αδυναμία ξεκλειδώματος του ευρετηρίου."
+
+#: lib/index.tcl:15
+msgid "Index Error"
+msgstr "Σφάλμα Ευρετηρίου"
+
+#: lib/index.tcl:21
+msgid ""
+"Updating the Git index failed.  A rescan will be automatically started to "
+"resynchronize git-gui."
+msgstr ""
+"Η ενημέρωση του ευρετηρίου Git απέτυχε. Θα ξεκινήσει αυτόματα επανανίχνευση "
+"για επανασυγχρονισμό του git-gui."
+
+#: lib/index.tcl:27
+msgid "Continue"
+msgstr "Συνέχεια"
+
+#: lib/index.tcl:31
+msgid "Unlock Index"
+msgstr "Ξεκλείδωμα Ευρετηρίου"
+
+#: lib/index.tcl:282
+#, tcl-format
+msgid "Unstaging %s from commit"
+msgstr "Αποσταδιοποίηση %s από υποβολή"
+
+#: lib/index.tcl:313
+msgid "Ready to commit."
+msgstr "Έτοιμο προς υποβολή."
+
+#: lib/index.tcl:326
+#, tcl-format
+msgid "Adding %s"
+msgstr "Προσθήκη %s"
+
+#: lib/index.tcl:381
+#, tcl-format
+msgid "Revert changes in file %s?"
+msgstr "Αναίρεση αλλαγών στο αρχείο %s;"
+
+#: lib/index.tcl:383
+#, tcl-format
+msgid "Revert changes in these %i files?"
+msgstr "Αναίρεση αλλαγών σε αυτά τα %i αρχεία;"
+
+#: lib/index.tcl:391
+msgid "Any unstaged changes will be permanently lost by the revert."
+msgstr ""
+"Όλες οι μη σταδιοποιημένες αλλαγές θα χαθούν οριστικά από την αναίρεση."
+
+#: lib/index.tcl:394
+msgid "Do Nothing"
+msgstr "Καμία Ενέργεια"
+
+#: lib/merge.tcl:13
+msgid ""
+"Cannot merge while amending.\n"
+"\n"
+"You must finish amending this commit before starting any type of merge.\n"
+msgstr ""
+"Δε γίνεται συγχώνευση καθώς διορθώνετε.\n"
+"\n"
+"Πρέπει να τελειώσετε τη διόρθωση αυτής της υποβολής πριν να ξεκινήσετε "
+"οποιασδήποτε μορφής συγχώνευση.\n"
+
+#: lib/merge.tcl:27
+msgid ""
+"Last scanned state does not match repository state.\n"
+"\n"
+"Another Git program has modified this repository since the last scan.  A "
+"rescan must be performed before a merge can be performed.\n"
+"\n"
+"The rescan will be automatically started now.\n"
+msgstr ""
+"Η τελευταία κατάσταση που ανιχνεύθηκε δε συμφωνεί με την κατάσταση του "
+"αποθετηρίου.\n"
+"\n"
+"Κάποιο άλλο πρόγραμμα Git τροποποίησε το αποθετήριο από την τελευταία "
+"ανίχνευση. Πρέπει να γίνει επανανίχνευση πριν τη διενέργεια συγχώνευσης.\n"
+"\n"
+"Η επανανίχνευση θα ξεκινήσει αυτόματα τώρα.\n"
+
+#: lib/merge.tcl:44
+#, tcl-format
+msgid ""
+"You are in the middle of a conflicted merge.\n"
+"\n"
+"File %s has merge conflicts.\n"
+"\n"
+"You must resolve them, stage the file, and commit to complete the current "
+"merge.  Only then can you begin another merge.\n"
+msgstr ""
+"Βρίσκεστε στο μέσο μιας συγκρουόμενης συγχώνευσης.\n"
+"\n"
+"Το αρχείο %s έχει συγκρούσεις συγχώνευσης.\n"
+"\n"
+"Πρέπει να τις επιλύσετε, να σταδιοποιήσετε το αρχείο, και να κάνετε υποβολή "
+"για να ολοκληρώσετε την τρέχουσα συγχώνευση. Μόνο τότε μπορείτε να "
+"ξεκινήσετε άλλη συγχώνευση.\n"
+
+#: lib/merge.tcl:54
+#, tcl-format
+msgid ""
+"You are in the middle of a change.\n"
+"\n"
+"File %s is modified.\n"
+"\n"
+"You should complete the current commit before starting a merge.  Doing so "
+"will help you abort a failed merge, should the need arise.\n"
+msgstr ""
+"Βρίσκεστε στο μέσο μιας αλλαγής.\n"
+"\n"
+"Το αρχείο %s έχει τροποποιηθεί.\n"
+"\n"
+"Πρέπει να ολοκληρώσετε την τρέχουσα συγχώνευση πριν να ξεκινήσετε συγχώνευση."
+" Αυτό βοηθά στην ακύρωση αποτυχημένης συγχώνευσης, εάν χρειαστεί.\n"
+
+#: lib/merge.tcl:106
+#, tcl-format
+msgid "%s of %s"
+msgstr "%s από %s"
+
+#: lib/merge.tcl:119
+#, tcl-format
+msgid "Merging %s and %s..."
+msgstr "Γίνεται συγχώνευση του %s με το %s..."
+
+#: lib/merge.tcl:130
+msgid "Merge completed successfully."
+msgstr "Η συγχώνευση ολοκληρώθηκε επιτυχώς."
+
+#: lib/merge.tcl:132
+msgid "Merge failed.  Conflict resolution is required."
+msgstr "Η συγχώνευση απέτυχε. Απαιτείται επίλυση συγκρούσεων."
+
+#: lib/merge.tcl:157
+#, tcl-format
+msgid "Merge Into %s"
+msgstr "Συγχώνευση με %s"
+
+#: lib/merge.tcl:176
+msgid "Revision To Merge"
+msgstr "Αναθεώρηση Προς Συγχώνευση"
+
+#: lib/merge.tcl:211
+msgid ""
+"Cannot abort while amending.\n"
+"\n"
+"You must finish amending this commit.\n"
+msgstr ""
+"Δε γίνεται ακύρωση καθώς διορθώνετε.\n"
+"\n"
+"Πρέπει να τελειώσετε τη διόρθωση αυτής της υποβολής.\n"
+
+#: lib/merge.tcl:221
+msgid ""
+"Abort merge?\n"
+"\n"
+"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n"
+"\n"
+"Continue with aborting the current merge?"
+msgstr ""
+"Ακύρωση συγχώνευσης;\n"
+"\n"
+"Η ακύρωση της τρέχουσας συγχώνευσης θα προκαλέσει απώλεια *ΟΛΩΝ* των μη "
+"υποβεβλημένων αλλαγών.\n"
+"\n"
+"Να προχωρήσει η ακύρωση της τρέχουσας συγχώνευσης;"
+
+#: lib/merge.tcl:227
+msgid ""
+"Reset changes?\n"
+"\n"
+"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n"
+"\n"
+"Continue with resetting the current changes?"
+msgstr ""
+"Επαναφορά αλλαγών;\n"
+"\n"
+"Η επαναφορά των αλλαγών θα προκαλέσει απώλεια *ΟΛΩΝ* των μη υποβεβλημένων "
+"αλλαγών.\n"
+"\n"
+"Να συνεχίσει η επαναφορά των τρέχουσων αλλαγών;"
+
+#: lib/merge.tcl:238
+msgid "Aborting"
+msgstr "Γίνεται ακύρωση"
+
+#: lib/merge.tcl:238
+msgid "files reset"
+msgstr "αρχεία που επαναφέρθηκαν"
+
+#: lib/merge.tcl:265
+msgid "Abort failed."
+msgstr "Η ακύρωση απέτυχε."
+
+#: lib/merge.tcl:267
+msgid "Abort completed.  Ready."
+msgstr "Η ακύρωση απέτυχε. Έτοιμο."
+
+#: lib/option.tcl:95
+msgid "Restore Defaults"
+msgstr "Επαναφορά Προεπιλογών"
+
+#: lib/option.tcl:99
+msgid "Save"
+msgstr "Αποθήκευση"
+
+#: lib/option.tcl:109
+#, tcl-format
+msgid "%s Repository"
+msgstr "%s Αποθετήριο"
+
+#: lib/option.tcl:110
+msgid "Global (All Repositories)"
+msgstr "Ολικό (Όλα τα Αποθετήρια)"
+
+#: lib/option.tcl:116
+msgid "User Name"
+msgstr "Όνομα Χρήστη"
+
+#: lib/option.tcl:117
+msgid "Email Address"
+msgstr "Διεύθυνση Email"
+
+#: lib/option.tcl:119
+msgid "Summarize Merge Commits"
+msgstr "Περίληψη Υποβολών Συγχώνευσης"
+
+#: lib/option.tcl:120
+msgid "Merge Verbosity"
+msgstr "Λεπτομέρεια Συγχώνευσης"
+
+#: lib/option.tcl:121
+msgid "Show Diffstat After Merge"
+msgstr "Προβολή Στατιστικών Διαφοράς Μετά από Συγχώνευση"
+
+#: lib/option.tcl:123
+msgid "Trust File Modification Timestamps"
+msgstr "Εμπιστοσύνη Ημερομηνιών Μετατροπής Αρχείων"
+
+#: lib/option.tcl:124
+msgid "Prune Tracking Branches During Fetch"
+msgstr "Κλάδεμα Κλάδων Παρακολούθησης Κατά Την Ανάκτηση"
+
+#: lib/option.tcl:125
+msgid "Match Tracking Branches"
+msgstr "Συμφωνία Κλάδων Παρακολούθησης"
+
+#: lib/option.tcl:126
+msgid "Number of Diff Context Lines"
+msgstr "Αριθμός Γραμμών Εννοιολογικού Πλαισίου Διαφοράς"
+
+#: lib/option.tcl:127
+msgid "Commit Message Text Width"
+msgstr "Πλάτος Κειμένου Μηνύματος Υποβολής"
+
+#: lib/option.tcl:128
+msgid "New Branch Name Template"
+msgstr "Νέο Πρότυπο Ονόματος Κλάδου"
+
+#: lib/option.tcl:192
+msgid "Spelling Dictionary:"
+msgstr "Λεξικό Ορθογραφίας:"
+
+#: lib/option.tcl:216
+msgid "Change Font"
+msgstr "Αλλαγή Γραμματοσειράς"
+
+#: lib/option.tcl:220
+#, tcl-format
+msgid "Choose %s"
+msgstr "Επιλογή %s"
+
+#: lib/option.tcl:226
+#, fuzzy
+msgid "pt."
+msgstr ""
+
+#: lib/option.tcl:240
+msgid "Preferences"
+msgstr "Προτιμήσεις"
+
+#: lib/option.tcl:275
+msgid "Failed to completely save options:"
+msgstr "Αποτυχία πλήρους αποθήκευσης επιλογών:"
+
+#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34
+msgid "Delete Remote Branch"
+msgstr "Διαγραφή Απομακρυσμένου Κλάδου"
+
+#: lib/remote_branch_delete.tcl:47
+msgid "From Repository"
+msgstr "Από Αποθετήριο"
+
+#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123
+msgid "Remote:"
+msgstr "Απομακρυσμένο:"
+
+#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138
+#, fuzzy
+msgid "Arbitrary URL:"
+msgstr "Αυθαίρετο URL:"
+
+#: lib/remote_branch_delete.tcl:84
+msgid "Branches"
+msgstr "Κλάδοι"
+
+#: lib/remote_branch_delete.tcl:109
+msgid "Delete Only If"
+msgstr "Διαγραφή Μόνο Εάν"
+
+#: lib/remote_branch_delete.tcl:111
+msgid "Merged Into:"
+msgstr "Συγχωνευμένο Με:"
+
+#: lib/remote_branch_delete.tcl:119
+msgid "Always (Do not perform merge checks)"
+msgstr "Πάντα (Μη διενεργηθούν έλεγχοι συγχώνευσης)"
+
+#: lib/remote_branch_delete.tcl:152
+msgid "A branch is required for 'Merged Into'."
+msgstr "Απαιτείται ένας κλάδος για 'Συγχωνευμένο Με'."
+
+#: lib/remote_branch_delete.tcl:184
+#, tcl-format
+msgid ""
+"The following branches are not completely merged into %s:\n"
+"\n"
+" - %s"
+msgstr ""
+"Οι εξής κλάδοι δεν είναι πλήρως συγχωνευμένοι με το %s:\n"
+"\n"
+" - %s"
+
+#: lib/remote_branch_delete.tcl:189
+#, tcl-format
+msgid ""
+"One or more of the merge tests failed because you have not fetched the "
+"necessary commits.  Try fetching from %s first."
+msgstr ""
+"Μία ή περισσότερες από τις δοκιμές συγχώνευσης απέτυχαν επειδή δεν έχετε "
+"φέρει τις αναγκαίες υποβολές. Δοκιμάστε ανάκτηση από το %s πρώτα."
+
+#: lib/remote_branch_delete.tcl:207
+msgid "Please select one or more branches to delete."
+msgstr "Παρακαλώ επιλέξτε έναν ή περισσότερους κλάδους προς διαγραφή."
+
+#: lib/remote_branch_delete.tcl:216
+msgid ""
+"Recovering deleted branches is difficult.\n"
+"\n"
+"Delete the selected branches?"
+msgstr ""
+"Η ανάκτηση διεγραμμένων κλάδων είναι δύσκολη.\n"
+"\n"
+"Διαγραφή των επιλεγμένων κλάδων;"
+
+#: lib/remote_branch_delete.tcl:226
+#, tcl-format
+msgid "Deleting branches from %s"
+msgstr "Γίνεται διαγραφή κλάδων από %s"
+
+#: lib/remote_branch_delete.tcl:286
+msgid "No repository selected."
+msgstr "Δεν έχει επιλεγεί αποθετήριο."
+
+#: lib/remote_branch_delete.tcl:291
+#, tcl-format
+msgid "Scanning %s..."
+msgstr "Ανίχνευση %s..."
+
+#: lib/remote.tcl:165
+msgid "Prune from"
+msgstr "Κλάδεμα από"
+
+#: lib/remote.tcl:170
+msgid "Fetch from"
+msgstr "Ανάκτηση από"
+
+#: lib/remote.tcl:213
+msgid "Push to"
+msgstr "Ώθηση σε"
+
+#: lib/shortcut.tcl:20 lib/shortcut.tcl:61
+msgid "Cannot write shortcut:"
+msgstr "Δε μπόρεσε να αποθηκευτεί η συντόμευση:"
+
+#: lib/shortcut.tcl:136
+msgid "Cannot write icon:"
+msgstr "Δε μπόρεσε να αποθηκευτεί το εικονίδιο:"
+
+#: lib/spellcheck.tcl:57
+msgid "Unsupported spell checker"
+msgstr "Mη υποστηριζόμενος ελεγκτής ορθογραφίας"
+
+#: lib/spellcheck.tcl:65
+msgid "Spell checking is unavailable"
+msgstr "Έλεγχος ορθογραφίας μη διαθέσιμος"
+
+#: lib/spellcheck.tcl:68
+msgid "Invalid spell checking configuration"
+msgstr "Μη έγκυρη ρύθμιση ελέγχου ορθογραφίας"
+
+#: lib/spellcheck.tcl:70
+#, tcl-format
+msgid "Reverting dictionary to %s."
+msgstr "Γίνεται επαναφορά του λεξικού σε %s."
+
+#: lib/spellcheck.tcl:73
+msgid "Spell checker silently failed on startup"
+msgstr "Ο ελεγκτής ορθογραφίας απέτυχε σιωπηλά κατά την εκκίνηση"
+
+#: lib/spellcheck.tcl:80
+msgid "Unrecognized spell checker"
+msgstr "Μη αναγνωρίσιμος ελεγκτής ορθογραφίας"
+
+#: lib/spellcheck.tcl:180
+msgid "No Suggestions"
+msgstr "Καμία Πρόταση"
+
+#: lib/spellcheck.tcl:381
+msgid "Unexpected EOF from spell checker"
+msgstr "Μη αναμενόμενο τέλος αρχείου από τον ελεγκτή ορθογραφίας"
+
+#: lib/spellcheck.tcl:385
+msgid "Spell Checker Failed"
+msgstr "Αποτυχία Ελεγκτή Ορθογραφίας"
+
+#: lib/status_bar.tcl:83
+#, tcl-format
+msgid "%s ... %*i of %*i %s (%3i%%)"
+msgstr "%s ... %*i από %*i %s (%3i%%)"
+
+#: lib/transport.tcl:6
+#, tcl-format
+msgid "fetch %s"
+msgstr "ανάκτηση %s"
+
+#: lib/transport.tcl:7
+#, tcl-format
+msgid "Fetching new changes from %s"
+msgstr "Ανάκτηση νέων αλλαγών από το %s"
+
+#: lib/transport.tcl:18
+#, tcl-format
+msgid "remote prune %s"
+msgstr "απομακρυσμένο κλάδεμα %s"
+
+#: lib/transport.tcl:19
+#, tcl-format
+msgid "Pruning tracking branches deleted from %s"
+msgstr "Γίνεται κλάδεμα κλάδων παρακολούθησης που διεγράφησαν από το %s"
+
+#: lib/transport.tcl:25 lib/transport.tcl:71
+#, tcl-format
+msgid "push %s"
+msgstr "ώθηση %s"
+
+#: lib/transport.tcl:26
+#, tcl-format
+msgid "Pushing changes to %s"
+msgstr "Γίνεται ώθηση αλλαγών στο %s"
+
+#: lib/transport.tcl:72
+#, tcl-format
+msgid "Pushing %s %s to %s"
+msgstr "Γίνεται ώθηση %s %s στο %s"
+
+#: lib/transport.tcl:89
+msgid "Push Branches"
+msgstr "Ώθηση Κλάδων"
+
+#: lib/transport.tcl:103
+msgid "Source Branches"
+msgstr "Πηγαίοι Κλάδοι"
+
+#: lib/transport.tcl:120
+msgid "Destination Repository"
+msgstr "Αποθετήριο Προορισμού"
+
+#: lib/transport.tcl:158
+msgid "Transfer Options"
+msgstr "Επιλογές Μεταφοράς"
+
+#: lib/transport.tcl:160
+msgid "Force overwrite existing branch (may discard changes)"
+msgstr ""
+"Εξαναγκασμός επεγγραφής υπάρχοντος κλάδου (μπορεί να απορρίψει αλλαγές)"
+
+#: lib/transport.tcl:164
+msgid "Use thin pack (for slow network connections)"
+msgstr "Χρήση ισχνού πακέτου (για αργές συνδέσεις δικτύου)"
+
+#: lib/transport.tcl:168
+msgid "Include tags"
+msgstr "Συμπερίληψη ετικετών"
+
+
index a944ace6ced4c68b55b7b25023d546521a8ecd68..81706965f377787f8048bc5705ab8da10af55586 100644 (file)
@@ -9,8 +9,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: fr\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-11-16 13:56-0800\n"
-"PO-Revision-Date: 2008-11-20 10:20+0100\n"
+"POT-Creation-Date: 2010-01-26 15:47-0800\n"
+"PO-Revision-Date: 2010-02-02 12:59+0100\n"
 "Last-Translator: Christian Couder <chriscool@tuxfamily.org>\n"
 "Language-Team: French\n"
 "MIME-Version: 1.0\n"
@@ -19,33 +19,33 @@ msgstr ""
 "X-Generator: KBabel 1.11.4\n"
 "Plural-Forms:  nplurals=2; plural=(n > 1);\n"
 
-#: git-gui.sh:41 git-gui.sh:737 git-gui.sh:751 git-gui.sh:764 git-gui.sh:847
-#: git-gui.sh:866
+#: git-gui.sh:41 git-gui.sh:793 git-gui.sh:807 git-gui.sh:820 git-gui.sh:903
+#: git-gui.sh:922
 msgid "git-gui: fatal error"
 msgstr "git-gui: erreur fatale"
 
-#: git-gui.sh:689
+#: git-gui.sh:743
 #, tcl-format
 msgid "Invalid font specified in %s:"
 msgstr "Police invalide spécifiée dans %s :"
 
-#: git-gui.sh:723
+#: git-gui.sh:779
 msgid "Main Font"
 msgstr "Police principale"
 
-#: git-gui.sh:724
+#: git-gui.sh:780
 msgid "Diff/Console Font"
 msgstr "Police diff/console"
 
-#: git-gui.sh:738
+#: git-gui.sh:794
 msgid "Cannot find git in PATH."
 msgstr "Impossible de trouver git dans PATH."
 
-#: git-gui.sh:765
+#: git-gui.sh:821
 msgid "Cannot parse Git version string:"
 msgstr "Impossible de parser la version de Git :"
 
-#: git-gui.sh:783
+#: git-gui.sh:839
 #, tcl-format
 msgid ""
 "Git version cannot be determined.\n"
@@ -64,446 +64,475 @@ msgstr ""
 "\n"
 "Peut-on considérer que '%s' est en version 1.5.0 ?\n"
 
-#: git-gui.sh:1062
+#: git-gui.sh:1128
 msgid "Git directory not found:"
 msgstr "Impossible de trouver le répertoire git :"
 
-#: git-gui.sh:1069
+#: git-gui.sh:1146
 msgid "Cannot move to top of working directory:"
 msgstr "Impossible d'aller à la racine du répertoire de travail :"
 
-#: git-gui.sh:1076
-msgid "Cannot use funny .git directory:"
-msgstr "Impossible d'utiliser le répertoire .git:"
+#: git-gui.sh:1154
+msgid "Cannot use bare repository:"
+msgstr "Impossible d'utiliser un dépôt nu (bare) :"
 
-#: git-gui.sh:1081
+#: git-gui.sh:1162
 msgid "No working directory"
 msgstr "Aucun répertoire de travail"
 
-#: git-gui.sh:1247 lib/checkout_op.tcl:305
+#: git-gui.sh:1334 lib/checkout_op.tcl:306
 msgid "Refreshing file status..."
 msgstr "Rafraîchissement du statut des fichiers..."
 
-#: git-gui.sh:1303
+#: git-gui.sh:1390
 msgid "Scanning for modified files ..."
 msgstr "Recherche de fichiers modifiés..."
 
-#: git-gui.sh:1367
+#: git-gui.sh:1454
 msgid "Calling prepare-commit-msg hook..."
 msgstr "Lancement de l'action de préparation du message de commit..."
 
-#: git-gui.sh:1384
+#: git-gui.sh:1471
 msgid "Commit declined by prepare-commit-msg hook."
 msgstr "Commit refusé par l'action de préparation du message de commit."
 
-#: git-gui.sh:1542 lib/browser.tcl:246
+#: git-gui.sh:1629 lib/browser.tcl:246
 msgid "Ready."
 msgstr "Prêt."
 
-#: git-gui.sh:1819
+#: git-gui.sh:1787
+#, tcl-format
+msgid "Displaying only %s of %s files."
+msgstr "Affiche seulement %s fichiers sur %s."
+
+#: git-gui.sh:1913
 msgid "Unmodified"
 msgstr "Non modifié"
 
-#: git-gui.sh:1821
+#: git-gui.sh:1915
 msgid "Modified, not staged"
 msgstr "Modifié, pas indexé"
 
-#: git-gui.sh:1822 git-gui.sh:1830
+#: git-gui.sh:1916 git-gui.sh:1924
 msgid "Staged for commit"
 msgstr "Indexé"
 
-#: git-gui.sh:1823 git-gui.sh:1831
+#: git-gui.sh:1917 git-gui.sh:1925
 msgid "Portions staged for commit"
 msgstr "Portions indexées"
 
-#: git-gui.sh:1824 git-gui.sh:1832
+#: git-gui.sh:1918 git-gui.sh:1926
 msgid "Staged for commit, missing"
 msgstr "Indexés, manquant"
 
-#: git-gui.sh:1826
+#: git-gui.sh:1920
 msgid "File type changed, not staged"
 msgstr "Le type de fichier a changé, non indexé"
 
-#: git-gui.sh:1827
+#: git-gui.sh:1921
 msgid "File type changed, staged"
 msgstr "Le type de fichier a changé, indexé"
 
-#: git-gui.sh:1829
+#: git-gui.sh:1923
 msgid "Untracked, not staged"
 msgstr "Non versionné, non indexé"
 
-#: git-gui.sh:1834
+#: git-gui.sh:1928
 msgid "Missing"
 msgstr "Manquant"
 
-#: git-gui.sh:1835
+#: git-gui.sh:1929
 msgid "Staged for removal"
 msgstr "Indexé pour suppression"
 
-#: git-gui.sh:1836
+#: git-gui.sh:1930
 msgid "Staged for removal, still present"
 msgstr "Indexé pour suppression, toujours présent"
 
-#: git-gui.sh:1838 git-gui.sh:1839 git-gui.sh:1840 git-gui.sh:1841
-#: git-gui.sh:1842 git-gui.sh:1843
+#: git-gui.sh:1932 git-gui.sh:1933 git-gui.sh:1934 git-gui.sh:1935
+#: git-gui.sh:1936 git-gui.sh:1937
 msgid "Requires merge resolution"
 msgstr "Nécessite la résolution d'une fusion"
 
-#: git-gui.sh:1878
+#: git-gui.sh:1972
 msgid "Starting gitk... please wait..."
 msgstr "Lancement de gitk... un instant..."
 
-#: git-gui.sh:1887
+#: git-gui.sh:1984
 msgid "Couldn't find gitk in PATH"
 msgstr "Impossible de trouver gitk dans PATH."
 
-#: git-gui.sh:2280 lib/choose_repository.tcl:36
+#: git-gui.sh:2043
+msgid "Couldn't find git gui in PATH"
+msgstr "Impossible de trouver git gui dans PATH"
+
+#: git-gui.sh:2455 lib/choose_repository.tcl:36
 msgid "Repository"
 msgstr "Dépôt"
 
-#: git-gui.sh:2281
+#: git-gui.sh:2456
 msgid "Edit"
 msgstr "Édition"
 
-#: git-gui.sh:2283 lib/choose_rev.tcl:561
+#: git-gui.sh:2458 lib/choose_rev.tcl:561
 msgid "Branch"
 msgstr "Branche"
 
-#: git-gui.sh:2286 lib/choose_rev.tcl:548
+#: git-gui.sh:2461 lib/choose_rev.tcl:548
 msgid "Commit@@noun"
 msgstr "Commit"
 
-#: git-gui.sh:2289 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
+#: git-gui.sh:2464 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
 msgid "Merge"
 msgstr "Fusionner"
 
-#: git-gui.sh:2290 lib/choose_rev.tcl:557
+#: git-gui.sh:2465 lib/choose_rev.tcl:557
 msgid "Remote"
 msgstr "Dépôt distant"
 
-#: git-gui.sh:2293
+#: git-gui.sh:2468
 msgid "Tools"
 msgstr "Outils"
 
-#: git-gui.sh:2302
+#: git-gui.sh:2477
 msgid "Explore Working Copy"
 msgstr "Explorer la copie de travail"
 
-#: git-gui.sh:2307
+#: git-gui.sh:2483
 msgid "Browse Current Branch's Files"
 msgstr "Naviguer dans la branche courante"
 
-#: git-gui.sh:2311
+#: git-gui.sh:2487
 msgid "Browse Branch Files..."
 msgstr "Naviguer dans la branche..."
 
-#: git-gui.sh:2316
+#: git-gui.sh:2492
 msgid "Visualize Current Branch's History"
 msgstr "Visualiser l'historique de la branche courante"
 
-#: git-gui.sh:2320
+#: git-gui.sh:2496
 msgid "Visualize All Branch History"
 msgstr "Voir l'historique de toutes les branches"
 
-#: git-gui.sh:2327
+#: git-gui.sh:2503
 #, tcl-format
 msgid "Browse %s's Files"
 msgstr "Parcourir l'arborescence de %s"
 
-#: git-gui.sh:2329
+#: git-gui.sh:2505
 #, tcl-format
 msgid "Visualize %s's History"
 msgstr "Voir l'historique de la branche : %s"
 
-#: git-gui.sh:2334 lib/database.tcl:27 lib/database.tcl:67
+#: git-gui.sh:2510 lib/database.tcl:27 lib/database.tcl:67
 msgid "Database Statistics"
 msgstr "Statistiques du dépôt"
 
-#: git-gui.sh:2337 lib/database.tcl:34
+#: git-gui.sh:2513 lib/database.tcl:34
 msgid "Compress Database"
 msgstr "Comprimer le dépôt"
 
-#: git-gui.sh:2340
+#: git-gui.sh:2516
 msgid "Verify Database"
 msgstr "Vérifier le dépôt"
 
-#: git-gui.sh:2347 git-gui.sh:2351 git-gui.sh:2355 lib/shortcut.tcl:7
-#: lib/shortcut.tcl:39 lib/shortcut.tcl:71
+#: git-gui.sh:2523 git-gui.sh:2527 git-gui.sh:2531 lib/shortcut.tcl:8
+#: lib/shortcut.tcl:40 lib/shortcut.tcl:72
 msgid "Create Desktop Icon"
 msgstr "Créer une icône sur le bureau"
 
-#: git-gui.sh:2363 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191
+#: git-gui.sh:2539 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191
 msgid "Quit"
 msgstr "Quitter"
 
-#: git-gui.sh:2371
+#: git-gui.sh:2547
 msgid "Undo"
 msgstr "Défaire"
 
-#: git-gui.sh:2374
+#: git-gui.sh:2550
 msgid "Redo"
 msgstr "Refaire"
 
-#: git-gui.sh:2378 git-gui.sh:2923
+#: git-gui.sh:2554 git-gui.sh:3109
 msgid "Cut"
 msgstr "Couper"
 
-#: git-gui.sh:2381 git-gui.sh:2926 git-gui.sh:3000 git-gui.sh:3082
+#: git-gui.sh:2557 git-gui.sh:3112 git-gui.sh:3186 git-gui.sh:3259
 #: lib/console.tcl:69
 msgid "Copy"
 msgstr "Copier"
 
-#: git-gui.sh:2384 git-gui.sh:2929
+#: git-gui.sh:2560 git-gui.sh:3115
 msgid "Paste"
 msgstr "Coller"
 
-#: git-gui.sh:2387 git-gui.sh:2932 lib/branch_delete.tcl:26
+#: git-gui.sh:2563 git-gui.sh:3118 lib/branch_delete.tcl:26
 #: lib/remote_branch_delete.tcl:38
 msgid "Delete"
 msgstr "Supprimer"
 
-#: git-gui.sh:2391 git-gui.sh:2936 git-gui.sh:3086 lib/console.tcl:71
+#: git-gui.sh:2567 git-gui.sh:3122 git-gui.sh:3263 lib/console.tcl:71
 msgid "Select All"
 msgstr "Tout sélectionner"
 
-#: git-gui.sh:2400
+#: git-gui.sh:2576
 msgid "Create..."
 msgstr "Créer..."
 
-#: git-gui.sh:2406
+#: git-gui.sh:2582
 msgid "Checkout..."
 msgstr "Charger (checkout)..."
 
-#: git-gui.sh:2412
+#: git-gui.sh:2588
 msgid "Rename..."
 msgstr "Renommer..."
 
-#: git-gui.sh:2417
+#: git-gui.sh:2593
 msgid "Delete..."
 msgstr "Supprimer..."
 
-#: git-gui.sh:2422
+#: git-gui.sh:2598
 msgid "Reset..."
 msgstr "Réinitialiser..."
 
-#: git-gui.sh:2432
+#: git-gui.sh:2608
 msgid "Done"
 msgstr "Effectué"
 
-#: git-gui.sh:2434
+#: git-gui.sh:2610
 msgid "Commit@@verb"
 msgstr "Commiter@@verb"
 
-#: git-gui.sh:2443 git-gui.sh:2864
+#: git-gui.sh:2619 git-gui.sh:3050
 msgid "New Commit"
 msgstr "Nouveau commit"
 
-#: git-gui.sh:2451 git-gui.sh:2871
+#: git-gui.sh:2627 git-gui.sh:3057
 msgid "Amend Last Commit"
 msgstr "Corriger dernier commit"
 
-#: git-gui.sh:2461 git-gui.sh:2825 lib/remote_branch_delete.tcl:99
+#: git-gui.sh:2637 git-gui.sh:3011 lib/remote_branch_delete.tcl:99
 msgid "Rescan"
 msgstr "Recharger modifs."
 
-#: git-gui.sh:2467
+#: git-gui.sh:2643
 msgid "Stage To Commit"
 msgstr "Indexer"
 
-#: git-gui.sh:2473
+#: git-gui.sh:2649
 msgid "Stage Changed Files To Commit"
 msgstr "Indexer toutes modifications"
 
-#: git-gui.sh:2479
+#: git-gui.sh:2655
 msgid "Unstage From Commit"
 msgstr "Désindexer"
 
-#: git-gui.sh:2484 lib/index.tcl:410
+#: git-gui.sh:2661 lib/index.tcl:412
 msgid "Revert Changes"
 msgstr "Annuler les modifications"
 
-#: git-gui.sh:2491 git-gui.sh:3069
+#: git-gui.sh:2669 git-gui.sh:3310 git-gui.sh:3341
 msgid "Show Less Context"
 msgstr "Montrer moins de contexte"
 
-#: git-gui.sh:2495 git-gui.sh:3073
+#: git-gui.sh:2673 git-gui.sh:3314 git-gui.sh:3345
 msgid "Show More Context"
 msgstr "Montrer plus de contexte"
 
-#: git-gui.sh:2502 git-gui.sh:2838 git-gui.sh:2947
+#: git-gui.sh:2680 git-gui.sh:3024 git-gui.sh:3133
 msgid "Sign Off"
 msgstr "Signer"
 
-#: git-gui.sh:2518
+#: git-gui.sh:2696
 msgid "Local Merge..."
 msgstr "Fusion locale..."
 
-#: git-gui.sh:2523
+#: git-gui.sh:2701
 msgid "Abort Merge..."
 msgstr "Abandonner fusion..."
 
-#: git-gui.sh:2535 git-gui.sh:2575
+#: git-gui.sh:2713 git-gui.sh:2741
 msgid "Add..."
 msgstr "Ajouter..."
 
-#: git-gui.sh:2539
+#: git-gui.sh:2717
 msgid "Push..."
 msgstr "Pousser..."
 
-#: git-gui.sh:2543
+#: git-gui.sh:2721
 msgid "Delete Branch..."
 msgstr "Supprimer branche..."
 
-#: git-gui.sh:2553 git-gui.sh:2589 lib/about.tcl:14
-#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53
-#, tcl-format
-msgid "About %s"
-msgstr "À propos de %s"
-
-#: git-gui.sh:2557
-msgid "Preferences..."
-msgstr "Préférences..."
-
-#: git-gui.sh:2565 git-gui.sh:3115
+#: git-gui.sh:2731 git-gui.sh:3292
 msgid "Options..."
 msgstr "Options..."
 
-#: git-gui.sh:2576
+#: git-gui.sh:2742
 msgid "Remove..."
 msgstr "Supprimer..."
 
-#: git-gui.sh:2585 lib/choose_repository.tcl:50
+#: git-gui.sh:2751 lib/choose_repository.tcl:50
 msgid "Help"
 msgstr "Aide"
 
-#: git-gui.sh:2611
+#: git-gui.sh:2755 git-gui.sh:2759 lib/about.tcl:14
+#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53
+#, tcl-format
+msgid "About %s"
+msgstr "À propos de %s"
+
+#: git-gui.sh:2783
 msgid "Online Documentation"
 msgstr "Documentation en ligne"
 
-#: git-gui.sh:2614 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
+#: git-gui.sh:2786 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
 msgid "Show SSH Key"
 msgstr "Montrer la clé SSH"
 
-#: git-gui.sh:2707
+#: git-gui.sh:2893
 #, tcl-format
 msgid "fatal: cannot stat path %s: No such file or directory"
 msgstr ""
 "erreur fatale : pas d'infos sur le chemin %s : Fichier ou répertoire "
 "inexistant"
 
-#: git-gui.sh:2740
+#: git-gui.sh:2926
 msgid "Current Branch:"
 msgstr "Branche courante :"
 
-#: git-gui.sh:2761
+#: git-gui.sh:2947
 msgid "Staged Changes (Will Commit)"
 msgstr "Modifs. indexées (pour commit)"
 
-#: git-gui.sh:2781
+#: git-gui.sh:2967
 msgid "Unstaged Changes"
 msgstr "Modifs. non indexées"
 
-#: git-gui.sh:2831
+#: git-gui.sh:3017
 msgid "Stage Changed"
 msgstr "Indexer modifs."
 
-#: git-gui.sh:2850 lib/transport.tcl:93 lib/transport.tcl:182
+#: git-gui.sh:3036 lib/transport.tcl:104 lib/transport.tcl:193
 msgid "Push"
 msgstr "Pousser"
 
-#: git-gui.sh:2885
+#: git-gui.sh:3071
 msgid "Initial Commit Message:"
 msgstr "Message de commit initial :"
 
-#: git-gui.sh:2886
+#: git-gui.sh:3072
 msgid "Amended Commit Message:"
 msgstr "Message de commit corrigé :"
 
-#: git-gui.sh:2887
+#: git-gui.sh:3073
 msgid "Amended Initial Commit Message:"
 msgstr "Message de commit initial corrigé :"
 
-#: git-gui.sh:2888
+#: git-gui.sh:3074
 msgid "Amended Merge Commit Message:"
 msgstr "Message de commit de fusion corrigé :"
 
-#: git-gui.sh:2889
+#: git-gui.sh:3075
 msgid "Merge Commit Message:"
 msgstr "Message de commit de fusion :"
 
-#: git-gui.sh:2890
+#: git-gui.sh:3076
 msgid "Commit Message:"
 msgstr "Message de commit :"
 
-#: git-gui.sh:2939 git-gui.sh:3090 lib/console.tcl:73
+#: git-gui.sh:3125 git-gui.sh:3267 lib/console.tcl:73
 msgid "Copy All"
 msgstr "Copier tout"
 
-#: git-gui.sh:2963 lib/blame.tcl:104
+#: git-gui.sh:3149 lib/blame.tcl:104
 msgid "File:"
 msgstr "Fichier :"
 
-#: git-gui.sh:3078
+#: git-gui.sh:3255
 msgid "Refresh"
 msgstr "Rafraîchir"
 
-#: git-gui.sh:3099
+#: git-gui.sh:3276
 msgid "Decrease Font Size"
 msgstr "Diminuer la police"
 
-#: git-gui.sh:3103
+#: git-gui.sh:3280
 msgid "Increase Font Size"
 msgstr "Agrandir la police"
 
-#: git-gui.sh:3111 lib/blame.tcl:281
+#: git-gui.sh:3288 lib/blame.tcl:281
 msgid "Encoding"
 msgstr "Codage des caractères"
 
-#: git-gui.sh:3122
+#: git-gui.sh:3299
 msgid "Apply/Reverse Hunk"
 msgstr "Appliquer/Inverser section"
 
-#: git-gui.sh:3127
+#: git-gui.sh:3304
 msgid "Apply/Reverse Line"
 msgstr "Appliquer/Inverser la ligne"
 
-#: git-gui.sh:3137
+#: git-gui.sh:3323
 msgid "Run Merge Tool"
 msgstr "Lancer l'outil de fusion"
 
-#: git-gui.sh:3142
+#: git-gui.sh:3328
 msgid "Use Remote Version"
 msgstr "Utiliser la version distante"
 
-#: git-gui.sh:3146
+#: git-gui.sh:3332
 msgid "Use Local Version"
 msgstr "Utiliser la version locale"
 
-#: git-gui.sh:3150
+#: git-gui.sh:3336
 msgid "Revert To Base"
 msgstr "Revenir à la version de base"
 
-#: git-gui.sh:3169
+#: git-gui.sh:3354
+msgid "Visualize These Changes In The Submodule"
+msgstr "Voir les changments dans le sous-module"
+
+#: git-gui.sh:3358
+msgid "Visualize Current Branch History In The Submodule"
+msgstr "Voir l'historique de la branche courante du sous-module"
+
+#: git-gui.sh:3362
+msgid "Visualize All Branch History In The Submodule"
+msgstr "Voir l'historique de toutes les branches du sous-module"
+
+#: git-gui.sh:3367
+msgid "Start git gui In The Submodule"
+msgstr "Démarrer git gui dans le sous-module"
+
+#: git-gui.sh:3389
 msgid "Unstage Hunk From Commit"
 msgstr "Désindexer la section"
 
-#: git-gui.sh:3170
+#: git-gui.sh:3391
+msgid "Unstage Lines From Commit"
+msgstr "Désindexer la ligne du commit"
+
+#: git-gui.sh:3393
 msgid "Unstage Line From Commit"
 msgstr "Désindexer la ligne"
 
-#: git-gui.sh:3172
+#: git-gui.sh:3396
 msgid "Stage Hunk For Commit"
 msgstr "Indexer la section"
 
-#: git-gui.sh:3173
+#: git-gui.sh:3398
+msgid "Stage Lines For Commit"
+msgstr "Indexer les lignes"
+
+#: git-gui.sh:3400
 msgid "Stage Line For Commit"
 msgstr "Indexer la ligne"
 
-#: git-gui.sh:3196
+#: git-gui.sh:3424
 msgid "Initializing..."
 msgstr "Initialisation..."
 
-#: git-gui.sh:3301
+#: git-gui.sh:3541
 #, tcl-format
 msgid ""
 "Possible environment issues exist.\n"
@@ -520,7 +549,7 @@ msgstr ""
 "sous-processus de Git lancés par %s\n"
 "\n"
 
-#: git-gui.sh:3331
+#: git-gui.sh:3570
 msgid ""
 "\n"
 "This is due to a known issue with the\n"
@@ -530,7 +559,7 @@ msgstr ""
 "Ceci est dû à un problème connu avec\n"
 "le binaire Tcl distribué par Cygwin."
 
-#: git-gui.sh:3336
+#: git-gui.sh:3575
 #, tcl-format
 msgid ""
 "\n"
@@ -616,43 +645,43 @@ msgstr "Recherche de copie approfondie en cours..."
 msgid "Loading annotation..."
 msgstr "Chargement des annotations..."
 
-#: lib/blame.tcl:964
+#: lib/blame.tcl:963
 msgid "Author:"
 msgstr "Auteur :"
 
-#: lib/blame.tcl:968
+#: lib/blame.tcl:967
 msgid "Committer:"
 msgstr "Commiteur :"
 
-#: lib/blame.tcl:973
+#: lib/blame.tcl:972
 msgid "Original File:"
 msgstr "Fichier original :"
 
-#: lib/blame.tcl:1021
+#: lib/blame.tcl:1020
 msgid "Cannot find HEAD commit:"
 msgstr "Impossible de trouver le commit HEAD :"
 
-#: lib/blame.tcl:1076
+#: lib/blame.tcl:1075
 msgid "Cannot find parent commit:"
 msgstr "Impossible de trouver le commit parent :"
 
-#: lib/blame.tcl:1091
+#: lib/blame.tcl:1090
 msgid "Unable to display parent"
 msgstr "Impossible d'afficher le parent"
 
-#: lib/blame.tcl:1092 lib/diff.tcl:297
+#: lib/blame.tcl:1091 lib/diff.tcl:320
 msgid "Error loading diff:"
 msgstr "Erreur lors du chargement des différences :"
 
-#: lib/blame.tcl:1232
+#: lib/blame.tcl:1231
 msgid "Originally By:"
 msgstr "À l'origine par :"
 
-#: lib/blame.tcl:1238
+#: lib/blame.tcl:1237
 msgid "In File:"
 msgstr "Dans le fichier :"
 
-#: lib/blame.tcl:1243
+#: lib/blame.tcl:1242
 msgid "Copied Or Moved Here By:"
 msgstr "Copié ou déplacé ici par :"
 
@@ -666,10 +695,10 @@ msgstr "Charger (checkout)"
 
 #: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
 #: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282
-#: lib/checkout_op.tcl:544 lib/choose_font.tcl:43 lib/merge.tcl:172
+#: lib/checkout_op.tcl:579 lib/choose_font.tcl:43 lib/merge.tcl:172
 #: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42
 #: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352
-#: lib/transport.tcl:97
+#: lib/transport.tcl:108
 msgid "Cancel"
 msgstr "Annuler"
 
@@ -697,7 +726,7 @@ msgstr "Créer une branche"
 msgid "Create New Branch"
 msgstr "Créer une nouvelle branche"
 
-#: lib/branch_create.tcl:31 lib/choose_repository.tcl:377
+#: lib/branch_create.tcl:31 lib/choose_repository.tcl:381
 msgid "Create"
 msgstr "Créer"
 
@@ -729,7 +758,7 @@ msgstr "Non"
 msgid "Fast Forward Only"
 msgstr "Mise à jour rectiligne seulement (fast-forward)"
 
-#: lib/branch_create.tcl:85 lib/checkout_op.tcl:536
+#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571
 msgid "Reset"
 msgstr "Réinitialiser"
 
@@ -771,15 +800,25 @@ msgstr "Branches locales"
 msgid "Delete Only If Merged Into"
 msgstr "Supprimer seulement si fusionnée dans :"
 
-#: lib/branch_delete.tcl:54
-msgid "Always (Do not perform merge test.)"
-msgstr "Toujours (Ne pas faire de test de fusion.)"
+#: lib/branch_delete.tcl:54 lib/remote_branch_delete.tcl:119
+msgid "Always (Do not perform merge checks)"
+msgstr "Toujours (ne pas vérifier les fusions)"
 
 #: lib/branch_delete.tcl:103
 #, tcl-format
 msgid "The following branches are not completely merged into %s:"
 msgstr "Les branches suivantes ne sont pas complètement fusionnées dans %s :"
 
+#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:217
+msgid ""
+"Recovering deleted branches is difficult.\n"
+"\n"
+"Delete the selected branches?"
+msgstr ""
+"Il est difficile de récupérer des branches supprimées.\n"
+"\n"
+"Supprimer les branches sélectionnées ?"
+
 #: lib/branch_delete.tcl:141
 #, tcl-format
 msgid ""
@@ -809,7 +848,7 @@ msgstr "Nouveau nom :"
 msgid "Please select a branch to rename."
 msgstr "Merci de sélectionner une branche à renommer."
 
-#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:201
+#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202
 #, tcl-format
 msgid "Branch '%s' already exists."
 msgstr "La branche '%s' existe déjà."
@@ -840,38 +879,38 @@ msgstr "[Jusqu'au parent]"
 msgid "Browse Branch Files"
 msgstr "Naviguer dans les fichiers de le branche"
 
-#: lib/browser.tcl:278 lib/choose_repository.tcl:394
-#: lib/choose_repository.tcl:480 lib/choose_repository.tcl:491
-#: lib/choose_repository.tcl:995
+#: lib/browser.tcl:278 lib/choose_repository.tcl:398
+#: lib/choose_repository.tcl:486 lib/choose_repository.tcl:497
+#: lib/choose_repository.tcl:1028
 msgid "Browse"
 msgstr "Naviguer"
 
-#: lib/checkout_op.tcl:84
+#: lib/checkout_op.tcl:85
 #, tcl-format
 msgid "Fetching %s from %s"
 msgstr "Récupération de %s à partir de %s"
 
-#: lib/checkout_op.tcl:132
+#: lib/checkout_op.tcl:133
 #, tcl-format
 msgid "fatal: Cannot resolve %s"
 msgstr "erreur fatale : Impossible de résoudre %s"
 
-#: lib/checkout_op.tcl:145 lib/console.tcl:81 lib/database.tcl:31
+#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:31
 #: lib/sshkey.tcl:53
 msgid "Close"
 msgstr "Fermer"
 
-#: lib/checkout_op.tcl:174
+#: lib/checkout_op.tcl:175
 #, tcl-format
 msgid "Branch '%s' does not exist."
 msgstr "La branche '%s' n'existe pas."
 
-#: lib/checkout_op.tcl:193
+#: lib/checkout_op.tcl:194
 #, tcl-format
 msgid "Failed to configure simplified git-pull for '%s'."
 msgstr "Échec de la configuration simplifiée de git-pull pour '%s'."
 
-#: lib/checkout_op.tcl:228
+#: lib/checkout_op.tcl:229
 #, tcl-format
 msgid ""
 "Branch '%s' already exists.\n"
@@ -884,21 +923,21 @@ msgstr ""
 "Impossible de faire une avance rapide (fast forward) vers %s.\n"
 "Une fusion est nécessaire."
 
-#: lib/checkout_op.tcl:242
+#: lib/checkout_op.tcl:243
 #, tcl-format
 msgid "Merge strategy '%s' not supported."
 msgstr "La stratégie de fusion '%s' n'est pas supportée."
 
-#: lib/checkout_op.tcl:261
+#: lib/checkout_op.tcl:262
 #, tcl-format
 msgid "Failed to update '%s'."
 msgstr "La mise à jour de '%s' a échoué."
 
-#: lib/checkout_op.tcl:273
+#: lib/checkout_op.tcl:274
 msgid "Staging area (index) is already locked."
 msgstr "L'index (staging area) est déjà verrouillé."
 
-#: lib/checkout_op.tcl:288
+#: lib/checkout_op.tcl:289
 msgid ""
 "Last scanned state does not match repository state.\n"
 "\n"
@@ -916,30 +955,31 @@ msgstr ""
 "\n"
 "Cela va être fait tout de suite automatiquement.\n"
 
-#: lib/checkout_op.tcl:344
+#: lib/checkout_op.tcl:345
 #, tcl-format
 msgid "Updating working directory to '%s'..."
 msgstr "Mise à jour du répertoire courant avec '%s'..."
 
-#: lib/checkout_op.tcl:345
+#: lib/checkout_op.tcl:346
 msgid "files checked out"
 msgstr "fichiers chargés"
 
-#: lib/checkout_op.tcl:375
+#: lib/checkout_op.tcl:376
 #, tcl-format
 msgid "Aborted checkout of '%s' (file level merging is required)."
-msgstr "Chargement de '%s' abandonné (il est nécessaire de fusionner des fichiers)."
+msgstr ""
+"Chargement de '%s' abandonné (il est nécessaire de fusionner des fichiers)."
 
-#: lib/checkout_op.tcl:376
+#: lib/checkout_op.tcl:377
 msgid "File level merge required."
 msgstr "Il est nécessaire de fusionner des fichiers."
 
-#: lib/checkout_op.tcl:380
+#: lib/checkout_op.tcl:381
 #, tcl-format
 msgid "Staying on branch '%s'."
 msgstr "Le répertoire de travail reste sur la branche '%s'."
 
-#: lib/checkout_op.tcl:451
+#: lib/checkout_op.tcl:452
 msgid ""
 "You are no longer on a local branch.\n"
 "\n"
@@ -951,30 +991,30 @@ msgstr ""
 "Si vous vouliez être sur une branche, créez-en une maintenant en partant de "
 "'Cet emprunt détaché'."
 
-#: lib/checkout_op.tcl:468 lib/checkout_op.tcl:472
+#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507
 #, tcl-format
 msgid "Checked out '%s'."
 msgstr "'%s' chargé."
 
-#: lib/checkout_op.tcl:500
+#: lib/checkout_op.tcl:535
 #, tcl-format
 msgid "Resetting '%s' to '%s' will lose the following commits:"
 msgstr "Réinitialiser '%s' à '%s' va faire perdre les commits suivants :"
 
-#: lib/checkout_op.tcl:522
+#: lib/checkout_op.tcl:557
 msgid "Recovering lost commits may not be easy."
 msgstr "Récupérer les commits perdus ne sera peut être pas facile."
 
-#: lib/checkout_op.tcl:527
+#: lib/checkout_op.tcl:562
 #, tcl-format
 msgid "Reset '%s'?"
 msgstr "Réinitialiser '%s' ?"
 
-#: lib/checkout_op.tcl:532 lib/merge.tcl:164 lib/tools_dlg.tcl:343
+#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:343
 msgid "Visualize"
 msgstr "Visualiser"
 
-#: lib/checkout_op.tcl:600
+#: lib/checkout_op.tcl:635
 #, tcl-format
 msgid ""
 "Failed to set current branch.\n"
@@ -1020,7 +1060,7 @@ msgstr ""
 msgid "Git Gui"
 msgstr "Git Gui"
 
-#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:382
+#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:386
 msgid "Create New Repository"
 msgstr "Créer nouveau dépôt"
 
@@ -1028,7 +1068,7 @@ msgstr "Créer nouveau dépôt"
 msgid "New..."
 msgstr "Nouveau..."
 
-#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:465
+#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:471
 msgid "Clone Existing Repository"
 msgstr "Cloner un dépôt existant"
 
@@ -1036,7 +1076,7 @@ msgstr "Cloner un dépôt existant"
 msgid "Clone..."
 msgstr "Cloner..."
 
-#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:983
+#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:1016
 msgid "Open Existing Repository"
 msgstr "Ouvrir un dépôt existant"
 
@@ -1052,197 +1092,199 @@ msgstr "Dépôts récemment utilisés"
 msgid "Open Recent Repository:"
 msgstr "Ouvrir un dépôt récent :"
 
-#: lib/choose_repository.tcl:302 lib/choose_repository.tcl:309
-#: lib/choose_repository.tcl:316
+#: lib/choose_repository.tcl:306 lib/choose_repository.tcl:313
+#: lib/choose_repository.tcl:320
 #, tcl-format
 msgid "Failed to create repository %s:"
 msgstr "La création du dépôt %s a échoué :"
 
-#: lib/choose_repository.tcl:387
+#: lib/choose_repository.tcl:391
 msgid "Directory:"
 msgstr "Répertoire :"
 
-#: lib/choose_repository.tcl:417 lib/choose_repository.tcl:544
-#: lib/choose_repository.tcl:1017
+#: lib/choose_repository.tcl:423 lib/choose_repository.tcl:550
+#: lib/choose_repository.tcl:1052
 msgid "Git Repository"
 msgstr "Dépôt Git"
 
-#: lib/choose_repository.tcl:442
+#: lib/choose_repository.tcl:448
 #, tcl-format
 msgid "Directory %s already exists."
 msgstr "Le répertoire %s existe déjà."
 
-#: lib/choose_repository.tcl:446
+#: lib/choose_repository.tcl:452
 #, tcl-format
 msgid "File %s already exists."
 msgstr "Le fichier %s existe déjà."
 
-#: lib/choose_repository.tcl:460
+#: lib/choose_repository.tcl:466
 msgid "Clone"
 msgstr "Cloner"
 
-#: lib/choose_repository.tcl:473
+#: lib/choose_repository.tcl:479
 msgid "Source Location:"
 msgstr "Emplacement source :"
 
-#: lib/choose_repository.tcl:484
+#: lib/choose_repository.tcl:490
 msgid "Target Directory:"
 msgstr "Répertoire cible :"
 
-#: lib/choose_repository.tcl:496
+#: lib/choose_repository.tcl:502
 msgid "Clone Type:"
 msgstr "Type de clonage :"
 
-#: lib/choose_repository.tcl:502
+#: lib/choose_repository.tcl:508
 msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
 msgstr "Standard (rapide, semi-redondant, liens durs)"
 
-#: lib/choose_repository.tcl:508
+#: lib/choose_repository.tcl:514
 msgid "Full Copy (Slower, Redundant Backup)"
 msgstr "Copy complète (plus lent, sauvegarde redondante)"
 
-#: lib/choose_repository.tcl:514
+#: lib/choose_repository.tcl:520
 msgid "Shared (Fastest, Not Recommended, No Backup)"
 msgstr "Partagé (le plus rapide, non recommandé, pas de sauvegarde)"
 
-#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597
-#: lib/choose_repository.tcl:743 lib/choose_repository.tcl:813
-#: lib/choose_repository.tcl:1023 lib/choose_repository.tcl:1031
+#: lib/choose_repository.tcl:556 lib/choose_repository.tcl:603
+#: lib/choose_repository.tcl:749 lib/choose_repository.tcl:819
+#: lib/choose_repository.tcl:1058 lib/choose_repository.tcl:1066
 #, tcl-format
 msgid "Not a Git repository: %s"
 msgstr "'%s' n'est pas un dépôt Git."
 
-#: lib/choose_repository.tcl:586
+#: lib/choose_repository.tcl:592
 msgid "Standard only available for local repository."
 msgstr "Standard n'est disponible que pour un dépôt local."
 
-#: lib/choose_repository.tcl:590
+#: lib/choose_repository.tcl:596
 msgid "Shared only available for local repository."
 msgstr "Partagé n'est disponible que pour un dépôt local."
 
-#: lib/choose_repository.tcl:611
+#: lib/choose_repository.tcl:617
 #, tcl-format
 msgid "Location %s already exists."
 msgstr "L'emplacement %s existe déjà."
 
-#: lib/choose_repository.tcl:622
+#: lib/choose_repository.tcl:628
 msgid "Failed to configure origin"
 msgstr "La configuration de l'origine a échoué."
 
-#: lib/choose_repository.tcl:634
+#: lib/choose_repository.tcl:640
 msgid "Counting objects"
 msgstr "Décompte des objets"
 
-#: lib/choose_repository.tcl:635
+#: lib/choose_repository.tcl:641
 msgid "buckets"
 msgstr "paniers"
 
-#: lib/choose_repository.tcl:659
+#: lib/choose_repository.tcl:665
 #, tcl-format
 msgid "Unable to copy objects/info/alternates: %s"
 msgstr "Impossible de copier 'objects/info/alternates' : %s"
 
-#: lib/choose_repository.tcl:695
+#: lib/choose_repository.tcl:701
 #, tcl-format
 msgid "Nothing to clone from %s."
 msgstr "Il n'y a rien à cloner depuis %s."
 
-#: lib/choose_repository.tcl:697 lib/choose_repository.tcl:911
-#: lib/choose_repository.tcl:923
+#: lib/choose_repository.tcl:703 lib/choose_repository.tcl:917
+#: lib/choose_repository.tcl:929
 msgid "The 'master' branch has not been initialized."
 msgstr "La branche 'master' n'a pas été initialisée."
 
-#: lib/choose_repository.tcl:710
+#: lib/choose_repository.tcl:716
 msgid "Hardlinks are unavailable.  Falling back to copying."
-msgstr "Les liens durs ne sont pas supportés. Une copie sera effectuée à la place."
+msgstr ""
+"Les liens durs ne sont pas supportés. Une copie sera effectuée à la place."
 
-#: lib/choose_repository.tcl:722
+#: lib/choose_repository.tcl:728
 #, tcl-format
 msgid "Cloning from %s"
 msgstr "Clonage depuis %s"
 
-#: lib/choose_repository.tcl:753
+#: lib/choose_repository.tcl:759
 msgid "Copying objects"
 msgstr "Copie des objets"
 
-#: lib/choose_repository.tcl:754
+#: lib/choose_repository.tcl:760
 msgid "KiB"
 msgstr "KiB"
 
-#: lib/choose_repository.tcl:778
+#: lib/choose_repository.tcl:784
 #, tcl-format
 msgid "Unable to copy object: %s"
 msgstr "Impossible de copier l'objet : %s"
 
-#: lib/choose_repository.tcl:788
+#: lib/choose_repository.tcl:794
 msgid "Linking objects"
 msgstr "Liaison des objets"
 
-#: lib/choose_repository.tcl:789
+#: lib/choose_repository.tcl:795
 msgid "objects"
 msgstr "objets"
 
-#: lib/choose_repository.tcl:797
+#: lib/choose_repository.tcl:803
 #, tcl-format
 msgid "Unable to hardlink object: %s"
 msgstr "Impossible créer un lien dur pour l'objet : %s"
 
-#: lib/choose_repository.tcl:852
+#: lib/choose_repository.tcl:858
 msgid "Cannot fetch branches and objects.  See console output for details."
 msgstr ""
 "Impossible de récupérer les branches et objets. Voir la sortie console pour "
 "plus de détails."
 
-#: lib/choose_repository.tcl:863
+#: lib/choose_repository.tcl:869
 msgid "Cannot fetch tags.  See console output for details."
 msgstr ""
 "Impossible de récupérer les marques (tags). Voir la sortie console pour plus "
 "de détails."
 
-#: lib/choose_repository.tcl:887
+#: lib/choose_repository.tcl:893
 msgid "Cannot determine HEAD.  See console output for details."
-msgstr "Impossible de déterminer HEAD. Voir la sortie console pour plus de détails."
+msgstr ""
+"Impossible de déterminer HEAD. Voir la sortie console pour plus de détails."
 
-#: lib/choose_repository.tcl:896
+#: lib/choose_repository.tcl:902
 #, tcl-format
 msgid "Unable to cleanup %s"
 msgstr "Impossible de nettoyer %s"
 
-#: lib/choose_repository.tcl:902
+#: lib/choose_repository.tcl:908
 msgid "Clone failed."
 msgstr "Le clonage a échoué."
 
-#: lib/choose_repository.tcl:909
+#: lib/choose_repository.tcl:915
 msgid "No default branch obtained."
 msgstr "Aucune branche par défaut n'a été obtenue."
 
-#: lib/choose_repository.tcl:920
+#: lib/choose_repository.tcl:926
 #, tcl-format
 msgid "Cannot resolve %s as a commit."
 msgstr "Impossible de résoudre %s comme commit."
 
-#: lib/choose_repository.tcl:932
+#: lib/choose_repository.tcl:938
 msgid "Creating working directory"
 msgstr "Création du répertoire de travail"
 
-#: lib/choose_repository.tcl:933 lib/index.tcl:65 lib/index.tcl:128
-#: lib/index.tcl:196
+#: lib/choose_repository.tcl:939 lib/index.tcl:67 lib/index.tcl:130
+#: lib/index.tcl:198
 msgid "files"
 msgstr "fichiers"
 
-#: lib/choose_repository.tcl:962
+#: lib/choose_repository.tcl:968
 msgid "Initial file checkout failed."
 msgstr "Le chargement initial du fichier a échoué."
 
-#: lib/choose_repository.tcl:978
+#: lib/choose_repository.tcl:1011
 msgid "Open"
 msgstr "Ouvrir"
 
-#: lib/choose_repository.tcl:988
+#: lib/choose_repository.tcl:1021
 msgid "Repository:"
 msgstr "Dépôt :"
 
-#: lib/choose_repository.tcl:1037
+#: lib/choose_repository.tcl:1072
 #, tcl-format
 msgid "Failed to open repository %s:"
 msgstr "Impossible d'ouvrir le dépôt %s :"
@@ -1314,19 +1356,24 @@ msgstr ""
 "terminée. Vous ne pouvez pas corriger le commit précédent sauf si vous "
 "abandonnez la fusion courante.\n"
 
-#: lib/commit.tcl:49
+#: lib/commit.tcl:48
 msgid "Error loading commit data for amend:"
 msgstr "Erreur lors du chargement des données de commit pour correction :"
 
-#: lib/commit.tcl:76
+#: lib/commit.tcl:75
 msgid "Unable to obtain your identity:"
 msgstr "Impossible d'obtenir votre identité :"
 
-#: lib/commit.tcl:81
+#: lib/commit.tcl:80
 msgid "Invalid GIT_COMMITTER_IDENT:"
 msgstr "GIT_COMMITTER_IDENT invalide :"
 
-#: lib/commit.tcl:133
+#: lib/commit.tcl:129
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr "attention : Tcl ne supporte pas le codage '%s'."
+
+#: lib/commit.tcl:149
 msgid ""
 "Last scanned state does not match repository state.\n"
 "\n"
@@ -1344,7 +1391,7 @@ msgstr ""
 "\n"
 "Cela va être fait tout de suite automatiquement.\n"
 
-#: lib/commit.tcl:156
+#: lib/commit.tcl:172
 #, tcl-format
 msgid ""
 "Unmerged files cannot be committed.\n"
@@ -1357,7 +1404,7 @@ msgstr ""
 "Le fichier %s a des conflicts de fusion. Vous devez les résoudre et pré-"
 "commiter le fichier avant de pouvoir commiter.\n"
 
-#: lib/commit.tcl:164
+#: lib/commit.tcl:180
 #, tcl-format
 msgid ""
 "Unknown file state %s detected.\n"
@@ -1368,7 +1415,7 @@ msgstr ""
 "\n"
 "Le fichier %s ne peut pas être commité par ce programme.\n"
 
-#: lib/commit.tcl:172
+#: lib/commit.tcl:188
 msgid ""
 "No changes to commit.\n"
 "\n"
@@ -1378,7 +1425,7 @@ msgstr ""
 "\n"
 "Vous devez indexer au moins 1 fichier avant de pouvoir commiter.\n"
 
-#: lib/commit.tcl:187
+#: lib/commit.tcl:203
 msgid ""
 "Please supply a commit message.\n"
 "\n"
@@ -1396,45 +1443,40 @@ msgstr ""
 "- Deuxième ligne : rien.\n"
 "- Lignes suivantes : Décrire pourquoi ces modifications sont bonnes.\n"
 
-#: lib/commit.tcl:211
-#, tcl-format
-msgid "warning: Tcl does not support encoding '%s'."
-msgstr "attention : Tcl ne supporte pas le codage '%s'."
-
-#: lib/commit.tcl:227
+#: lib/commit.tcl:234
 msgid "Calling pre-commit hook..."
 msgstr "Lancement de l'action d'avant-commit..."
 
-#: lib/commit.tcl:242
+#: lib/commit.tcl:249
 msgid "Commit declined by pre-commit hook."
 msgstr "Commit refusé par l'action d'avant-commit."
 
-#: lib/commit.tcl:265
+#: lib/commit.tcl:272
 msgid "Calling commit-msg hook..."
 msgstr "Lancement de l'action \"message de commit\"..."
 
-#: lib/commit.tcl:280
+#: lib/commit.tcl:287
 msgid "Commit declined by commit-msg hook."
 msgstr "Commit refusé par l'action \"message de commit\"."
 
-#: lib/commit.tcl:293
+#: lib/commit.tcl:300
 msgid "Committing changes..."
 msgstr "Commit des modifications..."
 
-#: lib/commit.tcl:309
+#: lib/commit.tcl:316
 msgid "write-tree failed:"
 msgstr "write-tree a échoué :"
 
-#: lib/commit.tcl:310 lib/commit.tcl:354 lib/commit.tcl:374
+#: lib/commit.tcl:317 lib/commit.tcl:361 lib/commit.tcl:382
 msgid "Commit failed."
 msgstr "Le commit a échoué."
 
-#: lib/commit.tcl:327
+#: lib/commit.tcl:334
 #, tcl-format
 msgid "Commit %s appears to be corrupt"
 msgstr "Le commit %s semble être corrompu"
 
-#: lib/commit.tcl:332
+#: lib/commit.tcl:339
 msgid ""
 "No changes to commit.\n"
 "\n"
@@ -1449,19 +1491,19 @@ msgstr ""
 "\n"
 "Une resynchronisation va être lancée tout de suite automatiquement.\n"
 
-#: lib/commit.tcl:339
+#: lib/commit.tcl:346
 msgid "No changes to commit."
 msgstr "Pas de modifications à commiter."
 
-#: lib/commit.tcl:353
+#: lib/commit.tcl:360
 msgid "commit-tree failed:"
 msgstr "commit-tree a échoué :"
 
-#: lib/commit.tcl:373
+#: lib/commit.tcl:381
 msgid "update-ref failed:"
 msgstr "update-ref a échoué :"
 
-#: lib/commit.tcl:461
+#: lib/commit.tcl:469
 #, tcl-format
 msgid "Created commit %s: %s"
 msgstr "Commit %s créé : %s"
@@ -1514,13 +1556,13 @@ msgstr "Compression de la base des objets"
 msgid "Verifying the object database with fsck-objects"
 msgstr "Vérification de la base des objets avec fsck-objects"
 
-#: lib/database.tcl:108
+#: lib/database.tcl:107
 #, tcl-format
 msgid ""
 "This repository currently has approximately %i loose objects.\n"
 "\n"
 "To maintain optimal performance it is strongly recommended that you compress "
-"the database when more than %i loose objects exist.\n"
+"the database.\n"
 "\n"
 "Compress the database now?"
 msgstr ""
@@ -1528,8 +1570,7 @@ msgstr ""
 "particulier.\n"
 "\n"
 "Pour conserver une performance optimale, il est fortement recommandé de "
-"comprimer la base quand plus de %i objets ayant leur fichier particulier "
-"existent.\n"
+"comprimer la base de donnée.\n"
 "\n"
 "Comprimer la base maintenant ?"
 
@@ -1538,7 +1579,7 @@ msgstr ""
 msgid "Invalid date from Git: %s"
 msgstr "Date invalide de Git : %s"
 
-#: lib/diff.tcl:59
+#: lib/diff.tcl:64
 #, tcl-format
 msgid ""
 "No differences detected.\n"
@@ -1561,12 +1602,12 @@ msgstr ""
 "Une resynchronisation va être lancée automatiquement pour trouver d'autres "
 "fichiers qui pourraient se trouver dans le même état."
 
-#: lib/diff.tcl:99
+#: lib/diff.tcl:104
 #, tcl-format
 msgid "Loading diff of %s..."
 msgstr "Chargement des différences de %s..."
 
-#: lib/diff.tcl:120
+#: lib/diff.tcl:125
 msgid ""
 "LOCAL: deleted\n"
 "REMOTE:\n"
@@ -1574,7 +1615,7 @@ msgstr ""
 "LOCAL : supprimé\n"
 "DISTANT :\n"
 
-#: lib/diff.tcl:125
+#: lib/diff.tcl:130
 msgid ""
 "REMOTE: deleted\n"
 "LOCAL:\n"
@@ -1582,32 +1623,32 @@ msgstr ""
 "DISTANT : supprimé\n"
 "LOCAL :\n"
 
-#: lib/diff.tcl:132
+#: lib/diff.tcl:137
 msgid "LOCAL:\n"
 msgstr "LOCAL :\n"
 
-#: lib/diff.tcl:135
+#: lib/diff.tcl:140
 msgid "REMOTE:\n"
 msgstr "DISTANT :\n"
 
-#: lib/diff.tcl:197 lib/diff.tcl:296
+#: lib/diff.tcl:202 lib/diff.tcl:319
 #, tcl-format
 msgid "Unable to display %s"
 msgstr "Impossible d'afficher %s"
 
-#: lib/diff.tcl:198
+#: lib/diff.tcl:203
 msgid "Error loading file:"
 msgstr "Erreur lors du chargement du fichier :"
 
-#: lib/diff.tcl:205
+#: lib/diff.tcl:210
 msgid "Git Repository (subproject)"
 msgstr "Dépôt Git (sous projet)"
 
-#: lib/diff.tcl:217
+#: lib/diff.tcl:222
 msgid "* Binary file (not showing content)."
 msgstr "* Fichier binaire (pas d'apperçu du contenu)."
 
-#: lib/diff.tcl:222
+#: lib/diff.tcl:227
 #, tcl-format
 msgid ""
 "* Untracked file is %d bytes.\n"
@@ -1616,7 +1657,7 @@ msgstr ""
 "* Le fichier non suivi fait %d octets.\n"
 "* Seuls les %d premiers octets sont montrés.\n"
 
-#: lib/diff.tcl:228
+#: lib/diff.tcl:233
 #, tcl-format
 msgid ""
 "\n"
@@ -1627,19 +1668,19 @@ msgstr ""
 "* Fichier suivi raccourcis ici de %s.\n"
 "* Pour voir le fichier entier, utilisez un éditeur externe.\n"
 
-#: lib/diff.tcl:436
+#: lib/diff.tcl:482
 msgid "Failed to unstage selected hunk."
 msgstr "Échec lors de la désindexation de la section sélectionnée."
 
-#: lib/diff.tcl:443
+#: lib/diff.tcl:489
 msgid "Failed to stage selected hunk."
 msgstr "Échec lors de l'indexation de la section."
 
-#: lib/diff.tcl:509
+#: lib/diff.tcl:568
 msgid "Failed to unstage selected line."
 msgstr "Échec lors de la désindexation de la ligne sélectionnée."
 
-#: lib/diff.tcl:517
+#: lib/diff.tcl:576
 msgid "Failed to stage selected line."
 msgstr "Échec lors de l'indexation de la ligne."
 
@@ -1676,7 +1717,7 @@ msgstr "Impossible de déverrouiller l'index."
 msgid "Index Error"
 msgstr "Erreur de l'index"
 
-#: lib/index.tcl:21
+#: lib/index.tcl:17
 msgid ""
 "Updating the Git index failed.  A rescan will be automatically started to "
 "resynchronize git-gui."
@@ -1684,7 +1725,7 @@ msgstr ""
 "Échec de la mise à jour de l'index. Une resynchronisation va être lancée "
 "automatiquement."
 
-#: lib/index.tcl:27
+#: lib/index.tcl:28
 msgid "Continue"
 msgstr "Continuer"
 
@@ -1692,45 +1733,45 @@ msgstr "Continuer"
 msgid "Unlock Index"
 msgstr "Déverrouiller l'index"
 
-#: lib/index.tcl:287
+#: lib/index.tcl:289
 #, tcl-format
 msgid "Unstaging %s from commit"
 msgstr "Désindexation de : %s"
 
-#: lib/index.tcl:326
+#: lib/index.tcl:328
 msgid "Ready to commit."
 msgstr "Prêt à être commité."
 
-#: lib/index.tcl:339
+#: lib/index.tcl:341
 #, tcl-format
 msgid "Adding %s"
 msgstr "Ajout de %s"
 
-#: lib/index.tcl:396
+#: lib/index.tcl:398
 #, tcl-format
 msgid "Revert changes in file %s?"
 msgstr "Annuler les modifications dans le fichier %s ? "
 
-#: lib/index.tcl:398
+#: lib/index.tcl:400
 #, tcl-format
 msgid "Revert changes in these %i files?"
 msgstr "Annuler les modifications dans ces %i fichiers ?"
 
-#: lib/index.tcl:406
+#: lib/index.tcl:408
 msgid "Any unstaged changes will be permanently lost by the revert."
 msgstr ""
 "Toutes les modifications non-indexées seront définitivement perdues par "
 "l'annulation."
 
-#: lib/index.tcl:409
+#: lib/index.tcl:411
 msgid "Do Nothing"
 msgstr "Ne rien faire"
 
-#: lib/index.tcl:427
+#: lib/index.tcl:429
 msgid "Reverting selected files"
 msgstr "Annuler modifications dans fichiers selectionnés"
 
-#: lib/index.tcl:431
+#: lib/index.tcl:433
 #, tcl-format
 msgid "Reverting %s"
 msgstr "Annulation des modifications dans %s"
@@ -1913,7 +1954,8 @@ msgstr ""
 #: lib/mergetool.tcl:45
 #, tcl-format
 msgid "File %s seems to have unresolved conflicts, still stage?"
-msgstr "Le fichier %s semble avoir des conflits non résolus, indexer quand même ?"
+msgstr ""
+"Le fichier %s semble avoir des conflits non résolus, indexer quand même ?"
 
 #: lib/mergetool.tcl:60
 #, tcl-format
@@ -1922,7 +1964,9 @@ msgstr "Ajouter une résolution pour %s"
 
 #: lib/mergetool.tcl:141
 msgid "Cannot resolve deletion or link conflicts using a tool"
-msgstr "Impossible de résoudre la suppression ou de relier des conflits en utilisant un outil"
+msgstr ""
+"Impossible de résoudre la suppression ou de relier des conflits en utilisant "
+"un outil"
 
 #: lib/mergetool.tcl:146
 msgid "Conflict file does not exist"
@@ -2171,7 +2215,8 @@ msgstr "Récupération de %s"
 msgid "Do not know how to initialize repository at location '%s'."
 msgstr "Pas de méthode connue pour initialiser le dépôt à l'emplacement '%s'."
 
-#: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:71
+#: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:63
+#: lib/transport.tcl:81
 #, tcl-format
 msgid "push %s"
 msgstr "pousser %s"
@@ -2189,11 +2234,11 @@ msgstr "Supprimer une branche à distance"
 msgid "From Repository"
 msgstr "Dépôt source"
 
-#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123
+#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:134
 msgid "Remote:"
 msgstr "Branche distante :"
 
-#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138
+#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:149
 msgid "Arbitrary Location:"
 msgstr "Emplacement arbitraire :"
 
@@ -2209,10 +2254,6 @@ msgstr "Supprimer seulement si"
 msgid "Merged Into:"
 msgstr "Fusionné dans :"
 
-#: lib/remote_branch_delete.tcl:119
-msgid "Always (Do not perform merge checks)"
-msgstr "Toujours (ne pas vérifier les fusions)"
-
 #: lib/remote_branch_delete.tcl:152
 msgid "A branch is required for 'Merged Into'."
 msgstr "Une branche est nécessaire pour 'Fusionné dans'."
@@ -2235,32 +2276,23 @@ msgid ""
 "necessary commits.  Try fetching from %s first."
 msgstr ""
 "Un ou plusieurs des tests de fusion ont échoué parce que vous n'avez pas "
-"récupéré les commits nécessaires. Essayez de récupérer à partir de %s d'abord."
+"récupéré les commits nécessaires. Essayez de récupérer à partir de %s "
+"d'abord."
 
 #: lib/remote_branch_delete.tcl:207
 msgid "Please select one or more branches to delete."
 msgstr "Merci de sélectionner une ou plusieurs branches à supprimer."
 
-#: lib/remote_branch_delete.tcl:216
-msgid ""
-"Recovering deleted branches is difficult.\n"
-"\n"
-"Delete the selected branches?"
-msgstr ""
-"Il est difficile de récupérer des branches supprimées.\n"
-"\n"
-"Supprimer les branches sélectionnées ?"
-
 #: lib/remote_branch_delete.tcl:226
 #, tcl-format
 msgid "Deleting branches from %s"
 msgstr "Suppression des branches de %s"
 
-#: lib/remote_branch_delete.tcl:286
+#: lib/remote_branch_delete.tcl:292
 msgid "No repository selected."
 msgstr "Aucun dépôt n'est sélectionné."
 
-#: lib/remote_branch_delete.tcl:291
+#: lib/remote_branch_delete.tcl:297
 #, tcl-format
 msgid "Scanning %s..."
 msgstr "Synchronisation de %s..."
@@ -2281,11 +2313,11 @@ msgstr "Précédent"
 msgid "Case-Sensitive"
 msgstr "Sensible à la casse"
 
-#: lib/shortcut.tcl:20 lib/shortcut.tcl:61
+#: lib/shortcut.tcl:21 lib/shortcut.tcl:62
 msgid "Cannot write shortcut:"
 msgstr "Impossible d'écrire le raccourci :"
 
-#: lib/shortcut.tcl:136
+#: lib/shortcut.tcl:137
 msgid "Cannot write icon:"
 msgstr "Impossible d'écrire l'icône :"
 
@@ -2440,7 +2472,8 @@ msgstr "Montrer une boîte de dialogue avant le lancement"
 
 #: lib/tools_dlg.tcl:80
 msgid "Ask the user to select a revision (sets $REVISION)"
-msgstr "Demander à l'utilisateur de sélectionner une révision (change $REVISION)"
+msgstr ""
+"Demander à l'utilisateur de sélectionner une révision (change $REVISION)"
 
 #: lib/tools_dlg.tcl:85
 msgid "Ask the user for additional arguments (sets $ARGS)"
@@ -2521,38 +2554,51 @@ msgstr "Nettoyer les branches de suivi supprimées de %s"
 msgid "Pushing changes to %s"
 msgstr "Les modifications sont poussées vers %s"
 
-#: lib/transport.tcl:72
+#: lib/transport.tcl:64
+#, tcl-format
+msgid "Mirroring to %s"
+msgstr "Dupliquer dans %s"
+
+#: lib/transport.tcl:82
 #, tcl-format
 msgid "Pushing %s %s to %s"
 msgstr "Pousse %s %s vers %s"
 
-#: lib/transport.tcl:89
+#: lib/transport.tcl:100
 msgid "Push Branches"
 msgstr "Pousser branches"
 
-#: lib/transport.tcl:103
+#: lib/transport.tcl:114
 msgid "Source Branches"
 msgstr "Branches source"
 
-#: lib/transport.tcl:120
+#: lib/transport.tcl:131
 msgid "Destination Repository"
 msgstr "Dépôt de destination"
 
-#: lib/transport.tcl:158
+#: lib/transport.tcl:169
 msgid "Transfer Options"
 msgstr "Options de transfert"
 
-#: lib/transport.tcl:160
+#: lib/transport.tcl:171
 msgid "Force overwrite existing branch (may discard changes)"
 msgstr ""
 "Forcer l'écrasement d'une branche existante (peut supprimer des "
 "modifications)"
 
-#: lib/transport.tcl:164
+#: lib/transport.tcl:175
 msgid "Use thin pack (for slow network connections)"
 msgstr "Utiliser des petits paquets (pour les connexions lentes)"
 
-#: lib/transport.tcl:168
+#: lib/transport.tcl:179
 msgid "Include tags"
 msgstr "Inclure les marques (tags)"
 
+#~ msgid "Cannot use funny .git directory:"
+#~ msgstr "Impossible d'utiliser le répertoire .git:"
+
+#~ msgid "Preferences..."
+#~ msgstr "Préférences..."
+
+#~ msgid "Always (Do not perform merge test.)"
+#~ msgstr "Toujours (Ne pas faire de test de fusion.)"
index 53b7d3634d842a43f8043e3f825d4232e17588aa..1ae2aaa0b3bb46302a9a4876dbbb1be00d2c2ddf 100644 (file)
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-12-08 08:31-0800\n"
+"POT-Creation-Date: 2010-01-26 15:47-0800\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -16,33 +16,33 @@ msgstr ""
 "Content-Type: text/plain; charset=CHARSET\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: git-gui.sh:41 git-gui.sh:737 git-gui.sh:751 git-gui.sh:764 git-gui.sh:847
-#: git-gui.sh:866
+#: git-gui.sh:41 git-gui.sh:793 git-gui.sh:807 git-gui.sh:820 git-gui.sh:903
+#: git-gui.sh:922
 msgid "git-gui: fatal error"
 msgstr ""
 
-#: git-gui.sh:689
+#: git-gui.sh:743
 #, tcl-format
 msgid "Invalid font specified in %s:"
 msgstr ""
 
-#: git-gui.sh:723
+#: git-gui.sh:779
 msgid "Main Font"
 msgstr ""
 
-#: git-gui.sh:724
+#: git-gui.sh:780
 msgid "Diff/Console Font"
 msgstr ""
 
-#: git-gui.sh:738
+#: git-gui.sh:794
 msgid "Cannot find git in PATH."
 msgstr ""
 
-#: git-gui.sh:765
+#: git-gui.sh:821
 msgid "Cannot parse Git version string:"
 msgstr ""
 
-#: git-gui.sh:783
+#: git-gui.sh:839
 #, tcl-format
 msgid ""
 "Git version cannot be determined.\n"
@@ -54,444 +54,473 @@ msgid ""
 "Assume '%s' is version 1.5.0?\n"
 msgstr ""
 
-#: git-gui.sh:1062
+#: git-gui.sh:1128
 msgid "Git directory not found:"
 msgstr ""
 
-#: git-gui.sh:1069
+#: git-gui.sh:1146
 msgid "Cannot move to top of working directory:"
 msgstr ""
 
-#: git-gui.sh:1076
-msgid "Cannot use funny .git directory:"
+#: git-gui.sh:1154
+msgid "Cannot use bare repository:"
 msgstr ""
 
-#: git-gui.sh:1081
+#: git-gui.sh:1162
 msgid "No working directory"
 msgstr ""
 
-#: git-gui.sh:1247 lib/checkout_op.tcl:305
+#: git-gui.sh:1334 lib/checkout_op.tcl:306
 msgid "Refreshing file status..."
 msgstr ""
 
-#: git-gui.sh:1303
+#: git-gui.sh:1390
 msgid "Scanning for modified files ..."
 msgstr ""
 
-#: git-gui.sh:1367
+#: git-gui.sh:1454
 msgid "Calling prepare-commit-msg hook..."
 msgstr ""
 
-#: git-gui.sh:1384
+#: git-gui.sh:1471
 msgid "Commit declined by prepare-commit-msg hook."
 msgstr ""
 
-#: git-gui.sh:1542 lib/browser.tcl:246
+#: git-gui.sh:1629 lib/browser.tcl:246
 msgid "Ready."
 msgstr ""
 
-#: git-gui.sh:1819
+#: git-gui.sh:1787
+#, tcl-format
+msgid "Displaying only %s of %s files."
+msgstr ""
+
+#: git-gui.sh:1913
 msgid "Unmodified"
 msgstr ""
 
-#: git-gui.sh:1821
+#: git-gui.sh:1915
 msgid "Modified, not staged"
 msgstr ""
 
-#: git-gui.sh:1822 git-gui.sh:1830
+#: git-gui.sh:1916 git-gui.sh:1924
 msgid "Staged for commit"
 msgstr ""
 
-#: git-gui.sh:1823 git-gui.sh:1831
+#: git-gui.sh:1917 git-gui.sh:1925
 msgid "Portions staged for commit"
 msgstr ""
 
-#: git-gui.sh:1824 git-gui.sh:1832
+#: git-gui.sh:1918 git-gui.sh:1926
 msgid "Staged for commit, missing"
 msgstr ""
 
-#: git-gui.sh:1826
+#: git-gui.sh:1920
 msgid "File type changed, not staged"
 msgstr ""
 
-#: git-gui.sh:1827
+#: git-gui.sh:1921
 msgid "File type changed, staged"
 msgstr ""
 
-#: git-gui.sh:1829
+#: git-gui.sh:1923
 msgid "Untracked, not staged"
 msgstr ""
 
-#: git-gui.sh:1834
+#: git-gui.sh:1928
 msgid "Missing"
 msgstr ""
 
-#: git-gui.sh:1835
+#: git-gui.sh:1929
 msgid "Staged for removal"
 msgstr ""
 
-#: git-gui.sh:1836
+#: git-gui.sh:1930
 msgid "Staged for removal, still present"
 msgstr ""
 
-#: git-gui.sh:1838 git-gui.sh:1839 git-gui.sh:1840 git-gui.sh:1841
-#: git-gui.sh:1842 git-gui.sh:1843
+#: git-gui.sh:1932 git-gui.sh:1933 git-gui.sh:1934 git-gui.sh:1935
+#: git-gui.sh:1936 git-gui.sh:1937
 msgid "Requires merge resolution"
 msgstr ""
 
-#: git-gui.sh:1878
+#: git-gui.sh:1972
 msgid "Starting gitk... please wait..."
 msgstr ""
 
-#: git-gui.sh:1887
+#: git-gui.sh:1984
 msgid "Couldn't find gitk in PATH"
 msgstr ""
 
-#: git-gui.sh:2280 lib/choose_repository.tcl:36
+#: git-gui.sh:2043
+msgid "Couldn't find git gui in PATH"
+msgstr ""
+
+#: git-gui.sh:2455 lib/choose_repository.tcl:36
 msgid "Repository"
 msgstr ""
 
-#: git-gui.sh:2281
+#: git-gui.sh:2456
 msgid "Edit"
 msgstr ""
 
-#: git-gui.sh:2283 lib/choose_rev.tcl:561
+#: git-gui.sh:2458 lib/choose_rev.tcl:561
 msgid "Branch"
 msgstr ""
 
-#: git-gui.sh:2286 lib/choose_rev.tcl:548
+#: git-gui.sh:2461 lib/choose_rev.tcl:548
 msgid "Commit@@noun"
 msgstr ""
 
-#: git-gui.sh:2289 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
+#: git-gui.sh:2464 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
 msgid "Merge"
 msgstr ""
 
-#: git-gui.sh:2290 lib/choose_rev.tcl:557
+#: git-gui.sh:2465 lib/choose_rev.tcl:557
 msgid "Remote"
 msgstr ""
 
-#: git-gui.sh:2293
+#: git-gui.sh:2468
 msgid "Tools"
 msgstr ""
 
-#: git-gui.sh:2302
+#: git-gui.sh:2477
 msgid "Explore Working Copy"
 msgstr ""
 
-#: git-gui.sh:2307
+#: git-gui.sh:2483
 msgid "Browse Current Branch's Files"
 msgstr ""
 
-#: git-gui.sh:2311
+#: git-gui.sh:2487
 msgid "Browse Branch Files..."
 msgstr ""
 
-#: git-gui.sh:2316
+#: git-gui.sh:2492
 msgid "Visualize Current Branch's History"
 msgstr ""
 
-#: git-gui.sh:2320
+#: git-gui.sh:2496
 msgid "Visualize All Branch History"
 msgstr ""
 
-#: git-gui.sh:2327
+#: git-gui.sh:2503
 #, tcl-format
 msgid "Browse %s's Files"
 msgstr ""
 
-#: git-gui.sh:2329
+#: git-gui.sh:2505
 #, tcl-format
 msgid "Visualize %s's History"
 msgstr ""
 
-#: git-gui.sh:2334 lib/database.tcl:27 lib/database.tcl:67
+#: git-gui.sh:2510 lib/database.tcl:27 lib/database.tcl:67
 msgid "Database Statistics"
 msgstr ""
 
-#: git-gui.sh:2337 lib/database.tcl:34
+#: git-gui.sh:2513 lib/database.tcl:34
 msgid "Compress Database"
 msgstr ""
 
-#: git-gui.sh:2340
+#: git-gui.sh:2516
 msgid "Verify Database"
 msgstr ""
 
-#: git-gui.sh:2347 git-gui.sh:2351 git-gui.sh:2355 lib/shortcut.tcl:7
-#: lib/shortcut.tcl:39 lib/shortcut.tcl:71
+#: git-gui.sh:2523 git-gui.sh:2527 git-gui.sh:2531 lib/shortcut.tcl:8
+#: lib/shortcut.tcl:40 lib/shortcut.tcl:72
 msgid "Create Desktop Icon"
 msgstr ""
 
-#: git-gui.sh:2363 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191
+#: git-gui.sh:2539 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191
 msgid "Quit"
 msgstr ""
 
-#: git-gui.sh:2371
+#: git-gui.sh:2547
 msgid "Undo"
 msgstr ""
 
-#: git-gui.sh:2374
+#: git-gui.sh:2550
 msgid "Redo"
 msgstr ""
 
-#: git-gui.sh:2378 git-gui.sh:2937
+#: git-gui.sh:2554 git-gui.sh:3109
 msgid "Cut"
 msgstr ""
 
-#: git-gui.sh:2381 git-gui.sh:2940 git-gui.sh:3014 git-gui.sh:3096
+#: git-gui.sh:2557 git-gui.sh:3112 git-gui.sh:3186 git-gui.sh:3259
 #: lib/console.tcl:69
 msgid "Copy"
 msgstr ""
 
-#: git-gui.sh:2384 git-gui.sh:2943
+#: git-gui.sh:2560 git-gui.sh:3115
 msgid "Paste"
 msgstr ""
 
-#: git-gui.sh:2387 git-gui.sh:2946 lib/branch_delete.tcl:26
+#: git-gui.sh:2563 git-gui.sh:3118 lib/branch_delete.tcl:26
 #: lib/remote_branch_delete.tcl:38
 msgid "Delete"
 msgstr ""
 
-#: git-gui.sh:2391 git-gui.sh:2950 git-gui.sh:3100 lib/console.tcl:71
+#: git-gui.sh:2567 git-gui.sh:3122 git-gui.sh:3263 lib/console.tcl:71
 msgid "Select All"
 msgstr ""
 
-#: git-gui.sh:2400
+#: git-gui.sh:2576
 msgid "Create..."
 msgstr ""
 
-#: git-gui.sh:2406
+#: git-gui.sh:2582
 msgid "Checkout..."
 msgstr ""
 
-#: git-gui.sh:2412
+#: git-gui.sh:2588
 msgid "Rename..."
 msgstr ""
 
-#: git-gui.sh:2417
+#: git-gui.sh:2593
 msgid "Delete..."
 msgstr ""
 
-#: git-gui.sh:2422
+#: git-gui.sh:2598
 msgid "Reset..."
 msgstr ""
 
-#: git-gui.sh:2432
+#: git-gui.sh:2608
 msgid "Done"
 msgstr ""
 
-#: git-gui.sh:2434
+#: git-gui.sh:2610
 msgid "Commit@@verb"
 msgstr ""
 
-#: git-gui.sh:2443 git-gui.sh:2878
+#: git-gui.sh:2619 git-gui.sh:3050
 msgid "New Commit"
 msgstr ""
 
-#: git-gui.sh:2451 git-gui.sh:2885
+#: git-gui.sh:2627 git-gui.sh:3057
 msgid "Amend Last Commit"
 msgstr ""
 
-#: git-gui.sh:2461 git-gui.sh:2839 lib/remote_branch_delete.tcl:99
+#: git-gui.sh:2637 git-gui.sh:3011 lib/remote_branch_delete.tcl:99
 msgid "Rescan"
 msgstr ""
 
-#: git-gui.sh:2467
+#: git-gui.sh:2643
 msgid "Stage To Commit"
 msgstr ""
 
-#: git-gui.sh:2473
+#: git-gui.sh:2649
 msgid "Stage Changed Files To Commit"
 msgstr ""
 
-#: git-gui.sh:2479
+#: git-gui.sh:2655
 msgid "Unstage From Commit"
 msgstr ""
 
-#: git-gui.sh:2484 lib/index.tcl:410
+#: git-gui.sh:2661 lib/index.tcl:412
 msgid "Revert Changes"
 msgstr ""
 
-#: git-gui.sh:2491 git-gui.sh:3083
+#: git-gui.sh:2669 git-gui.sh:3310 git-gui.sh:3341
 msgid "Show Less Context"
 msgstr ""
 
-#: git-gui.sh:2495 git-gui.sh:3087
+#: git-gui.sh:2673 git-gui.sh:3314 git-gui.sh:3345
 msgid "Show More Context"
 msgstr ""
 
-#: git-gui.sh:2502 git-gui.sh:2852 git-gui.sh:2961
+#: git-gui.sh:2680 git-gui.sh:3024 git-gui.sh:3133
 msgid "Sign Off"
 msgstr ""
 
-#: git-gui.sh:2518
+#: git-gui.sh:2696
 msgid "Local Merge..."
 msgstr ""
 
-#: git-gui.sh:2523
+#: git-gui.sh:2701
 msgid "Abort Merge..."
 msgstr ""
 
-#: git-gui.sh:2535 git-gui.sh:2575
+#: git-gui.sh:2713 git-gui.sh:2741
 msgid "Add..."
 msgstr ""
 
-#: git-gui.sh:2539
+#: git-gui.sh:2717
 msgid "Push..."
 msgstr ""
 
-#: git-gui.sh:2543
+#: git-gui.sh:2721
 msgid "Delete Branch..."
 msgstr ""
 
-#: git-gui.sh:2553 git-gui.sh:2589 lib/about.tcl:14
-#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53
-#, tcl-format
-msgid "About %s"
-msgstr ""
-
-#: git-gui.sh:2557
-msgid "Preferences..."
-msgstr ""
-
-#: git-gui.sh:2565 git-gui.sh:3129
+#: git-gui.sh:2731 git-gui.sh:3292
 msgid "Options..."
 msgstr ""
 
-#: git-gui.sh:2576
+#: git-gui.sh:2742
 msgid "Remove..."
 msgstr ""
 
-#: git-gui.sh:2585 lib/choose_repository.tcl:50
+#: git-gui.sh:2751 lib/choose_repository.tcl:50
 msgid "Help"
 msgstr ""
 
-#: git-gui.sh:2611
+#: git-gui.sh:2755 git-gui.sh:2759 lib/about.tcl:14
+#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53
+#, tcl-format
+msgid "About %s"
+msgstr ""
+
+#: git-gui.sh:2783
 msgid "Online Documentation"
 msgstr ""
 
-#: git-gui.sh:2614 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
+#: git-gui.sh:2786 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
 msgid "Show SSH Key"
 msgstr ""
 
-#: git-gui.sh:2721
+#: git-gui.sh:2893
 #, tcl-format
 msgid "fatal: cannot stat path %s: No such file or directory"
 msgstr ""
 
-#: git-gui.sh:2754
+#: git-gui.sh:2926
 msgid "Current Branch:"
 msgstr ""
 
-#: git-gui.sh:2775
+#: git-gui.sh:2947
 msgid "Staged Changes (Will Commit)"
 msgstr ""
 
-#: git-gui.sh:2795
+#: git-gui.sh:2967
 msgid "Unstaged Changes"
 msgstr ""
 
-#: git-gui.sh:2845
+#: git-gui.sh:3017
 msgid "Stage Changed"
 msgstr ""
 
-#: git-gui.sh:2864 lib/transport.tcl:104 lib/transport.tcl:193
+#: git-gui.sh:3036 lib/transport.tcl:104 lib/transport.tcl:193
 msgid "Push"
 msgstr ""
 
-#: git-gui.sh:2899
+#: git-gui.sh:3071
 msgid "Initial Commit Message:"
 msgstr ""
 
-#: git-gui.sh:2900
+#: git-gui.sh:3072
 msgid "Amended Commit Message:"
 msgstr ""
 
-#: git-gui.sh:2901
+#: git-gui.sh:3073
 msgid "Amended Initial Commit Message:"
 msgstr ""
 
-#: git-gui.sh:2902
+#: git-gui.sh:3074
 msgid "Amended Merge Commit Message:"
 msgstr ""
 
-#: git-gui.sh:2903
+#: git-gui.sh:3075
 msgid "Merge Commit Message:"
 msgstr ""
 
-#: git-gui.sh:2904
+#: git-gui.sh:3076
 msgid "Commit Message:"
 msgstr ""
 
-#: git-gui.sh:2953 git-gui.sh:3104 lib/console.tcl:73
+#: git-gui.sh:3125 git-gui.sh:3267 lib/console.tcl:73
 msgid "Copy All"
 msgstr ""
 
-#: git-gui.sh:2977 lib/blame.tcl:104
+#: git-gui.sh:3149 lib/blame.tcl:104
 msgid "File:"
 msgstr ""
 
-#: git-gui.sh:3092
+#: git-gui.sh:3255
 msgid "Refresh"
 msgstr ""
 
-#: git-gui.sh:3113
+#: git-gui.sh:3276
 msgid "Decrease Font Size"
 msgstr ""
 
-#: git-gui.sh:3117
+#: git-gui.sh:3280
 msgid "Increase Font Size"
 msgstr ""
 
-#: git-gui.sh:3125 lib/blame.tcl:281
+#: git-gui.sh:3288 lib/blame.tcl:281
 msgid "Encoding"
 msgstr ""
 
-#: git-gui.sh:3136
+#: git-gui.sh:3299
 msgid "Apply/Reverse Hunk"
 msgstr ""
 
-#: git-gui.sh:3141
+#: git-gui.sh:3304
 msgid "Apply/Reverse Line"
 msgstr ""
 
-#: git-gui.sh:3151
+#: git-gui.sh:3323
 msgid "Run Merge Tool"
 msgstr ""
 
-#: git-gui.sh:3156
+#: git-gui.sh:3328
 msgid "Use Remote Version"
 msgstr ""
 
-#: git-gui.sh:3160
+#: git-gui.sh:3332
 msgid "Use Local Version"
 msgstr ""
 
-#: git-gui.sh:3164
+#: git-gui.sh:3336
 msgid "Revert To Base"
 msgstr ""
 
-#: git-gui.sh:3183
+#: git-gui.sh:3354
+msgid "Visualize These Changes In The Submodule"
+msgstr ""
+
+#: git-gui.sh:3358
+msgid "Visualize Current Branch History In The Submodule"
+msgstr ""
+
+#: git-gui.sh:3362
+msgid "Visualize All Branch History In The Submodule"
+msgstr ""
+
+#: git-gui.sh:3367
+msgid "Start git gui In The Submodule"
+msgstr ""
+
+#: git-gui.sh:3389
 msgid "Unstage Hunk From Commit"
 msgstr ""
 
-#: git-gui.sh:3184
+#: git-gui.sh:3391
+msgid "Unstage Lines From Commit"
+msgstr ""
+
+#: git-gui.sh:3393
 msgid "Unstage Line From Commit"
 msgstr ""
 
-#: git-gui.sh:3186
+#: git-gui.sh:3396
 msgid "Stage Hunk For Commit"
 msgstr ""
 
-#: git-gui.sh:3187
+#: git-gui.sh:3398
+msgid "Stage Lines For Commit"
+msgstr ""
+
+#: git-gui.sh:3400
 msgid "Stage Line For Commit"
 msgstr ""
 
-#: git-gui.sh:3210
+#: git-gui.sh:3424
 msgid "Initializing..."
 msgstr ""
 
-#: git-gui.sh:3315
+#: git-gui.sh:3541
 #, tcl-format
 msgid ""
 "Possible environment issues exist.\n"
@@ -502,14 +531,14 @@ msgid ""
 "\n"
 msgstr ""
 
-#: git-gui.sh:3345
+#: git-gui.sh:3570
 msgid ""
 "\n"
 "This is due to a known issue with the\n"
 "Tcl binary distributed by Cygwin."
 msgstr ""
 
-#: git-gui.sh:3350
+#: git-gui.sh:3575
 #, tcl-format
 msgid ""
 "\n"
@@ -613,7 +642,7 @@ msgstr ""
 msgid "Unable to display parent"
 msgstr ""
 
-#: lib/blame.tcl:1091 lib/diff.tcl:297
+#: lib/blame.tcl:1091 lib/diff.tcl:320
 msgid "Error loading diff:"
 msgstr ""
 
@@ -639,7 +668,7 @@ msgstr ""
 
 #: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
 #: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282
-#: lib/checkout_op.tcl:544 lib/choose_font.tcl:43 lib/merge.tcl:172
+#: lib/checkout_op.tcl:579 lib/choose_font.tcl:43 lib/merge.tcl:172
 #: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42
 #: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352
 #: lib/transport.tcl:108
@@ -670,7 +699,7 @@ msgstr ""
 msgid "Create New Branch"
 msgstr ""
 
-#: lib/branch_create.tcl:31 lib/choose_repository.tcl:377
+#: lib/branch_create.tcl:31 lib/choose_repository.tcl:381
 msgid "Create"
 msgstr ""
 
@@ -702,7 +731,7 @@ msgstr ""
 msgid "Fast Forward Only"
 msgstr ""
 
-#: lib/branch_create.tcl:85 lib/checkout_op.tcl:536
+#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571
 msgid "Reset"
 msgstr ""
 
@@ -744,8 +773,8 @@ msgstr ""
 msgid "Delete Only If Merged Into"
 msgstr ""
 
-#: lib/branch_delete.tcl:54
-msgid "Always (Do not perform merge test.)"
+#: lib/branch_delete.tcl:54 lib/remote_branch_delete.tcl:119
+msgid "Always (Do not perform merge checks)"
 msgstr ""
 
 #: lib/branch_delete.tcl:103
@@ -753,6 +782,13 @@ msgstr ""
 msgid "The following branches are not completely merged into %s:"
 msgstr ""
 
+#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:217
+msgid ""
+"Recovering deleted branches is difficult.\n"
+"\n"
+"Delete the selected branches?"
+msgstr ""
+
 #: lib/branch_delete.tcl:141
 #, tcl-format
 msgid ""
@@ -780,7 +816,7 @@ msgstr ""
 msgid "Please select a branch to rename."
 msgstr ""
 
-#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:201
+#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202
 #, tcl-format
 msgid "Branch '%s' already exists."
 msgstr ""
@@ -811,38 +847,38 @@ msgstr ""
 msgid "Browse Branch Files"
 msgstr ""
 
-#: lib/browser.tcl:278 lib/choose_repository.tcl:394
-#: lib/choose_repository.tcl:480 lib/choose_repository.tcl:491
-#: lib/choose_repository.tcl:995
+#: lib/browser.tcl:278 lib/choose_repository.tcl:398
+#: lib/choose_repository.tcl:486 lib/choose_repository.tcl:497
+#: lib/choose_repository.tcl:1028
 msgid "Browse"
 msgstr ""
 
-#: lib/checkout_op.tcl:84
+#: lib/checkout_op.tcl:85
 #, tcl-format
 msgid "Fetching %s from %s"
 msgstr ""
 
-#: lib/checkout_op.tcl:132
+#: lib/checkout_op.tcl:133
 #, tcl-format
 msgid "fatal: Cannot resolve %s"
 msgstr ""
 
-#: lib/checkout_op.tcl:145 lib/console.tcl:81 lib/database.tcl:31
+#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:31
 #: lib/sshkey.tcl:53
 msgid "Close"
 msgstr ""
 
-#: lib/checkout_op.tcl:174
+#: lib/checkout_op.tcl:175
 #, tcl-format
 msgid "Branch '%s' does not exist."
 msgstr ""
 
-#: lib/checkout_op.tcl:193
+#: lib/checkout_op.tcl:194
 #, tcl-format
 msgid "Failed to configure simplified git-pull for '%s'."
 msgstr ""
 
-#: lib/checkout_op.tcl:228
+#: lib/checkout_op.tcl:229
 #, tcl-format
 msgid ""
 "Branch '%s' already exists.\n"
@@ -851,21 +887,21 @@ msgid ""
 "A merge is required."
 msgstr ""
 
-#: lib/checkout_op.tcl:242
+#: lib/checkout_op.tcl:243
 #, tcl-format
 msgid "Merge strategy '%s' not supported."
 msgstr ""
 
-#: lib/checkout_op.tcl:261
+#: lib/checkout_op.tcl:262
 #, tcl-format
 msgid "Failed to update '%s'."
 msgstr ""
 
-#: lib/checkout_op.tcl:273
+#: lib/checkout_op.tcl:274
 msgid "Staging area (index) is already locked."
 msgstr ""
 
-#: lib/checkout_op.tcl:288
+#: lib/checkout_op.tcl:289
 msgid ""
 "Last scanned state does not match repository state.\n"
 "\n"
@@ -875,30 +911,30 @@ msgid ""
 "The rescan will be automatically started now.\n"
 msgstr ""
 
-#: lib/checkout_op.tcl:344
+#: lib/checkout_op.tcl:345
 #, tcl-format
 msgid "Updating working directory to '%s'..."
 msgstr ""
 
-#: lib/checkout_op.tcl:345
+#: lib/checkout_op.tcl:346
 msgid "files checked out"
 msgstr ""
 
-#: lib/checkout_op.tcl:375
+#: lib/checkout_op.tcl:376
 #, tcl-format
 msgid "Aborted checkout of '%s' (file level merging is required)."
 msgstr ""
 
-#: lib/checkout_op.tcl:376
+#: lib/checkout_op.tcl:377
 msgid "File level merge required."
 msgstr ""
 
-#: lib/checkout_op.tcl:380
+#: lib/checkout_op.tcl:381
 #, tcl-format
 msgid "Staying on branch '%s'."
 msgstr ""
 
-#: lib/checkout_op.tcl:451
+#: lib/checkout_op.tcl:452
 msgid ""
 "You are no longer on a local branch.\n"
 "\n"
@@ -906,30 +942,30 @@ msgid ""
 "Checkout'."
 msgstr ""
 
-#: lib/checkout_op.tcl:468 lib/checkout_op.tcl:472
+#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507
 #, tcl-format
 msgid "Checked out '%s'."
 msgstr ""
 
-#: lib/checkout_op.tcl:500
+#: lib/checkout_op.tcl:535
 #, tcl-format
 msgid "Resetting '%s' to '%s' will lose the following commits:"
 msgstr ""
 
-#: lib/checkout_op.tcl:522
+#: lib/checkout_op.tcl:557
 msgid "Recovering lost commits may not be easy."
 msgstr ""
 
-#: lib/checkout_op.tcl:527
+#: lib/checkout_op.tcl:562
 #, tcl-format
 msgid "Reset '%s'?"
 msgstr ""
 
-#: lib/checkout_op.tcl:532 lib/merge.tcl:164 lib/tools_dlg.tcl:343
+#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:343
 msgid "Visualize"
 msgstr ""
 
-#: lib/checkout_op.tcl:600
+#: lib/checkout_op.tcl:635
 #, tcl-format
 msgid ""
 "Failed to set current branch.\n"
@@ -966,7 +1002,7 @@ msgstr ""
 msgid "Git Gui"
 msgstr ""
 
-#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:382
+#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:386
 msgid "Create New Repository"
 msgstr ""
 
@@ -974,7 +1010,7 @@ msgstr ""
 msgid "New..."
 msgstr ""
 
-#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:465
+#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:471
 msgid "Clone Existing Repository"
 msgstr ""
 
@@ -982,7 +1018,7 @@ msgstr ""
 msgid "Clone..."
 msgstr ""
 
-#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:983
+#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:1016
 msgid "Open Existing Repository"
 msgstr ""
 
@@ -998,193 +1034,193 @@ msgstr ""
 msgid "Open Recent Repository:"
 msgstr ""
 
-#: lib/choose_repository.tcl:302 lib/choose_repository.tcl:309
-#: lib/choose_repository.tcl:316
+#: lib/choose_repository.tcl:306 lib/choose_repository.tcl:313
+#: lib/choose_repository.tcl:320
 #, tcl-format
 msgid "Failed to create repository %s:"
 msgstr ""
 
-#: lib/choose_repository.tcl:387
+#: lib/choose_repository.tcl:391
 msgid "Directory:"
 msgstr ""
 
-#: lib/choose_repository.tcl:417 lib/choose_repository.tcl:544
-#: lib/choose_repository.tcl:1017
+#: lib/choose_repository.tcl:423 lib/choose_repository.tcl:550
+#: lib/choose_repository.tcl:1052
 msgid "Git Repository"
 msgstr ""
 
-#: lib/choose_repository.tcl:442
+#: lib/choose_repository.tcl:448
 #, tcl-format
 msgid "Directory %s already exists."
 msgstr ""
 
-#: lib/choose_repository.tcl:446
+#: lib/choose_repository.tcl:452
 #, tcl-format
 msgid "File %s already exists."
 msgstr ""
 
-#: lib/choose_repository.tcl:460
+#: lib/choose_repository.tcl:466
 msgid "Clone"
 msgstr ""
 
-#: lib/choose_repository.tcl:473
+#: lib/choose_repository.tcl:479
 msgid "Source Location:"
 msgstr ""
 
-#: lib/choose_repository.tcl:484
+#: lib/choose_repository.tcl:490
 msgid "Target Directory:"
 msgstr ""
 
-#: lib/choose_repository.tcl:496
+#: lib/choose_repository.tcl:502
 msgid "Clone Type:"
 msgstr ""
 
-#: lib/choose_repository.tcl:502
+#: lib/choose_repository.tcl:508
 msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
 msgstr ""
 
-#: lib/choose_repository.tcl:508
+#: lib/choose_repository.tcl:514
 msgid "Full Copy (Slower, Redundant Backup)"
 msgstr ""
 
-#: lib/choose_repository.tcl:514
+#: lib/choose_repository.tcl:520
 msgid "Shared (Fastest, Not Recommended, No Backup)"
 msgstr ""
 
-#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597
-#: lib/choose_repository.tcl:743 lib/choose_repository.tcl:813
-#: lib/choose_repository.tcl:1023 lib/choose_repository.tcl:1031
+#: lib/choose_repository.tcl:556 lib/choose_repository.tcl:603
+#: lib/choose_repository.tcl:749 lib/choose_repository.tcl:819
+#: lib/choose_repository.tcl:1058 lib/choose_repository.tcl:1066
 #, tcl-format
 msgid "Not a Git repository: %s"
 msgstr ""
 
-#: lib/choose_repository.tcl:586
+#: lib/choose_repository.tcl:592
 msgid "Standard only available for local repository."
 msgstr ""
 
-#: lib/choose_repository.tcl:590
+#: lib/choose_repository.tcl:596
 msgid "Shared only available for local repository."
 msgstr ""
 
-#: lib/choose_repository.tcl:611
+#: lib/choose_repository.tcl:617
 #, tcl-format
 msgid "Location %s already exists."
 msgstr ""
 
-#: lib/choose_repository.tcl:622
+#: lib/choose_repository.tcl:628
 msgid "Failed to configure origin"
 msgstr ""
 
-#: lib/choose_repository.tcl:634
+#: lib/choose_repository.tcl:640
 msgid "Counting objects"
 msgstr ""
 
-#: lib/choose_repository.tcl:635
+#: lib/choose_repository.tcl:641
 msgid "buckets"
 msgstr ""
 
-#: lib/choose_repository.tcl:659
+#: lib/choose_repository.tcl:665
 #, tcl-format
 msgid "Unable to copy objects/info/alternates: %s"
 msgstr ""
 
-#: lib/choose_repository.tcl:695
+#: lib/choose_repository.tcl:701
 #, tcl-format
 msgid "Nothing to clone from %s."
 msgstr ""
 
-#: lib/choose_repository.tcl:697 lib/choose_repository.tcl:911
-#: lib/choose_repository.tcl:923
+#: lib/choose_repository.tcl:703 lib/choose_repository.tcl:917
+#: lib/choose_repository.tcl:929
 msgid "The 'master' branch has not been initialized."
 msgstr ""
 
-#: lib/choose_repository.tcl:710
+#: lib/choose_repository.tcl:716
 msgid "Hardlinks are unavailable.  Falling back to copying."
 msgstr ""
 
-#: lib/choose_repository.tcl:722
+#: lib/choose_repository.tcl:728
 #, tcl-format
 msgid "Cloning from %s"
 msgstr ""
 
-#: lib/choose_repository.tcl:753
+#: lib/choose_repository.tcl:759
 msgid "Copying objects"
 msgstr ""
 
-#: lib/choose_repository.tcl:754
+#: lib/choose_repository.tcl:760
 msgid "KiB"
 msgstr ""
 
-#: lib/choose_repository.tcl:778
+#: lib/choose_repository.tcl:784
 #, tcl-format
 msgid "Unable to copy object: %s"
 msgstr ""
 
-#: lib/choose_repository.tcl:788
+#: lib/choose_repository.tcl:794
 msgid "Linking objects"
 msgstr ""
 
-#: lib/choose_repository.tcl:789
+#: lib/choose_repository.tcl:795
 msgid "objects"
 msgstr ""
 
-#: lib/choose_repository.tcl:797
+#: lib/choose_repository.tcl:803
 #, tcl-format
 msgid "Unable to hardlink object: %s"
 msgstr ""
 
-#: lib/choose_repository.tcl:852
+#: lib/choose_repository.tcl:858
 msgid "Cannot fetch branches and objects.  See console output for details."
 msgstr ""
 
-#: lib/choose_repository.tcl:863
+#: lib/choose_repository.tcl:869
 msgid "Cannot fetch tags.  See console output for details."
 msgstr ""
 
-#: lib/choose_repository.tcl:887
+#: lib/choose_repository.tcl:893
 msgid "Cannot determine HEAD.  See console output for details."
 msgstr ""
 
-#: lib/choose_repository.tcl:896
+#: lib/choose_repository.tcl:902
 #, tcl-format
 msgid "Unable to cleanup %s"
 msgstr ""
 
-#: lib/choose_repository.tcl:902
+#: lib/choose_repository.tcl:908
 msgid "Clone failed."
 msgstr ""
 
-#: lib/choose_repository.tcl:909
+#: lib/choose_repository.tcl:915
 msgid "No default branch obtained."
 msgstr ""
 
-#: lib/choose_repository.tcl:920
+#: lib/choose_repository.tcl:926
 #, tcl-format
 msgid "Cannot resolve %s as a commit."
 msgstr ""
 
-#: lib/choose_repository.tcl:932
+#: lib/choose_repository.tcl:938
 msgid "Creating working directory"
 msgstr ""
 
-#: lib/choose_repository.tcl:933 lib/index.tcl:65 lib/index.tcl:128
-#: lib/index.tcl:196
+#: lib/choose_repository.tcl:939 lib/index.tcl:67 lib/index.tcl:130
+#: lib/index.tcl:198
 msgid "files"
 msgstr ""
 
-#: lib/choose_repository.tcl:962
+#: lib/choose_repository.tcl:968
 msgid "Initial file checkout failed."
 msgstr ""
 
-#: lib/choose_repository.tcl:978
+#: lib/choose_repository.tcl:1011
 msgid "Open"
 msgstr ""
 
-#: lib/choose_repository.tcl:988
+#: lib/choose_repository.tcl:1021
 msgid "Repository:"
 msgstr ""
 
-#: lib/choose_repository.tcl:1037
+#: lib/choose_repository.tcl:1072
 #, tcl-format
 msgid "Failed to open repository %s:"
 msgstr ""
@@ -1259,7 +1295,12 @@ msgstr ""
 msgid "Invalid GIT_COMMITTER_IDENT:"
 msgstr ""
 
-#: lib/commit.tcl:132
+#: lib/commit.tcl:129
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr ""
+
+#: lib/commit.tcl:149
 msgid ""
 "Last scanned state does not match repository state.\n"
 "\n"
@@ -1269,7 +1310,7 @@ msgid ""
 "The rescan will be automatically started now.\n"
 msgstr ""
 
-#: lib/commit.tcl:155
+#: lib/commit.tcl:172
 #, tcl-format
 msgid ""
 "Unmerged files cannot be committed.\n"
@@ -1278,7 +1319,7 @@ msgid ""
 "before committing.\n"
 msgstr ""
 
-#: lib/commit.tcl:163
+#: lib/commit.tcl:180
 #, tcl-format
 msgid ""
 "Unknown file state %s detected.\n"
@@ -1286,14 +1327,14 @@ msgid ""
 "File %s cannot be committed by this program.\n"
 msgstr ""
 
-#: lib/commit.tcl:171
+#: lib/commit.tcl:188
 msgid ""
 "No changes to commit.\n"
 "\n"
 "You must stage at least 1 file before you can commit.\n"
 msgstr ""
 
-#: lib/commit.tcl:186
+#: lib/commit.tcl:203
 msgid ""
 "Please supply a commit message.\n"
 "\n"
@@ -1304,45 +1345,40 @@ msgid ""
 "- Remaining lines: Describe why this change is good.\n"
 msgstr ""
 
-#: lib/commit.tcl:210
-#, tcl-format
-msgid "warning: Tcl does not support encoding '%s'."
-msgstr ""
-
-#: lib/commit.tcl:226
+#: lib/commit.tcl:234
 msgid "Calling pre-commit hook..."
 msgstr ""
 
-#: lib/commit.tcl:241
+#: lib/commit.tcl:249
 msgid "Commit declined by pre-commit hook."
 msgstr ""
 
-#: lib/commit.tcl:264
+#: lib/commit.tcl:272
 msgid "Calling commit-msg hook..."
 msgstr ""
 
-#: lib/commit.tcl:279
+#: lib/commit.tcl:287
 msgid "Commit declined by commit-msg hook."
 msgstr ""
 
-#: lib/commit.tcl:292
+#: lib/commit.tcl:300
 msgid "Committing changes..."
 msgstr ""
 
-#: lib/commit.tcl:308
+#: lib/commit.tcl:316
 msgid "write-tree failed:"
 msgstr ""
 
-#: lib/commit.tcl:309 lib/commit.tcl:353 lib/commit.tcl:373
+#: lib/commit.tcl:317 lib/commit.tcl:361 lib/commit.tcl:382
 msgid "Commit failed."
 msgstr ""
 
-#: lib/commit.tcl:326
+#: lib/commit.tcl:334
 #, tcl-format
 msgid "Commit %s appears to be corrupt"
 msgstr ""
 
-#: lib/commit.tcl:331
+#: lib/commit.tcl:339
 msgid ""
 "No changes to commit.\n"
 "\n"
@@ -1351,19 +1387,19 @@ msgid ""
 "A rescan will be automatically started now.\n"
 msgstr ""
 
-#: lib/commit.tcl:338
+#: lib/commit.tcl:346
 msgid "No changes to commit."
 msgstr ""
 
-#: lib/commit.tcl:352
+#: lib/commit.tcl:360
 msgid "commit-tree failed:"
 msgstr ""
 
-#: lib/commit.tcl:372
+#: lib/commit.tcl:381
 msgid "update-ref failed:"
 msgstr ""
 
-#: lib/commit.tcl:460
+#: lib/commit.tcl:469
 #, tcl-format
 msgid "Created commit %s: %s"
 msgstr ""
@@ -1416,13 +1452,13 @@ msgstr ""
 msgid "Verifying the object database with fsck-objects"
 msgstr ""
 
-#: lib/database.tcl:108
+#: lib/database.tcl:107
 #, tcl-format
 msgid ""
 "This repository currently has approximately %i loose objects.\n"
 "\n"
 "To maintain optimal performance it is strongly recommended that you compress "
-"the database when more than %i loose objects exist.\n"
+"the database.\n"
 "\n"
 "Compress the database now?"
 msgstr ""
@@ -1432,7 +1468,7 @@ msgstr ""
 msgid "Invalid date from Git: %s"
 msgstr ""
 
-#: lib/diff.tcl:59
+#: lib/diff.tcl:64
 #, tcl-format
 msgid ""
 "No differences detected.\n"
@@ -1446,56 +1482,56 @@ msgid ""
 "the same state."
 msgstr ""
 
-#: lib/diff.tcl:99
+#: lib/diff.tcl:104
 #, tcl-format
 msgid "Loading diff of %s..."
 msgstr ""
 
-#: lib/diff.tcl:120
+#: lib/diff.tcl:125
 msgid ""
 "LOCAL: deleted\n"
 "REMOTE:\n"
 msgstr ""
 
-#: lib/diff.tcl:125
+#: lib/diff.tcl:130
 msgid ""
 "REMOTE: deleted\n"
 "LOCAL:\n"
 msgstr ""
 
-#: lib/diff.tcl:132
+#: lib/diff.tcl:137
 msgid "LOCAL:\n"
 msgstr ""
 
-#: lib/diff.tcl:135
+#: lib/diff.tcl:140
 msgid "REMOTE:\n"
 msgstr ""
 
-#: lib/diff.tcl:197 lib/diff.tcl:296
+#: lib/diff.tcl:202 lib/diff.tcl:319
 #, tcl-format
 msgid "Unable to display %s"
 msgstr ""
 
-#: lib/diff.tcl:198
+#: lib/diff.tcl:203
 msgid "Error loading file:"
 msgstr ""
 
-#: lib/diff.tcl:205
+#: lib/diff.tcl:210
 msgid "Git Repository (subproject)"
 msgstr ""
 
-#: lib/diff.tcl:217
+#: lib/diff.tcl:222
 msgid "* Binary file (not showing content)."
 msgstr ""
 
-#: lib/diff.tcl:222
+#: lib/diff.tcl:227
 #, tcl-format
 msgid ""
 "* Untracked file is %d bytes.\n"
 "* Showing only first %d bytes.\n"
 msgstr ""
 
-#: lib/diff.tcl:228
+#: lib/diff.tcl:233
 #, tcl-format
 msgid ""
 "\n"
@@ -1503,19 +1539,19 @@ msgid ""
 "* To see the entire file, use an external editor.\n"
 msgstr ""
 
-#: lib/diff.tcl:436
+#: lib/diff.tcl:482
 msgid "Failed to unstage selected hunk."
 msgstr ""
 
-#: lib/diff.tcl:443
+#: lib/diff.tcl:489
 msgid "Failed to stage selected hunk."
 msgstr ""
 
-#: lib/diff.tcl:509
+#: lib/diff.tcl:568
 msgid "Failed to unstage selected line."
 msgstr ""
 
-#: lib/diff.tcl:517
+#: lib/diff.tcl:576
 msgid "Failed to stage selected line."
 msgstr ""
 
@@ -1552,13 +1588,13 @@ msgstr ""
 msgid "Index Error"
 msgstr ""
 
-#: lib/index.tcl:21
+#: lib/index.tcl:17
 msgid ""
 "Updating the Git index failed.  A rescan will be automatically started to "
 "resynchronize git-gui."
 msgstr ""
 
-#: lib/index.tcl:27
+#: lib/index.tcl:28
 msgid "Continue"
 msgstr ""
 
@@ -1566,43 +1602,43 @@ msgstr ""
 msgid "Unlock Index"
 msgstr ""
 
-#: lib/index.tcl:287
+#: lib/index.tcl:289
 #, tcl-format
 msgid "Unstaging %s from commit"
 msgstr ""
 
-#: lib/index.tcl:326
+#: lib/index.tcl:328
 msgid "Ready to commit."
 msgstr ""
 
-#: lib/index.tcl:339
+#: lib/index.tcl:341
 #, tcl-format
 msgid "Adding %s"
 msgstr ""
 
-#: lib/index.tcl:396
+#: lib/index.tcl:398
 #, tcl-format
 msgid "Revert changes in file %s?"
 msgstr ""
 
-#: lib/index.tcl:398
+#: lib/index.tcl:400
 #, tcl-format
 msgid "Revert changes in these %i files?"
 msgstr ""
 
-#: lib/index.tcl:406
+#: lib/index.tcl:408
 msgid "Any unstaged changes will be permanently lost by the revert."
 msgstr ""
 
-#: lib/index.tcl:409
+#: lib/index.tcl:411
 msgid "Do Nothing"
 msgstr ""
 
-#: lib/index.tcl:427
+#: lib/index.tcl:429
 msgid "Reverting selected files"
 msgstr ""
 
-#: lib/index.tcl:431
+#: lib/index.tcl:433
 #, tcl-format
 msgid "Reverting %s"
 msgstr ""
@@ -2031,10 +2067,6 @@ msgstr ""
 msgid "Merged Into:"
 msgstr ""
 
-#: lib/remote_branch_delete.tcl:119
-msgid "Always (Do not perform merge checks)"
-msgstr ""
-
 #: lib/remote_branch_delete.tcl:152
 msgid "A branch is required for 'Merged Into'."
 msgstr ""
@@ -2058,23 +2090,16 @@ msgstr ""
 msgid "Please select one or more branches to delete."
 msgstr ""
 
-#: lib/remote_branch_delete.tcl:216
-msgid ""
-"Recovering deleted branches is difficult.\n"
-"\n"
-"Delete the selected branches?"
-msgstr ""
-
 #: lib/remote_branch_delete.tcl:226
 #, tcl-format
 msgid "Deleting branches from %s"
 msgstr ""
 
-#: lib/remote_branch_delete.tcl:286
+#: lib/remote_branch_delete.tcl:292
 msgid "No repository selected."
 msgstr ""
 
-#: lib/remote_branch_delete.tcl:291
+#: lib/remote_branch_delete.tcl:297
 #, tcl-format
 msgid "Scanning %s..."
 msgstr ""
@@ -2095,11 +2120,11 @@ msgstr ""
 msgid "Case-Sensitive"
 msgstr ""
 
-#: lib/shortcut.tcl:20 lib/shortcut.tcl:61
+#: lib/shortcut.tcl:21 lib/shortcut.tcl:62
 msgid "Cannot write shortcut:"
 msgstr ""
 
-#: lib/shortcut.tcl:136
+#: lib/shortcut.tcl:137
 msgid "Cannot write icon:"
 msgstr ""
 
diff --git a/git-gui/po/glossary/el.po b/git-gui/po/glossary/el.po
new file mode 100644 (file)
index 0000000..1d3cc81
--- /dev/null
@@ -0,0 +1,171 @@
+# Translation of git-gui glossary to Greek
+# Copyright (C) 2009 Jimmy Angelakos
+# This file is distributed under the same license as the git-gui package.
+# Jimmy Angelakos <vyruss@hellug.gr>, 2009.
+msgid ""
+msgstr ""
+"Project-Id-Version: git-gui-glossary\n"
+"POT-Creation-Date: 2008-01-07 21:20+0100\n"
+"PO-Revision-Date: 2009-06-23 20:41+0300\n"
+"Last-Translator: Jimmy Angelakos <vyruss@hellug.gr>\n"
+"Language-Team: Greek <i18n@lists.hellug.gr>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Lokalize 0.3\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. "English Definition (Dear translator: This file will never be visible to the user! It should only serve as a tool for you, the translator. Nothing more.)"
+msgid "English Term (Dear translator: This file will never be visible to the user!)"
+msgstr ""
+
+#. ""
+msgid "amend"
+msgstr "διόρθωση"
+
+#. ""
+msgid "annotate"
+msgstr "σχολιασμός"
+
+#. "A 'branch' is an active line of development."
+msgid "branch [noun]"
+msgstr "κλάδος [αντικείμενο]"
+
+#. ""
+msgid "branch [verb]"
+msgstr "διακλάδωση [ενέργεια]"
+
+#. ""
+msgid "checkout [noun]"
+msgstr "εξαγωγή [αντικείμενο]"
+
+#. "The action of updating the working tree to a revision which was stored in the object database."
+msgid "checkout [verb]"
+msgstr "εξαγωγή [ενέργεια]"
+
+#. ""
+msgid "clone [verb]"
+msgstr "κλωνοποίηση [ενέργεια]"
+
+#. "A single point in the git history."
+msgid "commit [noun]"
+msgstr "υποβολή [αντικείμενο] "
+
+#. "The action of storing a new snapshot of the project's state in the git history."
+msgid "commit [verb]"
+msgstr "υποβολή [ενέργεια]"
+
+#. ""
+msgid "diff [noun]"
+msgstr "διαφορά [αντικείμενο] "
+
+#. ""
+msgid "diff [verb]"
+msgstr "διαφορά [ενέργεια]"
+
+#. "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."
+msgid "fast forward merge"
+msgstr "συγχώνευση επιτάχυνσης"
+
+#. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too."
+msgid "fetch"
+msgstr "ανάκτηση"
+
+#. "One context of consecutive lines in a whole patch, which consists of many such hunks"
+msgid "hunk"
+msgstr "κομμάτι"
+
+#. "A collection of files. The index is a stored version of your working tree."
+msgid "index (in git-gui: staging area)"
+msgstr "ευρετήριο (στο git-gui: περιοχή σταδιοποίησης)"
+
+#. "A successful merge results in the creation of a new commit representing the result of the merge."
+msgid "merge [noun]"
+msgstr "συγχώνευση [αντικείμενο]"
+
+#. "To bring the contents of another branch into the current branch."
+msgid "merge [verb]"
+msgstr "συγχώνευση [ενέργεια]"
+
+#. ""
+msgid "message"
+msgstr "μήνυμα"
+
+#. "Deletes all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in 'remotes/<name>'."
+msgid "prune"
+msgstr "κλάδεμα"
+
+#. "Pulling a branch means to fetch it and merge it."
+msgid "pull"
+msgstr "λήψη"
+
+#. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)"
+msgid "push"
+msgstr "ώθηση"
+
+#. ""
+msgid "redo"
+msgstr "ξανά"
+
+#. "An other repository ('remote'). One might have a set of remotes whose branches one tracks."
+msgid "remote"
+msgstr "απομακρυσμένο"
+
+#. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)"
+msgid "repository"
+msgstr "αποθετήριο"
+
+#. ""
+msgid "reset"
+msgstr "επαναφορά"
+
+#. ""
+msgid "revert"
+msgstr "αναίρεση"
+
+#. "A particular state of files and directories which was stored in the object database."
+msgid "revision"
+msgstr "αναθεώρηση"
+
+#. ""
+#, fuzzy
+msgid "sign off"
+msgstr "αποσύνδεση"
+
+#. ""
+msgid "staging area"
+msgstr "περιοχή σταδιοποίησης"
+
+#. ""
+msgid "status"
+msgstr "κατάσταση"
+
+#. "A ref pointing to a tag or commit object"
+msgid "tag [noun]"
+msgstr "ετικέτα [αντικείμενο]"
+
+#. ""
+msgid "tag [verb]"
+msgstr "ετικέτα [ενέργεια]"
+
+#. "A regular git branch that is used to follow changes from another repository."
+msgid "tracking branch"
+msgstr "κλάδος παρακολούθησης"
+
+#. ""
+msgid "undo"
+msgstr "αναίρεση"
+
+#. ""
+msgid "update"
+msgstr "ενημέρωση"
+
+#. ""
+msgid "verify"
+msgstr "επαλήθευση"
+
+#. "The tree of actual checked out files."
+msgid "working copy, working tree"
+msgstr "αντίγραφο εργασίας"
+
+
index 762632c22f9e5567bf4603e2958401e6156f1872..aa15a204d85c5fdffbb74a5882c759c36f547184 100644 (file)
@@ -9,41 +9,41 @@ msgid ""
 msgstr ""
 "Project-Id-Version: git-gui\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-12-08 08:31-0800\n"
-"PO-Revision-Date: 2008-12-09 13:04+0100\n"
+"POT-Creation-Date: 2010-01-26 15:47-0800\n"
+"PO-Revision-Date: 2010-01-28 10:04+0100\n"
 "Last-Translator: Michele Ballabio <barra_cuda@katamail.com>\n"
 "Language-Team: Italian <tp@lists.linux.it>\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: git-gui.sh:41 git-gui.sh:737 git-gui.sh:751 git-gui.sh:764 git-gui.sh:847
-#: git-gui.sh:866
+#: git-gui.sh:41 git-gui.sh:793 git-gui.sh:807 git-gui.sh:820 git-gui.sh:903
+#: git-gui.sh:922
 msgid "git-gui: fatal error"
 msgstr "git-gui: errore grave"
 
-#: git-gui.sh:689
+#: git-gui.sh:743
 #, tcl-format
 msgid "Invalid font specified in %s:"
 msgstr "Caratteri non validi specificati in %s:"
 
-#: git-gui.sh:723
+#: git-gui.sh:779
 msgid "Main Font"
 msgstr "Caratteri principali"
 
-#: git-gui.sh:724
+#: git-gui.sh:780
 msgid "Diff/Console Font"
 msgstr "Caratteri per confronti e terminale"
 
-#: git-gui.sh:738
+#: git-gui.sh:794
 msgid "Cannot find git in PATH."
 msgstr "Impossibile trovare git nel PATH"
 
-#: git-gui.sh:765
+#: git-gui.sh:821
 msgid "Cannot parse Git version string:"
 msgstr "Impossibile determinare la versione di Git:"
 
-#: git-gui.sh:783
+#: git-gui.sh:839
 #, tcl-format
 msgid ""
 "Git version cannot be determined.\n"
@@ -62,446 +62,475 @@ msgstr ""
 "\n"
 "Assumere che '%s' sia alla versione 1.5.0?\n"
 
-#: git-gui.sh:1062
+#: git-gui.sh:1128
 msgid "Git directory not found:"
 msgstr "Non trovo la directory di git: "
 
-#: git-gui.sh:1069
+#: git-gui.sh:1146
 msgid "Cannot move to top of working directory:"
 msgstr "Impossibile spostarsi sulla directory principale del progetto:"
 
-#: git-gui.sh:1076
-msgid "Cannot use funny .git directory:"
-msgstr "Impossibile usare una .git directory strana:"
+#: git-gui.sh:1154
+msgid "Cannot use bare repository:"
+msgstr "Impossibile usare un archivio senza directory di lavoro:"
 
-#: git-gui.sh:1081
+#: git-gui.sh:1162
 msgid "No working directory"
 msgstr "Nessuna directory di lavoro"
 
-#: git-gui.sh:1247 lib/checkout_op.tcl:305
+#: git-gui.sh:1334 lib/checkout_op.tcl:306
 msgid "Refreshing file status..."
 msgstr "Controllo dello stato dei file in corso..."
 
-#: git-gui.sh:1303
+#: git-gui.sh:1390
 msgid "Scanning for modified files ..."
 msgstr "Ricerca di file modificati in corso..."
 
-#: git-gui.sh:1367
+#: git-gui.sh:1454
 msgid "Calling prepare-commit-msg hook..."
 msgstr "Avvio prepare-commit-msg hook..."
 
-#: git-gui.sh:1384
+#: git-gui.sh:1471
 msgid "Commit declined by prepare-commit-msg hook."
 msgstr "Revisione rifiutata dal prepare-commit-msg hook."
 
-#: git-gui.sh:1542 lib/browser.tcl:246
+#: git-gui.sh:1629 lib/browser.tcl:246
 msgid "Ready."
 msgstr "Pronto."
 
-#: git-gui.sh:1819
+#: git-gui.sh:1787
+#, tcl-format
+msgid "Displaying only %s of %s files."
+msgstr "Saranno mostrati solo %s file su %s."
+
+#: git-gui.sh:1913
 msgid "Unmodified"
 msgstr "Non modificato"
 
-#: git-gui.sh:1821
+#: git-gui.sh:1915
 msgid "Modified, not staged"
 msgstr "Modificato, non preparato per una nuova revisione"
 
-#: git-gui.sh:1822 git-gui.sh:1830
+#: git-gui.sh:1916 git-gui.sh:1924
 msgid "Staged for commit"
 msgstr "Preparato per una nuova revisione"
 
-#: git-gui.sh:1823 git-gui.sh:1831
+#: git-gui.sh:1917 git-gui.sh:1925
 msgid "Portions staged for commit"
 msgstr "Parti preparate per una nuova revisione"
 
-#: git-gui.sh:1824 git-gui.sh:1832
+#: git-gui.sh:1918 git-gui.sh:1926
 msgid "Staged for commit, missing"
 msgstr "Preparato per una nuova revisione, mancante"
 
-#: git-gui.sh:1826
+#: git-gui.sh:1920
 msgid "File type changed, not staged"
 msgstr "Tipo di file modificato, non preparato per una nuova revisione"
 
-#: git-gui.sh:1827
+#: git-gui.sh:1921
 msgid "File type changed, staged"
 msgstr "Tipo di file modificato, preparato per una nuova revisione"
 
-#: git-gui.sh:1829
+#: git-gui.sh:1923
 msgid "Untracked, not staged"
 msgstr "Non tracciato, non preparato per una nuova revisione"
 
-#: git-gui.sh:1834
+#: git-gui.sh:1928
 msgid "Missing"
 msgstr "Mancante"
 
-#: git-gui.sh:1835
+#: git-gui.sh:1929
 msgid "Staged for removal"
 msgstr "Preparato per la rimozione"
 
-#: git-gui.sh:1836
+#: git-gui.sh:1930
 msgid "Staged for removal, still present"
 msgstr "Preparato alla rimozione, ancora presente"
 
-#: git-gui.sh:1838 git-gui.sh:1839 git-gui.sh:1840 git-gui.sh:1841
-#: git-gui.sh:1842 git-gui.sh:1843
+#: git-gui.sh:1932 git-gui.sh:1933 git-gui.sh:1934 git-gui.sh:1935
+#: git-gui.sh:1936 git-gui.sh:1937
 msgid "Requires merge resolution"
 msgstr "Richiede risoluzione dei conflitti"
 
-#: git-gui.sh:1878
+#: git-gui.sh:1972
 msgid "Starting gitk... please wait..."
 msgstr "Avvio di gitk... attendere..."
 
-#: git-gui.sh:1887
+#: git-gui.sh:1984
 msgid "Couldn't find gitk in PATH"
 msgstr "Impossibile trovare gitk nel PATH"
 
-#: git-gui.sh:2280 lib/choose_repository.tcl:36
+#: git-gui.sh:2043
+msgid "Couldn't find git gui in PATH"
+msgstr "Impossibile trovare git gui nel PATH"
+
+#: git-gui.sh:2455 lib/choose_repository.tcl:36
 msgid "Repository"
 msgstr "Archivio"
 
-#: git-gui.sh:2281
+#: git-gui.sh:2456
 msgid "Edit"
 msgstr "Modifica"
 
-#: git-gui.sh:2283 lib/choose_rev.tcl:561
+#: git-gui.sh:2458 lib/choose_rev.tcl:561
 msgid "Branch"
 msgstr "Ramo"
 
-#: git-gui.sh:2286 lib/choose_rev.tcl:548
+#: git-gui.sh:2461 lib/choose_rev.tcl:548
 msgid "Commit@@noun"
 msgstr "Revisione"
 
-#: git-gui.sh:2289 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
+#: git-gui.sh:2464 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
 msgid "Merge"
 msgstr "Fusione (Merge)"
 
-#: git-gui.sh:2290 lib/choose_rev.tcl:557
+#: git-gui.sh:2465 lib/choose_rev.tcl:557
 msgid "Remote"
 msgstr "Remoto"
 
-#: git-gui.sh:2293
+#: git-gui.sh:2468
 msgid "Tools"
-msgstr "Strumenti"
+msgstr "Accessori"
 
-#: git-gui.sh:2302
+#: git-gui.sh:2477
 msgid "Explore Working Copy"
 msgstr "Esplora copia di lavoro"
 
-#: git-gui.sh:2307
+#: git-gui.sh:2483
 msgid "Browse Current Branch's Files"
 msgstr "Esplora i file del ramo attuale"
 
-#: git-gui.sh:2311
+#: git-gui.sh:2487
 msgid "Browse Branch Files..."
 msgstr "Esplora i file del ramo..."
 
-#: git-gui.sh:2316
+#: git-gui.sh:2492
 msgid "Visualize Current Branch's History"
 msgstr "Visualizza la cronologia del ramo attuale"
 
-#: git-gui.sh:2320
+#: git-gui.sh:2496
 msgid "Visualize All Branch History"
 msgstr "Visualizza la cronologia di tutti i rami"
 
-#: git-gui.sh:2327
+#: git-gui.sh:2503
 #, tcl-format
 msgid "Browse %s's Files"
 msgstr "Esplora i file di %s"
 
-#: git-gui.sh:2329
+#: git-gui.sh:2505
 #, tcl-format
 msgid "Visualize %s's History"
 msgstr "Visualizza la cronologia di %s"
 
-#: git-gui.sh:2334 lib/database.tcl:27 lib/database.tcl:67
+#: git-gui.sh:2510 lib/database.tcl:27 lib/database.tcl:67
 msgid "Database Statistics"
 msgstr "Statistiche dell'archivio"
 
-#: git-gui.sh:2337 lib/database.tcl:34
+#: git-gui.sh:2513 lib/database.tcl:34
 msgid "Compress Database"
 msgstr "Comprimi l'archivio"
 
-#: git-gui.sh:2340
+#: git-gui.sh:2516
 msgid "Verify Database"
 msgstr "Verifica l'archivio"
 
-#: git-gui.sh:2347 git-gui.sh:2351 git-gui.sh:2355 lib/shortcut.tcl:7
-#: lib/shortcut.tcl:39 lib/shortcut.tcl:71
+#: git-gui.sh:2523 git-gui.sh:2527 git-gui.sh:2531 lib/shortcut.tcl:8
+#: lib/shortcut.tcl:40 lib/shortcut.tcl:72
 msgid "Create Desktop Icon"
 msgstr "Crea icona desktop"
 
-#: git-gui.sh:2363 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191
+#: git-gui.sh:2539 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191
 msgid "Quit"
 msgstr "Esci"
 
-#: git-gui.sh:2371
+#: git-gui.sh:2547
 msgid "Undo"
 msgstr "Annulla"
 
-#: git-gui.sh:2374
+#: git-gui.sh:2550
 msgid "Redo"
 msgstr "Ripeti"
 
-#: git-gui.sh:2378 git-gui.sh:2937
+#: git-gui.sh:2554 git-gui.sh:3109
 msgid "Cut"
 msgstr "Taglia"
 
-#: git-gui.sh:2381 git-gui.sh:2940 git-gui.sh:3014 git-gui.sh:3096
+#: git-gui.sh:2557 git-gui.sh:3112 git-gui.sh:3186 git-gui.sh:3259
 #: lib/console.tcl:69
 msgid "Copy"
 msgstr "Copia"
 
-#: git-gui.sh:2384 git-gui.sh:2943
+#: git-gui.sh:2560 git-gui.sh:3115
 msgid "Paste"
 msgstr "Incolla"
 
-#: git-gui.sh:2387 git-gui.sh:2946 lib/branch_delete.tcl:26
+#: git-gui.sh:2563 git-gui.sh:3118 lib/branch_delete.tcl:26
 #: lib/remote_branch_delete.tcl:38
 msgid "Delete"
 msgstr "Elimina"
 
-#: git-gui.sh:2391 git-gui.sh:2950 git-gui.sh:3100 lib/console.tcl:71
+#: git-gui.sh:2567 git-gui.sh:3122 git-gui.sh:3263 lib/console.tcl:71
 msgid "Select All"
 msgstr "Seleziona tutto"
 
-#: git-gui.sh:2400
+#: git-gui.sh:2576
 msgid "Create..."
 msgstr "Crea..."
 
-#: git-gui.sh:2406
+#: git-gui.sh:2582
 msgid "Checkout..."
 msgstr "Attiva..."
 
-#: git-gui.sh:2412
+#: git-gui.sh:2588
 msgid "Rename..."
 msgstr "Rinomina"
 
-#: git-gui.sh:2417
+#: git-gui.sh:2593
 msgid "Delete..."
 msgstr "Elimina..."
 
-#: git-gui.sh:2422
+#: git-gui.sh:2598
 msgid "Reset..."
 msgstr "Ripristina..."
 
-#: git-gui.sh:2432
+#: git-gui.sh:2608
 msgid "Done"
 msgstr "Fatto"
 
-#: git-gui.sh:2434
+#: git-gui.sh:2610
 msgid "Commit@@verb"
 msgstr "Nuova revisione"
 
-#: git-gui.sh:2443 git-gui.sh:2878
+#: git-gui.sh:2619 git-gui.sh:3050
 msgid "New Commit"
 msgstr "Nuova revisione"
 
-#: git-gui.sh:2451 git-gui.sh:2885
+#: git-gui.sh:2627 git-gui.sh:3057
 msgid "Amend Last Commit"
 msgstr "Correggi l'ultima revisione"
 
-#: git-gui.sh:2461 git-gui.sh:2839 lib/remote_branch_delete.tcl:99
+#: git-gui.sh:2637 git-gui.sh:3011 lib/remote_branch_delete.tcl:99
 msgid "Rescan"
 msgstr "Analizza nuovamente"
 
-#: git-gui.sh:2467
+#: git-gui.sh:2643
 msgid "Stage To Commit"
 msgstr "Prepara per una nuova revisione"
 
-#: git-gui.sh:2473
+#: git-gui.sh:2649
 msgid "Stage Changed Files To Commit"
 msgstr "Prepara i file modificati per una nuova revisione"
 
-#: git-gui.sh:2479
+#: git-gui.sh:2655
 msgid "Unstage From Commit"
 msgstr "Annulla preparazione"
 
-#: git-gui.sh:2484 lib/index.tcl:410
+#: git-gui.sh:2661 lib/index.tcl:412
 msgid "Revert Changes"
 msgstr "Annulla modifiche"
 
-#: git-gui.sh:2491 git-gui.sh:3083
+#: git-gui.sh:2669 git-gui.sh:3310 git-gui.sh:3341
 msgid "Show Less Context"
 msgstr "Mostra meno contesto"
 
-#: git-gui.sh:2495 git-gui.sh:3087
+#: git-gui.sh:2673 git-gui.sh:3314 git-gui.sh:3345
 msgid "Show More Context"
 msgstr "Mostra più contesto"
 
-#: git-gui.sh:2502 git-gui.sh:2852 git-gui.sh:2961
+#: git-gui.sh:2680 git-gui.sh:3024 git-gui.sh:3133
 msgid "Sign Off"
 msgstr "Sign Off"
 
-#: git-gui.sh:2518
+#: git-gui.sh:2696
 msgid "Local Merge..."
 msgstr "Fusione locale..."
 
-#: git-gui.sh:2523
+#: git-gui.sh:2701
 msgid "Abort Merge..."
 msgstr "Interrompi fusione..."
 
-#: git-gui.sh:2535 git-gui.sh:2575
+#: git-gui.sh:2713 git-gui.sh:2741
 msgid "Add..."
 msgstr "Aggiungi..."
 
-#: git-gui.sh:2539
+#: git-gui.sh:2717
 msgid "Push..."
 msgstr "Propaga..."
 
-#: git-gui.sh:2543
+#: git-gui.sh:2721
 msgid "Delete Branch..."
 msgstr "Elimina ramo..."
 
-#: git-gui.sh:2553 git-gui.sh:2589 lib/about.tcl:14
-#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53
-#, tcl-format
-msgid "About %s"
-msgstr "Informazioni su %s"
-
-#: git-gui.sh:2557
-msgid "Preferences..."
-msgstr "Preferenze..."
-
-#: git-gui.sh:2565 git-gui.sh:3129
+#: git-gui.sh:2731 git-gui.sh:3292
 msgid "Options..."
 msgstr "Opzioni..."
 
-#: git-gui.sh:2576
+#: git-gui.sh:2742
 msgid "Remove..."
 msgstr "Rimuovi..."
 
-#: git-gui.sh:2585 lib/choose_repository.tcl:50
+#: git-gui.sh:2751 lib/choose_repository.tcl:50
 msgid "Help"
 msgstr "Aiuto"
 
-#: git-gui.sh:2611
+#: git-gui.sh:2755 git-gui.sh:2759 lib/about.tcl:14
+#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53
+#, tcl-format
+msgid "About %s"
+msgstr "Informazioni su %s"
+
+#: git-gui.sh:2783
 msgid "Online Documentation"
 msgstr "Documentazione sul web"
 
-#: git-gui.sh:2614 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
+#: git-gui.sh:2786 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
 msgid "Show SSH Key"
 msgstr "Mostra chave SSH"
 
-#: git-gui.sh:2721
+#: git-gui.sh:2893
 #, tcl-format
 msgid "fatal: cannot stat path %s: No such file or directory"
 msgstr ""
 "errore grave: impossibile effettuare lo stat del path %s: file o directory "
 "non trovata"
 
-#: git-gui.sh:2754
+#: git-gui.sh:2926
 msgid "Current Branch:"
 msgstr "Ramo attuale:"
 
-#: git-gui.sh:2775
+#: git-gui.sh:2947
 msgid "Staged Changes (Will Commit)"
 msgstr "Modifiche preparate (saranno nella nuova revisione)"
 
-#: git-gui.sh:2795
+#: git-gui.sh:2967
 msgid "Unstaged Changes"
 msgstr "Modifiche non preparate"
 
-#: git-gui.sh:2845
+#: git-gui.sh:3017
 msgid "Stage Changed"
 msgstr "Prepara modificati"
 
-#: git-gui.sh:2864 lib/transport.tcl:104 lib/transport.tcl:193
+#: git-gui.sh:3036 lib/transport.tcl:104 lib/transport.tcl:193
 msgid "Push"
 msgstr "Propaga (Push)"
 
-#: git-gui.sh:2899
+#: git-gui.sh:3071
 msgid "Initial Commit Message:"
 msgstr "Messaggio di revisione iniziale:"
 
-#: git-gui.sh:2900
+#: git-gui.sh:3072
 msgid "Amended Commit Message:"
 msgstr "Messaggio di revisione corretto:"
 
-#: git-gui.sh:2901
+#: git-gui.sh:3073
 msgid "Amended Initial Commit Message:"
 msgstr "Messaggio iniziale di revisione corretto:"
 
-#: git-gui.sh:2902
+#: git-gui.sh:3074
 msgid "Amended Merge Commit Message:"
 msgstr "Messaggio di fusione corretto:"
 
-#: git-gui.sh:2903
+#: git-gui.sh:3075
 msgid "Merge Commit Message:"
 msgstr "Messaggio di fusione:"
 
-#: git-gui.sh:2904
+#: git-gui.sh:3076
 msgid "Commit Message:"
 msgstr "Messaggio di revisione:"
 
-#: git-gui.sh:2953 git-gui.sh:3104 lib/console.tcl:73
+#: git-gui.sh:3125 git-gui.sh:3267 lib/console.tcl:73
 msgid "Copy All"
 msgstr "Copia tutto"
 
-#: git-gui.sh:2977 lib/blame.tcl:104
+#: git-gui.sh:3149 lib/blame.tcl:104
 msgid "File:"
 msgstr "File:"
 
-#: git-gui.sh:3092
+#: git-gui.sh:3255
 msgid "Refresh"
 msgstr "Rinfresca"
 
-#: git-gui.sh:3113
+#: git-gui.sh:3276
 msgid "Decrease Font Size"
 msgstr "Diminuisci dimensione caratteri"
 
-#: git-gui.sh:3117
+#: git-gui.sh:3280
 msgid "Increase Font Size"
 msgstr "Aumenta dimensione caratteri"
 
-#: git-gui.sh:3125 lib/blame.tcl:281
+#: git-gui.sh:3288 lib/blame.tcl:281
 msgid "Encoding"
 msgstr "Codifica"
 
-#: git-gui.sh:3136
+#: git-gui.sh:3299
 msgid "Apply/Reverse Hunk"
 msgstr "Applica/Inverti sezione"
 
-#: git-gui.sh:3141
+#: git-gui.sh:3304
 msgid "Apply/Reverse Line"
 msgstr "Applica/Inverti riga"
 
-#: git-gui.sh:3151
+#: git-gui.sh:3323
 msgid "Run Merge Tool"
 msgstr "Avvia programma esterno per la risoluzione dei conflitti"
 
-#: git-gui.sh:3156
+#: git-gui.sh:3328
 msgid "Use Remote Version"
 msgstr "Usa versione remota"
 
-#: git-gui.sh:3160
+#: git-gui.sh:3332
 msgid "Use Local Version"
 msgstr "Usa versione locale"
 
-#: git-gui.sh:3164
+#: git-gui.sh:3336
 msgid "Revert To Base"
 msgstr "Ritorna alla revisione comune"
 
-#: git-gui.sh:3183
+#: git-gui.sh:3354
+msgid "Visualize These Changes In The Submodule"
+msgstr "Visualizza queste modifiche nel sottomodulo"
+
+#: git-gui.sh:3358
+msgid "Visualize Current Branch History In The Submodule"
+msgstr "Visualizza la cronologia del ramo attuale nel sottomodulo"
+
+#: git-gui.sh:3362
+msgid "Visualize All Branch History In The Submodule"
+msgstr "Visualizza la cronologia di tutti i rami nel sottomodulo"
+
+#: git-gui.sh:3367
+msgid "Start git gui In The Submodule"
+msgstr "Avvia git gui nel sottomodulo"
+
+#: git-gui.sh:3389
 msgid "Unstage Hunk From Commit"
 msgstr "Annulla preparazione della sezione per una nuova revisione"
 
-#: git-gui.sh:3184
+#: git-gui.sh:3391
+msgid "Unstage Lines From Commit"
+msgstr "Annulla preparazione delle linee per una nuova revisione"
+
+#: git-gui.sh:3393
 msgid "Unstage Line From Commit"
 msgstr "Annulla preparazione della linea per una nuova revisione"
 
-#: git-gui.sh:3186
+#: git-gui.sh:3396
 msgid "Stage Hunk For Commit"
 msgstr "Prepara sezione per una nuova revisione"
 
-#: git-gui.sh:3187
+#: git-gui.sh:3398
+msgid "Stage Lines For Commit"
+msgstr "Prepara linee per una nuova revisione"
+
+#: git-gui.sh:3400
 msgid "Stage Line For Commit"
 msgstr "Prepara linea per una nuova revisione"
 
-#: git-gui.sh:3210
+#: git-gui.sh:3424
 msgid "Initializing..."
 msgstr "Inizializzazione..."
 
-#: git-gui.sh:3315
+#: git-gui.sh:3541
 #, tcl-format
 msgid ""
 "Possible environment issues exist.\n"
@@ -518,7 +547,7 @@ msgstr ""
 "da %s:\n"
 "\n"
 
-#: git-gui.sh:3345
+#: git-gui.sh:3570
 msgid ""
 "\n"
 "This is due to a known issue with the\n"
@@ -528,7 +557,7 @@ msgstr ""
 "Ciò è dovuto a un problema conosciuto\n"
 "causato dall'eseguibile Tcl distribuito da Cygwin."
 
-#: git-gui.sh:3350
+#: git-gui.sh:3575
 #, tcl-format
 msgid ""
 "\n"
@@ -637,7 +666,7 @@ msgstr "Impossibile trovare la revisione precedente:"
 msgid "Unable to display parent"
 msgstr "Impossibile visualizzare la revisione precedente"
 
-#: lib/blame.tcl:1091 lib/diff.tcl:297
+#: lib/blame.tcl:1091 lib/diff.tcl:320
 msgid "Error loading diff:"
 msgstr "Errore nel caricamento delle differenze:"
 
@@ -663,7 +692,7 @@ msgstr "Attiva"
 
 #: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
 #: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282
-#: lib/checkout_op.tcl:544 lib/choose_font.tcl:43 lib/merge.tcl:172
+#: lib/checkout_op.tcl:579 lib/choose_font.tcl:43 lib/merge.tcl:172
 #: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42
 #: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352
 #: lib/transport.tcl:108
@@ -694,7 +723,7 @@ msgstr "Crea ramo"
 msgid "Create New Branch"
 msgstr "Crea nuovo ramo"
 
-#: lib/branch_create.tcl:31 lib/choose_repository.tcl:377
+#: lib/branch_create.tcl:31 lib/choose_repository.tcl:381
 msgid "Create"
 msgstr "Crea"
 
@@ -726,7 +755,7 @@ msgstr "No"
 msgid "Fast Forward Only"
 msgstr "Solo fast forward"
 
-#: lib/branch_create.tcl:85 lib/checkout_op.tcl:536
+#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571
 msgid "Reset"
 msgstr "Ripristina"
 
@@ -769,15 +798,25 @@ msgstr "Rami locali"
 msgid "Delete Only If Merged Into"
 msgstr "Cancella solo se fuso con un altro ramo"
 
-#: lib/branch_delete.tcl:54
-msgid "Always (Do not perform merge test.)"
-msgstr "Sempre (Non effettuare verifiche di fusione)."
+#: lib/branch_delete.tcl:54 lib/remote_branch_delete.tcl:119
+msgid "Always (Do not perform merge checks)"
+msgstr "Sempre (non verificare le fusioni)"
 
 #: lib/branch_delete.tcl:103
 #, tcl-format
 msgid "The following branches are not completely merged into %s:"
 msgstr "I rami seguenti non sono stati fusi completamente in %s:"
 
+#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:217
+msgid ""
+"Recovering deleted branches is difficult.\n"
+"\n"
+"Delete the selected branches?"
+msgstr ""
+"Ripristinare rami cancellati è difficile.\n"
+"\n"
+"Cancellare i rami selezionati?"
+
 #: lib/branch_delete.tcl:141
 #, tcl-format
 msgid ""
@@ -807,7 +846,7 @@ msgstr "Nuovo Nome:"
 msgid "Please select a branch to rename."
 msgstr "Scegliere un ramo da rinominare."
 
-#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:201
+#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202
 #, tcl-format
 msgid "Branch '%s' already exists."
 msgstr "Il ramo '%s' esiste già."
@@ -838,38 +877,38 @@ msgstr "[Directory superiore]"
 msgid "Browse Branch Files"
 msgstr "Esplora i file del ramo"
 
-#: lib/browser.tcl:278 lib/choose_repository.tcl:394
-#: lib/choose_repository.tcl:480 lib/choose_repository.tcl:491
-#: lib/choose_repository.tcl:995
+#: lib/browser.tcl:278 lib/choose_repository.tcl:398
+#: lib/choose_repository.tcl:486 lib/choose_repository.tcl:497
+#: lib/choose_repository.tcl:1028
 msgid "Browse"
 msgstr "Esplora"
 
-#: lib/checkout_op.tcl:84
+#: lib/checkout_op.tcl:85
 #, tcl-format
 msgid "Fetching %s from %s"
 msgstr "Recupero %s da %s"
 
-#: lib/checkout_op.tcl:132
+#: lib/checkout_op.tcl:133
 #, tcl-format
 msgid "fatal: Cannot resolve %s"
 msgstr "errore grave: impossibile risolvere %s"
 
-#: lib/checkout_op.tcl:145 lib/console.tcl:81 lib/database.tcl:31
+#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:31
 #: lib/sshkey.tcl:53
 msgid "Close"
 msgstr "Chiudi"
 
-#: lib/checkout_op.tcl:174
+#: lib/checkout_op.tcl:175
 #, tcl-format
 msgid "Branch '%s' does not exist."
 msgstr "Il ramo '%s' non esiste."
 
-#: lib/checkout_op.tcl:193
+#: lib/checkout_op.tcl:194
 #, tcl-format
 msgid "Failed to configure simplified git-pull for '%s'."
 msgstr "Impossibile configurare git-pull semplificato per '%s'."
 
-#: lib/checkout_op.tcl:228
+#: lib/checkout_op.tcl:229
 #, tcl-format
 msgid ""
 "Branch '%s' already exists.\n"
@@ -882,22 +921,22 @@ msgstr ""
 "Non può effettuare un 'fast-forward' a %s.\n"
 "E' necessaria una fusione."
 
-#: lib/checkout_op.tcl:242
+#: lib/checkout_op.tcl:243
 #, tcl-format
 msgid "Merge strategy '%s' not supported."
 msgstr "La strategia di fusione '%s' non è supportata."
 
-#: lib/checkout_op.tcl:261
+#: lib/checkout_op.tcl:262
 #, tcl-format
 msgid "Failed to update '%s'."
 msgstr "Impossibile aggiornare '%s'."
 
-#: lib/checkout_op.tcl:273
+#: lib/checkout_op.tcl:274
 msgid "Staging area (index) is already locked."
 msgstr ""
 "L'area di preparazione per una nuova revisione (indice) è già bloccata."
 
-#: lib/checkout_op.tcl:288
+#: lib/checkout_op.tcl:289
 msgid ""
 "Last scanned state does not match repository state.\n"
 "\n"
@@ -914,30 +953,30 @@ msgstr ""
 "\n"
 "La nuova analisi comincerà ora.\n"
 
-#: lib/checkout_op.tcl:344
+#: lib/checkout_op.tcl:345
 #, tcl-format
 msgid "Updating working directory to '%s'..."
 msgstr "Aggiornamento della directory di lavoro a '%s' in corso..."
 
-#: lib/checkout_op.tcl:345
+#: lib/checkout_op.tcl:346
 msgid "files checked out"
 msgstr "file presenti nella directory di lavoro"
 
-#: lib/checkout_op.tcl:375
+#: lib/checkout_op.tcl:376
 #, tcl-format
 msgid "Aborted checkout of '%s' (file level merging is required)."
 msgstr "Attivazione di '%s' fallita (richiesta una fusione a livello file)."
 
-#: lib/checkout_op.tcl:376
+#: lib/checkout_op.tcl:377
 msgid "File level merge required."
 msgstr "E' richiesta una fusione a livello file."
 
-#: lib/checkout_op.tcl:380
+#: lib/checkout_op.tcl:381
 #, tcl-format
 msgid "Staying on branch '%s'."
 msgstr "Si rimarrà sul ramo '%s'."
 
-#: lib/checkout_op.tcl:451
+#: lib/checkout_op.tcl:452
 msgid ""
 "You are no longer on a local branch.\n"
 "\n"
@@ -949,31 +988,31 @@ msgstr ""
 "Se si vuole rimanere su un ramo, crearne uno ora a partire da 'Questa "
 "revisione attiva staccata'."
 
-#: lib/checkout_op.tcl:468 lib/checkout_op.tcl:472
+#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507
 #, tcl-format
 msgid "Checked out '%s'."
 msgstr "Attivazione di '%s' completata."
 
-#: lib/checkout_op.tcl:500
+#: lib/checkout_op.tcl:535
 #, tcl-format
 msgid "Resetting '%s' to '%s' will lose the following commits:"
 msgstr ""
 "Ripristinare '%s' a '%s' comporterà la perdita delle seguenti revisioni:"
 
-#: lib/checkout_op.tcl:522
+#: lib/checkout_op.tcl:557
 msgid "Recovering lost commits may not be easy."
 msgstr "Ricomporre le revisioni perdute potrebbe non essere semplice."
 
-#: lib/checkout_op.tcl:527
+#: lib/checkout_op.tcl:562
 #, tcl-format
 msgid "Reset '%s'?"
 msgstr "Ripristinare '%s'?"
 
-#: lib/checkout_op.tcl:532 lib/merge.tcl:164 lib/tools_dlg.tcl:343
+#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:343
 msgid "Visualize"
 msgstr "Visualizza"
 
-#: lib/checkout_op.tcl:600
+#: lib/checkout_op.tcl:635
 #, tcl-format
 msgid ""
 "Failed to set current branch.\n"
@@ -1019,7 +1058,7 @@ msgstr ""
 msgid "Git Gui"
 msgstr "Git Gui"
 
-#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:382
+#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:386
 msgid "Create New Repository"
 msgstr "Crea nuovo archivio"
 
@@ -1027,7 +1066,7 @@ msgstr "Crea nuovo archivio"
 msgid "New..."
 msgstr "Nuovo..."
 
-#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:465
+#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:471
 msgid "Clone Existing Repository"
 msgstr "Clona archivio esistente"
 
@@ -1035,7 +1074,7 @@ msgstr "Clona archivio esistente"
 msgid "Clone..."
 msgstr "Clona..."
 
-#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:983
+#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:1016
 msgid "Open Existing Repository"
 msgstr "Apri archivio esistente"
 
@@ -1051,198 +1090,198 @@ msgstr "Archivi recenti"
 msgid "Open Recent Repository:"
 msgstr "Apri archivio recente:"
 
-#: lib/choose_repository.tcl:302 lib/choose_repository.tcl:309
-#: lib/choose_repository.tcl:316
+#: lib/choose_repository.tcl:306 lib/choose_repository.tcl:313
+#: lib/choose_repository.tcl:320
 #, tcl-format
 msgid "Failed to create repository %s:"
 msgstr "Impossibile creare l'archivio %s:"
 
-#: lib/choose_repository.tcl:387
+#: lib/choose_repository.tcl:391
 msgid "Directory:"
 msgstr "Directory:"
 
-#: lib/choose_repository.tcl:417 lib/choose_repository.tcl:544
-#: lib/choose_repository.tcl:1017
+#: lib/choose_repository.tcl:423 lib/choose_repository.tcl:550
+#: lib/choose_repository.tcl:1052
 msgid "Git Repository"
 msgstr "Archivio Git"
 
-#: lib/choose_repository.tcl:442
+#: lib/choose_repository.tcl:448
 #, tcl-format
 msgid "Directory %s already exists."
 msgstr "La directory %s esiste già."
 
-#: lib/choose_repository.tcl:446
+#: lib/choose_repository.tcl:452
 #, tcl-format
 msgid "File %s already exists."
 msgstr "Il file %s esiste già."
 
-#: lib/choose_repository.tcl:460
+#: lib/choose_repository.tcl:466
 msgid "Clone"
 msgstr "Clona"
 
-#: lib/choose_repository.tcl:473
+#: lib/choose_repository.tcl:479
 msgid "Source Location:"
 msgstr "Posizione sorgente:"
 
-#: lib/choose_repository.tcl:484
+#: lib/choose_repository.tcl:490
 msgid "Target Directory:"
 msgstr "Directory di destinazione:"
 
-#: lib/choose_repository.tcl:496
+#: lib/choose_repository.tcl:502
 msgid "Clone Type:"
 msgstr "Tipo di clone:"
 
-#: lib/choose_repository.tcl:502
+#: lib/choose_repository.tcl:508
 msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
 msgstr "Standard (veloce, semi-ridondante, con hardlink)"
 
-#: lib/choose_repository.tcl:508
+#: lib/choose_repository.tcl:514
 msgid "Full Copy (Slower, Redundant Backup)"
 msgstr "Copia completa (più lento, backup ridondante)"
 
-#: lib/choose_repository.tcl:514
+#: lib/choose_repository.tcl:520
 msgid "Shared (Fastest, Not Recommended, No Backup)"
 msgstr "Shared (il più veloce, non raccomandato, nessun backup)"
 
-#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597
-#: lib/choose_repository.tcl:743 lib/choose_repository.tcl:813
-#: lib/choose_repository.tcl:1023 lib/choose_repository.tcl:1031
+#: lib/choose_repository.tcl:556 lib/choose_repository.tcl:603
+#: lib/choose_repository.tcl:749 lib/choose_repository.tcl:819
+#: lib/choose_repository.tcl:1058 lib/choose_repository.tcl:1066
 #, tcl-format
 msgid "Not a Git repository: %s"
 msgstr "%s non è un archivio Git."
 
-#: lib/choose_repository.tcl:586
+#: lib/choose_repository.tcl:592
 msgid "Standard only available for local repository."
 msgstr "Standard è disponibile solo per archivi locali."
 
-#: lib/choose_repository.tcl:590
+#: lib/choose_repository.tcl:596
 msgid "Shared only available for local repository."
 msgstr "Shared è disponibile solo per archivi locali."
 
-#: lib/choose_repository.tcl:611
+#: lib/choose_repository.tcl:617
 #, tcl-format
 msgid "Location %s already exists."
 msgstr "Il file/directory %s esiste già."
 
-#: lib/choose_repository.tcl:622
+#: lib/choose_repository.tcl:628
 msgid "Failed to configure origin"
 msgstr "Impossibile configurare origin"
 
-#: lib/choose_repository.tcl:634
+#: lib/choose_repository.tcl:640
 msgid "Counting objects"
 msgstr "Calcolo oggetti"
 
-#: lib/choose_repository.tcl:635
+#: lib/choose_repository.tcl:641
 msgid "buckets"
 msgstr ""
 
-#: lib/choose_repository.tcl:659
+#: lib/choose_repository.tcl:665
 #, tcl-format
 msgid "Unable to copy objects/info/alternates: %s"
 msgstr "Impossibile copiare oggetti/info/alternate: %s"
 
-#: lib/choose_repository.tcl:695
+#: lib/choose_repository.tcl:701
 #, tcl-format
 msgid "Nothing to clone from %s."
 msgstr "Niente da clonare da %s."
 
-#: lib/choose_repository.tcl:697 lib/choose_repository.tcl:911
-#: lib/choose_repository.tcl:923
+#: lib/choose_repository.tcl:703 lib/choose_repository.tcl:917
+#: lib/choose_repository.tcl:929
 msgid "The 'master' branch has not been initialized."
 msgstr "Il ramo 'master' non è stato inizializzato."
 
-#: lib/choose_repository.tcl:710
+#: lib/choose_repository.tcl:716
 msgid "Hardlinks are unavailable.  Falling back to copying."
 msgstr "Impossibile utilizzare gli hardlink. Si ricorrerà alla copia."
 
-#: lib/choose_repository.tcl:722
+#: lib/choose_repository.tcl:728
 #, tcl-format
 msgid "Cloning from %s"
 msgstr "Clonazione da %s"
 
-#: lib/choose_repository.tcl:753
+#: lib/choose_repository.tcl:759
 msgid "Copying objects"
 msgstr "Copia degli oggetti"
 
-#: lib/choose_repository.tcl:754
+#: lib/choose_repository.tcl:760
 msgid "KiB"
 msgstr "KiB"
 
-#: lib/choose_repository.tcl:778
+#: lib/choose_repository.tcl:784
 #, tcl-format
 msgid "Unable to copy object: %s"
 msgstr "Impossibile copiare oggetto: %s"
 
-#: lib/choose_repository.tcl:788
+#: lib/choose_repository.tcl:794
 msgid "Linking objects"
 msgstr "Collegamento oggetti"
 
-#: lib/choose_repository.tcl:789
+#: lib/choose_repository.tcl:795
 msgid "objects"
 msgstr "oggetti"
 
-#: lib/choose_repository.tcl:797
+#: lib/choose_repository.tcl:803
 #, tcl-format
 msgid "Unable to hardlink object: %s"
 msgstr "Hardlink impossibile sull'oggetto: %s"
 
-#: lib/choose_repository.tcl:852
+#: lib/choose_repository.tcl:858
 msgid "Cannot fetch branches and objects.  See console output for details."
 msgstr ""
 "Impossibile recuperare rami e oggetti. Controllare i dettagli forniti dalla "
 "console."
 
-#: lib/choose_repository.tcl:863
+#: lib/choose_repository.tcl:869
 msgid "Cannot fetch tags.  See console output for details."
 msgstr ""
 "Impossibile recuperare le etichette. Controllare i dettagli forniti dalla "
 "console."
 
-#: lib/choose_repository.tcl:887
+#: lib/choose_repository.tcl:893
 msgid "Cannot determine HEAD.  See console output for details."
 msgstr ""
 "Impossibile determinare HEAD. Controllare i dettagli forniti dalla console."
 
-#: lib/choose_repository.tcl:896
+#: lib/choose_repository.tcl:902
 #, tcl-format
 msgid "Unable to cleanup %s"
 msgstr "Impossibile ripulire %s"
 
-#: lib/choose_repository.tcl:902
+#: lib/choose_repository.tcl:908
 msgid "Clone failed."
 msgstr "Clonazione non riuscita."
 
-#: lib/choose_repository.tcl:909
+#: lib/choose_repository.tcl:915
 msgid "No default branch obtained."
 msgstr "Non è stato trovato un ramo predefinito."
 
-#: lib/choose_repository.tcl:920
+#: lib/choose_repository.tcl:926
 #, tcl-format
 msgid "Cannot resolve %s as a commit."
 msgstr "Impossibile risolvere %s come una revisione."
 
-#: lib/choose_repository.tcl:932
+#: lib/choose_repository.tcl:938
 msgid "Creating working directory"
 msgstr "Creazione directory di lavoro"
 
-#: lib/choose_repository.tcl:933 lib/index.tcl:65 lib/index.tcl:128
-#: lib/index.tcl:196
+#: lib/choose_repository.tcl:939 lib/index.tcl:67 lib/index.tcl:130
+#: lib/index.tcl:198
 msgid "files"
 msgstr "file"
 
-#: lib/choose_repository.tcl:962
+#: lib/choose_repository.tcl:968
 msgid "Initial file checkout failed."
 msgstr "Attivazione iniziale non riuscita."
 
-#: lib/choose_repository.tcl:978
+#: lib/choose_repository.tcl:1011
 msgid "Open"
 msgstr "Apri"
 
-#: lib/choose_repository.tcl:988
+#: lib/choose_repository.tcl:1021
 msgid "Repository:"
 msgstr "Archivio:"
 
-#: lib/choose_repository.tcl:1037
+#: lib/choose_repository.tcl:1072
 #, tcl-format
 msgid "Failed to open repository %s:"
 msgstr "Impossibile accedere all'archivio %s:"
@@ -1326,7 +1365,12 @@ msgstr "Impossibile ottenere la tua identità:"
 msgid "Invalid GIT_COMMITTER_IDENT:"
 msgstr "GIT_COMMITTER_IDENT non valida:"
 
-#: lib/commit.tcl:132
+#: lib/commit.tcl:129
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr "attenzione: Tcl non supporta la codifica '%s'."
+
+#: lib/commit.tcl:149
 msgid ""
 "Last scanned state does not match repository state.\n"
 "\n"
@@ -1343,7 +1387,7 @@ msgstr ""
 "\n"
 "La nuova analisi comincerà ora.\n"
 
-#: lib/commit.tcl:155
+#: lib/commit.tcl:172
 #, tcl-format
 msgid ""
 "Unmerged files cannot be committed.\n"
@@ -1356,7 +1400,7 @@ msgstr ""
 "Il file %s presenta dei conflitti. Devi risolverli e preparare il file per "
 "creare una nuova revisione prima di effettuare questa azione.\n"
 
-#: lib/commit.tcl:163
+#: lib/commit.tcl:180
 #, tcl-format
 msgid ""
 "Unknown file state %s detected.\n"
@@ -1367,7 +1411,7 @@ msgstr ""
 "\n"
 "Questo programma non può creare una revisione contenente il file %s.\n"
 
-#: lib/commit.tcl:171
+#: lib/commit.tcl:188
 msgid ""
 "No changes to commit.\n"
 "\n"
@@ -1378,7 +1422,7 @@ msgstr ""
 "Devi preparare per una nuova revisione almeno 1 file prima di effettuare "
 "questa operazione.\n"
 
-#: lib/commit.tcl:186
+#: lib/commit.tcl:203
 msgid ""
 "Please supply a commit message.\n"
 "\n"
@@ -1396,45 +1440,40 @@ msgstr ""
 "- Seconda linea: vuota.\n"
 "- Terza linea: spiega a cosa serve la tua modifica.\n"
 
-#: lib/commit.tcl:210
-#, tcl-format
-msgid "warning: Tcl does not support encoding '%s'."
-msgstr "attenzione: Tcl non supporta la codifica '%s'."
-
-#: lib/commit.tcl:226
+#: lib/commit.tcl:234
 msgid "Calling pre-commit hook..."
 msgstr "Avvio pre-commit hook..."
 
-#: lib/commit.tcl:241
+#: lib/commit.tcl:249
 msgid "Commit declined by pre-commit hook."
 msgstr "Revisione rifiutata dal pre-commit hook."
 
-#: lib/commit.tcl:264
+#: lib/commit.tcl:272
 msgid "Calling commit-msg hook..."
 msgstr "Avvio commit-msg hook..."
 
-#: lib/commit.tcl:279
+#: lib/commit.tcl:287
 msgid "Commit declined by commit-msg hook."
 msgstr "Revisione rifiutata dal commit-msg hook."
 
-#: lib/commit.tcl:292
+#: lib/commit.tcl:300
 msgid "Committing changes..."
 msgstr "Archiviazione modifiche..."
 
-#: lib/commit.tcl:308
+#: lib/commit.tcl:316
 msgid "write-tree failed:"
 msgstr "write-tree non riuscito:"
 
-#: lib/commit.tcl:309 lib/commit.tcl:353 lib/commit.tcl:373
+#: lib/commit.tcl:317 lib/commit.tcl:361 lib/commit.tcl:382
 msgid "Commit failed."
 msgstr "Impossibile creare una nuova revisione."
 
-#: lib/commit.tcl:326
+#: lib/commit.tcl:334
 #, tcl-format
 msgid "Commit %s appears to be corrupt"
 msgstr "La revisione %s sembra essere danneggiata"
 
-#: lib/commit.tcl:331
+#: lib/commit.tcl:339
 msgid ""
 "No changes to commit.\n"
 "\n"
@@ -1448,19 +1487,19 @@ msgstr ""
 "\n"
 "Si procederà subito ad una nuova analisi.\n"
 
-#: lib/commit.tcl:338
+#: lib/commit.tcl:346
 msgid "No changes to commit."
 msgstr "Nessuna modifica per la nuova revisione."
 
-#: lib/commit.tcl:352
+#: lib/commit.tcl:360
 msgid "commit-tree failed:"
 msgstr "commit-tree non riuscito:"
 
-#: lib/commit.tcl:372
+#: lib/commit.tcl:381
 msgid "update-ref failed:"
 msgstr "update-ref non riuscito:"
 
-#: lib/commit.tcl:460
+#: lib/commit.tcl:469
 #, tcl-format
 msgid "Created commit %s: %s"
 msgstr "Creata revisione %s: %s"
@@ -1513,20 +1552,19 @@ msgstr "Compressione dell'archivio in corso"
 msgid "Verifying the object database with fsck-objects"
 msgstr "Verifica dell'archivio con fsck-objects in corso"
 
-#: lib/database.tcl:108
+#: lib/database.tcl:107
 #, tcl-format
 msgid ""
 "This repository currently has approximately %i loose objects.\n"
 "\n"
 "To maintain optimal performance it is strongly recommended that you compress "
-"the database when more than %i loose objects exist.\n"
+"the database.\n"
 "\n"
 "Compress the database now?"
 msgstr ""
 "Questo archivio attualmente ha circa %i oggetti slegati.\n"
 "\n"
-"Per mantenere buone prestazioni si raccomanda di comprimere l'archivio "
-"quando sono presenti più di %i oggetti slegati.\n"
+"Per mantenere buone prestazioni si raccomanda di comprimere l'archivio.\n"
 "\n"
 "Comprimere l'archivio ora?"
 
@@ -1535,7 +1573,7 @@ msgstr ""
 msgid "Invalid date from Git: %s"
 msgstr "Git ha restituito una data non valida: %s"
 
-#: lib/diff.tcl:59
+#: lib/diff.tcl:64
 #, tcl-format
 msgid ""
 "No differences detected.\n"
@@ -1558,12 +1596,12 @@ msgstr ""
 "Si procederà automaticamente ad una nuova analisi per trovare altri file che "
 "potrebbero avere lo stesso stato."
 
-#: lib/diff.tcl:99
+#: lib/diff.tcl:104
 #, tcl-format
 msgid "Loading diff of %s..."
 msgstr "Caricamento delle differenze di %s..."
 
-#: lib/diff.tcl:120
+#: lib/diff.tcl:125
 msgid ""
 "LOCAL: deleted\n"
 "REMOTE:\n"
@@ -1571,7 +1609,7 @@ msgstr ""
 "LOCALE: cancellato\n"
 "REMOTO:\n"
 
-#: lib/diff.tcl:125
+#: lib/diff.tcl:130
 msgid ""
 "REMOTE: deleted\n"
 "LOCAL:\n"
@@ -1579,32 +1617,32 @@ msgstr ""
 "REMOTO: cancellato\n"
 "LOCALE:\n"
 
-#: lib/diff.tcl:132
+#: lib/diff.tcl:137
 msgid "LOCAL:\n"
 msgstr "LOCALE:\n"
 
-#: lib/diff.tcl:135
+#: lib/diff.tcl:140
 msgid "REMOTE:\n"
 msgstr "REMOTO:\n"
 
-#: lib/diff.tcl:197 lib/diff.tcl:296
+#: lib/diff.tcl:202 lib/diff.tcl:319
 #, tcl-format
 msgid "Unable to display %s"
 msgstr "Impossibile visualizzare %s"
 
-#: lib/diff.tcl:198
+#: lib/diff.tcl:203
 msgid "Error loading file:"
 msgstr "Errore nel caricamento del file:"
 
-#: lib/diff.tcl:205
+#: lib/diff.tcl:210
 msgid "Git Repository (subproject)"
 msgstr "Archivio Git (sottoprogetto)"
 
-#: lib/diff.tcl:217
+#: lib/diff.tcl:222
 msgid "* Binary file (not showing content)."
 msgstr "* File binario (il contenuto non sarà mostrato)."
 
-#: lib/diff.tcl:222
+#: lib/diff.tcl:227
 #, tcl-format
 msgid ""
 "* Untracked file is %d bytes.\n"
@@ -1613,7 +1651,7 @@ msgstr ""
 "* Il file non tracciato è di %d byte.\n"
 "* Saranno visualizzati solo i primi %d byte.\n"
 
-#: lib/diff.tcl:228
+#: lib/diff.tcl:233
 #, tcl-format
 msgid ""
 "\n"
@@ -1624,19 +1662,19 @@ msgstr ""
 "* %s non visualizza completamente questo file non tracciato.\n"
 "* Per visualizzare il file completo, usare un programma esterno.\n"
 
-#: lib/diff.tcl:436
+#: lib/diff.tcl:482
 msgid "Failed to unstage selected hunk."
 msgstr "Impossibile rimuovere la sezione scelta dalla nuova revisione."
 
-#: lib/diff.tcl:443
+#: lib/diff.tcl:489
 msgid "Failed to stage selected hunk."
 msgstr "Impossibile preparare la sezione scelta per una nuova revisione."
 
-#: lib/diff.tcl:509
+#: lib/diff.tcl:568
 msgid "Failed to unstage selected line."
 msgstr "Impossibile rimuovere la riga scelta dalla nuova revisione."
 
-#: lib/diff.tcl:517
+#: lib/diff.tcl:576
 msgid "Failed to stage selected line."
 msgstr "Impossibile preparare la riga scelta per una nuova revisione."
 
@@ -1674,7 +1712,7 @@ msgstr "Impossibile sbloccare l'accesso all'indice"
 msgid "Index Error"
 msgstr "Errore nell'indice"
 
-#: lib/index.tcl:21
+#: lib/index.tcl:17
 msgid ""
 "Updating the Git index failed.  A rescan will be automatically started to "
 "resynchronize git-gui."
@@ -1682,7 +1720,7 @@ msgstr ""
 "Impossibile aggiornare l'indice. Ora sarà avviata una nuova analisi che "
 "aggiornerà git-gui."
 
-#: lib/index.tcl:27
+#: lib/index.tcl:28
 msgid "Continue"
 msgstr "Continua"
 
@@ -1690,45 +1728,45 @@ msgstr "Continua"
 msgid "Unlock Index"
 msgstr "Sblocca l'accesso all'indice"
 
-#: lib/index.tcl:287
+#: lib/index.tcl:289
 #, tcl-format
 msgid "Unstaging %s from commit"
 msgstr "%s non farà parte della prossima revisione"
 
-#: lib/index.tcl:326
+#: lib/index.tcl:328
 msgid "Ready to commit."
 msgstr "Pronto per creare una nuova revisione."
 
-#: lib/index.tcl:339
+#: lib/index.tcl:341
 #, tcl-format
 msgid "Adding %s"
 msgstr "Aggiunta di %s in corso"
 
-#: lib/index.tcl:396
+#: lib/index.tcl:398
 #, tcl-format
 msgid "Revert changes in file %s?"
 msgstr "Annullare le modifiche nel file %s?"
 
-#: lib/index.tcl:398
+#: lib/index.tcl:400
 #, tcl-format
 msgid "Revert changes in these %i files?"
 msgstr "Annullare le modifiche in questi %i file?"
 
-#: lib/index.tcl:406
+#: lib/index.tcl:408
 msgid "Any unstaged changes will be permanently lost by the revert."
 msgstr ""
 "Tutte le modifiche non preparate per una nuova revisione saranno perse per "
 "sempre."
 
-#: lib/index.tcl:409
+#: lib/index.tcl:411
 msgid "Do Nothing"
 msgstr "Non fare niente"
 
-#: lib/index.tcl:427
+#: lib/index.tcl:429
 msgid "Reverting selected files"
 msgstr "Annullo le modifiche nei file selezionati"
 
-#: lib/index.tcl:431
+#: lib/index.tcl:433
 #, tcl-format
 msgid "Reverting %s"
 msgstr "Annullo le modifiche in %s"
@@ -2215,10 +2253,6 @@ msgstr "Elimina solo se"
 msgid "Merged Into:"
 msgstr "Fuso in:"
 
-#: lib/remote_branch_delete.tcl:119
-msgid "Always (Do not perform merge checks)"
-msgstr "Sempre (non verificare le fusioni)"
-
 #: lib/remote_branch_delete.tcl:152
 msgid "A branch is required for 'Merged Into'."
 msgstr "Si richiede un ramo per 'Fuso in'."
@@ -2247,26 +2281,16 @@ msgstr ""
 msgid "Please select one or more branches to delete."
 msgstr "Scegliere uno o più rami da cancellare."
 
-#: lib/remote_branch_delete.tcl:216
-msgid ""
-"Recovering deleted branches is difficult.\n"
-"\n"
-"Delete the selected branches?"
-msgstr ""
-"Ricomporre rami cancellati è difficile.\n"
-"\n"
-"Cancellare i rami selezionati?"
-
 #: lib/remote_branch_delete.tcl:226
 #, tcl-format
 msgid "Deleting branches from %s"
 msgstr "Cancellazione rami da %s"
 
-#: lib/remote_branch_delete.tcl:286
+#: lib/remote_branch_delete.tcl:292
 msgid "No repository selected."
 msgstr "Nessun archivio selezionato."
 
-#: lib/remote_branch_delete.tcl:291
+#: lib/remote_branch_delete.tcl:297
 #, tcl-format
 msgid "Scanning %s..."
 msgstr "Analisi in corso %s..."
@@ -2287,11 +2311,11 @@ msgstr "Prec"
 msgid "Case-Sensitive"
 msgstr "Distingui maiuscole"
 
-#: lib/shortcut.tcl:20 lib/shortcut.tcl:61
+#: lib/shortcut.tcl:21 lib/shortcut.tcl:62
 msgid "Cannot write shortcut:"
 msgstr "Impossibile scrivere shortcut:"
 
-#: lib/shortcut.tcl:136
+#: lib/shortcut.tcl:137
 msgid "Cannot write icon:"
 msgstr "Impossibile scrivere icona:"
 
@@ -2399,7 +2423,7 @@ msgstr "Vuoi davvero eseguire %s?"
 #: lib/tools.tcl:110
 #, tcl-format
 msgid "Tool: %s"
-msgstr "Strumento: %s"
+msgstr "Accessorio: %s"
 
 #: lib/tools.tcl:111
 #, tcl-format
@@ -2418,7 +2442,7 @@ msgstr "Il programma esterno ha riportato un errore: %s"
 
 #: lib/tools_dlg.tcl:22
 msgid "Add Tool"
-msgstr "Aggiungi strumento"
+msgstr "Aggiungi accessorio"
 
 #: lib/tools_dlg.tcl:28
 msgid "Add New Tool Command"
@@ -2430,7 +2454,7 @@ msgstr "Aggiungi per tutti gli archivi"
 
 #: lib/tools_dlg.tcl:45
 msgid "Tool Details"
-msgstr "Dettagli sullo strumento"
+msgstr "Dettagli sull'accessorio"
 
 #: lib/tools_dlg.tcl:48
 msgid "Use '/' separators to create a submenu tree:"
@@ -2462,12 +2486,12 @@ msgstr "Avvia solo se è selezionata una differenza ($FILENAME non è vuoto)"
 
 #: lib/tools_dlg.tcl:121
 msgid "Please supply a name for the tool."
-msgstr "Bisogna dare un nome allo strumento."
+msgstr "Bisogna dare un nome all'accessorio."
 
 #: lib/tools_dlg.tcl:129
 #, tcl-format
 msgid "Tool '%s' already exists."
-msgstr "Lo strumento '%s' esiste già."
+msgstr "L'accessorio '%s' esiste già."
 
 #: lib/tools_dlg.tcl:151
 #, tcl-format
@@ -2475,17 +2499,17 @@ msgid ""
 "Could not add tool:\n"
 "%s"
 msgstr ""
-"Impossibile aggiungere lo strumento:\n"
+"Impossibile aggiungere l'accessorio:\n"
 "\n"
 "%s"
 
 #: lib/tools_dlg.tcl:190
 msgid "Remove Tool"
-msgstr "Rimuovi strumento"
+msgstr "Rimuovi accessorio"
 
 #: lib/tools_dlg.tcl:196
 msgid "Remove Tool Commands"
-msgstr "Rimuovi i comandi dello strumento"
+msgstr "Rimuovi i comandi accessori"
 
 #: lib/tools_dlg.tcl:200
 msgid "Remove"
@@ -2493,7 +2517,7 @@ msgstr "Rimuovi"
 
 #: lib/tools_dlg.tcl:236
 msgid "(Blue denotes repository-local tools)"
-msgstr "(Il colore blu indica strumenti per l'archivio locale)"
+msgstr "(Il colore blu indica accessori per l'archivio locale)"
 
 #: lib/tools_dlg.tcl:297
 #, tcl-format
index 63c4695103a764acbf116df1946fd0bc311c47f0..15017985e5f3a731cd9b16f1d71442b22e67f771 100644 (file)
@@ -3,46 +3,45 @@
 # This file is distributed under the same license as the git-gui package.
 # しらいし ななこ <nanako3@bluebottle.com>, 2007.
 #
-#, fuzzy
 msgid ""
 msgstr ""
 "Project-Id-Version: git-gui\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-12-08 08:31-0800\n"
-"PO-Revision-Date: 2008-12-09 06:27+0900\n"
+"POT-Creation-Date: 2010-01-26 15:47-0800\n"
+"PO-Revision-Date: 2010-02-02 19:03+0900\n"
 "Last-Translator: しらいし ななこ <nanako3@lavabit.com>\n"
 "Language-Team: Japanese\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: git-gui.sh:41 git-gui.sh:737 git-gui.sh:751 git-gui.sh:764 git-gui.sh:847
-#: git-gui.sh:866
+#: git-gui.sh:41 git-gui.sh:793 git-gui.sh:807 git-gui.sh:820 git-gui.sh:903
+#: git-gui.sh:922
 msgid "git-gui: fatal error"
 msgstr "git-gui: 致命的なエラー"
 
-#: git-gui.sh:689
+#: git-gui.sh:743
 #, tcl-format
 msgid "Invalid font specified in %s:"
 msgstr "%s に無効なフォントが指定されています:"
 
-#: git-gui.sh:723
+#: git-gui.sh:779
 msgid "Main Font"
 msgstr "主フォント"
 
-#: git-gui.sh:724
+#: git-gui.sh:780
 msgid "Diff/Console Font"
 msgstr "diff/コンソール・フォント"
 
-#: git-gui.sh:738
+#: git-gui.sh:794
 msgid "Cannot find git in PATH."
 msgstr "PATH 中に git が見つかりません"
 
-#: git-gui.sh:765
+#: git-gui.sh:821
 msgid "Cannot parse Git version string:"
 msgstr "Git バージョン名が理解できません:"
 
-#: git-gui.sh:783
+#: git-gui.sh:839
 #, tcl-format
 msgid ""
 "Git version cannot be determined.\n"
@@ -61,446 +60,475 @@ msgstr ""
 "\n"
 "'%s' はバージョン 1.5.0 と思って良いですか?\n"
 
-#: git-gui.sh:1062
+#: git-gui.sh:1128
 msgid "Git directory not found:"
 msgstr "Git ディレクトリが見つかりません:"
 
-#: git-gui.sh:1069
+#: git-gui.sh:1146
 msgid "Cannot move to top of working directory:"
 msgstr "作業ディレクトリの最上位に移動できません"
 
-#: git-gui.sh:1076
-msgid "Cannot use funny .git directory:"
-msgstr "変な .git ディレクトリは使えません"
+#: git-gui.sh:1154
+msgid "Cannot use bare repository:"
+msgstr "裸のリポジトリは使えません:"
 
-#: git-gui.sh:1081
+#: git-gui.sh:1162
 msgid "No working directory"
 msgstr "作業ディレクトリがありません"
 
-#: git-gui.sh:1247 lib/checkout_op.tcl:305
+#: git-gui.sh:1334 lib/checkout_op.tcl:306
 msgid "Refreshing file status..."
 msgstr "ファイル状態を更新しています…"
 
-#: git-gui.sh:1303
+#: git-gui.sh:1390
 msgid "Scanning for modified files ..."
 msgstr "変更されたファイルをスキャンしています…"
 
-#: git-gui.sh:1367
+#: git-gui.sh:1454
 msgid "Calling prepare-commit-msg hook..."
 msgstr "prepare-commit-msg フックを実行中・・・"
 
-#: git-gui.sh:1384
+#: git-gui.sh:1471
 msgid "Commit declined by prepare-commit-msg hook."
 msgstr "prepare-commit-msg フックがコミットを拒否しました"
 
-#: git-gui.sh:1542 lib/browser.tcl:246
+#: git-gui.sh:1629 lib/browser.tcl:246
 msgid "Ready."
 msgstr "準備完了"
 
-#: git-gui.sh:1819
+#: git-gui.sh:1787
+#, tcl-format
+msgid "Displaying only %s of %s files."
+msgstr "全体で%s個の内の%sファイルだけ表示しています"
+
+#: git-gui.sh:1913
 msgid "Unmodified"
 msgstr "変更無し"
 
-#: git-gui.sh:1821
+#: git-gui.sh:1915
 msgid "Modified, not staged"
 msgstr "変更あり、コミット未予定"
 
-#: git-gui.sh:1822 git-gui.sh:1830
+#: git-gui.sh:1916 git-gui.sh:1924
 msgid "Staged for commit"
 msgstr "コミット予定済"
 
-#: git-gui.sh:1823 git-gui.sh:1831
+#: git-gui.sh:1917 git-gui.sh:1925
 msgid "Portions staged for commit"
 msgstr "部分的にコミット予定済"
 
-#: git-gui.sh:1824 git-gui.sh:1832
+#: git-gui.sh:1918 git-gui.sh:1926
 msgid "Staged for commit, missing"
 msgstr "コミット予定済、ファイル無し"
 
-#: git-gui.sh:1826
+#: git-gui.sh:1920
 msgid "File type changed, not staged"
 msgstr "ファイル型変更、コミット未予定"
 
-#: git-gui.sh:1827
+#: git-gui.sh:1921
 msgid "File type changed, staged"
 msgstr "ファイル型変更、コミット予定済"
 
-#: git-gui.sh:1829
+#: git-gui.sh:1923
 msgid "Untracked, not staged"
 msgstr "管理外、コミット未予定"
 
-#: git-gui.sh:1834
+#: git-gui.sh:1928
 msgid "Missing"
 msgstr "ファイル無し"
 
-#: git-gui.sh:1835
+#: git-gui.sh:1929
 msgid "Staged for removal"
 msgstr "削除予定済"
 
-#: git-gui.sh:1836
+#: git-gui.sh:1930
 msgid "Staged for removal, still present"
 msgstr "削除予定済、ファイル未削除"
 
-#: git-gui.sh:1838 git-gui.sh:1839 git-gui.sh:1840 git-gui.sh:1841
-#: git-gui.sh:1842 git-gui.sh:1843
+#: git-gui.sh:1932 git-gui.sh:1933 git-gui.sh:1934 git-gui.sh:1935
+#: git-gui.sh:1936 git-gui.sh:1937
 msgid "Requires merge resolution"
 msgstr "要マージ解決"
 
-#: git-gui.sh:1878
+#: git-gui.sh:1972
 msgid "Starting gitk... please wait..."
 msgstr "gitk を起動中…お待ち下さい…"
 
-#: git-gui.sh:1887
+#: git-gui.sh:1984
 msgid "Couldn't find gitk in PATH"
 msgstr "PATH 中に gitk が見つかりません"
 
-#: git-gui.sh:2280 lib/choose_repository.tcl:36
+#: git-gui.sh:2043
+msgid "Couldn't find git gui in PATH"
+msgstr "PATH 中に git gui が見つかりません"
+
+#: git-gui.sh:2455 lib/choose_repository.tcl:36
 msgid "Repository"
 msgstr "リポジトリ"
 
-#: git-gui.sh:2281
+#: git-gui.sh:2456
 msgid "Edit"
 msgstr "編集"
 
-#: git-gui.sh:2283 lib/choose_rev.tcl:561
+#: git-gui.sh:2458 lib/choose_rev.tcl:561
 msgid "Branch"
 msgstr "ブランチ"
 
-#: git-gui.sh:2286 lib/choose_rev.tcl:548
+#: git-gui.sh:2461 lib/choose_rev.tcl:548
 msgid "Commit@@noun"
 msgstr "コミット"
 
-#: git-gui.sh:2289 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
+#: git-gui.sh:2464 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
 msgid "Merge"
 msgstr "マージ"
 
-#: git-gui.sh:2290 lib/choose_rev.tcl:557
+#: git-gui.sh:2465 lib/choose_rev.tcl:557
 msgid "Remote"
 msgstr "リモート"
 
-#: git-gui.sh:2293
+#: git-gui.sh:2468
 msgid "Tools"
 msgstr "ツール"
 
-#: git-gui.sh:2302
+#: git-gui.sh:2477
 msgid "Explore Working Copy"
 msgstr "ワーキングコピーをブラウズ"
 
-#: git-gui.sh:2307
+#: git-gui.sh:2483
 msgid "Browse Current Branch's Files"
 msgstr "現在のブランチのファイルを見る"
 
-#: git-gui.sh:2311
+#: git-gui.sh:2487
 msgid "Browse Branch Files..."
 msgstr "ブランチのファイルを見る…"
 
-#: git-gui.sh:2316
+#: git-gui.sh:2492
 msgid "Visualize Current Branch's History"
 msgstr "現在のブランチの履歴を見る"
 
-#: git-gui.sh:2320
+#: git-gui.sh:2496
 msgid "Visualize All Branch History"
 msgstr "全てのブランチの履歴を見る"
 
-#: git-gui.sh:2327
+#: git-gui.sh:2503
 #, tcl-format
 msgid "Browse %s's Files"
 msgstr "ブランチ %s のファイルを見る"
 
-#: git-gui.sh:2329
+#: git-gui.sh:2505
 #, tcl-format
 msgid "Visualize %s's History"
 msgstr "ブランチ %s の履歴を見る"
 
-#: git-gui.sh:2334 lib/database.tcl:27 lib/database.tcl:67
+#: git-gui.sh:2510 lib/database.tcl:27 lib/database.tcl:67
 msgid "Database Statistics"
 msgstr "データベース統計"
 
-#: git-gui.sh:2337 lib/database.tcl:34
+#: git-gui.sh:2513 lib/database.tcl:34
 msgid "Compress Database"
 msgstr "データベース圧縮"
 
-#: git-gui.sh:2340
+#: git-gui.sh:2516
 msgid "Verify Database"
 msgstr "データベース検証"
 
-#: git-gui.sh:2347 git-gui.sh:2351 git-gui.sh:2355 lib/shortcut.tcl:7
-#: lib/shortcut.tcl:39 lib/shortcut.tcl:71
+#: git-gui.sh:2523 git-gui.sh:2527 git-gui.sh:2531 lib/shortcut.tcl:8
+#: lib/shortcut.tcl:40 lib/shortcut.tcl:72
 msgid "Create Desktop Icon"
 msgstr "デスクトップ・アイコンを作る"
 
-#: git-gui.sh:2363 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191
+#: git-gui.sh:2539 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191
 msgid "Quit"
 msgstr "終了"
 
-#: git-gui.sh:2371
+#: git-gui.sh:2547
 msgid "Undo"
 msgstr "元に戻す"
 
-#: git-gui.sh:2374
+#: git-gui.sh:2550
 msgid "Redo"
 msgstr "やり直し"
 
-#: git-gui.sh:2378 git-gui.sh:2923
+#: git-gui.sh:2554 git-gui.sh:3109
 msgid "Cut"
 msgstr "切り取り"
 
-#: git-gui.sh:2381 git-gui.sh:2926 git-gui.sh:3000 git-gui.sh:3082
+#: git-gui.sh:2557 git-gui.sh:3112 git-gui.sh:3186 git-gui.sh:3259
 #: lib/console.tcl:69
 msgid "Copy"
 msgstr "コピー"
 
-#: git-gui.sh:2384 git-gui.sh:2929
+#: git-gui.sh:2560 git-gui.sh:3115
 msgid "Paste"
 msgstr "貼り付け"
 
-#: git-gui.sh:2387 git-gui.sh:2932 lib/branch_delete.tcl:26
+#: git-gui.sh:2563 git-gui.sh:3118 lib/branch_delete.tcl:26
 #: lib/remote_branch_delete.tcl:38
 msgid "Delete"
 msgstr "削除"
 
-#: git-gui.sh:2391 git-gui.sh:2936 git-gui.sh:3086 lib/console.tcl:71
+#: git-gui.sh:2567 git-gui.sh:3122 git-gui.sh:3263 lib/console.tcl:71
 msgid "Select All"
 msgstr "全て選択"
 
-#: git-gui.sh:2400
+#: git-gui.sh:2576
 msgid "Create..."
 msgstr "作成…"
 
-#: git-gui.sh:2406
+#: git-gui.sh:2582
 msgid "Checkout..."
 msgstr "チェックアウト"
 
-#: git-gui.sh:2412
+#: git-gui.sh:2588
 msgid "Rename..."
 msgstr "名前変更…"
 
-#: git-gui.sh:2417
+#: git-gui.sh:2593
 msgid "Delete..."
 msgstr "削除…"
 
-#: git-gui.sh:2422
+#: git-gui.sh:2598
 msgid "Reset..."
 msgstr "リセット…"
 
-#: git-gui.sh:2432
+#: git-gui.sh:2608
 msgid "Done"
 msgstr "完了"
 
-#: git-gui.sh:2434
+#: git-gui.sh:2610
 msgid "Commit@@verb"
 msgstr "コミット"
 
-#: git-gui.sh:2443 git-gui.sh:2864
+#: git-gui.sh:2619 git-gui.sh:3050
 msgid "New Commit"
 msgstr "新規コミット"
 
-#: git-gui.sh:2451 git-gui.sh:2871
+#: git-gui.sh:2627 git-gui.sh:3057
 msgid "Amend Last Commit"
 msgstr "最新コミットを訂正"
 
-#: git-gui.sh:2461 git-gui.sh:2825 lib/remote_branch_delete.tcl:99
+#: git-gui.sh:2637 git-gui.sh:3011 lib/remote_branch_delete.tcl:99
 msgid "Rescan"
 msgstr "再スキャン"
 
-#: git-gui.sh:2467
+#: git-gui.sh:2643
 msgid "Stage To Commit"
 msgstr "コミット予定する"
 
-#: git-gui.sh:2473
+#: git-gui.sh:2649
 msgid "Stage Changed Files To Commit"
 msgstr "変更されたファイルをコミット予定"
 
-#: git-gui.sh:2479
+#: git-gui.sh:2655
 msgid "Unstage From Commit"
 msgstr "コミットから降ろす"
 
-#: git-gui.sh:2484 lib/index.tcl:410
+#: git-gui.sh:2661 lib/index.tcl:412
 msgid "Revert Changes"
 msgstr "変更を元に戻す"
 
-#: git-gui.sh:2491 git-gui.sh:3069
+#: git-gui.sh:2669 git-gui.sh:3310 git-gui.sh:3341
 msgid "Show Less Context"
 msgstr "文脈を少なく"
 
-#: git-gui.sh:2495 git-gui.sh:3073
+#: git-gui.sh:2673 git-gui.sh:3314 git-gui.sh:3345
 msgid "Show More Context"
 msgstr "文脈を多く"
 
-#: git-gui.sh:2502 git-gui.sh:2838 git-gui.sh:2947
+#: git-gui.sh:2680 git-gui.sh:3024 git-gui.sh:3133
 msgid "Sign Off"
 msgstr "署名"
 
-#: git-gui.sh:2518
+#: git-gui.sh:2696
 msgid "Local Merge..."
 msgstr "ローカル・マージ…"
 
-#: git-gui.sh:2523
+#: git-gui.sh:2701
 msgid "Abort Merge..."
 msgstr "マージ中止…"
 
-#: git-gui.sh:2535 git-gui.sh:2575
+#: git-gui.sh:2713 git-gui.sh:2741
 msgid "Add..."
 msgstr "追加"
 
-#: git-gui.sh:2539
+#: git-gui.sh:2717
 msgid "Push..."
 msgstr "プッシュ…"
 
-#: git-gui.sh:2543
+#: git-gui.sh:2721
 msgid "Delete Branch..."
 msgstr "ブランチ削除..."
 
-#: git-gui.sh:2553 git-gui.sh:2589 lib/about.tcl:14
-#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53
-#, tcl-format
-msgid "About %s"
-msgstr "%s について"
-
-#: git-gui.sh:2557
-msgid "Preferences..."
-msgstr "設定…"
-
-#: git-gui.sh:2565 git-gui.sh:3115
+#: git-gui.sh:2731 git-gui.sh:3292
 msgid "Options..."
 msgstr "オプション…"
 
-#: git-gui.sh:2576
+#: git-gui.sh:2742
 msgid "Remove..."
 msgstr "削除..."
 
-#: git-gui.sh:2585 lib/choose_repository.tcl:50
+#: git-gui.sh:2751 lib/choose_repository.tcl:50
 msgid "Help"
 msgstr "ヘルプ"
 
-#: git-gui.sh:2611
+#: git-gui.sh:2755 git-gui.sh:2759 lib/about.tcl:14
+#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53
+#, tcl-format
+msgid "About %s"
+msgstr "%s について"
+
+#: git-gui.sh:2783
 msgid "Online Documentation"
 msgstr "オンライン・ドキュメント"
 
-#: git-gui.sh:2614 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
+#: git-gui.sh:2786 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
 msgid "Show SSH Key"
 msgstr "SSH キーを表示"
 
-#: git-gui.sh:2707
+#: git-gui.sh:2893
 #, tcl-format
 msgid "fatal: cannot stat path %s: No such file or directory"
 msgstr ""
 "致命的: パス %s が stat できません。そのようなファイルやディレクトリはありま"
 "せん"
 
-#: git-gui.sh:2740
+#: git-gui.sh:2926
 msgid "Current Branch:"
 msgstr "現在のブランチ"
 
-#: git-gui.sh:2761
+#: git-gui.sh:2947
 msgid "Staged Changes (Will Commit)"
 msgstr "ステージングされた(コミット予定済の)変更"
 
-#: git-gui.sh:2781
+#: git-gui.sh:2967
 msgid "Unstaged Changes"
 msgstr "コミット予定に入っていない変更"
 
-#: git-gui.sh:2831
+#: git-gui.sh:3017
 msgid "Stage Changed"
 msgstr "変更をコミット予定に入れる"
 
-#: git-gui.sh:2850 lib/transport.tcl:93 lib/transport.tcl:182
+#: git-gui.sh:3036 lib/transport.tcl:104 lib/transport.tcl:193
 msgid "Push"
 msgstr "プッシュ"
 
-#: git-gui.sh:2885
+#: git-gui.sh:3071
 msgid "Initial Commit Message:"
 msgstr "最初のコミットメッセージ:"
 
-#: git-gui.sh:2886
+#: git-gui.sh:3072
 msgid "Amended Commit Message:"
 msgstr "訂正したコミットメッセージ:"
 
-#: git-gui.sh:2887
+#: git-gui.sh:3073
 msgid "Amended Initial Commit Message:"
 msgstr "訂正した最初のコミットメッセージ:"
 
-#: git-gui.sh:2888
+#: git-gui.sh:3074
 msgid "Amended Merge Commit Message:"
 msgstr "訂正したマージコミットメッセージ:"
 
-#: git-gui.sh:2889
+#: git-gui.sh:3075
 msgid "Merge Commit Message:"
 msgstr "マージコミットメッセージ:"
 
-#: git-gui.sh:2890
+#: git-gui.sh:3076
 msgid "Commit Message:"
 msgstr "コミットメッセージ:"
 
-#: git-gui.sh:2939 git-gui.sh:3090 lib/console.tcl:73
+#: git-gui.sh:3125 git-gui.sh:3267 lib/console.tcl:73
 msgid "Copy All"
 msgstr "全てコピー"
 
-#: git-gui.sh:2963 lib/blame.tcl:104
+#: git-gui.sh:3149 lib/blame.tcl:104
 msgid "File:"
 msgstr "ファイル:"
 
-#: git-gui.sh:3078
+#: git-gui.sh:3255
 msgid "Refresh"
 msgstr "再読み込み"
 
-#: git-gui.sh:3099
+#: git-gui.sh:3276
 msgid "Decrease Font Size"
 msgstr "フォントを小さく"
 
-#: git-gui.sh:3103
+#: git-gui.sh:3280
 msgid "Increase Font Size"
 msgstr "フォントを大きく"
 
-#: git-gui.sh:3111 lib/blame.tcl:281
+#: git-gui.sh:3288 lib/blame.tcl:281
 msgid "Encoding"
 msgstr "エンコーディング"
 
-#: git-gui.sh:3122
+#: git-gui.sh:3299
 msgid "Apply/Reverse Hunk"
 msgstr "パッチを適用/取り消す"
 
-#: git-gui.sh:3127
+#: git-gui.sh:3304
 msgid "Apply/Reverse Line"
 msgstr "パッチ行を適用/取り消す"
 
-#: git-gui.sh:3137
+#: git-gui.sh:3323
 msgid "Run Merge Tool"
 msgstr "マージツールを起動"
 
-#: git-gui.sh:3142
+#: git-gui.sh:3328
 msgid "Use Remote Version"
 msgstr "リモートの方を採用"
 
-#: git-gui.sh:3146
+#: git-gui.sh:3332
 msgid "Use Local Version"
 msgstr "ローカルの方を採用"
 
-#: git-gui.sh:3150
+#: git-gui.sh:3336
 msgid "Revert To Base"
 msgstr "ベース版を採用"
 
-#: git-gui.sh:3169
+#: git-gui.sh:3354
+msgid "Visualize These Changes In The Submodule"
+msgstr "サブモジュール内のこれらの変更を見る"
+
+#: git-gui.sh:3358
+msgid "Visualize Current Branch History In The Submodule"
+msgstr "サブモジュール内で現在のブランチの履歴を見る"
+
+#: git-gui.sh:3362
+msgid "Visualize All Branch History In The Submodule"
+msgstr "サブモジュール内で全てのブランチの履歴を見る"
+
+#: git-gui.sh:3367
+msgid "Start git gui In The Submodule"
+msgstr "サブモジュール内でgit guiを起動する"
+
+#: git-gui.sh:3389
 msgid "Unstage Hunk From Commit"
 msgstr "パッチをコミット予定から外す"
 
-#: git-gui.sh:3170
+#: git-gui.sh:3391
+msgid "Unstage Lines From Commit"
+msgstr "コミット予定から行を外す"
+
+#: git-gui.sh:3393
 msgid "Unstage Line From Commit"
 msgstr "コミット予定から行を外す"
 
-#: git-gui.sh:3172
+#: git-gui.sh:3396
 msgid "Stage Hunk For Commit"
 msgstr "パッチをコミット予定に加える"
 
-#: git-gui.sh:3173
+#: git-gui.sh:3398
+msgid "Stage Lines For Commit"
+msgstr "パッチ行をコミット予定に加える"
+
+#: git-gui.sh:3400
 msgid "Stage Line For Commit"
 msgstr "パッチ行をコミット予定に加える"
 
-#: git-gui.sh:3196
+#: git-gui.sh:3424
 msgid "Initializing..."
 msgstr "初期化しています…"
 
-#: git-gui.sh:3301
+#: git-gui.sh:3541
 #, tcl-format
 msgid ""
 "Possible environment issues exist.\n"
@@ -515,7 +543,7 @@ msgstr ""
 "以下の環境変数は %s が起動する Git サブプロセスによって無視されるでしょう:\n"
 "\n"
 
-#: git-gui.sh:3331
+#: git-gui.sh:3570
 msgid ""
 "\n"
 "This is due to a known issue with the\n"
@@ -525,7 +553,7 @@ msgstr ""
 "これは Cygwin で配布されている Tcl バイナリに\n"
 "関しての既知の問題によります"
 
-#: git-gui.sh:3336
+#: git-gui.sh:3575
 #, tcl-format
 msgid ""
 "\n"
@@ -609,43 +637,43 @@ msgstr "コピー検知を実行中…"
 msgid "Loading annotation..."
 msgstr "注釈を読み込んでいます…"
 
-#: lib/blame.tcl:964
+#: lib/blame.tcl:963
 msgid "Author:"
 msgstr "作者:"
 
-#: lib/blame.tcl:968
+#: lib/blame.tcl:967
 msgid "Committer:"
 msgstr "コミット者:"
 
-#: lib/blame.tcl:973
+#: lib/blame.tcl:972
 msgid "Original File:"
 msgstr "元ファイル"
 
-#: lib/blame.tcl:1021
+#: lib/blame.tcl:1020
 msgid "Cannot find HEAD commit:"
 msgstr "HEAD コミットが見つかりません"
 
-#: lib/blame.tcl:1076
+#: lib/blame.tcl:1075
 msgid "Cannot find parent commit:"
 msgstr "親コミットが見つかりません:"
 
-#: lib/blame.tcl:1091
+#: lib/blame.tcl:1090
 msgid "Unable to display parent"
 msgstr "親を表示できません"
 
-#: lib/blame.tcl:1092 lib/diff.tcl:297
+#: lib/blame.tcl:1091 lib/diff.tcl:320
 msgid "Error loading diff:"
 msgstr "diff を読む際のエラーです:"
 
-#: lib/blame.tcl:1232
+#: lib/blame.tcl:1231
 msgid "Originally By:"
 msgstr "原作者:"
 
-#: lib/blame.tcl:1238
+#: lib/blame.tcl:1237
 msgid "In File:"
 msgstr "ファイル:"
 
-#: lib/blame.tcl:1243
+#: lib/blame.tcl:1242
 msgid "Copied Or Moved Here By:"
 msgstr "複写・移動者:"
 
@@ -659,10 +687,10 @@ msgstr "チェックアウト"
 
 #: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
 #: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282
-#: lib/checkout_op.tcl:544 lib/choose_font.tcl:43 lib/merge.tcl:172
+#: lib/checkout_op.tcl:579 lib/choose_font.tcl:43 lib/merge.tcl:172
 #: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42
 #: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352
-#: lib/transport.tcl:97
+#: lib/transport.tcl:108
 msgid "Cancel"
 msgstr "中止"
 
@@ -690,7 +718,7 @@ msgstr "ブランチを作成"
 msgid "Create New Branch"
 msgstr "ブランチを新規作成"
 
-#: lib/branch_create.tcl:31 lib/choose_repository.tcl:377
+#: lib/branch_create.tcl:31 lib/choose_repository.tcl:381
 msgid "Create"
 msgstr "作成"
 
@@ -722,7 +750,7 @@ msgstr "いいえ"
 msgid "Fast Forward Only"
 msgstr "早送りのみ"
 
-#: lib/branch_create.tcl:85 lib/checkout_op.tcl:536
+#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571
 msgid "Reset"
 msgstr "リセット"
 
@@ -764,15 +792,25 @@ msgstr "ローカル・ブランチ"
 msgid "Delete Only If Merged Into"
 msgstr "マージ済みの時のみ削除"
 
-#: lib/branch_delete.tcl:54
-msgid "Always (Do not perform merge test.)"
-msgstr "無条件(マージテストしない)"
+#: lib/branch_delete.tcl:54 lib/remote_branch_delete.tcl:119
+msgid "Always (Do not perform merge checks)"
+msgstr "無条件(マージ検査をしない)"
 
 #: lib/branch_delete.tcl:103
 #, tcl-format
 msgid "The following branches are not completely merged into %s:"
 msgstr "以下のブランチは %s に完全にマージされていません:"
 
+#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:217
+msgid ""
+"Recovering deleted branches is difficult.\n"
+"\n"
+"Delete the selected branches?"
+msgstr ""
+"削除したブランチを回復するのは困難です。\n"
+"\n"
+"選択したブランチを削除して良いですか?"
+
 #: lib/branch_delete.tcl:141
 #, tcl-format
 msgid ""
@@ -802,7 +840,7 @@ msgstr "新しい名前:"
 msgid "Please select a branch to rename."
 msgstr "名前を変更するブランチを選んで下さい。"
 
-#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:201
+#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202
 #, tcl-format
 msgid "Branch '%s' already exists."
 msgstr "'%s'というブランチは既に存在します。"
@@ -833,38 +871,38 @@ msgstr "[上位フォルダへ]"
 msgid "Browse Branch Files"
 msgstr "現在のブランチのファイルを見る"
 
-#: lib/browser.tcl:278 lib/choose_repository.tcl:394
-#: lib/choose_repository.tcl:480 lib/choose_repository.tcl:491
-#: lib/choose_repository.tcl:995
+#: lib/browser.tcl:278 lib/choose_repository.tcl:398
+#: lib/choose_repository.tcl:486 lib/choose_repository.tcl:497
+#: lib/choose_repository.tcl:1028
 msgid "Browse"
 msgstr "ブラウズ"
 
-#: lib/checkout_op.tcl:84
+#: lib/checkout_op.tcl:85
 #, tcl-format
 msgid "Fetching %s from %s"
 msgstr "%s から %s をフェッチしています"
 
-#: lib/checkout_op.tcl:132
+#: lib/checkout_op.tcl:133
 #, tcl-format
 msgid "fatal: Cannot resolve %s"
 msgstr "致命的エラー: %s を解決できません"
 
-#: lib/checkout_op.tcl:145 lib/console.tcl:81 lib/database.tcl:31
+#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:31
 #: lib/sshkey.tcl:53
 msgid "Close"
 msgstr "閉じる"
 
-#: lib/checkout_op.tcl:174
+#: lib/checkout_op.tcl:175
 #, tcl-format
 msgid "Branch '%s' does not exist."
 msgstr "ブランチ'%s'は存在しません。"
 
-#: lib/checkout_op.tcl:193
+#: lib/checkout_op.tcl:194
 #, tcl-format
 msgid "Failed to configure simplified git-pull for '%s'."
 msgstr "'%s' に簡易 git-pull を設定できませんでした"
 
-#: lib/checkout_op.tcl:228
+#: lib/checkout_op.tcl:229
 #, tcl-format
 msgid ""
 "Branch '%s' already exists.\n"
@@ -877,21 +915,21 @@ msgstr ""
 "%s に早送りできません。\n"
 "マージが必要です。"
 
-#: lib/checkout_op.tcl:242
+#: lib/checkout_op.tcl:243
 #, tcl-format
 msgid "Merge strategy '%s' not supported."
 msgstr "'%s' マージ戦略はサポートされていません。"
 
-#: lib/checkout_op.tcl:261
+#: lib/checkout_op.tcl:262
 #, tcl-format
 msgid "Failed to update '%s'."
 msgstr "'%s' の更新に失敗しました。"
 
-#: lib/checkout_op.tcl:273
+#: lib/checkout_op.tcl:274
 msgid "Staging area (index) is already locked."
 msgstr "インデックスは既にロックされています。"
 
-#: lib/checkout_op.tcl:288
+#: lib/checkout_op.tcl:289
 msgid ""
 "Last scanned state does not match repository state.\n"
 "\n"
@@ -907,30 +945,30 @@ msgstr ""
 "\n"
 "自動的に再スキャンを開始します。\n"
 
-#: lib/checkout_op.tcl:344
+#: lib/checkout_op.tcl:345
 #, tcl-format
 msgid "Updating working directory to '%s'..."
 msgstr "作業ディレクトリを '%s' に更新しています…"
 
-#: lib/checkout_op.tcl:345
+#: lib/checkout_op.tcl:346
 msgid "files checked out"
 msgstr "チェックアウトされたファイル"
 
-#: lib/checkout_op.tcl:375
+#: lib/checkout_op.tcl:376
 #, tcl-format
 msgid "Aborted checkout of '%s' (file level merging is required)."
 msgstr "'%s' のチェックアウトを中止しました(ファイル毎のマージが必要です)。"
 
-#: lib/checkout_op.tcl:376
+#: lib/checkout_op.tcl:377
 msgid "File level merge required."
 msgstr "ファイル毎のマージが必要です。"
 
-#: lib/checkout_op.tcl:380
+#: lib/checkout_op.tcl:381
 #, tcl-format
 msgid "Staying on branch '%s'."
 msgstr "ブランチ '%s' に滞まります。"
 
-#: lib/checkout_op.tcl:451
+#: lib/checkout_op.tcl:452
 msgid ""
 "You are no longer on a local branch.\n"
 "\n"
@@ -942,30 +980,30 @@ msgstr ""
 "ブランチ上に滞まりたいときは、この「分離されたチェックアウト」から新規ブラン"
 "チを開始してください。"
 
-#: lib/checkout_op.tcl:468 lib/checkout_op.tcl:472
+#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507
 #, tcl-format
 msgid "Checked out '%s'."
 msgstr "'%s' をチェックアウトしました"
 
-#: lib/checkout_op.tcl:500
+#: lib/checkout_op.tcl:535
 #, tcl-format
 msgid "Resetting '%s' to '%s' will lose the following commits:"
 msgstr "'%s' を '%s' にリセットすると、以下のコミットが失なわれます:"
 
-#: lib/checkout_op.tcl:522
+#: lib/checkout_op.tcl:557
 msgid "Recovering lost commits may not be easy."
 msgstr "失なわれたコミットを回復するのは簡単ではありません。"
 
-#: lib/checkout_op.tcl:527
+#: lib/checkout_op.tcl:562
 #, tcl-format
 msgid "Reset '%s'?"
 msgstr "'%s' をリセットしますか?"
 
-#: lib/checkout_op.tcl:532 lib/merge.tcl:164 lib/tools_dlg.tcl:343
+#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:343
 msgid "Visualize"
 msgstr "可視化"
 
-#: lib/checkout_op.tcl:600
+#: lib/checkout_op.tcl:635
 #, tcl-format
 msgid ""
 "Failed to set current branch.\n"
@@ -1009,7 +1047,7 @@ msgstr ""
 msgid "Git Gui"
 msgstr "Git GUI"
 
-#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:382
+#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:386
 msgid "Create New Repository"
 msgstr "新しいリポジトリを作る"
 
@@ -1017,7 +1055,7 @@ msgstr "新しいリポジトリを作る"
 msgid "New..."
 msgstr "新規…"
 
-#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:465
+#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:471
 msgid "Clone Existing Repository"
 msgstr "既存リポジトリを複製する"
 
@@ -1025,7 +1063,7 @@ msgstr "既存リポジトリを複製する"
 msgid "Clone..."
 msgstr "複製…"
 
-#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:983
+#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:1016
 msgid "Open Existing Repository"
 msgstr "既存リポジトリを開く"
 
@@ -1041,193 +1079,193 @@ msgstr "最近使ったリポジトリ"
 msgid "Open Recent Repository:"
 msgstr "最近使ったリポジトリを開く"
 
-#: lib/choose_repository.tcl:302 lib/choose_repository.tcl:309
-#: lib/choose_repository.tcl:316
+#: lib/choose_repository.tcl:306 lib/choose_repository.tcl:313
+#: lib/choose_repository.tcl:320
 #, tcl-format
 msgid "Failed to create repository %s:"
 msgstr "リポジトリ %s を作製できません:"
 
-#: lib/choose_repository.tcl:387
+#: lib/choose_repository.tcl:391
 msgid "Directory:"
 msgstr "ディレクトリ:"
 
-#: lib/choose_repository.tcl:417 lib/choose_repository.tcl:544
-#: lib/choose_repository.tcl:1017
+#: lib/choose_repository.tcl:423 lib/choose_repository.tcl:550
+#: lib/choose_repository.tcl:1052
 msgid "Git Repository"
 msgstr "GIT リポジトリ"
 
-#: lib/choose_repository.tcl:442
+#: lib/choose_repository.tcl:448
 #, tcl-format
 msgid "Directory %s already exists."
 msgstr "ディレクトリ '%s' は既に存在します。"
 
-#: lib/choose_repository.tcl:446
+#: lib/choose_repository.tcl:452
 #, tcl-format
 msgid "File %s already exists."
 msgstr "ファイル '%s' は既に存在します。"
 
-#: lib/choose_repository.tcl:460
+#: lib/choose_repository.tcl:466
 msgid "Clone"
 msgstr "複製"
 
-#: lib/choose_repository.tcl:473
+#: lib/choose_repository.tcl:479
 msgid "Source Location:"
 msgstr "ソースの位置"
 
-#: lib/choose_repository.tcl:484
+#: lib/choose_repository.tcl:490
 msgid "Target Directory:"
 msgstr "先ディレクトリ:"
 
-#: lib/choose_repository.tcl:496
+#: lib/choose_repository.tcl:502
 msgid "Clone Type:"
 msgstr "複製方式:"
 
-#: lib/choose_repository.tcl:502
+#: lib/choose_repository.tcl:508
 msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
 msgstr "標準(高速・中冗長度・ハードリンク)"
 
-#: lib/choose_repository.tcl:508
+#: lib/choose_repository.tcl:514
 msgid "Full Copy (Slower, Redundant Backup)"
 msgstr "全複写(低速・冗長バックアップ)"
 
-#: lib/choose_repository.tcl:514
+#: lib/choose_repository.tcl:520
 msgid "Shared (Fastest, Not Recommended, No Backup)"
 msgstr "共有(最高速・非推奨・バックアップ無し)"
 
-#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597
-#: lib/choose_repository.tcl:743 lib/choose_repository.tcl:813
-#: lib/choose_repository.tcl:1023 lib/choose_repository.tcl:1031
+#: lib/choose_repository.tcl:556 lib/choose_repository.tcl:603
+#: lib/choose_repository.tcl:749 lib/choose_repository.tcl:819
+#: lib/choose_repository.tcl:1058 lib/choose_repository.tcl:1066
 #, tcl-format
 msgid "Not a Git repository: %s"
 msgstr "Git リポジトリではありません: %s"
 
-#: lib/choose_repository.tcl:586
+#: lib/choose_repository.tcl:592
 msgid "Standard only available for local repository."
 msgstr "標準方式は同一計算機上のリポジトリにのみ使えます。"
 
-#: lib/choose_repository.tcl:590
+#: lib/choose_repository.tcl:596
 msgid "Shared only available for local repository."
 msgstr "共有方式は同一計算機上のリポジトリにのみ使えます。"
 
-#: lib/choose_repository.tcl:611
+#: lib/choose_repository.tcl:617
 #, tcl-format
 msgid "Location %s already exists."
 msgstr "'%s' は既に存在します。"
 
-#: lib/choose_repository.tcl:622
+#: lib/choose_repository.tcl:628
 msgid "Failed to configure origin"
 msgstr "origin を設定できませんでした"
 
-#: lib/choose_repository.tcl:634
+#: lib/choose_repository.tcl:640
 msgid "Counting objects"
 msgstr "オブジェクトを数えています"
 
-#: lib/choose_repository.tcl:635
+#: lib/choose_repository.tcl:641
 msgid "buckets"
 msgstr "バケツ"
 
-#: lib/choose_repository.tcl:659
+#: lib/choose_repository.tcl:665
 #, tcl-format
 msgid "Unable to copy objects/info/alternates: %s"
 msgstr "objects/info/alternates を複写できません: %s"
 
-#: lib/choose_repository.tcl:695
+#: lib/choose_repository.tcl:701
 #, tcl-format
 msgid "Nothing to clone from %s."
 msgstr "%s から複製する内容はありません"
 
-#: lib/choose_repository.tcl:697 lib/choose_repository.tcl:911
-#: lib/choose_repository.tcl:923
+#: lib/choose_repository.tcl:703 lib/choose_repository.tcl:917
+#: lib/choose_repository.tcl:929
 msgid "The 'master' branch has not been initialized."
 msgstr "'master' ブランチが初期化されていません"
 
-#: lib/choose_repository.tcl:710
+#: lib/choose_repository.tcl:716
 msgid "Hardlinks are unavailable.  Falling back to copying."
 msgstr "ハードリンクが作れないので、コピーします"
 
-#: lib/choose_repository.tcl:722
+#: lib/choose_repository.tcl:728
 #, tcl-format
 msgid "Cloning from %s"
 msgstr "%s から複製しています"
 
-#: lib/choose_repository.tcl:753
+#: lib/choose_repository.tcl:759
 msgid "Copying objects"
 msgstr "オブジェクトを複写しています"
 
-#: lib/choose_repository.tcl:754
+#: lib/choose_repository.tcl:760
 msgid "KiB"
 msgstr "KiB"
 
-#: lib/choose_repository.tcl:778
+#: lib/choose_repository.tcl:784
 #, tcl-format
 msgid "Unable to copy object: %s"
 msgstr "オブジェクトを複写できません: %s"
 
-#: lib/choose_repository.tcl:788
+#: lib/choose_repository.tcl:794
 msgid "Linking objects"
 msgstr "オブジェクトを連結しています"
 
-#: lib/choose_repository.tcl:789
+#: lib/choose_repository.tcl:795
 msgid "objects"
 msgstr "オブジェクト"
 
-#: lib/choose_repository.tcl:797
+#: lib/choose_repository.tcl:803
 #, tcl-format
 msgid "Unable to hardlink object: %s"
 msgstr "オブジェクトをハードリンクできません: %s"
 
-#: lib/choose_repository.tcl:852
+#: lib/choose_repository.tcl:858
 msgid "Cannot fetch branches and objects.  See console output for details."
 msgstr "ブランチやオブジェクトを取得できません。コンソール出力を見て下さい"
 
-#: lib/choose_repository.tcl:863
+#: lib/choose_repository.tcl:869
 msgid "Cannot fetch tags.  See console output for details."
 msgstr "タグを取得できません。コンソール出力を見て下さい"
 
-#: lib/choose_repository.tcl:887
+#: lib/choose_repository.tcl:893
 msgid "Cannot determine HEAD.  See console output for details."
 msgstr "HEAD を確定できません。コンソール出力を見て下さい"
 
-#: lib/choose_repository.tcl:896
+#: lib/choose_repository.tcl:902
 #, tcl-format
 msgid "Unable to cleanup %s"
 msgstr "%s を掃除できません"
 
-#: lib/choose_repository.tcl:902
+#: lib/choose_repository.tcl:908
 msgid "Clone failed."
 msgstr "複写に失敗しました。"
 
-#: lib/choose_repository.tcl:909
+#: lib/choose_repository.tcl:915
 msgid "No default branch obtained."
 msgstr "デフォールト・ブランチが取得されませんでした"
 
-#: lib/choose_repository.tcl:920
+#: lib/choose_repository.tcl:926
 #, tcl-format
 msgid "Cannot resolve %s as a commit."
 msgstr "%s をコミットとして解釈できません"
 
-#: lib/choose_repository.tcl:932
+#: lib/choose_repository.tcl:938
 msgid "Creating working directory"
 msgstr "作業ディレクトリを作成しています"
 
-#: lib/choose_repository.tcl:933 lib/index.tcl:65 lib/index.tcl:128
-#: lib/index.tcl:196
+#: lib/choose_repository.tcl:939 lib/index.tcl:67 lib/index.tcl:130
+#: lib/index.tcl:198
 msgid "files"
 msgstr "ファイル"
 
-#: lib/choose_repository.tcl:962
+#: lib/choose_repository.tcl:968
 msgid "Initial file checkout failed."
 msgstr "初期チェックアウトに失敗しました"
 
-#: lib/choose_repository.tcl:978
+#: lib/choose_repository.tcl:1011
 msgid "Open"
 msgstr "開く"
 
-#: lib/choose_repository.tcl:988
+#: lib/choose_repository.tcl:1021
 msgid "Repository:"
 msgstr "リポジトリ:"
 
-#: lib/choose_repository.tcl:1037
+#: lib/choose_repository.tcl:1072
 #, tcl-format
 msgid "Failed to open repository %s:"
 msgstr "リポジトリ %s を開けません:"
@@ -1298,19 +1336,24 @@ msgstr ""
 "現在はまだマージの途中です。先にこのマージを中止しないと、前のコミットの訂正"
 "はできません\n"
 
-#: lib/commit.tcl:49
+#: lib/commit.tcl:48
 msgid "Error loading commit data for amend:"
 msgstr "訂正するコミットのデータを読めません:"
 
-#: lib/commit.tcl:76
+#: lib/commit.tcl:75
 msgid "Unable to obtain your identity:"
 msgstr "ユーザの正体を確認できません:"
 
-#: lib/commit.tcl:81
+#: lib/commit.tcl:80
 msgid "Invalid GIT_COMMITTER_IDENT:"
 msgstr "GIT_COMMITTER_IDENT が無効です:"
 
-#: lib/commit.tcl:133
+#: lib/commit.tcl:129
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr "警告: Tcl はエンコーディング '%s' をサポートしていません"
+
+#: lib/commit.tcl:149
 msgid ""
 "Last scanned state does not match repository state.\n"
 "\n"
@@ -1326,7 +1369,7 @@ msgstr ""
 "\n"
 "自動的に再スキャンを開始します。\n"
 
-#: lib/commit.tcl:156
+#: lib/commit.tcl:172
 #, tcl-format
 msgid ""
 "Unmerged files cannot be committed.\n"
@@ -1339,7 +1382,7 @@ msgstr ""
 "ファイル %s にはマージ衝突が残っています。まず解決してコミット予定に加える必"
 "要があります。\n"
 
-#: lib/commit.tcl:164
+#: lib/commit.tcl:180
 #, tcl-format
 msgid ""
 "Unknown file state %s detected.\n"
@@ -1350,7 +1393,7 @@ msgstr ""
 "\n"
 "ファイル %s は本プログラムではコミットできません。\n"
 
-#: lib/commit.tcl:172
+#: lib/commit.tcl:188
 msgid ""
 "No changes to commit.\n"
 "\n"
@@ -1360,7 +1403,7 @@ msgstr ""
 "\n"
 "最低一つの変更をコミット予定に加えてからコミットして下さい。\n"
 
-#: lib/commit.tcl:187
+#: lib/commit.tcl:203
 msgid ""
 "Please supply a commit message.\n"
 "\n"
@@ -1378,45 +1421,40 @@ msgstr ""
 "- 第2行: 空白\n"
 "- 残りの行: なぜ、この変更が良い変更か、の説明。\n"
 
-#: lib/commit.tcl:211
-#, tcl-format
-msgid "warning: Tcl does not support encoding '%s'."
-msgstr "警告: Tcl はエンコーディング '%s' をサポートしていません"
-
-#: lib/commit.tcl:227
+#: lib/commit.tcl:234
 msgid "Calling pre-commit hook..."
 msgstr "コミット前フックを実行中・・・"
 
-#: lib/commit.tcl:242
+#: lib/commit.tcl:249
 msgid "Commit declined by pre-commit hook."
 msgstr "コミット前フックがコミットを拒否しました"
 
-#: lib/commit.tcl:265
+#: lib/commit.tcl:272
 msgid "Calling commit-msg hook..."
 msgstr "コミット・メッセージ・フックを実行中・・・"
 
-#: lib/commit.tcl:280
+#: lib/commit.tcl:287
 msgid "Commit declined by commit-msg hook."
 msgstr "コミット・メッセージ・フックがコミットを拒否しました"
 
-#: lib/commit.tcl:293
+#: lib/commit.tcl:300
 msgid "Committing changes..."
 msgstr "変更点をコミット中・・・"
 
-#: lib/commit.tcl:309
+#: lib/commit.tcl:316
 msgid "write-tree failed:"
 msgstr "write-tree が失敗しました:"
 
-#: lib/commit.tcl:310 lib/commit.tcl:354 lib/commit.tcl:374
+#: lib/commit.tcl:317 lib/commit.tcl:361 lib/commit.tcl:382
 msgid "Commit failed."
 msgstr "コミットに失敗しました。"
 
-#: lib/commit.tcl:327
+#: lib/commit.tcl:334
 #, tcl-format
 msgid "Commit %s appears to be corrupt"
 msgstr "コミット %s は壊れています"
 
-#: lib/commit.tcl:332
+#: lib/commit.tcl:339
 msgid ""
 "No changes to commit.\n"
 "\n"
@@ -1430,19 +1468,19 @@ msgstr ""
 "\n"
 "自動的に再スキャンを開始します。\n"
 
-#: lib/commit.tcl:339
+#: lib/commit.tcl:346
 msgid "No changes to commit."
 msgstr "コミットする変更がありません。"
 
-#: lib/commit.tcl:353
+#: lib/commit.tcl:360
 msgid "commit-tree failed:"
 msgstr "commit-tree が失敗しました:"
 
-#: lib/commit.tcl:373
+#: lib/commit.tcl:381
 msgid "update-ref failed:"
 msgstr "update-ref が失敗しました:"
 
-#: lib/commit.tcl:461
+#: lib/commit.tcl:469
 #, tcl-format
 msgid "Created commit %s: %s"
 msgstr "コミット %s を作成しました: %s"
@@ -1495,20 +1533,19 @@ msgstr "データベース圧縮"
 msgid "Verifying the object database with fsck-objects"
 msgstr "fsck-objects でオブジェクト・データベースを検証しています"
 
-#: lib/database.tcl:108
+#: lib/database.tcl:107
 #, tcl-format
 msgid ""
 "This repository currently has approximately %i loose objects.\n"
 "\n"
 "To maintain optimal performance it is strongly recommended that you compress "
-"the database when more than %i loose objects exist.\n"
+"the database.\n"
 "\n"
 "Compress the database now?"
 msgstr ""
 "このリポジトリにはおおよそ %i 個の個別オブジェクトがあります\n"
 "\n"
-"最適な性能を保つために、%i 個以上の個別オブジェクトを作る毎にデータベースを圧"
-"縮することを推奨します\n"
+"最適な性能を保つために、データベースを圧縮することを推奨します\n"
 "\n"
 "データベースを圧縮しますか?"
 
@@ -1517,7 +1554,7 @@ msgstr ""
 msgid "Invalid date from Git: %s"
 msgstr "Git から出た無効な日付: %s"
 
-#: lib/diff.tcl:59
+#: lib/diff.tcl:64
 #, tcl-format
 msgid ""
 "No differences detected.\n"
@@ -1539,12 +1576,12 @@ msgstr ""
 "\n"
 "同様な状態のファイルを探すために、自動的に再スキャンを開始します。"
 
-#: lib/diff.tcl:99
+#: lib/diff.tcl:104
 #, tcl-format
 msgid "Loading diff of %s..."
 msgstr "%s の変更点をロード中…"
 
-#: lib/diff.tcl:120
+#: lib/diff.tcl:125
 msgid ""
 "LOCAL: deleted\n"
 "REMOTE:\n"
@@ -1552,7 +1589,7 @@ msgstr ""
 "LOCAL: 削除\n"
 "Remote:\n"
 
-#: lib/diff.tcl:125
+#: lib/diff.tcl:130
 msgid ""
 "REMOTE: deleted\n"
 "LOCAL:\n"
@@ -1560,32 +1597,32 @@ msgstr ""
 "REMOTE: 削除\n"
 "LOCAL:\n"
 
-#: lib/diff.tcl:132
+#: lib/diff.tcl:137
 msgid "LOCAL:\n"
 msgstr "LOCAL:\n"
 
-#: lib/diff.tcl:135
+#: lib/diff.tcl:140
 msgid "REMOTE:\n"
 msgstr "REMOTE\n"
 
-#: lib/diff.tcl:197 lib/diff.tcl:296
+#: lib/diff.tcl:202 lib/diff.tcl:319
 #, tcl-format
 msgid "Unable to display %s"
 msgstr "%s を表示できません"
 
-#: lib/diff.tcl:198
+#: lib/diff.tcl:203
 msgid "Error loading file:"
 msgstr "ファイルを読む際のエラーです:"
 
-#: lib/diff.tcl:205
+#: lib/diff.tcl:210
 msgid "Git Repository (subproject)"
 msgstr "Git リポジトリ(サブプロジェクト)"
 
-#: lib/diff.tcl:217
+#: lib/diff.tcl:222
 msgid "* Binary file (not showing content)."
 msgstr "* バイナリファイル(内容は表示しません)"
 
-#: lib/diff.tcl:222
+#: lib/diff.tcl:227
 #, tcl-format
 msgid ""
 "* Untracked file is %d bytes.\n"
@@ -1594,7 +1631,7 @@ msgstr ""
 "* 管理外のファイルの大きさは %d バイトです。\n"
 "* 最初の %d バイトだけ表示しています。\n"
 
-#: lib/diff.tcl:228
+#: lib/diff.tcl:233
 #, tcl-format
 msgid ""
 "\n"
@@ -1606,19 +1643,19 @@ msgstr ""
 "* %s は管理外のファイルをここで切りおとしました。\n"
 "* 全体を見るには外部エディタを使ってください。\n"
 
-#: lib/diff.tcl:436
+#: lib/diff.tcl:482
 msgid "Failed to unstage selected hunk."
 msgstr "選択されたパッチをコミット予定から外せません。"
 
-#: lib/diff.tcl:443
+#: lib/diff.tcl:489
 msgid "Failed to stage selected hunk."
 msgstr "選択されたパッチをコミット予定に加えられません。"
 
-#: lib/diff.tcl:509
+#: lib/diff.tcl:568
 msgid "Failed to unstage selected line."
 msgstr "選択されたパッチ行をコミット予定から外せません。"
 
-#: lib/diff.tcl:517
+#: lib/diff.tcl:576
 msgid "Failed to stage selected line."
 msgstr "選択されたパッチ行をコミット予定に加えられません。"
 
@@ -1655,7 +1692,7 @@ msgstr "インデックスをロックできません"
 msgid "Index Error"
 msgstr "索引エラー"
 
-#: lib/index.tcl:21
+#: lib/index.tcl:17
 msgid ""
 "Updating the Git index failed.  A rescan will be automatically started to "
 "resynchronize git-gui."
@@ -1663,7 +1700,7 @@ msgstr ""
 "GIT インデックスの更新が失敗しました。git-gui と同期をとるために再スキャンし"
 "ます。"
 
-#: lib/index.tcl:27
+#: lib/index.tcl:28
 msgid "Continue"
 msgstr "続行"
 
@@ -1671,43 +1708,43 @@ msgstr "続行"
 msgid "Unlock Index"
 msgstr "インデックスのロック解除"
 
-#: lib/index.tcl:287
+#: lib/index.tcl:289
 #, tcl-format
 msgid "Unstaging %s from commit"
 msgstr "コミットから '%s' を降ろす"
 
-#: lib/index.tcl:326
+#: lib/index.tcl:328
 msgid "Ready to commit."
 msgstr "コミット準備完了"
 
-#: lib/index.tcl:339
+#: lib/index.tcl:341
 #, tcl-format
 msgid "Adding %s"
 msgstr "コミットに %s を加えています"
 
-#: lib/index.tcl:396
+#: lib/index.tcl:398
 #, tcl-format
 msgid "Revert changes in file %s?"
 msgstr "ファイル %s にした変更を元に戻しますか?"
 
-#: lib/index.tcl:398
+#: lib/index.tcl:400
 #, tcl-format
 msgid "Revert changes in these %i files?"
 msgstr "これら %i 個のファイルにした変更を元に戻しますか?"
 
-#: lib/index.tcl:406
+#: lib/index.tcl:408
 msgid "Any unstaged changes will be permanently lost by the revert."
 msgstr "変更を元に戻すとコミット予定していない変更は全て失われます。"
 
-#: lib/index.tcl:409
+#: lib/index.tcl:411
 msgid "Do Nothing"
 msgstr "何もしない"
 
-#: lib/index.tcl:427
+#: lib/index.tcl:429
 msgid "Reverting selected files"
 msgstr "選択されたファイルにした変更を元に戻します"
 
-#: lib/index.tcl:431
+#: lib/index.tcl:433
 #, tcl-format
 msgid "Reverting %s"
 msgstr "%s にした変更を元に戻します"
@@ -1883,7 +1920,8 @@ msgstr ""
 #: lib/mergetool.tcl:45
 #, tcl-format
 msgid "File %s seems to have unresolved conflicts, still stage?"
-msgstr "ファイル %s には解決していない競合部分がまだあるようですが、いいですか?"
+msgstr ""
+"ファイル %s には解決していない競合部分がまだあるようですが、いいですか?"
 
 #: lib/mergetool.tcl:60
 #, tcl-format
@@ -2141,7 +2179,8 @@ msgstr "%s からフェッチしています"
 msgid "Do not know how to initialize repository at location '%s'."
 msgstr "リポジトリ '%s' を初期化できません。"
 
-#: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:71
+#: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:63
+#: lib/transport.tcl:81
 #, tcl-format
 msgid "push %s"
 msgstr "%s をプッシュ"
@@ -2159,11 +2198,11 @@ msgstr "遠隔でブランチ削除"
 msgid "From Repository"
 msgstr "元のリポジトリ"
 
-#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123
+#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:134
 msgid "Remote:"
 msgstr "リモート:"
 
-#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138
+#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:149
 msgid "Arbitrary Location:"
 msgstr "任意の位置:"
 
@@ -2179,10 +2218,6 @@ msgstr "条件付で削除"
 msgid "Merged Into:"
 msgstr "マージ先:"
 
-#: lib/remote_branch_delete.tcl:119
-msgid "Always (Do not perform merge checks)"
-msgstr "無条件(マージ検査をしない)"
-
 #: lib/remote_branch_delete.tcl:152
 msgid "A branch is required for 'Merged Into'."
 msgstr "'マージ先' にはブランチが必要です。"
@@ -2211,26 +2246,16 @@ msgstr ""
 msgid "Please select one or more branches to delete."
 msgstr "削除するブランチを選択して下さい。"
 
-#: lib/remote_branch_delete.tcl:216
-msgid ""
-"Recovering deleted branches is difficult.\n"
-"\n"
-"Delete the selected branches?"
-msgstr ""
-"削除したブランチを回復するのは困難です。\n"
-"\n"
-"選択したブランチを削除して良いですか?"
-
 #: lib/remote_branch_delete.tcl:226
 #, tcl-format
 msgid "Deleting branches from %s"
 msgstr "%s からブランチを削除しています。"
 
-#: lib/remote_branch_delete.tcl:286
+#: lib/remote_branch_delete.tcl:292
 msgid "No repository selected."
 msgstr "リポジトリが選択されていません。"
 
-#: lib/remote_branch_delete.tcl:291
+#: lib/remote_branch_delete.tcl:297
 #, tcl-format
 msgid "Scanning %s..."
 msgstr "%s をスキャンしています…"
@@ -2251,11 +2276,11 @@ msgstr "前"
 msgid "Case-Sensitive"
 msgstr "大文字小文字を区別"
 
-#: lib/shortcut.tcl:20 lib/shortcut.tcl:61
+#: lib/shortcut.tcl:21 lib/shortcut.tcl:62
 msgid "Cannot write shortcut:"
 msgstr "ショートカットが書けません:"
 
-#: lib/shortcut.tcl:136
+#: lib/shortcut.tcl:137
 msgid "Cannot write icon:"
 msgstr "アイコンが書けません:"
 
@@ -2501,30 +2526,30 @@ msgstr "%s へミラーしています"
 msgid "Pushing %s %s to %s"
 msgstr "%3$s へ %1$s %2$s をプッシュしています"
 
-#: lib/transport.tcl:89
+#: lib/transport.tcl:100
 msgid "Push Branches"
 msgstr "ブランチをプッシュ"
 
-#: lib/transport.tcl:103
+#: lib/transport.tcl:114
 msgid "Source Branches"
 msgstr "元のブランチ"
 
-#: lib/transport.tcl:120
+#: lib/transport.tcl:131
 msgid "Destination Repository"
 msgstr "送り先リポジトリ"
 
-#: lib/transport.tcl:158
+#: lib/transport.tcl:169
 msgid "Transfer Options"
 msgstr "通信オプション"
 
-#: lib/transport.tcl:160
+#: lib/transport.tcl:171
 msgid "Force overwrite existing branch (may discard changes)"
 msgstr "既存ブランチを上書き(変更を破棄する可能性があります)"
 
-#: lib/transport.tcl:164
+#: lib/transport.tcl:175
 msgid "Use thin pack (for slow network connections)"
 msgstr "Thin Pack を使う(遅いネットワーク接続)"
 
-#: lib/transport.tcl:168
+#: lib/transport.tcl:179
 msgid "Include tags"
 msgstr "タグを含める"
index 0ffc4a418fccb4453c1601ca87668e2d185e14db..364c074c504ce2150effdbf01927aa052107c1af 100644 (file)
@@ -90,12 +90,18 @@ msgstr "Вызов программы поддержки репозитория
 
 #: git-gui.sh:1384
 msgid "Commit declined by prepare-commit-msg hook."
-msgstr "Сохранение прервано программой поддержки репозитория prepare-commit-msg"
+msgstr ""
+"Сохранение прервано программой поддержки репозитория prepare-commit-msg"
 
 #: git-gui.sh:1542 lib/browser.tcl:246
 msgid "Ready."
 msgstr "Готово."
 
+#: git-gui.sh:1726
+#, tcl-format
+msgid "Displaying only %s of %s files."
+msgstr "Показано %s из %s файлов."
+
 #: git-gui.sh:1819
 msgid "Unmodified"
 msgstr "Не изменено"
@@ -1297,8 +1303,8 @@ msgid ""
 msgstr ""
 "Невозможно исправить состояние во время операции слияния.\n"
 "\n"
-"Текущее слияние не завершено. Невозможно исправить предыдущее "
-"Ñ\81оÑ\85Ñ\80аненное Ñ\81оÑ\81Ñ\82оÑ\8fние, Ð½Ðµ Ð¿Ñ\80еÑ\80Ñ\8bваÑ\8f Ñ\8dÑ\82Ñ\83 Ð¾Ð¿ÐµÑ\80аÑ\86иÑ\8e.\n"
+"Текущее слияние не завершено. Невозможно исправить предыдущее сохраненное "
+"состояние, не прерывая эту операцию.\n"
 
 #: lib/commit.tcl:48
 msgid "Error loading commit data for amend:"
@@ -1723,8 +1729,7 @@ msgid ""
 msgstr ""
 "Невозможно выполнить слияние во время исправления.\n"
 "\n"
-"Завершите исправление данного состояния перед выполнением операции "
-"слияния.\n"
+"Завершите исправление данного состояния перед выполнением операции слияния.\n"
 
 #: lib/merge.tcl:27
 msgid ""
@@ -1888,8 +1893,8 @@ msgstr ""
 #, tcl-format
 msgid "File %s seems to have unresolved conflicts, still stage?"
 msgstr ""
-"Файл %s кажется содержит необработаные конфликты. "
-"Продолжить подготовку к сохранению?"
+"Файл %s кажется содержит необработаные конфликты. Продолжить подготовку к "
+"сохранению?"
 
 #: lib/mergetool.tcl:60
 #, tcl-format
@@ -2213,8 +2218,8 @@ msgid ""
 "One or more of the merge tests failed because you have not fetched the "
 "necessary commits.  Try fetching from %s first."
 msgstr ""
-"Некоторые тесты на слияние не прошли, потому что Вы не "
-"получили необходимые состояния. Попытайтесь получить их из %s."
+"Некоторые тесты на слияние не прошли, потому что Вы не получили необходимые "
+"состояния. Попытайтесь получить их из %s."
 
 #: lib/remote_branch_delete.tcl:207
 msgid "Please select one or more branches to delete."
@@ -2381,8 +2386,8 @@ msgstr "Выполнение: %s"
 
 #: lib/tools.tcl:149
 #, tcl-format
-msgid "Tool completed succesfully: %s"
-msgstr "Программа %s успешно завершилась."
+msgid "Tool completed successfully: %s"
+msgstr "Программа %s завершилась успешно."
 
 #: lib/tools.tcl:151
 #, tcl-format
@@ -2538,4 +2543,3 @@ msgstr "Использовать thin pack (для медленных сетев
 #: lib/transport.tcl:179
 msgid "Include tags"
 msgstr "Передать метки"
-
index c1535f94e86f2d619b4609965324a7c8ca0b396c..8bd3c5d75feea3dfe310adbfab705d8045aa8367 100644 (file)
@@ -8,41 +8,41 @@ msgid ""
 msgstr ""
 "Project-Id-Version: sv\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-12-08 08:31-0800\n"
-"PO-Revision-Date: 2008-12-10 09:49+0100\n"
+"POT-Creation-Date: 2010-09-12 21:11+0100\n"
+"PO-Revision-Date: 2010-09-12 21:12+0100\n"
 "Last-Translator: Peter Krefting <peter@softwolves.pp.se>\n"
 "Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
+"Content-Transfer-Encoding: 8bit"
 
-#: git-gui.sh:41 git-gui.sh:737 git-gui.sh:751 git-gui.sh:764 git-gui.sh:847
-#: git-gui.sh:866
-msgid "git-gui: fatal error"
-msgstr "git-gui: ödesdigert fel"
-
-#: git-gui.sh:689
+#: git-gui.sh:781
 #, tcl-format
 msgid "Invalid font specified in %s:"
 msgstr "Ogiltigt teckensnitt angivet i %s:"
 
-#: git-gui.sh:723
+#: git-gui.sh:831
 msgid "Main Font"
 msgstr "Huvudteckensnitt"
 
-#: git-gui.sh:724
+#: git-gui.sh:832
 msgid "Diff/Console Font"
 msgstr "Diff/konsolteckensnitt"
 
-#: git-gui.sh:738
+#: git-gui.sh:845 git-gui.sh:859 git-gui.sh:872 git-gui.sh:955 git-gui.sh:974
+#: git-gui.sh:2964
+msgid "git-gui: fatal error"
+msgstr "git-gui: ödesdigert fel"
+
+#: git-gui.sh:846
 msgid "Cannot find git in PATH."
 msgstr "Hittar inte git i PATH."
 
-#: git-gui.sh:765
+#: git-gui.sh:873
 msgid "Cannot parse Git version string:"
 msgstr "Kan inte tolka versionssträng från Git:"
 
-#: git-gui.sh:783
+#: git-gui.sh:891
 #, tcl-format
 msgid ""
 "Git version cannot be determined.\n"
@@ -61,449 +61,478 @@ msgstr ""
 "\n"
 "Anta att \"%s\" är version 1.5.0?\n"
 
-#: git-gui.sh:1062
+#: git-gui.sh:1180
 msgid "Git directory not found:"
 msgstr "Git-katalogen hittades inte:"
 
-#: git-gui.sh:1069
+#: git-gui.sh:1201
 msgid "Cannot move to top of working directory:"
 msgstr "Kan inte gå till början på arbetskatalogen:"
 
-#: git-gui.sh:1076
-msgid "Cannot use funny .git directory:"
-msgstr "Kan inte använda underlig .git-katalog:"
+#: git-gui.sh:1209
+msgid "Cannot use bare repository:"
+msgstr "Kan inte använda naket arkiv:"
 
-#: git-gui.sh:1081
+#: git-gui.sh:1217
 msgid "No working directory"
 msgstr "Ingen arbetskatalog"
 
-#: git-gui.sh:1247 lib/checkout_op.tcl:305
+#: git-gui.sh:1389 lib/checkout_op.tcl:306
 msgid "Refreshing file status..."
 msgstr "Uppdaterar filstatus..."
 
-#: git-gui.sh:1303
+#: git-gui.sh:1445
 msgid "Scanning for modified files ..."
 msgstr "Söker efter ändrade filer..."
 
-#: git-gui.sh:1367
+#: git-gui.sh:1509
 msgid "Calling prepare-commit-msg hook..."
 msgstr ""
 "Anropar kroken för förberedelse av incheckningsmeddelande (prepare-commit-"
 "msg)..."
 
-#: git-gui.sh:1384
+#: git-gui.sh:1526
 msgid "Commit declined by prepare-commit-msg hook."
 msgstr ""
 "Incheckningen avvisades av kroken för förberedelse av incheckningsmeddelande "
 "(prepare-commit-msg)."
 
-#: git-gui.sh:1542 lib/browser.tcl:246
+#: git-gui.sh:1684 lib/browser.tcl:246
 msgid "Ready."
 msgstr "Klar."
 
-#: git-gui.sh:1819
+#: git-gui.sh:1842
+#, tcl-format
+msgid "Displaying only %s of %s files."
+msgstr "Visar endast %s av %s filer."
+
+#: git-gui.sh:1968
 msgid "Unmodified"
 msgstr "Oförändrade"
 
-#: git-gui.sh:1821
+#: git-gui.sh:1970
 msgid "Modified, not staged"
 msgstr "Förändrade, ej köade"
 
-#: git-gui.sh:1822 git-gui.sh:1830
+#: git-gui.sh:1971 git-gui.sh:1979
 msgid "Staged for commit"
 msgstr "Köade för incheckning"
 
-#: git-gui.sh:1823 git-gui.sh:1831
+#: git-gui.sh:1972 git-gui.sh:1980
 msgid "Portions staged for commit"
 msgstr "Delar köade för incheckning"
 
-#: git-gui.sh:1824 git-gui.sh:1832
+#: git-gui.sh:1973 git-gui.sh:1981
 msgid "Staged for commit, missing"
 msgstr "Köade för incheckning, saknade"
 
-#: git-gui.sh:1826
+#: git-gui.sh:1975
 msgid "File type changed, not staged"
 msgstr "Filtyp ändrad, ej köade"
 
-#: git-gui.sh:1827
+#: git-gui.sh:1976
 msgid "File type changed, staged"
 msgstr "Filtyp ändrad, köade"
 
-#: git-gui.sh:1829
+#: git-gui.sh:1978
 msgid "Untracked, not staged"
 msgstr "Ej spårade, ej köade"
 
-#: git-gui.sh:1834
+#: git-gui.sh:1983
 msgid "Missing"
 msgstr "Saknade"
 
-#: git-gui.sh:1835
+#: git-gui.sh:1984
 msgid "Staged for removal"
 msgstr "Köade för borttagning"
 
-#: git-gui.sh:1836
+#: git-gui.sh:1985
 msgid "Staged for removal, still present"
 msgstr "Köade för borttagning, fortfarande närvarande"
 
-#: git-gui.sh:1838 git-gui.sh:1839 git-gui.sh:1840 git-gui.sh:1841
-#: git-gui.sh:1842 git-gui.sh:1843
+#: git-gui.sh:1987 git-gui.sh:1988 git-gui.sh:1989 git-gui.sh:1990
+#: git-gui.sh:1991 git-gui.sh:1992
 msgid "Requires merge resolution"
 msgstr "Kräver konflikthantering efter sammanslagning"
 
-#: git-gui.sh:1878
+#: git-gui.sh:2027
 msgid "Starting gitk... please wait..."
 msgstr "Startar gitk... vänta..."
 
-#: git-gui.sh:1887
+#: git-gui.sh:2039
 msgid "Couldn't find gitk in PATH"
-msgstr "Hittar inte gitk i PATH."
+msgstr "Hittade inte gitk i PATH."
+
+#: git-gui.sh:2098
+msgid "Couldn't find git gui in PATH"
+msgstr "Hittade inte git gui i PATH."
 
-#: git-gui.sh:2280 lib/choose_repository.tcl:36
+#: git-gui.sh:2515 lib/choose_repository.tcl:36
 msgid "Repository"
 msgstr "Arkiv"
 
-#: git-gui.sh:2281
+#: git-gui.sh:2516
 msgid "Edit"
 msgstr "Redigera"
 
-#: git-gui.sh:2283 lib/choose_rev.tcl:561
+#: git-gui.sh:2518 lib/choose_rev.tcl:566
 msgid "Branch"
 msgstr "Gren"
 
-#: git-gui.sh:2286 lib/choose_rev.tcl:548
+#: git-gui.sh:2521 lib/choose_rev.tcl:553
 msgid "Commit@@noun"
 msgstr "Incheckning"
 
-#: git-gui.sh:2289 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
+#: git-gui.sh:2524 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
 msgid "Merge"
 msgstr "Slå ihop"
 
-#: git-gui.sh:2290 lib/choose_rev.tcl:557
+#: git-gui.sh:2525 lib/choose_rev.tcl:562
 msgid "Remote"
 msgstr "Fjärrarkiv"
 
-#: git-gui.sh:2293
+#: git-gui.sh:2528
 msgid "Tools"
 msgstr "Verktyg"
 
-#: git-gui.sh:2302
+#: git-gui.sh:2537
 msgid "Explore Working Copy"
 msgstr "Utforska arbetskopia"
 
-#: git-gui.sh:2307
+#: git-gui.sh:2543
 msgid "Browse Current Branch's Files"
 msgstr "Bläddra i grenens filer"
 
-#: git-gui.sh:2311
+#: git-gui.sh:2547
 msgid "Browse Branch Files..."
 msgstr "Bläddra filer på gren..."
 
-#: git-gui.sh:2316
+#: git-gui.sh:2552
 msgid "Visualize Current Branch's History"
 msgstr "Visualisera grenens historik"
 
-#: git-gui.sh:2320
+#: git-gui.sh:2556
 msgid "Visualize All Branch History"
 msgstr "Visualisera alla grenars historik"
 
-#: git-gui.sh:2327
+#: git-gui.sh:2563
 #, tcl-format
 msgid "Browse %s's Files"
 msgstr "Bläddra i filer för %s"
 
-#: git-gui.sh:2329
+#: git-gui.sh:2565
 #, tcl-format
 msgid "Visualize %s's History"
 msgstr "Visualisera historik för %s"
 
-#: git-gui.sh:2334 lib/database.tcl:27 lib/database.tcl:67
+#: git-gui.sh:2570 lib/database.tcl:40 lib/database.tcl:66
 msgid "Database Statistics"
 msgstr "Databasstatistik"
 
-#: git-gui.sh:2337 lib/database.tcl:34
+#: git-gui.sh:2573 lib/database.tcl:33
 msgid "Compress Database"
 msgstr "Komprimera databas"
 
-#: git-gui.sh:2340
+#: git-gui.sh:2576
 msgid "Verify Database"
 msgstr "Verifiera databas"
 
-#: git-gui.sh:2347 git-gui.sh:2351 git-gui.sh:2355 lib/shortcut.tcl:7
-#: lib/shortcut.tcl:39 lib/shortcut.tcl:71
+#: git-gui.sh:2583 git-gui.sh:2587 git-gui.sh:2591 lib/shortcut.tcl:8
+#: lib/shortcut.tcl:40 lib/shortcut.tcl:72
 msgid "Create Desktop Icon"
 msgstr "Skapa skrivbordsikon"
 
-#: git-gui.sh:2363 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191
+#: git-gui.sh:2599 lib/choose_repository.tcl:188 lib/choose_repository.tcl:196
 msgid "Quit"
 msgstr "Avsluta"
 
-#: git-gui.sh:2371
+#: git-gui.sh:2607
 msgid "Undo"
 msgstr "Ångra"
 
-#: git-gui.sh:2374
+#: git-gui.sh:2610
 msgid "Redo"
 msgstr "Gör om"
 
-#: git-gui.sh:2378 git-gui.sh:2937
+#: git-gui.sh:2614 git-gui.sh:3190
 msgid "Cut"
 msgstr "Klipp ut"
 
-#: git-gui.sh:2381 git-gui.sh:2940 git-gui.sh:3014 git-gui.sh:3096
+#: git-gui.sh:2617 git-gui.sh:3193 git-gui.sh:3267 git-gui.sh:3340
 #: lib/console.tcl:69
 msgid "Copy"
 msgstr "Kopiera"
 
-#: git-gui.sh:2384 git-gui.sh:2943
+#: git-gui.sh:2620 git-gui.sh:3196
 msgid "Paste"
 msgstr "Klistra in"
 
-#: git-gui.sh:2387 git-gui.sh:2946 lib/branch_delete.tcl:26
-#: lib/remote_branch_delete.tcl:38
+#: git-gui.sh:2623 git-gui.sh:3199 lib/branch_delete.tcl:28
+#: lib/remote_branch_delete.tcl:39
 msgid "Delete"
 msgstr "Ta bort"
 
-#: git-gui.sh:2391 git-gui.sh:2950 git-gui.sh:3100 lib/console.tcl:71
+#: git-gui.sh:2627 git-gui.sh:3203 git-gui.sh:3344 lib/console.tcl:71
 msgid "Select All"
 msgstr "Markera alla"
 
-#: git-gui.sh:2400
+#: git-gui.sh:2636
 msgid "Create..."
 msgstr "Skapa..."
 
-#: git-gui.sh:2406
+#: git-gui.sh:2642
 msgid "Checkout..."
 msgstr "Checka ut..."
 
-#: git-gui.sh:2412
+#: git-gui.sh:2648
 msgid "Rename..."
 msgstr "Byt namn..."
 
-#: git-gui.sh:2417
+#: git-gui.sh:2653
 msgid "Delete..."
 msgstr "Ta bort..."
 
-#: git-gui.sh:2422
+#: git-gui.sh:2658
 msgid "Reset..."
 msgstr "Återställ..."
 
-#: git-gui.sh:2432
+#: git-gui.sh:2668
 msgid "Done"
 msgstr "Färdig"
 
-#: git-gui.sh:2434
+#: git-gui.sh:2670
 msgid "Commit@@verb"
 msgstr "Checka in"
 
-#: git-gui.sh:2443 git-gui.sh:2878
+#: git-gui.sh:2679 git-gui.sh:3131
 msgid "New Commit"
 msgstr "Ny incheckning"
 
-#: git-gui.sh:2451 git-gui.sh:2885
+#: git-gui.sh:2687 git-gui.sh:3138
 msgid "Amend Last Commit"
 msgstr "Lägg till föregående incheckning"
 
-#: git-gui.sh:2461 git-gui.sh:2839 lib/remote_branch_delete.tcl:99
+#: git-gui.sh:2697 git-gui.sh:3092 lib/remote_branch_delete.tcl:101
 msgid "Rescan"
 msgstr "Sök på nytt"
 
-#: git-gui.sh:2467
+#: git-gui.sh:2703
 msgid "Stage To Commit"
 msgstr "Köa för incheckning"
 
-#: git-gui.sh:2473
+#: git-gui.sh:2709
 msgid "Stage Changed Files To Commit"
 msgstr "Köa ändrade filer för incheckning"
 
-#: git-gui.sh:2479
+#: git-gui.sh:2715
 msgid "Unstage From Commit"
 msgstr "Ta bort från incheckningskö"
 
-#: git-gui.sh:2484 lib/index.tcl:410
+#: git-gui.sh:2721 lib/index.tcl:415
 msgid "Revert Changes"
 msgstr "Återställ ändringar"
 
-#: git-gui.sh:2491 git-gui.sh:3083
+#: git-gui.sh:2729 git-gui.sh:3391 git-gui.sh:3422
 msgid "Show Less Context"
 msgstr "Visa mindre sammanhang"
 
-#: git-gui.sh:2495 git-gui.sh:3087
+#: git-gui.sh:2733 git-gui.sh:3395 git-gui.sh:3426
 msgid "Show More Context"
 msgstr "Visa mer sammanhang"
 
-#: git-gui.sh:2502 git-gui.sh:2852 git-gui.sh:2961
+#: git-gui.sh:2740 git-gui.sh:3105 git-gui.sh:3214
 msgid "Sign Off"
 msgstr "Skriv under"
 
-#: git-gui.sh:2518
+#: git-gui.sh:2756
 msgid "Local Merge..."
 msgstr "Lokal sammanslagning..."
 
-#: git-gui.sh:2523
+#: git-gui.sh:2761
 msgid "Abort Merge..."
 msgstr "Avbryt sammanslagning..."
 
-#: git-gui.sh:2535 git-gui.sh:2575
+#: git-gui.sh:2773 git-gui.sh:2801
 msgid "Add..."
 msgstr "Lägg till..."
 
-#: git-gui.sh:2539
+#: git-gui.sh:2777
 msgid "Push..."
 msgstr "Sänd..."
 
-#: git-gui.sh:2543
+#: git-gui.sh:2781
 msgid "Delete Branch..."
 msgstr "Ta bort gren..."
 
-#: git-gui.sh:2553 git-gui.sh:2589 lib/about.tcl:14
-#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53
-#, tcl-format
-msgid "About %s"
-msgstr "Om %s"
-
-#: git-gui.sh:2557
-msgid "Preferences..."
-msgstr "Inställningar..."
-
-#: git-gui.sh:2565 git-gui.sh:3129
+#: git-gui.sh:2791 git-gui.sh:3373
 msgid "Options..."
 msgstr "Alternativ..."
 
-#: git-gui.sh:2576
+#: git-gui.sh:2802
 msgid "Remove..."
 msgstr "Ta bort..."
 
-#: git-gui.sh:2585 lib/choose_repository.tcl:50
+#: git-gui.sh:2811 lib/choose_repository.tcl:50
 msgid "Help"
 msgstr "Hjälp"
 
-#: git-gui.sh:2611
+#: git-gui.sh:2815 git-gui.sh:2819 lib/about.tcl:14
+#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53
+#, tcl-format
+msgid "About %s"
+msgstr "Om %s"
+
+#: git-gui.sh:2843
 msgid "Online Documentation"
 msgstr "Webbdokumentation"
 
-#: git-gui.sh:2614 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
+#: git-gui.sh:2846 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
 msgid "Show SSH Key"
 msgstr "Visa SSH-nyckel"
 
-#: git-gui.sh:2721
+#: git-gui.sh:2965
 #, tcl-format
 msgid "fatal: cannot stat path %s: No such file or directory"
 msgstr ""
 "ödesdigert: kunde inte ta status på sökvägen %s: Fil eller katalog saknas"
 
-#: git-gui.sh:2754
+#: git-gui.sh:2997
 msgid "Current Branch:"
 msgstr "Aktuell gren:"
 
-#: git-gui.sh:2775
+#: git-gui.sh:3023
 msgid "Staged Changes (Will Commit)"
 msgstr "Köade ändringar (kommer att checkas in)"
 
-#: git-gui.sh:2795
+#: git-gui.sh:3043
 msgid "Unstaged Changes"
 msgstr "Oköade ändringar"
 
-#: git-gui.sh:2845
+#: git-gui.sh:3098
 msgid "Stage Changed"
 msgstr "Köa ändrade"
 
-#: git-gui.sh:2864 lib/transport.tcl:104 lib/transport.tcl:193
+#: git-gui.sh:3117 lib/transport.tcl:107 lib/transport.tcl:196
 msgid "Push"
 msgstr "Sänd"
 
-#: git-gui.sh:2899
+#: git-gui.sh:3152
 msgid "Initial Commit Message:"
 msgstr "Inledande incheckningsmeddelande:"
 
-#: git-gui.sh:2900
+#: git-gui.sh:3153
 msgid "Amended Commit Message:"
 msgstr "Utökat incheckningsmeddelande:"
 
-#: git-gui.sh:2901
+#: git-gui.sh:3154
 msgid "Amended Initial Commit Message:"
 msgstr "Utökat inledande incheckningsmeddelande:"
 
-#: git-gui.sh:2902
+#: git-gui.sh:3155
 msgid "Amended Merge Commit Message:"
 msgstr "Utökat incheckningsmeddelande för sammanslagning:"
 
-#: git-gui.sh:2903
+#: git-gui.sh:3156
 msgid "Merge Commit Message:"
 msgstr "Incheckningsmeddelande för sammanslagning:"
 
-#: git-gui.sh:2904
+#: git-gui.sh:3157
 msgid "Commit Message:"
 msgstr "Incheckningsmeddelande:"
 
-#: git-gui.sh:2953 git-gui.sh:3104 lib/console.tcl:73
+#: git-gui.sh:3206 git-gui.sh:3348 lib/console.tcl:73
 msgid "Copy All"
 msgstr "Kopiera alla"
 
-#: git-gui.sh:2977 lib/blame.tcl:104
+#: git-gui.sh:3230 lib/blame.tcl:104
 msgid "File:"
 msgstr "Fil:"
 
-#: git-gui.sh:3092
+#: git-gui.sh:3336
 msgid "Refresh"
 msgstr "Uppdatera"
 
-#: git-gui.sh:3113
+#: git-gui.sh:3357
 msgid "Decrease Font Size"
 msgstr "Minska teckensnittsstorlek"
 
-#: git-gui.sh:3117
+#: git-gui.sh:3361
 msgid "Increase Font Size"
 msgstr "Öka teckensnittsstorlek"
 
-#: git-gui.sh:3125 lib/blame.tcl:281
+#: git-gui.sh:3369 lib/blame.tcl:281
 msgid "Encoding"
 msgstr "Teckenkodning"
 
-#: git-gui.sh:3136
+#: git-gui.sh:3380
 msgid "Apply/Reverse Hunk"
 msgstr "Använd/återställ del"
 
-#: git-gui.sh:3141
+#: git-gui.sh:3385
 msgid "Apply/Reverse Line"
 msgstr "Använd/återställ rad"
 
-#: git-gui.sh:3151
+#: git-gui.sh:3404
 msgid "Run Merge Tool"
 msgstr "Starta verktyg för sammanslagning"
 
-#: git-gui.sh:3156
+#: git-gui.sh:3409
 msgid "Use Remote Version"
 msgstr "Använd versionen från fjärrarkivet"
 
-#: git-gui.sh:3160
+#: git-gui.sh:3413
 msgid "Use Local Version"
 msgstr "Använd lokala versionen"
 
-#: git-gui.sh:3164
+#: git-gui.sh:3417
 msgid "Revert To Base"
 msgstr "Återställ till basversionen"
 
-#: git-gui.sh:3183
+#: git-gui.sh:3435
+msgid "Visualize These Changes In The Submodule"
+msgstr "Visualisera ändringarna i undermodulen"
+
+#: git-gui.sh:3439
+msgid "Visualize Current Branch History In The Submodule"
+msgstr "Visualisera grenens historik i undermodulen"
+
+#: git-gui.sh:3443
+msgid "Visualize All Branch History In The Submodule"
+msgstr "Visualisera alla grenars historik i undermodulen"
+
+#: git-gui.sh:3448
+msgid "Start git gui In The Submodule"
+msgstr "Starta git gui i undermodulen"
+
+#: git-gui.sh:3483
 msgid "Unstage Hunk From Commit"
 msgstr "Ta bort del ur incheckningskö"
 
-#: git-gui.sh:3184
+#: git-gui.sh:3485
+msgid "Unstage Lines From Commit"
+msgstr "Ta bort rader ur incheckningskö"
+
+#: git-gui.sh:3487
 msgid "Unstage Line From Commit"
 msgstr "Ta bort rad ur incheckningskö"
 
-#: git-gui.sh:3186
+#: git-gui.sh:3490
 msgid "Stage Hunk For Commit"
 msgstr "Ställ del i incheckningskö"
 
-#: git-gui.sh:3187
+#: git-gui.sh:3492
+msgid "Stage Lines For Commit"
+msgstr "Ställ rader i incheckningskö"
+
+#: git-gui.sh:3494
 msgid "Stage Line For Commit"
 msgstr "Ställ rad i incheckningskö"
 
-#: git-gui.sh:3210
+#: git-gui.sh:3519
 msgid "Initializing..."
 msgstr "Initierar..."
 
-#: git-gui.sh:3315
+#: git-gui.sh:3658
 #, tcl-format
 msgid ""
 "Possible environment issues exist.\n"
@@ -520,7 +549,7 @@ msgstr ""
 "av %s:\n"
 "\n"
 
-#: git-gui.sh:3345
+#: git-gui.sh:3687
 msgid ""
 "\n"
 "This is due to a known issue with the\n"
@@ -530,7 +559,7 @@ msgstr ""
 "Detta beror på ett känt problem med\n"
 "Tcl-binären som följer med Cygwin."
 
-#: git-gui.sh:3350
+#: git-gui.sh:3692
 #, tcl-format
 msgid ""
 "\n"
@@ -584,132 +613,132 @@ msgstr "Klandra föräldraincheckning"
 msgid "Reading %s..."
 msgstr "Läser %s..."
 
-#: lib/blame.tcl:557
+#: lib/blame.tcl:581
 msgid "Loading copy/move tracking annotations..."
 msgstr "Läser annoteringar för kopiering/flyttning..."
 
-#: lib/blame.tcl:577
+#: lib/blame.tcl:601
 msgid "lines annotated"
 msgstr "rader annoterade"
 
-#: lib/blame.tcl:769
+#: lib/blame.tcl:793
 msgid "Loading original location annotations..."
 msgstr "Läser in annotering av originalplacering..."
 
-#: lib/blame.tcl:772
+#: lib/blame.tcl:796
 msgid "Annotation complete."
 msgstr "Annotering fullbordad."
 
-#: lib/blame.tcl:802
+#: lib/blame.tcl:826
 msgid "Busy"
 msgstr "Upptagen"
 
-#: lib/blame.tcl:803
+#: lib/blame.tcl:827
 msgid "Annotation process is already running."
 msgstr "Annoteringsprocess körs redan."
 
-#: lib/blame.tcl:842
+#: lib/blame.tcl:866
 msgid "Running thorough copy detection..."
 msgstr "Kör grundlig kopieringsigenkänning..."
 
-#: lib/blame.tcl:910
+#: lib/blame.tcl:934
 msgid "Loading annotation..."
 msgstr "Läser in annotering..."
 
-#: lib/blame.tcl:963
+#: lib/blame.tcl:987
 msgid "Author:"
 msgstr "Författare:"
 
-#: lib/blame.tcl:967
+#: lib/blame.tcl:991
 msgid "Committer:"
 msgstr "Incheckare:"
 
-#: lib/blame.tcl:972
+#: lib/blame.tcl:996
 msgid "Original File:"
 msgstr "Ursprunglig fil:"
 
-#: lib/blame.tcl:1020
+#: lib/blame.tcl:1044
 msgid "Cannot find HEAD commit:"
 msgstr "Hittar inte incheckning för HEAD:"
 
-#: lib/blame.tcl:1075
+#: lib/blame.tcl:1099
 msgid "Cannot find parent commit:"
 msgstr "Hittar inte föräldraincheckning:"
 
-#: lib/blame.tcl:1090
+#: lib/blame.tcl:1114
 msgid "Unable to display parent"
 msgstr "Kan inte visa förälder"
 
-#: lib/blame.tcl:1091 lib/diff.tcl:297
+#: lib/blame.tcl:1115 lib/diff.tcl:323
 msgid "Error loading diff:"
 msgstr "Fel vid inläsning av differens:"
 
-#: lib/blame.tcl:1231
+#: lib/blame.tcl:1255
 msgid "Originally By:"
 msgstr "Ursprungligen av:"
 
-#: lib/blame.tcl:1237
+#: lib/blame.tcl:1261
 msgid "In File:"
 msgstr "I filen:"
 
-#: lib/blame.tcl:1242
+#: lib/blame.tcl:1266
 msgid "Copied Or Moved Here By:"
 msgstr "Kopierad eller flyttad hit av:"
 
-#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19
+#: lib/branch_checkout.tcl:16 lib/branch_checkout.tcl:21
 msgid "Checkout Branch"
 msgstr "Checka ut gren"
 
-#: lib/branch_checkout.tcl:23
+#: lib/branch_checkout.tcl:26
 msgid "Checkout"
 msgstr "Checka ut"
 
-#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
-#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282
-#: lib/checkout_op.tcl:544 lib/choose_font.tcl:43 lib/merge.tcl:172
-#: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42
-#: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352
-#: lib/transport.tcl:108
+#: lib/branch_checkout.tcl:30 lib/branch_create.tcl:37
+#: lib/branch_delete.tcl:34 lib/branch_rename.tcl:32 lib/browser.tcl:286
+#: lib/checkout_op.tcl:579 lib/choose_font.tcl:45 lib/merge.tcl:172
+#: lib/option.tcl:127 lib/remote_add.tcl:34 lib/remote_branch_delete.tcl:43
+#: lib/tools_dlg.tcl:41 lib/tools_dlg.tcl:202 lib/tools_dlg.tcl:345
+#: lib/transport.tcl:111
 msgid "Cancel"
 msgstr "Avbryt"
 
-#: lib/branch_checkout.tcl:32 lib/browser.tcl:287 lib/tools_dlg.tcl:328
+#: lib/branch_checkout.tcl:35 lib/browser.tcl:291 lib/tools_dlg.tcl:321
 msgid "Revision"
 msgstr "Revision"
 
-#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:280
+#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:69 lib/option.tcl:287
 msgid "Options"
 msgstr "Alternativ"
 
-#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92
+#: lib/branch_checkout.tcl:42 lib/branch_create.tcl:92
 msgid "Fetch Tracking Branch"
 msgstr "Hämta spårande gren"
 
-#: lib/branch_checkout.tcl:44
+#: lib/branch_checkout.tcl:47
 msgid "Detach From Local Branch"
 msgstr "Koppla bort från lokal gren"
 
-#: lib/branch_create.tcl:22
+#: lib/branch_create.tcl:23
 msgid "Create Branch"
 msgstr "Skapa gren"
 
-#: lib/branch_create.tcl:27
+#: lib/branch_create.tcl:28
 msgid "Create New Branch"
 msgstr "Skapa ny gren"
 
-#: lib/branch_create.tcl:31 lib/choose_repository.tcl:377
+#: lib/branch_create.tcl:33 lib/choose_repository.tcl:389
 msgid "Create"
 msgstr "Skapa"
 
-#: lib/branch_create.tcl:40
+#: lib/branch_create.tcl:42
 msgid "Branch Name"
 msgstr "Namn på gren"
 
-#: lib/branch_create.tcl:43 lib/remote_add.tcl:39 lib/tools_dlg.tcl:50
+#: lib/branch_create.tcl:44 lib/remote_add.tcl:41 lib/tools_dlg.tcl:51
 msgid "Name:"
 msgstr "Namn:"
 
-#: lib/branch_create.tcl:58
+#: lib/branch_create.tcl:57
 msgid "Match Tracking Branch Name"
 msgstr "Använd namn på spårad gren"
 
@@ -729,7 +758,7 @@ msgstr "Nej"
 msgid "Fast Forward Only"
 msgstr "Endast snabbspolning"
 
-#: lib/branch_create.tcl:85 lib/checkout_op.tcl:536
+#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571
 msgid "Reset"
 msgstr "Återställ"
 
@@ -737,49 +766,59 @@ msgstr "Återställ"
 msgid "Checkout After Creation"
 msgstr "Checka ut när skapad"
 
-#: lib/branch_create.tcl:131
+#: lib/branch_create.tcl:132
 msgid "Please select a tracking branch."
 msgstr "Välj en gren att spåra."
 
-#: lib/branch_create.tcl:140
+#: lib/branch_create.tcl:141
 #, tcl-format
 msgid "Tracking branch %s is not a branch in the remote repository."
 msgstr "Den spårade grenen %s är inte en gren i fjärrarkivet."
 
-#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86
+#: lib/branch_create.tcl:154 lib/branch_rename.tcl:92
 msgid "Please supply a branch name."
 msgstr "Ange ett namn för grenen."
 
-#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106
+#: lib/branch_create.tcl:165 lib/branch_rename.tcl:112
 #, tcl-format
 msgid "'%s' is not an acceptable branch name."
 msgstr "\"%s\" kan inte användas som namn på grenen."
 
-#: lib/branch_delete.tcl:15
+#: lib/branch_delete.tcl:16
 msgid "Delete Branch"
 msgstr "Ta bort gren"
 
-#: lib/branch_delete.tcl:20
+#: lib/branch_delete.tcl:21
 msgid "Delete Local Branch"
 msgstr "Ta bort lokal gren"
 
-#: lib/branch_delete.tcl:37
+#: lib/branch_delete.tcl:39
 msgid "Local Branches"
 msgstr "Lokala grenar"
 
-#: lib/branch_delete.tcl:52
+#: lib/branch_delete.tcl:51
 msgid "Delete Only If Merged Into"
 msgstr "Ta bara bort om sammanslagen med"
 
-#: lib/branch_delete.tcl:54
-msgid "Always (Do not perform merge test.)"
-msgstr "Alltid (utför inte sammanslagningstest)."
+#: lib/branch_delete.tcl:53 lib/remote_branch_delete.tcl:120
+msgid "Always (Do not perform merge checks)"
+msgstr "Alltid (utför inte sammanslagningstest)"
 
 #: lib/branch_delete.tcl:103
 #, tcl-format
 msgid "The following branches are not completely merged into %s:"
 msgstr "Följande grenar är inte till fullo sammanslagna med %s:"
 
+#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:218
+msgid ""
+"Recovering deleted branches is difficult.\n"
+"\n"
+"Delete the selected branches?"
+msgstr ""
+"Det kan vara svårt att återställa borttagna grenar.\n"
+"\n"
+"Ta bort de valda grenarna?"
+
 #: lib/branch_delete.tcl:141
 #, tcl-format
 msgid ""
@@ -789,32 +828,32 @@ msgstr ""
 "Kunde inte ta bort grenar:\n"
 "%s"
 
-#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22
+#: lib/branch_rename.tcl:15 lib/branch_rename.tcl:23
 msgid "Rename Branch"
 msgstr "Byt namn på gren"
 
-#: lib/branch_rename.tcl:26
+#: lib/branch_rename.tcl:28
 msgid "Rename"
 msgstr "Byt namn"
 
-#: lib/branch_rename.tcl:36
+#: lib/branch_rename.tcl:38
 msgid "Branch:"
 msgstr "Gren:"
 
-#: lib/branch_rename.tcl:39
+#: lib/branch_rename.tcl:46
 msgid "New Name:"
 msgstr "Nytt namn:"
 
-#: lib/branch_rename.tcl:75
+#: lib/branch_rename.tcl:81
 msgid "Please select a branch to rename."
 msgstr "Välj en gren att byta namn på."
 
-#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:201
+#: lib/branch_rename.tcl:102 lib/checkout_op.tcl:202
 #, tcl-format
 msgid "Branch '%s' already exists."
 msgstr "Grenen \"%s\" finns redan."
 
-#: lib/branch_rename.tcl:117
+#: lib/branch_rename.tcl:123
 #, tcl-format
 msgid "Failed to rename '%s'."
 msgstr "Kunde inte byta namn på \"%s\"."
@@ -823,7 +862,7 @@ msgstr "Kunde inte byta namn på \"%s\"."
 msgid "Starting..."
 msgstr "Startar..."
 
-#: lib/browser.tcl:26
+#: lib/browser.tcl:27
 msgid "File Browser"
 msgstr "Filbläddrare"
 
@@ -836,42 +875,42 @@ msgstr "Läser %s..."
 msgid "[Up To Parent]"
 msgstr "[Upp till förälder]"
 
-#: lib/browser.tcl:267 lib/browser.tcl:273
+#: lib/browser.tcl:269 lib/browser.tcl:276
 msgid "Browse Branch Files"
 msgstr "Bläddra filer på grenen"
 
-#: lib/browser.tcl:278 lib/choose_repository.tcl:394
-#: lib/choose_repository.tcl:480 lib/choose_repository.tcl:491
-#: lib/choose_repository.tcl:995
+#: lib/browser.tcl:282 lib/choose_repository.tcl:404
+#: lib/choose_repository.tcl:491 lib/choose_repository.tcl:500
+#: lib/choose_repository.tcl:1027
 msgid "Browse"
 msgstr "Bläddra"
 
-#: lib/checkout_op.tcl:84
+#: lib/checkout_op.tcl:85
 #, tcl-format
 msgid "Fetching %s from %s"
 msgstr "Hämtar %s från %s"
 
-#: lib/checkout_op.tcl:132
+#: lib/checkout_op.tcl:133
 #, tcl-format
 msgid "fatal: Cannot resolve %s"
 msgstr "ödesdigert: Kunde inte slå upp %s"
 
-#: lib/checkout_op.tcl:145 lib/console.tcl:81 lib/database.tcl:31
-#: lib/sshkey.tcl:53
+#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:30
+#: lib/sshkey.tcl:55
 msgid "Close"
 msgstr "Stäng"
 
-#: lib/checkout_op.tcl:174
+#: lib/checkout_op.tcl:175
 #, tcl-format
 msgid "Branch '%s' does not exist."
 msgstr "Grenen \"%s\" finns inte."
 
-#: lib/checkout_op.tcl:193
+#: lib/checkout_op.tcl:194
 #, tcl-format
 msgid "Failed to configure simplified git-pull for '%s'."
 msgstr "Kunde inte konfigurera förenklad git-pull för '%s'."
 
-#: lib/checkout_op.tcl:228
+#: lib/checkout_op.tcl:229
 #, tcl-format
 msgid ""
 "Branch '%s' already exists.\n"
@@ -884,21 +923,21 @@ msgstr ""
 "Den kan inte snabbspolas till %s.\n"
 "En sammanslagning krävs."
 
-#: lib/checkout_op.tcl:242
+#: lib/checkout_op.tcl:243
 #, tcl-format
 msgid "Merge strategy '%s' not supported."
 msgstr "Sammanslagningsstrategin \"%s\" stöds inte."
 
-#: lib/checkout_op.tcl:261
+#: lib/checkout_op.tcl:262
 #, tcl-format
 msgid "Failed to update '%s'."
 msgstr "Misslyckades med att uppdatera \"%s\"."
 
-#: lib/checkout_op.tcl:273
+#: lib/checkout_op.tcl:274
 msgid "Staging area (index) is already locked."
 msgstr "Köområdet (index) är redan låst."
 
-#: lib/checkout_op.tcl:288
+#: lib/checkout_op.tcl:289
 msgid ""
 "Last scanned state does not match repository state.\n"
 "\n"
@@ -914,30 +953,30 @@ msgstr ""
 "\n"
 "Sökningen kommer att startas automatiskt nu.\n"
 
-#: lib/checkout_op.tcl:344
+#: lib/checkout_op.tcl:345
 #, tcl-format
 msgid "Updating working directory to '%s'..."
 msgstr "Uppdaterar arbetskatalogen till \"%s\"..."
 
-#: lib/checkout_op.tcl:345
+#: lib/checkout_op.tcl:346
 msgid "files checked out"
 msgstr "filer utcheckade"
 
-#: lib/checkout_op.tcl:375
+#: lib/checkout_op.tcl:376
 #, tcl-format
 msgid "Aborted checkout of '%s' (file level merging is required)."
 msgstr "Avbryter utcheckning av \"%s\" (sammanslagning på filnivå krävs)."
 
-#: lib/checkout_op.tcl:376
+#: lib/checkout_op.tcl:377
 msgid "File level merge required."
 msgstr "Sammanslagning på filnivå krävs."
 
-#: lib/checkout_op.tcl:380
+#: lib/checkout_op.tcl:381
 #, tcl-format
 msgid "Staying on branch '%s'."
 msgstr "Stannar på grenen \"%s\"."
 
-#: lib/checkout_op.tcl:451
+#: lib/checkout_op.tcl:452
 msgid ""
 "You are no longer on a local branch.\n"
 "\n"
@@ -949,31 +988,31 @@ msgstr ""
 "Om du ville vara på en gren skapar du en nu, baserad på \"Denna frånkopplade "
 "utcheckning\"."
 
-#: lib/checkout_op.tcl:468 lib/checkout_op.tcl:472
+#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507
 #, tcl-format
 msgid "Checked out '%s'."
 msgstr "Checkade ut \"%s\"."
 
-#: lib/checkout_op.tcl:500
+#: lib/checkout_op.tcl:535
 #, tcl-format
 msgid "Resetting '%s' to '%s' will lose the following commits:"
 msgstr ""
 "Om du återställer \"%s\" till \"%s\" går följande incheckningar förlorade:"
 
-#: lib/checkout_op.tcl:522
+#: lib/checkout_op.tcl:557
 msgid "Recovering lost commits may not be easy."
 msgstr "Det kanske inte är så enkelt att återskapa förlorade incheckningar."
 
-#: lib/checkout_op.tcl:527
+#: lib/checkout_op.tcl:562
 #, tcl-format
 msgid "Reset '%s'?"
 msgstr "Återställa \"%s\"?"
 
-#: lib/checkout_op.tcl:532 lib/merge.tcl:164 lib/tools_dlg.tcl:343
+#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:336
 msgid "Visualize"
 msgstr "Visualisera"
 
-#: lib/checkout_op.tcl:600
+#: lib/checkout_op.tcl:635
 #, tcl-format
 msgid ""
 "Failed to set current branch.\n"
@@ -990,23 +1029,23 @@ msgstr ""
 "\n"
 "Detta skulle inte ha hänt. %s kommer nu stängas och ge upp."
 
-#: lib/choose_font.tcl:39
+#: lib/choose_font.tcl:41
 msgid "Select"
 msgstr "Välj"
 
-#: lib/choose_font.tcl:53
+#: lib/choose_font.tcl:55
 msgid "Font Family"
 msgstr "Teckensnittsfamilj"
 
-#: lib/choose_font.tcl:74
+#: lib/choose_font.tcl:76
 msgid "Font Size"
 msgstr "Storlek"
 
-#: lib/choose_font.tcl:91
+#: lib/choose_font.tcl:93
 msgid "Font Example"
 msgstr "Exempel"
 
-#: lib/choose_font.tcl:103
+#: lib/choose_font.tcl:105
 msgid ""
 "This is example text.\n"
 "If you like this text, it can be your font."
@@ -1018,7 +1057,7 @@ msgstr ""
 msgid "Git Gui"
 msgstr "Git Gui"
 
-#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:382
+#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:394
 msgid "Create New Repository"
 msgstr "Skapa nytt arkiv"
 
@@ -1026,222 +1065,222 @@ msgstr "Skapa nytt arkiv"
 msgid "New..."
 msgstr "Nytt..."
 
-#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:465
+#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:478
 msgid "Clone Existing Repository"
 msgstr "Klona befintligt arkiv"
 
-#: lib/choose_repository.tcl:106
+#: lib/choose_repository.tcl:111
 msgid "Clone..."
 msgstr "Klona..."
 
-#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:983
+#: lib/choose_repository.tcl:118 lib/choose_repository.tcl:1017
 msgid "Open Existing Repository"
 msgstr "Öppna befintligt arkiv"
 
-#: lib/choose_repository.tcl:119
+#: lib/choose_repository.tcl:124
 msgid "Open..."
 msgstr "Öppna..."
 
-#: lib/choose_repository.tcl:132
+#: lib/choose_repository.tcl:137
 msgid "Recent Repositories"
 msgstr "Senaste arkiven"
 
-#: lib/choose_repository.tcl:138
+#: lib/choose_repository.tcl:143
 msgid "Open Recent Repository:"
 msgstr "Öppna tidigare arkiv:"
 
-#: lib/choose_repository.tcl:302 lib/choose_repository.tcl:309
-#: lib/choose_repository.tcl:316
+#: lib/choose_repository.tcl:313 lib/choose_repository.tcl:320
+#: lib/choose_repository.tcl:327
 #, tcl-format
 msgid "Failed to create repository %s:"
 msgstr "Kunde inte skapa arkivet %s:"
 
-#: lib/choose_repository.tcl:387
+#: lib/choose_repository.tcl:399
 msgid "Directory:"
 msgstr "Katalog:"
 
-#: lib/choose_repository.tcl:417 lib/choose_repository.tcl:544
-#: lib/choose_repository.tcl:1017
+#: lib/choose_repository.tcl:429 lib/choose_repository.tcl:550
+#: lib/choose_repository.tcl:1051
 msgid "Git Repository"
 msgstr "Gitarkiv"
 
-#: lib/choose_repository.tcl:442
+#: lib/choose_repository.tcl:454
 #, tcl-format
 msgid "Directory %s already exists."
 msgstr "Katalogen %s finns redan."
 
-#: lib/choose_repository.tcl:446
+#: lib/choose_repository.tcl:458
 #, tcl-format
 msgid "File %s already exists."
 msgstr "Filen %s finns redan."
 
-#: lib/choose_repository.tcl:460
+#: lib/choose_repository.tcl:473
 msgid "Clone"
 msgstr "Klona"
 
-#: lib/choose_repository.tcl:473
+#: lib/choose_repository.tcl:486
 msgid "Source Location:"
 msgstr "Plats för källkod:"
 
-#: lib/choose_repository.tcl:484
+#: lib/choose_repository.tcl:495
 msgid "Target Directory:"
 msgstr "Målkatalog:"
 
-#: lib/choose_repository.tcl:496
+#: lib/choose_repository.tcl:505
 msgid "Clone Type:"
 msgstr "Typ av klon:"
 
-#: lib/choose_repository.tcl:502
+#: lib/choose_repository.tcl:510
 msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
 msgstr "Standard (snabb, semiredundant, hårda länkar)"
 
-#: lib/choose_repository.tcl:508
+#: lib/choose_repository.tcl:515
 msgid "Full Copy (Slower, Redundant Backup)"
 msgstr "Full kopia (långsammare, redundant säkerhetskopia)"
 
-#: lib/choose_repository.tcl:514
+#: lib/choose_repository.tcl:520
 msgid "Shared (Fastest, Not Recommended, No Backup)"
 msgstr "Delad (snabbast, rekommenderas ej, ingen säkerhetskopia)"
 
-#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597
-#: lib/choose_repository.tcl:743 lib/choose_repository.tcl:813
-#: lib/choose_repository.tcl:1023 lib/choose_repository.tcl:1031
+#: lib/choose_repository.tcl:556 lib/choose_repository.tcl:603
+#: lib/choose_repository.tcl:749 lib/choose_repository.tcl:819
+#: lib/choose_repository.tcl:1057 lib/choose_repository.tcl:1065
 #, tcl-format
 msgid "Not a Git repository: %s"
 msgstr "Inte ett Gitarkiv: %s"
 
-#: lib/choose_repository.tcl:586
+#: lib/choose_repository.tcl:592
 msgid "Standard only available for local repository."
 msgstr "Standard är endast tillgängligt för lokala arkiv."
 
-#: lib/choose_repository.tcl:590
+#: lib/choose_repository.tcl:596
 msgid "Shared only available for local repository."
 msgstr "Delat är endast tillgängligt för lokala arkiv."
 
-#: lib/choose_repository.tcl:611
+#: lib/choose_repository.tcl:617
 #, tcl-format
 msgid "Location %s already exists."
 msgstr "Platsen %s finns redan."
 
-#: lib/choose_repository.tcl:622
+#: lib/choose_repository.tcl:628
 msgid "Failed to configure origin"
 msgstr "Kunde inte konfigurera ursprung"
 
-#: lib/choose_repository.tcl:634
+#: lib/choose_repository.tcl:640
 msgid "Counting objects"
 msgstr "Räknar objekt"
 
-#: lib/choose_repository.tcl:635
+#: lib/choose_repository.tcl:641
 msgid "buckets"
 msgstr "hinkar"
 
-#: lib/choose_repository.tcl:659
+#: lib/choose_repository.tcl:665
 #, tcl-format
 msgid "Unable to copy objects/info/alternates: %s"
 msgstr "Kunde inte kopiera objekt/info/alternativ: %s"
 
-#: lib/choose_repository.tcl:695
+#: lib/choose_repository.tcl:701
 #, tcl-format
 msgid "Nothing to clone from %s."
 msgstr "Ingenting att klona från %s."
 
-#: lib/choose_repository.tcl:697 lib/choose_repository.tcl:911
-#: lib/choose_repository.tcl:923
+#: lib/choose_repository.tcl:703 lib/choose_repository.tcl:917
+#: lib/choose_repository.tcl:929
 msgid "The 'master' branch has not been initialized."
 msgstr "Grenen \"master\" har inte initierats."
 
-#: lib/choose_repository.tcl:710
+#: lib/choose_repository.tcl:716
 msgid "Hardlinks are unavailable.  Falling back to copying."
 msgstr "Hårda länkar är inte tillgängliga. Faller tillbaka på kopiering."
 
-#: lib/choose_repository.tcl:722
+#: lib/choose_repository.tcl:728
 #, tcl-format
 msgid "Cloning from %s"
 msgstr "Klonar från %s"
 
-#: lib/choose_repository.tcl:753
+#: lib/choose_repository.tcl:759
 msgid "Copying objects"
 msgstr "Kopierar objekt"
 
-#: lib/choose_repository.tcl:754
+#: lib/choose_repository.tcl:760
 msgid "KiB"
 msgstr "KiB"
 
-#: lib/choose_repository.tcl:778
+#: lib/choose_repository.tcl:784
 #, tcl-format
 msgid "Unable to copy object: %s"
 msgstr "Kunde inte kopiera objekt: %s"
 
-#: lib/choose_repository.tcl:788
+#: lib/choose_repository.tcl:794
 msgid "Linking objects"
 msgstr "Länkar objekt"
 
-#: lib/choose_repository.tcl:789
+#: lib/choose_repository.tcl:795
 msgid "objects"
 msgstr "objekt"
 
-#: lib/choose_repository.tcl:797
+#: lib/choose_repository.tcl:803
 #, tcl-format
 msgid "Unable to hardlink object: %s"
 msgstr "Kunde inte hårdlänka objekt: %s"
 
-#: lib/choose_repository.tcl:852
+#: lib/choose_repository.tcl:858
 msgid "Cannot fetch branches and objects.  See console output for details."
 msgstr "Kunde inte hämta grenar och objekt. Se konsolutdata för detaljer."
 
-#: lib/choose_repository.tcl:863
+#: lib/choose_repository.tcl:869
 msgid "Cannot fetch tags.  See console output for details."
 msgstr "Kunde inte hämta taggar. Se konsolutdata för detaljer."
 
-#: lib/choose_repository.tcl:887
+#: lib/choose_repository.tcl:893
 msgid "Cannot determine HEAD.  See console output for details."
 msgstr "Kunde inte avgöra HEAD. Se konsolutdata för detaljer."
 
-#: lib/choose_repository.tcl:896
+#: lib/choose_repository.tcl:902
 #, tcl-format
 msgid "Unable to cleanup %s"
 msgstr "Kunde inte städa upp %s"
 
-#: lib/choose_repository.tcl:902
+#: lib/choose_repository.tcl:908
 msgid "Clone failed."
 msgstr "Kloning misslyckades."
 
-#: lib/choose_repository.tcl:909
+#: lib/choose_repository.tcl:915
 msgid "No default branch obtained."
 msgstr "Hämtade ingen standardgren."
 
-#: lib/choose_repository.tcl:920
+#: lib/choose_repository.tcl:926
 #, tcl-format
 msgid "Cannot resolve %s as a commit."
 msgstr "Kunde inte slå upp %s till någon incheckning."
 
-#: lib/choose_repository.tcl:932
+#: lib/choose_repository.tcl:938
 msgid "Creating working directory"
 msgstr "Skapar arbetskatalog"
 
-#: lib/choose_repository.tcl:933 lib/index.tcl:65 lib/index.tcl:128
-#: lib/index.tcl:196
+#: lib/choose_repository.tcl:939 lib/index.tcl:70 lib/index.tcl:133
+#: lib/index.tcl:201
 msgid "files"
 msgstr "filer"
 
-#: lib/choose_repository.tcl:962
+#: lib/choose_repository.tcl:968
 msgid "Initial file checkout failed."
 msgstr "Inledande filutcheckning misslyckades."
 
-#: lib/choose_repository.tcl:978
+#: lib/choose_repository.tcl:1012
 msgid "Open"
 msgstr "Öppna"
 
-#: lib/choose_repository.tcl:988
+#: lib/choose_repository.tcl:1022
 msgid "Repository:"
 msgstr "Arkiv:"
 
-#: lib/choose_repository.tcl:1037
+#: lib/choose_repository.tcl:1071
 #, tcl-format
 msgid "Failed to open repository %s:"
 msgstr "Kunde inte öppna arkivet %s:"
 
-#: lib/choose_rev.tcl:53
+#: lib/choose_rev.tcl:52
 msgid "This Detached Checkout"
 msgstr "Denna frånkopplade utcheckning"
 
@@ -1249,36 +1288,36 @@ msgstr "Denna frånkopplade utcheckning"
 msgid "Revision Expression:"
 msgstr "Revisionsuttryck:"
 
-#: lib/choose_rev.tcl:74
+#: lib/choose_rev.tcl:72
 msgid "Local Branch"
 msgstr "Lokal gren"
 
-#: lib/choose_rev.tcl:79
+#: lib/choose_rev.tcl:77
 msgid "Tracking Branch"
 msgstr "Spårande gren"
 
-#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:538
+#: lib/choose_rev.tcl:82 lib/choose_rev.tcl:543
 msgid "Tag"
 msgstr "Tagg"
 
-#: lib/choose_rev.tcl:317
+#: lib/choose_rev.tcl:321
 #, tcl-format
 msgid "Invalid revision: %s"
 msgstr "Ogiltig revision: %s"
 
-#: lib/choose_rev.tcl:338
+#: lib/choose_rev.tcl:342
 msgid "No revision selected."
 msgstr "Ingen revision vald."
 
-#: lib/choose_rev.tcl:346
+#: lib/choose_rev.tcl:350
 msgid "Revision expression is empty."
 msgstr "Revisionsuttrycket är tomt."
 
-#: lib/choose_rev.tcl:531
+#: lib/choose_rev.tcl:536
 msgid "Updated"
 msgstr "Uppdaterad"
 
-#: lib/choose_rev.tcl:559
+#: lib/choose_rev.tcl:564
 msgid "URL"
 msgstr "Webbadress"
 
@@ -1320,7 +1359,12 @@ msgstr "Kunde inte hämta din identitet:"
 msgid "Invalid GIT_COMMITTER_IDENT:"
 msgstr "Felaktig GIT_COMMITTER_IDENT:"
 
-#: lib/commit.tcl:132
+#: lib/commit.tcl:129
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr "varning: Tcl stöder inte teckenkodningen \"%s\"."
+
+#: lib/commit.tcl:149
 msgid ""
 "Last scanned state does not match repository state.\n"
 "\n"
@@ -1336,7 +1380,7 @@ msgstr ""
 "\n"
 "Sökningen kommer att startas automatiskt nu.\n"
 
-#: lib/commit.tcl:155
+#: lib/commit.tcl:172
 #, tcl-format
 msgid ""
 "Unmerged files cannot be committed.\n"
@@ -1349,7 +1393,7 @@ msgstr ""
 "Filen %s har sammanslagningskonflikter. Du måste lösa dem och köa filen "
 "innan du checkar in den.\n"
 
-#: lib/commit.tcl:163
+#: lib/commit.tcl:180
 #, tcl-format
 msgid ""
 "Unknown file state %s detected.\n"
@@ -1360,7 +1404,7 @@ msgstr ""
 "\n"
 "Filen %s kan inte checkas in av programmet.\n"
 
-#: lib/commit.tcl:171
+#: lib/commit.tcl:188
 msgid ""
 "No changes to commit.\n"
 "\n"
@@ -1370,7 +1414,7 @@ msgstr ""
 "\n"
 "Du måste köa åtminstone en fil innan du kan checka in.\n"
 
-#: lib/commit.tcl:186
+#: lib/commit.tcl:203
 msgid ""
 "Please supply a commit message.\n"
 "\n"
@@ -1388,45 +1432,40 @@ msgstr ""
 "- Andra raden: Tom\n"
 "- Följande rader: Beskriv varför det här är en bra ändring.\n"
 
-#: lib/commit.tcl:210
-#, tcl-format
-msgid "warning: Tcl does not support encoding '%s'."
-msgstr "varning: Tcl stöder inte teckenkodningen \"%s\"."
-
-#: lib/commit.tcl:226
+#: lib/commit.tcl:234
 msgid "Calling pre-commit hook..."
 msgstr "Anropar kroken före incheckning (pre-commit)..."
 
-#: lib/commit.tcl:241
+#: lib/commit.tcl:249
 msgid "Commit declined by pre-commit hook."
 msgstr "Incheckningen avvisades av kroken före incheckning (pre-commit)."
 
-#: lib/commit.tcl:264
+#: lib/commit.tcl:272
 msgid "Calling commit-msg hook..."
 msgstr "Anropar kroken för incheckningsmeddelande (commit-msg)..."
 
-#: lib/commit.tcl:279
+#: lib/commit.tcl:287
 msgid "Commit declined by commit-msg hook."
 msgstr "Incheckning avvisad av kroken för incheckningsmeddelande (commit-msg)."
 
-#: lib/commit.tcl:292
+#: lib/commit.tcl:300
 msgid "Committing changes..."
 msgstr "Checkar in ändringar..."
 
-#: lib/commit.tcl:308
+#: lib/commit.tcl:316
 msgid "write-tree failed:"
 msgstr "write-tree misslyckades:"
 
-#: lib/commit.tcl:309 lib/commit.tcl:353 lib/commit.tcl:373
+#: lib/commit.tcl:317 lib/commit.tcl:361 lib/commit.tcl:382
 msgid "Commit failed."
 msgstr "Incheckningen misslyckades."
 
-#: lib/commit.tcl:326
+#: lib/commit.tcl:334
 #, tcl-format
 msgid "Commit %s appears to be corrupt"
 msgstr "Incheckningen %s verkar vara trasig"
 
-#: lib/commit.tcl:331
+#: lib/commit.tcl:339
 msgid ""
 "No changes to commit.\n"
 "\n"
@@ -1440,19 +1479,19 @@ msgstr ""
 "\n"
 "En sökning kommer att startas automatiskt nu.\n"
 
-#: lib/commit.tcl:338
+#: lib/commit.tcl:346
 msgid "No changes to commit."
 msgstr "Inga ändringar att checka in."
 
-#: lib/commit.tcl:352
+#: lib/commit.tcl:360
 msgid "commit-tree failed:"
 msgstr "commit-tree misslyckades:"
 
-#: lib/commit.tcl:372
+#: lib/commit.tcl:381
 msgid "update-ref failed:"
 msgstr "update-ref misslyckades:"
 
-#: lib/commit.tcl:460
+#: lib/commit.tcl:469
 #, tcl-format
 msgid "Created commit %s: %s"
 msgstr "Skapade incheckningen %s: %s"
@@ -1469,31 +1508,31 @@ msgstr "Lyckades"
 msgid "Error: Command Failed"
 msgstr "Fel: Kommando misslyckades"
 
-#: lib/database.tcl:43
+#: lib/database.tcl:42
 msgid "Number of loose objects"
 msgstr "Antal lösa objekt"
 
-#: lib/database.tcl:44
+#: lib/database.tcl:43
 msgid "Disk space used by loose objects"
 msgstr "Diskutrymme använt av lösa objekt"
 
-#: lib/database.tcl:45
+#: lib/database.tcl:44
 msgid "Number of packed objects"
 msgstr "Antal packade objekt"
 
-#: lib/database.tcl:46
+#: lib/database.tcl:45
 msgid "Number of packs"
 msgstr "Antal paket"
 
-#: lib/database.tcl:47
+#: lib/database.tcl:46
 msgid "Disk space used by packed objects"
 msgstr "Diskutrymme använt av packade objekt"
 
-#: lib/database.tcl:48
+#: lib/database.tcl:47
 msgid "Packed objects waiting for pruning"
 msgstr "Packade objekt som väntar på städning"
 
-#: lib/database.tcl:49
+#: lib/database.tcl:48
 msgid "Garbage files"
 msgstr "Skräpfiler"
 
@@ -1505,20 +1544,20 @@ msgstr "Komprimerar objektdatabasen"
 msgid "Verifying the object database with fsck-objects"
 msgstr "Verifierar objektdatabasen med fsck-objects"
 
-#: lib/database.tcl:108
+#: lib/database.tcl:107
 #, tcl-format
 msgid ""
 "This repository currently has approximately %i loose objects.\n"
 "\n"
 "To maintain optimal performance it is strongly recommended that you compress "
-"the database when more than %i loose objects exist.\n"
+"the database.\n"
 "\n"
 "Compress the database now?"
 msgstr ""
 "Arkivet har för närvarande omkring %i lösa objekt.\n"
 "\n"
 "För att bibehålla optimal prestanda rekommenderas det å det bestämdaste att "
-"du komprimerar databasen när den innehåller mer än %i lösa objekt.\n"
+"du komprimerar databasen.\n"
 "\n"
 "Komprimera databasen nu?"
 
@@ -1527,7 +1566,7 @@ msgstr ""
 msgid "Invalid date from Git: %s"
 msgstr "Ogiltigt datum från Git: %s"
 
-#: lib/diff.tcl:59
+#: lib/diff.tcl:64
 #, tcl-format
 msgid ""
 "No differences detected.\n"
@@ -1550,12 +1589,12 @@ msgstr ""
 "En sökning kommer automatiskt att startas för att hitta andra filer som kan "
 "vara i samma tillstånd."
 
-#: lib/diff.tcl:99
+#: lib/diff.tcl:104
 #, tcl-format
 msgid "Loading diff of %s..."
 msgstr "Läser differens för %s..."
 
-#: lib/diff.tcl:120
+#: lib/diff.tcl:125
 msgid ""
 "LOCAL: deleted\n"
 "REMOTE:\n"
@@ -1563,7 +1602,7 @@ msgstr ""
 "LOKAL: borttagen\n"
 "FJÄRR:\n"
 
-#: lib/diff.tcl:125
+#: lib/diff.tcl:130
 msgid ""
 "REMOTE: deleted\n"
 "LOCAL:\n"
@@ -1571,32 +1610,32 @@ msgstr ""
 "FJÄRR: borttagen\n"
 "LOKAL:\n"
 
-#: lib/diff.tcl:132
+#: lib/diff.tcl:137
 msgid "LOCAL:\n"
 msgstr "LOKAL:\n"
 
-#: lib/diff.tcl:135
+#: lib/diff.tcl:140
 msgid "REMOTE:\n"
 msgstr "FJÄRR:\n"
 
-#: lib/diff.tcl:197 lib/diff.tcl:296
+#: lib/diff.tcl:202 lib/diff.tcl:322
 #, tcl-format
 msgid "Unable to display %s"
 msgstr "Kan inte visa %s"
 
-#: lib/diff.tcl:198
+#: lib/diff.tcl:203
 msgid "Error loading file:"
 msgstr "Fel vid läsning av fil:"
 
-#: lib/diff.tcl:205
+#: lib/diff.tcl:210
 msgid "Git Repository (subproject)"
 msgstr "Gitarkiv (underprojekt)"
 
-#: lib/diff.tcl:217
+#: lib/diff.tcl:222
 msgid "* Binary file (not showing content)."
 msgstr "* Binärfil (visar inte innehållet)."
 
-#: lib/diff.tcl:222
+#: lib/diff.tcl:227
 #, tcl-format
 msgid ""
 "* Untracked file is %d bytes.\n"
@@ -1605,7 +1644,7 @@ msgstr ""
 "* Den ospårade filen är %d byte.\n"
 "* Visar endast inledande %d byte.\n"
 
-#: lib/diff.tcl:228
+#: lib/diff.tcl:233
 #, tcl-format
 msgid ""
 "\n"
@@ -1616,19 +1655,19 @@ msgstr ""
 "* Den ospårade filen klipptes här av %s.\n"
 "* För att se hela filen, använd ett externt redigeringsprogram.\n"
 
-#: lib/diff.tcl:436
+#: lib/diff.tcl:485
 msgid "Failed to unstage selected hunk."
 msgstr "Kunde inte ta bort den valda delen från kön."
 
-#: lib/diff.tcl:443
+#: lib/diff.tcl:492
 msgid "Failed to stage selected hunk."
 msgstr "Kunde inte lägga till den valda delen till kön."
 
-#: lib/diff.tcl:509
+#: lib/diff.tcl:571
 msgid "Failed to unstage selected line."
 msgstr "Kunde inte ta bort den valda raden från kön."
 
-#: lib/diff.tcl:517
+#: lib/diff.tcl:579
 msgid "Failed to stage selected line."
 msgstr "Kunde inte lägga till den valda raden till kön."
 
@@ -1645,7 +1684,7 @@ msgstr "Systemets (%s)"
 msgid "Other"
 msgstr "Annan"
 
-#: lib/error.tcl:20 lib/error.tcl:114
+#: lib/error.tcl:20 lib/error.tcl:116
 msgid "error"
 msgstr "fel"
 
@@ -1653,7 +1692,7 @@ msgstr "fel"
 msgid "warning"
 msgstr "varning"
 
-#: lib/error.tcl:94
+#: lib/error.tcl:96
 msgid "You must correct the above errors before committing."
 msgstr "Du måste rätta till felen ovan innan du checkar in."
 
@@ -1661,11 +1700,11 @@ msgstr "Du måste rätta till felen ovan innan du checkar in."
 msgid "Unable to unlock the index."
 msgstr "Kunde inte låsa upp indexet."
 
-#: lib/index.tcl:15
+#: lib/index.tcl:17
 msgid "Index Error"
 msgstr "Indexfel"
 
-#: lib/index.tcl:21
+#: lib/index.tcl:19
 msgid ""
 "Updating the Git index failed.  A rescan will be automatically started to "
 "resynchronize git-gui."
@@ -1673,52 +1712,52 @@ msgstr ""
 "Misslyckades med att uppdatera Gitindexet. En omsökning kommer att startas "
 "automatiskt för att synkronisera om git-gui."
 
-#: lib/index.tcl:27
+#: lib/index.tcl:30
 msgid "Continue"
 msgstr "Forstätt"
 
-#: lib/index.tcl:31
+#: lib/index.tcl:33
 msgid "Unlock Index"
 msgstr "Lås upp index"
 
-#: lib/index.tcl:287
+#: lib/index.tcl:292
 #, tcl-format
 msgid "Unstaging %s from commit"
 msgstr "Tar bort %s för incheckningskön"
 
-#: lib/index.tcl:326
+#: lib/index.tcl:331
 msgid "Ready to commit."
 msgstr "Redo att checka in."
 
-#: lib/index.tcl:339
+#: lib/index.tcl:344
 #, tcl-format
 msgid "Adding %s"
 msgstr "Lägger till %s"
 
-#: lib/index.tcl:396
+#: lib/index.tcl:401
 #, tcl-format
 msgid "Revert changes in file %s?"
 msgstr "Återställ ändringarna i filen %s?"
 
-#: lib/index.tcl:398
+#: lib/index.tcl:403
 #, tcl-format
 msgid "Revert changes in these %i files?"
 msgstr "Återställ ändringarna i dessa %i filer?"
 
-#: lib/index.tcl:406
+#: lib/index.tcl:411
 msgid "Any unstaged changes will be permanently lost by the revert."
 msgstr ""
 "Alla oköade ändringar kommer permanent gå förlorade vid återställningen."
 
-#: lib/index.tcl:409
+#: lib/index.tcl:414
 msgid "Do Nothing"
 msgstr "Gör ingenting"
 
-#: lib/index.tcl:427
+#: lib/index.tcl:432
 msgid "Reverting selected files"
 msgstr "Återställer valda filer"
 
-#: lib/index.tcl:431
+#: lib/index.tcl:436
 #, tcl-format
 msgid "Reverting %s"
 msgstr "Återställer %s"
@@ -1965,145 +2004,133 @@ msgstr "Den globala teckenkodningen \"%s\" är ogiltig"
 msgid "Invalid repo encoding '%s'"
 msgstr "Arkivets teckenkodning \"%s\" är ogiltig"
 
-#: lib/option.tcl:117
+#: lib/option.tcl:119
 msgid "Restore Defaults"
 msgstr "Återställ standardvärden"
 
-#: lib/option.tcl:121
+#: lib/option.tcl:123
 msgid "Save"
 msgstr "Spara"
 
-#: lib/option.tcl:131
+#: lib/option.tcl:133
 #, tcl-format
 msgid "%s Repository"
 msgstr "Arkivet %s"
 
-#: lib/option.tcl:132
+#: lib/option.tcl:134
 msgid "Global (All Repositories)"
 msgstr "Globalt (alla arkiv)"
 
-#: lib/option.tcl:138
+#: lib/option.tcl:140
 msgid "User Name"
 msgstr "Användarnamn"
 
-#: lib/option.tcl:139
+#: lib/option.tcl:141
 msgid "Email Address"
 msgstr "E-postadress"
 
-#: lib/option.tcl:141
+#: lib/option.tcl:143
 msgid "Summarize Merge Commits"
 msgstr "Summera sammanslagningsincheckningar"
 
-#: lib/option.tcl:142
+#: lib/option.tcl:144
 msgid "Merge Verbosity"
 msgstr "Pratsamhet för sammanslagningar"
 
-#: lib/option.tcl:143
+#: lib/option.tcl:145
 msgid "Show Diffstat After Merge"
 msgstr "Visa diffstatistik efter sammanslagning"
 
-#: lib/option.tcl:144
+#: lib/option.tcl:146
 msgid "Use Merge Tool"
 msgstr "Använd verktyg för sammanslagning"
 
-#: lib/option.tcl:146
+#: lib/option.tcl:148
 msgid "Trust File Modification Timestamps"
 msgstr "Lita på filändringstidsstämplar"
 
-#: lib/option.tcl:147
+#: lib/option.tcl:149
 msgid "Prune Tracking Branches During Fetch"
 msgstr "Städa spårade grenar vid hämtning"
 
-#: lib/option.tcl:148
+#: lib/option.tcl:150
 msgid "Match Tracking Branches"
 msgstr "Matcha spårade grenar"
 
-#: lib/option.tcl:149
+#: lib/option.tcl:151
+msgid "Use Textconv For Diffs and Blames"
+msgstr "Använd Textconv för diff och klandring"
+
+#: lib/option.tcl:152
 msgid "Blame Copy Only On Changed Files"
 msgstr "Klandra kopiering bara i ändrade filer"
 
-#: lib/option.tcl:150
+#: lib/option.tcl:153
 msgid "Minimum Letters To Blame Copy On"
 msgstr "Minsta antal tecken att klandra kopiering för"
 
-#: lib/option.tcl:151
+#: lib/option.tcl:154
 msgid "Blame History Context Radius (days)"
 msgstr "Historikradie för klandring (dagar)"
 
-#: lib/option.tcl:152
+#: lib/option.tcl:155
 msgid "Number of Diff Context Lines"
 msgstr "Antal rader sammanhang i differenser"
 
-#: lib/option.tcl:153
+#: lib/option.tcl:156
 msgid "Commit Message Text Width"
 msgstr "Textbredd för incheckningsmeddelande"
 
-#: lib/option.tcl:154
+#: lib/option.tcl:157
 msgid "New Branch Name Template"
 msgstr "Mall för namn på nya grenar"
 
-#: lib/option.tcl:155
+#: lib/option.tcl:158
 msgid "Default File Contents Encoding"
 msgstr "Standardteckenkodning för filinnehåll"
 
-#: lib/option.tcl:203
+#: lib/option.tcl:204
 msgid "Change"
 msgstr "Ändra"
 
-#: lib/option.tcl:230
+#: lib/option.tcl:231
 msgid "Spelling Dictionary:"
 msgstr "Stavningsordlista:"
 
-#: lib/option.tcl:254
+#: lib/option.tcl:261
 msgid "Change Font"
 msgstr "Byt teckensnitt"
 
-#: lib/option.tcl:258
+#: lib/option.tcl:265
 #, tcl-format
 msgid "Choose %s"
 msgstr "Välj %s"
 
-#: lib/option.tcl:264
+#: lib/option.tcl:271
 msgid "pt."
 msgstr "p."
 
-#: lib/option.tcl:278
+#: lib/option.tcl:285
 msgid "Preferences"
 msgstr "Inställningar"
 
-#: lib/option.tcl:314
+#: lib/option.tcl:322
 msgid "Failed to completely save options:"
 msgstr "Misslyckades med att helt spara alternativ:"
 
-#: lib/remote.tcl:163
-msgid "Remove Remote"
-msgstr "Ta bort fjärrarkiv"
-
-#: lib/remote.tcl:168
-msgid "Prune from"
-msgstr "Ta bort från"
-
-#: lib/remote.tcl:173
-msgid "Fetch from"
-msgstr "Hämta från"
-
-#: lib/remote.tcl:215
-msgid "Push to"
-msgstr "Sänd till"
-
-#: lib/remote_add.tcl:19
+#: lib/remote_add.tcl:20
 msgid "Add Remote"
 msgstr "Lägg till fjärrarkiv"
 
-#: lib/remote_add.tcl:24
+#: lib/remote_add.tcl:25
 msgid "Add New Remote"
 msgstr "Lägg till nytt fjärrarkiv"
 
-#: lib/remote_add.tcl:28 lib/tools_dlg.tcl:36
+#: lib/remote_add.tcl:30 lib/tools_dlg.tcl:37
 msgid "Add"
 msgstr "Lägg till"
 
-#: lib/remote_add.tcl:37
+#: lib/remote_add.tcl:39
 msgid "Remote Details"
 msgstr "Detaljer för fjärrarkiv"
 
@@ -2111,58 +2138,58 @@ msgstr "Detaljer för fjärrarkiv"
 msgid "Location:"
 msgstr "Plats:"
 
-#: lib/remote_add.tcl:62
+#: lib/remote_add.tcl:60
 msgid "Further Action"
 msgstr "Ytterligare åtgärd"
 
-#: lib/remote_add.tcl:65
+#: lib/remote_add.tcl:63
 msgid "Fetch Immediately"
 msgstr "Hämta omedelbart"
 
-#: lib/remote_add.tcl:71
+#: lib/remote_add.tcl:69
 msgid "Initialize Remote Repository and Push"
 msgstr "Initiera fjärrarkiv och sänd till"
 
-#: lib/remote_add.tcl:77
+#: lib/remote_add.tcl:75
 msgid "Do Nothing Else Now"
 msgstr "Gör ingent mer nu"
 
-#: lib/remote_add.tcl:101
+#: lib/remote_add.tcl:100
 msgid "Please supply a remote name."
 msgstr "Ange ett namn för fjärrarkivet."
 
-#: lib/remote_add.tcl:114
+#: lib/remote_add.tcl:113
 #, tcl-format
 msgid "'%s' is not an acceptable remote name."
 msgstr "\"%s\" kan inte användas som namn på fjärrarkivet."
 
-#: lib/remote_add.tcl:125
+#: lib/remote_add.tcl:124
 #, tcl-format
 msgid "Failed to add remote '%s' of location '%s'."
 msgstr "Kunde inte lägga till fjärrarkivet \"%s\" på platsen \"%s\"."
 
-#: lib/remote_add.tcl:133 lib/transport.tcl:6
+#: lib/remote_add.tcl:132 lib/transport.tcl:6
 #, tcl-format
 msgid "fetch %s"
 msgstr "hämta %s"
 
-#: lib/remote_add.tcl:134
+#: lib/remote_add.tcl:133
 #, tcl-format
 msgid "Fetching the %s"
 msgstr "Hämtar %s"
 
-#: lib/remote_add.tcl:157
+#: lib/remote_add.tcl:156
 #, tcl-format
 msgid "Do not know how to initialize repository at location '%s'."
 msgstr "Vet inte hur arkivet på platsen \"%s\" skall initieras."
 
-#: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:63
+#: lib/remote_add.tcl:162 lib/transport.tcl:25 lib/transport.tcl:63
 #: lib/transport.tcl:81
 #, tcl-format
 msgid "push %s"
 msgstr "sänd %s"
 
-#: lib/remote_add.tcl:164
+#: lib/remote_add.tcl:163
 #, tcl-format
 msgid "Setting up the %s (at %s)"
 msgstr "Konfigurerar %s (på %s)"
@@ -2171,39 +2198,35 @@ msgstr "Konfigurerar %s (på %s)"
 msgid "Delete Branch Remotely"
 msgstr "Ta bort gren från fjärrarkiv"
 
-#: lib/remote_branch_delete.tcl:47
+#: lib/remote_branch_delete.tcl:48
 msgid "From Repository"
 msgstr "Från arkiv"
 
-#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:134
+#: lib/remote_branch_delete.tcl:51 lib/transport.tcl:134
 msgid "Remote:"
 msgstr "Fjärrarkiv:"
 
-#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:149
+#: lib/remote_branch_delete.tcl:72 lib/transport.tcl:154
 msgid "Arbitrary Location:"
 msgstr "Godtycklig plats:"
 
-#: lib/remote_branch_delete.tcl:84
+#: lib/remote_branch_delete.tcl:88
 msgid "Branches"
 msgstr "Grenar"
 
-#: lib/remote_branch_delete.tcl:109
+#: lib/remote_branch_delete.tcl:110
 msgid "Delete Only If"
 msgstr "Ta endast bort om"
 
-#: lib/remote_branch_delete.tcl:111
+#: lib/remote_branch_delete.tcl:112
 msgid "Merged Into:"
 msgstr "Sammanslagen i:"
 
-#: lib/remote_branch_delete.tcl:119
-msgid "Always (Do not perform merge checks)"
-msgstr "Alltid (utför inte sammanslagningstest)"
-
-#: lib/remote_branch_delete.tcl:152
+#: lib/remote_branch_delete.tcl:153
 msgid "A branch is required for 'Merged Into'."
 msgstr "En gren krävs för \"Sammanslagen i\"."
 
-#: lib/remote_branch_delete.tcl:184
+#: lib/remote_branch_delete.tcl:185
 #, tcl-format
 msgid ""
 "The following branches are not completely merged into %s:\n"
@@ -2214,7 +2237,7 @@ msgstr ""
 "\n"
 " - %s"
 
-#: lib/remote_branch_delete.tcl:189
+#: lib/remote_branch_delete.tcl:190
 #, tcl-format
 msgid ""
 "One or more of the merge tests failed because you have not fetched the "
@@ -2223,55 +2246,61 @@ msgstr ""
 "En eller flera av sammanslagningstesterna misslyckades eftersom du inte har "
 "hämtat de nödvändiga incheckningarna. Försök hämta från %s först."
 
-#: lib/remote_branch_delete.tcl:207
+#: lib/remote_branch_delete.tcl:208
 msgid "Please select one or more branches to delete."
 msgstr "Välj en eller flera grenar att ta bort."
 
-#: lib/remote_branch_delete.tcl:216
-msgid ""
-"Recovering deleted branches is difficult.\n"
-"\n"
-"Delete the selected branches?"
-msgstr ""
-"Det kan vara svårt att återställa borttagna grenar.\n"
-"\n"
-"Ta bort de valda grenarna?"
-
-#: lib/remote_branch_delete.tcl:226
+#: lib/remote_branch_delete.tcl:227
 #, tcl-format
 msgid "Deleting branches from %s"
 msgstr "Tar bort grenar från %s"
 
-#: lib/remote_branch_delete.tcl:286
+#: lib/remote_branch_delete.tcl:293
 msgid "No repository selected."
 msgstr "Inget arkiv markerat."
 
-#: lib/remote_branch_delete.tcl:291
+#: lib/remote_branch_delete.tcl:298
 #, tcl-format
 msgid "Scanning %s..."
 msgstr "Söker %s..."
 
-#: lib/search.tcl:21
+#: lib/remote.tcl:163
+msgid "Remove Remote"
+msgstr "Ta bort fjärrarkiv"
+
+#: lib/remote.tcl:168
+msgid "Prune from"
+msgstr "Ta bort från"
+
+#: lib/remote.tcl:173
+msgid "Fetch from"
+msgstr "Hämta från"
+
+#: lib/remote.tcl:215
+msgid "Push to"
+msgstr "Sänd till"
+
+#: lib/search.tcl:22
 msgid "Find:"
 msgstr "Sök:"
 
-#: lib/search.tcl:23
+#: lib/search.tcl:24
 msgid "Next"
 msgstr "Nästa"
 
-#: lib/search.tcl:24
+#: lib/search.tcl:25
 msgid "Prev"
 msgstr "Föreg"
 
-#: lib/search.tcl:25
+#: lib/search.tcl:26
 msgid "Case-Sensitive"
 msgstr "Skilj på VERSALER/gemener"
 
-#: lib/shortcut.tcl:20 lib/shortcut.tcl:61
+#: lib/shortcut.tcl:21 lib/shortcut.tcl:62
 msgid "Cannot write shortcut:"
 msgstr "Kan inte skriva genväg:"
 
-#: lib/shortcut.tcl:136
+#: lib/shortcut.tcl:137
 msgid "Cannot write icon:"
 msgstr "Kan inte skriva ikon:"
 
@@ -2325,19 +2354,19 @@ msgstr "Hittade öppen nyckel i: %s"
 msgid "Generate Key"
 msgstr "Skapa nyckel"
 
-#: lib/sshkey.tcl:56
+#: lib/sshkey.tcl:58
 msgid "Copy To Clipboard"
 msgstr "Kopiera till Urklipp"
 
-#: lib/sshkey.tcl:70
+#: lib/sshkey.tcl:72
 msgid "Your OpenSSH Public Key"
 msgstr "Din öppna OpenSSH-nyckel"
 
-#: lib/sshkey.tcl:78
+#: lib/sshkey.tcl:80
 msgid "Generating..."
 msgstr "Skapar..."
 
-#: lib/sshkey.tcl:84
+#: lib/sshkey.tcl:86
 #, tcl-format
 msgid ""
 "Could not start ssh-keygen:\n"
@@ -2348,54 +2377,24 @@ msgstr ""
 "\n"
 "%s"
 
-#: lib/sshkey.tcl:111
+#: lib/sshkey.tcl:113
 msgid "Generation failed."
 msgstr "Misslyckades med att skapa."
 
-#: lib/sshkey.tcl:118
+#: lib/sshkey.tcl:120
 msgid "Generation succeded, but no keys found."
 msgstr "Lyckades skapa nyckeln, men hittar inte någon nyckel."
 
-#: lib/sshkey.tcl:121
+#: lib/sshkey.tcl:123
 #, tcl-format
 msgid "Your key is in: %s"
 msgstr "Din nyckel finns i: %s"
 
-#: lib/status_bar.tcl:83
+#: lib/status_bar.tcl:86
 #, tcl-format
 msgid "%s ... %*i of %*i %s (%3i%%)"
 msgstr "%s... %*i av %*i %s (%3i%%)"
 
-#: lib/tools.tcl:75
-#, tcl-format
-msgid "Running %s requires a selected file."
-msgstr "För att starta %s måste du välja en fil."
-
-#: lib/tools.tcl:90
-#, tcl-format
-msgid "Are you sure you want to run %s?"
-msgstr "Är du säker på att du vill starta %s?"
-
-#: lib/tools.tcl:110
-#, tcl-format
-msgid "Tool: %s"
-msgstr "Verktyg: %s"
-
-#: lib/tools.tcl:111
-#, tcl-format
-msgid "Running: %s"
-msgstr "Exekverar: %s"
-
-#: lib/tools.tcl:149
-#, tcl-format
-msgid "Tool completed successfully: %s"
-msgstr "Verktyget avslutades framgångsrikt: %s"
-
-#: lib/tools.tcl:151
-#, tcl-format
-msgid "Tool failed: %s"
-msgstr "Verktyget misslyckades: %s"
-
 #: lib/tools_dlg.tcl:22
 msgid "Add Tool"
 msgstr "Lägg till verktyg"
@@ -2404,52 +2403,52 @@ msgstr "Lägg till verktyg"
 msgid "Add New Tool Command"
 msgstr "Lägg till nytt verktygskommando"
 
-#: lib/tools_dlg.tcl:33
+#: lib/tools_dlg.tcl:34
 msgid "Add globally"
 msgstr "Lägg till globalt"
 
-#: lib/tools_dlg.tcl:45
+#: lib/tools_dlg.tcl:46
 msgid "Tool Details"
 msgstr "Detaljer för verktyg"
 
-#: lib/tools_dlg.tcl:48
+#: lib/tools_dlg.tcl:49
 msgid "Use '/' separators to create a submenu tree:"
 msgstr "Använd \"/\"-avdelare för att skapa ett undermenyträd:"
 
-#: lib/tools_dlg.tcl:61
+#: lib/tools_dlg.tcl:60
 msgid "Command:"
 msgstr "Kommando:"
 
-#: lib/tools_dlg.tcl:74
+#: lib/tools_dlg.tcl:71
 msgid "Show a dialog before running"
 msgstr "Visa dialog innan programmet startas"
 
-#: lib/tools_dlg.tcl:80
+#: lib/tools_dlg.tcl:77
 msgid "Ask the user to select a revision (sets $REVISION)"
 msgstr "Be användaren välja en version (sätter $REVISION)"
 
-#: lib/tools_dlg.tcl:85
+#: lib/tools_dlg.tcl:82
 msgid "Ask the user for additional arguments (sets $ARGS)"
 msgstr "Be användaren om ytterligare parametrar (sätter $ARGS)"
 
-#: lib/tools_dlg.tcl:92
+#: lib/tools_dlg.tcl:89
 msgid "Don't show the command output window"
 msgstr "Visa inte kommandots utdatafönster"
 
-#: lib/tools_dlg.tcl:97
+#: lib/tools_dlg.tcl:94
 msgid "Run only if a diff is selected ($FILENAME not empty)"
 msgstr "Kör endast om en diff har markerats ($FILENAME är inte tomt)"
 
-#: lib/tools_dlg.tcl:121
+#: lib/tools_dlg.tcl:118
 msgid "Please supply a name for the tool."
 msgstr "Ange ett namn för verktyget."
 
-#: lib/tools_dlg.tcl:129
+#: lib/tools_dlg.tcl:126
 #, tcl-format
 msgid "Tool '%s' already exists."
 msgstr "Verktyget \"%s\" finns redan."
 
-#: lib/tools_dlg.tcl:151
+#: lib/tools_dlg.tcl:148
 #, tcl-format
 msgid ""
 "Could not add tool:\n"
@@ -2458,35 +2457,65 @@ msgstr ""
 "Kunde inte lägga till verktyget:\n"
 "%s"
 
-#: lib/tools_dlg.tcl:190
+#: lib/tools_dlg.tcl:187
 msgid "Remove Tool"
 msgstr "Ta bort verktyg"
 
-#: lib/tools_dlg.tcl:196
+#: lib/tools_dlg.tcl:193
 msgid "Remove Tool Commands"
 msgstr "Ta bort verktygskommandon"
 
-#: lib/tools_dlg.tcl:200
+#: lib/tools_dlg.tcl:198
 msgid "Remove"
 msgstr "Ta bort"
 
-#: lib/tools_dlg.tcl:236
+#: lib/tools_dlg.tcl:231
 msgid "(Blue denotes repository-local tools)"
 msgstr "(Blått anger verktyg lokala för arkivet)"
 
-#: lib/tools_dlg.tcl:297
+#: lib/tools_dlg.tcl:292
 #, tcl-format
 msgid "Run Command: %s"
 msgstr "Kör kommandot: %s"
 
-#: lib/tools_dlg.tcl:311
+#: lib/tools_dlg.tcl:306
 msgid "Arguments"
 msgstr "Argument"
 
-#: lib/tools_dlg.tcl:348
+#: lib/tools_dlg.tcl:341
 msgid "OK"
 msgstr "OK"
 
+#: lib/tools.tcl:75
+#, tcl-format
+msgid "Running %s requires a selected file."
+msgstr "För att starta %s måste du välja en fil."
+
+#: lib/tools.tcl:90
+#, tcl-format
+msgid "Are you sure you want to run %s?"
+msgstr "Är du säker på att du vill starta %s?"
+
+#: lib/tools.tcl:110
+#, tcl-format
+msgid "Tool: %s"
+msgstr "Verktyg: %s"
+
+#: lib/tools.tcl:111
+#, tcl-format
+msgid "Running: %s"
+msgstr "Exekverar: %s"
+
+#: lib/tools.tcl:149
+#, tcl-format
+msgid "Tool completed successfully: %s"
+msgstr "Verktyget avslutades framgångsrikt: %s"
+
+#: lib/tools.tcl:151
+#, tcl-format
+msgid "Tool failed: %s"
+msgstr "Verktyget misslyckades: %s"
+
 #: lib/transport.tcl:7
 #, tcl-format
 msgid "Fetching new changes from %s"
@@ -2517,11 +2546,11 @@ msgstr "Speglar till %s"
 msgid "Pushing %s %s to %s"
 msgstr "Sänder %s %s till %s"
 
-#: lib/transport.tcl:100
+#: lib/transport.tcl:102
 msgid "Push Branches"
 msgstr "Sänd grenar"
 
-#: lib/transport.tcl:114
+#: lib/transport.tcl:117
 msgid "Source Branches"
 msgstr "Källgrenar"
 
@@ -2529,22 +2558,31 @@ msgstr "Källgrenar"
 msgid "Destination Repository"
 msgstr "Destinationsarkiv"
 
-#: lib/transport.tcl:169
+#: lib/transport.tcl:172
 msgid "Transfer Options"
 msgstr "Överföringsalternativ"
 
-#: lib/transport.tcl:171
+#: lib/transport.tcl:174
 msgid "Force overwrite existing branch (may discard changes)"
 msgstr "Tvinga överskrivning av befintlig gren (kan kasta bort ändringar)"
 
-#: lib/transport.tcl:175
+#: lib/transport.tcl:178
 msgid "Use thin pack (for slow network connections)"
 msgstr "Använd tunt paket (för långsamma nätverksanslutningar)"
 
-#: lib/transport.tcl:179
+#: lib/transport.tcl:182
 msgid "Include tags"
 msgstr "Ta med taggar"
 
+#~ msgid "Cannot use funny .git directory:"
+#~ msgstr "Kan inte använda underlig .git-katalog:"
+
+#~ msgid "Preferences..."
+#~ msgstr "Inställningar..."
+
+#~ msgid "Always (Do not perform merge test.)"
+#~ msgstr "Alltid (utför inte sammanslagningstest)."
+
 #~ msgid "URL:"
 #~ msgstr "Webbadress:"
 
index 66bbb2f8faaf83bc87819a9e288a0592f400e147..b1845c505500a0f079b2299de07d481c6b2550c4 100644 (file)
@@ -13,10 +13,11 @@ if { $argc >=2 && [lindex $argv 0] == "--working-dir" } {
        incr argc -2
 }
 
-set bindir [file dirname \
+set basedir [file dirname \
             [file dirname \
              [file dirname [info script]]]]
-set bindir [file join $bindir bin]
+set bindir [file join $basedir bin]
+set bindir "$bindir;[file join $basedir mingw bin]"
 regsub -all ";" $bindir "\\;" bindir
 set env(PATH) "$bindir;$env(PATH)"
 unset bindir
index 5f4419b69b58769e8fee7a60109520a095a831c5..e6f6ecda177c66a79b18359f7bf8cad9c4744c8c 100755 (executable)
@@ -24,6 +24,7 @@ restart        restart the web server
 fqgitdir="$GIT_DIR"
 local="$(git config --bool --get instaweb.local)"
 httpd="$(git config --get instaweb.httpd)"
+root="$(git config --get instaweb.gitwebdir)"
 port=$(git config --get instaweb.port)
 module_path="$(git config --get instaweb.modulepath)"
 
@@ -34,18 +35,35 @@ conf="$GIT_DIR/gitweb/httpd.conf"
 # if installed, it doesn't need further configuration (module_path)
 test -z "$httpd" && httpd='lighttpd -f'
 
+# Default is @@GITWEBDIR@@
+test -z "$root" && root='@@GITWEBDIR@@'
+
 # any untaken local port will do...
 test -z "$port" && port=1234
 
 resolve_full_httpd () {
        case "$httpd" in
-       *apache2*|*lighttpd*)
+       *apache2*|*lighttpd*|*httpd*)
+               # yes, *httpd* covers *lighttpd* above, but it is there for clarity
                # ensure that the apache2/lighttpd command ends with "-f"
-               if ! echo "$httpd" | grep -- '-f *$' >/dev/null 2>&1
+               if ! echo "$httpd" | sane_grep -- '-f *$' >/dev/null 2>&1
                then
                        httpd="$httpd -f"
                fi
                ;;
+       *plackup*)
+               # server is started by running via generated gitweb.psgi in $fqgitdir/gitweb
+               full_httpd="$fqgitdir/gitweb/gitweb.psgi"
+               httpd_only="${httpd%% *}" # cut on first space
+               return
+               ;;
+       *webrick*)
+               # server is started by running via generated webrick.rb in
+               # $fqgitdir/gitweb
+               full_httpd="$fqgitdir/gitweb/webrick.rb"
+               httpd_only="${httpd%% *}" # cut on first space
+               return
+               ;;
        esac
 
        httpd_only="$(echo $httpd | cut -f1 -d' ')"
@@ -57,7 +75,7 @@ resolve_full_httpd () {
                # these days and those are not in most users $PATHs
                # in addition, we may have generated a server script
                # in $fqgitdir/gitweb.
-               for i in /usr/local/sbin /usr/sbin "$fqgitdir/gitweb"
+               for i in /usr/local/sbin /usr/sbin "$root" "$fqgitdir/gitweb"
                do
                        if test -x "$i/$httpd_only"
                        then
@@ -73,19 +91,57 @@ resolve_full_httpd () {
 }
 
 start_httpd () {
+       if test -f "$fqgitdir/pid"; then
+               say "Instance already running. Restarting..."
+               stop_httpd
+       fi
+
        # here $httpd should have a meaningful value
        resolve_full_httpd
 
        # don't quote $full_httpd, there can be arguments to it (-f)
-       $full_httpd "$fqgitdir/gitweb/httpd.conf"
-       if test $? != 0; then
-               echo "Could not execute http daemon $httpd."
-               exit 1
-       fi
+       case "$httpd" in
+       *mongoose*|*plackup*)
+               #These servers don't have a daemon mode so we'll have to fork it
+               $full_httpd "$fqgitdir/gitweb/httpd.conf" &
+               #Save the pid before doing anything else (we'll print it later)
+               pid=$!
+
+               if test $? != 0; then
+                       echo "Could not execute http daemon $httpd."
+                       exit 1
+               fi
+
+               cat > "$fqgitdir/pid" <<EOF
+$pid
+EOF
+               ;;
+       *)
+               $full_httpd "$fqgitdir/gitweb/httpd.conf"
+               if test $? != 0; then
+                       echo "Could not execute http daemon $httpd."
+                       exit 1
+               fi
+               ;;
+       esac
 }
 
 stop_httpd () {
        test -f "$fqgitdir/pid" && kill $(cat "$fqgitdir/pid")
+       rm -f "$fqgitdir/pid"
+}
+
+httpd_is_ready () {
+       "$PERL" -MIO::Socket::INET -e "
+local \$| = 1; # turn on autoflush
+exit if (IO::Socket::INET->new('127.0.0.1:$port'));
+print 'Waiting for \'$httpd\' to start ..';
+do {
+       print '.';
+       sleep(1);
+} until (IO::Socket::INET->new('127.0.0.1:$port'));
+print qq! (done)\n!;
+"
 }
 
 while test $# != 0
@@ -135,60 +191,73 @@ done
 mkdir -p "$GIT_DIR/gitweb/tmp"
 GIT_EXEC_PATH="$(git --exec-path)"
 GIT_DIR="$fqgitdir"
-export GIT_EXEC_PATH GIT_DIR
-
+GITWEB_CONFIG="$fqgitdir/gitweb/gitweb_config.perl"
+export GIT_EXEC_PATH GIT_DIR GITWEB_CONFIG
 
 webrick_conf () {
+       # webrick seems to have no way of passing arbitrary environment
+       # variables to the underlying CGI executable, so we wrap the
+       # actual gitweb.cgi using a shell script to force it
+  wrapper="$fqgitdir/gitweb/$httpd/wrapper.sh"
+       cat > "$wrapper" <<EOF
+#!/bin/sh
+# we use this shell script wrapper around the real gitweb.cgi since
+# there appears to be no other way to pass arbitrary environment variables
+# into the CGI process
+GIT_EXEC_PATH=$GIT_EXEC_PATH GIT_DIR=$GIT_DIR GITWEB_CONFIG=$GITWEB_CONFIG
+export GIT_EXEC_PATH GIT_DIR GITWEB_CONFIG
+exec $root/gitweb.cgi
+EOF
+       chmod +x "$wrapper"
+
+       # This assumes _ruby_ is in the user's $PATH. that's _one_
+       # portable way to run ruby, which could be installed anywhere, really.
        # generate a standalone server script in $fqgitdir/gitweb.
        cat >"$fqgitdir/gitweb/$httpd.rb" <<EOF
+#!/usr/bin/env ruby
 require 'webrick'
-require 'yaml'
-options = YAML::load_file(ARGV[0])
-options[:StartCallback] = proc do
-  File.open(options[:PidFile],"w") do |f|
-    f.puts Process.pid
-  end
-end
-options[:ServerType] = WEBrick::Daemon
+require 'logger'
+options = {
+  :Port => $port,
+  :DocumentRoot => "$root",
+  :Logger => Logger.new('$fqgitdir/gitweb/error.log'),
+  :AccessLog => [
+    [ Logger.new('$fqgitdir/gitweb/access.log'),
+      WEBrick::AccessLog::COMBINED_LOG_FORMAT ]
+  ],
+  :DirectoryIndex => ["gitweb.cgi"],
+  :CGIInterpreter => "$wrapper",
+  :StartCallback => lambda do
+    File.open("$fqgitdir/pid", "w") { |f| f.puts Process.pid }
+  end,
+  :ServerType => WEBrick::Daemon,
+}
+options[:BindAddress] = '127.0.0.1' if "$local" == "true"
 server = WEBrick::HTTPServer.new(options)
 ['INT', 'TERM'].each do |signal|
   trap(signal) {server.shutdown}
 end
 server.start
 EOF
-       # generate a shell script to invoke the above ruby script,
-       # which assumes _ruby_ is in the user's $PATH. that's _one_
-       # portable way to run ruby, which could be installed anywhere,
-       # really.
-       cat >"$fqgitdir/gitweb/$httpd" <<EOF
-#!/bin/sh
-exec ruby "$fqgitdir/gitweb/$httpd.rb" \$*
-EOF
-       chmod +x "$fqgitdir/gitweb/$httpd"
-
-       cat >"$conf" <<EOF
-:Port: $port
-:DocumentRoot: "$fqgitdir/gitweb"
-:DirectoryIndex: ["gitweb.cgi"]
-:PidFile: "$fqgitdir/pid"
-EOF
-       test "$local" = true && echo ':BindAddress: "127.0.0.1"' >> "$conf"
+       chmod +x "$fqgitdir/gitweb/$httpd.rb"
+       # configuration is embedded in server script file, webrick.rb
+       rm -f "$conf"
 }
 
 lighttpd_conf () {
        cat > "$conf" <<EOF
-server.document-root = "$fqgitdir/gitweb"
+server.document-root = "$root"
 server.port = $port
 server.modules = ( "mod_setenv", "mod_cgi" )
 server.indexfiles = ( "gitweb.cgi" )
 server.pid-file = "$fqgitdir/pid"
-server.errorlog = "$fqgitdir/gitweb/error.log"
+server.errorlog = "$fqgitdir/gitweb/$httpd_only/error.log"
 
 # to enable, add "mod_access", "mod_accesslog" to server.modules
 # variable above and uncomment this
-#accesslog.filename = "$fqgitdir/gitweb/access.log"
+#accesslog.filename = "$fqgitdir/gitweb/$httpd_only/access.log"
 
-setenv.add-environment = ( "PATH" => "/usr/local/bin:/usr/bin:/bin" )
+setenv.add-environment = ( "PATH" => env.PATH, "GITWEB_CONFIG" => env.GITWEB_CONFIG )
 
 cgi.assign = ( ".cgi" => "" )
 
@@ -252,40 +321,50 @@ EOF
 }
 
 apache2_conf () {
-       test -z "$module_path" && module_path=/usr/lib/apache2/modules
-       mkdir -p "$GIT_DIR/gitweb/logs"
+       if test -z "$module_path"
+       then
+               test -d "/usr/lib/httpd/modules" &&
+                       module_path="/usr/lib/httpd/modules"
+               test -d "/usr/lib/apache2/modules" &&
+                       module_path="/usr/lib/apache2/modules"
+       fi
        bind=
        test x"$local" = xtrue && bind='127.0.0.1:'
-       echo 'text/css css' > $fqgitdir/mime.types
+       echo 'text/css css' > "$fqgitdir/mime.types"
        cat > "$conf" <<EOF
 ServerName "git-instaweb"
-ServerRoot "$fqgitdir/gitweb"
-DocumentRoot "$fqgitdir/gitweb"
+ServerRoot "$root"
+DocumentRoot "$root"
+ErrorLog "$fqgitdir/gitweb/$httpd_only/error.log"
+CustomLog "$fqgitdir/gitweb/$httpd_only/access.log" combined
 PidFile "$fqgitdir/pid"
 Listen $bind$port
 EOF
 
-       for mod in mime dir; do
-               if test -e $module_path/mod_${mod}.so; then
+       for mod in mime dir env log_config
+       do
+               if test -e $module_path/mod_${mod}.so
+               then
                        echo "LoadModule ${mod}_module " \
                             "$module_path/mod_${mod}.so" >> "$conf"
                fi
        done
        cat >> "$conf" <<EOF
-TypesConfig $fqgitdir/mime.types
+TypesConfig "$fqgitdir/mime.types"
 DirectoryIndex gitweb.cgi
 EOF
 
        # check to see if Dennis Stosberg's mod_perl compatibility patch
        # (<20060621130708.Gcbc6e5c@leonov.stosberg.net>) has been applied
-       if test -f "$module_path/mod_perl.so" && grep '^our $gitbin' \
-                               "$GIT_DIR/gitweb/gitweb.cgi" >/dev/null
+       if test -f "$module_path/mod_perl.so" &&
+          sane_grep 'MOD_PERL' "$root/gitweb.cgi" >/dev/null
        then
                # favor mod_perl if available
                cat >> "$conf" <<EOF
 LoadModule perl_module $module_path/mod_perl.so
 PerlPassEnv GIT_DIR
-PerlPassEnv GIT_EXEC_DIR
+PerlPassEnv GIT_EXEC_PATH
+PerlPassEnv GITWEB_CONFIG
 <Location /gitweb.cgi>
        SetHandler perl-script
        PerlResponseHandler ModPerl::Registry
@@ -296,10 +375,27 @@ EOF
        else
                # plain-old CGI
                resolve_full_httpd
-               list_mods=$(echo "$full_httpd" | sed "s/-f$/-l/")
-               $list_mods | grep 'mod_cgi\.c' >/dev/null 2>&1 || \
-               echo "LoadModule cgi_module $module_path/mod_cgi.so" >> "$conf"
+               list_mods=$(echo "$full_httpd" | sed 's/-f$/-l/')
+               $list_mods | sane_grep 'mod_cgi\.c' >/dev/null 2>&1 || \
+               if test -f "$module_path/mod_cgi.so"
+               then
+                       echo "LoadModule cgi_module $module_path/mod_cgi.so" >> "$conf"
+               else
+                       $list_mods | grep 'mod_cgid\.c' >/dev/null 2>&1 || \
+                       if test -f "$module_path/mod_cgid.so"
+                       then
+                               echo "LoadModule cgid_module $module_path/mod_cgid.so" \
+                                       >> "$conf"
+                       else
+                               echo "You have no CGI support!"
+                               exit 2
+                       fi
+                       echo "ScriptSock logs/gitweb.sock" >> "$conf"
+               fi
                cat >> "$conf" <<EOF
+PassEnv GIT_DIR
+PassEnv GIT_EXEC_PATH
+PassEnv GITWEB_CONFIG
 AddHandler cgi-script .cgi
 <Location /gitweb.cgi>
        Options +ExecCGI
@@ -308,42 +404,206 @@ EOF
        fi
 }
 
-script='
-s#^(my|our) \$projectroot =.*#$1 \$projectroot = "'$(dirname "$fqgitdir")'";#;
-s#(my|our) \$gitbin =.*#$1 \$gitbin = "'$GIT_EXEC_PATH'";#;
-s#(my|our) \$projects_list =.*#$1 \$projects_list = \$projectroot;#;
-s#(my|our) \$git_temp =.*#$1 \$git_temp = "'$fqgitdir/gitweb/tmp'";#;'
-
-gitweb_cgi () {
-       cat > "$1.tmp" <<\EOFGITWEB
-@@GITWEB_CGI@@
-EOFGITWEB
-       # Use the configured full path to perl to match the generated
-       # scripts' 'hashpling' line
-       "$PERL" -p -e "$script" "$1.tmp"  > "$1"
-       chmod +x "$1"
-       rm -f "$1.tmp"
+mongoose_conf() {
+       cat > "$conf" <<EOF
+# Mongoose web server configuration file.
+# Lines starting with '#' and empty lines are ignored.
+# For detailed description of every option, visit
+# http://code.google.com/p/mongoose/wiki/MongooseManual
+
+root           $root
+ports          $port
+index_files    gitweb.cgi
+#ssl_cert      $fqgitdir/gitweb/ssl_cert.pem
+error_log      $fqgitdir/gitweb/$httpd_only/error.log
+access_log     $fqgitdir/gitweb/$httpd_only/access.log
+
+#cgi setup
+cgi_env                PATH=$PATH,GIT_DIR=$GIT_DIR,GIT_EXEC_PATH=$GIT_EXEC_PATH,GITWEB_CONFIG=$GITWEB_CONFIG
+cgi_interp     $PERL
+cgi_ext                cgi,pl
+
+# mimetype mapping
+mime_types     .gz=application/x-gzip,.tar.gz=application/x-tgz,.tgz=application/x-tgz,.tar=application/x-tar,.zip=application/zip,.gif=image/gif,.jpg=image/jpeg,.jpeg=image/jpeg,.png=image/png,.css=text/css,.html=text/html,.htm=text/html,.js=text/javascript,.c=text/plain,.cpp=text/plain,.log=text/plain,.conf=text/plain,.text=text/plain,.txt=text/plain,.dtd=text/xml,.bz2=application/x-bzip,.tbz=application/x-bzip-compressed-tar,.tar.bz2=application/x-bzip-compressed-tar
+EOF
+}
+
+plackup_conf () {
+       # generate a standalone 'plackup' server script in $fqgitdir/gitweb
+       # with embedded configuration; it does not use "$conf" file
+       cat > "$fqgitdir/gitweb/gitweb.psgi" <<EOF
+#!$PERL
+
+# gitweb - simple web interface to track changes in git repositories
+#          PSGI wrapper and server starter (see http://plackperl.org)
+
+use strict;
+
+use IO::Handle;
+use Plack::MIME;
+use Plack::Builder;
+use Plack::App::WrapCGI;
+use CGI::Emulate::PSGI 0.07; # minimum version required to work with gitweb
+
+# mimetype mapping (from lighttpd_conf)
+Plack::MIME->add_type(
+       ".pdf"          =>      "application/pdf",
+       ".sig"          =>      "application/pgp-signature",
+       ".spl"          =>      "application/futuresplash",
+       ".class"        =>      "application/octet-stream",
+       ".ps"           =>      "application/postscript",
+       ".torrent"      =>      "application/x-bittorrent",
+       ".dvi"          =>      "application/x-dvi",
+       ".gz"           =>      "application/x-gzip",
+       ".pac"          =>      "application/x-ns-proxy-autoconfig",
+       ".swf"          =>      "application/x-shockwave-flash",
+       ".tar.gz"       =>      "application/x-tgz",
+       ".tgz"          =>      "application/x-tgz",
+       ".tar"          =>      "application/x-tar",
+       ".zip"          =>      "application/zip",
+       ".mp3"          =>      "audio/mpeg",
+       ".m3u"          =>      "audio/x-mpegurl",
+       ".wma"          =>      "audio/x-ms-wma",
+       ".wax"          =>      "audio/x-ms-wax",
+       ".ogg"          =>      "application/ogg",
+       ".wav"          =>      "audio/x-wav",
+       ".gif"          =>      "image/gif",
+       ".jpg"          =>      "image/jpeg",
+       ".jpeg"         =>      "image/jpeg",
+       ".png"          =>      "image/png",
+       ".xbm"          =>      "image/x-xbitmap",
+       ".xpm"          =>      "image/x-xpixmap",
+       ".xwd"          =>      "image/x-xwindowdump",
+       ".css"          =>      "text/css",
+       ".html"         =>      "text/html",
+       ".htm"          =>      "text/html",
+       ".js"           =>      "text/javascript",
+       ".asc"          =>      "text/plain",
+       ".c"            =>      "text/plain",
+       ".cpp"          =>      "text/plain",
+       ".log"          =>      "text/plain",
+       ".conf"         =>      "text/plain",
+       ".text"         =>      "text/plain",
+       ".txt"          =>      "text/plain",
+       ".dtd"          =>      "text/xml",
+       ".xml"          =>      "text/xml",
+       ".mpeg"         =>      "video/mpeg",
+       ".mpg"          =>      "video/mpeg",
+       ".mov"          =>      "video/quicktime",
+       ".qt"           =>      "video/quicktime",
+       ".avi"          =>      "video/x-msvideo",
+       ".asf"          =>      "video/x-ms-asf",
+       ".asx"          =>      "video/x-ms-asf",
+       ".wmv"          =>      "video/x-ms-wmv",
+       ".bz2"          =>      "application/x-bzip",
+       ".tbz"          =>      "application/x-bzip-compressed-tar",
+       ".tar.bz2"      =>      "application/x-bzip-compressed-tar",
+       ""              =>      "text/plain"
+);
+
+my \$app = builder {
+       # to be able to override \$SIG{__WARN__} to log build time warnings
+       use CGI::Carp; # it sets \$SIG{__WARN__} itself
+
+       my \$logdir = "$fqgitdir/gitweb/$httpd_only";
+       open my \$access_log_fh, '>>', "\$logdir/access.log"
+               or die "Couldn't open access log '\$logdir/access.log': \$!";
+       open my \$error_log_fh,  '>>', "\$logdir/error.log"
+               or die "Couldn't open error log '\$logdir/error.log': \$!";
+
+       \$access_log_fh->autoflush(1);
+       \$error_log_fh->autoflush(1);
+
+       # redirect build time warnings to error.log
+       \$SIG{'__WARN__'} = sub {
+               my \$msg = shift;
+               # timestamp warning like in CGI::Carp::warn
+               my \$stamp = CGI::Carp::stamp();
+               \$msg =~ s/^/\$stamp/gm;
+               print \$error_log_fh \$msg;
+       };
+
+       # write errors to error.log, access to access.log
+       enable 'AccessLog',
+               format => "combined",
+               logger => sub { print \$access_log_fh @_; };
+       enable sub {
+               my \$app = shift;
+               sub {
+                       my \$env = shift;
+                       \$env->{'psgi.errors'} = \$error_log_fh;
+                       \$app->(\$env);
+               }
+       };
+       # gitweb currently doesn't work with $SIG{CHLD} set to 'IGNORE',
+       # because it uses 'close $fd or die...' on piped filehandle $fh
+       # (which causes the parent process to wait for child to finish).
+       enable_if { \$SIG{'CHLD'} eq 'IGNORE' } sub {
+               my \$app = shift;
+               sub {
+                       my \$env = shift;
+                       local \$SIG{'CHLD'} = 'DEFAULT';
+                       local \$SIG{'CLD'}  = 'DEFAULT';
+                       \$app->(\$env);
+               }
+       };
+       # serve static files, i.e. stylesheet, images, script
+       enable 'Static',
+               path => sub { m!\.(js|css|png)\$! && s!^/gitweb/!! },
+               root => "$root/",
+               encoding => 'utf-8'; # encoding for 'text/plain' files
+       # convert CGI application to PSGI app
+       Plack::App::WrapCGI->new(script => "$root/gitweb.cgi")->to_app;
+};
+
+# make it runnable as standalone app,
+# like it would be run via 'plackup' utility
+if (__FILE__ eq \$0) {
+       require Plack::Runner;
+
+       my \$runner = Plack::Runner->new();
+       \$runner->parse_options(qw(--env deployment --port $port),
+                              "$local" ? qw(--host 127.0.0.1) : ());
+       \$runner->run(\$app);
+}
+__END__
+EOF
+
+       chmod a+x "$fqgitdir/gitweb/gitweb.psgi"
+       # configuration is embedded in server script file, gitweb.psgi
+       rm -f "$conf"
 }
 
-gitweb_css () {
-       cat > "$1" <<\EOFGITWEB
-@@GITWEB_CSS@@
-EOFGITWEB
+gitweb_conf() {
+       cat > "$fqgitdir/gitweb/gitweb_config.perl" <<EOF
+#!/usr/bin/perl
+our \$projectroot = "$(dirname "$fqgitdir")";
+our \$git_temp = "$fqgitdir/gitweb/tmp";
+our \$projects_list = \$projectroot;
+EOF
 }
 
-gitweb_cgi "$GIT_DIR/gitweb/gitweb.cgi"
-gitweb_css "$GIT_DIR/gitweb/gitweb.css"
+gitweb_conf
+
+resolve_full_httpd
+mkdir -p "$fqgitdir/gitweb/$httpd_only"
 
 case "$httpd" in
 *lighttpd*)
        lighttpd_conf
        ;;
-*apache2*)
+*apache2*|*httpd*)
        apache2_conf
        ;;
 webrick)
        webrick_conf
        ;;
+*mongoose*)
+       mongoose_conf
+       ;;
+*plackup*)
+       plackup_conf
+       ;;
 *)
        echo "Unknown httpd specified: $httpd"
        exit 1
@@ -354,7 +614,7 @@ start_httpd
 url=http://127.0.0.1:$port
 
 if test -n "$browser"; then
-       git web--browse -b "$browser" $url || echo $url
+       httpd_is_ready && git web--browse -b "$browser" $url || echo $url
 else
-       git web--browse -c "instaweb.browser" $url || echo $url
+       httpd_is_ready && git web--browse -c "instaweb.browser" $url || echo $url
 fi
index 1dadbb49666c6d796df76babbfd291a2de4357e4..615753c83c38f4a97752d5ca3400beeef4acd5f1 100755 (executable)
@@ -44,9 +44,8 @@ esac
 # MRC is the current "merge reference commit"
 # MRT is the current "merge result tree"
 
-MRC=$head MSG= PARENT="-p $head"
+MRC=$(git rev-parse --verify -q $head)
 MRT=$(git write-tree)
-CNT=1 ;# counting our head
 NON_FF_MERGE=0
 OCTOPUS_FAILURE=0
 for SHA1 in $remotes
@@ -61,19 +60,17 @@ do
                exit 2
        esac
 
+       eval pretty_name=\${GITHEAD_$SHA1:-$SHA1}
        common=$(git merge-base --all $SHA1 $MRC) ||
-               die "Unable to find common commit with $SHA1"
+               die "Unable to find common commit with $pretty_name"
 
        case "$LF$common$LF" in
        *"$LF$SHA1$LF"*)
-               echo "Already up-to-date with $SHA1"
+               echo "Already up-to-date with $pretty_name"
                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.
@@ -81,7 +78,7 @@ do
                # 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"
+               echo "Fast-forwarding to: $pretty_name"
                git read-tree -u -m $head $SHA1 || exit
                MRC=$SHA1 MRT=$(git write-tree)
                continue
@@ -89,7 +86,7 @@ do
 
        NON_FF_MERGE=1
 
-       echo "Trying simple merge with $SHA1"
+       echo "Trying simple merge with $pretty_name"
        git read-tree -u -m --aggressive  $common $MRT $SHA1 || exit 2
        next=$(git write-tree 2>/dev/null)
        if test $? -ne 0
index e1eb9632660146396a0b5f3f2a410d8cb027ff9d..b86402afa5d079df8d1cf6eebb15e5727cc8a5de 100755 (executable)
 # been handled already by git read-tree, but that one doesn't
 # do any merges that might change the tree layout.
 
+USAGE='<orig blob> <our blob> <their blob> <path>'
+USAGE="$USAGE <orig mode> <our mode> <their mode>"
+LONG_USAGE="Usage: git merge-one-file $USAGE
+
+Blob ids and modes should be empty for missing files."
+
+if ! test "$#" -eq 7
+then
+       echo "$LONG_USAGE"
+       exit 1
+fi
+
 case "${1:-.}${2:-.}${3:-.}" in
 #
 # Deleted in both or deleted in one and unchanged in the other
@@ -95,7 +107,7 @@ case "${1:-.}${2:-.}${3:-.}" in
                # remove lines that are unique to ours.
                orig=`git-unpack-file $2`
                sz0=`wc -c <"$orig"`
-               diff -u -La/$orig -Lb/$orig $orig $src2 | git apply --no-add
+               @@DIFF@@ -u -La/$orig -Lb/$orig $orig $src2 | git apply --no-add
                sz1=`wc -c <"$orig"`
 
                # If we do not have enough common material, it is not
@@ -113,6 +125,10 @@ case "${1:-.}${2:-.}${3:-.}" in
        src1=`git-unpack-file $2`
        git merge-file "$src1" "$orig" "$src2"
        ret=$?
+       msg=
+       if [ $ret -ne 0 ]; then
+               msg='content conflict'
+       fi
 
        # Create the working tree file, using "our tree" version from the
        # index, and then store the result of the merge.
@@ -120,7 +136,10 @@ case "${1:-.}${2:-.}${3:-.}" in
        rm -f -- "$orig" "$src1" "$src2"
 
        if [ "$6" != "$7" ]; then
-               echo "ERROR: Permissions conflict: $5->$6,$7."
+               if [ -n "$msg" ]; then
+                       msg="$msg, "
+               fi
+               msg="${msg}permissions conflict: $5->$6,$7"
                ret=1
        fi
        if [ "$1" = '' ]; then
@@ -128,7 +147,7 @@ case "${1:-.}${2:-.}${3:-.}" in
        fi
 
        if [ $ret -ne 0 ]; then
-               echo "ERROR: Merge conflict in $4"
+               echo "ERROR: $msg in $4"
                exit 1
        fi
        exec git update-index -- "$4"
index a16a2795d70779365212c09b6d0dd42d1746842b..b5e1943b1d166b543998c80579c65e82de36516a 100644 (file)
@@ -1,3 +1,4 @@
+#!/bin/sh
 # git-mergetool--lib is a library for common merge tool functions
 diff_mode() {
        test "$TOOL_MODE" = diff
@@ -18,6 +19,9 @@ translate_merge_tool_path () {
        emerge)
                echo emacs
                ;;
+       araxis)
+               echo compare
+               ;;
        *)
                echo "$1"
                ;;
@@ -31,7 +35,7 @@ check_unchanged () {
                while true; do
                        echo "$MERGED seems unchanged."
                        printf "Was the merge successful? [y/n] "
-                       read answer < /dev/tty
+                       read answer
                        case "$answer" in
                        y*|Y*) status=0; break ;;
                        n*|N*) status=1; break ;;
@@ -43,7 +47,7 @@ check_unchanged () {
 valid_tool () {
        case "$1" in
        kdiff3 | tkdiff | xxdiff | meld | opendiff | \
-       emerge | vimdiff | gvimdiff | ecmerge | diffuse)
+       emerge | vimdiff | gvimdiff | ecmerge | diffuse | araxis | p4merge)
                ;; # happy
        tortoisemerge)
                if ! merge_mode; then
@@ -127,6 +131,19 @@ run_merge_tool () {
                        "$merge_tool_path" "$LOCAL" "$REMOTE"
                fi
                ;;
+       p4merge)
+               if merge_mode; then
+                   touch "$BACKUP"
+                       if $base_present; then
+                               "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED"
+                       else
+                               "$merge_tool_path" "$LOCAL" "$LOCAL" "$REMOTE" "$MERGED"
+                       fi
+                       check_unchanged
+               else
+                       "$merge_tool_path" "$LOCAL" "$REMOTE"
+               fi
+               ;;
        meld)
                if merge_mode; then
                        touch "$BACKUP"
@@ -228,8 +245,8 @@ run_merge_tool () {
                        fi
                        check_unchanged
                else
-                       "$merge_tool_path" "$LOCAL" "$REMOTE" \
-                               --default --mode=merge2 --to="$MERGED"
+                       "$merge_tool_path" --default --mode=diff2 \
+                               "$LOCAL" "$REMOTE"
                fi
                ;;
        emerge)
@@ -248,7 +265,7 @@ run_merge_tool () {
                        status=$?
                else
                        "$merge_tool_path" -f emerge-files-command \
-                               "$LOCAL" "$REMOTE" "$(basename "$MERGED")"
+                               "$LOCAL" "$REMOTE"
                fi
                ;;
        tortoisemerge)
@@ -263,6 +280,24 @@ run_merge_tool () {
                        status=1
                fi
                ;;
+       araxis)
+               if merge_mode; then
+                       touch "$BACKUP"
+                       if $base_present; then
+                               "$merge_tool_path" -wait -merge -3 -a1 \
+                                       "$BASE" "$LOCAL" "$REMOTE" "$MERGED" \
+                                       >/dev/null 2>&1
+                       else
+                               "$merge_tool_path" -wait -2 \
+                                       "$LOCAL" "$REMOTE" "$MERGED" \
+                                       >/dev/null 2>&1
+                       fi
+                       check_unchanged
+               else
+                       "$merge_tool_path" -wait -2 "$LOCAL" "$REMOTE" \
+                               >/dev/null 2>&1
+               fi
+               ;;
        *)
                merge_tool_cmd="$(get_merge_tool_cmd "$1")"
                if test -z "$merge_tool_cmd"; then
@@ -302,17 +337,16 @@ guess_merge_tool () {
                else
                        tools="opendiff kdiff3 tkdiff xxdiff meld $tools"
                fi
-               tools="$tools gvimdiff diffuse ecmerge"
+               tools="$tools gvimdiff diffuse ecmerge p4merge araxis"
        fi
-       if echo "${VISUAL:-$EDITOR}" | grep emacs > /dev/null 2>&1; then
-               # $EDITOR is emacs so add emerge as a candidate
-               tools="$tools emerge vimdiff"
-       elif echo "${VISUAL:-$EDITOR}" | grep vim > /dev/null 2>&1; then
-               # $EDITOR is vim so add vimdiff as a candidate
+       case "${VISUAL:-$EDITOR}" in
+       *vim*)
                tools="$tools vimdiff emerge"
-       else
+               ;;
+       *)
                tools="$tools emerge vimdiff"
-       fi
+               ;;
+       esac
        echo >&2 "merge tool candidates: $tools"
 
        # Loop over each candidate and stop when a valid merge tool is found.
index b52a7410bcb7b37dce0f4d6213dddedd2c1e42e7..2f8dc441c6d575323f2fad920ecdb7b20e1445e6 100755 (executable)
@@ -264,24 +264,46 @@ merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo fa
 
 last_status=0
 rollup_status=0
+rerere=false
+
+files_to_merge() {
+    if test "$rerere" = true
+    then
+       git rerere status
+    else
+       git ls-files -u | sed -e 's/^[^ ]*      //' | sort -u
+    fi
+}
+
 
 if test $# -eq 0 ; then
-    files=$(git ls-files -u | sed -e 's/^[^    ]*      //' | sort -u)
+    cd_to_toplevel
+
+    if test -e "$GIT_DIR/MERGE_RR"
+    then
+       rerere=true
+    fi
+
+    files=$(files_to_merge)
     if test -z "$files" ; then
        echo "No files need merging"
        exit 0
     fi
-    echo Merging the files: "$files"
-    git ls-files -u |
-    sed -e 's/^[^      ]*      //' |
-    sort -u |
+
+    # Save original stdin
+    exec 3<&0
+
+    printf "Merging:\n"
+    printf "$files\n"
+
+    files_to_merge |
     while IFS= read i
     do
        if test $last_status -ne 0; then
-           prompt_after_failed_merge < /dev/tty || exit 1
+           prompt_after_failed_merge <&3 || exit 1
        fi
        printf "\n"
-       merge_file "$i" < /dev/tty > /dev/tty
+       merge_file "$i" <&3
        last_status=$?
        if test $last_status -ne 0; then
            rollup_status=1
old mode 100755 (executable)
new mode 100644 (file)
index a296719..5f47b18
@@ -60,205 +60,36 @@ get_default_remote () {
        echo ${origin:-origin}
 }
 
-get_remote_default_refs_for_push () {
-       data_source=$(get_data_source "$1")
-       case "$data_source" in
-       '' | branches | self)
-               ;; # no default push mapping, just send matching refs.
-       config)
-               git 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
-}
-
-# Called from canon_refs_list_for_fetch -d "$remote", which
-# is called from get_remote_default_refs_for_fetch to grok
-# refspecs that are retrieved from the configuration, but not
-# from get_remote_refs_for_fetch when it deals with refspecs
-# supplied on the command line.  $ls_remote_result has the list
-# of refs available at remote.
-#
-# The first token returned is either "explicit" or "glob"; this
-# is to help prevent randomly "globbed" ref from being chosen as
-# a merge candidate
-expand_refs_wildcard () {
-       echo "$ls_remote_result" |
-       git fetch--tool expand-refs-wildcard "-" "$@"
-}
-
-# Subroutine to canonicalize remote:local notation.
-canon_refs_list_for_fetch () {
-       # If called from get_remote_default_refs_for_fetch
-       # leave the branches in branch.${curr_branch}.merge alone,
-       # or the first one otherwise; add prefix . to the rest
-       # to prevent the secondary branches to be merged by default.
-       merge_branches=
-       curr_branch=
-       if test "$1" = "-d"
-       then
-               shift ; remote="$1" ; shift
-               set $(expand_refs_wildcard "$remote" "$@")
-               is_explicit="$1"
-               shift
-               if test "$remote" = "$(get_default_remote)"
-               then
-                       curr_branch=$(git symbolic-ref -q HEAD | \
-                           sed -e 's|^refs/heads/||')
-                       merge_branches=$(git config \
-                           --get-all "branch.${curr_branch}.merge")
-               fi
-               if test -z "$merge_branches" && test $is_explicit != explicit
-               then
-                       merge_branches=..this.will.never.match.any.ref..
-               fi
-       fi
-       for ref
-       do
-               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[^:]*:\(.*\)')
-               dot_prefix=.
-               if test -z "$merge_branches"
-               then
-                       merge_branches=$remote
-                       dot_prefix=
-               else
-                       for merge_branch in $merge_branches
-                       do
-                           [ "$remote" = "$merge_branch" ] &&
-                           dot_prefix= && break
-                       done
-               fi
-               case "$remote" in
-               '' | HEAD ) remote=HEAD ;;
-               refs/*) ;;
-               heads/* | tags/* | remotes/* ) remote="refs/$remote" ;;
-               *) remote="refs/heads/$remote" ;;
-               esac
-               case "$local" in
-               '') local= ;;
-               refs/*) ;;
-               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}"
-       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
-       '')
-               echo "HEAD:" ;;
-       self)
-               canon_refs_list_for_fetch -d "$1" \
-                       $(git for-each-ref --format='%(refname):')
-               ;;
-       config)
-               canon_refs_list_for_fetch -d "$1" \
-                       $(git config --get-all "remote.$1.fetch") ;;
-       branches)
-               remote_branch=$(sed -ne '/#/s/.*#//p' "$GIT_DIR/branches/$1")
-               case "$remote_branch" in '') remote_branch=master ;; esac
-               echo "refs/heads/${remote_branch}:refs/heads/$1"
-               ;;
-       remotes)
-               canon_refs_list_for_fetch -d "$1" $(sed -ne '/^Pull: */{
-                                               s///p
-                                       }' "$GIT_DIR/remotes/$1")
-               ;;
-       *)
-               die "internal error: get-remote-default-ref-for-fetch $1" ;;
-       esac
-}
-
-get_remote_refs_for_push () {
+get_remote_merge_branch () {
        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
+       0|1)
+           origin="$1"
+           default=$(get_default_remote)
+           test -z "$origin" && origin=$default
+           curr_branch=$(git symbolic-ref -q HEAD)
+           [ "$origin" = "$default" ] &&
+           echo $(git for-each-ref --format='%(upstream)' $curr_branch)
            ;;
-       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
-}
-
-get_uploadpack () {
-       data_source=$(get_data_source "$1")
-       case "$data_source" in
-       config)
-               uplp=$(git config --get "remote.$1.uploadpack")
-               echo ${uplp:-git-upload-pack}
-               ;;
        *)
-               echo "git-upload-pack"
+           repo=$1
+           shift
+           ref=$1
+           # FIXME: It should return the tracking branch
+           #        Currently only works with the default mapping
+           case "$ref" in
+           +*)
+               ref=$(expr "z$ref" : 'z+\(.*\)')
                ;;
+           esac
+           expr "z$ref" : 'z.*:' >/dev/null || ref="${ref}:"
+           remote=$(expr "z$ref" : 'z\([^:]*\):')
+           case "$remote" in
+           '' | HEAD ) remote=HEAD ;;
+           heads/*) remote=${remote#heads/} ;;
+           refs/heads/*) remote=${remote#refs/heads/} ;;
+           refs/* | tags/* | remotes/* ) remote=
+           esac
+
+           [ -n "$remote" ] && echo "refs/remotes/$repo/$remote"
        esac
 }
index 35261539ab80ffa46fef945dce1a82c5636c1b49..8eb74d45debe7ab2bd2cc9b5ed57b1ba49d94bdb 100755 (executable)
@@ -13,13 +13,37 @@ set_reflog_action "pull $*"
 require_work_tree
 cd_to_toplevel
 
-test -z "$(git ls-files -u)" ||
-       die "You are in the middle of a conflicted merge."
 
-strategy_args= diffstat= no_commit= squash= no_ff= log_arg= verbosity=
+die_conflict () {
+    git diff-index --cached --name-status -r --ignore-submodules HEAD --
+    if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
+       die "Pull is not possible because you have unmerged files.
+Please, fix them up in the work tree, and then use 'git add/rm <file>'
+as appropriate to mark resolution, or use 'git commit -a'."
+    else
+       die "Pull is not possible because you have unmerged files."
+    fi
+}
+
+die_merge () {
+    if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
+       die "You have not concluded your merge (MERGE_HEAD exists).
+Please, commit your changes before you can merge."
+    else
+       die "You have not concluded your merge (MERGE_HEAD exists)."
+    fi
+}
+
+test -z "$(git ls-files -u)" || die_conflict
+test -f "$GIT_DIR/MERGE_HEAD" && die_merge
+
+strategy_args= diffstat= no_commit= squash= no_ff= ff_only=
+log_arg= verbosity= progress=
+merge_args=
 curr_branch=$(git symbolic-ref -q HEAD)
-curr_branch_short=$(echo "$curr_branch" | sed "s|refs/heads/||")
+curr_branch_short="${curr_branch#refs/heads/}"
 rebase=$(git config --bool branch.$curr_branch_short.rebase)
+dry_run=
 while :
 do
        case "$1" in
@@ -27,6 +51,8 @@ do
                verbosity="$verbosity -q" ;;
        -v|--verbose)
                verbosity="$verbosity -v" ;;
+       --progress)
+               progress=--progress ;;
        -n|--no-stat|--no-summary)
                diffstat=--no-stat ;;
        --stat|--summary)
@@ -45,6 +71,8 @@ do
                no_ff=--ff ;;
        --no-ff)
                no_ff=--no-ff ;;
+       --ff-only)
+               ff_only=--ff-only ;;
        -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\
                --strateg=*|--strategy=*|\
        -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy)
@@ -59,12 +87,27 @@ do
                esac
                strategy_args="${strategy_args}-s $strategy "
                ;;
+       -X*)
+               case "$#,$1" in
+               1,-X)
+                       usage ;;
+               *,-X)
+                       xx="-X $(git rev-parse --sq-quote "$2")"
+                       shift ;;
+               *,*)
+                       xx=$(git rev-parse --sq-quote "$1") ;;
+               esac
+               merge_args="$merge_args$xx "
+               ;;
        -r|--r|--re|--reb|--reba|--rebas|--rebase)
                rebase=true
                ;;
        --no-r|--no-re|--no-reb|--no-reba|--no-rebas|--no-rebase)
                rebase=false
                ;;
+       --d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run)
+               dry_run=--dry-run
+               ;;
        -h|--h|--he|--hel|--help)
                usage
                ;;
@@ -88,52 +131,97 @@ error_on_no_merge_candidates () {
                esac
        done
 
+       if test true = "$rebase"
+       then
+               op_type=rebase
+               op_prep=against
+       else
+               op_type=merge
+               op_prep=with
+       fi
+
        curr_branch=${curr_branch#refs/heads/}
+       upstream=$(git config "branch.$curr_branch.merge")
+       remote=$(git config "branch.$curr_branch.remote")
 
-       if [ -z "$curr_branch" ]; then
+       if [ $# -gt 1 ]; then
+               if [ "$rebase" = true ]; then
+                       printf "There is no candidate for rebasing against "
+               else
+                       printf "There are no candidates for merging "
+               fi
+               echo "among the refs that you just fetched."
+               echo "Generally this means that you provided a wildcard refspec which had no"
+               echo "matches on the remote end."
+       elif [ $# -gt 0 ] && [ "$1" != "$remote" ]; then
+               echo "You asked to pull from the remote '$1', but did not specify"
+               echo "a branch. Because this is not the default configured remote"
+               echo "for your current branch, you must specify a branch on the command line."
+       elif [ -z "$curr_branch" ]; then
                echo "You are not currently on a branch, so I cannot use any"
                echo "'branch.<branchname>.merge' in your configuration file."
-               echo "Please specify which branch you want to merge on the command"
+               echo "Please specify which remote branch you want to use on the command"
                echo "line and try again (e.g. 'git pull <repository> <refspec>')."
                echo "See git-pull(1) for details."
-       else
+       elif [ -z "$upstream" ]; then
                echo "You asked me to pull without telling me which branch you"
-               echo "want to merge with, and 'branch.${curr_branch}.merge' in"
-               echo "your configuration file does not tell me either.  Please"
-               echo "specify which branch you want to merge on the command line and"
+               echo "want to $op_type $op_prep, and 'branch.${curr_branch}.merge' in"
+               echo "your configuration file does not tell me, either. Please"
+               echo "specify which branch you want to use on the command line and"
                echo "try again (e.g. 'git pull <repository> <refspec>')."
                echo "See git-pull(1) for details."
                echo
-               echo "If you often merge with the same branch, you may want to"
-               echo "configure the following variables in your configuration"
-               echo "file:"
+               echo "If you often $op_type $op_prep the same branch, you may want to"
+               echo "use something like the following in your configuration file:"
                echo
-               echo "    branch.${curr_branch}.remote = <nickname>"
-               echo "    branch.${curr_branch}.merge = <remote-ref>"
-               echo "    remote.<nickname>.url = <url>"
-               echo "    remote.<nickname>.fetch = <refspec>"
+               echo "    [branch \"${curr_branch}\"]"
+               echo "    remote = <nickname>"
+               echo "    merge = <remote-ref>"
+               test rebase = "$op_type" &&
+                       echo "    rebase = true"
+               echo
+               echo "    [remote \"<nickname>\"]"
+               echo "    url = <url>"
+               echo "    fetch = <refspec>"
                echo
                echo "See git-config(1) for details."
+       else
+               echo "Your configuration specifies to $op_type $op_prep the ref '${upstream#refs/heads/}'"
+               echo "from the remote, but no such ref was fetched."
        fi
        exit 1
 }
 
 test true = "$rebase" && {
-       git update-index --ignore-submodules --refresh &&
-       git diff-files --ignore-submodules --quiet &&
-       git diff-index --ignore-submodules --cached --quiet HEAD -- ||
-       die "refusing to pull with rebase: your working tree is not up-to-date"
-
+       if ! git rev-parse -q --verify HEAD >/dev/null
+       then
+               # On an unborn branch
+               if test -f "$GIT_DIR/index"
+               then
+                       die "updating an unborn branch with changes added to the index"
+               fi
+       else
+               git update-index --ignore-submodules --refresh &&
+               git diff-files --ignore-submodules --quiet &&
+               git diff-index --ignore-submodules --cached --quiet HEAD -- ||
+               die "refusing to pull with rebase: your working tree is not up-to-date"
+       fi
+       oldremoteref= &&
        . git-parse-remote &&
-       origin="$1"
-       test -z "$origin" && origin=$(get_default_remote)
-       reflist="$(get_remote_refs_for_fetch "$@" 2>/dev/null |
-               sed "s|refs/heads/\(.*\):|\1|")" &&
-       oldremoteref="$(git rev-parse -q --verify \
-               "refs/remotes/$origin/$reflist")"
+       remoteref="$(get_remote_merge_branch "$@" 2>/dev/null)" &&
+       oldremoteref="$(git rev-parse -q --verify "$remoteref")" &&
+       for reflog in $(git rev-list -g $remoteref 2>/dev/null)
+       do
+               if test "$reflog" = "$(git merge-base $reflog $curr_branch)"
+               then
+                       oldremoteref="$reflog"
+                       break
+               fi
+       done
 }
 orig_head=$(git rev-parse -q --verify HEAD)
-git fetch $verbosity --update-head-ok "$@" || exit 1
+git fetch $verbosity $progress $dry_run --update-head-ok "$@" || exit 1
+test -z "$dry_run" || exit 0
 
 curr_head=$(git rev-parse -q --verify HEAD)
 if test -n "$orig_head" && test "$curr_head" != "$orig_head"
@@ -145,7 +233,7 @@ then
        # 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: fast-forwarding your working tree from"
        echo >&2 "Warning: commit $orig_head."
        git update-index -q --refresh
        git read-tree -u -m "$orig_head" "$curr_head" ||
@@ -164,25 +252,16 @@ merge_head=$(sed -e '/    not-for-merge   /d' \
 
 case "$merge_head" in
 '')
-       case $? in
-       0) error_on_no_merge_candidates "$@";;
-       1) echo >&2 "You are not currently on a branch; you must explicitly"
-          echo >&2 "specify which branch you wish to merge:"
-          echo >&2 "  git pull <remote> <branch>"
-          exit 1;;
-       *) exit $?;;
-       esac
+       error_on_no_merge_candidates "$@"
        ;;
 ?*' '?*)
        if test -z "$orig_head"
        then
-               echo >&2 "Cannot merge multiple branches into empty head"
-               exit 1
+               die "Cannot merge multiple branches into empty head"
        fi
        if test true = "$rebase"
        then
-               echo >&2 "Cannot rebase onto multiple branches"
-               exit 1
+               die "Cannot rebase onto multiple branches"
        fi
        ;;
 esac
@@ -194,9 +273,25 @@ then
        exit
 fi
 
+if test true = "$rebase"
+then
+       o=$(git show-branch --merge-base $curr_branch $merge_head $oldremoteref)
+       if test "$oldremoteref" = "$o"
+       then
+               unset oldremoteref
+       fi
+fi
+
 merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit
-test true = "$rebase" &&
-       exec git-rebase $diffstat $strategy_args --onto $merge_head \
-       ${oldremoteref:-$merge_head}
-exec git-merge $diffstat $no_commit $squash $no_ff $log_arg $strategy_args \
-       "$merge_name" HEAD $merge_head $verbosity
+case "$rebase" in
+true)
+       eval="git-rebase $diffstat $strategy_args $merge_args"
+       eval="$eval --onto $merge_head ${oldremoteref:-$merge_head}"
+       ;;
+*)
+       eval="git-merge $diffstat $no_commit $squash $no_ff $ff_only"
+       eval="$eval  $log_arg $strategy_args $merge_args"
+       eval="$eval \"\$merge_name\" HEAD $merge_head $verbosity"
+       ;;
+esac
+eval "exec $eval"
index 314cd364b8f4df5e170dd0ffd9e874b3e6c2737c..a27952d9fdfb517f684b7d304831bf74d0ce237b 100755 (executable)
@@ -20,6 +20,7 @@ v,verbose          display a diffstat of what changed upstream
 onto=              rebase onto given branch instead of upstream
 p,preserve-merges  try to recreate merges instead of ignoring them
 s,strategy=        use the given merge strategy
+no-ff              cherry-pick all commits, even if unchanged
 m,merge            always used (no-op)
 i,interactive      always used (no-op)
  Actions:
@@ -28,32 +29,98 @@ abort              abort rebasing process and restore original branch
 skip               skip current patch and continue rebasing process
 no-verify          override pre-rebase hook from stopping the operation
 root               rebase all reachable commmits up to the root(s)
+autosquash         move commits that begin with squash!/fixup! under -i
 "
 
 . git-sh-setup
 require_work_tree
 
 DOTEST="$GIT_DIR/rebase-merge"
+
+# The file containing rebase commands, comments, and empty lines.
+# This file is created by "git rebase -i" then edited by the user.  As
+# the lines are processed, they are removed from the front of this
+# file and written to the tail of $DONE.
 TODO="$DOTEST"/git-rebase-todo
+
+# The rebase command lines that have already been processed.  A line
+# is moved here when it is first handled, before any associated user
+# actions.
 DONE="$DOTEST"/done
+
+# The commit message that is planned to be used for any changes that
+# need to be committed following a user interaction.
 MSG="$DOTEST"/message
+
+# The file into which is accumulated the suggested commit message for
+# squash/fixup commands.  When the first of a series of squash/fixups
+# is seen, the file is created and the commit message from the
+# previous commit and from the first squash/fixup commit are written
+# to it.  The commit message for each subsequent squash/fixup commit
+# is appended to the file as it is processed.
+#
+# The first line of the file is of the form
+#     # This is a combination of $COUNT commits.
+# where $COUNT is the number of commits whose messages have been
+# written to the file so far (including the initial "pick" commit).
+# Each time that a commit message is processed, this line is read and
+# updated.  It is deleted just before the combined commit is made.
 SQUASH_MSG="$DOTEST"/message-squash
+
+# If the current series of squash/fixups has not yet included a squash
+# command, then this file exists and holds the commit message of the
+# original "pick" commit.  (If the series ends without a "squash"
+# command, then this can be used as the commit message of the combined
+# commit without opening the editor.)
+FIXUP_MSG="$DOTEST"/message-fixup
+
+# $REWRITTEN is the name of a directory containing files for each
+# commit that is reachable by at least one merge base of $HEAD and
+# $UPSTREAM. They are not necessarily rewritten, but their children
+# might be.  This ensures that commits on merged, but otherwise
+# unrelated side branches are left alone. (Think "X" in the man page's
+# example.)
 REWRITTEN="$DOTEST"/rewritten
+
 DROPPED="$DOTEST"/dropped
+
+# A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
+# GIT_AUTHOR_DATE that will be used for the commit that is currently
+# being rebased.
+AUTHOR_SCRIPT="$DOTEST"/author-script
+
+# When an "edit" rebase command is being processed, the SHA1 of the
+# commit to be edited is recorded in this file.  When "git rebase
+# --continue" is executed, if there are any staged changes then they
+# will be amended to the HEAD commit, but only provided the HEAD
+# commit is still the commit to be edited.  When any other rebase
+# command is processed, this file is deleted.
+AMEND="$DOTEST"/amend
+
+# For the post-rewrite hook, we make a list of rewritten commits and
+# their new sha1s.  The rewritten-pending list keeps the sha1s of
+# commits that have been processed, but not committed yet,
+# e.g. because they are waiting for a 'squash' command.
+REWRITTEN_LIST="$DOTEST"/rewritten-list
+REWRITTEN_PENDING="$DOTEST"/rewritten-pending
+
 PRESERVE_MERGES=
 STRATEGY=
 ONTO=
 VERBOSE=
 OK_TO_SKIP_PRE_REBASE=
 REBASE_ROOT=
+AUTOSQUASH=
+test "$(git config --bool rebase.autosquash)" = "true" && AUTOSQUASH=t
+NEVER_FF=
 
-GIT_CHERRY_PICK_HELP="  After resolving the conflicts,
-mark the corrected paths with 'git add <paths>', and
-run 'git rebase --continue'"
+GIT_CHERRY_PICK_HELP="\
+hint: after resolving the conflicts, mark the corrected paths
+hint: with 'git add <paths>' and run 'git rebase --continue'"
 export GIT_CHERRY_PICK_HELP
 
 warn () {
-       echo "$*" >&2
+       printf '%s\n' "$*" >&2
 }
 
 output () {
@@ -70,6 +137,11 @@ output () {
        esac
 }
 
+# Output the commit message for the specified commit.
+commit_message () {
+       git cat-file commit "$1" | sed "1,/^$/d"
+}
+
 run_pre_rebase_hook () {
        if test -z "$OK_TO_SKIP_PRE_REBASE" &&
           test -x "$GIT_DIR/hooks/pre-rebase"
@@ -106,8 +178,8 @@ mark_action_done () {
        sed -e 1q < "$TODO" >> "$DONE"
        sed -e 1d < "$TODO" >> "$TODO".new
        mv -f "$TODO".new "$TODO"
-       count=$(grep -c '^[^#]' < "$DONE")
-       total=$(($count+$(grep -c '^[^#]' < "$TODO")))
+       count=$(sane_grep -c '^[^#]' < "$DONE")
+       total=$(($count+$(sane_grep -c '^[^#]' < "$TODO")))
        if test "$last_count" != "$count"
        then
                last_count=$count
@@ -129,13 +201,14 @@ make_patch () {
                echo "Root commit"
                ;;
        esac > "$DOTEST"/patch
-       test -f "$DOTEST"/message ||
-               git cat-file commit "$1" | sed "1,/^$/d" > "$DOTEST"/message
-       test -f "$DOTEST"/author-script ||
-               get_author_ident_from_commit "$1" > "$DOTEST"/author-script
+       test -f "$MSG" ||
+               commit_message "$1" > "$MSG"
+       test -f "$AUTHOR_SCRIPT" ||
+               get_author_ident_from_commit "$1" > "$AUTHOR_SCRIPT"
 }
 
 die_with_patch () {
+       echo "$1" > "$DOTEST"/stopped-sha
        make_patch "$1"
        git rerere
        die "$2"
@@ -147,31 +220,31 @@ die_abort () {
 }
 
 has_action () {
-       grep '^[^#]' "$1" >/dev/null
+       sane_grep '^[^#]' "$1" >/dev/null
+}
+
+# Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
+# GIT_AUTHOR_DATE exported from the current environment.
+do_with_author () {
+       (
+               export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
+               "$@"
+       )
 }
 
 pick_one () {
-       no_ff=
-       case "$1" in -n) sha1=$2; no_ff=t ;; *) sha1=$1 ;; esac
+       ff=--ff
+       case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
+       case "$NEVER_FF" in '') ;; ?*) ff= ;; esac
        output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
        test -d "$REWRITTEN" &&
                pick_one_preserving_merges "$@" && return
-       if test ! -z "$REBASE_ROOT"
+       if test -n "$REBASE_ROOT"
        then
                output git cherry-pick "$@"
                return
        fi
-       parent_sha1=$(git rev-parse --verify $sha1^) ||
-               die "Could not get the parent of $sha1"
-       current_sha1=$(git rev-parse --verify HEAD)
-       if test "$no_ff$current_sha1" = "$parent_sha1"; then
-               output git reset --hard $sha1
-               test "a$1" = a-n && output git reset --soft $current_sha1
-               sha1=$(git rev-parse --short $sha1)
-               output warn Fast forward to $sha1
-       else
-               output git cherry-pick "$@"
-       fi
+       output git cherry-pick $ff "$@"
 }
 
 pick_one_preserving_merges () {
@@ -191,10 +264,10 @@ pick_one_preserving_merges () {
        then
                if test "$fast_forward" = t
                then
-                       cat "$DOTEST"/current-commit | while read current_commit
+                       while read current_commit
                        do
                                git rev-parse HEAD > "$REWRITTEN"/$current_commit
-                       done
+                       done <"$DOTEST"/current-commit
                        rm "$DOTEST"/current-commit ||
                        die "Cannot write current commit's replacement sha1"
                fi
@@ -248,9 +321,9 @@ pick_one_preserving_merges () {
        done
        case $fast_forward in
        t)
-               output warn "Fast forward to $sha1"
+               output warn "Fast-forward to $sha1"
                output git reset --hard $sha1 ||
-                       die "Cannot fast forward to $sha1"
+                       die "Cannot fast-forward to $sha1"
                ;;
        f)
                first_parent=$(expr "$new_parents" : ' \([^ ]*\)')
@@ -269,18 +342,16 @@ pick_one_preserving_merges () {
                        # redo merge
                        author_script=$(get_author_ident_from_commit $sha1)
                        eval "$author_script"
-                       msg="$(git cat-file commit $sha1 | sed -e '1,/^$/d')"
+                       msg="$(commit_message $sha1)"
                        # No point in merging the first parent, that's HEAD
                        new_parents=${new_parents# $first_parent}
-                       if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
-                               GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
-                               GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
-                               output git merge $STRATEGY -m "$msg" \
-                                       $new_parents
+                       if ! do_with_author output \
+                               git merge $STRATEGY -m "$msg" $new_parents
                        then
                                printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
                                die_with_patch $sha1 "Error redoing merge $sha1"
                        fi
+                       echo "$sha1 $(git rev-parse HEAD^0)" >> "$REWRITTEN_LIST"
                        ;;
                *)
                        output git cherry-pick "$@" ||
@@ -300,35 +371,87 @@ nth_string () {
        esac
 }
 
-make_squash_message () {
+update_squash_messages () {
        if test -f "$SQUASH_MSG"; then
-               COUNT=$(($(sed -n "s/^# This is [^0-9]*\([1-9][0-9]*\).*/\1/p" \
-                       < "$SQUASH_MSG" | sed -ne '$p')+1))
-               echo "# This is a combination of $COUNT commits."
-               sed -e 1d -e '2,/^./{
-                       /^$/d
-               }' <"$SQUASH_MSG"
+               mv "$SQUASH_MSG" "$SQUASH_MSG".bak || exit
+               COUNT=$(($(sed -n \
+                       -e "1s/^# This is a combination of \(.*\) commits\./\1/p" \
+                       -e "q" < "$SQUASH_MSG".bak)+1))
+               {
+                       echo "# This is a combination of $COUNT commits."
+                       sed -e 1d -e '2,/^./{
+                               /^$/d
+                       }' <"$SQUASH_MSG".bak
+               } >"$SQUASH_MSG"
        else
+               commit_message HEAD > "$FIXUP_MSG" || die "Cannot write $FIXUP_MSG"
                COUNT=2
-               echo "# This is a combination of two commits."
-               echo "# The first commit's message is:"
-               echo
-               git cat-file commit HEAD | sed -e '1,/^$/d'
+               {
+                       echo "# This is a combination of 2 commits."
+                       echo "# The first commit's message is:"
+                       echo
+                       cat "$FIXUP_MSG"
+               } >"$SQUASH_MSG"
        fi
-       echo
-       echo "# This is the $(nth_string $COUNT) commit message:"
-       echo
-       git cat-file commit $1 | sed -e '1,/^$/d'
+       case $1 in
+       squash)
+               rm -f "$FIXUP_MSG"
+               echo
+               echo "# This is the $(nth_string $COUNT) commit message:"
+               echo
+               commit_message $2
+               ;;
+       fixup)
+               echo
+               echo "# The $(nth_string $COUNT) commit message will be skipped:"
+               echo
+               commit_message $2 | sed -e 's/^/#       /'
+               ;;
+       esac >>"$SQUASH_MSG"
 }
 
 peek_next_command () {
-       sed -n "1s/ .*$//p" < "$TODO"
+       sed -n -e "/^#/d" -e '/^$/d' -e "s/ .*//p" -e "q" < "$TODO"
+}
+
+# A squash/fixup has failed.  Prepare the long version of the squash
+# commit message, then die_with_patch.  This code path requires the
+# user to edit the combined commit message for all commits that have
+# been squashed/fixedup so far.  So also erase the old squash
+# messages, effectively causing the combined commit to be used as the
+# new basis for any further squash/fixups.  Args: sha1 rest
+die_failed_squash() {
+       mv "$SQUASH_MSG" "$MSG" || exit
+       rm -f "$FIXUP_MSG"
+       cp "$MSG" "$GIT_DIR"/MERGE_MSG || exit
+       warn
+       warn "Could not apply $1... $2"
+       die_with_patch $1 ""
+}
+
+flush_rewritten_pending() {
+       test -s "$REWRITTEN_PENDING" || return
+       newsha1="$(git rev-parse HEAD^0)"
+       sed "s/$/ $newsha1/" < "$REWRITTEN_PENDING" >> "$REWRITTEN_LIST"
+       rm -f "$REWRITTEN_PENDING"
+}
+
+record_in_rewritten() {
+       oldsha1="$(git rev-parse $1)"
+       echo "$oldsha1" >> "$REWRITTEN_PENDING"
+
+       case "$(peek_next_command)" in
+       squash|s|fixup|f)
+               ;;
+       *)
+               flush_rewritten_pending
+               ;;
+       esac
 }
 
 do_next () {
-       rm -f "$DOTEST"/message "$DOTEST"/author-script \
-               "$DOTEST"/amend || exit
-       read command sha1 rest < "$TODO"
+       rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit
+       read -r command sha1 rest < "$TODO"
        case "$command" in
        '#'*|''|noop)
                mark_action_done
@@ -339,6 +462,16 @@ do_next () {
                mark_action_done
                pick_one $sha1 ||
                        die_with_patch $sha1 "Could not apply $sha1... $rest"
+               record_in_rewritten $sha1
+               ;;
+       reword|r)
+               comment_for_reflog reword
+
+               mark_action_done
+               pick_one $sha1 ||
+                       die_with_patch $sha1 "Could not apply $sha1... $rest"
+               git commit --amend --no-post-rewrite
+               record_in_rewritten $sha1
                ;;
        edit|e)
                comment_for_reflog edit
@@ -346,8 +479,9 @@ do_next () {
                mark_action_done
                pick_one $sha1 ||
                        die_with_patch $sha1 "Could not apply $sha1... $rest"
+               echo "$sha1" > "$DOTEST"/stopped-sha
                make_patch $sha1
-               git rev-parse --verify HEAD > "$DOTEST"/amend
+               git rev-parse --verify HEAD > "$AMEND"
                warn "Stopped at $sha1... $rest"
                warn "You can amend the commit now, with"
                warn
@@ -359,56 +493,87 @@ do_next () {
                warn
                exit 0
                ;;
-       squash|s)
-               comment_for_reflog squash
+       squash|s|fixup|f)
+               case "$command" in
+               squash|s)
+                       squash_style=squash
+                       ;;
+               fixup|f)
+                       squash_style=fixup
+                       ;;
+               esac
+               comment_for_reflog $squash_style
 
                test -f "$DONE" && has_action "$DONE" ||
-                       die "Cannot 'squash' without a previous commit"
+                       die "Cannot '$squash_style' without a previous commit"
 
                mark_action_done
-               make_squash_message $sha1 > "$MSG"
-               failed=f
+               update_squash_messages $squash_style $sha1
                author_script=$(get_author_ident_from_commit HEAD)
+               echo "$author_script" > "$AUTHOR_SCRIPT"
+               eval "$author_script"
                output git reset --soft HEAD^
-               pick_one -n $sha1 || failed=t
+               pick_one -n $sha1 || die_failed_squash $sha1 "$rest"
                case "$(peek_next_command)" in
-               squash|s)
-                       USE_OUTPUT=output
-                       MSG_OPT=-F
-                       EDIT_OR_FILE="$MSG"
-                       cp "$MSG" "$SQUASH_MSG"
+               squash|s|fixup|f)
+                       # This is an intermediate commit; its message will only be
+                       # used in case of trouble.  So use the long version:
+                       do_with_author output git commit --no-verify -F "$SQUASH_MSG" ||
+                               die_failed_squash $sha1 "$rest"
                        ;;
                *)
-                       USE_OUTPUT=
-                       MSG_OPT=
-                       EDIT_OR_FILE=-e
-                       rm -f "$SQUASH_MSG" || exit
-                       cp "$MSG" "$GIT_DIR"/SQUASH_MSG
-                       rm -f "$GIT_DIR"/MERGE_MSG || exit
+                       # This is the final command of this squash/fixup group
+                       if test -f "$FIXUP_MSG"
+                       then
+                               do_with_author git commit --no-verify -F "$FIXUP_MSG" ||
+                                       die_failed_squash $sha1 "$rest"
+                       else
+                               cp "$SQUASH_MSG" "$GIT_DIR"/SQUASH_MSG || exit
+                               rm -f "$GIT_DIR"/MERGE_MSG
+                               do_with_author git commit --no-verify -e ||
+                                       die_failed_squash $sha1 "$rest"
+                       fi
+                       rm -f "$SQUASH_MSG" "$FIXUP_MSG"
                        ;;
                esac
-               echo "$author_script" > "$DOTEST"/author-script
-               if test $failed = f
+               record_in_rewritten $sha1
+               ;;
+       x|"exec")
+               read -r command rest < "$TODO"
+               mark_action_done
+               printf 'Executing: %s\n' "$rest"
+               # "exec" command doesn't take a sha1 in the todo-list.
+               # => can't just use $sha1 here.
+               git rev-parse --verify HEAD > "$DOTEST"/stopped-sha
+               ${SHELL:-@SHELL_PATH@} -c "$rest" # Actual execution
+               status=$?
+               if test "$status" -ne 0
                then
-                       # This is like --amend, but with a different message
-                       eval "$author_script"
-                       GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
-                       GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
-                       GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
-                       $USE_OUTPUT git commit --no-verify \
-                               $MSG_OPT "$EDIT_OR_FILE" || failed=t
+                       warn "Execution failed: $rest"
+                       warn "You can fix the problem, and then run"
+                       warn
+                       warn "  git rebase --continue"
+                       warn
+                       exit "$status"
                fi
-               if test $failed = t
+               # Run in subshell because require_clean_work_tree can die.
+               if ! (require_clean_work_tree)
                then
-                       cp "$MSG" "$GIT_DIR"/MERGE_MSG
+                       warn "Commit or stash your changes, and then run"
+                       warn
+                       warn "  git rebase --continue"
                        warn
-                       warn "Could not apply $sha1... $rest"
-                       die_with_patch $sha1 ""
+                       exit 1
                fi
                ;;
        *)
                warn "Unknown command: $command $sha1 $rest"
-               die_with_patch $sha1 "Please fix this in the file $TODO."
+               if git rev-parse --verify -q "$sha1" >/dev/null
+               then
+                       die_with_patch $sha1 "Please fix this in the file $TODO."
+               else
+                       die "Please fix this in the file $TODO."
+               fi
                ;;
        esac
        test -s "$TODO" && return
@@ -420,7 +585,7 @@ do_next () {
        NEWHEAD=$(git rev-parse HEAD) &&
        case $HEADNAME in
        refs/*)
-               message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)" &&
+               message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO" &&
                git update-ref -m "$message" $HEADNAME $NEWHEAD $OLDHEAD &&
                git symbolic-ref HEAD $HEADNAME
                ;;
@@ -428,6 +593,16 @@ do_next () {
                test ! -f "$DOTEST"/verbose ||
                        git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
        } &&
+       {
+               test -s "$REWRITTEN_LIST" &&
+               git notes copy --for-rewrite=rebase < "$REWRITTEN_LIST" ||
+               true # we don't care if this copying failed
+       } &&
+       if test -x "$GIT_DIR"/hooks/post-rewrite &&
+               test -s "$REWRITTEN_LIST"; then
+               "$GIT_DIR"/hooks/post-rewrite rebase < "$REWRITTEN_LIST"
+               true # we don't care if this hook failed
+       fi &&
        rm -rf "$DOTEST" &&
        git gc --auto &&
        warn "Successfully rebased and updated $HEADNAME."
@@ -445,24 +620,37 @@ do_rest () {
 # skip picking commits whose parents are unchanged
 skip_unnecessary_picks () {
        fd=3
-       while read command sha1 rest
+       while read -r command rest
        do
                # fd=3 means we skip the command
-               case "$fd,$command,$(git rev-parse --verify --quiet $sha1^)" in
-               3,pick,"$ONTO"*|3,p,"$ONTO"*)
+               case "$fd,$command" in
+               3,pick|3,p)
                        # pick a commit whose parent is current $ONTO -> skip
-                       ONTO=$sha1
+                       sha1=${rest%% *}
+                       case "$(git rev-parse --verify --quiet "$sha1"^)" in
+                       "$ONTO"*)
+                               ONTO=$sha1
+                               ;;
+                       *)
+                               fd=1
+                               ;;
+                       esac
                        ;;
-               3,#*|3,,*)
+               3,#*|3,)
                        # copy comments
                        ;;
                *)
                        fd=1
                        ;;
                esac
-               echo "$command${sha1:+ }$sha1${rest:+ }$rest" >&$fd
+               printf '%s\n' "$command${rest:+ }$rest" >&$fd
        done <"$TODO" >"$TODO.new" 3>>"$DONE" &&
-       mv -f "$TODO".new "$TODO" ||
+       mv -f "$TODO".new "$TODO" &&
+       case "$(peek_next_command)" in
+       squash|s|fixup|f)
+               record_in_rewritten "$ONTO"
+               ;;
+       esac ||
        die "Could not skip unnecessary pick commands"
 }
 
@@ -482,6 +670,56 @@ get_saved_options () {
        test -f "$DOTEST"/rebase-root && REBASE_ROOT=t
 }
 
+# Rearrange the todo list that has both "pick sha1 msg" and
+# "pick sha1 fixup!/squash! msg" appears in it so that the latter
+# comes immediately after the former, and change "pick" to
+# "fixup"/"squash".
+rearrange_squash () {
+       sed -n -e 's/^pick \([0-9a-f]*\) \(squash\)! /\1 \2 /p' \
+               -e 's/^pick \([0-9a-f]*\) \(fixup\)! /\1 \2 /p' \
+               "$1" >"$1.sq"
+       test -s "$1.sq" || return
+
+       used=
+       while read -r pick sha1 message
+       do
+               case " $used" in
+               *" $sha1 "*) continue ;;
+               esac
+               printf '%s\n' "$pick $sha1 $message"
+               while read -r squash action msg
+               do
+                       case "$message" in
+                       "$msg"*)
+                               printf '%s\n' "$action $squash $action! $msg"
+                               used="$used$squash "
+                               ;;
+                       esac
+               done <"$1.sq"
+       done >"$1.rearranged" <"$1"
+       cat "$1.rearranged" >"$1"
+       rm -f "$1.sq" "$1.rearranged"
+}
+
+LF='
+'
+parse_onto () {
+       case "$1" in
+       *...*)
+               if      left=${1%...*} right=${1#*...} &&
+                       onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD})
+               then
+                       case "$onto" in
+                       ?*"$LF"?* | '')
+                               exit 1 ;;
+                       esac
+                       echo "$onto"
+                       exit 0
+               fi
+       esac
+       git rev-parse --verify "$1^0"
+}
+
 while test $# != 0
 do
        case "$1" in
@@ -509,26 +747,27 @@ do
                then
                        : Nothing to commit -- skip this
                else
-                       . "$DOTEST"/author-script ||
+                       . "$AUTHOR_SCRIPT" ||
                                die "Cannot find the author identity"
                        amend=
-                       if test -f "$DOTEST"/amend
+                       if test -f "$AMEND"
                        then
                                amend=$(git rev-parse --verify HEAD)
-                               test "$amend" = $(cat "$DOTEST"/amend) ||
+                               test "$amend" = $(cat "$AMEND") ||
                                die "\
 You have uncommitted changes in your working tree. Please, commit them
 first and then run 'git rebase --continue' again."
                                git reset --soft HEAD^ ||
                                die "Cannot rewind the HEAD"
                        fi
-                       export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE &&
-                       git commit --no-verify -F "$DOTEST"/message -e || {
+                       do_with_author git commit --no-verify -F "$MSG" -e || {
                                test -n "$amend" && git reset --soft $amend
                                die "Could not commit staged changes."
                        }
                fi
 
+               record_in_rewritten "$(cat "$DOTEST"/stopped-sha)"
+
                require_clean_work_tree
                do_rest
                ;;
@@ -584,12 +823,21 @@ first and then run 'git rebase --continue' again."
        -i)
                # yeah, we know
                ;;
+       --no-ff)
+               NEVER_FF=t
+               ;;
        --root)
                REBASE_ROOT=t
                ;;
+       --autosquash)
+               AUTOSQUASH=t
+               ;;
+       --no-autosquash)
+               AUTOSQUASH=
+               ;;
        --onto)
                shift
-               ONTO=$(git rev-parse --verify "$1") ||
+               ONTO=$(parse_onto "$1") ||
                        die "Does not point to a valid commit: $1"
                ;;
        --)
@@ -622,8 +870,6 @@ first and then run 'git rebase --continue' again."
 
                if test ! -z "$1"
                then
-                       output git show-ref --verify --quiet "refs/heads/$1" ||
-                               die "Invalid branchname: $1"
                        output git checkout "$1" ||
                                die "Could not checkout $1"
                fi
@@ -647,13 +893,6 @@ first and then run 'git rebase --continue' again."
                test t = "$VERBOSE" && : > "$DOTEST"/verbose
                if test t = "$PRESERVE_MERGES"
                then
-                       # $REWRITTEN contains files for each commit that is
-                       # reachable by at least one merge base of $HEAD and
-                       # $UPSTREAM. They are not necessarily rewritten, but
-                       # their children might be.
-                       # This ensures that commits on merged, but otherwise
-                       # unrelated side branches are left alone. (Think "X"
-                       # in the man page's example.)
                        if test -z "$REBASE_ROOT"
                        then
                                mkdir "$REWRITTEN" &&
@@ -691,11 +930,12 @@ first and then run 'git rebase --continue' again."
                git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \
                        --abbrev=7 --reverse --left-right --topo-order \
                        $REVISIONS | \
-                       sed -n "s/^>//p" | while read shortsha1 rest
+                       sed -n "s/^>//p" |
+               while read -r shortsha1 rest
                do
                        if test t != "$PRESERVE_MERGES"
                        then
-                               echo "pick $shortsha1 $rest" >> "$TODO"
+                               printf '%s\n' "pick $shortsha1 $rest" >> "$TODO"
                        else
                                sha1=$(git rev-parse $shortsha1)
                                if test -z "$REBASE_ROOT"
@@ -703,7 +943,7 @@ first and then run 'git rebase --continue' again."
                                        preserve=t
                                        for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)
                                        do
-                                               if test -f "$REWRITTEN"/$p -a \( $p != $UPSTREAM -o $sha1 = $first_after_upstream \)
+                                               if test -f "$REWRITTEN"/$p -a \( $p != $ONTO -o $sha1 = $first_after_upstream \)
                                                then
                                                        preserve=f
                                                fi
@@ -714,7 +954,7 @@ first and then run 'git rebase --continue' again."
                                if test f = "$preserve"
                                then
                                        touch "$REWRITTEN"/$sha1
-                                       echo "pick $shortsha1 $rest" >> "$TODO"
+                                       printf '%s\n' "pick $shortsha1 $rest" >> "$TODO"
                                fi
                        fi
                done
@@ -731,7 +971,7 @@ first and then run 'git rebase --continue' again."
                        git rev-list $REVISIONS |
                        while read rev
                        do
-                               if test -f "$REWRITTEN"/$rev -a "$(grep "$rev" "$DOTEST"/not-cherry-picks)" = ""
+                               if test -f "$REWRITTEN"/$rev -a "$(sane_grep "$rev" "$DOTEST"/not-cherry-picks)" = ""
                                then
                                        # Use -f2 because if rev-list is telling us this commit is
                                        # not worthwhile, we don't want to track its multiple heads,
@@ -739,21 +979,25 @@ first and then run 'git rebase --continue' again."
                                        # be rebasing on top of it
                                        git rev-list --parents -1 $rev | cut -d' ' -s -f2 > "$DROPPED"/$rev
                                        short=$(git rev-list -1 --abbrev-commit --abbrev=7 $rev)
-                                       grep -v "^[a-z][a-z]* $short" <"$TODO" > "${TODO}2" ; mv "${TODO}2" "$TODO"
+                                       sane_grep -v "^[a-z][a-z]* $short" <"$TODO" > "${TODO}2" ; mv "${TODO}2" "$TODO"
                                        rm "$REWRITTEN"/$rev
                                fi
                        done
                fi
 
                test -s "$TODO" || echo noop >> "$TODO"
+               test -n "$AUTOSQUASH" && rearrange_squash "$TODO"
                cat >> "$TODO" << EOF
 
 # Rebase $SHORTREVISIONS onto $SHORTONTO
 #
 # Commands:
 #  p, pick = use commit
+#  r, reword = use commit, but edit the commit message
 #  e, edit = use commit, but stop for amending
 #  s, squash = use commit, but meld into previous commit
+#  f, fixup = like "squash", but discard this commit's log message
+#  x <cmd>, exec <cmd> = Run a shell command <cmd>, and stop if it fails
 #
 # If you remove a line here THAT COMMIT WILL BE LOST.
 # However, if you remove everything, the rebase will be aborted.
@@ -765,15 +1009,16 @@ EOF
 
                cp "$TODO" "$TODO".backup
                git_editor "$TODO" ||
-                       die "Could not execute editor"
+                       die_abort "Could not execute editor"
 
                has_action "$TODO" ||
                        die_abort "Nothing to do"
 
-               test -d "$REWRITTEN" || skip_unnecessary_picks
+               test -d "$REWRITTEN" || test -n "$NEVER_FF" || skip_unnecessary_picks
 
+               output git checkout $ONTO || die_abort "could not detach HEAD"
                git update-ref ORIG_HEAD $HEAD
-               output git checkout $ONTO && do_rest
+               do_rest
                ;;
        esac
        shift
index b83fd3f970161d9ca8a4212a0b335f784fc3ff84..3335cee70b8ea5510f109da83d8470d3bbabab52 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano.
 #
 
-USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--onto <newbase>] [<upstream>|--root] [<branch>]'
+USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--no-ff] [--onto <newbase>] (<upstream>|--root) [<branch>] [--quiet | -q]'
 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>
@@ -34,6 +34,8 @@ set_reflog_action rebase
 require_work_tree
 cd_to_toplevel
 
+LF='
+'
 OK_TO_SKIP_PRE_REBASE=
 RESOLVEMSG="
 When you have resolved this problem run \"git rebase --continue\".
@@ -42,6 +44,7 @@ To restore the original branch and stop rebasing run \"git rebase --abort\".
 "
 unset newbase
 strategy=recursive
+strategy_opts=
 do_merge=
 dotest="$GIT_DIR"/rebase-merge
 prec=4
@@ -50,6 +53,7 @@ diffstat=$(git config --bool rebase.stat)
 git_am_opt=
 rebase_root=
 force_rebase=
+allow_rerere_autoupdate=
 
 continue_merge () {
        test -n "$prev_head" || die "prev_head must be defined"
@@ -72,11 +76,19 @@ continue_merge () {
                        echo "directly, but instead do one of the following: "
                        die "$RESOLVEMSG"
                fi
-               printf "Committed: %0${prec}d " $msgnum
+               if test -z "$GIT_QUIET"
+               then
+                       printf "Committed: %0${prec}d " $msgnum
+               fi
+               echo "$cmt $(git rev-parse HEAD^0)" >> "$dotest/rewritten"
        else
-               printf "Already applied: %0${prec}d " $msgnum
+               if test -z "$GIT_QUIET"
+               then
+                       printf "Already applied: %0${prec}d " $msgnum
+               fi
        fi
-       git rev-list --pretty=oneline -1 "$cmt" | sed -e 's/^[^ ]* //'
+       test -z "$GIT_QUIET" &&
+       GIT_PAGER='' git log --format=%s -1 "$cmt"
 
        prev_head=`git rev-parse HEAD^0`
        # save the resulting commit so we can read-tree on it later
@@ -97,7 +109,11 @@ call_merge () {
        eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"'
        eval GITHEAD_$hd='$(cat "$dotest/onto_name")'
        export GITHEAD_$cmt GITHEAD_$hd
-       git-merge-$strategy "$cmt^" -- "$hd" "$cmt"
+       if test -n "$GIT_QUIET"
+       then
+               export GIT_MERGE_VERBOSITY=1
+       fi
+       eval 'git-merge-$strategy' $strategy_opts '"$cmt^" -- "$hd" "$cmt"'
        rv=$?
        case "$rv" in
        0)
@@ -105,7 +121,7 @@ call_merge () {
                return
                ;;
        1)
-               git rerere
+               git rerere $allow_rerere_autoupdate
                die "$RESOLVEMSG"
                ;;
        2)
@@ -137,8 +153,13 @@ move_to_original_branch () {
 
 finish_rb_merge () {
        move_to_original_branch
+       git notes copy --for-rewrite=rebase < "$dotest"/rewritten
+       if test -x "$GIT_DIR"/hooks/post-rewrite &&
+               test -s "$dotest"/rewritten; then
+               "$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+       fi
        rm -r "$dotest"
-       echo "All done."
+       say All done.
 }
 
 is_interactive () {
@@ -168,10 +189,8 @@ run_pre_rebase_hook () {
        if test -z "$OK_TO_SKIP_PRE_REBASE" &&
           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
-               }
+               "$GIT_DIR/hooks/pre-rebase" ${1+"$@"} ||
+               die "The pre-rebase hook refused to rebase."
        fi
 }
 
@@ -180,14 +199,6 @@ test -f "$GIT_DIR"/rebase-apply/applying &&
 
 is_interactive "$@" && exec git-rebase--interactive "$@"
 
-if test $# -eq 0
-then
-       test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply || usage
-       test -d "$dotest" -o -f "$GIT_DIR"/rebase-apply/rebasing &&
-               die 'A rebase is in progress, try --continue, --skip or --abort.'
-       die "No arguments given and $GIT_DIR/rebase-apply already exists."
-fi
-
 while test $# != 0
 do
        case "$1" in
@@ -198,6 +209,7 @@ do
                test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply ||
                        die "No rebase in progress?"
 
+               git update-index --ignore-submodules --refresh &&
                git diff-files --quiet --ignore-submodules || {
                        echo "You must edit all merge conflicts and then"
                        echo "mark them as resolved using git add"
@@ -209,6 +221,7 @@ do
                        end=$(cat "$dotest/end")
                        msgnum=$(cat "$dotest/msgnum")
                        onto=$(cat "$dotest/onto")
+                       GIT_QUIET=$(cat "$dotest/quiet")
                        continue_merge
                        while test "$msgnum" -le "$end"
                        do
@@ -221,6 +234,7 @@ do
                head_name=$(cat "$GIT_DIR"/rebase-apply/head-name) &&
                onto=$(cat "$GIT_DIR"/rebase-apply/onto) &&
                orig_head=$(cat "$GIT_DIR"/rebase-apply/orig-head) &&
+               GIT_QUIET=$(cat "$GIT_DIR"/rebase-apply/quiet)
                git am --resolved --3way --resolvemsg="$RESOLVEMSG" &&
                move_to_original_branch
                exit
@@ -238,6 +252,7 @@ do
                        msgnum=$(cat "$dotest/msgnum")
                        msgnum=$(($msgnum + 1))
                        onto=$(cat "$dotest/onto")
+                       GIT_QUIET=$(cat "$dotest/quiet")
                        while test "$msgnum" -le "$end"
                        do
                                call_merge "$msgnum"
@@ -249,6 +264,7 @@ do
                head_name=$(cat "$GIT_DIR"/rebase-apply/head-name) &&
                onto=$(cat "$GIT_DIR"/rebase-apply/onto) &&
                orig_head=$(cat "$GIT_DIR"/rebase-apply/orig-head) &&
+               GIT_QUIET=$(cat "$GIT_DIR"/rebase-apply/quiet)
                git am -3 --skip --resolvemsg="$RESOLVEMSG" &&
                move_to_original_branch
                exit
@@ -260,9 +276,11 @@ do
                git rerere clear
                if test -d "$dotest"
                then
+                       GIT_QUIET=$(cat "$dotest/quiet")
                        move_to_original_branch
                else
                        dotest="$GIT_DIR"/rebase-apply
+                       GIT_QUIET=$(cat "$dotest/quiet")
                        move_to_original_branch
                fi
                git reset --hard $(cat "$dotest/orig-head")
@@ -277,6 +295,27 @@ do
        -M|-m|--m|--me|--mer|--merg|--merge)
                do_merge=t
                ;;
+       -X*|--strategy-option*)
+               case "$#,$1" in
+               1,-X|1,--strategy-option)
+                       usage ;;
+               *,-X|*,--strategy-option)
+                       newopt="$2"
+                       shift ;;
+               *,--strategy-option=*)
+                       newopt="$(expr " $1" : ' --strategy-option=\(.*\)')" ;;
+               *,-X*)
+                       newopt="$(expr " $1" : ' -X\(.*\)')" ;;
+               1,*)
+                       usage ;;
+               esac
+               strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--$newopt")"
+               do_merge=t
+               if test -n "$strategy"
+               then
+                       strategy=recursive
+               fi
+               ;;
        -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\
                --strateg=*|--strategy=*|\
        -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy)
@@ -300,6 +339,13 @@ do
        -v|--verbose)
                verbose=t
                diffstat=t
+               GIT_QUIET=
+               ;;
+       -q|--quiet)
+               GIT_QUIET=t
+               git_am_opt="$git_am_opt -q"
+               verbose=
+               diffstat=
                ;;
        --whitespace=*)
                git_am_opt="$git_am_opt $1"
@@ -309,6 +355,9 @@ do
                        ;;
                esac
                ;;
+       --ignore-whitespace)
+               git_am_opt="$git_am_opt $1"
+               ;;
        --committer-date-is-author-date|--ignore-date)
                git_am_opt="$git_am_opt $1"
                force_rebase=t
@@ -319,9 +368,12 @@ do
        --root)
                rebase_root=t
                ;;
-       -f|--f|--fo|--for|--forc|force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase)
+       -f|--f|--fo|--for|--forc|--force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase|--no-ff)
                force_rebase=t
                ;;
+       --rerere-autoupdate|--no-rerere-autoupdate)
+               allow_rerere_autoupdate="$1"
+               ;;
        -*)
                usage
                ;;
@@ -333,6 +385,13 @@ do
 done
 test $# -gt 2 && usage
 
+if test $# -eq 0 && test -z "$rebase_root"
+then
+       test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply || usage
+       test -d "$dotest" -o -f "$GIT_DIR"/rebase-apply/rebasing &&
+               die 'A rebase is in progress, try --continue, --skip or --abort.'
+fi
+
 # Make sure we do not have $GIT_DIR/rebase-apply
 if test -z "$do_merge"
 then
@@ -358,8 +417,9 @@ else
 fi
 
 # The tree must be really really clean.
-if ! git update-index --ignore-submodules --refresh; then
+if ! git update-index --ignore-submodules --refresh > /dev/null; then
        echo >&2 "cannot rebase: you have unstaged changes"
+       git diff-files --name-status -r --ignore-submodules -- >&2
        exit 1
 fi
 diff=$(git diff-index --cached --name-status -r --ignore-submodules HEAD --)
@@ -389,7 +449,27 @@ fi
 
 # Make sure the branch to rebase onto is valid.
 onto_name=${newbase-"$upstream_name"}
-onto=$(git rev-parse --verify "${onto_name}^0") || exit
+case "$onto_name" in
+*...*)
+       if      left=${onto_name%...*} right=${onto_name#*...} &&
+               onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD})
+       then
+               case "$onto" in
+               ?*"$LF"?*)
+                       die "$onto_name: there are more than one merge bases"
+                       ;;
+               '')
+                       die "$onto_name: there is no merge base"
+                       ;;
+               esac
+       else
+               die "$onto_name: there is no merge base"
+       fi
+       ;;
+*)
+       onto=$(git rev-parse --verify "${onto_name}^0") || exit
+       ;;
+esac
 
 # If a hook exists, give it a chance to interrupt
 run_pre_rebase_hook "$upstream_arg" "$@"
@@ -439,21 +519,21 @@ orig_head=$branch
 mb=$(git merge-base "$onto" "$branch")
 if test "$upstream" = "$onto" && test "$mb" = "$onto" &&
        # linear history?
-       ! (git rev-list --parents "$onto".."$branch" | grep " .* ") > /dev/null
+       ! (git rev-list --parents "$onto".."$branch" | sane_grep " .* ") > /dev/null
 then
        if test -z "$force_rebase"
        then
                # Lazily switch to the target branch if needed...
                test -z "$switch_to" || git checkout "$switch_to"
-               echo >&2 "Current branch $branch_name is up to date."
+               say "Current branch $branch_name is up to date."
                exit 0
        else
-               echo "Current branch $branch_name is up to date, rebase forced."
+               say "Current branch $branch_name is up to date, rebase forced."
        fi
 fi
 
 # Detach HEAD and reset the tree
-echo "First, rewinding head to replay your work on top of it..."
+say "First, rewinding head to replay your work on top of it..."
 git checkout -q "$onto^0" || die "could not detach HEAD"
 git update-ref ORIG_HEAD $branch
 
@@ -468,10 +548,10 @@ then
 fi
 
 # If the $onto is a proper descendant of the tip of the branch, then
-# we just fast forwarded.
+# we just fast-forwarded.
 if test "$mb" = "$branch"
 then
-       echo >&2 "Fast-forwarded $branch_name to $onto_name."
+       say "Fast-forwarded $branch_name to $onto_name."
        move_to_original_branch
        exit 0
 fi
@@ -486,14 +566,16 @@ fi
 if test -z "$do_merge"
 then
        git format-patch -k --stdout --full-index --ignore-if-in-upstream \
-               $root_flag "$revisions" |
+               --src-prefix=a/ --dst-prefix=b/ \
+               --no-renames $root_flag "$revisions" |
        git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" &&
        move_to_original_branch
        ret=$?
        test 0 != $ret -a -d "$GIT_DIR"/rebase-apply &&
                echo $head_name > "$GIT_DIR"/rebase-apply/head-name &&
                echo $onto > "$GIT_DIR"/rebase-apply/onto &&
-               echo $orig_head > "$GIT_DIR"/rebase-apply/orig-head
+               echo $orig_head > "$GIT_DIR"/rebase-apply/orig-head &&
+               echo "$GIT_QUIET" > "$GIT_DIR"/rebase-apply/quiet
        exit $ret
 fi
 
@@ -507,6 +589,7 @@ prev_head=$orig_head
 echo "$prev_head" > "$dotest/prev_head"
 echo "$orig_head" > "$dotest/orig-head"
 echo "$head_name" > "$dotest/head-name"
+echo "$GIT_QUIET" > "$dotest/quiet"
 
 msgnum=0
 for cmt in `git rev-list --reverse --no-merges "$revisions"`
diff --git a/git-remote-testgit.py b/git-remote-testgit.py
new file mode 100644 (file)
index 0000000..df9d512
--- /dev/null
@@ -0,0 +1,244 @@
+#!/usr/bin/env python
+
+# hashlib is only available in python >= 2.5
+try:
+    import hashlib
+    _digest = hashlib.sha1
+except ImportError:
+    import sha
+    _digest = sha.new
+import sys
+import os
+sys.path.insert(0, os.getenv("GITPYTHONLIB","."))
+
+from git_remote_helpers.util import die, debug, warn
+from git_remote_helpers.git.repo import GitRepo
+from git_remote_helpers.git.exporter import GitExporter
+from git_remote_helpers.git.importer import GitImporter
+from git_remote_helpers.git.non_local import NonLocalGit
+
+def get_repo(alias, url):
+    """Returns a git repository object initialized for usage.
+    """
+
+    repo = GitRepo(url)
+    repo.get_revs()
+    repo.get_head()
+
+    hasher = _digest()
+    hasher.update(repo.path)
+    repo.hash = hasher.hexdigest()
+
+    repo.get_base_path = lambda base: os.path.join(
+        base, 'info', 'fast-import', repo.hash)
+
+    prefix = 'refs/testgit/%s/' % alias
+    debug("prefix: '%s'", prefix)
+
+    repo.gitdir = ""
+    repo.alias = alias
+    repo.prefix = prefix
+
+    repo.exporter = GitExporter(repo)
+    repo.importer = GitImporter(repo)
+    repo.non_local = NonLocalGit(repo)
+
+    return repo
+
+
+def local_repo(repo, path):
+    """Returns a git repository object initalized for usage.
+    """
+
+    local = GitRepo(path)
+
+    local.non_local = None
+    local.gitdir = repo.gitdir
+    local.alias = repo.alias
+    local.prefix = repo.prefix
+    local.hash = repo.hash
+    local.get_base_path = repo.get_base_path
+    local.exporter = GitExporter(local)
+    local.importer = GitImporter(local)
+
+    return local
+
+
+def do_capabilities(repo, args):
+    """Prints the supported capabilities.
+    """
+
+    print "import"
+    print "export"
+    print "gitdir"
+    print "refspec refs/heads/*:%s*" % repo.prefix
+
+    print # end capabilities
+
+
+def do_list(repo, args):
+    """Lists all known references.
+
+    Bug: This will always set the remote head to master for non-local
+    repositories, since we have no way of determining what the remote
+    head is at clone time.
+    """
+
+    for ref in repo.revs:
+        debug("? refs/heads/%s", ref)
+        print "? refs/heads/%s" % ref
+
+    if repo.head:
+        debug("@refs/heads/%s HEAD" % repo.head)
+        print "@refs/heads/%s HEAD" % repo.head
+    else:
+        debug("@refs/heads/master HEAD")
+        print "@refs/heads/master HEAD"
+
+    print # end list
+
+
+def update_local_repo(repo):
+    """Updates (or clones) a local repo.
+    """
+
+    if repo.local:
+        return repo
+
+    path = repo.non_local.clone(repo.gitdir)
+    repo.non_local.update(repo.gitdir)
+    repo = local_repo(repo, path)
+    return repo
+
+
+def do_import(repo, args):
+    """Exports a fast-import stream from testgit for git to import.
+    """
+
+    if len(args) != 1:
+        die("Import needs exactly one ref")
+
+    if not repo.gitdir:
+        die("Need gitdir to import")
+
+    repo = update_local_repo(repo)
+    repo.exporter.export_repo(repo.gitdir)
+
+
+def do_export(repo, args):
+    """Imports a fast-import stream from git to testgit.
+    """
+
+    if not repo.gitdir:
+        die("Need gitdir to export")
+
+    dirname = repo.get_base_path(repo.gitdir)
+
+    if not os.path.exists(dirname):
+        os.makedirs(dirname)
+
+    path = os.path.join(dirname, 'testgit.marks')
+    print path
+    if os.path.exists(path):
+        print path
+    else:
+        print ""
+    sys.stdout.flush()
+
+    update_local_repo(repo)
+    repo.importer.do_import(repo.gitdir)
+    repo.non_local.push(repo.gitdir)
+
+
+def do_gitdir(repo, args):
+    """Stores the location of the gitdir.
+    """
+
+    if not args:
+        die("gitdir needs an argument")
+
+    repo.gitdir = ' '.join(args)
+
+
+COMMANDS = {
+    'capabilities': do_capabilities,
+    'list': do_list,
+    'import': do_import,
+    'export': do_export,
+    'gitdir': do_gitdir,
+}
+
+
+def sanitize(value):
+    """Cleans up the url.
+    """
+
+    if value.startswith('testgit::'):
+        value = value[9:]
+
+    return value
+
+
+def read_one_line(repo):
+    """Reads and processes one command.
+    """
+
+    line = sys.stdin.readline()
+
+    cmdline = line
+
+    if not cmdline:
+        warn("Unexpected EOF")
+        return False
+
+    cmdline = cmdline.strip().split()
+    if not cmdline:
+        # Blank line means we're about to quit
+        return False
+
+    cmd = cmdline.pop(0)
+    debug("Got command '%s' with args '%s'", cmd, ' '.join(cmdline))
+
+    if cmd not in COMMANDS:
+        die("Unknown command, %s", cmd)
+
+    func = COMMANDS[cmd]
+    func(repo, cmdline)
+    sys.stdout.flush()
+
+    return True
+
+
+def main(args):
+    """Starts a new remote helper for the specified repository.
+    """
+
+    if len(args) != 3:
+        die("Expecting exactly three arguments.")
+        sys.exit(1)
+
+    if os.getenv("GIT_DEBUG_TESTGIT"):
+        import git_remote_helpers.util
+        git_remote_helpers.util.DEBUG = True
+
+    alias = sanitize(args[1])
+    url = sanitize(args[2])
+
+    if not alias.isalnum():
+        warn("non-alnum alias '%s'", alias)
+        alias = "tmp"
+
+    args[1] = alias
+    args[2] = url
+
+    repo = get_repo(alias, url)
+
+    debug("Got arguments %s", args[1:])
+
+    more = True
+
+    while (more):
+        more = read_one_line(repo)
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv))
index 0868734723b3c96144bfa9360a9e19ebae1995f7..1eb3bca352f38ec3807383cc5fff7f7e62731b2b 100755 (executable)
@@ -24,7 +24,7 @@ SUBDIRECTORY_OK='Yes'
 . git-sh-setup
 
 no_update_info= all_into_one= remove_redundant= unpack_unreachable=
-local= quiet= no_reuse= extra=
+local= no_reuse= extra=
 while test $# != 0
 do
        case "$1" in
@@ -33,7 +33,7 @@ do
        -A)     all_into_one=t
                unpack_unreachable=--unpack-unreachable ;;
        -d)     remove_redundant=t ;;
-       -q)     quiet=-q ;;
+       -q)     GIT_QUIET=t ;;
        -f)     no_reuse=--no-reuse-object ;;
        -l)     local=--local ;;
        --max-pack-size|--window|--window-memory|--depth)
@@ -80,13 +80,11 @@ case ",$all_into_one," in
        ;;
 esac
 
-args="$args $local $quiet $no_reuse$extra"
-names=$(git pack-objects --honor-pack-keep --non-empty --all --reflog $args </dev/null "$PACKTMP") ||
+args="$args $local ${GIT_QUIET:+-q} $no_reuse$extra"
+names=$(git pack-objects --keep-true-parents --honor-pack-keep --non-empty --all --reflog $args </dev/null "$PACKTMP") ||
        exit 1
 if [ -z "$names" ]; then
-       if test -z "$quiet"; then
-               echo Nothing new to pack.
-       fi
+       say Nothing new to pack.
 fi
 
 # Ok we have prepared all new packfiles.
@@ -176,7 +174,7 @@ then
                  done
                )
        fi
-       git prune-packed $quiet
+       git prune-packed ${GIT_QUIET:+-q}
 fi
 
 case "$no_update_info" in
index a2cf5b82150a77fd9ddb775fea1bc8d5e8ee7392..6fdea397ddec3cfa4ff37cdf672e7cced840bb60 100755 (executable)
@@ -1,4 +1,4 @@
-#!/bin/sh -e
+#!/bin/sh
 # Copyright 2005, Ryan Anderson <ryan@michonline.com>
 #
 # This file is licensed under the GPL v2, or a later version
@@ -8,10 +8,34 @@ USAGE='<start> <url> [<end>]'
 LONG_USAGE='Summarizes the changes between two commits to the standard output,
 and includes the given URL in the generated summary.'
 SUBDIRECTORY_OK='Yes'
-OPTIONS_SPEC=
+OPTIONS_KEEPDASHDASH=
+OPTIONS_SPEC='git request-pull [options] start url [end]
+--
+p    show patch text as well
+'
+
 . git-sh-setup
 . git-parse-remote
 
+GIT_PAGER=
+export GIT_PAGER
+
+patch=
+while  case "$#" in 0) break ;; esac
+do
+       case "$1" in
+       -p)
+               patch=-p ;;
+       --)
+               shift; break ;;
+       -*)
+               usage ;;
+       *)
+               break ;;
+       esac
+       shift
+done
+
 base=$1
 url=$2
 head=${3-HEAD}
@@ -25,16 +49,16 @@ headrev=`git rev-parse --verify "$head"^0` || exit
 merge_base=`git merge-base $baserev $headrev` ||
 die "fatal: No commits in common between $base and $head"
 
-url=$(get_remote_url "$url")
 branch=$(git ls-remote "$url" \
        | sed -n -e "/^$headrev refs.heads./{
                s/^.*   refs.heads.//
                p
                q
        }")
+url=$(get_remote_url "$url")
 if [ -z "$branch" ]; then
        echo "warn: No branch of $url is at:" >&2
-       git log --max-count=1 --pretty='format:warn:   %h: %s' $headrev >&2
+       git log --max-count=1 --pretty='tformat:warn:   %h: %s' $headrev >&2
        echo "warn: Are you sure you pushed $head there?" >&2
        echo >&2
        echo >&2
@@ -42,16 +66,14 @@ if [ -z "$branch" ]; then
        status=1
 fi
 
-PAGER=
-export PAGER
-echo "The following changes since commit $baserev:"
-git shortlog --max-count=1 $baserev | sed -e 's/^\(.\)/  \1/'
+git show -s --format='The following changes since commit %H:
+
+  %s (%ci)
 
-echo "are available in the git repository at:"
-echo
-echo "  $url $branch"
-echo
+are available in the git repository at:' $baserev &&
+echo "  $url $branch" &&
+echo &&
 
-git shortlog ^$baserev $headrev
-git diff -M --stat --summary $merge_base $headrev
+git shortlog ^$baserev $headrev &&
+git diff -M --stat --summary $patch $merge_base..$headrev || exit
 exit $status
index cccbf4517aa46d4d217a1ce25105f7c413301d2d..6dab3bf6a74bb52c7294e33dbac5bf87b51a9fdf 100755 (executable)
@@ -47,13 +47,14 @@ git send-email [options] <file | directory | rev-list options >
 
   Composing:
     --from                  <str>  * Email From:
-    --to                    <str>  * Email To:
-    --cc                    <str>  * Email Cc:
-    --bcc                   <str>  * Email Bcc:
+    --[no-]to               <str>  * Email To:
+    --[no-]cc               <str>  * Email Cc:
+    --[no-]bcc              <str>  * Email Bcc:
     --subject               <str>  * Email "Subject:"
     --in-reply-to           <str>  * Email "In-Reply-To:"
     --annotate                     * Review each patch that will be sent in an editor.
     --compose                      * Open an editor for introduction.
+    --8bit-encoding         <str>  * Encoding to assume 8bit mails if undeclared
 
   Sending:
     --envelope-sender       <str>  * Email envelope sender.
@@ -64,6 +65,8 @@ git send-email [options] <file | directory | rev-list options >
     --smtp-pass             <str>  * Password for SMTP-AUTH; not necessary.
     --smtp-encryption       <str>  * tls or ssl; anything else disables.
     --smtp-ssl                     * Deprecated. Use '--smtp-encryption ssl'.
+    --smtp-domain           <str>  * The domain name sent to HELO/EHLO handshake
+    --smtp-debug            <0|1>  * Disable, enable Net::SMTP debug.
 
   Automating:
     --identity              <str>  * Use the sendemail.<id> options.
@@ -71,7 +74,7 @@ git send-email [options] <file | directory | rev-list options >
     --suppress-cc           <str>  * author, self, sob, cc, cccmd, body, bodycc, all.
     --[no-]signed-off-by-cc        * Send to Signed-off-by: addresses. Default on.
     --[no-]suppress-from           * Send to self. Default off.
-    --[no-]chain-reply-to          * Chain In-Reply-To: fields. Default on.
+    --[no-]chain-reply-to          * Chain In-Reply-To: fields. Default off.
     --[no-]thread                  * Use In-Reply-To: field. Default on.
 
   Administering:
@@ -135,7 +138,7 @@ sub unique_email_list(@);
 sub cleanup_compose_files();
 
 # Variables we fill in automatically, or via prompting:
-my (@to,@cc,@initial_cc,@bcclist,@xh,
+my (@to,$no_to,@cc,$no_cc,@initial_cc,@bcclist,$no_bcc,@xh,
        $initial_reply_to,$initial_subject,@files,
        $author,$sender,$smtp_authpass,$annotate,$compose,$time);
 
@@ -162,8 +165,12 @@ my $compose_filename;
 
 # Handle interactive edition of files.
 my $multiedit;
-my $editor = $ENV{GIT_EDITOR} || Git::config(@repo, "core.editor") || $ENV{VISUAL} || $ENV{EDITOR} || "vi";
+my $editor;
+
 sub do_edit {
+       if (!defined($editor)) {
+               $editor = Git::command_oneline('var', 'GIT_EDITOR');
+       }
        if (defined($multiedit) && !$multiedit) {
                map {
                        system('sh', '-c', $editor.' "$@"', $editor, $_);
@@ -182,13 +189,18 @@ sub do_edit {
 # Variables with corresponding config settings
 my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc, $cc_cmd);
 my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_encryption);
-my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts);
+my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts, $smtp_domain);
 my ($validate, $confirm);
 my (@suppress_cc);
+my ($auto_8bit_encoding);
+
+my ($debug_net_smtp) = 0;              # Net::SMTP, see send_message()
+
+my $not_set_by_user = "true but not set by the user";
 
 my %config_bool_settings = (
     "thread" => [\$thread, 1],
-    "chainreplyto" => [\$chain_reply_to, 1],
+    "chainreplyto" => [\$chain_reply_to, $not_set_by_user],
     "suppressfrom" => [\$suppress_from, undef],
     "signedoffbycc" => [\$signed_off_by_cc, undef],
     "signedoffcc" => [\$signed_off_by_cc, undef],      # Deprecated
@@ -200,6 +212,7 @@ my %config_settings = (
     "smtpserverport" => \$smtp_server_port,
     "smtpuser" => \$smtp_authuser,
     "smtppass" => \$smtp_authpass,
+       "smtpdomain" => \$smtp_domain,
     "to" => \@to,
     "cc" => \@initial_cc,
     "cccmd" => \$cc_cmd,
@@ -210,8 +223,23 @@ my %config_settings = (
     "envelopesender" => \$envelope_sender,
     "multiedit" => \$multiedit,
     "confirm"   => \$confirm,
+    "from" => \$sender,
+    "assume8bitencoding" => \$auto_8bit_encoding,
 );
 
+# Help users prepare for 1.7.0
+sub chain_reply_to {
+       if (defined $chain_reply_to &&
+           $chain_reply_to eq $not_set_by_user) {
+               print STDERR
+                   "In git 1.7.0, the default has changed to --no-chain-reply-to\n" .
+                   "Set sendemail.chainreplyto configuration variable to true if\n" .
+                   "you want to keep --chain-reply-to as your default.\n";
+               $chain_reply_to = 0;
+       }
+       return $chain_reply_to;
+}
+
 # Handle Uncouth Termination
 sub signal_handler {
 
@@ -244,8 +272,11 @@ my $rc = GetOptions("sender|from=s" => \$sender,
                     "in-reply-to=s" => \$initial_reply_to,
                    "subject=s" => \$initial_subject,
                    "to=s" => \@to,
+                   "no-to" => \$no_to,
                    "cc=s" => \@initial_cc,
+                   "no-cc" => \$no_cc,
                    "bcc=s" => \@bcclist,
+                   "no-bcc" => \$no_bcc,
                    "chain-reply-to!" => \$chain_reply_to,
                    "smtp-server=s" => \$smtp_server,
                    "smtp-server-port=s" => \$smtp_server_port,
@@ -253,6 +284,8 @@ my $rc = GetOptions("sender|from=s" => \$sender,
                    "smtp-pass:s" => \$smtp_authpass,
                    "smtp-ssl" => sub { $smtp_encryption = 'ssl' },
                    "smtp-encryption=s" => \$smtp_encryption,
+                   "smtp-debug:i" => \$debug_net_smtp,
+                   "smtp-domain:s" => \$smtp_domain,
                    "identity=s" => \$identity,
                    "annotate" => \$annotate,
                    "compose" => \$compose,
@@ -267,6 +300,7 @@ my $rc = GetOptions("sender|from=s" => \$sender,
                    "thread!" => \$thread,
                    "validate!" => \$validate,
                    "format-patch!" => \$format_patch,
+                   "8bit-encoding=s" => \$auto_8bit_encoding,
         );
 
 unless ($rc) {
@@ -288,6 +322,9 @@ sub read_config {
 
        foreach my $setting (keys %config_settings) {
                my $target = $config_settings{$setting};
+               next if $setting eq "to" and defined $no_to;
+               next if $setting eq "cc" and defined $no_cc;
+               next if $setting eq "bcc" and defined $no_bcc;
                if (ref($target) eq "ARRAY") {
                        unless (@$target) {
                                my @values = Git::config(@repo, "$prefix.$setting");
@@ -333,7 +370,7 @@ if (@suppress_cc) {
 }
 
 if ($suppress_cc{'all'}) {
-       foreach my $entry (qw (ccmd cc author self sob body bodycc)) {
+       foreach my $entry (qw (cccmd cc author self sob body bodycc)) {
                $suppress_cc{$entry} = 1;
        }
        delete $suppress_cc{'all'};
@@ -400,7 +437,7 @@ my %aliases;
 my %parse_alias = (
        # multiline formats can be supported in the future
        mutt => sub { my $fh = shift; while (<$fh>) {
-               if (/^\s*alias\s+(\S+)\s+(.*)$/) {
+               if (/^\s*alias\s+(?:-group\s+\S+\s+)*(\S+)\s+(.*)$/) {
                        my ($alias, $addr) = ($1, $2);
                        $addr =~ s/#.*$//; # mutt allows # comments
                         # commas delimit multiple addresses
@@ -409,7 +446,7 @@ my %parse_alias = (
        mailrc => sub { my $fh = shift; while (<$fh>) {
                if (/^alias\s+(\S+)\s+(.*)$/) {
                        # spaces delimit multiple addresses
-                       $aliases{$1} = [ split(/\s+/, $2) ];
+                       $aliases{$1} = [ quotewords('\s+', 0, $2) ];
                }}},
        pine => sub { my $fh = shift; my $f='\t[^\t]*';
                for (my $x = ''; defined($x); $x = $_) {
@@ -449,7 +486,6 @@ sub check_file_rev_conflict($) {
        try {
                $repo->command('rev-parse', '--verify', '--quiet', $f);
                if (defined($format_patch)) {
-                       print "foo\n";
                        return $format_patch;
                }
                die(<<EOF);
@@ -537,7 +573,7 @@ if ($compose) {
 
        print C <<EOT;
 From $tpl_sender # This line is ignored.
-GIT: Lines beginning in "GIT: " will be removed.
+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.
 GIT:
@@ -552,8 +588,6 @@ EOT
        }
        close(C);
 
-       my $editor = $ENV{GIT_EDITOR} || Git::config(@repo, "core.editor") || $ENV{VISUAL} || $ENV{EDITOR} || "vi";
-
        if ($annotate) {
                do_edit($compose_filename, @files);
        } else {
@@ -570,7 +604,7 @@ EOT
        my $in_body = 0;
        my $summary_empty = 1;
        while(<C>) {
-               next if m/^GIT: /;
+               next if m/^GIT:/;
                if ($in_body) {
                        $summary_empty = 0 unless (/^\n$/);
                } elsif (/^\n$/) {
@@ -578,7 +612,7 @@ EOT
                        if ($need_8bit_cte) {
                                print C2 "MIME-Version: 1.0\n",
                                         "Content-Type: text/plain; ",
-                                          "charset=utf-8\n",
+                                          "charset=UTF-8\n",
                                         "Content-Transfer-Encoding: 8bit\n";
                        }
                } elsif (/^MIME-Version:/i) {
@@ -639,6 +673,35 @@ sub ask {
        return undef;
 }
 
+my %broken_encoding;
+
+sub file_declares_8bit_cte($) {
+       my $fn = shift;
+       open (my $fh, '<', $fn);
+       while (my $line = <$fh>) {
+               last if ($line =~ /^$/);
+               return 1 if ($line =~ /^Content-Transfer-Encoding: .*8bit.*$/);
+       }
+       close $fh;
+       return 0;
+}
+
+foreach my $f (@files) {
+       next unless (body_or_subject_has_nonascii($f)
+                    && !file_declares_8bit_cte($f));
+       $broken_encoding{$f} = 1;
+}
+
+if (!defined $auto_8bit_encoding && scalar %broken_encoding) {
+       print "The following files are 8bit, but do not declare " .
+               "a Content-Transfer-Encoding.\n";
+       foreach my $f (sort keys %broken_encoding) {
+               print "    $f\n";
+       }
+       $auto_8bit_encoding = ask("Which 8bit encoding should I declare [UTF-8]? ",
+                                 default => "UTF-8");
+}
+
 my $prompting = 0;
 if (!defined $sender) {
        $sender = $repoauthor || $repocommitter || '';
@@ -655,13 +718,17 @@ if (!@to) {
 }
 
 sub expand_aliases {
-       my @cur = @_;
-       my @last;
-       do {
-               @last = @cur;
-               @cur = map { $aliases{$_} ? @{$aliases{$_}} : $_ } @last;
-       } while (join(',',@cur) ne join(',',@last));
-       return @cur;
+       return map { expand_one_alias($_) } @_;
+}
+
+my %EXPANDED_ALIASES;
+sub expand_one_alias {
+       my $alias = shift;
+       if ($EXPANDED_ALIASES{$alias}) {
+               die "fatal: alias '$alias' expands to itself\n";
+       }
+       local $EXPANDED_ALIASES{$alias} = 1;
+       return $aliases{$alias} ? expand_aliases(@{$aliases{$alias}}) : $alias;
 }
 
 @to = expand_aliases(@to);
@@ -726,8 +793,7 @@ sub extract_valid_address {
 # We'll setup a template for the message id, using the "from" address:
 
 my ($message_id_stamp, $message_id_serial);
-sub make_message_id
-{
+sub make_message_id {
        my $uniq;
        if (!defined $message_id_stamp) {
                $message_id_stamp = sprintf("%s-%s", time, $$);
@@ -767,15 +833,22 @@ sub unquote_rfc2047 {
 
 sub quote_rfc2047 {
        local $_ = shift;
-       my $encoding = shift || 'utf-8';
+       my $encoding = shift || 'UTF-8';
        s/([^-a-zA-Z0-9!*+\/])/sprintf("=%02X", ord($1))/eg;
        s/(.*)/=\?$encoding\?q\?$1\?=/;
        return $_;
 }
 
+sub is_rfc2047_quoted {
+       my $s = shift;
+       my $token = '[^][()<>@,;:"\/?.= \000-\037\177-\377]+';
+       my $encoded_text = '[!->@-~]+';
+       length($s) <= 75 &&
+       $s =~ m/^(?:"[[:ascii:]]*"|=\?$token\?$token\?$encoded_text\?=)$/o;
+}
+
 # use the simplest quoting being able to handle the recipient
-sub sanitize_address
-{
+sub sanitize_address {
        my ($recipient) = @_;
        my ($recipient_name, $recipient_addr) = ($recipient =~ /^(.*?)\s*(<.*)/);
 
@@ -784,7 +857,7 @@ sub sanitize_address
        }
 
        # if recipient_name is already quoted, do nothing
-       if ($recipient_name =~ /^("[[:ascii:]]*"|=\?utf-8\?q\?.*\?=)$/) {
+       if (is_rfc2047_quoted($recipient_name)) {
                return $recipient;
        }
 
@@ -804,8 +877,67 @@ sub sanitize_address
 
 }
 
-sub send_message
-{
+# Returns the local Fully Qualified Domain Name (FQDN) if available.
+#
+# Tightly configured MTAa require that a caller sends a real DNS
+# domain name that corresponds the IP address in the HELO/EHLO
+# handshake. This is used to verify the connection and prevent
+# spammers from trying to hide their identity. If the DNS and IP don't
+# match, the receiveing MTA may deny the connection.
+#
+# Here is a deny example of Net::SMTP with the default "localhost.localdomain"
+#
+# Net::SMTP=GLOB(0x267ec28)>>> EHLO localhost.localdomain
+# Net::SMTP=GLOB(0x267ec28)<<< 550 EHLO argument does not match calling host
+#
+# This maildomain*() code is based on ideas in Perl library Test::Reporter
+# /usr/share/perl5/Test/Reporter/Mail/Util.pm ==> sub _maildomain ()
+
+sub valid_fqdn {
+       my $domain = shift;
+       return !($^O eq 'darwin' && $domain =~ /\.local$/) && $domain =~ /\./;
+}
+
+sub maildomain_net {
+       my $maildomain;
+
+       if (eval { require Net::Domain; 1 }) {
+               my $domain = Net::Domain::domainname();
+               $maildomain = $domain if valid_fqdn($domain);
+       }
+
+       return $maildomain;
+}
+
+sub maildomain_mta {
+       my $maildomain;
+
+       if (eval { require Net::SMTP; 1 }) {
+               for my $host (qw(mailhost localhost)) {
+                       my $smtp = Net::SMTP->new($host);
+                       if (defined $smtp) {
+                               my $domain = $smtp->domain;
+                               $smtp->quit;
+
+                               $maildomain = $domain if valid_fqdn($domain);
+
+                               last if $maildomain;
+                       }
+               }
+       }
+
+       return $maildomain;
+}
+
+sub maildomain {
+       return maildomain_net() || maildomain_mta() || 'localhost.localdomain';
+}
+
+# Returns 1 if the message was sent, and 0 otherwise.
+# In actuality, the whole program dies when there
+# is an error sending a message.
+
+sub send_message {
        my @recipients = unique_email_list(@to);
        @cc = (grep { my $cc = extract_valid_address($_);
                      not grep { $cc eq $_ } @recipients
@@ -821,7 +953,7 @@ sub send_message
            $gitversion = Git::version();
        }
 
-       my $cc = join(", ", unique_email_list(@cc));
+       my $cc = join(",\n\t", unique_email_list(@cc));
        my $ccline = "";
        if ($cc ne '') {
                $ccline = "\nCc: $cc";
@@ -847,7 +979,9 @@ X-Mailer: git-send-email $gitversion
 
        my @sendmail_parameters = ('-i', @recipients);
        my $raw_from = $sanitized_sender;
-       $raw_from = $envelope_sender if (defined $envelope_sender);
+       if (defined $envelope_sender && $envelope_sender ne "auto") {
+               $raw_from = $envelope_sender;
+       }
        $raw_from = extract_valid_address($raw_from);
        unshift (@sendmail_parameters,
                        '-f', $raw_from) if(defined $envelope_sender);
@@ -872,7 +1006,7 @@ X-Mailer: git-send-email $gitversion
                         default => $ask_default);
                die "Send this email reply required" unless defined $_;
                if (/^n/i) {
-                       return;
+                       return 0;
                } elsif (/^q/i) {
                        cleanup_compose_files();
                        exit(0);
@@ -900,14 +1034,20 @@ X-Mailer: git-send-email $gitversion
                if ($smtp_encryption eq 'ssl') {
                        $smtp_server_port ||= 465; # ssmtp
                        require Net::SMTP::SSL;
-                       $smtp ||= Net::SMTP::SSL->new($smtp_server, Port => $smtp_server_port);
+                       $smtp_domain ||= maildomain();
+                       $smtp ||= Net::SMTP::SSL->new($smtp_server,
+                                                     Hello => $smtp_domain,
+                                                     Port => $smtp_server_port);
                }
                else {
                        require Net::SMTP;
+                       $smtp_domain ||= maildomain();
                        $smtp ||= Net::SMTP->new((defined $smtp_server_port)
                                                 ? "$smtp_server:$smtp_server_port"
-                                                : $smtp_server);
-                       if ($smtp_encryption eq 'tls') {
+                                                : $smtp_server,
+                                                Hello => $smtp_domain,
+                                                Debug => $debug_net_smtp);
+                       if ($smtp_encryption eq 'tls' && $smtp) {
                                require Net::SMTP::SSL;
                                $smtp->command('STARTTLS');
                                $smtp->response();
@@ -925,7 +1065,11 @@ X-Mailer: git-send-email $gitversion
                }
 
                if (!$smtp) {
-                       die "Unable to initialize SMTP properly.  Is there something wrong with your config?";
+                       die "Unable to initialize SMTP properly. Check config and use --smtp-debug. ",
+                           "VALUES: server=$smtp_server ",
+                           "encryption=$smtp_encryption ",
+                           "hello=$smtp_domain",
+                           defined $smtp_server_port ? "port=$smtp_server_port" : "";
                }
 
                if (defined $smtp_authuser) {
@@ -953,7 +1097,7 @@ X-Mailer: git-send-email $gitversion
                $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;
+               $smtp->code =~ /250|200/ or die "Failed to send $subject\n".$smtp->message;
        }
        if ($quiet) {
                printf (($dry_run ? "Dry-" : "")."Sent %s\n", $subject);
@@ -962,7 +1106,9 @@ X-Mailer: git-send-email $gitversion
                if ($smtp_server !~ m#^/#) {
                        print "Server: $smtp_server\n";
                        print "MAIL FROM:<$raw_from>\n";
-                       print "RCPT TO:".join(',',(map { "<$_>" } @recipients))."\n";
+                       foreach my $entry (@recipients) {
+                           print "RCPT TO:<$entry>\n";
+                       }
                } else {
                        print "Sendmail: $smtp_server ".join(' ',@sendmail_parameters)."\n";
                }
@@ -974,6 +1120,8 @@ X-Mailer: git-send-email $gitversion
                        print "Result: OK\n";
                }
        }
+
+       return 1;
 }
 
 $reply_to = $initial_reply_to;
@@ -1091,7 +1239,7 @@ foreach my $t (@files) {
        close F;
 
        if (defined $cc_cmd && !$suppress_cc{'cccmd'}) {
-               open(F, "$cc_cmd $t |")
+               open(F, "$cc_cmd \Q$t\E |")
                        or die "(cc-cmd) Could not execute '$cc_cmd'";
                while(<F>) {
                        my $c = $_;
@@ -1106,6 +1254,18 @@ foreach my $t (@files) {
                        or die "(cc-cmd) failed to close pipe to '$cc_cmd'";
        }
 
+       if ($broken_encoding{$t} && !$has_content_type) {
+               $has_content_type = 1;
+               push @xh, "MIME-Version: 1.0",
+                       "Content-Type: text/plain; charset=$auto_8bit_encoding",
+                       "Content-Transfer-Encoding: 8bit";
+               $body_encoding = $auto_8bit_encoding;
+       }
+
+       if ($broken_encoding{$t} && !is_rfc2047_quoted($subject)) {
+               $subject = quote_rfc2047($subject, $auto_8bit_encoding);
+       }
+
        if (defined $author and $author ne $sender) {
                $message = "From: $author\n\n$message";
                if (defined $author_encoding) {
@@ -1118,6 +1278,7 @@ foreach my $t (@files) {
                                }
                        }
                        else {
+                               $has_content_type = 1;
                                push @xh,
                                  'MIME-Version: 1.0',
                                  "Content-Type: text/plain; charset=$author_encoding",
@@ -1134,10 +1295,11 @@ foreach my $t (@files) {
 
        @cc = (@initial_cc, @cc);
 
-       send_message();
+       my $message_was_sent = send_message();
 
        # set up for the next message
-       if ($chain_reply_to || !defined $reply_to || length($reply_to) == 0) {
+       if ($thread && $message_was_sent &&
+               (chain_reply_to() || !defined $reply_to || length($reply_to) == 0)) {
                $reply_to = $message_id;
                if (length $references > 0) {
                        $references .= "\n $message_id";
@@ -1194,3 +1356,17 @@ sub file_has_nonascii {
        }
        return 0;
 }
+
+sub body_or_subject_has_nonascii {
+       my $fn = shift;
+       open(my $fh, '<', $fn)
+               or die "unable to open $fn: $!\n";
+       while (my $line = <$fh>) {
+               last if $line =~ /^$/;
+               return 1 if $line =~ /^Subject.*[^[:ascii:]]/;
+       }
+       while (my $line = <$fh>) {
+               return 1 if $line =~ /[^[:ascii:]]/;
+       }
+       return 0;
+}
old mode 100755 (executable)
new mode 100644 (file)
index 8382339..6131670
 # exporting it.
 unset CDPATH
 
+git_broken_path_fix () {
+       case ":$PATH:" in
+       *:$1:*) : ok ;;
+       *)
+               PATH=$(
+                       SANE_TOOL_PATH="$1"
+                       IFS=: path= sep=
+                       set x $PATH
+                       shift
+                       for elem
+                       do
+                               case "$SANE_TOOL_PATH:$elem" in
+                               (?*:/bin | ?*:/usr/bin)
+                                       path="$path$sep$SANE_TOOL_PATH"
+                                       sep=:
+                                       SANE_TOOL_PATH=
+                               esac
+                               path="$path$sep$elem"
+                               sep=:
+                       done
+                       echo "$path"
+               )
+               ;;
+       esac
+}
+
+# @@BROKEN_PATH_FIX@@
+
 die() {
        echo >&2 "$@"
        exit 1
 }
 
+GIT_QUIET=
+
+say () {
+       if test -z "$GIT_QUIET"
+       then
+               printf '%s\n' "$*"
+       fi
+}
+
 if test -n "$OPTIONS_SPEC"; then
        usage() {
                "$0" -h
@@ -62,19 +99,33 @@ set_reflog_action() {
 }
 
 git_editor() {
-       : "${GIT_EDITOR:=$(git config core.editor)}"
-       : "${GIT_EDITOR:=${VISUAL:-${EDITOR}}}"
-       case "$GIT_EDITOR,$TERM" in
-       ,dumb)
-               echo >&2 "No editor specified in GIT_EDITOR, core.editor, VISUAL,"
-               echo >&2 "or EDITOR. Tried to fall back to vi but terminal is dumb."
-               echo >&2 "Please set one of these variables to an appropriate"
-               echo >&2 "editor or run $0 with options that will not cause an"
-               echo >&2 "editor to be invoked (e.g., -m or -F for git-commit)."
-               exit 1
-               ;;
-       esac
-       eval "${GIT_EDITOR:=vi}" '"$@"'
+       if test -z "${GIT_EDITOR:+set}"
+       then
+               GIT_EDITOR="$(git var GIT_EDITOR)" || return $?
+       fi
+
+       eval "$GIT_EDITOR" '"$@"'
+}
+
+git_pager() {
+       if test -t 1
+       then
+               GIT_PAGER=$(git var GIT_PAGER)
+       else
+               GIT_PAGER=cat
+       fi
+       : ${LESS=-FRSX}
+       export LESS
+
+       eval "$GIT_PAGER" '"$@"'
+}
+
+sane_grep () {
+       GREP_OPTIONS= LC_ALL=C grep "$@"
+}
+
+sane_egrep () {
+       GREP_OPTIONS= LC_ALL=C egrep "$@"
 }
 
 is_bare_repository () {
@@ -82,24 +133,15 @@ is_bare_repository () {
 }
 
 cd_to_toplevel () {
-       cdup=$(git rev-parse --show-cdup)
-       if test ! -z "$cdup"
-       then
-               # The "-P" option says to follow "physical" directory
-               # structure instead of following symbolic links.  When cdup is
-               # "../", this means following the ".." entry in the current
-               # directory instead textually removing a symlink path element
-               # from the PWD shell variable.  The "-P" behavior is more
-               # consistent with the C-style chdir used by most of Git.
-               cd -P "$cdup" || {
-                       echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree"
-                       exit 1
-               }
-       fi
+       cdup=$(git rev-parse --show-toplevel) &&
+       cd "$cdup" || {
+               echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree"
+               exit 1
+       }
 }
 
 require_work_tree () {
-       test $(git rev-parse --is-inside-work-tree) = true ||
+       test "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true ||
        die "fatal: $0 cannot be used without a working tree."
 }
 
@@ -130,6 +172,13 @@ get_author_ident_from_commit () {
        LANG=C LC_ALL=C sed -ne "$pick_author_script"
 }
 
+# Clear repo-local GIT_* environment variables. Useful when switching to
+# another repository (e.g. when entering a submodule). See also the env
+# list in git_connect()
+clear_local_git_env() {
+       unset $(git rev-parse --local-env-vars)
+}
+
 # Make sure we are in a valid repository of a vintage we understand,
 # if we require to be in a git repository.
 if test -z "$NONGIT_OK"
index b9ace9970492aaf48472904d978d809d90ca33db..7ce818bd1baecfb48447f862bb2654ccbd3cf2ff 100755 (executable)
@@ -3,10 +3,11 @@
 
 dashless=$(basename "$0" | sed -e 's/-/ /')
 USAGE="list [<options>]
-   or: $dashless (show | drop | pop ) [<stash>]
-   or: $dashless apply [--index] [<stash>]
+   or: $dashless show [<stash>]
+   or: $dashless drop [-q|--quiet] [<stash>]
+   or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>]
    or: $dashless branch <branchname> [<stash>]
-   or: $dashless [save [--keep-index] [<message>]]
+   or: $dashless [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]]
    or: $dashless clear"
 
 SUBDIRECTORY_OK=Yes
@@ -20,6 +21,14 @@ trap 'rm -f "$TMP-*"' 0
 
 ref_stash=refs/stash
 
+if git config --get-colorbool color.interactive; then
+       help_color="$(git config --get-color color.interactive.help 'red bold')"
+       reset_color="$(git config --get-color '' reset)"
+else
+       help_color=
+       reset_color=
+fi
+
 no_changes () {
        git diff-index --quiet --cached HEAD --ignore-submodules -- &&
        git diff-files --quiet --ignore-submodules
@@ -48,7 +57,7 @@ create_stash () {
        # state of the base commit
        if b_commit=$(git rev-parse --verify HEAD)
        then
-               head=$(git log --no-color --abbrev-commit --pretty=oneline -n 1 HEAD --)
+               head=$(git rev-list --oneline -n 1 HEAD --)
        else
                die "You do not have the initial commit yet"
        fi
@@ -67,19 +76,44 @@ create_stash () {
                git commit-tree $i_tree -p $b_commit) ||
                die "Cannot save the current index state"
 
-       # state of the working tree
-       w_tree=$( (
+       if test -z "$patch_mode"
+       then
+
+               # state of the working tree
+               w_tree=$( (
+                       rm -f "$TMP-index" &&
+                       cp -p ${GIT_INDEX_FILE-"$GIT_DIR/index"} "$TMP-index" &&
+                       GIT_INDEX_FILE="$TMP-index" &&
+                       export GIT_INDEX_FILE &&
+                       git read-tree -m $i_tree &&
+                       git diff --name-only -z HEAD | git update-index -z --add --remove --stdin &&
+                       git write-tree &&
+                       rm -f "$TMP-index"
+               ) ) ||
+                       die "Cannot save the current worktree state"
+
+       else
+
                rm -f "$TMP-index" &&
-               cp -p ${GIT_INDEX_FILE-"$GIT_DIR/index"} "$TMP-index" &&
-               GIT_INDEX_FILE="$TMP-index" &&
-               export GIT_INDEX_FILE &&
-               git read-tree -m $i_tree &&
-               git add -u &&
-               git write-tree &&
-               rm -f "$TMP-index"
-       ) ||
+               GIT_INDEX_FILE="$TMP-index" git read-tree HEAD &&
+
+               # find out what the user wants
+               GIT_INDEX_FILE="$TMP-index" \
+                       git add--interactive --patch=stash -- &&
+
+               # state of the working tree
+               w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
                die "Cannot save the current worktree state"
 
+               git diff-tree -p HEAD $w_tree > "$TMP-patch" &&
+               test -s "$TMP-patch" ||
+               die "No changes selected"
+
+               rm -f "$TMP-index" ||
+               die "Cannot remove temporary index (can't happen)"
+
+       fi
+
        # create the stash
        if test -z "$stash_msg"
        then
@@ -94,18 +128,45 @@ create_stash () {
 
 save_stash () {
        keep_index=
-       case "$1" in
-       --keep-index)
-               keep_index=t
+       patch_mode=
+       while test $# != 0
+       do
+               case "$1" in
+               -k|--keep-index)
+                       keep_index=t
+                       ;;
+               --no-keep-index)
+                       keep_index=
+                       ;;
+               -p|--patch)
+                       patch_mode=t
+                       keep_index=t
+                       ;;
+               -q|--quiet)
+                       GIT_QUIET=t
+                       ;;
+               --)
+                       shift
+                       break
+                       ;;
+               -*)
+                       echo "error: unknown option for 'stash save': $1"
+                       echo "       To provide a message, use git stash save -- '$1'"
+                       usage
+                       ;;
+               *)
+                       break
+                       ;;
+               esac
                shift
-       esac
+       done
 
        stash_msg="$*"
 
        git update-index -q --refresh
        if no_changes
        then
-               echo 'No local changes to save'
+               say 'No local changes to save'
                exit 0
        fi
        test -f "$GIT_DIR/logs/$ref_stash" ||
@@ -118,13 +179,24 @@ save_stash () {
 
        git update-ref -m "$stash_msg" $ref_stash $w_commit ||
                die "Cannot save the current status"
-       printf 'Saved working directory and index state "%s"\n' "$stash_msg"
-
-       git reset --hard
+       say Saved working directory and index state "$stash_msg"
 
-       if test -n "$keep_index" && test -n $i_tree
+       if test -z "$patch_mode"
        then
-               git read-tree --reset -u $i_tree
+               git reset --hard ${GIT_QUIET:+-q}
+
+               if test -n "$keep_index" && test -n $i_tree
+               then
+                       git read-tree --reset -u $i_tree
+               fi
+       else
+               git apply -R < "$TMP-patch" ||
+               die "Cannot remove worktree changes"
+
+               if test -z "$keep_index"
+               then
+                       git reset
+               fi
        fi
 }
 
@@ -134,54 +206,167 @@ have_stash () {
 
 list_stash () {
        have_stash || return 0
-       git log --no-color --pretty=oneline -g "$@" $ref_stash -- |
-       sed -n -e 's/^[.0-9a-f]* refs\///p'
+       git log --format="%gd: %gs" -g "$@" $ref_stash --
 }
 
 show_stash () {
-       flags=$(git rev-parse --no-revs --flags "$@")
-       if test -z "$flags"
+       assert_stash_like "$@"
+
+       git diff ${FLAGS:---stat} $b_commit $w_commit
+}
+
+#
+# Parses the remaining options looking for flags and
+# at most one revision defaulting to ${ref_stash}@{0}
+# if none found.
+#
+# Derives related tree and commit objects from the
+# revision, if one is found.
+#
+# stash records the work tree, and is a merge between the
+# base commit (first parent) and the index tree (second parent).
+#
+#   REV is set to the symbolic version of the specified stash-like commit
+#   IS_STASH_LIKE is non-blank if ${REV} looks like a stash
+#   IS_STASH_REF is non-blank if the ${REV} looks like a stash ref
+#   s is set to the SHA1 of the stash commit
+#   w_commit is set to the commit containing the working tree
+#   b_commit is set to the base commit
+#   i_commit is set to the commit containing the index tree
+#   w_tree is set to the working tree
+#   b_tree is set to the base tree
+#   i_tree is set to the index tree
+#
+#   GIT_QUIET is set to t if -q is specified
+#   INDEX_OPTION is set to --index if --index is specified.
+#   FLAGS is set to the remaining flags
+#
+# dies if:
+#   * too many revisions specified
+#   * no revision is specified and there is no stash stack
+#   * a revision is specified which cannot be resolve to a SHA1
+#   * a non-existent stash reference is specified
+#
+
+parse_flags_and_rev()
+{
+       test "$PARSE_CACHE" = "$*" && return 0 # optimisation
+       PARSE_CACHE="$*"
+
+       IS_STASH_LIKE=
+       IS_STASH_REF=
+       INDEX_OPTION=
+       s=
+       w_commit=
+       b_commit=
+       i_commit=
+       w_tree=
+       b_tree=
+       i_tree=
+
+       REV=$(git rev-parse --no-flags --symbolic "$@" 2>/dev/null)
+       FLAGS=$(git rev-parse --no-revs -- "$@" 2>/dev/null)
+
+       set -- $FLAGS
+
+       FLAGS=
+       while test $# -ne 0
+       do
+               case "$1" in
+                       -q|--quiet)
+                               GIT_QUIET=-t
+                       ;;
+                       --index)
+                               INDEX_OPTION=--index
+                       ;;
+                       --)
+                               :
+                       ;;
+                       *)
+                               FLAGS="${FLAGS}${FLAGS:+ }$1"
+                       ;;
+               esac
+               shift
+       done
+
+       set -- $REV
+
+       case $# in
+               0)
+                       have_stash || die "No stash found."
+                       set -- ${ref_stash}@{0}
+               ;;
+               1)
+                       :
+               ;;
+               *)
+                       die "Too many revisions specified: $REV"
+               ;;
+       esac
+
+       REV=$(git rev-parse --quiet --symbolic --verify $1 2>/dev/null) || die "$1 is not valid reference"
+
+       i_commit=$(git rev-parse --quiet --verify $REV^2 2>/dev/null) &&
+       set -- $(git rev-parse $REV $REV^1 $REV: $REV^1: $REV^2: 2>/dev/null) &&
+       s=$1 &&
+       w_commit=$1 &&
+       b_commit=$2 &&
+       w_tree=$3 &&
+       b_tree=$4 &&
+       i_tree=$5 &&
+       IS_STASH_LIKE=t &&
+       test "$ref_stash" = "$(git rev-parse --symbolic-full-name "${REV%@*}")" &&
+       IS_STASH_REF=t
+
+       if test "${REV}" != "${REV%{*\}}"
        then
-               flags=--stat
+               # maintainers: it would be better if git rev-parse indicated
+               # this condition with a non-zero status code but as of 1.7.2.1 it
+               # it did not. So, we use non-empty stderr output as a proxy for the
+               # condition of interest.
+               test -z "$(git rev-parse "$REV" 2>&1 >/dev/null)" || die "$REV does not exist in the stash log"
        fi
 
-       w_commit=$(git rev-parse --verify --default $ref_stash "$@") &&
-       b_commit=$(git rev-parse --verify "$w_commit^") &&
-       git diff $flags $b_commit $w_commit
+}
+
+is_stash_like()
+{
+       parse_flags_and_rev "$@"
+       test -n "$IS_STASH_LIKE"
+}
+
+assert_stash_like() {
+       is_stash_like "$@" || die "'$*' is not a stash-like commit"
+}
+
+is_stash_ref() {
+       is_stash_like "$@" && test -n "$IS_STASH_REF"
+}
+
+assert_stash_ref() {
+       is_stash_ref "$@" || die "'$*' is not a stash reference"
 }
 
 apply_stash () {
+
+       assert_stash_like "$@"
+
        git update-index -q --refresh &&
        git diff-files --quiet --ignore-submodules ||
                die 'Cannot apply to a dirty working tree, please stage your changes'
 
-       unstash_index=
-       case "$1" in
-       --index)
-               unstash_index=t
-               shift
-       esac
-
        # current index state
        c_tree=$(git write-tree) ||
                die 'Cannot apply a stash in the middle of a merge'
 
-       # stash records the work tree, and is a merge between the
-       # base commit (first parent) and the index tree (second parent).
-       s=$(git rev-parse --verify --default $ref_stash "$@") &&
-       w_tree=$(git rev-parse --verify "$s:") &&
-       b_tree=$(git rev-parse --verify "$s^1:") &&
-       i_tree=$(git rev-parse --verify "$s^2:") ||
-               die "$*: no valid stashed state found"
-
        unstashed_index_tree=
-       if test -n "$unstash_index" && test "$b_tree" != "$i_tree" &&
+       if test -n "$INDEX_OPTION" && test "$b_tree" != "$i_tree" &&
                        test "$c_tree" != "$i_tree"
        then
                git diff-tree --binary $s^2^..$s^2 | git apply --cached
                test $? -ne 0 &&
                        die 'Conflicts in index. Try without --index.'
-               unstashed_index_tree=$(git-write-tree) ||
+               unstashed_index_tree=$(git write-tree) ||
                        die 'Could not save index tree'
                git reset
        fi
@@ -193,7 +378,11 @@ apply_stash () {
                export GITHEAD_$w_tree GITHEAD_$c_tree GITHEAD_$b_tree
        "
 
-       if git-merge-recursive $b_tree -- $c_tree $w_tree
+       if test -n "$GIT_QUIET"
+       then
+               export GIT_MERGE_VERBOSITY=0
+       fi
+       if git merge-recursive $b_tree -- $c_tree $w_tree
        then
                # No conflict
                if test -n "$unstashed_index_tree"
@@ -207,11 +396,16 @@ apply_stash () {
                                die "Cannot unstage modified files"
                        rm -f "$a"
                fi
-               git status || :
+               squelch=
+               if test -n "$GIT_QUIET"
+               then
+                       squelch='>/dev/null 2>&1'
+               fi
+               eval "git status $squelch" || :
        else
                # Merge conflict; keep the exit status from merge-recursive
                status=$?
-               if test -n "$unstash_index"
+               if test -n "$INDEX_OPTION"
                then
                        echo >&2 'Index was not unstashed.'
                fi
@@ -219,54 +413,54 @@ apply_stash () {
        fi
 }
 
-drop_stash () {
-       have_stash || die 'No stash entries to drop'
+pop_stash() {
+       assert_stash_ref "$@"
 
-       if test $# = 0
-       then
-               set x "$ref_stash@{0}"
-               shift
-       fi
-       # Verify supplied argument looks like a stash entry
-       s=$(git rev-parse --verify "$@") &&
-       git rev-parse --verify "$s:"   > /dev/null 2>&1 &&
-       git rev-parse --verify "$s^1:" > /dev/null 2>&1 &&
-       git rev-parse --verify "$s^2:" > /dev/null 2>&1 ||
-               die "$*: not a valid stashed state"
+       apply_stash "$@" &&
+       drop_stash "$@"
+}
+
+drop_stash () {
+       assert_stash_ref "$@"
 
-       git reflog delete --updateref --rewrite "$@" &&
-               echo "Dropped $* ($s)" || die "$*: Could not drop stash entry"
+       git reflog delete --updateref --rewrite "${REV}" &&
+               say "Dropped ${REV} ($s)" || die "${REV}: Could not drop stash entry"
 
        # clear_stash if we just dropped the last stash entry
        git rev-parse --verify "$ref_stash@{0}" > /dev/null 2>&1 || clear_stash
 }
 
 apply_to_branch () {
-       have_stash || die 'Nothing to apply'
-
        test -n "$1" || die 'No branch name specified'
        branch=$1
+       shift 1
 
-       if test -z "$2"
-       then
-               set x "$ref_stash@{0}"
-       fi
-       stash=$2
+       set -- --index "$@"
+       assert_stash_like "$@"
+
+       git checkout -b $branch $REV^ &&
+       apply_stash "$@"
 
-       git-checkout -b $branch $stash^ &&
-       apply_stash --index $stash &&
-       drop_stash $stash
+       test -z "$IS_STASH_REF" || drop_stash "$@"
 }
 
+PARSE_CACHE='--not-parsed'
+# The default command is "save" if nothing but options are given
+seen_non_option=
+for opt
+do
+       case "$opt" in
+       -*) ;;
+       *) seen_non_option=t; break ;;
+       esac
+done
+
+test -n "$seen_non_option" || set "save" "$@"
+
 # Main command set
 case "$1" in
 list)
        shift
-       if test $# = 0
-       then
-               set x -n 10
-               shift
-       fi
        list_stash "$@"
        ;;
 show)
@@ -298,23 +492,20 @@ drop)
        ;;
 pop)
        shift
-       if apply_stash "$@"
-       then
-               test -z "$unstash_index" || shift
-               drop_stash "$@"
-       fi
+       pop_stash "$@"
        ;;
 branch)
        shift
        apply_to_branch "$@"
        ;;
 *)
-       if test $# -eq 0
-       then
+       case $# in
+       0)
                save_stash &&
-               echo '(To restore them type "git stash apply")'
-       else
+               say '(To restore them type "git stash apply")'
+               ;;
+       *)
                usage
-       fi
+       esac
        ;;
 esac
index 8e234a4028d22e11baedba11f871d33f56945716..9ebbab798d21147fc019b6183cad0c462c9dafca 100755 (executable)
@@ -4,9 +4,14 @@
 #
 # Copyright (c) 2007 Lars Hjemli
 
-USAGE="[--quiet] [--cached] \
-[add [-b branch] <repo> <path>]|[status|init|update [-i|--init] [-N|--no-fetch]|summary [-n|--summary-limit <n>] [<commit>]] \
-[--] [<path>...]|[foreach <command>]|[sync [--] [<path>...]]"
+dashless=$(basename "$0" | sed -e 's/-/ /')
+USAGE="[--quiet] add [-b branch] [-f|--force] [--reference <repository>] [--] <repository> [<path>]
+   or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...]
+   or: $dashless [--quiet] init [--] [<path>...]
+   or: $dashless [--quiet] update [--init] [-N|--no-fetch] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...]
+   or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]
+   or: $dashless [--quiet] foreach [--recursive] <command>
+   or: $dashless [--quiet] sync [--] [<path>...]"
 OPTIONS_SPEC=
 . git-sh-setup
 . git-parse-remote
@@ -14,20 +19,15 @@ require_work_tree
 
 command=
 branch=
-quiet=
+force=
+reference=
 cached=
+recursive=
+init=
+files=
 nofetch=
-
-#
-# print stuff on stdout unless -q was specified
-#
-say()
-{
-       if test -z "$quiet"
-       then
-               echo "$@"
-       fi
-}
+update=
+prefix=
 
 # Resolve relative url by appending to parent's url
 resolve_relative_url ()
@@ -60,7 +60,7 @@ resolve_relative_url ()
 #
 module_list()
 {
-       git ls-files --error-unmatch --stage -- "$@" | grep '^160000 '
+       git ls-files --error-unmatch --stage -- "$@" | sane_grep '^160000 '
 }
 
 #
@@ -91,6 +91,7 @@ module_clone()
 {
        path=$1
        url=$2
+       reference="$3"
 
        # If there already is a directory at the submodule path,
        # expect it to be empty (since that is the default checkout
@@ -100,13 +101,18 @@ module_clone()
        if test -d "$path"
        then
                rmdir "$path" 2>/dev/null ||
-               die "Directory '$path' exist, but is neither empty nor a git repository"
+               die "Directory '$path' exists, but is neither empty nor a git repository"
        fi
 
        test -e "$path" &&
        die "A file already exist at path '$path'"
 
-       git-clone -n "$url" "$path" ||
+       if test -n "$reference"
+       then
+               git-clone "$reference" -n "$url" "$path"
+       else
+               git-clone -n "$url" "$path"
+       fi ||
        die "Clone of '$url' into submodule path '$path' failed"
 }
 
@@ -128,8 +134,20 @@ cmd_add()
                        branch=$2
                        shift
                        ;;
+               -f | --force)
+                       force=$1
+                       ;;
                -q|--quiet)
-                       quiet=1
+                       GIT_QUIET=1
+                       ;;
+               --reference)
+                       case "$2" in '') usage ;; esac
+                       reference="--reference=$2"
+                       shift
+                       ;;
+               --reference=*)
+                       reference="$1"
+                       shift
                        ;;
                --)
                        shift
@@ -148,6 +166,11 @@ cmd_add()
        repo=$1
        path=$2
 
+       if test -z "$path"; then
+               path=$(echo "$repo" |
+                       sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
+       fi
+
        if test -z "$repo" -o -z "$path"; then
                usage
        fi
@@ -182,6 +205,14 @@ cmd_add()
        git ls-files --error-unmatch "$path" > /dev/null 2>&1 &&
        die "'$path' already exists in the index"
 
+       if test -z "$force" && ! git add --dry-run --ignore-missing "$path" > /dev/null 2>&1
+       then
+               echo >&2 "The following path is ignored by one of your .gitignore files:" &&
+               echo >&2 $path &&
+               echo >&2 "Use -f if you really want to add it."
+               exit 1
+       fi
+
        # perhaps the path exists and is already a git repo, else clone it
        if test -e "$path"
        then
@@ -203,9 +234,9 @@ cmd_add()
                git config submodule."$path".url "$url"
        else
 
-               module_clone "$path" "$realrepo" || exit
+               module_clone "$path" "$realrepo" "$reference" || exit
                (
-                       unset GIT_DIR
+                       clear_local_git_env
                        cd "$path" &&
                        # ash fails to wordsplit ${branch:+-b "$branch"...}
                        case "$branch" in
@@ -215,12 +246,12 @@ cmd_add()
                ) || die "Unable to checkout submodule '$path'"
        fi
 
-       git add "$path" ||
+       git add $force "$path" ||
        die "Failed to add submodule '$path'"
 
        git config -f .gitmodules submodule."$path".path "$path" &&
        git config -f .gitmodules submodule."$path".url "$repo" &&
-       git add .gitmodules ||
+       git add --force .gitmodules ||
        die "Failed to register submodule '$path'"
 }
 
@@ -232,13 +263,45 @@ cmd_add()
 #
 cmd_foreach()
 {
+       # parse $args after "submodule ... foreach".
+       while test $# -ne 0
+       do
+               case "$1" in
+               -q|--quiet)
+                       GIT_QUIET=1
+                       ;;
+               --recursive)
+                       recursive=1
+                       ;;
+               -*)
+                       usage
+                       ;;
+               *)
+                       break
+                       ;;
+               esac
+               shift
+       done
+
+       toplevel=$(pwd)
+
        module_list |
        while read mode sha1 stage path
        do
                if test -e "$path"/.git
                then
-                       say "Entering '$path'"
-                       (cd "$path" && eval "$@") ||
+                       say "Entering '$prefix$path'"
+                       name=$(module_name "$path")
+                       (
+                               prefix="$prefix$path/"
+                               clear_local_git_env
+                               cd "$path" &&
+                               eval "$@" &&
+                               if test -n "$recursive"
+                               then
+                                       cmd_foreach "--recursive" "$@"
+                               fi
+                       ) ||
                        die "Stopping at '$path'; script returned non-zero status."
                fi
        done
@@ -256,7 +319,7 @@ cmd_init()
        do
                case "$1" in
                -q|--quiet)
-                       quiet=1
+                       GIT_QUIET=1
                        ;;
                --)
                        shift
@@ -294,6 +357,11 @@ cmd_init()
                git config submodule."$name".url "$url" ||
                die "Failed to register url for submodule path '$path'"
 
+               upd="$(git config -f .gitmodules submodule."$name".update)"
+               test -z "$upd" ||
+               git config submodule."$name".update "$upd" ||
+               die "Failed to register update mode for submodule path '$path'"
+
                say "Submodule '$name' ($url) registered for path '$path'"
        done
 }
@@ -306,21 +374,43 @@ cmd_init()
 cmd_update()
 {
        # parse $args after "submodule ... update".
+       orig_args="$@"
        while test $# -ne 0
        do
                case "$1" in
                -q|--quiet)
                        shift
-                       quiet=1
+                       GIT_QUIET=1
                        ;;
                -i|--init)
+                       init=1
                        shift
-                       cmd_init "$@" || return
                        ;;
                -N|--no-fetch)
                        shift
                        nofetch=1
                        ;;
+               -r|--rebase)
+                       shift
+                       update="rebase"
+                       ;;
+               --reference)
+                       case "$2" in '') usage ;; esac
+                       reference="--reference=$2"
+                       shift 2
+                       ;;
+               --reference=*)
+                       reference="$1"
+                       shift
+                       ;;
+               -m|--merge)
+                       shift
+                       update="merge"
+                       ;;
+               --recursive)
+                       shift
+                       recursive=1
+                       ;;
                --)
                        shift
                        break
@@ -334,11 +424,17 @@ cmd_update()
                esac
        done
 
+       if test -n "$init"
+       then
+               cmd_init "--" "$@" || return
+       fi
+
        module_list "$@" |
        while read mode sha1 stage path
        do
                name=$(module_name "$path") || exit
                url=$(git config submodule."$name".url)
+               update_module=$(git config submodule."$name".update)
                if test -z "$url"
                then
                        # Only mention uninitialized submodules when its
@@ -351,14 +447,19 @@ cmd_update()
 
                if ! test -d "$path"/.git -o -f "$path"/.git
                then
-                       module_clone "$path" "$url" || exit
+                       module_clone "$path" "$url" "$reference"|| exit
                        subsha1=
                else
-                       subsha1=$(unset GIT_DIR; cd "$path" &&
+                       subsha1=$(clear_local_git_env; cd "$path" &&
                                git rev-parse --verify HEAD) ||
                        die "Unable to find current revision in submodule path '$path'"
                fi
 
+               if ! test -z "$update"
+               then
+                       update_module=$update
+               fi
+
                if test "$subsha1" != "$sha1"
                then
                        force=
@@ -369,23 +470,45 @@ cmd_update()
 
                        if test -z "$nofetch"
                        then
-                               (unset GIT_DIR; cd "$path" &&
+                               (clear_local_git_env; cd "$path" &&
                                        git-fetch) ||
                                die "Unable to fetch in submodule path '$path'"
                        fi
 
-                       (unset GIT_DIR; cd "$path" &&
-                                 git-checkout $force -q "$sha1") ||
-                       die "Unable to checkout '$sha1' in submodule path '$path'"
+                       case "$update_module" in
+                       rebase)
+                               command="git rebase"
+                               action="rebase"
+                               msg="rebased onto"
+                               ;;
+                       merge)
+                               command="git merge"
+                               action="merge"
+                               msg="merged in"
+                               ;;
+                       *)
+                               command="git checkout $force -q"
+                               action="checkout"
+                               msg="checked out"
+                               ;;
+                       esac
+
+                       (clear_local_git_env; cd "$path" && $command "$sha1") ||
+                       die "Unable to $action '$sha1' in submodule path '$path'"
+                       say "Submodule path '$path': $msg '$sha1'"
+               fi
 
-                       say "Submodule path '$path': checked out '$sha1'"
+               if test -n "$recursive"
+               then
+                       (clear_local_git_env; cd "$path" && cmd_update $orig_args) ||
+                       die "Failed to recurse into submodule path '$path'"
                fi
        done
 }
 
 set_name_rev () {
        revname=$( (
-               unset GIT_DIR
+               clear_local_git_env
                cd "$1" && {
                        git describe "$2" 2>/dev/null ||
                        git describe --tags "$2" 2>/dev/null ||
@@ -406,6 +529,7 @@ set_name_rev () {
 cmd_summary() {
        summary_limit=-1
        for_status=
+       diff_cmd=diff-index
 
        # parse $args after "submodule ... summary".
        while test $# -ne 0
@@ -414,6 +538,9 @@ cmd_summary() {
                --cached)
                        cached="$1"
                        ;;
+               --files)
+                       files="$1"
+                       ;;
                --for-status)
                        for_status="$1"
                        ;;
@@ -442,18 +569,31 @@ cmd_summary() {
 
        test $summary_limit = 0 && return
 
-       if rev=$(git rev-parse -q --verify "$1^0")
+       if rev=$(git rev-parse -q --verify --default HEAD ${1+"$1"})
        then
                head=$rev
-               shift
+               test $# = 0 || shift
+       elif test -z "$1" -o "$1" = "HEAD"
+       then
+               # before the first commit: compare with an empty tree
+               head=$(git hash-object -w -t tree --stdin </dev/null)
+               test -z "$1" || shift
        else
-               head=HEAD
+               head="HEAD"
+       fi
+
+       if [ -n "$files" ]
+       then
+               test -n "$cached" &&
+               die "--cached cannot be used with --files"
+               diff_cmd=diff-files
+               head=
        fi
 
        cd_to_toplevel
        # Get modified modules cared by user
-       modules=$(git diff-index $cached --raw $head -- "$@" |
-               egrep '^:([0-7]* )?160000' |
+       modules=$(git $diff_cmd $cached --ignore-submodules=dirty --raw $head -- "$@" |
+               sane_egrep '^:([0-7]* )?160000' |
                while read mod_src mod_dst sha1_src sha1_dst status name
                do
                        # Always show modules deleted or type-changed (blob<->module)
@@ -466,8 +606,8 @@ cmd_summary() {
 
        test -z "$modules" && return
 
-       git diff-index $cached --raw $head -- $modules |
-       egrep '^:([0-7]* )?160000' |
+       git $diff_cmd $cached --ignore-submodules=dirty --raw $head -- $modules |
+       sane_egrep '^:([0-7]* )?160000' |
        cut -c2- |
        while read mod_src mod_dst sha1_src sha1_dst status name
        do
@@ -524,7 +664,7 @@ cmd_summary() {
                                range=$sha1_dst
                        fi
                        GIT_DIR="$name/.git" \
-                       git log --pretty=oneline --first-parent $range | wc -l
+                       git rev-list --first-parent $range -- | wc -l
                        )
                        total_commits=" ($(($total_commits + 0)))"
                        ;;
@@ -569,7 +709,11 @@ cmd_summary() {
                echo
        done |
        if test -n "$for_status"; then
-               echo "# Modified submodules:"
+               if [ -n "$files" ]; then
+                       echo "# Submodules changed but not updated:"
+               else
+                       echo "# Submodule changes to be committed:"
+               fi
                echo "#"
                sed -e 's|^|# |' -e 's|^# $|#|'
        else
@@ -589,15 +733,19 @@ cmd_summary() {
 cmd_status()
 {
        # parse $args after "submodule ... status".
+       orig_args="$@"
        while test $# -ne 0
        do
                case "$1" in
                -q|--quiet)
-                       quiet=1
+                       GIT_QUIET=1
                        ;;
                --cached)
                        cached=1
                        ;;
+               --recursive)
+                       recursive=1
+                       ;;
                --)
                        shift
                        break
@@ -617,22 +765,34 @@ cmd_status()
        do
                name=$(module_name "$path") || exit
                url=$(git config submodule."$name".url)
+               displaypath="$prefix$path"
                if test -z "$url" || ! test -d "$path"/.git -o -f "$path"/.git
                then
-                       say "-$sha1 $path"
+                       say "-$sha1 $displaypath"
                        continue;
                fi
                set_name_rev "$path" "$sha1"
-               if git diff-files --quiet -- "$path"
+               if git diff-files --ignore-submodules=dirty --quiet -- "$path"
                then
-                       say " $sha1 $path$revname"
+                       say " $sha1 $displaypath$revname"
                else
                        if test -z "$cached"
                        then
-                               sha1=$(unset GIT_DIR; cd "$path" && git rev-parse --verify HEAD)
+                               sha1=$(clear_local_git_env; cd "$path" && git rev-parse --verify HEAD)
                                set_name_rev "$path" "$sha1"
                        fi
-                       say "+$sha1 $path$revname"
+                       say "+$sha1 $displaypath$revname"
+               fi
+
+               if test -n "$recursive"
+               then
+                       (
+                               prefix="$displaypath/"
+                               clear_local_git_env
+                               cd "$path" &&
+                               cmd_status $orig_args
+                       ) ||
+                       die "Failed to recurse into submodule path '$path'"
                fi
        done
 }
@@ -647,7 +807,7 @@ cmd_sync()
        do
                case "$1" in
                -q|--quiet)
-                       quiet=1
+                       GIT_QUIET=1
                        shift
                        ;;
                --)
@@ -679,10 +839,11 @@ cmd_sync()
                if test -e "$path"/.git
                then
                (
-                       unset GIT_DIR
+                       say "Synchronizing submodule url for '$name'"
+                       git config submodule."$name".url "$url"
+                       clear_local_git_env
                        cd "$path"
                        remote=$(get_default_remote)
-                       say "Synchronizing submodule url for '$name'"
                        git config remote."$remote".url "$url"
                )
                fi
@@ -702,7 +863,7 @@ do
                command=$1
                ;;
        -q|--quiet)
-               quiet=1
+               GIT_QUIET=1
                ;;
        -b|--branch)
                case "$2" in
index ef1d30db3889d0d33b43b1d15cd19281291f5aea..9b046b693fe82c992b3844bf6e8f79214dfa7a59 100755 (executable)
@@ -5,7 +5,7 @@ use warnings;
 use strict;
 use vars qw/   $AUTHOR $VERSION
                $sha1 $sha1_short $_revision $_repository
-               $_q $_authors %users/;
+               $_q $_authors $_authors_prog %users/;
 $AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
 $VERSION = '@@GIT_VERSION@@';
 
@@ -19,18 +19,32 @@ $ENV{GIT_DIR} ||= '.git';
 $Git::SVN::default_repo_id = 'svn';
 $Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn';
 $Git::SVN::Ra::_log_window_size = 100;
+$Git::SVN::_minimize_url = 'unset';
+
+if (! exists $ENV{SVN_SSH}) {
+       if (exists $ENV{GIT_SSH}) {
+               $ENV{SVN_SSH} = $ENV{GIT_SSH};
+               if ($^O eq 'msys') {
+                       $ENV{SVN_SSH} =~ s/\\/\\\\/g;
+                       $ENV{SVN_SSH} =~ s/(.*)/"$1"/;
+               }
+       }
+}
 
 $Git::SVN::Log::TZ = $ENV{TZ};
 $ENV{TZ} = 'UTC';
 $| = 1; # unbuffer STDOUT
 
 sub fatal (@) { print STDERR "@_\n"; exit 1 }
-require SVN::Core; # use()-ing this causes segfaults for me... *shrug*
-require SVN::Ra;
-require SVN::Delta;
-if ($SVN::Core::VERSION lt '1.1.0') {
-       fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)";
+sub _req_svn {
+       require SVN::Core; # use()-ing this causes segfaults for me... *shrug*
+       require SVN::Ra;
+       require SVN::Delta;
+       if ($SVN::Core::VERSION lt '1.1.0') {
+               fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)";
+       }
 }
+my $can_compress = eval { require Compress::Zlib; 1};
 push @Git::SVN::Ra::ISA, 'SVN::Ra';
 push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor';
 push @SVN::Git::Fetcher::ISA, 'SVN::Delta::Editor';
@@ -39,6 +53,8 @@ use Digest::MD5;
 use IO::File qw//;
 use File::Basename qw/dirname basename/;
 use File::Path qw/mkpath/;
+use File::Spec;
+use File::Find;
 use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev/;
 use IPC::Open3;
 use Git;
@@ -62,7 +78,7 @@ my ($SVN);
 $sha1 = qr/[a-f\d]{40}/;
 $sha1_short = qr/[a-f\d]{4,40}/;
 my ($_stdin, $_help, $_edit,
-       $_message, $_file,
+       $_message, $_file, $_branch_dest,
        $_template, $_shared,
        $_version, $_fetch_all, $_no_rebase, $_fetch_parent,
        $_merge, $_strategy, $_dry_run, $_local,
@@ -76,6 +92,7 @@ my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
                     'ignore-paths=s' => \$SVN::Git::Fetcher::_ignore_regex );
 my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent,
                'authors-file|A=s' => \$_authors,
+               'authors-prog=s' => \$_authors_prog,
                'repack:i' => \$Git::SVN::_repack,
                'noMetadata' => \$Git::SVN::_no_metadata,
                'useSvmProps' => \$Git::SVN::_use_svm_props,
@@ -90,17 +107,18 @@ my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent,
                'localtime' => \$Git::SVN::_localtime,
                %remote_opts );
 
-my ($_trunk, $_tags, $_branches, $_stdlayout);
+my ($_trunk, @_tags, @_branches, $_stdlayout);
 my %icv;
 my %init_opts = ( 'template=s' => \$_template, 'shared:s' => \$_shared,
-                  'trunk|T=s' => \$_trunk, 'tags|t=s' => \$_tags,
-                  'branches|b=s' => \$_branches, 'prefix=s' => \$_prefix,
+                  'trunk|T=s' => \$_trunk, 'tags|t=s@' => \@_tags,
+                  'branches|b=s@' => \@_branches, 'prefix=s' => \$_prefix,
                   'stdlayout|s' => \$_stdlayout,
-                  'minimize-url|m' => \$Git::SVN::_minimize_url,
+                  'minimize-url|m!' => \$Git::SVN::_minimize_url,
                  'no-metadata' => sub { $icv{noMetadata} = 1 },
                  'use-svm-props' => sub { $icv{useSvmProps} = 1 },
                  'use-svnsync-props' => sub { $icv{useSvnsyncProps} = 1 },
                  'rewrite-root=s' => sub { $icv{rewriteRoot} = $_[1] },
+                 'rewrite-uuid=s' => sub { $icv{rewriteUUID} = $_[1] },
                   %remote_opts );
 my %cmt_opts = ( 'edit|e' => \$_edit,
                'rmdir' => \$SVN::Git::Editor::_rmdir,
@@ -139,12 +157,18 @@ my %cmd = (
        branch => [ \&cmd_branch,
                    'Create a branch in the SVN repository',
                    { 'message|m=s' => \$_message,
+                     'destination|d=s' => \$_branch_dest,
                      'dry-run|n' => \$_dry_run,
-                     'tag|t' => \$_tag } ],
+                     'tag|t' => \$_tag,
+                     'username=s' => \$Git::SVN::Prompt::_username,
+                     'commit-url=s' => \$_commit_url } ],
        tag => [ sub { $_tag = 1; cmd_branch(@_) },
                 'Create a tag in the SVN repository',
                 { 'message|m=s' => \$_message,
-                  'dry-run|n' => \$_dry_run } ],
+                  'destination|d=s' => \$_branch_dest,
+                  'dry-run|n' => \$_dry_run,
+                  'username=s' => \$Git::SVN::Prompt::_username,
+                  'commit-url=s' => \$_commit_url } ],
        'set-tree' => [ \&cmd_set_tree,
                        "Set an SVN repository to a git tree-ish",
                        { 'stdin' => \$_stdin, %cmt_opts, %fc_opts, } ],
@@ -152,6 +176,9 @@ my %cmd = (
                             'Create a .gitignore per svn:ignore',
                             { 'revision|r=i' => \$_revision
                             } ],
+       'mkdirs' => [ \&cmd_mkdirs ,
+                     "recreate empty directories after a checkout",
+                     { 'revision|r=i' => \$_revision } ],
         'propget' => [ \&cmd_propget,
                       'Print the value of a property on a file or directory',
                       { 'revision|r=i' => \$_revision } ],
@@ -209,6 +236,14 @@ my %cmd = (
        'blame' => [ \&Git::SVN::Log::cmd_blame,
                    "Show what revision and author last modified each line of a file",
                    { 'git-format' => \$_git_format } ],
+       'reset' => [ \&cmd_reset,
+                    "Undo fetches back to the specified SVN revision",
+                    { 'revision|r=s' => \$_revision,
+                      'parent|p' => \$_fetch_parent } ],
+       'gc' => [ \&cmd_gc,
+                 "Compress unhandled.log files in .git/svn and remove " .
+                 "index files in .git/svn",
+               {} ],
 );
 
 my $cmd;
@@ -217,6 +252,9 @@ for (my $i = 0; $i < @ARGV; $i++) {
                $cmd = $ARGV[$i];
                splice @ARGV, $i, 1;
                last;
+       } elsif ($ARGV[$i] eq 'help') {
+               $cmd = $ARGV[$i+1];
+               usage(0);
        }
 };
 
@@ -247,7 +285,7 @@ unless ($cmd && $cmd =~ /(?:clone|init|multi-init)$/) {
 
 my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
 
-read_repo_config(\%opts);
+read_git_config(\%opts);
 if ($cmd && ($cmd eq 'log' || $cmd eq 'blame')) {
        Getopt::Long::Configure('pass_through');
 }
@@ -263,6 +301,9 @@ usage(0) if $_help;
 version() if $_version;
 usage(1) unless defined $cmd;
 load_authors() if $_authors;
+if (defined $_authors_prog) {
+       $_authors_prog = "'" . File::Spec->rel2abs($_authors_prog) . "'";
+}
 
 unless ($cmd =~ /^(?:clone|init|multi-init|commit-diff)$/) {
        Git::SVN::Migration::migration_check();
@@ -310,6 +351,7 @@ information.
 }
 
 sub version {
+       ::_req_svn();
        print "git-svn version $VERSION (svn $SVN::Core::VERSION)\n";
        exit 0;
 }
@@ -328,7 +370,6 @@ sub do_git_init_db {
                command_noisy(@init_db);
                $_repository = Git->repository(Repository => ".git");
        }
-       command_noisy('config', 'core.autocrlf', 'false');
        my $set;
        my $pfx = "svn-remote.$Git::SVN::default_repo_id";
        foreach my $i (keys %icv) {
@@ -353,30 +394,38 @@ sub init_subdir {
 sub cmd_clone {
        my ($url, $path) = @_;
        if (!defined $path &&
-           (defined $_trunk || defined $_branches || defined $_tags ||
+           (defined $_trunk || @_branches || @_tags ||
             defined $_stdlayout) &&
            $url !~ m#^[a-z\+]+://#) {
                $path = $url;
        }
        $path = basename($url) if !defined $path || !length $path;
+       my $authors_absolute = $_authors ? File::Spec->rel2abs($_authors) : "";
        cmd_init($url, $path);
+       command_oneline('config', 'svn.authorsfile', $authors_absolute)
+           if $_authors;
        Git::SVN::fetch_all($Git::SVN::default_repo_id);
 }
 
 sub cmd_init {
        if (defined $_stdlayout) {
                $_trunk = 'trunk' if (!defined $_trunk);
-               $_tags = 'tags' if (!defined $_tags);
-               $_branches = 'branches' if (!defined $_branches);
+               @_tags = 'tags' if (! @_tags);
+               @_branches = 'branches' if (! @_branches);
        }
-       if (defined $_trunk || defined $_branches || defined $_tags) {
+       if (defined $_trunk || @_branches || @_tags) {
                return cmd_multi_init(@_);
        }
        my $url = shift or die "SVN repository location required ",
                               "as a command-line argument\n";
+       $url = canonicalize_url($url);
        init_subdir(@_);
        do_git_init_db();
 
+       if ($Git::SVN::_minimize_url eq 'unset') {
+               $Git::SVN::_minimize_url = 0;
+       }
+
        Git::SVN->init($url);
 }
 
@@ -389,6 +438,7 @@ sub cmd_fetch {
        if (@_ > 1) {
                die "Usage: $0 fetch [--all] [--parent] [svn-remote]\n";
        }
+       $Git::SVN::no_reuse_existing = undef;
        if ($_fetch_parent) {
                my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
                unless ($gs) {
@@ -444,12 +494,27 @@ sub cmd_set_tree {
 
 sub cmd_dcommit {
        my $head = shift;
+       command_noisy(qw/update-index --refresh/);
        git_cmd_try { command_oneline(qw/diff-index --quiet HEAD/) }
                'Cannot dcommit with a dirty index.  Commit your changes first, '
                . "or stash them with `git stash'.\n";
        $head ||= 'HEAD';
+
+       my $old_head;
+       if ($head ne 'HEAD') {
+               $old_head = eval {
+                       command_oneline([qw/symbolic-ref -q HEAD/])
+               };
+               if ($old_head) {
+                       $old_head =~ s{^refs/heads/}{};
+               } else {
+                       $old_head = eval { command_oneline(qw/rev-parse HEAD/) };
+               }
+               command(['checkout', $head], STDERR => 0);
+       }
+
        my @refs;
-       my ($url, $rev, $uuid, $gs) = working_head_info($head, \@refs);
+       my ($url, $rev, $uuid, $gs) = working_head_info('HEAD', \@refs);
        unless ($gs) {
                die "Unable to determine upstream SVN information from ",
                    "$head history.\nPerhaps the repository is empty.";
@@ -535,7 +600,7 @@ sub cmd_dcommit {
                        if (@diff) {
                                @refs = ();
                                my ($url_, $rev_, $uuid_, $gs_) =
-                                             working_head_info($head, \@refs);
+                                             working_head_info('HEAD', \@refs);
                                my ($linear_refs_, $parents_) =
                                              linearize_history($gs_, \@refs);
                                if (scalar(@$linear_refs) !=
@@ -553,8 +618,15 @@ sub cmd_dcommit {
                                          "\nBefore dcommitting";
                                }
                                if ($url_ ne $expect_url) {
-                                       fatal "URL mismatch after rebase: ",
-                                             "$url_ != $expect_url";
+                                       if ($url_ eq $gs->metadata_url) {
+                                               print
+                                                 "Accepting rewritten URL:",
+                                                 " $url_\n";
+                                       } else {
+                                               fatal
+                                                 "URL mismatch after rebase:",
+                                                 " $url_ != $expect_url";
+                                       }
                                }
                                if ($uuid_ ne $uuid) {
                                        fatal "uuid mismatch after rebase: ",
@@ -573,6 +645,22 @@ sub cmd_dcommit {
                        }
                }
        }
+
+       if ($old_head) {
+               my $new_head = command_oneline(qw/rev-parse HEAD/);
+               my $new_is_symbolic = eval {
+                       command_oneline(qw/symbolic-ref -q HEAD/);
+               };
+               if ($new_is_symbolic) {
+                       print "dcommitted the branch ", $head, "\n";
+               } else {
+                       print "dcommitted on a detached HEAD because you gave ",
+                             "a revision argument.\n",
+                             "The rewritten commit is: ", $new_head, "\n";
+               }
+               command(['checkout', $old_head], STDERR => 0);
+       }
+
        unlink $gs->{index};
 }
 
@@ -584,12 +672,68 @@ sub cmd_branch {
        }
        $head ||= 'HEAD';
 
-       my ($src, $rev, undef, $gs) = working_head_info($head);
+       my (undef, $rev, undef, $gs) = working_head_info($head);
+       my $src = $gs->full_url;
 
        my $remote = Git::SVN::read_all_remotes()->{$gs->{repo_id}};
-       my $glob = $remote->{ $_tag ? 'tags' : 'branches' };
+       my $allglobs = $remote->{ $_tag ? 'tags' : 'branches' };
+       my $glob;
+       if ($#{$allglobs} == 0) {
+               $glob = $allglobs->[0];
+       } else {
+               unless(defined $_branch_dest) {
+                       die "Multiple ",
+                           $_tag ? "tag" : "branch",
+                           " paths defined for Subversion repository.\n",
+                           "You must specify where you want to create the ",
+                           $_tag ? "tag" : "branch",
+                           " with the --destination argument.\n";
+               }
+               foreach my $g (@{$allglobs}) {
+                       # SVN::Git::Editor could probably be moved to Git.pm..
+                       my $re = SVN::Git::Editor::glob2pat($g->{path}->{left});
+                       if ($_branch_dest =~ /$re/) {
+                               $glob = $g;
+                               last;
+                       }
+               }
+               unless (defined $glob) {
+                       my $dest_re = qr/\b\Q$_branch_dest\E\b/;
+                       foreach my $g (@{$allglobs}) {
+                               $g->{path}->{left} =~ /$dest_re/ or next;
+                               if (defined $glob) {
+                                       die "Ambiguous destination: ",
+                                           $_branch_dest, "\nmatches both '",
+                                           $glob->{path}->{left}, "' and '",
+                                           $g->{path}->{left}, "'\n";
+                               }
+                               $glob = $g;
+                       }
+                       unless (defined $glob) {
+                               die "Unknown ",
+                                   $_tag ? "tag" : "branch",
+                                   " destination $_branch_dest\n";
+                       }
+               }
+       }
        my ($lft, $rgt) = @{ $glob->{path} }{qw/left right/};
-       my $dst = join '/', $remote->{url}, $lft, $branch_name, ($rgt || ());
+       my $url;
+       if (defined $_commit_url) {
+               $url = $_commit_url;
+       } else {
+               $url = eval { command_oneline('config', '--get',
+                       "svn-remote.$gs->{repo_id}.commiturl") };
+               if (!$url) {
+                       $url = $remote->{url};
+               }
+       }
+       my $dst = join '/', $url, $lft, $branch_name, ($rgt || ());
+
+       if ($dst =~ /^https:/ && $src =~ /^http:/) {
+               $src=~s/^http:/https:/;
+       }
+
+       ::_req_svn();
 
        my $ctx = SVN::Client->new(
                auth    => Git::SVN::Ra::_auth_providers(),
@@ -657,6 +801,7 @@ sub cmd_rebase {
                $_fetch_all ? $gs->fetch_all : $gs->fetch;
        }
        command_noisy(rebase_cmd(), $gs->refname);
+       $gs->mkemptydirs;
 }
 
 sub cmd_show_ignore {
@@ -668,6 +813,7 @@ sub cmd_show_ignore {
                print STDOUT "\n# $path\n";
                my $s = $props->{'svn:ignore'} or return;
                $s =~ s/[\r\n]+/\n/g;
+               $s =~ s/^\n+//;
                chomp $s;
                $s =~ s#^#$path#gm;
                print STDOUT "$s\n";
@@ -705,6 +851,7 @@ sub cmd_create_ignore {
                open(GITIGNORE, '>', $ignore)
                  or fatal("Failed to open `$ignore' for writing: $!");
                $s =~ s/[\r\n]+/\n/g;
+               $s =~ s/^\n+//;
                chomp $s;
                # Prefix all patterns so that the ignore doesn't apply
                # to sub-directories.
@@ -716,6 +863,12 @@ sub cmd_create_ignore {
        });
 }
 
+sub cmd_mkdirs {
+       my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
+       $gs ||= Git::SVN->new;
+       $gs->mkemptydirs($_revision);
+}
+
 sub canonicalize_path {
        my ($path) = @_;
        my $dot_slash_added = 0;
@@ -735,6 +888,12 @@ sub canonicalize_path {
        return $path;
 }
 
+sub canonicalize_url {
+       my ($url) = @_;
+       $url =~ s#^([^:]+://[^/]*/)(.*)$#$1 . canonicalize_path($2)#e;
+       return $url;
+}
+
 # get_svnprops(PATH)
 # ------------------
 # Helper for cmd_propget and cmd_proplist below.
@@ -794,22 +953,19 @@ sub cmd_proplist {
 
 sub cmd_multi_init {
        my $url = shift;
-       unless (defined $_trunk || defined $_branches || defined $_tags) {
+       unless (defined $_trunk || @_branches || @_tags) {
                usage(1);
        }
 
-       # there are currently some bugs that prevent multi-init/multi-fetch
-       # setups from working well without this.
-       $Git::SVN::_minimize_url = 1;
-
        $_prefix = '' unless defined $_prefix;
        if (defined $url) {
-               $url =~ s#/+$##;
+               $url = canonicalize_url($url);
                init_subdir(@_);
        }
        do_git_init_db();
        if (defined $_trunk) {
-               my $trunk_ref = $_prefix . 'trunk';
+               $_trunk =~ s#^/+##;
+               my $trunk_ref = 'refs/remotes/' . $_prefix . 'trunk';
                # try both old-style and new-style lookups:
                my $gs_trunk = eval { Git::SVN->new($trunk_ref) };
                unless ($gs_trunk) {
@@ -819,13 +975,18 @@ sub cmd_multi_init {
                                                   undef, $trunk_ref);
                }
        }
-       return unless defined $_branches || defined $_tags;
+       return unless @_branches || @_tags;
        my $ra = $url ? Git::SVN::Ra->new($url) : undef;
-       complete_url_ls_init($ra, $_branches, '--branches/-b', $_prefix);
-       complete_url_ls_init($ra, $_tags, '--tags/-t', $_prefix . 'tags/');
+       foreach my $path (@_branches) {
+               complete_url_ls_init($ra, $path, '--branches/-b', $_prefix);
+       }
+       foreach my $path (@_tags) {
+               complete_url_ls_init($ra, $path, '--tags/-t', $_prefix.'tags/');
+       }
 }
 
 sub cmd_multi_fetch {
+       $Git::SVN::no_reuse_existing = undef;
        my $remotes = Git::SVN::read_all_remotes();
        foreach my $repo_id (sort keys %$remotes) {
                if ($remotes->{$repo_id}->{url}) {
@@ -943,6 +1104,7 @@ sub cmd_info {
        if ($@) {
                $result .= "Repository Root: (offline)\n";
        }
+       ::_req_svn();
        $result .= "Repository UUID: $uuid\n" unless $diff_status eq "A" &&
                ($SVN::Core::VERSION le '1.5.4' || $file_type ne "dir");
        $result .= "Revision: " . ($diff_status eq "A" ? 0 : $rev) . "\n";
@@ -1015,6 +1177,29 @@ sub cmd_info {
        print $result, "\n";
 }
 
+sub cmd_reset {
+       my $target = shift || $_revision or die "SVN revision required\n";
+       $target = $1 if $target =~ /^r(\d+)$/;
+       $target =~ /^\d+$/ or die "Numeric SVN revision expected\n";
+       my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
+       unless ($gs) {
+               die "Unable to determine upstream SVN information from ".
+                   "history\n";
+       }
+       my ($r, $c) = $gs->find_rev_before($target, not $_fetch_parent);
+       die "Cannot find SVN revision $target\n" unless defined($c);
+       $gs->rev_map_set($r, $c, 'reset', $uuid);
+       print "r$r = $c ($gs->{ref_id})\n";
+}
+
+sub cmd_gc {
+       if (!$can_compress) {
+               warn "Compress::Zlib could not be found; unhandled.log " .
+                    "files will not be compressed.\n";
+       }
+       find({ wanted => \&gc_directory, no_chdir => 1}, "$ENV{GIT_DIR}/svn");
+}
+
 ########################### utility functions #########################
 
 sub rebase_cmd {
@@ -1030,6 +1215,17 @@ sub post_fetch_checkout {
        my $gs = $Git::SVN::_head or return;
        return if verify_ref('refs/heads/master^0');
 
+       # look for "trunk" ref if it exists
+       my $remote = Git::SVN::read_all_remotes()->{$gs->{repo_id}};
+       my $fetch = $remote->{fetch};
+       if ($fetch) {
+               foreach my $p (keys %$fetch) {
+                       basename($fetch->{$p}) eq 'trunk' or next;
+                       $gs = Git::SVN->new($fetch->{$p}, $gs->{repo_id}, $p);
+                       last;
+               }
+       }
+
        my $valid_head = verify_ref('HEAD^0');
        command_noisy(qw(update-ref refs/heads/master), $gs->refname);
        return if ($valid_head || !verify_ref('HEAD^0'));
@@ -1043,6 +1239,7 @@ sub post_fetch_checkout {
        command_noisy(qw/read-tree -m -u -v HEAD HEAD/);
        print STDERR "Checked out HEAD:\n  ",
                     $gs->full_url, " r", $gs->last_rev, "\n";
+       $gs->mkemptydirs($gs->last_rev);
 }
 
 sub complete_svn_url {
@@ -1084,7 +1281,8 @@ sub complete_url_ls_init {
                    "wanted to set to: $gs->{url}\n";
        }
        command_oneline('config', $k, $gs->{url}) unless $orig_url;
-       my $remote_path = "$ra->{svn_path}/$repo_path";
+       my $remote_path = "$gs->{path}/$repo_path";
+       $remote_path =~ s{%([0-9A-F]{2})}{chr hex($1)}ieg;
        $remote_path =~ s#/+#/#g;
        $remote_path =~ s#^/##g;
        $remote_path .= "/*" if $remote_path !~ /\*/;
@@ -1093,6 +1291,7 @@ sub complete_url_ls_init {
                die "--prefix='$pfx' must have a trailing slash '/'\n";
        }
        command_noisy('config',
+                     '--add',
                      "svn-remote.$gs->{repo_id}.$n",
                      "$remote_path:refs/remotes/$pfx*" .
                        ('/*' x (($remote_path =~ tr/*/*/) - 1)) );
@@ -1166,22 +1365,32 @@ sub get_commit_entry {
        close $log_fh or croak $!;
 
        if ($_edit || ($type eq 'tree')) {
-               my $editor = $ENV{VISUAL} || $ENV{EDITOR} || 'vi';
-               # TODO: strip out spaces, comments, like git-commit.sh
-               system($editor, $commit_editmsg);
+               chomp(my $editor = command_oneline(qw(var GIT_EDITOR)));
+               system('sh', '-c', $editor.' "$@"', $editor, $commit_editmsg);
        }
        rename $commit_editmsg, $commit_msg or croak $!;
        {
+               require Encode;
                # SVN requires messages to be UTF-8 when entering the repo
                local $/;
                open $log_fh, '<', $commit_msg or croak $!;
                binmode $log_fh;
                chomp($log_entry{log} = <$log_fh>);
 
-               if (my $enc = Git::config('i18n.commitencoding')) {
-                       require Encode;
-                       Encode::from_to($log_entry{log}, $enc, 'UTF-8');
+               my $enc = Git::config('i18n.commitencoding') || 'UTF-8';
+               my $msg = $log_entry{log};
+
+               eval { $msg = Encode::decode($enc, $msg, 1) };
+               if ($@) {
+                       die "Could not decode as $enc:\n", $msg,
+                           "\nPerhaps you need to set i18n.commitencoding\n";
                }
+
+               eval { $msg = Encode::encode('UTF-8', $msg, 1) };
+               die "Could not encode as UTF-8:\n$msg\n" if $@;
+
+               $log_entry{log} = $msg;
+
                close $log_fh or croak $!;
        }
        unlink $commit_msg;
@@ -1224,8 +1433,7 @@ sub load_authors {
 }
 
 # convert GetOpt::Long specs for use by git-config
-sub read_repo_config {
-       return unless -d $ENV{GIT_DIR};
+sub read_git_config {
        my $opts = shift;
        my @config_only;
        foreach my $o (keys %$opts) {
@@ -1255,11 +1463,11 @@ sub read_repo_config {
 sub extract_metadata {
        my $id = shift or return (undef, undef, undef);
        my ($url, $rev, $uuid) = ($id =~ /^\s*git-svn-id:\s+(.*)\@(\d+)
-                                                       \s([a-f\d\-]+)$/x);
+                                                       \s([a-f\d\-]+)$/ix);
        if (!defined $rev || !$uuid || !$url) {
                # some of the original repositories I made had
                # identifiers like this:
-               ($rev, $uuid) = ($id =~/^\s*git-svn-id:\s(\d+)\@([a-f\d\-]+)/);
+               ($rev, $uuid) = ($id =~/^\s*git-svn-id:\s(\d+)\@([a-f\d\-]+)/i);
        }
        return ($url, $rev, $uuid);
 }
@@ -1423,6 +1631,25 @@ sub md5sum {
        return $md5->hexdigest();
 }
 
+sub gc_directory {
+       if ($can_compress && -f $_ && basename($_) eq "unhandled.log") {
+               my $out_filename = $_ . ".gz";
+               open my $in_fh, "<", $_ or die "Unable to open $_: $!\n";
+               binmode $in_fh;
+               my $gz = Compress::Zlib::gzopen($out_filename, "ab") or
+                               die "Unable to open $out_filename: $!\n";
+
+               my $res;
+               while ($res = sysread($in_fh, my $str, 1024)) {
+                       $gz->gzwrite($str) or
+                               die "Unable to write: ".$gz->gzerror()."!\n";
+               }
+               unlink $_ or die "unlink $File::Find::name: $!\n";
+       } elsif (-f $_ && basename($_) eq "index") {
+               unlink $_ or die "unlink $_: $!\n";
+       }
+}
+
 package Git::SVN;
 use strict;
 use warnings;
@@ -1436,6 +1663,8 @@ use Carp qw/croak/;
 use File::Path qw/mkpath/;
 use File::Copy qw/copy/;
 use IPC::Open3;
+use Memoize;  # core since 5.8.0, Jul 2002
+use Memoize::Storable;
 
 my ($_gc_nr, $_gc_period);
 
@@ -1486,23 +1715,23 @@ sub resolve_local_globs {
        return unless defined $glob_spec;
        my $ref = $glob_spec->{ref};
        my $path = $glob_spec->{path};
-       foreach (command(qw#for-each-ref --format=%(refname) refs/remotes#)) {
-               next unless m#^refs/remotes/$ref->{regex}$#;
+       foreach (command(qw#for-each-ref --format=%(refname) refs/#)) {
+               next unless m#^$ref->{regex}$#;
                my $p = $1;
                my $pathname = desanitize_refname($path->full_path($p));
                my $refname = desanitize_refname($ref->full_path($p));
                if (my $existing = $fetch->{$pathname}) {
                        if ($existing ne $refname) {
                                die "Refspec conflict:\n",
-                                   "existing: refs/remotes/$existing\n",
-                                   " globbed: refs/remotes/$refname\n";
+                                   "existing: $existing\n",
+                                   " globbed: $refname\n";
                        }
-                       my $u = (::cmt_metadata("refs/remotes/$refname"))[0];
+                       my $u = (::cmt_metadata("$refname"))[0];
                        $u =~ s!^\Q$url\E(/|$)!! or die
-                         "refs/remotes/$refname: '$url' not found in '$u'\n";
+                         "$refname: '$url' not found in '$u'\n";
                        if ($pathname ne $u) {
                                warn "W: Refspec glob conflict ",
-                                    "(ref: refs/remotes/$refname):\n",
+                                    "(ref: $refname):\n",
                                     "expected path: $pathname\n",
                                     "    real path: $u\n",
                                     "Continuing ahead with $u\n";
@@ -1543,12 +1772,18 @@ sub fetch_all {
        my $ra = Git::SVN::Ra->new($url);
        my $uuid = $ra->get_uuid;
        my $head = $ra->get_latest_revnum;
+
+       # ignore errors, $head revision may not even exist anymore
+       eval { $ra->get_log("", $head, 0, 1, 0, 1, sub { $head = $_[1] }) };
+       warn "W: $@\n" if $@;
+
        my $base = defined $fetch ? $head : 0;
 
        # read the max revs for wildcard expansion (branches/*, tags/*)
        foreach my $t (qw/branches tags/) {
                defined $remote->{$t} or next;
-               push @globs, $remote->{$t};
+               push @globs, @{$remote->{$t}};
+
                my $max_rev = eval { tmp_config(qw/--int --get/,
                                         "svn-remote.$repo_id.${t}-maxRev") };
                if (defined $max_rev && ($max_rev < $base)) {
@@ -1578,32 +1813,37 @@ sub read_all_remotes {
        my $use_svm_props = eval { command_oneline(qw/config --bool
            svn.useSvmProps/) };
        $use_svm_props = $use_svm_props eq 'true' if $use_svm_props;
+       my $svn_refspec = qr{\s*(.*?)\s*:\s*(.+?)\s*};
        foreach (grep { s/^svn-remote\.// } command(qw/config -l/)) {
-               if (m!^(.+)\.fetch=\s*(.*)\s*:\s*(.+)\s*$!) {
-                       my ($remote, $local_ref, $_remote_ref) = ($1, $2, $3);
-                       die("svn-remote.$remote: remote ref '$_remote_ref' "
-                           . "must start with 'refs/remotes/'\n")
-                               unless $_remote_ref =~ m{^refs/remotes/(.+)};
-                       my $remote_ref = $1;
-                       $local_ref =~ s{^/}{};
+               if (m!^(.+)\.fetch=$svn_refspec$!) {
+                       my ($remote, $local_ref, $remote_ref) = ($1, $2, $3);
+                       die("svn-remote.$remote: remote ref '$remote_ref' "
+                           . "must start with 'refs/'\n")
+                               unless $remote_ref =~ m{^refs/};
+                       $local_ref = uri_decode($local_ref);
                        $r->{$remote}->{fetch}->{$local_ref} = $remote_ref;
                        $r->{$remote}->{svm} = {} if $use_svm_props;
                } elsif (m!^(.+)\.usesvmprops=\s*(.*)\s*$!) {
                        $r->{$1}->{svm} = {};
                } elsif (m!^(.+)\.url=\s*(.*)\s*$!) {
                        $r->{$1}->{url} = $2;
-               } elsif (m!^(.+)\.(branches|tags)=
-                          (.*):refs/remotes/(.+)\s*$/!x) {
-                       my ($p, $g) = ($3, $4);
-                       my $rs = $r->{$1}->{$2} = {
-                                         t => $2,
-                                         remote => $1,
-                                         path => Git::SVN::GlobSpec->new($p),
-                                         ref => Git::SVN::GlobSpec->new($g) };
+               } elsif (m!^(.+)\.(branches|tags)=$svn_refspec$!) {
+                       my ($remote, $t, $local_ref, $remote_ref) =
+                                                            ($1, $2, $3, $4);
+                       die("svn-remote.$remote: remote ref '$remote_ref' ($t) "
+                           . "must start with 'refs/'\n")
+                               unless $remote_ref =~ m{^refs/};
+                       $local_ref = uri_decode($local_ref);
+                       my $rs = {
+                           t => $t,
+                           remote => $remote,
+                           path => Git::SVN::GlobSpec->new($local_ref, 1),
+                           ref => Git::SVN::GlobSpec->new($remote_ref, 0) };
                        if (length($rs->{ref}->{right}) != 0) {
                                die "The '*' glob character must be the last ",
-                                   "character of '$g'\n";
+                                   "character of '$remote_ref'\n";
                        }
+                       push @{ $r->{$remote}->{$t} }, $rs;
                }
        }
 
@@ -1711,14 +1951,15 @@ sub init_remote_config {
                }
        }
        my ($xrepo_id, $xpath) = find_ref($self->refname);
-       if (defined $xpath) {
+       if (!$no_write && defined $xpath) {
                die "svn-remote.$xrepo_id.fetch already set to track ",
-                   "$xpath:refs/remotes/", $self->refname, "\n";
+                   "$xpath:", $self->refname, "\n";
        }
        unless ($no_write) {
                command_noisy('config',
                              "svn-remote.$self->{repo_id}.url", $url);
                $self->{path} =~ s{^/}{};
+               $self->{path} =~ s{%([0-9A-F]{2})}{chr hex($1)}ieg;
                command_noisy('config', '--add',
                              "svn-remote.$self->{repo_id}.fetch",
                              "$self->{path}:".$self->refname);
@@ -1743,9 +1984,10 @@ sub find_by_url { # repos_root and, path are optional
                next if defined $repos_root && $repos_root ne $u;
 
                my $fetch = $remotes->{$repo_id}->{fetch} || {};
-               foreach (qw/branches tags/) {
-                       resolve_local_globs($u, $fetch,
-                                           $remotes->{$repo_id}->{$_});
+               foreach my $t (qw/branches tags/) {
+                       foreach my $globspec (@{$remotes->{$repo_id}->{$t}}) {
+                               resolve_local_globs($u, $fetch, $globspec);
+                       }
                }
                my $p = $path;
                my $rwr = rewrite_root({repo_id => $repo_id});
@@ -1787,7 +2029,7 @@ sub find_ref {
        my ($ref_id) = @_;
        foreach (command(qw/config -l/)) {
                next unless m!^svn-remote\.(.+)\.fetch=
-                             \s*(.*)\s*:\s*refs/remotes/(.+)\s*$!x;
+                             \s*(.*?)\s*:\s*(.+?)\s*$!x;
                my ($repo_id, $path, $ref) = ($1, $2, $3);
                if ($ref eq $ref_id) {
                        $path = '' if ($path =~ m#^\./?#);
@@ -1804,18 +2046,21 @@ sub new {
                if (!defined $repo_id) {
                        die "Could not find a \"svn-remote.*.fetch\" key ",
                            "in the repository configuration matching: ",
-                           "refs/remotes/$ref_id\n";
+                           "$ref_id\n";
                }
        }
        my $self = _new($class, $repo_id, $ref_id, $path);
        if (!defined $self->{path} || !length $self->{path}) {
                my $fetch = command_oneline('config', '--get',
                                            "svn-remote.$repo_id.fetch",
-                                           ":refs/remotes/$ref_id\$") or
+                                           ":$ref_id\$") or
                     die "Failed to read \"svn-remote.$repo_id.fetch\" ",
-                        "\":refs/remotes/$ref_id\$\" in config\n";
+                        "\":$ref_id\$\" in config\n";
                ($self->{path}, undef) = split(/\s*:\s*/, $fetch);
        }
+       $self->{path} =~ s{/+}{/}g;
+       $self->{path} =~ s{\A/}{};
+       $self->{path} =~ s{/\z}{};
        $self->{url} = command_oneline('config', '--get',
                                       "svn-remote.$repo_id.url") or
                   die "Failed to read \"svn-remote.$repo_id.url\" in config\n";
@@ -1824,7 +2069,7 @@ sub new {
 }
 
 sub refname {
-       my ($refname) = "refs/remotes/$_[0]->{ref_id}" ;
+       my ($refname) = $_[0]->{ref_id} ;
 
        # It cannot end with a slash /, we'll throw up on this because
        # SVN can't have directories with a slash in their name, either:
@@ -1849,6 +2094,14 @@ sub refname {
        # .. becomes %2E%2E
        $refname =~ s{\.\.}{%2E%2E}g;
 
+       # trailing dots and .lock are not allowed
+       # .$ becomes %2E and .lock becomes %2Elock
+       $refname =~ s{\.(?=$|lock$)}{%2E};
+
+       # the sequence @{ is used to access the reflog
+       # @{ becomes %40{
+       $refname =~ s{\@\{}{%40\{}g;
+
        return $refname;
 }
 
@@ -1903,7 +2156,7 @@ sub _set_svm_vars {
 
                chomp($src, $uuid);
 
-               $uuid =~ m{^[0-9a-f\-]{30,}$}
+               $uuid =~ m{^[0-9a-f\-]{30,}$}i
                    or die "doesn't look right - svm:uuid is '$uuid'\n";
 
                # the '!' is used to mark the repos_root!/relative/path
@@ -1978,6 +2231,10 @@ sub svnsync {
                die "Can't have both 'useSvnsyncProps' and 'rewriteRoot' ",
                    "options set!\n";
        }
+       if ($self->rewrite_uuid) {
+               die "Can't have both 'useSvnsyncProps' and 'rewriteUUID' ",
+                   "options set!\n";
+       }
 
        my $svnsync;
        # see if we have it in our config, first:
@@ -1989,7 +2246,7 @@ sub svnsync {
                   die "doesn't look right - svn:sync-from-url is '$url'\n";
 
                my $uuid = tmp_config('--get', "$section.svnsync-uuid");
-               ($uuid) = ($uuid =~ m{^([0-9a-f\-]{30,})$}) or
+               ($uuid) = ($uuid =~ m{^([0-9a-f\-]{30,})$}i) or
                   die "doesn't look right - svn:sync-from-uuid is '$uuid'\n";
 
                $svnsync = { url => $url, uuid => $uuid }
@@ -2007,7 +2264,7 @@ sub svnsync {
                   die "doesn't look right - svn:sync-from-url is '$url'\n";
 
        my $uuid = $rp->{'svn:sync-from-uuid'} or die $err . "uuid\n";
-       ($uuid) = ($uuid =~ m{^([0-9a-f\-]{30,})$}) or
+       ($uuid) = ($uuid =~ m{^([0-9a-f\-]{30,})$}i) or
                   die "doesn't look right - svn:sync-from-uuid is '$uuid'\n";
 
        my $section = "svn-remote.$self->{repo_id}";
@@ -2023,7 +2280,7 @@ sub ra_uuid {
        unless ($self->{ra_uuid}) {
                my $key = "svn-remote.$self->{repo_id}.uuid";
                my $uuid = eval { tmp_config('--get', $key) };
-               if (!$@ && $uuid && $uuid =~ /^([a-f\d\-]{30,})$/) {
+               if (!$@ && $uuid && $uuid =~ /^([a-f\d\-]{30,})$/i) {
                        $self->{ra_uuid} = $uuid;
                } else {
                        die "ra_uuid called without URL\n" unless $self->{url};
@@ -2066,16 +2323,6 @@ sub ra {
        $ra;
 }
 
-sub rel_path {
-       my ($self) = @_;
-       my $repos_root = $self->ra->{repos_root};
-       return $self->{path} if ($self->{url} eq $repos_root);
-       my $url = $self->{url} .
-                 (length $self->{path} ? "/$self->{path}" : $self->{path});
-       $url =~ s!^\Q$repos_root\E(?:/+|$)!!g;
-       $url;
-}
-
 # prop_walk(PATH, REV, SUB)
 # -------------------------
 # Recursively traverse PATH at revision REV and invoke SUB for each
@@ -2251,12 +2498,6 @@ sub get_commit_parents {
                next if $seen{$p};
                $seen{$p} = 1;
                push @ret, $p;
-               # MAXPARENT is defined to 16 in commit-tree.c:
-               last if @ret >= 16;
-       }
-       if (@tmp) {
-               die "r$log_entry->{revision}: No room for parents:\n\t",
-                   join("\n\t", @tmp), "\n";
        }
        @ret;
 }
@@ -2275,6 +2516,20 @@ sub rewrite_root {
        $self->{-rewrite_root} = $rwr;
 }
 
+sub rewrite_uuid {
+       my ($self) = @_;
+       return $self->{-rewrite_uuid} if exists $self->{-rewrite_uuid};
+       my $k = "svn-remote.$self->{repo_id}.rewriteUUID";
+       my $rwid = eval { command_oneline(qw/config --get/, $k) };
+       if ($rwid) {
+               $rwid =~ s#/+$##;
+               if ($rwid !~ m#^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$#) {
+                       die "$rwid is not a valid UUID (key: $k)\n";
+               }
+       }
+       $self->{-rewrite_uuid} = $rwid;
+}
+
 sub metadata_url {
        my ($self) = @_;
        ($self->rewrite_root || $self->{url}) .
@@ -2401,10 +2656,7 @@ sub match_paths {
        if (my $path = $paths->{"/$self->{path}"}) {
                return ($path->{action} eq 'D') ? 0 : 1;
        }
-       my $repos_root = $self->ra->{repos_root};
-       my $extended_path = $self->{url} . '/' . $self->{path};
-       $extended_path =~ s#^\Q$repos_root\E(/|$)##;
-       $self->{path_regex} ||= qr/^\/\Q$extended_path\E\//;
+       $self->{path_regex} ||= qr/^\/\Q$self->{path}\E\//;
        if (grep /$self->{path_regex}/, keys %$paths) {
                return 1;
        }
@@ -2427,15 +2679,14 @@ sub find_parent_branch {
        unless (defined $paths) {
                my $err_handler = $SVN::Error::handler;
                $SVN::Error::handler = \&Git::SVN::Ra::skip_unknown_revs;
-               $self->ra->get_log([$self->{path}], $rev, $rev, 0, 1, 1, sub {
-                                  $paths =
-                                     Git::SVN::Ra::dup_changed_paths($_[0]) });
+               $self->ra->get_log([$self->{path}], $rev, $rev, 0, 1, 1,
+                                  sub { $paths = $_[0] });
                $SVN::Error::handler = $err_handler;
        }
        return undef unless defined $paths;
 
        # look for a parent from another branch:
-       my @b_path_components = split m#/#, $self->rel_path;
+       my @b_path_components = split m#/#, $self->{path};
        my @a_path_components;
        my $i;
        while (@b_path_components) {
@@ -2453,11 +2704,12 @@ sub find_parent_branch {
        my $r = $i->{copyfrom_rev};
        my $repos_root = $self->ra->{repos_root};
        my $url = $self->ra->{url};
-       my $new_url = $repos_root . $branch_from;
+       my $new_url = $url . $branch_from;
        print STDERR  "Found possible branch point: ",
-                     "$new_url => ", $self->full_url, ", $r\n";
+                     "$new_url => ", $self->full_url, ", $r\n"
+                     unless $::_q > 1;
        $branch_from =~ s#^/##;
-       my $gs = $self->other_gs($new_url, $url, $repos_root,
+       my $gs = $self->other_gs($new_url, $url,
                                 $branch_from, $r, $self->{ref_id});
        my ($r0, $parent) = $gs->find_rev_before($r, 1);
        {
@@ -2476,11 +2728,13 @@ sub find_parent_branch {
                ($r0, $parent) = $gs->find_rev_before($r, 1);
        }
        if (defined $r0 && defined $parent) {
-               print STDERR "Found branch parent: ($self->{ref_id}) $parent\n";
+               print STDERR "Found branch parent: ($self->{ref_id}) $parent\n"
+                            unless $::_q > 1;
                my $ed;
                if ($self->ra->can_do_switch) {
                        $self->assert_index_clean($parent);
-                       print STDERR "Following parent with do_switch\n";
+                       print STDERR "Following parent with do_switch\n"
+                                    unless $::_q > 1;
                        # do_switch works with svn/trunk >= r22312, but that
                        # is not included with SVN 1.4.3 (the latest version
                        # at the moment), so we can't rely on it
@@ -2495,18 +2749,20 @@ sub find_parent_branch {
                        print STDERR "Trees match:\n",
                                     "  $new_url\@$r0\n",
                                     "  ${\$self->full_url}\@$rev\n",
-                                    "Following parent with no changes\n";
+                                    "Following parent with no changes\n"
+                                    unless $::_q > 1;
                        $self->tmp_index_do(sub {
                            command_noisy('read-tree', $parent);
                        });
                        $self->{last_commit} = $parent;
                } else {
-                       print STDERR "Following parent with do_update\n";
+                       print STDERR "Following parent with do_update\n"
+                                    unless $::_q > 1;
                        $ed = SVN::Git::Fetcher->new($self);
                        $self->ra->gs_do_update($rev, $rev, $self, $ed)
                          or die "SVN connection failed somewhere...\n";
                }
-               print STDERR "Successfully followed parent\n";
+               print STDERR "Successfully followed parent\n" unless $::_q > 1;
                return $self->make_log_entry($rev, [$parent], $ed);
        }
        return undef;
@@ -2542,6 +2798,62 @@ sub do_fetch {
        $self->make_log_entry($rev, \@parents, $ed);
 }
 
+sub mkemptydirs {
+       my ($self, $r) = @_;
+
+       sub scan {
+               my ($r, $empty_dirs, $line) = @_;
+               if (defined $r && $line =~ /^r(\d+)$/) {
+                       return 0 if $1 > $r;
+               } elsif ($line =~ /^  \+empty_dir: (.+)$/) {
+                       $empty_dirs->{$1} = 1;
+               } elsif ($line =~ /^  \-empty_dir: (.+)$/) {
+                       my @d = grep {m[^\Q$1\E(/|$)]} (keys %$empty_dirs);
+                       delete @$empty_dirs{@d};
+               }
+               1; # continue
+       };
+
+       my %empty_dirs = ();
+       my $gz_file = "$self->{dir}/unhandled.log.gz";
+       if (-f $gz_file) {
+               if (!$can_compress) {
+                       warn "Compress::Zlib could not be found; ",
+                            "empty directories in $gz_file will not be read\n";
+               } else {
+                       my $gz = Compress::Zlib::gzopen($gz_file, "rb") or
+                               die "Unable to open $gz_file: $!\n";
+                       my $line;
+                       while ($gz->gzreadline($line) > 0) {
+                               scan($r, \%empty_dirs, $line) or last;
+                       }
+                       $gz->gzclose;
+               }
+       }
+
+       if (open my $fh, '<', "$self->{dir}/unhandled.log") {
+               binmode $fh or croak "binmode: $!";
+               while (<$fh>) {
+                       scan($r, \%empty_dirs, $_) or last;
+               }
+               close $fh;
+       }
+
+       my $strip = qr/\A\Q$self->{path}\E(?:\/|$)/;
+       foreach my $d (sort keys %empty_dirs) {
+               $d = uri_decode($d);
+               $d =~ s/$strip//;
+               next unless length($d);
+               next if -d $d;
+               if (-e $d) {
+                       warn "$d exists but is not a directory\n";
+               } else {
+                       print "creating empty directory: $d\n";
+                       mkpath([$d]);
+               }
+       }
+}
+
 sub get_untracked {
        my ($self, $ed) = @_;
        my @out;
@@ -2642,45 +2954,406 @@ sub parse_svn_date {
 }
 
 sub other_gs {
-       my ($self, $new_url, $url, $repos_root,
+       my ($self, $new_url, $url,
            $branch_from, $r, $old_ref_id) = @_;
-       my $gs = Git::SVN->find_by_url($new_url, $repos_root, $branch_from);
+       my $gs = Git::SVN->find_by_url($new_url, $url, $branch_from);
        unless ($gs) {
                my $ref_id = $old_ref_id;
-               $ref_id =~ s/\@\d+$//;
+               $ref_id =~ s/\@\d+-*$//;
                $ref_id .= "\@$r";
                # just grow a tail if we're not unique enough :x
                $ref_id .= '-' while find_ref($ref_id);
-               print STDERR "Initializing parent: $ref_id\n";
                my ($u, $p, $repo_id) = ($new_url, '', $ref_id);
                if ($u =~ s#^\Q$url\E(/|$)##) {
                        $p = $u;
                        $u = $url;
                        $repo_id = $self->{repo_id};
                }
-               $gs = Git::SVN->init($u, $p, $repo_id, $ref_id, 1);
+               while (1) {
+                       # It is possible to tag two different subdirectories at
+                       # the same revision.  If the url for an existing ref
+                       # does not match, we must either find a ref with a
+                       # matching url or create a new ref by growing a tail.
+                       $gs = Git::SVN->init($u, $p, $repo_id, $ref_id, 1);
+                       my (undef, $max_commit) = $gs->rev_map_max(1);
+                       last if (!$max_commit);
+                       my ($url) = ::cmt_metadata($max_commit);
+                       last if ($url eq $gs->full_url);
+                       $ref_id .= '-';
+               }
+               print STDERR "Initializing parent: $ref_id\n" unless $::_q > 1;
        }
        $gs
 }
 
+sub call_authors_prog {
+       my ($orig_author) = @_;
+       $orig_author = command_oneline('rev-parse', '--sq-quote', $orig_author);
+       my $author = `$::_authors_prog $orig_author`;
+       if ($? != 0) {
+               die "$::_authors_prog failed with exit code $?\n"
+       }
+       if ($author =~ /^\s*(.+?)\s*<(.*)>\s*$/) {
+               my ($name, $email) = ($1, $2);
+               $email = undef if length $2 == 0;
+               return [$name, $email];
+       } else {
+               die "Author: $orig_author: $::_authors_prog returned "
+                       . "invalid author format: $author\n";
+       }
+}
+
 sub check_author {
        my ($author) = @_;
        if (!defined $author || length $author == 0) {
                $author = '(no author)';
-       } elsif (defined $::_authors && ! defined $::users{$author}) {
-               die "Author: $author not defined in $::_authors file\n";
+       }
+       if (!defined $::users{$author}) {
+               if (defined $::_authors_prog) {
+                       $::users{$author} = call_authors_prog($author);
+               } elsif (defined $::_authors) {
+                       die "Author: $author not defined in $::_authors file\n";
+               }
        }
        $author;
 }
 
+sub find_extra_svk_parents {
+       my ($self, $ed, $tickets, $parents) = @_;
+       # aha!  svk:merge property changed...
+       my @tickets = split "\n", $tickets;
+       my @known_parents;
+       for my $ticket ( @tickets ) {
+               my ($uuid, $path, $rev) = split /:/, $ticket;
+               if ( $uuid eq $self->ra_uuid ) {
+                       my $url = $self->{url};
+                       my $repos_root = $url;
+                       my $branch_from = $path;
+                       $branch_from =~ s{^/}{};
+                       my $gs = $self->other_gs($repos_root."/".$branch_from,
+                                                $url,
+                                                $branch_from,
+                                                $rev,
+                                                $self->{ref_id});
+                       if ( my $commit = $gs->rev_map_get($rev, $uuid) ) {
+                               # wahey!  we found it, but it might be
+                               # an old one (!)
+                               push @known_parents, [ $rev, $commit ];
+                       }
+               }
+       }
+       # Ordering matters; highest-numbered commit merge tickets
+       # first, as they may account for later merge ticket additions
+       # or changes.
+       @known_parents = map {$_->[1]} sort {$b->[0] <=> $a->[0]} @known_parents;
+       for my $parent ( @known_parents ) {
+               my @cmd = ('rev-list', $parent, map { "^$_" } @$parents );
+               my ($msg_fh, $ctx) = command_output_pipe(@cmd);
+               my $new;
+               while ( <$msg_fh> ) {
+                       $new=1;last;
+               }
+               command_close_pipe($msg_fh, $ctx);
+               if ( $new ) {
+                       print STDERR
+                           "Found merge parent (svk:merge ticket): $parent\n";
+                       push @$parents, $parent;
+               }
+       }
+}
+
+sub lookup_svn_merge {
+       my $uuid = shift;
+       my $url = shift;
+       my $merge = shift;
+
+       my ($source, $revs) = split ":", $merge;
+       my $path = $source;
+       $path =~ s{^/}{};
+       my $gs = Git::SVN->find_by_url($url.$source, $url, $path);
+       if ( !$gs ) {
+               warn "Couldn't find revmap for $url$source\n";
+               return;
+       }
+       my @ranges = split ",", $revs;
+       my ($tip, $tip_commit);
+       my @merged_commit_ranges;
+       # find the tip
+       for my $range ( @ranges ) {
+               my ($bottom, $top) = split "-", $range;
+               $top ||= $bottom;
+               my $bottom_commit = $gs->find_rev_after( $bottom, 1, $top );
+               my $top_commit = $gs->find_rev_before( $top, 1, $bottom );
+
+               unless ($top_commit and $bottom_commit) {
+                       warn "W:unknown path/rev in svn:mergeinfo "
+                               ."dirprop: $source:$range\n";
+                       next;
+               }
+
+               push @merged_commit_ranges,
+                       "$bottom_commit^..$top_commit";
+
+               if ( !defined $tip or $top > $tip ) {
+                       $tip = $top;
+                       $tip_commit = $top_commit;
+               }
+       }
+       return ($tip_commit, @merged_commit_ranges);
+}
+
+sub _rev_list {
+       my ($msg_fh, $ctx) = command_output_pipe(
+               "rev-list", @_,
+              );
+       my @rv;
+       while ( <$msg_fh> ) {
+               chomp;
+               push @rv, $_;
+       }
+       command_close_pipe($msg_fh, $ctx);
+       @rv;
+}
+
+sub check_cherry_pick {
+       my $base = shift;
+       my $tip = shift;
+       my @ranges = @_;
+       my %commits = map { $_ => 1 }
+               _rev_list("--no-merges", $tip, "--not", $base);
+       for my $range ( @ranges ) {
+               delete @commits{_rev_list($range)};
+       }
+       for my $commit (keys %commits) {
+               if (has_no_changes($commit)) {
+                       delete $commits{$commit};
+               }
+       }
+       return (keys %commits);
+}
+
+sub has_no_changes {
+       my $commit = shift;
+
+       my @revs = split / /, command_oneline(
+               qw(rev-list --parents -1 -m), $commit);
+
+       # Commits with no parents, e.g. the start of a partial branch,
+       # have changes by definition.
+       return 1 if (@revs < 2);
+
+       # Commits with multiple parents, e.g a merge, have no changes
+       # by definition.
+       return 0 if (@revs > 2);
+
+       return (command_oneline("rev-parse", "$commit^{tree}") eq
+               command_oneline("rev-parse", "$commit~1^{tree}"));
+}
+
+# The GIT_DIR environment variable is not always set until after the command
+# line arguments are processed, so we can't memoize in a BEGIN block.
+{
+       my $memoized = 0;
+
+       sub memoize_svn_mergeinfo_functions {
+               return if $memoized;
+               $memoized = 1;
+
+               my $cache_path = "$ENV{GIT_DIR}/svn/.caches/";
+               mkpath([$cache_path]) unless -d $cache_path;
+
+               tie my %lookup_svn_merge_cache => 'Memoize::Storable',
+                   "$cache_path/lookup_svn_merge.db", 'nstore';
+               memoize 'lookup_svn_merge',
+                       SCALAR_CACHE => 'FAULT',
+                       LIST_CACHE => ['HASH' => \%lookup_svn_merge_cache],
+               ;
+
+               tie my %check_cherry_pick_cache => 'Memoize::Storable',
+                   "$cache_path/check_cherry_pick.db", 'nstore';
+               memoize 'check_cherry_pick',
+                       SCALAR_CACHE => 'FAULT',
+                       LIST_CACHE => ['HASH' => \%check_cherry_pick_cache],
+               ;
+
+               tie my %has_no_changes_cache => 'Memoize::Storable',
+                   "$cache_path/has_no_changes.db", 'nstore';
+               memoize 'has_no_changes',
+                       SCALAR_CACHE => ['HASH' => \%has_no_changes_cache],
+                       LIST_CACHE => 'FAULT',
+               ;
+       }
+
+       sub unmemoize_svn_mergeinfo_functions {
+               return if not $memoized;
+               $memoized = 0;
+
+               Memoize::unmemoize 'lookup_svn_merge';
+               Memoize::unmemoize 'check_cherry_pick';
+               Memoize::unmemoize 'has_no_changes';
+       }
+}
+
+END {
+       # Force cache writeout explicitly instead of waiting for
+       # global destruction to avoid segfault in Storable:
+       # http://rt.cpan.org/Public/Bug/Display.html?id=36087
+       unmemoize_svn_mergeinfo_functions();
+}
+
+sub parents_exclude {
+       my $parents = shift;
+       my @commits = @_;
+       return unless @commits;
+
+       my @excluded;
+       my $excluded;
+       do {
+               my @cmd = ('rev-list', "-1", @commits, "--not", @$parents );
+               $excluded = command_oneline(@cmd);
+               if ( $excluded ) {
+                       my @new;
+                       my $found;
+                       for my $commit ( @commits ) {
+                               if ( $commit eq $excluded ) {
+                                       push @excluded, $commit;
+                                       $found++;
+                                       last;
+                               }
+                               else {
+                                       push @new, $commit;
+                               }
+                       }
+                       die "saw commit '$excluded' in rev-list output, "
+                               ."but we didn't ask for that commit (wanted: @commits --not @$parents)"
+                                       unless $found;
+                       @commits = @new;
+               }
+       }
+               while ($excluded and @commits);
+
+       return @excluded;
+}
+
+
+# note: this function should only be called if the various dirprops
+# have actually changed
+sub find_extra_svn_parents {
+       my ($self, $ed, $mergeinfo, $parents) = @_;
+       # aha!  svk:merge property changed...
+
+       memoize_svn_mergeinfo_functions();
+
+       # We first search for merged tips which are not in our
+       # history.  Then, we figure out which git revisions are in
+       # that tip, but not this revision.  If all of those revisions
+       # are now marked as merge, we can add the tip as a parent.
+       my @merges = split "\n", $mergeinfo;
+       my @merge_tips;
+       my $url = $self->{url};
+       my $uuid = $self->ra_uuid;
+       my %ranges;
+       for my $merge ( @merges ) {
+               my ($tip_commit, @ranges) =
+                       lookup_svn_merge( $uuid, $url, $merge );
+               unless (!$tip_commit or
+                               grep { $_ eq $tip_commit } @$parents ) {
+                       push @merge_tips, $tip_commit;
+                       $ranges{$tip_commit} = \@ranges;
+               } else {
+                       push @merge_tips, undef;
+               }
+       }
+
+       my %excluded = map { $_ => 1 }
+               parents_exclude($parents, grep { defined } @merge_tips);
+
+       # check merge tips for new parents
+       my @new_parents;
+       for my $merge_tip ( @merge_tips ) {
+               my $spec = shift @merges;
+               next unless $merge_tip and $excluded{$merge_tip};
+
+               my $ranges = $ranges{$merge_tip};
+
+               # check out 'new' tips
+               my $merge_base;
+               eval {
+                       $merge_base = command_oneline(
+                               "merge-base",
+                               @$parents, $merge_tip,
+                       );
+               };
+               if ($@) {
+                       die "An error occurred during merge-base"
+                               unless $@->isa("Git::Error::Command");
+
+                       warn "W: Cannot find common ancestor between ".
+                            "@$parents and $merge_tip. Ignoring merge info.\n";
+                       next;
+               }
+
+               # double check that there are no missing non-merge commits
+               my (@incomplete) = check_cherry_pick(
+                       $merge_base, $merge_tip,
+                       @$ranges,
+                      );
+
+               if ( @incomplete ) {
+                       warn "W:svn cherry-pick ignored ($spec) - missing "
+                               .@incomplete." commit(s) (eg $incomplete[0])\n";
+               } else {
+                       warn
+                               "Found merge parent (svn:mergeinfo prop): ",
+                                       $merge_tip, "\n";
+                       push @new_parents, $merge_tip;
+               }
+       }
+
+       # cater for merges which merge commits from multiple branches
+       if ( @new_parents > 1 ) {
+               for ( my $i = 0; $i <= $#new_parents; $i++ ) {
+                       for ( my $j = 0; $j <= $#new_parents; $j++ ) {
+                               next if $i == $j;
+                               next unless $new_parents[$i];
+                               next unless $new_parents[$j];
+                               my $revs = command_oneline(
+                                       "rev-list", "-1",
+                                       "$new_parents[$i]..$new_parents[$j]",
+                                      );
+                               if ( !$revs ) {
+                                       undef($new_parents[$j]);
+                               }
+                       }
+               }
+       }
+       push @$parents, grep { defined } @new_parents;
+}
+
 sub make_log_entry {
        my ($self, $rev, $parents, $ed) = @_;
        my $untracked = $self->get_untracked($ed);
 
+       my @parents = @$parents;
+       my $ps = $ed->{path_strip} || "";
+       for my $path ( grep { m/$ps/ } %{$ed->{dir_prop}} ) {
+               my $props = $ed->{dir_prop}{$path};
+               if ( $props->{"svk:merge"} ) {
+                       $self->find_extra_svk_parents
+                               ($ed, $props->{"svk:merge"}, \@parents);
+               }
+               if ( $props->{"svn:mergeinfo"} ) {
+                       $self->find_extra_svn_parents
+                               ($ed,
+                                $props->{"svn:mergeinfo"},
+                                \@parents);
+               }
+       }
+
        open my $un, '>>', "$self->{dir}/unhandled.log" or croak $!;
        print $un "r$rev\n" or croak $!;
        print $un $_, "\n" foreach @$untracked;
-       my %log_entry = ( parents => $parents || [], revision => $rev,
+       my %log_entry = ( parents => \@parents, revision => $rev,
                          log => '');
 
        my $headrev;
@@ -2734,7 +3407,11 @@ sub make_log_entry {
                        die "Can't have both 'useSvmProps' and 'rewriteRoot' ",
                            "options set!\n";
                }
-               my ($uuid, $r) = $headrev =~ m{^([a-f\d\-]{30,}):(\d+)$};
+               if ($self->rewrite_uuid) {
+                       die "Can't have both 'useSvmProps' and 'rewriteUUID' ",
+                           "options set!\n";
+               }
+               my ($uuid, $r) = $headrev =~ m{^([a-f\d\-]{30,}):(\d+)$}i;
                # we don't want "SVM: initializing mirror for junk" ...
                return undef if $r == 0;
                my $svm = $self->svm;
@@ -2764,10 +3441,10 @@ sub make_log_entry {
        } else {
                my $url = $self->metadata_url;
                remove_username($url);
-               $log_entry{metadata} = "$url\@$rev " .
-                                      $self->ra->get_uuid;
-               $email ||= "$author\@" . $self->ra->get_uuid;
-               $commit_email ||= "$author\@" . $self->ra->get_uuid;
+               my $uuid = $self->rewrite_uuid || $self->ra->get_uuid;
+               $log_entry{metadata} = "$url\@$rev " . $uuid;
+               $email ||= "$author\@" . $uuid;
+               $commit_email ||= "$author\@" . $uuid;
        }
        $log_entry{name} = $name;
        $log_entry{email} = $email;
@@ -2849,7 +3526,7 @@ sub rebuild {
                                '--');
        my $metadata_url = $self->metadata_url;
        remove_username($metadata_url);
-       my $svn_uuid = $self->ra_uuid;
+       my $svn_uuid = $self->rewrite_uuid || $self->ra_uuid;
        my $c;
        while (<$log>) {
                if ( m{^commit ($::sha1)$} ) {
@@ -2952,6 +3629,14 @@ sub _rev_map_set {
          croak "write: $!";
 }
 
+sub _rev_map_reset {
+       my ($fh, $rev, $commit) = @_;
+       my $c = _rev_map_get($fh, $rev);
+       $c eq $commit or die "_rev_map_reset(@_) commit $c does not match!\n";
+       my $offset = sysseek($fh, 0, SEEK_CUR) or croak "seek: $!";
+       truncate $fh, $offset or croak "truncate: $!";
+}
+
 sub mkfile {
        my ($path) = @_;
        unless (-e $path) {
@@ -2964,10 +3649,12 @@ sub mkfile {
 
 sub rev_map_set {
        my ($self, $rev, $commit, $update_ref, $uuid) = @_;
+       defined $commit or die "missing arg3\n";
        length $commit == 40 or die "arg3 must be a full SHA1 hexsum\n";
        my $db = $self->map_path($uuid);
        my $db_lock = "$db.lock";
        my $sig;
+       $update_ref ||= 0;
        if ($update_ref) {
                $SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} =
                            $SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] };
@@ -2991,7 +3678,8 @@ sub rev_map_set {
 
        sysopen(my $fh, $db_lock, O_RDWR | O_CREAT)
             or croak "Couldn't open $db_lock: $!\n";
-       _rev_map_set($fh, $rev, $commit);
+       $update_ref eq 'reset' ? _rev_map_reset($fh, $rev, $commit) :
+                                _rev_map_set($fh, $rev, $commit);
        if ($sync) {
                $fh->flush or die "Couldn't flush $db_lock: $!\n";
                $fh->sync or die "Couldn't sync $db_lock: $!\n";
@@ -2999,7 +3687,9 @@ sub rev_map_set {
        close $fh or croak $!;
        if ($update_ref) {
                $_head = $self;
-               command_noisy('update-ref', '-m', "r$rev",
+               my $note = "";
+               $note = " ($update_ref)" if ($update_ref !~ /^\d*$/);
+               command_noisy('update-ref', '-m', "r$rev$note",
                              $self->refname, $commit);
        }
        rename $db_lock, $db or die "rev_map_set(@_): ", "Failed to rename: ",
@@ -3061,12 +3751,19 @@ sub rev_map_get {
        return undef unless -e $map_path;
 
        sysopen(my $fh, $map_path, O_RDONLY) or croak "open: $!";
+       my $c = _rev_map_get($fh, $rev);
+       close($fh) or croak "close: $!";
+       $c
+}
+
+sub _rev_map_get {
+       my ($fh, $rev) = @_;
+
        binmode $fh or croak "binmode: $!";
        my $size = (stat($fh))[7];
        ($size % 24) == 0 or croak "inconsistent size: $size";
 
        if ($size == 0) {
-               close $fh or croak "close: $fh";
                return undef;
        }
 
@@ -3077,18 +3774,16 @@ sub rev_map_get {
                my $i = int(($l/24 + $u/24) / 2) * 24;
                sysseek($fh, $i, SEEK_SET) or croak "seek: $!";
                sysread($fh, my $buf, 24) == 24 or croak "read: $!";
-               my ($r, $c) = unpack('NH40', $buf);
+               my ($r, $c) = unpack(rev_map_fmt, $buf);
 
                if ($r < $rev) {
                        $l = $i + 24;
                } elsif ($r > $rev) {
                        $u = $i - 24;
                } else { # $r == $rev
-                       close($fh) or croak "close: $!";
                        return $c eq ('0' x 40) ? undef : $c;
                }
        }
-       close($fh) or croak "close: $!";
        undef;
 }
 
@@ -3100,6 +3795,8 @@ sub find_rev_before {
        my ($self, $rev, $eq_ok, $min_rev) = @_;
        --$rev unless $eq_ok;
        $min_rev ||= 1;
+       my $max_rev = $self->rev_map_max;
+       $rev = $max_rev if ($rev > $max_rev);
        while ($rev >= $min_rev) {
                if (my $c = $self->rev_map_get($rev)) {
                        return ($rev, $c);
@@ -3132,12 +3829,24 @@ sub _new {
                $repo_id = $Git::SVN::default_repo_id;
        }
        unless (defined $ref_id && length $ref_id) {
-               $_[2] = $ref_id = $Git::SVN::default_ref_id;
+               $_prefix = '' unless defined($_prefix);
+               $_[2] = $ref_id =
+                            "refs/remotes/$_prefix$Git::SVN::default_ref_id";
        }
        $_[1] = $repo_id;
        my $dir = "$ENV{GIT_DIR}/svn/$ref_id";
+
+       # Older repos imported by us used $GIT_DIR/svn/foo instead of
+       # $GIT_DIR/svn/refs/remotes/foo when tracking refs/remotes/foo
+       if ($ref_id =~ m{^refs/remotes/(.*)}) {
+               my $old_dir = "$ENV{GIT_DIR}/svn/$1";
+               if (-d $old_dir && ! -d $dir) {
+                       $dir = $old_dir;
+               }
+       }
+
        $_[3] = $path = '' unless (defined $path);
-       mkpath(["$ENV{GIT_DIR}/svn"]);
+       mkpath([$dir]);
        bless {
                ref_id => $ref_id, dir => $dir, index => "$dir/index",
                path => $path, config => "$ENV{GIT_DIR}/svn/config",
@@ -3175,6 +3884,12 @@ sub uri_encode {
        $f
 }
 
+sub uri_decode {
+       my ($f) = @_;
+       $f =~ s#%([0-9a-fA-F]{2})#chr(hex($1))#eg;
+       $f
+}
+
 sub remove_username {
        $_[0] =~ s{^([^:]*://)[^@]+@}{$1};
 }
@@ -3301,18 +4016,25 @@ sub username {
 
 sub _read_password {
        my ($prompt, $realm) = @_;
-       print STDERR $prompt;
-       STDERR->flush;
-       require Term::ReadKey;
-       Term::ReadKey::ReadMode('noecho');
        my $password = '';
-       while (defined(my $key = Term::ReadKey::ReadKey(0))) {
-               last if $key =~ /[\012\015]/; # \n\r
-               $password .= $key;
+       if (exists $ENV{GIT_ASKPASS}) {
+               open(PH, "-|", $ENV{GIT_ASKPASS}, $prompt);
+               $password = <PH>;
+               $password =~ s/[\012\015]//; # \n\r
+               close(PH);
+       } else {
+               print STDERR $prompt;
+               STDERR->flush;
+               require Term::ReadKey;
+               Term::ReadKey::ReadMode('noecho');
+               while (defined(my $key = Term::ReadKey::ReadKey(0))) {
+                       last if $key =~ /[\012\015]/; # \n\r
+                       $password .= $key;
+               }
+               Term::ReadKey::ReadMode('restore');
+               print STDERR "\n";
+               STDERR->flush;
        }
-       Term::ReadKey::ReadMode('restore');
-       print STDERR "\n";
-       STDERR->flush;
        $password;
 }
 
@@ -3321,7 +4043,6 @@ use vars qw/@ISA/;
 use strict;
 use warnings;
 use Carp qw/croak/;
-use File::Temp qw/tempfile/;
 use IO::File qw//;
 use vars qw/$_ignore_regex/;
 
@@ -3343,6 +4064,7 @@ sub new {
        $self->{absent_dir} = {};
        $self->{absent_file} = {};
        $self->{gii} = $git_svn->tmp_index_do(sub { Git::IndexInfo->new });
+       $self->{pathnameencoding} = Git::config('svn.pathnameencoding');
        $self;
 }
 
@@ -3426,6 +4148,10 @@ sub open_directory {
 
 sub git_path {
        my ($self, $path) = @_;
+       if (my $enc = $self->{pathnameencoding}) {
+               require Encode;
+               Encode::from_to($path, 'UTF-8', $enc);
+       }
        if ($self->{path_strip}) {
                $path =~ s!$self->{path_strip}!! or
                  die "Failed to strip path '$path' ($self->{path_strip})\n";
@@ -3456,11 +4182,11 @@ sub delete_entry {
                }
                print "\tD\t$gpath/\n" unless $::_q;
                command_close_pipe($ls, $ctx);
-               $self->{empty}->{$path} = 0
        } else {
                $self->{gii}->remove($gpath);
                print "\tD\t$gpath\n" unless $::_q;
        }
+       $self->{empty}->{$path} = 0;
        undef;
 }
 
@@ -3814,13 +4540,17 @@ sub split_path {
 
 sub repo_path {
        my ($self, $path) = @_;
+       if (my $enc = $self->{pathnameencoding}) {
+               require Encode;
+               Encode::from_to($path, $enc, 'UTF-8');
+       }
        $self->{path_prefix}.(defined $path ? $path : '');
 }
 
 sub url_path {
        my ($self, $path) = @_;
        if ($self->{url} =~ m#^https?://#) {
-               $path =~ s/([^~a-zA-Z0-9_.-])/uc sprintf("%%%02x",ord($1))/eg;
+               $path =~ s!([^~a-zA-Z0-9_./-])!uc sprintf("%%%02x",ord($1))!eg;
        }
        $self->{url} . '/' . $self->repo_path($path);
 }
@@ -4194,6 +4924,8 @@ sub new {
        $url =~ s!/+$!!;
        return $RA if ($RA && $RA->{url} eq $url);
 
+       ::_req_svn();
+
        SVN::_Core::svn_config_ensure($config_dir, undef);
        my ($baton, $callbacks) = SVN::Core::auth_open_helper(_auth_providers);
        my $config = SVN::Core::config_get_config($config_dir);
@@ -4280,6 +5012,34 @@ sub get_log {
        my ($self, @args) = @_;
        my $pool = SVN::Pool->new;
 
+       # svn_log_changed_path_t objects passed to get_log are likely to be
+       # overwritten even if only the refs are copied to an external variable,
+       # so we should dup the structures in their entirety.  Using an
+       # externally passed pool (instead of our temporary and quickly cleared
+       # pool in Git::SVN::Ra) does not help matters at all...
+       my $receiver = pop @args;
+       my $prefix = "/".$self->{svn_path};
+       $prefix =~ s#/+($)##;
+       my $prefix_regex = qr#^\Q$prefix\E#;
+       push(@args, sub {
+               my ($paths) = $_[0];
+               return &$receiver(@_) unless $paths;
+               $_[0] = ();
+               foreach my $p (keys %$paths) {
+                       my $i = $paths->{$p};
+                       # Make path relative to our url, not repos_root
+                       $p =~ s/$prefix_regex//;
+                       my %s = map { $_ => $i->$_; }
+                               qw/copyfrom_path copyfrom_rev action/;
+                       if ($s{'copyfrom_path'}) {
+                               $s{'copyfrom_path'} =~ s/$prefix_regex//;
+                       }
+                       $_[0]{$p} = \%s;
+               }
+               &$receiver(@_);
+       });
+
+
        # the limit parameter was not supported in SVN 1.1.x, so we
        # drop it.  Therefore, the receiver callback passed to it
        # is made aware of this limitation by being wrapped if
@@ -4364,10 +5124,12 @@ sub gs_do_switch {
 
        my $full_url = $self->{url};
        my $old_url = $full_url;
-       $full_url .= '/' . escape_uri_only($path) if length $path;
+       $full_url .= '/' . $path if length $path;
        my ($ra, $reparented);
 
-       if ($old_url =~ m#^svn(\+ssh)?://#) {
+       if ($old_url =~ m#^svn(\+ssh)?://# ||
+           ($full_url =~ m#^https?://# &&
+            escape_url($full_url) ne $full_url)) {
                $_[0] = undef;
                $self = undef;
                $RA = undef;
@@ -4438,6 +5200,7 @@ sub gs_fetch_loop_common {
        my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc);
        my $longest_path = longest_common_path($gsv, $globs);
        my $ra_url = $self->{url};
+       my $find_trailing_edge;
        while (1) {
                my %revs;
                my $err;
@@ -4448,15 +5211,17 @@ sub gs_fetch_loop_common {
                };
                sub _cb {
                        my ($paths, $r, $author, $date, $log) = @_;
-                       [ dup_changed_paths($paths),
+                       [ $paths,
                          { author => $author, date => $date, log => $log } ];
                }
                $self->get_log([$longest_path], $min, $max, 0, 1, 1,
                               sub { $revs{$_[1]} = _cb(@_) });
                if ($err) {
                        print "Checked through r$max\r";
+               } else {
+                       $find_trailing_edge = 1;
                }
-               if ($err && $max >= $head) {
+               if ($err and $find_trailing_edge) {
                        print STDERR "Path '$longest_path' ",
                                     "was probably deleted:\n",
                                     $err->expanded_message,
@@ -4468,13 +5233,14 @@ sub gs_fetch_loop_common {
                                my $ok;
                                $self->get_log([$longest_path], $min, $hi,
                                               0, 1, 1, sub {
-                                              $ok ||= $_[1];
+                                              $ok = $_[1];
                                               $revs{$_[1]} = _cb(@_) });
                                if ($ok) {
                                        print STDERR "r$min .. r$ok OK\n";
                                        last;
                                }
                        }
+                       $find_trailing_edge = 0;
                }
                $SVN::Error::handler = $err_handler;
 
@@ -4567,6 +5333,7 @@ sub match_globs {
                        next if (length $g->{path}->{right} &&
                                 ($self->check_path($p, $r) !=
                                  $SVN::Node::dir));
+                       next unless $p =~ /$g->{path}->{regex}/;
                        $exists->{$p} = Git::SVN->init($self->{url}, $p, undef,
                                         $g->{ref}->full_path($de), 1);
                }
@@ -4612,7 +5379,11 @@ sub minimize_url {
        my $c = '';
        do {
                $url .= "/$c" if length $c;
-               eval { (ref $self)->new($url)->get_latest_revnum };
+               eval {
+                       my $ra = (ref $self)->new($url);
+                       my $latest = $ra->get_latest_revnum;
+                       $ra->get_log("", $latest, 0, 1, 0, 1, sub {});
+               };
        } while ($@ && ($c = shift @components));
        $url;
 }
@@ -4668,24 +5439,6 @@ sub skip_unknown_revs {
        die "Error from SVN, ($errno): ", $err->expanded_message,"\n";
 }
 
-# svn_log_changed_path_t objects passed to get_log are likely to be
-# overwritten even if only the refs are copied to an external variable,
-# so we should dup the structures in their entirety.  Using an externally
-# passed pool (instead of our temporary and quickly cleared pool in
-# Git::SVN::Ra) does not help matters at all...
-sub dup_changed_paths {
-       my ($paths) = @_;
-       return undef unless $paths;
-       my %ret;
-       foreach my $p (keys %$paths) {
-               my $i = $paths->{$p};
-               my %s = map { $_ => $i->$_ }
-                             qw/copyfrom_path copyfrom_rev action/;
-               $ret{$p} = \%s;
-       }
-       \%ret;
-}
-
 package Git::SVN::Log;
 use strict;
 use warnings;
@@ -4773,17 +5526,20 @@ sub git_svn_log_cmd {
 
 # adapted from pager.c
 sub config_pager {
-       $pager ||= $ENV{GIT_PAGER} || $ENV{PAGER};
-       if (!defined $pager) {
-               $pager = 'less';
-       } elsif (length $pager == 0 || $pager eq 'cat') {
+       if (! -t *STDOUT) {
+               $ENV{GIT_PAGER_IN_USE} = 'false';
+               $pager = undef;
+               return;
+       }
+       chomp($pager = command_oneline(qw(var GIT_PAGER)));
+       if ($pager eq 'cat') {
                $pager = undef;
        }
        $ENV{GIT_PAGER_IN_USE} = defined($pager);
 }
 
 sub run_pager {
-       return unless -t *STDOUT && defined $pager;
+       return unless defined $pager;
        pipe my ($rfd, $wfd) or return;
        defined(my $pid = fork) or ::fatal "Can't fork: $!";
        if (!$pid) {
@@ -5290,7 +6046,7 @@ sub minimize_connections {
                        my $pfx = "svn-remote.$x->{old_repo_id}";
 
                        my $old_fetch = quotemeta("$x->{old_path}:".
-                                                 "refs/remotes/$x->{ref_id}");
+                                                 "$x->{ref_id}");
                        command_noisy(qw/config --unset/,
                                      "$pfx.fetch", '^'. $old_fetch . '$');
                        delete $r->{$x->{old_repo_id}}->
@@ -5356,29 +6112,48 @@ use strict;
 use warnings;
 
 sub new {
-       my ($class, $glob) = @_;
+       my ($class, $glob, $pattern_ok) = @_;
        my $re = $glob;
        $re =~ s!/+$!!g; # no need for trailing slashes
-       $re =~ m!^([^*]*)(\*(?:/\*)*)([^*]*)$!;
-       my $temp = $re;
-       my ($left, $right) = ($1, $3);
-       $re = $2;
-       my $depth = $re =~ tr/*/*/;
-       if ($depth != $temp =~ tr/*/*/) {
-               die "Only one set of wildcard directories " .
-                       "(e.g. '*' or '*/*/*') is supported: '$glob'\n";
+       my (@left, @right, @patterns);
+       my $state = "left";
+       my $die_msg = "Only one set of wildcard directories " .
+                               "(e.g. '*' or '*/*/*') is supported: '$glob'\n";
+       for my $part (split(m|/|, $glob)) {
+               if ($part =~ /\*/ && $part ne "*") {
+                       die "Invalid pattern in '$glob': $part\n";
+               } elsif ($pattern_ok && $part =~ /[{}]/ &&
+                        $part !~ /^\{[^{}]+\}/) {
+                       die "Invalid pattern in '$glob': $part\n";
+               }
+               if ($part eq "*") {
+                       die $die_msg if $state eq "right";
+                       $state = "pattern";
+                       push(@patterns, "[^/]*");
+               } elsif ($pattern_ok && $part =~ /^\{(.*)\}$/) {
+                       die $die_msg if $state eq "right";
+                       $state = "pattern";
+                       my $p = quotemeta($1);
+                       $p =~ s/\\,/|/g;
+                       push(@patterns, "(?:$p)");
+               } else {
+                       if ($state eq "left") {
+                               push(@left, $part);
+                       } else {
+                               push(@right, $part);
+                               $state = "right";
+                       }
+               }
        }
+       my $depth = @patterns;
        if ($depth == 0) {
-               die "One '*' is needed for glob: '$glob'\n";
-       }
-       $re =~ s!\*!\[^/\]*!g;
-       $re = quotemeta($left) . "($re)" . quotemeta($right);
-       if (length $left && !($left =~ s!/+$!!g)) {
-               die "Missing trailing '/' on left side of: '$glob' ($left)\n";
-       }
-       if (length $right && !($right =~ s!^/+!!g)) {
-               die "Missing leading '/' on right side of: '$glob' ($right)\n";
+               die "One '*' is needed in glob: '$glob'\n";
        }
+       my $left = join('/', @left);
+       my $right = join('/', @right);
+       $re = join('/', @patterns);
+       $re = join('\/',
+                  grep(length, quotemeta($left), "($re)", quotemeta($right)));
        my $left_re = qr/^\/\Q$left\E(\/|$)/;
        bless { left => $left, right => $right, left_regex => $left_re,
                regex => qr/$re/, glob => $glob, depth => $depth }, $class;
index 7ed0faddcd75e024ce18d8b80994d41a9207a7ca..3fc4166b25714911b6b1294c7439da1e237e4918 100755 (executable)
@@ -31,7 +31,7 @@ valid_custom_tool()
 
 valid_tool() {
        case "$1" in
-               firefox | iceweasel | konqueror | w3m | links | lynx | dillo | open | start)
+               firefox | iceweasel | chrome | google-chrome | chromium | konqueror | w3m | links | lynx | dillo | open | start)
                        ;; # happy
                *)
                        valid_custom_tool "$1" || return 1
@@ -103,7 +103,7 @@ fi
 
 if test -z "$browser" ; then
     if test -n "$DISPLAY"; then
-       browser_candidates="firefox iceweasel konqueror w3m links lynx dillo"
+       browser_candidates="firefox iceweasel google-chrome chrome chromium konqueror w3m links lynx dillo"
        if test "$KDE_FULL_SESSION" = "true"; then
            browser_candidates="konqueror $browser_candidates"
        fi
@@ -111,7 +111,8 @@ if test -z "$browser" ; then
        browser_candidates="w3m links lynx"
     fi
     # SECURITYSESSIONID indicates an OS X GUI login session
-    if test -n "$SECURITYSESSIONID"; then
+    if test -n "$SECURITYSESSIONID" \
+           -o "$TERM_PROGRAM" = "Apple_Terminal" ; then
        browser_candidates="open $browser_candidates"
     fi
     # /bin/start indicates MinGW
@@ -145,6 +146,11 @@ case "$browser" in
        test "$vers" -lt 2 && NEWTAB=''
        "$browser_path" $NEWTAB "$@" &
        ;;
+    google-chrome|chrome|chromium)
+       # Actual command for chromium is chromium-browser.
+       # No need to specify newTab. It's default in chromium
+       eval "$browser_path" "$@" &
+       ;;
     konqueror)
        case "$(basename "$browser_path")" in
            konqueror)
@@ -161,9 +167,12 @@ case "$browser" in
                ;;
        esac
        ;;
-    w3m|links|lynx|open|start)
+    w3m|links|lynx|open)
        eval "$browser_path" "$@"
        ;;
+    start)
+        exec "$browser_path" '"web-browse"' "$@"
+        ;;
     dillo)
        "$browser_path" "$@" &
        ;;
diff --git a/git.c b/git.c
index 5a00726d09a13d5f70ab0378e02bbbd299124d5c..50a14013c5a87fed200df0a5a4ec6db0f1e2c223 100644 (file)
--- a/git.c
+++ b/git.c
@@ -1,15 +1,21 @@
 #include "builtin.h"
-#include "exec_cmd.h"
 #include "cache.h"
+#include "exec_cmd.h"
+#include "help.h"
 #include "quote.h"
 #include "run-command.h"
 
 const char git_usage_string[] =
-       "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path] [-p|--paginate|--no-pager] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]";
+       "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]\n"
+       "           [-p|--paginate|--no-pager] [--no-replace-objects]\n"
+       "           [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]\n"
+       "           [-c name=value] [--help]\n"
+       "           COMMAND [ARGS]";
 
 const char git_more_info_string[] =
        "See 'git help COMMAND' for more information on a specific command.";
 
+static struct startup_info git_startup_info;
 static int use_pager = -1;
 struct pager_config {
        const char *cmd;
@@ -84,6 +90,11 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
                        use_pager = 0;
                        if (envchanged)
                                *envchanged = 1;
+               } else if (!strcmp(cmd, "--no-replace-objects")) {
+                       read_replace_refs = 0;
+                       setenv(NO_REPLACE_OBJECTS_ENVIRONMENT, "1", 1);
+                       if (envchanged)
+                               *envchanged = 1;
                } else if (!strcmp(cmd, "--git-dir")) {
                        if (*argc < 2) {
                                fprintf(stderr, "No directory given for --git-dir.\n" );
@@ -119,6 +130,14 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
                        setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 0);
                        if (envchanged)
                                *envchanged = 1;
+               } else if (!strcmp(cmd, "-c")) {
+                       if (*argc < 2) {
+                               fprintf(stderr, "-c expects a configuration string\n" );
+                               usage(git_usage_string);
+                       }
+                       git_config_push_parameter((*argv)[1]);
+                       (*argv)++;
+                       (*argc)--;
                } else {
                        fprintf(stderr, "Unknown option: %s\n", cmd);
                        usage(git_usage_string);
@@ -147,6 +166,7 @@ static int handle_alias(int *argcp, const char ***argv)
        alias_string = alias_lookup(alias_command);
        if (alias_string) {
                if (alias_string[0] == '!') {
+                       commit_pager_choice();
                        if (*argcp > 1) {
                                struct strbuf buf;
 
@@ -167,7 +187,8 @@ static int handle_alias(int *argcp, const char ***argv)
                }
                count = split_cmdline(alias_string, &new_argv);
                if (count < 0)
-                       die("Bad alias.%s string", alias_command);
+                       die("Bad alias.%s string: %s", alias_command,
+                           split_cmdline_strerror(count));
                option_count = handle_options(&new_argv, &count, &envchanged);
                if (envchanged)
                        die("alias '%s' changes environment variables\n"
@@ -188,10 +209,9 @@ static int handle_alias(int *argcp, const char ***argv)
                                  alias_command);
 
                new_argv = xrealloc(new_argv, sizeof(char *) *
-                                   (count + *argcp + 1));
+                                   (count + *argcp));
                /* insert after command name */
                memcpy(new_argv + count, *argv + 1, sizeof(char *) * *argcp);
-               new_argv[count+*argcp] = NULL;
 
                *argv = new_argv;
                *argcp += count - 1;
@@ -200,7 +220,7 @@ static int handle_alias(int *argcp, const char ***argv)
        }
 
        if (subdir && chdir(subdir))
-               die("Cannot change to %s: %s", subdir, strerror(errno));
+               die_errno("Cannot change to '%s'", subdir);
 
        errno = saved_errno;
 
@@ -209,13 +229,14 @@ static int handle_alias(int *argcp, const char ***argv)
 
 const char git_version_string[] = GIT_VERSION;
 
-#define RUN_SETUP      (1<<0)
-#define USE_PAGER      (1<<1)
+#define RUN_SETUP              (1<<0)
+#define RUN_SETUP_GENTLY       (1<<1)
+#define USE_PAGER              (1<<2)
 /*
  * require working tree to be present -- anything uses this needs
  * RUN_SETUP for reading from the configuration file.
  */
-#define NEED_WORK_TREE (1<<2)
+#define NEED_WORK_TREE         (1<<3)
 
 struct cmd_struct {
        const char *cmd;
@@ -225,28 +246,35 @@ struct cmd_struct {
 
 static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
 {
-       int status;
+       int status, help;
        struct stat st;
        const char *prefix;
 
        prefix = NULL;
-       if (p->option & RUN_SETUP)
-               prefix = setup_git_directory();
+       help = argc == 2 && !strcmp(argv[1], "-h");
+       if (!help) {
+               if (p->option & RUN_SETUP)
+                       prefix = setup_git_directory();
+               if (p->option & RUN_SETUP_GENTLY) {
+                       int nongit_ok;
+                       prefix = setup_git_directory_gently(&nongit_ok);
+               }
 
-       if (use_pager == -1 && p->option & RUN_SETUP)
-               use_pager = check_pager_config(p->cmd);
-       if (use_pager == -1 && p->option & USE_PAGER)
-               use_pager = 1;
+               if (use_pager == -1 && p->option & (RUN_SETUP | RUN_SETUP_GENTLY))
+                       use_pager = check_pager_config(p->cmd);
+               if (use_pager == -1 && p->option & USE_PAGER)
+                       use_pager = 1;
+       }
        commit_pager_choice();
 
-       if (p->option & NEED_WORK_TREE)
+       if (!help && p->option & NEED_WORK_TREE)
                setup_work_tree();
 
        trace_argv_printf(argv, "trace: built-in: git");
 
        status = p->fn(argc, argv, prefix);
        if (status)
-               return status & 0xff;
+               return status;
 
        /* Somebody closed stdout? */
        if (fstat(fileno(stdout), &st))
@@ -257,11 +285,11 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
 
        /* Check for ENOSPC and EIO errors.. */
        if (fflush(stdout))
-               die("write failure on standard output: %s", strerror(errno));
+               die_errno("write failure on standard output");
        if (ferror(stdout))
                die("unknown write failure on standard output");
        if (fclose(stdout))
-               die("close failed on standard output: %s", strerror(errno));
+               die_errno("close failed on standard output");
        return 0;
 }
 
@@ -272,12 +300,12 @@ static void handle_internal_command(int argc, const char **argv)
                { "add", cmd_add, RUN_SETUP | NEED_WORK_TREE },
                { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
                { "annotate", cmd_annotate, RUN_SETUP },
-               { "apply", cmd_apply },
+               { "apply", cmd_apply, RUN_SETUP_GENTLY },
                { "archive", cmd_archive },
                { "bisect--helper", cmd_bisect__helper, RUN_SETUP | NEED_WORK_TREE },
                { "blame", cmd_blame, RUN_SETUP },
                { "branch", cmd_branch, RUN_SETUP },
-               { "bundle", cmd_bundle },
+               { "bundle", cmd_bundle, RUN_SETUP_GENTLY },
                { "cat-file", cmd_cat_file, RUN_SETUP },
                { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
                { "checkout-index", cmd_checkout_index,
@@ -290,7 +318,7 @@ static void handle_internal_command(int argc, const char **argv)
                { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
                { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
                { "commit-tree", cmd_commit_tree, RUN_SETUP },
-               { "config", cmd_config },
+               { "config", cmd_config, RUN_SETUP_GENTLY },
                { "count-objects", cmd_count_objects, RUN_SETUP },
                { "describe", cmd_describe, RUN_SETUP },
                { "diff", cmd_diff },
@@ -300,7 +328,6 @@ static void handle_internal_command(int argc, const char **argv)
                { "fast-export", cmd_fast_export, RUN_SETUP },
                { "fetch", cmd_fetch, RUN_SETUP },
                { "fetch-pack", cmd_fetch_pack, RUN_SETUP },
-               { "fetch--tool", cmd_fetch__tool, RUN_SETUP },
                { "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP },
                { "for-each-ref", cmd_for_each_ref, RUN_SETUP },
                { "format-patch", cmd_format_patch, RUN_SETUP },
@@ -308,29 +335,37 @@ static void handle_internal_command(int argc, const char **argv)
                { "fsck-objects", cmd_fsck, RUN_SETUP },
                { "gc", cmd_gc, RUN_SETUP },
                { "get-tar-commit-id", cmd_get_tar_commit_id },
-               { "grep", cmd_grep, RUN_SETUP | USE_PAGER },
+               { "grep", cmd_grep, RUN_SETUP_GENTLY },
+               { "hash-object", cmd_hash_object },
                { "help", cmd_help },
-#ifndef NO_CURL
-               { "http-fetch", cmd_http_fetch, RUN_SETUP },
-#endif
+               { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY },
                { "init", cmd_init_db },
                { "init-db", cmd_init_db },
-               { "log", cmd_log, RUN_SETUP | USE_PAGER },
+               { "log", cmd_log, RUN_SETUP },
                { "ls-files", cmd_ls_files, RUN_SETUP },
                { "ls-tree", cmd_ls_tree, RUN_SETUP },
-               { "ls-remote", cmd_ls_remote },
+               { "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
                { "mailinfo", cmd_mailinfo },
                { "mailsplit", cmd_mailsplit },
                { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
                { "merge-base", cmd_merge_base, RUN_SETUP },
-               { "merge-file", cmd_merge_file },
+               { "merge-file", cmd_merge_file, RUN_SETUP_GENTLY },
+               { "merge-index", cmd_merge_index, RUN_SETUP },
                { "merge-ours", cmd_merge_ours, RUN_SETUP },
                { "merge-recursive", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
+               { "merge-recursive-ours", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
+               { "merge-recursive-theirs", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
                { "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
+               { "merge-tree", cmd_merge_tree, RUN_SETUP },
+               { "mktag", cmd_mktag, RUN_SETUP },
+               { "mktree", cmd_mktree, RUN_SETUP },
                { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
                { "name-rev", cmd_name_rev, RUN_SETUP },
+               { "notes", cmd_notes, RUN_SETUP },
                { "pack-objects", cmd_pack_objects, RUN_SETUP },
-               { "peek-remote", cmd_ls_remote },
+               { "pack-redundant", cmd_pack_redundant, RUN_SETUP },
+               { "patch-id", cmd_patch_id },
+               { "peek-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
                { "pickaxe", cmd_blame, RUN_SETUP },
                { "prune", cmd_prune, RUN_SETUP },
                { "prune-packed", cmd_prune_packed, RUN_SETUP },
@@ -339,7 +374,8 @@ static void handle_internal_command(int argc, const char **argv)
                { "receive-pack", cmd_receive_pack },
                { "reflog", cmd_reflog, RUN_SETUP },
                { "remote", cmd_remote, RUN_SETUP },
-               { "repo-config", cmd_config },
+               { "replace", cmd_replace, RUN_SETUP },
+               { "repo-config", cmd_config, RUN_SETUP_GENTLY },
                { "rerere", cmd_rerere, RUN_SETUP },
                { "reset", cmd_reset, RUN_SETUP },
                { "rev-list", cmd_rev_list, RUN_SETUP },
@@ -347,21 +383,24 @@ static void handle_internal_command(int argc, const char **argv)
                { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
                { "rm", cmd_rm, RUN_SETUP },
                { "send-pack", cmd_send_pack, RUN_SETUP },
-               { "shortlog", cmd_shortlog, USE_PAGER },
+               { "shortlog", cmd_shortlog, RUN_SETUP_GENTLY | USE_PAGER },
                { "show-branch", cmd_show_branch, RUN_SETUP },
-               { "show", cmd_show, RUN_SETUP | USE_PAGER },
+               { "show", cmd_show, RUN_SETUP },
                { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
                { "stripspace", cmd_stripspace },
                { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
                { "tag", cmd_tag, RUN_SETUP },
                { "tar-tree", cmd_tar_tree },
+               { "unpack-file", cmd_unpack_file, RUN_SETUP },
                { "unpack-objects", cmd_unpack_objects, RUN_SETUP },
                { "update-index", cmd_update_index, RUN_SETUP },
                { "update-ref", cmd_update_ref, RUN_SETUP },
+               { "update-server-info", cmd_update_server_info, RUN_SETUP },
                { "upload-archive", cmd_upload_archive },
+               { "var", cmd_var, RUN_SETUP_GENTLY },
                { "verify-tag", cmd_verify_tag, RUN_SETUP },
                { "version", cmd_version },
-               { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER },
+               { "whatchanged", cmd_whatchanged, RUN_SETUP },
                { "write-tree", cmd_write_tree, RUN_SETUP },
                { "verify-pack", cmd_verify_pack },
                { "show-ref", cmd_show_ref, RUN_SETUP },
@@ -399,6 +438,8 @@ static void execv_dashed_external(const char **argv)
        const char *tmp;
        int status;
 
+       commit_pager_choice();
+
        strbuf_addf(&cmd, "git-%s", argv[0]);
 
        /*
@@ -416,13 +457,9 @@ static void execv_dashed_external(const char **argv)
         * if we fail because the command is not found, it is
         * OK to return. Otherwise, we just pass along the status code.
         */
-       status = run_command_v_opt(argv, 0);
-       if (status != -ERR_RUN_COMMAND_EXEC) {
-               if (IS_RUN_COMMAND_ERR(status))
-                       die("unable to run '%s'", argv[0]);
-               exit(-status);
-       }
-       errno = ENOENT; /* as if we called execvp */
+       status = run_command_v_opt(argv, RUN_SILENT_EXEC_FAILURE);
+       if (status >= 0 || errno != ENOENT)
+               exit(status);
 
        argv[0] = tmp;
 
@@ -457,6 +494,8 @@ int main(int argc, const char **argv)
 {
        const char *cmd;
 
+       startup_info = &git_startup_info;
+
        cmd = git_extract_argv0_path(argv[0]);
        if (!cmd)
                cmd = "git-help";
@@ -482,12 +521,12 @@ int main(int argc, const char **argv)
        argv++;
        argc--;
        handle_options(&argv, &argc, NULL);
-       commit_pager_choice();
        if (argc > 0) {
                if (!prefixcmp(argv[0], "--"))
                        argv[0] += 2;
        } else {
                /* The user didn't specify a command; give them help */
+               commit_pager_choice();
                printf("usage: %s\n\n", git_usage_string);
                list_common_cmds_help();
                printf("\n%s\n", git_more_info_string);
@@ -511,7 +550,7 @@ int main(int argc, const char **argv)
                        break;
                if (was_alias) {
                        fprintf(stderr, "Expansion of alias '%s' failed; "
-                               "'%s' is not a git-command\n",
+                               "'%s' is not a git command\n",
                                cmd, argv[0]);
                        exit(1);
                }
index 4be0834f0bc1cebc2b341ef6f54c3c37f48eb832..91c8462b7dae171247c40db7f7b5b36d2c0633a1 100644 (file)
@@ -35,6 +35,7 @@ Requires:     git-cvs = %{version}-%{release}
 Requires:      git-arch = %{version}-%{release}
 Requires:      git-email = %{version}-%{release}
 Requires:      gitk = %{version}-%{release}
+Requires:      gitweb = %{version}-%{release}
 Requires:      git-gui = %{version}-%{release}
 Obsoletes:     git <= 1.5.4.2
 
@@ -87,6 +88,13 @@ Requires:       git = %{version}-%{release}, tk >= 8.4
 %description -n gitk
 Git revision tree visualiser ('gitk')
 
+%package -n gitweb
+Summary:       Git web interface
+Group:          Development/Tools
+Requires:       git = %{version}-%{release}
+%description -n gitweb
+Browsing git repository on the web
+
 %package -n perl-Git
 Summary:        Perl interface to Git
 Group:          Development/Libraries
@@ -98,6 +106,7 @@ BuildRequires:  perl(Error)
 Perl interface to Git
 
 %define path_settings ETC_GITCONFIG=/etc/gitconfig prefix=%{_prefix} mandir=%{_mandir} htmldir=%{_docdir}/%{name}-%{version}
+%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
 
 %prep
 %setup -q
@@ -112,6 +121,7 @@ rm -rf $RPM_BUILD_ROOT
 make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" DESTDIR=$RPM_BUILD_ROOT \
      %{path_settings} \
      INSTALLDIRS=vendor install %{!?_without_docs: install-doc}
+test ! -d $RPM_BUILD_ROOT%{python_sitelib} || rm -fr $RPM_BUILD_ROOT%{python_sitelib}
 find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';'
 find $RPM_BUILD_ROOT -type f -name '*.bs' -empty -exec rm -f {} ';'
 find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} ';'
@@ -125,6 +135,9 @@ find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} ';'
 rm -rf $RPM_BUILD_ROOT%{_mandir}
 %endif
 
+mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d
+install -m 644 -T contrib/completion/git-completion.bash $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d/git
+
 %clean
 rm -rf $RPM_BUILD_ROOT
 
@@ -134,6 +147,7 @@ rm -rf $RPM_BUILD_ROOT
 %doc README COPYING Documentation/*.txt
 %{!?_without_docs: %doc Documentation/*.html Documentation/howto}
 %{!?_without_docs: %doc Documentation/technical}
+%{_sysconfdir}/bash_completion.d
 
 %files svn
 %defattr(-,root,root)
@@ -183,6 +197,10 @@ rm -rf $RPM_BUILD_ROOT
 %{!?_without_docs: %{_mandir}/man1/*gitk*.1*}
 %{!?_without_docs: %doc Documentation/*gitk*.html }
 
+%files -n gitweb
+%defattr(-,root,root)
+%{_datadir}/gitweb
+
 %files -n perl-Git -f perl-files
 %defattr(-,root,root)
 
@@ -190,6 +208,18 @@ rm -rf $RPM_BUILD_ROOT
 # No files for you!
 
 %changelog
+* Wed Jun 30 2010 Junio C Hamano <gitster@pobox.com>
+- Add 'gitweb' subpackage.
+
+* Fri Mar 26 2010 Ian Ward Comfort <icomfort@stanford.edu>
+- Ship bash completion support from contrib/ in the core package.
+
+* Sun Jan 31 2010 Junio C Hamano <gitster@pobox.com>
+- Do not use %define inside %{!?...} construct.
+
+* Sat Jan 30 2010 Junio C Hamano <gitster@pobox.com>
+- We don't ship Python bits until a real foreign scm interface comes.
+
 * Mon Feb 04 2009 David J. Mellor <dmellor@whistlingcat.com>
 - fixed broken git help -w after renaming the git-core package to git.
 
@@ -233,7 +263,7 @@ rm -rf $RPM_BUILD_ROOT
 * Tue Mar 27 2007 Eygene Ryabinkin <rea-git@codelabs.ru>
 - Added the git-p4 package: Perforce import stuff.
 
-* Mon Feb 13 2007 Nicolas Pitre <nico@cam.org>
+* Mon Feb 13 2007 Nicolas Pitre <nico@fluxnic.net>
 - Update core package description (Git isn't as stupid as it used to be)
 
 * Mon Feb 12 2007 Junio C Hamano <junkio@cox.net>
diff --git a/git_remote_helpers/.gitignore b/git_remote_helpers/.gitignore
new file mode 100644 (file)
index 0000000..2247d5f
--- /dev/null
@@ -0,0 +1,2 @@
+/build
+/dist
diff --git a/git_remote_helpers/Makefile b/git_remote_helpers/Makefile
new file mode 100644 (file)
index 0000000..74b05dc
--- /dev/null
@@ -0,0 +1,39 @@
+#
+# Makefile for the git_remote_helpers python support modules
+#
+pysetupfile:=setup.py
+
+# Shell quote (do not use $(call) to accommodate ancient setups);
+DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
+
+ifndef PYTHON_PATH
+       ifeq ($(uname_S),FreeBSD)
+               PYTHON_PATH = /usr/local/bin/python
+       else
+               PYTHON_PATH = /usr/bin/python
+       endif
+endif
+ifndef prefix
+       prefix = $(HOME)
+endif
+ifndef V
+       QUIET = @
+       QUIETSETUP = --quiet
+endif
+
+PYLIBDIR=$(shell $(PYTHON_PATH) -c \
+        "import sys; \
+        print 'lib/python%i.%i/site-packages' % sys.version_info[:2]")
+
+all: $(pysetupfile)
+       $(QUIET)$(PYTHON_PATH) $(pysetupfile) $(QUIETSETUP) build
+
+install: $(pysetupfile)
+       $(PYTHON_PATH) $(pysetupfile) install --prefix $(DESTDIR_SQ)$(prefix)
+
+instlibdir: $(pysetupfile)
+       @echo "$(DESTDIR_SQ)$(prefix)/$(PYLIBDIR)"
+
+clean:
+       $(QUIET)$(PYTHON_PATH) $(pysetupfile) $(QUIETSETUP) clean -a
+       $(RM) *.pyo *.pyc
diff --git a/git_remote_helpers/__init__.py b/git_remote_helpers/__init__.py
new file mode 100644 (file)
index 0000000..00f69cb
--- /dev/null
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+
+"""Support library package for git remote helpers.
+
+Git remote helpers are helper commands that interfaces with a non-git
+repository to provide automatic import of non-git history into a Git
+repository.
+
+This package provides the support library needed by these helpers..
+The following modules are included:
+
+- git.git - Interaction with Git repositories
+
+- util - General utility functionality use by the other modules in
+         this package, and also used directly by the helpers.
+"""
diff --git a/git_remote_helpers/git/__init__.py b/git_remote_helpers/git/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/git_remote_helpers/git/exporter.py b/git_remote_helpers/git/exporter.py
new file mode 100644 (file)
index 0000000..f40f9d6
--- /dev/null
@@ -0,0 +1,53 @@
+import os
+import subprocess
+import sys
+
+
+class GitExporter(object):
+    """An exporter for testgit repositories.
+
+    The exporter simply delegates to git fast-export.
+    """
+
+    def __init__(self, repo):
+        """Creates a new exporter for the specified repo.
+        """
+
+        self.repo = repo
+
+    def export_repo(self, base):
+        """Exports a fast-export stream for the given directory.
+
+        Simply delegates to git fast-epxort and pipes it through sed
+        to make the refs show up under the prefix rather than the
+        default refs/heads. This is to demonstrate how the export
+        data can be stored under it's own ref (using the refspec
+        capability).
+        """
+
+        dirname = self.repo.get_base_path(base)
+        path = os.path.abspath(os.path.join(dirname, 'testgit.marks'))
+
+        if not os.path.exists(dirname):
+            os.makedirs(dirname)
+
+        print "feature relative-marks"
+        if os.path.exists(os.path.join(dirname, 'git.marks')):
+            print "feature import-marks=%s/git.marks" % self.repo.hash
+        print "feature export-marks=%s/git.marks" % self.repo.hash
+        sys.stdout.flush()
+
+        args = ["git", "--git-dir=" + self.repo.gitpath, "fast-export", "--export-marks=" + path]
+
+        if os.path.exists(path):
+            args.append("--import-marks=" + path)
+
+        args.append("HEAD")
+
+        p1 = subprocess.Popen(args, stdout=subprocess.PIPE)
+
+        args = ["sed", "s_refs/heads/_" + self.repo.prefix + "_g"]
+
+        child = subprocess.Popen(args, stdin=p1.stdout)
+        if child.wait() != 0:
+            raise CalledProcessError
diff --git a/git_remote_helpers/git/git.py b/git_remote_helpers/git/git.py
new file mode 100644 (file)
index 0000000..a383e6c
--- /dev/null
@@ -0,0 +1,678 @@
+#!/usr/bin/env python
+
+"""Functionality for interacting with Git repositories.
+
+This module provides classes for interfacing with a Git repository.
+"""
+
+import os
+import re
+import time
+from binascii import hexlify
+from cStringIO import StringIO
+import unittest
+
+from git_remote_helpers.util import debug, error, die, start_command, run_command
+
+
+def get_git_dir ():
+    """Return the path to the GIT_DIR for this repo."""
+    args = ("git", "rev-parse", "--git-dir")
+    exit_code, output, errors = run_command(args)
+    if exit_code:
+        die("Failed to retrieve git dir")
+    assert not errors
+    return output.strip()
+
+
+def parse_git_config ():
+    """Return a dict containing the parsed version of 'git config -l'."""
+    exit_code, output, errors = run_command(("git", "config", "-z", "-l"))
+    if exit_code:
+        die("Failed to retrieve git configuration")
+    assert not errors
+    return dict([e.split('\n', 1) for e in output.split("\0") if e])
+
+
+def git_config_bool (value):
+    """Convert the given git config string value to True or False.
+
+    Raise ValueError if the given string was not recognized as a
+    boolean value.
+
+    """
+    norm_value = str(value).strip().lower()
+    if norm_value in ("true", "1", "yes", "on", ""):
+        return True
+    if norm_value in ("false", "0", "no", "off", "none"):
+        return False
+    raise ValueError("Failed to parse '%s' into a boolean value" % (value))
+
+
+def valid_git_ref (ref_name):
+    """Return True iff the given ref name is a valid git ref name."""
+    # The following is a reimplementation of the git check-ref-format
+    # command.  The rules were derived from the git check-ref-format(1)
+    # manual page.  This code should be replaced by a call to
+    # check_ref_format() in the git library, when such is available.
+    if ref_name.endswith('/') or \
+       ref_name.startswith('.') or \
+       ref_name.count('/.') or \
+       ref_name.count('..') or \
+       ref_name.endswith('.lock'):
+        return False
+    for c in ref_name:
+        if ord(c) < 0x20 or ord(c) == 0x7f or c in " ~^:?*[":
+            return False
+    return True
+
+
+class GitObjectFetcher(object):
+
+    """Provide parsed access to 'git cat-file --batch'.
+
+    This provides a read-only interface to the Git object database.
+
+    """
+
+    def __init__ (self):
+        """Initiate a 'git cat-file --batch' session."""
+        self.queue = []  # List of object names to be submitted
+        self.in_transit = None  # Object name currently in transit
+
+        # 'git cat-file --batch' produces binary output which is likely
+        # to be corrupted by the default "rU"-mode pipe opened by
+        # start_command.  (Mode == "rU" does universal new-line
+        # conversion, which mangles carriage returns.) Therefore, we
+        # open an explicitly binary-safe pipe for transferring the
+        # output from 'git cat-file --batch'.
+        pipe_r_fd, pipe_w_fd = os.pipe()
+        pipe_r = os.fdopen(pipe_r_fd, "rb")
+        pipe_w = os.fdopen(pipe_w_fd, "wb")
+        self.proc = start_command(("git", "cat-file", "--batch"),
+                                  stdout = pipe_w)
+        self.f = pipe_r
+
+    def __del__ (self):
+        """Verify completed communication with 'git cat-file --batch'."""
+        assert not self.queue
+        assert self.in_transit is None
+        self.proc.stdin.close()
+        assert self.proc.wait() == 0  # Zero exit code
+        assert self.f.read() == ""  # No remaining output
+
+    def _submit_next_object (self):
+        """Submit queue items to the 'git cat-file --batch' process.
+
+        If there are items in the queue, and there is currently no item
+        currently in 'transit', then pop the first item off the queue,
+        and submit it.
+
+        """
+        if self.queue and self.in_transit is None:
+            self.in_transit = self.queue.pop(0)
+            print >> self.proc.stdin, self.in_transit[0]
+
+    def push (self, obj, callback):
+        """Push the given object name onto the queue.
+
+        The given callback function will at some point in the future
+        be called exactly once with the following arguments:
+        - self - this GitObjectFetcher instance
+        - obj  - the object name provided to push()
+        - sha1 - the SHA1 of the object, if 'None' obj is missing
+        - t    - the type of the object (tag/commit/tree/blob)
+        - size - the size of the object in bytes
+        - data - the object contents
+
+        """
+        self.queue.append((obj, callback))
+        self._submit_next_object()  # (Re)start queue processing
+
+    def process_next_entry (self):
+        """Read the next entry off the queue and invoke callback."""
+        obj, cb = self.in_transit
+        self.in_transit = None
+        header = self.f.readline()
+        if header == "%s missing\n" % (obj):
+            cb(self, obj, None, None, None, None)
+            return
+        sha1, t, size = header.split(" ")
+        assert len(sha1) == 40
+        assert t in ("tag", "commit", "tree", "blob")
+        assert size.endswith("\n")
+        size = int(size.strip())
+        data = self.f.read(size)
+        assert self.f.read(1) == "\n"
+        cb(self, obj, sha1, t, size, data)
+        self._submit_next_object()
+
+    def process (self):
+        """Process the current queue until empty."""
+        while self.in_transit is not None:
+            self.process_next_entry()
+
+    # High-level convenience methods:
+
+    def get_sha1 (self, objspec):
+        """Return the SHA1 of the object specified by 'objspec'.
+
+        Return None if 'objspec' does not specify an existing object.
+
+        """
+        class _ObjHandler(object):
+            """Helper class for getting the returned SHA1."""
+            def __init__ (self, parser):
+                self.parser = parser
+                self.sha1 = None
+
+            def __call__ (self, parser, obj, sha1, t, size, data):
+                # FIXME: Many unused arguments. Could this be cheaper?
+                assert parser == self.parser
+                self.sha1 = sha1
+
+        handler = _ObjHandler(self)
+        self.push(objspec, handler)
+        self.process()
+        return handler.sha1
+
+    def open_obj (self, objspec):
+        """Return a file object wrapping the contents of a named object.
+
+        The caller is responsible for calling .close() on the returned
+        file object.
+
+        Raise KeyError if 'objspec' does not exist in the repo.
+
+        """
+        class _ObjHandler(object):
+            """Helper class for parsing the returned git object."""
+            def __init__ (self, parser):
+                """Set up helper."""
+                self.parser = parser
+                self.contents = StringIO()
+                self.err = None
+
+            def __call__ (self, parser, obj, sha1, t, size, data):
+                """Git object callback (see GitObjectFetcher documentation)."""
+                assert parser == self.parser
+                if not sha1:  # Missing object
+                    self.err = "Missing object '%s'" % obj
+                else:
+                    assert size == len(data)
+                    self.contents.write(data)
+
+        handler = _ObjHandler(self)
+        self.push(objspec, handler)
+        self.process()
+        if handler.err:
+            raise KeyError(handler.err)
+        handler.contents.seek(0)
+        return handler.contents
+
+    def walk_tree (self, tree_objspec, callback, prefix = ""):
+        """Recursively walk the given Git tree object.
+
+        Recursively walk all subtrees of the given tree object, and
+        invoke the given callback passing three arguments:
+        (path, mode, data) with the path, permission bits, and contents
+        of all the blobs found in the entire tree structure.
+
+        """
+        class _ObjHandler(object):
+            """Helper class for walking a git tree structure."""
+            def __init__ (self, parser, cb, path, mode = None):
+                """Set up helper."""
+                self.parser = parser
+                self.cb = cb
+                self.path = path
+                self.mode = mode
+                self.err = None
+
+            def parse_tree (self, treedata):
+                """Parse tree object data, yield tree entries.
+
+                Each tree entry is a 3-tuple (mode, sha1, path)
+
+                self.path is prepended to all paths yielded
+                from this method.
+
+                """
+                while treedata:
+                    mode = int(treedata[:6], 10)
+                    # Turn 100xxx into xxx
+                    if mode > 100000:
+                        mode -= 100000
+                    assert treedata[6] == " "
+                    i = treedata.find("\0", 7)
+                    assert i > 0
+                    path = treedata[7:i]
+                    sha1 = hexlify(treedata[i + 1: i + 21])
+                    yield (mode, sha1, self.path + path)
+                    treedata = treedata[i + 21:]
+
+            def __call__ (self, parser, obj, sha1, t, size, data):
+                """Git object callback (see GitObjectFetcher documentation)."""
+                assert parser == self.parser
+                if not sha1:  # Missing object
+                    self.err = "Missing object '%s'" % (obj)
+                    return
+                assert size == len(data)
+                if t == "tree":
+                    if self.path:
+                        self.path += "/"
+                    # Recurse into all blobs and subtrees
+                    for m, s, p in self.parse_tree(data):
+                        parser.push(s,
+                                    self.__class__(self.parser, self.cb, p, m))
+                elif t == "blob":
+                    self.cb(self.path, self.mode, data)
+                else:
+                    raise ValueError("Unknown object type '%s'" % (t))
+
+        self.push(tree_objspec, _ObjHandler(self, callback, prefix))
+        self.process()
+
+
+class GitRefMap(object):
+
+    """Map Git ref names to the Git object names they currently point to.
+
+    Behaves like a dictionary of Git ref names -> Git object names.
+
+    """
+
+    def __init__ (self, obj_fetcher):
+        """Create a new Git ref -> object map."""
+        self.obj_fetcher = obj_fetcher
+        self._cache = {}  # dict: refname -> objname
+
+    def _load (self, ref):
+        """Retrieve the object currently bound to the given ref.
+
+        The name of the object pointed to by the given ref is stored
+        into this mapping, and also returned.
+
+        """
+        if ref not in self._cache:
+            self._cache[ref] = self.obj_fetcher.get_sha1(ref)
+        return self._cache[ref]
+
+    def __contains__ (self, refname):
+        """Return True if the given refname is present in this cache."""
+        return bool(self._load(refname))
+
+    def __getitem__ (self, refname):
+        """Return the git object name pointed to by the given refname."""
+        commit = self._load(refname)
+        if commit is None:
+            raise KeyError("Unknown ref '%s'" % (refname))
+        return commit
+
+    def get (self, refname, default = None):
+        """Return the git object name pointed to by the given refname."""
+        commit = self._load(refname)
+        if commit is None:
+            return default
+        return commit
+
+
+class GitFICommit(object):
+
+    """Encapsulate the data in a Git fast-import commit command."""
+
+    SHA1RE = re.compile(r'^[0-9a-f]{40}$')
+
+    @classmethod
+    def parse_mode (cls, mode):
+        """Verify the given git file mode, and return it as a string."""
+        assert mode in (644, 755, 100644, 100755, 120000)
+        return "%i" % (mode)
+
+    @classmethod
+    def parse_objname (cls, objname):
+        """Return the given object name (or mark number) as a string."""
+        if isinstance(objname, int):  # Object name is a mark number
+            assert objname > 0
+            return ":%i" % (objname)
+
+        # No existence check is done, only checks for valid format
+        assert cls.SHA1RE.match(objname)  # Object name is valid SHA1
+        return objname
+
+    @classmethod
+    def quote_path (cls, path):
+        """Return a quoted version of the given path."""
+        path = path.replace("\\", "\\\\")
+        path = path.replace("\n", "\\n")
+        path = path.replace('"', '\\"')
+        return '"%s"' % (path)
+
+    @classmethod
+    def parse_path (cls, path):
+        """Verify that the given path is valid, and quote it, if needed."""
+        assert not isinstance(path, int)  # Cannot be a mark number
+
+        # These checks verify the rules on the fast-import man page
+        assert not path.count("//")
+        assert not path.endswith("/")
+        assert not path.startswith("/")
+        assert not path.count("/./")
+        assert not path.count("/../")
+        assert not path.endswith("/.")
+        assert not path.endswith("/..")
+        assert not path.startswith("./")
+        assert not path.startswith("../")
+
+        if path.count('"') + path.count('\n') + path.count('\\'):
+            return cls.quote_path(path)
+        return path
+
+    def __init__ (self, name, email, timestamp, timezone, message):
+        """Create a new Git fast-import commit, with the given metadata."""
+        self.name = name
+        self.email = email
+        self.timestamp = timestamp
+        self.timezone = timezone
+        self.message = message
+        self.pathops = []  # List of path operations in this commit
+
+    def modify (self, mode, blobname, path):
+        """Add a file modification to this Git fast-import commit."""
+        self.pathops.append(("M",
+                             self.parse_mode(mode),
+                             self.parse_objname(blobname),
+                             self.parse_path(path)))
+
+    def delete (self, path):
+        """Add a file deletion to this Git fast-import commit."""
+        self.pathops.append(("D", self.parse_path(path)))
+
+    def copy (self, path, newpath):
+        """Add a file copy to this Git fast-import commit."""
+        self.pathops.append(("C",
+                             self.parse_path(path),
+                             self.parse_path(newpath)))
+
+    def rename (self, path, newpath):
+        """Add a file rename to this Git fast-import commit."""
+        self.pathops.append(("R",
+                             self.parse_path(path),
+                             self.parse_path(newpath)))
+
+    def note (self, blobname, commit):
+        """Add a note object to this Git fast-import commit."""
+        self.pathops.append(("N",
+                             self.parse_objname(blobname),
+                             self.parse_objname(commit)))
+
+    def deleteall (self):
+        """Delete all files in this Git fast-import commit."""
+        self.pathops.append("deleteall")
+
+
+class TestGitFICommit(unittest.TestCase):
+
+    """GitFICommit selftests."""
+
+    def test_basic (self):
+        """GitFICommit basic selftests."""
+
+        def expect_fail (method, data):
+            """Verify that the method(data) raises an AssertionError."""
+            try:
+                method(data)
+            except AssertionError:
+                return
+            raise AssertionError("Failed test for invalid data '%s(%s)'" %
+                                 (method.__name__, repr(data)))
+
+    def test_parse_mode (self):
+        """GitFICommit.parse_mode() selftests."""
+        self.assertEqual(GitFICommit.parse_mode(644), "644")
+        self.assertEqual(GitFICommit.parse_mode(755), "755")
+        self.assertEqual(GitFICommit.parse_mode(100644), "100644")
+        self.assertEqual(GitFICommit.parse_mode(100755), "100755")
+        self.assertEqual(GitFICommit.parse_mode(120000), "120000")
+        self.assertRaises(AssertionError, GitFICommit.parse_mode, 0)
+        self.assertRaises(AssertionError, GitFICommit.parse_mode, 123)
+        self.assertRaises(AssertionError, GitFICommit.parse_mode, 600)
+        self.assertRaises(AssertionError, GitFICommit.parse_mode, "644")
+        self.assertRaises(AssertionError, GitFICommit.parse_mode, "abc")
+
+    def test_parse_objname (self):
+        """GitFICommit.parse_objname() selftests."""
+        self.assertEqual(GitFICommit.parse_objname(1), ":1")
+        self.assertRaises(AssertionError, GitFICommit.parse_objname, 0)
+        self.assertRaises(AssertionError, GitFICommit.parse_objname, -1)
+        self.assertEqual(GitFICommit.parse_objname("0123456789" * 4),
+                         "0123456789" * 4)
+        self.assertEqual(GitFICommit.parse_objname("2468abcdef" * 4),
+                         "2468abcdef" * 4)
+        self.assertRaises(AssertionError, GitFICommit.parse_objname,
+                          "abcdefghij" * 4)
+
+    def test_parse_path (self):
+        """GitFICommit.parse_path() selftests."""
+        self.assertEqual(GitFICommit.parse_path("foo/bar"), "foo/bar")
+        self.assertEqual(GitFICommit.parse_path("path/with\n and \" in it"),
+                         '"path/with\\n and \\" in it"')
+        self.assertRaises(AssertionError, GitFICommit.parse_path, 1)
+        self.assertRaises(AssertionError, GitFICommit.parse_path, 0)
+        self.assertRaises(AssertionError, GitFICommit.parse_path, -1)
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "foo//bar")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/bar/")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "/foo/bar")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/./bar")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/../bar")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/bar/.")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/bar/..")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "./foo/bar")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "../foo/bar")
+
+
+class GitFastImport(object):
+
+    """Encapsulate communication with git fast-import."""
+
+    def __init__ (self, f, obj_fetcher, last_mark = 0):
+        """Set up self to communicate with a fast-import process through f."""
+        self.f = f  # File object where fast-import stream is written
+        self.obj_fetcher = obj_fetcher  # GitObjectFetcher instance
+        self.next_mark = last_mark + 1  # Next mark number
+        self.refs = set()  # Keep track of the refnames we've seen
+
+    def comment (self, s):
+        """Write the given comment in the fast-import stream."""
+        assert "\n" not in s, "Malformed comment: '%s'" % (s)
+        self.f.write("# %s\n" % (s))
+
+    def commit (self, ref, commitdata):
+        """Make a commit on the given ref, with the given GitFICommit.
+
+        Return the mark number identifying this commit.
+
+        """
+        self.f.write("""\
+commit %(ref)s
+mark :%(mark)i
+committer %(name)s <%(email)s> %(timestamp)i %(timezone)s
+data %(msgLength)i
+%(msg)s
+""" % {
+    'ref': ref,
+    'mark': self.next_mark,
+    'name': commitdata.name,
+    'email': commitdata.email,
+    'timestamp': commitdata.timestamp,
+    'timezone': commitdata.timezone,
+    'msgLength': len(commitdata.message),
+    'msg': commitdata.message,
+})
+
+        if ref not in self.refs:
+            self.refs.add(ref)
+            parent = ref + "^0"
+            if self.obj_fetcher.get_sha1(parent):
+                self.f.write("from %s\n" % (parent))
+
+        for op in commitdata.pathops:
+            self.f.write(" ".join(op))
+            self.f.write("\n")
+        self.f.write("\n")
+        retval = self.next_mark
+        self.next_mark += 1
+        return retval
+
+    def blob (self, data):
+        """Import the given blob.
+
+        Return the mark number identifying this blob.
+
+        """
+        self.f.write("blob\nmark :%i\ndata %i\n%s\n" %
+                     (self.next_mark, len(data), data))
+        retval = self.next_mark
+        self.next_mark += 1
+        return retval
+
+    def reset (self, ref, objname):
+        """Reset the given ref to point at the given Git object."""
+        self.f.write("reset %s\nfrom %s\n\n" %
+                     (ref, GitFICommit.parse_objname(objname)))
+        if ref not in self.refs:
+            self.refs.add(ref)
+
+
+class GitNotes(object):
+
+    """Encapsulate access to Git notes.
+
+    Simulates a dictionary of object name (SHA1) -> Git note mappings.
+
+    """
+
+    def __init__ (self, notes_ref, obj_fetcher):
+        """Create a new Git notes interface, bound to the given notes ref."""
+        self.notes_ref = notes_ref
+        self.obj_fetcher = obj_fetcher  # Used to get objects from repo
+        self.imports = []  # list: (objname, note data blob name) tuples
+
+    def __del__ (self):
+        """Verify that self.commit_notes() was called before destruction."""
+        if self.imports:
+            error("Missing call to self.commit_notes().")
+            error("%i notes are not committed!", len(self.imports))
+
+    def _load (self, objname):
+        """Return the note data associated with the given git object.
+
+        The note data is returned in string form. If no note is found
+        for the given object, None is returned.
+
+        """
+        try:
+            f = self.obj_fetcher.open_obj("%s:%s" % (self.notes_ref, objname))
+            ret = f.read()
+            f.close()
+        except KeyError:
+            ret = None
+        return ret
+
+    def __getitem__ (self, objname):
+        """Return the note contents associated with the given object.
+
+        Raise KeyError if given object has no associated note.
+
+        """
+        blobdata = self._load(objname)
+        if blobdata is None:
+            raise KeyError("Object '%s' has no note" % (objname))
+        return blobdata
+
+    def get (self, objname, default = None):
+        """Return the note contents associated with the given object.
+
+        Return given default if given object has no associated note.
+
+        """
+        blobdata = self._load(objname)
+        if blobdata is None:
+            return default
+        return blobdata
+
+    def import_note (self, objname, data, gfi):
+        """Tell git fast-import to store data as a note for objname.
+
+        This method uses the given GitFastImport object to create a
+        blob containing the given note data.  Also an entry mapping the
+        given object name to the created blob is stored until
+        commit_notes() is called.
+
+        Note that this method only works if it is later followed by a
+        call to self.commit_notes() (which produces the note commit
+        that refers to the blob produced here).
+
+        """
+        if not data.endswith("\n"):
+            data += "\n"
+        gfi.comment("Importing note for object %s" % (objname))
+        mark = gfi.blob(data)
+        self.imports.append((objname, mark))
+
+    def commit_notes (self, gfi, author, message):
+        """Produce a git fast-import note commit for the imported notes.
+
+        This method uses the given GitFastImport object to create a
+        commit on the notes ref, introducing the notes previously
+        submitted to import_note().
+
+        """
+        if not self.imports:
+            return
+        commitdata = GitFICommit(author[0], author[1],
+                                 time.time(), "0000", message)
+        for objname, blobname in self.imports:
+            assert isinstance(objname, int) and objname > 0
+            assert isinstance(blobname, int) and blobname > 0
+            commitdata.note(blobname, objname)
+        gfi.commit(self.notes_ref, commitdata)
+        self.imports = []
+
+
+class GitCachedNotes(GitNotes):
+
+    """Encapsulate access to Git notes (cached version).
+
+    Only use this class if no caching is done at a higher level.
+
+    Simulates a dictionary of object name (SHA1) -> Git note mappings.
+
+    """
+
+    def __init__ (self, notes_ref, obj_fetcher):
+        """Set up a caching wrapper around GitNotes."""
+        GitNotes.__init__(self, notes_ref, obj_fetcher)
+        self._cache = {}  # Cache: object name -> note data
+
+    def __del__ (self):
+        """Verify that GitNotes' destructor is called."""
+        GitNotes.__del__(self)
+
+    def _load (self, objname):
+        """Extend GitNotes._load() with a local objname -> note cache."""
+        if objname not in self._cache:
+            self._cache[objname] = GitNotes._load(self, objname)
+        return self._cache[objname]
+
+    def import_note (self, objname, data, gfi):
+        """Extend GitNotes.import_note() with a local objname -> note cache."""
+        if not data.endswith("\n"):
+            data += "\n"
+        assert objname not in self._cache
+        self._cache[objname] = data
+        GitNotes.import_note(self, objname, data, gfi)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/git_remote_helpers/git/importer.py b/git_remote_helpers/git/importer.py
new file mode 100644 (file)
index 0000000..70a7127
--- /dev/null
@@ -0,0 +1,40 @@
+import os
+import subprocess
+
+
+class GitImporter(object):
+    """An importer for testgit repositories.
+
+    This importer simply delegates to git fast-import.
+    """
+
+    def __init__(self, repo):
+        """Creates a new importer for the specified repo.
+        """
+
+        self.repo = repo
+
+    def do_import(self, base):
+        """Imports a fast-import stream to the given directory.
+
+        Simply delegates to git fast-import.
+        """
+
+        dirname = self.repo.get_base_path(base)
+        if self.repo.local:
+            gitdir = self.repo.gitpath
+        else:
+            gitdir = os.path.abspath(os.path.join(dirname, '.git'))
+        path = os.path.abspath(os.path.join(dirname, 'git.marks'))
+
+        if not os.path.exists(dirname):
+            os.makedirs(dirname)
+
+        args = ["git", "--git-dir=" + gitdir, "fast-import", "--quiet", "--export-marks=" + path]
+
+        if os.path.exists(path):
+            args.append("--import-marks=" + path)
+
+        child = subprocess.Popen(args)
+        if child.wait() != 0:
+            raise CalledProcessError
diff --git a/git_remote_helpers/git/non_local.py b/git_remote_helpers/git/non_local.py
new file mode 100644 (file)
index 0000000..f27389b
--- /dev/null
@@ -0,0 +1,69 @@
+import os
+import subprocess
+
+from git_remote_helpers.util import die, warn
+
+
+class NonLocalGit(object):
+    """Handler to interact with non-local repos.
+    """
+
+    def __init__(self, repo):
+        """Creates a new non-local handler for the specified repo.
+        """
+
+        self.repo = repo
+
+    def clone(self, base):
+        """Clones the non-local repo to base.
+
+        Does nothing if a clone already exists.
+        """
+
+        path = os.path.join(self.repo.get_base_path(base), '.git')
+
+        # already cloned
+        if os.path.exists(path):
+            return path
+
+        os.makedirs(path)
+        args = ["git", "clone", "--bare", "--quiet", self.repo.gitpath, path]
+
+        child = subprocess.Popen(args)
+        if child.wait() != 0:
+            raise CalledProcessError
+
+        return path
+
+    def update(self, base):
+        """Updates checkout of the non-local repo in base.
+        """
+
+        path = os.path.join(self.repo.get_base_path(base), '.git')
+
+        if not os.path.exists(path):
+            die("could not find repo at %s", path)
+
+        args = ["git", "--git-dir=" + path, "fetch", "--quiet", self.repo.gitpath]
+        child = subprocess.Popen(args)
+        if child.wait() != 0:
+            raise CalledProcessError
+
+        args = ["git", "--git-dir=" + path, "update-ref", "refs/heads/master", "FETCH_HEAD"]
+        child = subprocess.Popen(args)
+        if child.wait() != 0:
+            raise CalledProcessError
+
+    def push(self, base):
+        """Pushes from the non-local repo to base.
+        """
+
+        path = os.path.join(self.repo.get_base_path(base), '.git')
+
+        if not os.path.exists(path):
+            die("could not find repo at %s", path)
+
+        args = ["git", "--git-dir=" + path, "push", "--quiet", self.repo.gitpath]
+        child = subprocess.Popen(args)
+        if child.wait() != 0:
+            raise CalledProcessError
diff --git a/git_remote_helpers/git/repo.py b/git_remote_helpers/git/repo.py
new file mode 100644 (file)
index 0000000..58e1cdb
--- /dev/null
@@ -0,0 +1,75 @@
+import os
+import subprocess
+
+def sanitize(rev, sep='\t'):
+    """Converts a for-each-ref line to a name/value pair.
+    """
+
+    splitrev = rev.split(sep)
+    branchval = splitrev[0]
+    branchname = splitrev[1].strip()
+    if branchname.startswith("refs/heads/"):
+        branchname = branchname[11:]
+
+    return branchname, branchval
+
+def is_remote(url):
+    """Checks whether the specified value is a remote url.
+    """
+
+    prefixes = ["http", "file", "git"]
+
+    for prefix in prefixes:
+        if url.startswith(prefix):
+            return True
+    return False
+
+class GitRepo(object):
+    """Repo object representing a repo.
+    """
+
+    def __init__(self, path):
+        """Initializes a new repo at the given path.
+        """
+
+        self.path = path
+        self.head = None
+        self.revmap = {}
+        self.local = not is_remote(self.path)
+
+        if(self.path.endswith('.git')):
+            self.gitpath = self.path
+        else:
+            self.gitpath = os.path.join(self.path, '.git')
+
+        if self.local and not os.path.exists(self.gitpath):
+            os.makedirs(self.gitpath)
+
+    def get_revs(self):
+        """Fetches all revs from the remote.
+        """
+
+        args = ["git", "ls-remote", self.gitpath]
+        path = ".cached_revs"
+        ofile = open(path, "w")
+
+        child = subprocess.Popen(args, stdout=ofile)
+        if child.wait() != 0:
+            raise CalledProcessError
+        output = open(path).readlines()
+        self.revmap = dict(sanitize(i) for i in output)
+        if "HEAD" in self.revmap:
+            del self.revmap["HEAD"]
+        self.revs = self.revmap.keys()
+        ofile.close()
+
+    def get_head(self):
+        """Determines the head of a local repo.
+        """
+
+        if not self.local:
+            return
+
+        path = os.path.join(self.gitpath, "HEAD")
+        head = open(path).readline()
+        self.head, _ = sanitize(head, ' ')
diff --git a/git_remote_helpers/setup.py b/git_remote_helpers/setup.py
new file mode 100644 (file)
index 0000000..4d434b6
--- /dev/null
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+
+"""Distutils build/install script for the git_remote_helpers package."""
+
+from distutils.core import setup
+
+setup(
+    name = 'git_remote_helpers',
+    version = '0.1.0',
+    description = 'Git remote helper program for non-git repositories',
+    license = 'GPLv2',
+    author = 'The Git Community',
+    author_email = 'git@vger.kernel.org',
+    url = 'http://www.git-scm.com/',
+    package_dir = {'git_remote_helpers': ''},
+    packages = ['git_remote_helpers', 'git_remote_helpers.git'],
+)
diff --git a/git_remote_helpers/util.py b/git_remote_helpers/util.py
new file mode 100644 (file)
index 0000000..dce83e6
--- /dev/null
@@ -0,0 +1,194 @@
+#!/usr/bin/env python
+
+"""Misc. useful functionality used by the rest of this package.
+
+This module provides common functionality used by the other modules in
+this package.
+
+"""
+
+import sys
+import os
+import subprocess
+
+
+# Whether or not to show debug messages
+DEBUG = False
+
+def notify(msg, *args):
+    """Print a message to stderr."""
+    print >> sys.stderr, msg % args
+
+def debug (msg, *args):
+    """Print a debug message to stderr when DEBUG is enabled."""
+    if DEBUG:
+        print >> sys.stderr, msg % args
+
+def error (msg, *args):
+    """Print an error message to stderr."""
+    print >> sys.stderr, "ERROR:", msg % args
+
+def warn(msg, *args):
+    """Print a warning message to stderr."""
+    print >> sys.stderr, "warning:", msg % args
+
+def die (msg, *args):
+    """Print as error message to stderr and exit the program."""
+    error(msg, *args)
+    sys.exit(1)
+
+
+class ProgressIndicator(object):
+
+    """Simple progress indicator.
+
+    Displayed as a spinning character by default, but can be customized
+    by passing custom messages that overrides the spinning character.
+
+    """
+
+    States = ("|", "/", "-", "\\")
+
+    def __init__ (self, prefix = "", f = sys.stdout):
+        """Create a new ProgressIndicator, bound to the given file object."""
+        self.n = 0  # Simple progress counter
+        self.f = f  # Progress is written to this file object
+        self.prev_len = 0  # Length of previous msg (to be overwritten)
+        self.prefix = prefix  # Prefix prepended to each progress message
+        self.prefix_lens = [] # Stack of prefix string lengths
+
+    def pushprefix (self, prefix):
+        """Append the given prefix onto the prefix stack."""
+        self.prefix_lens.append(len(self.prefix))
+        self.prefix += prefix
+
+    def popprefix (self):
+        """Remove the last prefix from the prefix stack."""
+        prev_len = self.prefix_lens.pop()
+        self.prefix = self.prefix[:prev_len]
+
+    def __call__ (self, msg = None, lf = False):
+        """Indicate progress, possibly with a custom message."""
+        if msg is None:
+            msg = self.States[self.n % len(self.States)]
+        msg = self.prefix + msg
+        print >> self.f, "\r%-*s" % (self.prev_len, msg),
+        self.prev_len = len(msg.expandtabs())
+        if lf:
+            print >> self.f
+            self.prev_len = 0
+        self.n += 1
+
+    def finish (self, msg = "done", noprefix = False):
+        """Finalize progress indication with the given message."""
+        if noprefix:
+            self.prefix = ""
+        self(msg, True)
+
+
+def start_command (args, cwd = None, shell = False, add_env = None,
+                   stdin = subprocess.PIPE, stdout = subprocess.PIPE,
+                   stderr = subprocess.PIPE):
+    """Start the given command, and return a subprocess object.
+
+    This provides a simpler interface to the subprocess module.
+
+    """
+    env = None
+    if add_env is not None:
+        env = os.environ.copy()
+        env.update(add_env)
+    return subprocess.Popen(args, bufsize = 1, stdin = stdin, stdout = stdout,
+                            stderr = stderr, cwd = cwd, shell = shell,
+                            env = env, universal_newlines = True)
+
+
+def run_command (args, cwd = None, shell = False, add_env = None,
+                 flag_error = True):
+    """Run the given command to completion, and return its results.
+
+    This provides a simpler interface to the subprocess module.
+
+    The results are formatted as a 3-tuple: (exit_code, output, errors)
+
+    If flag_error is enabled, Error messages will be produced if the
+    subprocess terminated with a non-zero exit code and/or stderr
+    output.
+
+    The other arguments are passed on to start_command().
+
+    """
+    process = start_command(args, cwd, shell, add_env)
+    (output, errors) = process.communicate()
+    exit_code = process.returncode
+    if flag_error and errors:
+        error("'%s' returned errors:\n---\n%s---", " ".join(args), errors)
+    if flag_error and exit_code:
+        error("'%s' returned exit code %i", " ".join(args), exit_code)
+    return (exit_code, output, errors)
+
+
+def file_reader_method (missing_ok = False):
+    """Decorator for simplifying reading of files.
+
+    If missing_ok is True, a failure to open a file for reading will
+    not raise the usual IOError, but instead the wrapped method will be
+    called with f == None.  The method must in this case properly
+    handle f == None.
+
+    """
+    def _wrap (method):
+        """Teach given method to handle both filenames and file objects.
+
+        The given method must take a file object as its second argument
+        (the first argument being 'self', of course).  This decorator
+        will take a filename given as the second argument and promote
+        it to a file object.
+
+        """
+        def _wrapped_method (self, filename, *args, **kwargs):
+            if isinstance(filename, file):
+                f = filename
+            else:
+                try:
+                    f = open(filename, 'r')
+                except IOError:
+                    if missing_ok:
+                        f = None
+                    else:
+                        raise
+            try:
+                return method(self, f, *args, **kwargs)
+            finally:
+                if not isinstance(filename, file) and f:
+                    f.close()
+        return _wrapped_method
+    return _wrap
+
+
+def file_writer_method (method):
+    """Decorator for simplifying writing of files.
+
+    Enables the given method to handle both filenames and file objects.
+
+    The given method must take a file object as its second argument
+    (the first argument being 'self', of course).  This decorator will
+    take a filename given as the second argument and promote it to a
+    file object.
+
+    """
+    def _new_method (self, filename, *args, **kwargs):
+        if isinstance(filename, file):
+            f = filename
+        else:
+            # Make sure the containing directory exists
+            parent_dir = os.path.dirname(filename)
+            if not os.path.isdir(parent_dir):
+                os.makedirs(parent_dir)
+            f = open(filename, 'w')
+        try:
+            return method(self, f, *args, **kwargs)
+        finally:
+            if not isinstance(filename, file):
+                f.close()
+    return _new_method
index 1a7887b2528140d63b84a72843c0d0ef64f56538..1b0e09a561e01c51bdc6dbced052e0c753924618 100644 (file)
@@ -2,11 +2,13 @@
 # Tcl ignores the next line -*- tcl -*- \
 exec wish "$0" -- "$@"
 
-# Copyright © 2005-2008 Paul Mackerras.  All rights reserved.
+# Copyright © 2005-2009 Paul Mackerras.  All rights reserved.
 # This program is free software; it may be used, copied, modified
 # and distributed under the terms of the GNU General Public Licence,
 # either version 2, or (at your option) any later version.
 
+package require Tk
+
 proc gitdir {} {
     global env
     if {[info exists env(GIT_DIR)]} {
@@ -128,7 +130,7 @@ proc unmerged_files {files} {
 }
 
 proc parseviewargs {n arglist} {
-    global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs
+    global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs env
 
     set vdatemode($n) 0
     set vmergeonly($n) 0
@@ -187,7 +189,8 @@ proc parseviewargs {n arglist} {
            "--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" -
            "--author=*" - "--committer=*" - "--grep=*" - "-[iE]" -
            "--remove-empty" - "--first-parent" - "--cherry-pick" -
-           "-S*" - "--pickaxe-all" - "--pickaxe-regex" {
+           "-S*" - "--pickaxe-all" - "--pickaxe-regex" -
+           "--simplify-by-decoration" {
                # These mean that we get a subset of the commits
                set filtered 1
                lappend glflags $arg
@@ -207,6 +210,9 @@ proc parseviewargs {n arglist} {
                # git rev-parse doesn't understand --merge
                lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD
            }
+           "--no-replace-objects" {
+               set env(GIT_NO_REPLACE_OBJECTS) "1"
+           }
            "-*" {
                # Other flag arguments including -<n>
                if {[string is digit -strict [string range $arg 1 end]]} {
@@ -264,7 +270,7 @@ proc parseviewrevs {view revs} {
                }
                lappend badrev $line
            }
-       }                   
+       }
        error_popup "[mc "Error parsing revisions:"] $err"
        return {}
     }
@@ -287,7 +293,7 @@ proc parseviewrevs {view revs} {
            if {$sdm != 2} {
                lappend ret $id
            } else {
-               lset ret end [lindex $ret end]...$id
+               lset ret end $id...[lindex $ret end]
            }
            lappend pos $id
        }
@@ -986,6 +992,18 @@ proc removefakerow {id} {
     drawvisible
 }
 
+proc real_children {vp} {
+    global children nullid nullid2
+
+    set kids {}
+    foreach id $children($vp) {
+       if {$id ne $nullid && $id ne $nullid2} {
+           lappend kids $id
+       }
+    }
+    return $kids
+}
+
 proc first_real_child {vp} {
     global children nullid nullid2
 
@@ -1676,6 +1694,7 @@ proc readrefs {} {
     global tagids idtags headids idheads tagobjid
     global otherrefids idotherrefs mainhead mainheadid
     global selecthead selectheadid
+    global hideremotes
 
     foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
        catch {unset $v}
@@ -1688,7 +1707,7 @@ proc readrefs {} {
        if {![string match "refs/*" $ref]} continue
        set name [string range $ref 5 end]
        if {[string match "remotes/*" $name]} {
-           if {![string match "*/HEAD" $name]} {
+           if {![string match "*/HEAD" $name] && !$hideremotes} {
                set headids($name) $id
                lappend idheads($id) $name
            }
@@ -1767,6 +1786,15 @@ proc removehead {id name} {
     unset headids($name)
 }
 
+proc ttk_toplevel {w args} {
+    global use_ttk
+    eval [linsert $args 0 ::toplevel $w]
+    if {$use_ttk} {
+        place [ttk::frame $w._toplevel_background] -x 0 -y 0 -relwidth 1 -relheight 1
+    }
+    return $w
+}
+
 proc make_transient {window origin} {
     global have_tk85
 
@@ -1785,10 +1813,13 @@ proc make_transient {window origin} {
     }
 }
 
-proc show_error {w top msg} {
+proc show_error {w top msg {mc mc}} {
+    global NS
+    if {![info exists NS]} {set NS ""}
+    if {[wm state $top] eq "withdrawn"} { wm deiconify $top }
     message $w.m -text $msg -justify center -aspect 400
     pack $w.m -side top -fill x -padx 20 -pady 20
-    button $w.ok -text [mc OK] -command "destroy $top"
+    ${NS}::button $w.ok -default active -text [$mc OK] -command "destroy $top"
     pack $w.ok -side bottom -fill x
     bind $top <Visibility> "grab $top; focus $top"
     bind $top <Key-Return> "destroy $top"
@@ -1798,45 +1829,59 @@ proc show_error {w top msg} {
 }
 
 proc error_popup {msg {owner .}} {
-    set w .error
-    toplevel $w
-    make_transient $w $owner
-    show_error $w $w $msg
+    if {[tk windowingsystem] eq "win32"} {
+        tk_messageBox -icon error -type ok -title [wm title .] \
+            -parent $owner -message $msg
+    } else {
+        set w .error
+        ttk_toplevel $w
+        make_transient $w $owner
+        show_error $w $w $msg
+    }
 }
 
 proc confirm_popup {msg {owner .}} {
-    global confirm_ok
+    global confirm_ok NS
     set confirm_ok 0
     set w .confirm
-    toplevel $w
+    ttk_toplevel $w
     make_transient $w $owner
     message $w.m -text $msg -justify center -aspect 400
     pack $w.m -side top -fill x -padx 20 -pady 20
-    button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
+    ${NS}::button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
     pack $w.ok -side left -fill x
-    button $w.cancel -text [mc Cancel] -command "destroy $w"
+    ${NS}::button $w.cancel -text [mc Cancel] -command "destroy $w"
     pack $w.cancel -side right -fill x
     bind $w <Visibility> "grab $w; focus $w"
     bind $w <Key-Return> "set confirm_ok 1; destroy $w"
     bind $w <Key-space>  "set confirm_ok 1; destroy $w"
     bind $w <Key-Escape> "destroy $w"
+    tk::PlaceWindow $w widget $owner
     tkwait window $w
     return $confirm_ok
 }
 
 proc setoptions {} {
-    option add *Panedwindow.showHandle 1 startupFile
-    option add *Panedwindow.sashRelief raised startupFile
+    if {[tk windowingsystem] ne "win32"} {
+        option add *Panedwindow.showHandle 1 startupFile
+        option add *Panedwindow.sashRelief raised startupFile
+        if {[tk windowingsystem] ne "aqua"} {
+            option add *Menu.font uifont startupFile
+        }
+    } else {
+        option add *Menu.TearOff 0 startupFile
+    }
     option add *Button.font uifont startupFile
     option add *Checkbutton.font uifont startupFile
     option add *Radiobutton.font uifont startupFile
-    if {[tk windowingsystem] ne "aqua"} {
-       option add *Menu.font uifont startupFile
-    }
     option add *Menubutton.font uifont startupFile
     option add *Label.font uifont startupFile
     option add *Message.font uifont startupFile
-    option add *Entry.font uifont startupFile
+    option add *Entry.font textfont startupFile
+    option add *Text.font textfont startupFile
+    option add *Labelframe.font uifont startupFile
+    option add *Spinbox.font textfont startupFile
+    option add *Listbox.font mainfont startupFile
 }
 
 # Make a menu and submenus.
@@ -1893,6 +1938,22 @@ proc mca {str} {
     return [string map {&& & & {}} [mc $str]]
 }
 
+proc makedroplist {w varname args} {
+    global use_ttk
+    if {$use_ttk} {
+        set width 0
+        foreach label $args {
+            set cx [string length $label]
+            if {$cx > $width} {set width $cx}
+        }
+       set gm [ttk::combobox $w -width $width -state readonly\
+                   -textvariable $varname -values $args]
+    } else {
+       set gm [eval [linsert $args 0 tk_optionMenu $w $varname]]
+    }
+    return $gm
+}
+
 proc makewindow {} {
     global canv canv2 canv3 linespc charspc ctext cflist cscroll
     global tabstop
@@ -1908,7 +1969,7 @@ proc makewindow {} {
     global headctxmenu progresscanv progressitem progresscoords statusw
     global fprogitem fprogcoord lastprogupdate progupdatepending
     global rprogitem rprogcoord rownumsel numcommits
-    global have_tk85
+    global have_tk85 use_ttk NS
 
     # The "mc" arguments here are purely so that xgettext
     # sees the following string as needing to be translated
@@ -1960,8 +2021,13 @@ proc makewindow {} {
     makemenu .bar $bar
     . configure -menu .bar
 
+    if {$use_ttk} {
+        # cover the non-themed toplevel with a themed frame.
+        place [ttk::frame ._main_background] -x 0 -y 0 -relwidth 1 -relheight 1
+    }
+
     # the gui has upper and lower half, parts of a paned window.
-    panedwindow .ctop -orient vertical
+    ${NS}::panedwindow .ctop -orient vertical
 
     # possibly use assumed geometry
     if {![info exists geometry(pwsash0)]} {
@@ -1969,14 +2035,17 @@ proc makewindow {} {
         set geometry(topwidth) [expr {80 * $charspc}]
         set geometry(botheight) [expr {15 * $linespc}]
         set geometry(botwidth) [expr {50 * $charspc}]
-        set geometry(pwsash0) "[expr {40 * $charspc}] 2"
-        set geometry(pwsash1) "[expr {60 * $charspc}] 2"
+        set geometry(pwsash0) [list [expr {40 * $charspc}] 2]
+        set geometry(pwsash1) [list [expr {60 * $charspc}] 2]
     }
 
     # the upper half will have a paned window, a scroll bar to the right, and some stuff below
-    frame .tf -height $geometry(topheight) -width $geometry(topwidth)
-    frame .tf.histframe
-    panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
+    ${NS}::frame .tf -height $geometry(topheight) -width $geometry(topwidth)
+    ${NS}::frame .tf.histframe
+    ${NS}::panedwindow .tf.histframe.pwclist -orient horizontal
+    if {!$use_ttk} {
+       .tf.histframe.pwclist configure -sashpad 0 -handlesize 4
+    }
 
     # create three canvases
     set cscroll .tf.histframe.csb
@@ -1996,28 +2065,37 @@ proc makewindow {} {
        -selectbackground $selectbgcolor \
        -background $bgcolor -bd 0 -yscrollincr $linespc
     .tf.histframe.pwclist add $canv3
-    eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
-    eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
+    if {$use_ttk} {
+       bind .tf.histframe.pwclist <Map> {
+           bind %W <Map> {}
+           .tf.histframe.pwclist sashpos 1 [lindex $::geometry(pwsash1) 0]
+           .tf.histframe.pwclist sashpos 0 [lindex $::geometry(pwsash0) 0]
+       }
+    } else {
+       eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
+       eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
+    }
 
     # a scroll bar to rule them
-    scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
+    ${NS}::scrollbar $cscroll -command {allcanvs yview}
+    if {!$use_ttk} {$cscroll configure -highlightthickness 0}
     pack $cscroll -side right -fill y
     bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
     lappend bglist $canv $canv2 $canv3
     pack .tf.histframe.pwclist -fill both -expand 1 -side left
 
     # we have two button bars at bottom of top frame. Bar 1
-    frame .tf.bar
-    frame .tf.lbar -height 15
+    ${NS}::frame .tf.bar
+    ${NS}::frame .tf.lbar -height 15
 
     set sha1entry .tf.bar.sha1
     set entries $sha1entry
     set sha1but .tf.bar.sha1label
-    button $sha1but -text [mc "SHA1 ID: "] -state disabled -relief flat \
+    button $sha1but -text "[mc "SHA1 ID:"] " -state disabled -relief flat \
        -command gotocommit -width 8
     $sha1but conf -disabledforeground [$sha1but cget -foreground]
     pack .tf.bar.sha1label -side left
-    entry $sha1entry -width 40 -font textfont -textvariable sha1string
+    ${NS}::entry $sha1entry -width 40 -font textfont -textvariable sha1string
     trace add variable sha1string write sha1change
     pack $sha1entry -side left -pady 2
 
@@ -2037,36 +2115,43 @@ proc makewindow {} {
        0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
        0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
     }
-    button .tf.bar.leftbut -image bm-left -command goback \
+    ${NS}::button .tf.bar.leftbut -image bm-left -command goback \
        -state disabled -width 26
     pack .tf.bar.leftbut -side left -fill y
-    button .tf.bar.rightbut -image bm-right -command goforw \
+    ${NS}::button .tf.bar.rightbut -image bm-right -command goforw \
        -state disabled -width 26
     pack .tf.bar.rightbut -side left -fill y
 
-    label .tf.bar.rowlabel -text [mc "Row"]
+    ${NS}::label .tf.bar.rowlabel -text [mc "Row"]
     set rownumsel {}
-    label .tf.bar.rownum -width 7 -font textfont -textvariable rownumsel \
+    ${NS}::label .tf.bar.rownum -width 7 -textvariable rownumsel \
        -relief sunken -anchor e
-    label .tf.bar.rowlabel2 -text "/"
-    label .tf.bar.numcommits -width 7 -font textfont -textvariable numcommits \
+    ${NS}::label .tf.bar.rowlabel2 -text "/"
+    ${NS}::label .tf.bar.numcommits -width 7 -textvariable numcommits \
        -relief sunken -anchor e
     pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
        -side left
+    if {!$use_ttk} {
+        foreach w {rownum numcommits} {.tf.bar.$w configure -font textfont}
+    }
     global selectedline
     trace add variable selectedline write selectedline_change
 
     # Status label and progress bar
     set statusw .tf.bar.status
-    label $statusw -width 15 -relief sunken
+    ${NS}::label $statusw -width 15 -relief sunken
     pack $statusw -side left -padx 5
-    set h [expr {[font metrics uifont -linespace] + 2}]
-    set progresscanv .tf.bar.progress
-    canvas $progresscanv -relief sunken -height $h -borderwidth 2
-    set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
-    set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
-    set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
-    pack $progresscanv -side right -expand 1 -fill x
+    if {$use_ttk} {
+       set progresscanv [ttk::progressbar .tf.bar.progress]
+    } else {
+       set h [expr {[font metrics uifont -linespace] + 2}]
+       set progresscanv .tf.bar.progress
+       canvas $progresscanv -relief sunken -height $h -borderwidth 2
+       set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
+       set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
+       set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
+    }
+    pack $progresscanv -side right -expand 1 -fill x -padx {0 2}
     set progresscoords {0 0}
     set fprogcoord 0
     set rprogcoord 0
@@ -2075,14 +2160,14 @@ proc makewindow {} {
     set progupdatepending 0
 
     # build up the bottom bar of upper window
-    label .tf.lbar.flabel -text "[mc "Find"] "
-    button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
-    button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
-    label .tf.lbar.flab2 -text " [mc "commit"] "
+    ${NS}::label .tf.lbar.flabel -text "[mc "Find"] "
+    ${NS}::button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
+    ${NS}::button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
+    ${NS}::label .tf.lbar.flab2 -text " [mc "commit"] "
     pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
        -side left -fill y
     set gdttype [mc "containing:"]
-    set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
+    set gm [makedroplist .tf.lbar.gdttype gdttype \
                [mc "containing:"] \
                [mc "touching paths:"] \
                [mc "adding/removing string:"]]
@@ -2092,14 +2177,14 @@ proc makewindow {} {
     set findstring {}
     set fstring .tf.lbar.findstring
     lappend entries $fstring
-    entry $fstring -width 30 -font textfont -textvariable findstring
+    ${NS}::entry $fstring -width 30 -textvariable findstring
     trace add variable findstring write find_change
     set findtype [mc "Exact"]
-    set findtypemenu [tk_optionMenu .tf.lbar.findtype \
-                     findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
+    set findtypemenu [makedroplist .tf.lbar.findtype \
+                         findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
     trace add variable findtype write findcom_change
     set findloc [mc "All fields"]
-    tk_optionMenu .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
+    makedroplist .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
        [mc "Comments"] [mc "Author"] [mc "Committer"]
     trace add variable findloc write find_change
     pack .tf.lbar.findloc -side right
@@ -2111,48 +2196,51 @@ proc makewindow {} {
     pack .tf.bar -in .tf -side bottom -fill x
     pack .tf.histframe -fill both -side top -expand 1
     .ctop add .tf
-    .ctop paneconfigure .tf -height $geometry(topheight)
-    .ctop paneconfigure .tf -width $geometry(topwidth)
+    if {!$use_ttk} {
+       .ctop paneconfigure .tf -height $geometry(topheight)
+       .ctop paneconfigure .tf -width $geometry(topwidth)
+    }
 
     # now build up the bottom
-    panedwindow .pwbottom -orient horizontal
+    ${NS}::panedwindow .pwbottom -orient horizontal
 
     # lower left, a text box over search bar, scroll bar to the right
     # if we know window height, then that will set the lower text height, otherwise
     # we set lower text height which will drive window height
     if {[info exists geometry(main)]} {
-        frame .bleft -width $geometry(botwidth)
+       ${NS}::frame .bleft -width $geometry(botwidth)
     } else {
-        frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
+       ${NS}::frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
     }
-    frame .bleft.top
-    frame .bleft.mid
-    frame .bleft.bottom
+    ${NS}::frame .bleft.top
+    ${NS}::frame .bleft.mid
+    ${NS}::frame .bleft.bottom
 
-    button .bleft.top.search -text [mc "Search"] -command dosearch
+    ${NS}::button .bleft.top.search -text [mc "Search"] -command dosearch
     pack .bleft.top.search -side left -padx 5
     set sstring .bleft.top.sstring
-    entry $sstring -width 20 -font textfont -textvariable searchstring
+    set searchstring ""
+    ${NS}::entry $sstring -width 20 -textvariable searchstring
     lappend entries $sstring
     trace add variable searchstring write incrsearch
     pack $sstring -side left -expand 1 -fill x
-    radiobutton .bleft.mid.diff -text [mc "Diff"] \
+    ${NS}::radiobutton .bleft.mid.diff -text [mc "Diff"] \
        -command changediffdisp -variable diffelide -value {0 0}
-    radiobutton .bleft.mid.old -text [mc "Old version"] \
+    ${NS}::radiobutton .bleft.mid.old -text [mc "Old version"] \
        -command changediffdisp -variable diffelide -value {0 1}
-    radiobutton .bleft.mid.new -text [mc "New version"] \
+    ${NS}::radiobutton .bleft.mid.new -text [mc "New version"] \
        -command changediffdisp -variable diffelide -value {1 0}
-    label .bleft.mid.labeldiffcontext -text "      [mc "Lines of context"]: "
+    ${NS}::label .bleft.mid.labeldiffcontext -text "      [mc "Lines of context"]: "
     pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
-    spinbox .bleft.mid.diffcontext -width 5 -font textfont \
-       -from 1 -increment 1 -to 10000000 \
+    spinbox .bleft.mid.diffcontext -width 5 \
+       -from 0 -increment 1 -to 10000000 \
        -validate all -validatecommand "diffcontextvalidate %P" \
        -textvariable diffcontextstring
     .bleft.mid.diffcontext set $diffcontext
     trace add variable diffcontextstring write diffcontextchange
     lappend entries .bleft.mid.diffcontext
     pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
-    checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
+    ${NS}::checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
        -command changeignorespace -variable ignorespace
     pack .bleft.mid.ignspace -side left -padx 5
     set ctext .bleft.bottom.ctext
@@ -2163,9 +2251,8 @@ proc makewindow {} {
     if {$have_tk85} {
        $ctext conf -tabstyle wordprocessor
     }
-    scrollbar .bleft.bottom.sb -command "$ctext yview"
-    scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h \
-       -width 10
+    ${NS}::scrollbar .bleft.bottom.sb -command "$ctext yview"
+    ${NS}::scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h
     pack .bleft.top -side top -fill x
     pack .bleft.mid -side top -fill x
     grid $ctext .bleft.bottom.sb -sticky nsew
@@ -2205,14 +2292,16 @@ proc makewindow {} {
     $ctext tag conf found -back yellow
 
     .pwbottom add .bleft
-    .pwbottom paneconfigure .bleft -width $geometry(botwidth)
+    if {!$use_ttk} {
+       .pwbottom paneconfigure .bleft -width $geometry(botwidth)
+    }
 
     # lower right
-    frame .bright
-    frame .bright.mode
-    radiobutton .bright.mode.patch -text [mc "Patch"] \
+    ${NS}::frame .bright
+    ${NS}::frame .bright.mode
+    ${NS}::radiobutton .bright.mode.patch -text [mc "Patch"] \
        -command reselectline -variable cmitmode -value "patch"
-    radiobutton .bright.mode.tree -text [mc "Tree"] \
+    ${NS}::radiobutton .bright.mode.tree -text [mc "Tree"] \
        -command reselectline -variable cmitmode -value "tree"
     grid .bright.mode.patch .bright.mode.tree -sticky ew
     pack .bright.mode -side top -fill x
@@ -2228,7 +2317,7 @@ proc makewindow {} {
        -spacing1 1 -spacing3 1
     lappend bglist $cflist
     lappend fglist $cflist
-    scrollbar .bright.sb -command "$cflist yview"
+    ${NS}::scrollbar .bright.sb -command "$cflist yview"
     pack .bright.sb -side right -fill y
     pack $cflist -side left -fill both -expand 1
     $cflist tag configure highlight \
@@ -2263,6 +2352,17 @@ proc makewindow {} {
         set ::BM "2"
     }
 
+    if {$use_ttk} {
+        bind .ctop <Map> {
+            bind %W <Map> {}
+            %W sashpos 0 $::geometry(topheight)
+        }
+        bind .pwbottom <Map> {
+            bind %W <Map> {}
+            %W sashpos 0 $::geometry(botwidth)
+        }
+    }
+
     bind .pwbottom <Configure> {resizecdetpanes %W %w}
     pack .ctop -fill both -expand 1
     bindall <1> {selcanvline %W %x %y}
@@ -2286,6 +2386,8 @@ proc makewindow {} {
     }
     bindall <$::BM> "canvscan mark %W %x %y"
     bindall <B$::BM-Motion> "canvscan dragto %W %x %y"
+    bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
+    bind . <$M1B-Key-w> doquit
     bindkey <Home> selfirstline
     bindkey <End> sellastline
     bind . <Key-Up> "selnextline -1"
@@ -2482,7 +2584,12 @@ proc click {w} {
 proc adjustprogress {} {
     global progresscanv progressitem progresscoords
     global fprogitem fprogcoord lastprogupdate progupdatepending
-    global rprogitem rprogcoord
+    global rprogitem rprogcoord use_ttk
+
+    if {$use_ttk} {
+       $progresscanv configure -value [expr {int($fprogcoord * 100)}]
+       return
+    }
 
     set w [expr {[winfo width $progresscanv] - 4}]
     set x0 [expr {$w * [lindex $progresscoords 0]}]
@@ -2517,12 +2624,14 @@ proc savestuff {w} {
     global maxwidth showneartags showlocalchanges
     global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
     global cmitmode wrapcomment datetimeformat limitdiffs
-    global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
-    global autoselect extdifftool perfile_attrs markbgcolor
+    global colors uicolor bgcolor fgcolor diffcolors diffcontext selectbgcolor
+    global autoselect extdifftool perfile_attrs markbgcolor use_ttk
+    global hideremotes want_ttk
 
     if {$stuffsaved} return
     if {![winfo viewable .]} return
     catch {
+       if {[file exists ~/.gitk-new]} {file delete -force ~/.gitk-new}
        set f [open "~/.gitk-new" w]
        if {$::tcl_platform(platform) eq {windows}} {
            file attributes "~/.gitk-new" -hidden true
@@ -2538,9 +2647,12 @@ proc savestuff {w} {
        puts $f [list set wrapcomment $wrapcomment]
        puts $f [list set autoselect $autoselect]
        puts $f [list set showneartags $showneartags]
+       puts $f [list set hideremotes $hideremotes]
        puts $f [list set showlocalchanges $showlocalchanges]
        puts $f [list set datetimeformat $datetimeformat]
        puts $f [list set limitdiffs $limitdiffs]
+       puts $f [list set uicolor $uicolor]
+       puts $f [list set want_ttk $want_ttk]
        puts $f [list set bgcolor $bgcolor]
        puts $f [list set fgcolor $fgcolor]
        puts $f [list set colors $colors]
@@ -2555,8 +2667,13 @@ proc savestuff {w} {
        puts $f "set geometry(state) [wm state .]"
        puts $f "set geometry(topwidth) [winfo width .tf]"
        puts $f "set geometry(topheight) [winfo height .tf]"
-        puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
-        puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
+       if {$use_ttk} {
+           puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sashpos 0] 1\""
+           puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sashpos 1] 1\""
+       } else {
+           puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
+           puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
+       }
        puts $f "set geometry(botwidth) [winfo width .bleft]"
        puts $f "set geometry(botheight) [winfo height .bleft]"
 
@@ -2574,10 +2691,15 @@ proc savestuff {w} {
 }
 
 proc resizeclistpanes {win w} {
-    global oldwidth
+    global oldwidth use_ttk
     if {[info exists oldwidth($win)]} {
-       set s0 [$win sash coord 0]
-       set s1 [$win sash coord 1]
+       if {$use_ttk} {
+           set s0 [$win sashpos 0]
+           set s1 [$win sashpos 1]
+       } else {
+           set s0 [$win sash coord 0]
+           set s1 [$win sash coord 1]
+       }
        if {$w < 60} {
            set sash0 [expr {int($w/2 - 2)}]
            set sash1 [expr {int($w*5/6 - 2)}]
@@ -2598,16 +2720,25 @@ proc resizeclistpanes {win w} {
                }
            }
        }
-       $win sash place 0 $sash0 [lindex $s0 1]
-       $win sash place 1 $sash1 [lindex $s1 1]
+       if {$use_ttk} {
+           $win sashpos 0 $sash0
+           $win sashpos 1 $sash1
+       } else {
+           $win sash place 0 $sash0 [lindex $s0 1]
+           $win sash place 1 $sash1 [lindex $s1 1]
+       }
     }
     set oldwidth($win) $w
 }
 
 proc resizecdetpanes {win w} {
-    global oldwidth
+    global oldwidth use_ttk
     if {[info exists oldwidth($win)]} {
-       set s0 [$win sash coord 0]
+       if {$use_ttk} {
+           set s0 [$win sashpos 0]
+       } else {
+           set s0 [$win sash coord 0]
+       }
        if {$w < 60} {
            set sash0 [expr {int($w*3/4 - 2)}]
        } else {
@@ -2620,7 +2751,11 @@ proc resizecdetpanes {win w} {
                set sash0 [expr {$w - 15}]
            }
        }
-       $win sash place 0 $sash0 [lindex $s0 1]
+       if {$use_ttk} {
+           $win sashpos 0 $sash0
+       } else {
+           $win sash place 0 $sash0 [lindex $s0 1]
+       }
     }
     set oldwidth($win) $w
 }
@@ -2640,31 +2775,33 @@ proc bindall {event action} {
 }
 
 proc about {} {
-    global uifont
+    global uifont NS
     set w .about
     if {[winfo exists $w]} {
        raise $w
        return
     }
-    toplevel $w
+    ttk_toplevel $w
     wm title $w [mc "About gitk"]
     make_transient $w .
     message $w.m -text [mc "
 Gitk - a commit viewer for git
 
-Copyright © 2005-2008 Paul Mackerras
+Copyright \u00a9 2005-2010 Paul Mackerras
 
 Use and redistribute under the terms of the GNU General Public License"] \
            -justify center -aspect 400 -border 2 -bg white -relief groove
     pack $w.m -side top -fill x -padx 2 -pady 2
-    button $w.ok -text [mc "Close"] -command "destroy $w" -default active
+    ${NS}::button $w.ok -text [mc "Close"] -command "destroy $w" -default active
     pack $w.ok -side bottom
     bind $w <Visibility> "focus $w.ok"
     bind $w <Key-Escape> "destroy $w"
     bind $w <Key-Return> "destroy $w"
+    tk::PlaceWindow $w widget .
 }
 
 proc keys {} {
+    global NS
     set w .keys
     if {[winfo exists $w]} {
        raise $w
@@ -2675,13 +2812,14 @@ proc keys {} {
     } else {
        set M1T Ctrl
     }
-    toplevel $w
+    ttk_toplevel $w
     wm title $w [mc "Gitk key bindings"]
     make_transient $w .
     message $w.m -text "
 [mc "Gitk key bindings:"]
 
 [mc "<%s-Q>            Quit" $M1T]
+[mc "<%s-W>            Close window" $M1T]
 [mc "<Home>            Move to first commit"]
 [mc "<End>             Move to last commit"]
 [mc "<Up>, p, i        Move up one commit"]
@@ -2719,7 +2857,7 @@ proc keys {} {
 " \
            -justify left -bg white -border 2 -relief groove
     pack $w.m -side top -fill both -padx 2 -pady 2
-    button $w.ok -text [mc "Close"] -command "destroy $w" -default active
+    ${NS}::button $w.ok -text [mc "Close"] -command "destroy $w" -default active
     bind $w <Key-Escape> [list destroy $w]
     pack $w.ok -side bottom
     bind $w <Visibility> "focus $w.ok"
@@ -3163,6 +3301,28 @@ proc flist_hl {only} {
     set gdttype [mc "touching paths:"]
 }
 
+proc gitknewtmpdir {} {
+    global diffnum gitktmpdir gitdir
+
+    if {![info exists gitktmpdir]} {
+       set gitktmpdir [file join [file dirname $gitdir] \
+                           [format ".gitk-tmp.%s" [pid]]]
+       if {[catch {file mkdir $gitktmpdir} err]} {
+           error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err"
+           unset gitktmpdir
+           return {}
+       }
+       set diffnum 0
+    }
+    incr diffnum
+    set diffdir [file join $gitktmpdir $diffnum]
+    if {[catch {file mkdir $diffdir} err]} {
+       error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err"
+       return {}
+    }
+    return $diffdir
+}
+
 proc save_file_from_commit {filename output what} {
     global nullfile
 
@@ -3197,11 +3357,10 @@ proc external_diff_get_one_file {diffid filename diffdir} {
 }
 
 proc external_diff {} {
-    global gitktmpdir nullid nullid2
+    global nullid nullid2
     global flist_menu_file
     global diffids
-    global diffnum
-    global gitdir extdifftool
+    global extdifftool
 
     if {[llength $diffids] == 1} {
         # no reference commit given
@@ -3223,22 +3382,8 @@ proc external_diff {} {
     }
 
     # make sure that several diffs wont collide
-    if {![info exists gitktmpdir]} {
-       set gitktmpdir [file join [file dirname $gitdir] \
-                           [format ".gitk-tmp.%s" [pid]]]
-       if {[catch {file mkdir $gitktmpdir} err]} {
-           error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err"
-           unset gitktmpdir
-           return
-       }
-       set diffnum 0
-    }
-    incr diffnum
-    set diffdir [file join $gitktmpdir $diffnum]
-    if {[catch {file mkdir $diffdir} err]} {
-       error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err"
-       return
-    }
+    set diffdir [gitknewtmpdir]
+    if {$diffdir eq {}} return
 
     # gather files to diff
     set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
@@ -3365,6 +3510,9 @@ proc index_sha1 {fname} {
 
 # Turn an absolute path into one relative to the current directory
 proc make_relative {f} {
+    if {[file pathtype $f] eq "relative"} {
+       return $f
+    }
     set elts [file split $f]
     set here [file split [pwd]]
     set ei 0
@@ -3663,27 +3811,47 @@ proc newview {ishighlight} {
        raise $top
        return
     }
+    decode_view_opts $nextviewnum $revtreeargs
     set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
     set newviewopts($nextviewnum,perm) 0
     set newviewopts($nextviewnum,cmd)  $viewargscmd($curview)
-    decode_view_opts $nextviewnum $revtreeargs
     vieweditor $top $nextviewnum [mc "Gitk view definition"]
 }
 
 set known_view_options {
-    {perm    b    . {}               {mc "Remember this view"}}
-    {args    t50= + {}               {mc "Commits to include (arguments to git log):"}}
-    {all     b    * "--all"          {mc "Use all refs"}}
-    {dorder  b    . {"--date-order" "-d"}      {mc "Strictly sort by date"}}
-    {lright  b    . "--left-right"   {mc "Mark branch sides"}}
-    {since   t15  + {"--since=*" "--after=*"}  {mc "Since date:"}}
-    {until   t15  . {"--until=*" "--before=*"} {mc "Until date:"}}
-    {limit   t10  + "--max-count=*"  {mc "Max count:"}}
-    {skip    t10  . "--skip=*"       {mc "Skip:"}}
-    {first   b    . "--first-parent" {mc "Limit to first parent"}}
-    {cmd     t50= + {}               {mc "Command to generate more commits to include:"}}
-    }
-
+    {perm      b    .  {}               {mc "Remember this view"}}
+    {reflabel  l    +  {}               {mc "References (space separated list):"}}
+    {refs      t15  .. {}               {mc "Branches & tags:"}}
+    {allrefs   b    *. "--all"          {mc "All refs"}}
+    {branches  b    .  "--branches"     {mc "All (local) branches"}}
+    {tags      b    .  "--tags"         {mc "All tags"}}
+    {remotes   b    .  "--remotes"      {mc "All remote-tracking branches"}}
+    {commitlbl l    +  {}               {mc "Commit Info (regular expressions):"}}
+    {author    t15  .. "--author=*"     {mc "Author:"}}
+    {committer t15  .  "--committer=*"  {mc "Committer:"}}
+    {loginfo   t15  .. "--grep=*"       {mc "Commit Message:"}}
+    {allmatch  b    .. "--all-match"    {mc "Matches all Commit Info criteria"}}
+    {changes_l l    +  {}               {mc "Changes to Files:"}}
+    {pickaxe_s r0   .  {}               {mc "Fixed String"}}
+    {pickaxe_t r1   .  "--pickaxe-regex"  {mc "Regular Expression"}}
+    {pickaxe   t15  .. "-S*"            {mc "Search string:"}}
+    {datelabel l    +  {}               {mc "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 15:27:38\"):"}}
+    {since     t15  ..  {"--since=*" "--after=*"}  {mc "Since:"}}
+    {until     t15  .   {"--until=*" "--before=*"} {mc "Until:"}}
+    {limit_lbl l    +  {}               {mc "Limit and/or skip a number of revisions (positive integer):"}}
+    {limit     t10  *. "--max-count=*"  {mc "Number to show:"}}
+    {skip      t10  .  "--skip=*"       {mc "Number to skip:"}}
+    {misc_lbl  l    +  {}               {mc "Miscellaneous options:"}}
+    {dorder    b    *. {"--date-order" "-d"}      {mc "Strictly sort by date"}}
+    {lright    b    .  "--left-right"   {mc "Mark branch sides"}}
+    {first     b    .  "--first-parent" {mc "Limit to first parent"}}
+    {smplhst   b    .  "--simplify-by-decoration"   {mc "Simple history"}}
+    {args      t50  *. {}               {mc "Additional arguments to git log:"}}
+    {allpaths  path +  {}               {mc "Enter files and directories to include, one per line:"}}
+    {cmd       t50= +  {}               {mc "Command to generate more commits to include:"}}
+    }
+
+# Convert $newviewopts($n, ...) into args for git log.
 proc encode_view_opts {n} {
     global known_view_options newviewopts
 
@@ -3693,13 +3861,19 @@ proc encode_view_opts {n} {
        if {$patterns eq {}} continue
        set pattern [lindex $patterns 0]
 
-       set val $newviewopts($n,[lindex $opt 0])
-       
        if {[lindex $opt 1] eq "b"} {
+           set val $newviewopts($n,[lindex $opt 0])
            if {$val} {
                lappend rargs $pattern
            }
+       } elseif {[regexp {^r(\d+)$} [lindex $opt 1] type value]} {
+           regexp {^(.*_)} [lindex $opt 0] uselessvar button_id
+           set val $newviewopts($n,$button_id)
+           if {$val eq $value} {
+               lappend rargs $pattern
+           }
        } else {
+           set val $newviewopts($n,[lindex $opt 0])
            set val [string trim $val]
            if {$val ne {}} {
                set pfix [string range $pattern 0 end-1]
@@ -3707,21 +3881,31 @@ proc encode_view_opts {n} {
            }
        }
     }
+    set rargs [concat $rargs [shellsplit $newviewopts($n,refs)]]
     return [concat $rargs [shellsplit $newviewopts($n,args)]]
 }
 
+# Fill $newviewopts($n, ...) based on args for git log.
 proc decode_view_opts {n view_args} {
     global known_view_options newviewopts
 
     foreach opt $known_view_options {
+       set id [lindex $opt 0]
        if {[lindex $opt 1] eq "b"} {
+           # Checkboxes
+           set val 0
+        } elseif {[regexp {^r(\d+)$} [lindex $opt 1]]} {
+           # Radiobuttons
+           regexp {^(.*_)} $id uselessvar id
            set val 0
        } else {
+           # Text fields
            set val {}
        }
-       set newviewopts($n,[lindex $opt 0]) $val
+       set newviewopts($n,$id) $val
     }
     set oargs [list]
+    set refargs [list]
     foreach arg $view_args {
        if {[regexp -- {^-([0-9]+)$} $arg arg cnt]
            && ![info exists found(limit)]} {
@@ -3735,11 +3919,17 @@ proc decode_view_opts {n view_args} {
            if {[info exists found($id)]} continue
            foreach pattern [lindex $opt 3] {
                if {![string match $pattern $arg]} continue
-               if {[lindex $opt 1] ne "b"} {
+               if {[lindex $opt 1] eq "b"} {
+                   # Check buttons
+                   set val 1
+               } elseif {[regexp {^r(\d+)$} [lindex $opt 1] match num]} {
+                   # Radio buttons
+                   regexp {^(.*_)} $id uselessvar id
+                   set val $num
+               } else {
+                   # Text input fields
                    set size [string length $pattern]
                    set val [string range $arg [expr {$size-1}] end]
-               } else {
-                   set val 1
                }
                set newviewopts($n,$id) $val
                set found($id) 1
@@ -3748,8 +3938,13 @@ proc decode_view_opts {n view_args} {
            if {[info exists val]} break
        }
        if {[info exists val]} continue
-       lappend oargs $arg
+       if {[regexp {^-} $arg]} {
+           lappend oargs $arg
+       } else {
+           lappend refargs $arg
+       }
     }
+    set newviewopts($n,refs) [shellarglist $refargs]
     set newviewopts($n,args) [shellarglist $oargs]
 }
 
@@ -3773,28 +3968,28 @@ proc editview {} {
        raise $top
        return
     }
+    decode_view_opts $curview $viewargs($curview)
     set newviewname($curview)      $viewname($curview)
     set newviewopts($curview,perm) $viewperm($curview)
     set newviewopts($curview,cmd)  $viewargscmd($curview)
-    decode_view_opts $curview $viewargs($curview)
     vieweditor $top $curview "[mc "Gitk: edit view"] $viewname($curview)"
 }
 
 proc vieweditor {top n title} {
     global newviewname newviewopts viewfiles bgcolor
-    global known_view_options
+    global known_view_options NS
 
-    toplevel $top
-    wm title $top $title
+    ttk_toplevel $top
+    wm title $top [concat $title [mc "-- criteria for selecting revisions"]]
     make_transient $top .
 
     # View name
-    frame $top.nfr
-    label $top.nl -text [mc "Name"]
-    entry $top.name -width 20 -textvariable newviewname($n)
+    ${NS}::frame $top.nfr
+    ${NS}::label $top.nl -text [mc "View Name"]
+    ${NS}::entry $top.name -width 20 -textvariable newviewname($n)
     pack $top.nfr -in $top -fill x -pady 5 -padx 3
-    pack $top.nl -in $top.nfr -side left -padx {0 30}
-    pack $top.name -in $top.nfr -side left
+    pack $top.nl -in $top.nfr -side left -padx {0 5}
+    pack $top.name -in $top.nfr -side left -padx {0 25}
 
     # View options
     set cframe $top.nfr
@@ -3810,50 +4005,63 @@ proc vieweditor {top n title} {
        if {$flags eq "+" || $flags eq "*"} {
            set cframe $top.fr$cnt
            incr cnt
-           frame $cframe
+           ${NS}::frame $cframe
            pack $cframe -in $top -fill x -pady 3 -padx 3
            set cexpand [expr {$flags eq "*"}]
+        } elseif {$flags eq ".." || $flags eq "*."} {
+           set cframe $top.fr$cnt
+           incr cnt
+           ${NS}::frame $cframe
+           pack $cframe -in $top -fill x -pady 3 -padx [list 15 3]
+           set cexpand [expr {$flags eq "*."}]
        } else {
            set lxpad 5
        }
 
-       if {$type eq "b"} {
-           checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id)
+       if {$type eq "l"} {
+            ${NS}::label $cframe.l_$id -text $title
+            pack $cframe.l_$id -in $cframe -side left -pady [list 3 0] -anchor w
+       } elseif {$type eq "b"} {
+           ${NS}::checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id)
+           pack $cframe.c_$id -in $cframe -side left \
+               -padx [list $lxpad 0] -expand $cexpand -anchor w
+       } elseif {[regexp {^r(\d+)$} $type type sz]} {
+           regexp {^(.*_)} $id uselessvar button_id
+           ${NS}::radiobutton $cframe.c_$id -text $title -variable newviewopts($n,$button_id) -value $sz
            pack $cframe.c_$id -in $cframe -side left \
                -padx [list $lxpad 0] -expand $cexpand -anchor w
        } elseif {[regexp {^t(\d+)$} $type type sz]} {
-           message $cframe.l_$id -aspect 1500 -text $title
-           entry $cframe.e_$id -width $sz -background $bgcolor \
+           ${NS}::label $cframe.l_$id -text $title
+           ${NS}::entry $cframe.e_$id -width $sz -background $bgcolor \
                -textvariable newviewopts($n,$id)
            pack $cframe.l_$id -in $cframe -side left -padx [list $lxpad 0]
            pack $cframe.e_$id -in $cframe -side left -expand 1 -fill x
        } elseif {[regexp {^t(\d+)=$} $type type sz]} {
-           message $cframe.l_$id -aspect 1500 -text $title
-           entry $cframe.e_$id -width $sz -background $bgcolor \
+           ${NS}::label $cframe.l_$id -text $title
+           ${NS}::entry $cframe.e_$id -width $sz -background $bgcolor \
                -textvariable newviewopts($n,$id)
            pack $cframe.l_$id -in $cframe -side top -pady [list 3 0] -anchor w
            pack $cframe.e_$id -in $cframe -side top -fill x
+       } elseif {$type eq "path"} {
+           ${NS}::label $top.l -text $title
+           pack $top.l -in $top -side top -pady [list 3 0] -anchor w -padx 3
+           text $top.t -width 40 -height 5 -background $bgcolor
+           if {[info exists viewfiles($n)]} {
+               foreach f $viewfiles($n) {
+                   $top.t insert end $f
+                   $top.t insert end "\n"
+               }
+               $top.t delete {end - 1c} end
+               $top.t mark set insert 0.0
+           }
+           pack $top.t -in $top -side top -pady [list 0 5] -fill both -expand 1 -padx 3
        }
     }
 
-    # Path list
-    message $top.l -aspect 1500 \
-       -text [mc "Enter files and directories to include, one per line:"]
-    pack $top.l -in $top -side top -pady [list 7 0] -anchor w -padx 3
-    text $top.t -width 40 -height 5 -background $bgcolor -font uifont
-    if {[info exists viewfiles($n)]} {
-       foreach f $viewfiles($n) {
-           $top.t insert end $f
-           $top.t insert end "\n"
-       }
-       $top.t delete {end - 1c} end
-       $top.t mark set insert 0.0
-    }
-    pack $top.t -in $top -side top -pady [list 0 5] -fill both -expand 1 -padx 3
-    frame $top.buts
-    button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
-    button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1]
-    button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
+    ${NS}::frame $top.buts
+    ${NS}::button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
+    ${NS}::button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1]
+    ${NS}::button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
     bind $top <Control-Return> [list newviewok $top $n]
     bind $top <F5> [list newviewok $top $n 1]
     bind $top <Escape> [list destroy $top]
@@ -6717,14 +6925,13 @@ proc selectline {l isnew {desired_loc {}}} {
     make_secsel $id
 
     if {$isnew} {
-       addtohistory [list selbyid $id]
+       addtohistory [list selbyid $id 0] savecmitpos
     }
 
     $sha1entry delete 0 end
     $sha1entry insert 0 $id
     if {$autoselect} {
-       $sha1entry selection from 0
-       $sha1entry selection to end
+       $sha1entry selection range 0 end
     }
     rhighlight_sel $id
 
@@ -6869,10 +7076,12 @@ proc reselectline {} {
     }
 }
 
-proc addtohistory {cmd} {
+proc addtohistory {cmd {saveproc {}}} {
     global history historyindex curview
 
-    set elt [list $curview $cmd]
+    unset_posvars
+    save_position
+    set elt [list $curview $cmd $saveproc {}]
     if {$historyindex > 0
        && [lindex $history [expr {$historyindex - 1}]] == $elt} {
        return
@@ -6892,14 +7101,45 @@ proc addtohistory {cmd} {
     .tf.bar.rightbut conf -state disabled
 }
 
+# save the scrolling position of the diff display pane
+proc save_position {} {
+    global historyindex history
+
+    if {$historyindex < 1} return
+    set hi [expr {$historyindex - 1}]
+    set fn [lindex $history $hi 2]
+    if {$fn ne {}} {
+       lset history $hi 3 [eval $fn]
+    }
+}
+
+proc unset_posvars {} {
+    global last_posvars
+
+    if {[info exists last_posvars]} {
+       foreach {var val} $last_posvars {
+           global $var
+           catch {unset $var}
+       }
+       unset last_posvars
+    }
+}
+
 proc godo {elt} {
-    global curview
+    global curview last_posvars
 
     set view [lindex $elt 0]
     set cmd [lindex $elt 1]
+    set pv [lindex $elt 3]
     if {$curview != $view} {
        showview $view
     }
+    unset_posvars
+    foreach {var val} $pv {
+       global $var
+       set $var $val
+    }
+    set last_posvars $pv
     eval $cmd
 }
 
@@ -6908,6 +7148,7 @@ proc goback {} {
     focus .
 
     if {$historyindex > 1} {
+       save_position
        incr historyindex -1
        godo [lindex $history [expr {$historyindex - 1}]]
        .tf.bar.rightbut conf -state normal
@@ -6922,6 +7163,7 @@ proc goforw {} {
     focus .
 
     if {$historyindex < [llength $history]} {
+       save_position
        set cmd [lindex $history $historyindex]
        incr historyindex
        godo $cmd
@@ -7154,7 +7396,7 @@ proc diffcmd {ids flags} {
        set cmd [concat | git diff-index --cached $flags]
        if {[llength $ids] > 1} {
            # comparing index with specific revision
-           if {$i == 0} {
+           if {$j == 0} {
                lappend cmd -R [lindex $ids 1]
            } else {
                lappend cmd [lindex $ids 0]
@@ -7249,7 +7491,7 @@ proc diffcontextchange {n1 n2 op} {
     global diffcontextstring diffcontext
 
     if {[string is integer -strict $diffcontextstring]} {
-       if {$diffcontextstring > 0} {
+       if {$diffcontextstring >= 0} {
            set diffcontext $diffcontextstring
            reselectline
        }
@@ -7267,8 +7509,17 @@ proc getblobdiffs {ids} {
     global ignorespace
     global limitdiffs vfilelimit curview
     global diffencoding targetline diffnparents
+    global git_version currdiffsubmod
 
-    set cmd [diffcmd $ids "-p -C --cc --no-commit-id -U$diffcontext"]
+    set textconv {}
+    if {[package vcompare $git_version "1.6.1"] >= 0} {
+       set textconv "--textconv"
+    }
+    set submodule {}
+    if {[package vcompare $git_version "1.6.6"] >= 0} {
+       set submodule "--submodule"
+    }
+    set cmd [diffcmd $ids "-p $textconv $submodule  -C --cc --no-commit-id -U$diffcontext"]
     if {$ignorespace} {
        append cmd " -w"
     }
@@ -7285,9 +7536,38 @@ proc getblobdiffs {ids} {
     set diffencoding [get_path_encoding {}]
     fconfigure $bdf -blocking 0 -encoding binary -eofchar {}
     set blobdifffd($ids) $bdf
+    set currdiffsubmod ""
     filerun $bdf [list getblobdiffline $bdf $diffids]
 }
 
+proc savecmitpos {} {
+    global ctext cmitmode
+
+    if {$cmitmode eq "tree"} {
+       return {}
+    }
+    return [list target_scrollpos [$ctext index @0,0]]
+}
+
+proc savectextpos {} {
+    global ctext
+
+    return [list target_scrollpos [$ctext index @0,0]]
+}
+
+proc maybe_scroll_ctext {ateof} {
+    global ctext target_scrollpos
+
+    if {![info exists target_scrollpos]} return
+    if {!$ateof} {
+       set nlines [expr {[winfo height $ctext]
+                         / [font metrics textfont -linespace]}]
+       if {[$ctext compare "$target_scrollpos + $nlines lines" <= end]} return
+    }
+    $ctext yview $target_scrollpos
+    unset target_scrollpos
+}
+
 proc setinlist {var i val} {
     global $var
 
@@ -7327,13 +7607,13 @@ proc getblobdiffline {bdf ids} {
     global diffnexthead diffnextnote difffilestart
     global ctext_file_names ctext_file_lines
     global diffinhdr treediffs mergemax diffnparents
-    global diffencoding jump_to_here targetline diffline
+    global diffencoding jump_to_here targetline diffline currdiffsubmod
 
     set nr 0
     $ctext conf -state normal
     while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
        if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
-           close $bdf
+           catch {close $bdf}
            return 0
        }
        if {![string compare -length 5 "diff " $line]} {
@@ -7406,6 +7686,34 @@ proc getblobdiffline {bdf ids} {
            set diffnparents [expr {[string length $ats] - 1}]
            set diffinhdr 0
 
+       } elseif {![string compare -length 10 "Submodule " $line]} {
+           # start of a new submodule
+           if {[regexp -indices "\[0-9a-f\]+\\.\\." $line nameend]} {
+               set fname [string range $line 10 [expr [lindex $nameend 0] - 2]]
+           } else {
+               set fname [string range $line 10 [expr [string first "contains " $line] - 2]]
+           }
+           if {$currdiffsubmod != $fname} {
+               $ctext insert end "\n";     # Add newline after commit message
+           }
+           set curdiffstart [$ctext index "end - 1c"]
+           lappend ctext_file_names ""
+           if {$currdiffsubmod != $fname} {
+               lappend ctext_file_lines $fname
+               makediffhdr $fname $ids
+               set currdiffsubmod $fname
+               $ctext insert end "\n$line\n" filesep
+           } else {
+               $ctext insert end "$line\n" filesep
+           }
+       } elseif {![string compare -length 3 "  >" $line]} {
+           set $currdiffsubmod ""
+           set line [encoding convertfrom $diffencoding $line]
+           $ctext insert end "$line\n" dresult
+       } elseif {![string compare -length 3 "  <" $line]} {
+           set $currdiffsubmod ""
+           set line [encoding convertfrom $diffencoding $line]
+           $ctext insert end "$line\n" d0
        } elseif {$diffinhdr} {
            if {![string compare -length 12 "rename from " $line]} {
                set fname [string range $line [expr 6 + [string first " from " $line] ] end]
@@ -7483,9 +7791,10 @@ proc getblobdiffline {bdf ids} {
     if {[info exists seehere]} {
        mark_ctext_line [lindex [split $seehere .] 0]
     }
+    maybe_scroll_ctext [eof $bdf]
     $ctext conf -state disabled
     if {[eof $bdf]} {
-       close $bdf
+       catch {close $bdf}
        return 0
     }
     return [expr {$nr >= 1000? 2: 1}]
@@ -7842,6 +8151,11 @@ proc gotocommit {} {
                }
                set id [lindex $matches 0]
            }
+       } else {
+           if {[catch {set id [exec git rev-parse --verify $sha1string]}]} {
+               error_popup [mc "Revision %s is not known" $sha1string]
+               return
+           }
        }
     }
     if {[commitinview $id $curview]} {
@@ -7851,7 +8165,7 @@ proc gotocommit {} {
     if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
        set msg [mc "SHA1 id %s is not known" $sha1string]
     } else {
-       set msg [mc "Tag/Head %s is not known" $sha1string]
+       set msg [mc "Revision %s is not in the current view" $sha1string]
     }
     error_popup $msg
 }
@@ -7977,7 +8291,7 @@ proc lineclick {x y id isnew} {
     }
 
     if {$isnew} {
-       addtohistory [list lineclick $x $y $id 0]
+       addtohistory [list lineclick $x $y $id 0] savectextpos
     }
     # fill the details pane with info about this line
     $ctext conf -state normal
@@ -8008,6 +8322,7 @@ proc lineclick {x y id isnew} {
            $ctext insert end "\n\t[mc "Date"]:\t$date\n"
        }
     }
+    maybe_scroll_ctext 1
     $ctext conf -state disabled
     init_flist {}
 }
@@ -8021,10 +8336,10 @@ proc normalline {} {
     }
 }
 
-proc selbyid {id} {
+proc selbyid {id {isnew 1}} {
     global curview
     if {[commitinview $id $curview]} {
-       selectline [rowofcommit $id] 1
+       selectline [rowofcommit $id] $isnew
     }
 }
 
@@ -8201,33 +8516,62 @@ proc do_cmp_commits {a b} {
                appendshortlink $a [mc "Commit "] "  $heada\n"
                appendshortlink $b [mc " differs from\n       "] \
                    "  $headb\n"
-               $ctext insert end [mc "- stopping\n"]
-               break
+               $ctext insert end [mc "Diff of commits:\n\n"]
+               $ctext conf -state disabled
+               update
+               diffcommits $a $b
+               return
            }
        }
        if {$skipa} {
-           if {[llength $children($curview,$a)] != 1} {
+           set kids [real_children $curview,$a]
+           if {[llength $kids] != 1} {
                $ctext insert end "\n"
                appendshortlink $a [mc "Commit "] \
-                   [mc " has %s children - stopping\n" \
-                        [llength $children($curview,$a)]]
+                   [mc " has %s children - stopping\n" [llength $kids]]
                break
            }
-           set a [lindex $children($curview,$a) 0]
+           set a [lindex $kids 0]
        }
        if {$skipb} {
-           if {[llength $children($curview,$b)] != 1} {
+           set kids [real_children $curview,$b]
+           if {[llength $kids] != 1} {
                appendshortlink $b [mc "Commit "] \
-                   [mc " has %s children - stopping\n" \
-                        [llength $children($curview,$b)]]
+                   [mc " has %s children - stopping\n" [llength $kids]]
                break
            }
-           set b [lindex $children($curview,$b) 0]
+           set b [lindex $kids 0]
        }
     }
     $ctext conf -state disabled
 }
 
+proc diffcommits {a b} {
+    global diffcontext diffids blobdifffd diffinhdr currdiffsubmod
+
+    set tmpdir [gitknewtmpdir]
+    set fna [file join $tmpdir "commit-[string range $a 0 7]"]
+    set fnb [file join $tmpdir "commit-[string range $b 0 7]"]
+    if {[catch {
+       exec git diff-tree -p --pretty $a >$fna
+       exec git diff-tree -p --pretty $b >$fnb
+    } err]} {
+       error_popup [mc "Error writing commit to file: %s" $err]
+       return
+    }
+    if {[catch {
+       set fd [open "| diff -U$diffcontext $fna $fnb" r]
+    } err]} {
+       error_popup [mc "Error diffing commits: %s" $err]
+       return
+    }
+    set diffids [list commits $a $b]
+    set blobdifffd($diffids) $fd
+    set diffinhdr 0
+    set currdiffsubmod ""
+    filerun $fd [list getblobdiffline $fd $diffids]
+}
+
 proc diffvssel {dirn} {
     global rowmenuid selectedline
 
@@ -8239,7 +8583,7 @@ proc diffvssel {dirn} {
        set oldid $rowmenuid
        set newid [commitonrow $selectedline]
     }
-    addtohistory [list doseldiff $oldid $newid]
+    addtohistory [list doseldiff $oldid $newid] savectextpos
     doseldiff $oldid $newid
 }
 
@@ -8267,7 +8611,7 @@ proc doseldiff {oldid newid} {
 }
 
 proc mkpatch {} {
-    global rowmenuid currentid commitinfo patchtop patchnum
+    global rowmenuid currentid commitinfo patchtop patchnum NS
 
     if {![info exists currentid]} return
     set oldid $currentid
@@ -8277,38 +8621,38 @@ proc mkpatch {} {
     set top .patch
     set patchtop $top
     catch {destroy $top}
-    toplevel $top
+    ttk_toplevel $top
     make_transient $top .
-    label $top.title -text [mc "Generate patch"]
+    ${NS}::label $top.title -text [mc "Generate patch"]
     grid $top.title - -pady 10
-    label $top.from -text [mc "From:"]
-    entry $top.fromsha1 -width 40 -relief flat
+    ${NS}::label $top.from -text [mc "From:"]
+    ${NS}::entry $top.fromsha1 -width 40
     $top.fromsha1 insert 0 $oldid
     $top.fromsha1 conf -state readonly
     grid $top.from $top.fromsha1 -sticky w
-    entry $top.fromhead -width 60 -relief flat
+    ${NS}::entry $top.fromhead -width 60
     $top.fromhead insert 0 $oldhead
     $top.fromhead conf -state readonly
     grid x $top.fromhead -sticky w
-    label $top.to -text [mc "To:"]
-    entry $top.tosha1 -width 40 -relief flat
+    ${NS}::label $top.to -text [mc "To:"]
+    ${NS}::entry $top.tosha1 -width 40
     $top.tosha1 insert 0 $newid
     $top.tosha1 conf -state readonly
     grid $top.to $top.tosha1 -sticky w
-    entry $top.tohead -width 60 -relief flat
+    ${NS}::entry $top.tohead -width 60
     $top.tohead insert 0 $newhead
     $top.tohead conf -state readonly
     grid x $top.tohead -sticky w
-    button $top.rev -text [mc "Reverse"] -command mkpatchrev -padx 5
-    grid $top.rev x -pady 10
-    label $top.flab -text [mc "Output file:"]
-    entry $top.fname -width 60
+    ${NS}::button $top.rev -text [mc "Reverse"] -command mkpatchrev
+    grid $top.rev x -pady 10 -padx 5
+    ${NS}::label $top.flab -text [mc "Output file:"]
+    ${NS}::entry $top.fname -width 60
     $top.fname insert 0 [file normalize "patch$patchnum.patch"]
     incr patchnum
     grid $top.flab $top.fname -sticky w
-    frame $top.buts
-    button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
-    button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
+    ${NS}::frame $top.buts
+    ${NS}::button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
+    ${NS}::button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
     bind $top <Key-Return> mkpatchgo
     bind $top <Key-Escape> mkpatchcan
     grid $top.buts.gen $top.buts.can
@@ -8359,30 +8703,35 @@ proc mkpatchcan {} {
 }
 
 proc mktag {} {
-    global rowmenuid mktagtop commitinfo
+    global rowmenuid mktagtop commitinfo NS
 
     set top .maketag
     set mktagtop $top
     catch {destroy $top}
-    toplevel $top
+    ttk_toplevel $top
     make_transient $top .
-    label $top.title -text [mc "Create tag"]
+    ${NS}::label $top.title -text [mc "Create tag"]
     grid $top.title - -pady 10
-    label $top.id -text [mc "ID:"]
-    entry $top.sha1 -width 40 -relief flat
+    ${NS}::label $top.id -text [mc "ID:"]
+    ${NS}::entry $top.sha1 -width 40
     $top.sha1 insert 0 $rowmenuid
     $top.sha1 conf -state readonly
     grid $top.id $top.sha1 -sticky w
-    entry $top.head -width 60 -relief flat
+    ${NS}::entry $top.head -width 60
     $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
     $top.head conf -state readonly
     grid x $top.head -sticky w
-    label $top.tlab -text [mc "Tag name:"]
-    entry $top.tag -width 60
+    ${NS}::label $top.tlab -text [mc "Tag name:"]
+    ${NS}::entry $top.tag -width 60
     grid $top.tlab $top.tag -sticky w
-    frame $top.buts
-    button $top.buts.gen -text [mc "Create"] -command mktaggo
-    button $top.buts.can -text [mc "Cancel"] -command mktagcan
+    ${NS}::label $top.op -text [mc "Tag message is optional"]
+    grid $top.op -columnspan 2 -sticky we
+    ${NS}::label $top.mlab -text [mc "Tag message:"]
+    ${NS}::entry $top.msg -width 60
+    grid $top.mlab $top.msg -sticky w
+    ${NS}::frame $top.buts
+    ${NS}::button $top.buts.gen -text [mc "Create"] -command mktaggo
+    ${NS}::button $top.buts.can -text [mc "Cancel"] -command mktagcan
     bind $top <Key-Return> mktaggo
     bind $top <Key-Escape> mktagcan
     grid $top.buts.gen $top.buts.can
@@ -8397,6 +8746,7 @@ proc domktag {} {
 
     set id [$mktagtop.sha1 get]
     set tag [$mktagtop.tag get]
+    set msg [$mktagtop.msg get]
     if {$tag == {}} {
        error_popup [mc "No tag name specified"] $mktagtop
        return 0
@@ -8406,7 +8756,11 @@ proc domktag {} {
        return 0
     }
     if {[catch {
-       exec git tag $tag $id
+       if {$msg != {}} {
+           exec git tag -a -m $msg $tag $id
+       } else {
+           exec git tag $tag $id
+       }
     } err]} {
        error_popup "[mc "Error creating tag:"] $err" $mktagtop
        return 0
@@ -8465,34 +8819,34 @@ proc mktaggo {} {
 }
 
 proc writecommit {} {
-    global rowmenuid wrcomtop commitinfo wrcomcmd
+    global rowmenuid wrcomtop commitinfo wrcomcmd NS
 
     set top .writecommit
     set wrcomtop $top
     catch {destroy $top}
-    toplevel $top
+    ttk_toplevel $top
     make_transient $top .
-    label $top.title -text [mc "Write commit to file"]
+    ${NS}::label $top.title -text [mc "Write commit to file"]
     grid $top.title - -pady 10
-    label $top.id -text [mc "ID:"]
-    entry $top.sha1 -width 40 -relief flat
+    ${NS}::label $top.id -text [mc "ID:"]
+    ${NS}::entry $top.sha1 -width 40
     $top.sha1 insert 0 $rowmenuid
     $top.sha1 conf -state readonly
     grid $top.id $top.sha1 -sticky w
-    entry $top.head -width 60 -relief flat
+    ${NS}::entry $top.head -width 60
     $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
     $top.head conf -state readonly
     grid x $top.head -sticky w
-    label $top.clab -text [mc "Command:"]
-    entry $top.cmd -width 60 -textvariable wrcomcmd
+    ${NS}::label $top.clab -text [mc "Command:"]
+    ${NS}::entry $top.cmd -width 60 -textvariable wrcomcmd
     grid $top.clab $top.cmd -sticky w -pady 10
-    label $top.flab -text [mc "Output file:"]
-    entry $top.fname -width 60
+    ${NS}::label $top.flab -text [mc "Output file:"]
+    ${NS}::entry $top.fname -width 60
     $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
     grid $top.flab $top.fname -sticky w
-    frame $top.buts
-    button $top.buts.gen -text [mc "Write"] -command wrcomgo
-    button $top.buts.can -text [mc "Cancel"] -command wrcomcan
+    ${NS}::frame $top.buts
+    ${NS}::button $top.buts.gen -text [mc "Write"] -command wrcomgo
+    ${NS}::button $top.buts.can -text [mc "Cancel"] -command wrcomcan
     bind $top <Key-Return> wrcomgo
     bind $top <Key-Escape> wrcomcan
     grid $top.buts.gen $top.buts.can
@@ -8523,25 +8877,25 @@ proc wrcomcan {} {
 }
 
 proc mkbranch {} {
-    global rowmenuid mkbrtop
+    global rowmenuid mkbrtop NS
 
     set top .makebranch
     catch {destroy $top}
-    toplevel $top
+    ttk_toplevel $top
     make_transient $top .
-    label $top.title -text [mc "Create new branch"]
+    ${NS}::label $top.title -text [mc "Create new branch"]
     grid $top.title - -pady 10
-    label $top.id -text [mc "ID:"]
-    entry $top.sha1 -width 40 -relief flat
+    ${NS}::label $top.id -text [mc "ID:"]
+    ${NS}::entry $top.sha1 -width 40
     $top.sha1 insert 0 $rowmenuid
     $top.sha1 conf -state readonly
     grid $top.id $top.sha1 -sticky w
-    label $top.nlab -text [mc "Name:"]
-    entry $top.name -width 40
+    ${NS}::label $top.nlab -text [mc "Name:"]
+    ${NS}::entry $top.name -width 40
     grid $top.nlab $top.name -sticky w
-    frame $top.buts
-    button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
-    button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
+    ${NS}::frame $top.buts
+    ${NS}::button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
+    ${NS}::button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
     bind $top <Key-Return> [list mkbrgo $top]
     bind $top <Key-Escape> "catch {destroy $top}"
     grid $top.buts.go $top.buts.can
@@ -8686,34 +9040,31 @@ proc cherrypick {} {
 }
 
 proc resethead {} {
-    global mainhead rowmenuid confirm_ok resettype
+    global mainhead rowmenuid confirm_ok resettype NS
 
     set confirm_ok 0
     set w ".confirmreset"
-    toplevel $w
+    ttk_toplevel $w
     make_transient $w .
     wm title $w [mc "Confirm reset"]
-    message $w.m -text \
-       [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \
-       -justify center -aspect 1000
+    ${NS}::label $w.m -text \
+       [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]]
     pack $w.m -side top -fill x -padx 20 -pady 20
-    frame $w.f -relief sunken -border 2
-    message $w.f.rt -text [mc "Reset type:"] -aspect 1000
-    grid $w.f.rt -sticky w
+    ${NS}::labelframe $w.f -text [mc "Reset type:"]
     set resettype mixed
-    radiobutton $w.f.soft -value soft -variable resettype -justify left \
+    ${NS}::radiobutton $w.f.soft -value soft -variable resettype \
        -text [mc "Soft: Leave working tree and index untouched"]
     grid $w.f.soft -sticky w
-    radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
+    ${NS}::radiobutton $w.f.mixed -value mixed -variable resettype \
        -text [mc "Mixed: Leave working tree untouched, reset index"]
     grid $w.f.mixed -sticky w
-    radiobutton $w.f.hard -value hard -variable resettype -justify left \
+    ${NS}::radiobutton $w.f.hard -value hard -variable resettype \
        -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
     grid $w.f.hard -sticky w
-    pack $w.f -side top -fill x
-    button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
+    pack $w.f -side top -fill x -padx 4
+    ${NS}::button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
     pack $w.ok -side left -fill x -padx 20 -pady 20
-    button $w.cancel -text [mc Cancel] -command "destroy $w"
+    ${NS}::button $w.cancel -text [mc Cancel] -command "destroy $w"
     bind $w <Key-Escape> [list destroy $w]
     pack $w.cancel -side right -fill x -padx 20 -pady 20
     bind $w <Visibility> "grab $w; focus $w"
@@ -8769,6 +9120,9 @@ proc headmenu {x y id head} {
     set headmenuid $id
     set headmenuhead $head
     set state normal
+    if {[string match "remotes/*" $head]} {
+       set state disabled
+    }
     if {$head eq $mainhead} {
        set state disabled
     }
@@ -8861,7 +9215,7 @@ proc rmbranch {} {
 
 # Display a list of tags and heads
 proc showrefs {} {
-    global showrefstop bgcolor fgcolor selectbgcolor
+    global showrefstop bgcolor fgcolor selectbgcolor NS
     global bglist fglist reflistfilter reflist maincursor
 
     set top .showrefs
@@ -8871,7 +9225,7 @@ proc showrefs {} {
        refill_reflist
        return
     }
-    toplevel $top
+    ttk_toplevel $top
     wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
     make_transient $top .
     text $top.list -background $bgcolor -foreground $fgcolor \
@@ -8882,19 +9236,19 @@ proc showrefs {} {
     $top.list tag configure highlight -background $selectbgcolor
     lappend bglist $top.list
     lappend fglist $top.list
-    scrollbar $top.ysb -command "$top.list yview" -orient vertical
-    scrollbar $top.xsb -command "$top.list xview" -orient horizontal
+    ${NS}::scrollbar $top.ysb -command "$top.list yview" -orient vertical
+    ${NS}::scrollbar $top.xsb -command "$top.list xview" -orient horizontal
     grid $top.list $top.ysb -sticky nsew
     grid $top.xsb x -sticky ew
-    frame $top.f
-    label $top.f.l -text "[mc "Filter"]: "
-    entry $top.f.e -width 20 -textvariable reflistfilter
+    ${NS}::frame $top.f
+    ${NS}::label $top.f.l -text "[mc "Filter"]: "
+    ${NS}::entry $top.f.e -width 20 -textvariable reflistfilter
     set reflistfilter "*"
     trace add variable reflistfilter write reflistfilter_change
     pack $top.f.e -side right -fill x -expand 1
     pack $top.f.l -side left
     grid $top.f - -sticky ew -pady 2
-    button $top.close -command [list destroy $top] -text [mc "Close"]
+    ${NS}::button $top.close -command [list destroy $top] -text [mc "Close"]
     bind $top <Key-Escape> [list destroy $top]
     grid $top.close -
     grid columnconfigure $top 0 -weight 1
@@ -9092,7 +9446,7 @@ proc getallclines {fd} {
     global allparents allchildren idtags idheads nextarc
     global arcnos arcids arctags arcout arcend arcstart archeads growing
     global seeds allcommits cachedarcs allcupdate
-    
+
     set nid 0
     while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
        set id [lindex $line 0]
@@ -10154,7 +10508,7 @@ proc showtag {tag isnew} {
     global ctext tagcontents tagids linknum tagobjid
 
     if {$isnew} {
-       addtohistory [list showtag $tag 0]
+       addtohistory [list showtag $tag 0] savectextpos
     }
     $ctext conf -state normal
     clear_ctext
@@ -10162,7 +10516,7 @@ proc showtag {tag isnew} {
     set linknum 0
     if {![info exists tagcontents($tag)]} {
        catch {
-           set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
+           set tagcontents($tag) [exec git cat-file tag $tag]
        }
     }
     if {[info exists tagcontents($tag)]} {
@@ -10171,6 +10525,7 @@ proc showtag {tag isnew} {
        set text "[mc "Tag"]: $tag\n[mc "Id"]:  $tagids($tag)"
     }
     appendwithlinks $text {}
+    maybe_scroll_ctext 1
     $ctext conf -state disabled
     init_flist {}
 }
@@ -10189,19 +10544,19 @@ proc doquit {} {
 }
 
 proc mkfontdisp {font top which} {
-    global fontattr fontpref $font
+    global fontattr fontpref $font NS use_ttk
 
     set fontpref($font) [set $font]
-    button $top.${font}but -text $which -font optionfont \
+    ${NS}::button $top.${font}but -text $which \
        -command [list choosefont $font $which]
-    label $top.$font -relief flat -font $font \
+    ${NS}::label $top.$font -relief flat -font $font \
        -text $fontattr($font,family) -justify left
     grid x $top.${font}but $top.$font -sticky w
 }
 
 proc choosefont {font which} {
     global fontparam fontlist fonttop fontattr
-    global prefstop
+    global prefstop NS
 
     set fontparam(which) $which
     set fontparam(font) $font
@@ -10214,21 +10569,21 @@ proc choosefont {font which} {
     if {![winfo exists $top]} {
        font create sample
        eval font config sample [font actual $font]
-       toplevel $top
+       ttk_toplevel $top
        make_transient $top $prefstop
        wm title $top [mc "Gitk font chooser"]
-       label $top.l -textvariable fontparam(which)
+       ${NS}::label $top.l -textvariable fontparam(which)
        pack $top.l -side top
        set fontlist [lsort [font families]]
-       frame $top.f
+       ${NS}::frame $top.f
        listbox $top.f.fam -listvariable fontlist \
            -yscrollcommand [list $top.f.sb set]
        bind $top.f.fam <<ListboxSelect>> selfontfam
-       scrollbar $top.f.sb -command [list $top.f.fam yview]
+       ${NS}::scrollbar $top.f.sb -command [list $top.f.fam yview]
        pack $top.f.sb -side right -fill y
        pack $top.f.fam -side left -fill both -expand 1
        pack $top.f -side top -fill both -expand 1
-       frame $top.g
+       ${NS}::frame $top.g
        spinbox $top.g.size -from 4 -to 40 -width 4 \
            -textvariable fontparam(size) \
            -validatecommand {string is integer -strict %s}
@@ -10246,9 +10601,9 @@ proc choosefont {font which} {
            -fill black -tags text
        bind $top.c <Configure> [list centertext $top.c]
        pack $top.c -side top -fill x
-       frame $top.buts
-       button $top.buts.ok -text [mc "OK"] -command fontok -default active
-       button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
+       ${NS}::frame $top.buts
+       ${NS}::button $top.buts.ok -text [mc "OK"] -command fontok -default active
+       ${NS}::button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
        bind $top <Key-Return> fontok
        bind $top <Key-Escape> fontcan
        grid $top.buts.ok $top.buts.can
@@ -10284,7 +10639,7 @@ proc fontok {} {
     }
     set w $prefstop.$f
     $w conf -text $fontparam(family) -font $fontpref($f)
-       
+
     fontcan
 }
 
@@ -10299,6 +10654,28 @@ proc fontcan {} {
     }
 }
 
+if {[package vsatisfies [package provide Tk] 8.6]} {
+    # In Tk 8.6 we have a native font chooser dialog. Overwrite the above
+    # function to make use of it.
+    proc choosefont {font which} {
+       tk fontchooser configure -title $which -font $font \
+           -command [list on_choosefont $font $which]
+       tk fontchooser show
+    }
+    proc on_choosefont {font which newfont} {
+       global fontparam
+       puts stderr "$font $newfont"
+       array set f [font actual $newfont]
+       set fontparam(which) $which
+       set fontparam(font) $font
+       set fontparam(family) $f(-family)
+       set fontparam(size) $f(-size)
+       set fontparam(weight) $f(-weight)
+       set fontparam(slant) $f(-slant)
+       fontok
+    }
+}
+
 proc selfontfam {} {
     global fonttop fontparam
 
@@ -10315,10 +10692,11 @@ proc chg_fontparam {v sub op} {
 }
 
 proc doprefs {} {
-    global maxwidth maxgraphpct
+    global maxwidth maxgraphpct use_ttk NS
     global oldprefs prefstop showneartags showlocalchanges
-    global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
+    global uicolor bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
     global tabstop limitdiffs autoselect extdifftool perfile_attrs
+    global hideremotes want_ttk have_ttk
 
     set top .gitkprefs
     set prefstop $top
@@ -10327,106 +10705,122 @@ proc doprefs {} {
        return
     }
     foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
-                  limitdiffs tabstop perfile_attrs} {
+                  limitdiffs tabstop perfile_attrs hideremotes want_ttk} {
        set oldprefs($v) [set $v]
     }
-    toplevel $top
+    ttk_toplevel $top
     wm title $top [mc "Gitk preferences"]
     make_transient $top .
-    label $top.ldisp -text [mc "Commit list display options"]
+    ${NS}::label $top.ldisp -text [mc "Commit list display options"]
     grid $top.ldisp - -sticky w -pady 10
-    label $top.spacer -text " "
-    label $top.maxwidthl -text [mc "Maximum graph width (lines)"] \
-       -font optionfont
+    ${NS}::label $top.spacer -text " "
+    ${NS}::label $top.maxwidthl -text [mc "Maximum graph width (lines)"]
     spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
     grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
-    label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] \
-       -font optionfont
+    ${NS}::label $top.maxpctl -text [mc "Maximum graph width (% of pane)"]
     spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
     grid x $top.maxpctl $top.maxpct -sticky w
-    checkbutton $top.showlocal -text [mc "Show local changes"] \
-       -font optionfont -variable showlocalchanges
+    ${NS}::checkbutton $top.showlocal -text [mc "Show local changes"] \
+       -variable showlocalchanges
     grid x $top.showlocal -sticky w
-    checkbutton $top.autoselect -text [mc "Auto-select SHA1"] \
-       -font optionfont -variable autoselect
+    ${NS}::checkbutton $top.autoselect -text [mc "Auto-select SHA1"] \
+       -variable autoselect
     grid x $top.autoselect -sticky w
+    ${NS}::checkbutton $top.hideremotes -text [mc "Hide remote refs"] \
+       -variable hideremotes
+    grid x $top.hideremotes -sticky w
 
-    label $top.ddisp -text [mc "Diff display options"]
+    ${NS}::label $top.ddisp -text [mc "Diff display options"]
     grid $top.ddisp - -sticky w -pady 10
-    label $top.tabstopl -text [mc "Tab spacing"] -font optionfont
+    ${NS}::label $top.tabstopl -text [mc "Tab spacing"]
     spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
     grid x $top.tabstopl $top.tabstop -sticky w
-    checkbutton $top.ntag -text [mc "Display nearby tags"] \
-       -font optionfont -variable showneartags
+    ${NS}::checkbutton $top.ntag -text [mc "Display nearby tags"] \
+       -variable showneartags
     grid x $top.ntag -sticky w
-    checkbutton $top.ldiff -text [mc "Limit diffs to listed paths"] \
-       -font optionfont -variable limitdiffs
+    ${NS}::checkbutton $top.ldiff -text [mc "Limit diffs to listed paths"] \
+       -variable limitdiffs
     grid x $top.ldiff -sticky w
-    checkbutton $top.lattr -text [mc "Support per-file encodings"] \
-       -font optionfont -variable perfile_attrs
+    ${NS}::checkbutton $top.lattr -text [mc "Support per-file encodings"] \
+       -variable perfile_attrs
     grid x $top.lattr -sticky w
 
-    entry $top.extdifft -textvariable extdifftool
-    frame $top.extdifff
-    label $top.extdifff.l -text [mc "External diff tool" ] -font optionfont \
-       -padx 10
-    button $top.extdifff.b -text [mc "Choose..."] -font optionfont \
-       -command choose_extdiff
+    ${NS}::entry $top.extdifft -textvariable extdifftool
+    ${NS}::frame $top.extdifff
+    ${NS}::label $top.extdifff.l -text [mc "External diff tool" ]
+    ${NS}::button $top.extdifff.b -text [mc "Choose..."] -command choose_extdiff
     pack $top.extdifff.l $top.extdifff.b -side left
-    grid x $top.extdifff $top.extdifft -sticky w
+    pack configure $top.extdifff.l -padx 10
+    grid x $top.extdifff $top.extdifft -sticky ew
+
+    ${NS}::label $top.lgen -text [mc "General options"]
+    grid $top.lgen - -sticky w -pady 10
+    ${NS}::checkbutton $top.want_ttk -variable want_ttk \
+       -text [mc "Use themed widgets"]
+    if {$have_ttk} {
+       ${NS}::label $top.ttk_note -text [mc "(change requires restart)"]
+    } else {
+       ${NS}::label $top.ttk_note -text [mc "(currently unavailable)"]
+    }
+    grid x $top.want_ttk $top.ttk_note -sticky w
 
-    label $top.cdisp -text [mc "Colors: press to choose"]
+    ${NS}::label $top.cdisp -text [mc "Colors: press to choose"]
     grid $top.cdisp - -sticky w -pady 10
+    label $top.ui -padx 40 -relief sunk -background $uicolor
+    ${NS}::button $top.uibut -text [mc "Interface"] \
+       -command [list choosecolor uicolor {} $top.ui [mc "interface"] setui]
+    grid x $top.uibut $top.ui -sticky w
     label $top.bg -padx 40 -relief sunk -background $bgcolor
-    button $top.bgbut -text [mc "Background"] -font optionfont \
+    ${NS}::button $top.bgbut -text [mc "Background"] \
        -command [list choosecolor bgcolor {} $top.bg [mc "background"] setbg]
     grid x $top.bgbut $top.bg -sticky w
     label $top.fg -padx 40 -relief sunk -background $fgcolor
-    button $top.fgbut -text [mc "Foreground"] -font optionfont \
+    ${NS}::button $top.fgbut -text [mc "Foreground"] \
        -command [list choosecolor fgcolor {} $top.fg [mc "foreground"] setfg]
     grid x $top.fgbut $top.fg -sticky w
     label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
-    button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \
+    ${NS}::button $top.diffoldbut -text [mc "Diff: old lines"] \
        -command [list choosecolor diffcolors 0 $top.diffold [mc "diff old lines"] \
                      [list $ctext tag conf d0 -foreground]]
     grid x $top.diffoldbut $top.diffold -sticky w
     label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
-    button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \
+    ${NS}::button $top.diffnewbut -text [mc "Diff: new lines"] \
        -command [list choosecolor diffcolors 1 $top.diffnew [mc "diff new lines"] \
                      [list $ctext tag conf dresult -foreground]]
     grid x $top.diffnewbut $top.diffnew -sticky w
     label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
-    button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \
+    ${NS}::button $top.hunksepbut -text [mc "Diff: hunk header"] \
        -command [list choosecolor diffcolors 2 $top.hunksep \
                      [mc "diff hunk header"] \
                      [list $ctext tag conf hunksep -foreground]]
     grid x $top.hunksepbut $top.hunksep -sticky w
     label $top.markbgsep -padx 40 -relief sunk -background $markbgcolor
-    button $top.markbgbut -text [mc "Marked line bg"] -font optionfont \
+    ${NS}::button $top.markbgbut -text [mc "Marked line bg"] \
        -command [list choosecolor markbgcolor {} $top.markbgsep \
                      [mc "marked line background"] \
                      [list $ctext tag conf omark -background]]
     grid x $top.markbgbut $top.markbgsep -sticky w
     label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
-    button $top.selbgbut -text [mc "Select bg"] -font optionfont \
+    ${NS}::button $top.selbgbut -text [mc "Select bg"] \
        -command [list choosecolor selectbgcolor {} $top.selbgsep [mc "background"] setselbg]
     grid x $top.selbgbut $top.selbgsep -sticky w
 
-    label $top.cfont -text [mc "Fonts: press to choose"]
+    ${NS}::label $top.cfont -text [mc "Fonts: press to choose"]
     grid $top.cfont - -sticky w -pady 10
     mkfontdisp mainfont $top [mc "Main font"]
     mkfontdisp textfont $top [mc "Diff display font"]
     mkfontdisp uifont $top [mc "User interface font"]
 
-    frame $top.buts
-    button $top.buts.ok -text [mc "OK"] -command prefsok -default active
-    button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
+    ${NS}::frame $top.buts
+    ${NS}::button $top.buts.ok -text [mc "OK"] -command prefsok -default active
+    ${NS}::button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
     bind $top <Key-Return> prefsok
     bind $top <Key-Escape> prefscan
     grid $top.buts.ok $top.buts.can
     grid columnconfigure $top.buts 0 -weight 1 -uniform a
     grid columnconfigure $top.buts 1 -weight 1 -uniform a
     grid $top.buts - - -pady 10 -sticky ew
+    grid columnconfigure $top 2 -weight 1
     bind $top <Visibility> "focus $top.buts.ok"
 }
 
@@ -10460,6 +10854,21 @@ proc setselbg {c} {
     allcanvs itemconf secsel -fill $c
 }
 
+# This sets the background color and the color scheme for the whole UI.
+# For some reason, tk_setPalette chooses a nasty dark red for selectColor
+# if we don't specify one ourselves, which makes the checkbuttons and
+# radiobuttons look bad.  This chooses white for selectColor if the
+# background color is light, or black if it is dark.
+proc setui {c} {
+    if {[tk windowingsystem] eq "win32"} { return }
+    set bg [winfo rgb . $c]
+    set selc black
+    if {[lindex $bg 0] + 1.5 * [lindex $bg 1] + 0.5 * [lindex $bg 2] > 100000} {
+       set selc white
+    }
+    tk_setPalette background $c selectColor $selc
+}
+
 proc setbg {c} {
     global bglist
 
@@ -10483,7 +10892,7 @@ proc prefscan {} {
     global oldprefs prefstop
 
     foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
-                  limitdiffs tabstop perfile_attrs} {
+                  limitdiffs tabstop perfile_attrs hideremotes want_ttk} {
        global $v
        set $v $oldprefs($v)
     }
@@ -10497,6 +10906,7 @@ proc prefsok {} {
     global oldprefs prefstop showneartags showlocalchanges
     global fontpref mainfont textfont uifont
     global limitdiffs treediffs perfile_attrs
+    global hideremotes
 
     catch {destroy $prefstop}
     unset prefstop
@@ -10542,6 +10952,9 @@ proc prefsok {} {
          $limitdiffs != $oldprefs(limitdiffs)} {
        reselectline
     }
+    if {$hideremotes != $oldprefs(hideremotes)} {
+       rereadrefs
+    }
 }
 
 proc formatdate {d} {
@@ -10837,7 +11250,7 @@ proc gitattr {path attr default} {
     } else {
        set r "unspecified"
        if {![catch {set line [exec git check-attr $attr -- $path]}]} {
-           regexp "(.*): encoding: (.*)" $line m f r
+           regexp "(.*): $attr: (.*)" $line m f r
        }
        set path_attr_cache($attr,$path) $r
     }
@@ -10865,7 +11278,7 @@ proc cache_gitattr {attr pathlist} {
        set newlist [lrange $newlist $lim end]
        if {![catch {set rlist [eval exec git check-attr $attr -- $head]}]} {
            foreach row [split $rlist "\n"] {
-               if {[regexp "(.*): encoding: (.*)" $row m path value]} {
+               if {[regexp "(.*): $attr: (.*)" $row m path value]} {
                    if {[string index $path 0] eq "\""} {
                        set path [encoding convertfrom [lindex $path 0]]
                    }
@@ -10890,8 +11303,8 @@ proc get_path_encoding {path} {
 
 # First check that Tcl/Tk is recent enough
 if {[catch {package require Tk 8.4} err]} {
-    show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
-                    Gitk requires at least Tcl/Tk 8.4."]
+    show_error {} . "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
+                    Gitk requires at least Tcl/Tk 8.4." list
     exit 1
 }
 
@@ -10947,6 +11360,7 @@ set mingaplen 100
 set cmitmode "patch"
 set wrapcomment "none"
 set showneartags 1
+set hideremotes 0
 set maxrefs 20
 set maxlinelen 200
 set showlocalchanges 1
@@ -10954,6 +11368,7 @@ set limitdiffs 1
 set datetimeformat "%Y-%m-%d %H:%M:%S"
 set autoselect 1
 set perfile_attrs 0
+set want_ttk 1
 
 if {[tk windowingsystem] eq "aqua"} {
     set extdifftool "opendiff"
@@ -10962,12 +11377,20 @@ if {[tk windowingsystem] eq "aqua"} {
 }
 
 set colors {green red blue magenta darkgrey brown orange}
-set bgcolor white
-set fgcolor black
+if {[tk windowingsystem] eq "win32"} {
+    set uicolor SystemButtonFace
+    set bgcolor SystemWindow
+    set fgcolor SystemButtonText
+    set selectbgcolor SystemHighlight
+} else {
+    set uicolor grey85
+    set bgcolor white
+    set fgcolor black
+    set selectbgcolor gray85
+}
 set diffcolors {red "#00a000" blue}
 set diffcontext 3
 set ignorespace 0
-set selectbgcolor gray85
 set markbgcolor "#e0e0ff"
 
 set circlecolors {white blue gray blue blue}
@@ -11000,8 +11423,6 @@ namespace import ::msgcat::mc
 
 catch {source ~/.gitk}
 
-font create optionfont -family sans-serif -size -12
-
 parsefont mainfont $mainfont
 eval font create mainfont [fontflags mainfont]
 eval font create mainfontbold [fontflags mainfont 1]
@@ -11013,6 +11434,8 @@ eval font create textfontbold [fontflags textfont 1]
 parsefont uifont $uifont
 eval font create uifont [fontflags uifont]
 
+setui $uicolor
+
 setoptions
 
 # check that we can find a .git directory somewhere...
@@ -11090,6 +11513,13 @@ set nullid2 "0000000000000000000000000000000000000001"
 set nullfile "/dev/null"
 
 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
+if {![info exists have_ttk]} {
+    set have_ttk [llength [info commands ::ttk::style]]
+}
+set use_ttk [expr {$have_ttk && $want_ttk}]
+set NS [expr {$use_ttk ? "ttk" : ""}]
+
+set git_version [join [lrange [split [lindex [exec git version] end] .] 0 2] .]
 
 set runq {}
 set history {}
@@ -11193,3 +11623,9 @@ if {[tk windowingsystem] eq "win32"} {
 }
 
 getcommits {}
+
+# Local variables:
+# mode: tcl
+# indent-tabs-mode: t
+# tab-width: 8
+# End:
index 825dc98f74b97b577d74dacf61bfb4fe482ee2a8..bd194a3dff9fd36b2edbe64f053a975f489a159b 100644 (file)
 # Translation of gitk to German.
 # Copyright (C) 2007 Paul Mackerras.
 # This file is distributed under the same license as the gitk package.
-# Christian Stimming <stimming@tuhh.de>, 2007
 #
+# Christian Stimming <stimming@tuhh.de>, 2007.
+# Frederik Schwarzer <schwarzerf@gmail.com>, 2008.
 msgid ""
 msgstr ""
 "Project-Id-Version: git-gui\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-12-06 20:40+0100\n"
-"PO-Revision-Date: 2008-12-06 20:45+0100\n"
+"POT-Creation-Date: 2010-01-27 20:21+0100\n"
+"PO-Revision-Date: 2010-01-27 20:27+0100\n"
 "Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
 "Language-Team: German\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: gitk:113
+#: gitk:115
 msgid "Couldn't get list of unmerged files:"
-msgstr "Liste der nicht-zusammengeführten Dateien nicht gefunden:"
+msgstr "Liste der nicht zusammengeführten Dateien nicht gefunden:"
 
-#: gitk:272
+#: gitk:274
 msgid "Error parsing revisions:"
 msgstr "Fehler beim Laden der Versionen:"
 
-#: gitk:327
+#: gitk:329
 msgid "Error executing --argscmd command:"
-msgstr "Fehler beim --argscmd Kommando:"
+msgstr "Fehler beim Ausführen des --argscmd-Kommandos:"
 
-#: gitk:340
+#: gitk:342
 msgid "No files selected: --merge specified but no files are unmerged."
 msgstr ""
-"Keine Dateien ausgewählt: --merge angegeben, es existieren aber keine nicht-"
-"zusammengeführten Dateien."
+"Keine Dateien ausgewählt: Es wurde --merge angegeben, aber es existieren "
+"keine nicht zusammengeführten Dateien."
 
-#: gitk:343
+#: gitk:345
 msgid ""
 "No files selected: --merge specified but no unmerged files are within file "
 "limit."
 msgstr ""
-"Keine Dateien ausgewähle: --merge angegeben, aber keine nicht-"
-"zusammengeführten Dateien sind in der Dateiauswahl."
+"Keine Dateien ausgewählt: Es wurde --merge angegeben, aber es sind keine "
+"nicht zusammengeführten Dateien in der Dateiauswahl."
 
-#: gitk:365 gitk:503
+#: gitk:367 gitk:514
 msgid "Error executing git log:"
-msgstr "Fehler beim Ausführen von git-log:"
+msgstr "Fehler beim Ausführen von »git log«:"
 
-#: gitk:378
+#: gitk:385 gitk:530
 msgid "Reading"
 msgstr "Lesen"
 
-#: gitk:438 gitk:3462
+#: gitk:445 gitk:4261
 msgid "Reading commits..."
-msgstr "Versionen lesen..."
+msgstr "Versionen werden gelesen ..."
 
-#: gitk:441 gitk:1528 gitk:3465
+#: gitk:448 gitk:1578 gitk:4264
 msgid "No commits selected"
-msgstr "Keine Versionen ausgewählt."
+msgstr "Keine Versionen ausgewählt"
 
-#: gitk:1399
+#: gitk:1454
 msgid "Can't parse git log output:"
-msgstr "Ausgabe von git-log kann nicht erkannt werden:"
+msgstr "Ausgabe von »git log« kann nicht erkannt werden:"
 
-#: gitk:1605
+#: gitk:1674
 msgid "No commit information available"
 msgstr "Keine Versionsinformation verfügbar"
 
-#: gitk:1709 gitk:1731 gitk:3259 gitk:7764 gitk:9293 gitk:9466
+#: gitk:1816
+msgid "mc"
+msgstr "mc"
+
+#: gitk:1851 gitk:4054 gitk:9044 gitk:10585 gitk:10804
 msgid "OK"
 msgstr "Ok"
 
-#: gitk:1733 gitk:3260 gitk:7439 gitk:7510 gitk:7613 gitk:7660 gitk:7766
-#: gitk:9294 gitk:9467
+#: gitk:1853 gitk:4056 gitk:8634 gitk:8713 gitk:8828 gitk:8877 gitk:9046
+#: gitk:10586 gitk:10805
 msgid "Cancel"
 msgstr "Abbrechen"
 
-#: gitk:1811
+#: gitk:1975
 msgid "Update"
 msgstr "Aktualisieren"
 
-#: gitk:1812
+#: gitk:1976
 msgid "Reload"
 msgstr "Neu laden"
 
-#: gitk:1813
+#: gitk:1977
 msgid "Reread references"
 msgstr "Zweige neu laden"
 
-#: gitk:1814
+#: gitk:1978
 msgid "List references"
 msgstr "Zweige/Markierungen auflisten"
 
-#: gitk:1915
+#: gitk:1980
 msgid "Start git gui"
 msgstr "»git gui« starten"
 
-#: gitk:1917
+#: gitk:1982
 msgid "Quit"
 msgstr "Beenden"
 
-#: gitk:1810
+#: gitk:1974
 msgid "File"
 msgstr "Datei"
 
-#: gitk:1818
+#: gitk:1986
 msgid "Preferences"
 msgstr "Einstellungen"
 
-#: gitk:1817
+#: gitk:1985
 msgid "Edit"
 msgstr "Bearbeiten"
 
-#: gitk:1821
+#: gitk:1990
 msgid "New view..."
-msgstr "Neue Ansicht..."
+msgstr "Neue Ansicht ..."
 
-#: gitk:1822
+#: gitk:1991
 msgid "Edit view..."
-msgstr "Ansicht bearbeiten..."
+msgstr "Ansicht bearbeiten ..."
 
-#: gitk:1823
+#: gitk:1992
 msgid "Delete view"
-msgstr "Ansicht löschen"
+msgstr "Ansicht entfernen"
 
-#: gitk:1825
+#: gitk:1994
 msgid "All files"
 msgstr "Alle Dateien"
 
-#: gitk:1820 gitk:3196
+#: gitk:1989 gitk:3808
 msgid "View"
 msgstr "Ansicht"
 
-#: gitk:1828 gitk:2487
+#: gitk:1999 gitk:2009 gitk:2780
 msgid "About gitk"
 msgstr "Über gitk"
 
-#: gitk:1829
+#: gitk:2000 gitk:2014
 msgid "Key bindings"
 msgstr "Tastenkürzel"
 
-#: gitk:1827
+#: gitk:1998 gitk:2013
 msgid "Help"
 msgstr "Hilfe"
 
-#: gitk:1887
-msgid "SHA1 ID: "
-msgstr "SHA1:"
+#: gitk:2091 gitk:8110
+msgid "SHA1 ID:"
+msgstr "SHA1 ID:"
 
-#: gitk:1918
+#: gitk:2122
 msgid "Row"
 msgstr "Zeile"
 
-#: gitk:1949
+#: gitk:2160
 msgid "Find"
 msgstr "Suche"
 
-#: gitk:1950
+#: gitk:2161
 msgid "next"
 msgstr "nächste"
 
-#: gitk:1951
+#: gitk:2162
 msgid "prev"
 msgstr "vorige"
 
-#: gitk:1952
+#: gitk:2163
 msgid "commit"
 msgstr "Version nach"
 
-#: gitk:1955 gitk:1957 gitk:3617 gitk:3640 gitk:3664 gitk:5550 gitk:5621
+#: gitk:2166 gitk:2168 gitk:4422 gitk:4445 gitk:4469 gitk:6410 gitk:6482
+#: gitk:6566
 msgid "containing:"
 msgstr "Beschreibung:"
 
-#: gitk:1958 gitk:2954 gitk:2959 gitk:3692
+#: gitk:2169 gitk:3290 gitk:3295 gitk:4497
 msgid "touching paths:"
 msgstr "Dateien:"
 
-#: gitk:1959 gitk:3697
+#: gitk:2170 gitk:4502
 msgid "adding/removing string:"
 msgstr "Änderungen:"
 
-#: gitk:1968 gitk:1970
+#: gitk:2179 gitk:2181
 msgid "Exact"
 msgstr "Exakt"
 
-#: gitk:1970 gitk:3773 gitk:5518
+#: gitk:2181 gitk:4577 gitk:6378
 msgid "IgnCase"
 msgstr "Kein Groß/Klein"
 
-#: gitk:1970 gitk:3666 gitk:3771 gitk:5514
+#: gitk:2181 gitk:4471 gitk:4575 gitk:6374
 msgid "Regexp"
 msgstr "Regexp"
 
-#: gitk:1972 gitk:1973 gitk:3792 gitk:3822 gitk:3829 gitk:5641 gitk:5708
+#: gitk:2183 gitk:2184 gitk:4596 gitk:4626 gitk:4633 gitk:6502 gitk:6570
 msgid "All fields"
 msgstr "Alle Felder"
 
-#: gitk:1973 gitk:3790 gitk:3822 gitk:5580
+#: gitk:2184 gitk:4594 gitk:4626 gitk:6441
 msgid "Headline"
 msgstr "Überschrift"
 
-#: gitk:1974 gitk:3790 gitk:5580 gitk:5708 gitk:6109
+#: gitk:2185 gitk:4594 gitk:6441 gitk:6570 gitk:7003
 msgid "Comments"
 msgstr "Beschreibung"
 
-#: gitk:1974 gitk:3790 gitk:3794 gitk:3829 gitk:5580 gitk:6045 gitk:7285
-#: gitk:7300
+#: gitk:2185 gitk:4594 gitk:4598 gitk:4633 gitk:6441 gitk:6938 gitk:8285
+#: gitk:8300
 msgid "Author"
 msgstr "Autor"
 
-#: gitk:1974 gitk:3790 gitk:5580 gitk:6047
+#: gitk:2185 gitk:4594 gitk:6441 gitk:6940
 msgid "Committer"
 msgstr "Eintragender"
 
-#: gitk:2003
+#: gitk:2216
 msgid "Search"
-msgstr "Suche"
+msgstr "Suchen"
 
-#: gitk:2010
+#: gitk:2224
 msgid "Diff"
 msgstr "Vergleich"
 
-#: gitk:2012
+#: gitk:2226
 msgid "Old version"
 msgstr "Alte Version"
 
-#: gitk:2014
+#: gitk:2228
 msgid "New version"
 msgstr "Neue Version"
 
-#: gitk:2016
+#: gitk:2230
 msgid "Lines of context"
 msgstr "Kontextzeilen"
 
-#: gitk:2026
+#: gitk:2240
 msgid "Ignore space change"
 msgstr "Leerzeichenänderungen ignorieren"
 
-#: gitk:2084
+#: gitk:2299
 msgid "Patch"
 msgstr "Patch"
 
-#: gitk:2086
+#: gitk:2301
 msgid "Tree"
 msgstr "Baum"
 
-#: gitk:2213 gitk:2226
+#: gitk:2456 gitk:2473
 msgid "Diff this -> selected"
-msgstr "Vergleich diese -> gewählte"
+msgstr "Vergleich: diese -> gewählte"
 
-#: gitk:2214 gitk:2227
+#: gitk:2457 gitk:2474
 msgid "Diff selected -> this"
-msgstr "Vergleich gewählte -> diese"
+msgstr "Vergleich: gewählte -> diese"
 
-#: gitk:2215 gitk:2228
+#: gitk:2458 gitk:2475
 msgid "Make patch"
 msgstr "Patch erstellen"
 
-#: gitk:2216 gitk:7494
+#: gitk:2459 gitk:8692
 msgid "Create tag"
 msgstr "Markierung erstellen"
 
-#: gitk:2217 gitk:7593
+#: gitk:2460 gitk:8808
 msgid "Write commit to file"
 msgstr "Version in Datei schreiben"
 
-#: gitk:2218 gitk:7647
+#: gitk:2461 gitk:8865
 msgid "Create new branch"
 msgstr "Neuen Zweig erstellen"
 
-#: gitk:2219
+#: gitk:2462
 msgid "Cherry-pick this commit"
 msgstr "Diese Version pflücken"
 
-#: gitk:2220
+#: gitk:2463
 msgid "Reset HEAD branch to here"
 msgstr "HEAD-Zweig auf diese Version zurücksetzen"
 
-#: gitk:2234
+#: gitk:2464
+msgid "Mark this commit"
+msgstr "Lesezeichen setzen"
+
+#: gitk:2465
+msgid "Return to mark"
+msgstr "Zum Lesezeichen"
+
+#: gitk:2466
+msgid "Find descendant of this and mark"
+msgstr "Abkömmling von Lesezeichen und dieser Version finden"
+
+#: gitk:2467
+msgid "Compare with marked commit"
+msgstr "Mit Lesezeichen vergleichen"
+
+#: gitk:2481
 msgid "Check out this branch"
 msgstr "Auf diesen Zweig umstellen"
 
-#: gitk:2235
+#: gitk:2482
 msgid "Remove this branch"
 msgstr "Zweig löschen"
 
-#: gitk:2242
+#: gitk:2489
 msgid "Highlight this too"
 msgstr "Diesen auch hervorheben"
 
-#: gitk:2243
+#: gitk:2490
 msgid "Highlight this only"
 msgstr "Nur diesen hervorheben"
 
-#: gitk:2244
+#: gitk:2491
 msgid "External diff"
-msgstr "Externer Vergleich"
+msgstr "Externes Diff-Programm"
 
-#: gitk:2255
+#: gitk:2492
 msgid "Blame parent commit"
 msgstr "Annotieren der Elternversion"
 
-#: gitk:2360
+#: gitk:2499
 msgid "Show origin of this line"
 msgstr "Herkunft dieser Zeile anzeigen"
 
-#: gitk:2361
+#: gitk:2500
 msgid "Run git gui blame on this line"
-msgstr "Annotieren (»git gui blame«) von dieser Zeile"
+msgstr "Diese Zeile annotieren (»git gui blame«)"
 
-#: gitk:2606
+#: gitk:2782
 msgid ""
 "\n"
 "Gitk - a commit viewer for git\n"
 "\n"
-"Copyright © 2005-2008 Paul Mackerras\n"
+"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
 "\n"
 "Use and redistribute under the terms of the GNU General Public License"
 msgstr ""
 "\n"
-"Gitk - eine Visualisierung der Git Historie\n"
+"Gitk - eine Visualisierung der Git-Historie\n"
 "\n"
-"Copyright © 2005-2008 Paul Mackerras\n"
+"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
 "\n"
-"Benutzung und Weiterverbreitung gemäß den Bedingungen der GNU General Public "
-"License"
+"Benutzung und Weiterverbreitung gemäß den Bedingungen der GNU General Public License"
 
-#: gitk:2496 gitk:2557 gitk:7943
+#: gitk:2790 gitk:2854 gitk:9230
 msgid "Close"
 msgstr "Schließen"
 
-#: gitk:2515
+#: gitk:2811
 msgid "Gitk key bindings"
-msgstr "Gitk Tastaturbelegung"
+msgstr "Gitk-Tastaturbelegung"
 
-#: gitk:2517
+#: gitk:2814
 msgid "Gitk key bindings:"
-msgstr "Gitk Tastaturbelegung:"
+msgstr "Gitk-Tastaturbelegung:"
 
-#: gitk:2519
+#: gitk:2816
 #, tcl-format
 msgid "<%s-Q>\t\tQuit"
 msgstr "<%s-Q>\t\tBeenden"
 
-#: gitk:2520
+#: gitk:2817
 msgid "<Home>\t\tMove to first commit"
 msgstr "<Pos1>\t\tZur neuesten Version springen"
 
-#: gitk:2521
+#: gitk:2818
 msgid "<End>\t\tMove to last commit"
 msgstr "<Ende>\t\tZur ältesten Version springen"
 
-#: gitk:2522
+#: gitk:2819
 msgid "<Up>, p, i\tMove up one commit"
 msgstr "<Hoch>, p, i\tNächste neuere Version"
 
-#: gitk:2523
+#: gitk:2820
 msgid "<Down>, n, k\tMove down one commit"
 msgstr "<Runter>, n, k\tNächste ältere Version"
 
-#: gitk:2524
+#: gitk:2821
 msgid "<Left>, z, j\tGo back in history list"
 msgstr "<Links>, z, j\tEine Version zurückgehen"
 
-#: gitk:2525
+#: gitk:2822
 msgid "<Right>, x, l\tGo forward in history list"
 msgstr "<Rechts>, x, l\tEine Version weitergehen"
 
-#: gitk:2526
+#: gitk:2823
 msgid "<PageUp>\tMove up one page in commit list"
 msgstr "<BildHoch>\tEine Seite nach oben blättern"
 
-#: gitk:2527
+#: gitk:2824
 msgid "<PageDown>\tMove down one page in commit list"
 msgstr "<BildRunter>\tEine Seite nach unten blättern"
 
-#: gitk:2528
+#: gitk:2825
 #, tcl-format
 msgid "<%s-Home>\tScroll to top of commit list"
 msgstr "<%s-Pos1>\tZum oberen Ende der Versionsliste blättern"
 
-#: gitk:2529
+#: gitk:2826
 #, tcl-format
 msgid "<%s-End>\tScroll to bottom of commit list"
 msgstr "<%s-Ende>\tZum unteren Ende der Versionsliste blättern"
 
-#: gitk:2530
+#: gitk:2827
 #, tcl-format
 msgid "<%s-Up>\tScroll commit list up one line"
 msgstr "<%s-Hoch>\tVersionsliste eine Zeile nach oben blättern"
 
-#: gitk:2531
+#: gitk:2828
 #, tcl-format
 msgid "<%s-Down>\tScroll commit list down one line"
 msgstr "<%s-Runter>\tVersionsliste eine Zeile nach unten blättern"
 
-#: gitk:2532
+#: gitk:2829
 #, tcl-format
 msgid "<%s-PageUp>\tScroll commit list up one page"
-msgstr "<%s-BildHoch>\tVersionsliste eine Seite hoch blättern"
+msgstr "<%s-BildHoch>\tVersionsliste eine Seite nach oben blättern"
 
-#: gitk:2533
+#: gitk:2830
 #, tcl-format
 msgid "<%s-PageDown>\tScroll commit list down one page"
 msgstr "<%s-BildRunter>\tVersionsliste eine Seite nach unten blättern"
 
-#: gitk:2534
+#: gitk:2831
 msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
 msgstr "<Umschalt-Hoch>\tRückwärts suchen (nach oben; neuere Versionen)"
 
-#: gitk:2535
+#: gitk:2832
 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
 msgstr "<Umschalt-Runter> Suchen (nach unten; ältere Versionen)"
 
-#: gitk:2536
+#: gitk:2833
 msgid "<Delete>, b\tScroll diff view up one page"
 msgstr "<Entf>, b\t\tVergleich eine Seite nach oben blättern"
 
-#: gitk:2537
+#: gitk:2834
 msgid "<Backspace>\tScroll diff view up one page"
 msgstr "<Löschtaste>\tVergleich eine Seite nach oben blättern"
 
-#: gitk:2538
+#: gitk:2835
 msgid "<Space>\t\tScroll diff view down one page"
 msgstr "<Leertaste>\tVergleich eine Seite nach unten blättern"
 
-#: gitk:2539
+#: gitk:2836
 msgid "u\t\tScroll diff view up 18 lines"
-msgstr "u\t\tVergleich um 18 Zeilen nach oben (»up«) blättern"
+msgstr "u\t\tVergleich um 18 Zeilen nach oben blättern"
 
-#: gitk:2540
+#: gitk:2837
 msgid "d\t\tScroll diff view down 18 lines"
-msgstr "d\t\tVergleich um 18 Zeilen nach unten (»down«) blättern"
+msgstr "d\t\tVergleich um 18 Zeilen nach unten blättern"
 
-#: gitk:2541
+#: gitk:2838
 #, tcl-format
 msgid "<%s-F>\t\tFind"
 msgstr "<%s-F>\t\tSuchen"
 
-#: gitk:2542
+#: gitk:2839
 #, tcl-format
 msgid "<%s-G>\t\tMove to next find hit"
 msgstr "<%s-G>\t\tWeitersuchen"
 
-#: gitk:2543
+#: gitk:2840
 msgid "<Return>\tMove to next find hit"
 msgstr "<Eingabetaste>\tWeitersuchen"
 
-#: gitk:2544
-msgid "/\t\tMove to next find hit, or redo find"
-msgstr "/\t\tWeitersuchen oder neue Suche beginnen"
+#: gitk:2841
+msgid "/\t\tFocus the search box"
+msgstr "/\t\tTastaturfokus ins Suchfeld"
 
-#: gitk:2545
+#: gitk:2842
 msgid "?\t\tMove to previous find hit"
 msgstr "?\t\tRückwärts weitersuchen"
 
-#: gitk:2546
+#: gitk:2843
 msgid "f\t\tScroll diff view to next file"
-msgstr "f\t\tVergleich zur nächsten Datei (»file«) blättern"
+msgstr "f\t\tVergleich zur nächsten Datei blättern"
 
-#: gitk:2547
+#: gitk:2844
 #, tcl-format
 msgid "<%s-S>\t\tSearch for next hit in diff view"
 msgstr "<%s-S>\t\tWeitersuchen im Vergleich"
 
-#: gitk:2548
+#: gitk:2845
 #, tcl-format
 msgid "<%s-R>\t\tSearch for previous hit in diff view"
 msgstr "<%s-R>\t\tRückwärts weitersuchen im Vergleich"
 
-#: gitk:2549
+#: gitk:2846
 #, tcl-format
 msgid "<%s-KP+>\tIncrease font size"
-msgstr "<%s-Nummerblock-Plus>\tSchriftgröße vergrößern"
+msgstr "<%s-Nummerblock-Plus>\tSchrift vergrößern"
 
-#: gitk:2550
+#: gitk:2847
 #, tcl-format
 msgid "<%s-plus>\tIncrease font size"
-msgstr "<%s-Plus>\tSchriftgröße vergrößern"
+msgstr "<%s-Plus>\tSchrift vergrößern"
 
-#: gitk:2551
+#: gitk:2848
 #, tcl-format
 msgid "<%s-KP->\tDecrease font size"
-msgstr "<%s-Nummernblock-> Schriftgröße verkleinern"
+msgstr "<%s-Nummernblock-Minus> Schrift verkleinern"
 
-#: gitk:2552
+#: gitk:2849
 #, tcl-format
 msgid "<%s-minus>\tDecrease font size"
-msgstr "<%s-Minus>\tSchriftgröße verkleinern"
+msgstr "<%s-Minus>\tSchrift verkleinern"
 
-#: gitk:2553
+#: gitk:2850
 msgid "<F5>\t\tUpdate"
 msgstr "<F5>\t\tAktualisieren"
 
-#: gitk:2979
+#: gitk:3305 gitk:3314
 #, tcl-format
-msgid "Error getting \"%s\" from %s:"
-msgstr "Fehler beim Holen von »%s« von »%s«:"
+msgid "Error creating temporary directory %s:"
+msgstr "Fehler beim Erzeugen des temporären Verzeichnisses »%s«:"
 
-#: gitk:3036 gitk:3045
+#: gitk:3327
 #, tcl-format
-msgid "Error creating temporary directory %s:"
-msgstr "Fehler beim Erzeugen eines temporären Verzeichnisses »%s«:"
+msgid "Error getting \"%s\" from %s:"
+msgstr "Fehler beim Holen von »%s« von »%s«:"
 
-#: gitk:3058
+#: gitk:3390
 msgid "command failed:"
 msgstr "Kommando fehlgeschlagen:"
 
-#: gitk:3078
+#: gitk:3539
 msgid "No such commit"
 msgstr "Version nicht gefunden"
 
-#: gitk:3083
+#: gitk:3553
 msgid "git gui blame: command failed:"
 msgstr "git gui blame: Kommando fehlgeschlagen:"
 
-#: gitk:3398
+#: gitk:3584
 #, tcl-format
 msgid "Couldn't read merge head: %s"
 msgstr "Zusammenführungs-Spitze konnte nicht gelesen werden: %s"
 
-#: gitk:3406
+#: gitk:3592
 #, tcl-format
 msgid "Error reading index: %s"
 msgstr "Fehler beim Lesen der Bereitstellung (»index«): %s"
 
-#: gitk:3431
+#: gitk:3617
 #, tcl-format
 msgid "Couldn't start git blame: %s"
 msgstr "»git blame« konnte nicht gestartet werden: %s"
 
-#: gitk:3434 gitk:6160
+#: gitk:3620 gitk:6409
 msgid "Searching"
 msgstr "Suchen"
 
-#: gitk:3466
+#: gitk:3652
 #, tcl-format
 msgid "Error running git blame: %s"
 msgstr "Fehler beim Ausführen von »git blame«: %s"
 
-#: gitk:3494
+#: gitk:3680
 #, tcl-format
 msgid "That line comes from commit %s,  which is not in this view"
 msgstr ""
-"Diese Zeile stammt aus Version %s, welche nicht in dieser Ansicht gezeigt "
-"wird."
+"Diese Zeile stammt aus Version %s, die nicht in dieser Ansicht gezeigt wird"
 
-#: gitk:3508
+#: gitk:3694
 msgid "External diff viewer failed:"
-msgstr "Externes Vergleich-(Diff-)Programm fehlgeschlagen:"
+msgstr "Externes Diff-Programm fehlgeschlagen:"
 
-#: gitk:3210
+#: gitk:3812
 msgid "Gitk view definition"
-msgstr "Gitk Ansichten"
+msgstr "Gitk-Ansichten"
 
-#: gitk:3630
+#: gitk:3816
 msgid "Remember this view"
 msgstr "Diese Ansicht speichern"
 
-#: gitk:3232
-msgid "Commits to include (arguments to git log):"
-msgstr "Versionen anzeigen (Argumente von git-log):"
+#: gitk:3817
+msgid "References (space separated list):"
+msgstr "Zweige/Markierungen (durch Leerzeichen getrennte Liste):"
 
-#: gitk:3632
-msgid "Use all refs"
-msgstr "Alle Zweige verwenden"
+#: gitk:3818
+msgid "Branches & tags:"
+msgstr "Zweige/Markierungen:"
 
-#: gitk:3633
-msgid "Strictly sort by date"
-msgstr "Streng nach Datum sortieren"
+#: gitk:3819
+msgid "All refs"
+msgstr "Alle Markierungen und Zweige"
 
-#: gitk:3634
-msgid "Mark branch sides"
-msgstr "Zweig-Seiten markieren"
+#: gitk:3820
+msgid "All (local) branches"
+msgstr "Alle (lokalen) Zweige"
+
+#: gitk:3821
+msgid "All tags"
+msgstr "Alle Markierungen"
+
+#: gitk:3822
+msgid "All remote-tracking branches"
+msgstr "Alle Übernahmezweige"
+
+#: gitk:3823
+msgid "Commit Info (regular expressions):"
+msgstr "Versionsinformationen (reguläre Ausdrücke):"
+
+#: gitk:3824
+msgid "Author:"
+msgstr "Autor:"
 
-#: gitk:3635
-msgid "Since date:"
-msgstr "Von Datum:"
+#: gitk:3825
+msgid "Committer:"
+msgstr "Eintragender:"
 
-#: gitk:3636
-msgid "Until date:"
-msgstr "Bis Datum:"
+#: gitk:3826
+msgid "Commit Message:"
+msgstr "Versionsbeschreibung:"
 
-#: gitk:3637
-msgid "Max count:"
-msgstr "Max. Anzahl:"
+#: gitk:3827
+msgid "Matches all Commit Info criteria"
+msgstr "Alle Versionsinformationen-Kriterien erfüllen"
 
-#: gitk:3638
-msgid "Skip:"
+#: gitk:3828
+msgid "Changes to Files:"
+msgstr "Dateien:"
+
+#: gitk:3829
+msgid "Fixed String"
+msgstr "Zeichenkette"
+
+#: gitk:3830
+msgid "Regular Expression"
+msgstr "Regulärer Ausdruck"
+
+#: gitk:3831
+msgid "Search string:"
+msgstr "Suchausdruck:"
+
+#: gitk:3832
+msgid ""
+"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+msgstr "Datum (»2 weeks ago«, »2009-03-17 15:27:38«, »March 17, 2009 15:27:38«)"
+
+#: gitk:3833
+msgid "Since:"
+msgstr "Von:"
+
+#: gitk:3834
+msgid "Until:"
+msgstr "Bis:"
+
+#: gitk:3835
+msgid "Limit and/or skip a number of revisions (positive integer):"
+msgstr "Versionsanzahl begrenzen oder einige überspringen (ganzzahliger Wert):"
+
+#: gitk:3836
+msgid "Number to show:"
+msgstr "Anzeigen:"
+
+#: gitk:3837
+msgid "Number to skip:"
 msgstr "Überspringen:"
 
-#: gitk:3639
+#: gitk:3838
+msgid "Miscellaneous options:"
+msgstr "Sonstiges:"
+
+#: gitk:3839
+msgid "Strictly sort by date"
+msgstr "Streng nach Datum sortieren"
+
+#: gitk:3840
+msgid "Mark branch sides"
+msgstr "Zweig-Seiten markieren"
+
+#: gitk:3841
 msgid "Limit to first parent"
 msgstr "Auf erste Elternversion beschränken"
 
-#: gitk:3640
-msgid "Command to generate more commits to include:"
-msgstr "Versionsliste durch folgendes Kommando erzeugen lassen:"
+#: gitk:3842
+msgid "Simple history"
+msgstr "Einfache Historie"
 
-#: gitk:3749
-msgid "Name"
-msgstr "Name"
+#: gitk:3843
+msgid "Additional arguments to git log:"
+msgstr "Zusätzliche Argumente für »git log«:"
 
-#: gitk:3797
+#: gitk:3844
 msgid "Enter files and directories to include, one per line:"
 msgstr "Folgende Dateien und Verzeichnisse anzeigen (eine pro Zeile):"
 
-#: gitk:3811
+#: gitk:3845
+msgid "Command to generate more commits to include:"
+msgstr "Versionsliste durch folgendes Kommando erzeugen lassen:"
+
+#: gitk:3967
+msgid "Gitk: edit view"
+msgstr "Gitk: Ansicht bearbeiten"
+
+#: gitk:3975
+msgid "-- criteria for selecting revisions"
+msgstr "-- Auswahl der angezeigten Versionen"
+
+#: gitk:3980
+msgid "View Name"
+msgstr "Ansichtsname"
+
+#: gitk:4055
 msgid "Apply (F5)"
 msgstr "Anwenden (F5)"
 
-#: gitk:3849
+#: gitk:4093
 msgid "Error in commit selection arguments:"
 msgstr "Fehler in den ausgewählten Versionen:"
 
-#: gitk:3347 gitk:3399 gitk:3842 gitk:3856 gitk:5060 gitk:10141 gitk:10142
+#: gitk:4146 gitk:4198 gitk:4646 gitk:4660 gitk:5921 gitk:11534 gitk:11535
 msgid "None"
 msgstr "Keine"
 
-#: gitk:3790 gitk:5580 gitk:7287 gitk:7302
+#: gitk:4594 gitk:6441 gitk:8287 gitk:8302
 msgid "Date"
 msgstr "Datum"
 
-#: gitk:3790 gitk:5580
+#: gitk:4594 gitk:6441
 msgid "CDate"
 msgstr "Eintragedatum"
 
-#: gitk:3939 gitk:3944
+#: gitk:4743 gitk:4748
 msgid "Descendant"
 msgstr "Abkömmling"
 
-#: gitk:3940
+#: gitk:4744
 msgid "Not descendant"
-msgstr "Nicht Abkömmling"
+msgstr "Kein Abkömmling"
 
-#: gitk:3947 gitk:3952
+#: gitk:4751 gitk:4756
 msgid "Ancestor"
 msgstr "Vorgänger"
 
-#: gitk:3948
+#: gitk:4752
 msgid "Not ancestor"
-msgstr "Nicht Vorgänger"
+msgstr "Kein Vorgänger"
 
-#: gitk:4187
+#: gitk:5042
 msgid "Local changes checked in to index but not committed"
 msgstr "Lokale Änderungen bereitgestellt, aber nicht eingetragen"
 
-#: gitk:4220
+#: gitk:5078
 msgid "Local uncommitted changes, not checked in to index"
 msgstr "Lokale Änderungen, nicht bereitgestellt"
 
-#: gitk:6673
+#: gitk:6759
+msgid "many"
+msgstr "viele"
+
+#: gitk:6942
 msgid "Tags:"
 msgstr "Markierungen:"
 
-#: gitk:6066 gitk:6072 gitk:7280
+#: gitk:6959 gitk:6965 gitk:8280
 msgid "Parent"
 msgstr "Eltern"
 
-#: gitk:6077
+#: gitk:6970
 msgid "Child"
 msgstr "Kind"
 
-#: gitk:6086
+#: gitk:6979
 msgid "Branch"
 msgstr "Zweig"
 
-#: gitk:6089
+#: gitk:6982
 msgid "Follows"
 msgstr "Folgt auf"
 
-#: gitk:6092
+#: gitk:6985
 msgid "Precedes"
 msgstr "Vorgänger von"
 
-#: gitk:7209
+#: gitk:7522
 #, tcl-format
 msgid "Error getting diffs: %s"
 msgstr "Fehler beim Laden des Vergleichs: %s"
 
-#: gitk:7748
+#: gitk:8108
 msgid "Goto:"
 msgstr "Gehe zu:"
 
-#: gitk:7115
-msgid "SHA1 ID:"
-msgstr "SHA1-Hashwert:"
-
-#: gitk:7134
+#: gitk:8129
 #, tcl-format
 msgid "Short SHA1 id %s is ambiguous"
 msgstr "Kurzer SHA1-Hashwert »%s« ist mehrdeutig"
 
-#: gitk:7146
+#: gitk:8136
+#, tcl-format
+msgid "Revision %s is not known"
+msgstr "Version »%s« ist unbekannt"
+
+#: gitk:8146
 #, tcl-format
 msgid "SHA1 id %s is not known"
-msgstr "SHA1-Hashwert »%s« unbekannt"
+msgstr "SHA1-Hashwert »%s« ist unbekannt"
 
-#: gitk:7148
+#: gitk:8148
 #, tcl-format
-msgid "Tag/Head %s is not known"
-msgstr "Markierung/Zweig »%s« ist unbekannt"
+msgid "Revision %s is not in the current view"
+msgstr "Version »%s« wird in der aktuellen Ansicht nicht angezeigt"
 
-#: gitk:7290
+#: gitk:8290
 msgid "Children"
 msgstr "Kinder"
 
-#: gitk:7347
+#: gitk:8348
 #, tcl-format
 msgid "Reset %s branch to here"
 msgstr "Zweig »%s« hierher zurücksetzen"
 
-#: gitk:7349
+#: gitk:8350
 msgid "Detached head: can't reset"
 msgstr "Zweigspitze ist abgetrennt: Zurücksetzen nicht möglich"
 
-#: gitk:7381
+#: gitk:8459 gitk:8465
+msgid "Skipping merge commit "
+msgstr "Überspringe Zusammenführungs-Version "
+
+#: gitk:8474 gitk:8479
+msgid "Error getting patch ID for "
+msgstr "Fehler beim Holen der Patch-ID für "
+
+#: gitk:8475 gitk:8480
+msgid " - stopping\n"
+msgstr " - Abbruch.\n"
+
+#: gitk:8485 gitk:8488 gitk:8496 gitk:8510 gitk:8519
+msgid "Commit "
+msgstr "Version "
+
+#: gitk:8489
+msgid ""
+" is the same patch as\n"
+"       "
+msgstr ""
+" ist das gleiche Patch wie\n"
+"       "
+
+#: gitk:8497
+msgid ""
+" differs from\n"
+"       "
+msgstr ""
+" ist unterschiedlich von\n"
+"       "
+
+#: gitk:8499
+msgid ""
+"Diff of commits:\n"
+"\n"
+msgstr ""
+"Vergleich der Versionen:\n"
+"\n"
+
+#: gitk:8511 gitk:8520
+#, tcl-format
+msgid " has %s children - stopping\n"
+msgstr " hat %s Kinder. Abbruch\n"
+
+#: gitk:8539
+#, tcl-format
+msgid "Error writing commit to file: %s"
+msgstr "Fehler beim Schreiben der Version in Datei: %s"
+
+#: gitk:8545
+#, tcl-format
+msgid "Error diffing commits: %s"
+msgstr "Fehler beim Vergleichen der Versionen: %s"
+
+#: gitk:8575
 msgid "Top"
 msgstr "Oben"
 
-#: gitk:7382
+#: gitk:8576
 msgid "From"
 msgstr "Von"
 
-#: gitk:7387
+#: gitk:8581
 msgid "To"
 msgstr "bis"
 
-#: gitk:7410
+#: gitk:8605
 msgid "Generate patch"
 msgstr "Patch erstellen"
 
-#: gitk:7412
+#: gitk:8607
 msgid "From:"
 msgstr "Von:"
 
-#: gitk:7421
+#: gitk:8616
 msgid "To:"
 msgstr "bis:"
 
-#: gitk:7430
+#: gitk:8625
 msgid "Reverse"
 msgstr "Umgekehrt"
 
-#: gitk:7432 gitk:7607
+#: gitk:8627 gitk:8822
 msgid "Output file:"
 msgstr "Ausgabedatei:"
 
-#: gitk:7438
+#: gitk:8633
 msgid "Generate"
 msgstr "Erzeugen"
 
-#: gitk:7474
+#: gitk:8671
 msgid "Error creating patch:"
-msgstr "Fehler beim Patch erzeugen:"
+msgstr "Fehler beim Erzeugen des Patches:"
 
-#: gitk:7496 gitk:7595 gitk:7649
+#: gitk:8694 gitk:8810 gitk:8867
 msgid "ID:"
 msgstr "ID:"
 
-#: gitk:7505
+#: gitk:8703
 msgid "Tag name:"
 msgstr "Markierungsname:"
 
-#: gitk:7509 gitk:7659
+#: gitk:8706
+msgid "Tag message is optional"
+msgstr "Eine Markierungsbeschreibung ist optional"
+
+#: gitk:8708
+msgid "Tag message:"
+msgstr "Markierungsbeschreibung:"
+
+#: gitk:8712 gitk:8876
 msgid "Create"
 msgstr "Erstellen"
 
-#: gitk:7524
+#: gitk:8730
 msgid "No tag name specified"
 msgstr "Kein Markierungsname angegeben"
 
-#: gitk:7528
+#: gitk:8734
 #, tcl-format
 msgid "Tag \"%s\" already exists"
 msgstr "Markierung »%s« existiert bereits."
 
-#: gitk:7534
+#: gitk:8744
 msgid "Error creating tag:"
-msgstr "Fehler bei Markierung erstellen:"
+msgstr "Fehler beim Erstellen der Markierung:"
 
-#: gitk:7604
+#: gitk:8819
 msgid "Command:"
 msgstr "Kommando:"
 
-#: gitk:7612
+#: gitk:8827
 msgid "Write"
 msgstr "Schreiben"
 
-#: gitk:7628
+#: gitk:8845
 msgid "Error writing commit:"
 msgstr "Fehler beim Schreiben der Version:"
 
-#: gitk:7654
+#: gitk:8872
 msgid "Name:"
 msgstr "Name:"
 
-#: gitk:7674
+#: gitk:8895
 msgid "Please specify a name for the new branch"
 msgstr "Bitte geben Sie einen Namen für den neuen Zweig an."
 
-#: gitk:8328
+#: gitk:8900
 #, tcl-format
 msgid "Branch '%s' already exists. Overwrite?"
 msgstr "Zweig »%s« existiert bereits. Soll er überschrieben werden?"
 
-#: gitk:8394
+#: gitk:8966
 #, tcl-format
 msgid "Commit %s is already included in branch %s -- really re-apply it?"
 msgstr ""
-"Version »%s« ist bereits im Zweig »%s« enthalten -- trotzdem erneut "
-"eintragen?"
+"Version »%s« ist bereits im Zweig »%s« enthalten -- trotzdem erneut eintragen?"
 
-#: gitk:7718
+#: gitk:8971
 msgid "Cherry-picking"
 msgstr "Version pflücken"
 
-#: gitk:8408
+#: gitk:8980
 #, tcl-format
 msgid ""
 "Cherry-pick failed because of local changes to file '%s'.\n"
@@ -822,45 +991,45 @@ msgid ""
 msgstr ""
 "Pflücken fehlgeschlagen, da noch lokale Änderungen in Datei »%s«\n"
 "vorliegen. Bitte diese Änderungen eintragen, zurücksetzen oder\n"
-"zwischenspeichern (»git stash») und dann erneut versuchen."
+"zwischenspeichern (»git stash«) und dann erneut versuchen."
 
-#: gitk:8414
+#: gitk:8986
 msgid ""
 "Cherry-pick failed because of merge conflict.\n"
 "Do you wish to run git citool to resolve it?"
 msgstr ""
 "Pflücken fehlgeschlagen, da ein Zusammenführungs-Konflikt aufgetreten\n"
-"ist. Soll das »git citool« (Zusammenführungs-Werkzeug) aufgerufen\n"
+"ist. Soll das Zusammenführungs-Werkzeug (»git citool«) aufgerufen\n"
 "werden, um diesen Konflikt aufzulösen?"
 
-#: gitk:8430
+#: gitk:9002
 msgid "No changes committed"
 msgstr "Keine Änderungen eingetragen"
 
-#: gitk:7745
+#: gitk:9028
 msgid "Confirm reset"
 msgstr "Zurücksetzen bestätigen"
 
-#: gitk:7747
+#: gitk:9030
 #, tcl-format
 msgid "Reset branch %s to %s?"
 msgstr "Zweig »%s« auf »%s« zurücksetzen?"
 
-#: gitk:7751
+#: gitk:9032
 msgid "Reset type:"
 msgstr "Art des Zurücksetzens:"
 
-#: gitk:7755
+#: gitk:9035
 msgid "Soft: Leave working tree and index untouched"
 msgstr "Harmlos: Arbeitskopie und Bereitstellung unverändert"
 
-#: gitk:7758
+#: gitk:9038
 msgid "Mixed: Leave working tree untouched, reset index"
 msgstr ""
 "Gemischt: Arbeitskopie unverändert,\n"
 "Bereitstellung zurückgesetzt"
 
-#: gitk:7761
+#: gitk:9041
 msgid ""
 "Hard: Reset working tree and index\n"
 "(discard ALL local changes)"
@@ -868,21 +1037,21 @@ msgstr ""
 "Hart: Arbeitskopie und Bereitstellung\n"
 "(Alle lokalen Änderungen werden gelöscht)"
 
-#: gitk:7777
+#: gitk:9058
 msgid "Resetting"
 msgstr "Zurücksetzen"
 
-#: gitk:7834
+#: gitk:9118
 msgid "Checking out"
 msgstr "Umstellen"
 
-#: gitk:7885
+#: gitk:9171
 msgid "Cannot delete the currently checked-out branch"
 msgstr ""
 "Der Zweig, auf den die Arbeitskopie momentan umgestellt ist, kann nicht "
 "gelöscht werden."
 
-#: gitk:7891
+#: gitk:9177
 #, tcl-format
 msgid ""
 "The commits on branch %s aren't on any other branch.\n"
@@ -891,199 +1060,219 @@ msgstr ""
 "Die Versionen auf Zweig »%s« existieren auf keinem anderen Zweig.\n"
 "Zweig »%s« trotzdem löschen?"
 
-#: gitk:7922
+#: gitk:9208
 #, tcl-format
 msgid "Tags and heads: %s"
 msgstr "Markierungen und Zweige: %s"
 
-#: gitk:7936
+#: gitk:9223
 msgid "Filter"
 msgstr "Filtern"
 
-#: gitk:8230
+#: gitk:9518
 msgid ""
 "Error reading commit topology information; branch and preceding/following "
 "tag information will be incomplete."
 msgstr ""
-"Fehler beim Lesen der Strukturinformationen; Zweige und Vorgänger/Nachfolger "
-"Informationen werden unvollständig sein."
+"Fehler beim Lesen der Strukturinformationen; Zweige und Informationen zu "
+"Vorgänger/Nachfolger werden unvollständig sein."
 
-#: gitk:9216
+#: gitk:10504
 msgid "Tag"
 msgstr "Markierung"
 
-#: gitk:9216
+#: gitk:10504
 msgid "Id"
 msgstr "Id"
 
-#: gitk:9262
+#: gitk:10554
 msgid "Gitk font chooser"
-msgstr "Gitk Schriften wählen"
+msgstr "Gitk-Schriften wählen"
 
-#: gitk:9279
+#: gitk:10571
 msgid "B"
 msgstr "F"
 
-#: gitk:9282
+#: gitk:10574
 msgid "I"
 msgstr "K"
 
-#: gitk:9375
+#: gitk:10692
 msgid "Gitk preferences"
-msgstr "Gitk Einstellungen"
+msgstr "Gitk-Einstellungen"
 
-#: gitk:9376
+#: gitk:10694
 msgid "Commit list display options"
-msgstr "Anzeige Versionsliste"
+msgstr "Anzeige der Versionsliste"
 
-#: gitk:9379
+#: gitk:10697
 msgid "Maximum graph width (lines)"
 msgstr "Maximale Graphenbreite (Zeilen)"
 
-#: gitk:9383
+#: gitk:10700
 #, tcl-format
 msgid "Maximum graph width (% of pane)"
 msgstr "Maximale Graphenbreite (% des Fensters)"
 
-#: gitk:9388
+#: gitk:10703
 msgid "Show local changes"
 msgstr "Lokale Änderungen anzeigen"
 
-#: gitk:9393
+#: gitk:10706
 msgid "Auto-select SHA1"
-msgstr "SHA1-Hashwert automatisch markieren"
+msgstr "SHA1-Hashwert automatisch auswählen"
 
-#: gitk:9398
+#: gitk:10709
+msgid "Hide remote refs"
+msgstr "Entfernte Zweige/Markierungen ausblenden"
+
+#: gitk:10713
 msgid "Diff display options"
-msgstr "Anzeige Vergleich"
+msgstr "Anzeige des Vergleichs"
 
-#: gitk:9400
+#: gitk:10715
 msgid "Tab spacing"
 msgstr "Tabulatorbreite"
 
-#: gitk:9404
+#: gitk:10718
 msgid "Display nearby tags"
-msgstr "Naheliegende Überschriften anzeigen"
+msgstr "Naheliegende Markierungen anzeigen"
 
-#: gitk:9409
+#: gitk:10721
 msgid "Limit diffs to listed paths"
 msgstr "Vergleich nur für angezeigte Pfade"
 
-#: gitk:9414
+#: gitk:10724
 msgid "Support per-file encodings"
 msgstr "Zeichenkodierung pro Datei ermitteln"
 
-#: gitk:9421
+#: gitk:10730 gitk:10819
 msgid "External diff tool"
-msgstr "Externes Vergleich-(Diff-)Programm"
+msgstr "Externes Diff-Programm"
 
-#: gitk:9423
+#: gitk:10731
 msgid "Choose..."
-msgstr "Wählen..."
+msgstr "Wählen ..."
+
+#: gitk:10736
+msgid "General options"
+msgstr "Allgemeine Optionen"
+
+#: gitk:10739
+msgid "Use themed widgets"
+msgstr "Aussehen der Benutzeroberfläche durch Thema bestimmen"
 
-#: gitk:9428
+#: gitk:10741
+msgid "(change requires restart)"
+msgstr "(Änderungen werden erst nach Neustart wirksam)"
+
+#: gitk:10743
+msgid "(currently unavailable)"
+msgstr "(Momentan nicht verfügbar)"
+
+#: gitk:10747
 msgid "Colors: press to choose"
 msgstr "Farben: Klicken zum Wählen"
 
-#: gitk:9431
+#: gitk:10750
+msgid "Interface"
+msgstr "Benutzeroberfläche"
+
+#: gitk:10751
+msgid "interface"
+msgstr "Benutzeroberfläche"
+
+#: gitk:10754
 msgid "Background"
 msgstr "Hintergrund"
 
-#: gitk:10153 gitk:10183
+#: gitk:10755 gitk:10785
 msgid "background"
 msgstr "Hintergrund"
 
-#: gitk:10156
+#: gitk:10758
 msgid "Foreground"
 msgstr "Vordergrund"
 
-#: gitk:10157
+#: gitk:10759
 msgid "foreground"
 msgstr "Vordergrund"
 
-#: gitk:10160
+#: gitk:10762
 msgid "Diff: old lines"
 msgstr "Vergleich: Alte Zeilen"
 
-#: gitk:10161
+#: gitk:10763
 msgid "diff old lines"
 msgstr "Vergleich - Alte Zeilen"
 
-#: gitk:10165
+#: gitk:10767
 msgid "Diff: new lines"
 msgstr "Vergleich: Neue Zeilen"
 
-#: gitk:10166
+#: gitk:10768
 msgid "diff new lines"
 msgstr "Vergleich - Neue Zeilen"
 
-#: gitk:10170
+#: gitk:10772
 msgid "Diff: hunk header"
 msgstr "Vergleich: Änderungstitel"
 
-#: gitk:10172
+#: gitk:10774
 msgid "diff hunk header"
 msgstr "Vergleich - Änderungstitel"
 
-#: gitk:10176
+#: gitk:10778
 msgid "Marked line bg"
-msgstr "Markierte Zeile Hintergrund"
+msgstr "Hintergrund für markierte Zeile"
 
-#: gitk:10178
+#: gitk:10780
 msgid "marked line background"
-msgstr "markierte Zeile Hintergrund"
+msgstr "Hintergrund für markierte Zeile"
 
-#: gitk:10182
+#: gitk:10784
 msgid "Select bg"
-msgstr "Hintergrundfarbe Auswählen"
+msgstr "Hintergrundfarbe auswählen"
 
-#: gitk:9459
+#: gitk:10788
 msgid "Fonts: press to choose"
 msgstr "Schriftart: Klicken zum Wählen"
 
-#: gitk:9461
+#: gitk:10790
 msgid "Main font"
 msgstr "Programmschriftart"
 
-#: gitk:9462
+#: gitk:10791
 msgid "Diff display font"
-msgstr "Vergleich"
+msgstr "Schriftart für Vergleich"
 
-#: gitk:9463
+#: gitk:10792
 msgid "User interface font"
 msgstr "Beschriftungen"
 
-#: gitk:9488
+#: gitk:10829
 #, tcl-format
 msgid "Gitk: choose color for %s"
 msgstr "Gitk: Farbe wählen für %s"
 
-#: gitk:9934
-msgid ""
-"Sorry, gitk cannot run with this version of Tcl/Tk.\n"
-" Gitk requires at least Tcl/Tk 8.4."
-msgstr ""
-"Gitk läuft nicht mit dieser Version von Tcl/Tk.\n"
-"Gitk benötigt mindestens Tcl/Tk 8.4."
-
-#: gitk:10047
+#: gitk:11433
 msgid "Cannot find a git repository here."
 msgstr "Kein Git-Projektarchiv gefunden."
 
-#: gitk:10051
+#: gitk:11437
 #, tcl-format
 msgid "Cannot find the git directory \"%s\"."
 msgstr "Git-Verzeichnis »%s« wurde nicht gefunden."
 
-#: gitk:10098
+#: gitk:11484
 #, tcl-format
 msgid "Ambiguous argument '%s': both revision and filename"
 msgstr "Mehrdeutige Angabe »%s«: Sowohl Version als auch Dateiname existiert."
 
-#: gitk:10110
+#: gitk:11496
 msgid "Bad arguments to gitk:"
 msgstr "Falsche Kommandozeilen-Parameter für gitk:"
 
-#: gitk:10170
+#: gitk:11587
 msgid "Command line"
 msgstr "Kommandozeile"
index 0e19b5eae27ed10e6cae524e9dc69cca1b1f428f..0471dd0672d837371fad32db5eddd32207e63c1f 100644 (file)
@@ -281,14 +281,14 @@ msgid ""
 "\n"
 "Gitk - a commit viewer for git\n"
 "\n"
-"Copyright © 2005-2008 Paul Mackerras\n"
+"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
 "\n"
 "Use and redistribute under the terms of the GNU General Public License"
 msgstr ""
 "\n"
 "Gitk - un visualizador de revisiones para git\n"
 "\n"
-"Copyright © 2005-2008 Paul Mackerras\n"
+"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
 "\n"
 "Uso y redistribución permitidos según los términos de la Licencia Pública "
 "General de GNU (GNU GPL)"
diff --git a/gitk-git/po/fr.po b/gitk-git/po/fr.po
new file mode 100644 (file)
index 0000000..5370ddc
--- /dev/null
@@ -0,0 +1,1254 @@
+# French translation of the gitk package
+# Copyright (C) 2005-2008 Paul Mackerras.  All rights reserved.
+# This file is distributed under the same license as the gitk package.
+# Translators:
+# Emmanuel Trillaud <etrillaud@gmail.com>
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: gitk\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-10-05 15:16+0200\n"
+"PO-Revision-Date: 2009-11-19 22:13+0100\n"
+"Last-Translator: Emmanuel Trillaud <etrillaud@gmail.com>\n"
+"Language-Team: git@vger.kernel.org\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: French\n"
+"X-Poedit-Country: FRANCE\n"
+
+#: gitk:113
+msgid "Couldn't get list of unmerged files:"
+msgstr "Impossible de récupérer la liste des fichiers non fusionnés :"
+
+#: gitk:269
+msgid "Error parsing revisions:"
+msgstr "Erreur lors du parcours des révisions :"
+
+#: gitk:324
+msgid "Error executing --argscmd command:"
+msgstr "Erreur à l'exécution de la commande --argscmd :"
+
+#: gitk:337
+msgid "No files selected: --merge specified but no files are unmerged."
+msgstr ""
+"Aucun fichier sélectionné : --merge précisé, mais tous les fichiers sont "
+"fusionnés."
+
+# FIXME : améliorer la traduction de 'file limite'
+#: gitk:340
+#, fuzzy
+msgid ""
+"No files selected: --merge specified but no unmerged files are within file "
+"limit."
+msgstr ""
+"Aucun fichier sélectionné : --merge précisé mais aucun fichier non fusionné "
+"n'est dans la limite des fichiers."
+
+#: gitk:362 gitk:509
+msgid "Error executing git log:"
+msgstr "Erreur à l'exécution de git log :"
+
+#: gitk:380 gitk:525
+msgid "Reading"
+msgstr "Lecture en cours"
+
+#: gitk:440 gitk:4123
+msgid "Reading commits..."
+msgstr "Lecture des commits..."
+
+#: gitk:443 gitk:1561 gitk:4126
+msgid "No commits selected"
+msgstr "Aucun commit sélectionné"
+
+#: gitk:1437
+msgid "Can't parse git log output:"
+msgstr "Impossible de lire la sortie de git log :"
+
+#: gitk:1657
+msgid "No commit information available"
+msgstr "Aucune information disponible sur le commit"
+
+#: gitk:1793 gitk:1817 gitk:3916 gitk:8786 gitk:10322 gitk:10498
+msgid "OK"
+msgstr "OK"
+
+#: gitk:1819 gitk:3918 gitk:8383 gitk:8457 gitk:8567 gitk:8616 gitk:8788
+#: gitk:10323 gitk:10499
+msgid "Cancel"
+msgstr "Annuler"
+
+#: gitk:1919
+msgid "Update"
+msgstr "Mise à jour"
+
+#: gitk:1920
+msgid "Reload"
+msgstr "Recharger"
+
+#: gitk:1921
+msgid "Reread references"
+msgstr "Relire les références"
+
+#: gitk:1922
+msgid "List references"
+msgstr "Lister les références"
+
+#: gitk:1924
+msgid "Start git gui"
+msgstr "Démarrer git gui"
+
+#: gitk:1926
+msgid "Quit"
+msgstr "Quitter"
+
+#: gitk:1918
+msgid "File"
+msgstr "Fichier"
+
+#: gitk:1930
+msgid "Preferences"
+msgstr "Préférences"
+
+#: gitk:1929
+msgid "Edit"
+msgstr "Éditer"
+
+#: gitk:1934
+msgid "New view..."
+msgstr "Nouvelle vue..."
+
+#: gitk:1935
+msgid "Edit view..."
+msgstr "Éditer la vue..."
+
+#: gitk:1936
+msgid "Delete view"
+msgstr "Supprimer la vue"
+
+#: gitk:1938
+msgid "All files"
+msgstr "Tous les fichiers"
+
+#: gitk:1933 gitk:3670
+msgid "View"
+msgstr "Vue"
+
+#: gitk:1943 gitk:1953 gitk:2654
+msgid "About gitk"
+msgstr "À propos de gitk"
+
+#: gitk:1944 gitk:1958
+msgid "Key bindings"
+msgstr "Raccourcis clavier"
+
+#: gitk:1942 gitk:1957
+msgid "Help"
+msgstr "Aide"
+
+#: gitk:2018
+msgid "SHA1 ID: "
+msgstr "ID SHA1 :"
+
+#: gitk:2049
+msgid "Row"
+msgstr "Colonne"
+
+#: gitk:2080
+msgid "Find"
+msgstr "Recherche"
+
+#: gitk:2081
+msgid "next"
+msgstr "suivant"
+
+#: gitk:2082
+msgid "prev"
+msgstr "précédent"
+
+#: gitk:2083
+msgid "commit"
+msgstr "commit"
+
+#: gitk:2086 gitk:2088 gitk:4284 gitk:4307 gitk:4331 gitk:6272 gitk:6344
+#: gitk:6428
+msgid "containing:"
+msgstr "contient :"
+
+#: gitk:2089 gitk:3162 gitk:3167 gitk:4359
+msgid "touching paths:"
+msgstr "chemins modifiés :"
+
+#: gitk:2090 gitk:4364
+msgid "adding/removing string:"
+msgstr "ajoute/supprime la chaîne :"
+
+#: gitk:2099 gitk:2101
+msgid "Exact"
+msgstr "Exact"
+
+#: gitk:2101 gitk:4439 gitk:6240
+msgid "IgnCase"
+msgstr "Ignorer la casse"
+
+#: gitk:2101 gitk:4333 gitk:4437 gitk:6236
+msgid "Regexp"
+msgstr "Expression régulière"
+
+#: gitk:2103 gitk:2104 gitk:4458 gitk:4488 gitk:4495 gitk:6364 gitk:6432
+msgid "All fields"
+msgstr "Tous les champs"
+
+#: gitk:2104 gitk:4456 gitk:4488 gitk:6303
+msgid "Headline"
+msgstr "Surligner"
+
+#: gitk:2105 gitk:4456 gitk:6303 gitk:6432 gitk:6866
+msgid "Comments"
+msgstr "Commentaires"
+
+#: gitk:2105 gitk:4456 gitk:4460 gitk:4495 gitk:6303 gitk:6801 gitk:8063
+#: gitk:8078
+msgid "Author"
+msgstr "Auteur"
+
+#: gitk:2105 gitk:4456 gitk:6303 gitk:6803
+msgid "Committer"
+msgstr "Auteur du commit"
+
+#: gitk:2134
+msgid "Search"
+msgstr "Rechercher"
+
+#: gitk:2141
+msgid "Diff"
+msgstr "Diff"
+
+#: gitk:2143
+msgid "Old version"
+msgstr "Ancienne version"
+
+#: gitk:2145
+msgid "New version"
+msgstr "Nouvelle version"
+
+#: gitk:2147
+msgid "Lines of context"
+msgstr "Lignes de contexte"
+
+#: gitk:2157
+msgid "Ignore space change"
+msgstr "Ignorer les modifications d'espace"
+
+#: gitk:2215
+msgid "Patch"
+msgstr "Patch"
+
+#: gitk:2217
+msgid "Tree"
+msgstr "Arbre"
+
+#: gitk:2361 gitk:2378
+msgid "Diff this -> selected"
+msgstr "Diff entre ceci et la sélection"
+
+#: gitk:2362 gitk:2379
+msgid "Diff selected -> this"
+msgstr "Diff entre sélection et ceci"
+
+#: gitk:2363 gitk:2380
+msgid "Make patch"
+msgstr "Créer patch"
+
+#: gitk:2364 gitk:8441
+msgid "Create tag"
+msgstr "Créer tag"
+
+#: gitk:2365 gitk:8547
+msgid "Write commit to file"
+msgstr "Écrire le commit dans un fichier"
+
+#: gitk:2366 gitk:8604
+msgid "Create new branch"
+msgstr "Créer une nouvelle branche"
+
+#: gitk:2367
+msgid "Cherry-pick this commit"
+msgstr "Cueillir (cherry-pick) ce commit"
+
+#: gitk:2368
+msgid "Reset HEAD branch to here"
+msgstr "Réinitialiser la branche HEAD vers cet état"
+
+#: gitk:2369
+msgid "Mark this commit"
+msgstr "Marquer ce commit"
+
+#: gitk:2370
+msgid "Return to mark"
+msgstr "Retourner à la marque"
+
+#: gitk:2371
+msgid "Find descendant of this and mark"
+msgstr "Chercher le descendant de ceci et le marquer"
+
+#: gitk:2372
+msgid "Compare with marked commit"
+msgstr "Comparer avec le commit marqué"
+
+#: gitk:2386
+msgid "Check out this branch"
+msgstr "Récupérer cette branche"
+
+#: gitk:2387
+msgid "Remove this branch"
+msgstr "Supprimer cette branche"
+
+#: gitk:2394
+msgid "Highlight this too"
+msgstr "Surligner également ceci"
+
+#: gitk:2395
+msgid "Highlight this only"
+msgstr "Surligner seulement ceci"
+
+#: gitk:2396
+msgid "External diff"
+msgstr "Diff externe"
+
+#: gitk:2397
+msgid "Blame parent commit"
+msgstr "Blâmer le commit parent"
+
+#: gitk:2404
+msgid "Show origin of this line"
+msgstr "Montrer l'origine de cette ligne"
+
+#: gitk:2405
+msgid "Run git gui blame on this line"
+msgstr "Exécuter git gui blame sur cette ligne"
+
+#: gitk:2656
+msgid ""
+"\n"
+"Gitk - a commit viewer for git\n"
+"\n"
+"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
+"\n"
+"Use and redistribute under the terms of the GNU General Public License"
+msgstr ""
+"\n"
+"Gitk - visualisateur de commit pour git\n"
+"\n"
+"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
+"\n"
+"Utilisation et redistribution soumises aux termes de la GNU General Public "
+"License"
+
+#: gitk:2664 gitk:2726 gitk:8969
+msgid "Close"
+msgstr "Fermer"
+
+#: gitk:2683
+msgid "Gitk key bindings"
+msgstr "Raccourcis clavier de Gitk"
+
+#: gitk:2686
+msgid "Gitk key bindings:"
+msgstr "Raccourcis clavier de Gitk :"
+
+#: gitk:2688
+#, tcl-format
+msgid "<%s-Q>\t\tQuit"
+msgstr "<%s-Q>\t\tQuitter"
+
+#: gitk:2689
+msgid "<Home>\t\tMove to first commit"
+msgstr "<Début>\t\tAller au premier commit"
+
+#: gitk:2690
+msgid "<End>\t\tMove to last commit"
+msgstr "<Fin>\t\tAller au dernier commit"
+
+#: gitk:2691
+msgid "<Up>, p, i\tMove up one commit"
+msgstr "<Haut>, p, i\t Aller au commit suivant"
+
+#: gitk:2692
+msgid "<Down>, n, k\tMove down one commit"
+msgstr "<Bas>, n, k\t Aller au commit précédent"
+
+#: gitk:2693
+msgid "<Left>, z, j\tGo back in history list"
+msgstr "<Gauche>, z, j\tReculer dans l'historique"
+
+#: gitk:2694
+msgid "<Right>, x, l\tGo forward in history list"
+msgstr "<Droite>, x, l\tAvancer dans l'historique"
+
+#: gitk:2695
+msgid "<PageUp>\tMove up one page in commit list"
+msgstr "<PageUp>\tMonter d'une page dans la liste des commits"
+
+#: gitk:2696
+msgid "<PageDown>\tMove down one page in commit list"
+msgstr "<PageDown>\tDescendre d'une page dans la liste des commits"
+
+#: gitk:2697
+#, tcl-format
+msgid "<%s-Home>\tScroll to top of commit list"
+msgstr "<%s-Début>\tAller en haut de la liste des commits"
+
+#: gitk:2698
+#, tcl-format
+msgid "<%s-End>\tScroll to bottom of commit list"
+msgstr "<%s-End>\tAller en bas de la liste des commits"
+
+#: gitk:2699
+#, tcl-format
+msgid "<%s-Up>\tScroll commit list up one line"
+msgstr "<%s-Up>\tMonter d'une ligne dans la liste des commits"
+
+#: gitk:2700
+#, tcl-format
+msgid "<%s-Down>\tScroll commit list down one line"
+msgstr "<%s-Down>\tDescendre d'une ligne dans la liste des commits"
+
+#: gitk:2701
+#, tcl-format
+msgid "<%s-PageUp>\tScroll commit list up one page"
+msgstr "<%s-PageUp>\tMonter d'une page dans la liste des commits"
+
+#: gitk:2702
+#, tcl-format
+msgid "<%s-PageDown>\tScroll commit list down one page"
+msgstr "<%s-PageDown>\tDescendre d'une page dans la liste des commits"
+
+#: gitk:2703
+msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
+msgstr ""
+"<Shift-Up>\tRecherche en arrière (vers l'avant, commits les plus anciens)"
+
+#: gitk:2704
+msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
+msgstr ""
+"<Shift-Down>\tRecherche en avant (vers l'arrière, commit les plus récents)"
+
+#: gitk:2705
+msgid "<Delete>, b\tScroll diff view up one page"
+msgstr "<Supprimer>, b\tMonter d'une page dans la vue des diff"
+
+#: gitk:2706
+msgid "<Backspace>\tScroll diff view up one page"
+msgstr "<Backspace>\tMonter d'une page dans la vue des diff"
+
+#: gitk:2707
+msgid "<Space>\t\tScroll diff view down one page"
+msgstr "<Espace>\t\tDescendre d'une page dans la vue des diff"
+
+#: gitk:2708
+msgid "u\t\tScroll diff view up 18 lines"
+msgstr "u\t\tMonter de 18 lignes dans la vue des diff"
+
+#: gitk:2709
+msgid "d\t\tScroll diff view down 18 lines"
+msgstr "d\t\tDescendre de 18 lignes dans la vue des diff"
+
+#: gitk:2710
+#, tcl-format
+msgid "<%s-F>\t\tFind"
+msgstr "<%s-F>\t\tRechercher"
+
+#: gitk:2711
+#, tcl-format
+msgid "<%s-G>\t\tMove to next find hit"
+msgstr "<%s-G>\t\tAller au résultat de recherche suivant"
+
+#: gitk:2712
+msgid "<Return>\tMove to next find hit"
+msgstr "<Return>\t\tAller au résultat de recherche suivant"
+
+#: gitk:2713
+msgid "/\t\tFocus the search box"
+msgstr "/\t\tFocus sur la zone de recherche"
+
+#: gitk:2714
+msgid "?\t\tMove to previous find hit"
+msgstr "?\t\tAller au résultat de recherche précédent"
+
+#: gitk:2715
+msgid "f\t\tScroll diff view to next file"
+msgstr "f\t\tAller au prochain fichier dans la vue des diff"
+
+#: gitk:2716
+#, tcl-format
+msgid "<%s-S>\t\tSearch for next hit in diff view"
+msgstr "<%s-S>\t\tAller au résultat suivant dans la vue des diff"
+
+#: gitk:2717
+#, tcl-format
+msgid "<%s-R>\t\tSearch for previous hit in diff view"
+msgstr "<%s-R>\t\tAller au résultat précédent dans la vue des diff"
+
+#: gitk:2718
+#, tcl-format
+msgid "<%s-KP+>\tIncrease font size"
+msgstr "<%s-KP+>\tAugmenter la taille de la police"
+
+#: gitk:2719
+#, tcl-format
+msgid "<%s-plus>\tIncrease font size"
+msgstr "<%s-plus>\tAugmenter la taille de la police"
+
+#: gitk:2720
+#, tcl-format
+msgid "<%s-KP->\tDecrease font size"
+msgstr "<%s-KP->\tDiminuer la taille de la police"
+
+#: gitk:2721
+#, tcl-format
+msgid "<%s-minus>\tDecrease font size"
+msgstr "<%s-minus>\tDiminuer la taille de la police"
+
+#: gitk:2722
+msgid "<F5>\t\tUpdate"
+msgstr "<F5>\t\tMise à jour"
+
+#: gitk:3177
+#, tcl-format
+msgid "Error getting \"%s\" from %s:"
+msgstr "Erreur en obtenant \"%s\" de %s:"
+
+#: gitk:3234 gitk:3243
+#, tcl-format
+msgid "Error creating temporary directory %s:"
+msgstr "Erreur lors de la création du répertoire temporaire %s :"
+
+#: gitk:3255
+msgid "command failed:"
+msgstr "échec de la commande :"
+
+#: gitk:3401
+msgid "No such commit"
+msgstr "Commit inexistant"
+
+#: gitk:3415
+msgid "git gui blame: command failed:"
+msgstr "git gui blame : échec de la commande :"
+
+#: gitk:3446
+#, tcl-format
+msgid "Couldn't read merge head: %s"
+msgstr "Impossible de lire le head de la fusion : %s"
+
+#: gitk:3454
+#, tcl-format
+msgid "Error reading index: %s"
+msgstr "Erreur à la lecture de l'index : %s"
+
+#: gitk:3479
+#, tcl-format
+msgid "Couldn't start git blame: %s"
+msgstr "Impossible de démarrer git blame : %s"
+
+#: gitk:3482 gitk:6271
+msgid "Searching"
+msgstr "Recherche en cours"
+
+#: gitk:3514
+#, tcl-format
+msgid "Error running git blame: %s"
+msgstr "Erreur à l'exécution de git blame : %s"
+
+#: gitk:3542
+#, tcl-format
+msgid "That line comes from commit %s,  which is not in this view"
+msgstr "Cette ligne est issue du commit %s, qui n'est pas dans cette vue"
+
+#: gitk:3556
+msgid "External diff viewer failed:"
+msgstr "Échec de l'outil externe de visualisation des diff"
+
+#: gitk:3674
+msgid "Gitk view definition"
+msgstr "Définition des vues de Gitk"
+
+#: gitk:3678
+msgid "Remember this view"
+msgstr "Se souvenir de cette vue"
+
+#: gitk:3679
+msgid "References (space separated list):"
+msgstr "Références (liste d'éléments séparés par des espaces) :"
+
+#: gitk:3680
+msgid "Branches & tags:"
+msgstr "Branches & tags :"
+
+#: gitk:3681
+msgid "All refs"
+msgstr "Toutes les références"
+
+#: gitk:3682
+msgid "All (local) branches"
+msgstr "Toutes les branches (locales)"
+
+#: gitk:3683
+msgid "All tags"
+msgstr "Tous les tags"
+
+#: gitk:3684
+msgid "All remote-tracking branches"
+msgstr "Toutes les branches de suivi à distance"
+
+#: gitk:3685
+msgid "Commit Info (regular expressions):"
+msgstr "Info sur les commits (expressions régulières) :"
+
+#: gitk:3686
+msgid "Author:"
+msgstr "Auteur :"
+
+#: gitk:3687
+msgid "Committer:"
+msgstr "Commiteur :"
+
+#: gitk:3688
+msgid "Commit Message:"
+msgstr "Message de commit :"
+
+#: gitk:3689
+msgid "Matches all Commit Info criteria"
+msgstr "Correspond à tous les critères d'Info sur les commits"
+
+#: gitk:3690
+msgid "Changes to Files:"
+msgstr "Changements des fichiers :"
+
+#: gitk:3691
+msgid "Fixed String"
+msgstr "Chaîne Figée"
+
+#: gitk:3692
+msgid "Regular Expression"
+msgstr "Expression Régulière"
+
+#: gitk:3693
+msgid "Search string:"
+msgstr "Recherche de la chaîne :"
+
+#: gitk:3694
+msgid ""
+"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+msgstr ""
+"Dates des commits (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, "
+"2009 15:27:38\") :"
+
+#: gitk:3695
+msgid "Since:"
+msgstr "De :"
+
+#: gitk:3696
+msgid "Until:"
+msgstr "Jusqu'au :"
+
+#: gitk:3697
+msgid "Limit and/or skip a number of revisions (positive integer):"
+msgstr "Limiter et/ou sauter un certain nombre (entier positif) de révisions :"
+
+#: gitk:3698
+msgid "Number to show:"
+msgstr "Nombre à afficher :"
+
+#: gitk:3699
+msgid "Number to skip:"
+msgstr "Nombre à sauter :"
+
+#: gitk:3700
+msgid "Miscellaneous options:"
+msgstr "Options diverses :"
+
+#: gitk:3701
+msgid "Strictly sort by date"
+msgstr "Trier par date"
+
+# FIXME : traduction de "branch sides"
+#: gitk:3702
+#, fuzzy
+msgid "Mark branch sides"
+msgstr "Marquer les extrémités des branches"
+
+#: gitk:3703
+msgid "Limit to first parent"
+msgstr "Limiter au premier ancêtre"
+
+#: gitk:3704
+msgid "Simple history"
+msgstr "Historique simple"
+
+#: gitk:3705
+msgid "Additional arguments to git log:"
+msgstr "Arguments supplémentaires de git log :"
+
+#: gitk:3706
+msgid "Enter files and directories to include, one per line:"
+msgstr "Saisir les fichiers et répertoires à inclure, un par ligne :"
+
+#: gitk:3707
+msgid "Command to generate more commits to include:"
+msgstr "Commande pour générer plus de commits à inclure :"
+
+#: gitk:3829
+msgid "Gitk: edit view"
+msgstr "Gitk : éditer la vue"
+
+#: gitk:3837
+msgid "-- criteria for selecting revisions"
+msgstr "-- critère pour la sélection des révisions"
+
+#: gitk:3842
+msgid "View Name:"
+msgstr "Nom de la vue :"
+
+#: gitk:3917
+msgid "Apply (F5)"
+msgstr "Appliquer (F5)"
+
+#: gitk:3955
+msgid "Error in commit selection arguments:"
+msgstr "Erreur dans les arguments de sélection des commits :"
+
+#: gitk:4008 gitk:4060 gitk:4508 gitk:4522 gitk:5783 gitk:11196 gitk:11197
+msgid "None"
+msgstr "Aucun"
+
+#: gitk:4456 gitk:6303 gitk:8065 gitk:8080
+msgid "Date"
+msgstr "Date"
+
+#: gitk:4456 gitk:6303
+msgid "CDate"
+msgstr "CDate"
+
+#: gitk:4605 gitk:4610
+msgid "Descendant"
+msgstr "Descendant"
+
+#: gitk:4606
+msgid "Not descendant"
+msgstr "Pas un descendant"
+
+#: gitk:4613 gitk:4618
+msgid "Ancestor"
+msgstr "Ancêtre"
+
+#: gitk:4614
+msgid "Not ancestor"
+msgstr "Pas un ancêtre"
+
+#: gitk:4904
+msgid "Local changes checked in to index but not committed"
+msgstr "Modifications locales enregistrées dans l'index mais non commitées"
+
+#: gitk:4940
+msgid "Local uncommitted changes, not checked in to index"
+msgstr "Modifications locales non enregistrées dans l'index et non commitées"
+
+#: gitk:6621
+msgid "many"
+msgstr "nombreux"
+
+#: gitk:6805
+msgid "Tags:"
+msgstr "Tags :"
+
+#: gitk:6822 gitk:6828 gitk:8058
+msgid "Parent"
+msgstr "Parent"
+
+#: gitk:6833
+msgid "Child"
+msgstr "Enfant"
+
+#: gitk:6842
+msgid "Branch"
+msgstr "Branche"
+
+#: gitk:6845
+msgid "Follows"
+msgstr "Suit"
+
+#: gitk:6848
+msgid "Precedes"
+msgstr "Précède"
+
+#: gitk:7346
+#, tcl-format
+msgid "Error getting diffs: %s"
+msgstr "Erreur lors de la récupération des diff : %s"
+
+#: gitk:7886
+msgid "Goto:"
+msgstr "Aller à :"
+
+#: gitk:7888
+msgid "SHA1 ID:"
+msgstr "Id SHA1 :"
+
+#: gitk:7907
+#, tcl-format
+msgid "Short SHA1 id %s is ambiguous"
+msgstr "Id SHA1 court %s est ambigu"
+
+#: gitk:7914
+#, tcl-format
+msgid "Revision %s is not known"
+msgstr "Id SHA1 %s est inconnu"
+
+#: gitk:7924
+#, tcl-format
+msgid "SHA1 id %s is not known"
+msgstr "Id SHA1 %s est inconnu"
+
+#: gitk:7926
+#, tcl-format
+msgid "Revision %s is not in the current view"
+msgstr "La révision %s n'est pas dans la vue courante"
+
+#: gitk:8068
+msgid "Children"
+msgstr "Enfants"
+
+#: gitk:8125
+#, tcl-format
+msgid "Reset %s branch to here"
+msgstr "Réinitialiser la branche %s vers cet état"
+
+#: gitk:8127
+msgid "Detached head: can't reset"
+msgstr "Head détaché : impossible de réinitialiser"
+
+#: gitk:8236 gitk:8242
+msgid "Skipping merge commit "
+msgstr "Éviter le commit de la fusion "
+
+#: gitk:8251 gitk:8256
+msgid "Error getting patch ID for "
+msgstr "Erreur à l'obtention de l'ID du patch pour "
+
+#: gitk:8252 gitk:8257
+msgid " - stopping\n"
+msgstr " - arrêt en cours\n"
+
+#: gitk:8262 gitk:8265 gitk:8273 gitk:8283 gitk:8292
+msgid "Commit "
+msgstr "Commit "
+
+#: gitk:8266
+msgid ""
+" is the same patch as\n"
+"       "
+msgstr ""
+"est le même patch que \n"
+"       "
+
+#: gitk:8274
+msgid ""
+" differs from\n"
+"       "
+msgstr ""
+" diffère de\n"
+"       "
+
+#: gitk:8276
+msgid "- stopping\n"
+msgstr "- arrêt en cours\n"
+
+#: gitk:8284 gitk:8293
+#, tcl-format
+msgid " has %s children - stopping\n"
+msgstr "a %s enfants - arrêt en cours\n"
+
+#: gitk:8324
+msgid "Top"
+msgstr "Haut"
+
+#: gitk:8325
+msgid "From"
+msgstr "De"
+
+#: gitk:8330
+msgid "To"
+msgstr "À"
+
+#: gitk:8354
+msgid "Generate patch"
+msgstr "Générer le patch"
+
+#: gitk:8356
+msgid "From:"
+msgstr "De :"
+
+#: gitk:8365
+msgid "To:"
+msgstr "À :"
+
+#: gitk:8374
+msgid "Reverse"
+msgstr "Inverser"
+
+#: gitk:8376 gitk:8561
+msgid "Output file:"
+msgstr "Fichier de sortie :"
+
+#: gitk:8382
+msgid "Generate"
+msgstr "Générer"
+
+#: gitk:8420
+msgid "Error creating patch:"
+msgstr "Erreur à la création du patch :"
+
+#: gitk:8443 gitk:8549 gitk:8606
+msgid "ID:"
+msgstr "ID :"
+
+#: gitk:8452
+msgid "Tag name:"
+msgstr "Nom du Tag :"
+
+#: gitk:8456 gitk:8615
+msgid "Create"
+msgstr "Créer"
+
+#: gitk:8473
+msgid "No tag name specified"
+msgstr "Aucun nom de tag spécifié"
+
+#: gitk:8477
+#, tcl-format
+msgid "Tag \"%s\" already exists"
+msgstr "Le tag \"%s\" existe déjà"
+
+#: gitk:8483
+msgid "Error creating tag:"
+msgstr "Erreur à la création du tag :"
+
+#: gitk:8558
+msgid "Command:"
+msgstr "Commande :"
+
+#: gitk:8566
+msgid "Write"
+msgstr "Écrire"
+
+#: gitk:8584
+msgid "Error writing commit:"
+msgstr "Erreur à l'ecriture du commit :"
+
+#: gitk:8611
+msgid "Name:"
+msgstr "Nom :"
+
+#: gitk:8634
+msgid "Please specify a name for the new branch"
+msgstr "Veuillez spécifier un nom pour la nouvelle branche"
+
+#: gitk:8639
+#, tcl-format
+msgid "Branch '%s' already exists. Overwrite?"
+msgstr "La branche '%s' existe déjà. Écraser?"
+
+#: gitk:8705
+#, tcl-format
+msgid "Commit %s is already included in branch %s -- really re-apply it?"
+msgstr ""
+"Le Commit %s est déjà inclus dans la branche %s -- le ré-appliquer malgré "
+"tout?"
+
+#: gitk:8710
+msgid "Cherry-picking"
+msgstr "Cueillir (Cherry-picking)"
+
+#: gitk:8719
+#, tcl-format
+msgid ""
+"Cherry-pick failed because of local changes to file '%s'.\n"
+"Please commit, reset or stash your changes and try again."
+msgstr ""
+"La cueillette (cherry-pick) a échouée à cause de modifications locales du "
+"fichier '%s'.\n"
+"Veuillez commiter, réinitialiser ou stasher vos changements et essayer de "
+"nouveau."
+
+#: gitk:8725
+msgid ""
+"Cherry-pick failed because of merge conflict.\n"
+"Do you wish to run git citool to resolve it?"
+msgstr ""
+"La cueillette (cherry-pick) a échouée à cause d'un conflit lors d'une "
+"fusion.\n"
+"Souhaitez-vous exécuter git citool pour le résoudre ?"
+
+#: gitk:8741
+msgid "No changes committed"
+msgstr "Aucun changement commité"
+
+#: gitk:8767
+msgid "Confirm reset"
+msgstr "Confirmer la réinitialisation"
+
+#: gitk:8769
+#, tcl-format
+msgid "Reset branch %s to %s?"
+msgstr "Réinitialiser la branche %s à %s?"
+
+#: gitk:8773
+msgid "Reset type:"
+msgstr "Type de réinitialisation :"
+
+#: gitk:8777
+msgid "Soft: Leave working tree and index untouched"
+msgstr "Douce : Laisse le répertoire de travail et l'index intacts"
+
+#: gitk:8780
+msgid "Mixed: Leave working tree untouched, reset index"
+msgstr ""
+"Hybride : Laisse le répertoire de travail dans son état courant, "
+"réinitialise l'index"
+
+#: gitk:8783
+msgid ""
+"Hard: Reset working tree and index\n"
+"(discard ALL local changes)"
+msgstr ""
+"Dure : Réinitialise le répertoire de travail et l'index\n"
+"(abandonne TOUS les changements locaux)"
+
+#: gitk:8800
+msgid "Resetting"
+msgstr "Réinitialisation"
+
+# Fixme: Récupération est-il vraiment une mauvaise traduction?
+#: gitk:8857
+#, fuzzy
+msgid "Checking out"
+msgstr "Récupération"
+
+#: gitk:8910
+msgid "Cannot delete the currently checked-out branch"
+msgstr "Impossible de supprimer la branche en cours"
+
+#: gitk:8916
+#, tcl-format
+msgid ""
+"The commits on branch %s aren't on any other branch.\n"
+"Really delete branch %s?"
+msgstr ""
+"Les commits de la branche %s ne sont dans aucune autre branche.\n"
+"Voulez-vous vraiment supprimer cette branche %s ?"
+
+#: gitk:8947
+#, tcl-format
+msgid "Tags and heads: %s"
+msgstr "Tags et heads : %s"
+
+#: gitk:8962
+msgid "Filter"
+msgstr "Filtrer"
+
+#: gitk:9257
+msgid ""
+"Error reading commit topology information; branch and preceding/following "
+"tag information will be incomplete."
+msgstr ""
+"Erreur à la lecture des informations sur la topologie des commits, les "
+"informations sur les branches et les tags précédents/suivants seront "
+"incomplètes."
+
+#: gitk:10243
+msgid "Tag"
+msgstr "Tag"
+
+#: gitk:10243
+msgid "Id"
+msgstr "Id"
+
+#: gitk:10291
+msgid "Gitk font chooser"
+msgstr "Sélecteur de police de Gitk"
+
+#: gitk:10308
+msgid "B"
+msgstr "B"
+
+#: gitk:10311
+msgid "I"
+msgstr "I"
+
+#: gitk:10407
+msgid "Gitk preferences"
+msgstr "Préférences de Gitk"
+
+#: gitk:10409
+msgid "Commit list display options"
+msgstr "Options d'affichage de la liste des commits"
+
+#: gitk:10412
+msgid "Maximum graph width (lines)"
+msgstr "Longueur maximum du graphe (lignes)"
+
+# FIXME : Traduction standard de "pane"?
+#: gitk:10416
+#, fuzzy, tcl-format
+msgid "Maximum graph width (% of pane)"
+msgstr "Longueur maximum du graphe (% du panneau)"
+
+#: gitk:10420
+msgid "Show local changes"
+msgstr "Montrer les changements locaux"
+
+#: gitk:10423
+msgid "Auto-select SHA1"
+msgstr "Sélection auto. du SHA1"
+
+#: gitk:10427
+msgid "Diff display options"
+msgstr "Options d'affichage des diff"
+
+#: gitk:10429
+msgid "Tab spacing"
+msgstr "Taille des tabulations"
+
+#: gitk:10432
+msgid "Display nearby tags"
+msgstr "Afficher les tags les plus proches"
+
+#: gitk:10435
+msgid "Hide remote refs"
+msgstr "Cacher les refs distantes"
+
+#: gitk:10438
+msgid "Limit diffs to listed paths"
+msgstr "Limiter les différences aux chemins listés"
+
+#: gitk:10441
+msgid "Support per-file encodings"
+msgstr "Support pour un encodage des caractères par fichier"
+
+#: gitk:10447 gitk:10512
+msgid "External diff tool"
+msgstr "Outil diff externe"
+
+#: gitk:10449
+msgid "Choose..."
+msgstr "Choisir..."
+
+#: gitk:10454
+msgid "Colors: press to choose"
+msgstr "Couleurs : cliquer pour choisir"
+
+#: gitk:10457
+msgid "Background"
+msgstr "Arrière-plan"
+
+#: gitk:10458 gitk:10488
+msgid "background"
+msgstr "arrière-plan"
+
+#: gitk:10461
+msgid "Foreground"
+msgstr "Premier plan"
+
+#: gitk:10462
+msgid "foreground"
+msgstr "premier plan"
+
+#: gitk:10465
+msgid "Diff: old lines"
+msgstr "Diff : anciennes lignes"
+
+#: gitk:10466
+msgid "diff old lines"
+msgstr "diff anciennes lignes"
+
+#: gitk:10470
+msgid "Diff: new lines"
+msgstr "Diff : nouvelles lignes"
+
+#: gitk:10471
+msgid "diff new lines"
+msgstr "diff nouvelles lignes"
+
+#: gitk:10475
+msgid "Diff: hunk header"
+msgstr "Diff : entête du hunk"
+
+#: gitk:10477
+msgid "diff hunk header"
+msgstr "diff : entête du hunk"
+
+#: gitk:10481
+msgid "Marked line bg"
+msgstr "Arrière-plan de la ligne marquée"
+
+#: gitk:10483
+msgid "marked line background"
+msgstr "Arrière-plan de la ligne marquée"
+
+#: gitk:10487
+msgid "Select bg"
+msgstr "Sélectionner l'arrière-plan"
+
+#: gitk:10491
+msgid "Fonts: press to choose"
+msgstr "Polices : cliquer pour choisir"
+
+#: gitk:10493
+msgid "Main font"
+msgstr "Police principale"
+
+#: gitk:10494
+msgid "Diff display font"
+msgstr "Police d'affichage des diff"
+
+#: gitk:10495
+msgid "User interface font"
+msgstr "Police de l'interface utilisateur"
+
+#: gitk:10522
+#, tcl-format
+msgid "Gitk: choose color for %s"
+msgstr "Gitk : choisir la couleur de %s"
+
+#: gitk:10973
+msgid ""
+"Sorry, gitk cannot run with this version of Tcl/Tk.\n"
+" Gitk requires at least Tcl/Tk 8.4."
+msgstr ""
+"Désolé, gitk ne peut être exécuté avec cette version de Tcl/Tk.\n"
+" Gitk requiert Tcl/Tk version 8.4 ou supérieur."
+
+#: gitk:11101
+msgid "Cannot find a git repository here."
+msgstr "Impossible de trouver un dépôt git ici."
+
+#: gitk:11105
+#, tcl-format
+msgid "Cannot find the git directory \"%s\"."
+msgstr "Impossible de trouver le répertoire git \"%s\"."
+
+#: gitk:11152
+#, tcl-format
+msgid "Ambiguous argument '%s': both revision and filename"
+msgstr "Argument '%s' ambigu : à la fois une révision et un nom de fichier"
+
+#: gitk:11164
+msgid "Bad arguments to gitk:"
+msgstr "Arguments invalides pour gitk :"
+
+#: gitk:11249
+msgid "Command line"
+msgstr "Ligne de commande"
diff --git a/gitk-git/po/hu.po b/gitk-git/po/hu.po
new file mode 100644 (file)
index 0000000..7262b61
--- /dev/null
@@ -0,0 +1,1295 @@
+# Translation of gitk to Hungarian.
+# Copyright (C) 2007 Paul Mackerras.
+# This file is distributed under the same license as the gitk package.
+#
+# Laszlo Papp <laszlo.papp@arhungary.hu>, 2009.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: git-gui\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-12-14 13:33+0100\n"
+"PO-Revision-Date: 2009-12-14 14:04+0100\n"
+"Last-Translator: Laszlo Papp <djszapi@archlinux.us>\n"
+"Language-Team: Hungarian\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: gitk:115
+msgid "Couldn't get list of unmerged files:"
+msgstr "Nem sikerült letölteni az unmerged fájl listát:"
+
+#: gitk:271
+msgid "Error parsing revisions:"
+msgstr "Hiba történt értelmezés közben:"
+
+#: gitk:326
+msgid "Error executing --argscmd command:"
+msgstr "Hiba történt a végrehajtáskor --argscmd parancs:"
+
+#: gitk:339
+msgid "No files selected: --merge specified but no files are unmerged."
+msgstr ""
+"Nincsen fájl kiválasztva: --merge megadve, de egyetlen fájl sem unmerged."
+
+#: gitk:342
+msgid ""
+"No files selected: --merge specified but no unmerged files are within file "
+"limit."
+msgstr ""
+"Nincsen fájl kiválasztva: --merge megadva, de nincsenek unmerged fájlok a "
+"fájlon belül limit."
+
+#: gitk:364 gitk:511
+msgid "Error executing git log:"
+msgstr "Hiba történt a git log végrehajtása közben:"
+
+#: gitk:382 gitk:527
+msgid "Reading"
+msgstr "Olvasás"
+
+#: gitk:442 gitk:4258
+msgid "Reading commits..."
+msgstr "Commitok olvasása ..."
+
+#: gitk:445 gitk:1575 gitk:4261
+msgid "No commits selected"
+msgstr "Nincsen commit kiválasztva"
+
+#: gitk:1451
+msgid "Can't parse git log output:"
+msgstr "Nem lehet értelmezni a git log kimenetét:"
+
+#: gitk:1671
+msgid "No commit information available"
+msgstr "Nincsen elérhető commit információ"
+
+#: gitk:1813
+msgid "mc"
+msgstr "mc"
+
+#: gitk:1848 gitk:4051 gitk:9029 gitk:10570 gitk:10789
+msgid "OK"
+msgstr "OK"
+
+#: gitk:1850 gitk:4053 gitk:8629 gitk:8703 gitk:8813 gitk:8862 gitk:9031
+#: gitk:10571 gitk:10790
+msgid "Cancel"
+msgstr "Visszavonás"
+
+#: gitk:1972
+msgid "Update"
+msgstr "Frissités"
+
+#: gitk:1973
+msgid "Reload"
+msgstr "Újratöltés"
+
+#: gitk:1974
+msgid "Reread references"
+msgstr "Referenciák újraolvasása"
+
+#: gitk:1975
+msgid "List references"
+msgstr "Referenciák listázása"
+
+#: gitk:1977
+msgid "Start git gui"
+msgstr "Git gui indítása"
+
+#: gitk:1979
+msgid "Quit"
+msgstr "Kilépés"
+
+#: gitk:1971
+msgid "File"
+msgstr "Fájl"
+
+#: gitk:1983
+msgid "Preferences"
+msgstr "Beállítások"
+
+#: gitk:1982
+msgid "Edit"
+msgstr "Szerkesztés"
+
+#: gitk:1987
+msgid "New view..."
+msgstr "Új nézet ..."
+
+#: gitk:1988
+msgid "Edit view..."
+msgstr "Nézet szerkesztése ..."
+
+#: gitk:1989
+msgid "Delete view"
+msgstr "Nézet törlése"
+
+#: gitk:1991
+msgid "All files"
+msgstr "Minden fájl"
+
+#: gitk:1986 gitk:3805
+msgid "View"
+msgstr "Nézet"
+
+#: gitk:1996 gitk:2006 gitk:2777
+msgid "About gitk"
+msgstr "Gitk névjegy"
+
+#: gitk:1997 gitk:2011
+msgid "Key bindings"
+msgstr "Billentyűkombináció"
+
+#: gitk:1995 gitk:2010
+msgid "Help"
+msgstr "Segítség"
+
+#: gitk:2088
+msgid "SHA1 ID: "
+msgstr "SHA1 ID: "
+
+#: gitk:2119
+msgid "Row"
+msgstr "Sor"
+
+#: gitk:2157
+msgid "Find"
+msgstr "Keresés"
+
+#: gitk:2158
+msgid "next"
+msgstr "következő"
+
+#: gitk:2159
+msgid "prev"
+msgstr "előző"
+
+#: gitk:2160
+msgid "commit"
+msgstr "commit"
+
+#: gitk:2163 gitk:2165 gitk:4419 gitk:4442 gitk:4466 gitk:6407 gitk:6479
+#: gitk:6563
+msgid "containing:"
+msgstr "tartalmazás:"
+
+#: gitk:2166 gitk:3287 gitk:3292 gitk:4494
+msgid "touching paths:"
+msgstr "érintendő útvonalak:"
+
+#: gitk:2167 gitk:4499
+msgid "adding/removing string:"
+msgstr "string hozzáadása/törlése:"
+
+#: gitk:2176 gitk:2178
+msgid "Exact"
+msgstr "Pontos"
+
+#: gitk:2178 gitk:4574 gitk:6375
+msgid "IgnCase"
+msgstr "Kis/nagy betű nem számít"
+
+#: gitk:2178 gitk:4468 gitk:4572 gitk:6371
+msgid "Regexp"
+msgstr "Regexp"
+
+#: gitk:2180 gitk:2181 gitk:4593 gitk:4623 gitk:4630 gitk:6499 gitk:6567
+msgid "All fields"
+msgstr "Minden mező"
+
+#: gitk:2181 gitk:4591 gitk:4623 gitk:6438
+msgid "Headline"
+msgstr "Főcím"
+
+#: gitk:2182 gitk:4591 gitk:6438 gitk:6567 gitk:7000
+msgid "Comments"
+msgstr "Megjegyzések"
+
+#: gitk:2182 gitk:4591 gitk:4595 gitk:4630 gitk:6438 gitk:6935 gitk:8280
+#: gitk:8295
+msgid "Author"
+msgstr "Szerző"
+
+#: gitk:2182 gitk:4591 gitk:6438 gitk:6937
+msgid "Committer"
+msgstr "Commitoló"
+
+#: gitk:2213
+msgid "Search"
+msgstr "Keresés"
+
+#: gitk:2221
+msgid "Diff"
+msgstr "Diff"
+
+#: gitk:2223
+msgid "Old version"
+msgstr "Régi verzió"
+
+#: gitk:2225
+msgid "New version"
+msgstr "Új verzió"
+
+#: gitk:2227
+msgid "Lines of context"
+msgstr "Tartalmi sorok"
+
+#: gitk:2237
+msgid "Ignore space change"
+msgstr "Space váltás mellőzése"
+
+#: gitk:2296
+msgid "Patch"
+msgstr "Patch"
+
+#: gitk:2298
+msgid "Tree"
+msgstr "Tree"
+
+#: gitk:2453 gitk:2470
+msgid "Diff this -> selected"
+msgstr "Diff ezeket -> kiválasztott"
+
+#: gitk:2454 gitk:2471
+msgid "Diff selected -> this"
+msgstr "Diff kiválasztottakat -> ezt"
+
+#: gitk:2455 gitk:2472
+msgid "Make patch"
+msgstr "Patch készítése"
+
+#: gitk:2456 gitk:8687
+msgid "Create tag"
+msgstr "Tag készítése"
+
+#: gitk:2457 gitk:8793
+msgid "Write commit to file"
+msgstr "Commit fáljba írása"
+
+#: gitk:2458 gitk:8850
+msgid "Create new branch"
+msgstr "Új branch készítése"
+
+#: gitk:2459
+msgid "Cherry-pick this commit"
+msgstr "Cherry-pick erre a commitra"
+
+#: gitk:2460
+msgid "Reset HEAD branch to here"
+msgstr "HEAD branch újraindítása ide"
+
+#: gitk:2461
+msgid "Mark this commit"
+msgstr "Ezen commit megjelölése"
+
+#: gitk:2462
+msgid "Return to mark"
+msgstr "Visszatérés a megjelöléshez"
+
+#: gitk:2463
+msgid "Find descendant of this and mark"
+msgstr "Találd meg ezen utódokat és jelöld meg"
+
+#: gitk:2464
+msgid "Compare with marked commit"
+msgstr "Összehasonlítás a megjelölt commit-tal"
+
+#: gitk:2478
+msgid "Check out this branch"
+msgstr "Check out ezt a branchot"
+
+#: gitk:2479
+msgid "Remove this branch"
+msgstr "Töröld ezt a branch-ot"
+
+#: gitk:2486
+msgid "Highlight this too"
+msgstr "Emeld ki ezt is"
+
+#: gitk:2487
+msgid "Highlight this only"
+msgstr "Csak ezt emeld ki"
+
+#: gitk:2488
+msgid "External diff"
+msgstr "Külső diff"
+
+#: gitk:2489
+msgid "Blame parent commit"
+msgstr "Blame szülő kommitra"
+
+#: gitk:2496
+msgid "Show origin of this line"
+msgstr "Mutasd meg ennek a sornak az eredetét"
+
+#: gitk:2497
+msgid "Run git gui blame on this line"
+msgstr "Futtasd a git gui blame-t ezen a soron"
+
+#: gitk:2779
+msgid ""
+"\n"
+"Gitk - a commit viewer for git\n"
+"\n"
+"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
+"\n"
+"Use and redistribute under the terms of the GNU General Public License"
+msgstr ""
+"\n"
+"Gitk - commit nézegető a githez\n"
+"\n"
+"Szerzői jog \\u00a9 2005-2010 Paul Mackerras\n"
+"\n"
+"Használd és terjeszd a GNU General Public License feltételei mellett"
+
+#: gitk:2787 gitk:2851 gitk:9215
+msgid "Close"
+msgstr "Bezárás"
+
+#: gitk:2808
+msgid "Gitk key bindings"
+msgstr "Gitk-billentyű hozzárendelés"
+
+#: gitk:2811
+msgid "Gitk key bindings:"
+msgstr "Gitk-billentyű hozzaárendelés:"
+
+#: gitk:2813
+#, tcl-format
+msgid "<%s-Q>\t\tQuit"
+msgstr "<%s-Q>\t\tKilépés"
+
+#: gitk:2814
+msgid "<Home>\t\tMove to first commit"
+msgstr "<Pos1>\t\tElső commithoz"
+
+#: gitk:2815
+msgid "<End>\t\tMove to last commit"
+msgstr "<Ende>\t\tUtolsó commithoz"
+
+#: gitk:2816
+msgid "<Up>, p, i\tMove up one commit"
+msgstr "<Hoch>, p, i\tEgy committal feljebb"
+
+#: gitk:2817
+msgid "<Down>, n, k\tMove down one commit"
+msgstr "<Runter>, n, k\tEgy committal lejjebb"
+
+#: gitk:2818
+msgid "<Left>, z, j\tGo back in history list"
+msgstr "<Links>, z, j\tVissza a history listába"
+
+#: gitk:2819
+msgid "<Right>, x, l\tGo forward in history list"
+msgstr "<Rechts>, x, l\tElőre a history listába"
+
+#: gitk:2820
+msgid "<PageUp>\tMove up one page in commit list"
+msgstr "<BildHoch>\tEgy lappal feljebb a commit listába"
+
+#: gitk:2821
+msgid "<PageDown>\tMove down one page in commit list"
+msgstr "<BildRunter>\tEgy lappal lejjebb a commit listába"
+
+#: gitk:2822
+#, tcl-format
+msgid "<%s-Home>\tScroll to top of commit list"
+msgstr "<%s-Pos1>\tGörgetés a commit lista tetejéhez"
+
+#: gitk:2823
+#, tcl-format
+msgid "<%s-End>\tScroll to bottom of commit list"
+msgstr "<%s-Ende>\tGörgetés a commit lista aljához"
+
+#: gitk:2824
+#, tcl-format
+msgid "<%s-Up>\tScroll commit list up one line"
+msgstr "<%s-Hoch>\tEgy sorral feljebb görgetés a commit listában"
+
+#: gitk:2825
+#, tcl-format
+msgid "<%s-Down>\tScroll commit list down one line"
+msgstr "<%s-Runter>\tEgy sorral lejjebb görgetés a commit listában"
+
+#: gitk:2826
+#, tcl-format
+msgid "<%s-PageUp>\tScroll commit list up one page"
+msgstr "<%s-BildHoch>\tEgy lappal feljebb görgetés a commit listában"
+
+#: gitk:2827
+#, tcl-format
+msgid "<%s-PageDown>\tScroll commit list down one page"
+msgstr "<%s-BildRunter>\tEgy sorral lejjebb görgetés a commit listában"
+
+#: gitk:2828
+msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
+msgstr "<Umschalt-Hoch>\tKeresés visszafele (felfele, utolsó commitok)"
+
+#: gitk:2829
+msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
+msgstr "<Umschalt-Runter>\tKeresés előre (lefelé; korábbi commitok)"
+
+#: gitk:2830
+msgid "<Delete>, b\tScroll diff view up one page"
+msgstr "<Entf>, b\t\tEgy lappal feljebb görgetés a diff nézetben"
+
+#: gitk:2831
+msgid "<Backspace>\tScroll diff view up one page"
+msgstr "<Löschtaste>\tEgy lappal feljebb görgetés a diff nézetben"
+
+#: gitk:2832
+msgid "<Space>\t\tScroll diff view down one page"
+msgstr "<Leertaste>\tEgy lappal lejjebb görgetés a diff nézetben"
+
+#: gitk:2833
+msgid "u\t\tScroll diff view up 18 lines"
+msgstr "u\t\t18 sorral felfelé görgetés diff nézetben"
+
+#: gitk:2834
+msgid "d\t\tScroll diff view down 18 lines"
+msgstr "d\t\t18 sorral lejjebb görgetés a diff nézetben"
+
+#: gitk:2835
+#, tcl-format
+msgid "<%s-F>\t\tFind"
+msgstr "<%s-F>\t\tKeresés"
+
+#: gitk:2836
+#, tcl-format
+msgid "<%s-G>\t\tMove to next find hit"
+msgstr "<%s-G>\t\tKövetkező találathoz"
+
+#: gitk:2837
+msgid "<Return>\tMove to next find hit"
+msgstr "<Eingabetaste>\tKövetkező találathoz"
+
+#: gitk:2838
+msgid "/\t\tFocus the search box"
+msgstr "/\t\tLépj a keresési mezőre"
+
+#: gitk:2839
+msgid "?\t\tMove to previous find hit"
+msgstr "?\t\tElőző találathoz"
+
+#: gitk:2840
+msgid "f\t\tScroll diff view to next file"
+msgstr "f\t\tKövetkező fájlra görgetés diff nézetben"
+
+#: gitk:2841
+#, tcl-format
+msgid "<%s-S>\t\tSearch for next hit in diff view"
+msgstr "<%s-S>\t\tKövetkező találatra keresés diff nézetben"
+
+#: gitk:2842
+#, tcl-format
+msgid "<%s-R>\t\tSearch for previous hit in diff view"
+msgstr "<%s-R>\t\tElőző találatra keresés diff nézetben"
+
+#: gitk:2843
+#, tcl-format
+msgid "<%s-KP+>\tIncrease font size"
+msgstr "<%s-Nummerblock-Plus>\tBetűméret növelése"
+
+#: gitk:2844
+#, tcl-format
+msgid "<%s-plus>\tIncrease font size"
+msgstr "<%s-Plus>\tBetűméret növelése"
+
+#: gitk:2845
+#, tcl-format
+msgid "<%s-KP->\tDecrease font size"
+msgstr "<%s-Nummernblock-Minus> Betűméret csökkentése"
+
+#: gitk:2846
+#, tcl-format
+msgid "<%s-minus>\tDecrease font size"
+msgstr "<%s-Minus>\tBetűméret csökkentése"
+
+#: gitk:2847
+msgid "<F5>\t\tUpdate"
+msgstr "<F5>\t\tFrissítés"
+
+#: gitk:3302 gitk:3311
+#, tcl-format
+msgid "Error creating temporary directory %s:"
+msgstr "Hiba történt az ideiglenes könyvtár létrehozása közben %s:"
+
+#: gitk:3324
+#, tcl-format
+msgid "Error getting \"%s\" from %s:"
+msgstr "Hiba történt \"%s\" letöltése közben %s-ről:"
+
+#: gitk:3387
+msgid "command failed:"
+msgstr "parancs hiba:"
+
+#: gitk:3536
+msgid "No such commit"
+msgstr "Nincs ilyen commit"
+
+#: gitk:3550
+msgid "git gui blame: command failed:"
+msgstr "git gui blame: parancs hiba:"
+
+#: gitk:3581
+#, tcl-format
+msgid "Couldn't read merge head: %s"
+msgstr "Nem sikerült a Merge head olvasása: %s"
+
+#: gitk:3589
+#, tcl-format
+msgid "Error reading index: %s"
+msgstr "Hiba történt az index olvasása közben: %s"
+
+#: gitk:3614
+#, tcl-format
+msgid "Couldn't start git blame: %s"
+msgstr "Nem sikerült a git blame indítása: %s"
+
+#: gitk:3617 gitk:6406
+msgid "Searching"
+msgstr "Keresés"
+
+#: gitk:3649
+#, tcl-format
+msgid "Error running git blame: %s"
+msgstr "Hiba történt a git blame futtatása közben: %s"
+
+#: gitk:3677
+#, tcl-format
+msgid "That line comes from commit %s,  which is not in this view"
+msgstr ""
+"A %s commitból származik az a sor, amelyik nem található ebben a nézetben"
+
+#: gitk:3691
+msgid "External diff viewer failed:"
+msgstr "Külső diff nézegető hiba:"
+
+#: gitk:3809
+msgid "Gitk view definition"
+msgstr "Gitk nézet meghatározása"
+
+#: gitk:3813
+msgid "Remember this view"
+msgstr "Maradj ennél a nézetnél"
+
+#: gitk:3814
+msgid "References (space separated list):"
+msgstr "Referenciák (szóközzel tagolt lista"
+
+#: gitk:3815
+msgid "Branches & tags:"
+msgstr "Branch-ek & tagek:"
+
+#: gitk:3816
+msgid "All refs"
+msgstr "Minden ref"
+
+#: gitk:3817
+msgid "All (local) branches"
+msgstr "Minden (helyi) branch"
+
+#: gitk:3818
+msgid "All tags"
+msgstr "Minden tag"
+
+#: gitk:3819
+msgid "All remote-tracking branches"
+msgstr "Minden távoli követő branch"
+
+#: gitk:3820
+msgid "Commit Info (regular expressions):"
+msgstr "Commit Infó (reguláris kifejezés):"
+
+#: gitk:3821
+msgid "Author:"
+msgstr "Szerző:"
+
+#: gitk:3822
+msgid "Committer:"
+msgstr "Commitoló:"
+
+#: gitk:3823
+msgid "Commit Message:"
+msgstr "Commit üzenet:"
+
+#: gitk:3824
+msgid "Matches all Commit Info criteria"
+msgstr "Egyezik minen Commit Infó feltétellel"
+
+#: gitk:3825
+msgid "Changes to Files:"
+msgstr "Fájl változások:"
+
+#: gitk:3826
+msgid "Fixed String"
+msgstr "Fix String"
+
+#: gitk:3827
+msgid "Regular Expression"
+msgstr "Reguláris kifejezés"
+
+#: gitk:3828
+msgid "Search string:"
+msgstr "Keresés szöveg:"
+
+#: gitk:3829
+msgid ""
+"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+msgstr ""
+"Commit Dátumok (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+
+#: gitk:3830
+msgid "Since:"
+msgstr "Ettől:"
+
+#: gitk:3831
+msgid "Until:"
+msgstr "Eddig:"
+
+#: gitk:3832
+msgid "Limit and/or skip a number of revisions (positive integer):"
+msgstr "Limitálva és/vagy kihagyva egy adott számú revíziót (pozitív egész):"
+
+#: gitk:3833
+msgid "Number to show:"
+msgstr "Mutatandó szám:"
+
+#: gitk:3834
+msgid "Number to skip:"
+msgstr "Kihagyandó szám:"
+
+#: gitk:3835
+msgid "Miscellaneous options:"
+msgstr "Különféle opciók:"
+
+#: gitk:3836
+msgid "Strictly sort by date"
+msgstr "Szigorú rendezás dátum alapján"
+
+#: gitk:3837
+msgid "Mark branch sides"
+msgstr "Jelölje meg az ágakat"
+
+#: gitk:3838
+msgid "Limit to first parent"
+msgstr "Korlátozás az első szülőre"
+
+#: gitk:3839
+msgid "Simple history"
+msgstr "Egyszerű history"
+
+#: gitk:3840
+msgid "Additional arguments to git log:"
+msgstr "További argumentok a git log-hoz:"
+
+#: gitk:3841
+msgid "Enter files and directories to include, one per line:"
+msgstr "Fájlok és könyvtárak bejegyzése amiket tartalmaz, soronként:"
+
+#: gitk:3842
+msgid "Command to generate more commits to include:"
+msgstr "Parancs több tartalmazó commit generálására:"
+
+#: gitk:3964
+msgid "Gitk: edit view"
+msgstr "Gitk: szerkesztés nézet"
+
+#: gitk:3972
+msgid "-- criteria for selecting revisions"
+msgstr "-- kritériumok a revíziók kiválasztásához"
+
+#: gitk:3977
+msgid "View Name"
+msgstr "Nézet neve"
+
+#: gitk:4052
+msgid "Apply (F5)"
+msgstr "Alkalmaz (F5)"
+
+#: gitk:4090
+msgid "Error in commit selection arguments:"
+msgstr "Hiba történt a commit argumentumok kiválasztása közben:"
+
+#: gitk:4143 gitk:4195 gitk:4643 gitk:4657 gitk:5918 gitk:11519 gitk:11520
+msgid "None"
+msgstr "Keine"
+
+#: gitk:4591 gitk:6438 gitk:8282 gitk:8297
+msgid "Date"
+msgstr "Dátum"
+
+#: gitk:4591 gitk:6438
+msgid "CDate"
+msgstr "Dátum"
+
+#: gitk:4740 gitk:4745
+msgid "Descendant"
+msgstr "Leszármazott"
+
+#: gitk:4741
+msgid "Not descendant"
+msgstr "Nem leszármazott"
+
+#: gitk:4748 gitk:4753
+msgid "Ancestor"
+msgstr "Előd"
+
+#: gitk:4749
+msgid "Not ancestor"
+msgstr "Nem előd"
+
+#: gitk:5039
+msgid "Local changes checked in to index but not committed"
+msgstr ""
+"Lokális változtatások, melyek be vannak téve az indexbe, de még nincsenek "
+"commitolva"
+
+#: gitk:5075
+msgid "Local uncommitted changes, not checked in to index"
+msgstr "Lokális nem commitolt változások, nincsenek betéve az indexbe"
+
+#: gitk:6756
+msgid "many"
+msgstr "sok"
+
+#: gitk:6939
+msgid "Tags:"
+msgstr "Tagek:"
+
+#: gitk:6956 gitk:6962 gitk:8275
+msgid "Parent"
+msgstr "Eltern"
+
+#: gitk:6967
+msgid "Child"
+msgstr "Gyerek"
+
+#: gitk:6976
+msgid "Branch"
+msgstr "Ág"
+
+#: gitk:6979
+msgid "Follows"
+msgstr "Következők"
+
+#: gitk:6982
+msgid "Precedes"
+msgstr "Megelőzők"
+
+#: gitk:7519
+#, tcl-format
+msgid "Error getting diffs: %s"
+msgstr "Hiba történt a diff-ek letöltése közben: %s"
+
+#: gitk:8103
+msgid "Goto:"
+msgstr "Menj:"
+
+#: gitk:8105
+msgid "SHA1 ID:"
+msgstr "SHA1 ID:"
+
+#: gitk:8124
+#, tcl-format
+msgid "Short SHA1 id %s is ambiguous"
+msgstr "Rövid SHA1 id %s félreérthető"
+
+#: gitk:8131
+msgid "Revision %s is not known"
+msgstr "A(z) %s revízió nem ismert"
+
+#: gitk:8141
+#, tcl-format
+msgid "SHA1 id %s is not known"
+msgstr "SHA1 id %s nem ismert"
+
+#: gitk:8143
+#, tcl-format
+msgid "Revision %s is not in the current view"
+msgstr "A(z) %s revízió nincs a jelenlegi nézetben"
+
+#: gitk:8285
+msgid "Children"
+msgstr "Gyerekek"
+
+#: gitk:8343
+#, tcl-format
+msgid "Reset %s branch to here"
+msgstr "Állítsd vissza a %s branch-ot ide"
+
+#: gitk:8345
+msgid "Detached head: can't reset"
+msgstr "Elkülönített head: nem lehet visszaállítani"
+
+#: gitk:8454 gitk:8460
+msgid "Skipping merge commit "
+msgstr "Merge commit kihagyása "
+
+#: gitk:8469 gitk:8474
+msgid "Error getting patch ID for "
+msgstr "Hiba történt a patch ID megszerzése közben a következőnél "
+
+#: gitk:8470 gitk:8475
+msgid " - stopping\n"
+msgstr " - abbahagyás\n"
+
+#: gitk:8480 gitk:8483 gitk:8491 gitk:8505 gitk:8514
+msgid "Commit "
+msgstr "Commit "
+
+#: gitk:8484
+msgid ""
+" is the same patch as\n"
+"       "
+msgstr ""
+" Ugyanaz a patch mint\n"
+"       "
+
+#: gitk:8492
+msgid ""
+" differs from\n"
+"       "
+msgstr ""
+" különbözik innentől\n"
+"       "
+
+#: gitk:8494
+msgid ""
+"Diff of commits:\n"
+"\n"
+msgstr ""
+"A commitok diffje:\n"
+"\n"
+
+#: gitk:8506 gitk:8515
+#, tcl-format
+msgid " has %s children - stopping\n"
+msgstr " %s gyereke van. abbahagyás\n"
+
+#: gitk:8534
+msgid "Error writing commit to file: %s"
+msgstr "Hiba történt a commit fájlba írása közben: %s"
+
+#: gitk:8540
+msgid "Error diffing commits: %s"
+msgstr "Hiba történt a commitok diffelése közben: %s"
+
+#: gitk:8570
+msgid "Top"
+msgstr "Teteje"
+
+#: gitk:8571
+msgid "From"
+msgstr "Innen"
+
+#: gitk:8576
+msgid "To"
+msgstr "Ide"
+
+#: gitk:8600
+msgid "Generate patch"
+msgstr "Patch generálása"
+
+#: gitk:8602
+msgid "From:"
+msgstr "Innen:"
+
+#: gitk:8611
+msgid "To:"
+msgstr "Ide:"
+
+#: gitk:8620
+msgid "Reverse"
+msgstr "Visszafele"
+
+#: gitk:8622 gitk:8807
+msgid "Output file:"
+msgstr "Kimeneti fájl:"
+
+#: gitk:8628
+msgid "Generate"
+msgstr "Generálás"
+
+#: gitk:8666
+msgid "Error creating patch:"
+msgstr "Hiba törtét a patch készítése közben:"
+
+#: gitk:8689 gitk:8795 gitk:8852
+msgid "ID:"
+msgstr "ID:"
+
+#: gitk:8698
+msgid "Tag name:"
+msgstr "Tag név:"
+
+#: gitk:8702 gitk:8861
+msgid "Create"
+msgstr "Létrehozás"
+
+#: gitk:8719
+msgid "No tag name specified"
+msgstr "A tag neve nincsen megadva"
+
+#: gitk:8723
+#, tcl-format
+msgid "Tag \"%s\" already exists"
+msgstr "%s Tag már létezik"
+
+#: gitk:8729
+msgid "Error creating tag:"
+msgstr "Hiba történt a tag létrehozása közben:"
+
+#: gitk:8804
+msgid "Command:"
+msgstr "Parancs:"
+
+#: gitk:8812
+msgid "Write"
+msgstr "Írás"
+
+#: gitk:8830
+msgid "Error writing commit:"
+msgstr "Hiba történt a commit írása közben:"
+
+#: gitk:8857
+msgid "Name:"
+msgstr "Név:"
+
+#: gitk:8880
+msgid "Please specify a name for the new branch"
+msgstr "Kérem adja meg a nevét az új branchhoz"
+
+#: gitk:8885
+#, tcl-format
+msgid "Branch '%s' already exists. Overwrite?"
+msgstr "%s branch már létezik. Felülírja?"
+
+#: gitk:8951
+#, tcl-format
+msgid "Commit %s is already included in branch %s -- really re-apply it?"
+msgstr ""
+"%s commit már benne van a %s branchban -- biztos hogy újra csinálja ?"
+"eintragen?"
+
+#: gitk:8956
+msgid "Cherry-picking"
+msgstr "Cherry-picking"
+
+#: gitk:8965
+#, tcl-format
+msgid ""
+"Cherry-pick failed because of local changes to file '%s'.\n"
+"Please commit, reset or stash your changes and try again."
+msgstr ""
+"Cherry-pick hiba történt lokális váltotások miatt a '%s' fájlban.\n"
+"Kérem commitolja, indítsa újra vagy rejtse el a változtatásait és próbálja "
+"újra."
+
+#: gitk:8971
+msgid ""
+"Cherry-pick failed because of merge conflict.\n"
+"Do you wish to run git citool to resolve it?"
+msgstr ""
+"Cherry-pick hiba történt merge konfliktus miatt.\n"
+"Kívánja futtatni a git citool-t a probléma megoldásához?"
+
+#: gitk:8987
+msgid "No changes committed"
+msgstr "Nincsen változás commitolva"
+
+#: gitk:9013
+msgid "Confirm reset"
+msgstr "Újraindítás megerősítése"
+
+#: gitk:9015
+#, tcl-format
+msgid "Reset branch %s to %s?"
+msgstr "Újraindítja a %s branchot %s-ig?"
+
+#: gitk:9017
+msgid "Reset type:"
+msgstr "Újraindítás típusa:"
+
+#: gitk:9020
+msgid "Soft: Leave working tree and index untouched"
+msgstr "Soft: Hagyd a working tree-t és az indexet érintetlenül"
+
+#: gitk:9023
+msgid "Mixed: Leave working tree untouched, reset index"
+msgstr "Kevert: Hagyd a working tree-t érintetlenül, töröld az indexet"
+
+#: gitk:9026
+msgid ""
+"Hard: Reset working tree and index\n"
+"(discard ALL local changes)"
+msgstr ""
+"Hard: Indítsd újra a working tree-t és az indexet\n"
+"(MINDEN lokális változás eldobása)"
+
+#: gitk:9043
+msgid "Resetting"
+msgstr "Újraindítás"
+
+#: gitk:9103
+msgid "Checking out"
+msgstr "Kivesz"
+
+#: gitk:9156
+msgid "Cannot delete the currently checked-out branch"
+msgstr "Nem lehet a jelenleg kivett branch-ot törölni"
+
+#: gitk:9162
+#, tcl-format
+msgid ""
+"The commits on branch %s aren't on any other branch.\n"
+"Really delete branch %s?"
+msgstr ""
+"A %s branchon található commit nem található meg semelyik másik branchon.\n"
+"Tényleg törli a %s branchot?"
+
+#: gitk:9193
+#, tcl-format
+msgid "Tags and heads: %s"
+msgstr "Tagek és headek: %s"
+
+#: gitk:9208
+msgid "Filter"
+msgstr "Szűrő"
+
+#: gitk:9503
+msgid ""
+"Error reading commit topology information; branch and preceding/following "
+"tag information will be incomplete."
+msgstr ""
+"Hiba történt a commit topológiai információ olvasása közben; branch ésa "
+"megelőző/következő információ nem lesz teljes."
+
+#: gitk:10489
+msgid "Tag"
+msgstr "Tag"
+
+#: gitk:10489
+msgid "Id"
+msgstr "Id"
+
+#: gitk:10539
+msgid "Gitk font chooser"
+msgstr "Gitk-betű kiválasztó"
+
+#: gitk:10556
+msgid "B"
+msgstr "F"
+
+#: gitk:10559
+msgid "I"
+msgstr "K"
+
+#: gitk:10677
+msgid "Gitk preferences"
+msgstr "Gitk beállítások"
+
+#: gitk:10679
+msgid "Commit list display options"
+msgstr "Commit lista kijelzési opciók"
+
+#: gitk:10682
+msgid "Maximum graph width (lines)"
+msgstr "Maximális grafikon szélesség (sorok)"
+
+#: gitk:10685
+#, tcl-format
+msgid "Maximum graph width (% of pane)"
+msgstr "Maximális grafikon szélesség (táble %-je)"
+
+#: gitk:10688
+msgid "Show local changes"
+msgstr "Mutasd a lokális változtatásokat"
+
+#: gitk:10691
+msgid "Auto-select SHA1"
+msgstr "SHA1 Automatikus kiválasztása"
+
+#: gitk:10694
+msgid "Hide remote refs"
+msgstr "A távoli refek elrejtése"
+
+#: gitk:10698
+msgid "Diff display options"
+msgstr "Diff kijelző opciók"
+
+#: gitk:10700
+msgid "Tab spacing"
+msgstr "Tab sorköz"
+
+#: gitk:10703
+msgid "Display nearby tags"
+msgstr "Szomszédos tagek kijelzése"
+
+#: gitk:10706
+msgid "Limit diffs to listed paths"
+msgstr "Korlátozott diffek a kilistázott útvonalakhoz"
+
+#: gitk:10709
+msgid "Support per-file encodings"
+msgstr "Fájlonkénti kódolás támgatása"
+
+#: gitk:10715 gitk:10804
+msgid "External diff tool"
+msgstr "Külső diff alkalmazás"
+
+#: gitk:10716
+msgid "Choose..."
+msgstr "Válaszd ..."
+
+#: gitk:10721
+msgid "General options"
+msgstr "Általános opciók"
+
+#: gitk:10724
+msgid "Use themed widgets"
+msgstr "Témázott vezérlők használata"
+
+#: gitk:10726
+msgid "(change requires restart)"
+msgstr "(a változás újraindítást igényel)"
+
+#: gitk:10728
+msgid "(currently unavailable)"
+msgstr "(jelenleg nem elérhető)"
+
+#: gitk:10732
+msgid "Colors: press to choose"
+msgstr "Színek: nyomja meg a kiválasztáshoz"
+
+#: gitk:10735
+msgid "Interface"
+msgstr "Interfész"
+
+#: gitk:10736
+msgid "interface"
+msgstr "interfész"
+
+#: gitk:10739
+msgid "Background"
+msgstr "Háttér"
+
+#: gitk:10740 gitk:10770
+msgid "background"
+msgstr "háttér"
+
+#: gitk:10743
+msgid "Foreground"
+msgstr "Előtér"
+
+#: gitk:10744
+msgid "foreground"
+msgstr "előtér"
+
+#: gitk:10747
+msgid "Diff: old lines"
+msgstr "Diff: régi sorok"
+
+#: gitk:10748
+msgid "diff old lines"
+msgstr "diff régi sorok"
+
+#: gitk:10752
+msgid "Diff: new lines"
+msgstr "Diff: új sorok"
+
+#: gitk:10753
+msgid "diff new lines"
+msgstr "diff - új sorok"
+
+#: gitk:10757
+msgid "Diff: hunk header"
+msgstr "Diff: nagy headerök"
+
+#: gitk:10759
+msgid "diff hunk header"
+msgstr "diff - nagy headerök"
+
+#: gitk:10763
+msgid "Marked line bg"
+msgstr "Megjelölt sor háttér"
+
+#: gitk:10765
+msgid "marked line background"
+msgstr "megjelölt sor háttér"
+
+#: gitk:10769
+msgid "Select bg"
+msgstr "Válasszon hátteret"
+
+#: gitk:10773
+msgid "Fonts: press to choose"
+msgstr "Betű: nyomja meg a kiválasztáshoz"
+
+#: gitk:10775
+msgid "Main font"
+msgstr "Fő betű"
+
+#: gitk:10776
+msgid "Diff display font"
+msgstr "Diff kijelző betű"
+
+#: gitk:10777
+msgid "User interface font"
+msgstr "Felhasználói interfész betű"
+
+#: gitk:10814
+#, tcl-format
+msgid "Gitk: choose color for %s"
+msgstr "Gitk: válasszon színt a %s-ra"
+
+#: gitk:11418
+msgid "Cannot find a git repository here."
+msgstr "Nem találhatü git repository itt."
+
+#: gitk:11422
+#, tcl-format
+msgid "Cannot find the git directory \"%s\"."
+msgstr "Nem található a \"%s\" könyvtár."
+
+#: gitk:11469
+#, tcl-format
+msgid "Ambiguous argument '%s': both revision and filename"
+msgstr "Félreérthető argumentum '%s': revízió és fájlnév is"
+
+#: gitk:11481
+msgid "Bad arguments to gitk:"
+msgstr "Rossz gitk argumentumok:"
+
+#: gitk:11572
+msgid "Command line"
+msgstr "Parancs sor"
+
+#~ msgid "Use all refs"
+#~ msgstr "Használd az összes referenciát"
+
+#~ msgid "Max count:"
+#~ msgstr "Max. szám:"
+
+#~ msgid "Skip:"
+#~ msgstr "Kihagy:"
+
+#~ msgid "Name"
+#~ msgstr "Név"
+
+#~ msgid "Tag/Head %s is not known"
+#~ msgstr "Tag/Head %s nem ismert"
+
+#~ msgid "- stopping\n"
+#~ msgstr "- abbahagyás.\n"
+
+#~ msgid ""
+#~ "Sorry, gitk cannot run with this version of Tcl/Tk.\n"
+#~ " Gitk requires at least Tcl/Tk 8.4."
+#~ msgstr ""
+#~ "Sajnáljuk, de a gitk nem futtatható ezzel a Tcl/Tk verzióval.\n"
+#~ "Gitk futtatásához legalább Tcl/Tk 8.4 szükséges."
index e89c95702cf71d259543f6bb6713e69f624b9b53..a730d63a42ad380548e454410eafcbadece85387 100644 (file)
@@ -8,25 +8,33 @@ msgid ""
 msgstr ""
 "Project-Id-Version: gitk\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-10-18 22:03+1100\n"
-"PO-Revision-Date: 2008-03-13 17:34+0100\n"
+"POT-Creation-Date: 2010-01-28 18:40+0100\n"
+"PO-Revision-Date: 2010-01-28 18:41+0100\n"
 "Last-Translator: Michele Ballabio <barra_cuda@katamail.com>\n"
 "Language-Team: Italian\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: gitk:113
+#: gitk:115
 msgid "Couldn't get list of unmerged files:"
 msgstr "Impossibile ottenere l'elenco dei file in attesa di fusione:"
 
-#: gitk:340
+#: gitk:274
+msgid "Error parsing revisions:"
+msgstr "Errore nella lettura delle revisioni:"
+
+#: gitk:329
+msgid "Error executing --argscmd command:"
+msgstr "Errore nell'esecuzione del comando specificato con --argscmd:"
+
+#: gitk:342
 msgid "No files selected: --merge specified but no files are unmerged."
 msgstr ""
 "Nessun file selezionato: è stata specificata l'opzione --merge ma non ci "
 "sono file in attesa di fusione."
 
-#: gitk:343
+#: gitk:345
 msgid ""
 "No files selected: --merge specified but no unmerged files are within file "
 "limit."
@@ -34,688 +42,994 @@ msgstr ""
 "Nessun file selezionato: è stata specificata l'opzione --merge ma i file "
 "specificati non sono in attesa di fusione."
 
-#: gitk:365 gitk:503
+#: gitk:367 gitk:514
 msgid "Error executing git log:"
 msgstr "Errore nell'esecuzione di git log:"
 
-#: gitk:378
+#: gitk:385 gitk:530
 msgid "Reading"
 msgstr "Lettura in corso"
 
-#: gitk:438 gitk:3462
+#: gitk:445 gitk:4261
 msgid "Reading commits..."
 msgstr "Lettura delle revisioni in corso..."
 
-#: gitk:441 gitk:1528 gitk:3465
+#: gitk:448 gitk:1578 gitk:4264
 msgid "No commits selected"
 msgstr "Nessuna revisione selezionata"
 
-#: gitk:1399
+#: gitk:1454
 msgid "Can't parse git log output:"
 msgstr "Impossibile elaborare i dati di git log:"
 
-#: gitk:1605
+#: gitk:1674
 msgid "No commit information available"
 msgstr "Nessuna informazione disponibile sulle revisioni"
 
-#: gitk:1709 gitk:1731 gitk:3259 gitk:7764 gitk:9293 gitk:9466
+#: gitk:1816
+msgid "mc"
+msgstr ""
+
+#: gitk:1851 gitk:4054 gitk:9044 gitk:10585 gitk:10804
 msgid "OK"
 msgstr "OK"
 
-#: gitk:1733 gitk:3260 gitk:7439 gitk:7510 gitk:7613 gitk:7660 gitk:7766
-#: gitk:9294 gitk:9467
+#: gitk:1853 gitk:4056 gitk:8634 gitk:8713 gitk:8828 gitk:8877 gitk:9046
+#: gitk:10586 gitk:10805
 msgid "Cancel"
 msgstr "Annulla"
 
-#: gitk:1811
+#: gitk:1975
 msgid "Update"
 msgstr "Aggiorna"
 
-#: gitk:1813
+#: gitk:1976
+msgid "Reload"
+msgstr "Ricarica"
+
+#: gitk:1977
 msgid "Reread references"
 msgstr "Rileggi riferimenti"
 
-#: gitk:1814
+#: gitk:1978
 msgid "List references"
 msgstr "Elenca riferimenti"
 
-#: gitk:1815
+#: gitk:1980
+msgid "Start git gui"
+msgstr "Avvia git gui"
+
+#: gitk:1982
 msgid "Quit"
 msgstr "Esci"
 
-#: gitk:1810
+#: gitk:1974
 msgid "File"
 msgstr "File"
 
-#: gitk:1818
+#: gitk:1986
 msgid "Preferences"
 msgstr "Preferenze"
 
-#: gitk:1817
+#: gitk:1985
 msgid "Edit"
 msgstr "Modifica"
 
-#: gitk:1821
+#: gitk:1990
 msgid "New view..."
 msgstr "Nuova vista..."
 
-#: gitk:1822
+#: gitk:1991
 msgid "Edit view..."
 msgstr "Modifica vista..."
 
-#: gitk:1823
+#: gitk:1992
 msgid "Delete view"
 msgstr "Elimina vista"
 
-#: gitk:1825
+#: gitk:1994
 msgid "All files"
 msgstr "Tutti i file"
 
-#: gitk:1820 gitk:3196
+#: gitk:1989 gitk:3808
 msgid "View"
 msgstr "Vista"
 
-#: gitk:1828 gitk:2487
+#: gitk:1999 gitk:2009 gitk:2780
 msgid "About gitk"
 msgstr "Informazioni su gitk"
 
-#: gitk:1829
+#: gitk:2000 gitk:2014
 msgid "Key bindings"
 msgstr "Scorciatoie da tastiera"
 
-#: gitk:1827
+#: gitk:1998 gitk:2013
 msgid "Help"
 msgstr "Aiuto"
 
-#: gitk:1887
-msgid "SHA1 ID: "
-msgstr "SHA1 ID: "
+#: gitk:2091 gitk:8110
+msgid "SHA1 ID:"
+msgstr "SHA1 ID:"
 
-#: gitk:1918
+#: gitk:2122
 msgid "Row"
-msgstr ""
+msgstr "Riga"
 
-#: gitk:1949
+#: gitk:2160
 msgid "Find"
 msgstr "Trova"
 
-#: gitk:1950
+#: gitk:2161
 msgid "next"
 msgstr "succ"
 
-#: gitk:1951
+#: gitk:2162
 msgid "prev"
 msgstr "prec"
 
-#: gitk:1952
+#: gitk:2163
 msgid "commit"
 msgstr "revisione"
 
-#: gitk:1955 gitk:1957 gitk:3617 gitk:3640 gitk:3664 gitk:5550 gitk:5621
+#: gitk:2166 gitk:2168 gitk:4422 gitk:4445 gitk:4469 gitk:6410 gitk:6482
+#: gitk:6566
 msgid "containing:"
 msgstr "contenente:"
 
-#: gitk:1958 gitk:2954 gitk:2959 gitk:3692
+#: gitk:2169 gitk:3290 gitk:3295 gitk:4497
 msgid "touching paths:"
 msgstr "che riguarda i percorsi:"
 
-#: gitk:1959 gitk:3697
+#: gitk:2170 gitk:4502
 msgid "adding/removing string:"
 msgstr "che aggiunge/rimuove la stringa:"
 
-#: gitk:1968 gitk:1970
+#: gitk:2179 gitk:2181
 msgid "Exact"
 msgstr "Esatto"
 
-#: gitk:1970 gitk:3773 gitk:5518
+#: gitk:2181 gitk:4577 gitk:6378
 msgid "IgnCase"
 msgstr ""
 
-#: gitk:1970 gitk:3666 gitk:3771 gitk:5514
+#: gitk:2181 gitk:4471 gitk:4575 gitk:6374
 msgid "Regexp"
 msgstr ""
 
-#: gitk:1972 gitk:1973 gitk:3792 gitk:3822 gitk:3829 gitk:5641 gitk:5708
+#: gitk:2183 gitk:2184 gitk:4596 gitk:4626 gitk:4633 gitk:6502 gitk:6570
 msgid "All fields"
 msgstr "Tutti i campi"
 
-#: gitk:1973 gitk:3790 gitk:3822 gitk:5580
+#: gitk:2184 gitk:4594 gitk:4626 gitk:6441
 msgid "Headline"
 msgstr "Titolo"
 
-#: gitk:1974 gitk:3790 gitk:5580 gitk:5708 gitk:6109
+#: gitk:2185 gitk:4594 gitk:6441 gitk:6570 gitk:7003
 msgid "Comments"
 msgstr "Commenti"
 
-#: gitk:1974 gitk:3790 gitk:3794 gitk:3829 gitk:5580 gitk:6045 gitk:7285
-#: gitk:7300
+#: gitk:2185 gitk:4594 gitk:4598 gitk:4633 gitk:6441 gitk:6938 gitk:8285
+#: gitk:8300
 msgid "Author"
 msgstr "Autore"
 
-#: gitk:1974 gitk:3790 gitk:5580 gitk:6047
+#: gitk:2185 gitk:4594 gitk:6441 gitk:6940
 msgid "Committer"
 msgstr "Revisione creata da"
 
-#: gitk:2003
+#: gitk:2216
 msgid "Search"
 msgstr "Cerca"
 
-#: gitk:2010
+#: gitk:2224
 msgid "Diff"
 msgstr ""
 
-#: gitk:2012
+#: gitk:2226
 msgid "Old version"
 msgstr "Vecchia versione"
 
-#: gitk:2014
+#: gitk:2228
 msgid "New version"
 msgstr "Nuova versione"
 
-#: gitk:2016
+#: gitk:2230
 msgid "Lines of context"
 msgstr "Linee di contesto"
 
-#: gitk:2026
+#: gitk:2240
 msgid "Ignore space change"
 msgstr "Ignora modifiche agli spazi"
 
-#: gitk:2084
+#: gitk:2299
 msgid "Patch"
 msgstr "Modifiche"
 
-#: gitk:2086
+#: gitk:2301
 msgid "Tree"
 msgstr "Directory"
 
-#: gitk:2213 gitk:2226
+#: gitk:2456 gitk:2473
 msgid "Diff this -> selected"
 msgstr "Diff questo -> selezionato"
 
-#: gitk:2214 gitk:2227
+#: gitk:2457 gitk:2474
 msgid "Diff selected -> this"
 msgstr "Diff selezionato -> questo"
 
-#: gitk:2215 gitk:2228
+#: gitk:2458 gitk:2475
 msgid "Make patch"
 msgstr "Crea patch"
 
-#: gitk:2216 gitk:7494
+#: gitk:2459 gitk:8692
 msgid "Create tag"
 msgstr "Crea etichetta"
 
-#: gitk:2217 gitk:7593
+#: gitk:2460 gitk:8808
 msgid "Write commit to file"
 msgstr "Scrivi revisione in un file"
 
-#: gitk:2218 gitk:7647
+#: gitk:2461 gitk:8865
 msgid "Create new branch"
 msgstr "Crea un nuovo ramo"
 
-#: gitk:2219
+#: gitk:2462
 msgid "Cherry-pick this commit"
 msgstr "Porta questa revisione in cima al ramo attuale"
 
-#: gitk:2220
+#: gitk:2463
 msgid "Reset HEAD branch to here"
 msgstr "Aggiorna il ramo HEAD a questa revisione"
 
-#: gitk:2234
+#: gitk:2464
+msgid "Mark this commit"
+msgstr "Segna questa revisione"
+
+#: gitk:2465
+msgid "Return to mark"
+msgstr "Torna alla revisione segnata"
+
+#: gitk:2466
+msgid "Find descendant of this and mark"
+msgstr "Trova il discendente di questa revisione e di quella segnata"
+
+#: gitk:2467
+msgid "Compare with marked commit"
+msgstr "Confronta con la revisione segnata"
+
+#: gitk:2481
 msgid "Check out this branch"
 msgstr "Attiva questo ramo"
 
-#: gitk:2235
+#: gitk:2482
 msgid "Remove this branch"
 msgstr "Elimina questo ramo"
 
-#: gitk:2242
+#: gitk:2489
 msgid "Highlight this too"
 msgstr "Evidenzia anche questo"
 
-#: gitk:2243
+#: gitk:2490
 msgid "Highlight this only"
 msgstr "Evidenzia solo questo"
 
-#: gitk:2245
+#: gitk:2491
+msgid "External diff"
+msgstr "Visualizza differenze in un altro programma"
+
+#: gitk:2492
 msgid "Blame parent commit"
-msgstr ""
+msgstr "Annota la revisione precedente"
+
+#: gitk:2499
+msgid "Show origin of this line"
+msgstr "Mostra la provenienza di questa riga"
 
-#: gitk:2488
+#: gitk:2500
+msgid "Run git gui blame on this line"
+msgstr "Esegui git gui blame su questa riga"
+
+#: gitk:2782
 msgid ""
 "\n"
 "Gitk - a commit viewer for git\n"
 "\n"
-"Copyright © 2005-2008 Paul Mackerras\n"
+"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
 "\n"
 "Use and redistribute under the terms of the GNU General Public License"
 msgstr ""
 "\n"
 "Gitk - un visualizzatore di revisioni per git\n"
 "\n"
-"Copyright © 2005-2008 Paul Mackerras\n"
+"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
 "\n"
 "Utilizzo e redistribuzione permessi sotto i termini della GNU General Public "
 "License"
 
-#: gitk:2496 gitk:2557 gitk:7943
+#: gitk:2790 gitk:2854 gitk:9230
 msgid "Close"
 msgstr "Chiudi"
 
-#: gitk:2515
+#: gitk:2811
 msgid "Gitk key bindings"
 msgstr "Scorciatoie da tastiera di Gitk"
 
-#: gitk:2517
+#: gitk:2814
 msgid "Gitk key bindings:"
 msgstr "Scorciatoie da tastiera di Gitk:"
 
-#: gitk:2519
+#: gitk:2816
 #, tcl-format
 msgid "<%s-Q>\t\tQuit"
 msgstr "<%s-Q>\t\tEsci"
 
-#: gitk:2520
+#: gitk:2817
 msgid "<Home>\t\tMove to first commit"
 msgstr "<Home>\t\tVai alla prima revisione"
 
-#: gitk:2521
+#: gitk:2818
 msgid "<End>\t\tMove to last commit"
 msgstr "<End>\t\tVai all'ultima revisione"
 
-#: gitk:2522
+#: gitk:2819
 msgid "<Up>, p, i\tMove up one commit"
-msgstr "<Up>, p, i\tVai più in alto di una revisione"
+msgstr "<Su>, p, i\tVai più in alto di una revisione"
 
-#: gitk:2523
+#: gitk:2820
 msgid "<Down>, n, k\tMove down one commit"
-msgstr "<Down>, n, k\tVai più in basso di una revisione"
+msgstr "<Giù>, n, k\tVai più in basso di una revisione"
 
-#: gitk:2524
+#: gitk:2821
 msgid "<Left>, z, j\tGo back in history list"
-msgstr "<Left>, z, j\tTorna indietro nella cronologia"
+msgstr "<Sinistra>, z, j\tTorna indietro nella cronologia"
 
-#: gitk:2525
+#: gitk:2822
 msgid "<Right>, x, l\tGo forward in history list"
-msgstr "<Right>, x, l\tVai avanti nella cronologia"
+msgstr "<Destra>, x, l\tVai avanti nella cronologia"
 
-#: gitk:2526
+#: gitk:2823
 msgid "<PageUp>\tMove up one page in commit list"
-msgstr "<PageUp>\tVai più in alto di una pagina nella lista delle revisioni"
+msgstr "<PaginaSu>\tVai più in alto di una pagina nella lista delle revisioni"
 
-#: gitk:2527
+#: gitk:2824
 msgid "<PageDown>\tMove down one page in commit list"
-msgstr "<PageDown>\tVai più in basso di una pagina nella lista delle revisioni"
+msgstr ""
+"<PaginaGiù>\tVai più in basso di una pagina nella lista delle revisioni"
 
-#: gitk:2528
+#: gitk:2825
 #, tcl-format
 msgid "<%s-Home>\tScroll to top of commit list"
 msgstr "<%s-Home>\tScorri alla cima della lista delle revisioni"
 
-#: gitk:2529
+#: gitk:2826
 #, tcl-format
 msgid "<%s-End>\tScroll to bottom of commit list"
 msgstr "<%s-End>\tScorri alla fine della lista delle revisioni"
 
-#: gitk:2530
+#: gitk:2827
 #, tcl-format
 msgid "<%s-Up>\tScroll commit list up one line"
-msgstr "<%s-Up>\tScorri la lista delle revisioni in alto di una riga"
+msgstr "<%s-Su>\tScorri la lista delle revisioni in alto di una riga"
 
-#: gitk:2531
+#: gitk:2828
 #, tcl-format
 msgid "<%s-Down>\tScroll commit list down one line"
-msgstr "<%s-Down>\tScorri la lista delle revisioni in basso di una riga"
+msgstr "<%s-Giù>\tScorri la lista delle revisioni in basso di una riga"
 
-#: gitk:2532
+#: gitk:2829
 #, tcl-format
 msgid "<%s-PageUp>\tScroll commit list up one page"
-msgstr "<%s-PageUp>\tScorri la lista delle revisioni in alto di una pagina"
+msgstr "<%s-PaginaSu>\tScorri la lista delle revisioni in alto di una pagina"
 
-#: gitk:2533
+#: gitk:2830
 #, tcl-format
 msgid "<%s-PageDown>\tScroll commit list down one page"
-msgstr "<%s-PageDown>\tScorri la lista delle revisioni in basso di una pagina"
+msgstr "<%s-PaginaGiù>\tScorri la lista delle revisioni in basso di una pagina"
 
-#: gitk:2534
+#: gitk:2831
 msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
-msgstr "<Shift-Up>\tTrova all'indietro (verso l'alto, revisioni successive)"
+msgstr "<Shift-Su>\tTrova all'indietro (verso l'alto, revisioni successive)"
 
-#: gitk:2535
+#: gitk:2832
 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
-msgstr "<Shift-Down>\tTrova in avanti (verso il basso, revisioni precedenti)"
+msgstr "<Shift-Giù>\tTrova in avanti (verso il basso, revisioni precedenti)"
 
-#: gitk:2536
+#: gitk:2833
 msgid "<Delete>, b\tScroll diff view up one page"
 msgstr "<Delete>, b\tScorri la vista delle differenze in alto di una pagina"
 
-#: gitk:2537
+#: gitk:2834
 msgid "<Backspace>\tScroll diff view up one page"
 msgstr "<Backspace>\tScorri la vista delle differenze in alto di una pagina"
 
-#: gitk:2538
+#: gitk:2835
 msgid "<Space>\t\tScroll diff view down one page"
-msgstr "<Space>\t\tScorri la vista delle differenze in basso di una pagina"
+msgstr "<Spazio>\t\tScorri la vista delle differenze in basso di una pagina"
 
-#: gitk:2539
+#: gitk:2836
 msgid "u\t\tScroll diff view up 18 lines"
 msgstr "u\t\tScorri la vista delle differenze in alto di 18 linee"
 
-#: gitk:2540
+#: gitk:2837
 msgid "d\t\tScroll diff view down 18 lines"
 msgstr "d\t\tScorri la vista delle differenze in basso di 18 linee"
 
-#: gitk:2541
+#: gitk:2838
 #, tcl-format
 msgid "<%s-F>\t\tFind"
 msgstr "<%s-F>\t\tTrova"
 
-#: gitk:2542
+#: gitk:2839
 #, tcl-format
 msgid "<%s-G>\t\tMove to next find hit"
 msgstr "<%s-G>\t\tTrova in avanti"
 
-#: gitk:2543
+#: gitk:2840
 msgid "<Return>\tMove to next find hit"
-msgstr "<Return>\tTrova in avanti"
+msgstr "<Invio>\tTrova in avanti"
 
-#: gitk:2544
-msgid "/\t\tMove to next find hit, or redo find"
-msgstr "/\t\tTrova in avanti, o cerca di nuovo"
+#: gitk:2841
+msgid "/\t\tFocus the search box"
+msgstr "/\t\tCursore nel box di ricerca"
 
-#: gitk:2545
+#: gitk:2842
 msgid "?\t\tMove to previous find hit"
 msgstr "?\t\tTrova all'indietro"
 
-#: gitk:2546
+#: gitk:2843
 msgid "f\t\tScroll diff view to next file"
 msgstr "f\t\tScorri la vista delle differenze al file successivo"
 
-#: gitk:2547
+#: gitk:2844
 #, tcl-format
 msgid "<%s-S>\t\tSearch for next hit in diff view"
 msgstr "<%s-S>\t\tCerca in avanti nella vista delle differenze"
 
-#: gitk:2548
+#: gitk:2845
 #, tcl-format
 msgid "<%s-R>\t\tSearch for previous hit in diff view"
 msgstr "<%s-R>\t\tCerca all'indietro nella vista delle differenze"
 
-#: gitk:2549
+#: gitk:2846
 #, tcl-format
 msgid "<%s-KP+>\tIncrease font size"
-msgstr "<%s-KP+>\tAumenta grandezza carattere"
+msgstr "<%s-KP+>\tAumenta dimensione carattere"
 
-#: gitk:2550
+#: gitk:2847
 #, tcl-format
 msgid "<%s-plus>\tIncrease font size"
-msgstr "<%s-plus>\tAumenta grandezza carattere"
+msgstr "<%s-più>\tAumenta dimensione carattere"
 
-#: gitk:2551
+#: gitk:2848
 #, tcl-format
 msgid "<%s-KP->\tDecrease font size"
-msgstr "<%s-KP->\tDiminuisci grandezza carattere"
+msgstr "<%s-KP->\tDiminuisci dimensione carattere"
 
-#: gitk:2552
+#: gitk:2849
 #, tcl-format
 msgid "<%s-minus>\tDecrease font size"
-msgstr "<%s-minus>\tDiminuisci grandezza carattere"
+msgstr "<%s-meno>\tDiminuisci dimensione carattere"
 
-#: gitk:2553
+#: gitk:2850
 msgid "<F5>\t\tUpdate"
 msgstr "<F5>\t\tAggiorna"
 
-#: gitk:3200
+#: gitk:3305 gitk:3314
+#, tcl-format
+msgid "Error creating temporary directory %s:"
+msgstr "Errore durante la creazione della directory temporanea %s:"
+
+#: gitk:3327
+#, tcl-format
+msgid "Error getting \"%s\" from %s:"
+msgstr "Errore nella lettura di \"%s\" da %s:"
+
+#: gitk:3390
+msgid "command failed:"
+msgstr "impossibile eseguire il comando:"
+
+#: gitk:3539
+msgid "No such commit"
+msgstr "Revisione inesistente"
+
+#: gitk:3553
+msgid "git gui blame: command failed:"
+msgstr "git gui blame: impossibile eseguire il comando:"
+
+#: gitk:3584
+#, tcl-format
+msgid "Couldn't read merge head: %s"
+msgstr "Impossibile leggere merge head: %s"
+
+#: gitk:3592
+#, tcl-format
+msgid "Error reading index: %s"
+msgstr "Errore nella lettura dell'indice: %s"
+
+#: gitk:3617
+#, tcl-format
+msgid "Couldn't start git blame: %s"
+msgstr "Impossibile eseguire git blame: %s"
+
+#: gitk:3620 gitk:6409
+msgid "Searching"
+msgstr "Ricerca in corso"
+
+#: gitk:3652
+#, tcl-format
+msgid "Error running git blame: %s"
+msgstr "Errore nell'esecuzione di git blame: %s"
+
+#: gitk:3680
+#, tcl-format
+msgid "That line comes from commit %s,  which is not in this view"
+msgstr "Quella riga proviene dalla revisione %s, non presente in questa vista"
+
+#: gitk:3694
+msgid "External diff viewer failed:"
+msgstr "Impossibile eseguire il visualizzatore di differenze:"
+
+#: gitk:3812
 msgid "Gitk view definition"
 msgstr "Scelta vista Gitk"
 
-#: gitk:3225
-msgid "Name"
-msgstr "Nome"
-
-#: gitk:3228
+#: gitk:3816
 msgid "Remember this view"
 msgstr "Ricorda questa vista"
 
-#: gitk:3232
-msgid "Commits to include (arguments to git log):"
-msgstr "Revisioni da includere (argomenti di git log):"
+#: gitk:3817
+msgid "References (space separated list):"
+msgstr "Riferimenti (lista di elementi separati da spazi)"
 
-#: gitk:3239
-msgid "Command to generate more commits to include:"
-msgstr "Comando che genera altre revisioni da visualizzare:"
+#: gitk:3818
+msgid "Branches & tags:"
+msgstr "Rami ed etichette"
+
+#: gitk:3819
+msgid "All refs"
+msgstr "Tutti i riferimenti"
+
+#: gitk:3820
+msgid "All (local) branches"
+msgstr "Tutti i rami (locali)"
+
+#: gitk:3821
+msgid "All tags"
+msgstr "Tutte le etichette"
+
+#: gitk:3822
+msgid "All remote-tracking branches"
+msgstr "Tutti i rami remoti"
+
+#: gitk:3823
+msgid "Commit Info (regular expressions):"
+msgstr "Informazioni sulla revisione (espressioni regolari):"
+
+#: gitk:3824
+msgid "Author:"
+msgstr "Autore:"
+
+#: gitk:3825
+msgid "Committer:"
+msgstr "Revisione creata da:"
+
+#: gitk:3826
+msgid "Commit Message:"
+msgstr "Messaggio di revisione:"
+
+#: gitk:3827
+msgid "Matches all Commit Info criteria"
+msgstr "Risponde a tutti i criteri di ricerca sulle revisioni"
+
+#: gitk:3828
+msgid "Changes to Files:"
+msgstr "Modifiche ai file:"
+
+#: gitk:3829
+msgid "Fixed String"
+msgstr "Stringa fissa"
+
+#: gitk:3830
+msgid "Regular Expression"
+msgstr "Espressione regolare"
+
+#: gitk:3831
+msgid "Search string:"
+msgstr "Cerca stringa:"
+
+#: gitk:3832
+msgid ""
+"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+msgstr ""
+"Date di revisione (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, "
+"2009 15:27:38\"):"
+
+#: gitk:3833
+msgid "Since:"
+msgstr "Da:"
+
+#: gitk:3834
+msgid "Until:"
+msgstr "A:"
+
+#: gitk:3835
+msgid "Limit and/or skip a number of revisions (positive integer):"
+msgstr "Limita e/o salta N revisioni (intero positivo):"
+
+#: gitk:3836
+msgid "Number to show:"
+msgstr "Numero di revisioni da mostrare:"
+
+#: gitk:3837
+msgid "Number to skip:"
+msgstr "Numero di revisioni da saltare:"
+
+#: gitk:3838
+msgid "Miscellaneous options:"
+msgstr "Altre opzioni:"
+
+#: gitk:3839
+msgid "Strictly sort by date"
+msgstr "Ordina solo per data"
+
+#: gitk:3840
+msgid "Mark branch sides"
+msgstr "Segna i lati del ramo"
+
+#: gitk:3841
+msgid "Limit to first parent"
+msgstr "Limita al primo genitore"
 
-#: gitk:3246
+#: gitk:3842
+msgid "Simple history"
+msgstr "Cronologia semplificata"
+
+#: gitk:3843
+msgid "Additional arguments to git log:"
+msgstr "Ulteriori argomenti da passare a git log:"
+
+#: gitk:3844
 msgid "Enter files and directories to include, one per line:"
 msgstr "Inserire file e directory da includere, uno per riga:"
 
-#: gitk:3293
+#: gitk:3845
+msgid "Command to generate more commits to include:"
+msgstr "Comando che genera altre revisioni da visualizzare:"
+
+#: gitk:3967
+msgid "Gitk: edit view"
+msgstr "Gitk: modifica vista"
+
+#: gitk:3975
+msgid "-- criteria for selecting revisions"
+msgstr "-- criteri per la scelta delle revisioni"
+
+#: gitk:3980
+msgid "View Name"
+msgstr "Nome vista"
+
+#: gitk:4055
+msgid "Apply (F5)"
+msgstr "Applica (F5)"
+
+#: gitk:4093
 msgid "Error in commit selection arguments:"
 msgstr "Errore negli argomenti di selezione delle revisioni:"
 
-#: gitk:3347 gitk:3399 gitk:3842 gitk:3856 gitk:5060 gitk:10141 gitk:10142
+#: gitk:4146 gitk:4198 gitk:4646 gitk:4660 gitk:5921 gitk:11534 gitk:11535
 msgid "None"
 msgstr "Nessuno"
 
-#: gitk:3790 gitk:5580 gitk:7287 gitk:7302
+#: gitk:4594 gitk:6441 gitk:8287 gitk:8302
 msgid "Date"
 msgstr "Data"
 
-#: gitk:3790 gitk:5580
+#: gitk:4594 gitk:6441
 msgid "CDate"
 msgstr ""
 
-#: gitk:3939 gitk:3944
+#: gitk:4743 gitk:4748
 msgid "Descendant"
 msgstr "Discendente"
 
-#: gitk:3940
+#: gitk:4744
 msgid "Not descendant"
 msgstr "Non discendente"
 
-#: gitk:3947 gitk:3952
+#: gitk:4751 gitk:4756
 msgid "Ancestor"
 msgstr "Ascendente"
 
-#: gitk:3948
+#: gitk:4752
 msgid "Not ancestor"
 msgstr "Non ascendente"
 
-#: gitk:4187
+#: gitk:5042
 msgid "Local changes checked in to index but not committed"
 msgstr "Modifiche locali presenti nell'indice ma non nell'archivio"
 
-#: gitk:4220
+#: gitk:5078
 msgid "Local uncommitted changes, not checked in to index"
 msgstr "Modifiche locali non presenti né nell'archivio né nell'indice"
 
-#: gitk:5549
-msgid "Searching"
-msgstr "Ricerca in corso"
+#: gitk:6759
+msgid "many"
+msgstr "molti"
 
-#: gitk:6049
+#: gitk:6942
 msgid "Tags:"
 msgstr "Etichette:"
 
-#: gitk:6066 gitk:6072 gitk:7280
+#: gitk:6959 gitk:6965 gitk:8280
 msgid "Parent"
 msgstr "Genitore"
 
-#: gitk:6077
+#: gitk:6970
 msgid "Child"
 msgstr "Figlio"
 
-#: gitk:6086
+#: gitk:6979
 msgid "Branch"
 msgstr "Ramo"
 
-#: gitk:6089
+#: gitk:6982
 msgid "Follows"
 msgstr "Segue"
 
-#: gitk:6092
+#: gitk:6985
 msgid "Precedes"
 msgstr "Precede"
 
-#: gitk:6378
-msgid "Error getting merge diffs:"
-msgstr "Errore nella lettura delle differenze di fusione:"
+#: gitk:7522
+#, tcl-format
+msgid "Error getting diffs: %s"
+msgstr "Errore nella lettura delle differenze:"
 
-#: gitk:7113
+#: gitk:8108
 msgid "Goto:"
 msgstr "Vai a:"
 
-#: gitk:7115
-msgid "SHA1 ID:"
-msgstr "SHA1 ID:"
-
-#: gitk:7134
+#: gitk:8129
 #, tcl-format
 msgid "Short SHA1 id %s is ambiguous"
 msgstr "La SHA1 id abbreviata %s è ambigua"
 
-#: gitk:7146
+#: gitk:8136
+#, tcl-format
+msgid "Revision %s is not known"
+msgstr "La revisione %s è sconosciuta"
+
+#: gitk:8146
 #, tcl-format
 msgid "SHA1 id %s is not known"
 msgstr "La SHA1 id %s è sconosciuta"
 
-#: gitk:7148
+#: gitk:8148
 #, tcl-format
-msgid "Tag/Head %s is not known"
-msgstr "L'etichetta/ramo %s è sconosciuto"
+msgid "Revision %s is not in the current view"
+msgstr "La revisione %s non è presente nella vista attuale"
 
-#: gitk:7290
+#: gitk:8290
 msgid "Children"
 msgstr "Figli"
 
-#: gitk:7347
+#: gitk:8348
 #, tcl-format
 msgid "Reset %s branch to here"
 msgstr "Aggiorna il ramo %s a questa revisione"
 
-#: gitk:7349
+#: gitk:8350
 msgid "Detached head: can't reset"
+msgstr "Nessun ramo attivo: reset impossibile"
+
+#: gitk:8459 gitk:8465
+msgid "Skipping merge commit "
+msgstr "Salto la revisione di fusione "
+
+#: gitk:8474 gitk:8479
+msgid "Error getting patch ID for "
+msgstr "Errore nella identificazione della patch per "
+
+#: gitk:8475 gitk:8480
+msgid " - stopping\n"
+msgstr " - fine\n"
+
+#: gitk:8485 gitk:8488 gitk:8496 gitk:8510 gitk:8519
+msgid "Commit "
+msgstr "La revisione "
+
+#: gitk:8489
+msgid ""
+" is the same patch as\n"
+"       "
 msgstr ""
+" ha le stesse differenze di\n"
+"       "
+
+#: gitk:8497
+msgid ""
+" differs from\n"
+"       "
+msgstr ""
+" è diversa da\n"
+"       "
+
+#: gitk:8499
+msgid ""
+"Diff of commits:\n"
+"\n"
+msgstr ""
+"Differenze tra le revisioni:\n"
+"\n"
+
+#: gitk:8511 gitk:8520
+#, tcl-format
+msgid " has %s children - stopping\n"
+msgstr " ha %s figli - fine\n"
+
+#: gitk:8539
+#, tcl-format
+msgid "Error writing commit to file: %s"
+msgstr "Errore nella scrittura della revisione nel file: %s"
 
-#: gitk:7381
+#: gitk:8545
+#, tcl-format
+msgid "Error diffing commits: %s"
+msgstr "Errore nelle differenze tra le revisioni: %s"
+
+#: gitk:8575
 msgid "Top"
 msgstr "Inizio"
 
-#: gitk:7382
+#: gitk:8576
 msgid "From"
 msgstr "Da"
 
-#: gitk:7387
+#: gitk:8581
 msgid "To"
 msgstr "A"
 
-#: gitk:7410
+#: gitk:8605
 msgid "Generate patch"
 msgstr "Genera patch"
 
-#: gitk:7412
+#: gitk:8607
 msgid "From:"
 msgstr "Da:"
 
-#: gitk:7421
+#: gitk:8616
 msgid "To:"
 msgstr "A:"
 
-#: gitk:7430
+#: gitk:8625
 msgid "Reverse"
 msgstr "Inverti"
 
-#: gitk:7432 gitk:7607
+#: gitk:8627 gitk:8822
 msgid "Output file:"
 msgstr "Scrivi sul file:"
 
-#: gitk:7438
+#: gitk:8633
 msgid "Generate"
 msgstr "Genera"
 
-#: gitk:7474
+#: gitk:8671
 msgid "Error creating patch:"
 msgstr "Errore nella creazione della patch:"
 
-#: gitk:7496 gitk:7595 gitk:7649
+#: gitk:8694 gitk:8810 gitk:8867
 msgid "ID:"
 msgstr "ID:"
 
-#: gitk:7505
+#: gitk:8703
 msgid "Tag name:"
 msgstr "Nome etichetta:"
 
-#: gitk:7509 gitk:7659
+#: gitk:8706
+msgid "Tag message is optional"
+msgstr "Il messaggio dell'etichetta è opzionale"
+
+#: gitk:8708
+msgid "Tag message:"
+msgstr "Messaggio dell'etichetta:"
+
+#: gitk:8712 gitk:8876
 msgid "Create"
 msgstr "Crea"
 
-#: gitk:7524
+#: gitk:8730
 msgid "No tag name specified"
 msgstr "Nessuna etichetta specificata"
 
-#: gitk:7528
+#: gitk:8734
 #, tcl-format
 msgid "Tag \"%s\" already exists"
 msgstr "L'etichetta \"%s\" esiste già"
 
-#: gitk:7534
+#: gitk:8744
 msgid "Error creating tag:"
 msgstr "Errore nella creazione dell'etichetta:"
 
-#: gitk:7604
+#: gitk:8819
 msgid "Command:"
 msgstr "Comando:"
 
-#: gitk:7612
+#: gitk:8827
 msgid "Write"
 msgstr "Scrivi"
 
-#: gitk:7628
+#: gitk:8845
 msgid "Error writing commit:"
 msgstr "Errore nella scrittura della revisione:"
 
-#: gitk:7654
+#: gitk:8872
 msgid "Name:"
 msgstr "Nome:"
 
-#: gitk:7674
+#: gitk:8895
 msgid "Please specify a name for the new branch"
 msgstr "Specificare un nome per il nuovo ramo"
 
-#: gitk:7703
+#: gitk:8900
+#, tcl-format
+msgid "Branch '%s' already exists. Overwrite?"
+msgstr "Il ramo '%s' esiste già. Sovrascrivere?"
+
+#: gitk:8966
 #, tcl-format
 msgid "Commit %s is already included in branch %s -- really re-apply it?"
 msgstr "La revisione %s è già inclusa nel ramo %s -- applicarla di nuovo?"
 
-#: gitk:7708
+#: gitk:8971
 msgid "Cherry-picking"
 msgstr ""
 
-#: gitk:7720
+#: gitk:8980
+#, tcl-format
+msgid ""
+"Cherry-pick failed because of local changes to file '%s'.\n"
+"Please commit, reset or stash your changes and try again."
+msgstr ""
+"Impossibile eseguire cherry-pick perché il file '%s' è stato modificato "
+"nella directory di lavoro.\n"
+"Prima di riprovare, bisogna creare una nuova revisione, annullare le "
+"modifiche o usare 'git stash'."
+
+#: gitk:8986
+msgid ""
+"Cherry-pick failed because of merge conflict.\n"
+"Do you wish to run git citool to resolve it?"
+msgstr ""
+"Impossibile eseguire cherry-pick a causa di un conflitto nella fusione.\n"
+"Vuoi avviare git citool per risolverlo?"
+
+#: gitk:9002
 msgid "No changes committed"
 msgstr "Nessuna modifica archiviata"
 
-#: gitk:7745
+#: gitk:9028
 msgid "Confirm reset"
 msgstr "Conferma git reset"
 
-#: gitk:7747
+#: gitk:9030
 #, tcl-format
 msgid "Reset branch %s to %s?"
 msgstr "Aggiornare il ramo %s a %s?"
 
-#: gitk:7751
+#: gitk:9032
 msgid "Reset type:"
 msgstr "Tipo di aggiornamento:"
 
-#: gitk:7755
+#: gitk:9035
 msgid "Soft: Leave working tree and index untouched"
 msgstr "Soft: Lascia la direcory di lavoro e l'indice come sono"
 
-#: gitk:7758
+#: gitk:9038
 msgid "Mixed: Leave working tree untouched, reset index"
 msgstr "Mixed: Lascia la directory di lavoro come è, aggiorna l'indice"
 
-#: gitk:7761
+#: gitk:9041
 msgid ""
 "Hard: Reset working tree and index\n"
 "(discard ALL local changes)"
@@ -723,19 +1037,19 @@ msgstr ""
 "Hard: Aggiorna la directory di lavoro e l'indice\n"
 "(abbandona TUTTE le modifiche locali)"
 
-#: gitk:7777
+#: gitk:9058
 msgid "Resetting"
 msgstr "git reset in corso"
 
-#: gitk:7834
+#: gitk:9118
 msgid "Checking out"
 msgstr "Attivazione in corso"
 
-#: gitk:7885
+#: gitk:9171
 msgid "Cannot delete the currently checked-out branch"
 msgstr "Impossibile cancellare il ramo attualmente attivo"
 
-#: gitk:7891
+#: gitk:9177
 #, tcl-format
 msgid ""
 "The commits on branch %s aren't on any other branch.\n"
@@ -744,16 +1058,16 @@ msgstr ""
 "Le revisioni nel ramo %s non sono presenti su altri rami.\n"
 "Cancellare il ramo %s?"
 
-#: gitk:7922
+#: gitk:9208
 #, tcl-format
 msgid "Tags and heads: %s"
 msgstr "Etichette e rami: %s"
 
-#: gitk:7936
+#: gitk:9223
 msgid "Filter"
 msgstr "Filtro"
 
-#: gitk:8230
+#: gitk:9518
 msgid ""
 "Error reading commit topology information; branch and preceding/following "
 "tag information will be incomplete."
@@ -761,154 +1075,202 @@ msgstr ""
 "Errore nella lettura della topologia delle revisioni: le informazioni sul "
 "ramo e le etichette precedenti e seguenti saranno incomplete."
 
-#: gitk:9216
+#: gitk:10504
 msgid "Tag"
 msgstr "Etichetta"
 
-#: gitk:9216
+#: gitk:10504
 msgid "Id"
 msgstr "Id"
 
-#: gitk:9262
+#: gitk:10554
 msgid "Gitk font chooser"
 msgstr "Scelta caratteri gitk"
 
-#: gitk:9279
+#: gitk:10571
 msgid "B"
 msgstr "B"
 
-#: gitk:9282
+#: gitk:10574
 msgid "I"
 msgstr "I"
 
-#: gitk:9375
+#: gitk:10692
 msgid "Gitk preferences"
 msgstr "Preferenze gitk"
 
-#: gitk:9376
+#: gitk:10694
 msgid "Commit list display options"
 msgstr "Opzioni visualizzazione dell'elenco revisioni"
 
-#: gitk:9379
+#: gitk:10697
 msgid "Maximum graph width (lines)"
 msgstr "Larghezza massima del grafico (in linee)"
 
-#: gitk:9383
+#: gitk:10700
 #, tcl-format
 msgid "Maximum graph width (% of pane)"
 msgstr "Larghezza massima del grafico (% del pannello)"
 
-#: gitk:9388
+#: gitk:10703
 msgid "Show local changes"
 msgstr "Mostra modifiche locali"
 
-#: gitk:9393
+#: gitk:10706
 msgid "Auto-select SHA1"
 msgstr "Seleziona automaticamente SHA1 hash"
 
-#: gitk:9398
+#: gitk:10709
+msgid "Hide remote refs"
+msgstr "Nascondi i riferimenti remoti"
+
+#: gitk:10713
 msgid "Diff display options"
 msgstr "Opzioni di visualizzazione delle differenze"
 
-#: gitk:9400
+#: gitk:10715
 msgid "Tab spacing"
 msgstr "Spaziatura tabulazioni"
 
-#: gitk:9404
+#: gitk:10718
 msgid "Display nearby tags"
 msgstr "Mostra etichette vicine"
 
-#: gitk:9409
+#: gitk:10721
 msgid "Limit diffs to listed paths"
 msgstr "Limita le differenze ai percorsi elencati"
 
-#: gitk:9414
+#: gitk:10724
 msgid "Support per-file encodings"
-msgstr ""
+msgstr "Attiva codifica file per file"
 
-#: gitk:9421
+#: gitk:10730 gitk:10819
 msgid "External diff tool"
-msgstr ""
+msgstr "Visualizzatore di differenze"
 
-#: gitk:9423
+#: gitk:10731
 msgid "Choose..."
-msgstr ""
+msgstr "Scegli..."
 
-#: gitk:9428
+#: gitk:10736
+msgid "General options"
+msgstr "Opzioni generali"
+
+#: gitk:10739
+msgid "Use themed widgets"
+msgstr "Utilizza interfaccia a tema"
+
+#: gitk:10741
+msgid "(change requires restart)"
+msgstr "(una modifica richiede il riavvio)"
+
+#: gitk:10743
+msgid "(currently unavailable)"
+msgstr "(momentaneamente non disponibile)"
+
+#: gitk:10747
 msgid "Colors: press to choose"
 msgstr "Colori: premere per scegliere"
 
-#: gitk:9431
+#: gitk:10750
+msgid "Interface"
+msgstr "Interfaccia"
+
+#: gitk:10751
+msgid "interface"
+msgstr "interfaccia"
+
+#: gitk:10754
 msgid "Background"
 msgstr "Sfondo"
 
-#: gitk:9435
+#: gitk:10755 gitk:10785
+msgid "background"
+msgstr "sfondo"
+
+#: gitk:10758
 msgid "Foreground"
 msgstr "Primo piano"
 
-#: gitk:9439
+#: gitk:10759
+msgid "foreground"
+msgstr "primo piano"
+
+#: gitk:10762
 msgid "Diff: old lines"
 msgstr "Diff: vecchie linee"
 
-#: gitk:9444
+#: gitk:10763
+msgid "diff old lines"
+msgstr "vecchie linee"
+
+#: gitk:10767
 msgid "Diff: new lines"
 msgstr "Diff: nuove linee"
 
-#: gitk:9449
+#: gitk:10768
+msgid "diff new lines"
+msgstr "nuove linee"
+
+#: gitk:10772
 msgid "Diff: hunk header"
 msgstr "Diff: intestazione della sezione"
 
-#: gitk:9455
+#: gitk:10774
+msgid "diff hunk header"
+msgstr "intestazione della sezione"
+
+#: gitk:10778
+msgid "Marked line bg"
+msgstr "Sfondo riga selezionata"
+
+#: gitk:10780
+msgid "marked line background"
+msgstr "sfondo riga selezionata"
+
+#: gitk:10784
 msgid "Select bg"
-msgstr "Sfondo selezione"
+msgstr "Sfondo"
 
-#: gitk:9459
+#: gitk:10788
 msgid "Fonts: press to choose"
 msgstr "Carattere: premere per scegliere"
 
-#: gitk:9461
+#: gitk:10790
 msgid "Main font"
 msgstr "Carattere principale"
 
-#: gitk:9462
+#: gitk:10791
 msgid "Diff display font"
 msgstr "Carattere per differenze"
 
-#: gitk:9463
+#: gitk:10792
 msgid "User interface font"
 msgstr "Carattere per interfaccia utente"
 
-#: gitk:9488
+#: gitk:10829
 #, tcl-format
 msgid "Gitk: choose color for %s"
 msgstr "Gitk: scegliere un colore per %s"
 
-#: gitk:9934
-msgid ""
-"Sorry, gitk cannot run with this version of Tcl/Tk.\n"
-" Gitk requires at least Tcl/Tk 8.4."
-msgstr ""
-"Questa versione di Tcl/Tk non può avviare gitk.\n"
-" Gitk richiede Tcl/Tk versione 8.4 o superiore."
-
-#: gitk:10047
+#: gitk:11433
 msgid "Cannot find a git repository here."
 msgstr "Archivio git non trovato."
 
-#: gitk:10051
+#: gitk:11437
 #, tcl-format
 msgid "Cannot find the git directory \"%s\"."
 msgstr "Directory git \"%s\" non trovata."
 
-#: gitk:10098
+#: gitk:11484
 #, tcl-format
 msgid "Ambiguous argument '%s': both revision and filename"
 msgstr "Argomento ambiguo: '%s' è sia revisione che nome di file"
 
-#: gitk:10110
+#: gitk:11496
 msgid "Bad arguments to gitk:"
 msgstr "Gitk: argomenti errati:"
 
-#: gitk:10170
+#: gitk:11587
 msgid "Command line"
 msgstr "Linea di comando"
diff --git a/gitk-git/po/ja.po b/gitk-git/po/ja.po
new file mode 100644 (file)
index 0000000..4f47051
--- /dev/null
@@ -0,0 +1,1255 @@
+# Japanese translations for gitk package.
+# Copyright (C) 2005-2009 Paul Mackerras
+# This file is distributed under the same license as the gitk package.
+#
+# Mizar <mizar.jp@gmail.com>, 2009.
+# Junio C Hamano <gitster@pobox.com>, 2009.
+msgid ""
+msgstr ""
+"Project-Id-Version: gitk\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-11-04 00:08+0900\n"
+"PO-Revision-Date: 2009-11-06 01:45+0900\n"
+"Last-Translator: Mizar <mizar.jp@gmail.com>\n"
+"Language-Team: Japanese\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: gitk:113
+msgid "Couldn't get list of unmerged files:"
+msgstr "マージされていないファイルのリストを取得できません:"
+
+#: gitk:269
+msgid "Error parsing revisions:"
+msgstr "リビジョン解析エラー:"
+
+#: gitk:324
+msgid "Error executing --argscmd command:"
+msgstr "--argscmd コマンド実行エラー:"
+
+#: gitk:337
+msgid "No files selected: --merge specified but no files are unmerged."
+msgstr ""
+"ファイル未選択: --merge が指定されましたが、マージされていないファイルはあり"
+"ません。"
+
+#: gitk:340
+msgid ""
+"No files selected: --merge specified but no unmerged files are within file "
+"limit."
+msgstr ""
+"ファイル未選択: --merge が指定されましたが、ファイル制限内にマージされていな"
+"いファイルはありません。"
+
+#: gitk:362 gitk:509
+msgid "Error executing git log:"
+msgstr "git log 実行エラー:"
+
+#: gitk:380 gitk:525
+msgid "Reading"
+msgstr "読み込み中"
+
+#: gitk:440 gitk:4132
+msgid "Reading commits..."
+msgstr "コミット読み込み中..."
+
+#: gitk:443 gitk:1561 gitk:4135
+msgid "No commits selected"
+msgstr "コミットが選択されていません"
+
+#: gitk:1437
+msgid "Can't parse git log output:"
+msgstr "git log の出力を解析できません:"
+
+#: gitk:1657
+msgid "No commit information available"
+msgstr "有効なコミットの情報がありません"
+
+#: gitk:1790
+msgid "mc"
+msgstr "mc"
+
+#: gitk:1817 gitk:3925 gitk:8842 gitk:10378 gitk:10558
+msgid "OK"
+msgstr "OK"
+
+#: gitk:1819 gitk:3927 gitk:8439 gitk:8513 gitk:8623 gitk:8672 gitk:8844
+#: gitk:10379 gitk:10559
+msgid "Cancel"
+msgstr "キャンセル"
+
+#: gitk:1919
+msgid "Update"
+msgstr "更新"
+
+#: gitk:1920
+msgid "Reload"
+msgstr "リロード"
+
+#: gitk:1921
+msgid "Reread references"
+msgstr "リファレンスを再読み込み"
+
+#: gitk:1922
+msgid "List references"
+msgstr "リファレンスリストを表示"
+
+#: gitk:1924
+msgid "Start git gui"
+msgstr "git gui の開始"
+
+#: gitk:1926
+msgid "Quit"
+msgstr "終了"
+
+#: gitk:1918
+msgid "File"
+msgstr "ファイル"
+
+#: gitk:1930
+msgid "Preferences"
+msgstr "設定"
+
+#: gitk:1929
+msgid "Edit"
+msgstr "編集"
+
+#: gitk:1934
+msgid "New view..."
+msgstr "新規ビュー..."
+
+#: gitk:1935
+msgid "Edit view..."
+msgstr "ビュー編集..."
+
+#: gitk:1936
+msgid "Delete view"
+msgstr "ビュー削除"
+
+#: gitk:1938
+msgid "All files"
+msgstr "全てのファイル"
+
+#: gitk:1933 gitk:3679
+msgid "View"
+msgstr "ビュー"
+
+#: gitk:1943 gitk:1953 gitk:2656
+msgid "About gitk"
+msgstr "gitk について"
+
+#: gitk:1944 gitk:1958
+msgid "Key bindings"
+msgstr "キーバインディング"
+
+#: gitk:1942 gitk:1957
+msgid "Help"
+msgstr "ヘルプ"
+
+#: gitk:2018
+msgid "SHA1 ID: "
+msgstr "SHA1 ID: "
+
+#: gitk:2049
+msgid "Row"
+msgstr "行"
+
+#: gitk:2080
+msgid "Find"
+msgstr "検索"
+
+#: gitk:2081
+msgid "next"
+msgstr "次"
+
+#: gitk:2082
+msgid "prev"
+msgstr "前"
+
+#: gitk:2083
+msgid "commit"
+msgstr "コミット"
+
+#: gitk:2086 gitk:2088 gitk:4293 gitk:4316 gitk:4340 gitk:6281 gitk:6353
+#: gitk:6437
+msgid "containing:"
+msgstr "含む:"
+
+#: gitk:2089 gitk:3164 gitk:3169 gitk:4368
+msgid "touching paths:"
+msgstr "パスの一部:"
+
+#: gitk:2090 gitk:4373
+msgid "adding/removing string:"
+msgstr "追加/除去する文字列:"
+
+#: gitk:2099 gitk:2101
+msgid "Exact"
+msgstr "英字の大小を区別する"
+
+#: gitk:2101 gitk:4448 gitk:6249
+msgid "IgnCase"
+msgstr "英字の大小を区別しない"
+
+#: gitk:2101 gitk:4342 gitk:4446 gitk:6245
+msgid "Regexp"
+msgstr "正規表現"
+
+#: gitk:2103 gitk:2104 gitk:4467 gitk:4497 gitk:4504 gitk:6373 gitk:6441
+msgid "All fields"
+msgstr "全ての項目"
+
+#: gitk:2104 gitk:4465 gitk:4497 gitk:6312
+msgid "Headline"
+msgstr "ヘッドライン"
+
+#: gitk:2105 gitk:4465 gitk:6312 gitk:6441 gitk:6875
+msgid "Comments"
+msgstr "コメント"
+
+#: gitk:2105 gitk:4465 gitk:4469 gitk:4504 gitk:6312 gitk:6810 gitk:8091
+#: gitk:8106
+msgid "Author"
+msgstr "作者"
+
+#: gitk:2105 gitk:4465 gitk:6312 gitk:6812
+msgid "Committer"
+msgstr "コミット者"
+
+#: gitk:2134
+msgid "Search"
+msgstr "検索"
+
+#: gitk:2141
+msgid "Diff"
+msgstr "Diff"
+
+#: gitk:2143
+msgid "Old version"
+msgstr "旧バージョン"
+
+#: gitk:2145
+msgid "New version"
+msgstr "新バージョン"
+
+#: gitk:2147
+msgid "Lines of context"
+msgstr "文脈行数"
+
+#: gitk:2157
+msgid "Ignore space change"
+msgstr "空白の違いを無視"
+
+#: gitk:2215
+msgid "Patch"
+msgstr "パッチ"
+
+#: gitk:2217
+msgid "Tree"
+msgstr "ツリー"
+
+#: gitk:2361 gitk:2378
+msgid "Diff this -> selected"
+msgstr "これと選択したコミットのdiffを見る"
+
+#: gitk:2362 gitk:2379
+msgid "Diff selected -> this"
+msgstr "選択したコミットとこれのdiffを見る"
+
+#: gitk:2363 gitk:2380
+msgid "Make patch"
+msgstr "パッチ作成"
+
+#: gitk:2364 gitk:8497
+msgid "Create tag"
+msgstr "タグ生成"
+
+#: gitk:2365 gitk:8603
+msgid "Write commit to file"
+msgstr "コミットをファイルに書き出す"
+
+#: gitk:2366 gitk:8660
+msgid "Create new branch"
+msgstr "新規ブランチ生成"
+
+#: gitk:2367
+msgid "Cherry-pick this commit"
+msgstr "このコミットをチェリーピックする"
+
+#: gitk:2368
+msgid "Reset HEAD branch to here"
+msgstr "ブランチのHEADをここにリセットする"
+
+#: gitk:2369
+msgid "Mark this commit"
+msgstr "このコミットにマークをつける"
+
+#: gitk:2370
+msgid "Return to mark"
+msgstr "マークを付けた所に戻る"
+
+#: gitk:2371
+msgid "Find descendant of this and mark"
+msgstr "これとマークをつけた所との子孫を見つける"
+
+#: gitk:2372
+msgid "Compare with marked commit"
+msgstr "マークを付けたコミットと比較する"
+
+#: gitk:2386
+msgid "Check out this branch"
+msgstr "このブランチをチェックアウトする"
+
+#: gitk:2387
+msgid "Remove this branch"
+msgstr "このブランチを除去する"
+
+#: gitk:2394
+msgid "Highlight this too"
+msgstr "これもハイライトさせる"
+
+#: gitk:2395
+msgid "Highlight this only"
+msgstr "これだけをハイライトさせる"
+
+#: gitk:2396
+msgid "External diff"
+msgstr "外部diffツール"
+
+#: gitk:2397
+msgid "Blame parent commit"
+msgstr "親コミットから blame をかける"
+
+#: gitk:2404
+msgid "Show origin of this line"
+msgstr "この行の出自を表示する"
+
+#: gitk:2405
+msgid "Run git gui blame on this line"
+msgstr "この行に git gui で blame をかける"
+
+#: gitk:2658
+msgid ""
+"\n"
+"Gitk - a commit viewer for git\n"
+"\n"
+"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
+"\n"
+"Use and redistribute under the terms of the GNU General Public License"
+msgstr ""
+"\n"
+"Gitk - gitコミットビューア\n"
+"\n"
+"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
+"\n"
+"使用および再配布は GNU General Public License に従ってください"
+
+#: gitk:2666 gitk:2728 gitk:9025
+msgid "Close"
+msgstr "閉じる"
+
+#: gitk:2685
+msgid "Gitk key bindings"
+msgstr "Gitk キーバインディング"
+
+#: gitk:2688
+msgid "Gitk key bindings:"
+msgstr "Gitk キーバインディング:"
+
+#: gitk:2690
+#, tcl-format
+msgid "<%s-Q>\t\tQuit"
+msgstr "<%s-Q>\t\t終了"
+
+#: gitk:2691
+msgid "<Home>\t\tMove to first commit"
+msgstr "<Home>\t\t最初のコミットに移動"
+
+#: gitk:2692
+msgid "<End>\t\tMove to last commit"
+msgstr "<End>\t\t最後のコミットに移動"
+
+#: gitk:2693
+msgid "<Up>, p, i\tMove up one commit"
+msgstr "<Up>, p, i\t一つ上のコミットに移動"
+
+#: gitk:2694
+msgid "<Down>, n, k\tMove down one commit"
+msgstr "<Down>, n, k\t一つ下のコミットに移動"
+
+#: gitk:2695
+msgid "<Left>, z, j\tGo back in history list"
+msgstr "<Left>, z, j\t履歴の前に戻る"
+
+#: gitk:2696
+msgid "<Right>, x, l\tGo forward in history list"
+msgstr "<Right>, x, l\t履歴の次へ進む"
+
+#: gitk:2697
+msgid "<PageUp>\tMove up one page in commit list"
+msgstr "<PageUp>\tコミットリストの一つ上のページに移動"
+
+#: gitk:2698
+msgid "<PageDown>\tMove down one page in commit list"
+msgstr "<PageDown>\tコミットリストの一つ下のページに移動"
+
+#: gitk:2699
+#, tcl-format
+msgid "<%s-Home>\tScroll to top of commit list"
+msgstr "<%s-Home>\tコミットリストの一番上にスクロールする"
+
+#: gitk:2700
+#, tcl-format
+msgid "<%s-End>\tScroll to bottom of commit list"
+msgstr "<%s-End>\tコミットリストの一番下にスクロールする"
+
+#: gitk:2701
+#, tcl-format
+msgid "<%s-Up>\tScroll commit list up one line"
+msgstr "<%s-Up>\tコミットリストの一つ下の行にスクロールする"
+
+#: gitk:2702
+#, tcl-format
+msgid "<%s-Down>\tScroll commit list down one line"
+msgstr "<%s-Down>\tコミットリストの一つ下の行にスクロールする"
+
+#: gitk:2703
+#, tcl-format
+msgid "<%s-PageUp>\tScroll commit list up one page"
+msgstr "<%s-PageUp>\tコミットリストの上のページにスクロールする"
+
+#: gitk:2704
+#, tcl-format
+msgid "<%s-PageDown>\tScroll commit list down one page"
+msgstr "<%s-PageDown>\tコミットリストの下のページにスクロールする"
+
+#: gitk:2705
+msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
+msgstr "<Shift-Up>\t後方を検索 (上方の・新しいコミット)"
+
+#: gitk:2706
+msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
+msgstr "<Shift-Down>\t前方を検索(下方の・古いコミット)"
+
+#: gitk:2707
+msgid "<Delete>, b\tScroll diff view up one page"
+msgstr "<Delete>, b\tdiff画面を上のページにスクロールする"
+
+#: gitk:2708
+msgid "<Backspace>\tScroll diff view up one page"
+msgstr "<Backspace>\tdiff画面を上のページにスクロールする"
+
+#: gitk:2709
+msgid "<Space>\t\tScroll diff view down one page"
+msgstr "<Space>\t\tdiff画面を下のページにスクロールする"
+
+#: gitk:2710
+msgid "u\t\tScroll diff view up 18 lines"
+msgstr "u\t\tdiff画面を上に18行スクロールする"
+
+#: gitk:2711
+msgid "d\t\tScroll diff view down 18 lines"
+msgstr "d\t\tdiff画面を下に18行スクロールする"
+
+#: gitk:2712
+#, tcl-format
+msgid "<%s-F>\t\tFind"
+msgstr "<%s-F>\t\t検索"
+
+#: gitk:2713
+#, tcl-format
+msgid "<%s-G>\t\tMove to next find hit"
+msgstr "<%s-G>\t\t次を検索して移動"
+
+#: gitk:2714
+msgid "<Return>\tMove to next find hit"
+msgstr "<Return>\t次を検索して移動"
+
+#: gitk:2715
+msgid "/\t\tFocus the search box"
+msgstr "/\t\t検索ボックスにフォーカス"
+
+#: gitk:2716
+msgid "?\t\tMove to previous find hit"
+msgstr "?\t\t前を検索して移動"
+
+#: gitk:2717
+msgid "f\t\tScroll diff view to next file"
+msgstr "f\t\t次のファイルにdiff画面をスクロールする"
+
+#: gitk:2718
+#, tcl-format
+msgid "<%s-S>\t\tSearch for next hit in diff view"
+msgstr "<%s-S>\t\tdiff画面の次を検索"
+
+#: gitk:2719
+#, tcl-format
+msgid "<%s-R>\t\tSearch for previous hit in diff view"
+msgstr "<%s-R>\t\tdiff画面の前を検索"
+
+#: gitk:2720
+#, tcl-format
+msgid "<%s-KP+>\tIncrease font size"
+msgstr "<%s-KP+>\t文字サイズを拡大"
+
+#: gitk:2721
+#, tcl-format
+msgid "<%s-plus>\tIncrease font size"
+msgstr "<%s-plus>\t文字サイズを拡大"
+
+#: gitk:2722
+#, tcl-format
+msgid "<%s-KP->\tDecrease font size"
+msgstr "<%s-KP->\t文字サイズを縮小"
+
+#: gitk:2723
+#, tcl-format
+msgid "<%s-minus>\tDecrease font size"
+msgstr "<%s-minus>\t文字サイズを縮小"
+
+#: gitk:2724
+msgid "<F5>\t\tUpdate"
+msgstr "<F5>\t\t更新"
+
+#: gitk:3179 gitk:3188
+#, tcl-format
+msgid "Error creating temporary directory %s:"
+msgstr "一時ディレクトリ %s 生成時エラー:"
+
+#: gitk:3201
+#, tcl-format
+msgid "Error getting \"%s\" from %s:"
+msgstr "\"%s\" のエラーが %s に発生:"
+
+#: gitk:3264
+msgid "command failed:"
+msgstr "コマンド失敗:"
+
+#: gitk:3410
+msgid "No such commit"
+msgstr "そのようなコミットはありません"
+
+#: gitk:3424
+msgid "git gui blame: command failed:"
+msgstr "git gui blame: コマンド失敗:"
+
+#: gitk:3455
+#, tcl-format
+msgid "Couldn't read merge head: %s"
+msgstr "マージする HEAD を読み込めません: %s"
+
+#: gitk:3463
+#, tcl-format
+msgid "Error reading index: %s"
+msgstr "インデックス読み込みエラー: %s"
+
+#: gitk:3488
+#, tcl-format
+msgid "Couldn't start git blame: %s"
+msgstr "git blame を始められません: %s"
+
+#: gitk:3491 gitk:6280
+msgid "Searching"
+msgstr "検索中"
+
+#: gitk:3523
+#, tcl-format
+msgid "Error running git blame: %s"
+msgstr "git blame 実行エラー: %s"
+
+#: gitk:3551
+#, tcl-format
+msgid "That line comes from commit %s,  which is not in this view"
+msgstr "コミット %s に由来するその行は、このビューに表示されていません"
+
+#: gitk:3565
+msgid "External diff viewer failed:"
+msgstr "外部diffビューアが失敗:"
+
+#: gitk:3683
+msgid "Gitk view definition"
+msgstr "Gitk ビュー定義"
+
+#: gitk:3687
+msgid "Remember this view"
+msgstr "このビューを記憶する"
+
+#: gitk:3688
+msgid "References (space separated list):"
+msgstr "リファレンス(スペース区切りのリスト):"
+
+#: gitk:3689
+msgid "Branches & tags:"
+msgstr "ブランチ&タグ:"
+
+#: gitk:3690
+msgid "All refs"
+msgstr "全てのリファレンス"
+
+#: gitk:3691
+msgid "All (local) branches"
+msgstr "全ての(ローカルな)ブランチ"
+
+#: gitk:3692
+msgid "All tags"
+msgstr "全てのタグ"
+
+#: gitk:3693
+msgid "All remote-tracking branches"
+msgstr "全てのリモート追跡ブランチ"
+
+#: gitk:3694
+msgid "Commit Info (regular expressions):"
+msgstr "コミット情報(正規表現):"
+
+#: gitk:3695
+msgid "Author:"
+msgstr "作者:"
+
+#: gitk:3696
+msgid "Committer:"
+msgstr "コミット者:"
+
+#: gitk:3697
+msgid "Commit Message:"
+msgstr "コミットメッセージ:"
+
+#: gitk:3698
+msgid "Matches all Commit Info criteria"
+msgstr "コミット情報の全ての条件に一致"
+
+#: gitk:3699
+msgid "Changes to Files:"
+msgstr "変更したファイル:"
+
+#: gitk:3700
+msgid "Fixed String"
+msgstr "固定文字列"
+
+#: gitk:3701
+msgid "Regular Expression"
+msgstr "正規表現"
+
+#: gitk:3702
+msgid "Search string:"
+msgstr "検索文字列:"
+
+#: gitk:3703
+msgid ""
+"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+msgstr ""
+"コミット日時 (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+
+#: gitk:3704
+msgid "Since:"
+msgstr "期間の始め:"
+
+#: gitk:3705
+msgid "Until:"
+msgstr "期間の終わり:"
+
+#: gitk:3706
+msgid "Limit and/or skip a number of revisions (positive integer):"
+msgstr "制限・省略するリビジョンの数(正の整数):"
+
+#: gitk:3707
+msgid "Number to show:"
+msgstr "表示する数:"
+
+#: gitk:3708
+msgid "Number to skip:"
+msgstr "省略する数:"
+
+#: gitk:3709
+msgid "Miscellaneous options:"
+msgstr "その他のオプション:"
+
+#: gitk:3710
+msgid "Strictly sort by date"
+msgstr "厳密に日付順で並び替え"
+
+#: gitk:3711
+msgid "Mark branch sides"
+msgstr "側枝マーク"
+
+#: gitk:3712
+msgid "Limit to first parent"
+msgstr "最初の親に制限"
+
+#: gitk:3713
+msgid "Simple history"
+msgstr "簡易な履歴"
+
+#: gitk:3714
+msgid "Additional arguments to git log:"
+msgstr "git log への追加の引数:"
+
+#: gitk:3715
+msgid "Enter files and directories to include, one per line:"
+msgstr "含まれるファイル・ディレクトリを一行ごとに入力:"
+
+#: gitk:3716
+msgid "Command to generate more commits to include:"
+msgstr "コミット追加コマンド:"
+
+#: gitk:3838
+msgid "Gitk: edit view"
+msgstr "Gitk: ビュー編集"
+
+#: gitk:3846
+msgid "-- criteria for selecting revisions"
+msgstr "― リビジョンの選択条件"
+
+#: gitk:3851
+msgid "View Name:"
+msgstr "ビュー名:"
+
+#: gitk:3926
+msgid "Apply (F5)"
+msgstr "適用 (F5)"
+
+#: gitk:3964
+msgid "Error in commit selection arguments:"
+msgstr "コミット選択引数のエラー:"
+
+#: gitk:4017 gitk:4069 gitk:4517 gitk:4531 gitk:5792 gitk:11263 gitk:11264
+msgid "None"
+msgstr "無し"
+
+#: gitk:4465 gitk:6312 gitk:8093 gitk:8108
+msgid "Date"
+msgstr "日付"
+
+#: gitk:4465 gitk:6312
+msgid "CDate"
+msgstr "作成日"
+
+#: gitk:4614 gitk:4619
+msgid "Descendant"
+msgstr "子孫"
+
+#: gitk:4615
+msgid "Not descendant"
+msgstr "非子孫"
+
+#: gitk:4622 gitk:4627
+msgid "Ancestor"
+msgstr "祖先"
+
+#: gitk:4623
+msgid "Not ancestor"
+msgstr "非祖先"
+
+#: gitk:4913
+msgid "Local changes checked in to index but not committed"
+msgstr "ステージされた、コミット前のローカルな変更"
+
+#: gitk:4949
+msgid "Local uncommitted changes, not checked in to index"
+msgstr "ステージされていない、コミット前のローカルな変更"
+
+#: gitk:6630
+msgid "many"
+msgstr "多数"
+
+#: gitk:6814
+msgid "Tags:"
+msgstr "タグ:"
+
+#: gitk:6831 gitk:6837 gitk:8086
+msgid "Parent"
+msgstr "親"
+
+#: gitk:6842
+msgid "Child"
+msgstr "子"
+
+#: gitk:6851
+msgid "Branch"
+msgstr "ブランチ"
+
+#: gitk:6854
+msgid "Follows"
+msgstr "下位"
+
+#: gitk:6857
+msgid "Precedes"
+msgstr "上位"
+
+#: gitk:7359
+#, tcl-format
+msgid "Error getting diffs: %s"
+msgstr "diff取得エラー: %s"
+
+#: gitk:7914
+msgid "Goto:"
+msgstr "Goto:"
+
+#: gitk:7916
+msgid "SHA1 ID:"
+msgstr "SHA1 ID:"
+
+#: gitk:7935
+#, tcl-format
+msgid "Short SHA1 id %s is ambiguous"
+msgstr "%s を含む SHA1 ID は複数存在します"
+
+#: gitk:7942
+#, tcl-format
+msgid "Revision %s is not known"
+msgstr "リビジョン %s は不明です"
+
+#: gitk:7952
+#, tcl-format
+msgid "SHA1 id %s is not known"
+msgstr "SHA1 id %s は不明です"
+
+#: gitk:7954
+#, tcl-format
+msgid "Revision %s is not in the current view"
+msgstr "リビジョン %s は現在のビューにはありません"
+
+#: gitk:8096
+msgid "Children"
+msgstr "子"
+
+#: gitk:8153
+#, tcl-format
+msgid "Reset %s branch to here"
+msgstr "%s ブランチをここにリセットする"
+
+#: gitk:8155
+msgid "Detached head: can't reset"
+msgstr "切り離されたHEAD: リセットできません"
+
+#: gitk:8264 gitk:8270
+msgid "Skipping merge commit "
+msgstr "コミットマージをスキップ: "
+
+#: gitk:8279 gitk:8284
+msgid "Error getting patch ID for "
+msgstr "パッチ取得エラー: ID "
+
+#: gitk:8280 gitk:8285
+msgid " - stopping\n"
+msgstr " - 停止\n"
+
+#: gitk:8290 gitk:8293 gitk:8301 gitk:8314 gitk:8323
+msgid "Commit "
+msgstr "コミット "
+
+#: gitk:8294
+msgid ""
+" is the same patch as\n"
+"       "
+msgstr ""
+" は下記のパッチと同等\n"
+"       "
+
+#: gitk:8302
+msgid ""
+" differs from\n"
+"       "
+msgstr ""
+" 下記からのdiff\n"
+"       "
+
+#: gitk:8304
+msgid ""
+"Diff of commits:\n"
+"\n"
+msgstr ""
+"コミットのdiff:\n"
+"\n"
+
+#: gitk:8315 gitk:8324
+#, tcl-format
+msgid " has %s children - stopping\n"
+msgstr " には %s の子があります - 停止\n"
+
+#: gitk:8344
+#, tcl-format
+msgid "Error writing commit to file: %s"
+msgstr "ファイルへのコミット書き出しエラー: %s"
+
+#: gitk:8350
+#, tcl-format
+msgid "Error diffing commits: %s"
+msgstr "コミットのdiff実行エラー: %s"
+
+#: gitk:8380
+msgid "Top"
+msgstr "Top"
+
+#: gitk:8381
+msgid "From"
+msgstr "From"
+
+#: gitk:8386
+msgid "To"
+msgstr "To"
+
+#: gitk:8410
+msgid "Generate patch"
+msgstr "パッチ生成"
+
+#: gitk:8412
+msgid "From:"
+msgstr "From:"
+
+#: gitk:8421
+msgid "To:"
+msgstr "To:"
+
+#: gitk:8430
+msgid "Reverse"
+msgstr "逆"
+
+#: gitk:8432 gitk:8617
+msgid "Output file:"
+msgstr "出力ファイル:"
+
+#: gitk:8438
+msgid "Generate"
+msgstr "生成"
+
+#: gitk:8476
+msgid "Error creating patch:"
+msgstr "パッチ生成エラー:"
+
+#: gitk:8499 gitk:8605 gitk:8662
+msgid "ID:"
+msgstr "ID:"
+
+#: gitk:8508
+msgid "Tag name:"
+msgstr "タグ名:"
+
+#: gitk:8512 gitk:8671
+msgid "Create"
+msgstr "生成"
+
+#: gitk:8529
+msgid "No tag name specified"
+msgstr "タグの名称が指定されていません"
+
+#: gitk:8533
+#, tcl-format
+msgid "Tag \"%s\" already exists"
+msgstr "タグ \"%s\" は既に存在します"
+
+#: gitk:8539
+msgid "Error creating tag:"
+msgstr "タグ生成エラー:"
+
+#: gitk:8614
+msgid "Command:"
+msgstr "コマンド:"
+
+#: gitk:8622
+msgid "Write"
+msgstr "書き出し"
+
+#: gitk:8640
+msgid "Error writing commit:"
+msgstr "コミット書き出しエラー:"
+
+#: gitk:8667
+msgid "Name:"
+msgstr "名前:"
+
+#: gitk:8690
+msgid "Please specify a name for the new branch"
+msgstr "新しいブランチの名前を指定してください"
+
+#: gitk:8695
+#, tcl-format
+msgid "Branch '%s' already exists. Overwrite?"
+msgstr "ブランチ '%s' は既に存在します。上書きしますか?"
+
+#: gitk:8761
+#, tcl-format
+msgid "Commit %s is already included in branch %s -- really re-apply it?"
+msgstr ""
+"コミット %s は既にブランチ %s に含まれています ― 本当にこれを再適用しますか?"
+
+#: gitk:8766
+msgid "Cherry-picking"
+msgstr "チェリーピック中"
+
+#: gitk:8775
+#, tcl-format
+msgid ""
+"Cherry-pick failed because of local changes to file '%s'.\n"
+"Please commit, reset or stash your changes and try again."
+msgstr ""
+"ファイル '%s' のローカルな変更のためにチェリーピックは失敗しました。\n"
+"あなたの変更に commit, reset, stash のいずれかを行ってからやり直してくださ"
+"い。"
+
+#: gitk:8781
+msgid ""
+"Cherry-pick failed because of merge conflict.\n"
+"Do you wish to run git citool to resolve it?"
+msgstr ""
+"マージの衝突によってチェリーピックは失敗しました。\n"
+"この解決のために git citool を実行したいですか?"
+
+#: gitk:8797
+msgid "No changes committed"
+msgstr "何の変更もコミットされていません"
+
+#: gitk:8823
+msgid "Confirm reset"
+msgstr "確認を取り消す"
+
+#: gitk:8825
+#, tcl-format
+msgid "Reset branch %s to %s?"
+msgstr "ブランチ %s を %s にリセットしますか?"
+
+#: gitk:8829
+msgid "Reset type:"
+msgstr "Reset タイプ:"
+
+#: gitk:8833
+msgid "Soft: Leave working tree and index untouched"
+msgstr "Soft: 作業ツリーもインデックスもそのままにする"
+
+#: gitk:8836
+msgid "Mixed: Leave working tree untouched, reset index"
+msgstr "Mixed: 作業ツリーをそのままにして、インデックスをリセット"
+
+#: gitk:8839
+msgid ""
+"Hard: Reset working tree and index\n"
+"(discard ALL local changes)"
+msgstr ""
+"Hard: 作業ツリーやインデックスをリセット\n"
+"(「全ての」ローカルな変更を破棄)"
+
+#: gitk:8856
+msgid "Resetting"
+msgstr "リセット中"
+
+#: gitk:8913
+msgid "Checking out"
+msgstr "チェックアウト"
+
+#: gitk:8966
+msgid "Cannot delete the currently checked-out branch"
+msgstr "現在チェックアウトされているブランチを削除することはできません"
+
+#: gitk:8972
+#, tcl-format
+msgid ""
+"The commits on branch %s aren't on any other branch.\n"
+"Really delete branch %s?"
+msgstr ""
+"ブランチ %s には他のブランチに存在しないコミットがあります。\n"
+"本当にブランチ %s を削除しますか?"
+
+#: gitk:9003
+#, tcl-format
+msgid "Tags and heads: %s"
+msgstr "タグとHEAD: %s"
+
+#: gitk:9018
+msgid "Filter"
+msgstr "フィルター"
+
+#: gitk:9313
+msgid ""
+"Error reading commit topology information; branch and preceding/following "
+"tag information will be incomplete."
+msgstr ""
+"コミット構造情報読み込みエラー; ブランチ及び上位/下位のタグ情報が不完全である"
+"ようです。"
+
+#: gitk:10299
+msgid "Tag"
+msgstr "タグ"
+
+#: gitk:10299
+msgid "Id"
+msgstr "ID"
+
+#: gitk:10347
+msgid "Gitk font chooser"
+msgstr "Gitk フォント選択"
+
+#: gitk:10364
+msgid "B"
+msgstr "B"
+
+#: gitk:10367
+msgid "I"
+msgstr "I"
+
+#: gitk:10463
+msgid "Gitk preferences"
+msgstr "Gitk 設定"
+
+#: gitk:10465
+msgid "Commit list display options"
+msgstr "コミットリスト表示オプション"
+
+#: gitk:10468
+msgid "Maximum graph width (lines)"
+msgstr "最大グラフ幅(線の本数)"
+
+#: gitk:10472
+#, tcl-format
+msgid "Maximum graph width (% of pane)"
+msgstr "最大グラフ幅(ペインに対する%)"
+
+#: gitk:10476
+msgid "Show local changes"
+msgstr "ローカルな変更を表示"
+
+#: gitk:10479
+msgid "Auto-select SHA1"
+msgstr "SHA1 の自動選択"
+
+#: gitk:10483
+msgid "Diff display options"
+msgstr "diff表示オプション"
+
+#: gitk:10485
+msgid "Tab spacing"
+msgstr "タブ空白幅"
+
+#: gitk:10488
+msgid "Display nearby tags"
+msgstr "近くのタグを表示する"
+
+#: gitk:10491
+msgid "Hide remote refs"
+msgstr "リモートリファレンスを隠す"
+
+#: gitk:10494
+msgid "Limit diffs to listed paths"
+msgstr "diff をリストのパスに制限"
+
+#: gitk:10497
+msgid "Support per-file encodings"
+msgstr "ファイルごとのエンコーディングのサポート"
+
+#: gitk:10503 gitk:10572
+msgid "External diff tool"
+msgstr "外部diffツール"
+
+#: gitk:10505
+msgid "Choose..."
+msgstr "選択..."
+
+#: gitk:10510
+msgid "Colors: press to choose"
+msgstr "色: ボタンを押して選択"
+
+#: gitk:10513
+msgid "Interface"
+msgstr "インターフェイス"
+
+#: gitk:10514
+msgid "interface"
+msgstr "インターフェイス"
+
+#: gitk:10517
+msgid "Background"
+msgstr "背景"
+
+#: gitk:10518 gitk:10548
+msgid "background"
+msgstr "背景"
+
+#: gitk:10521
+msgid "Foreground"
+msgstr "前景"
+
+#: gitk:10522
+msgid "foreground"
+msgstr "前景"
+
+#: gitk:10525
+msgid "Diff: old lines"
+msgstr "Diff: 旧バージョン"
+
+#: gitk:10526
+msgid "diff old lines"
+msgstr "diff 旧バージョン"
+
+#: gitk:10530
+msgid "Diff: new lines"
+msgstr "Diff: 新バージョン"
+
+#: gitk:10531
+msgid "diff new lines"
+msgstr "diff 新バージョン"
+
+#: gitk:10535
+msgid "Diff: hunk header"
+msgstr "Diff: hunkヘッダ"
+
+#: gitk:10537
+msgid "diff hunk header"
+msgstr "diff hunkヘッダ"
+
+#: gitk:10541
+msgid "Marked line bg"
+msgstr "マーク行の背景"
+
+#: gitk:10543
+msgid "marked line background"
+msgstr "マーク行の背景"
+
+#: gitk:10547
+msgid "Select bg"
+msgstr "選択の背景"
+
+#: gitk:10551
+msgid "Fonts: press to choose"
+msgstr "フォント: ボタンを押して選択"
+
+#: gitk:10553
+msgid "Main font"
+msgstr "主フォント"
+
+#: gitk:10554
+msgid "Diff display font"
+msgstr "Diff表示用フォント"
+
+#: gitk:10555
+msgid "User interface font"
+msgstr "UI用フォント"
+
+#: gitk:10582
+#, tcl-format
+msgid "Gitk: choose color for %s"
+msgstr "Gitk: 「%s」 の色を選択"
+
+#: gitk:11168
+msgid "Cannot find a git repository here."
+msgstr "ここにはgitリポジトリがありません。"
+
+#: gitk:11172
+#, tcl-format
+msgid "Cannot find the git directory \"%s\"."
+msgstr "gitディレクトリ \"%s\" を見つけられません。"
+
+#: gitk:11219
+#, tcl-format
+msgid "Ambiguous argument '%s': both revision and filename"
+msgstr "あいまいな引数 '%s': リビジョンとファイル名の両方に解釈できます"
+
+#: gitk:11231
+msgid "Bad arguments to gitk:"
+msgstr "gitkへの不正な引数:"
+
+#: gitk:11316
+msgid "Command line"
+msgstr "コマンド行"
index 704eba8f9d39655099aa66864d1dd34a84bd0a9d..c3d0285b2429d92ca40297bfb2135365ba49f371 100644 (file)
@@ -313,14 +313,14 @@ msgid ""
 "\n"
 "Gitk - a commit viewer for git\n"
 "\n"
-"Copyright © 2005-2008 Paul Mackerras\n"
+"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
 "\n"
 "Use and redistribute under the terms of the GNU General Public License"
 msgstr ""
 "\n"
 "Gitk - программа просмотра истории репозиториев Git\n"
 "\n"
-"Copyright (c) 2005-2008 Paul Mackerras\n"
+"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
 "\n"
 "Использование и распространение согласно условиям GNU General Public License"
 
index 947b53f6b0d76bc169e4aee2e8a030963fdc131f..386763ade786d96b657dd0ab737b633eef44b424 100644 (file)
@@ -1,32 +1,40 @@
 # Swedish translation for gitk
-# Copyright (C) 2005-2008 Paul Mackerras
+# Copyright (C) 2005-2009 Paul Mackerras
 # This file is distributed under the same license as the gitk package.
 #
-# Peter Karlsson <peter@softwolves.pp.se>, 2008.
+# Peter Krefting <peter@softwolves.pp.se>, 2008-2010.
 # Mikael Magnusson <mikachu@gmail.com>, 2008.
 msgid ""
 msgstr ""
 "Project-Id-Version: sv\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-10-18 22:03+1100\n"
-"PO-Revision-Date: 2008-08-03 19:03+0200\n"
-"Last-Translator: Mikael Magnusson <mikachu@gmail.com>\n"
-"Language-Team: Swedish <sv@li.org>\n"
+"POT-Creation-Date: 2010-01-28 13:16+0100\n"
+"PO-Revision-Date: 2010-01-28 13:48+0100\n"
+"Last-Translator: Peter Krefting <peter@softwolves.pp.se>\n"
+"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
+"Content-Transfer-Encoding: 8bit"
 
-#: gitk:113
+#: gitk:115
 msgid "Couldn't get list of unmerged files:"
-msgstr "Kunde inta hämta lista över ej sammanslagna filer:"
+msgstr "Kunde inte hämta lista över ej sammanslagna filer:"
 
-#: gitk:340
+#: gitk:274
+msgid "Error parsing revisions:"
+msgstr "Fel vid tolkning av revisioner:"
+
+#: gitk:329
+msgid "Error executing --argscmd command:"
+msgstr "Fel vid körning av --argscmd-kommando:"
+
+#: gitk:342
 msgid "No files selected: --merge specified but no files are unmerged."
 msgstr ""
 "Inga filer valdes: --merge angavs men det finns inga filer som inte har "
 "slagits samman."
 
-#: gitk:343
+#: gitk:345
 msgid ""
 "No files selected: --merge specified but no unmerged files are within file "
 "limit."
@@ -34,697 +42,993 @@ msgstr ""
 "Inga filer valdes: --merge angavs men det finns inga filer inom "
 "filbegränsningen."
 
-#: gitk:365 gitk:503
+#: gitk:367 gitk:514
 msgid "Error executing git log:"
 msgstr "Fel vid körning av git log:"
 
-#: gitk:378
+#: gitk:385 gitk:530
 msgid "Reading"
 msgstr "Läser"
 
-#: gitk:438 gitk:3462
+#: gitk:445 gitk:4261
 msgid "Reading commits..."
 msgstr "Läser incheckningar..."
 
-#: gitk:441 gitk:1528 gitk:3465
+#: gitk:448 gitk:1578 gitk:4264
 msgid "No commits selected"
 msgstr "Inga incheckningar markerade"
 
-#: gitk:1399
+#: gitk:1454
 msgid "Can't parse git log output:"
 msgstr "Kan inte tolka utdata från git log:"
 
-#: gitk:1605
+#: gitk:1674
 msgid "No commit information available"
 msgstr "Ingen incheckningsinformation är tillgänglig"
 
-#: gitk:1709 gitk:1731 gitk:3259 gitk:7764 gitk:9293 gitk:9466
+#: gitk:1816
+msgid "mc"
+msgstr "mc"
+
+#: gitk:1851 gitk:4054 gitk:9044 gitk:10585 gitk:10804
 msgid "OK"
 msgstr "OK"
 
-#: gitk:1733 gitk:3260 gitk:7439 gitk:7510 gitk:7613 gitk:7660 gitk:7766
-#: gitk:9294 gitk:9467
+#: gitk:1853 gitk:4056 gitk:8634 gitk:8713 gitk:8828 gitk:8877 gitk:9046
+#: gitk:10586 gitk:10805
 msgid "Cancel"
 msgstr "Avbryt"
 
-#: gitk:1811
+#: gitk:1975
 msgid "Update"
 msgstr "Uppdatera"
 
-#: gitk:1812
+#: gitk:1976
 msgid "Reload"
 msgstr "Ladda om"
 
-#: gitk:1813
+#: gitk:1977
 msgid "Reread references"
 msgstr "Läs om referenser"
 
-#: gitk:1814
+#: gitk:1978
 msgid "List references"
 msgstr "Visa referenser"
 
-#: gitk:1815
+#: gitk:1980
+msgid "Start git gui"
+msgstr "Starta git gui"
+
+#: gitk:1982
 msgid "Quit"
 msgstr "Avsluta"
 
-#: gitk:1810
+#: gitk:1974
 msgid "File"
 msgstr "Arkiv"
 
-#: gitk:1818
+#: gitk:1986
 msgid "Preferences"
 msgstr "Inställningar"
 
-#: gitk:1817
+#: gitk:1985
 msgid "Edit"
 msgstr "Redigera"
 
-#: gitk:1821
+#: gitk:1990
 msgid "New view..."
 msgstr "Ny vy..."
 
-#: gitk:1822
+#: gitk:1991
 msgid "Edit view..."
 msgstr "Ändra vy..."
 
-#: gitk:1823
+#: gitk:1992
 msgid "Delete view"
 msgstr "Ta bort vy"
 
-#: gitk:1825
+#: gitk:1994
 msgid "All files"
 msgstr "Alla filer"
 
-#: gitk:1820 gitk:3196
+#: gitk:1989 gitk:3808
 msgid "View"
 msgstr "Visa"
 
-#: gitk:1828 gitk:2487
+#: gitk:1999 gitk:2009 gitk:2780
 msgid "About gitk"
 msgstr "Om gitk"
 
-#: gitk:1829
+#: gitk:2000 gitk:2014
 msgid "Key bindings"
 msgstr "Tangentbordsbindningar"
 
-#: gitk:1827
+#: gitk:1998 gitk:2013
 msgid "Help"
 msgstr "Hjälp"
 
-#: gitk:1887
-msgid "SHA1 ID: "
-msgstr "SHA1-id: "
+#: gitk:2091 gitk:8110
+msgid "SHA1 ID:"
+msgstr "SHA1-id:"
 
-#: gitk:1918
+#: gitk:2122
 msgid "Row"
 msgstr "Rad"
 
-#: gitk:1949
+#: gitk:2160
 msgid "Find"
 msgstr "Sök"
 
-#: gitk:1950
+#: gitk:2161
 msgid "next"
 msgstr "nästa"
 
-#: gitk:1951
+#: gitk:2162
 msgid "prev"
 msgstr "föreg"
 
-#: gitk:1952
+#: gitk:2163
 msgid "commit"
 msgstr "incheckning"
 
-#: gitk:1955 gitk:1957 gitk:3617 gitk:3640 gitk:3664 gitk:5550 gitk:5621
+#: gitk:2166 gitk:2168 gitk:4422 gitk:4445 gitk:4469 gitk:6410 gitk:6482
+#: gitk:6566
 msgid "containing:"
 msgstr "som innehåller:"
 
-#: gitk:1958 gitk:2954 gitk:2959 gitk:3692
+#: gitk:2169 gitk:3290 gitk:3295 gitk:4497
 msgid "touching paths:"
 msgstr "som rör sökväg:"
 
-#: gitk:1959 gitk:3697
+#: gitk:2170 gitk:4502
 msgid "adding/removing string:"
 msgstr "som lägger/till tar bort sträng:"
 
-#: gitk:1968 gitk:1970
+#: gitk:2179 gitk:2181
 msgid "Exact"
 msgstr "Exakt"
 
-#: gitk:1970 gitk:3773 gitk:5518
+#: gitk:2181 gitk:4577 gitk:6378
 msgid "IgnCase"
 msgstr "IgnVersaler"
 
-#: gitk:1970 gitk:3666 gitk:3771 gitk:5514
+#: gitk:2181 gitk:4471 gitk:4575 gitk:6374
 msgid "Regexp"
 msgstr "Reg.uttr."
 
-#: gitk:1972 gitk:1973 gitk:3792 gitk:3822 gitk:3829 gitk:5641 gitk:5708
+#: gitk:2183 gitk:2184 gitk:4596 gitk:4626 gitk:4633 gitk:6502 gitk:6570
 msgid "All fields"
 msgstr "Alla fält"
 
-#: gitk:1973 gitk:3790 gitk:3822 gitk:5580
+#: gitk:2184 gitk:4594 gitk:4626 gitk:6441
 msgid "Headline"
 msgstr "Rubrik"
 
-#: gitk:1974 gitk:3790 gitk:5580 gitk:5708 gitk:6109
+#: gitk:2185 gitk:4594 gitk:6441 gitk:6570 gitk:7003
 msgid "Comments"
 msgstr "Kommentarer"
 
-#: gitk:1974 gitk:3790 gitk:3794 gitk:3829 gitk:5580 gitk:6045 gitk:7285
-#: gitk:7300
+#: gitk:2185 gitk:4594 gitk:4598 gitk:4633 gitk:6441 gitk:6938 gitk:8285
+#: gitk:8300
 msgid "Author"
 msgstr "Författare"
 
-#: gitk:1974 gitk:3790 gitk:5580 gitk:6047
+#: gitk:2185 gitk:4594 gitk:6441 gitk:6940
 msgid "Committer"
 msgstr "Incheckare"
 
-#: gitk:2003
+#: gitk:2216
 msgid "Search"
 msgstr "Sök"
 
-#: gitk:2010
+#: gitk:2224
 msgid "Diff"
 msgstr "Diff"
 
-#: gitk:2012
+#: gitk:2226
 msgid "Old version"
 msgstr "Gammal version"
 
-#: gitk:2014
+#: gitk:2228
 msgid "New version"
 msgstr "Ny version"
 
-#: gitk:2016
+#: gitk:2230
 msgid "Lines of context"
 msgstr "Rader sammanhang"
 
-#: gitk:2026
+#: gitk:2240
 msgid "Ignore space change"
 msgstr "Ignorera ändringar i blanksteg"
 
-#: gitk:2084
+#: gitk:2299
 msgid "Patch"
 msgstr "Patch"
 
-#: gitk:2086
+#: gitk:2301
 msgid "Tree"
 msgstr "Träd"
 
-#: gitk:2213 gitk:2226
+#: gitk:2456 gitk:2473
 msgid "Diff this -> selected"
 msgstr "Diff denna -> markerad"
 
-#: gitk:2214 gitk:2227
+#: gitk:2457 gitk:2474
 msgid "Diff selected -> this"
 msgstr "Diff markerad -> denna"
 
-#: gitk:2215 gitk:2228
+#: gitk:2458 gitk:2475
 msgid "Make patch"
 msgstr "Skapa patch"
 
-#: gitk:2216 gitk:7494
+#: gitk:2459 gitk:8692
 msgid "Create tag"
 msgstr "Skapa tagg"
 
-#: gitk:2217 gitk:7593
+#: gitk:2460 gitk:8808
 msgid "Write commit to file"
 msgstr "Skriv incheckning till fil"
 
-#: gitk:2218 gitk:7647
+#: gitk:2461 gitk:8865
 msgid "Create new branch"
 msgstr "Skapa ny gren"
 
-#: gitk:2219
+#: gitk:2462
 msgid "Cherry-pick this commit"
 msgstr "Plocka denna incheckning"
 
-#: gitk:2220
+#: gitk:2463
 msgid "Reset HEAD branch to here"
 msgstr "Återställ HEAD-grenen hit"
 
-#: gitk:2234
+#: gitk:2464
+msgid "Mark this commit"
+msgstr "Markera denna incheckning"
+
+#: gitk:2465
+msgid "Return to mark"
+msgstr "Återgå till markering"
+
+#: gitk:2466
+msgid "Find descendant of this and mark"
+msgstr "Hitta efterföljare till denna och markera"
+
+#: gitk:2467
+msgid "Compare with marked commit"
+msgstr "Jämför med markerad incheckning"
+
+#: gitk:2481
 msgid "Check out this branch"
 msgstr "Checka ut denna gren"
 
-#: gitk:2235
+#: gitk:2482
 msgid "Remove this branch"
 msgstr "Ta bort denna gren"
 
-#: gitk:2242
+#: gitk:2489
 msgid "Highlight this too"
 msgstr "Markera även detta"
 
-#: gitk:2243
+#: gitk:2490
 msgid "Highlight this only"
 msgstr "Markera bara detta"
 
-#: gitk:2244
+#: gitk:2491
 msgid "External diff"
 msgstr "Extern diff"
 
-#: gitk:2245
+#: gitk:2492
 msgid "Blame parent commit"
-msgstr ""
+msgstr "Klandra föräldraincheckning"
+
+#: gitk:2499
+msgid "Show origin of this line"
+msgstr "Visa ursprunget för den här raden"
+
+#: gitk:2500
+msgid "Run git gui blame on this line"
+msgstr "Kör git gui blame på den här raden"
 
-#: gitk:2488
+#: gitk:2782
 msgid ""
 "\n"
 "Gitk - a commit viewer for git\n"
 "\n"
-"Copyright © 2005-2008 Paul Mackerras\n"
+"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
 "\n"
 "Use and redistribute under the terms of the GNU General Public License"
 msgstr ""
 "\n"
 "Gitk - en incheckningsvisare för git\n"
 "\n"
-"Copyright © 2005-2008 Paul Mackerras\n"
+"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
 "\n"
 "Använd och vidareförmedla enligt villkoren i GNU General Public License"
 
-#: gitk:2496 gitk:2557 gitk:7943
+#: gitk:2790 gitk:2854 gitk:9230
 msgid "Close"
 msgstr "Stäng"
 
-#: gitk:2515
+#: gitk:2811
 msgid "Gitk key bindings"
 msgstr "Tangentbordsbindningar för Gitk"
 
-#: gitk:2517
+#: gitk:2814
 msgid "Gitk key bindings:"
 msgstr "Tangentbordsbindningar för Gitk:"
 
-#: gitk:2519
+#: gitk:2816
 #, tcl-format
 msgid "<%s-Q>\t\tQuit"
 msgstr "<%s-Q>\t\tAvsluta"
 
-#: gitk:2520
+#: gitk:2817
 msgid "<Home>\t\tMove to first commit"
 msgstr "<Home>\t\tGå till första incheckning"
 
-#: gitk:2521
+#: gitk:2818
 msgid "<End>\t\tMove to last commit"
 msgstr "<End>\t\tGå till sista incheckning"
 
-#: gitk:2522
+#: gitk:2819
 msgid "<Up>, p, i\tMove up one commit"
 msgstr "<Upp>, p, i\tGå en incheckning upp"
 
-#: gitk:2523
+#: gitk:2820
 msgid "<Down>, n, k\tMove down one commit"
 msgstr "<Ned>, n, k\tGå en incheckning ned"
 
-#: gitk:2524
+#: gitk:2821
 msgid "<Left>, z, j\tGo back in history list"
 msgstr "<Vänster>, z, j\tGå bakåt i historiken"
 
-#: gitk:2525
+#: gitk:2822
 msgid "<Right>, x, l\tGo forward in history list"
 msgstr "<Höger>, x, l\tGå framåt i historiken"
 
-#: gitk:2526
+#: gitk:2823
 msgid "<PageUp>\tMove up one page in commit list"
 msgstr "<PageUp>\tGå upp en sida i incheckningslistan"
 
-#: gitk:2527
+#: gitk:2824
 msgid "<PageDown>\tMove down one page in commit list"
 msgstr "<PageDown>\tGå ned en sida i incheckningslistan"
 
-#: gitk:2528
+#: gitk:2825
 #, tcl-format
 msgid "<%s-Home>\tScroll to top of commit list"
 msgstr "<%s-Home>\tRulla till början av incheckningslistan"
 
-#: gitk:2529
+#: gitk:2826
 #, tcl-format
 msgid "<%s-End>\tScroll to bottom of commit list"
 msgstr "<%s-End>\tRulla till slutet av incheckningslistan"
 
-#: gitk:2530
+#: gitk:2827
 #, tcl-format
 msgid "<%s-Up>\tScroll commit list up one line"
 msgstr "<%s-Upp>\tRulla incheckningslistan upp ett steg"
 
-#: gitk:2531
+#: gitk:2828
 #, tcl-format
 msgid "<%s-Down>\tScroll commit list down one line"
 msgstr "<%s-Ned>\tRulla incheckningslistan ned ett steg"
 
-#: gitk:2532
+#: gitk:2829
 #, tcl-format
 msgid "<%s-PageUp>\tScroll commit list up one page"
 msgstr "<%s-PageUp>\tRulla incheckningslistan upp en sida"
 
-#: gitk:2533
+#: gitk:2830
 #, tcl-format
 msgid "<%s-PageDown>\tScroll commit list down one page"
 msgstr "<%s-PageDown>\tRulla incheckningslistan ned en sida"
 
-#: gitk:2534
+#: gitk:2831
 msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
 msgstr "<Skift-Upp>\tSök bakåt (uppåt, senare incheckningar)"
 
-#: gitk:2535
+#: gitk:2832
 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
 msgstr "<Skift-Ned>\tSök framåt (nedåt, tidigare incheckningar)"
 
-#: gitk:2536
+#: gitk:2833
 msgid "<Delete>, b\tScroll diff view up one page"
 msgstr "<Delete>, b\tRulla diffvisningen upp en sida"
 
-#: gitk:2537
+#: gitk:2834
 msgid "<Backspace>\tScroll diff view up one page"
 msgstr "<Baksteg>\tRulla diffvisningen upp en sida"
 
-#: gitk:2538
+#: gitk:2835
 msgid "<Space>\t\tScroll diff view down one page"
 msgstr "<Blanksteg>\tRulla diffvisningen ned en sida"
 
-#: gitk:2539
+#: gitk:2836
 msgid "u\t\tScroll diff view up 18 lines"
 msgstr "u\t\tRulla diffvisningen upp 18 rader"
 
-#: gitk:2540
+#: gitk:2837
 msgid "d\t\tScroll diff view down 18 lines"
 msgstr "d\t\tRulla diffvisningen ned 18 rader"
 
-#: gitk:2541
+#: gitk:2838
 #, tcl-format
 msgid "<%s-F>\t\tFind"
 msgstr "<%s-F>\t\tSök"
 
-#: gitk:2542
+#: gitk:2839
 #, tcl-format
 msgid "<%s-G>\t\tMove to next find hit"
 msgstr "<%s-G>\t\tGå till nästa sökträff"
 
-#: gitk:2543
+#: gitk:2840
 msgid "<Return>\tMove to next find hit"
 msgstr "<Return>\t\tGå till nästa sökträff"
 
-#: gitk:2544
-msgid "/\t\tMove to next find hit, or redo find"
-msgstr "/\t\tGå till nästa sökträff, eller sök på nytt"
+#: gitk:2841
+msgid "/\t\tFocus the search box"
+msgstr "/\t\tFokusera sökrutan"
 
-#: gitk:2545
+#: gitk:2842
 msgid "?\t\tMove to previous find hit"
 msgstr "?\t\tGå till föregående sökträff"
 
-#: gitk:2546
+#: gitk:2843
 msgid "f\t\tScroll diff view to next file"
 msgstr "f\t\tRulla diffvisningen till nästa fil"
 
-#: gitk:2547
+#: gitk:2844
 #, tcl-format
 msgid "<%s-S>\t\tSearch for next hit in diff view"
 msgstr "<%s-S>\t\tGå till nästa sökträff i diffvisningen"
 
-#: gitk:2548
+#: gitk:2845
 #, tcl-format
 msgid "<%s-R>\t\tSearch for previous hit in diff view"
 msgstr "<%s-R>\t\tGå till föregående sökträff i diffvisningen"
 
-#: gitk:2549
+#: gitk:2846
 #, tcl-format
 msgid "<%s-KP+>\tIncrease font size"
 msgstr "<%s-Num+>\tÖka teckenstorlek"
 
-#: gitk:2550
+#: gitk:2847
 #, tcl-format
 msgid "<%s-plus>\tIncrease font size"
 msgstr "<%s-plus>\tÖka teckenstorlek"
 
-#: gitk:2551
+#: gitk:2848
 #, tcl-format
 msgid "<%s-KP->\tDecrease font size"
 msgstr "<%s-Num->\tMinska teckenstorlek"
 
-#: gitk:2552
+#: gitk:2849
 #, tcl-format
 msgid "<%s-minus>\tDecrease font size"
 msgstr "<%s-minus>\tMinska teckenstorlek"
 
-#: gitk:2553
+#: gitk:2850
 msgid "<F5>\t\tUpdate"
 msgstr "<F5>\t\tUppdatera"
 
-#: gitk:3200
+#: gitk:3305 gitk:3314
+#, tcl-format
+msgid "Error creating temporary directory %s:"
+msgstr "Fel vid skapande av temporär katalog %s:"
+
+#: gitk:3327
+#, tcl-format
+msgid "Error getting \"%s\" from %s:"
+msgstr "Fel vid hämtning av  \"%s\" från %s:"
+
+#: gitk:3390
+msgid "command failed:"
+msgstr "kommando misslyckades:"
+
+#: gitk:3539
+msgid "No such commit"
+msgstr "Incheckning saknas"
+
+#: gitk:3553
+msgid "git gui blame: command failed:"
+msgstr "git gui blame: kommando misslyckades:"
+
+#: gitk:3584
+#, tcl-format
+msgid "Couldn't read merge head: %s"
+msgstr "Kunde inte läsa sammanslagningshuvud: %s"
+
+#: gitk:3592
+#, tcl-format
+msgid "Error reading index: %s"
+msgstr "Fel vid läsning av index: %s"
+
+#: gitk:3617
+#, tcl-format
+msgid "Couldn't start git blame: %s"
+msgstr "Kunde inte starta git blame: %s"
+
+#: gitk:3620 gitk:6409
+msgid "Searching"
+msgstr "Söker"
+
+#: gitk:3652
+#, tcl-format
+msgid "Error running git blame: %s"
+msgstr "Fel vid körning av git blame: %s"
+
+#: gitk:3680
+#, tcl-format
+msgid "That line comes from commit %s,  which is not in this view"
+msgstr "Raden kommer från incheckningen %s, som inte finns i denna vy"
+
+#: gitk:3694
+msgid "External diff viewer failed:"
+msgstr "Externt diff-verktyg misslyckades:"
+
+#: gitk:3812
 msgid "Gitk view definition"
 msgstr "Definition av Gitk-vy"
 
-#: gitk:3225
-msgid "Name"
-msgstr "Namn"
-
-#: gitk:3228
+#: gitk:3816
 msgid "Remember this view"
 msgstr "Spara denna vy"
 
-#: gitk:3232
-msgid "Commits to include (arguments to git log):"
-msgstr "Incheckningar att ta med (argument till git log):"
+#: gitk:3817
+msgid "References (space separated list):"
+msgstr "Referenser (blankstegsavdelad lista):"
 
-#: gitk:3239
-msgid "Command to generate more commits to include:"
-msgstr "Kommando för att generera fler incheckningar att ta med:"
+#: gitk:3818
+msgid "Branches & tags:"
+msgstr "Grenar & taggar:"
+
+#: gitk:3819
+msgid "All refs"
+msgstr "Alla referenser"
+
+#: gitk:3820
+msgid "All (local) branches"
+msgstr "Alla (lokala) grenar"
+
+#: gitk:3821
+msgid "All tags"
+msgstr "Alla taggar"
+
+#: gitk:3822
+msgid "All remote-tracking branches"
+msgstr "Alla fjärrspårande grenar"
+
+#: gitk:3823
+msgid "Commit Info (regular expressions):"
+msgstr "Incheckningsinfo (reguljära uttryck):"
+
+#: gitk:3824
+msgid "Author:"
+msgstr "Författare:"
+
+#: gitk:3825
+msgid "Committer:"
+msgstr "Incheckare:"
+
+#: gitk:3826
+msgid "Commit Message:"
+msgstr "Incheckningsmeddelande:"
+
+#: gitk:3827
+msgid "Matches all Commit Info criteria"
+msgstr "Motsvarar alla kriterier för incheckningsinfo"
+
+#: gitk:3828
+msgid "Changes to Files:"
+msgstr "Ändringar av filer:"
+
+#: gitk:3829
+msgid "Fixed String"
+msgstr "Fast sträng"
+
+#: gitk:3830
+msgid "Regular Expression"
+msgstr "Reguljärt uttryck"
+
+#: gitk:3831
+msgid "Search string:"
+msgstr "Söksträng:"
+
+#: gitk:3832
+msgid ""
+"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+msgstr ""
+"Incheckingsdatum (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+
+#: gitk:3833
+msgid "Since:"
+msgstr "Från:"
+
+#: gitk:3834
+msgid "Until:"
+msgstr "Till:"
+
+#: gitk:3835
+msgid "Limit and/or skip a number of revisions (positive integer):"
+msgstr "Begränsa och/eller hoppa över ett antal revisioner (positivt heltal):"
+
+#: gitk:3836
+msgid "Number to show:"
+msgstr "Antal att visa:"
+
+#: gitk:3837
+msgid "Number to skip:"
+msgstr "Antal att hoppa över:"
 
-#: gitk:3246
+#: gitk:3838
+msgid "Miscellaneous options:"
+msgstr "Diverse alternativ:"
+
+#: gitk:3839
+msgid "Strictly sort by date"
+msgstr "Strikt datumsortering"
+
+#: gitk:3840
+msgid "Mark branch sides"
+msgstr "Markera sidogrenar"
+
+#: gitk:3841
+msgid "Limit to first parent"
+msgstr "Begränsa till första förälder"
+
+#: gitk:3842
+msgid "Simple history"
+msgstr "Enkel historik"
+
+#: gitk:3843
+msgid "Additional arguments to git log:"
+msgstr "Ytterligare argument till git log:"
+
+#: gitk:3844
 msgid "Enter files and directories to include, one per line:"
 msgstr "Ange filer och kataloger att ta med, en per rad:"
 
-#: gitk:3293
+#: gitk:3845
+msgid "Command to generate more commits to include:"
+msgstr "Kommando för att generera fler incheckningar att ta med:"
+
+#: gitk:3967
+msgid "Gitk: edit view"
+msgstr "Gitk: redigera vy"
+
+#: gitk:3975
+msgid "-- criteria for selecting revisions"
+msgstr " - kriterier för val av revisioner"
+
+#: gitk:3980
+msgid "View Name"
+msgstr "Namn på vy"
+
+#: gitk:4055
+msgid "Apply (F5)"
+msgstr "Använd (F5)"
+
+#: gitk:4093
 msgid "Error in commit selection arguments:"
 msgstr "Fel i argument för val av incheckningar:"
 
-#: gitk:3347 gitk:3399 gitk:3842 gitk:3856 gitk:5060 gitk:10141 gitk:10142
+#: gitk:4146 gitk:4198 gitk:4646 gitk:4660 gitk:5921 gitk:11534 gitk:11535
 msgid "None"
 msgstr "Inget"
 
-#: gitk:3790 gitk:5580 gitk:7287 gitk:7302
+#: gitk:4594 gitk:6441 gitk:8287 gitk:8302
 msgid "Date"
 msgstr "Datum"
 
-#: gitk:3790 gitk:5580
+#: gitk:4594 gitk:6441
 msgid "CDate"
 msgstr "Skapat datum"
 
-#: gitk:3939 gitk:3944
+#: gitk:4743 gitk:4748
 msgid "Descendant"
 msgstr "Avkomling"
 
-#: gitk:3940
+#: gitk:4744
 msgid "Not descendant"
 msgstr "Inte avkomling"
 
-#: gitk:3947 gitk:3952
+#: gitk:4751 gitk:4756
 msgid "Ancestor"
 msgstr "Förfader"
 
-#: gitk:3948
+#: gitk:4752
 msgid "Not ancestor"
 msgstr "Inte förfader"
 
-#: gitk:4187
+#: gitk:5042
 msgid "Local changes checked in to index but not committed"
 msgstr "Lokala ändringar sparade i indexet men inte incheckade"
 
-#: gitk:4220
+#: gitk:5078
 msgid "Local uncommitted changes, not checked in to index"
 msgstr "Lokala ändringar, ej sparade i indexet"
 
-#: gitk:5549
-msgid "Searching"
-msgstr "Söker"
+#: gitk:6759
+msgid "many"
+msgstr "många"
 
-#: gitk:6049
+#: gitk:6942
 msgid "Tags:"
 msgstr "Taggar:"
 
-#: gitk:6066 gitk:6072 gitk:7280
+#: gitk:6959 gitk:6965 gitk:8280
 msgid "Parent"
 msgstr "Förälder"
 
-#: gitk:6077
+#: gitk:6970
 msgid "Child"
 msgstr "Barn"
 
-#: gitk:6086
+#: gitk:6979
 msgid "Branch"
 msgstr "Gren"
 
-#: gitk:6089
+#: gitk:6982
 msgid "Follows"
 msgstr "Följer"
 
-#: gitk:6092
+#: gitk:6985
 msgid "Precedes"
 msgstr "Föregår"
 
-#: gitk:6378
-msgid "Error getting merge diffs:"
-msgstr "Fel vid hämtning av sammanslagningsdiff:"
+#: gitk:7522
+#, tcl-format
+msgid "Error getting diffs: %s"
+msgstr "Fel vid hämtning av diff: %s"
 
-#: gitk:7113
+#: gitk:8108
 msgid "Goto:"
 msgstr "Gå till:"
 
-#: gitk:7115
-msgid "SHA1 ID:"
-msgstr "SHA1-id:"
-
-#: gitk:7134
+#: gitk:8129
 #, tcl-format
 msgid "Short SHA1 id %s is ambiguous"
 msgstr "Förkortat SHA1-id %s är tvetydigt"
 
-#: gitk:7146
+#: gitk:8136
+#, tcl-format
+msgid "Revision %s is not known"
+msgstr "Revisionen %s är inte känd"
+
+#: gitk:8146
 #, tcl-format
 msgid "SHA1 id %s is not known"
 msgstr "SHA-id:t %s är inte känt"
 
-#: gitk:7148
+#: gitk:8148
 #, tcl-format
-msgid "Tag/Head %s is not known"
-msgstr "Tagg/huvud %s är okänt"
+msgid "Revision %s is not in the current view"
+msgstr "Revisionen %s finns inte i den nuvarande vyn"
 
-#: gitk:7290
+#: gitk:8290
 msgid "Children"
 msgstr "Barn"
 
-#: gitk:7347
+#: gitk:8348
 #, tcl-format
 msgid "Reset %s branch to here"
 msgstr "Återställ grenen %s hit"
 
-#: gitk:7349
+#: gitk:8350
 msgid "Detached head: can't reset"
 msgstr "Frånkopplad head: kan inte återställa"
 
-#: gitk:7381
+#: gitk:8459 gitk:8465
+msgid "Skipping merge commit "
+msgstr "Hoppar över sammanslagningsincheckning "
+
+#: gitk:8474 gitk:8479
+msgid "Error getting patch ID for "
+msgstr "Fel vid hämtning av patch-id för "
+
+#: gitk:8475 gitk:8480
+msgid " - stopping\n"
+msgstr " - stannar\n"
+
+#: gitk:8485 gitk:8488 gitk:8496 gitk:8510 gitk:8519
+msgid "Commit "
+msgstr "Incheckning "
+
+#: gitk:8489
+msgid ""
+" is the same patch as\n"
+"       "
+msgstr ""
+" är samma patch som\n"
+"       "
+
+#: gitk:8497
+msgid ""
+" differs from\n"
+"       "
+msgstr ""
+" skiljer sig från\n"
+"       "
+
+#: gitk:8499
+msgid ""
+"Diff of commits:\n"
+"\n"
+msgstr "Skillnad mellan incheckningar:\n"
+"\n"
+""
+
+#: gitk:8511 gitk:8520
+#, tcl-format
+msgid " has %s children - stopping\n"
+msgstr " har %s barn - stannar\n"
+
+#: gitk:8539
+#, tcl-format
+msgid "Error writing commit to file: %s"
+msgstr "Fel vid skrivning av incheckning till fil: %s"
+
+#: gitk:8545
+#, tcl-format
+msgid "Error diffing commits: %s"
+msgstr "Fel vid jämförelse av incheckningar: %s"
+
+#: gitk:8575
 msgid "Top"
 msgstr "Topp"
 
-#: gitk:7382
+#: gitk:8576
 msgid "From"
 msgstr "Från"
 
-#: gitk:7387
+#: gitk:8581
 msgid "To"
 msgstr "Till"
 
-#: gitk:7410
+#: gitk:8605
 msgid "Generate patch"
 msgstr "Generera patch"
 
-#: gitk:7412
+#: gitk:8607
 msgid "From:"
 msgstr "Från:"
 
-#: gitk:7421
+#: gitk:8616
 msgid "To:"
 msgstr "Till:"
 
-#: gitk:7430
+#: gitk:8625
 msgid "Reverse"
 msgstr "Vänd"
 
-#: gitk:7432 gitk:7607
+#: gitk:8627 gitk:8822
 msgid "Output file:"
 msgstr "Utdatafil:"
 
-#: gitk:7438
+#: gitk:8633
 msgid "Generate"
 msgstr "Generera"
 
-#: gitk:7474
+#: gitk:8671
 msgid "Error creating patch:"
 msgstr "Fel vid generering av patch:"
 
-#: gitk:7496 gitk:7595 gitk:7649
+#: gitk:8694 gitk:8810 gitk:8867
 msgid "ID:"
 msgstr "Id:"
 
-#: gitk:7505
+#: gitk:8703
 msgid "Tag name:"
 msgstr "Taggnamn:"
 
-#: gitk:7509 gitk:7659
+#: gitk:8706
+msgid "Tag message is optional"
+msgstr "Taggmeddelandet är valfritt"
+
+#: gitk:8708
+msgid "Tag message:"
+msgstr "Taggmeddelande:"
+
+#: gitk:8712 gitk:8876
 msgid "Create"
 msgstr "Skapa"
 
-#: gitk:7524
+#: gitk:8730
 msgid "No tag name specified"
 msgstr "Inget taggnamn angavs"
 
-#: gitk:7528
+#: gitk:8734
 #, tcl-format
 msgid "Tag \"%s\" already exists"
 msgstr "Taggen \"%s\" finns redan"
 
-#: gitk:7534
+#: gitk:8744
 msgid "Error creating tag:"
 msgstr "Fel vid skapande av tagg:"
 
-#: gitk:7604
+#: gitk:8819
 msgid "Command:"
 msgstr "Kommando:"
 
-#: gitk:7612
+#: gitk:8827
 msgid "Write"
 msgstr "Skriv"
 
-#: gitk:7628
+#: gitk:8845
 msgid "Error writing commit:"
 msgstr "Fel vid skrivning av incheckning:"
 
-#: gitk:7654
+#: gitk:8872
 msgid "Name:"
 msgstr "Namn:"
 
-#: gitk:7674
+#: gitk:8895
 msgid "Please specify a name for the new branch"
 msgstr "Ange ett namn för den nya grenen"
 
-#: gitk:7703
+#: gitk:8900
+#, tcl-format
+msgid "Branch '%s' already exists. Overwrite?"
+msgstr "Grenen \"%s\" finns redan. Skriva över?"
+
+#: gitk:8966
 #, tcl-format
 msgid "Commit %s is already included in branch %s -- really re-apply it?"
 msgstr ""
 "Incheckningen %s finns redan på grenen %s -- skall den verkligen appliceras "
 "på nytt?"
 
-#: gitk:7708
+#: gitk:8971
 msgid "Cherry-picking"
 msgstr "Plockar"
 
-#: gitk:7720
+#: gitk:8980
+#, tcl-format
+msgid ""
+"Cherry-pick failed because of local changes to file '%s'.\n"
+"Please commit, reset or stash your changes and try again."
+msgstr ""
+"Cherry-pick misslyckades på grund av lokala ändringar i filen \"%s\".\n"
+"Checka in, återställ eller spara undan (stash) dina ändringar och försök "
+"igen."
+
+#: gitk:8986
+msgid ""
+"Cherry-pick failed because of merge conflict.\n"
+"Do you wish to run git citool to resolve it?"
+msgstr ""
+"Cherry-pick misslyckades på grund av en sammanslagningskonflikt.\n"
+"Vill du köra git citool för att lösa den?"
+
+#: gitk:9002
 msgid "No changes committed"
 msgstr "Inga ändringar incheckade"
 
-#: gitk:7745
+#: gitk:9028
 msgid "Confirm reset"
 msgstr "Bekräfta återställning"
 
-#: gitk:7747
+#: gitk:9030
 #, tcl-format
 msgid "Reset branch %s to %s?"
 msgstr "Återställa grenen %s till %s?"
 
-#: gitk:7751
+#: gitk:9032
 msgid "Reset type:"
 msgstr "Typ av återställning:"
 
-#: gitk:7755
+#: gitk:9035
 msgid "Soft: Leave working tree and index untouched"
 msgstr "Mjuk: Rör inte utcheckning och index"
 
-#: gitk:7758
+#: gitk:9038
 msgid "Mixed: Leave working tree untouched, reset index"
 msgstr "Blandad: Rör inte utcheckning, återställ index"
 
-#: gitk:7761
+#: gitk:9041
 msgid ""
 "Hard: Reset working tree and index\n"
 "(discard ALL local changes)"
@@ -732,19 +1036,19 @@ msgstr ""
 "Hård: Återställ utcheckning och index\n"
 "(förkastar ALLA lokala ändringar)"
 
-#: gitk:7777
+#: gitk:9058
 msgid "Resetting"
 msgstr "Återställer"
 
-#: gitk:7834
+#: gitk:9118
 msgid "Checking out"
 msgstr "Checkar ut"
 
-#: gitk:7885
+#: gitk:9171
 msgid "Cannot delete the currently checked-out branch"
 msgstr "Kan inte ta bort den just nu utcheckade grenen"
 
-#: gitk:7891
+#: gitk:9177
 #, tcl-format
 msgid ""
 "The commits on branch %s aren't on any other branch.\n"
@@ -753,16 +1057,16 @@ msgstr ""
 "Incheckningarna på grenen %s existerar inte på någon annan gren.\n"
 "Vill du verkligen ta bort grenen %s?"
 
-#: gitk:7922
+#: gitk:9208
 #, tcl-format
 msgid "Tags and heads: %s"
 msgstr "Taggar och huvuden: %s"
 
-#: gitk:7936
+#: gitk:9223
 msgid "Filter"
 msgstr "Filter"
 
-#: gitk:8230
+#: gitk:9518
 msgid ""
 "Error reading commit topology information; branch and preceding/following "
 "tag information will be incomplete."
@@ -770,154 +1074,224 @@ msgstr ""
 "Fel vid läsning av information om incheckningstopologi; information om "
 "grenar och föregående/senare taggar kommer inte vara komplett."
 
-#: gitk:9216
+#: gitk:10504
 msgid "Tag"
 msgstr "Tagg"
 
-#: gitk:9216
+#: gitk:10504
 msgid "Id"
 msgstr "Id"
 
-#: gitk:9262
+#: gitk:10554
 msgid "Gitk font chooser"
 msgstr "Teckensnittsväljare för Gitk"
 
-#: gitk:9279
+#: gitk:10571
 msgid "B"
 msgstr "F"
 
-#: gitk:9282
+#: gitk:10574
 msgid "I"
 msgstr "K"
 
-#: gitk:9375
+#: gitk:10692
 msgid "Gitk preferences"
 msgstr "Inställningar för Gitk"
 
-#: gitk:9376
+#: gitk:10694
 msgid "Commit list display options"
 msgstr "Alternativ för incheckningslistvy"
 
-#: gitk:9379
+#: gitk:10697
 msgid "Maximum graph width (lines)"
 msgstr "Maximal grafbredd (rader)"
 
-#: gitk:9383
+#: gitk:10700
 #, tcl-format
 msgid "Maximum graph width (% of pane)"
 msgstr "Maximal grafbredd (% av ruta)"
 
-#: gitk:9388
+#: gitk:10703
 msgid "Show local changes"
 msgstr "Visa lokala ändringar"
 
-#: gitk:9393
+#: gitk:10706
 msgid "Auto-select SHA1"
 msgstr "Välj SHA1 automatiskt"
 
-#: gitk:9398
+#: gitk:10709
+msgid "Hide remote refs"
+msgstr "Dölj fjärr-referenser"
+
+#: gitk:10713
 msgid "Diff display options"
 msgstr "Alternativ för diffvy"
 
-#: gitk:9400
+#: gitk:10715
 msgid "Tab spacing"
 msgstr "Blanksteg för tabulatortecken"
 
-#: gitk:9404
+#: gitk:10718
 msgid "Display nearby tags"
 msgstr "Visa närliggande taggar"
 
-#: gitk:9409
+#: gitk:10721
 msgid "Limit diffs to listed paths"
 msgstr "Begränsa diff till listade sökvägar"
 
-#: gitk:9414
+#: gitk:10724
 msgid "Support per-file encodings"
-msgstr ""
+msgstr "Stöd för filspecifika teckenkodningar"
 
-#: gitk:9421
+#: gitk:10730 gitk:10819
 msgid "External diff tool"
 msgstr "Externt diff-verktyg"
 
-#: gitk:9423
+#: gitk:10731
 msgid "Choose..."
 msgstr "Välj..."
 
-#: gitk:9428
+#: gitk:10736
+msgid "General options"
+msgstr "Allmänna inställningar"
+
+#: gitk:10739
+msgid "Use themed widgets"
+msgstr "Använd tema på fönsterelement"
+
+#: gitk:10741
+msgid "(change requires restart)"
+msgstr "(ändringen kräver omstart)"
+
+#: gitk:10743
+msgid "(currently unavailable)"
+msgstr "(för närvarande inte tillgängligt)"
+
+#: gitk:10747
 msgid "Colors: press to choose"
 msgstr "Färger: tryck för att välja"
 
-#: gitk:9431
+#: gitk:10750
+msgid "Interface"
+msgstr "Gränssnitt"
+
+#: gitk:10751
+msgid "interface"
+msgstr "gränssnitt"
+
+#: gitk:10754
 msgid "Background"
 msgstr "Bakgrund"
 
-#: gitk:9435
+#: gitk:10755 gitk:10785
+msgid "background"
+msgstr "bakgrund"
+
+#: gitk:10758
 msgid "Foreground"
 msgstr "Förgrund"
 
-#: gitk:9439
+#: gitk:10759
+msgid "foreground"
+msgstr "förgrund"
+
+#: gitk:10762
 msgid "Diff: old lines"
 msgstr "Diff: gamla rader"
 
-#: gitk:9444
+#: gitk:10763
+msgid "diff old lines"
+msgstr "diff gamla rader"
+
+#: gitk:10767
 msgid "Diff: new lines"
 msgstr "Diff: nya rader"
 
-#: gitk:9449
+#: gitk:10768
+msgid "diff new lines"
+msgstr "diff nya rader"
+
+#: gitk:10772
 msgid "Diff: hunk header"
 msgstr "Diff: delhuvud"
 
-#: gitk:9455
+#: gitk:10774
+msgid "diff hunk header"
+msgstr "diff delhuvud"
+
+#: gitk:10778
+msgid "Marked line bg"
+msgstr "Markerad rad bakgrund"
+
+#: gitk:10780
+msgid "marked line background"
+msgstr "markerad rad bakgrund"
+
+#: gitk:10784
 msgid "Select bg"
 msgstr "Markerad bakgrund"
 
-#: gitk:9459
+#: gitk:10788
 msgid "Fonts: press to choose"
 msgstr "Teckensnitt: tryck för att välja"
 
-#: gitk:9461
+#: gitk:10790
 msgid "Main font"
 msgstr "Huvudteckensnitt"
 
-#: gitk:9462
+#: gitk:10791
 msgid "Diff display font"
 msgstr "Teckensnitt för diffvisning"
 
-#: gitk:9463
+#: gitk:10792
 msgid "User interface font"
 msgstr "Teckensnitt för användargränssnitt"
 
-#: gitk:9488
+#: gitk:10829
 #, tcl-format
 msgid "Gitk: choose color for %s"
 msgstr "Gitk: välj färg för %s"
 
-#: gitk:9934
-msgid ""
-"Sorry, gitk cannot run with this version of Tcl/Tk.\n"
-" Gitk requires at least Tcl/Tk 8.4."
-msgstr ""
-"Gitk kan tyvärr inte köra med denna version av Tcl/Tk.\n"
-" Gitk kräver åtminstone Tcl/Tk 8.4."
-
-#: gitk:10047
+#: gitk:11433
 msgid "Cannot find a git repository here."
 msgstr "Hittar inget gitk-arkiv här."
 
-#: gitk:10051
+#: gitk:11437
 #, tcl-format
 msgid "Cannot find the git directory \"%s\"."
 msgstr "Hittar inte git-katalogen \"%s\"."
 
-#: gitk:10098
+#: gitk:11484
 #, tcl-format
 msgid "Ambiguous argument '%s': both revision and filename"
 msgstr "Tvetydigt argument \"%s\": både revision och filnamn"
 
-#: gitk:10110
+#: gitk:11496
 msgid "Bad arguments to gitk:"
 msgstr "Felaktiga argument till gitk:"
 
-#: gitk:10170
+#: gitk:11587
 msgid "Command line"
 msgstr "Kommandorad"
+
+#~ msgid "SHA1 ID: "
+#~ msgstr "SHA1-id: "
+
+#~ msgid "- stopping\n"
+#~ msgstr "- stannar\n"
+
+#~ msgid ""
+#~ "Sorry, gitk cannot run with this version of Tcl/Tk.\n"
+#~ " Gitk requires at least Tcl/Tk 8.4."
+#~ msgstr ""
+#~ "Gitk kan tyvärr inte köra med denna version av Tcl/Tk.\n"
+#~ " Gitk kräver åtminstone Tcl/Tk 8.4."
+
+#~ msgid "Tag/Head %s is not known"
+#~ msgstr "Tagg/huvud %s är okänt"
+
+#~ msgid "/\t\tMove to next find hit, or redo find"
+#~ msgstr "/\t\tGå till nästa sökträff, eller sök på nytt"
+
+#~ msgid "Name"
+#~ msgstr "Namn"
index 18c9ce35e8fc6566663ad76dd04bd1aa70035c25..823053173c92e4097dad54945db16f42b4b5d994 100644 (file)
@@ -2,12 +2,13 @@ GIT web Interface (gitweb) Installation
 =======================================
 
 First you have to generate gitweb.cgi from gitweb.perl using
-"make gitweb/gitweb.cgi", then copy appropriate files (gitweb.cgi,
-gitweb.css, git-logo.png and git-favicon.png) to their destination.
-For example if git was (or is) installed with /usr prefix, you can do
+"make gitweb", then "make install-gitweb" appropriate files
+(gitweb.cgi, gitweb.js, gitweb.css, git-logo.png and git-favicon.png)
+to their destination. For example if git was (or is) installed with
+/usr prefix and gitwebdir is /var/www/cgi-bin, you can do
 
-       $ make prefix=/usr gitweb/gitweb.cgi  ;# as yourself
-       # cp gitweb/git* /var/www/cgi-bin/    ;# as root
+       $ make prefix=/usr gitweb                            ;# as yourself
+       # make gitwebdir=/var/www/cgi-bin install-gitweb     ;# as root
 
 Alternatively you can use autoconf generated ./configure script to
 set up path to git binaries (via config.mak.autogen), so you can write
@@ -15,8 +16,9 @@ instead
 
        $ make configure                     ;# as yourself
        $ ./configure --prefix=/usr          ;# as yourself
-       $ make gitweb/gitweb.cgi             ;# as yourself
-       # cp gitweb/git* /var/www/cgi-bin/   ;# as root
+       $ make gitweb                        ;# as yourself
+       # make gitwebdir=/var/www/cgi-bin \
+              install-gitweb                ;# as root
 
 The above example assumes that your web server is configured to run
 [executable] files in /var/www/cgi-bin/ as server scripts (as CGI
@@ -31,8 +33,7 @@ file for gitweb (in gitweb/README).
 
 - There are many configuration variables which affect building of
   gitweb.cgi; see "default configuration for gitweb" section in main
-  (top dir) Makefile, and instructions for building gitweb/gitweb.cgi
-  target.
+  (top dir) Makefile, and instructions for building gitweb target.
 
   One of the most important is where to find the git wrapper binary. Gitweb
   tries to find the git wrapper at $(bindir)/git, so you have to set $bindir
@@ -62,27 +63,33 @@ file for gitweb (in gitweb/README).
   a suggestion).
 
 - You can control where gitweb tries to find its main CSS style file,
-  its favicon and logo with the GITWEB_CSS, GITWEB_FAVICON and GITWEB_LOGO
-  build configuration variables. By default gitweb tries to find them
-  in the same directory as gitweb.cgi script.
+  its JavaScript file, its favicon and logo with the GITWEB_CSS, GITWEB_JS
+  GITWEB_FAVICON and GITWEB_LOGO build configuration variables. By default
+  gitweb tries to find them in the same directory as gitweb.cgi script.
+
+- You can optionally generate minified versions of gitweb.js and gitweb.css
+  by defining the JSMIN and CSSMIN build configuration variables. By default
+  the non-minified versions will be used. NOTE: if you enable this option,
+  substitute gitweb.min.js and gitweb.min.css for all uses of gitweb.js and
+  gitweb.css in the help files.
 
 Build example
 ~~~~~~~~~~~~~
 
-- To install gitweb to /var/www/cgi-bin/gitweb/ when git wrapper
-  is installed at /usr/local/bin/git and the repositories (projects)
-  we want to display are under /home/local/scm, you can do
+- To install gitweb to /var/www/cgi-bin/gitweb/, when git wrapper
+  is installed at /usr/local/bin/git, the repositories (projects)
+  we want to display are under /home/local/scm, and you do not use
+  minifiers, you can do
 
        make GITWEB_PROJECTROOT="/home/local/scm" \
-            GITWEB_CSS="/gitweb/gitweb.css" \
-            GITWEB_LOGO="/gitweb/git-logo.png" \
-            GITWEB_FAVICON="/gitweb/git-favicon.png" \
+            GITWEB_JS="gitweb/static/gitweb.js" \
+            GITWEB_CSS="gitweb/static/gitweb.css" \
+            GITWEB_LOGO="gitweb/static/git-logo.png" \
+            GITWEB_FAVICON="gitweb/static/git-favicon.png" \
             bindir=/usr/local/bin \
-            gitweb/gitweb.cgi
+            gitweb
 
-       cp -fv ~/git/gitweb/gitweb.{cgi,css} \
-              ~/git/gitweb/git-{favicon,logo}.png \
-            /var/www/cgi-bin/gitweb/
+       make gitwebdir=/var/www/cgi-bin/gitweb install-gitweb
 
 
 Gitweb config file
@@ -123,6 +130,15 @@ GITWEB_CONFIG file:
        $feature{'snapshot'}{'default'} = ['zip', 'tgz'];
        $feature{'snapshot'}{'override'} = 1;
 
+If you allow overriding for the snapshot feature, you can specify which
+snapshot formats are globally disabled. You can also add any command line
+options you want (such as setting the compression level). For instance,
+you can disable Zip compressed snapshots and set GZip to run at level 6 by
+adding the following lines to your $GITWEB_CONFIG:
+
+       $known_snapshot_formats{'zip'}{'disabled'} = 1;
+       $known_snapshot_formats{'tgz'}{'compressor'} = ['gzip','-6'];
+
 
 Gitweb repositories
 -------------------
diff --git a/gitweb/Makefile b/gitweb/Makefile
new file mode 100644 (file)
index 0000000..2fb7c2d
--- /dev/null
@@ -0,0 +1,160 @@
+# The default target of this Makefile is...
+all::
+
+# Define V=1 to have a more verbose compile.
+#
+# Define JSMIN to point to JavaScript minifier that functions as
+# a filter to have static/gitweb.js minified.
+#
+# Define CSSMIN to point to a CSS minifier in order to generate a minified
+# version of static/gitweb.css
+#
+
+prefix ?= $(HOME)
+bindir ?= $(prefix)/bin
+gitwebdir ?= /var/www/cgi-bin
+
+RM ?= rm -f
+INSTALL ?= install
+
+# default configuration for gitweb
+GITWEB_CONFIG = gitweb_config.perl
+GITWEB_CONFIG_SYSTEM = /etc/gitweb.conf
+GITWEB_HOME_LINK_STR = projects
+GITWEB_SITENAME =
+GITWEB_PROJECTROOT = /pub/git
+GITWEB_PROJECT_MAXDEPTH = 2007
+GITWEB_EXPORT_OK =
+GITWEB_STRICT_EXPORT =
+GITWEB_BASE_URL =
+GITWEB_LIST =
+GITWEB_HOMETEXT = indextext.html
+GITWEB_CSS = static/gitweb.css
+GITWEB_LOGO = static/git-logo.png
+GITWEB_FAVICON = static/git-favicon.png
+GITWEB_JS = static/gitweb.js
+GITWEB_SITE_HEADER =
+GITWEB_SITE_FOOTER =
+
+# include user config
+-include ../config.mak.autogen
+-include ../config.mak
+
+# determine version
+../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
+       $(QUIET_SUBDIR0)../ $(QUIET_SUBDIR1) GIT-VERSION-FILE
+
+-include ../GIT-VERSION-FILE
+
+### Build rules
+
+SHELL_PATH ?= $(SHELL)
+PERL_PATH  ?= /usr/bin/perl
+
+# Shell quote;
+bindir_SQ = $(subst ','\'',$(bindir))#'
+gitwebdir_SQ = $(subst ','\'',$(gitwebdir))#'
+gitwebstaticdir_SQ = $(subst ','\'',$(gitwebdir)/static)#'
+SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))#'
+PERL_PATH_SQ  = $(subst ','\'',$(PERL_PATH))#'
+DESTDIR_SQ    = $(subst ','\'',$(DESTDIR))#'
+
+# Quiet generation (unless V=1)
+QUIET_SUBDIR0  = +$(MAKE) -C # space to separate -C and subdir
+QUIET_SUBDIR1  =
+
+ifneq ($(findstring $(MAKEFLAGS),w),w)
+PRINT_DIR = --no-print-directory
+else # "make -w"
+NO_SUBDIR = :
+endif
+
+ifneq ($(findstring $(MAKEFLAGS),s),s)
+ifndef V
+       QUIET          = @
+       QUIET_GEN      = $(QUIET)echo '   ' GEN $@;
+       QUIET_SUBDIR0  = +@subdir=
+       QUIET_SUBDIR1  = ;$(NO_SUBDIR) echo '   ' SUBDIR $$subdir; \
+                        $(MAKE) $(PRINT_DIR) -C $$subdir
+       export V
+       export QUIET
+       export QUIET_GEN
+       export QUIET_SUBDIR0
+       export QUIET_SUBDIR1
+endif
+endif
+
+all:: gitweb.cgi
+
+GITWEB_PROGRAMS = gitweb.cgi
+
+ifdef JSMIN
+GITWEB_FILES += static/gitweb.min.js
+GITWEB_JS = static/gitweb.min.js
+all:: static/gitweb.min.js
+static/gitweb.min.js: static/gitweb.js GITWEB-BUILD-OPTIONS
+       $(QUIET_GEN)$(JSMIN) <$< >$@
+else
+GITWEB_FILES += static/gitweb.js
+endif
+
+ifdef CSSMIN
+GITWEB_FILES += static/gitweb.min.css
+GITWEB_CSS = static/gitweb.min.css
+all:: static/gitweb.min.css
+static/gitweb.min.css: static/gitweb.css GITWEB-BUILD-OPTIONS
+       $(QUIET_GEN)$(CSSMIN) <$< >$@
+else
+GITWEB_FILES += static/gitweb.css
+endif
+
+GITWEB_FILES += static/git-logo.png static/git-favicon.png
+
+GITWEB_REPLACE = \
+       -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
+       -e 's|++GIT_BINDIR++|$(bindir)|g' \
+       -e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \
+       -e 's|++GITWEB_CONFIG_SYSTEM++|$(GITWEB_CONFIG_SYSTEM)|g' \
+       -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \
+       -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \
+       -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \
+       -e 's|"++GITWEB_PROJECT_MAXDEPTH++"|$(GITWEB_PROJECT_MAXDEPTH)|g' \
+       -e 's|++GITWEB_EXPORT_OK++|$(GITWEB_EXPORT_OK)|g' \
+       -e 's|++GITWEB_STRICT_EXPORT++|$(GITWEB_STRICT_EXPORT)|g' \
+       -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \
+       -e 's|++GITWEB_LIST++|$(GITWEB_LIST)|g' \
+       -e 's|++GITWEB_HOMETEXT++|$(GITWEB_HOMETEXT)|g' \
+       -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \
+       -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \
+       -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \
+       -e 's|++GITWEB_JS++|$(GITWEB_JS)|g' \
+       -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \
+       -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g'
+
+GITWEB-BUILD-OPTIONS: FORCE
+       @rm -f $@+
+       @echo "x" '$(PERL_PATH_SQ)' $(GITWEB_REPLACE) "$(JSMIN)|$(CSSMIN)" >$@+
+       @cmp -s $@+ $@ && rm -f $@+ || mv -f $@+ $@
+
+gitweb.cgi: gitweb.perl GITWEB-BUILD-OPTIONS
+       $(QUIET_GEN)$(RM) $@ $@+ && \
+       sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
+               $(GITWEB_REPLACE) $< >$@+ && \
+       chmod +x $@+ && \
+       mv $@+ $@
+
+### Installation rules
+
+install: all
+       $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitwebdir_SQ)'
+       $(INSTALL) -m 755 $(GITWEB_PROGRAMS) '$(DESTDIR_SQ)$(gitwebdir_SQ)'
+       $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitwebstaticdir_SQ)'
+       $(INSTALL) -m 644 $(GITWEB_FILES) '$(DESTDIR_SQ)$(gitwebstaticdir_SQ)'
+
+### Cleaning rules
+
+clean:
+       $(RM) gitweb.cgi static/gitweb.min.js static/gitweb.min.css GITWEB-BUILD-OPTIONS
+
+.PHONY: all clean install .FORCE-GIT-VERSION-FILE FORCE
+
index ccda890c0ef1b2d1fb1c451d3f9a10d97817c8f6..d4811987965e5a5036b7ed71e32d93578579073a 100644 (file)
@@ -80,18 +80,26 @@ You can specify the following configuration variables when building GIT:
    Points to the location where you put gitweb.css on your web server
    (or to be more generic, the URI of gitweb stylesheet).  Relative to the
    base URI of gitweb.  Note that you can setup multiple stylesheets from
-   the gitweb config file.  [Default: gitweb.css]
+   the gitweb config file.  [Default: static/gitweb.css (or
+   static/gitweb.min.css if the CSSMIN variable is defined / CSS minifier
+   is used)]
  * GITWEB_LOGO
    Points to the location where you put git-logo.png on your web server
    (or to be more generic URI of logo, 72x27 size, displayed in top right
    corner of each gitweb page, and used as logo for Atom feed).  Relative
-   to base URI of gitweb.  [Default: git-logo.png]
+   to base URI of gitweb.  [Default: static/git-logo.png]
  * GITWEB_FAVICON
    Points to the location where you put git-favicon.png on your web server
    (or to be more generic URI of favicon, assumed to be image/png type;
    web browsers that support favicons (website icons) may display them
    in the browser's URL bar and next to site name in bookmarks).  Relative
-   to base URI of gitweb.  [Default: git-favicon.png]
+   to base URI of gitweb.  [Default: static/git-favicon.png]
+ * GITWEB_JS
+   Points to the location where you put gitweb.js on your web server
+   (or to be more generic URI of JavaScript code used by gitweb).
+   Relative to base URI of gitweb.  [Default: static/gitweb.js (or
+   static/gitweb.min.js if JSMIN build variable is defined / JavaScript
+   minifier is used)]
  * GITWEB_CONFIG
    This Perl file will be loaded using 'do' and can be used to override any
    of the options above as well as some other options -- see the "Runtime
@@ -165,6 +173,12 @@ not include variables usually directly set during build):
    Full URL and absolute URL of gitweb script;
    in earlier versions of gitweb you might have need to set those
    variables, now there should be no need to do it.
+ * $base_url
+   Base URL for relative URLs in pages generated by gitweb,
+   (e.g. $logo, $favicon, @stylesheets if they are relative URLs),
+   needed and used only for URLs with nonempty PATH_INFO via
+   <base href="$base_url">.  Usually gitweb sets its value correctly,
+   and there is no need to set this variable, e.g. to $my_uri or "/".
  * $home_link
    Target of the home link on top of all pages (the first part of view
    "breadcrumbs").  By default set to absolute URI of a page ($my_uri).
@@ -217,6 +231,11 @@ not include variables usually directly set during build):
    repositories from launching cross-site scripting (XSS) attacks.  Set this
    to true if you don't trust the content of your repositories. The default
    is false.
+ * $maxload
+   Used to set the maximum load that we will still respond to gitweb queries.
+   If server load exceed this value then return "503 Service Unavailable" error.
+   Server load is taken to be 0 if gitweb cannot determine its value.  Set it to
+   undefined value to turn it off.  The default is 300.
 
 
 Projects list file format
@@ -296,12 +315,16 @@ If you want to have one URL for both gitweb and your http://
 repositories, you can configure apache like this:
 
 <VirtualHost *:80>
-    ServerName git.example.org
-    DocumentRoot /pub/git
-    SetEnv     GITWEB_CONFIG   /etc/gitweb.conf
+    ServerName         git.example.org
+    DocumentRoot       /pub/git
+    SetEnv                     GITWEB_CONFIG   /etc/gitweb.conf
+
+    # turning on mod rewrite
     RewriteEngine on
+
     # make the front page an internal rewrite to the gitweb script
     RewriteRule ^/$  /cgi-bin/gitweb.cgi
+
     # make access for "dumb clients" work
     RewriteRule ^/(.*\.git/(?!/?(HEAD|info|objects|refs)).*)?$ /cgi-bin/gitweb.cgi%{REQUEST_URI}  [L,PT]
 </VirtualHost>
@@ -327,6 +350,63 @@ something like the following in your gitweb.conf (or gitweb_config.perl) file:
   $home_link = "/";
 
 
+Webserver configuration with multiple projects' root
+----------------------------------------------------
+
+If you want to use gitweb with several project roots you can edit your apache
+virtual host and gitweb.conf configuration files like this :
+
+virtual host configuration :
+
+<VirtualHost *:80>
+    ServerName                 git.example.org
+    DocumentRoot               /pub/git
+    SetEnv                             GITWEB_CONFIG   /etc/gitweb.conf
+
+    # turning on mod rewrite
+    RewriteEngine on
+
+    # make the front page an internal rewrite to the gitweb script
+    RewriteRule ^/$            /cgi-bin/gitweb.cgi [QSA,L,PT]
+
+    # look for a public_git folder in unix users' home
+    # http://git.example.org/~<user>/
+    RewriteRule ^/\~([^\/]+)(/|/gitweb.cgi)?$  /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT]
+
+    # http://git.example.org/+<user>/
+    #RewriteRule ^/\+([^\/]+)(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT]
+
+    # http://git.example.org/user/<user>/
+    #RewriteRule ^/user/([^\/]+)/(gitweb.cgi)?$        /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT]
+
+    # defined list of project roots
+    RewriteRule ^/scm(/|/gitweb.cgi)?$         /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/pub/scm/,L,PT]
+    RewriteRule ^/var(/|/gitweb.cgi)?$         /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/var/git/,L,PT]
+
+    # make access for "dumb clients" work
+    RewriteRule ^/(.*\.git/(?!/?(HEAD|info|objects|refs)).*)?$ /cgi-bin/gitweb.cgi%{REQUEST_URI}  [L,PT]
+</VirtualHost>
+
+gitweb.conf configuration :
+
+$projectroot = $ENV{'GITWEB_PROJECTROOT'} || "/pub/git";
+
+These configurations enable two things. First, each unix user (<user>) of the
+server will be able to browse through gitweb git repositories found in
+~/public_git/ with the following url : http://git.example.org/~<user>/
+
+If you do not want this feature on your server just remove the second rewrite rule.
+
+If you already use mod_userdir in your virtual host or you don't want to use
+the '~' as first character just comment or remove the second rewrite rule and
+uncomment one of the following according to what you want.
+
+Second, repositories found in /pub/scm/ and /var/git/ will be accesible
+through http://git.example.org/scm/ and http://git.example.org/var/.
+You can add as many project roots as you want by adding rewrite rules like the
+third and the fourth.
+
+
 PATH_INFO usage
 -----------------------
 If you enable PATH_INFO usage in gitweb by putting
@@ -377,7 +457,7 @@ named without a .git extension (e.g. /pub/git/project instead of
 
        DocumentRoot /var/www/gitweb
 
-       AliasMatch ^(/.*?)(\.git)(/.*)? /pub/git$1$3
+       AliasMatch ^(/.*?)(\.git)(/.*)?$ /pub/git$1$3
        <Directory /var/www/gitweb>
                Options ExecCGI
                AddHandler cgi-script cgi
@@ -402,6 +482,14 @@ http://git.example.com/project
 
 will provide human-friendly gitweb access.
 
+This solution is not 100% bulletproof, in the sense that if some project
+has a named ref (branch, tag) starting with 'git/', then paths such as
+
+http://git.example.com/project/command/abranch..git/abranch
+
+will fail with a 404 error.
+
+
 
 Originally written by:
   Kay Sievers <kay.sievers@vrfy.org>
diff --git a/gitweb/git-favicon.png b/gitweb/git-favicon.png
deleted file mode 100644 (file)
index de637c0..0000000
Binary files a/gitweb/git-favicon.png and /dev/null differ
diff --git a/gitweb/git-logo.png b/gitweb/git-logo.png
deleted file mode 100644 (file)
index 16ae8d5..0000000
Binary files a/gitweb/git-logo.png and /dev/null differ
diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css
deleted file mode 100644 (file)
index a01eac8..0000000
+++ /dev/null
@@ -1,526 +0,0 @@
-body {
-       font-family: sans-serif;
-       font-size: small;
-       border: solid #d9d8d1;
-       border-width: 1px;
-       margin: 10px;
-       background-color: #ffffff;
-       color: #000000;
-}
-
-a {
-       color: #0000cc;
-}
-
-a:hover, a:visited, a:active {
-       color: #880000;
-}
-
-span.cntrl {
-       border: dashed #aaaaaa;
-       border-width: 1px;
-       padding: 0px 2px 0px 2px;
-       margin:  0px 2px 0px 2px;
-}
-
-img.logo {
-       float: right;
-       border-width: 0px;
-}
-
-div.page_header {
-       height: 25px;
-       padding: 8px;
-       font-size: 150%;
-       font-weight: bold;
-       background-color: #d9d8d1;
-}
-
-div.page_header a:visited, a.header {
-       color: #0000cc;
-}
-
-div.page_header a:hover {
-       color: #880000;
-}
-
-div.page_nav {
-       padding: 8px;
-}
-
-div.page_nav a:visited {
-       color: #0000cc;
-}
-
-div.page_path {
-       padding: 8px;
-       font-weight: bold;
-       border: solid #d9d8d1;
-       border-width: 0px 0px 1px;
-}
-
-div.page_footer {
-       height: 17px;
-       padding: 4px 8px;
-       background-color: #d9d8d1;
-}
-
-div.page_footer_text {
-       float: left;
-       color: #555555;
-       font-style: italic;
-}
-
-div.page_body {
-       padding: 8px;
-       font-family: monospace;
-}
-
-div.title, a.title {
-       display: block;
-       padding: 6px 8px;
-       font-weight: bold;
-       background-color: #edece6;
-       text-decoration: none;
-       color: #000000;
-}
-
-div.readme {
-       padding: 8px;
-}
-
-a.title:hover {
-       background-color: #d9d8d1;
-}
-
-div.title_text {
-       padding: 6px 0px;
-       border: solid #d9d8d1;
-       border-width: 0px 0px 1px;
-       font-family: monospace;
-}
-
-div.log_body {
-       padding: 8px 8px 8px 150px;
-}
-
-span.age {
-       position: relative;
-       float: left;
-       width: 142px;
-       font-style: italic;
-}
-
-span.signoff {
-       color: #888888;
-}
-
-div.log_link {
-       padding: 0px 8px;
-       font-size: 70%;
-       font-family: sans-serif;
-       font-style: normal;
-       position: relative;
-       float: left;
-       width: 136px;
-}
-
-div.list_head {
-       padding: 6px 8px 4px;
-       border: solid #d9d8d1;
-       border-width: 1px 0px 0px;
-       font-style: italic;
-}
-
-div.author_date {
-       padding: 8px;
-       border: solid #d9d8d1;
-       border-width: 0px 0px 1px 0px;
-       font-style: italic;
-}
-
-a.list {
-       text-decoration: none;
-       color: #000000;
-}
-
-a.subject, a.name {
-       font-weight: bold;
-}
-
-table.tags a.subject {
-       font-weight: normal;
-}
-
-a.list:hover {
-       text-decoration: underline;
-       color: #880000;
-}
-
-a.text {
-       text-decoration: none;
-       color: #0000cc;
-}
-
-a.text:visited {
-       text-decoration: none;
-       color: #880000;
-}
-
-a.text:hover {
-       text-decoration: underline;
-       color: #880000;
-}
-
-table {
-       padding: 8px 4px;
-       border-spacing: 0;
-}
-
-table.diff_tree {
-       font-family: monospace;
-}
-
-table.combined.diff_tree th {
-       text-align: center;
-}
-
-table.combined.diff_tree td {
-       padding-right: 24px;
-}
-
-table.combined.diff_tree th.link,
-table.combined.diff_tree td.link {
-       padding: 0px 2px;
-}
-
-table.combined.diff_tree td.nochange a {
-       color: #6666ff;
-}
-
-table.combined.diff_tree td.nochange a:hover,
-table.combined.diff_tree td.nochange a:visited {
-       color: #d06666;
-}
-
-table.blame {
-       border-collapse: collapse;
-}
-
-table.blame td {
-       padding: 0px 5px;
-       font-size: 100%;
-       vertical-align: top;
-}
-
-th {
-       padding: 2px 5px;
-       font-size: 100%;
-       text-align: left;
-}
-
-tr.light:hover {
-       background-color: #edece6;
-}
-
-tr.dark {
-       background-color: #f6f6f0;
-}
-
-tr.dark2 {
-       background-color: #f6f6f0;
-}
-
-tr.dark:hover {
-       background-color: #edece6;
-}
-
-td {
-       padding: 2px 5px;
-       font-size: 100%;
-       vertical-align: top;
-}
-
-td.link, td.selflink {
-       padding: 2px 5px;
-       font-family: sans-serif;
-       font-size: 70%;
-}
-
-td.selflink {
-       padding-right: 0px;
-}
-
-td.sha1 {
-       font-family: monospace;
-}
-
-td.error {
-       color: red;
-       background-color: yellow;
-}
-
-td.current_head {
-       text-decoration: underline;
-}
-
-table.diff_tree span.file_status.new {
-       color: #008000;
-}
-
-table.diff_tree span.file_status.deleted {
-       color: #c00000;
-}
-
-table.diff_tree span.file_status.moved,
-table.diff_tree span.file_status.mode_chnge {
-       color: #777777;
-}
-
-table.diff_tree span.file_status.copied {
-  color: #70a070;
-}
-
-/* noage: "No commits" */
-table.project_list td.noage {
-       color: #808080;
-       font-style: italic;
-}
-
-/* age2: 60*60*24*2 <= age */
-table.project_list td.age2, table.blame td.age2 {
-       font-style: italic;
-}
-
-/* age1: 60*60*2 <= age < 60*60*24*2 */
-table.project_list td.age1 {
-       color: #009900;
-       font-style: italic;
-}
-
-table.blame td.age1 {
-       color: #009900;
-       background: transparent;
-}
-
-/* age0: age < 60*60*2 */
-table.project_list td.age0 {
-       color: #009900;
-       font-style: italic;
-       font-weight: bold;
-}
-
-table.blame td.age0 {
-       color: #009900;
-       background: transparent;
-       font-weight: bold;
-}
-
-td.pre, div.pre, div.diff {
-       font-family: monospace;
-       font-size: 12px;
-       white-space: pre;
-}
-
-td.mode {
-       font-family: monospace;
-}
-
-/* styling of diffs (patchsets): commitdiff and blobdiff views */
-div.diff.header,
-div.diff.extended_header {
-       white-space: normal;
-}
-
-div.diff.header {
-       font-weight: bold;
-
-       background-color: #edece6;
-
-       margin-top: 4px;
-       padding: 4px 0px 2px 0px;
-       border: solid #d9d8d1;
-       border-width: 1px 0px 1px 0px;
-}
-
-div.diff.header a.path {
-       text-decoration: underline;
-}
-
-div.diff.extended_header,
-div.diff.extended_header a.path,
-div.diff.extended_header a.hash {
-       color: #777777;
-}
-
-div.diff.extended_header .info {
-       color: #b0b0b0;
-}
-
-div.diff.extended_header {
-       background-color: #f6f5ee;
-       padding: 2px 0px 2px 0px;
-}
-
-div.diff a.list,
-div.diff a.path,
-div.diff a.hash {
-       text-decoration: none;
-}
-
-div.diff a.list:hover,
-div.diff a.path:hover,
-div.diff a.hash:hover {
-       text-decoration: underline;
-}
-
-div.diff.to_file a.path,
-div.diff.to_file {
-       color: #007000;
-}
-
-div.diff.add {
-       color: #008800;
-}
-
-div.diff.from_file a.path,
-div.diff.from_file {
-       color: #aa0000;
-}
-
-div.diff.rem {
-       color: #cc0000;
-}
-
-div.diff.chunk_header a,
-div.diff.chunk_header {
-       color: #990099;
-}
-
-div.diff.chunk_header {
-       border: dotted #ffe0ff;
-       border-width: 1px 0px 0px 0px;
-       margin-top: 2px;
-}
-
-div.diff.chunk_header span.chunk_info {
-       background-color: #ffeeff;
-}
-
-div.diff.chunk_header span.section {
-       color: #aa22aa;
-}
-
-div.diff.incomplete {
-       color: #cccccc;
-}
-
-div.diff.nodifferences {
-       font-weight: bold;
-       color: #600000;
-}
-
-div.index_include {
-       border: solid #d9d8d1;
-       border-width: 0px 0px 1px;
-       padding: 12px 8px;
-}
-
-div.search {
-       font-size: 100%;
-       font-weight: normal;
-       margin: 4px 8px;
-       float: right;
-       top: 56px;
-       right: 12px
-}
-
-p.projsearch {
-       text-align: center;
-}
-
-td.linenr {
-       text-align: right;
-}
-
-a.linenr {
-       color: #999999;
-       text-decoration: none
-}
-
-a.rss_logo {
-       float: right;
-       padding: 3px 0px;
-       width: 35px;
-       line-height: 10px;
-       border: 1px solid;
-       border-color: #fcc7a5 #7d3302 #3e1a01 #ff954e;
-       color: #ffffff;
-       background-color: #ff6600;
-       font-weight: bold;
-       font-family: sans-serif;
-       font-size: 70%;
-       text-align: center;
-       text-decoration: none;
-}
-
-a.rss_logo:hover {
-       background-color: #ee5500;
-}
-
-a.rss_logo.generic {
-       background-color: #ff8800;
-}
-
-a.rss_logo.generic:hover {
-       background-color: #ee7700;
-}
-
-span.refs span {
-       padding: 0px 4px;
-       font-size: 70%;
-       font-weight: normal;
-       border: 1px solid;
-       background-color: #ffaaff;
-       border-color: #ffccff #ff00ee #ff00ee #ffccff;
-}
-
-span.refs span a {
-       text-decoration: none;
-       color: inherit;
-}
-
-span.refs span a:hover {
-       text-decoration: underline;
-}
-
-span.refs span.indirect {
-       font-style: italic;
-}
-
-span.refs span.ref {
-       background-color: #aaaaff;
-       border-color: #ccccff #0033cc #0033cc #ccccff;
-}
-
-span.refs span.tag {
-       background-color: #ffffaa;
-       border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
-}
-
-span.refs span.head {
-       background-color: #aaffaa;
-       border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
-}
-
-span.atnight {
-       color: #cc0000;
-}
-
-span.match {
-       color: #e00000;
-}
-
-div.binary {
-       font-style: italic;
-}
index 3f99361ed03b8d5202cb17e894c65678364fc1ed..a85e2f63197649bff4842ea2970448205618c104 100755 (executable)
@@ -11,45 +11,59 @@ use strict;
 use warnings;
 use CGI qw(:standard :escapeHTML -nosticky);
 use CGI::Util qw(unescape);
-use CGI::Carp qw(fatalsToBrowser);
+use CGI::Carp qw(fatalsToBrowser set_message);
 use Encode;
 use Fcntl ':mode';
 use File::Find qw();
 use File::Basename qw(basename);
 binmode STDOUT, ':utf8';
 
+our $t0;
+if (eval { require Time::HiRes; 1; }) {
+       $t0 = [Time::HiRes::gettimeofday()];
+}
+our $number_of_git_cmds = 0;
+
 BEGIN {
        CGI->compile() if $ENV{'MOD_PERL'};
 }
 
-our $cgi = new CGI;
 our $version = "++GIT_VERSION++";
-our $my_url = $cgi->url();
-our $my_uri = $cgi->url(-absolute => 1);
 
-# Base URL for relative URLs in gitweb ($logo, $favicon, ...),
-# needed and used only for URLs with nonempty PATH_INFO
-our $base_url = $my_url;
+our ($my_url, $my_uri, $base_url, $path_info, $home_link);
+sub evaluate_uri {
+       our $cgi;
 
-# When the script is used as DirectoryIndex, the URL does not contain the name
-# of the script file itself, and $cgi->url() fails to strip PATH_INFO, so we
-# have to do it ourselves. We make $path_info global because it's also used
-# later on.
-#
-# Another issue with the script being the DirectoryIndex is that the resulting
-# $my_url data is not the full script URL: this is good, because we want
-# generated links to keep implying the script name if it wasn't explicitly
-# indicated in the URL we're handling, but it means that $my_url cannot be used
-# as base URL.
-# Therefore, if we needed to strip PATH_INFO, then we know that we have
-# to build the base URL ourselves:
-our $path_info = $ENV{"PATH_INFO"};
-if ($path_info) {
-       if ($my_url =~ s,\Q$path_info\E$,, &&
-           $my_uri =~ s,\Q$path_info\E$,, &&
-           defined $ENV{'SCRIPT_NAME'}) {
-               $base_url = $cgi->url(-base => 1) . $ENV{'SCRIPT_NAME'};
+       our $my_url = $cgi->url();
+       our $my_uri = $cgi->url(-absolute => 1);
+
+       # Base URL for relative URLs in gitweb ($logo, $favicon, ...),
+       # needed and used only for URLs with nonempty PATH_INFO
+       our $base_url = $my_url;
+
+       # When the script is used as DirectoryIndex, the URL does not contain the name
+       # of the script file itself, and $cgi->url() fails to strip PATH_INFO, so we
+       # have to do it ourselves. We make $path_info global because it's also used
+       # later on.
+       #
+       # Another issue with the script being the DirectoryIndex is that the resulting
+       # $my_url data is not the full script URL: this is good, because we want
+       # generated links to keep implying the script name if it wasn't explicitly
+       # indicated in the URL we're handling, but it means that $my_url cannot be used
+       # as base URL.
+       # Therefore, if we needed to strip PATH_INFO, then we know that we have
+       # to build the base URL ourselves:
+       our $path_info = $ENV{"PATH_INFO"};
+       if ($path_info) {
+               if ($my_url =~ s,\Q$path_info\E$,, &&
+                   $my_uri =~ s,\Q$path_info\E$,, &&
+                   defined $ENV{'SCRIPT_NAME'}) {
+                       $base_url = $cgi->url(-base => 1) . $ENV{'SCRIPT_NAME'};
+               }
        }
+
+       # target of the home link on top of all pages
+       our $home_link = $my_uri || "/";
 }
 
 # core git executable to use
@@ -64,9 +78,6 @@ our $projectroot = "++GITWEB_PROJECTROOT++";
 # the number is relative to the projectroot
 our $project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++";
 
-# target of the home link on top of all pages
-our $home_link = $my_uri || "/";
-
 # string of the home link on top of all pages
 our $home_link_str = "++GITWEB_HOME_LINK_STR++";
 
@@ -90,11 +101,13 @@ our $stylesheet = undef;
 our $logo = "++GITWEB_LOGO++";
 # URI of GIT favicon, assumed to be image/png type
 our $favicon = "++GITWEB_FAVICON++";
+# URI of gitweb.js (JavaScript code for gitweb)
+our $javascript = "++GITWEB_JS++";
 
 # URI and label (title) of GIT logo link
 #our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/";
 #our $logo_label = "git documentation";
-our $logo_url = "http://git.or.cz/";
+our $logo_url = "http://git-scm.com/";
 our $logo_label = "git homepage";
 
 # source of projects list
@@ -160,7 +173,8 @@ our %known_snapshot_formats = (
        #       'suffix' => filename suffix,
        #       'format' => --format for git-archive,
        #       'compressor' => [compressor command and arguments]
-       #                       (array reference, optional)}
+       #                       (array reference, optional)
+       #       'disabled' => boolean (optional)}
        #
        'tgz' => {
                'display' => 'tar.gz',
@@ -176,6 +190,14 @@ our %known_snapshot_formats = (
                'format' => 'tar',
                'compressor' => ['bzip2']},
 
+       'txz' => {
+               'display' => 'tar.xz',
+               'type' => 'application/x-xz',
+               'suffix' => '.tar.xz',
+               'format' => 'tar',
+               'compressor' => ['xz'],
+               'disabled' => 1},
+
        'zip' => {
                'display' => 'zip',
                'type' => 'application/x-zip',
@@ -188,6 +210,7 @@ our %known_snapshot_formats = (
 our %known_snapshot_format_aliases = (
        'gzip'  => 'tgz',
        'bzip2' => 'tbz2',
+       'xz'    => 'txz',
 
        # backward compatibility: legacy gitweb config support
        'x-gzip' => undef, 'gz' => undef,
@@ -195,6 +218,43 @@ our %known_snapshot_format_aliases = (
        'x-zip' => undef, '' => undef,
 );
 
+# Pixel sizes for icons and avatars. If the default font sizes or lineheights
+# are changed, it may be appropriate to change these values too via
+# $GITWEB_CONFIG.
+our %avatar_size = (
+       'default' => 16,
+       'double'  => 32
+);
+
+# Used to set the maximum load that we will still respond to gitweb queries.
+# If server load exceed this value then return "503 server busy" error.
+# If gitweb cannot determined server load, it is taken to be 0.
+# Leave it undefined (or set to 'undef') to turn off load checking.
+our $maxload = 300;
+
+# configuration for 'highlight' (http://www.andre-simon.de/)
+# match by basename
+our %highlight_basename = (
+       #'Program' => 'py',
+       #'Library' => 'py',
+       'SConstruct' => 'py', # SCons equivalent of Makefile
+       'Makefile' => 'make',
+);
+# match by extension
+our %highlight_ext = (
+       # main extensions, defining name of syntax;
+       # see files in /usr/share/highlight/langDefs/ directory
+       map { $_ => $_ }
+               qw(py c cpp rb java css php sh pl js tex bib xml awk bat ini spec tcl),
+       # alternate extensions, see /etc/highlight/filetypes.conf
+       'h' => 'c',
+       map { $_ => 'cpp' } qw(cxx c++ cc),
+       map { $_ => 'php' } qw(php3 php4),
+       map { $_ => 'pl'  } qw(perl pm), # perhaps also 'cgi'
+       'mak' => 'make',
+       map { $_ => 'xml' } qw(xhtml html htm),
+);
+
 # You define site-wide feature defaults here; override them with
 # $GITWEB_CONFIG as necessary.
 our %feature = (
@@ -208,7 +268,7 @@ our %feature = (
        # return value of feature-sub indicates if to enable specified feature
        #
        # if there is no 'sub' key (no feature-sub), then feature cannot be
-       # overriden
+       # overridden
        #
        # use gitweb_get_feature(<feature>) to retrieve the <feature> value
        # (an array) or gitweb_check_feature(<feature>) to check if <feature>
@@ -279,6 +339,19 @@ our %feature = (
                'override' => 0,
                'default' => [1]},
 
+       # Enable showing size of blobs in a 'tree' view, in a separate
+       # column, similar to what 'ls -l' does.  This cost a bit of IO.
+
+       # To disable system wide have in $GITWEB_CONFIG
+       # $feature{'show-sizes'}{'default'} = [0];
+       # To have project specific config enable override in $GITWEB_CONFIG
+       # $feature{'show-sizes'}{'override'} = 1;
+       # and in project config gitweb.showsizes = 0|1;
+       'show-sizes' => {
+               'sub' => sub { feature_bool('showsizes', @_) },
+               'override' => 0,
+               'default' => [1]},
+
        # Make gitweb use an alternative format of the URLs which can be
        # more readable and natural-looking: project name is embedded
        # directly in the path and the query string contains other
@@ -365,6 +438,54 @@ our %feature = (
                'sub' => \&feature_patches,
                'override' => 0,
                'default' => [16]},
+
+       # Avatar support. When this feature is enabled, views such as
+       # shortlog or commit will display an avatar associated with
+       # the email of the committer(s) and/or author(s).
+
+       # Currently available providers are gravatar and picon.
+       # If an unknown provider is specified, the feature is disabled.
+
+       # Gravatar depends on Digest::MD5.
+       # Picon currently relies on the indiana.edu database.
+
+       # To enable system wide have in $GITWEB_CONFIG
+       # $feature{'avatar'}{'default'} = ['<provider>'];
+       # where <provider> is either gravatar or picon.
+       # To have project specific config enable override in $GITWEB_CONFIG
+       # $feature{'avatar'}{'override'} = 1;
+       # and in project config gitweb.avatar = <provider>;
+       'avatar' => {
+               'sub' => \&feature_avatar,
+               'override' => 0,
+               'default' => ['']},
+
+       # Enable displaying how much time and how many git commands
+       # it took to generate and display page.  Disabled by default.
+       # Project specific override is not supported.
+       'timed' => {
+               'override' => 0,
+               'default' => [0]},
+
+       # Enable turning some links into links to actions which require
+       # JavaScript to run (like 'blame_incremental').  Not enabled by
+       # default.  Project specific override is currently not supported.
+       'javascript-actions' => {
+               'override' => 0,
+               'default' => [0]},
+
+       # Syntax highlighting support. This is based on Daniel Svensson's
+       # and Sham Chukoury's work in gitweb-xmms2.git.
+       # It requires the 'highlight' program present in $PATH,
+       # and therefore is disabled by default.
+
+       # To enable system wide have in $GITWEB_CONFIG
+       # $feature{'highlight'}{'default'} = [1];
+
+       'highlight' => {
+               'sub' => sub { feature_bool('highlight', @_) },
+               'override' => 0,
+               'default' => [0]},
 );
 
 sub gitweb_get_feature {
@@ -374,9 +495,13 @@ sub gitweb_get_feature {
                $feature{$name}{'sub'},
                $feature{$name}{'override'},
                @{$feature{$name}{'default'}});
-       if (!$override) { return @defaults; }
+       # project specific override is possible only if we have project
+       our $git_dir; # global variable, declared later
+       if (!$override || !defined $git_dir) {
+               return @defaults;
+       }
        if (!defined $sub) {
-               warn "feature $name is not overrideable";
+               warn "feature $name is not overridable";
                return @defaults;
        }
        return $sub->(@defaults);
@@ -433,6 +558,12 @@ sub feature_patches {
        return ($_[0]);
 }
 
+sub feature_avatar {
+       my @val = (git_get_project_config('avatar'));
+
+       return @val ? @val : @_;
+}
+
 # checking HEAD file with -e is fragile if the repository was
 # initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed
 # and then pruned.
@@ -458,22 +589,59 @@ sub filter_snapshot_fmts {
        @fmts = map {
                exists $known_snapshot_format_aliases{$_} ?
                       $known_snapshot_format_aliases{$_} : $_} @fmts;
-       @fmts = grep(exists $known_snapshot_formats{$_}, @fmts);
-
+       @fmts = grep {
+               exists $known_snapshot_formats{$_} &&
+               !$known_snapshot_formats{$_}{'disabled'}} @fmts;
 }
 
-our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
-if (-e $GITWEB_CONFIG) {
-       do $GITWEB_CONFIG;
-} else {
+our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM);
+sub evaluate_gitweb_config {
+       our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
        our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++";
-       do $GITWEB_CONFIG_SYSTEM if -e $GITWEB_CONFIG_SYSTEM;
+       # die if there are errors parsing config file
+       if (-e $GITWEB_CONFIG) {
+               do $GITWEB_CONFIG;
+               die $@ if $@;
+       } elsif (-e $GITWEB_CONFIG_SYSTEM) {
+               do $GITWEB_CONFIG_SYSTEM;
+               die $@ if $@;
+       }
+}
+
+# Get loadavg of system, to compare against $maxload.
+# Currently it requires '/proc/loadavg' present to get loadavg;
+# if it is not present it returns 0, which means no load checking.
+sub get_loadavg {
+       if( -e '/proc/loadavg' ){
+               open my $fd, '<', '/proc/loadavg'
+                       or return 0;
+               my @load = split(/\s+/, scalar <$fd>);
+               close $fd;
+
+               # The first three columns measure CPU and IO utilization of the last one,
+               # five, and 10 minute periods.  The fourth column shows the number of
+               # currently running processes and the total number of processes in the m/n
+               # format.  The last column displays the last process ID used.
+               return $load[0] || 0;
+       }
+       # additional checks for load average should go here for things that don't export
+       # /proc/loadavg
+
+       return 0;
 }
 
 # version of the core git binary
-our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
+our $git_version;
+sub evaluate_git_version {
+       our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
+       $number_of_git_cmds++;
+}
 
-$projects_list ||= $projectroot;
+sub check_loadavg {
+       if (defined $maxload && get_loadavg() > $maxload) {
+               die_error(503, "The load average on the server is too high");
+       }
+}
 
 # ======================================================================
 # input validation and dispatch
@@ -509,12 +677,16 @@ our @cgi_param_mapping = (
        snapshot_format => "sf",
        extra_options => "opt",
        search_use_regexp => "sr",
+       # this must be last entry (for manipulation from JavaScript)
+       javascript => "js"
 );
 our %cgi_param_mapping = @cgi_param_mapping;
 
 # we will also need to know the possible actions, for validation
 our %actions = (
        "blame" => \&git_blame,
+       "blame_incremental" => \&git_blame_incremental,
+       "blame_data" => \&git_blame_data,
        "blobdiff" => \&git_blobdiff,
        "blobdiff_plain" => \&git_blobdiff_plain,
        "blob" => \&git_blob,
@@ -555,11 +727,15 @@ our %allowed_options = (
 # should be single values, but opt can be an array. We should probably
 # build an array of parameters that can be multi-valued, but since for the time
 # being it's only this one, we just single it out
-while (my ($name, $symbol) = each %cgi_param_mapping) {
-       if ($symbol eq 'opt') {
-               $input_params{$name} = [ $cgi->param($symbol) ];
-       } else {
-               $input_params{$name} = $cgi->param($symbol);
+sub evaluate_query_params {
+       our $cgi;
+
+       while (my ($name, $symbol) = each %cgi_param_mapping) {
+               if ($symbol eq 'opt') {
+                       $input_params{$name} = [ $cgi->param($symbol) ];
+               } else {
+                       $input_params{$name} = $cgi->param($symbol);
+               }
        }
 }
 
@@ -690,9 +866,10 @@ sub evaluate_path_info {
                # format key itself, with a prepended dot
                while (my ($fmt, $opt) = each %known_snapshot_formats) {
                        my $hash = $refname;
-                       my $sfx;
-                       $hash =~ s/(\Q$opt->{'suffix'}\E|\Q.$fmt\E)$//;
-                       next unless $sfx = $1;
+                       unless ($hash =~ s/(\Q$opt->{'suffix'}\E|\Q.$fmt\E)$//) {
+                               next;
+                       }
+                       my $sfx = $1;
                        # a valid suffix was found, so set the snapshot format
                        # and reset the hash parameter
                        $input_params{'snapshot_format'} = $fmt;
@@ -705,140 +882,278 @@ sub evaluate_path_info {
                }
        }
 }
-evaluate_path_info();
 
-our $action = $input_params{'action'};
-if (defined $action) {
-       if (!validate_action($action)) {
-               die_error(400, "Invalid action parameter");
+our ($action, $project, $file_name, $file_parent, $hash, $hash_parent, $hash_base,
+     $hash_parent_base, @extra_options, $page, $searchtype, $search_use_regexp,
+     $searchtext, $search_regexp);
+sub evaluate_and_validate_params {
+       our $action = $input_params{'action'};
+       if (defined $action) {
+               if (!validate_action($action)) {
+                       die_error(400, "Invalid action parameter");
+               }
        }
-}
 
-# parameters which are pathnames
-our $project = $input_params{'project'};
-if (defined $project) {
-       if (!validate_project($project)) {
-               undef $project;
-               die_error(404, "No such project");
+       # parameters which are pathnames
+       our $project = $input_params{'project'};
+       if (defined $project) {
+               if (!validate_project($project)) {
+                       undef $project;
+                       die_error(404, "No such project");
+               }
        }
-}
 
-our $file_name = $input_params{'file_name'};
-if (defined $file_name) {
-       if (!validate_pathname($file_name)) {
-               die_error(400, "Invalid file parameter");
+       our $file_name = $input_params{'file_name'};
+       if (defined $file_name) {
+               if (!validate_pathname($file_name)) {
+                       die_error(400, "Invalid file parameter");
+               }
        }
-}
 
-our $file_parent = $input_params{'file_parent'};
-if (defined $file_parent) {
-       if (!validate_pathname($file_parent)) {
-               die_error(400, "Invalid file parent parameter");
+       our $file_parent = $input_params{'file_parent'};
+       if (defined $file_parent) {
+               if (!validate_pathname($file_parent)) {
+                       die_error(400, "Invalid file parent parameter");
+               }
        }
-}
 
-# parameters which are refnames
-our $hash = $input_params{'hash'};
-if (defined $hash) {
-       if (!validate_refname($hash)) {
-               die_error(400, "Invalid hash parameter");
+       # parameters which are refnames
+       our $hash = $input_params{'hash'};
+       if (defined $hash) {
+               if (!validate_refname($hash)) {
+                       die_error(400, "Invalid hash parameter");
+               }
        }
-}
 
-our $hash_parent = $input_params{'hash_parent'};
-if (defined $hash_parent) {
-       if (!validate_refname($hash_parent)) {
-               die_error(400, "Invalid hash parent parameter");
+       our $hash_parent = $input_params{'hash_parent'};
+       if (defined $hash_parent) {
+               if (!validate_refname($hash_parent)) {
+                       die_error(400, "Invalid hash parent parameter");
+               }
        }
-}
 
-our $hash_base = $input_params{'hash_base'};
-if (defined $hash_base) {
-       if (!validate_refname($hash_base)) {
-               die_error(400, "Invalid hash base parameter");
+       our $hash_base = $input_params{'hash_base'};
+       if (defined $hash_base) {
+               if (!validate_refname($hash_base)) {
+                       die_error(400, "Invalid hash base parameter");
+               }
        }
-}
 
-our @extra_options = @{$input_params{'extra_options'}};
-# @extra_options is always defined, since it can only be (currently) set from
-# CGI, and $cgi->param() returns the empty array in array context if the param
-# is not set
-foreach my $opt (@extra_options) {
-       if (not exists $allowed_options{$opt}) {
-               die_error(400, "Invalid option parameter");
-       }
-       if (not grep(/^$action$/, @{$allowed_options{$opt}})) {
-               die_error(400, "Invalid option parameter for this action");
+       our @extra_options = @{$input_params{'extra_options'}};
+       # @extra_options is always defined, since it can only be (currently) set from
+       # CGI, and $cgi->param() returns the empty array in array context if the param
+       # is not set
+       foreach my $opt (@extra_options) {
+               if (not exists $allowed_options{$opt}) {
+                       die_error(400, "Invalid option parameter");
+               }
+               if (not grep(/^$action$/, @{$allowed_options{$opt}})) {
+                       die_error(400, "Invalid option parameter for this action");
+               }
        }
-}
 
-our $hash_parent_base = $input_params{'hash_parent_base'};
-if (defined $hash_parent_base) {
-       if (!validate_refname($hash_parent_base)) {
-               die_error(400, "Invalid hash parent base parameter");
+       our $hash_parent_base = $input_params{'hash_parent_base'};
+       if (defined $hash_parent_base) {
+               if (!validate_refname($hash_parent_base)) {
+                       die_error(400, "Invalid hash parent base parameter");
+               }
        }
-}
 
-# other parameters
-our $page = $input_params{'page'};
-if (defined $page) {
-       if ($page =~ m/[^0-9]/) {
-               die_error(400, "Invalid page parameter");
+       # other parameters
+       our $page = $input_params{'page'};
+       if (defined $page) {
+               if ($page =~ m/[^0-9]/) {
+                       die_error(400, "Invalid page parameter");
+               }
        }
-}
 
-our $searchtype = $input_params{'searchtype'};
-if (defined $searchtype) {
-       if ($searchtype =~ m/[^a-z]/) {
-               die_error(400, "Invalid searchtype parameter");
+       our $searchtype = $input_params{'searchtype'};
+       if (defined $searchtype) {
+               if ($searchtype =~ m/[^a-z]/) {
+                       die_error(400, "Invalid searchtype parameter");
+               }
        }
-}
 
-our $search_use_regexp = $input_params{'search_use_regexp'};
+       our $search_use_regexp = $input_params{'search_use_regexp'};
 
-our $searchtext = $input_params{'searchtext'};
-our $search_regexp;
-if (defined $searchtext) {
-       if (length($searchtext) < 2) {
-               die_error(403, "At least two characters are required for search parameter");
+       our $searchtext = $input_params{'searchtext'};
+       our $search_regexp;
+       if (defined $searchtext) {
+               if (length($searchtext) < 2) {
+                       die_error(403, "At least two characters are required for search parameter");
+               }
+               $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext;
        }
-       $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext;
 }
 
 # path to the current git repository
 our $git_dir;
-$git_dir = "$projectroot/$project" if $project;
+sub evaluate_git_dir {
+       our $git_dir = "$projectroot/$project" if $project;
+}
+
+our (@snapshot_fmts, $git_avatar);
+sub configure_gitweb_features {
+       # list of supported snapshot formats
+       our @snapshot_fmts = gitweb_get_feature('snapshot');
+       @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
+
+       # check that the avatar feature is set to a known provider name,
+       # and for each provider check if the dependencies are satisfied.
+       # if the provider name is invalid or the dependencies are not met,
+       # reset $git_avatar to the empty string.
+       our ($git_avatar) = gitweb_get_feature('avatar');
+       if ($git_avatar eq 'gravatar') {
+               $git_avatar = '' unless (eval { require Digest::MD5; 1; });
+       } elsif ($git_avatar eq 'picon') {
+               # no dependencies
+       } else {
+               $git_avatar = '';
+       }
+}
+
+# custom error handler: 'die <message>' is Internal Server Error
+sub handle_errors_html {
+       my $msg = shift; # it is already HTML escaped
 
-# list of supported snapshot formats
-our @snapshot_fmts = gitweb_get_feature('snapshot');
-@snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
+       # to avoid infinite loop where error occurs in die_error,
+       # change handler to default handler, disabling handle_errors_html
+       set_message("Error occured when inside die_error:\n$msg");
+
+       # you cannot jump out of die_error when called as error handler;
+       # the subroutine set via CGI::Carp::set_message is called _after_
+       # HTTP headers are already written, so it cannot write them itself
+       die_error(undef, undef, $msg, -error_handler => 1, -no_http_header => 1);
+}
+set_message(\&handle_errors_html);
 
 # dispatch
-if (!defined $action) {
-       if (defined $hash) {
-               $action = git_get_type($hash);
-       } elsif (defined $hash_base && defined $file_name) {
-               $action = git_get_type("$hash_base:$file_name");
-       } elsif (defined $project) {
-               $action = 'summary';
-       } else {
-               $action = 'project_list';
+sub dispatch {
+       if (!defined $action) {
+               if (defined $hash) {
+                       $action = git_get_type($hash);
+               } elsif (defined $hash_base && defined $file_name) {
+                       $action = git_get_type("$hash_base:$file_name");
+               } elsif (defined $project) {
+                       $action = 'summary';
+               } else {
+                       $action = 'project_list';
+               }
        }
+       if (!defined($actions{$action})) {
+               die_error(400, "Unknown action");
+       }
+       if ($action !~ m/^(?:opml|project_list|project_index)$/ &&
+           !$project) {
+               die_error(400, "Project needed");
+       }
+       $actions{$action}->();
+}
+
+sub reset_timer {
+       our $t0 = [Time::HiRes::gettimeofday()]
+               if defined $t0;
+       our $number_of_git_cmds = 0;
 }
-if (!defined($actions{$action})) {
-       die_error(400, "Unknown action");
+
+sub run_request {
+       reset_timer();
+
+       evaluate_uri();
+       evaluate_gitweb_config();
+       check_loadavg();
+
+       # $projectroot and $projects_list might be set in gitweb config file
+       $projects_list ||= $projectroot;
+
+       evaluate_query_params();
+       evaluate_path_info();
+       evaluate_and_validate_params();
+       evaluate_git_dir();
+
+       configure_gitweb_features();
+
+       dispatch();
+}
+
+our $is_last_request = sub { 1 };
+our ($pre_dispatch_hook, $post_dispatch_hook, $pre_listen_hook);
+our $CGI = 'CGI';
+our $cgi;
+sub configure_as_fcgi {
+       require CGI::Fast;
+       our $CGI = 'CGI::Fast';
+
+       my $request_number = 0;
+       # let each child service 100 requests
+       our $is_last_request = sub { ++$request_number > 100 };
+}
+sub evaluate_argv {
+       my $script_name = $ENV{'SCRIPT_NAME'} || $ENV{'SCRIPT_FILENAME'} || __FILE__;
+       configure_as_fcgi()
+               if $script_name =~ /\.fcgi$/;
+
+       return unless (@ARGV);
+
+       require Getopt::Long;
+       Getopt::Long::GetOptions(
+               'fastcgi|fcgi|f' => \&configure_as_fcgi,
+               'nproc|n=i' => sub {
+                       my ($arg, $val) = @_;
+                       return unless eval { require FCGI::ProcManager; 1; };
+                       my $proc_manager = FCGI::ProcManager->new({
+                               n_processes => $val,
+                       });
+                       our $pre_listen_hook    = sub { $proc_manager->pm_manage()        };
+                       our $pre_dispatch_hook  = sub { $proc_manager->pm_pre_dispatch()  };
+                       our $post_dispatch_hook = sub { $proc_manager->pm_post_dispatch() };
+               },
+       );
+}
+
+sub run {
+       evaluate_argv();
+       evaluate_git_version();
+
+       $pre_listen_hook->()
+               if $pre_listen_hook;
+
+ REQUEST:
+       while ($cgi = $CGI->new()) {
+               $pre_dispatch_hook->()
+                       if $pre_dispatch_hook;
+
+               run_request();
+
+               $post_dispatch_hook->()
+                       if $post_dispatch_hook;
+
+               last REQUEST if ($is_last_request->());
+       }
+
+ DONE_GITWEB:
+       1;
 }
-if ($action !~ m/^(opml|project_list|project_index)$/ &&
-    !$project) {
-       die_error(400, "Project needed");
+
+run();
+
+if (defined caller) {
+       # wrapped in a subroutine processing requests,
+       # e.g. mod_perl with ModPerl::Registry, or PSGI with Plack::App::WrapCGI
+       return;
+} else {
+       # pure CGI script, serving single request
+       exit;
 }
-$actions{$action}->();
-exit;
 
 ## ======================================================================
 ## action links
 
-sub href (%) {
+# possible values of extra options
+# -full => 0|1      - use absolute/full URL ($my_uri/$my_url as base)
+# -replay => 1      - start from a current view (replay with modifications)
+# -path_info => 0|1 - don't use/use path_info URL (if possible)
+sub href {
        my %params = @_;
        # default is to use -absolute url() i.e. $my_uri
        my $href = $params{-full} ? $my_url : $my_uri;
@@ -854,7 +1169,8 @@ sub href (%) {
        }
 
        my $use_pathinfo = gitweb_check_feature('pathinfo');
-       if ($use_pathinfo and defined $params{'project'}) {
+       if (defined $params{'project'} &&
+           (exists $params{-path_info} ? $params{-path_info} : $use_pathinfo)) {
                # try to put as many parameters as possible in PATH_INFO:
                #   - project name
                #   - action
@@ -891,10 +1207,13 @@ sub href (%) {
                        if (defined $params{'hash_parent_base'}) {
                                $href .= esc_url($params{'hash_parent_base'});
                                # skip the file_parent if it's the same as the file_name
-                               delete $params{'file_parent'} if $params{'file_parent'} eq $params{'file_name'};
-                               if (defined $params{'file_parent'} && $params{'file_parent'} !~ /\.\./) {
-                                       $href .= ":/".esc_url($params{'file_parent'});
-                                       delete $params{'file_parent'};
+                               if (defined $params{'file_parent'}) {
+                                       if (defined $params{'file_name'} && $params{'file_parent'} eq $params{'file_name'}) {
+                                               delete $params{'file_parent'};
+                                       } elsif ($params{'file_parent'} !~ /\.\./) {
+                                               $href .= ":/".esc_url($params{'file_parent'});
+                                               delete $params{'file_parent'};
+                                       }
                                }
                                $href .= "..";
                                delete $params{'hash_parent'};
@@ -1008,6 +1327,7 @@ sub validate_refname {
 # in utf-8 thanks to "binmode STDOUT, ':utf8'" at beginning
 sub to_utf8 {
        my $str = shift;
+       return undef unless defined $str;
        if (utf8::valid($str)) {
                utf8::decode($str);
                return $str;
@@ -1020,26 +1340,28 @@ sub to_utf8 {
 # correct, but quoted slashes look too horrible in bookmarks
 sub esc_param {
        my $str = shift;
-       $str =~ s/([^A-Za-z0-9\-_.~()\/:@])/sprintf("%%%02X", ord($1))/eg;
-       $str =~ s/\+/%2B/g;
+       return undef unless defined $str;
+       $str =~ s/([^A-Za-z0-9\-_.~()\/:@ ]+)/CGI::escape($1)/eg;
        $str =~ s/ /\+/g;
        return $str;
 }
 
-# quote unsafe chars in whole URL, so some charactrs cannot be quoted
+# quote unsafe chars in whole URL, so some characters cannot be quoted
 sub esc_url {
        my $str = shift;
-       $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg;
-       $str =~ s/\+/%2B/g;
+       return undef unless defined $str;
+       $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&= ]+)/CGI::escape($1)/eg;
        $str =~ s/ /\+/g;
        return $str;
 }
 
 # replace invalid utf8 character with SUBSTITUTION sequence
-sub esc_html ($;%) {
+sub esc_html {
        my $str = shift;
        my %opts = @_;
 
+       return undef unless defined $str;
+
        $str = to_utf8($str);
        $str = $cgi->escapeHTML($str);
        if ($opts{'-nbsp'}) {
@@ -1054,6 +1376,8 @@ sub esc_path {
        my $str = shift;
        my %opts = @_;
 
+       return undef unless defined $str;
+
        $str = to_utf8($str);
        $str = $cgi->escapeHTML($str);
        if ($opts{'-nbsp'}) {
@@ -1196,7 +1520,6 @@ sub chop_str {
                $str =~ m/^(.*?)($begre)$/;
                my ($lead, $body) = ($1, $2);
                if (length($lead) > 4) {
-                       $body =~ s/^[^;]*;// if ($lead =~ m/&[^;]*$/);
                        $lead = " ...";
                }
                return "$lead$body";
@@ -1207,8 +1530,6 @@ sub chop_str {
                $str =~ m/^(.*?)($begre)$/;
                my ($mid, $right) = ($1, $2);
                if (length($mid) > 5) {
-                       $left  =~ s/&[^;]*$//;
-                       $right =~ s/^[^;]*;// if ($mid =~ m/&[^;]*$/);
                        $mid = " ... ";
                }
                return "$left$mid$right";
@@ -1218,7 +1539,6 @@ sub chop_str {
                my $body = $1;
                my $tail = $2;
                if (length($tail) > 4) {
-                       $body =~ s/&[^;]*$//;
                        $tail = "... ";
                }
                return "$body$tail";
@@ -1235,7 +1555,7 @@ sub chop_and_escape_str {
        if ($chopped eq $str) {
                return esc_html($chopped);
        } else {
-               $str =~ s/([[:cntrl:]])/?/g;
+               $str =~ s/[[:cntrl:]]/?/g;
                return $cgi->span({-title=>$str}, esc_html($chopped));
        }
 }
@@ -1296,7 +1616,7 @@ use constant {
 };
 
 # submodule/subproject, a commit object reference
-sub S_ISGITLINK($) {
+sub S_ISGITLINK {
        my $mode = shift;
 
        return (($mode & S_IFMT) == S_IFGITLINK)
@@ -1458,15 +1778,117 @@ sub format_subject_html {
        $extra = '' unless defined($extra);
 
        if (length($short) < length($long)) {
+               $long =~ s/[[:cntrl:]]/?/g;
                return $cgi->a({-href => $href, -class => "list subject",
                                -title => to_utf8($long)},
-                      esc_html($short) . $extra);
+                      esc_html($short)) . $extra;
        } else {
                return $cgi->a({-href => $href, -class => "list subject"},
-                      esc_html($long)  . $extra);
+                      esc_html($long)) . $extra;
+       }
+}
+
+# Rather than recomputing the url for an email multiple times, we cache it
+# after the first hit. This gives a visible benefit in views where the avatar
+# for the same email is used repeatedly (e.g. shortlog).
+# The cache is shared by all avatar engines (currently gravatar only), which
+# are free to use it as preferred. Since only one avatar engine is used for any
+# given page, there's no risk for cache conflicts.
+our %avatar_cache = ();
+
+# Compute the picon url for a given email, by using the picon search service over at
+# http://www.cs.indiana.edu/picons/search.html
+sub picon_url {
+       my $email = lc shift;
+       if (!$avatar_cache{$email}) {
+               my ($user, $domain) = split('@', $email);
+               $avatar_cache{$email} =
+                       "http://www.cs.indiana.edu/cgi-pub/kinzler/piconsearch.cgi/" .
+                       "$domain/$user/" .
+                       "users+domains+unknown/up/single";
+       }
+       return $avatar_cache{$email};
+}
+
+# Compute the gravatar url for a given email, if it's not in the cache already.
+# Gravatar stores only the part of the URL before the size, since that's the
+# one computationally more expensive. This also allows reuse of the cache for
+# different sizes (for this particular engine).
+sub gravatar_url {
+       my $email = lc shift;
+       my $size = shift;
+       $avatar_cache{$email} ||=
+               "http://www.gravatar.com/avatar/" .
+                       Digest::MD5::md5_hex($email) . "?s=";
+       return $avatar_cache{$email} . $size;
+}
+
+# Insert an avatar for the given $email at the given $size if the feature
+# is enabled.
+sub git_get_avatar {
+       my ($email, %opts) = @_;
+       my $pre_white  = ($opts{-pad_before} ? "&nbsp;" : "");
+       my $post_white = ($opts{-pad_after}  ? "&nbsp;" : "");
+       $opts{-size} ||= 'default';
+       my $size = $avatar_size{$opts{-size}} || $avatar_size{'default'};
+       my $url = "";
+       if ($git_avatar eq 'gravatar') {
+               $url = gravatar_url($email, $size);
+       } elsif ($git_avatar eq 'picon') {
+               $url = picon_url($email);
+       }
+       # Other providers can be added by extending the if chain, defining $url
+       # as needed. If no variant puts something in $url, we assume avatars
+       # are completely disabled/unavailable.
+       if ($url) {
+               return $pre_white .
+                      "<img width=\"$size\" " .
+                           "class=\"avatar\" " .
+                           "src=\"$url\" " .
+                           "alt=\"\" " .
+                      "/>" . $post_white;
+       } else {
+               return "";
        }
 }
 
+sub format_search_author {
+       my ($author, $searchtype, $displaytext) = @_;
+       my $have_search = gitweb_check_feature('search');
+
+       if ($have_search) {
+               my $performed = "";
+               if ($searchtype eq 'author') {
+                       $performed = "authored";
+               } elsif ($searchtype eq 'committer') {
+                       $performed = "committed";
+               }
+
+               return $cgi->a({-href => href(action=>"search", hash=>$hash,
+                               searchtext=>$author,
+                               searchtype=>$searchtype), class=>"list",
+                               title=>"Search for commits $performed by $author"},
+                               $displaytext);
+
+       } else {
+               return $displaytext;
+       }
+}
+
+# format the author name of the given commit with the given tag
+# the author name is chopped and escaped according to the other
+# optional parameters (see chop_str).
+sub format_author_html {
+       my $tag = shift;
+       my $co = shift;
+       my $author = chop_and_escape_str($co->{'author_name'}, @_);
+       return "<$tag class=\"author\">" .
+              format_search_author($co->{'author_name'}, "author",
+                      git_get_avatar($co->{'author_email'}, -pad_after => 1) .
+                      $author) .
+              "</$tag>";
+}
+
 # format git diff header line, i.e. "diff --(git|combined|cc) ..."
 sub format_git_diff_header_line {
        my $line = shift;
@@ -1829,6 +2251,7 @@ sub get_feed_info {
 
 # returns path to the core git executable and the --git-dir parameter as list
 sub git_cmd {
+       $number_of_git_cmds++;
        return $GIT, '--git-dir='.$git_dir;
 }
 
@@ -1838,21 +2261,32 @@ sub git_cmd {
 # Try to avoid using this function wherever possible.
 sub quote_command {
        return join(' ',
-                   map( { my $a = $_; $a =~ s/(['!])/'\\$1'/g; "'$a'" } @_ ));
+               map { my $a = $_; $a =~ s/(['!])/'\\$1'/g; "'$a'" } @_ );
 }
 
 # get HEAD ref of given project as hash
 sub git_get_head_hash {
-       my $project = shift;
+       return git_get_full_hash(shift, 'HEAD');
+}
+
+sub git_get_full_hash {
+       return git_get_hash(@_);
+}
+
+sub git_get_short_hash {
+       return git_get_hash(@_, '--short=7');
+}
+
+sub git_get_hash {
+       my ($project, $hash, @options) = @_;
        my $o_git_dir = $git_dir;
        my $retval = undef;
        $git_dir = "$projectroot/$project";
-       if (open my $fd, "-|", git_cmd(), "rev-parse", "--verify", "HEAD") {
-               my $head = <$fd>;
+       if (open my $fd, '-|', git_cmd(), 'rev-parse',
+           '--verify', '-q', @options, $hash) {
+               $retval = <$fd>;
+               chomp $retval if defined $retval;
                close $fd;
-               if (defined $head && $head =~ /^([0-9a-fA-F]{40})$/) {
-                       $retval = $1;
-               }
        }
        if (defined $o_git_dir) {
                $git_dir = $o_git_dir;
@@ -1958,6 +2392,8 @@ sub config_to_multi {
 sub git_get_project_config {
        my ($key, $type) = @_;
 
+       return unless defined $git_dir;
+
        # key sanity check
        return unless ($key);
        $key =~ s/^gitweb\.//;
@@ -2050,7 +2486,7 @@ sub git_get_project_description {
        my $path = shift;
 
        $git_dir = "$projectroot/$path";
-       open my $fd, "$git_dir/description"
+       open my $fd, '<', "$git_dir/description"
                or return git_get_project_config('description');
        my $descr = <$fd>;
        close $fd;
@@ -2065,18 +2501,17 @@ sub git_get_project_ctags {
        my $ctags = {};
 
        $git_dir = "$projectroot/$path";
-       unless (opendir D, "$git_dir/ctags") {
-               return $ctags;
-       }
-       foreach (grep { -f $_ } map { "$git_dir/ctags/$_" } readdir(D)) {
-               open CT, $_ or next;
-               my $val = <CT>;
+       opendir my $dh, "$git_dir/ctags"
+               or return $ctags;
+       foreach (grep { -f $_ } map { "$git_dir/ctags/$_" } readdir($dh)) {
+               open my $ct, '<', $_ or next;
+               my $val = <$ct>;
                chomp $val;
-               close CT;
+               close $ct;
                my $ctag = $_; $ctag =~ s#.*/##;
                $ctags->{$ctag} = $val;
        }
-       closedir D;
+       closedir $dh;
        $ctags;
 }
 
@@ -2129,7 +2564,7 @@ sub git_get_project_url_list {
        my $path = shift;
 
        $git_dir = "$projectroot/$path";
-       open my $fd, "$git_dir/cloneurl"
+       open my $fd, '<', "$git_dir/cloneurl"
                or return wantarray ?
                @{ config_to_multi(git_get_project_config('url')) } :
                   config_to_multi(git_get_project_config('url'));
@@ -2161,6 +2596,9 @@ sub git_get_projects_list {
                        follow_skip => 2, # ignore duplicates
                        dangling_symlinks => 0, # ignore dangling symlinks, silently
                        wanted => sub {
+                               # global variables
+                               our $project_maxdepth;
+                               our $projectroot;
                                # skip project-list toplevel, if we get it.
                                return if (m!^[/.]$!);
                                # only directories can be git repositories
@@ -2187,7 +2625,7 @@ sub git_get_projects_list {
                # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
                # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
                my %paths;
-               open my ($fd), $projects_list or return;
+               open my $fd, '<', $projects_list or return;
        PROJECT:
                while (my $line = <$fd>) {
                        chomp $line;
@@ -2250,7 +2688,7 @@ sub git_get_project_list_from_file {
        # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
        # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
        if (-f $projects_list) {
-               open (my $fd , $projects_list);
+               open(my $fd, '<', $projects_list);
                while (my $line = <$fd>) {
                        chomp $line;
                        my ($pr, $ow) = split ' ', $line;
@@ -2398,8 +2836,14 @@ sub parse_tag {
                        $tag{'name'} = $1;
                } elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) {
                        $tag{'author'} = $1;
-                       $tag{'epoch'} = $2;
-                       $tag{'tz'} = $3;
+                       $tag{'author_epoch'} = $2;
+                       $tag{'author_tz'} = $3;
+                       if ($tag{'author'} =~ m/^([^<]+) <([^>]*)>/) {
+                               $tag{'author_name'}  = $1;
+                               $tag{'author_email'} = $2;
+                       } else {
+                               $tag{'author_name'} = $tag{'author'};
+                       }
                } elsif ($line =~ m/--BEGIN/) {
                        push @comment, $line;
                        last;
@@ -2439,7 +2883,7 @@ sub parse_commit_text {
                } elsif ((!defined $withparents) && ($line =~ m/^parent ([0-9a-fA-F]{40})$/)) {
                        push @parents, $1;
                } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
-                       $co{'author'} = $1;
+                       $co{'author'} = to_utf8($1);
                        $co{'author_epoch'} = $2;
                        $co{'author_tz'} = $3;
                        if ($co{'author'} =~ m/^([^<]+) <([^>]*)>/) {
@@ -2449,10 +2893,9 @@ sub parse_commit_text {
                                $co{'author_name'} = $co{'author'};
                        }
                } elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) {
-                       $co{'committer'} = $1;
+                       $co{'committer'} = to_utf8($1);
                        $co{'committer_epoch'} = $2;
                        $co{'committer_tz'} = $3;
-                       $co{'committer_name'} = $co{'committer'};
                        if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) {
                                $co{'committer_name'}  = $1;
                                $co{'committer_email'} = $2;
@@ -2615,21 +3058,36 @@ sub parsed_difftree_line {
 }
 
 # parse line of git-ls-tree output
-sub parse_ls_tree_line ($;%) {
+sub parse_ls_tree_line {
        my $line = shift;
        my %opts = @_;
        my %res;
 
-       #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa  panic.c'
-       $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s;
+       if ($opts{'-l'}) {
+               #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa   16717  panic.c'
+               $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40}) +(-|[0-9]+)\t(.+)$/s;
 
-       $res{'mode'} = $1;
-       $res{'type'} = $2;
-       $res{'hash'} = $3;
-       if ($opts{'-z'}) {
-               $res{'name'} = $4;
+               $res{'mode'} = $1;
+               $res{'type'} = $2;
+               $res{'hash'} = $3;
+               $res{'size'} = $4;
+               if ($opts{'-z'}) {
+                       $res{'name'} = $5;
+               } else {
+                       $res{'name'} = unquote($5);
+               }
        } else {
-               $res{'name'} = unquote($4);
+               #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa  panic.c'
+               $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s;
+
+               $res{'mode'} = $1;
+               $res{'type'} = $2;
+               $res{'hash'} = $3;
+               if ($opts{'-z'}) {
+                       $res{'name'} = $4;
+               } else {
+                       $res{'name'} = unquote($4);
+               }
        }
 
        return wantarray ? %res : \%res;
@@ -2804,18 +3262,18 @@ sub mimetype_guess_file {
        -r $mimemap or return undef;
 
        my %mimemap;
-       open(MIME, $mimemap) or return undef;
-       while (<MIME>) {
+       open(my $mh, '<', $mimemap) or return undef;
+       while (<$mh>) {
                next if m/^#/; # skip comments
-               my ($mime, $exts) = split(/\t+/);
+               my ($mimetype, $exts) = split(/\t+/);
                if (defined $exts) {
                        my @exts = split(/\s+/, $exts);
                        foreach my $ext (@exts) {
-                               $mimemap{$ext} = $mime;
+                               $mimemap{$ext} = $mimetype;
                        }
                }
        }
-       close(MIME);
+       close($mh);
 
        $filename =~ /\.([^.]*)$/;
        return $mimemap{$1};
@@ -2876,26 +3334,64 @@ sub blob_contenttype {
        return $type;
 }
 
+# guess file syntax for syntax highlighting; return undef if no highlighting
+# the name of syntax can (in the future) depend on syntax highlighter used
+sub guess_file_syntax {
+       my ($highlight, $mimetype, $file_name) = @_;
+       return undef unless ($highlight && defined $file_name);
+       my $basename = basename($file_name, '.in');
+       return $highlight_basename{$basename}
+               if exists $highlight_basename{$basename};
+
+       $basename =~ /\.([^.]*)$/;
+       my $ext = $1 or return undef;
+       return $highlight_ext{$ext}
+               if exists $highlight_ext{$ext};
+
+       return undef;
+}
+
+# run highlighter and return FD of its output,
+# or return original FD if no highlighting
+sub run_highlighter {
+       my ($fd, $highlight, $syntax) = @_;
+       return $fd unless ($highlight && defined $syntax);
+
+       close $fd
+               or die_error(404, "Reading blob failed");
+       open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ".
+                 "highlight --xhtml --fragment --syntax $syntax |"
+               or die_error(500, "Couldn't open file or run syntax highlighter");
+       return $fd;
+}
+
 ## ======================================================================
 ## functions printing HTML: header, footer, error page
 
+sub get_page_title {
+       my $title = to_utf8($site_name);
+
+       return $title unless (defined $project);
+       $title .= " - " . to_utf8($project);
+
+       return $title unless (defined $action);
+       $title .= "/$action"; # $action is US-ASCII (7bit ASCII)
+
+       return $title unless (defined $file_name);
+       $title .= " - " . esc_path($file_name);
+       if ($action eq "tree" && $file_name !~ m|/$|) {
+               $title .= "/";
+       }
+
+       return $title;
+}
+
 sub git_header_html {
        my $status = shift || "200 OK";
        my $expires = shift;
+       my %opts = @_;
 
-       my $title = "$site_name";
-       if (defined $project) {
-               $title .= " - " . to_utf8($project);
-               if (defined $action) {
-                       $title .= "/$action";
-                       if (defined $file_name) {
-                               $title .= " - " . esc_path($file_name);
-                               if ($action eq "tree" && $file_name !~ m|/$|) {
-                                       $title .= "/";
-                               }
-                       }
-               }
-       }
+       my $title = get_page_title();
        my $content_type;
        # require explicit support from the UA if we are to send the page as
        # 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
@@ -2909,7 +3405,8 @@ sub git_header_html {
                $content_type = 'text/html';
        }
        print $cgi->header(-type=>$content_type, -charset => 'utf-8',
-                          -status=> $status, -expires => $expires);
+                          -status=> $status, -expires => $expires)
+               unless ($opts{'-no_http_header'});
        my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : '';
        print <<EOF;
 <?xml version="1.0" encoding="utf-8"?>
@@ -2987,7 +3484,7 @@ EOF
        print "</head>\n" .
              "<body>\n";
 
-       if (-f $site_header) {
+       if (defined $site_header && -f $site_header) {
                insert_file($site_header);
        }
 
@@ -3074,15 +3571,42 @@ sub git_footer_html {
        }
        print "</div>\n"; # class="page_footer"
 
-       if (-f $site_footer) {
+       if (defined $t0 && gitweb_check_feature('timed')) {
+               print "<div id=\"generating_info\">\n";
+               print 'This page took '.
+                     '<span id="generating_time" class="time_span">'.
+                     Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
+                     ' seconds </span>'.
+                     ' and '.
+                     '<span id="generating_cmd">'.
+                     $number_of_git_cmds.
+                     '</span> git commands '.
+                     " to generate.\n";
+               print "</div>\n"; # class="page_footer"
+       }
+
+       if (defined $site_footer && -f $site_footer) {
                insert_file($site_footer);
        }
 
+       print qq!<script type="text/javascript" src="$javascript"></script>\n!;
+       if (defined $action &&
+           $action eq 'blame_incremental') {
+               print qq!<script type="text/javascript">\n!.
+                     qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!.
+                     qq!           "!. href() .qq!");\n!.
+                     qq!</script>\n!;
+       } elsif (gitweb_check_feature('javascript-actions')) {
+               print qq!<script type="text/javascript">\n!.
+                     qq!window.onload = fixLinks;\n!.
+                     qq!</script>\n!;
+       }
+
        print "</body>\n" .
              "</html>";
 }
 
-# die_error(<http_status_code>, <error_message>)
+# die_error(<http_status_code>, <error_message>[, <detailed_html_description>])
 # Example: die_error(404, 'Hash not found')
 # By convention, use the following status codes (as defined in RFC 2616):
 # 400: Invalid or missing CGI parameters, or
@@ -3093,24 +3617,37 @@ sub git_footer_html {
 # 500: The server isn't configured properly, or
 #      an internal error occurred (e.g. failed assertions caused by bugs), or
 #      an unknown error occurred (e.g. the git binary died unexpectedly).
+# 503: The server is currently unavailable (because it is overloaded,
+#      or down for maintenance).  Generally, this is a temporary state.
 sub die_error {
        my $status = shift || 500;
-       my $error = shift || "Internal server error";
+       my $error = esc_html(shift) || "Internal Server Error";
+       my $extra = shift;
+       my %opts = @_;
 
-       my %http_responses = (400 => '400 Bad Request',
-                             403 => '403 Forbidden',
-                             404 => '404 Not Found',
-                             500 => '500 Internal Server Error');
-       git_header_html($http_responses{$status});
+       my %http_responses = (
+               400 => '400 Bad Request',
+               403 => '403 Forbidden',
+               404 => '404 Not Found',
+               500 => '500 Internal Server Error',
+               503 => '503 Service Unavailable',
+       );
+       git_header_html($http_responses{$status}, undef, %opts);
        print <<EOF;
 <div class="page_body">
 <br /><br />
 $status - $error
 <br />
-</div>
 EOF
+       if (defined $extra) {
+               print "<hr />\n" .
+                     "$extra\n";
+       }
+       print "</div>\n";
+
        git_footer_html();
-       exit;
+       goto DONE_GITWEB
+               unless ($opts{'-error_handler'});
 }
 
 ## ----------------------------------------------------------------------
@@ -3167,22 +3704,18 @@ sub git_print_page_nav {
 }
 
 sub format_paging_nav {
-       my ($action, $hash, $head, $page, $has_next_link) = @_;
+       my ($action, $page, $has_next_link) = @_;
        my $paging_nav;
 
 
-       if ($hash ne $head || $page) {
-               $paging_nav .= $cgi->a({-href => href(action=>$action)}, "HEAD");
-       } else {
-               $paging_nav .= "HEAD";
-       }
-
        if ($page > 0) {
-               $paging_nav .= " &sdot; " .
+               $paging_nav .=
+                       $cgi->a({-href => href(-replay=>1, page=>undef)}, "first") .
+                       " &sdot; " .
                        $cgi->a({-href => href(-replay=>1, page=>$page-1),
                                 -accesskey => "p", -title => "Alt-p"}, "prev");
        } else {
-               $paging_nav .= " &sdot; prev";
+               $paging_nav .= "first &sdot; prev";
        }
 
        if ($has_next_link) {
@@ -3213,22 +3746,66 @@ sub git_print_header_div {
              "\n</div>\n";
 }
 
-#sub git_print_authorship (\%) {
+sub print_local_time {
+       print format_local_time(@_);
+}
+
+sub format_local_time {
+       my $localtime = '';
+       my %date = @_;
+       if ($date{'hour_local'} < 6) {
+               $localtime .= sprintf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
+                       $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
+       } else {
+               $localtime .= sprintf(" (%02d:%02d %s)",
+                       $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
+       }
+
+       return $localtime;
+}
+
+# Outputs the author name and date in long form
 sub git_print_authorship {
        my $co = shift;
+       my %opts = @_;
+       my $tag = $opts{-tag} || 'div';
+       my $author = $co->{'author_name'};
 
        my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'});
-       print "<div class=\"author_date\">" .
-             esc_html($co->{'author_name'}) .
+       print "<$tag class=\"author_date\">" .
+             format_search_author($author, "author", esc_html($author)) .
              " [$ad{'rfc2822'}";
-       if ($ad{'hour_local'} < 6) {
-               printf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
-                      $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
-       } else {
-               printf(" (%02d:%02d %s)",
-                      $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
+       print_local_time(%ad) if ($opts{-localtime});
+       print "]" . git_get_avatar($co->{'author_email'}, -pad_before => 1)
+                 . "</$tag>\n";
+}
+
+# Outputs table rows containing the full author or committer information,
+# in the format expected for 'commit' view (& similar).
+# Parameters are a commit hash reference, followed by the list of people
+# to output information for. If the list is empty it defaults to both
+# author and committer.
+sub git_print_authorship_rows {
+       my $co = shift;
+       # too bad we can't use @people = @_ || ('author', 'committer')
+       my @people = @_;
+       @people = ('author', 'committer') unless @people;
+       foreach my $who (@people) {
+               my %wd = parse_date($co->{"${who}_epoch"}, $co->{"${who}_tz"});
+               print "<tr><td>$who</td><td>" .
+                     format_search_author($co->{"${who}_name"}, $who,
+                              esc_html($co->{"${who}_name"})) . " " .
+                     format_search_author($co->{"${who}_email"}, $who,
+                              esc_html("<" . $co->{"${who}_email"} . ">")) .
+                     "</td><td rowspan=\"2\">" .
+                     git_get_avatar($co->{"${who}_email"}, -size => 'double') .
+                     "</td></tr>\n" .
+                     "<tr>" .
+                     "<td></td><td> $wd{'rfc2822'}";
+               print_local_time(%wd);
+               print "</td>" .
+                     "</tr>\n";
        }
-       print "]</div>\n";
 }
 
 sub git_print_page_path {
@@ -3269,8 +3846,7 @@ sub git_print_page_path {
        print "<br/></div>\n";
 }
 
-# sub git_print_log (\@;%) {
-sub git_print_log ($;%) {
+sub git_print_log {
        my $log = shift;
        my %opts = @_;
 
@@ -3328,7 +3904,7 @@ sub git_get_link_target {
        open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
                or return;
        {
-               local $/;
+               local $/ = undef;
                $link_target = <$fd>;
        }
        close $fd
@@ -3341,10 +3917,7 @@ sub git_get_link_target {
 # return target of link relative to top directory (top tree);
 # return undef if it is not possible (including absolute links).
 sub normalize_link_target {
-       my ($link_target, $basedir, $hash_base) = @_;
-
-       # we can normalize symlink target only if $hash_base is provided
-       return unless $hash_base;
+       my ($link_target, $basedir) = @_;
 
        # absolute symlinks (beginning with '/') cannot be normalized
        return if (substr($link_target, 0, 1) eq '/');
@@ -3392,6 +3965,9 @@ sub git_print_tree_entry {
        # and link is the action links of the entry.
 
        print "<td class=\"mode\">" . mode_str($t->{'mode'}) . "</td>\n";
+       if (exists $t->{'size'}) {
+               print "<td class=\"size\">$t->{'size'}</td>\n";
+       }
        if ($t->{'type'} eq "blob") {
                print "<td class=\"list\">" .
                        $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
@@ -3400,7 +3976,7 @@ sub git_print_tree_entry {
                if (S_ISLNK(oct $t->{'mode'})) {
                        my $link_target = git_get_link_target($t->{'hash'});
                        if ($link_target) {
-                               my $norm_target = normalize_link_target($link_target, $basedir, $hash_base);
+                               my $norm_target = normalize_link_target($link_target, $basedir);
                                if (defined $norm_target) {
                                        print " -> " .
                                              $cgi->a({-href => href(action=>"object", hash_base=>$hash_base,
@@ -3437,12 +4013,14 @@ sub git_print_tree_entry {
        } elsif ($t->{'type'} eq "tree") {
                print "<td class=\"list\">";
                print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
-                                            file_name=>"$basedir$t->{'name'}", %base_key)},
+                                            file_name=>"$basedir$t->{'name'}",
+                                            %base_key)},
                              esc_path($t->{'name'}));
                print "</td>\n";
                print "<td class=\"link\">";
                print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
-                                            file_name=>"$basedir$t->{'name'}", %base_key)},
+                                            file_name=>"$basedir$t->{'name'}",
+                                            %base_key)},
                              "tree");
                if (defined $hash_base) {
                        print " | " .
@@ -3934,8 +4512,8 @@ sub git_patchset_body {
                print "</div>\n"; # class="patch"
        }
 
-       # for compact combined (--cc) format, with chunk and patch simpliciaction
-       # patchset might be empty, but there might be unprocessed raw lines
+       # for compact combined (--cc) format, with chunk and patch simplification
+       # the patchset might be empty, but there might be unprocessed raw lines
        for (++$patch_idx if $patch_number > 0;
             $patch_idx < @$difftree;
             ++$patch_idx) {
@@ -3993,7 +4571,7 @@ sub fill_project_list_info {
                            ($pname !~ /\/$/) &&
                            (-d "$projectroot/$pname")) {
                                $pr->{'forks'} = "-d $projectroot/$pname";
-                       }       else {
+                       } else {
                                $pr->{'forks'} = 0;
                        }
                }
@@ -4007,17 +4585,24 @@ sub fill_project_list_info {
 # print 'sort by' <th> element, generating 'sort by $name' replay link
 # if that order is not selected
 sub print_sort_th {
+       print format_sort_th(@_);
+}
+
+sub format_sort_th {
        my ($name, $order, $header) = @_;
+       my $sort_th = "";
        $header ||= ucfirst($name);
 
        if ($order eq $name) {
-               print "<th>$header</th>\n";
+               $sort_th .= "<th>$header</th>\n";
        } else {
-               print "<th>" .
-                     $cgi->a({-href => href(-replay=>1, order=>$name),
-                              -class => "header"}, $header) .
-                     "</th>\n";
+               $sort_th .= "<th>" .
+                           $cgi->a({-href => href(-replay=>1, order=>$name),
+                                    -class => "header"}, $header) .
+                           "</th>\n";
        }
+
+       return $sort_th;
 }
 
 sub git_project_list_body {
@@ -4127,6 +4712,46 @@ sub git_project_list_body {
        print "</table>\n";
 }
 
+sub git_log_body {
+       # uses global variable $project
+       my ($commitlist, $from, $to, $refs, $extra) = @_;
+
+       $from = 0 unless defined $from;
+       $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
+
+       for (my $i = 0; $i <= $to; $i++) {
+               my %co = %{$commitlist->[$i]};
+               next if !%co;
+               my $commit = $co{'id'};
+               my $ref = format_ref_marker($refs, $commit);
+               my %ad = parse_date($co{'author_epoch'});
+               git_print_header_div('commit',
+                              "<span class=\"age\">$co{'age_string'}</span>" .
+                              esc_html($co{'title'}) . $ref,
+                              $commit);
+               print "<div class=\"title_text\">\n" .
+                     "<div class=\"log_link\">\n" .
+                     $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") .
+                     " | " .
+                     $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") .
+                     " | " .
+                     $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") .
+                     "<br/>\n" .
+                     "</div>\n";
+                     git_print_authorship(\%co, -tag => 'span');
+                     print "<br/>\n</div>\n";
+
+               print "<div class=\"log_body\">\n";
+               git_print_log($co{'comment'}, -final_empty_line=> 1);
+               print "</div>\n";
+       }
+       if ($extra) {
+               print "<div class=\"page_nav\">\n";
+               print "$extra\n";
+               print "</div>\n";
+       }
+}
+
 sub git_shortlog_body {
        # uses global variable $project
        my ($commitlist, $from, $to, $refs, $extra) = @_;
@@ -4146,11 +4771,9 @@ sub git_shortlog_body {
                        print "<tr class=\"light\">\n";
                }
                $alternate ^= 1;
-               my $author = chop_and_escape_str($co{'author_name'}, 10);
                # git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" .
                print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
-                     "<td><i>" . $author . "</i></td>\n" .
-                     "<td>";
+                     format_author_html('td', \%co, 10) . "<td>";
                print format_subject_html($co{'title'}, $co{'title_short'},
                                          href(action=>"commit", hash=>$commit), $ref);
                print "</td>\n" .
@@ -4175,7 +4798,8 @@ sub git_shortlog_body {
 
 sub git_history_body {
        # Warning: assumes constant type (blob or tree) during history
-       my ($commitlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_;
+       my ($commitlist, $from, $to, $refs, $extra,
+           $file_name, $file_hash, $ftype) = @_;
 
        $from = 0 unless defined $from;
        $to = $#{$commitlist} unless (defined $to && $to <= $#{$commitlist});
@@ -4197,11 +4821,9 @@ sub git_history_body {
                        print "<tr class=\"light\">\n";
                }
                $alternate ^= 1;
-       # shortlog uses      chop_str($co{'author_name'}, 10)
-               my $author = chop_and_escape_str($co{'author_name'}, 15, 3);
                print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
-                     "<td><i>" . $author . "</i></td>\n" .
-                     "<td>";
+       # shortlog:   format_author_html('td', \%co, 10)
+                     format_author_html('td', \%co, 15, 3) . "<td>";
                # originally git_history used chop_str($co{'title'}, 50)
                print format_subject_html($co{'title'}, $co{'title_short'},
                                          href(action=>"commit", hash=>$commit), $ref);
@@ -4211,7 +4833,7 @@ sub git_history_body {
                      $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff");
 
                if ($ftype eq 'blob') {
-                       my $blob_current = git_get_hash_by_path($hash_base, $file_name);
+                       my $blob_current = $file_hash;
                        my $blob_parent  = git_get_hash_by_path($commit, $file_name);
                        if (defined $blob_current && defined $blob_parent &&
                                        $blob_current ne $blob_parent) {
@@ -4354,9 +4976,8 @@ sub git_search_grep_body {
                        print "<tr class=\"light\">\n";
                }
                $alternate ^= 1;
-               my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
                print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
-                     "<td><i>" . $author . "</i></td>\n" .
+                     format_author_html('td', \%co, 15, 5) .
                      "<td>" .
                      $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
                               -class => "list subject"},
@@ -4412,7 +5033,7 @@ sub git_project_list {
        }
 
        git_header_html();
-       if (-f $home_text) {
+       if (defined $home_text && -f $home_text) {
                print "<div class=\"index_include\">\n";
                insert_file($home_text);
                print "</div>\n";
@@ -4570,15 +5191,15 @@ sub git_summary {
 }
 
 sub git_tag {
-       my $head = git_get_head_hash($project);
-       git_header_html();
-       git_print_page_nav('','', $head,undef,$head);
        my %tag = parse_tag($hash);
 
        if (! %tag) {
                die_error(404, "Unknown tag object");
        }
 
+       my $head = git_get_head_hash($project);
+       git_header_html();
+       git_print_page_nav('','', $head,undef,$head);
        git_print_header_div('commit', esc_html($tag{'name'}), $hash);
        print "<div class=\"title_text\">\n" .
              "<table class=\"object_header\">\n" .
@@ -4590,11 +5211,7 @@ sub git_tag {
                                              $tag{'type'}) . "</td>\n" .
              "</tr>\n";
        if (defined($tag{'author'})) {
-               my %ad = parse_date($tag{'epoch'}, $tag{'tz'});
-               print "<tr><td>author</td><td>" . esc_html($tag{'author'}) . "</td></tr>\n";
-               print "<tr><td></td><td>" . $ad{'rfc2822'} .
-                       sprintf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}) .
-                       "</td></tr>\n";
+               git_print_authorship_rows(\%tag, 'author');
        }
        print "</table>\n\n" .
              "</div>\n";
@@ -4608,7 +5225,13 @@ sub git_tag {
        git_footer_html();
 }
 
-sub git_blame {
+sub git_blame_common {
+       my $format = shift || 'porcelain';
+       if ($format eq 'porcelain' && $cgi->param('js')) {
+               $format = 'incremental';
+               $action = 'blame_incremental'; # for page title etc
+       }
+
        # permissions
        gitweb_check_feature('blame')
                or die_error(403, "Blame view not allowed");
@@ -4630,107 +5253,220 @@ sub git_blame {
                }
        }
 
-       # run git-blame --porcelain
-       open my $fd, "-|", git_cmd(), "blame", '-p',
-               $hash_base, '--', $file_name
-               or die_error(500, "Open git-blame failed");
+       my $fd;
+       if ($format eq 'incremental') {
+               # get file contents (as base)
+               open $fd, "-|", git_cmd(), 'cat-file', 'blob', $hash
+                       or die_error(500, "Open git-cat-file failed");
+       } elsif ($format eq 'data') {
+               # run git-blame --incremental
+               open $fd, "-|", git_cmd(), "blame", "--incremental",
+                       $hash_base, "--", $file_name
+                       or die_error(500, "Open git-blame --incremental failed");
+       } else {
+               # run git-blame --porcelain
+               open $fd, "-|", git_cmd(), "blame", '-p',
+                       $hash_base, '--', $file_name
+                       or die_error(500, "Open git-blame --porcelain failed");
+       }
+
+       # incremental blame data returns early
+       if ($format eq 'data') {
+               print $cgi->header(
+                       -type=>"text/plain", -charset => "utf-8",
+                       -status=> "200 OK");
+               local $| = 1; # output autoflush
+               print while <$fd>;
+               close $fd
+                       or print "ERROR $!\n";
+
+               print 'END';
+               if (defined $t0 && gitweb_check_feature('timed')) {
+                       print ' '.
+                             Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
+                             ' '.$number_of_git_cmds;
+               }
+               print "\n";
+
+               return;
+       }
 
        # page header
        git_header_html();
        my $formats_nav =
                $cgi->a({-href => href(action=>"blob", -replay=>1)},
                        "blob") .
+               " | ";
+       if ($format eq 'incremental') {
+               $formats_nav .=
+                       $cgi->a({-href => href(action=>"blame", javascript=>0, -replay=>1)},
+                               "blame") . " (non-incremental)";
+       } else {
+               $formats_nav .=
+                       $cgi->a({-href => href(action=>"blame_incremental", -replay=>1)},
+                               "blame") . " (incremental)";
+       }
+       $formats_nav .=
                " | " .
                $cgi->a({-href => href(action=>"history", -replay=>1)},
                        "history") .
                " | " .
-               $cgi->a({-href => href(action=>"blame", file_name=>$file_name)},
+               $cgi->a({-href => href(action=>$action, file_name=>$file_name)},
                        "HEAD");
        git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
        git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
        git_print_page_path($file_name, $ftype, $hash_base);
 
        # page body
-       my @rev_color = qw(light2 dark2);
+       if ($format eq 'incremental') {
+               print "<noscript>\n<div class=\"error\"><center><b>\n".
+                     "This page requires JavaScript to run.\n Use ".
+                     $cgi->a({-href => href(action=>'blame',javascript=>0,-replay=>1)},
+                             'this page').
+                     " instead.\n".
+                     "</b></center></div>\n</noscript>\n";
+
+               print qq!<div id="progress_bar" style="width: 100%; background-color: yellow"></div>\n!;
+       }
+
+       print qq!<div class="page_body">\n!;
+       print qq!<div id="progress_info">... / ...</div>\n!
+               if ($format eq 'incremental');
+       print qq!<table id="blame_table" class="blame" width="100%">\n!.
+             #qq!<col width="5.5em" /><col width="2.5em" /><col width="*" />\n!.
+             qq!<thead>\n!.
+             qq!<tr><th>Commit</th><th>Line</th><th>Data</th></tr>\n!.
+             qq!</thead>\n!.
+             qq!<tbody>\n!;
+
+       my @rev_color = qw(light dark);
        my $num_colors = scalar(@rev_color);
        my $current_color = 0;
-       my %metainfo = ();
 
-       print <<HTML;
-<div class="page_body">
-<table class="blame">
-<tr><th>Commit</th><th>Line</th><th>Data</th></tr>
-HTML
- LINE:
-       while (my $line = <$fd>) {
-               chomp $line;
-               # the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]
-               # no <lines in group> for subsequent lines in group of lines
-               my ($full_rev, $orig_lineno, $lineno, $group_size) =
-                  ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/);
-               if (!exists $metainfo{$full_rev}) {
-                       $metainfo{$full_rev} = {};
-               }
-               my $meta = $metainfo{$full_rev};
-               my $data;
-               while ($data = <$fd>) {
-                       chomp $data;
-                       last if ($data =~ s/^\t//); # contents of line
-                       if ($data =~ /^(\S+) (.*)$/) {
-                               $meta->{$1} = $2;
-                       }
-               }
-               my $short_rev = substr($full_rev, 0, 8);
-               my $author = $meta->{'author'};
-               my %date =
-                       parse_date($meta->{'author-time'}, $meta->{'author-tz'});
-               my $date = $date{'iso-tz'};
-               if ($group_size) {
-                       $current_color = ($current_color + 1) % $num_colors;
-               }
-               print "<tr id=\"l$lineno\" class=\"$rev_color[$current_color]\">\n";
-               if ($group_size) {
-                       print "<td class=\"sha1\"";
-                       print " title=\"". esc_html($author) . ", $date\"";
-                       print " rowspan=\"$group_size\"" if ($group_size > 1);
-                       print ">";
-                       print $cgi->a({-href => href(action=>"commit",
-                                                    hash=>$full_rev,
-                                                    file_name=>$file_name)},
-                                     esc_html($short_rev));
-                       print "</td>\n";
+       if ($format eq 'incremental') {
+               my $color_class = $rev_color[$current_color];
+
+               #contents of a file
+               my $linenr = 0;
+       LINE:
+               while (my $line = <$fd>) {
+                       chomp $line;
+                       $linenr++;
+
+                       print qq!<tr id="l$linenr" class="$color_class">!.
+                             qq!<td class="sha1"><a href=""> </a></td>!.
+                             qq!<td class="linenr">!.
+                             qq!<a class="linenr" href="">$linenr</a></td>!;
+                       print qq!<td class="pre">! . esc_html($line) . "</td>\n";
+                       print qq!</tr>\n!;
                }
-               my $parent_commit;
-               if (!exists $meta->{'parent'}) {
-                       open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^")
-                               or die_error(500, "Open git-rev-parse failed");
-                       $parent_commit = <$dd>;
-                       close $dd;
-                       chomp($parent_commit);
-                       $meta->{'parent'} = $parent_commit;
-               } else {
-                       $parent_commit = $meta->{'parent'};
-               }
-               my $blamed = href(action => 'blame',
-                                 file_name => $meta->{'filename'},
-                                 hash_base => $parent_commit);
-               print "<td class=\"linenr\">";
-               print $cgi->a({ -href => "$blamed#l$orig_lineno",
-                               -class => "linenr" },
-                             esc_html($lineno));
-               print "</td>";
-               print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
-               print "</tr>\n";
+
+       } else { # porcelain, i.e. ordinary blame
+               my %metainfo = (); # saves information about commits
+
+               # blame data
+       LINE:
+               while (my $line = <$fd>) {
+                       chomp $line;
+                       # the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]
+                       # no <lines in group> for subsequent lines in group of lines
+                       my ($full_rev, $orig_lineno, $lineno, $group_size) =
+                          ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/);
+                       if (!exists $metainfo{$full_rev}) {
+                               $metainfo{$full_rev} = { 'nprevious' => 0 };
+                       }
+                       my $meta = $metainfo{$full_rev};
+                       my $data;
+                       while ($data = <$fd>) {
+                               chomp $data;
+                               last if ($data =~ s/^\t//); # contents of line
+                               if ($data =~ /^(\S+)(?: (.*))?$/) {
+                                       $meta->{$1} = $2 unless exists $meta->{$1};
+                               }
+                               if ($data =~ /^previous /) {
+                                       $meta->{'nprevious'}++;
+                               }
+                       }
+                       my $short_rev = substr($full_rev, 0, 8);
+                       my $author = $meta->{'author'};
+                       my %date =
+                               parse_date($meta->{'author-time'}, $meta->{'author-tz'});
+                       my $date = $date{'iso-tz'};
+                       if ($group_size) {
+                               $current_color = ($current_color + 1) % $num_colors;
+                       }
+                       my $tr_class = $rev_color[$current_color];
+                       $tr_class .= ' boundary' if (exists $meta->{'boundary'});
+                       $tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0);
+                       $tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1);
+                       print "<tr id=\"l$lineno\" class=\"$tr_class\">\n";
+                       if ($group_size) {
+                               print "<td class=\"sha1\"";
+                               print " title=\"". esc_html($author) . ", $date\"";
+                               print " rowspan=\"$group_size\"" if ($group_size > 1);
+                               print ">";
+                               print $cgi->a({-href => href(action=>"commit",
+                                                            hash=>$full_rev,
+                                                            file_name=>$file_name)},
+                                             esc_html($short_rev));
+                               if ($group_size >= 2) {
+                                       my @author_initials = ($author =~ /\b([[:upper:]])\B/g);
+                                       if (@author_initials) {
+                                               print "<br />" .
+                                                     esc_html(join('', @author_initials));
+                                               #           or join('.', ...)
+                                       }
+                               }
+                               print "</td>\n";
+                       }
+                       # 'previous' <sha1 of parent commit> <filename at commit>
+                       if (exists $meta->{'previous'} &&
+                           $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) {
+                               $meta->{'parent'} = $1;
+                               $meta->{'file_parent'} = unquote($2);
+                       }
+                       my $linenr_commit =
+                               exists($meta->{'parent'}) ?
+                               $meta->{'parent'} : $full_rev;
+                       my $linenr_filename =
+                               exists($meta->{'file_parent'}) ?
+                               $meta->{'file_parent'} : unquote($meta->{'filename'});
+                       my $blamed = href(action => 'blame',
+                                         file_name => $linenr_filename,
+                                         hash_base => $linenr_commit);
+                       print "<td class=\"linenr\">";
+                       print $cgi->a({ -href => "$blamed#l$orig_lineno",
+                                       -class => "linenr" },
+                                     esc_html($lineno));
+                       print "</td>";
+                       print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
+                       print "</tr>\n";
+               } # end while
+
        }
-       print "</table>\n";
-       print "</div>";
+
+       # footer
+       print "</tbody>\n".
+             "</table>\n"; # class="blame"
+       print "</div>\n";   # class="blame_body"
        close $fd
                or print "Reading blob failed\n";
 
-       # page footer
        git_footer_html();
 }
 
+sub git_blame {
+       git_blame_common();
+}
+
+sub git_blame_incremental {
+       git_blame_common('incremental');
+}
+
+sub git_blame_data {
+       git_blame_common('data');
+}
+
 sub git_tags {
        my $head = git_get_head_hash($project);
        git_header_html();
@@ -4803,11 +5539,10 @@ sub git_blob_plain {
                -content_disposition =>
                        ($sandbox ? 'attachment' : 'inline')
                        . '; filename="' . $save_as . '"');
-       undef $/;
+       local $/ = undef;
        binmode STDOUT, ':raw';
        print <$fd>;
        binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
-       $/ = "\n";
        close $fd;
 }
 
@@ -4831,6 +5566,7 @@ sub git_blob {
        open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
                or die_error(500, "Couldn't cat $file_name, $hash");
        my $mimetype = blob_mimetype($fd, $file_name);
+       # use 'blob_plain' (aka 'raw') view for files that cannot be displayed
        if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)! && -B $fd) {
                close $fd;
                return git_blob_plain($mimetype);
@@ -4838,6 +5574,11 @@ sub git_blob {
        # we can have blame only for text/* mimetype
        $have_blame &&= ($mimetype =~ m!^text/!);
 
+       my $highlight = gitweb_check_feature('highlight');
+       my $syntax = guess_file_syntax($highlight, $mimetype, $file_name);
+       $fd = run_highlighter($fd, $highlight, $syntax)
+               if $syntax;
+
        git_header_html(undef, $expires);
        my $formats_nav = '';
        if (defined $hash_base && (my %co = parse_commit($hash_base))) {
@@ -4887,8 +5628,8 @@ sub git_blob {
                        chomp $line;
                        $nr++;
                        $line = untabify($line);
-                       printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
-                              $nr, $nr, $nr, esc_html($line, -nbsp=>1);
+                       printf qq!<div class="pre"><a id="l%i" href="%s#l%i" class="linenr">%4i</a> %s</div>\n!,
+                              $nr, href(-replay => 1), $nr, $nr, $syntax ? $line : esc_html($line, -nbsp=>1);
                }
        }
        close $fd
@@ -4909,18 +5650,25 @@ sub git_tree {
                }
        }
        die_error(404, "No such tree") unless defined($hash);
-       $/ = "\0";
-       open my $fd, "-|", git_cmd(), "ls-tree", '-z', $hash
-               or die_error(500, "Open git-ls-tree failed");
-       my @entries = map { chomp; $_ } <$fd>;
-       close $fd or die_error(404, "Reading tree failed");
-       $/ = "\n";
+
+       my $show_sizes = gitweb_check_feature('show-sizes');
+       my $have_blame = gitweb_check_feature('blame');
+
+       my @entries = ();
+       {
+               local $/ = "\0";
+               open my $fd, "-|", git_cmd(), "ls-tree", '-z',
+                       ($show_sizes ? '-l' : ()), @extra_options, $hash
+                       or die_error(500, "Open git-ls-tree failed");
+               @entries = map { chomp; $_ } <$fd>;
+               close $fd
+                       or die_error(404, "Reading tree failed");
+       }
 
        my $refs = git_get_references();
        my $ref = format_ref_marker($refs, $hash_base);
        git_header_html();
        my $basedir = '';
-       my $have_blame = gitweb_check_feature('blame');
        if (defined $hash_base && (my %co = parse_commit($hash_base))) {
                my @views_nav = ();
                if (defined $file_name) {
@@ -4936,7 +5684,8 @@ sub git_tree {
                        # FIXME: Should be available when we have no hash base as well.
                        push @views_nav, $snapshot_links;
                }
-               git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav));
+               git_print_page_nav('tree','', $hash_base, undef, undef,
+                                  join(' | ', @views_nav));
                git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);
        } else {
                undef $hash_base;
@@ -4969,8 +5718,10 @@ sub git_tree {
                undef $up unless $up;
                # based on git_print_tree_entry
                print '<td class="mode">' . mode_str('040000') . "</td>\n";
+               print '<td class="size">&nbsp;</td>'."\n" if $show_sizes;
                print '<td class="list">';
-               print $cgi->a({-href => href(action=>"tree", hash_base=>$hash_base,
+               print $cgi->a({-href => href(action=>"tree",
+                                            hash_base=>$hash_base,
                                             file_name=>$up)},
                              "..");
                print "</td>\n";
@@ -4979,7 +5730,7 @@ sub git_tree {
                print "</tr>\n";
        }
        foreach my $line (@entries) {
-               my %t = parse_ls_tree_line($line, -z => 1);
+               my %t = parse_ls_tree_line($line, -z => 1, -l => $show_sizes);
 
                if ($alternate) {
                        print "<tr class=\"dark\">\n";
@@ -4997,6 +5748,43 @@ sub git_tree {
        git_footer_html();
 }
 
+sub snapshot_name {
+       my ($project, $hash) = @_;
+
+       # path/to/project.git  -> project
+       # path/to/project/.git -> project
+       my $name = to_utf8($project);
+       $name =~ s,([^/])/*\.git$,$1,;
+       $name = basename($name);
+       # sanitize name
+       $name =~ s/[[:cntrl:]]/?/g;
+
+       my $ver = $hash;
+       if ($hash =~ /^[0-9a-fA-F]+$/) {
+               # shorten SHA-1 hash
+               my $full_hash = git_get_full_hash($project, $hash);
+               if ($full_hash =~ /^$hash/ && length($hash) > 7) {
+                       $ver = git_get_short_hash($project, $hash);
+               }
+       } elsif ($hash =~ m!^refs/tags/(.*)$!) {
+               # tags don't need shortened SHA-1 hash
+               $ver = $1;
+       } else {
+               # branches and other need shortened SHA-1 hash
+               if ($hash =~ m!^refs/(?:heads|remotes)/(.*)$!) {
+                       $ver = $1;
+               }
+               $ver .= '-' . git_get_short_hash($project, $hash);
+       }
+       # in case of hierarchical branch names
+       $ver =~ s!/!.!g;
+
+       # name = project-version_string
+       $name = "$name-$ver";
+
+       return wantarray ? ($name, $name) : $name;
+}
+
 sub git_snapshot {
        my $format = $input_params{'snapshot_format'};
        if (!@snapshot_fmts) {
@@ -5008,32 +5796,33 @@ sub git_snapshot {
                die_error(400, "Invalid snapshot format parameter");
        } elsif (!exists($known_snapshot_formats{$format})) {
                die_error(400, "Unknown snapshot format");
+       } elsif ($known_snapshot_formats{$format}{'disabled'}) {
+               die_error(403, "Snapshot format not allowed");
        } elsif (!grep($_ eq $format, @snapshot_fmts)) {
                die_error(403, "Unsupported snapshot format");
        }
 
-       if (!defined $hash) {
-               $hash = git_get_head_hash($project);
+       my $type = git_get_type("$hash^{}");
+       if (!$type) {
+               die_error(404, 'Object does not exist');
+       }  elsif ($type eq 'blob') {
+               die_error(400, 'Object is not a tree-ish');
        }
 
-       my $name = $project;
-       $name =~ s,([^/])/*\.git$,$1,;
-       $name = basename($name);
-       my $filename = to_utf8($name);
-       $name =~ s/\047/\047\\\047\047/g;
-       my $cmd;
-       $filename .= "-$hash$known_snapshot_formats{$format}{'suffix'}";
-       $cmd = quote_command(
+       my ($name, $prefix) = snapshot_name($project, $hash);
+       my $filename = "$name$known_snapshot_formats{$format}{'suffix'}";
+       my $cmd = quote_command(
                git_cmd(), 'archive',
                "--format=$known_snapshot_formats{$format}{'format'}",
-               "--prefix=$name/", $hash);
+               "--prefix=$prefix/", $hash);
        if (exists $known_snapshot_formats{$format}{'compressor'}) {
                $cmd .= ' | ' . quote_command(@{$known_snapshot_formats{$format}{'compressor'}});
        }
 
+       $filename =~ s/(["\\])/\\$1/g;
        print $cgi->header(
                -type => $known_snapshot_formats{$format}{'type'},
-               -content_disposition => 'inline; filename="' . "$filename" . '"',
+               -content_disposition => 'inline; filename="' . $filename . '"',
                -status => '200 OK');
 
        open my $fd, "-|", $cmd
@@ -5044,22 +5833,57 @@ sub git_snapshot {
        close $fd;
 }
 
-sub git_log {
+sub git_log_generic {
+       my ($fmt_name, $body_subr, $base, $parent, $file_name, $file_hash) = @_;
+
        my $head = git_get_head_hash($project);
-       if (!defined $hash) {
-               $hash = $head;
+       if (!defined $base) {
+               $base = $head;
        }
        if (!defined $page) {
                $page = 0;
        }
        my $refs = git_get_references();
 
-       my @commitlist = parse_commits($hash, 101, (100 * $page));
+       my $commit_hash = $base;
+       if (defined $parent) {
+               $commit_hash = "$parent..$base";
+       }
+       my @commitlist =
+               parse_commits($commit_hash, 101, (100 * $page),
+                             defined $file_name ? ($file_name, "--full-history") : ());
 
-       my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#commitlist >= 100);
+       my $ftype;
+       if (!defined $file_hash && defined $file_name) {
+               # some commits could have deleted file in question,
+               # and not have it in tree, but one of them has to have it
+               for (my $i = 0; $i < @commitlist; $i++) {
+                       $file_hash = git_get_hash_by_path($commitlist[$i]{'id'}, $file_name);
+                       last if defined $file_hash;
+               }
+       }
+       if (defined $file_hash) {
+               $ftype = git_get_type($file_hash);
+       }
+       if (defined $file_name && !defined $ftype) {
+               die_error(500, "Unknown type of object");
+       }
+       my %co;
+       if (defined $file_name) {
+               %co = parse_commit($base)
+                       or die_error(404, "Unknown commit object");
+       }
 
-       my ($patch_max) = gitweb_get_feature('patches');
-       if ($patch_max) {
+
+       my $paging_nav = format_paging_nav($fmt_name, $page, $#commitlist >= 100);
+       my $next_link = '';
+       if ($#commitlist >= 100) {
+               $next_link =
+                       $cgi->a({-href => href(-replay=>1, page=>$page+1),
+                                -accesskey => "n", -title => "Alt-n"}, "next");
+       }
+       my $patch_max = gitweb_get_feature('patches');
+       if ($patch_max && !defined $file_name) {
                if ($patch_max < 0 || @commitlist <= $patch_max) {
                        $paging_nav .= " &sdot; " .
                                $cgi->a({-href => href(action=>"patches", -replay=>1)},
@@ -5068,56 +5892,30 @@ sub git_log {
        }
 
        git_header_html();
-       git_print_page_nav('log','', $hash,undef,undef, $paging_nav);
-
-       if (!@commitlist) {
-               my %co = parse_commit($hash);
-
-               git_print_header_div('summary', $project);
-               print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n";
+       git_print_page_nav($fmt_name,'', $hash,$hash,$hash, $paging_nav);
+       if (defined $file_name) {
+               git_print_header_div('commit', esc_html($co{'title'}), $base);
+       } else {
+               git_print_header_div('summary', $project)
        }
-       my $to = ($#commitlist >= 99) ? (99) : ($#commitlist);
-       for (my $i = 0; $i <= $to; $i++) {
-               my %co = %{$commitlist[$i]};
-               next if !%co;
-               my $commit = $co{'id'};
-               my $ref = format_ref_marker($refs, $commit);
-               my %ad = parse_date($co{'author_epoch'});
-               git_print_header_div('commit',
-                              "<span class=\"age\">$co{'age_string'}</span>" .
-                              esc_html($co{'title'}) . $ref,
-                              $commit);
-               print "<div class=\"title_text\">\n" .
-                     "<div class=\"log_link\">\n" .
-                     $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") .
-                     " | " .
-                     $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") .
-                     " | " .
-                     $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") .
-                     "<br/>\n" .
-                     "</div>\n" .
-                     "<i>" . esc_html($co{'author_name'}) .  " [$ad{'rfc2822'}]</i><br/>\n" .
-                     "</div>\n";
+       git_print_page_path($file_name, $ftype, $hash_base)
+               if (defined $file_name);
+
+       $body_subr->(\@commitlist, 0, 99, $refs, $next_link,
+                    $file_name, $file_hash, $ftype);
 
-               print "<div class=\"log_body\">\n";
-               git_print_log($co{'comment'}, -final_empty_line=> 1);
-               print "</div>\n";
-       }
-       if ($#commitlist >= 100) {
-               print "<div class=\"page_nav\">\n";
-               print $cgi->a({-href => href(-replay=>1, page=>$page+1),
-                              -accesskey => "n", -title => "Alt-n"}, "next");
-               print "</div>\n";
-       }
        git_footer_html();
 }
 
+sub git_log {
+       git_log_generic('log', \&git_log_body,
+                       $hash, $hash_parent);
+}
+
 sub git_commit {
        $hash ||= $hash_base || "HEAD";
        my %co = parse_commit($hash)
            or die_error(404, "Unknown commit object");
-       my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
-       my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'});
 
        my $parent  = $co{'parent'};
        my $parents = $co{'parents'}; # listref
@@ -5146,7 +5944,7 @@ sub git_commit {
                        } @$parents ) .
                        ')';
        }
-       if (gitweb_check_feature('patches')) {
+       if (gitweb_check_feature('patches') && @$parents <= 1) {
                $formats_nav .= " | " .
                        $cgi->a({-href => href(action=>"patch", -replay=>1)},
                                "patch");
@@ -5184,22 +5982,7 @@ sub git_commit {
        }
        print "<div class=\"title_text\">\n" .
              "<table class=\"object_header\">\n";
-       print "<tr><td>author</td><td>" . esc_html($co{'author'}) . "</td></tr>\n".
-             "<tr>" .
-             "<td></td><td> $ad{'rfc2822'}";
-       if ($ad{'hour_local'} < 6) {
-               printf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
-                      $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
-       } else {
-               printf(" (%02d:%02d %s)",
-                      $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
-       }
-       print "</td>" .
-             "</tr>\n";
-       print "<tr><td>committer</td><td>" . esc_html($co{'committer'}) . "</td></tr>\n";
-       print "<tr><td></td><td> $cd{'rfc2822'}" .
-             sprintf(" (%02d:%02d %s)", $cd{'hour_local'}, $cd{'minute_local'}, $cd{'tz_local'}) .
-             "</td></tr>\n";
+       git_print_authorship_rows(\%co);
        print "<tr><td>commit</td><td class=\"sha1\">$co{'id'}</td></tr>\n";
        print "<tr>" .
              "<td>tree</td>" .
@@ -5449,7 +6232,7 @@ sub git_commitdiff {
                $formats_nav =
                        $cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)},
                                "raw");
-               if ($patch_max) {
+               if ($patch_max && @{$co{'parents'}} <= 1) {
                        $formats_nav .= " | " .
                                $cgi->a({-href => href(action=>"patch", -replay=>1)},
                                        "patch");
@@ -5559,8 +6342,8 @@ sub git_commitdiff {
                        }
                        push @commit_spec, '--root', $hash;
                }
-               open $fd, "-|", git_cmd(), "format-patch", '--encoding=utf8',
-                       '--stdout', @commit_spec
+               open $fd, "-|", git_cmd(), "format-patch", @diff_opts,
+                       '--encoding=utf8', '--stdout', @commit_spec
                        or die_error(500, "Open git-format-patch failed");
        } else {
                die_error(400, "Unknown commitdiff format");
@@ -5580,7 +6363,11 @@ sub git_commitdiff {
                git_header_html(undef, $expires);
                git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
                git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
-               git_print_authorship(\%co);
+               print "<div class=\"title_text\">\n" .
+                     "<table class=\"object_header\">\n";
+               git_print_authorship_rows(\%co);
+               print "</table>".
+                     "</div>\n";
                print "<div class=\"page_body\">\n";
                if (@{$co{'comment'}} > 1) {
                        print "<div class=\"log\">\n";
@@ -5653,7 +6440,7 @@ sub git_commitdiff_plain {
 
 # format-patch-style patches
 sub git_patch {
-       git_commitdiff(-format => 'patch', -single=> 1);
+       git_commitdiff(-format => 'patch', -single => 1);
 }
 
 sub git_patches {
@@ -5661,70 +6448,9 @@ sub git_patches {
 }
 
 sub git_history {
-       if (!defined $hash_base) {
-               $hash_base = git_get_head_hash($project);
-       }
-       if (!defined $page) {
-               $page = 0;
-       }
-       my $ftype;
-       my %co = parse_commit($hash_base)
-           or die_error(404, "Unknown commit object");
-
-       my $refs = git_get_references();
-       my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
-
-       my @commitlist = parse_commits($hash_base, 101, (100 * $page),
-                                      $file_name, "--full-history")
-           or die_error(404, "No such file or directory on given branch");
-
-       if (!defined $hash && defined $file_name) {
-               # some commits could have deleted file in question,
-               # and not have it in tree, but one of them has to have it
-               for (my $i = 0; $i <= @commitlist; $i++) {
-                       $hash = git_get_hash_by_path($commitlist[$i]{'id'}, $file_name);
-                       last if defined $hash;
-               }
-       }
-       if (defined $hash) {
-               $ftype = git_get_type($hash);
-       }
-       if (!defined $ftype) {
-               die_error(500, "Unknown type of object");
-       }
-
-       my $paging_nav = '';
-       if ($page > 0) {
-               $paging_nav .=
-                       $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
-                                              file_name=>$file_name)},
-                               "first");
-               $paging_nav .= " &sdot; " .
-                       $cgi->a({-href => href(-replay=>1, page=>$page-1),
-                                -accesskey => "p", -title => "Alt-p"}, "prev");
-       } else {
-               $paging_nav .= "first";
-               $paging_nav .= " &sdot; prev";
-       }
-       my $next_link = '';
-       if ($#commitlist >= 100) {
-               $next_link =
-                       $cgi->a({-href => href(-replay=>1, page=>$page+1),
-                                -accesskey => "n", -title => "Alt-n"}, "next");
-               $paging_nav .= " &sdot; $next_link";
-       } else {
-               $paging_nav .= " &sdot; next";
-       }
-
-       git_header_html();
-       git_print_page_nav('history','', $hash_base,$co{'tree'},$hash_base, $paging_nav);
-       git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
-       git_print_page_path($file_name, $ftype, $hash_base);
-
-       git_history_body(\@commitlist, 0, 99,
-                        $refs, $hash_base, $ftype, $next_link);
-
-       git_footer_html();
+       git_log_generic('history', \&git_history_body,
+                       $hash_base, $hash_parent_base,
+                       $file_name, $hash);
 }
 
 sub git_search {
@@ -5795,12 +6521,13 @@ sub git_search {
                        $paging_nav .= " &sdot; next";
                }
 
-               if ($#commitlist >= 100) {
-               }
-
                git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
                git_print_header_div('commit', esc_html($co{'title'}), $hash);
-               git_search_grep_body(\@commitlist, 0, 99, $next_link);
+               if ($page == 0 && !@commitlist) {
+                       print "<p>No match.</p>\n";
+               } else {
+                       git_search_grep_body(\@commitlist, 0, 99, $next_link);
+               }
        }
 
        if ($searchtype eq 'pickaxe') {
@@ -5809,7 +6536,7 @@ sub git_search {
 
                print "<table class=\"pickaxe search\">\n";
                my $alternate = 1;
-               $/ = "\n";
+               local $/ = "\n";
                open my $fd, '-|', git_cmd(), '--no-pager', 'log', @diff_opts,
                        '--pretty=format:%H', '--no-abbrev', '--raw', "-S$searchtext",
                        ($search_use_regexp ? '--pickaxe-regex' : ());
@@ -5879,7 +6606,7 @@ sub git_search {
                print "<table class=\"grep_search\">\n";
                my $alternate = 1;
                my $matches = 0;
-               $/ = "\n";
+               local $/ = "\n";
                open my $fd, "-|", git_cmd(), 'grep', '-n',
                        $search_use_regexp ? ('-E', '-i') : '-F',
                        $searchtext, $co{'tree'};
@@ -5988,44 +6715,8 @@ EOT
 }
 
 sub git_shortlog {
-       my $head = git_get_head_hash($project);
-       if (!defined $hash) {
-               $hash = $head;
-       }
-       if (!defined $page) {
-               $page = 0;
-       }
-       my $refs = git_get_references();
-
-       my $commit_hash = $hash;
-       if (defined $hash_parent) {
-               $commit_hash = "$hash_parent..$hash";
-       }
-       my @commitlist = parse_commits($commit_hash, 101, (100 * $page));
-
-       my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, $#commitlist >= 100);
-       my $next_link = '';
-       if ($#commitlist >= 100) {
-               $next_link =
-                       $cgi->a({-href => href(-replay=>1, page=>$page+1),
-                                -accesskey => "n", -title => "Alt-n"}, "next");
-       }
-       my $patch_max = gitweb_check_feature('patches');
-       if ($patch_max) {
-               if ($patch_max < 0 || @commitlist <= $patch_max) {
-                       $paging_nav .= " &sdot; " .
-                               $cgi->a({-href => href(action=>"patches", -replay=>1)},
-                                       "patches");
-               }
-       }
-
-       git_header_html();
-       git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
-       git_print_header_div('summary', $project);
-
-       git_shortlog_body(\@commitlist, 0, 99, $refs, $next_link);
-
-       git_footer_html();
+       git_log_generic('shortlog', \&git_shortlog_body,
+                       $hash, $hash_parent);
 }
 
 ## ......................................................................
@@ -6282,7 +6973,7 @@ XML
        # end of feed
        if ($format eq 'rss') {
                print "</channel>\n</rss>\n";
-       }       elsif ($format eq 'atom') {
+       } elsif ($format eq 'atom') {
                print "</feed>\n";
        }
 }
diff --git a/gitweb/static/git-favicon.png b/gitweb/static/git-favicon.png
new file mode 100644 (file)
index 0000000..aae35a7
Binary files /dev/null and b/gitweb/static/git-favicon.png differ
diff --git a/gitweb/static/git-logo.png b/gitweb/static/git-logo.png
new file mode 100644 (file)
index 0000000..f4ede2e
Binary files /dev/null and b/gitweb/static/git-logo.png differ
diff --git a/gitweb/static/gitweb.css b/gitweb/static/gitweb.css
new file mode 100644 (file)
index 0000000..4132aab
--- /dev/null
@@ -0,0 +1,592 @@
+body {
+       font-family: sans-serif;
+       font-size: small;
+       border: solid #d9d8d1;
+       border-width: 1px;
+       margin: 10px;
+       background-color: #ffffff;
+       color: #000000;
+}
+
+a {
+       color: #0000cc;
+}
+
+a:hover, a:visited, a:active {
+       color: #880000;
+}
+
+span.cntrl {
+       border: dashed #aaaaaa;
+       border-width: 1px;
+       padding: 0px 2px 0px 2px;
+       margin:  0px 2px 0px 2px;
+}
+
+img.logo {
+       float: right;
+       border-width: 0px;
+}
+
+img.avatar {
+       vertical-align: middle;
+}
+
+a.list img.avatar {
+       border-style: none;
+}
+
+div.page_header {
+       height: 25px;
+       padding: 8px;
+       font-size: 150%;
+       font-weight: bold;
+       background-color: #d9d8d1;
+}
+
+div.page_header a:visited, a.header {
+       color: #0000cc;
+}
+
+div.page_header a:hover {
+       color: #880000;
+}
+
+div.page_nav {
+       padding: 8px;
+}
+
+div.page_nav a:visited {
+       color: #0000cc;
+}
+
+div.page_path {
+       padding: 8px;
+       font-weight: bold;
+       border: solid #d9d8d1;
+       border-width: 0px 0px 1px;
+}
+
+div.page_footer {
+       height: 17px;
+       padding: 4px 8px;
+       background-color: #d9d8d1;
+}
+
+div.page_footer_text {
+       float: left;
+       color: #555555;
+       font-style: italic;
+}
+
+div#generating_info {
+       margin: 4px;
+       font-size: smaller;
+       text-align: center;
+       color: #505050;
+}
+
+div.page_body {
+       padding: 8px;
+       font-family: monospace;
+}
+
+div.title, a.title {
+       display: block;
+       padding: 6px 8px;
+       font-weight: bold;
+       background-color: #edece6;
+       text-decoration: none;
+       color: #000000;
+}
+
+div.readme {
+       padding: 8px;
+}
+
+a.title:hover {
+       background-color: #d9d8d1;
+}
+
+div.title_text {
+       padding: 6px 0px;
+       border: solid #d9d8d1;
+       border-width: 0px 0px 1px;
+       font-family: monospace;
+}
+
+div.log_body {
+       padding: 8px 8px 8px 150px;
+}
+
+span.age {
+       position: relative;
+       float: left;
+       width: 142px;
+       font-style: italic;
+}
+
+span.signoff {
+       color: #888888;
+}
+
+div.log_link {
+       padding: 0px 8px;
+       font-size: 70%;
+       font-family: sans-serif;
+       font-style: normal;
+       position: relative;
+       float: left;
+       width: 136px;
+}
+
+div.list_head {
+       padding: 6px 8px 4px;
+       border: solid #d9d8d1;
+       border-width: 1px 0px 0px;
+       font-style: italic;
+}
+
+.author_date, .author {
+       font-style: italic;
+}
+
+div.author_date {
+       padding: 8px;
+       border: solid #d9d8d1;
+       border-width: 0px 0px 1px 0px;
+}
+
+a.list {
+       text-decoration: none;
+       color: #000000;
+}
+
+a.subject, a.name {
+       font-weight: bold;
+}
+
+table.tags a.subject {
+       font-weight: normal;
+}
+
+a.list:hover {
+       text-decoration: underline;
+       color: #880000;
+}
+
+a.text {
+       text-decoration: none;
+       color: #0000cc;
+}
+
+a.text:visited {
+       text-decoration: none;
+       color: #880000;
+}
+
+a.text:hover {
+       text-decoration: underline;
+       color: #880000;
+}
+
+table {
+       padding: 8px 4px;
+       border-spacing: 0;
+}
+
+table.diff_tree {
+       font-family: monospace;
+}
+
+table.combined.diff_tree th {
+       text-align: center;
+}
+
+table.combined.diff_tree td {
+       padding-right: 24px;
+}
+
+table.combined.diff_tree th.link,
+table.combined.diff_tree td.link {
+       padding: 0px 2px;
+}
+
+table.combined.diff_tree td.nochange a {
+       color: #6666ff;
+}
+
+table.combined.diff_tree td.nochange a:hover,
+table.combined.diff_tree td.nochange a:visited {
+       color: #d06666;
+}
+
+table.blame {
+       border-collapse: collapse;
+}
+
+table.blame td {
+       padding: 0px 5px;
+       font-size: 100%;
+       vertical-align: top;
+}
+
+th {
+       padding: 2px 5px;
+       font-size: 100%;
+       text-align: left;
+}
+
+/* do not change row style on hover for 'blame' view */
+tr.light,
+table.blame .light:hover {
+       background-color: #ffffff;
+}
+
+tr.dark,
+table.blame .dark:hover {
+       background-color: #f6f6f0;
+}
+
+/* currently both use the same, but it can change */
+tr.light:hover,
+tr.dark:hover {
+       background-color: #edece6;
+}
+
+/* boundary commits in 'blame' view */
+/* and commits without "previous" */
+tr.boundary td.sha1,
+tr.no-previous td.linenr {
+       font-weight: bold;
+}
+
+/* for 'blame_incremental', during processing */
+tr.color1 { background-color: #f6fff6; }
+tr.color2 { background-color: #f6f6ff; }
+tr.color3 { background-color: #fff6f6; }
+
+td {
+       padding: 2px 5px;
+       font-size: 100%;
+       vertical-align: top;
+}
+
+td.link, td.selflink {
+       padding: 2px 5px;
+       font-family: sans-serif;
+       font-size: 70%;
+}
+
+td.selflink {
+       padding-right: 0px;
+}
+
+td.sha1 {
+       font-family: monospace;
+}
+
+.error {
+       color: red;
+       background-color: yellow;
+}
+
+td.current_head {
+       text-decoration: underline;
+}
+
+table.diff_tree span.file_status.new {
+       color: #008000;
+}
+
+table.diff_tree span.file_status.deleted {
+       color: #c00000;
+}
+
+table.diff_tree span.file_status.moved,
+table.diff_tree span.file_status.mode_chnge {
+       color: #777777;
+}
+
+table.diff_tree span.file_status.copied {
+  color: #70a070;
+}
+
+/* noage: "No commits" */
+table.project_list td.noage {
+       color: #808080;
+       font-style: italic;
+}
+
+/* age2: 60*60*24*2 <= age */
+table.project_list td.age2, table.blame td.age2 {
+       font-style: italic;
+}
+
+/* age1: 60*60*2 <= age < 60*60*24*2 */
+table.project_list td.age1 {
+       color: #009900;
+       font-style: italic;
+}
+
+table.blame td.age1 {
+       color: #009900;
+       background: transparent;
+}
+
+/* age0: age < 60*60*2 */
+table.project_list td.age0 {
+       color: #009900;
+       font-style: italic;
+       font-weight: bold;
+}
+
+table.blame td.age0 {
+       color: #009900;
+       background: transparent;
+       font-weight: bold;
+}
+
+td.pre, div.pre, div.diff {
+       font-family: monospace;
+       font-size: 12px;
+       white-space: pre;
+}
+
+td.mode {
+       font-family: monospace;
+}
+
+/* progress of blame_interactive */
+div#progress_bar {
+       height: 2px;
+       margin-bottom: -2px;
+       background-color: #d8d9d0;
+}
+div#progress_info {
+       float: right;
+       text-align: right;
+}
+
+/* format of (optional) objects size in 'tree' view */
+td.size {
+       font-family: monospace;
+       text-align: right;
+}
+
+/* styling of diffs (patchsets): commitdiff and blobdiff views */
+div.diff.header,
+div.diff.extended_header {
+       white-space: normal;
+}
+
+div.diff.header {
+       font-weight: bold;
+
+       background-color: #edece6;
+
+       margin-top: 4px;
+       padding: 4px 0px 2px 0px;
+       border: solid #d9d8d1;
+       border-width: 1px 0px 1px 0px;
+}
+
+div.diff.header a.path {
+       text-decoration: underline;
+}
+
+div.diff.extended_header,
+div.diff.extended_header a.path,
+div.diff.extended_header a.hash {
+       color: #777777;
+}
+
+div.diff.extended_header .info {
+       color: #b0b0b0;
+}
+
+div.diff.extended_header {
+       background-color: #f6f5ee;
+       padding: 2px 0px 2px 0px;
+}
+
+div.diff a.list,
+div.diff a.path,
+div.diff a.hash {
+       text-decoration: none;
+}
+
+div.diff a.list:hover,
+div.diff a.path:hover,
+div.diff a.hash:hover {
+       text-decoration: underline;
+}
+
+div.diff.to_file a.path,
+div.diff.to_file {
+       color: #007000;
+}
+
+div.diff.add {
+       color: #008800;
+}
+
+div.diff.from_file a.path,
+div.diff.from_file {
+       color: #aa0000;
+}
+
+div.diff.rem {
+       color: #cc0000;
+}
+
+div.diff.chunk_header a,
+div.diff.chunk_header {
+       color: #990099;
+}
+
+div.diff.chunk_header {
+       border: dotted #ffe0ff;
+       border-width: 1px 0px 0px 0px;
+       margin-top: 2px;
+}
+
+div.diff.chunk_header span.chunk_info {
+       background-color: #ffeeff;
+}
+
+div.diff.chunk_header span.section {
+       color: #aa22aa;
+}
+
+div.diff.incomplete {
+       color: #cccccc;
+}
+
+div.diff.nodifferences {
+       font-weight: bold;
+       color: #600000;
+}
+
+div.index_include {
+       border: solid #d9d8d1;
+       border-width: 0px 0px 1px;
+       padding: 12px 8px;
+}
+
+div.search {
+       font-size: 100%;
+       font-weight: normal;
+       margin: 4px 8px;
+       float: right;
+       top: 56px;
+       right: 12px
+}
+
+p.projsearch {
+       text-align: center;
+}
+
+td.linenr {
+       text-align: right;
+}
+
+a.linenr {
+       color: #999999;
+       text-decoration: none
+}
+
+a.rss_logo {
+       float: right;
+       padding: 3px 0px;
+       width: 35px;
+       line-height: 10px;
+       border: 1px solid;
+       border-color: #fcc7a5 #7d3302 #3e1a01 #ff954e;
+       color: #ffffff;
+       background-color: #ff6600;
+       font-weight: bold;
+       font-family: sans-serif;
+       font-size: 70%;
+       text-align: center;
+       text-decoration: none;
+}
+
+a.rss_logo:hover {
+       background-color: #ee5500;
+}
+
+a.rss_logo.generic {
+       background-color: #ff8800;
+}
+
+a.rss_logo.generic:hover {
+       background-color: #ee7700;
+}
+
+span.refs span {
+       padding: 0px 4px;
+       font-size: 70%;
+       font-weight: normal;
+       border: 1px solid;
+       background-color: #ffaaff;
+       border-color: #ffccff #ff00ee #ff00ee #ffccff;
+}
+
+span.refs span a {
+       text-decoration: none;
+       color: inherit;
+}
+
+span.refs span a:hover {
+       text-decoration: underline;
+}
+
+span.refs span.indirect {
+       font-style: italic;
+}
+
+span.refs span.ref {
+       background-color: #aaaaff;
+       border-color: #ccccff #0033cc #0033cc #ccccff;
+}
+
+span.refs span.tag {
+       background-color: #ffffaa;
+       border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
+}
+
+span.refs span.head {
+       background-color: #aaffaa;
+       border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
+}
+
+span.atnight {
+       color: #cc0000;
+}
+
+span.match {
+       color: #e00000;
+}
+
+div.binary {
+       font-style: italic;
+}
+
+/* Style definition generated by highlight 2.4.5, http://www.andre-simon.de/ */
+
+/* Highlighting theme definition: */
+
+.num    { color:#2928ff; }
+.esc    { color:#ff00ff; }
+.str    { color:#ff0000; }
+.dstr   { color:#818100; }
+.slc    { color:#838183; font-style:italic; }
+.com    { color:#838183; font-style:italic; }
+.dir    { color:#008200; }
+.sym    { color:#000000; }
+.line   { color:#555555; }
+.kwa    { color:#000000; font-weight:bold; }
+.kwb    { color:#830000; }
+.kwc    { color:#000000; font-weight:bold; }
+.kwd    { color:#010181; }
diff --git a/gitweb/static/gitweb.js b/gitweb/static/gitweb.js
new file mode 100644 (file)
index 0000000..9c66928
--- /dev/null
@@ -0,0 +1,875 @@
+// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com>
+//               2007, Petr Baudis <pasky@suse.cz>
+//          2008-2009, Jakub Narebski <jnareb@gmail.com>
+
+/**
+ * @fileOverview JavaScript code for gitweb (git web interface).
+ * @license GPLv2 or later
+ */
+
+/* ============================================================ */
+/* functions for generic gitweb actions and views */
+
+/**
+ * used to check if link has 'js' query parameter already (at end),
+ * and other reasons to not add 'js=1' param at the end of link
+ * @constant
+ */
+var jsExceptionsRe = /[;?]js=[01]$/;
+
+/**
+ * Add '?js=1' or ';js=1' to the end of every link in the document
+ * that doesn't have 'js' query parameter set already.
+ *
+ * Links with 'js=1' lead to JavaScript version of given action, if it
+ * exists (currently there is only 'blame_incremental' for 'blame')
+ *
+ * @globals jsExceptionsRe
+ */
+function fixLinks() {
+       var allLinks = document.getElementsByTagName("a") || document.links;
+       for (var i = 0, len = allLinks.length; i < len; i++) {
+               var link = allLinks[i];
+               if (!jsExceptionsRe.test(link)) { // =~ /[;?]js=[01]$/;
+                       link.href +=
+                               (link.href.indexOf('?') === -1 ? '?' : ';') + 'js=1';
+               }
+       }
+}
+
+
+/* ============================================================ */
+
+/*
+ * This code uses DOM methods instead of (nonstandard) innerHTML
+ * to modify page.
+ *
+ * innerHTML is non-standard IE extension, though supported by most
+ * browsers; however Firefox up to version 1.5 didn't implement it in
+ * a strict mode (application/xml+xhtml mimetype).
+ *
+ * Also my simple benchmarks show that using elem.firstChild.data =
+ * 'content' is slightly faster than elem.innerHTML = 'content'.  It
+ * is however more fragile (text element fragment must exists), and
+ * less feature-rich (we cannot add HTML).
+ *
+ * Note that DOM 2 HTML is preferred over generic DOM 2 Core; the
+ * equivalent using DOM 2 Core is usually shown in comments.
+ */
+
+
+/* ============================================================ */
+/* generic utility functions */
+
+
+/**
+ * pad number N with nonbreakable spaces on the left, to WIDTH characters
+ * example: padLeftStr(12, 3, '\u00A0') == '\u00A012'
+ *          ('\u00A0' is nonbreakable space)
+ *
+ * @param {Number|String} input: number to pad
+ * @param {Number} width: visible width of output
+ * @param {String} str: string to prefix to string, e.g. '\u00A0'
+ * @returns {String} INPUT prefixed with (WIDTH - INPUT.length) x STR
+ */
+function padLeftStr(input, width, str) {
+       var prefix = '';
+
+       width -= input.toString().length;
+       while (width > 0) {
+               prefix += str;
+               width--;
+       }
+       return prefix + input;
+}
+
+/**
+ * Pad INPUT on the left to SIZE width, using given padding character CH,
+ * for example padLeft('a', 3, '_') is '__a'.
+ *
+ * @param {String} input: input value converted to string.
+ * @param {Number} width: desired length of output.
+ * @param {String} ch: single character to prefix to string.
+ *
+ * @returns {String} Modified string, at least SIZE length.
+ */
+function padLeft(input, width, ch) {
+       var s = input + "";
+       while (s.length < width) {
+               s = ch + s;
+       }
+       return s;
+}
+
+/**
+ * Create XMLHttpRequest object in cross-browser way
+ * @returns XMLHttpRequest object, or null
+ */
+function createRequestObject() {
+       try {
+               return new XMLHttpRequest();
+       } catch (e) {}
+       try {
+               return window.createRequest();
+       } catch (e) {}
+       try {
+               return new ActiveXObject("Msxml2.XMLHTTP");
+       } catch (e) {}
+       try {
+               return new ActiveXObject("Microsoft.XMLHTTP");
+       } catch (e) {}
+
+       return null;
+}
+
+
+/* ============================================================ */
+/* utility/helper functions (and variables) */
+
+var xhr;        // XMLHttpRequest object
+var projectUrl; // partial query + separator ('?' or ';')
+
+// 'commits' is an associative map. It maps SHA1s to Commit objects.
+var commits = {};
+
+/**
+ * constructor for Commit objects, used in 'blame'
+ * @class Represents a blamed commit
+ * @param {String} sha1: SHA-1 identifier of a commit
+ */
+function Commit(sha1) {
+       if (this instanceof Commit) {
+               this.sha1 = sha1;
+               this.nprevious = 0; /* number of 'previous', effective parents */
+       } else {
+               return new Commit(sha1);
+       }
+}
+
+/* ............................................................ */
+/* progress info, timing, error reporting */
+
+var blamedLines = 0;
+var totalLines  = '???';
+var div_progress_bar;
+var div_progress_info;
+
+/**
+ * Detects how many lines does a blamed file have,
+ * This information is used in progress info
+ *
+ * @returns {Number|String} Number of lines in file, or string '...'
+ */
+function countLines() {
+       var table =
+               document.getElementById('blame_table') ||
+               document.getElementsByTagName('table')[0];
+
+       if (table) {
+               return table.getElementsByTagName('tr').length - 1; // for header
+       } else {
+               return '...';
+       }
+}
+
+/**
+ * update progress info and length (width) of progress bar
+ *
+ * @globals div_progress_info, div_progress_bar, blamedLines, totalLines
+ */
+function updateProgressInfo() {
+       if (!div_progress_info) {
+               div_progress_info = document.getElementById('progress_info');
+       }
+       if (!div_progress_bar) {
+               div_progress_bar = document.getElementById('progress_bar');
+       }
+       if (!div_progress_info && !div_progress_bar) {
+               return;
+       }
+
+       var percentage = Math.floor(100.0*blamedLines/totalLines);
+
+       if (div_progress_info) {
+               div_progress_info.firstChild.data  = blamedLines + ' / ' + totalLines +
+                       ' (' + padLeftStr(percentage, 3, '\u00A0') + '%)';
+       }
+
+       if (div_progress_bar) {
+               //div_progress_bar.setAttribute('style', 'width: '+percentage+'%;');
+               div_progress_bar.style.width = percentage + '%';
+       }
+}
+
+
+var t_interval_server = '';
+var cmds_server = '';
+var t0 = new Date();
+
+/**
+ * write how much it took to generate data, and to run script
+ *
+ * @globals t0, t_interval_server, cmds_server
+ */
+function writeTimeInterval() {
+       var info_time = document.getElementById('generating_time');
+       if (!info_time || !t_interval_server) {
+               return;
+       }
+       var t1 = new Date();
+       info_time.firstChild.data += ' + (' +
+               t_interval_server + ' sec server blame_data / ' +
+               (t1.getTime() - t0.getTime())/1000 + ' sec client JavaScript)';
+
+       var info_cmds = document.getElementById('generating_cmd');
+       if (!info_time || !cmds_server) {
+               return;
+       }
+       info_cmds.firstChild.data += ' + ' + cmds_server;
+}
+
+/**
+ * show an error message alert to user within page (in prohress info area)
+ * @param {String} str: plain text error message (no HTML)
+ *
+ * @globals div_progress_info
+ */
+function errorInfo(str) {
+       if (!div_progress_info) {
+               div_progress_info = document.getElementById('progress_info');
+       }
+       if (div_progress_info) {
+               div_progress_info.className = 'error';
+               div_progress_info.firstChild.data = str;
+       }
+}
+
+/* ............................................................ */
+/* coloring rows during blame_data (git blame --incremental) run */
+
+/**
+ * used to extract N from 'colorN', where N is a number,
+ * @constant
+ */
+var colorRe = /\bcolor([0-9]*)\b/;
+
+/**
+ * return N if <tr class="colorN">, otherwise return null
+ * (some browsers require CSS class names to begin with letter)
+ *
+ * @param {HTMLElement} tr: table row element to check
+ * @param {String} tr.className: 'class' attribute of tr element
+ * @returns {Number|null} N if tr.className == 'colorN', otherwise null
+ *
+ * @globals colorRe
+ */
+function getColorNo(tr) {
+       if (!tr) {
+               return null;
+       }
+       var className = tr.className;
+       if (className) {
+               var match = colorRe.exec(className);
+               if (match) {
+                       return parseInt(match[1], 10);
+               }
+       }
+       return null;
+}
+
+var colorsFreq = [0, 0, 0];
+/**
+ * return one of given possible colors (curently least used one)
+ * example: chooseColorNoFrom(2, 3) returns 2 or 3
+ *
+ * @param {Number[]} arguments: one or more numbers
+ *        assumes that  1 <= arguments[i] <= colorsFreq.length
+ * @returns {Number} Least used color number from arguments
+ * @globals colorsFreq
+ */
+function chooseColorNoFrom() {
+       // choose the color which is least used
+       var colorNo = arguments[0];
+       for (var i = 1; i < arguments.length; i++) {
+               if (colorsFreq[arguments[i]-1] < colorsFreq[colorNo-1]) {
+                       colorNo = arguments[i];
+               }
+       }
+       colorsFreq[colorNo-1]++;
+       return colorNo;
+}
+
+/**
+ * given two neigbour <tr> elements, find color which would be different
+ * from color of both of neighbours; used to 3-color blame table
+ *
+ * @param {HTMLElement} tr_prev
+ * @param {HTMLElement} tr_next
+ * @returns {Number} color number N such that
+ * colorN != tr_prev.className && colorN != tr_next.className
+ */
+function findColorNo(tr_prev, tr_next) {
+       var color_prev = getColorNo(tr_prev);
+       var color_next = getColorNo(tr_next);
+
+
+       // neither of neighbours has color set
+       // THEN we can use any of 3 possible colors
+       if (!color_prev && !color_next) {
+               return chooseColorNoFrom(1,2,3);
+       }
+
+       // either both neighbours have the same color,
+       // or only one of neighbours have color set
+       // THEN we can use any color except given
+       var color;
+       if (color_prev === color_next) {
+               color = color_prev; // = color_next;
+       } else if (!color_prev) {
+               color = color_next;
+       } else if (!color_next) {
+               color = color_prev;
+       }
+       if (color) {
+               return chooseColorNoFrom((color % 3) + 1, ((color+1) % 3) + 1);
+       }
+
+       // neighbours have different colors
+       // THEN there is only one color left
+       return (3 - ((color_prev + color_next) % 3));
+}
+
+/* ............................................................ */
+/* coloring rows like 'blame' after 'blame_data' finishes */
+
+/**
+ * returns true if given row element (tr) is first in commit group
+ * to be used only after 'blame_data' finishes (after processing)
+ *
+ * @param {HTMLElement} tr: table row
+ * @returns {Boolean} true if TR is first in commit group
+ */
+function isStartOfGroup(tr) {
+       return tr.firstChild.className === 'sha1';
+}
+
+/**
+ * change colors to use zebra coloring (2 colors) instead of 3 colors
+ * concatenate neighbour commit groups belonging to the same commit
+ *
+ * @globals colorRe
+ */
+function fixColorsAndGroups() {
+       var colorClasses = ['light', 'dark'];
+       var linenum = 1;
+       var tr, prev_group;
+       var colorClass = 0;
+       var table =
+               document.getElementById('blame_table') ||
+               document.getElementsByTagName('table')[0];
+
+       while ((tr = document.getElementById('l'+linenum))) {
+       // index origin is 0, which is table header; start from 1
+       //while ((tr = table.rows[linenum])) { // <- it is slower
+               if (isStartOfGroup(tr, linenum, document)) {
+                       if (prev_group &&
+                           prev_group.firstChild.firstChild.href ===
+                                   tr.firstChild.firstChild.href) {
+                               // we have to concatenate groups
+                               var prev_rows = prev_group.firstChild.rowSpan || 1;
+                               var curr_rows =         tr.firstChild.rowSpan || 1;
+                               prev_group.firstChild.rowSpan = prev_rows + curr_rows;
+                               //tr.removeChild(tr.firstChild);
+                               tr.deleteCell(0); // DOM2 HTML way
+                       } else {
+                               colorClass = (colorClass + 1) % 2;
+                               prev_group = tr;
+                       }
+               }
+               var tr_class = tr.className;
+               tr.className = tr_class.replace(colorRe, colorClasses[colorClass]);
+               linenum++;
+       }
+}
+
+/* ............................................................ */
+/* time and data */
+
+/**
+ * used to extract hours and minutes from timezone info, e.g '-0900'
+ * @constant
+ */
+var tzRe = /^([+-][0-9][0-9])([0-9][0-9])$/;
+
+/**
+ * return date in local time formatted in iso-8601 like format
+ * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200'
+ *
+ * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC'
+ * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM'
+ * @returns {String} date in local time in iso-8601 like format
+ *
+ * @globals tzRe
+ */
+function formatDateISOLocal(epoch, timezoneInfo) {
+       var match = tzRe.exec(timezoneInfo);
+       // date corrected by timezone
+       var localDate = new Date(1000 * (epoch +
+               (parseInt(match[1],10)*3600 + parseInt(match[2],10)*60)));
+       var localDateStr = // e.g. '2005-08-07'
+               localDate.getUTCFullYear()                 + '-' +
+               padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' +
+               padLeft(localDate.getUTCDate(),    2, '0');
+       var localTimeStr = // e.g. '21:49:46'
+               padLeft(localDate.getUTCHours(),   2, '0') + ':' +
+               padLeft(localDate.getUTCMinutes(), 2, '0') + ':' +
+               padLeft(localDate.getUTCSeconds(), 2, '0');
+
+       return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo;
+}
+
+/* ............................................................ */
+/* unquoting/unescaping filenames */
+
+/**#@+
+ * @constant
+ */
+var escCodeRe = /\\([^0-7]|[0-7]{1,3})/g;
+var octEscRe = /^[0-7]{1,3}$/;
+var maybeQuotedRe = /^\"(.*)\"$/;
+/**#@-*/
+
+/**
+ * unquote maybe git-quoted filename
+ * e.g. 'aa' -> 'aa', '"a\ta"' -> 'a   a'
+ *
+ * @param {String} str: git-quoted string
+ * @returns {String} Unquoted and unescaped string
+ *
+ * @globals escCodeRe, octEscRe, maybeQuotedRe
+ */
+function unquote(str) {
+       function unq(seq) {
+               var es = {
+                       // character escape codes, aka escape sequences (from C)
+                       // replacements are to some extent JavaScript specific
+                       t: "\t",   // tab            (HT, TAB)
+                       n: "\n",   // newline        (NL)
+                       r: "\r",   // return         (CR)
+                       f: "\f",   // form feed      (FF)
+                       b: "\b",   // backspace      (BS)
+                       a: "\x07", // alarm (bell)   (BEL)
+                       e: "\x1B", // escape         (ESC)
+                       v: "\v"    // vertical tab   (VT)
+               };
+
+               if (seq.search(octEscRe) !== -1) {
+                       // octal char sequence
+                       return String.fromCharCode(parseInt(seq, 8));
+               } else if (seq in es) {
+                       // C escape sequence, aka character escape code
+                       return es[seq];
+               }
+               // quoted ordinary character
+               return seq;
+       }
+
+       var match = str.match(maybeQuotedRe);
+       if (match) {
+               str = match[1];
+               // perhaps str = eval('"'+str+'"'); would be enough?
+               str = str.replace(escCodeRe,
+                       function (substr, p1, offset, s) { return unq(p1); });
+       }
+       return str;
+}
+
+/* ============================================================ */
+/* main part: parsing response */
+
+/**
+ * Function called for each blame entry, as soon as it finishes.
+ * It updates page via DOM manipulation, adding sha1 info, etc.
+ *
+ * @param {Commit} commit: blamed commit
+ * @param {Object} group: object representing group of lines,
+ *                        which blame the same commit (blame entry)
+ *
+ * @globals blamedLines
+ */
+function handleLine(commit, group) {
+       /*
+          This is the structure of the HTML fragment we are working
+          with:
+
+          <tr id="l123" class="">
+            <td class="sha1" title=""><a href=""> </a></td>
+            <td class="linenr"><a class="linenr" href="">123</a></td>
+            <td class="pre"># times (my ext3 doesn&#39;t).</td>
+          </tr>
+       */
+
+       var resline = group.resline;
+
+       // format date and time string only once per commit
+       if (!commit.info) {
+               /* e.g. 'Kay Sievers, 2005-08-07 21:49:46 +0200' */
+               commit.info = commit.author + ', ' +
+                       formatDateISOLocal(commit.authorTime, commit.authorTimezone);
+       }
+
+       // color depends on group of lines, not only on blamed commit
+       var colorNo = findColorNo(
+               document.getElementById('l'+(resline-1)),
+               document.getElementById('l'+(resline+group.numlines))
+       );
+
+       // loop over lines in commit group
+       for (var i = 0; i < group.numlines; i++, resline++) {
+               var tr = document.getElementById('l'+resline);
+               if (!tr) {
+                       break;
+               }
+               /*
+                       <tr id="l123" class="">
+                         <td class="sha1" title=""><a href=""> </a></td>
+                         <td class="linenr"><a class="linenr" href="">123</a></td>
+                         <td class="pre"># times (my ext3 doesn&#39;t).</td>
+                       </tr>
+               */
+               var td_sha1  = tr.firstChild;
+               var a_sha1   = td_sha1.firstChild;
+               var a_linenr = td_sha1.nextSibling.firstChild;
+
+               /* <tr id="l123" class=""> */
+               var tr_class = '';
+               if (colorNo !== null) {
+                       tr_class = 'color'+colorNo;
+               }
+               if (commit.boundary) {
+                       tr_class += ' boundary';
+               }
+               if (commit.nprevious === 0) {
+                       tr_class += ' no-previous';
+               } else if (commit.nprevious > 1) {
+                       tr_class += ' multiple-previous';
+               }
+               tr.className = tr_class;
+
+               /* <td class="sha1" title="?" rowspan="?"><a href="?">?</a></td> */
+               if (i === 0) {
+                       td_sha1.title = commit.info;
+                       td_sha1.rowSpan = group.numlines;
+
+                       a_sha1.href = projectUrl + 'a=commit;h=' + commit.sha1;
+                       if (a_sha1.firstChild) {
+                               a_sha1.firstChild.data = commit.sha1.substr(0, 8);
+                       } else {
+                               a_sha1.appendChild(
+                                       document.createTextNode(commit.sha1.substr(0, 8)));
+                       }
+                       if (group.numlines >= 2) {
+                               var fragment = document.createDocumentFragment();
+                               var br   = document.createElement("br");
+                               var match = commit.author.match(/\b([A-Z])\B/g);
+                               if (match) {
+                                       var text = document.createTextNode(
+                                                       match.join(''));
+                               }
+                               if (br && text) {
+                                       var elem = fragment || td_sha1;
+                                       elem.appendChild(br);
+                                       elem.appendChild(text);
+                                       if (fragment) {
+                                               td_sha1.appendChild(fragment);
+                                       }
+                               }
+                       }
+               } else {
+                       //tr.removeChild(td_sha1); // DOM2 Core way
+                       tr.deleteCell(0); // DOM2 HTML way
+               }
+
+               /* <td class="linenr"><a class="linenr" href="?">123</a></td> */
+               var linenr_commit =
+                       ('previous' in commit ? commit.previous : commit.sha1);
+               var linenr_filename =
+                       ('file_parent' in commit ? commit.file_parent : commit.filename);
+               a_linenr.href = projectUrl + 'a=blame_incremental' +
+                       ';hb=' + linenr_commit +
+                       ';f='  + encodeURIComponent(linenr_filename) +
+                       '#l' + (group.srcline + i);
+
+               blamedLines++;
+
+               //updateProgressInfo();
+       }
+}
+
+// ----------------------------------------------------------------------
+
+var inProgress = false;   // are we processing response
+
+/**#@+
+ * @constant
+ */
+var sha1Re = /^([0-9a-f]{40}) ([0-9]+) ([0-9]+) ([0-9]+)/;
+var infoRe = /^([a-z-]+) ?(.*)/;
+var endRe  = /^END ?([^ ]*) ?(.*)/;
+/**@-*/
+
+var curCommit = new Commit();
+var curGroup  = {};
+
+var pollTimer = null;
+
+/**
+ * Parse output from 'git blame --incremental [...]', received via
+ * XMLHttpRequest from server (blamedataUrl), and call handleLine
+ * (which updates page) as soon as blame entry is completed.
+ *
+ * @param {String[]} lines: new complete lines from blamedata server
+ *
+ * @globals commits, curCommit, curGroup, t_interval_server, cmds_server
+ * @globals sha1Re, infoRe, endRe
+ */
+function processBlameLines(lines) {
+       var match;
+
+       for (var i = 0, len = lines.length; i < len; i++) {
+
+               if ((match = sha1Re.exec(lines[i]))) {
+                       var sha1 = match[1];
+                       var srcline  = parseInt(match[2], 10);
+                       var resline  = parseInt(match[3], 10);
+                       var numlines = parseInt(match[4], 10);
+
+                       var c = commits[sha1];
+                       if (!c) {
+                               c = new Commit(sha1);
+                               commits[sha1] = c;
+                       }
+                       curCommit = c;
+
+                       curGroup.srcline = srcline;
+                       curGroup.resline = resline;
+                       curGroup.numlines = numlines;
+
+               } else if ((match = infoRe.exec(lines[i]))) {
+                       var info = match[1];
+                       var data = match[2];
+                       switch (info) {
+                       case 'filename':
+                               curCommit.filename = unquote(data);
+                               // 'filename' information terminates the entry
+                               handleLine(curCommit, curGroup);
+                               updateProgressInfo();
+                               break;
+                       case 'author':
+                               curCommit.author = data;
+                               break;
+                       case 'author-time':
+                               curCommit.authorTime = parseInt(data, 10);
+                               break;
+                       case 'author-tz':
+                               curCommit.authorTimezone = data;
+                               break;
+                       case 'previous':
+                               curCommit.nprevious++;
+                               // store only first 'previous' header
+                               if (!'previous' in curCommit) {
+                                       var parts = data.split(' ', 2);
+                                       curCommit.previous    = parts[0];
+                                       curCommit.file_parent = unquote(parts[1]);
+                               }
+                               break;
+                       case 'boundary':
+                               curCommit.boundary = true;
+                               break;
+                       } // end switch
+
+               } else if ((match = endRe.exec(lines[i]))) {
+                       t_interval_server = match[1];
+                       cmds_server = match[2];
+
+               } else if (lines[i] !== '') {
+                       // malformed line
+
+               } // end if (match)
+
+       } // end for (lines)
+}
+
+/**
+ * Process new data and return pointer to end of processed part
+ *
+ * @param {String} unprocessed: new data (from nextReadPos)
+ * @param {Number} nextReadPos: end of last processed data
+ * @return {Number} end of processed data (new value for nextReadPos)
+ */
+function processData(unprocessed, nextReadPos) {
+       var lastLineEnd = unprocessed.lastIndexOf('\n');
+       if (lastLineEnd !== -1) {
+               var lines = unprocessed.substring(0, lastLineEnd).split('\n');
+               nextReadPos += lastLineEnd + 1 /* 1 == '\n'.length */;
+
+               processBlameLines(lines);
+       } // end if
+
+       return nextReadPos;
+}
+
+/**
+ * Handle XMLHttpRequest errors
+ *
+ * @param {XMLHttpRequest} xhr: XMLHttpRequest object
+ *
+ * @globals pollTimer, commits, inProgress
+ */
+function handleError(xhr) {
+       errorInfo('Server error: ' +
+               xhr.status + ' - ' + (xhr.statusText || 'Error contacting server'));
+
+       clearInterval(pollTimer);
+       commits = {}; // free memory
+
+       inProgress = false;
+}
+
+/**
+ * Called after XMLHttpRequest finishes (loads)
+ *
+ * @param {XMLHttpRequest} xhr: XMLHttpRequest object (unused)
+ *
+ * @globals pollTimer, commits, inProgress
+ */
+function responseLoaded(xhr) {
+       clearInterval(pollTimer);
+
+       fixColorsAndGroups();
+       writeTimeInterval();
+       commits = {}; // free memory
+
+       inProgress = false;
+}
+
+/**
+ * handler for XMLHttpRequest onreadystatechange event
+ * @see startBlame
+ *
+ * @globals xhr, inProgress
+ */
+function handleResponse() {
+
+       /*
+        * xhr.readyState
+        *
+        *  Value  Constant (W3C)    Description
+        *  -------------------------------------------------------------------
+        *  0      UNSENT            open() has not been called yet.
+        *  1      OPENED            send() has not been called yet.
+        *  2      HEADERS_RECEIVED  send() has been called, and headers
+        *                           and status are available.
+        *  3      LOADING           Downloading; responseText holds partial data.
+        *  4      DONE              The operation is complete.
+        */
+
+       if (xhr.readyState !== 4 && xhr.readyState !== 3) {
+               return;
+       }
+
+       // the server returned error
+       // try ... catch block is to work around bug in IE8
+       try {
+               if (xhr.readyState === 3 && xhr.status !== 200) {
+                       return;
+               }
+       } catch (e) {
+               return;
+       }
+       if (xhr.readyState === 4 && xhr.status !== 200) {
+               handleError(xhr);
+               return;
+       }
+
+       // In konqueror xhr.responseText is sometimes null here...
+       if (xhr.responseText === null) {
+               return;
+       }
+
+       // in case we were called before finished processing
+       if (inProgress) {
+               return;
+       } else {
+               inProgress = true;
+       }
+
+       // extract new whole (complete) lines, and process them
+       while (xhr.prevDataLength !== xhr.responseText.length) {
+               if (xhr.readyState === 4 &&
+                   xhr.prevDataLength === xhr.responseText.length) {
+                       break;
+               }
+
+               xhr.prevDataLength = xhr.responseText.length;
+               var unprocessed = xhr.responseText.substring(xhr.nextReadPos);
+               xhr.nextReadPos = processData(unprocessed, xhr.nextReadPos);
+       } // end while
+
+       // did we finish work?
+       if (xhr.readyState === 4 &&
+           xhr.prevDataLength === xhr.responseText.length) {
+               responseLoaded(xhr);
+       }
+
+       inProgress = false;
+}
+
+// ============================================================
+// ------------------------------------------------------------
+
+/**
+ * Incrementally update line data in blame_incremental view in gitweb.
+ *
+ * @param {String} blamedataUrl: URL to server script generating blame data.
+ * @param {String} bUrl: partial URL to project, used to generate links.
+ *
+ * Called from 'blame_incremental' view after loading table with
+ * file contents, a base for blame view.
+ *
+ * @globals xhr, t0, projectUrl, div_progress_bar, totalLines, pollTimer
+*/
+function startBlame(blamedataUrl, bUrl) {
+
+       xhr = createRequestObject();
+       if (!xhr) {
+               errorInfo('ERROR: XMLHttpRequest not supported');
+               return;
+       }
+
+       t0 = new Date();
+       projectUrl = bUrl + (bUrl.indexOf('?') === -1 ? '?' : ';');
+       if ((div_progress_bar = document.getElementById('progress_bar'))) {
+               //div_progress_bar.setAttribute('style', 'width: 100%;');
+               div_progress_bar.style.cssText = 'width: 100%;';
+       }
+       totalLines = countLines();
+       updateProgressInfo();
+
+       /* add extra properties to xhr object to help processing response */
+       xhr.prevDataLength = -1;  // used to detect if we have new data
+       xhr.nextReadPos = 0;      // where unread part of response starts
+
+       xhr.onreadystatechange = handleResponse;
+       //xhr.onreadystatechange = function () { handleResponse(xhr); };
+
+       xhr.open('GET', blamedataUrl);
+       xhr.setRequestHeader('Accept', 'text/plain');
+       xhr.send(null);
+
+       // not all browsers call onreadystatechange event on each server flush
+       // poll response using timer every second to handle this issue
+       pollTimer = setInterval(xhr.onreadystatechange, 1000);
+}
+
+// end of gitweb.js
diff --git a/graph.c b/graph.c
index 06fbeb6c2416d0c1c5fd7df1a12924656592cdc3..f1a63c2241a30cce0141b87b5e8005b1a899a8ef 100644 (file)
--- a/graph.c
+++ b/graph.c
@@ -7,17 +7,6 @@
 
 /* Internal API */
 
-/*
- * Output the next line for a graph.
- * This formats the next graph line into the specified strbuf.  It is not
- * terminated with a newline.
- *
- * Returns 1 if the line includes the current commit, and 0 otherwise.
- * graph_next_line() will return 1 exactly once for each time
- * graph_update() is called.
- */
-static int graph_next_line(struct git_graph *graph, struct strbuf *sb);
-
 /*
  * Output a padding line in the graph.
  * This is similar to graph_next_line().  However, it is guaranteed to
@@ -47,20 +36,6 @@ static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb);
  * - Limit the number of columns, similar to the way gitk does.
  *   If we reach more than a specified number of columns, omit
  *   sections of some columns.
- *
- * - The output during the GRAPH_PRE_COMMIT and GRAPH_COLLAPSING states
- *   could be made more compact by printing horizontal lines, instead of
- *   long diagonal lines.  For example, during collapsing, something like
- *   this:          instead of this:
- *   | | | | |      | | | | |
- *   | |_|_|/       | | | |/
- *   |/| | |        | | |/|
- *   | | | |        | |/| |
- *                  |/| | |
- *                  | | | |
- *
- *   If there are several parallel diagonal lines, they will need to be
- *   replaced with horizontal lines on subsequent rows.
  */
 
 struct column {
@@ -87,36 +62,46 @@ enum graph_state {
 /*
  * The list of available column colors.
  */
-static char column_colors[][COLOR_MAXLEN] = {
+static const char *column_colors_ansi[] = {
        GIT_COLOR_RED,
        GIT_COLOR_GREEN,
        GIT_COLOR_YELLOW,
        GIT_COLOR_BLUE,
        GIT_COLOR_MAGENTA,
        GIT_COLOR_CYAN,
-       GIT_COLOR_BOLD GIT_COLOR_RED,
-       GIT_COLOR_BOLD GIT_COLOR_GREEN,
-       GIT_COLOR_BOLD GIT_COLOR_YELLOW,
-       GIT_COLOR_BOLD GIT_COLOR_BLUE,
-       GIT_COLOR_BOLD GIT_COLOR_MAGENTA,
-       GIT_COLOR_BOLD GIT_COLOR_CYAN,
+       GIT_COLOR_BOLD_RED,
+       GIT_COLOR_BOLD_GREEN,
+       GIT_COLOR_BOLD_YELLOW,
+       GIT_COLOR_BOLD_BLUE,
+       GIT_COLOR_BOLD_MAGENTA,
+       GIT_COLOR_BOLD_CYAN,
+       GIT_COLOR_RESET,
 };
 
-#define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors))
+#define COLUMN_COLORS_ANSI_MAX (ARRAY_SIZE(column_colors_ansi) - 1)
+
+static const char **column_colors;
+static unsigned short column_colors_max;
+
+void graph_set_column_colors(const char **colors, unsigned short colors_max)
+{
+       column_colors = colors;
+       column_colors_max = colors_max;
+}
 
-static const char *column_get_color_code(const struct column *c)
+static const char *column_get_color_code(unsigned short color)
 {
-       return column_colors[c->color];
+       return column_colors[color];
 }
 
 static void strbuf_write_column(struct strbuf *sb, const struct column *c,
                                char col_char)
 {
-       if (c->color < COLUMN_COLORS_MAX)
-               strbuf_addstr(sb, column_get_color_code(c));
+       if (c->color < column_colors_max)
+               strbuf_addstr(sb, column_get_color_code(c->color));
        strbuf_addch(sb, col_char);
-       if (c->color < COLUMN_COLORS_MAX)
-               strbuf_addstr(sb, GIT_COLOR_RESET);
+       if (c->color < column_colors_max)
+               strbuf_addstr(sb, column_get_color_code(column_colors_max));
 }
 
 struct git_graph {
@@ -225,9 +210,26 @@ struct git_graph {
        unsigned short default_column_color;
 };
 
+static struct strbuf *diff_output_prefix_callback(struct diff_options *opt, void *data)
+{
+       struct git_graph *graph = data;
+       static struct strbuf msgbuf = STRBUF_INIT;
+
+       assert(graph);
+
+       strbuf_reset(&msgbuf);
+       graph_padding_line(graph, &msgbuf);
+       return &msgbuf;
+}
+
 struct git_graph *graph_init(struct rev_info *opt)
 {
        struct git_graph *graph = xmalloc(sizeof(struct git_graph));
+
+       if (!column_colors)
+               graph_set_column_colors(column_colors_ansi,
+                                       COLUMN_COLORS_ANSI_MAX);
+
        graph->commit = NULL;
        graph->revs = opt;
        graph->num_parents = 0;
@@ -239,7 +241,12 @@ struct git_graph *graph_init(struct rev_info *opt)
        graph->num_columns = 0;
        graph->num_new_columns = 0;
        graph->mapping_size = 0;
-       graph->default_column_color = 0;
+       /*
+        * Start the column color at the maximum value, since we'll
+        * always increment it for the first commit we output.
+        * This way we start at 0 for the first commit.
+        */
+       graph->default_column_color = column_colors_max - 1;
 
        /*
         * Allocate a reasonably large default number of columns
@@ -253,6 +260,13 @@ struct git_graph *graph_init(struct rev_info *opt)
        graph->mapping = xmalloc(sizeof(int) * 2 * graph->column_capacity);
        graph->new_mapping = xmalloc(sizeof(int) * 2 * graph->column_capacity);
 
+       /*
+        * The diff output prefix callback, with this we can make
+        * all the diff output to align with the graph lines.
+        */
+       opt->diffopt.output_prefix = diff_output_prefix_callback;
+       opt->diffopt.output_prefix_data = graph;
+
        return graph;
 }
 
@@ -300,9 +314,10 @@ static int graph_is_interesting(struct git_graph *graph, struct commit *commit)
        }
 
        /*
-        * Uninteresting and pruned commits won't be printed
+        * Otherwise, use get_commit_action() to see if this commit is
+        * interesting
         */
-       return (commit->object.flags & (UNINTERESTING | TREESAME)) ? 0 : 1;
+       return get_commit_action(graph->revs, commit) == commit_show;
 }
 
 static struct commit_list *next_interesting_parent(struct git_graph *graph,
@@ -354,7 +369,7 @@ static struct commit_list *first_interesting_parent(struct git_graph *graph)
 static unsigned short graph_get_current_column_color(const struct git_graph *graph)
 {
        if (!DIFF_OPT_TST(&graph->revs->diffopt, COLOR_DIFF))
-               return COLUMN_COLORS_MAX;
+               return column_colors_max;
        return graph->default_column_color;
 }
 
@@ -364,7 +379,7 @@ static unsigned short graph_get_current_column_color(const struct git_graph *gra
 static void graph_increment_column_color(struct git_graph *graph)
 {
        graph->default_column_color = (graph->default_column_color + 1) %
-               COLUMN_COLORS_MAX;
+               column_colors_max;
 }
 
 static unsigned short graph_find_commit_color(const struct git_graph *graph,
@@ -428,7 +443,7 @@ static void graph_update_width(struct git_graph *graph,
                max_cols++;
 
        /*
-        * We added a column for the the current commit as part of
+        * We added a column for the current commit as part of
         * graph->num_parents.  If the current commit was already in
         * graph->columns, then we have double counted it.
         */
@@ -513,11 +528,14 @@ static void graph_update_columns(struct git_graph *graph)
                             parent;
                             parent = next_interesting_parent(graph, parent)) {
                                /*
-                                * If this is a merge increment the current
+                                * If this is a merge, or the start of a new
+                                * childless column, increment the current
                                 * color.
                                 */
-                               if (graph->num_parents > 1)
+                               if (graph->num_parents > 1 ||
+                                   !is_commit_in_columns) {
                                        graph_increment_column_color(graph);
+                               }
                                graph_insert_into_new_columns(graph,
                                                              parent->item,
                                                              &mapping_idx);
@@ -907,7 +925,7 @@ static struct column *find_new_column_by_commit(struct git_graph *graph,
                if (graph->new_columns[i].commit == commit)
                        return &graph->new_columns[i];
        }
-       return 0;
+       return NULL;
 }
 
 static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)
@@ -982,6 +1000,9 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf
 {
        int i;
        int *tmp_mapping;
+       short used_horizontal = 0;
+       int horizontal_edge = -1;
+       int horizontal_edge_target = -1;
 
        /*
         * Clear out the new_mapping array
@@ -1019,6 +1040,23 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf
                         * Move to the left by one
                         */
                        graph->new_mapping[i - 1] = target;
+                       /*
+                        * If there isn't already an edge moving horizontally
+                        * select this one.
+                        */
+                       if (horizontal_edge == -1) {
+                               int j;
+                               horizontal_edge = i;
+                               horizontal_edge_target = target;
+                               /*
+                                * The variable target is the index of the graph
+                                * column, and therefore target*2+3 is the
+                                * actual screen column of the first horizontal
+                                * line.
+                                */
+                               for (j = (target * 2)+3; j < (i - 2); j += 2)
+                                       graph->new_mapping[j] = target;
+                       }
                } else if (graph->new_mapping[i - 1] == target) {
                        /*
                         * There is a branch line to our left
@@ -1039,10 +1077,21 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf
                         *
                         * The space just to the left of this
                         * branch should always be empty.
+                        *
+                        * The branch to the left of that space
+                        * should be our eventual target.
                         */
                        assert(graph->new_mapping[i - 1] > target);
                        assert(graph->new_mapping[i - 2] < 0);
+                       assert(graph->new_mapping[i - 3] == target);
                        graph->new_mapping[i - 2] = target;
+                       /*
+                        * Mark this branch as the horizontal edge to
+                        * prevent any other edges from moving
+                        * horizontally.
+                        */
+                       if (horizontal_edge == -1)
+                               horizontal_edge = i;
                }
        }
 
@@ -1061,8 +1110,23 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf
                        strbuf_addch(sb, ' ');
                else if (target * 2 == i)
                        strbuf_write_column(sb, &graph->new_columns[target], '|');
-               else
+               else if (target == horizontal_edge_target &&
+                        i != horizontal_edge - 1) {
+                               /*
+                                * Set the mappings for all but the
+                                * first segment to -1 so that they
+                                * won't continue into the next line.
+                                */
+                               if (i != (target * 2)+3)
+                                       graph->new_mapping[i] = -1;
+                               used_horizontal = 1;
+                       strbuf_write_column(sb, &graph->new_columns[target], '_');
+               } else {
+                       if (used_horizontal && i < horizontal_edge)
+                               graph->new_mapping[i] = -1;
                        strbuf_write_column(sb, &graph->new_columns[target], '/');
+
+               }
        }
 
        graph_pad_horizontally(graph, sb, graph->mapping_size);
@@ -1083,7 +1147,7 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf
                graph_update_state(graph, GRAPH_PADDING);
 }
 
-static int graph_next_line(struct git_graph *graph, struct strbuf *sb)
+int graph_next_line(struct git_graph *graph, struct strbuf *sb)
 {
        switch (graph->state) {
        case GRAPH_PADDING:
diff --git a/graph.h b/graph.h
index bc30d687c0dbfc06d459948788784b3d8ab1b5ff..aff960c7e8f63f49e3ec16afb0b891cf257ce999 100644 (file)
--- a/graph.h
+++ b/graph.h
@@ -4,9 +4,25 @@
 /* A graph is a pointer to this opaque structure */
 struct git_graph;
 
+/*
+ * Set up a custom scheme for column colors.
+ *
+ * The default column color scheme inserts ANSI color escapes to colorize
+ * the graph. The various color escapes are stored in an array of strings
+ * where each entry corresponds to a color, except for the last entry,
+ * which denotes the escape for resetting the color back to the default.
+ * When generating the graph, strings from this array are inserted before
+ * and after the various column characters.
+ *
+ * This function allows you to enable a custom array of color escapes.
+ * The 'colors_max' argument is the index of the last "reset" entry.
+ *
+ * This functions must be called BEFORE graph_init() is called.
+ */
+void graph_set_column_colors(const char **colors, unsigned short colors_max);
+
 /*
  * Create a new struct git_graph.
- * The graph should be freed with graph_release() when no longer needed.
  */
 struct git_graph *graph_init(struct rev_info *opt);
 
@@ -33,6 +49,17 @@ void graph_update(struct git_graph *graph, struct commit *commit);
  */
 int graph_is_commit_finished(struct git_graph const *graph);
 
+/*
+ * Output the next line for a graph.
+ * This formats the next graph line into the specified strbuf.  It is not
+ * terminated with a newline.
+ *
+ * Returns 1 if the line includes the current commit, and 0 otherwise.
+ * graph_next_line() will return 1 exactly once for each time
+ * graph_update() is called.
+ */
+int graph_next_line(struct git_graph *graph, struct strbuf *sb);
+
 
 /*
  * graph_show_*: helper functions for printing to stdout
diff --git a/grep.c b/grep.c
index 04c777a20c1a8c10417cc9d44e53e5b99dc32a27..82fb349be6f6ef8211d4828101cd8e88bcbb304f 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -1,25 +1,34 @@
 #include "cache.h"
 #include "grep.h"
+#include "userdiff.h"
 #include "xdiff-interface.h"
 
 void append_header_grep_pattern(struct grep_opt *opt, enum grep_header_field field, const char *pat)
 {
        struct grep_pat *p = xcalloc(1, sizeof(*p));
        p->pattern = pat;
+       p->patternlen = strlen(pat);
        p->origin = "header";
        p->no = 0;
        p->token = GREP_PATTERN_HEAD;
        p->field = field;
-       *opt->pattern_tail = p;
-       opt->pattern_tail = &p->next;
+       *opt->header_tail = p;
+       opt->header_tail = &p->next;
        p->next = NULL;
 }
 
 void append_grep_pattern(struct grep_opt *opt, const char *pat,
                         const char *origin, int no, enum grep_pat_token t)
+{
+       append_grep_pat(opt, pat, strlen(pat), origin, no, t);
+}
+
+void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen,
+                    const char *origin, int no, enum grep_pat_token t)
 {
        struct grep_pat *p = xcalloc(1, sizeof(*p));
        p->pattern = pat;
+       p->patternlen = patlen;
        p->origin = origin;
        p->no = no;
        p->token = t;
@@ -28,11 +37,26 @@ void append_grep_pattern(struct grep_opt *opt, const char *pat,
        p->next = NULL;
 }
 
-static int is_fixed(const char *s)
+struct grep_opt *grep_opt_dup(const struct grep_opt *opt)
 {
-       while (*s && !is_regex_special(*s))
-               s++;
-       return !*s;
+       struct grep_pat *pat;
+       struct grep_opt *ret = xmalloc(sizeof(struct grep_opt));
+       *ret = *opt;
+
+       ret->pattern_list = NULL;
+       ret->pattern_tail = &ret->pattern_list;
+
+       for(pat = opt->pattern_list; pat != NULL; pat = pat->next)
+       {
+               if(pat->token == GREP_PATTERN_HEAD)
+                       append_header_grep_pattern(ret, pat->field,
+                                                  pat->pattern);
+               else
+                       append_grep_pat(ret, pat->pattern, pat->patternlen,
+                                       pat->origin, pat->no, pat->token);
+       }
+
+       return ret;
 }
 
 static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
@@ -40,11 +64,9 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
        int err;
 
        p->word_regexp = opt->word_regexp;
+       p->ignore_case = opt->ignore_case;
+       p->fixed = opt->fixed;
 
-       if (opt->fixed || is_fixed(p->pattern))
-               p->fixed = 1;
-       if (opt->regflags & REG_ICASE)
-               p->fixed = 0;
        if (p->fixed)
                return;
 
@@ -170,9 +192,26 @@ static struct grep_expr *compile_pattern_expr(struct grep_pat **list)
 void compile_grep_patterns(struct grep_opt *opt)
 {
        struct grep_pat *p;
-
-       if (opt->all_match)
-               opt->extended = 1;
+       struct grep_expr *header_expr = NULL;
+
+       if (opt->header_list) {
+               p = opt->header_list;
+               header_expr = compile_pattern_expr(&p);
+               if (p)
+                       die("incomplete pattern expression: %s", p->pattern);
+               for (p = opt->header_list; p; p = p->next) {
+                       switch (p->token) {
+                       case GREP_PATTERN: /* atom */
+                       case GREP_PATTERN_HEAD:
+                       case GREP_PATTERN_BODY:
+                               compile_regexp(p, opt);
+                               break;
+                       default:
+                               opt->extended = 1;
+                               break;
+                       }
+               }
+       }
 
        for (p = opt->pattern_list; p; p = p->next) {
                switch (p->token) {
@@ -187,7 +226,9 @@ void compile_grep_patterns(struct grep_opt *opt)
                }
        }
 
-       if (!opt->extended)
+       if (opt->all_match || header_expr)
+               opt->extended = 1;
+       else if (!opt->extended)
                return;
 
        /* Then bundle them up in an expression.
@@ -198,6 +239,21 @@ void compile_grep_patterns(struct grep_opt *opt)
                opt->pattern_expression = compile_pattern_expr(&p);
        if (p)
                die("incomplete pattern expression: %s", p->pattern);
+
+       if (!header_expr)
+               return;
+
+       if (opt->pattern_expression) {
+               struct grep_expr *z;
+               z = xcalloc(1, sizeof(*z));
+               z->node = GREP_NODE_OR;
+               z->u.binary.left = opt->pattern_expression;
+               z->u.binary.right = header_expr;
+               opt->pattern_expression = z;
+       } else {
+               opt->pattern_expression = header_expr;
+       }
+       opt->all_match = 1;
 }
 
 static void free_pattern_expr(struct grep_expr *x)
@@ -256,25 +312,69 @@ static int word_char(char ch)
        return isalnum(ch) || ch == '_';
 }
 
+static void output_color(struct grep_opt *opt, const void *data, size_t size,
+                        const char *color)
+{
+       if (opt->color && color && color[0]) {
+               opt->output(opt, color, strlen(color));
+               opt->output(opt, data, size);
+               opt->output(opt, GIT_COLOR_RESET, strlen(GIT_COLOR_RESET));
+       } else
+               opt->output(opt, data, size);
+}
+
+static void output_sep(struct grep_opt *opt, char sign)
+{
+       if (opt->null_following_name)
+               opt->output(opt, "\0", 1);
+       else
+               output_color(opt, &sign, 1, opt->color_sep);
+}
+
 static void show_name(struct grep_opt *opt, const char *name)
 {
-       printf("%s%c", name, opt->null_following_name ? '\0' : '\n');
+       output_color(opt, name, strlen(name), opt->color_filename);
+       opt->output(opt, opt->null_following_name ? "\0" : "\n", 1);
 }
 
-static int fixmatch(const char *pattern, char *line, regmatch_t *match)
+static int fixmatch(struct grep_pat *p, char *line, char *eol,
+                   regmatch_t *match)
 {
-       char *hit = strstr(line, pattern);
+       char *hit;
+
+       if (p->ignore_case) {
+               char *s = line;
+               do {
+                       hit = strcasestr(s, p->pattern);
+                       if (hit)
+                               break;
+                       s += strlen(s) + 1;
+               } while (s < eol);
+       } else
+               hit = memmem(line, eol - line, p->pattern, p->patternlen);
+
        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);
+               match->rm_eo = match->rm_so + p->patternlen;
                return 0;
        }
 }
 
+static int regmatch(const regex_t *preg, char *line, char *eol,
+                   regmatch_t *match, int eflags)
+{
+#ifdef REG_STARTEND
+       match->rm_so = 0;
+       match->rm_eo = eol - line;
+       eflags |= REG_STARTEND;
+#endif
+       return regexec(preg, line, 1, match, eflags);
+}
+
 static int strip_timestamp(char *bol, char **eol_p)
 {
        char *eol = *eol_p;
@@ -305,6 +405,7 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
 {
        int hit = 0;
        int saved_ch = 0;
+       const char *start = bol;
 
        if ((p->token != GREP_PATTERN) &&
            ((p->token == GREP_PATTERN_HEAD) != (ctx == GREP_CONTEXT_HEAD)))
@@ -324,13 +425,13 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
 
  again:
        if (p->fixed)
-               hit = !fixmatch(p->pattern, bol, pmatch);
+               hit = !fixmatch(p, bol, eol, pmatch);
        else
-               hit = !regexec(&p->regexp, bol, 1, pmatch, eflags);
+               hit = !regmatch(&p->regexp, bol, eol, pmatch, eflags);
 
        if (hit && p->word_regexp) {
                if ((pmatch[0].rm_so < 0) ||
-                   (eol - bol) <= pmatch[0].rm_so ||
+                   (eol - bol) < pmatch[0].rm_so ||
                    (pmatch[0].rm_eo < 0) ||
                    (eol - bol) < pmatch[0].rm_eo)
                        die("regexp returned nonsense");
@@ -349,6 +450,10 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
                else
                        hit = 0;
 
+               /* Words consist of at least one character. */
+               if (pmatch->rm_so == pmatch->rm_eo)
+                       hit = 0;
+
                if (!hit && pmatch[0].rm_so + bol + 1 < eol) {
                        /* There could be more than one match on the
                         * line, and the first match might not be
@@ -359,12 +464,17 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
                        bol = pmatch[0].rm_so + bol + 1;
                        while (word_char(bol[-1]) && bol < eol)
                                bol++;
+                       eflags |= REG_NOTBOL;
                        if (bol < eol)
                                goto again;
                }
        }
        if (p->token == GREP_PATTERN_HEAD && saved_ch)
                *eol = saved_ch;
+       if (hit) {
+               pmatch[0].rm_so += bol - start;
+               pmatch[0].rm_eo += bol - start;
+       }
        return hit;
 }
 
@@ -479,33 +589,213 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
                      const char *name, unsigned lno, char sign)
 {
        int rest = eol - bol;
+       char *line_color = NULL;
 
-       if (opt->null_following_name)
-               sign = '\0';
-       if (opt->pathname)
-               printf("%s%c", name, sign);
-       if (opt->linenum)
-               printf("%d%c", lno, sign);
+       if (opt->pre_context || opt->post_context) {
+               if (opt->last_shown == 0) {
+                       if (opt->show_hunk_mark) {
+                               output_color(opt, "--", 2, opt->color_sep);
+                               opt->output(opt, "\n", 1);
+                       }
+               } else if (lno > opt->last_shown + 1) {
+                       output_color(opt, "--", 2, opt->color_sep);
+                       opt->output(opt, "\n", 1);
+               }
+       }
+       opt->last_shown = lno;
+
+       if (opt->pathname) {
+               output_color(opt, name, strlen(name), opt->color_filename);
+               output_sep(opt, sign);
+       }
+       if (opt->linenum) {
+               char buf[32];
+               snprintf(buf, sizeof(buf), "%d", lno);
+               output_color(opt, buf, strlen(buf), opt->color_lineno);
+               output_sep(opt, sign);
+       }
        if (opt->color) {
                regmatch_t match;
                enum grep_context ctx = GREP_CONTEXT_BODY;
                int ch = *eol;
                int eflags = 0;
 
+               if (sign == ':')
+                       line_color = opt->color_selected;
+               else if (sign == '-')
+                       line_color = opt->color_context;
+               else if (sign == '=')
+                       line_color = opt->color_function;
                *eol = '\0';
                while (next_match(opt, bol, eol, ctx, &match, eflags)) {
-                       printf("%.*s%s%.*s%s",
-                              (int)match.rm_so, bol,
-                              opt->color_match,
-                              (int)(match.rm_eo - match.rm_so), bol + match.rm_so,
-                              GIT_COLOR_RESET);
+                       if (match.rm_so == match.rm_eo)
+                               break;
+
+                       output_color(opt, bol, match.rm_so, line_color);
+                       output_color(opt, bol + match.rm_so,
+                                    match.rm_eo - match.rm_so,
+                                    opt->color_match);
                        bol += match.rm_eo;
                        rest -= match.rm_eo;
                        eflags = REG_NOTBOL;
                }
                *eol = ch;
        }
-       printf("%.*s\n", rest, bol);
+       output_color(opt, bol, rest, line_color);
+       opt->output(opt, "\n", 1);
+}
+
+static int match_funcname(struct grep_opt *opt, char *bol, char *eol)
+{
+       xdemitconf_t *xecfg = opt->priv;
+       if (xecfg && xecfg->find_func) {
+               char buf[1];
+               return xecfg->find_func(bol, eol - bol, buf, 1,
+                                       xecfg->find_func_priv) >= 0;
+       }
+
+       if (bol == eol)
+               return 0;
+       if (isalpha(*bol) || *bol == '_' || *bol == '$')
+               return 1;
+       return 0;
+}
+
+static void show_funcname_line(struct grep_opt *opt, const char *name,
+                              char *buf, char *bol, unsigned lno)
+{
+       while (bol > buf) {
+               char *eol = --bol;
+
+               while (bol > buf && bol[-1] != '\n')
+                       bol--;
+               lno--;
+
+               if (lno <= opt->last_shown)
+                       break;
+
+               if (match_funcname(opt, bol, eol)) {
+                       show_line(opt, bol, eol, name, lno, '=');
+                       break;
+               }
+       }
+}
+
+static void show_pre_context(struct grep_opt *opt, const char *name, char *buf,
+                            char *bol, unsigned lno)
+{
+       unsigned cur = lno, from = 1, funcname_lno = 0;
+       int funcname_needed = opt->funcname;
+
+       if (opt->pre_context < lno)
+               from = lno - opt->pre_context;
+       if (from <= opt->last_shown)
+               from = opt->last_shown + 1;
+
+       /* Rewind. */
+       while (bol > buf && cur > from) {
+               char *eol = --bol;
+
+               while (bol > buf && bol[-1] != '\n')
+                       bol--;
+               cur--;
+               if (funcname_needed && match_funcname(opt, bol, eol)) {
+                       funcname_lno = cur;
+                       funcname_needed = 0;
+               }
+       }
+
+       /* We need to look even further back to find a function signature. */
+       if (opt->funcname && funcname_needed)
+               show_funcname_line(opt, name, buf, bol, cur);
+
+       /* Back forward. */
+       while (cur < lno) {
+               char *eol = bol, sign = (cur == funcname_lno) ? '=' : '-';
+
+               while (*eol != '\n')
+                       eol++;
+               show_line(opt, bol, eol, name, cur, sign);
+               bol = eol + 1;
+               cur++;
+       }
+}
+
+static int should_lookahead(struct grep_opt *opt)
+{
+       struct grep_pat *p;
+
+       if (opt->extended)
+               return 0; /* punt for too complex stuff */
+       if (opt->invert)
+               return 0;
+       for (p = opt->pattern_list; p; p = p->next) {
+               if (p->token != GREP_PATTERN)
+                       return 0; /* punt for "header only" and stuff */
+       }
+       return 1;
+}
+
+static int look_ahead(struct grep_opt *opt,
+                     unsigned long *left_p,
+                     unsigned *lno_p,
+                     char **bol_p)
+{
+       unsigned lno = *lno_p;
+       char *bol = *bol_p;
+       struct grep_pat *p;
+       char *sp, *last_bol;
+       regoff_t earliest = -1;
+
+       for (p = opt->pattern_list; p; p = p->next) {
+               int hit;
+               regmatch_t m;
+
+               if (p->fixed)
+                       hit = !fixmatch(p, bol, bol + *left_p, &m);
+               else
+                       hit = !regmatch(&p->regexp, bol, bol + *left_p, &m, 0);
+               if (!hit || m.rm_so < 0 || m.rm_eo < 0)
+                       continue;
+               if (earliest < 0 || m.rm_so < earliest)
+                       earliest = m.rm_so;
+       }
+
+       if (earliest < 0) {
+               *bol_p = bol + *left_p;
+               *left_p = 0;
+               return 1;
+       }
+       for (sp = bol + earliest; bol < sp && sp[-1] != '\n'; sp--)
+               ; /* find the beginning of the line */
+       last_bol = sp;
+
+       for (sp = bol; sp < last_bol; sp++) {
+               if (*sp == '\n')
+                       lno++;
+       }
+       *left_p -= last_bol - bol;
+       *bol_p = last_bol;
+       *lno_p = lno;
+       return 0;
+}
+
+int grep_threads_ok(const struct grep_opt *opt)
+{
+       /* If this condition is true, then we may use the attribute
+        * machinery in grep_buffer_1. The attribute code is not
+        * thread safe, so we disable the use of threads.
+        */
+       if (opt->funcname && !opt->unmatch_name_only && !opt->status_only &&
+           !opt->name_only)
+               return 0;
+
+       return 1;
+}
+
+static void std_output(struct grep_opt *opt, const void *buf, size_t size)
+{
+       fwrite(buf, size, 1, stdout);
 }
 
 static int grep_buffer_1(struct grep_opt *opt, const char *name,
@@ -514,39 +804,66 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
        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;
+       int try_lookahead = 0;
        enum grep_context ctx = GREP_CONTEXT_HEAD;
+       xdemitconf_t xecfg;
+
+       if (!opt->output)
+               opt->output = std_output;
+
+       if (opt->last_shown && (opt->pre_context || opt->post_context) &&
+           opt->output == std_output)
+               opt->show_hunk_mark = 1;
+       opt->last_shown = 0;
 
-       if (buffer_is_binary(buf, size)) {
-               switch (opt->binary) {
-               case GREP_BINARY_DEFAULT:
+       switch (opt->binary) {
+       case GREP_BINARY_DEFAULT:
+               if (buffer_is_binary(buf, size))
                        binary_match_only = 1;
-                       break;
-               case GREP_BINARY_NOMATCH:
+               break;
+       case GREP_BINARY_NOMATCH:
+               if (buffer_is_binary(buf, size))
                        return 0; /* Assume unmatch */
-                       break;
-               default:
-                       break;
-               }
+               break;
+       case GREP_BINARY_TEXT:
+               break;
+       default:
+               die("bug: unknown binary handling mode");
        }
 
-       if (opt->pre_context)
-               prev = xcalloc(opt->pre_context, sizeof(*prev));
-       if (opt->pre_context || opt->post_context)
-               hunk_mark = "--\n";
+       memset(&xecfg, 0, sizeof(xecfg));
+       if (opt->funcname && !opt->unmatch_name_only && !opt->status_only &&
+           !opt->name_only && !binary_match_only && !collect_hits) {
+               struct userdiff_driver *drv = userdiff_find_by_path(name);
+               if (drv && drv->funcname.pattern) {
+                       const struct userdiff_funcname *pe = &drv->funcname;
+                       xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags);
+                       opt->priv = &xecfg;
+               }
+       }
+       try_lookahead = should_lookahead(opt);
 
        while (left) {
                char *eol, ch;
                int hit;
 
+               /*
+                * look_ahead() skips quicly to the line that possibly
+                * has the next hit; don't call it if we need to do
+                * something more than just skipping the current line
+                * in response to an unmatch for the current line.  E.g.
+                * inside a post-context window, we will show the current
+                * line as a context around the previous hit when it
+                * doesn't hit.
+                */
+               if (try_lookahead
+                   && !(last_hit
+                        && lno <= last_hit + opt->post_context)
+                   && look_ahead(opt, &left, &lno, &bol))
+                       break;
                eol = end_of_line(bol, &left);
                ch = *eol;
                *eol = 0;
@@ -575,59 +892,35 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
                        count++;
                        if (opt->status_only)
                                return 1;
-                       if (binary_match_only) {
-                               printf("Binary file %s matches\n", name);
-                               return 1;
-                       }
                        if (opt->name_only) {
                                show_name(opt, name);
                                return 1;
                        }
+                       if (opt->count)
+                               goto next_line;
+                       if (binary_match_only) {
+                               opt->output(opt, "Binary file ", 12);
+                               output_color(opt, name, strlen(name),
+                                            opt->color_filename);
+                               opt->output(opt, " matches\n", 9);
+                               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)
-                                       fputs(hunk_mark, stdout);
-                               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)
-                               fputs(hunk_mark, stdout);
-                       if (!opt->count)
-                               show_line(opt, bol, eol, name, lno, ':');
-                       last_shown = last_hit = lno;
+                       if (opt->pre_context)
+                               show_pre_context(opt, name, buf, bol, lno);
+                       else if (opt->funcname)
+                               show_funcname_line(opt, name, buf, bol, lno);
+                       show_line(opt, bol, eol, name, lno, ':');
+                       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)
-                               fputs(hunk_mark, stdout);
                        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:
@@ -638,7 +931,6 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
                lno++;
        }
 
-       free(prev);
        if (collect_hits)
                return 0;
 
@@ -650,14 +942,22 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
                return 1;
        }
 
+       xdiff_clear_find_func(&xecfg);
+       opt->priv = NULL;
+
        /* 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%c%u\n", name,
-                      opt->null_following_name ? '\0' : ':', count);
+       if (opt->count && count) {
+               char buf[32];
+               output_color(opt, name, strlen(name), opt->color_filename);
+               output_sep(opt, ':');
+               snprintf(buf, sizeof(buf), "%u\n", count);
+               opt->output(opt, buf, strlen(buf));
+               return 1;
+       }
        return !!last_hit;
 }
 
diff --git a/grep.h b/grep.h
index a67005de62d1442e7ba6a8dc27320225a0d55819..efa8cff980af2b2c06dad080876637d16b5c4985 100644 (file)
--- a/grep.h
+++ b/grep.h
@@ -10,17 +10,17 @@ enum grep_pat_token {
        GREP_OPEN_PAREN,
        GREP_CLOSE_PAREN,
        GREP_NOT,
-       GREP_OR,
+       GREP_OR
 };
 
 enum grep_context {
        GREP_CONTEXT_HEAD,
-       GREP_CONTEXT_BODY,
+       GREP_CONTEXT_BODY
 };
 
 enum grep_header_field {
        GREP_HEADER_AUTHOR = 0,
-       GREP_HEADER_COMMITTER,
+       GREP_HEADER_COMMITTER
 };
 
 struct grep_pat {
@@ -29,9 +29,11 @@ struct grep_pat {
        int no;
        enum grep_pat_token token;
        const char *pattern;
+       size_t patternlen;
        enum grep_header_field field;
        regex_t regexp;
        unsigned fixed:1;
+       unsigned ignore_case:1;
        unsigned word_regexp:1;
 };
 
@@ -39,7 +41,7 @@ enum grep_expr_node {
        GREP_NODE_ATOM,
        GREP_NODE_NOT,
        GREP_NODE_AND,
-       GREP_NODE_OR,
+       GREP_NODE_OR
 };
 
 struct grep_expr {
@@ -58,38 +60,59 @@ struct grep_expr {
 struct grep_opt {
        struct grep_pat *pattern_list;
        struct grep_pat **pattern_tail;
+       struct grep_pat *header_list;
+       struct grep_pat **header_tail;
        struct grep_expr *pattern_expression;
+       const char *prefix;
        int prefix_length;
        regex_t regexp;
-       unsigned linenum:1;
-       unsigned invert:1;
-       unsigned status_only:1;
-       unsigned name_only:1;
-       unsigned unmatch_name_only:1;
-       unsigned count:1;
-       unsigned word_regexp:1;
-       unsigned fixed:1;
-       unsigned all_match:1;
+       int linenum;
+       int invert;
+       int ignore_case;
+       int status_only;
+       int name_only;
+       int unmatch_name_only;
+       int count;
+       int word_regexp;
+       int fixed;
+       int all_match;
 #define GREP_BINARY_DEFAULT    0
 #define GREP_BINARY_NOMATCH    1
 #define GREP_BINARY_TEXT       2
-       unsigned binary:2;
-       unsigned extended:1;
-       unsigned relative:1;
-       unsigned pathname:1;
-       unsigned null_following_name:1;
+       int binary;
+       int extended;
+       int relative;
+       int pathname;
+       int null_following_name;
        int color;
+       int max_depth;
+       int funcname;
+       char color_context[COLOR_MAXLEN];
+       char color_filename[COLOR_MAXLEN];
+       char color_function[COLOR_MAXLEN];
+       char color_lineno[COLOR_MAXLEN];
        char color_match[COLOR_MAXLEN];
-       const char *color_external;
+       char color_selected[COLOR_MAXLEN];
+       char color_sep[COLOR_MAXLEN];
        int regflags;
        unsigned pre_context;
        unsigned post_context;
+       unsigned last_shown;
+       int show_hunk_mark;
+       void *priv;
+
+       void (*output)(struct grep_opt *opt, const void *data, size_t size);
+       void *output_priv;
 };
 
+extern void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen, const char *origin, int no, enum grep_pat_token t);
 extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t);
 extern void append_header_grep_pattern(struct grep_opt *, enum grep_header_field, const char *);
 extern void compile_grep_patterns(struct grep_opt *opt);
 extern void free_grep_patterns(struct grep_opt *opt);
 extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size);
 
+extern struct grep_opt *grep_opt_dup(const struct grep_opt *opt);
+extern int grep_threads_ok(const struct grep_opt *opt);
+
 #endif
diff --git a/hash-object.c b/hash-object.c
deleted file mode 100644 (file)
index ebb3bed..0000000
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Linus Torvalds, 2005
- * Copyright (C) Junio C Hamano, 2005
- */
-#include "cache.h"
-#include "blob.h"
-#include "quote.h"
-#include "parse-options.h"
-#include "exec_cmd.h"
-
-static void hash_fd(int fd, const char *type, int write_object, const char *path)
-{
-       struct stat st;
-       unsigned char sha1[20];
-       if (fstat(fd, &st) < 0 ||
-           index_fd(sha1, fd, &st, write_object, type_from_string(type), path))
-               die(write_object
-                   ? "Unable to add %s to database"
-                   : "Unable to hash %s", path);
-       printf("%s\n", sha1_to_hex(sha1));
-       maybe_flush_or_die(stdout, "hash to stdout");
-}
-
-static void hash_object(const char *path, const char *type, int write_object,
-                       const char *vpath)
-{
-       int fd;
-       fd = open(path, O_RDONLY);
-       if (fd < 0)
-               die("Cannot open %s", path);
-       hash_fd(fd, type, write_object, vpath);
-}
-
-static void hash_stdin_paths(const char *type, int write_objects)
-{
-       struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
-
-       while (strbuf_getline(&buf, stdin, '\n') != EOF) {
-               if (buf.buf[0] == '"') {
-                       strbuf_reset(&nbuf);
-                       if (unquote_c_style(&nbuf, buf.buf, NULL))
-                               die("line is badly quoted");
-                       strbuf_swap(&buf, &nbuf);
-               }
-               hash_object(buf.buf, type, write_objects, buf.buf);
-       }
-       strbuf_release(&buf);
-       strbuf_release(&nbuf);
-}
-
-static const char * const hash_object_usage[] = {
-       "git hash-object [-t <type>] [-w] [--path=<file>|--no-filters] [--stdin] [--] <file>...",
-       "git hash-object  --stdin-paths < <list-of-paths>",
-       NULL
-};
-
-static const char *type;
-static int write_object;
-static int hashstdin;
-static int stdin_paths;
-static int no_filters;
-static const char *vpath;
-
-static const struct option hash_object_options[] = {
-       OPT_STRING('t', NULL, &type, "type", "object type"),
-       OPT_BOOLEAN('w', NULL, &write_object, "write the object into the object database"),
-       OPT_BOOLEAN( 0 , "stdin", &hashstdin, "read the object from stdin"),
-       OPT_BOOLEAN( 0 , "stdin-paths", &stdin_paths, "read file names from stdin"),
-       OPT_BOOLEAN( 0 , "no-filters", &no_filters, "store file as is without filters"),
-       OPT_STRING( 0 , "path", &vpath, "file", "process file as it were from this path"),
-       OPT_END()
-};
-
-int main(int argc, const char **argv)
-{
-       int i;
-       const char *prefix = NULL;
-       int prefix_length = -1;
-       const char *errstr = NULL;
-
-       type = blob_type;
-
-       git_extract_argv0_path(argv[0]);
-
-       argc = parse_options(argc, argv, hash_object_options, hash_object_usage, 0);
-
-       if (write_object) {
-               prefix = setup_git_directory();
-               prefix_length = prefix ? strlen(prefix) : 0;
-               if (vpath && prefix)
-                       vpath = prefix_filename(prefix, prefix_length, vpath);
-       }
-
-       git_config(git_default_config, NULL);
-
-       if (stdin_paths) {
-               if (hashstdin)
-                       errstr = "Can't use --stdin-paths with --stdin";
-               else if (argc)
-                       errstr = "Can't specify files with --stdin-paths";
-               else if (vpath)
-                       errstr = "Can't use --stdin-paths with --path";
-               else if (no_filters)
-                       errstr = "Can't use --stdin-paths with --no-filters";
-       }
-       else {
-               if (hashstdin > 1)
-                       errstr = "Multiple --stdin arguments are not supported";
-               if (vpath && no_filters)
-                       errstr = "Can't use --path with --no-filters";
-       }
-
-       if (errstr) {
-               error("%s", errstr);
-               usage_with_options(hash_object_usage, hash_object_options);
-       }
-
-       if (hashstdin)
-               hash_fd(0, type, write_object, vpath);
-
-       for (i = 0 ; i < argc; i++) {
-               const char *arg = argv[i];
-
-               if (0 <= prefix_length)
-                       arg = prefix_filename(prefix, prefix_length, arg);
-               hash_object(arg, type, write_object,
-                           no_filters ? NULL : vpath ? vpath : arg);
-       }
-
-       if (stdin_paths)
-               hash_stdin_paths(type, write_object);
-
-       return 0;
-}
diff --git a/help.c b/help.c
index fd87bb5aeec82beec600be46248b19b13bb33804..7f4928e45954465d0401964289952aec4bd59e2a 100644 (file)
--- a/help.c
+++ b/help.c
@@ -100,7 +100,7 @@ static void pretty_print_string_list(struct cmdnames *cmds, int longest)
 
        if (space < max_cols)
                cols = max_cols / space;
-       rows = (cmds->cnt + cols - 1) / cols;
+       rows = DIV_ROUND_UP(cmds->cnt, cols);
 
        for (i = 0; i < rows; i++) {
                printf("  ");
@@ -126,8 +126,8 @@ static int is_executable(const char *name)
            !S_ISREG(st.st_mode))
                return 0;
 
-#ifdef __MINGW32__
-       /* cannot trust the executable bit, peek into the file instead */
+#ifdef WIN32
+{      /* cannot trust the executable bit, peek into the file instead */
        char buf[3] = { 0 };
        int n;
        int fd = open(name, O_RDONLY);
@@ -140,6 +140,7 @@ static int is_executable(const char *name)
                                st.st_mode |= S_IXUSR;
                close(fd);
        }
+}
 #endif
        return st.st_mode & S_IXUSR;
 }
@@ -296,13 +297,16 @@ static void add_cmd_list(struct cmdnames *cmds, struct cmdnames *old)
        old->names = NULL;
 }
 
+/* An empirically derived magic number */
+#define SIMILAR_ENOUGH(x) ((x) < 6)
+
 const char *help_unknown_cmd(const char *cmd)
 {
        int i, n, best_similarity = 0;
        struct cmdnames main_cmds, other_cmds;
 
        memset(&main_cmds, 0, sizeof(main_cmds));
-       memset(&other_cmds, 0, sizeof(main_cmds));
+       memset(&other_cmds, 0, sizeof(other_cmds));
        memset(&aliases, 0, sizeof(aliases));
 
        git_config(git_unknown_cmd_config, NULL);
@@ -330,11 +334,11 @@ const char *help_unknown_cmd(const char *cmd)
        n = 1;
        while (n < main_cmds.cnt && best_similarity == main_cmds.names[n]->len)
                ++n;
-       if (autocorrect && n == 1) {
+       if (autocorrect && n == 1 && SIMILAR_ENOUGH(best_similarity)) {
                const char *assumed = main_cmds.names[0]->name;
                main_cmds.names[0] = NULL;
                clean_cmdnames(&main_cmds);
-               fprintf(stderr, "WARNING: You called a Git program named '%s', "
+               fprintf(stderr, "WARNING: You called a Git command named '%s', "
                        "which does not exist.\n"
                        "Continuing under the assumption that you meant '%s'\n",
                        cmd, assumed);
@@ -346,9 +350,9 @@ const char *help_unknown_cmd(const char *cmd)
                return assumed;
        }
 
-       fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd);
+       fprintf(stderr, "git: '%s' is not a git command. See 'git --help'.\n", cmd);
 
-       if (best_similarity < 6) {
+       if (SIMILAR_ENOUGH(best_similarity)) {
                fprintf(stderr, "\nDid you mean %s?\n",
                        n < 2 ? "this": "one of these");
 
diff --git a/help.h b/help.h
index 56bc15406ffc55115fa1c827e0d1d5a7e74c516d..b6b12d5754888438085bb4e574b034951ffec473 100644 (file)
--- a/help.h
+++ b/help.h
@@ -16,14 +16,17 @@ static inline void mput_char(char c, unsigned int num)
                putchar(c);
 }
 
-void load_command_list(const char *prefix,
-               struct cmdnames *main_cmds,
-               struct cmdnames *other_cmds);
-void add_cmdname(struct cmdnames *cmds, const char *name, int len);
+extern void list_common_cmds_help(void);
+extern const char *help_unknown_cmd(const char *cmd);
+extern void load_command_list(const char *prefix,
+                             struct cmdnames *main_cmds,
+                             struct cmdnames *other_cmds);
+extern void add_cmdname(struct cmdnames *cmds, const char *name, int len);
 /* Here we require that excludes is a sorted list. */
-void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes);
-int is_in_cmdlist(struct cmdnames *c, const char *s);
-void list_commands(const char *title, struct cmdnames *main_cmds,
-                  struct cmdnames *other_cmds);
+extern void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes);
+extern int is_in_cmdlist(struct cmdnames *cmds, const char *name);
+extern void list_commands(const char *title,
+                         struct cmdnames *main_cmds,
+                         struct cmdnames *other_cmds);
 
 #endif /* HELP_H */
diff --git a/hex.c b/hex.c
new file mode 100644 (file)
index 0000000..bb402fb
--- /dev/null
+++ b/hex.c
@@ -0,0 +1,67 @@
+#include "cache.h"
+
+const signed char hexval_table[256] = {
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 00-07 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 08-0f */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 10-17 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 18-1f */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 20-27 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 28-2f */
+         0,  1,  2,  3,  4,  5,  6,  7,                /* 30-37 */
+         8,  9, -1, -1, -1, -1, -1, -1,                /* 38-3f */
+        -1, 10, 11, 12, 13, 14, 15, -1,                /* 40-47 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 48-4f */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 50-57 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 58-5f */
+        -1, 10, 11, 12, 13, 14, 15, -1,                /* 60-67 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 68-67 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 70-77 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 78-7f */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 80-87 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 88-8f */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 90-97 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 98-9f */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* a0-a7 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* a8-af */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* b0-b7 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* b8-bf */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* c0-c7 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* c8-cf */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* d0-d7 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* d8-df */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* e0-e7 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* e8-ef */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* f0-f7 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* f8-ff */
+};
+
+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;
+}
+
+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;
+}
diff --git a/http-backend.c b/http-backend.c
new file mode 100644 (file)
index 0000000..14c90c2
--- /dev/null
@@ -0,0 +1,607 @@
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include "object.h"
+#include "tag.h"
+#include "exec_cmd.h"
+#include "run-command.h"
+#include "string-list.h"
+#include "url.h"
+
+static const char content_type[] = "Content-Type";
+static const char content_length[] = "Content-Length";
+static const char last_modified[] = "Last-Modified";
+static int getanyfile = 1;
+
+static struct string_list *query_params;
+
+struct rpc_service {
+       const char *name;
+       const char *config_name;
+       signed enabled : 2;
+};
+
+static struct rpc_service rpc_service[] = {
+       { "upload-pack", "uploadpack", 1 },
+       { "receive-pack", "receivepack", -1 },
+};
+
+static struct string_list *get_parameters(void)
+{
+       if (!query_params) {
+               const char *query = getenv("QUERY_STRING");
+
+               query_params = xcalloc(1, sizeof(*query_params));
+               while (query && *query) {
+                       char *name = url_decode_parameter_name(&query);
+                       char *value = url_decode_parameter_value(&query);
+                       struct string_list_item *i;
+
+                       i = string_list_lookup(query_params, name);
+                       if (!i)
+                               i = string_list_insert(query_params, name);
+                       else
+                               free(i->util);
+                       i->util = value;
+               }
+       }
+       return query_params;
+}
+
+static const char *get_parameter(const char *name)
+{
+       struct string_list_item *i;
+       i = string_list_lookup(get_parameters(), name);
+       return i ? i->util : NULL;
+}
+
+__attribute__((format (printf, 2, 3)))
+static void format_write(int fd, const char *fmt, ...)
+{
+       static char buffer[1024];
+
+       va_list args;
+       unsigned n;
+
+       va_start(args, fmt);
+       n = vsnprintf(buffer, sizeof(buffer), fmt, args);
+       va_end(args);
+       if (n >= sizeof(buffer))
+               die("protocol error: impossibly long line");
+
+       safe_write(fd, buffer, n);
+}
+
+static void http_status(unsigned code, const char *msg)
+{
+       format_write(1, "Status: %u %s\r\n", code, msg);
+}
+
+static void hdr_str(const char *name, const char *value)
+{
+       format_write(1, "%s: %s\r\n", name, value);
+}
+
+static void hdr_int(const char *name, uintmax_t value)
+{
+       format_write(1, "%s: %" PRIuMAX "\r\n", name, value);
+}
+
+static void hdr_date(const char *name, unsigned long when)
+{
+       const char *value = show_date(when, 0, DATE_RFC2822);
+       hdr_str(name, value);
+}
+
+static void hdr_nocache(void)
+{
+       hdr_str("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
+       hdr_str("Pragma", "no-cache");
+       hdr_str("Cache-Control", "no-cache, max-age=0, must-revalidate");
+}
+
+static void hdr_cache_forever(void)
+{
+       unsigned long now = time(NULL);
+       hdr_date("Date", now);
+       hdr_date("Expires", now + 31536000);
+       hdr_str("Cache-Control", "public, max-age=31536000");
+}
+
+static void end_headers(void)
+{
+       safe_write(1, "\r\n", 2);
+}
+
+__attribute__((format (printf, 1, 2)))
+static NORETURN void not_found(const char *err, ...)
+{
+       va_list params;
+
+       http_status(404, "Not Found");
+       hdr_nocache();
+       end_headers();
+
+       va_start(params, err);
+       if (err && *err)
+               vfprintf(stderr, err, params);
+       va_end(params);
+       exit(0);
+}
+
+__attribute__((format (printf, 1, 2)))
+static NORETURN void forbidden(const char *err, ...)
+{
+       va_list params;
+
+       http_status(403, "Forbidden");
+       hdr_nocache();
+       end_headers();
+
+       va_start(params, err);
+       if (err && *err)
+               vfprintf(stderr, err, params);
+       va_end(params);
+       exit(0);
+}
+
+static void select_getanyfile(void)
+{
+       if (!getanyfile)
+               forbidden("Unsupported service: getanyfile");
+}
+
+static void send_strbuf(const char *type, struct strbuf *buf)
+{
+       hdr_int(content_length, buf->len);
+       hdr_str(content_type, type);
+       end_headers();
+       safe_write(1, buf->buf, buf->len);
+}
+
+static void send_local_file(const char *the_type, const char *name)
+{
+       const char *p = git_path("%s", name);
+       size_t buf_alloc = 8192;
+       char *buf = xmalloc(buf_alloc);
+       int fd;
+       struct stat sb;
+
+       fd = open(p, O_RDONLY);
+       if (fd < 0)
+               not_found("Cannot open '%s': %s", p, strerror(errno));
+       if (fstat(fd, &sb) < 0)
+               die_errno("Cannot stat '%s'", p);
+
+       hdr_int(content_length, sb.st_size);
+       hdr_str(content_type, the_type);
+       hdr_date(last_modified, sb.st_mtime);
+       end_headers();
+
+       for (;;) {
+               ssize_t n = xread(fd, buf, buf_alloc);
+               if (n < 0)
+                       die_errno("Cannot read '%s'", p);
+               if (!n)
+                       break;
+               safe_write(1, buf, n);
+       }
+       close(fd);
+       free(buf);
+}
+
+static void get_text_file(char *name)
+{
+       select_getanyfile();
+       hdr_nocache();
+       send_local_file("text/plain", name);
+}
+
+static void get_loose_object(char *name)
+{
+       select_getanyfile();
+       hdr_cache_forever();
+       send_local_file("application/x-git-loose-object", name);
+}
+
+static void get_pack_file(char *name)
+{
+       select_getanyfile();
+       hdr_cache_forever();
+       send_local_file("application/x-git-packed-objects", name);
+}
+
+static void get_idx_file(char *name)
+{
+       select_getanyfile();
+       hdr_cache_forever();
+       send_local_file("application/x-git-packed-objects-toc", name);
+}
+
+static int http_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "http.getanyfile")) {
+               getanyfile = git_config_bool(var, value);
+               return 0;
+       }
+
+       if (!prefixcmp(var, "http.")) {
+               int i;
+
+               for (i = 0; i < ARRAY_SIZE(rpc_service); i++) {
+                       struct rpc_service *svc = &rpc_service[i];
+                       if (!strcmp(var + 5, svc->config_name)) {
+                               svc->enabled = git_config_bool(var, value);
+                               return 0;
+                       }
+               }
+       }
+
+       /* we are not interested in parsing any other configuration here */
+       return 0;
+}
+
+static struct rpc_service *select_service(const char *name)
+{
+       struct rpc_service *svc = NULL;
+       int i;
+
+       if (prefixcmp(name, "git-"))
+               forbidden("Unsupported service: '%s'", name);
+
+       for (i = 0; i < ARRAY_SIZE(rpc_service); i++) {
+               struct rpc_service *s = &rpc_service[i];
+               if (!strcmp(s->name, name + 4)) {
+                       svc = s;
+                       break;
+               }
+       }
+
+       if (!svc)
+               forbidden("Unsupported service: '%s'", name);
+
+       if (svc->enabled < 0) {
+               const char *user = getenv("REMOTE_USER");
+               svc->enabled = (user && *user) ? 1 : 0;
+       }
+       if (!svc->enabled)
+               forbidden("Service not enabled: '%s'", svc->name);
+       return svc;
+}
+
+static void inflate_request(const char *prog_name, int out)
+{
+       z_stream stream;
+       unsigned char in_buf[8192];
+       unsigned char out_buf[8192];
+       unsigned long cnt = 0;
+       int ret;
+
+       memset(&stream, 0, sizeof(stream));
+       ret = inflateInit2(&stream, (15 + 16));
+       if (ret != Z_OK)
+               die("cannot start zlib inflater, zlib err %d", ret);
+
+       while (1) {
+               ssize_t n = xread(0, in_buf, sizeof(in_buf));
+               if (n <= 0)
+                       die("request ended in the middle of the gzip stream");
+
+               stream.next_in = in_buf;
+               stream.avail_in = n;
+
+               while (0 < stream.avail_in) {
+                       int ret;
+
+                       stream.next_out = out_buf;
+                       stream.avail_out = sizeof(out_buf);
+
+                       ret = inflate(&stream, Z_NO_FLUSH);
+                       if (ret != Z_OK && ret != Z_STREAM_END)
+                               die("zlib error inflating request, result %d", ret);
+
+                       n = stream.total_out - cnt;
+                       if (write_in_full(out, out_buf, n) != n)
+                               die("%s aborted reading request", prog_name);
+                       cnt += n;
+
+                       if (ret == Z_STREAM_END)
+                               goto done;
+               }
+       }
+
+done:
+       inflateEnd(&stream);
+       close(out);
+}
+
+static void run_service(const char **argv)
+{
+       const char *encoding = getenv("HTTP_CONTENT_ENCODING");
+       const char *user = getenv("REMOTE_USER");
+       const char *host = getenv("REMOTE_ADDR");
+       char *env[3];
+       struct strbuf buf = STRBUF_INIT;
+       int gzipped_request = 0;
+       struct child_process cld;
+
+       if (encoding && !strcmp(encoding, "gzip"))
+               gzipped_request = 1;
+       else if (encoding && !strcmp(encoding, "x-gzip"))
+               gzipped_request = 1;
+
+       if (!user || !*user)
+               user = "anonymous";
+       if (!host || !*host)
+               host = "(none)";
+
+       memset(&env, 0, sizeof(env));
+       strbuf_addf(&buf, "GIT_COMMITTER_NAME=%s", user);
+       env[0] = strbuf_detach(&buf, NULL);
+
+       strbuf_addf(&buf, "GIT_COMMITTER_EMAIL=%s@http.%s", user, host);
+       env[1] = strbuf_detach(&buf, NULL);
+       env[2] = NULL;
+
+       memset(&cld, 0, sizeof(cld));
+       cld.argv = argv;
+       cld.env = (const char *const *)env;
+       if (gzipped_request)
+               cld.in = -1;
+       cld.git_cmd = 1;
+       if (start_command(&cld))
+               exit(1);
+
+       close(1);
+       if (gzipped_request)
+               inflate_request(argv[0], cld.in);
+       else
+               close(0);
+
+       if (finish_command(&cld))
+               exit(1);
+       free(env[0]);
+       free(env[1]);
+       strbuf_release(&buf);
+}
+
+static int show_text_ref(const char *name, const unsigned char *sha1,
+       int flag, void *cb_data)
+{
+       struct strbuf *buf = cb_data;
+       struct object *o = parse_object(sha1);
+       if (!o)
+               return 0;
+
+       strbuf_addf(buf, "%s\t%s\n", sha1_to_hex(sha1), name);
+       if (o->type == OBJ_TAG) {
+               o = deref_tag(o, name, 0);
+               if (!o)
+                       return 0;
+               strbuf_addf(buf, "%s\t%s^{}\n", sha1_to_hex(o->sha1), name);
+       }
+       return 0;
+}
+
+static void get_info_refs(char *arg)
+{
+       const char *service_name = get_parameter("service");
+       struct strbuf buf = STRBUF_INIT;
+
+       hdr_nocache();
+
+       if (service_name) {
+               const char *argv[] = {NULL /* service name */,
+                       "--stateless-rpc", "--advertise-refs",
+                       ".", NULL};
+               struct rpc_service *svc = select_service(service_name);
+
+               strbuf_addf(&buf, "application/x-git-%s-advertisement",
+                       svc->name);
+               hdr_str(content_type, buf.buf);
+               end_headers();
+
+               packet_write(1, "# service=git-%s\n", svc->name);
+               packet_flush(1);
+
+               argv[0] = svc->name;
+               run_service(argv);
+
+       } else {
+               select_getanyfile();
+               for_each_ref(show_text_ref, &buf);
+               send_strbuf("text/plain", &buf);
+       }
+       strbuf_release(&buf);
+}
+
+static void get_info_packs(char *arg)
+{
+       size_t objdirlen = strlen(get_object_directory());
+       struct strbuf buf = STRBUF_INIT;
+       struct packed_git *p;
+       size_t cnt = 0;
+
+       select_getanyfile();
+       prepare_packed_git();
+       for (p = packed_git; p; p = p->next) {
+               if (p->pack_local)
+                       cnt++;
+       }
+
+       strbuf_grow(&buf, cnt * 53 + 2);
+       for (p = packed_git; p; p = p->next) {
+               if (p->pack_local)
+                       strbuf_addf(&buf, "P %s\n", p->pack_name + objdirlen + 6);
+       }
+       strbuf_addch(&buf, '\n');
+
+       hdr_nocache();
+       send_strbuf("text/plain; charset=utf-8", &buf);
+       strbuf_release(&buf);
+}
+
+static void check_content_type(const char *accepted_type)
+{
+       const char *actual_type = getenv("CONTENT_TYPE");
+
+       if (!actual_type)
+               actual_type = "";
+
+       if (strcmp(actual_type, accepted_type)) {
+               http_status(415, "Unsupported Media Type");
+               hdr_nocache();
+               end_headers();
+               format_write(1,
+                       "Expected POST with Content-Type '%s',"
+                       " but received '%s' instead.\n",
+                       accepted_type, actual_type);
+               exit(0);
+       }
+}
+
+static void service_rpc(char *service_name)
+{
+       const char *argv[] = {NULL, "--stateless-rpc", ".", NULL};
+       struct rpc_service *svc = select_service(service_name);
+       struct strbuf buf = STRBUF_INIT;
+
+       strbuf_reset(&buf);
+       strbuf_addf(&buf, "application/x-git-%s-request", svc->name);
+       check_content_type(buf.buf);
+
+       hdr_nocache();
+
+       strbuf_reset(&buf);
+       strbuf_addf(&buf, "application/x-git-%s-result", svc->name);
+       hdr_str(content_type, buf.buf);
+
+       end_headers();
+
+       argv[0] = svc->name;
+       run_service(argv);
+       strbuf_release(&buf);
+}
+
+static NORETURN void die_webcgi(const char *err, va_list params)
+{
+       static int dead;
+
+       if (!dead) {
+               dead = 1;
+               http_status(500, "Internal Server Error");
+               hdr_nocache();
+               end_headers();
+
+               vreportf("fatal: ", err, params);
+       }
+       exit(0); /* we successfully reported a failure ;-) */
+}
+
+static char* getdir(void)
+{
+       struct strbuf buf = STRBUF_INIT;
+       char *pathinfo = getenv("PATH_INFO");
+       char *root = getenv("GIT_PROJECT_ROOT");
+       char *path = getenv("PATH_TRANSLATED");
+
+       if (root && *root) {
+               if (!pathinfo || !*pathinfo)
+                       die("GIT_PROJECT_ROOT is set but PATH_INFO is not");
+               if (daemon_avoid_alias(pathinfo))
+                       die("'%s': aliased", pathinfo);
+               strbuf_addstr(&buf, root);
+               if (buf.buf[buf.len - 1] != '/')
+                       strbuf_addch(&buf, '/');
+               if (pathinfo[0] == '/')
+                       pathinfo++;
+               strbuf_addstr(&buf, pathinfo);
+               return strbuf_detach(&buf, NULL);
+       } else if (path && *path) {
+               return xstrdup(path);
+       } else
+               die("No GIT_PROJECT_ROOT or PATH_TRANSLATED from server");
+       return NULL;
+}
+
+static struct service_cmd {
+       const char *method;
+       const char *pattern;
+       void (*imp)(char *);
+} services[] = {
+       {"GET", "/HEAD$", get_text_file},
+       {"GET", "/info/refs$", get_info_refs},
+       {"GET", "/objects/info/alternates$", get_text_file},
+       {"GET", "/objects/info/http-alternates$", get_text_file},
+       {"GET", "/objects/info/packs$", get_info_packs},
+       {"GET", "/objects/[0-9a-f]{2}/[0-9a-f]{38}$", get_loose_object},
+       {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.pack$", get_pack_file},
+       {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.idx$", get_idx_file},
+
+       {"POST", "/git-upload-pack$", service_rpc},
+       {"POST", "/git-receive-pack$", service_rpc}
+};
+
+int main(int argc, char **argv)
+{
+       char *method = getenv("REQUEST_METHOD");
+       char *dir;
+       struct service_cmd *cmd = NULL;
+       char *cmd_arg = NULL;
+       int i;
+
+       git_extract_argv0_path(argv[0]);
+       set_die_routine(die_webcgi);
+
+       if (!method)
+               die("No REQUEST_METHOD from server");
+       if (!strcmp(method, "HEAD"))
+               method = "GET";
+       dir = getdir();
+
+       for (i = 0; i < ARRAY_SIZE(services); i++) {
+               struct service_cmd *c = &services[i];
+               regex_t re;
+               regmatch_t out[1];
+
+               if (regcomp(&re, c->pattern, REG_EXTENDED))
+                       die("Bogus regex in service table: %s", c->pattern);
+               if (!regexec(&re, dir, 1, out, 0)) {
+                       size_t n;
+
+                       if (strcmp(method, c->method)) {
+                               const char *proto = getenv("SERVER_PROTOCOL");
+                               if (proto && !strcmp(proto, "HTTP/1.1"))
+                                       http_status(405, "Method Not Allowed");
+                               else
+                                       http_status(400, "Bad Request");
+                               hdr_nocache();
+                               end_headers();
+                               return 0;
+                       }
+
+                       cmd = c;
+                       n = out[0].rm_eo - out[0].rm_so;
+                       cmd_arg = xmalloc(n);
+                       memcpy(cmd_arg, dir + out[0].rm_so + 1, n-1);
+                       cmd_arg[n-1] = '\0';
+                       dir[out[0].rm_so] = 0;
+                       break;
+               }
+               regfree(&re);
+       }
+
+       if (!cmd)
+               not_found("Request not supported: '%s'", dir);
+
+       setup_path();
+       if (!enter_repo(dir, 0))
+               not_found("Not a git repository: '%s'", dir);
+       if (!getenv("GIT_HTTP_EXPORT_ALL") &&
+           access("git-daemon-export-ok", F_OK) )
+               not_found("Repository not exported: '%s'", dir);
+
+       git_config(http_config, NULL);
+       cmd->imp(cmd_arg);
+       return 0;
+}
diff --git a/http-fetch.c b/http-fetch.c
new file mode 100644 (file)
index 0000000..762c750
--- /dev/null
@@ -0,0 +1,99 @@
+#include "cache.h"
+#include "exec_cmd.h"
+#include "http.h"
+#include "walker.h"
+
+static const char http_fetch_usage[] = "git http-fetch "
+"[-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url";
+
+int main(int argc, const char **argv)
+{
+       const char *prefix;
+       struct walker *walker;
+       int commits_on_stdin = 0;
+       int commits;
+       const char **write_ref = NULL;
+       char **commit_id;
+       const char *url;
+       char *rewritten_url = NULL;
+       int arg = 1;
+       int rc = 0;
+       int get_tree = 0;
+       int get_history = 0;
+       int get_all = 0;
+       int get_verbosely = 0;
+       int get_recover = 0;
+
+       git_extract_argv0_path(argv[0]);
+
+       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 (argv[arg][1] == 'h') {
+                       usage(http_fetch_usage);
+               } else if (!strcmp(argv[arg], "--recover")) {
+                       get_recover = 1;
+               } else if (!strcmp(argv[arg], "--stdin")) {
+                       commits_on_stdin = 1;
+               }
+               arg++;
+       }
+       if (argc != arg + 2 - commits_on_stdin)
+               usage(http_fetch_usage);
+       if (commits_on_stdin) {
+               commits = walker_targets_stdin(&commit_id, &write_ref);
+       } else {
+               commit_id = (char **) &argv[arg++];
+               commits = 1;
+       }
+       url = argv[arg];
+
+       prefix = setup_git_directory();
+
+       git_config(git_default_config, NULL);
+
+       if (url && url[strlen(url)-1] != '/') {
+               rewritten_url = xmalloc(strlen(url)+2);
+               strcpy(rewritten_url, url);
+               strcat(rewritten_url, "/");
+               url = rewritten_url;
+       }
+
+       http_init(NULL);
+       walker = get_http_walker(url);
+       walker->get_tree = get_tree;
+       walker->get_history = get_history;
+       walker->get_all = get_all;
+       walker->get_verbosely = get_verbosely;
+       walker->get_recover = get_recover;
+
+       rc = walker_fetch(walker, commits, commit_id, write_ref, url);
+
+       if (commits_on_stdin)
+               walker_targets_free(commits, commit_id, write_ref);
+
+       if (walker->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'.\n");
+       }
+
+       walker_free(walker);
+       http_cleanup();
+
+       free(rewritten_url);
+
+       return rc;
+}
index 5138224cc372482fb60afd350fa40e37d019f367..c9bcd116975635cabf30eceb7909abfc7f341948 100644 (file)
@@ -1,6 +1,5 @@
 #include "cache.h"
 #include "commit.h"
-#include "pack.h"
 #include "tag.h"
 #include "blob.h"
 #include "http.h"
@@ -27,7 +26,6 @@ enum XML_Status {
 #endif
 
 #define PREV_BUF_SIZE 4096
-#define RANGE_HEADER_SIZE 30
 
 /* DAV methods */
 #define DAV_LOCK "LOCK"
@@ -76,12 +74,11 @@ static int pushing;
 static int aborted;
 static signed char remote_dir_exists[256];
 
-static struct curl_slist *no_pragma_header;
-
 static int push_verbosely;
 static int push_all = MATCH_REFS_NONE;
 static int force_all;
 static int dry_run;
+static int helper_status;
 
 static struct object_list *objects;
 
@@ -108,7 +105,7 @@ enum transfer_state {
        RUN_PUT,
        RUN_MOVE,
        ABORTED,
-       COMPLETE,
+       COMPLETE
 };
 
 struct transfer_request
@@ -119,19 +116,10 @@ struct transfer_request
        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];
-       git_SHA_CTX c;
-       z_stream stream;
-       int zret;
-       int rename;
        void *userData;
        struct active_request_slot *slot;
        struct transfer_request *next;
@@ -206,6 +194,8 @@ static char *xml_entities(char *s)
                case '&':
                        strbuf_addstr(&buf, "&amp;");
                        break;
+               case 0:
+                       return strbuf_detach(&buf, NULL);
                }
                s++;
        }
@@ -237,15 +227,6 @@ static struct curl_slist *get_dav_token_headers(struct remote_lock *lock, enum d
        return dav_headers;
 }
 
-static void append_remote_object_url(struct strbuf *buf, const char *url,
-                                    const char *hex,
-                                    int only_two_digit_prefix)
-{
-       strbuf_addf(buf, "%sobjects/%.*s/", url, 2, hex);
-       if (!only_two_digit_prefix)
-               strbuf_addf(buf, "%s", hex+2);
-}
-
 static void finish_request(struct transfer_request *request);
 static void release_request(struct transfer_request *request);
 
@@ -259,163 +240,29 @@ static void process_response(void *callback_data)
 
 #ifdef USE_CURL_MULTI
 
-static char *get_remote_object_url(const char *url, const char *hex,
-                                  int only_two_digit_prefix)
-{
-       struct strbuf buf = STRBUF_INIT;
-       append_remote_object_url(&buf, url, hex, only_two_digit_prefix);
-       return strbuf_detach(&buf, NULL);
-}
-
-static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
-                              void *data)
-{
-       unsigned char expn[4096];
-       size_t size = eltsize * nmemb;
-       int posn = 0;
-       struct transfer_request *request = (struct transfer_request *)data;
-       do {
-               ssize_t retval = xwrite(request->local_fileno,
-                                      (char *) 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 = git_inflate(&request->stream, Z_SYNC_FLUSH);
-               git_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;
-       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;
+       struct http_object_request *obj_req;
 
-       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) {
+       obj_req = new_http_object_request(repo->url, request->obj->sha1);
+       if (obj_req == NULL) {
                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));
-
-       git_inflate_init(&request->stream);
-
-       git_SHA1_Init(&request->c);
-
-       url = get_remote_object_url(repo->url, hex, 0);
-       request->url = xstrdup(url);
-
-       /* If a previous temp file is present, process what was already
-          fetched. */
-       prevlocal = open(prevfile, O_RDONLY);
-       if (prevlocal != -1) {
-               do {
-                       prev_read = xread(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));
-               git_inflate_init(&request->stream);
-               git_SHA1_Init(&request->c);
-               if (prev_posn>0) {
-                       prev_posn = 0;
-                       lseek(request->local_fileno, 0, SEEK_SET);
-                       ftruncate(request->local_fileno, 0);
-               }
-       }
-
-       slot = get_active_slot();
+       slot = obj_req->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);
-       }
+       request->userData = obj_req;
 
        /* 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");
                repo->can_update_info_refs = 0;
+               release_http_object_request(obj_req);
                release_request(request);
        }
 }
@@ -449,16 +296,10 @@ static void start_mkcol(struct transfer_request *request)
 
 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;
+       struct http_pack_request *preq;
 
        target = find_sha1_pack(request->obj->sha1, repo->packs);
        if (!target) {
@@ -471,66 +312,35 @@ static void start_fetch_packed(struct transfer_request *request)
        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(repo->url) + 64);
-       sprintf(url, "%sobjects/pack/pack-%s.pack",
-               repo->url, sha1_to_hex(target->sha1));
+       preq = new_http_pack_request(target, repo->url);
+       if (preq == NULL) {
+               release_http_pack_request(preq);
+               repo->can_update_info_refs = 0;
+               return;
+       }
+       preq->lst = &repo->packs;
 
        /* 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);
+                   !strcmp(check_request->url, preq->url)) {
+                       release_http_pack_request(preq);
                        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",
-                       request->tmpfile);
-               repo->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);
-       }
+       preq->slot->callback_func = process_response;
+       preq->slot->callback_data = request;
+       request->slot = preq->slot;
+       request->userData = preq;
 
        /* Try to get the request started, abort the request on error */
        request->state = RUN_FETCH_PACKED;
-       if (!start_active_slot(slot)) {
+       if (!start_active_slot(preq->slot)) {
                fprintf(stderr, "Unable to start GET request\n");
+               release_http_pack_request(preq);
                repo->can_update_info_refs = 0;
                release_request(request);
        }
@@ -598,10 +408,10 @@ static void start_put(struct transfer_request *request)
        curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &request->buffer);
 #endif
        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+       curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
        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)) {
@@ -711,19 +521,14 @@ static void release_request(struct transfer_request *request)
                        entry->next = entry->next->next;
        }
 
-       if (request->local_fileno != -1)
-               close(request->local_fileno);
-       if (request->local_stream)
-               fclose(request->local_stream);
        free(request->url);
        free(request);
 }
 
 static void finish_request(struct transfer_request *request)
 {
-       struct stat st;
-       struct packed_git *target;
-       struct packed_git **lst;
+       struct http_pack_request *preq;
+       struct http_object_request *obj_req;
 
        request->curl_result = request->slot->curl_result;
        request->http_code = request->slot->http_code;
@@ -778,76 +583,46 @@ static void finish_request(struct transfer_request *request)
                        aborted = 1;
                }
        } else if (request->state == RUN_FETCH_LOOSE) {
-               close(request->local_fileno); request->local_fileno = -1;
+               obj_req = (struct http_object_request *)request->userData;
 
-               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)
-                               warning("requested range invalid; we may already have all the data.");
-
-                       git_inflate_end(&request->stream);
-                       git_SHA1_Final(request->real_sha1, &request->c);
-                       if (request->zret != Z_STREAM_END) {
-                               unlink(request->tmpfile);
-                       } else if (hashcmp(request->obj->sha1, request->real_sha1)) {
-                               unlink(request->tmpfile);
-                       } else {
-                               request->rename =
-                                       move_temp_to_file(
-                                               request->tmpfile,
-                                               request->filename);
-                               if (request->rename == 0) {
-                                       request->obj->flags |= (LOCAL | REMOTE);
-                               }
-                       }
-               }
+               if (finish_http_object_request(obj_req) == 0)
+                       if (obj_req->rename == 0)
+                               request->obj->flags |= (LOCAL | REMOTE);
 
                /* Try fetching packed if necessary */
-               if (request->obj->flags & LOCAL)
+               if (request->obj->flags & LOCAL) {
+                       release_http_object_request(obj_req);
                        release_request(request);
-               else
+               else
                        start_fetch_packed(request);
 
        } else if (request->state == RUN_FETCH_PACKED) {
+               int fail = 1;
                if (request->curl_result != CURLE_OK) {
                        fprintf(stderr, "Unable to get pack file %s\n%s",
                                request->url, curl_errorstr);
-                       repo->can_update_info_refs = 0;
                } else {
-                       off_t pack_size = ftell(request->local_stream);
-
-                       fclose(request->local_stream);
-                       request->local_stream = NULL;
-                       if (!move_temp_to_file(request->tmpfile,
-                                              request->filename)) {
-                               target = (struct packed_git *)request->userData;
-                               target->pack_size = pack_size;
-                               lst = &repo->packs;
-                               while (*lst != target)
-                                       lst = &((*lst)->next);
-                               *lst = (*lst)->next;
-
-                               if (!verify_pack(target))
-                                       install_packed_git(target);
-                               else
-                                       repo->can_update_info_refs = 0;
+                       preq = (struct http_pack_request *)request->userData;
+
+                       if (preq) {
+                               if (finish_http_pack_request(preq) == 0)
+                                       fail = 0;
+                               release_http_pack_request(preq);
                        }
                }
+               if (fail)
+                       repo->can_update_info_refs = 0;
                release_request(request);
        }
 }
 
 #ifdef USE_CURL_MULTI
+static int is_running_queue;
 static int fill_active_slot(void *unused)
 {
        struct transfer_request *request;
 
-       if (aborted)
+       if (aborted || !is_running_queue)
                return 0;
 
        for (request = request_queue_head; request; request = request->next) {
@@ -890,8 +665,6 @@ static void add_fetch_request(struct object *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;
@@ -930,8 +703,6 @@ static int add_send_request(struct object *obj, struct remote_lock *lock)
        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;
@@ -944,176 +715,23 @@ static int add_send_request(struct object *obj, struct remote_lock *lock)
        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(repo->url) + 64);
-       sprintf(url, "%sobjects/pack/pack-%s.pack", repo->url, hex);
-       slot = get_active_slot();
-       slot->results = &results;
-       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
-       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 {
-               free(url);
-               return error("Unable to start request");
-       }
-
-       if (has_pack_index(sha1)) {
-               free(url);
-               return 0;
-       }
-
-       if (push_verbosely)
-               fprintf(stderr, "Getting index for pack %s\n", hex);
-
-       sprintf(url, "%sobjects/pack/pack-%s.idx", repo->url, hex);
-
-       filename = sha1_pack_index_name(sha1);
-       snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
-       indexfile = fopen(tmpfile, "a");
-       if (!indexfile) {
-               free(url);
-               return error("Unable to open local file %s for pack index",
-                            tmpfile);
-       }
-
-       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 = repo->packs;
-       repo->packs = new_pack;
-       return 0;
-}
-
 static int fetch_indices(void)
 {
-       unsigned char sha1[20];
-       char *url;
-       struct strbuf buffer = STRBUF_INIT;
-       char *data;
-       int i = 0;
-
-       struct active_request_slot *slot;
-       struct slot_results results;
+       int ret;
 
        if (push_verbosely)
                fprintf(stderr, "Getting pack list\n");
 
-       url = xmalloc(strlen(repo->url) + 20);
-       sprintf(url, "%sobjects/info/packs", repo->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) {
-                       strbuf_release(&buffer);
-                       free(url);
-                       if (results.http_code == 404)
-                               return 0;
-                       else
-                               return error("%s", curl_errorstr);
-               }
-       } else {
-               strbuf_release(&buffer);
-               free(url);
-               return error("Unable to start request");
-       }
-       free(url);
-
-       data = buffer.buf;
-       while (i < buffer.len) {
-               switch (data[i]) {
-               case 'P':
-                       i++;
-                       if (i + 52 < buffer.len &&
-                           !prefixcmp(data + i, " pack-") &&
-                           !prefixcmp(data + i + 46, ".pack\n")) {
-                               get_sha1_hex(data + i + 6, sha1);
-                               setup_index(sha1);
-                               i += 51;
-                               break;
-                       }
-               default:
-                       while (data[i] != '\n')
-                               i++;
-               }
-               i++;
+       switch (http_get_info_packs(repo->url, &repo->packs)) {
+       case HTTP_OK:
+       case HTTP_MISSING_TARGET:
+               ret = 0;
+               break;
+       default:
+               ret = -1;
        }
 
-       strbuf_release(&buffer);
-       return 0;
+       return ret;
 }
 
 static void one_remote_object(const char *hex)
@@ -1415,8 +1033,9 @@ static void remove_locks(void)
 
        fprintf(stderr, "Removing remote locks...\n");
        while (lock) {
+               struct remote_lock *next = lock->next;
                unlock_remote(lock);
-               lock = lock->next;
+               lock = next;
        }
 }
 
@@ -1843,7 +1462,7 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock)
        return 1;
 }
 
-static struct ref *remote_refs, **remote_tail;
+static struct ref *remote_refs;
 
 static void one_remote_ref(char *refname)
 {
@@ -1873,27 +1492,15 @@ static void one_remote_ref(char *refname)
                }
        }
 
-       *remote_tail = ref;
-       remote_tail = &ref->next;
+       ref->next = remote_refs;
+       remote_refs = 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 add_remote_info_ref(struct remote_ls_ctx *ls)
 {
        struct strbuf *buf = (struct strbuf *)ls->userData;
@@ -1987,29 +1594,22 @@ static void update_remote_info_refs(struct remote_lock *lock)
 static int remote_exists(const char *path)
 {
        char *url = xmalloc(strlen(repo->url) + strlen(path) + 1);
-       struct active_request_slot *slot;
-       struct slot_results results;
-       int ret = -1;
+       int ret;
 
        sprintf(url, "%s%s", repo->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)
-                       ret = 0;
-               else if (results.curl_result == CURLE_OK)
-                       ret = 1;
-               else
-                       fprintf(stderr, "HEAD HTTP error %ld\n", results.http_code);
-       } else {
-               fprintf(stderr, "Unable to start HEAD request\n");
+       switch (http_get_strbuf(url, NULL, 0)) {
+       case HTTP_OK:
+               ret = 1;
+               break;
+       case HTTP_MISSING_TARGET:
+               ret = 0;
+               break;
+       case HTTP_ERROR:
+               http_error(url, HTTP_ERROR);
+       default:
+               ret = -1;
        }
-
        free(url);
        return ret;
 }
@@ -2018,27 +1618,13 @@ static void fetch_symref(const char *path, char **symref, unsigned char *sha1)
 {
        char *url;
        struct strbuf buffer = STRBUF_INIT;
-       struct active_request_slot *slot;
-       struct slot_results results;
 
        url = xmalloc(strlen(repo->url) + strlen(path) + 1);
        sprintf(url, "%s%s", repo->url, path);
 
-       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");
-       }
+       if (http_get_strbuf(url, &buffer, 0) != HTTP_OK)
+               die("Couldn't get %s for remote symref\n%s", url,
+                   curl_errorstr);
        free(url);
 
        free(*symref);
@@ -2119,13 +1705,13 @@ static int delete_remote_branch(char *pattern, int force)
                /* Remote HEAD must resolve to a known object */
                if (symref)
                        return error("Remote HEAD symrefs too deep");
-               if (is_zero_sha1(head_sha1))
+               if (is_null_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))
+               if (is_null_sha1(remote_ref->old_sha1))
                        return error("Unable to resolve remote branch %s",
                                     remote_ref->name);
                if (!has_sha1_file(remote_ref->old_sha1))
@@ -2167,6 +1753,25 @@ static int delete_remote_branch(char *pattern, int force)
        return 0;
 }
 
+static void run_request_queue(void)
+{
+#ifdef USE_CURL_MULTI
+       is_running_queue = 1;
+       fill_active_slots();
+       add_fill_function(NULL, fill_active_slot);
+#endif
+       do {
+               finish_all_active_slots();
+#ifdef USE_CURL_MULTI
+               fill_active_slots();
+#endif
+       } while (request_queue_head && !aborted);
+
+#ifdef USE_CURL_MULTI
+       is_running_queue = 0;
+#endif
+}
+
 int main(int argc, char **argv)
 {
        struct transfer_request *request;
@@ -2188,8 +1793,6 @@ int main(int argc, char **argv)
 
        git_extract_argv0_path(argv[0]);
 
-       setup_git_directory();
-
        repo = xcalloc(sizeof(*repo), 1);
 
        argv++;
@@ -2209,8 +1812,13 @@ int main(int argc, char **argv)
                                dry_run = 1;
                                continue;
                        }
+                       if (!strcmp(arg, "--helper-status")) {
+                               helper_status = 1;
+                               continue;
+                       }
                        if (!strcmp(arg, "--verbose")) {
                                push_verbosely = 1;
+                               http_is_verbose = 1;
                                continue;
                        }
                        if (!strcmp(arg, "-d")) {
@@ -2222,6 +1830,8 @@ int main(int argc, char **argv)
                                force_delete = 1;
                                continue;
                        }
+                       if (!strcmp(arg, "-h"))
+                               usage(http_push_usage);
                }
                if (!repo->url) {
                        char *path = strstr(arg, "//");
@@ -2249,6 +1859,8 @@ int main(int argc, char **argv)
        if (delete_branch && nr_refspec != 1)
                die("You must specify only one branch name when deleting a remote branch");
 
+       setup_git_directory();
+
        memset(remote_dir_exists, -1, 256);
 
        /*
@@ -2260,8 +1872,6 @@ int main(int argc, char **argv)
        remote->url[remote->url_nr++] = repo->url;
        http_init(remote);
 
-       no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
-
        if (repo->url && repo->url[strlen(repo->url)-1] != '/') {
                rewritten_url = xmalloc(strlen(repo->url)+2);
                strcpy(rewritten_url, repo->url);
@@ -2271,6 +1881,10 @@ int main(int argc, char **argv)
                repo->url = rewritten_url;
        }
 
+#ifdef USE_CURL_MULTI
+       is_running_queue = 0;
+#endif
+
        /* Verify DAV compliance/lock support */
        if (!locking_available()) {
                rc = 1;
@@ -2300,25 +1914,29 @@ int main(int argc, char **argv)
        local_refs = get_local_heads();
        fprintf(stderr, "Fetching remote heads...\n");
        get_dav_remote_heads();
+       run_request_queue();
 
        /* Remove a remote branch if -d or -D was specified */
        if (delete_branch) {
-               if (delete_remote_branch(refspec[0], force_delete) == -1)
+               if (delete_remote_branch(refspec[0], force_delete) == -1) {
                        fprintf(stderr, "Unable to delete remote branch %s\n",
                                refspec[0]);
+                       if (helper_status)
+                               printf("error %s cannot remove\n", refspec[0]);
+               }
                goto cleanup;
        }
 
        /* match them up */
-       if (!remote_tail)
-               remote_tail = &remote_refs;
-       if (match_refs(local_refs, remote_refs, &remote_tail,
+       if (match_refs(local_refs, &remote_refs,
                       nr_refspec, (const char **) refspec, push_all)) {
                rc = -1;
                goto cleanup;
        }
        if (!remote_refs) {
                fprintf(stderr, "No refs in common and none specified; doing nothing.\n");
+               if (helper_status)
+                       printf("error null no match\n");
                rc = 0;
                goto cleanup;
        }
@@ -2326,30 +1944,36 @@ int main(int argc, char **argv)
        new_refs = 0;
        for (ref = remote_refs; ref; ref = ref->next) {
                char old_hex[60], *new_hex;
-               const char *commit_argv[4];
+               const char *commit_argv[5];
                int commit_argc;
                char *new_sha1_hex, *old_sha1_hex;
 
                if (!ref->peer_ref)
                        continue;
 
-               if (is_zero_sha1(ref->peer_ref->new_sha1)) {
+               if (is_null_sha1(ref->peer_ref->new_sha1)) {
                        if (delete_remote_branch(ref->name, 1) == -1) {
                                error("Could not remove %s", ref->name);
+                               if (helper_status)
+                                       printf("error %s cannot remove\n", ref->name);
                                rc = -4;
                        }
+                       else if (helper_status)
+                               printf("ok %s\n", ref->name);
                        new_refs++;
                        continue;
                }
 
                if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
-                       if (push_verbosely || 1)
+                       if (push_verbosely)
                                fprintf(stderr, "'%s': up-to-date\n", ref->name);
+                       if (helper_status)
+                               printf("ok %s up to date\n", ref->name);
                        continue;
                }
 
                if (!force_all &&
-                   !is_zero_sha1(ref->old_sha1) &&
+                   !is_null_sha1(ref->old_sha1) &&
                    !ref->force) {
                        if (!has_sha1_file(ref->old_sha1) ||
                            !ref_newer(ref->peer_ref->new_sha1,
@@ -2368,6 +1992,8 @@ int main(int argc, char **argv)
                                      "need to pull first?",
                                      ref->name,
                                      ref->peer_ref->name);
+                               if (helper_status)
+                                       printf("error %s non-fast forward\n", ref->name);
                                rc = -2;
                                continue;
                        }
@@ -2381,14 +2007,19 @@ int main(int argc, char **argv)
                if (strcmp(ref->name, ref->peer_ref->name))
                        fprintf(stderr, " using '%s'", ref->peer_ref->name);
                fprintf(stderr, "\n  from %s\n  to   %s\n", old_hex, new_hex);
-               if (dry_run)
+               if (dry_run) {
+                       if (helper_status)
+                               printf("ok %s\n", ref->name);
                        continue;
+               }
 
                /* 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);
+                       if (helper_status)
+                               printf("error %s lock error\n", ref->name);
                        rc = 1;
                        continue;
                }
@@ -2399,13 +2030,14 @@ int main(int argc, char **argv)
                old_sha1_hex = NULL;
                commit_argv[1] = "--objects";
                commit_argv[2] = new_sha1_hex;
-               if (!push_all && !is_zero_sha1(ref->old_sha1)) {
+               if (!push_all && !is_null_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++;
                }
+               commit_argv[commit_argc] = NULL;
                init_revisions(&revs, setup_git_directory());
                setup_revisions(commit_argc, commit_argv, &revs, NULL);
                revs.edge_hint = 0; /* just in case */
@@ -2429,16 +2061,8 @@ int main(int argc, char **argv)
                if (objects_to_send)
                        fprintf(stderr, "    sending %d objects\n",
                                objects_to_send);
-#ifdef USE_CURL_MULTI
-               fill_active_slots();
-               add_fill_function(NULL, fill_active_slot);
-#endif
-               do {
-                       finish_all_active_slots();
-#ifdef USE_CURL_MULTI
-                       fill_active_slots();
-#endif
-               } while (request_queue_head && !aborted);
+
+               run_request_queue();
 
                /* Update the remote branch if all went well */
                if (aborted || !update_remote(ref->new_sha1, ref_lock))
@@ -2446,6 +2070,8 @@ int main(int argc, char **argv)
 
                if (!rc)
                        fprintf(stderr, "    done\n");
+               if (helper_status)
+                       printf("%s %s\n", !rc ? "ok" : "error", ref->name);
                unlock_remote(ref_lock);
                check_locks();
        }
@@ -2467,8 +2093,6 @@ int main(int argc, char **argv)
                unlock_remote(info_ref_lock);
        free(repo);
 
-       curl_slist_free_all(no_pragma_header);
-
        http_cleanup();
 
        request = request_queue_head;
index c5a3ea3b31045be9407579a9d1aba222ee3e9914..18bd6504beb99ab68f360c4fa93011efae42fdfb 100644 (file)
@@ -1,12 +1,8 @@
 #include "cache.h"
 #include "commit.h"
-#include "pack.h"
 #include "walker.h"
 #include "http.h"
 
-#define PREV_BUF_SIZE 4096
-#define RANGE_HEADER_SIZE 30
-
 struct alt_base
 {
        char *base;
@@ -19,7 +15,7 @@ enum object_request_state {
        WAITING,
        ABORTED,
        ACTIVE,
-       COMPLETE,
+       COMPLETE
 };
 
 struct object_request
@@ -27,20 +23,8 @@ struct object_request
        struct walker *walker;
        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];
-       git_SHA_CTX c;
-       z_stream stream;
-       int zret;
-       int rename;
-       struct active_request_slot *slot;
+       struct http_object_request *req;
        struct object_request *next;
 };
 
@@ -57,39 +41,10 @@ struct walker_data {
        const char *url;
        int got_alternates;
        struct alt_base *alt;
-       struct curl_slist *no_pragma_header;
 };
 
 static struct object_request *object_queue_head;
 
-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 = xwrite(obj_req->local,
-                                      (char *) 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 = git_inflate(&obj_req->stream, Z_SYNC_FLUSH);
-               git_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(struct walker *walker, const char *base);
 
 static void process_object_response(void *callback_data);
@@ -97,165 +52,35 @@ static void process_object_response(void *callback_data);
 static void start_object_request(struct walker *walker,
                                 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;
-       struct walker_data *data = walker->data;
+       struct http_object_request *req;
 
-       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) {
+       req = new_http_object_request(obj_req->repo->base, obj_req->sha1);
+       if (req == NULL) {
                obj_req->state = ABORTED;
-               error("Couldn't create temporary file %s for %s: %s",
-                     obj_req->tmpfile, obj_req->filename, strerror(errno));
                return;
        }
+       obj_req->req = req;
 
-       memset(&obj_req->stream, 0, sizeof(obj_req->stream));
-
-       git_inflate_init(&obj_req->stream);
-
-       git_SHA1_Init(&obj_req->c);
-
-       url = xmalloc(strlen(obj_req->repo->base) + 51);
-       obj_req->url = xmalloc(strlen(obj_req->repo->base) + 51);
-       strcpy(url, obj_req->repo->base);
-       posn = url + strlen(obj_req->repo->base);
-       strcpy(posn, "/objects/");
-       posn += 9;
-       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 = xread(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));
-               git_inflate_init(&obj_req->stream);
-               git_SHA1_Init(&obj_req->c);
-               if (prev_posn>0) {
-                       prev_posn = 0;
-                       lseek(obj_req->local, 0, SEEK_SET);
-                       ftruncate(obj_req->local, 0);
-               }
-       }
-
-       slot = get_active_slot();
+       slot = req->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, data->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 (walker->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);
+               release_http_object_request(req);
                return;
        }
 }
 
 static void finish_object_request(struct object_request *obj_req)
 {
-       struct stat st;
-
-       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);
+       if (finish_http_object_request(obj_req->req))
                return;
-       }
 
-       git_inflate_end(&obj_req->stream);
-       git_SHA1_Final(obj_req->real_sha1, &obj_req->c);
-       if (obj_req->zret != Z_STREAM_END) {
-               unlink(obj_req->tmpfile);
-               return;
-       }
-       if (hashcmp(obj_req->sha1, obj_req->real_sha1)) {
-               unlink(obj_req->tmpfile);
-               return;
-       }
-       obj_req->rename =
-               move_temp_to_file(obj_req->tmpfile, obj_req->filename);
-
-       if (obj_req->rename == 0)
+       if (obj_req->req->rename == 0)
                walker_say(obj_req->walker, "got %s\n", sha1_to_hex(obj_req->sha1));
 }
 
@@ -267,19 +92,16 @@ static void process_object_response(void *callback_data)
        struct walker_data *data = walker->data;
        struct alt_base *alt = data->alt;
 
-       obj_req->curl_result = obj_req->slot->curl_result;
-       obj_req->http_code = obj_req->slot->http_code;
-       obj_req->slot = NULL;
+       process_http_object_request(obj_req->req);
        obj_req->state = COMPLETE;
 
        /* Use alternates if necessary */
-       if (missing_target(obj_req)) {
+       if (missing_target(obj_req->req)) {
                fetch_alternates(walker, alt->base);
                if (obj_req->repo->next != NULL) {
                        obj_req->repo =
                                obj_req->repo->next;
-                       close(obj_req->local);
-                       obj_req->local = -1;
+                       release_http_object_request(obj_req->req);
                        start_object_request(walker, obj_req);
                        return;
                }
@@ -292,8 +114,8 @@ 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->req !=NULL && obj_req->req->localfile != -1)
+               error("fd leakage in release: %d", obj_req->req->localfile);
        if (obj_req == object_queue_head) {
                object_queue_head = obj_req->next;
        } else {
@@ -303,7 +125,6 @@ static void release_object_request(struct object_request *obj_req)
                        entry->next = entry->next->next;
        }
 
-       free(obj_req->url);
        free(obj_req);
 }
 
@@ -331,28 +152,23 @@ static void prefetch(struct walker *walker, unsigned char *sha1)
        struct object_request *newreq;
        struct object_request *tail;
        struct walker_data *data = walker->data;
-       char *filename = sha1_file_name(sha1);
 
        newreq = xmalloc(sizeof(*newreq));
        newreq->walker = walker;
        hashcpy(newreq->sha1, sha1);
        newreq->repo = data->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->req = NULL;
        newreq->next = NULL;
 
+       http_is_verbose = walker->get_verbosely;
+
        if (object_queue_head == NULL) {
                object_queue_head = newreq;
        } else {
                tail = object_queue_head;
-               while (tail->next != NULL) {
+               while (tail->next != NULL)
                        tail = tail->next;
-               }
                tail->next = newreq;
        }
 
@@ -362,92 +178,6 @@ static void prefetch(struct walker *walker, unsigned char *sha1)
 #endif
 }
 
-static int fetch_index(struct walker *walker, 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;
-       struct walker_data *data = walker->data;
-
-       FILE *indexfile;
-       struct active_request_slot *slot;
-       struct slot_results results;
-
-       if (has_pack_index(sha1))
-               return 0;
-
-       if (walker->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",
-                            tmpfile);
-
-       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, data->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 (walker->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 walker *walker, 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(walker, repo, sha1))
-               return -1;
-
-       new_pack = parse_pack_index(sha1);
-       if (!new_pack)
-               return -1; /* parse_pack_index() already issued error message */
-       new_pack->next = repo->packs;
-       repo->packs = new_pack;
-       return 0;
-}
-
 static void process_alternates_response(void *callback_data)
 {
        struct alternates_request *alt_req =
@@ -504,7 +234,8 @@ static void process_alternates_response(void *callback_data)
                        struct alt_base *newalt;
                        char *target = NULL;
                        if (data[i] == '/') {
-                               /* This counts
+                               /*
+                                * This counts
                                 * http://git.host/pub/scm/linux.git/
                                 * -----------here^
                                 * so memcpy(dst, base, serverlen) will
@@ -517,7 +248,8 @@ static void process_alternates_response(void *callback_data)
                                        okay = 1;
                                }
                        } else if (!memcmp(data + i, "../", 3)) {
-                               /* Relative URL; chop the corresponding
+                               /*
+                                * Relative URL; chop the corresponding
                                 * number of subpath from base (and ../
                                 * from data), and concatenate the result.
                                 *
@@ -546,7 +278,7 @@ static void process_alternates_response(void *callback_data)
                                }
                                /* If the server got removed, give up. */
                                okay = strchr(base, ':') - base + 3 <
-                                       serverlen;
+                                      serverlen;
                        } else if (alt_req->http_specific) {
                                char *colon = strchr(data + i, ':');
                                char *slash = strchr(data + i, '/');
@@ -590,9 +322,11 @@ static void fetch_alternates(struct walker *walker, const char *base)
        struct alternates_request alt_req;
        struct walker_data *cdata = walker->data;
 
-       /* If another request has already started fetching alternates,
-          wait for them to arrive and return to processing this request's
-          curl message */
+       /*
+        * 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 (cdata->got_alternates == 0) {
                step_active_slots();
@@ -612,8 +346,10 @@ static void fetch_alternates(struct walker *walker, const char *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 */
+       /*
+        * 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;
        alt_req.walker = walker;
@@ -640,15 +376,7 @@ static void fetch_alternates(struct walker *walker, const char *base)
 
 static int fetch_indices(struct walker *walker, struct alt_base *repo)
 {
-       unsigned char sha1[20];
-       char *url;
-       struct strbuf buffer = STRBUF_INIT;
-       char *data;
-       int i = 0;
-       int ret = 0;
-
-       struct active_request_slot *slot;
-       struct slot_results results;
+       int ret;
 
        if (repo->got_indices)
                return 0;
@@ -656,76 +384,26 @@ static int fetch_indices(struct walker *walker, struct alt_base *repo)
        if (walker->get_verbosely)
                fprintf(stderr, "Getting pack list for %s\n", repo->base);
 
-       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 (missing_target(&results)) {
-                               repo->got_indices = 1;
-                               goto cleanup;
-                       } else {
-                               repo->got_indices = 0;
-                               ret = error("%s", curl_errorstr);
-                               goto cleanup;
-                       }
-               }
-       } else {
+       switch (http_get_info_packs(repo->base, &repo->packs)) {
+       case HTTP_OK:
+       case HTTP_MISSING_TARGET:
+               repo->got_indices = 1;
+               ret = 0;
+               break;
+       default:
                repo->got_indices = 0;
-               ret = error("Unable to start request");
-               goto cleanup;
-       }
-
-       data = buffer.buf;
-       while (i < buffer.len) {
-               switch (data[i]) {
-               case 'P':
-                       i++;
-                       if (i + 52 <= buffer.len &&
-                           !prefixcmp(data + i, " pack-") &&
-                           !prefixcmp(data + i + 46, ".pack\n")) {
-                               get_sha1_hex(data + i + 6, sha1);
-                               setup_index(walker, repo, sha1);
-                               i += 51;
-                               break;
-                       }
-               default:
-                       while (i < buffer.len && data[i] != '\n')
-                               i++;
-               }
-               i++;
+               ret = -1;
        }
 
-       repo->got_indices = 1;
-cleanup:
-       strbuf_release(&buffer);
-       free(url);
        return ret;
 }
 
 static int fetch_pack(struct walker *walker, 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 walker_data *data = walker->data;
-
-       struct active_request_slot *slot;
        struct slot_results results;
+       struct http_pack_request *preq;
 
        if (fetch_indices(walker, repo))
                return -1;
@@ -740,80 +418,37 @@ static int fetch_pack(struct walker *walker, struct alt_base *repo, unsigned cha
                        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));
+       preq = new_http_pack_request(target, repo->base);
+       if (preq == NULL)
+               goto abort;
+       preq->lst = &repo->packs;
+       preq->slot->results = &results;
 
-       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",
-                            tmpfile);
-
-       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, data->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 (walker->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 (start_active_slot(preq->slot)) {
+               run_active_slot(preq->slot);
                if (results.curl_result != CURLE_OK) {
-                       fclose(packfile);
-                       return error("Unable to get pack file %s\n%s", url,
-                                    curl_errorstr);
+                       error("Unable to get pack file %s\n%s", preq->url,
+                             curl_errorstr);
+                       goto abort;
                }
        } else {
-               fclose(packfile);
-               return error("Unable to start request");
+               error("Unable to start request");
+               goto abort;
        }
 
-       target->pack_size = ftell(packfile);
-       fclose(packfile);
-
-       ret = move_temp_to_file(tmpfile, filename);
+       ret = finish_http_pack_request(preq);
+       release_http_pack_request(preq);
        if (ret)
                return ret;
 
-       lst = &repo->packs;
-       while (*lst != target)
-               lst = &((*lst)->next);
-       *lst = (*lst)->next;
-
-       if (verify_pack(target))
-               return -1;
-       install_packed_git(target);
-
        return 0;
+
+abort:
+       return -1;
 }
 
 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);
 }
 
@@ -822,6 +457,7 @@ static int fetch_object(struct walker *walker, struct alt_base *repo, unsigned c
        char *hex = sha1_to_hex(sha1);
        int ret = 0;
        struct object_request *obj_req = object_queue_head;
+       struct http_object_request *req;
 
        while (obj_req != NULL && hashcmp(obj_req->sha1, sha1))
                obj_req = obj_req->next;
@@ -829,45 +465,55 @@ static int fetch_object(struct walker *walker, struct alt_base *repo, unsigned c
                return error("Couldn't find request for %s in the queue", hex);
 
        if (has_sha1_file(obj_req->sha1)) {
+               if (obj_req->req != NULL)
+                       abort_http_object_request(obj_req->req);
                abort_object_request(obj_req);
                return 0;
        }
 
 #ifdef USE_CURL_MULTI
-       while (obj_req->state == WAITING) {
+       while (obj_req->state == WAITING)
                step_active_slots();
-       }
 #else
        start_object_request(walker, 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;
+       /*
+        * obj_req->req might change when fetching alternates in the callback
+        * process_object_response; therefore, the "shortcut" variable, req,
+        * is used only after we're done with slots.
+        */
+       while (obj_req->state == ACTIVE)
+               run_active_slot(obj_req->req->slot);
+
+       req = obj_req->req;
+
+       if (req->localfile != -1) {
+               close(req->localfile);
+               req->localfile = -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 (missing_target(obj_req))
+       } else if (req->curl_result != CURLE_OK &&
+                  req->http_code != 416) {
+               if (missing_target(req))
                        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) {
+                                   req->errorstr, req->curl_result,
+                                   req->http_code, hex);
+       } else if (req->zret != Z_STREAM_END) {
                walker->corrupt_object_found++;
-               ret = error("File %s (%s) corrupt", hex, obj_req->url);
-       } else if (hashcmp(obj_req->sha1, obj_req->real_sha1)) {
+               ret = error("File %s (%s) corrupt", hex, req->url);
+       } else if (hashcmp(obj_req->sha1, req->real_sha1)) {
                ret = error("File %s has bad hash", hex);
-       } else if (obj_req->rename < 0) {
+       } else if (req->rename < 0) {
                ret = error("unable to write sha1 filename %s",
-                           obj_req->filename);
+                           sha1_file_name(req->sha1));
        }
 
+       release_http_object_request(req);
        release_object_request(obj_req);
        return ret;
 }
@@ -898,21 +544,29 @@ static int fetch_ref(struct walker *walker, struct ref *ref)
 static void cleanup(struct walker *walker)
 {
        struct walker_data *data = walker->data;
-       http_cleanup();
+       struct alt_base *alt, *alt_next;
+
+       if (data) {
+               alt = data->alt;
+               while (alt) {
+                       alt_next = alt->next;
 
-       curl_slist_free_all(data->no_pragma_header);
+                       free(alt->base);
+                       free(alt);
+
+                       alt = alt_next;
+               }
+               free(data);
+               walker->data = NULL;
+       }
 }
 
-struct walker *get_http_walker(const char *url, struct remote *remote)
+struct walker *get_http_walker(const char *url)
 {
        char *s;
        struct walker_data *data = xmalloc(sizeof(struct walker_data));
        struct walker *walker = xmalloc(sizeof(struct walker));
 
-       http_init(remote);
-
-       data->no_pragma_header = curl_slist_append(NULL, "Pragma:");
-
        data->alt = xmalloc(sizeof(*data->alt));
        data->alt->base = xmalloc(strlen(url) + 1);
        strcpy(data->alt->base, url);
diff --git a/http.c b/http.c
index 2e3d6493ef40e1b34fea8d166d760f0b1fcccafc..0a5011f615bdfade0b69682b177dc97471ac20a3 100644 (file)
--- a/http.c
+++ b/http.c
@@ -1,8 +1,19 @@
 #include "http.h"
+#include "pack.h"
+#include "sideband.h"
+#include "run-command.h"
 
 int data_received;
 int active_requests;
+int http_is_verbose;
+size_t http_post_buffer = 16 * LARGE_PACKET_MAX;
 
+#if LIBCURL_VERSION_NUM >= 0x070a06
+#define LIBCURL_CAN_HANDLE_AUTH_ANY
+#endif
+
+static int min_curl_sessions = 1;
+static int curl_session_count;
 #ifdef USE_CURL_MULTI
 static int max_requests = -1;
 static CURLM *curlm;
@@ -10,11 +21,15 @@ static CURLM *curlm;
 #ifndef NO_CURL_EASY_DUPHANDLE
 static CURL *curl_default;
 #endif
+
+#define PREV_BUF_SIZE 4096
+#define RANGE_HEADER_SIZE 30
+
 char curl_errorstr[CURL_ERROR_SIZE];
 
 static int curl_ssl_verify = -1;
 static const char *ssl_cert;
-#if LIBCURL_VERSION_NUM >= 0x070902
+#if LIBCURL_VERSION_NUM >= 0x070903
 static const char *ssl_key;
 #endif
 #if LIBCURL_VERSION_NUM >= 0x070908
@@ -26,8 +41,21 @@ static long curl_low_speed_time = -1;
 static int curl_ftp_no_epsv;
 static const char *curl_http_proxy;
 static char *user_name, *user_pass;
+static const char *user_agent;
+
+#if LIBCURL_VERSION_NUM >= 0x071700
+/* Use CURLOPT_KEYPASSWD as is */
+#elif LIBCURL_VERSION_NUM >= 0x070903
+#define CURLOPT_KEYPASSWD CURLOPT_SSLKEYPASSWD
+#else
+#define CURLOPT_KEYPASSWD CURLOPT_SSLCERTPASSWD
+#endif
+
+static char *ssl_cert_password;
+static int ssl_cert_password_required;
 
 static struct curl_slist *pragma_header;
+static struct curl_slist *no_pragma_header;
 
 static struct active_request_slot *active_queue_head;
 
@@ -79,8 +107,6 @@ size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf)
        return eltsize * nmemb;
 }
 
-static void finish_active_slot(struct active_request_slot *slot);
-
 #ifdef USE_CURL_MULTI
 static void process_curl_messages(void)
 {
@@ -119,7 +145,7 @@ static int http_options(const char *var, const char *value, void *cb)
        }
        if (!strcmp("http.sslcert", var))
                return git_config_string(&ssl_cert, var, value);
-#if LIBCURL_VERSION_NUM >= 0x070902
+#if LIBCURL_VERSION_NUM >= 0x070903
        if (!strcmp("http.sslkey", var))
                return git_config_string(&ssl_key, var, value);
 #endif
@@ -129,6 +155,19 @@ static int http_options(const char *var, const char *value, void *cb)
 #endif
        if (!strcmp("http.sslcainfo", var))
                return git_config_string(&ssl_cainfo, var, value);
+       if (!strcmp("http.sslcertpasswordprotected", var)) {
+               if (git_config_bool(var, value))
+                       ssl_cert_password_required = 1;
+               return 0;
+       }
+       if (!strcmp("http.minsessions", var)) {
+               min_curl_sessions = git_config_int(var, value);
+#ifndef USE_CURL_MULTI
+               if (min_curl_sessions > 1)
+                       min_curl_sessions = 1;
+#endif
+               return 0;
+       }
 #ifdef USE_CURL_MULTI
        if (!strcmp("http.maxrequests", var)) {
                max_requests = git_config_int(var, value);
@@ -151,6 +190,16 @@ static int http_options(const char *var, const char *value, void *cb)
        if (!strcmp("http.proxy", var))
                return git_config_string(&curl_http_proxy, var, value);
 
+       if (!strcmp("http.postbuffer", var)) {
+               http_post_buffer = git_config_int(var, value);
+               if (http_post_buffer < LARGE_PACKET_MAX)
+                       http_post_buffer = LARGE_PACKET_MAX;
+               return 0;
+       }
+
+       if (!strcmp("http.useragent", var))
+               return git_config_string(&user_agent, var, value);
+
        /* Fall back on the default ones */
        return git_default_config(var, value, cb);
 }
@@ -160,13 +209,29 @@ static void init_curl_http_auth(CURL *result)
        if (user_name) {
                struct strbuf up = STRBUF_INIT;
                if (!user_pass)
-                       user_pass = xstrdup(getpass("Password: "));
+                       user_pass = xstrdup(git_getpass("Password: "));
                strbuf_addf(&up, "%s:%s", user_name, user_pass);
                curl_easy_setopt(result, CURLOPT_USERPWD,
                                 strbuf_detach(&up, NULL));
        }
 }
 
+static int has_cert_password(void)
+{
+       if (ssl_cert_password != NULL)
+               return 1;
+       if (ssl_cert == NULL || ssl_cert_password_required != 1)
+               return 0;
+       /* Only prompt the user once. */
+       ssl_cert_password_required = -1;
+       ssl_cert_password = git_getpass("Certificate Password: ");
+       if (ssl_cert_password != NULL) {
+               ssl_cert_password = xstrdup(ssl_cert_password);
+               return 1;
+       } else
+               return 0;
+}
+
 static CURL *get_curl_handle(void)
 {
        CURL *result = curl_easy_init();
@@ -184,12 +249,17 @@ static CURL *get_curl_handle(void)
 #if LIBCURL_VERSION_NUM >= 0x070907
        curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
 #endif
+#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
+       curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
+#endif
 
        init_curl_http_auth(result);
 
        if (ssl_cert != NULL)
                curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
-#if LIBCURL_VERSION_NUM >= 0x070902
+       if (has_cert_password())
+               curl_easy_setopt(result, CURLOPT_KEYPASSWD, ssl_cert_password);
+#if LIBCURL_VERSION_NUM >= 0x070903
        if (ssl_key != NULL)
                curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
 #endif
@@ -213,7 +283,8 @@ static CURL *get_curl_handle(void)
        if (getenv("GIT_CURL_VERBOSE"))
                curl_easy_setopt(result, CURLOPT_VERBOSE, 1);
 
-       curl_easy_setopt(result, CURLOPT_USERAGENT, GIT_USER_AGENT);
+       curl_easy_setopt(result, CURLOPT_USERAGENT,
+               user_agent ? user_agent : GIT_HTTP_USER_AGENT);
 
        if (curl_ftp_no_epsv)
                curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0);
@@ -276,6 +347,8 @@ void http_init(struct remote *remote)
        char *low_speed_limit;
        char *low_speed_time;
 
+       http_is_verbose = 0;
+
        git_config(http_options, NULL);
 
        curl_global_init(CURL_GLOBAL_ALL);
@@ -284,6 +357,7 @@ void http_init(struct remote *remote)
                curl_http_proxy = xstrdup(remote->http_proxy);
 
        pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache");
+       no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
 
 #ifdef USE_CURL_MULTI
        {
@@ -303,7 +377,7 @@ void http_init(struct remote *remote)
                curl_ssl_verify = 0;
 
        set_from_env(&ssl_cert, "GIT_SSL_CERT");
-#if LIBCURL_VERSION_NUM >= 0x070902
+#if LIBCURL_VERSION_NUM >= 0x070903
        set_from_env(&ssl_key, "GIT_SSL_KEY");
 #endif
 #if LIBCURL_VERSION_NUM >= 0x070908
@@ -311,6 +385,8 @@ void http_init(struct remote *remote)
 #endif
        set_from_env(&ssl_cainfo, "GIT_SSL_CAINFO");
 
+       set_from_env(&user_agent, "GIT_HTTP_USER_AGENT");
+
        low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT");
        if (low_speed_limit != NULL)
                curl_low_speed_limit = strtol(low_speed_limit, NULL, 10);
@@ -321,6 +397,7 @@ void http_init(struct remote *remote)
        if (curl_ssl_verify == -1)
                curl_ssl_verify = 1;
 
+       curl_session_count = 0;
 #ifdef USE_CURL_MULTI
        if (max_requests < 1)
                max_requests = DEFAULT_MAX_REQUESTS;
@@ -329,8 +406,13 @@ void http_init(struct remote *remote)
        if (getenv("GIT_CURL_FTP_NO_EPSV"))
                curl_ftp_no_epsv = 1;
 
-       if (remote && remote->url && remote->url[0])
+       if (remote && remote->url && remote->url[0]) {
                http_auth_init(remote->url[0]);
+               if (!ssl_cert_password_required &&
+                   getenv("GIT_SSL_CERT_PASSWORD_PROTECTED") &&
+                   !prefixcmp(remote->url[0], "https://"))
+                       ssl_cert_password_required = 1;
+       }
 
 #ifndef NO_CURL_EASY_DUPHANDLE
        curl_default = get_curl_handle();
@@ -366,10 +448,20 @@ void http_cleanup(void)
        curl_slist_free_all(pragma_header);
        pragma_header = NULL;
 
+       curl_slist_free_all(no_pragma_header);
+       no_pragma_header = NULL;
+
        if (curl_http_proxy) {
                free((void *)curl_http_proxy);
                curl_http_proxy = NULL;
        }
+
+       if (ssl_cert_password != NULL) {
+               memset(ssl_cert_password, 0, strlen(ssl_cert_password));
+               free(ssl_cert_password);
+               ssl_cert_password = NULL;
+       }
+       ssl_cert_password_required = 0;
 }
 
 struct active_request_slot *get_active_slot(void)
@@ -414,6 +506,7 @@ struct active_request_slot *get_active_slot(void)
 #else
                slot->curl = curl_easy_duphandle(curl_default);
 #endif
+               curl_session_count++;
        }
 
        active_requests++;
@@ -492,9 +585,11 @@ void fill_active_slots(void)
        }
 
        while (slot != NULL) {
-               if (!slot->in_use && slot->curl != NULL) {
+               if (!slot->in_use && slot->curl != NULL
+                       && curl_session_count > min_curl_sessions) {
                        curl_easy_cleanup(slot->curl);
                        slot->curl = NULL;
+                       curl_session_count--;
                }
                slot = slot->next;
        }
@@ -564,22 +659,23 @@ static void closedown_active_slot(struct active_request_slot *slot)
        slot->in_use = 0;
 }
 
-void release_active_slot(struct active_request_slot *slot)
+static void release_active_slot(struct active_request_slot *slot)
 {
        closedown_active_slot(slot);
-       if (slot->curl) {
+       if (slot->curl && curl_session_count > min_curl_sessions) {
 #ifdef USE_CURL_MULTI
                curl_multi_remove_handle(curlm, slot->curl);
 #endif
                curl_easy_cleanup(slot->curl);
                slot->curl = NULL;
+               curl_session_count--;
        }
 #ifdef USE_CURL_MULTI
        fill_active_slots();
 #endif
 }
 
-static void finish_active_slot(struct active_request_slot *slot)
+void finish_active_slot(struct active_request_slot *slot)
 {
        closedown_active_slot(slot);
        curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code);
@@ -611,6 +707,7 @@ void finish_all_active_slots(void)
                }
 }
 
+/* Helpers for modifying and creating URLs */
 static inline int needs_quote(int ch)
 {
        if (((ch >= 'A') && (ch <= 'Z'))
@@ -631,15 +728,20 @@ static inline int hex(int v)
                return 'A' + v - 10;
 }
 
+void end_url_with_slash(struct strbuf *buf, const char *url)
+{
+       strbuf_addstr(buf, url);
+       if (buf->len && buf->buf[buf->len - 1] != '/')
+               strbuf_addstr(buf, "/");
+}
+
 static char *quote_ref_url(const char *base, const char *ref)
 {
        struct strbuf buf = STRBUF_INIT;
        const char *cp;
        int ch;
 
-       strbuf_addstr(&buf, base);
-       if (buf.len && buf.buf[buf.len - 1] != '/' && *ref != '/')
-               strbuf_addstr(&buf, "/");
+       end_url_with_slash(&buf, base);
 
        for (cp = ref; (ch = *cp) != 0; cp++)
                if (needs_quote(ch))
@@ -650,41 +752,639 @@ static char *quote_ref_url(const char *base, const char *ref)
        return strbuf_detach(&buf, NULL);
 }
 
-int http_fetch_ref(const char *base, struct ref *ref)
+void append_remote_object_url(struct strbuf *buf, const char *url,
+                             const char *hex,
+                             int only_two_digit_prefix)
+{
+       end_url_with_slash(buf, url);
+
+       strbuf_addf(buf, "objects/%.*s/", 2, hex);
+       if (!only_two_digit_prefix)
+               strbuf_addf(buf, "%s", hex+2);
+}
+
+char *get_remote_object_url(const char *url, const char *hex,
+                           int only_two_digit_prefix)
+{
+       struct strbuf buf = STRBUF_INIT;
+       append_remote_object_url(&buf, url, hex, only_two_digit_prefix);
+       return strbuf_detach(&buf, NULL);
+}
+
+/* http_request() targets */
+#define HTTP_REQUEST_STRBUF    0
+#define HTTP_REQUEST_FILE      1
+
+static int http_request(const char *url, void *result, int target, int options)
 {
-       char *url;
-       struct strbuf buffer = STRBUF_INIT;
        struct active_request_slot *slot;
        struct slot_results results;
+       struct curl_slist *headers = NULL;
+       struct strbuf buf = STRBUF_INIT;
        int ret;
 
-       url = quote_ref_url(base, ref->name);
        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_HTTPGET, 1);
+
+       if (result == NULL) {
+               curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1);
+       } else {
+               curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
+               curl_easy_setopt(slot->curl, CURLOPT_FILE, result);
+
+               if (target == HTTP_REQUEST_FILE) {
+                       long posn = ftell(result);
+                       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
+                                        fwrite);
+                       if (posn > 0) {
+                               strbuf_addf(&buf, "Range: bytes=%ld-", posn);
+                               headers = curl_slist_append(headers, buf.buf);
+                               strbuf_reset(&buf);
+                       }
+                       slot->local = result;
+               } else
+                       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
+                                        fwrite_buffer);
+       }
+
+       strbuf_addstr(&buf, "Pragma:");
+       if (options & HTTP_NO_CACHE)
+               strbuf_addstr(&buf, " no-cache");
+
+       headers = curl_slist_append(headers, buf.buf);
+
        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers);
+
        if (start_active_slot(slot)) {
                run_active_slot(slot);
-               if (results.curl_result == CURLE_OK) {
-                       strbuf_rtrim(&buffer);
-                       if (buffer.len == 40)
-                               ret = get_sha1_hex(buffer.buf, ref->old_sha1);
-                       else if (!prefixcmp(buffer.buf, "ref: ")) {
-                               ref->symref = xstrdup(buffer.buf + 5);
-                               ret = 0;
-                       } else
-                               ret = 1;
-               } else {
-                       ret = error("Couldn't get %s for %s\n%s",
-                                   url, ref->name, curl_errorstr);
-               }
+               if (results.curl_result == CURLE_OK)
+                       ret = HTTP_OK;
+               else if (missing_target(&results))
+                       ret = HTTP_MISSING_TARGET;
+               else if (results.http_code == 401) {
+                       if (user_name) {
+                               ret = HTTP_NOAUTH;
+                       } else {
+                               /*
+                                * git_getpass is needed here because its very likely stdin/stdout are
+                                * pipes to our parent process.  So we instead need to use /dev/tty,
+                                * but that is non-portable.  Using git_getpass() can at least be stubbed
+                                * on other platforms with a different implementation if/when necessary.
+                                */
+                               user_name = xstrdup(git_getpass("Username: "));
+                               init_curl_http_auth(slot->curl);
+                               ret = HTTP_REAUTH;
+                       }
+               } else
+                       ret = HTTP_ERROR;
        } else {
-               ret = error("Unable to start request");
+               error("Unable to start HTTP request for %s", url);
+               ret = HTTP_START_FAILED;
+       }
+
+       slot->local = NULL;
+       curl_slist_free_all(headers);
+       strbuf_release(&buf);
+
+       return ret;
+}
+
+int http_get_strbuf(const char *url, struct strbuf *result, int options)
+{
+       int http_ret = http_request(url, result, HTTP_REQUEST_STRBUF, options);
+       if (http_ret == HTTP_REAUTH) {
+               http_ret = http_request(url, result, HTTP_REQUEST_STRBUF, options);
+       }
+       return http_ret;
+}
+
+/*
+ * Downloads an url and stores the result in the given file.
+ *
+ * If a previous interrupted download is detected (i.e. a previous temporary
+ * file is still around) the download is resumed.
+ */
+static int http_get_file(const char *url, const char *filename, int options)
+{
+       int ret;
+       struct strbuf tmpfile = STRBUF_INIT;
+       FILE *result;
+
+       strbuf_addf(&tmpfile, "%s.temp", filename);
+       result = fopen(tmpfile.buf, "a");
+       if (! result) {
+               error("Unable to open local file %s", tmpfile.buf);
+               ret = HTTP_ERROR;
+               goto cleanup;
+       }
+
+       ret = http_request(url, result, HTTP_REQUEST_FILE, options);
+       fclose(result);
+
+       if ((ret == HTTP_OK) && move_temp_to_file(tmpfile.buf, filename))
+               ret = HTTP_ERROR;
+cleanup:
+       strbuf_release(&tmpfile);
+       return ret;
+}
+
+int http_error(const char *url, int ret)
+{
+       /* http_request has already handled HTTP_START_FAILED. */
+       if (ret != HTTP_START_FAILED)
+               error("%s while accessing %s\n", curl_errorstr, url);
+
+       return ret;
+}
+
+int http_fetch_ref(const char *base, struct ref *ref)
+{
+       char *url;
+       struct strbuf buffer = STRBUF_INIT;
+       int ret = -1;
+
+       url = quote_ref_url(base, ref->name);
+       if (http_get_strbuf(url, &buffer, HTTP_NO_CACHE) == HTTP_OK) {
+               strbuf_rtrim(&buffer);
+               if (buffer.len == 40)
+                       ret = get_sha1_hex(buffer.buf, ref->old_sha1);
+               else if (!prefixcmp(buffer.buf, "ref: ")) {
+                       ref->symref = xstrdup(buffer.buf + 5);
+                       ret = 0;
+               }
        }
 
        strbuf_release(&buffer);
        free(url);
        return ret;
 }
+
+/* Helpers for fetching packs */
+static char *fetch_pack_index(unsigned char *sha1, const char *base_url)
+{
+       char *url, *tmp;
+       struct strbuf buf = STRBUF_INIT;
+
+       if (http_is_verbose)
+               fprintf(stderr, "Getting index for pack %s\n", sha1_to_hex(sha1));
+
+       end_url_with_slash(&buf, base_url);
+       strbuf_addf(&buf, "objects/pack/pack-%s.idx", sha1_to_hex(sha1));
+       url = strbuf_detach(&buf, NULL);
+
+       strbuf_addf(&buf, "%s.temp", sha1_pack_index_name(sha1));
+       tmp = strbuf_detach(&buf, NULL);
+
+       if (http_get_file(url, tmp, 0) != HTTP_OK) {
+               error("Unable to get pack index %s\n", url);
+               free(tmp);
+               tmp = NULL;
+       }
+
+       free(url);
+       return tmp;
+}
+
+static int fetch_and_setup_pack_index(struct packed_git **packs_head,
+       unsigned char *sha1, const char *base_url)
+{
+       struct packed_git *new_pack;
+       char *tmp_idx = NULL;
+       int ret;
+
+       if (has_pack_index(sha1)) {
+               new_pack = parse_pack_index(sha1, NULL);
+               if (!new_pack)
+                       return -1; /* parse_pack_index() already issued error message */
+               goto add_pack;
+       }
+
+       tmp_idx = fetch_pack_index(sha1, base_url);
+       if (!tmp_idx)
+               return -1;
+
+       new_pack = parse_pack_index(sha1, tmp_idx);
+       if (!new_pack) {
+               unlink(tmp_idx);
+               free(tmp_idx);
+
+               return -1; /* parse_pack_index() already issued error message */
+       }
+
+       ret = verify_pack_index(new_pack);
+       if (!ret) {
+               close_pack_index(new_pack);
+               ret = move_temp_to_file(tmp_idx, sha1_pack_index_name(sha1));
+       }
+       free(tmp_idx);
+       if (ret)
+               return -1;
+
+add_pack:
+       new_pack->next = *packs_head;
+       *packs_head = new_pack;
+       return 0;
+}
+
+int http_get_info_packs(const char *base_url, struct packed_git **packs_head)
+{
+       int ret = 0, i = 0;
+       char *url, *data;
+       struct strbuf buf = STRBUF_INIT;
+       unsigned char sha1[20];
+
+       end_url_with_slash(&buf, base_url);
+       strbuf_addstr(&buf, "objects/info/packs");
+       url = strbuf_detach(&buf, NULL);
+
+       ret = http_get_strbuf(url, &buf, HTTP_NO_CACHE);
+       if (ret != HTTP_OK)
+               goto cleanup;
+
+       data = buf.buf;
+       while (i < buf.len) {
+               switch (data[i]) {
+               case 'P':
+                       i++;
+                       if (i + 52 <= buf.len &&
+                           !prefixcmp(data + i, " pack-") &&
+                           !prefixcmp(data + i + 46, ".pack\n")) {
+                               get_sha1_hex(data + i + 6, sha1);
+                               fetch_and_setup_pack_index(packs_head, sha1,
+                                                     base_url);
+                               i += 51;
+                               break;
+                       }
+               default:
+                       while (i < buf.len && data[i] != '\n')
+                               i++;
+               }
+               i++;
+       }
+
+cleanup:
+       free(url);
+       return ret;
+}
+
+void release_http_pack_request(struct http_pack_request *preq)
+{
+       if (preq->packfile != NULL) {
+               fclose(preq->packfile);
+               preq->packfile = NULL;
+               preq->slot->local = NULL;
+       }
+       if (preq->range_header != NULL) {
+               curl_slist_free_all(preq->range_header);
+               preq->range_header = NULL;
+       }
+       preq->slot = NULL;
+       free(preq->url);
+}
+
+int finish_http_pack_request(struct http_pack_request *preq)
+{
+       struct packed_git **lst;
+       struct packed_git *p = preq->target;
+       char *tmp_idx;
+       struct child_process ip;
+       const char *ip_argv[8];
+
+       close_pack_index(p);
+
+       fclose(preq->packfile);
+       preq->packfile = NULL;
+       preq->slot->local = NULL;
+
+       lst = preq->lst;
+       while (*lst != p)
+               lst = &((*lst)->next);
+       *lst = (*lst)->next;
+
+       tmp_idx = xstrdup(preq->tmpfile);
+       strcpy(tmp_idx + strlen(tmp_idx) - strlen(".pack.temp"),
+              ".idx.temp");
+
+       ip_argv[0] = "index-pack";
+       ip_argv[1] = "-o";
+       ip_argv[2] = tmp_idx;
+       ip_argv[3] = preq->tmpfile;
+       ip_argv[4] = NULL;
+
+       memset(&ip, 0, sizeof(ip));
+       ip.argv = ip_argv;
+       ip.git_cmd = 1;
+       ip.no_stdin = 1;
+       ip.no_stdout = 1;
+
+       if (run_command(&ip)) {
+               unlink(preq->tmpfile);
+               unlink(tmp_idx);
+               free(tmp_idx);
+               return -1;
+       }
+
+       unlink(sha1_pack_index_name(p->sha1));
+
+       if (move_temp_to_file(preq->tmpfile, sha1_pack_name(p->sha1))
+        || move_temp_to_file(tmp_idx, sha1_pack_index_name(p->sha1))) {
+               free(tmp_idx);
+               return -1;
+       }
+
+       install_packed_git(p);
+       free(tmp_idx);
+       return 0;
+}
+
+struct http_pack_request *new_http_pack_request(
+       struct packed_git *target, const char *base_url)
+{
+       long prev_posn = 0;
+       char range[RANGE_HEADER_SIZE];
+       struct strbuf buf = STRBUF_INIT;
+       struct http_pack_request *preq;
+
+       preq = xmalloc(sizeof(*preq));
+       preq->target = target;
+       preq->range_header = NULL;
+
+       end_url_with_slash(&buf, base_url);
+       strbuf_addf(&buf, "objects/pack/pack-%s.pack",
+               sha1_to_hex(target->sha1));
+       preq->url = strbuf_detach(&buf, NULL);
+
+       snprintf(preq->tmpfile, sizeof(preq->tmpfile), "%s.temp",
+               sha1_pack_name(target->sha1));
+       preq->packfile = fopen(preq->tmpfile, "a");
+       if (!preq->packfile) {
+               error("Unable to open local file %s for pack",
+                     preq->tmpfile);
+               goto abort;
+       }
+
+       preq->slot = get_active_slot();
+       preq->slot->local = preq->packfile;
+       curl_easy_setopt(preq->slot->curl, CURLOPT_FILE, preq->packfile);
+       curl_easy_setopt(preq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
+       curl_easy_setopt(preq->slot->curl, CURLOPT_URL, preq->url);
+       curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER,
+               no_pragma_header);
+
+       /*
+        * If there is data present from a previous transfer attempt,
+        * resume where it left off
+        */
+       prev_posn = ftell(preq->packfile);
+       if (prev_posn>0) {
+               if (http_is_verbose)
+                       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);
+               preq->range_header = curl_slist_append(NULL, range);
+               curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER,
+                       preq->range_header);
+       }
+
+       return preq;
+
+abort:
+       free(preq->url);
+       free(preq);
+       return NULL;
+}
+
+/* Helpers for fetching objects (loose) */
+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 http_object_request *freq =
+               (struct http_object_request *)data;
+       do {
+               ssize_t retval = xwrite(freq->localfile,
+                                       (char *) ptr + posn, size - posn);
+               if (retval < 0)
+                       return posn;
+               posn += retval;
+       } while (posn < size);
+
+       freq->stream.avail_in = size;
+       freq->stream.next_in = ptr;
+       do {
+               freq->stream.next_out = expn;
+               freq->stream.avail_out = sizeof(expn);
+               freq->zret = git_inflate(&freq->stream, Z_SYNC_FLUSH);
+               git_SHA1_Update(&freq->c, expn,
+                               sizeof(expn) - freq->stream.avail_out);
+       } while (freq->stream.avail_in && freq->zret == Z_OK);
+       data_received++;
+       return size;
+}
+
+struct http_object_request *new_http_object_request(const char *base_url,
+       unsigned char *sha1)
+{
+       char *hex = sha1_to_hex(sha1);
+       char *filename;
+       char prevfile[PATH_MAX];
+       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 http_object_request *freq;
+
+       freq = xmalloc(sizeof(*freq));
+       hashcpy(freq->sha1, sha1);
+       freq->localfile = -1;
+
+       filename = sha1_file_name(sha1);
+       snprintf(freq->tmpfile, sizeof(freq->tmpfile),
+                "%s.temp", filename);
+
+       snprintf(prevfile, sizeof(prevfile), "%s.prev", filename);
+       unlink_or_warn(prevfile);
+       rename(freq->tmpfile, prevfile);
+       unlink_or_warn(freq->tmpfile);
+
+       if (freq->localfile != -1)
+               error("fd leakage in start: %d", freq->localfile);
+       freq->localfile = open(freq->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 (freq->localfile < 0 && errno == ENOENT) {
+               char *dir = strrchr(freq->tmpfile, '/');
+               if (dir) {
+                       *dir = 0;
+                       mkdir(freq->tmpfile, 0777);
+                       *dir = '/';
+               }
+               freq->localfile = open(freq->tmpfile,
+                                      O_WRONLY | O_CREAT | O_EXCL, 0666);
+       }
+
+       if (freq->localfile < 0) {
+               error("Couldn't create temporary file %s: %s",
+                     freq->tmpfile, strerror(errno));
+               goto abort;
+       }
+
+       memset(&freq->stream, 0, sizeof(freq->stream));
+
+       git_inflate_init(&freq->stream);
+
+       git_SHA1_Init(&freq->c);
+
+       freq->url = get_remote_object_url(base_url, hex, 0);
+
+       /*
+        * If a previous temp file is present, process what was already
+        * fetched.
+        */
+       prevlocal = open(prevfile, O_RDONLY);
+       if (prevlocal != -1) {
+               do {
+                       prev_read = xread(prevlocal, prev_buf, PREV_BUF_SIZE);
+                       if (prev_read>0) {
+                               if (fwrite_sha1_file(prev_buf,
+                                                    1,
+                                                    prev_read,
+                                                    freq) == prev_read) {
+                                       prev_posn += prev_read;
+                               } else {
+                                       prev_read = -1;
+                               }
+                       }
+               } while (prev_read > 0);
+               close(prevlocal);
+       }
+       unlink_or_warn(prevfile);
+
+       /*
+        * Reset inflate/SHA1 if there was an error reading the previous temp
+        * file; also rewind to the beginning of the local file.
+        */
+       if (prev_read == -1) {
+               memset(&freq->stream, 0, sizeof(freq->stream));
+               git_inflate_init(&freq->stream);
+               git_SHA1_Init(&freq->c);
+               if (prev_posn>0) {
+                       prev_posn = 0;
+                       lseek(freq->localfile, 0, SEEK_SET);
+                       if (ftruncate(freq->localfile, 0) < 0) {
+                               error("Couldn't truncate temporary file %s: %s",
+                                         freq->tmpfile, strerror(errno));
+                               goto abort;
+                       }
+               }
+       }
+
+       freq->slot = get_active_slot();
+
+       curl_easy_setopt(freq->slot->curl, CURLOPT_FILE, freq);
+       curl_easy_setopt(freq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
+       curl_easy_setopt(freq->slot->curl, CURLOPT_ERRORBUFFER, freq->errorstr);
+       curl_easy_setopt(freq->slot->curl, CURLOPT_URL, freq->url);
+       curl_easy_setopt(freq->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 (http_is_verbose)
+                       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(freq->slot->curl,
+                                CURLOPT_HTTPHEADER, range_header);
+       }
+
+       return freq;
+
+abort:
+       free(filename);
+       free(freq->url);
+       free(freq);
+       return NULL;
+}
+
+void process_http_object_request(struct http_object_request *freq)
+{
+       if (freq->slot == NULL)
+               return;
+       freq->curl_result = freq->slot->curl_result;
+       freq->http_code = freq->slot->http_code;
+       freq->slot = NULL;
+}
+
+int finish_http_object_request(struct http_object_request *freq)
+{
+       struct stat st;
+
+       close(freq->localfile);
+       freq->localfile = -1;
+
+       process_http_object_request(freq);
+
+       if (freq->http_code == 416) {
+               warning("requested range invalid; we may already have all the data.");
+       } else if (freq->curl_result != CURLE_OK) {
+               if (stat(freq->tmpfile, &st) == 0)
+                       if (st.st_size == 0)
+                               unlink_or_warn(freq->tmpfile);
+               return -1;
+       }
+
+       git_inflate_end(&freq->stream);
+       git_SHA1_Final(freq->real_sha1, &freq->c);
+       if (freq->zret != Z_STREAM_END) {
+               unlink_or_warn(freq->tmpfile);
+               return -1;
+       }
+       if (hashcmp(freq->sha1, freq->real_sha1)) {
+               unlink_or_warn(freq->tmpfile);
+               return -1;
+       }
+       freq->rename =
+               move_temp_to_file(freq->tmpfile, sha1_file_name(freq->sha1));
+
+       return freq->rename;
+}
+
+void abort_http_object_request(struct http_object_request *freq)
+{
+       unlink_or_warn(freq->tmpfile);
+
+       release_http_object_request(freq);
+}
+
+void release_http_object_request(struct http_object_request *freq)
+{
+       if (freq->localfile != -1) {
+               close(freq->localfile);
+               freq->localfile = -1;
+       }
+       if (freq->url != NULL) {
+               free(freq->url);
+               freq->url = NULL;
+       }
+       if (freq->slot != NULL) {
+               freq->slot->callback_func = NULL;
+               freq->slot->callback_data = NULL;
+               release_active_slot(freq->slot);
+               freq->slot = NULL;
+       }
+}
diff --git a/http.h b/http.h
index 26abebed1f35db971e423f79c433bed8c5338789..173f74c8298c3fa3989f88b06e2085127fe96db8 100644 (file)
--- a/http.h
+++ b/http.h
 #endif
 
 #if LIBCURL_VERSION_NUM < 0x070704
-#define curl_global_cleanup() do { /* nothing */ } while(0)
+#define curl_global_cleanup() do { /* nothing */ } while (0)
 #endif
 #if LIBCURL_VERSION_NUM < 0x070800
-#define curl_global_init(a) do { /* nothing */ } while(0)
+#define curl_global_init(a) do { /* nothing */ } while (0)
 #endif
 
 #if (LIBCURL_VERSION_NUM < 0x070c04) || (LIBCURL_VERSION_NUM == 0x071000)
@@ -79,8 +79,8 @@ extern curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp);
 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_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);
@@ -93,6 +93,8 @@ extern void http_cleanup(void);
 
 extern int data_received;
 extern int active_requests;
+extern int http_is_verbose;
+extern size_t http_post_buffer;
 
 extern char curl_errorstr[CURL_ERROR_SIZE];
 
@@ -109,6 +111,83 @@ static inline int missing__target(int code, int result)
 
 #define missing_target(a) missing__target((a)->http_code, (a)->curl_result)
 
+/* Helpers for modifying and creating URLs */
+extern void append_remote_object_url(struct strbuf *buf, const char *url,
+                                    const char *hex,
+                                    int only_two_digit_prefix);
+extern char *get_remote_object_url(const char *url, const char *hex,
+                                  int only_two_digit_prefix);
+extern void end_url_with_slash(struct strbuf *buf, const char *url);
+
+/* Options for http_request_*() */
+#define HTTP_NO_CACHE          1
+
+/* Return values for http_request_*() */
+#define HTTP_OK                        0
+#define HTTP_MISSING_TARGET    1
+#define HTTP_ERROR             2
+#define HTTP_START_FAILED      3
+#define HTTP_REAUTH    4
+#define HTTP_NOAUTH    5
+
+/*
+ * Requests an url and stores the result in a strbuf.
+ *
+ * If the result pointer is NULL, a HTTP HEAD request is made instead of GET.
+ */
+int http_get_strbuf(const char *url, struct strbuf *result, int options);
+
+/*
+ * Prints an error message using error() containing url and curl_errorstr,
+ * and returns ret.
+ */
+int http_error(const char *url, int ret);
+
 extern int http_fetch_ref(const char *base, struct ref *ref);
 
+/* Helpers for fetching packs */
+extern int http_get_info_packs(const char *base_url,
+       struct packed_git **packs_head);
+
+struct http_pack_request
+{
+       char *url;
+       struct packed_git *target;
+       struct packed_git **lst;
+       FILE *packfile;
+       char tmpfile[PATH_MAX];
+       struct curl_slist *range_header;
+       struct active_request_slot *slot;
+};
+
+extern struct http_pack_request *new_http_pack_request(
+       struct packed_git *target, const char *base_url);
+extern int finish_http_pack_request(struct http_pack_request *preq);
+extern void release_http_pack_request(struct http_pack_request *preq);
+
+/* Helpers for fetching object */
+struct http_object_request
+{
+       char *url;
+       char tmpfile[PATH_MAX];
+       int localfile;
+       CURLcode curl_result;
+       char errorstr[CURL_ERROR_SIZE];
+       long http_code;
+       unsigned char sha1[20];
+       unsigned char real_sha1[20];
+       git_SHA_CTX c;
+       z_stream stream;
+       int zret;
+       int rename;
+       struct active_request_slot *slot;
+};
+
+extern struct http_object_request *new_http_object_request(
+       const char *base_url, unsigned char *sha1);
+extern void process_http_object_request(struct http_object_request *freq);
+extern int finish_http_object_request(struct http_object_request *freq);
+extern void abort_http_object_request(struct http_object_request *freq);
+extern void release_http_object_request(struct http_object_request *freq);
+
 #endif /* HTTP_H */
diff --git a/ident.c b/ident.c
index 99f1c85ea5f5c83247f7affd3d801ff5c14393a6..9e2438826dfce158e04549933d5c588dd6abcf5c 100644 (file)
--- a/ident.c
+++ b/ident.c
@@ -85,10 +85,11 @@ static void setup_ident(void)
        if (!git_default_email[0]) {
                const char *email = getenv("EMAIL");
 
-               if (email && email[0])
+               if (email && email[0]) {
                        strlcpy(git_default_email, email,
                                sizeof(git_default_email));
-               else {
+                       user_ident_explicitly_given |= IDENT_MAIL_GIVEN;
+               } else {
                        if (!pw)
                                pw = getpwuid(getuid());
                        if (!pw)
@@ -168,8 +169,6 @@ static int copy(char *buf, size_t size, int offset, const char *src)
        return offset;
 }
 
-static const char au_env[] = "GIT_AUTHOR_NAME";
-static const char co_env[] = "GIT_COMMITTER_NAME";
 static const char *env_hint =
 "\n"
 "*** Please tell me who you are.\n"
@@ -204,8 +203,8 @@ const char *fmt_ident(const char *name, const char *email,
 
                if ((warn_on_no_name || error_on_no_name) &&
                    name == git_default_name && env_hint) {
-                       fprintf(stderr, env_hint, au_env, co_env);
-                       env_hint = NULL; /* warn only once, for "git var -l" */
+                       fputs(env_hint, stderr);
+                       env_hint = NULL; /* warn only once */
                }
                if (error_on_no_name)
                        die("empty ident %s <%s> not allowed", name, email);
@@ -251,11 +250,21 @@ const char *git_author_info(int flag)
 
 const char *git_committer_info(int flag)
 {
-       if (getenv("GIT_COMMITTER_NAME") &&
-           getenv("GIT_COMMITTER_EMAIL"))
-               user_ident_explicitly_given = 1;
+       if (getenv("GIT_COMMITTER_NAME"))
+               user_ident_explicitly_given |= IDENT_NAME_GIVEN;
+       if (getenv("GIT_COMMITTER_EMAIL"))
+               user_ident_explicitly_given |= IDENT_MAIL_GIVEN;
        return fmt_ident(getenv("GIT_COMMITTER_NAME"),
                         getenv("GIT_COMMITTER_EMAIL"),
                         getenv("GIT_COMMITTER_DATE"),
                         flag);
 }
+
+int user_ident_sufficiently_given(void)
+{
+#ifndef WINDOWS
+       return (user_ident_explicitly_given & IDENT_MAIL_GIVEN);
+#else
+       return (user_ident_explicitly_given == IDENT_ALL_GIVEN);
+#endif
+}
index 8154cb2116da9257ecf286c46f77fc1ed2a62afc..71506a8dd3ed07fe44c487a644ce9a42b94a7578 100644 (file)
 
 #include "cache.h"
 #include "exec_cmd.h"
+#include "run-command.h"
 #ifdef NO_OPENSSL
 typedef void *SSL;
+#else
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
 #endif
 
 struct store_conf {
@@ -90,9 +94,11 @@ struct msg_data {
        char *data;
        int len;
        unsigned char flags;
-       unsigned int crlf:1;
 };
 
+static const char imap_send_usage[] = "git imap-send < <mbox>";
+
+#undef DRV_OK
 #define DRV_OK          0
 #define DRV_MSG_BAD     -1
 #define DRV_BOX_BAD     -2
@@ -100,13 +106,16 @@ struct msg_data {
 
 static int Verbose, Quiet;
 
+__attribute__((format (printf, 1, 2)))
 static void imap_info(const char *, ...);
+__attribute__((format (printf, 1, 2)))
 static void imap_warn(const char *, ...);
 
 static char *next_arg(char **);
 
 static void free_generic_messages(struct message *);
 
+__attribute__((format (printf, 3, 4)))
 static int nfsnprintf(char *buf, int blen, const char *fmt, ...);
 
 static int nfvasprintf(char **strp, const char *fmt, va_list ap)
@@ -123,9 +132,6 @@ static int nfvasprintf(char **strp, const char *fmt, va_list ap)
        return len;
 }
 
-static void arc4_init(void);
-static unsigned char arc4_getbyte(void);
-
 struct imap_server_conf {
        char *name;
        char *tunnel;
@@ -136,6 +142,20 @@ struct imap_server_conf {
        int use_ssl;
        int ssl_verify;
        int use_html;
+       char *auth_method;
+};
+
+static struct imap_server_conf server = {
+       NULL,   /* name */
+       NULL,   /* tunnel */
+       NULL,   /* host */
+       0,      /* port */
+       NULL,   /* user */
+       NULL,   /* pass */
+       0,      /* use_ssl */
+       1,      /* ssl_verify */
+       0,      /* use_html */
+       NULL,   /* auth_method */
 };
 
 struct imap_store_conf {
@@ -154,7 +174,7 @@ struct imap_list {
 };
 
 struct imap_socket {
-       int fd;
+       int fd[2];
        SSL *ssl;
 };
 
@@ -210,6 +230,7 @@ enum CAPABILITY {
        LITERALPLUS,
        NAMESPACE,
        STARTTLS,
+       AUTH_CRAM_MD5
 };
 
 static const char *cap_list[] = {
@@ -218,6 +239,7 @@ static const char *cap_list[] = {
        "LITERAL+",
        "NAMESPACE",
        "STARTTLS",
+       "AUTH=CRAM-MD5",
 };
 
 #define RESP_OK    0
@@ -238,7 +260,7 @@ static const char *Flags[] = {
 #ifndef NO_OPENSSL
 static void ssl_socket_perror(const char *func)
 {
-       fprintf(stderr, "%s: %s\n", func, ERR_error_string(ERR_get_error(), 0));
+       fprintf(stderr, "%s: %s\n", func, ERR_error_string(ERR_get_error(), NULL));
 }
 #endif
 
@@ -272,8 +294,12 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
 #ifdef NO_OPENSSL
        fprintf(stderr, "SSL requested but SSL support not compiled in\n");
        return -1;
+#else
+#if (OPENSSL_VERSION_NUMBER >= 0x10000000L)
+       const SSL_METHOD *meth;
 #else
        SSL_METHOD *meth;
+#endif
        SSL_CTX *ctx;
        int ret;
 
@@ -304,8 +330,12 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
                ssl_socket_perror("SSL_new");
                return -1;
        }
-       if (!SSL_set_fd(sock->ssl, sock->fd)) {
-               ssl_socket_perror("SSL_set_fd");
+       if (!SSL_set_rfd(sock->ssl, sock->fd[0])) {
+               ssl_socket_perror("SSL_set_rfd");
+               return -1;
+       }
+       if (!SSL_set_wfd(sock->ssl, sock->fd[1])) {
+               ssl_socket_perror("SSL_set_wfd");
                return -1;
        }
 
@@ -327,11 +357,12 @@ static int socket_read(struct imap_socket *sock, char *buf, int len)
                n = SSL_read(sock->ssl, buf, len);
        else
 #endif
-               n = xread(sock->fd, buf, len);
+               n = xread(sock->fd[0], buf, len);
        if (n <= 0) {
                socket_perror("read", sock, n);
-               close(sock->fd);
-               sock->fd = -1;
+               close(sock->fd[0]);
+               close(sock->fd[1]);
+               sock->fd[0] = sock->fd[1] = -1;
        }
        return n;
 }
@@ -344,11 +375,12 @@ static int socket_write(struct imap_socket *sock, const char *buf, int len)
                n = SSL_write(sock->ssl, buf, len);
        else
 #endif
-               n = write_in_full(sock->fd, buf, len);
+               n = write_in_full(sock->fd[1], buf, len);
        if (n != len) {
                socket_perror("write", sock, n);
-               close(sock->fd);
-               sock->fd = -1;
+               close(sock->fd[0]);
+               close(sock->fd[1]);
+               sock->fd[0] = sock->fd[1] = -1;
        }
        return n;
 }
@@ -361,7 +393,8 @@ static void socket_shutdown(struct imap_socket *sock)
                SSL_free(sock->ssl);
        }
 #endif
-       close(sock->fd);
+       close(sock->fd[0]);
+       close(sock->fd[1]);
 }
 
 /* simple line buffering */
@@ -489,52 +522,6 @@ static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
        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_in_full(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(struct imap_store *ctx,
                                         struct imap_cmd_cb *cb,
                                         const char *fmt, va_list ap)
@@ -556,9 +543,13 @@ static struct imap_cmd *v_issue_imap_cmd(struct imap_store *ctx,
        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 (!cmd->cb.data)
+               bufl = nfsnprintf(buf, sizeof(buf), "%d %s\r\n", cmd->tag, cmd->cmd);
+       else
+               bufl = nfsnprintf(buf, sizeof(buf), "%d %s{%d%s}\r\n",
+                                 cmd->tag, cmd->cmd, cmd->cb.dlen,
+                                 CAP(LITERALPLUS) ? "+" : "");
+
        if (Verbose) {
                if (imap->num_in_progress)
                        printf("(%d in progress) ", imap->num_in_progress);
@@ -596,6 +587,7 @@ static struct imap_cmd *v_issue_imap_cmd(struct imap_store *ctx,
        return cmd;
 }
 
+__attribute__((format (printf, 3, 4)))
 static struct imap_cmd *issue_imap_cmd(struct imap_store *ctx,
                                       struct imap_cmd_cb *cb,
                                       const char *fmt, ...)
@@ -609,6 +601,7 @@ static struct imap_cmd *issue_imap_cmd(struct imap_store *ctx,
        return ret;
 }
 
+__attribute__((format (printf, 3, 4)))
 static int imap_exec(struct imap_store *ctx, struct imap_cmd_cb *cb,
                     const char *fmt, ...)
 {
@@ -624,6 +617,7 @@ static int imap_exec(struct imap_store *ctx, struct imap_cmd_cb *cb,
        return get_cmd_result(ctx, cmdp);
 }
 
+__attribute__((format (printf, 3, 4)))
 static int imap_exec_m(struct imap_store *ctx, struct imap_cmd_cb *cb,
                       const char *fmt, ...)
 {
@@ -914,7 +908,7 @@ static int get_cmd_result(struct imap_store *ctx, struct imap_cmd *tcmd)
                                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)) {
+                                               if (!issue_imap_cmd(ctx, NULL, "CREATE \"%.*s\"", (int)(strchr(p + 1, '"') - p + 1), p)) {
                                                        resp = RESP_BAD;
                                                        goto normal;
                                                }
@@ -960,7 +954,7 @@ static void imap_close_server(struct imap_store *ictx)
 {
        struct imap *imap = ictx->imap;
 
-       if (imap->buf.sock.fd != -1) {
+       if (imap->buf.sock.fd[0] != -1) {
                imap_exec(ictx, NULL, "LOGOUT");
                socket_shutdown(&imap->buf.sock);
        }
@@ -977,50 +971,165 @@ static void imap_close_store(struct store *ctx)
        free(ctx);
 }
 
+#ifndef NO_OPENSSL
+
+/*
+ * hexchar() and cram() functions are based on the code from the isync
+ * project (http://isync.sf.net/).
+ */
+static char hexchar(unsigned int b)
+{
+       return b < 10 ? '0' + b : 'a' + (b - 10);
+}
+
+#define ENCODED_SIZE(n) (4*((n+2)/3))
+static char *cram(const char *challenge_64, const char *user, const char *pass)
+{
+       int i, resp_len, encoded_len, decoded_len;
+       HMAC_CTX hmac;
+       unsigned char hash[16];
+       char hex[33];
+       char *response, *response_64, *challenge;
+
+       /*
+        * length of challenge_64 (i.e. base-64 encoded string) is a good
+        * enough upper bound for challenge (decoded result).
+        */
+       encoded_len = strlen(challenge_64);
+       challenge = xmalloc(encoded_len);
+       decoded_len = EVP_DecodeBlock((unsigned char *)challenge,
+                                     (unsigned char *)challenge_64, encoded_len);
+       if (decoded_len < 0)
+               die("invalid challenge %s", challenge_64);
+       HMAC_Init(&hmac, (unsigned char *)pass, strlen(pass), EVP_md5());
+       HMAC_Update(&hmac, (unsigned char *)challenge, decoded_len);
+       HMAC_Final(&hmac, hash, NULL);
+       HMAC_CTX_cleanup(&hmac);
+
+       hex[32] = 0;
+       for (i = 0; i < 16; i++) {
+               hex[2 * i] = hexchar((hash[i] >> 4) & 0xf);
+               hex[2 * i + 1] = hexchar(hash[i] & 0xf);
+       }
+
+       /* response: "<user> <digest in hex>" */
+       resp_len = strlen(user) + 1 + strlen(hex) + 1;
+       response = xmalloc(resp_len);
+       sprintf(response, "%s %s", user, hex);
+
+       response_64 = xmalloc(ENCODED_SIZE(resp_len) + 1);
+       encoded_len = EVP_EncodeBlock((unsigned char *)response_64,
+                                     (unsigned char *)response, resp_len);
+       if (encoded_len < 0)
+               die("EVP_EncodeBlock error");
+       response_64[encoded_len] = '\0';
+       return (char *)response_64;
+}
+
+#else
+
+static char *cram(const char *challenge_64, const char *user, const char *pass)
+{
+       die("If you want to use CRAM-MD5 authenticate method, "
+           "you have to build git-imap-send with OpenSSL library.");
+}
+
+#endif
+
+static int auth_cram_md5(struct imap_store *ctx, struct imap_cmd *cmd, const char *prompt)
+{
+       int ret;
+       char *response;
+
+       response = cram(prompt, server.user, server.pass);
+
+       ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));
+       if (ret != strlen(response))
+               return error("IMAP error: sending response failed\n");
+
+       free(response);
+
+       return 0;
+}
+
 static struct store *imap_open_store(struct imap_server_conf *srvc)
 {
        struct imap_store *ctx;
        struct imap *imap;
        char *arg, *rsp;
-       struct hostent *he;
-       struct sockaddr_in addr;
-       int s, a[2], preauth;
-       pid_t pid;
+       int s = -1, preauth;
 
        ctx = xcalloc(sizeof(*ctx), 1);
 
        ctx->imap = imap = xcalloc(sizeof(*imap), 1);
-       imap->buf.sock.fd = -1;
+       imap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1;
        imap->in_progress_append = &imap->in_progress;
 
        /* open connection to IMAP server */
 
        if (srvc->tunnel) {
+               const char *argv[] = { srvc->tunnel, NULL };
+               struct child_process tunnel = {0};
+
                imap_info("Starting tunnel '%s'... ", srvc->tunnel);
 
-               if (socketpair(PF_UNIX, SOCK_STREAM, 0, a)) {
-                       perror("socketpair");
-                       exit(1);
-               }
+               tunnel.argv = argv;
+               tunnel.use_shell = 1;
+               tunnel.in = -1;
+               tunnel.out = -1;
+               if (start_command(&tunnel))
+                       die("cannot start proxy %s", argv[0]);
 
-               pid = fork();
-               if (pid < 0)
-                       _exit(127);
-               if (!pid) {
-                       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);
-               }
+               imap->buf.sock.fd[0] = tunnel.out;
+               imap->buf.sock.fd[1] = tunnel.in;
+
+               imap_info("ok\n");
+       } else {
+#ifndef NO_IPV6
+               struct addrinfo hints, *ai0, *ai;
+               int gai;
+               char portstr[6];
 
-               close(a[0]);
+               snprintf(portstr, sizeof(portstr), "%d", srvc->port);
 
-               imap->buf.sock.fd = a[1];
+               memset(&hints, 0, sizeof(hints));
+               hints.ai_socktype = SOCK_STREAM;
+               hints.ai_protocol = IPPROTO_TCP;
 
+               imap_info("Resolving %s... ", srvc->host);
+               gai = getaddrinfo(srvc->host, portstr, &hints, &ai);
+               if (gai) {
+                       fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai));
+                       goto bail;
+               }
                imap_info("ok\n");
-       } else {
+
+               for (ai0 = ai; ai; ai = ai->ai_next) {
+                       char addr[NI_MAXHOST];
+
+                       s = socket(ai->ai_family, ai->ai_socktype,
+                                  ai->ai_protocol);
+                       if (s < 0)
+                               continue;
+
+                       getnameinfo(ai->ai_addr, ai->ai_addrlen, addr,
+                                   sizeof(addr), NULL, 0, NI_NUMERICHOST);
+                       imap_info("Connecting to [%s]:%s... ", addr, portstr);
+
+                       if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0) {
+                               close(s);
+                               s = -1;
+                               perror("connect");
+                               continue;
+                       }
+
+                       break;
+               }
+               freeaddrinfo(ai0);
+#else /* NO_IPV6 */
+               struct hostent *he;
+               struct sockaddr_in addr;
+
                memset(&addr, 0, sizeof(addr));
                addr.sin_port = htons(srvc->port);
                addr.sin_family = AF_INET;
@@ -1040,11 +1149,17 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
                imap_info("Connecting to %s:%hu... ", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
                if (connect(s, (struct sockaddr *)&addr, sizeof(addr))) {
                        close(s);
+                       s = -1;
                        perror("connect");
+               }
+#endif
+               if (s < 0) {
+                       fputs("Error: unable to connect to server.\n", stderr);
                        goto bail;
                }
 
-               imap->buf.sock.fd = s;
+               imap->buf.sock.fd[0] = s;
+               imap->buf.sock.fd[1] = dup(s);
 
                if (srvc->use_ssl &&
                    ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) {
@@ -1096,7 +1211,7 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
                if (!srvc->pass) {
                        char prompt[80];
                        sprintf(prompt, "Password (%s@%s): ", srvc->user, srvc->host);
-                       arg = getpass(prompt);
+                       arg = git_getpass(prompt);
                        if (!arg) {
                                perror("getpass");
                                exit(1);
@@ -1115,12 +1230,37 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
                        fprintf(stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host);
                        goto bail;
                }
-               if (!imap->buf.sock.ssl)
-                       imap_warn("*** IMAP Warning *** Password is being "
-                                 "sent in the clear\n");
-               if (imap_exec(ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass) != RESP_OK) {
-                       fprintf(stderr, "IMAP error: LOGIN failed\n");
-                       goto bail;
+
+               if (srvc->auth_method) {
+                       struct imap_cmd_cb cb;
+
+                       if (!strcmp(srvc->auth_method, "CRAM-MD5")) {
+                               if (!CAP(AUTH_CRAM_MD5)) {
+                                       fprintf(stderr, "You specified"
+                                               "CRAM-MD5 as authentication method, "
+                                               "but %s doesn't support it.\n", srvc->host);
+                                       goto bail;
+                               }
+                               /* CRAM-MD5 */
+
+                               memset(&cb, 0, sizeof(cb));
+                               cb.cont = auth_cram_md5;
+                               if (imap_exec(ctx, &cb, "AUTHENTICATE CRAM-MD5") != RESP_OK) {
+                                       fprintf(stderr, "IMAP error: AUTHENTICATE CRAM-MD5 failed\n");
+                                       goto bail;
+                               }
+                       } else {
+                               fprintf(stderr, "Unknown authentication method:%s\n", srvc->host);
+                               goto bail;
+                       }
+               } else {
+                       if (!imap->buf.sock.ssl)
+                               imap_warn("*** IMAP Warning *** Password is being "
+                                         "sent in the clear\n");
+                       if (imap_exec(ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass) != RESP_OK) {
+                               fprintf(stderr, "IMAP error: LOGIN failed\n");
+                               goto bail;
+                       }
                }
        } /* !preauth */
 
@@ -1150,88 +1290,59 @@ static int imap_make_flags(int flags, char *buf)
        return d;
 }
 
-#define TUIDL 8
+static void lf_to_crlf(struct msg_data *msg)
+{
+       char *new;
+       int i, j, lfnum = 0;
+
+       if (msg->data[0] == '\n')
+               lfnum++;
+       for (i = 1; i < msg->len; i++) {
+               if (msg->data[i - 1] != '\r' && msg->data[i] == '\n')
+                       lfnum++;
+       }
 
-static int imap_store_msg(struct store *gctx, struct msg_data *data, int *uid)
+       new = xmalloc(msg->len + lfnum);
+       if (msg->data[0] == '\n') {
+               new[0] = '\r';
+               new[1] = '\n';
+               i = 1;
+               j = 2;
+       } else {
+               new[0] = msg->data[0];
+               i = 1;
+               j = 1;
+       }
+       for ( ; i < msg->len; i++) {
+               if (msg->data[i] != '\n') {
+                       new[j++] = msg->data[i];
+                       continue;
+               }
+               if (msg->data[i - 1] != '\r')
+                       new[j++] = '\r';
+               /* otherwise it already had CR before */
+               new[j++] = '\n';
+       }
+       msg->len += lfnum;
+       free(msg->data);
+       msg->data = new;
+}
+
+static int imap_store_msg(struct store *gctx, struct msg_data *data)
 {
        struct imap_store *ctx = (struct imap_store *)gctx;
        struct imap *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];
+       int ret, d;
+       char flagstr[128];
 
+       lf_to_crlf(data);
        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);
+       cb.dlen = data->len;
+       cb.data = xmalloc(cb.dlen);
+       memcpy(cb.data, data->data, data->len);
 
        d = 0;
        if (data->flags) {
@@ -1240,26 +1351,14 @@ static int imap_store_msg(struct store *gctx, struct msg_data *data, int *uid)
        }
        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;
+       box = gctx->name;
+       prefix = !strcmp(box, "INBOX") ? "" : ctx->prefix;
+       cb.create = 0;
        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++;
+       gctx->count++;
 
        return DRV_OK;
 }
@@ -1336,8 +1435,14 @@ static int count_messages(struct msg_data *msg)
 
        while (1) {
                if (!prefixcmp(p, "From ")) {
+                       p = strstr(p+5, "\nFrom: ");
+                       if (!p) break;
+                       p = strstr(p+7, "\nDate: ");
+                       if (!p) break;
+                       p = strstr(p+7, "\nSubject: ");
+                       if (!p) break;
+                       p += 10;
                        count++;
-                       p += 5;
                }
                p = strstr(p+5, "\nFrom ");
                if (!p)
@@ -1378,18 +1483,6 @@ static int split_msg(struct msg_data *all_msgs, struct msg_data *msg, int *ofs)
        return 1;
 }
 
-static struct imap_server_conf server = {
-       NULL,   /* name */
-       NULL,   /* tunnel */
-       NULL,   /* host */
-       0,      /* port */
-       NULL,   /* user */
-       NULL,   /* pass */
-       0,      /* use_ssl */
-       1,      /* ssl_verify */
-       0,      /* use_html */
-};
-
 static char *imap_folder;
 
 static int git_imap_config(const char *key, const char *val, void *cb)
@@ -1399,11 +1492,16 @@ static int git_imap_config(const char *key, const char *val, void *cb)
        if (strncmp(key, imap_key, sizeof imap_key - 1))
                return 0;
 
-       if (!val)
-               return config_error_nonbool(key);
-
        key += sizeof imap_key - 1;
 
+       /* check booleans first, and barf on others */
+       if (!strcmp("sslverify", key))
+               server.ssl_verify = git_config_bool(key, val);
+       else if (!strcmp("preformattedhtml", key))
+               server.use_html = git_config_bool(key, val);
+       else if (!val)
+               return config_error_nonbool(key);
+
        if (!strcmp("folder", key)) {
                imap_folder = xstrdup(val);
        } else if (!strcmp("host", key)) {
@@ -1424,10 +1522,9 @@ static int git_imap_config(const char *key, const char *val, void *cb)
                server.port = git_config_int(key, val);
        else if (!strcmp("tunnel", key))
                server.tunnel = xstrdup(val);
-       else if (!strcmp("sslverify", key))
-               server.ssl_verify = git_config_bool(key, val);
-       else if (!strcmp("preformattedHTML", key))
-               server.use_html = git_config_bool(key, val);
+       else if (!strcmp("authmethod", key))
+               server.auth_method = xstrdup(val);
+
        return 0;
 }
 
@@ -1435,7 +1532,6 @@ int main(int argc, char **argv)
 {
        struct msg_data all_msgs, msg;
        struct store *ctx = NULL;
-       int uid = 0;
        int ofs = 0;
        int r;
        int total, n = 0;
@@ -1443,8 +1539,8 @@ int main(int argc, char **argv)
 
        git_extract_argv0_path(argv[0]);
 
-       /* init the random number generator */
-       arc4_init();
+       if (argc != 1)
+               usage(imap_send_usage);
 
        setup_git_directory_gently(&nongit_ok);
        git_config(git_imap_config, NULL);
@@ -1492,7 +1588,7 @@ int main(int argc, char **argv)
                        break;
                if (server.use_html)
                        wrap_in_html(&msg);
-               r = imap_store_msg(ctx, &msg, &uid);
+               r = imap_store_msg(ctx, &msg);
                if (r != DRV_OK)
                        break;
                n++;
diff --git a/index-pack.c b/index-pack.c
deleted file mode 100644 (file)
index 6e93ee6..0000000
+++ /dev/null
@@ -1,1045 +0,0 @@
-#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"
-#include "progress.h"
-#include "fsck.h"
-#include "exec_cmd.h"
-
-static const char index_pack_usage[] =
-"git index-pack [-v] [-o <index-file>] [{ ---keep | --keep=<msg> }] [--strict] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }";
-
-struct object_entry
-{
-       struct pack_idx_entry idx;
-       unsigned long size;
-       unsigned int hdr_size;
-       enum object_type type;
-       enum object_type real_type;
-};
-
-union delta_base {
-       unsigned char sha1[20];
-       off_t offset;
-};
-
-struct base_data {
-       struct base_data *base;
-       struct base_data *child;
-       struct object_entry *obj;
-       void *data;
-       unsigned long size;
-};
-
-/*
- * Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want
- * to memcmp() only the first 20 bytes.
- */
-#define UNION_BASE_SZ  20
-
-#define FLAG_LINK (1u<<20)
-#define FLAG_CHECKED (1u<<21)
-
-struct delta_entry
-{
-       union delta_base base;
-       int obj_no;
-};
-
-static struct object_entry *objects;
-static struct delta_entry *deltas;
-static struct base_data *base_cache;
-static size_t base_cache_used;
-static int nr_objects;
-static int nr_deltas;
-static int nr_resolved_deltas;
-
-static int from_stdin;
-static int strict;
-static int verbose;
-
-static struct progress *progress;
-
-/* We always read in 4kB chunks. */
-static unsigned char input_buffer[4096];
-static unsigned int input_offset, input_len;
-static off_t consumed_bytes;
-static git_SHA_CTX input_ctx;
-static uint32_t input_crc32;
-static int input_fd, output_fd, pack_fd;
-
-static int mark_link(struct object *obj, int type, void *data)
-{
-       if (!obj)
-               return -1;
-
-       if (type != OBJ_ANY && obj->type != type)
-               die("object type mismatch at %s", sha1_to_hex(obj->sha1));
-
-       obj->flags |= FLAG_LINK;
-       return 0;
-}
-
-/* The content of each linked object must have been checked
-   or it must be already present in the object database */
-static void check_object(struct object *obj)
-{
-       if (!obj)
-               return;
-
-       if (!(obj->flags & FLAG_LINK))
-               return;
-
-       if (!(obj->flags & FLAG_CHECKED)) {
-               unsigned long size;
-               int type = sha1_object_info(obj->sha1, &size);
-               if (type != obj->type || type <= 0)
-                       die("object of unexpected type");
-               obj->flags |= FLAG_CHECKED;
-               return;
-       }
-}
-
-static void check_objects(void)
-{
-       unsigned i, max;
-
-       max = get_max_object_index();
-       for (i = 0; i < max; i++)
-               check_object(get_indexed_object(i));
-}
-
-
-/* Discard current buffer used content. */
-static void flush(void)
-{
-       if (input_offset) {
-               if (output_fd >= 0)
-                       write_or_die(output_fd, input_buffer, input_offset);
-               git_SHA1_Update(&input_ctx, input_buffer, input_offset);
-               memmove(input_buffer, input_buffer + input_offset, input_len);
-               input_offset = 0;
-       }
-}
-
-/*
- * 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 <= input_len)
-               return input_buffer + input_offset;
-       if (min > sizeof(input_buffer))
-               die("cannot fill %d bytes", min);
-       flush();
-       do {
-               ssize_t ret = xread(input_fd, input_buffer + input_len,
-                               sizeof(input_buffer) - input_len);
-               if (ret <= 0) {
-                       if (!ret)
-                               die("early EOF");
-                       die("read error on input: %s", strerror(errno));
-               }
-               input_len += ret;
-               if (from_stdin)
-                       display_throughput(progress, consumed_bytes + input_len);
-       } while (input_len < min);
-       return input_buffer;
-}
-
-static void use(int bytes)
-{
-       if (bytes > input_len)
-               die("used more bytes than were available");
-       input_crc32 = crc32(input_crc32, input_buffer + input_offset, bytes);
-       input_len -= bytes;
-       input_offset += bytes;
-
-       /* make sure off_t is sufficiently large not to wrap */
-       if (consumed_bytes > consumed_bytes + bytes)
-               die("pack too large for current definition of off_t");
-       consumed_bytes += bytes;
-}
-
-static char *open_pack_file(char *pack_name)
-{
-       if (from_stdin) {
-               input_fd = 0;
-               if (!pack_name) {
-                       static char tmpfile[PATH_MAX];
-                       output_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
-                                               "pack/tmp_pack_XXXXXX");
-                       pack_name = xstrdup(tmpfile);
-               } else
-                       output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
-               if (output_fd < 0)
-                       die("unable to create %s: %s", pack_name, strerror(errno));
-               pack_fd = output_fd;
-       } else {
-               input_fd = open(pack_name, O_RDONLY);
-               if (input_fd < 0)
-                       die("cannot open packfile '%s': %s",
-                           pack_name, strerror(errno));
-               output_fd = -1;
-               pack_fd = input_fd;
-       }
-       git_SHA1_Init(&input_ctx);
-       return pack_name;
-}
-
-static void parse_pack_header(void)
-{
-       struct pack_header *hdr = fill(sizeof(struct pack_header));
-
-       /* Header consistency check */
-       if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
-               die("pack signature mismatch");
-       if (!pack_version_ok(hdr->hdr_version))
-               die("pack version %"PRIu32" unsupported",
-                       ntohl(hdr->hdr_version));
-
-       nr_objects = ntohl(hdr->hdr_entries);
-       use(sizeof(struct pack_header));
-}
-
-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("pack has bad object at offset %lu: %s", offset, buf);
-}
-
-static void free_base_data(struct base_data *c)
-{
-       if (c->data) {
-               free(c->data);
-               c->data = NULL;
-               base_cache_used -= c->size;
-       }
-}
-
-static void prune_base_data(struct base_data *retain)
-{
-       struct base_data *b;
-       for (b = base_cache;
-            base_cache_used > delta_base_cache_limit && b;
-            b = b->child) {
-               if (b->data && b != retain)
-                       free_base_data(b);
-       }
-}
-
-static void link_base_data(struct base_data *base, struct base_data *c)
-{
-       if (base)
-               base->child = c;
-       else
-               base_cache = c;
-
-       c->base = base;
-       c->child = NULL;
-       if (c->data)
-               base_cache_used += c->size;
-       prune_base_data(c);
-}
-
-static void unlink_base_data(struct base_data *c)
-{
-       struct base_data *base = c->base;
-       if (base)
-               base->child = NULL;
-       else
-               base_cache = NULL;
-       free_base_data(c);
-}
-
-static void *unpack_entry_data(unsigned long offset, 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 = input_len;
-       git_inflate_init(&stream);
-
-       for (;;) {
-               int ret = git_inflate(&stream, 0);
-               use(input_len - stream.avail_in);
-               if (stream.total_out == size && ret == Z_STREAM_END)
-                       break;
-               if (ret != Z_OK)
-                       bad_object(offset, "inflate returned %d", ret);
-               stream.next_in = fill(1);
-               stream.avail_in = input_len;
-       }
-       git_inflate_end(&stream);
-       return buf;
-}
-
-static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_base)
-{
-       unsigned char *p, c;
-       unsigned long size;
-       off_t base_offset;
-       unsigned shift;
-       void *data;
-
-       obj->idx.offset = consumed_bytes;
-       input_crc32 = crc32(0, Z_NULL, 0);
-
-       p = fill(1);
-       c = *p;
-       use(1);
-       obj->type = (c >> 4) & 7;
-       size = (c & 15);
-       shift = 4;
-       while (c & 0x80) {
-               p = fill(1);
-               c = *p;
-               use(1);
-               size += (c & 0x7fUL) << shift;
-               shift += 7;
-       }
-       obj->size = size;
-
-       switch (obj->type) {
-       case OBJ_REF_DELTA:
-               hashcpy(delta_base->sha1, fill(20));
-               use(20);
-               break;
-       case OBJ_OFS_DELTA:
-               memset(delta_base, 0, sizeof(*delta_base));
-               p = fill(1);
-               c = *p;
-               use(1);
-               base_offset = c & 127;
-               while (c & 128) {
-                       base_offset += 1;
-                       if (!base_offset || MSB(base_offset, 7))
-                               bad_object(obj->idx.offset, "offset value overflow for delta base object");
-                       p = fill(1);
-                       c = *p;
-                       use(1);
-                       base_offset = (base_offset << 7) + (c & 127);
-               }
-               delta_base->offset = obj->idx.offset - base_offset;
-               if (delta_base->offset <= 0 || delta_base->offset >= obj->idx.offset)
-                       bad_object(obj->idx.offset, "delta base offset is out of bound");
-               break;
-       case OBJ_COMMIT:
-       case OBJ_TREE:
-       case OBJ_BLOB:
-       case OBJ_TAG:
-               break;
-       default:
-               bad_object(obj->idx.offset, "unknown object type %d", obj->type);
-       }
-       obj->hdr_size = consumed_bytes - obj->idx.offset;
-
-       data = unpack_entry_data(obj->idx.offset, obj->size);
-       obj->idx.crc32 = input_crc32;
-       return data;
-}
-
-static void *get_data_from_pack(struct object_entry *obj)
-{
-       off_t from = obj[0].idx.offset + obj[0].hdr_size;
-       unsigned long len = obj[1].idx.offset - from;
-       unsigned long rdy = 0;
-       unsigned char *src, *data;
-       z_stream stream;
-       int st;
-
-       src = xmalloc(len);
-       data = src;
-       do {
-               ssize_t n = pread(pack_fd, data + rdy, len - rdy, from + rdy);
-               if (n < 0)
-                       die("cannot pread pack file: %s", strerror(errno));
-               if (!n)
-                       die("premature end of pack file, %lu bytes missing",
-                           len - rdy);
-               rdy += n;
-       } while (rdy < len);
-       data = xmalloc(obj->size);
-       memset(&stream, 0, sizeof(stream));
-       stream.next_out = data;
-       stream.avail_out = obj->size;
-       stream.next_in = src;
-       stream.avail_in = len;
-       git_inflate_init(&stream);
-       while ((st = git_inflate(&stream, Z_FINISH)) == Z_OK);
-       git_inflate_end(&stream);
-       if (st != Z_STREAM_END || stream.total_out != obj->size)
-               die("serious inflate inconsistency");
-       free(src);
-       return data;
-}
-
-static int find_delta(const union delta_base *base)
-{
-       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, &delta->base, UNION_BASE_SZ);
-                if (!cmp)
-                        return next;
-                if (cmp < 0) {
-                        last = next;
-                        continue;
-                }
-                first = next+1;
-        }
-        return -first-1;
-}
-
-static void find_delta_children(const union delta_base *base,
-                               int *first_index, int *last_index)
-{
-       int first = find_delta(base);
-       int last = first;
-       int end = nr_deltas - 1;
-
-       if (first < 0) {
-               *first_index = 0;
-               *last_index = -1;
-               return;
-       }
-       while (first > 0 && !memcmp(&deltas[first - 1].base, base, UNION_BASE_SZ))
-               --first;
-       while (last < end && !memcmp(&deltas[last + 1].base, base, UNION_BASE_SZ))
-               ++last;
-       *first_index = first;
-       *last_index = last;
-}
-
-static void sha1_object(const void *data, unsigned long size,
-                       enum object_type type, unsigned char *sha1)
-{
-       hash_sha1_file(data, size, typename(type), sha1);
-       if (has_sha1_file(sha1)) {
-               void *has_data;
-               enum object_type has_type;
-               unsigned long has_size;
-               has_data = read_sha1_file(sha1, &has_type, &has_size);
-               if (!has_data)
-                       die("cannot read existing object %s", sha1_to_hex(sha1));
-               if (size != has_size || type != has_type ||
-                   memcmp(data, has_data, size) != 0)
-                       die("SHA1 COLLISION FOUND WITH %s !", sha1_to_hex(sha1));
-               free(has_data);
-       }
-       if (strict) {
-               if (type == OBJ_BLOB) {
-                       struct blob *blob = lookup_blob(sha1);
-                       if (blob)
-                               blob->object.flags |= FLAG_CHECKED;
-                       else
-                               die("invalid blob object %s", sha1_to_hex(sha1));
-               } else {
-                       struct object *obj;
-                       int eaten;
-                       void *buf = (void *) data;
-
-                       /*
-                        * we do not need to free the memory here, as the
-                        * buf is deleted by the caller.
-                        */
-                       obj = parse_object_buffer(sha1, type, size, buf, &eaten);
-                       if (!obj)
-                               die("invalid %s", typename(type));
-                       if (fsck_object(obj, 1, fsck_error_function))
-                               die("Error in object");
-                       if (fsck_walk(obj, mark_link, 0))
-                               die("Not all child objects of %s are reachable", sha1_to_hex(obj->sha1));
-
-                       if (obj->type == OBJ_TREE) {
-                               struct tree *item = (struct tree *) obj;
-                               item->buffer = NULL;
-                       }
-                       if (obj->type == OBJ_COMMIT) {
-                               struct commit *commit = (struct commit *) obj;
-                               commit->buffer = NULL;
-                       }
-                       obj->flags |= FLAG_CHECKED;
-               }
-       }
-}
-
-static void *get_base_data(struct base_data *c)
-{
-       if (!c->data) {
-               struct object_entry *obj = c->obj;
-
-               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
-                       void *base = get_base_data(c->base);
-                       void *raw = get_data_from_pack(obj);
-                       c->data = patch_delta(
-                               base, c->base->size,
-                               raw, obj->size,
-                               &c->size);
-                       free(raw);
-                       if (!c->data)
-                               bad_object(obj->idx.offset, "failed to apply delta");
-               } else {
-                       c->data = get_data_from_pack(obj);
-                       c->size = obj->size;
-               }
-
-               base_cache_used += c->size;
-               prune_base_data(c);
-       }
-       return c->data;
-}
-
-static void resolve_delta(struct object_entry *delta_obj,
-                         struct base_data *base, struct base_data *result)
-{
-       void *base_data, *delta_data;
-
-       delta_obj->real_type = base->obj->real_type;
-       delta_data = get_data_from_pack(delta_obj);
-       base_data = get_base_data(base);
-       result->obj = delta_obj;
-       result->data = patch_delta(base_data, base->size,
-                                  delta_data, delta_obj->size, &result->size);
-       free(delta_data);
-       if (!result->data)
-               bad_object(delta_obj->idx.offset, "failed to apply delta");
-       sha1_object(result->data, result->size, delta_obj->real_type,
-                   delta_obj->idx.sha1);
-       nr_resolved_deltas++;
-}
-
-static void find_unresolved_deltas(struct base_data *base,
-                                  struct base_data *prev_base)
-{
-       int i, ref_first, ref_last, ofs_first, ofs_last;
-
-       /*
-        * This is a recursive function. Those brackets should help reducing
-        * stack usage by limiting the scope of the delta_base union.
-        */
-       {
-               union delta_base base_spec;
-
-               hashcpy(base_spec.sha1, base->obj->idx.sha1);
-               find_delta_children(&base_spec, &ref_first, &ref_last);
-
-               memset(&base_spec, 0, sizeof(base_spec));
-               base_spec.offset = base->obj->idx.offset;
-               find_delta_children(&base_spec, &ofs_first, &ofs_last);
-       }
-
-       if (ref_last == -1 && ofs_last == -1) {
-               free(base->data);
-               return;
-       }
-
-       link_base_data(prev_base, base);
-
-       for (i = ref_first; i <= ref_last; i++) {
-               struct object_entry *child = objects + deltas[i].obj_no;
-               if (child->real_type == OBJ_REF_DELTA) {
-                       struct base_data result;
-                       resolve_delta(child, base, &result);
-                       if (i == ref_last && ofs_last == -1)
-                               free_base_data(base);
-                       find_unresolved_deltas(&result, base);
-               }
-       }
-
-       for (i = ofs_first; i <= ofs_last; i++) {
-               struct object_entry *child = objects + deltas[i].obj_no;
-               if (child->real_type == OBJ_OFS_DELTA) {
-                       struct base_data result;
-                       resolve_delta(child, base, &result);
-                       if (i == ofs_last)
-                               free_base_data(base);
-                       find_unresolved_deltas(&result, base);
-               }
-       }
-
-       unlink_base_data(base);
-}
-
-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, &delta_b->base, UNION_BASE_SZ);
-}
-
-/* Parse all objects and return the pack content SHA1 hash */
-static void parse_pack_objects(unsigned char *sha1)
-{
-       int i;
-       struct delta_entry *delta = deltas;
-       struct stat st;
-
-       /*
-        * First pass:
-        * - find locations of all objects;
-        * - calculate SHA1 of all non-delta objects;
-        * - remember base (SHA1 or offset) for all deltas.
-        */
-       if (verbose)
-               progress = start_progress(
-                               from_stdin ? "Receiving objects" : "Indexing objects",
-                               nr_objects);
-       for (i = 0; i < nr_objects; i++) {
-               struct object_entry *obj = &objects[i];
-               void *data = unpack_raw_entry(obj, &delta->base);
-               obj->real_type = obj->type;
-               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
-                       nr_deltas++;
-                       delta->obj_no = i;
-                       delta++;
-               } else
-                       sha1_object(data, obj->size, obj->type, obj->idx.sha1);
-               free(data);
-               display_progress(progress, i+1);
-       }
-       objects[i].idx.offset = consumed_bytes;
-       stop_progress(&progress);
-
-       /* Check pack integrity */
-       flush();
-       git_SHA1_Final(sha1, &input_ctx);
-       if (hashcmp(fill(20), sha1))
-               die("pack is corrupted (SHA1 mismatch)");
-       use(20);
-
-       /* If input_fd is a file, we should have reached its end now. */
-       if (fstat(input_fd, &st))
-               die("cannot fstat packfile: %s", strerror(errno));
-       if (S_ISREG(st.st_mode) &&
-                       lseek(input_fd, 0, SEEK_CUR) - input_len != st.st_size)
-               die("pack has junk at the end");
-
-       if (!nr_deltas)
-               return;
-
-       /* Sort deltas by base SHA1/offset 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.
-        */
-       if (verbose)
-               progress = start_progress("Resolving deltas", nr_deltas);
-       for (i = 0; i < nr_objects; i++) {
-               struct object_entry *obj = &objects[i];
-               struct base_data base_obj;
-
-               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA)
-                       continue;
-               base_obj.obj = obj;
-               base_obj.data = NULL;
-               find_unresolved_deltas(&base_obj, NULL);
-               display_progress(progress, nr_resolved_deltas);
-       }
-}
-
-static int write_compressed(struct sha1file *f, void *in, unsigned int size)
-{
-       z_stream stream;
-       unsigned long maxsize;
-       void *out;
-
-       memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, zlib_compression_level);
-       maxsize = deflateBound(&stream, size);
-       out = xmalloc(maxsize);
-
-       /* Compress it */
-       stream.next_in = in;
-       stream.avail_in = size;
-       stream.next_out = out;
-       stream.avail_out = maxsize;
-       while (deflate(&stream, Z_FINISH) == Z_OK);
-       deflateEnd(&stream);
-
-       size = stream.total_out;
-       sha1write(f, out, size);
-       free(out);
-       return size;
-}
-
-static struct object_entry *append_obj_to_pack(struct sha1file *f,
-                              const unsigned char *sha1, void *buf,
-                              unsigned long size, enum object_type type)
-{
-       struct object_entry *obj = &objects[nr_objects++];
-       unsigned char header[10];
-       unsigned long s = size;
-       int n = 0;
-       unsigned char c = (type << 4) | (s & 15);
-       s >>= 4;
-       while (s) {
-               header[n++] = c | 0x80;
-               c = s & 0x7f;
-               s >>= 7;
-       }
-       header[n++] = c;
-       crc32_begin(f);
-       sha1write(f, header, n);
-       obj[0].size = size;
-       obj[0].hdr_size = n;
-       obj[0].type = type;
-       obj[0].real_type = type;
-       obj[1].idx.offset = obj[0].idx.offset + n;
-       obj[1].idx.offset += write_compressed(f, buf, size);
-       obj[0].idx.crc32 = crc32_end(f);
-       sha1flush(f);
-       hashcpy(obj->idx.sha1, sha1);
-       return obj;
-}
-
-static int delta_pos_compare(const void *_a, const void *_b)
-{
-       struct delta_entry *a = *(struct delta_entry **)_a;
-       struct delta_entry *b = *(struct delta_entry **)_b;
-       return a->obj_no - b->obj_no;
-}
-
-static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
-{
-       struct delta_entry **sorted_by_pos;
-       int i, n = 0;
-
-       /*
-        * Since many unresolved deltas may well be themselves base objects
-        * for more unresolved deltas, we really want to include the
-        * smallest number of base objects that would cover as much delta
-        * as possible by picking the
-        * trunc deltas first, allowing for other deltas to resolve without
-        * additional base objects.  Since most base objects are to be found
-        * before deltas depending on them, a good heuristic is to start
-        * resolving deltas in the same order as their position in the pack.
-        */
-       sorted_by_pos = xmalloc(nr_unresolved * sizeof(*sorted_by_pos));
-       for (i = 0; i < nr_deltas; i++) {
-               if (objects[deltas[i].obj_no].real_type != OBJ_REF_DELTA)
-                       continue;
-               sorted_by_pos[n++] = &deltas[i];
-       }
-       qsort(sorted_by_pos, n, sizeof(*sorted_by_pos), delta_pos_compare);
-
-       for (i = 0; i < n; i++) {
-               struct delta_entry *d = sorted_by_pos[i];
-               enum object_type type;
-               struct base_data base_obj;
-
-               if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
-                       continue;
-               base_obj.data = read_sha1_file(d->base.sha1, &type, &base_obj.size);
-               if (!base_obj.data)
-                       continue;
-
-               if (check_sha1_signature(d->base.sha1, base_obj.data,
-                               base_obj.size, typename(type)))
-                       die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
-               base_obj.obj = append_obj_to_pack(f, d->base.sha1,
-                                       base_obj.data, base_obj.size, type);
-               find_unresolved_deltas(&base_obj, NULL);
-               display_progress(progress, nr_resolved_deltas);
-       }
-       free(sorted_by_pos);
-}
-
-static void final(const char *final_pack_name, const char *curr_pack_name,
-                 const char *final_index_name, const char *curr_index_name,
-                 const char *keep_name, const char *keep_msg,
-                 unsigned char *sha1)
-{
-       const char *report = "pack";
-       char name[PATH_MAX];
-       int err;
-
-       if (!from_stdin) {
-               close(input_fd);
-       } else {
-               fsync_or_die(output_fd, curr_pack_name);
-               err = close(output_fd);
-               if (err)
-                       die("error while closing pack file: %s", strerror(errno));
-       }
-
-       if (keep_msg) {
-               int keep_fd, keep_msg_len = strlen(keep_msg);
-
-               if (!keep_name)
-                       keep_fd = odb_pack_keep(name, sizeof(name), sha1);
-               else
-                       keep_fd = open(keep_name, O_RDWR|O_CREAT|O_EXCL, 0600);
-
-               if (keep_fd < 0) {
-                       if (errno != EEXIST)
-                               die("cannot write keep file '%s' (%s)",
-                                   keep_name, strerror(errno));
-               } else {
-                       if (keep_msg_len > 0) {
-                               write_or_die(keep_fd, keep_msg, keep_msg_len);
-                               write_or_die(keep_fd, "\n", 1);
-                       }
-                       if (close(keep_fd) != 0)
-                               die("cannot close written keep file '%s' (%s)",
-                                   keep_name, strerror(errno));
-                       report = "keep";
-               }
-       }
-
-       if (final_pack_name != curr_pack_name) {
-               if (!final_pack_name) {
-                       snprintf(name, sizeof(name), "%s/pack/pack-%s.pack",
-                                get_object_directory(), sha1_to_hex(sha1));
-                       final_pack_name = name;
-               }
-               if (move_temp_to_file(curr_pack_name, final_pack_name))
-                       die("cannot store pack file");
-       } else if (from_stdin)
-               chmod(final_pack_name, 0444);
-
-       if (final_index_name != curr_index_name) {
-               if (!final_index_name) {
-                       snprintf(name, sizeof(name), "%s/pack/pack-%s.idx",
-                                get_object_directory(), sha1_to_hex(sha1));
-                       final_index_name = name;
-               }
-               if (move_temp_to_file(curr_index_name, final_index_name))
-                       die("cannot store index file");
-       } else
-               chmod(final_index_name, 0444);
-
-       if (!from_stdin) {
-               printf("%s\n", sha1_to_hex(sha1));
-       } else {
-               char buf[48];
-               int len = snprintf(buf, sizeof(buf), "%s\t%s\n",
-                                  report, sha1_to_hex(sha1));
-               write_or_die(1, buf, len);
-
-               /*
-                * Let's just mimic git-unpack-objects here and write
-                * the last part of the input buffer to stdout.
-                */
-               while (input_len) {
-                       err = xwrite(1, input_buffer + input_offset, input_len);
-                       if (err <= 0)
-                               break;
-                       input_len -= err;
-                       input_offset += err;
-               }
-       }
-}
-
-static int git_index_pack_config(const char *k, const char *v, void *cb)
-{
-       if (!strcmp(k, "pack.indexversion")) {
-               pack_idx_default_version = git_config_int(k, v);
-               if (pack_idx_default_version > 2)
-                       die("bad pack.indexversion=%"PRIu32,
-                               pack_idx_default_version);
-               return 0;
-       }
-       return git_default_config(k, v, cb);
-}
-
-int main(int argc, char **argv)
-{
-       int i, fix_thin_pack = 0;
-       char *curr_pack, *pack_name = NULL;
-       char *curr_index, *index_name = NULL;
-       const char *keep_name = NULL, *keep_msg = NULL;
-       char *index_name_buf = NULL, *keep_name_buf = NULL;
-       struct pack_idx_entry **idx_objects;
-       unsigned char pack_sha1[20];
-
-       git_extract_argv0_path(argv[0]);
-
-       /*
-        * We wish to read the repository's config file if any, and
-        * for that it is necessary to call setup_git_directory_gently().
-        * However if the cwd was inside .git/objects/pack/ then we need
-        * to go back there or all the pack name arguments will be wrong.
-        * And in that case we cannot rely on any prefix returned by
-        * setup_git_directory_gently() either.
-        */
-       {
-               char cwd[PATH_MAX+1];
-               int nongit;
-
-               if (!getcwd(cwd, sizeof(cwd)-1))
-                       die("Unable to get current working directory");
-               setup_git_directory_gently(&nongit);
-               git_config(git_index_pack_config, NULL);
-               if (chdir(cwd))
-                       die("Cannot come back to cwd");
-       }
-
-       for (i = 1; i < argc; i++) {
-               char *arg = argv[i];
-
-               if (*arg == '-') {
-                       if (!strcmp(arg, "--stdin")) {
-                               from_stdin = 1;
-                       } else if (!strcmp(arg, "--fix-thin")) {
-                               fix_thin_pack = 1;
-                       } else if (!strcmp(arg, "--strict")) {
-                               strict = 1;
-                       } else if (!strcmp(arg, "--keep")) {
-                               keep_msg = "";
-                       } else if (!prefixcmp(arg, "--keep=")) {
-                               keep_msg = arg + 7;
-                       } else if (!prefixcmp(arg, "--pack_header=")) {
-                               struct pack_header *hdr;
-                               char *c;
-
-                               hdr = (struct pack_header *)input_buffer;
-                               hdr->hdr_signature = htonl(PACK_SIGNATURE);
-                               hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10));
-                               if (*c != ',')
-                                       die("bad %s", arg);
-                               hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10));
-                               if (*c)
-                                       die("bad %s", arg);
-                               input_len = sizeof(*hdr);
-                       } else if (!strcmp(arg, "-v")) {
-                               verbose = 1;
-                       } else if (!strcmp(arg, "-o")) {
-                               if (index_name || (i+1) >= argc)
-                                       usage(index_pack_usage);
-                               index_name = argv[++i];
-                       } else if (!prefixcmp(arg, "--index-version=")) {
-                               char *c;
-                               pack_idx_default_version = strtoul(arg + 16, &c, 10);
-                               if (pack_idx_default_version > 2)
-                                       die("bad %s", arg);
-                               if (*c == ',')
-                                       pack_idx_off32_limit = strtoul(c+1, &c, 0);
-                               if (*c || pack_idx_off32_limit & 0x80000000)
-                                       die("bad %s", arg);
-                       } else
-                               usage(index_pack_usage);
-                       continue;
-               }
-
-               if (pack_name)
-                       usage(index_pack_usage);
-               pack_name = arg;
-       }
-
-       if (!pack_name && !from_stdin)
-               usage(index_pack_usage);
-       if (fix_thin_pack && !from_stdin)
-               die("--fix-thin cannot be used without --stdin");
-       if (!index_name && pack_name) {
-               int len = strlen(pack_name);
-               if (!has_extension(pack_name, ".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;
-       }
-       if (keep_msg && !keep_name && pack_name) {
-               int len = strlen(pack_name);
-               if (!has_extension(pack_name, ".pack"))
-                       die("packfile name '%s' does not end with '.pack'",
-                           pack_name);
-               keep_name_buf = xmalloc(len);
-               memcpy(keep_name_buf, pack_name, len - 5);
-               strcpy(keep_name_buf + len - 5, ".keep");
-               keep_name = keep_name_buf;
-       }
-
-       curr_pack = open_pack_file(pack_name);
-       parse_pack_header();
-       objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry));
-       deltas = xmalloc(nr_objects * sizeof(struct delta_entry));
-       parse_pack_objects(pack_sha1);
-       if (nr_deltas == nr_resolved_deltas) {
-               stop_progress(&progress);
-               /* Flush remaining pack final 20-byte SHA1. */
-               flush();
-       } else {
-               if (fix_thin_pack) {
-                       struct sha1file *f;
-                       unsigned char read_sha1[20], tail_sha1[20];
-                       char msg[48];
-                       int nr_unresolved = nr_deltas - nr_resolved_deltas;
-                       int nr_objects_initial = nr_objects;
-                       if (nr_unresolved <= 0)
-                               die("confusion beyond insanity");
-                       objects = xrealloc(objects,
-                                          (nr_objects + nr_unresolved + 1)
-                                          * sizeof(*objects));
-                       f = sha1fd(output_fd, curr_pack);
-                       fix_unresolved_deltas(f, nr_unresolved);
-                       sprintf(msg, "completed with %d local objects",
-                               nr_objects - nr_objects_initial);
-                       stop_progress_msg(&progress, msg);
-                       sha1close(f, tail_sha1, 0);
-                       hashcpy(read_sha1, pack_sha1);
-                       fixup_pack_header_footer(output_fd, pack_sha1,
-                                                curr_pack, nr_objects,
-                                                read_sha1, consumed_bytes-20);
-                       if (hashcmp(read_sha1, tail_sha1) != 0)
-                               die("Unexpected tail checksum for %s "
-                                   "(disk corruption?)", curr_pack);
-               }
-               if (nr_deltas != nr_resolved_deltas)
-                       die("pack has %d unresolved deltas",
-                           nr_deltas - nr_resolved_deltas);
-       }
-       free(deltas);
-       if (strict)
-               check_objects();
-
-       idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *));
-       for (i = 0; i < nr_objects; i++)
-               idx_objects[i] = &objects[i].idx;
-       curr_index = write_idx_file(index_name, idx_objects, nr_objects, pack_sha1);
-       free(idx_objects);
-
-       final(pack_name, curr_pack,
-               index_name, curr_index,
-               keep_name, keep_msg,
-               pack_sha1);
-       free(objects);
-       free(index_name_buf);
-       free(keep_name_buf);
-       if (pack_name == NULL)
-               free(curr_pack);
-       if (index_name == NULL)
-               free(curr_index);
-
-       return 0;
-}
index 0173abeef52c8dc3930a5c36420ff073ba8ddc87..4105bf3549560acc7bd187b8a16fff2a5e739d09 100644 (file)
@@ -2,7 +2,7 @@
 #define LEVENSHTEIN_H
 
 int levenshtein(const char *string1, const char *string2,
-       int swap_penalty, int substition_penalty,
+       int swap_penalty, int substitution_penalty,
        int insertion_penalty, int deletion_penalty);
 
 #endif
index fa2ca5250c75a0d335570a3a3c71b20ebd0b42fa..6bb3095c3a85d771bb60e8f2f025287d16e792df 100644 (file)
@@ -15,10 +15,11 @@ struct ll_merge_driver;
 typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
                           mmbuffer_t *result,
                           const char *path,
-                          mmfile_t *orig,
+                          mmfile_t *orig, const char *orig_name,
                           mmfile_t *src1, const char *name1,
                           mmfile_t *src2, const char *name2,
-                          int virtual_ancestor);
+                          int flag,
+                          int marker_size);
 
 struct ll_merge_driver {
        const char *name;
@@ -35,17 +36,17 @@ struct ll_merge_driver {
 static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
                           mmbuffer_t *result,
                           const char *path_unused,
-                          mmfile_t *orig,
+                          mmfile_t *orig, const char *orig_name,
                           mmfile_t *src1, const char *name1,
                           mmfile_t *src2, const char *name2,
-                          int virtual_ancestor)
+                          int flag, int marker_size)
 {
        /*
         * The tentative merge result is "ours" for the final round,
         * or common ancestor for an internal merge.  Still return
         * "conflicted merge" status.
         */
-       mmfile_t *stolen = virtual_ancestor ? orig : src1;
+       mmfile_t *stolen = (flag & LL_OPT_VIRTUAL_ANCESTOR) ? orig : src1;
 
        result->ptr = stolen->ptr;
        result->size = stolen->size;
@@ -55,84 +56,54 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
 
 static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
                        mmbuffer_t *result,
-                       const char *path_unused,
-                       mmfile_t *orig,
+                       const char *path,
+                       mmfile_t *orig, const char *orig_name,
                        mmfile_t *src1, const char *name1,
                        mmfile_t *src2, const char *name2,
-                       int virtual_ancestor)
+                       int flag, int marker_size)
 {
-       xpparam_t xpp;
-       int style = 0;
+       xmparam_t xmp;
 
        if (buffer_is_binary(orig->ptr, orig->size) ||
            buffer_is_binary(src1->ptr, src1->size) ||
            buffer_is_binary(src2->ptr, src2->size)) {
-               warning("Cannot merge binary files: %s vs. %s\n",
-                       name1, name2);
+               warning("Cannot merge binary files: %s (%s vs. %s)\n",
+                       path, name1, name2);
                return ll_binary_merge(drv_unused, result,
-                                      path_unused,
-                                      orig, src1, name1,
+                                      path,
+                                      orig, orig_name,
+                                      src1, name1,
                                       src2, name2,
-                                      virtual_ancestor);
+                                      flag, marker_size);
        }
 
-       memset(&xpp, 0, sizeof(xpp));
+       memset(&xmp, 0, sizeof(xmp));
+       xmp.level = XDL_MERGE_ZEALOUS;
+       xmp.favor = ll_opt_favor(flag);
        if (git_xmerge_style >= 0)
-               style = git_xmerge_style;
-       return xdl_merge(orig,
-                        src1, name1,
-                        src2, name2,
-                        &xpp, XDL_MERGE_ZEALOUS | style,
-                        result);
+               xmp.style = git_xmerge_style;
+       if (marker_size > 0)
+               xmp.marker_size = marker_size;
+       xmp.ancestor = orig_name;
+       xmp.file1 = name1;
+       xmp.file2 = name2;
+       return xdl_merge(orig, src1, src2, &xmp, result);
 }
 
 static int ll_union_merge(const struct ll_merge_driver *drv_unused,
                          mmbuffer_t *result,
                          const char *path_unused,
-                         mmfile_t *orig,
+                         mmfile_t *orig, const char *orig_name,
                          mmfile_t *src1, const char *name1,
                          mmfile_t *src2, const char *name2,
-                         int virtual_ancestor)
+                         int flag, int marker_size)
 {
-       char *src, *dst;
-       long size;
-       const int marker_size = 7;
-       int status, saved_style;
-
-       /* We have to force the RCS "merge" style */
-       saved_style = git_xmerge_style;
-       git_xmerge_style = 0;
-       status = ll_xdl_merge(drv_unused, result, path_unused,
-                             orig, src1, NULL, src2, NULL,
-                             virtual_ancestor);
-       git_xmerge_style = saved_style;
-       if (status <= 0)
-               return status;
-       size = result->size;
-       src = dst = result->ptr;
-       while (size) {
-               char ch;
-               if ((marker_size < size) &&
-                   (*src == '<' || *src == '=' || *src == '>')) {
-                       int i;
-                       ch = *src;
-                       for (i = 0; i < marker_size; i++)
-                               if (src[i] != ch)
-                                       goto not_a_marker;
-                       if (src[marker_size] != '\n')
-                               goto not_a_marker;
-                       src += marker_size + 1;
-                       size -= marker_size + 1;
-                       continue;
-               }
-       not_a_marker:
-               do {
-                       ch = *src++;
-                       *dst++ = ch;
-                       size--;
-               } while (ch != '\n' && size);
-       }
-       result->size = dst - result->ptr;
+       /* Use union favor */
+       flag &= ~LL_OPT_FAVOR_MASK;
+       flag |= create_ll_flag(XDL_MERGE_FAVOR_UNION);
+       return ll_xdl_merge(drv_unused, result, path_unused,
+                           orig, NULL, src1, NULL, src2, NULL,
+                           flag, marker_size);
        return 0;
 }
 
@@ -152,7 +123,7 @@ static void create_temp(mmfile_t *src, char *path)
        strcpy(path, ".merge_file_XXXXXX");
        fd = xmkstemp(path);
        if (write_in_full(fd, src->ptr, src->size) != src->size)
-               die("unable to write temp-file");
+               die_errno("unable to write temp-file");
        close(fd);
 }
 
@@ -162,24 +133,24 @@ static void create_temp(mmfile_t *src, char *path)
 static int ll_ext_merge(const struct ll_merge_driver *fn,
                        mmbuffer_t *result,
                        const char *path,
-                       mmfile_t *orig,
+                       mmfile_t *orig, const char *orig_name,
                        mmfile_t *src1, const char *name1,
                        mmfile_t *src2, const char *name2,
-                       int virtual_ancestor)
+                       int flag, int marker_size)
 {
-       char temp[3][50];
+       char temp[4][50];
        struct strbuf cmd = STRBUF_INIT;
-       struct strbuf_expand_dict_entry dict[] = {
-               { "O", temp[0] },
-               { "A", temp[1] },
-               { "B", temp[2] },
-               { NULL }
-       };
-       struct child_process child;
-       const char *args[20];
+       struct strbuf_expand_dict_entry dict[5];
+       const char *args[] = { NULL, NULL };
        int status, fd, i;
        struct stat st;
 
+       dict[0].placeholder = "O"; dict[0].value = temp[0];
+       dict[1].placeholder = "A"; dict[1].value = temp[1];
+       dict[2].placeholder = "B"; dict[2].value = temp[2];
+       dict[3].placeholder = "L"; dict[3].value = temp[3];
+       dict[4].placeholder = NULL; dict[4].value = NULL;
+
        if (fn->cmdline == NULL)
                die("custom merge driver %s lacks command line.", fn->name);
 
@@ -188,21 +159,12 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
        create_temp(orig, temp[0]);
        create_temp(src1, temp[1]);
        create_temp(src2, temp[2]);
+       sprintf(temp[3], "%d", marker_size);
 
        strbuf_expand(&cmd, fn->cmdline, strbuf_expand_dict_cb, &dict);
 
-       memset(&child, 0, sizeof(child));
-       child.argv = args;
-       args[0] = "sh";
-       args[1] = "-c";
-       args[2] = cmd.buf;
-       args[3] = NULL;
-
-       status = run_command(&child);
-       if (status < -ERR_RUN_COMMAND_FORK)
-               ; /* failure in run-command */
-       else
-               status = -status;
+       args[0] = cmd.buf;
+       status = run_command_v_opt(args, RUN_USING_SHELL);
        fd = open(temp[1], O_RDONLY);
        if (fd < 0)
                goto bad;
@@ -219,7 +181,7 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
        close(fd);
  bad:
        for (i = 0; i < 3; i++)
-               unlink(temp[i]);
+               unlink_or_warn(temp[i]);
        strbuf_release(&cmd);
        return status;
 }
@@ -238,7 +200,7 @@ static int read_merge_config(const char *var, const char *value, void *cb)
 
        if (!strcmp(var, "merge.default")) {
                if (value)
-                       default_ll_merge = strdup(value);
+                       default_ll_merge = xstrdup(value);
                return 0;
        }
 
@@ -272,7 +234,7 @@ static int read_merge_config(const char *var, const char *value, void *cb)
        if (!strcmp("name", ep)) {
                if (!value)
                        return error("%s: lacks value", var);
-               fn->description = strdup(value);
+               fn->description = xstrdup(value);
                return 0;
        }
 
@@ -290,19 +252,20 @@ static int read_merge_config(const char *var, const char *value, void *cb)
                 *    %O - temporary file name for the merge base.
                 *    %A - temporary file name for our version.
                 *    %B - temporary file name for the other branches' version.
+                *    %L - conflict marker length
                 *
                 * The external merge driver should write the results in the
                 * file named by %A, and signal that it has done with zero exit
                 * status.
                 */
-               fn->cmdline = strdup(value);
+               fn->cmdline = xstrdup(value);
                return 0;
        }
 
        if (!strcmp("recursive", ep)) {
                if (!value)
                        return error("%s: lacks value", var);
-               fn->recursive = strdup(value);
+               fn->recursive = xstrdup(value);
                return 0;
        }
 
@@ -350,35 +313,70 @@ static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr
        return &ll_merge_drv[LL_TEXT_MERGE];
 }
 
-static const char *git_path_check_merge(const char *path)
+static int git_path_check_merge(const char *path, struct git_attr_check check[2])
 {
-       static struct git_attr_check attr_merge_check;
-
-       if (!attr_merge_check.attr)
-               attr_merge_check.attr = git_attr("merge", 5);
+       if (!check[0].attr) {
+               check[0].attr = git_attr("merge");
+               check[1].attr = git_attr("conflict-marker-size");
+       }
+       return git_checkattr(path, 2, check);
+}
 
-       if (git_checkattr(path, 1, &attr_merge_check))
-               return NULL;
-       return attr_merge_check.value;
+static void normalize_file(mmfile_t *mm, const char *path)
+{
+       struct strbuf strbuf = STRBUF_INIT;
+       if (renormalize_buffer(path, mm->ptr, mm->size, &strbuf)) {
+               free(mm->ptr);
+               mm->size = strbuf.len;
+               mm->ptr = strbuf_detach(&strbuf, NULL);
+       }
 }
 
 int ll_merge(mmbuffer_t *result_buf,
             const char *path,
-            mmfile_t *ancestor,
+            mmfile_t *ancestor, const char *ancestor_label,
             mmfile_t *ours, const char *our_label,
             mmfile_t *theirs, const char *their_label,
-            int virtual_ancestor)
+            int flag)
 {
-       const char *ll_driver_name;
+       static struct git_attr_check check[2];
+       const char *ll_driver_name = NULL;
+       int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
        const struct ll_merge_driver *driver;
+       int virtual_ancestor = flag & LL_OPT_VIRTUAL_ANCESTOR;
 
-       ll_driver_name = git_path_check_merge(path);
+       if (flag & LL_OPT_RENORMALIZE) {
+               normalize_file(ancestor, path);
+               normalize_file(ours, path);
+               normalize_file(theirs, path);
+       }
+       if (!git_path_check_merge(path, check)) {
+               ll_driver_name = check[0].value;
+               if (check[1].value) {
+                       marker_size = atoi(check[1].value);
+                       if (marker_size <= 0)
+                               marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
+               }
+       }
        driver = find_ll_merge_driver(ll_driver_name);
-
        if (virtual_ancestor && driver->recursive)
                driver = find_ll_merge_driver(driver->recursive);
-       return driver->fn(driver, result_buf, path,
-                         ancestor,
-                         ours, our_label,
-                         theirs, their_label, virtual_ancestor);
+       return driver->fn(driver, result_buf, path, ancestor, ancestor_label,
+                         ours, our_label, theirs, their_label,
+                         flag, marker_size);
+}
+
+int ll_merge_marker_size(const char *path)
+{
+       static struct git_attr_check check;
+       int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
+
+       if (!check.attr)
+               check.attr = git_attr("conflict-marker-size");
+       if (!git_checkattr(path, 1, &check) && check.value) {
+               marker_size = atoi(check.value);
+               if (marker_size <= 0)
+                       marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
+       }
+       return marker_size;
 }
index 5388422d091ede134d42406291989c49553f7428..ff7ca87bfa01d0e9705f4bfd83b9e4d2c155d588 100644 (file)
@@ -5,11 +5,28 @@
 #ifndef LL_MERGE_H
 #define LL_MERGE_H
 
+#define LL_OPT_VIRTUAL_ANCESTOR        (1 << 0)
+#define LL_OPT_FAVOR_MASK      ((1 << 1) | (1 << 2))
+#define LL_OPT_FAVOR_SHIFT 1
+#define LL_OPT_RENORMALIZE     (1 << 3)
+
+static inline int ll_opt_favor(int flag)
+{
+       return (flag & LL_OPT_FAVOR_MASK) >> LL_OPT_FAVOR_SHIFT;
+}
+
+static inline int create_ll_flag(int favor)
+{
+       return ((favor << LL_OPT_FAVOR_SHIFT) & LL_OPT_FAVOR_MASK);
+}
+
 int ll_merge(mmbuffer_t *result_buf,
             const char *path,
-            mmfile_t *ancestor,
+            mmfile_t *ancestor, const char *ancestor_label,
             mmfile_t *ours, const char *our_label,
             mmfile_t *theirs, const char *their_label,
-            int virtual_ancestor);
+            int flag);
+
+int ll_merge_marker_size(const char *path);
 
 #endif
index 828d19f452a8bfffe2ee224a630e8246f4a1edab..b0d74cdddee2407de590cb9f725e8e24093d9d35 100644 (file)
@@ -16,7 +16,7 @@ static void remove_lock_file(void)
                    lock_file_list->filename[0]) {
                        if (lock_file_list->fd >= 0)
                                close(lock_file_list->fd);
-                       unlink(lock_file_list->filename);
+                       unlink_or_warn(lock_file_list->filename);
                }
                lock_file_list = lock_file_list->next;
        }
@@ -155,18 +155,33 @@ static int lock_file(struct lock_file *lk, const char *path, int flags)
        return lk->fd;
 }
 
-
-NORETURN void unable_to_lock_index_die(const char *path, int err)
+static char *unable_to_lock_message(const char *path, int err)
 {
+       struct strbuf buf = STRBUF_INIT;
+
        if (err == EEXIST) {
-               die("Unable to create '%s.lock': %s.\n\n"
+               strbuf_addf(&buf, "Unable to create '%s.lock': %s.\n\n"
                    "If no other git process is currently running, this probably means a\n"
                    "git process crashed in this repository earlier. Make sure no other git\n"
                    "process is running and remove the file manually to continue.",
-                   path, strerror(err));
-       } else {
-               die("Unable to create '%s.lock': %s", path, strerror(err));
-       }
+                           make_nonrelative_path(path), strerror(err));
+       } else
+               strbuf_addf(&buf, "Unable to create '%s.lock': %s",
+                           make_nonrelative_path(path), strerror(err));
+       return strbuf_detach(&buf, NULL);
+}
+
+int unable_to_lock_error(const char *path, int err)
+{
+       char *msg = unable_to_lock_message(path, err);
+       error("%s", msg);
+       free(msg);
+       return -1;
+}
+
+NORETURN void unable_to_lock_index_die(const char *path, int err)
+{
+       die("%s", unable_to_lock_message(path, err));
 }
 
 int hold_lock_file_for_update(struct lock_file *lk, const char *path, int flags)
@@ -259,7 +274,7 @@ void rollback_lock_file(struct lock_file *lk)
        if (lk->filename[0]) {
                if (lk->fd >= 0)
                        close(lk->fd);
-               unlink(lk->filename);
+               unlink_or_warn(lk->filename);
        }
        lk->filename[0] = 0;
 }
index 5bd29e6994c92268ec576671bb8564b57d1a5c9d..b46ed3baef7d9d9971a3886d700059217fbe974a 100644 (file)
 #include "reflog-walk.h"
 #include "refs.h"
 #include "string-list.h"
+#include "color.h"
 
 struct decoration name_decoration = { "object names" };
 
-static void add_name_decoration(const char *prefix, const char *name, struct object *obj)
+enum decoration_type {
+       DECORATION_NONE = 0,
+       DECORATION_REF_LOCAL,
+       DECORATION_REF_REMOTE,
+       DECORATION_REF_TAG,
+       DECORATION_REF_STASH,
+       DECORATION_REF_HEAD,
+};
+
+static char decoration_colors[][COLOR_MAXLEN] = {
+       GIT_COLOR_RESET,
+       GIT_COLOR_BOLD_GREEN,   /* REF_LOCAL */
+       GIT_COLOR_BOLD_RED,     /* REF_REMOTE */
+       GIT_COLOR_BOLD_YELLOW,  /* REF_TAG */
+       GIT_COLOR_BOLD_MAGENTA, /* REF_STASH */
+       GIT_COLOR_BOLD_CYAN,    /* REF_HEAD */
+};
+
+static const char *decorate_get_color(int decorate_use_color, enum decoration_type ix)
+{
+       if (decorate_use_color)
+               return decoration_colors[ix];
+       return "";
+}
+
+static int parse_decorate_color_slot(const char *slot)
+{
+       /*
+        * We're comparing with 'ignore-case' on
+        * (because config.c sets them all tolower),
+        * but let's match the letters in the literal
+        * string values here with how they are
+        * documented in Documentation/config.txt, for
+        * consistency.
+        *
+        * We love being consistent, don't we?
+        */
+       if (!strcasecmp(slot, "branch"))
+               return DECORATION_REF_LOCAL;
+       if (!strcasecmp(slot, "remoteBranch"))
+               return DECORATION_REF_REMOTE;
+       if (!strcasecmp(slot, "tag"))
+               return DECORATION_REF_TAG;
+       if (!strcasecmp(slot, "stash"))
+               return DECORATION_REF_STASH;
+       if (!strcasecmp(slot, "HEAD"))
+               return DECORATION_REF_HEAD;
+       return -1;
+}
+
+int parse_decorate_color_config(const char *var, const int ofs, const char *value)
+{
+       int slot = parse_decorate_color_slot(var + ofs);
+       if (slot < 0)
+               return 0;
+       if (!value)
+               return config_error_nonbool(var);
+       color_parse(value, var, decoration_colors[slot]);
+       return 0;
+}
+
+/*
+ * log-tree.c uses DIFF_OPT_TST for determining whether to use color
+ * for showing the commit sha1, use the same check for --decorate
+ */
+#define decorate_get_color_opt(o, ix) \
+       decorate_get_color(DIFF_OPT_TST((o), COLOR_DIFF), ix)
+
+static void add_name_decoration(enum decoration_type type, const char *name, struct object *obj)
 {
-       int plen = strlen(prefix);
        int nlen = strlen(name);
-       struct name_decoration *res = xmalloc(sizeof(struct name_decoration) + plen + nlen);
-       memcpy(res->name, prefix, plen);
-       memcpy(res->name + plen, name, nlen + 1);
+       struct name_decoration *res = xmalloc(sizeof(struct name_decoration) + nlen);
+       memcpy(res->name, name, nlen + 1);
+       res->type = type;
        res->next = add_decoration(&name_decoration, obj, res);
 }
 
 static int add_ref_decoration(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
 {
        struct object *obj = parse_object(sha1);
+       enum decoration_type type = DECORATION_NONE;
        if (!obj)
                return 0;
-       add_name_decoration("", refname, obj);
+
+       if (!prefixcmp(refname, "refs/heads"))
+               type = DECORATION_REF_LOCAL;
+       else if (!prefixcmp(refname, "refs/remotes"))
+               type = DECORATION_REF_REMOTE;
+       else if (!prefixcmp(refname, "refs/tags"))
+               type = DECORATION_REF_TAG;
+       else if (!prefixcmp(refname, "refs/stash"))
+               type = DECORATION_REF_STASH;
+       else if (!prefixcmp(refname, "HEAD"))
+               type = DECORATION_REF_HEAD;
+
+       if (!cb_data || *(int *)cb_data == DECORATE_SHORT_REFS)
+               refname = prettify_refname(refname);
+       add_name_decoration(type, refname, obj);
        while (obj->type == OBJ_TAG) {
                obj = ((struct tag *)obj)->tagged;
                if (!obj)
                        break;
-               add_name_decoration("tag: ", refname, obj);
+               add_name_decoration(DECORATION_REF_TAG, refname, obj);
        }
        return 0;
 }
 
-void load_ref_decorations(void)
+void load_ref_decorations(int flags)
 {
        static int loaded;
        if (!loaded) {
                loaded = 1;
-               for_each_ref(add_ref_decoration, NULL);
+               for_each_ref(add_ref_decoration, &flags);
+               head_ref(add_ref_decoration, &flags);
        }
 }
 
@@ -57,6 +141,10 @@ void show_decorations(struct rev_info *opt, struct commit *commit)
 {
        const char *prefix;
        struct name_decoration *decoration;
+       const char *color_commit =
+               diff_get_color_opt(&opt->diffopt, DIFF_COMMIT);
+       const char *color_reset =
+               decorate_get_color_opt(&opt->diffopt, DECORATION_NONE);
 
        if (opt->show_source && commit->util)
                printf("\t%s", (char *) commit->util);
@@ -67,7 +155,14 @@ void show_decorations(struct rev_info *opt, struct commit *commit)
                return;
        prefix = " (";
        while (decoration) {
-               printf("%s%s", prefix, decoration->name);
+               printf("%s", prefix);
+               fputs(decorate_get_color_opt(&opt->diffopt, decoration->type),
+                     stdout);
+               if (decoration->type == DECORATION_REF_TAG)
+                       fputs("tag: ", stdout);
+               printf("%s", decoration->name);
+               fputs(color_reset, stdout);
+               fputs(color_commit, stdout);
                prefix = ", ";
                decoration = decoration->next;
        }
@@ -167,18 +262,6 @@ static unsigned int digits_in_number(unsigned int number)
        return result;
 }
 
-static int has_non_ascii(const char *s)
-{
-       int ch;
-       if (!s)
-               return 0;
-       while ((ch = *s++) != '\0') {
-               if (non_ascii(ch))
-                       return 1;
-       }
-       return 0;
-}
-
 void get_patch_filename(struct commit *commit, int nr, const char *suffix,
                        struct strbuf *buf)
 {
@@ -188,8 +271,10 @@ void get_patch_filename(struct commit *commit, int nr, const char *suffix,
        strbuf_addf(buf, commit ? "%04d-" : "%d", nr);
        if (commit) {
                int max_len = start_len + FORMAT_PATCH_NAME_MAX - suffix_len;
+               struct pretty_print_context ctx = {0};
+               ctx.date_mode = DATE_NORMAL;
 
-               format_commit_message(commit, "%f", buf, DATE_NORMAL);
+               format_commit_message(commit, "%f", buf, &ctx);
                if (max_len < buf->len)
                        strbuf_setlen(buf, max_len);
                strbuf_addstr(buf, suffix);
@@ -286,12 +371,12 @@ void show_log(struct rev_info *opt)
        struct strbuf msgbuf = STRBUF_INIT;
        struct log_info *log = opt->loginfo;
        struct commit *commit = log->commit, *parent = log->parent;
-       int abbrev = opt->diffopt.abbrev;
        int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40;
-       const char *subject = NULL, *extra_headers = opt->extra_headers;
-       int need_8bit_cte = 0;
+       const char *extra_headers = opt->extra_headers;
+       struct pretty_print_context ctx = {0};
 
        opt->loginfo = NULL;
+       ctx.show_notes = opt->show_notes;
        if (!opt->verbose_header) {
                graph_show_commit(opt->graph);
 
@@ -320,7 +405,8 @@ void show_log(struct rev_info *opt)
        }
 
        /*
-        * If use_terminator is set, add a newline at the end of the entry.
+        * If use_terminator is set, we already handled any record termination
+        * at the end of the last record.
         * Otherwise, add a diffopt.line_termination character before all
         * entries but the first.  (IOW, as a separator between entries)
         */
@@ -355,8 +441,8 @@ void show_log(struct rev_info *opt)
         */
 
        if (opt->commit_format == CMIT_FMT_EMAIL) {
-               log_write_email_headers(opt, commit, &subject, &extra_headers,
-                                       &need_8bit_cte);
+               log_write_email_headers(opt, commit, &ctx.subject, &extra_headers,
+                                       &ctx.need_8bit_cte);
        } else if (opt->commit_format != CMIT_FMT_USERFORMAT) {
                fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), stdout);
                if (opt->commit_format != CMIT_FMT_ONELINE)
@@ -399,7 +485,9 @@ void show_log(struct rev_info *opt)
                         */
                        show_reflog_message(opt->reflog_info,
                                    opt->commit_format == CMIT_FMT_ONELINE,
-                                   opt->date_mode);
+                                   opt->date_mode_explicit ?
+                                       opt->date_mode :
+                                       DATE_NORMAL);
                        if (opt->commit_format == CMIT_FMT_ONELINE)
                                return;
                }
@@ -411,11 +499,13 @@ void show_log(struct rev_info *opt)
        /*
         * And then the pretty-printed message itself
         */
-       if (need_8bit_cte >= 0)
-               need_8bit_cte = has_non_ascii(opt->add_signoff);
-       pretty_print_commit(opt->commit_format, commit, &msgbuf,
-                           abbrev, subject, extra_headers, opt->date_mode,
-                           need_8bit_cte);
+       if (ctx.need_8bit_cte >= 0)
+               ctx.need_8bit_cte = has_non_ascii(opt->add_signoff);
+       ctx.date_mode = opt->date_mode;
+       ctx.abbrev = opt->diffopt.abbrev;
+       ctx.after_subject = extra_headers;
+       ctx.reflog_info = opt->reflog_info;
+       pretty_print_commit(opt->commit_format, commit, &msgbuf, &ctx);
 
        if (opt->add_signoff)
                append_signoff(&msgbuf, opt->add_signoff);
@@ -471,6 +561,12 @@ int log_tree_diff_flush(struct rev_info *opt)
                        int pch = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
                        if ((pch & opt->diffopt.output_format) == pch)
                                printf("---");
+                       if (opt->diffopt.output_prefix) {
+                               struct strbuf *msg = NULL;
+                               msg = opt->diffopt.output_prefix(&opt->diffopt,
+                                       opt->diffopt.output_prefix_data);
+                               fwrite(msg->buf, msg->len, 1, stdout);
+                       }
                        putchar('\n');
                }
        }
@@ -516,6 +612,16 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
                        return 0;
                else if (opt->combine_merges)
                        return do_diff_combined(opt, commit);
+               else if (opt->first_parent_only) {
+                       /*
+                        * Generate merge log entry only for the first
+                        * parent, showing summary diff of the others
+                        * we merged _in_.
+                        */
+                       diff_tree_sha1(parents->item->object.sha1, sha1, "", &opt->diffopt);
+                       log_tree_diff_flush(opt);
+                       return !opt->loginfo;
+               }
 
                /* If we show individual diffs, show the parent info */
                log->parent = parents->item;
index 20b5caf1aa45aa0ba076ec60320d252c42abfb64..5c4cf7cac3327e2ef6000dff414680cd5c4c0e1e 100644 (file)
@@ -7,6 +7,7 @@ struct log_info {
        struct commit *commit, *parent;
 };
 
+int parse_decorate_color_config(const char *var, const int ofs, const char *value);
 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 *);
@@ -17,7 +18,7 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit,
                             const char **subject_p,
                             const char **extra_headers_p,
                             int *need_8bit_cte_p);
-void load_ref_decorations(void);
+void load_ref_decorations(int flags);
 
 #define FORMAT_PATCH_NAME_MAX 64
 void get_patch_filename(struct commit *commit, int nr, const char *suffix,
index bb1f2fb711a588d2af0d61decbd4b3eb2f2aebbe..f80b701292f0d852950735ab20a63dac13e92362 100644 (file)
--- a/mailmap.c
+++ b/mailmap.c
@@ -69,7 +69,7 @@ static void add_mapping(struct string_list *map,
                index = -1 - index;
        } else {
                /* create mailmap entry */
-               struct string_list_item *item = string_list_insert_at_index(index, old_email, map);
+               struct string_list_item *item = string_list_insert_at_index(map, index, old_email);
                item->util = xmalloc(sizeof(struct mailmap_entry));
                memset(item->util, 0, sizeof(struct mailmap_entry));
                ((struct mailmap_entry *)item->util)->namemap.strdup_strings = 1;
@@ -92,7 +92,7 @@ static void add_mapping(struct string_list *map,
                        mi->name = xstrdup(new_name);
                if (new_email)
                        mi->email = xstrdup(new_email);
-               string_list_insert(old_name, &me->namemap)->util = mi;
+               string_list_insert(&me->namemap, old_name)->util = mi;
        }
 
        debug_mm("mailmap:  '%s' <%s> -> '%s' <%s>\n",
@@ -103,7 +103,7 @@ static char *parse_name_and_email(char *buffer, char **name,
                char **email, int allow_empty_email)
 {
        char *left, *right, *nstart, *nend;
-       *name = *email = 0;
+       *name = *email = NULL;
 
        if ((left = strchr(buffer, '<')) == NULL)
                return NULL;
@@ -136,7 +136,7 @@ static int read_single_mailmap(struct string_list *map, const char *filename, ch
        if (f == NULL)
                return 1;
        while (fgets(buffer, sizeof(buffer), f) != NULL) {
-               char *name1 = 0, *email1 = 0, *name2 = 0, *email2 = 0;
+               char *name1 = NULL, *email1 = NULL, *name2 = NULL, *email2 = NULL;
                if (buffer[0] == '#') {
                        static const char abbrev[] = "# repo-abbrev:";
                        int abblen = sizeof(abbrev) - 1;
@@ -200,7 +200,7 @@ int map_user(struct string_list *map,
        if (!p) {
                /* email passed in might not be wrapped in <>, but end with a \0 */
                p = memchr(email, '\0', maxlen_email);
-               if (p == 0)
+               if (!p)
                        return 0;
        }
        if (p - email + 1 < sizeof(buf))
@@ -214,13 +214,13 @@ int map_user(struct string_list *map,
        mailbuf[i] = 0;
 
        debug_mm("map_user: map '%s' <%s>\n", name, mailbuf);
-       item = string_list_lookup(mailbuf, map);
+       item = string_list_lookup(map, mailbuf);
        if (item != NULL) {
                me = (struct mailmap_entry *)item->util;
                if (me->namemap.nr) {
                        /* The item has multiple items, so we'll look up on name too */
                        /* If the name is not found, we choose the simple entry      */
-                       struct string_list_item *subitem = string_list_lookup(name, &me->namemap);
+                       struct string_list_item *subitem = string_list_lookup(&me->namemap, name);
                        if (subitem)
                                item = subitem;
                }
@@ -243,8 +243,3 @@ int map_user(struct string_list *map,
        debug_mm("map_user:  --\n");
        return 0;
 }
-
-int map_email(struct string_list *map, const char *email, char *name, int maxlen)
-{
-       return map_user(map, (char *)email, 0, name, maxlen);
-}
index 4b2ca3a7de972c10f214b38a25be522abcbbafd0..d5c3664322023d6552cb24006d833aa08224ff6f 100644 (file)
--- a/mailmap.h
+++ b/mailmap.h
@@ -4,7 +4,6 @@
 int read_mailmap(struct string_list *map, char **repo_abbrev);
 void clear_mailmap(struct string_list *map);
 
-int map_email(struct string_list *mailmap, const char *email, char *name, int maxlen);
 int map_user(struct string_list *mailmap,
             char *email, int maxlen_email, char *name, int maxlen_name);
 
index 0fd6df7d6ed839eaed536bc332312c2688a6bbad..26f7ed143e0649b6377ea427b5c57ca7ddb663d3 100644 (file)
@@ -185,7 +185,7 @@ static void match_trees(const unsigned char *hash1,
  * tree object by replacing it with another tree "hash2".
  */
 static int splice_tree(const unsigned char *hash1,
-                      char *prefix,
+                      const char *prefix,
                       const unsigned char *hash2,
                       unsigned char *result)
 {
@@ -264,6 +264,13 @@ void shift_tree(const unsigned char *hash1,
        char *del_prefix;
        int add_score, del_score;
 
+       /*
+        * NEEDSWORK: this limits the recursion depth to hardcoded
+        * value '2' to avoid excessive overhead.
+        */
+       if (!depth_limit)
+               depth_limit = 2;
+
        add_score = del_score = score_trees(hash1, hash2);
        add_prefix = xcalloc(1, 1);
        del_prefix = xcalloc(1, 1);
@@ -301,3 +308,63 @@ void shift_tree(const unsigned char *hash1,
 
        splice_tree(hash1, add_prefix, hash2, shifted);
 }
+
+/*
+ * The user says the trees will be shifted by this much.
+ * Unfortunately we cannot fundamentally tell which one to
+ * be prefixed, as recursive merge can work in either direction.
+ */
+void shift_tree_by(const unsigned char *hash1,
+                  const unsigned char *hash2,
+                  unsigned char *shifted,
+                  const char *shift_prefix)
+{
+       unsigned char sub1[20], sub2[20];
+       unsigned mode1, mode2;
+       unsigned candidate = 0;
+
+       /* Can hash2 be a tree at shift_prefix in tree hash1? */
+       if (!get_tree_entry(hash1, shift_prefix, sub1, &mode1) &&
+           S_ISDIR(mode1))
+               candidate |= 1;
+
+       /* Can hash1 be a tree at shift_prefix in tree hash2? */
+       if (!get_tree_entry(hash2, shift_prefix, sub2, &mode2) &&
+           S_ISDIR(mode2))
+               candidate |= 2;
+
+       if (candidate == 3) {
+               /* Both are plausible -- we need to evaluate the score */
+               int best_score = score_trees(hash1, hash2);
+               int score;
+
+               candidate = 0;
+               score = score_trees(sub1, hash2);
+               if (score > best_score) {
+                       candidate = 1;
+                       best_score = score;
+               }
+               score = score_trees(sub2, hash1);
+               if (score > best_score)
+                       candidate = 2;
+       }
+
+       if (!candidate) {
+               /* Neither is plausible -- do not shift */
+               hashcpy(shifted, hash2);
+               return;
+       }
+
+       if (candidate == 1)
+               /*
+                * shift tree2 down by adding shift_prefix above it
+                * to match tree1.
+                */
+               splice_tree(hash1, shift_prefix, hash2, shifted);
+       else
+               /*
+                * shift tree2 up by removing shift_prefix from it
+                * to match tree1.
+                */
+               hashcpy(shifted, sub2);
+}
index 3120a95f786eadd4cb5d167facc15714904e9665..db4d0d50d32d8852d1cb5173125e4173c3badb49 100644 (file)
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "run-command.h"
 #include "xdiff-interface.h"
+#include "ll-merge.h"
 #include "blob.h"
 
 static int fill_mmfile_blob(mmfile_t *f, struct blob *obj)
@@ -24,16 +25,19 @@ static void free_mmfile(mmfile_t *f)
        free(f->ptr);
 }
 
-static void *three_way_filemerge(mmfile_t *base, mmfile_t *our, mmfile_t *their, unsigned long *size)
+static void *three_way_filemerge(const char *path, mmfile_t *base, mmfile_t *our, mmfile_t *their, unsigned long *size)
 {
-       mmbuffer_t res;
-       xpparam_t xpp;
        int merge_status;
+       mmbuffer_t res;
 
-       memset(&xpp, 0, sizeof(xpp));
-       merge_status = xdl_merge(base, our, ".our", their, ".their",
-               &xpp, XDL_MERGE_ZEALOUS, &res);
-
+       /*
+        * This function is only used by cmd_merge_tree, which
+        * does not respect the merge.conflictstyle option.
+        * There is no need to worry about a label for the
+        * common ancestor.
+        */
+       merge_status = ll_merge(&res, path, base, NULL,
+                               our, ".our", their, ".their", 0);
        if (merge_status < 0)
                return NULL;
 
@@ -62,7 +66,7 @@ static int generate_common_file(mmfile_t *res, mmfile_t *f1, mmfile_t *f2)
        xdemitcb_t ecb;
 
        memset(&xpp, 0, sizeof(xpp));
-       xpp.flags = XDF_NEED_MINIMAL;
+       xpp.flags = 0;
        memset(&xecfg, 0, sizeof(xecfg));
        xecfg.ctxlen = 3;
        xecfg.flags = XDL_EMIT_COMMON;
@@ -75,7 +79,7 @@ static int generate_common_file(mmfile_t *res, mmfile_t *f1, mmfile_t *f2)
        return xdi_diff(f1, f2, &xpp, &xecfg, &ecb);
 }
 
-void *merge_file(struct blob *base, struct blob *our, struct blob *their, unsigned long *size)
+void *merge_file(const char *path, struct blob *base, struct blob *our, struct blob *their, unsigned long *size)
 {
        void *res = NULL;
        mmfile_t f1, f2, common;
@@ -108,7 +112,7 @@ void *merge_file(struct blob *base, struct blob *our, struct blob *their, unsign
                if (generate_common_file(&common, &f1, &f2) < 0)
                        goto out_free_f2_f1;
        }
-       res = three_way_filemerge(&common, &f1, &f2, size);
+       res = three_way_filemerge(path, &common, &f1, &f2, size);
        free_mmfile(&common);
 out_free_f2_f1:
        free_mmfile(&f2);
diff --git a/merge-index.c b/merge-index.c
deleted file mode 100644 (file)
index aa9cf23..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-#include "cache.h"
-#include "run-command.h"
-#include "exec_cmd.h"
-
-static const char *pgm;
-static const char *arguments[9];
-static int one_shot, quiet;
-static int err;
-
-static void run_program(void)
-{
-       struct child_process child;
-       memset(&child, 0, sizeof(child));
-       child.argv = arguments;
-       if (run_command(&child)) {
-               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] = "";
-       arguments[8] = NULL;
-       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", ce->ce_mode);
-               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;
-
-       /* Without this we cannot rely on waitpid() to tell
-        * what happened to our children.
-        */
-       signal(SIGCHLD, SIG_DFL);
-
-       if (argc < 3)
-               usage("git merge-index [-o] [-q] <merge-program> (-a | [--] <filename>*)");
-
-       git_extract_argv0_path(argv[0]);
-
-       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;
-}
index a3721efcaf9458e5c735b45794d60bac670366d2..c5746988196b8139ca3234f8b30d03af56d33324 100644 (file)
@@ -3,6 +3,7 @@
  * Fredrik Kuivinen.
  * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006
  */
+#include "advice.h"
 #include "cache.h"
 #include "cache-tree.h"
 #include "commit.h"
 #include "attr.h"
 #include "merge-recursive.h"
 #include "dir.h"
+#include "submodule.h"
 
-static struct tree *shift_tree_object(struct tree *one, struct tree *two)
+static struct tree *shift_tree_object(struct tree *one, struct tree *two,
+                                     const char *subtree_shift)
 {
        unsigned char shifted[20];
 
-       /*
-        * NEEDSWORK: this limits the recursion depth to hardcoded
-        * value '2' to avoid excessive overhead.
-        */
-       shift_tree(one->object.sha1, two->object.sha1, shifted, 2);
+       if (!*subtree_shift) {
+               shift_tree(one->object.sha1, two->object.sha1, shifted, 0);
+       } else {
+               shift_tree_by(one->object.sha1, two->object.sha1, shifted,
+                             subtree_shift);
+       }
        if (!hashcmp(two->object.sha1, shifted))
                return two;
        return lookup_tree(shifted);
@@ -38,7 +42,7 @@ static struct tree *shift_tree_object(struct tree *one, struct tree *two)
  * A virtual commit has (const char *)commit->util set to the name.
  */
 
-struct commit *make_virtual_commit(struct tree *tree, const char *comment)
+static struct commit *make_virtual_commit(struct tree *tree, const char *comment)
 {
        struct commit *commit = xcalloc(1, sizeof(struct commit));
        commit->tree = tree;
@@ -86,6 +90,7 @@ static void flush_output(struct merge_options *o)
        }
 }
 
+__attribute__((format (printf, 3, 4)))
 static void output(struct merge_options *o, int v, const char *fmt, ...)
 {
        int len;
@@ -132,16 +137,10 @@ static void output_commit_title(struct merge_options *o, struct commit *commit)
                if (parse_commit(commit) != 0)
                        printf("(bad commit)\n");
                else {
-                       const char *s;
-                       int len;
-                       for (s = commit->buffer; *s; s++)
-                               if (*s == '\n' && s[1] == '\n') {
-                                       s += 2;
-                                       break;
-                               }
-                       for (len = 0; s[len] && '\n' != s[len]; len++)
-                               ; /* do nothing */
-                       printf("%.*s\n", len, s);
+                       const char *title;
+                       int len = find_commit_subject(commit->buffer, &title);
+                       if (len)
+                               printf("%.*s\n", len, title);
                }
        }
 }
@@ -181,6 +180,7 @@ static int git_merge_trees(int index_only,
        opts.fn = threeway_merge;
        opts.src_index = &the_index;
        opts.dst_index = &the_index;
+       setup_unpack_trees_porcelain(&opts, "merge");
 
        init_tree_desc_from_tree(t+0, common);
        init_tree_desc_from_tree(t+1, head);
@@ -197,13 +197,14 @@ struct tree *write_tree_from_memory(struct merge_options *o)
 
        if (unmerged_cache()) {
                int i;
-               output(o, 0, "There are unmerged index entries:");
+               fprintf(stderr, "BUG: There are unmerged index entries:\n");
                for (i = 0; i < active_nr; i++) {
                        struct cache_entry *ce = active_cache[i];
                        if (ce_stage(ce))
-                               output(o, 0, "%d %.*s", ce_stage(ce), ce_namelen(ce), ce->name);
+                               fprintf(stderr, "BUG: %d %.*s", ce_stage(ce),
+                                       (int)ce_namelen(ce), ce->name);
                }
-               return NULL;
+               die("Bug in merge-recursive.c");
        }
 
        if (!active_cache_tree)
@@ -232,9 +233,9 @@ static int save_files_dirs(const unsigned char *sha1,
        newpath[baselen + len] = '\0';
 
        if (S_ISDIR(mode))
-               string_list_insert(newpath, &o->current_directory_set);
+               string_list_insert(&o->current_directory_set, newpath);
        else
-               string_list_insert(newpath, &o->current_file_set);
+               string_list_insert(&o->current_file_set, newpath);
        free(newpath);
 
        return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);
@@ -265,7 +266,7 @@ static struct stage_data *insert_stage_data(const char *path,
                        e->stages[2].sha, &e->stages[2].mode);
        get_tree_entry(b->object.sha1, path,
                        e->stages[3].sha, &e->stages[3].mode);
-       item = string_list_insert(path, entries);
+       item = string_list_insert(entries, path);
        item->util = e;
        return e;
 }
@@ -288,9 +289,9 @@ static struct string_list *get_unmerged(void)
                if (!ce_stage(ce))
                        continue;
 
-               item = string_list_lookup(ce->name, unmerged);
+               item = string_list_lookup(unmerged, ce->name);
                if (!item) {
-                       item = string_list_insert(ce->name, unmerged);
+                       item = string_list_insert(unmerged, ce->name);
                        item->util = xcalloc(1, sizeof(struct stage_data));
                }
                e = item->util;
@@ -350,20 +351,20 @@ static struct string_list *get_renames(struct merge_options *o,
                re = xmalloc(sizeof(*re));
                re->processed = 0;
                re->pair = pair;
-               item = string_list_lookup(re->pair->one->path, entries);
+               item = string_list_lookup(entries, re->pair->one->path);
                if (!item)
                        re->src_entry = insert_stage_data(re->pair->one->path,
                                        o_tree, a_tree, b_tree, entries);
                else
                        re->src_entry = item->util;
 
-               item = string_list_lookup(re->pair->two->path, entries);
+               item = string_list_lookup(entries, re->pair->two->path);
                if (!item)
                        re->dst_entry = insert_stage_data(re->pair->two->path,
                                        o_tree, a_tree, b_tree, entries);
                else
                        re->dst_entry = item->util;
-               item = string_list_insert(pair->one->path, renames);
+               item = string_list_insert(renames, pair->one->path);
                item->util = re;
        }
        opts.output_format = DIFF_FORMAT_NO_OUTPUT;
@@ -403,7 +404,7 @@ static int remove_file(struct merge_options *o, int clean,
                        return -1;
        }
        if (update_working_directory) {
-               if (remove_path(path) && errno != ENOENT)
+               if (remove_path(path))
                        return -1;
        }
        return 0;
@@ -426,7 +427,7 @@ static char *unique_path(struct merge_options *o, const char *path, const char *
               lstat(newpath, &st) == 0)
                sprintf(p, "_%d", suffix++);
 
-       string_list_insert(newpath, &o->current_file_set);
+       string_list_insert(&o->current_file_set, newpath);
        return newpath;
 }
 
@@ -438,7 +439,7 @@ static void flush_buffer(int fd, const char *buf, unsigned long size)
                        /* Ignore epipe */
                        if (errno == EPIPE)
                                break;
-                       die("merge-recursive: %s", strerror(errno));
+                       die_errno("merge-recursive");
                } else if (!ret) {
                        die("merge-recursive: disk full?");
                }
@@ -519,13 +520,15 @@ static void update_file_flags(struct merge_options *o,
                void *buf;
                unsigned long size;
 
-               if (S_ISGITLINK(mode))
+               if (S_ISGITLINK(mode)) {
                        /*
                         * We may later decide to recursively descend into
                         * the submodule directory and update its index
                         * and/or work tree, but we do not do that now.
                         */
+                       update_wd = 0;
                        goto update_index;
+               }
 
                buf = read_sha1_file(sha, &type, &size);
                if (!buf)
@@ -554,7 +557,7 @@ static void update_file_flags(struct merge_options *o,
                                mode = 0666;
                        fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
                        if (fd < 0)
-                               die("failed to open %s: %s", path, strerror(errno));
+                               die_errno("failed to open '%s'", path);
                        flush_buffer(fd, buf, size);
                        close(fd);
                } else if (S_ISLNK(mode)) {
@@ -562,7 +565,7 @@ static void update_file_flags(struct merge_options *o,
                        safe_create_leading_directories_const(path);
                        unlink(path);
                        if (symlink(lnk, path))
-                               die("failed to symlink %s: %s", path, strerror(errno));
+                               die_errno("failed to symlink '%s'", path);
                        free(lnk);
                } else
                        die("do not know what to do with %06o %s '%s'",
@@ -593,23 +596,6 @@ struct merge_file_info
                 merge:1;
 };
 
-static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
-{
-       unsigned long size;
-       enum object_type type;
-
-       if (!hashcmp(sha1, null_sha1)) {
-               mm->ptr = xstrdup("");
-               mm->size = 0;
-               return;
-       }
-
-       mm->ptr = read_sha1_file(sha1, &type, &size);
-       if (!mm->ptr || type != OBJ_BLOB)
-               die("unable to read blob object %s", sha1_to_hex(sha1));
-       mm->size = size;
-}
-
 static int merge_3way(struct merge_options *o,
                      mmbuffer_t *result_buf,
                      struct diff_filespec *one,
@@ -619,19 +605,48 @@ static int merge_3way(struct merge_options *o,
                      const char *branch2)
 {
        mmfile_t orig, src1, src2;
-       char *name1, *name2;
+       char *base_name, *name1, *name2;
        int merge_status;
+       int favor;
 
-       name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
-       name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
+       if (o->call_depth)
+               favor = 0;
+       else {
+               switch (o->recursive_variant) {
+               case MERGE_RECURSIVE_OURS:
+                       favor = XDL_MERGE_FAVOR_OURS;
+                       break;
+               case MERGE_RECURSIVE_THEIRS:
+                       favor = XDL_MERGE_FAVOR_THEIRS;
+                       break;
+               default:
+                       favor = 0;
+                       break;
+               }
+       }
 
-       fill_mm(one->sha1, &orig);
-       fill_mm(a->sha1, &src1);
-       fill_mm(b->sha1, &src2);
+       if (strcmp(a->path, b->path) ||
+           (o->ancestor != NULL && strcmp(a->path, one->path) != 0)) {
+               base_name = o->ancestor == NULL ? NULL :
+                       xstrdup(mkpath("%s:%s", o->ancestor, one->path));
+               name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
+               name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
+       } else {
+               base_name = o->ancestor == NULL ? NULL :
+                       xstrdup(mkpath("%s", o->ancestor));
+               name1 = xstrdup(mkpath("%s", branch1));
+               name2 = xstrdup(mkpath("%s", branch2));
+       }
+
+       read_mmblob(&orig, one->sha1);
+       read_mmblob(&src1, a->sha1);
+       read_mmblob(&src2, b->sha1);
 
-       merge_status = ll_merge(result_buf, a->path, &orig,
+       merge_status = ll_merge(result_buf, a->path, &orig, base_name,
                                &src1, name1, &src2, name2,
-                               o->call_depth);
+                               ((o->call_depth ? LL_OPT_VIRTUAL_ANCESTOR : 0) |
+                                (o->renormalize ? LL_OPT_RENORMALIZE : 0) |
+                                create_ll_flag(favor)));
 
        free(name1);
        free(name2);
@@ -700,8 +715,8 @@ static struct merge_file_info merge_file(struct merge_options *o,
                        free(result_buf.ptr);
                        result.clean = (merge_status == 0);
                } else if (S_ISGITLINK(a->mode)) {
-                       result.clean = 0;
-                       hashcpy(result.sha, a->sha1);
+                       result.clean = merge_submodule(result.sha, one->path, one->sha1,
+                                                      a->sha1, b->sha1);
                } else if (S_ISLNK(a->mode)) {
                        hashcpy(result.sha, a->sha1);
 
@@ -790,17 +805,18 @@ static int process_renames(struct merge_options *o,
                           struct string_list *b_renames)
 {
        int clean_merge = 1, i, j;
-       struct string_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0};
+       struct string_list a_by_dst = STRING_LIST_INIT_NODUP;
+       struct string_list b_by_dst = STRING_LIST_INIT_NODUP;
        const struct rename *sre;
 
        for (i = 0; i < a_renames->nr; i++) {
                sre = a_renames->items[i].util;
-               string_list_insert(sre->pair->two->path, &a_by_dst)->util
+               string_list_insert(&a_by_dst, sre->pair->two->path)->util
                        = sre->dst_entry;
        }
        for (i = 0; i < b_renames->nr; i++) {
                sre = b_renames->items[i].util;
-               string_list_insert(sre->pair->two->path, &b_by_dst)->util
+               string_list_insert(&b_by_dst, sre->pair->two->path)->util
                        = sre->dst_entry;
        }
 
@@ -933,11 +949,18 @@ static int process_renames(struct merge_options *o,
                                       ren1_src, ren1_dst, branch1,
                                       branch2);
                                update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);
-                               update_stages(ren1_dst, NULL,
-                                               branch1 == o->branch1 ?
-                                               ren1->pair->two : NULL,
-                                               branch1 == o->branch1 ?
-                                               NULL : ren1->pair->two, 1);
+                               if (!o->call_depth)
+                                       update_stages(ren1_dst, NULL,
+                                                       branch1 == o->branch1 ?
+                                                       ren1->pair->two : NULL,
+                                                       branch1 == o->branch1 ?
+                                                       NULL : ren1->pair->two, 1);
+                       } else if ((dst_other.mode == ren1->pair->two->mode) &&
+                                  sha_eq(dst_other.sha1, ren1->pair->two->sha1)) {
+                               /* Added file on the other side
+                                  identical to the file being
+                                  renamed: clean merge */
+                               update_file(o, 1, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);
                        } else if (!sha_eq(dst_other.sha1, null_sha1)) {
                                const char *new_path;
                                clean_merge = 0;
@@ -946,10 +969,32 @@ static int process_renames(struct merge_options *o,
                                       "%s added in %s",
                                       ren1_src, ren1_dst, branch1,
                                       ren1_dst, branch2);
-                               new_path = unique_path(o, ren1_dst, branch2);
-                               output(o, 1, "Adding as %s instead", new_path);
-                               update_file(o, 0, dst_other.sha1, dst_other.mode, new_path);
-                       } else if ((item = string_list_lookup(ren1_dst, renames2Dst))) {
+                               if (o->call_depth) {
+                                       struct merge_file_info mfi;
+                                       struct diff_filespec one, a, b;
+
+                                       one.path = a.path = b.path =
+                                               (char *)ren1_dst;
+                                       hashcpy(one.sha1, null_sha1);
+                                       one.mode = 0;
+                                       hashcpy(a.sha1, ren1->pair->two->sha1);
+                                       a.mode = ren1->pair->two->mode;
+                                       hashcpy(b.sha1, dst_other.sha1);
+                                       b.mode = dst_other.mode;
+                                       mfi = merge_file(o, &one, &a, &b,
+                                                        branch1,
+                                                        branch2);
+                                       output(o, 1, "Adding merged %s", ren1_dst);
+                                       update_file(o, 0,
+                                                   mfi.sha,
+                                                   mfi.mode,
+                                                   ren1_dst);
+                               } else {
+                                       new_path = unique_path(o, ren1_dst, branch2);
+                                       output(o, 1, "Adding as %s instead", new_path);
+                                       update_file(o, 0, dst_other.sha1, dst_other.mode, new_path);
+                               }
+                       } else if ((item = string_list_lookup(renames2Dst, ren1_dst))) {
                                ren2 = item->util;
                                clean_merge = 0;
                                ren2->processed = 1;
@@ -980,14 +1025,22 @@ static int process_renames(struct merge_options *o,
 
                                if (mfi.clean &&
                                    sha_eq(mfi.sha, ren1->pair->two->sha1) &&
-                                   mfi.mode == ren1->pair->two->mode)
+                                   mfi.mode == ren1->pair->two->mode) {
                                        /*
-                                        * This messaged is part of
+                                        * This message is part of
                                         * t6022 test. If you change
                                         * it update the test too.
                                         */
                                        output(o, 3, "Skipped %s (merged same as existing)", ren1_dst);
-                               else {
+
+                                       /* There may be higher stage entries left
+                                        * in the index (e.g. due to a D/F
+                                        * conflict) that need to be resolved.
+                                        */
+                                       if (!ren1->dst_entry->stages[2].mode !=
+                                           !ren1->dst_entry->stages[3].mode)
+                                               ren1->dst_entry->processed = 0;
+                               } else {
                                        if (mfi.merge || !mfi.clean)
                                                output(o, 1, "Renaming %s => %s", ren1_src, ren1_dst);
                                        if (mfi.merge)
@@ -1017,6 +1070,53 @@ static unsigned char *stage_sha(const unsigned char *sha, unsigned mode)
        return (is_null_sha1(sha) || mode == 0) ? NULL: (unsigned char *)sha;
 }
 
+static int read_sha1_strbuf(const unsigned char *sha1, struct strbuf *dst)
+{
+       void *buf;
+       enum object_type type;
+       unsigned long size;
+       buf = read_sha1_file(sha1, &type, &size);
+       if (!buf)
+               return error("cannot read object %s", sha1_to_hex(sha1));
+       if (type != OBJ_BLOB) {
+               free(buf);
+               return error("object %s is not a blob", sha1_to_hex(sha1));
+       }
+       strbuf_attach(dst, buf, size, size + 1);
+       return 0;
+}
+
+static int blob_unchanged(const unsigned char *o_sha,
+                         const unsigned char *a_sha,
+                         int renormalize, const char *path)
+{
+       struct strbuf o = STRBUF_INIT;
+       struct strbuf a = STRBUF_INIT;
+       int ret = 0; /* assume changed for safety */
+
+       if (sha_eq(o_sha, a_sha))
+               return 1;
+       if (!renormalize)
+               return 0;
+
+       assert(o_sha && a_sha);
+       if (read_sha1_strbuf(o_sha, &o) || read_sha1_strbuf(a_sha, &a))
+               goto error_return;
+       /*
+        * Note: binary | is used so that both renormalizations are
+        * performed.  Comparison can be skipped if both files are
+        * unchanged since their sha1s have already been compared.
+        */
+       if (renormalize_buffer(path, o.buf, o.len, &o) |
+           renormalize_buffer(path, a.buf, o.len, &a))
+               ret = (o.len == a.len && !memcmp(o.buf, a.buf, o.len));
+
+error_return:
+       strbuf_release(&o);
+       strbuf_release(&a);
+       return ret;
+}
+
 /* Per entry merge function */
 static int process_entry(struct merge_options *o,
                         const char *path, struct stage_data *entry)
@@ -1026,6 +1126,7 @@ static int process_entry(struct merge_options *o,
        print_index_entry("\tpath: ", entry);
        */
        int clean_merge = 1;
+       int normalize = o->renormalize;
        unsigned o_mode = entry->stages[1].mode;
        unsigned a_mode = entry->stages[2].mode;
        unsigned b_mode = entry->stages[3].mode;
@@ -1033,11 +1134,12 @@ static int process_entry(struct merge_options *o,
        unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
        unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
 
+       entry->processed = 1;
        if (o_sha && (!a_sha || !b_sha)) {
                /* Case A: Deleted in one */
                if ((!a_sha && !b_sha) ||
-                   (sha_eq(a_sha, o_sha) && !b_sha) ||
-                   (!a_sha && sha_eq(b_sha, o_sha))) {
+                   (!b_sha && blob_unchanged(o_sha, a_sha, normalize, path)) ||
+                   (!a_sha && blob_unchanged(o_sha, b_sha, normalize, path))) {
                        /* Deleted in both or deleted in one and
                         * unchanged in the other */
                        if (a_sha)
@@ -1065,33 +1167,28 @@ static int process_entry(struct merge_options *o,
        } else if ((!o_sha && a_sha && !b_sha) ||
                   (!o_sha && !a_sha && b_sha)) {
                /* Case B: Added in one. */
-               const char *add_branch;
-               const char *other_branch;
                unsigned mode;
                const unsigned char *sha;
-               const char *conf;
 
                if (a_sha) {
-                       add_branch = o->branch1;
-                       other_branch = o->branch2;
                        mode = a_mode;
                        sha = a_sha;
-                       conf = "file/directory";
                } else {
-                       add_branch = o->branch2;
-                       other_branch = o->branch1;
                        mode = b_mode;
                        sha = b_sha;
-                       conf = "directory/file";
                }
                if (string_list_has_string(&o->current_directory_set, path)) {
-                       const char *new_path = unique_path(o, path, add_branch);
-                       clean_merge = 0;
-                       output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
-                              "Adding %s as %s",
-                              conf, path, other_branch, path, new_path);
-                       remove_file(o, 0, path, 0);
-                       update_file(o, 0, sha, mode, new_path);
+                       /* Handle D->F conflicts after all subfiles */
+                       entry->processed = 0;
+                       /* But get any file out of the way now, so conflicted
+                        * entries below the directory of the same name can
+                        * be put in the working directory.
+                        */
+                       if (a_sha)
+                               output(o, 2, "Removing %s", path);
+                       /* do not touch working file if it did not exist */
+                       remove_file(o, 0, path, !a_sha);
+                       return 1; /* Assume clean till processed */
                } else {
                        output(o, 2, "Adding %s", path);
                        update_file(o, 1, sha, mode, path);
@@ -1139,6 +1236,64 @@ static int process_entry(struct merge_options *o,
        return clean_merge;
 }
 
+/*
+ * Per entry merge function for D/F conflicts, to be called only after
+ * all files below dir have been processed.  We do this because in the
+ * cases we can cleanly resolve D/F conflicts, process_entry() can clean
+ * out all the files below the directory for us.
+ */
+static int process_df_entry(struct merge_options *o,
+                        const char *path, struct stage_data *entry)
+{
+       int clean_merge = 1;
+       unsigned o_mode = entry->stages[1].mode;
+       unsigned a_mode = entry->stages[2].mode;
+       unsigned b_mode = entry->stages[3].mode;
+       unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode);
+       unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
+       unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
+       const char *add_branch;
+       const char *other_branch;
+       unsigned mode;
+       const unsigned char *sha;
+       const char *conf;
+       struct stat st;
+
+       /* We currently only handle D->F cases */
+       assert((!o_sha && a_sha && !b_sha) ||
+              (!o_sha && !a_sha && b_sha));
+
+       entry->processed = 1;
+
+       if (a_sha) {
+               add_branch = o->branch1;
+               other_branch = o->branch2;
+               mode = a_mode;
+               sha = a_sha;
+               conf = "file/directory";
+       } else {
+               add_branch = o->branch2;
+               other_branch = o->branch1;
+               mode = b_mode;
+               sha = b_sha;
+               conf = "directory/file";
+       }
+       if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
+               const char *new_path = unique_path(o, path, add_branch);
+               clean_merge = 0;
+               output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
+                      "Adding %s as %s",
+                      conf, path, other_branch, path, new_path);
+               remove_file(o, 0, path, 0);
+               update_file(o, 0, sha, mode, new_path);
+       } else {
+               output(o, 2, "Adding %s", path);
+               update_file(o, 1, sha, mode, path);
+       }
+
+       return clean_merge;
+}
+
 int merge_trees(struct merge_options *o,
                struct tree *head,
                struct tree *merge,
@@ -1147,23 +1302,27 @@ int merge_trees(struct merge_options *o,
 {
        int code, clean;
 
-       if (o->subtree_merge) {
-               merge = shift_tree_object(head, merge);
-               common = shift_tree_object(head, common);
+       if (o->subtree_shift) {
+               merge = shift_tree_object(head, merge, o->subtree_shift);
+               common = shift_tree_object(head, common, o->subtree_shift);
        }
 
        if (sha_eq(common->object.sha1, merge->object.sha1)) {
-               output(o, 0, "Already uptodate!");
+               output(o, 0, "Already up-to-date!");
                *result = head;
                return 1;
        }
 
        code = git_merge_trees(o->call_depth, common, head, merge);
 
-       if (code != 0)
-               die("merging of trees %s and %s failed",
-                   sha1_to_hex(head->object.sha1),
-                   sha1_to_hex(merge->object.sha1));
+       if (code != 0) {
+               if (show(o, 4) || o->call_depth)
+                       die("merging of trees %s and %s failed",
+                           sha1_to_hex(head->object.sha1),
+                           sha1_to_hex(merge->object.sha1));
+               else
+                       exit(128);
+       }
 
        if (unmerged_cache()) {
                struct string_list *entries, *re_head, *re_merge;
@@ -1184,6 +1343,13 @@ int merge_trees(struct merge_options *o,
                                && !process_entry(o, path, e))
                                clean = 0;
                }
+               for (i = 0; i < entries->nr; i++) {
+                       const char *path = entries->items[i].string;
+                       struct stage_data *e = entries->items[i].util;
+                       if (!e->processed
+                               && !process_df_entry(o, path, e))
+                               clean = 0;
+               }
 
                string_list_clear(re_merge, 0);
                string_list_clear(re_head, 0);
@@ -1282,6 +1448,7 @@ int merge_recursive(struct merge_options *o,
        if (!o->call_depth)
                read_cache();
 
+       o->ancestor = "merged common ancestors";
        clean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree,
                            &mrtree);
 
@@ -1370,6 +1537,7 @@ void init_merge_options(struct merge_options *o)
        o->buffer_output = 1;
        o->diff_rename_limit = -1;
        o->merge_rename_limit = -1;
+       o->renormalize = 0;
        git_config(merge_recursive_config, o);
        if (getenv("GIT_MERGE_VERBOSITY"))
                o->verbosity =
index fd138ca14006843a7ce0c38cfde4de580d5ce36e..34492dbd6ea22d715c98acbeeabe04033ff9a794 100644 (file)
@@ -4,10 +4,17 @@
 #include "string-list.h"
 
 struct merge_options {
+       const char *ancestor;
        const char *branch1;
        const char *branch2;
-       unsigned subtree_merge : 1;
+       enum {
+               MERGE_RECURSIVE_NORMAL = 0,
+               MERGE_RECURSIVE_OURS,
+               MERGE_RECURSIVE_THEIRS
+       } recursive_variant;
+       const char *subtree_shift;
        unsigned buffer_output : 1;
+       unsigned renormalize : 1;
        int verbosity;
        int diff_rename_limit;
        int merge_rename_limit;
@@ -45,4 +52,7 @@ int merge_recursive_generic(struct merge_options *o,
 void init_merge_options(struct merge_options *o);
 struct tree *write_tree_from_memory(struct merge_options *o);
 
+/* builtin/merge.c */
+int try_merge_command(const char *strategy, struct commit_list *common, const char *head_arg, struct commit_list *remotes);
+
 #endif
diff --git a/merge-tree.c b/merge-tree.c
deleted file mode 100644 (file)
index f01e7c8..0000000
+++ /dev/null
@@ -1,362 +0,0 @@
-#include "cache.h"
-#include "tree-walk.h"
-#include "xdiff-interface.h"
-#include "blob.h"
-#include "exec_cmd.h"
-
-static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>";
-static int resolve_directories = 1;
-
-struct merge_list {
-       struct merge_list *next;
-       struct merge_list *link;        /* other stages for this object */
-
-       unsigned int stage : 2,
-                    flags : 30;
-       unsigned int mode;
-       const char *path;
-       struct blob *blob;
-};
-
-static struct merge_list *merge_result, **merge_result_end = &merge_result;
-
-static void add_merge_entry(struct merge_list *entry)
-{
-       *merge_result_end = entry;
-       merge_result_end = &entry->next;
-}
-
-static void merge_trees(struct tree_desc t[3], const char *base);
-
-static const char *explanation(struct merge_list *entry)
-{
-       switch (entry->stage) {
-       case 0:
-               return "merged";
-       case 3:
-               return "added in remote";
-       case 2:
-               if (entry->link)
-                       return "added in both";
-               return "added in local";
-       }
-
-       /* Existed in base */
-       entry = entry->link;
-       if (!entry)
-               return "removed in both";
-
-       if (entry->link)
-               return "changed in both";
-
-       if (entry->stage == 3)
-               return "removed in local";
-       return "removed in remote";
-}
-
-extern void *merge_file(struct blob *, struct blob *, struct blob *, unsigned long *);
-
-static void *result(struct merge_list *entry, unsigned long *size)
-{
-       enum object_type type;
-       struct blob *base, *our, *their;
-
-       if (!entry->stage)
-               return read_sha1_file(entry->blob->object.sha1, &type, size);
-       base = NULL;
-       if (entry->stage == 1) {
-               base = entry->blob;
-               entry = entry->link;
-       }
-       our = NULL;
-       if (entry && entry->stage == 2) {
-               our = entry->blob;
-               entry = entry->link;
-       }
-       their = NULL;
-       if (entry)
-               their = entry->blob;
-       return merge_file(base, our, their, size);
-}
-
-static void *origin(struct merge_list *entry, unsigned long *size)
-{
-       enum object_type type;
-       while (entry) {
-               if (entry->stage == 2)
-                       return read_sha1_file(entry->blob->object.sha1, &type, size);
-               entry = entry->link;
-       }
-       return NULL;
-}
-
-static int show_outf(void *priv_, mmbuffer_t *mb, int nbuf)
-{
-       int i;
-       for (i = 0; i < nbuf; i++)
-               printf("%.*s", (int) mb[i].size, mb[i].ptr);
-       return 0;
-}
-
-static void show_diff(struct merge_list *entry)
-{
-       unsigned long size;
-       mmfile_t src, dst;
-       xpparam_t xpp;
-       xdemitconf_t xecfg;
-       xdemitcb_t ecb;
-
-       xpp.flags = XDF_NEED_MINIMAL;
-       memset(&xecfg, 0, sizeof(xecfg));
-       xecfg.ctxlen = 3;
-       ecb.outf = show_outf;
-       ecb.priv = NULL;
-
-       src.ptr = origin(entry, &size);
-       if (!src.ptr)
-               size = 0;
-       src.size = size;
-       dst.ptr = result(entry, &size);
-       if (!dst.ptr)
-               size = 0;
-       dst.size = size;
-       xdi_diff(&src, &dst, &xpp, &xecfg, &ecb);
-       free(src.ptr);
-       free(dst.ptr);
-}
-
-static void show_result_list(struct merge_list *entry)
-{
-       printf("%s\n", explanation(entry));
-       do {
-               struct merge_list *link = entry->link;
-               static const char *desc[4] = { "result", "base", "our", "their" };
-               printf("  %-6s %o %s %s\n", desc[entry->stage], entry->mode, sha1_to_hex(entry->blob->object.sha1), entry->path);
-               entry = link;
-       } while (entry);
-}
-
-static void show_result(void)
-{
-       struct merge_list *walk;
-
-       walk = merge_result;
-       while (walk) {
-               show_result_list(walk);
-               show_diff(walk);
-               walk = walk->next;
-       }
-}
-
-/* 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 &&
-               !hashcmp(a->sha1, b->sha1) &&
-               a->mode == b->mode;
-}
-
-static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsigned char *sha1, const char *path)
-{
-       struct merge_list *res = xcalloc(1, sizeof(*res));
-
-       res->stage = stage;
-       res->path = path;
-       res->mode = mode;
-       res->blob = lookup_blob(sha1);
-       return res;
-}
-
-static char *traverse_path(const struct traverse_info *info, const struct name_entry *n)
-{
-       char *path = xmalloc(traverse_path_len(info, n) + 1);
-       return make_traverse_path(path, info, n);
-}
-
-static void resolve(const struct traverse_info *info, struct name_entry *branch1, struct name_entry *result)
-{
-       struct merge_list *orig, *final;
-       const char *path;
-
-       /* If it's already branch1, don't bother showing it */
-       if (!branch1)
-               return;
-
-       path = traverse_path(info, result);
-       orig = create_entry(2, branch1->mode, branch1->sha1, path);
-       final = create_entry(0, result->mode, result->sha1, path);
-
-       final->link = orig;
-
-       add_merge_entry(final);
-}
-
-static int unresolved_directory(const struct traverse_info *info, struct name_entry n[3])
-{
-       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;
-       newbase = traverse_path(info, p);
-       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 struct merge_list *link_entry(unsigned stage, const struct traverse_info *info, struct name_entry *n, struct merge_list *entry)
-{
-       const char *path;
-       struct merge_list *link;
-
-       if (!n->mode)
-               return entry;
-       if (entry)
-               path = entry->path;
-       else
-               path = traverse_path(info, n);
-       link = create_entry(stage, n->mode, n->sha1, path);
-       link->link = entry;
-       return link;
-}
-
-static void unresolved(const struct traverse_info *info, struct name_entry n[3])
-{
-       struct merge_list *entry = NULL;
-
-       if (unresolved_directory(info, n))
-               return;
-
-       /*
-        * Do them in reverse order so that the resulting link
-        * list has the stages in order - link_entry adds new
-        * links at the front.
-        */
-       entry = link_entry(3, info, n + 2, entry);
-       entry = link_entry(2, info, n + 1, entry);
-       entry = link_entry(1, info, n + 0, entry);
-
-       add_merge_entry(entry);
-}
-
-/*
- * 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 int threeway_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *info)
-{
-       /* Same in both? */
-       if (same_entry(entry+1, entry+2)) {
-               if (entry[0].sha1) {
-                       resolve(info, NULL, entry+1);
-                       return mask;
-               }
-       }
-
-       if (same_entry(entry+0, entry+1)) {
-               if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) {
-                       resolve(info, entry+1, entry+2);
-                       return mask;
-               }
-       }
-
-       if (same_entry(entry+0, entry+2)) {
-               if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) {
-                       resolve(info, NULL, entry+1);
-                       return mask;
-               }
-       }
-
-       unresolved(info, entry);
-       return mask;
-}
-
-static void merge_trees(struct tree_desc t[3], const char *base)
-{
-       struct traverse_info info;
-
-       setup_traverse_info(&info, base);
-       info.fn = threeway_callback;
-       traverse_trees(3, t, &info);
-}
-
-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);
-
-       git_extract_argv0_path(argv[0]);
-
-       setup_git_directory();
-
-       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);
-
-       show_result();
-       return 0;
-}
diff --git a/mktag.c b/mktag.c
deleted file mode 100644 (file)
index 99a356e..0000000
--- a/mktag.c
+++ /dev/null
@@ -1,182 +0,0 @@
-#include "cache.h"
-#include "tag.h"
-#include "exec_cmd.h"
-
-/*
- * A signature file has a very simple fixed format: four lines
- * of "object <sha1>" + "type <typename>" + "tag <tagname>" +
- * "tagger <committer>", followed by a blank line, a free-form tag
- * message and a signature block that git itself doesn't care about,
- * but that can be verified with gpg or similar.
- *
- * The first four lines are guaranteed to be at least 83 bytes:
- * "object <sha1>\n" is 48 bytes, "type tag\n" at 9 bytes is the
- * shortest possible type-line, "tag .\n" at 6 bytes is the shortest
- * single-character-tag line, and "tagger . <> 0 +0000\n" at 20 bytes is
- * the shortest possible tagger-line.
- */
-
-/*
- * 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;
-       enum object_type type;
-       unsigned long size;
-       void *buffer = read_sha1_file(sha1, &type, &size);
-
-       if (buffer) {
-               if (type == type_from_string(expected_type))
-                       ret = check_sha1_signature(sha1, buffer, size, expected_type);
-               free(buffer);
-       }
-       return ret;
-}
-
-#ifdef NO_C99_FORMAT
-#define PD_FMT "%d"
-#else
-#define PD_FMT "%td"
-#endif
-
-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, *lb, *rb;
-       size_t len;
-
-       if (size < 84)
-               return error("wanna fool me ? you obviously got the size wrong !");
-
-       buffer[size] = 0;
-
-       /* Verify object line */
-       object = buffer;
-       if (memcmp(object, "object ", 7))
-               return error("char%d: does not start with \"object \"", 0);
-
-       if (get_sha1_hex(object + 7, sha1))
-               return error("char%d: could not get SHA1 hash", 7);
-
-       /* Verify type line */
-       type_line = object + 48;
-       if (memcmp(type_line - 1, "\ntype ", 6))
-               return error("char%d: could not find \"\\ntype \"", 47);
-
-       /* Verify tag-line */
-       tag_line = strchr(type_line, '\n');
-       if (!tag_line)
-               return error("char" PD_FMT ": could not find next \"\\n\"", type_line - buffer);
-       tag_line++;
-       if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n')
-               return error("char" PD_FMT ": no \"tag \" found", tag_line - buffer);
-
-       /* Get the actual type */
-       typelen = tag_line - type_line - strlen("type \n");
-       if (typelen >= sizeof(type))
-               return error("char" PD_FMT ": type too long", type_line+5 - buffer);
-
-       memcpy(type, type_line+5, typelen);
-       type[typelen] = 0;
-
-       /* Verify that the object matches */
-       if (verify_object(sha1, type))
-               return error("char%d: could not verify object %s", 7, sha1_to_hex(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" PD_FMT ": could not verify tag name", tag_line - buffer);
-       }
-
-       /* Verify the tagger line */
-       tagger_line = tag_line;
-
-       if (memcmp(tagger_line, "tagger ", 7))
-               return error("char" PD_FMT ": could not find \"tagger \"",
-                       tagger_line - buffer);
-
-       /*
-        * Check for correct form for name and email
-        * i.e. " <" followed by "> " on _this_ line
-        * No angle brackets within the name or email address fields.
-        * No spaces within the email address field.
-        */
-       tagger_line += 7;
-       if (!(lb = strstr(tagger_line, " <")) || !(rb = strstr(lb+2, "> ")) ||
-               strpbrk(tagger_line, "<>\n") != lb+1 ||
-               strpbrk(lb+2, "><\n ") != rb)
-               return error("char" PD_FMT ": malformed tagger field",
-                       tagger_line - buffer);
-
-       /* Check for author name, at least one character, space is acceptable */
-       if (lb == tagger_line)
-               return error("char" PD_FMT ": missing tagger name",
-                       tagger_line - buffer);
-
-       /* timestamp, 1 or more digits followed by space */
-       tagger_line = rb + 2;
-       if (!(len = strspn(tagger_line, "0123456789")))
-               return error("char" PD_FMT ": missing tag timestamp",
-                       tagger_line - buffer);
-       tagger_line += len;
-       if (*tagger_line != ' ')
-               return error("char" PD_FMT ": malformed tag timestamp",
-                       tagger_line - buffer);
-       tagger_line++;
-
-       /* timezone, 5 digits [+-]hhmm, max. 1400 */
-       if (!((tagger_line[0] == '+' || tagger_line[0] == '-') &&
-             strspn(tagger_line+1, "0123456789") == 4 &&
-             tagger_line[5] == '\n' && atoi(tagger_line+1) <= 1400))
-               return error("char" PD_FMT ": malformed tag timezone",
-                       tagger_line - buffer);
-       tagger_line += 6;
-
-       /* Verify the blank line separating the header from the body */
-       if (*tagger_line != '\n')
-               return error("char" PD_FMT ": trailing garbage in tag header",
-                       tagger_line - buffer);
-
-       /* The actual stuff afterwards we don't care about.. */
-       return 0;
-}
-
-#undef PD_FMT
-
-int main(int argc, char **argv)
-{
-       struct strbuf buf = STRBUF_INIT;
-       unsigned char result_sha1[20];
-
-       if (argc != 1)
-               usage("git mktag < signaturefile");
-
-       git_extract_argv0_path(argv[0]);
-
-       setup_git_directory();
-
-       if (strbuf_read(&buf, 0, 4096) < 0) {
-               die("could not read from stdin");
-       }
-
-       /* Verify it for some basic sanity: it needs to start with
-          "object <sha1>\ntype\ntagger " */
-       if (verify_tag(buf.buf, buf.len) < 0)
-               die("invalid tag signature file");
-
-       if (write_sha1_file(buf.buf, buf.len, tag_type, result_sha1) < 0)
-               die("unable to write tag file");
-
-       strbuf_release(&buf);
-       printf("%s\n", sha1_to_hex(result_sha1));
-       return 0;
-}
diff --git a/mktree.c b/mktree.c
deleted file mode 100644 (file)
index 137a095..0000000
--- a/mktree.c
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * GIT - the stupid content tracker
- *
- * Copyright (c) Junio C Hamano, 2006
- */
-#include "cache.h"
-#include "quote.h"
-#include "tree.h"
-#include "exec_cmd.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;
-       hashcpy(ent->sha1, sha1);
-       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)
-{
-       struct strbuf buf;
-       size_t size;
-       int i;
-
-       qsort(entries, used, sizeof(*entries), ent_compare);
-       for (size = i = 0; i < used; i++)
-               size += 32 + entries[i]->len;
-
-       strbuf_init(&buf, size);
-       for (i = 0; i < used; i++) {
-               struct treeent *ent = entries[i];
-               strbuf_addf(&buf, "%o %s%c", ent->mode, ent->name, '\0');
-               strbuf_add(&buf, ent->sha1, 20);
-       }
-
-       write_sha1_file(buf.buf, buf.len, tree_type, sha1);
-}
-
-static const char mktree_usage[] = "git mktree [-z]";
-
-int main(int ac, char **av)
-{
-       struct strbuf sb = STRBUF_INIT;
-       struct strbuf p_uq = STRBUF_INIT;
-       unsigned char sha1[20];
-       int line_termination = '\n';
-
-       git_extract_argv0_path(av[0]);
-
-       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++;
-       }
-
-       while (strbuf_getline(&sb, stdin, line_termination) != EOF) {
-               char *ptr, *ntr;
-               unsigned mode;
-               enum object_type type;
-               char *path;
-
-               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 + sb.len <= ntr + 40 ||
-                   ntr[41] != '\t' ||
-                   get_sha1_hex(ntr + 1, sha1))
-                       die("input format error: %s", sb.buf);
-               type = sha1_object_info(sha1, NULL);
-               if (type < 0)
-                       die("object %s unavailable", sha1_to_hex(sha1));
-               *ntr++ = 0; /* now at the beginning of SHA1 */
-               if (type != type_from_string(ptr))
-                       die("object type %s mismatch (%s)", ptr, typename(type));
-
-               path = ntr + 41;  /* at the beginning of name */
-               if (line_termination && path[0] == '"') {
-                       strbuf_reset(&p_uq);
-                       if (unquote_c_style(&p_uq, path, NULL)) {
-                               die("invalid quoting");
-                       }
-                       path = p_uq.buf;
-               }
-
-               append_to_tree(mode, sha1, path);
-       }
-       strbuf_release(&p_uq);
-       strbuf_release(&sb);
-
-       write_tree(sha1);
-       puts(sha1_to_hex(sha1));
-       exit(0);
-}
diff --git a/mozilla-sha1/sha1.c b/mozilla-sha1/sha1.c
deleted file mode 100644 (file)
index 95a4ebf..0000000
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * 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(moz_SHA_CTX *ctx);
-
-void moz_SHA1_Init(moz_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 moz_SHA1_Update(moz_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 moz_SHA1_Final(unsigned char hashout[20], moz_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);
-  moz_SHA1_Update(ctx, &pad0x80, 1);
-  while (ctx->lenW != 56)
-    moz_SHA1_Update(ctx, &pad0x00, 1);
-  moz_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)
-   */
-  moz_SHA1_Init(ctx);
-}
-
-
-#define SHA_ROT(X,n) (((X) << (n)) | ((X) >> (32-(n))))
-
-static void shaHashBlock(moz_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
deleted file mode 100644 (file)
index aa48a46..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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;
-} moz_SHA_CTX;
-
-void moz_SHA1_Init(moz_SHA_CTX *ctx);
-void moz_SHA1_Update(moz_SHA_CTX *ctx, const void *dataIn, int len);
-void moz_SHA1_Final(unsigned char hashout[20], moz_SHA_CTX *ctx);
-
-#define git_SHA_CTX    moz_SHA_CTX
-#define git_SHA1_Init  moz_SHA1_Init
-#define git_SHA1_Update        moz_SHA1_Update
-#define git_SHA1_Final moz_SHA1_Final
diff --git a/notes-cache.c b/notes-cache.c
new file mode 100644 (file)
index 0000000..dee6d62
--- /dev/null
@@ -0,0 +1,94 @@
+#include "cache.h"
+#include "notes-cache.h"
+#include "commit.h"
+#include "refs.h"
+
+static int notes_cache_match_validity(const char *ref, const char *validity)
+{
+       unsigned char sha1[20];
+       struct commit *commit;
+       struct pretty_print_context pretty_ctx;
+       struct strbuf msg = STRBUF_INIT;
+       int ret;
+
+       if (read_ref(ref, sha1) < 0)
+               return 0;
+
+       commit = lookup_commit_reference_gently(sha1, 1);
+       if (!commit)
+               return 0;
+
+       memset(&pretty_ctx, 0, sizeof(pretty_ctx));
+       format_commit_message(commit, "%s", &msg, &pretty_ctx);
+       strbuf_trim(&msg);
+
+       ret = !strcmp(msg.buf, validity);
+       strbuf_release(&msg);
+
+       return ret;
+}
+
+void notes_cache_init(struct notes_cache *c, const char *name,
+                    const char *validity)
+{
+       struct strbuf ref = STRBUF_INIT;
+       int flags = 0;
+
+       memset(c, 0, sizeof(*c));
+       c->validity = xstrdup(validity);
+
+       strbuf_addf(&ref, "refs/notes/%s", name);
+       if (!notes_cache_match_validity(ref.buf, validity))
+               flags = NOTES_INIT_EMPTY;
+       init_notes(&c->tree, ref.buf, combine_notes_overwrite, flags);
+       strbuf_release(&ref);
+}
+
+int notes_cache_write(struct notes_cache *c)
+{
+       unsigned char tree_sha1[20];
+       unsigned char commit_sha1[20];
+
+       if (!c || !c->tree.initialized || !c->tree.ref || !*c->tree.ref)
+               return -1;
+       if (!c->tree.dirty)
+               return 0;
+
+       if (write_notes_tree(&c->tree, tree_sha1))
+               return -1;
+       if (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL) < 0)
+               return -1;
+       if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL,
+                      0, QUIET_ON_ERR) < 0)
+               return -1;
+
+       return 0;
+}
+
+char *notes_cache_get(struct notes_cache *c, unsigned char key_sha1[20],
+                     size_t *outsize)
+{
+       const unsigned char *value_sha1;
+       enum object_type type;
+       char *value;
+       unsigned long size;
+
+       value_sha1 = get_note(&c->tree, key_sha1);
+       if (!value_sha1)
+               return NULL;
+       value = read_sha1_file(value_sha1, &type, &size);
+
+       *outsize = size;
+       return value;
+}
+
+int notes_cache_put(struct notes_cache *c, unsigned char key_sha1[20],
+                   const char *data, size_t size)
+{
+       unsigned char value_sha1[20];
+
+       if (write_sha1_file(data, size, "blob", value_sha1) < 0)
+               return -1;
+       add_note(&c->tree, key_sha1, value_sha1, NULL);
+       return 0;
+}
diff --git a/notes-cache.h b/notes-cache.h
new file mode 100644 (file)
index 0000000..356f88f
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef NOTES_CACHE_H
+#define NOTES_CACHE_H
+
+#include "notes.h"
+
+struct notes_cache {
+       struct notes_tree tree;
+       char *validity;
+};
+
+void notes_cache_init(struct notes_cache *c, const char *name,
+                    const char *validity);
+int notes_cache_write(struct notes_cache *c);
+
+char *notes_cache_get(struct notes_cache *c, unsigned char sha1[20], size_t
+                     *outsize);
+int notes_cache_put(struct notes_cache *c, unsigned char sha1[20],
+                   const char *data, size_t size);
+
+#endif /* NOTES_CACHE_H */
diff --git a/notes.c b/notes.c
new file mode 100644 (file)
index 0000000..70d0013
--- /dev/null
+++ b/notes.c
@@ -0,0 +1,1199 @@
+#include "cache.h"
+#include "notes.h"
+#include "blob.h"
+#include "tree.h"
+#include "utf8.h"
+#include "strbuf.h"
+#include "tree-walk.h"
+#include "string-list.h"
+#include "refs.h"
+
+/*
+ * Use a non-balancing simple 16-tree structure with struct int_node as
+ * internal nodes, and struct leaf_node as leaf nodes. Each int_node has a
+ * 16-array of pointers to its children.
+ * The bottom 2 bits of each pointer is used to identify the pointer type
+ * - ptr & 3 == 0 - NULL pointer, assert(ptr == NULL)
+ * - ptr & 3 == 1 - pointer to next internal node - cast to struct int_node *
+ * - ptr & 3 == 2 - pointer to note entry - cast to struct leaf_node *
+ * - ptr & 3 == 3 - pointer to subtree entry - cast to struct leaf_node *
+ *
+ * The root node is a statically allocated struct int_node.
+ */
+struct int_node {
+       void *a[16];
+};
+
+/*
+ * Leaf nodes come in two variants, note entries and subtree entries,
+ * distinguished by the LSb of the leaf node pointer (see above).
+ * As a note entry, the key is the SHA1 of the referenced object, and the
+ * value is the SHA1 of the note object.
+ * As a subtree entry, the key is the prefix SHA1 (w/trailing NULs) of the
+ * referenced object, using the last byte of the key to store the length of
+ * the prefix. The value is the SHA1 of the tree object containing the notes
+ * subtree.
+ */
+struct leaf_node {
+       unsigned char key_sha1[20];
+       unsigned char val_sha1[20];
+};
+
+/*
+ * A notes tree may contain entries that are not notes, and that do not follow
+ * the naming conventions of notes. There are typically none/few of these, but
+ * we still need to keep track of them. Keep a simple linked list sorted alpha-
+ * betically on the non-note path. The list is populated when parsing tree
+ * objects in load_subtree(), and the non-notes are correctly written back into
+ * the tree objects produced by write_notes_tree().
+ */
+struct non_note {
+       struct non_note *next; /* grounded (last->next == NULL) */
+       char *path;
+       unsigned int mode;
+       unsigned char sha1[20];
+};
+
+#define PTR_TYPE_NULL     0
+#define PTR_TYPE_INTERNAL 1
+#define PTR_TYPE_NOTE     2
+#define PTR_TYPE_SUBTREE  3
+
+#define GET_PTR_TYPE(ptr)       ((uintptr_t) (ptr) & 3)
+#define CLR_PTR_TYPE(ptr)       ((void *) ((uintptr_t) (ptr) & ~3))
+#define SET_PTR_TYPE(ptr, type) ((void *) ((uintptr_t) (ptr) | (type)))
+
+#define GET_NIBBLE(n, sha1) (((sha1[(n) >> 1]) >> ((~(n) & 0x01) << 2)) & 0x0f)
+
+#define SUBTREE_SHA1_PREFIXCMP(key_sha1, subtree_sha1) \
+       (memcmp(key_sha1, subtree_sha1, subtree_sha1[19]))
+
+struct notes_tree default_notes_tree;
+
+static struct string_list display_notes_refs;
+static struct notes_tree **display_notes_trees;
+
+static void load_subtree(struct notes_tree *t, struct leaf_node *subtree,
+               struct int_node *node, unsigned int n);
+
+/*
+ * Search the tree until the appropriate location for the given key is found:
+ * 1. Start at the root node, with n = 0
+ * 2. If a[0] at the current level is a matching subtree entry, unpack that
+ *    subtree entry and remove it; restart search at the current level.
+ * 3. Use the nth nibble of the key as an index into a:
+ *    - If a[n] is an int_node, recurse from #2 into that node and increment n
+ *    - If a matching subtree entry, unpack that subtree entry (and remove it);
+ *      restart search at the current level.
+ *    - Otherwise, we have found one of the following:
+ *      - a subtree entry which does not match the key
+ *      - a note entry which may or may not match the key
+ *      - an unused leaf node (NULL)
+ *      In any case, set *tree and *n, and return pointer to the tree location.
+ */
+static void **note_tree_search(struct notes_tree *t, struct int_node **tree,
+               unsigned char *n, const unsigned char *key_sha1)
+{
+       struct leaf_node *l;
+       unsigned char i;
+       void *p = (*tree)->a[0];
+
+       if (GET_PTR_TYPE(p) == PTR_TYPE_SUBTREE) {
+               l = (struct leaf_node *) CLR_PTR_TYPE(p);
+               if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) {
+                       /* unpack tree and resume search */
+                       (*tree)->a[0] = NULL;
+                       load_subtree(t, l, *tree, *n);
+                       free(l);
+                       return note_tree_search(t, tree, n, key_sha1);
+               }
+       }
+
+       i = GET_NIBBLE(*n, key_sha1);
+       p = (*tree)->a[i];
+       switch (GET_PTR_TYPE(p)) {
+       case PTR_TYPE_INTERNAL:
+               *tree = CLR_PTR_TYPE(p);
+               (*n)++;
+               return note_tree_search(t, tree, n, key_sha1);
+       case PTR_TYPE_SUBTREE:
+               l = (struct leaf_node *) CLR_PTR_TYPE(p);
+               if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) {
+                       /* unpack tree and resume search */
+                       (*tree)->a[i] = NULL;
+                       load_subtree(t, l, *tree, *n);
+                       free(l);
+                       return note_tree_search(t, tree, n, key_sha1);
+               }
+               /* fall through */
+       default:
+               return &((*tree)->a[i]);
+       }
+}
+
+/*
+ * To find a leaf_node:
+ * Search to the tree location appropriate for the given key:
+ * If a note entry with matching key, return the note entry, else return NULL.
+ */
+static struct leaf_node *note_tree_find(struct notes_tree *t,
+               struct int_node *tree, unsigned char n,
+               const unsigned char *key_sha1)
+{
+       void **p = note_tree_search(t, &tree, &n, key_sha1);
+       if (GET_PTR_TYPE(*p) == PTR_TYPE_NOTE) {
+               struct leaf_node *l = (struct leaf_node *) CLR_PTR_TYPE(*p);
+               if (!hashcmp(key_sha1, l->key_sha1))
+                       return l;
+       }
+       return NULL;
+}
+
+/*
+ * To insert a leaf_node:
+ * Search to the tree location appropriate for the given leaf_node's key:
+ * - If location is unused (NULL), store the tweaked pointer directly there
+ * - If location holds a note entry that matches the note-to-be-inserted, then
+ *   combine the two notes (by calling the given combine_notes function).
+ * - If location holds a note entry that matches the subtree-to-be-inserted,
+ *   then unpack the subtree-to-be-inserted into the location.
+ * - If location holds a matching subtree entry, unpack the subtree at that
+ *   location, and restart the insert operation from that level.
+ * - Else, create a new int_node, holding both the node-at-location and the
+ *   node-to-be-inserted, and store the new int_node into the location.
+ */
+static void note_tree_insert(struct notes_tree *t, struct int_node *tree,
+               unsigned char n, struct leaf_node *entry, unsigned char type,
+               combine_notes_fn combine_notes)
+{
+       struct int_node *new_node;
+       struct leaf_node *l;
+       void **p = note_tree_search(t, &tree, &n, entry->key_sha1);
+
+       assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */
+       l = (struct leaf_node *) CLR_PTR_TYPE(*p);
+       switch (GET_PTR_TYPE(*p)) {
+       case PTR_TYPE_NULL:
+               assert(!*p);
+               *p = SET_PTR_TYPE(entry, type);
+               return;
+       case PTR_TYPE_NOTE:
+               switch (type) {
+               case PTR_TYPE_NOTE:
+                       if (!hashcmp(l->key_sha1, entry->key_sha1)) {
+                               /* skip concatenation if l == entry */
+                               if (!hashcmp(l->val_sha1, entry->val_sha1))
+                                       return;
+
+                               if (combine_notes(l->val_sha1, entry->val_sha1))
+                                       die("failed to combine notes %s and %s"
+                                           " for object %s",
+                                           sha1_to_hex(l->val_sha1),
+                                           sha1_to_hex(entry->val_sha1),
+                                           sha1_to_hex(l->key_sha1));
+                               free(entry);
+                               return;
+                       }
+                       break;
+               case PTR_TYPE_SUBTREE:
+                       if (!SUBTREE_SHA1_PREFIXCMP(l->key_sha1,
+                                                   entry->key_sha1)) {
+                               /* unpack 'entry' */
+                               load_subtree(t, entry, tree, n);
+                               free(entry);
+                               return;
+                       }
+                       break;
+               }
+               break;
+       case PTR_TYPE_SUBTREE:
+               if (!SUBTREE_SHA1_PREFIXCMP(entry->key_sha1, l->key_sha1)) {
+                       /* unpack 'l' and restart insert */
+                       *p = NULL;
+                       load_subtree(t, l, tree, n);
+                       free(l);
+                       note_tree_insert(t, tree, n, entry, type,
+                                        combine_notes);
+                       return;
+               }
+               break;
+       }
+
+       /* non-matching leaf_node */
+       assert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE ||
+              GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE);
+       new_node = (struct int_node *) xcalloc(sizeof(struct int_node), 1);
+       note_tree_insert(t, new_node, n + 1, l, GET_PTR_TYPE(*p),
+                        combine_notes);
+       *p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL);
+       note_tree_insert(t, new_node, n + 1, entry, type, combine_notes);
+}
+
+/*
+ * How to consolidate an int_node:
+ * If there are > 1 non-NULL entries, give up and return non-zero.
+ * Otherwise replace the int_node at the given index in the given parent node
+ * with the only entry (or a NULL entry if no entries) from the given tree,
+ * and return 0.
+ */
+static int note_tree_consolidate(struct int_node *tree,
+       struct int_node *parent, unsigned char index)
+{
+       unsigned int i;
+       void *p = NULL;
+
+       assert(tree && parent);
+       assert(CLR_PTR_TYPE(parent->a[index]) == tree);
+
+       for (i = 0; i < 16; i++) {
+               if (GET_PTR_TYPE(tree->a[i]) != PTR_TYPE_NULL) {
+                       if (p) /* more than one entry */
+                               return -2;
+                       p = tree->a[i];
+               }
+       }
+
+       /* replace tree with p in parent[index] */
+       parent->a[index] = p;
+       free(tree);
+       return 0;
+}
+
+/*
+ * To remove a leaf_node:
+ * Search to the tree location appropriate for the given leaf_node's key:
+ * - If location does not hold a matching entry, abort and do nothing.
+ * - Copy the matching entry's value into the given entry.
+ * - Replace the matching leaf_node with a NULL entry (and free the leaf_node).
+ * - Consolidate int_nodes repeatedly, while walking up the tree towards root.
+ */
+static void note_tree_remove(struct notes_tree *t,
+               struct int_node *tree, unsigned char n,
+               struct leaf_node *entry)
+{
+       struct leaf_node *l;
+       struct int_node *parent_stack[20];
+       unsigned char i, j;
+       void **p = note_tree_search(t, &tree, &n, entry->key_sha1);
+
+       assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */
+       if (GET_PTR_TYPE(*p) != PTR_TYPE_NOTE)
+               return; /* type mismatch, nothing to remove */
+       l = (struct leaf_node *) CLR_PTR_TYPE(*p);
+       if (hashcmp(l->key_sha1, entry->key_sha1))
+               return; /* key mismatch, nothing to remove */
+
+       /* we have found a matching entry */
+       hashcpy(entry->val_sha1, l->val_sha1);
+       free(l);
+       *p = SET_PTR_TYPE(NULL, PTR_TYPE_NULL);
+
+       /* consolidate this tree level, and parent levels, if possible */
+       if (!n)
+               return; /* cannot consolidate top level */
+       /* first, build stack of ancestors between root and current node */
+       parent_stack[0] = t->root;
+       for (i = 0; i < n; i++) {
+               j = GET_NIBBLE(i, entry->key_sha1);
+               parent_stack[i + 1] = CLR_PTR_TYPE(parent_stack[i]->a[j]);
+       }
+       assert(i == n && parent_stack[i] == tree);
+       /* next, unwind stack until note_tree_consolidate() is done */
+       while (i > 0 &&
+              !note_tree_consolidate(parent_stack[i], parent_stack[i - 1],
+                                     GET_NIBBLE(i - 1, entry->key_sha1)))
+               i--;
+}
+
+/* Free the entire notes data contained in the given tree */
+static void note_tree_free(struct int_node *tree)
+{
+       unsigned int i;
+       for (i = 0; i < 16; i++) {
+               void *p = tree->a[i];
+               switch (GET_PTR_TYPE(p)) {
+               case PTR_TYPE_INTERNAL:
+                       note_tree_free(CLR_PTR_TYPE(p));
+                       /* fall through */
+               case PTR_TYPE_NOTE:
+               case PTR_TYPE_SUBTREE:
+                       free(CLR_PTR_TYPE(p));
+               }
+       }
+}
+
+/*
+ * Convert a partial SHA1 hex string to the corresponding partial SHA1 value.
+ * - hex      - Partial SHA1 segment in ASCII hex format
+ * - hex_len  - Length of above segment. Must be multiple of 2 between 0 and 40
+ * - sha1     - Partial SHA1 value is written here
+ * - sha1_len - Max #bytes to store in sha1, Must be >= hex_len / 2, and < 20
+ * Returns -1 on error (invalid arguments or invalid SHA1 (not in hex format)).
+ * Otherwise, returns number of bytes written to sha1 (i.e. hex_len / 2).
+ * Pads sha1 with NULs up to sha1_len (not included in returned length).
+ */
+static int get_sha1_hex_segment(const char *hex, unsigned int hex_len,
+               unsigned char *sha1, unsigned int sha1_len)
+{
+       unsigned int i, len = hex_len >> 1;
+       if (hex_len % 2 != 0 || len > sha1_len)
+               return -1;
+       for (i = 0; i < len; i++) {
+               unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]);
+               if (val & ~0xff)
+                       return -1;
+               *sha1++ = val;
+               hex += 2;
+       }
+       for (; i < sha1_len; i++)
+               *sha1++ = 0;
+       return len;
+}
+
+static int non_note_cmp(const struct non_note *a, const struct non_note *b)
+{
+       return strcmp(a->path, b->path);
+}
+
+static void add_non_note(struct notes_tree *t, const char *path,
+               unsigned int mode, const unsigned char *sha1)
+{
+       struct non_note *p = t->prev_non_note, *n;
+       n = (struct non_note *) xmalloc(sizeof(struct non_note));
+       n->next = NULL;
+       n->path = xstrdup(path);
+       n->mode = mode;
+       hashcpy(n->sha1, sha1);
+       t->prev_non_note = n;
+
+       if (!t->first_non_note) {
+               t->first_non_note = n;
+               return;
+       }
+
+       if (non_note_cmp(p, n) < 0)
+               ; /* do nothing  */
+       else if (non_note_cmp(t->first_non_note, n) <= 0)
+               p = t->first_non_note;
+       else {
+               /* n sorts before t->first_non_note */
+               n->next = t->first_non_note;
+               t->first_non_note = n;
+               return;
+       }
+
+       /* n sorts equal or after p */
+       while (p->next && non_note_cmp(p->next, n) <= 0)
+               p = p->next;
+
+       if (non_note_cmp(p, n) == 0) { /* n ~= p; overwrite p with n */
+               assert(strcmp(p->path, n->path) == 0);
+               p->mode = n->mode;
+               hashcpy(p->sha1, n->sha1);
+               free(n);
+               t->prev_non_note = p;
+               return;
+       }
+
+       /* n sorts between p and p->next */
+       n->next = p->next;
+       p->next = n;
+}
+
+static void load_subtree(struct notes_tree *t, struct leaf_node *subtree,
+               struct int_node *node, unsigned int n)
+{
+       unsigned char object_sha1[20];
+       unsigned int prefix_len;
+       void *buf;
+       struct tree_desc desc;
+       struct name_entry entry;
+       int len, path_len;
+       unsigned char type;
+       struct leaf_node *l;
+
+       buf = fill_tree_descriptor(&desc, subtree->val_sha1);
+       if (!buf)
+               die("Could not read %s for notes-index",
+                    sha1_to_hex(subtree->val_sha1));
+
+       prefix_len = subtree->key_sha1[19];
+       assert(prefix_len * 2 >= n);
+       memcpy(object_sha1, subtree->key_sha1, prefix_len);
+       while (tree_entry(&desc, &entry)) {
+               path_len = strlen(entry.path);
+               len = get_sha1_hex_segment(entry.path, path_len,
+                               object_sha1 + prefix_len, 20 - prefix_len);
+               if (len < 0)
+                       goto handle_non_note; /* entry.path is not a SHA1 */
+               len += prefix_len;
+
+               /*
+                * If object SHA1 is complete (len == 20), assume note object
+                * If object SHA1 is incomplete (len < 20), and current
+                * component consists of 2 hex chars, assume note subtree
+                */
+               if (len <= 20) {
+                       type = PTR_TYPE_NOTE;
+                       l = (struct leaf_node *)
+                               xcalloc(sizeof(struct leaf_node), 1);
+                       hashcpy(l->key_sha1, object_sha1);
+                       hashcpy(l->val_sha1, entry.sha1);
+                       if (len < 20) {
+                               if (!S_ISDIR(entry.mode) || path_len != 2)
+                                       goto handle_non_note; /* not subtree */
+                               l->key_sha1[19] = (unsigned char) len;
+                               type = PTR_TYPE_SUBTREE;
+                       }
+                       note_tree_insert(t, node, n, l, type,
+                                        combine_notes_concatenate);
+               }
+               continue;
+
+handle_non_note:
+               /*
+                * Determine full path for this non-note entry:
+                * The filename is already found in entry.path, but the
+                * directory part of the path must be deduced from the subtree
+                * containing this entry. We assume here that the overall notes
+                * tree follows a strict byte-based progressive fanout
+                * structure (i.e. using 2/38, 2/2/36, etc. fanouts, and not
+                * e.g. 4/36 fanout). This means that if a non-note is found at
+                * path "dead/beef", the following code will register it as
+                * being found on "de/ad/beef".
+                * On the other hand, if you use such non-obvious non-note
+                * paths in the middle of a notes tree, you deserve what's
+                * coming to you ;). Note that for non-notes that are not
+                * SHA1-like at the top level, there will be no problems.
+                *
+                * To conclude, it is strongly advised to make sure non-notes
+                * have at least one non-hex character in the top-level path
+                * component.
+                */
+               {
+                       char non_note_path[PATH_MAX];
+                       char *p = non_note_path;
+                       const char *q = sha1_to_hex(subtree->key_sha1);
+                       int i;
+                       for (i = 0; i < prefix_len; i++) {
+                               *p++ = *q++;
+                               *p++ = *q++;
+                               *p++ = '/';
+                       }
+                       strcpy(p, entry.path);
+                       add_non_note(t, non_note_path, entry.mode, entry.sha1);
+               }
+       }
+       free(buf);
+}
+
+/*
+ * Determine optimal on-disk fanout for this part of the notes tree
+ *
+ * Given a (sub)tree and the level in the internal tree structure, determine
+ * whether or not the given existing fanout should be expanded for this
+ * (sub)tree.
+ *
+ * Values of the 'fanout' variable:
+ * - 0: No fanout (all notes are stored directly in the root notes tree)
+ * - 1: 2/38 fanout
+ * - 2: 2/2/36 fanout
+ * - 3: 2/2/2/34 fanout
+ * etc.
+ */
+static unsigned char determine_fanout(struct int_node *tree, unsigned char n,
+               unsigned char fanout)
+{
+       /*
+        * The following is a simple heuristic that works well in practice:
+        * For each even-numbered 16-tree level (remember that each on-disk
+        * fanout level corresponds to _two_ 16-tree levels), peek at all 16
+        * entries at that tree level. If all of them are either int_nodes or
+        * subtree entries, then there are likely plenty of notes below this
+        * level, so we return an incremented fanout.
+        */
+       unsigned int i;
+       if ((n % 2) || (n > 2 * fanout))
+               return fanout;
+       for (i = 0; i < 16; i++) {
+               switch (GET_PTR_TYPE(tree->a[i])) {
+               case PTR_TYPE_SUBTREE:
+               case PTR_TYPE_INTERNAL:
+                       continue;
+               default:
+                       return fanout;
+               }
+       }
+       return fanout + 1;
+}
+
+static void construct_path_with_fanout(const unsigned char *sha1,
+               unsigned char fanout, char *path)
+{
+       unsigned int i = 0, j = 0;
+       const char *hex_sha1 = sha1_to_hex(sha1);
+       assert(fanout < 20);
+       while (fanout) {
+               path[i++] = hex_sha1[j++];
+               path[i++] = hex_sha1[j++];
+               path[i++] = '/';
+               fanout--;
+       }
+       strcpy(path + i, hex_sha1 + j);
+}
+
+static int for_each_note_helper(struct notes_tree *t, struct int_node *tree,
+               unsigned char n, unsigned char fanout, int flags,
+               each_note_fn fn, void *cb_data)
+{
+       unsigned int i;
+       void *p;
+       int ret = 0;
+       struct leaf_node *l;
+       static char path[40 + 19 + 1];  /* hex SHA1 + 19 * '/' + NUL */
+
+       fanout = determine_fanout(tree, n, fanout);
+       for (i = 0; i < 16; i++) {
+redo:
+               p = tree->a[i];
+               switch (GET_PTR_TYPE(p)) {
+               case PTR_TYPE_INTERNAL:
+                       /* recurse into int_node */
+                       ret = for_each_note_helper(t, CLR_PTR_TYPE(p), n + 1,
+                               fanout, flags, fn, cb_data);
+                       break;
+               case PTR_TYPE_SUBTREE:
+                       l = (struct leaf_node *) CLR_PTR_TYPE(p);
+                       /*
+                        * Subtree entries in the note tree represent parts of
+                        * the note tree that have not yet been explored. There
+                        * is a direct relationship between subtree entries at
+                        * level 'n' in the tree, and the 'fanout' variable:
+                        * Subtree entries at level 'n <= 2 * fanout' should be
+                        * preserved, since they correspond exactly to a fanout
+                        * directory in the on-disk structure. However, subtree
+                        * entries at level 'n > 2 * fanout' should NOT be
+                        * preserved, but rather consolidated into the above
+                        * notes tree level. We achieve this by unconditionally
+                        * unpacking subtree entries that exist below the
+                        * threshold level at 'n = 2 * fanout'.
+                        */
+                       if (n <= 2 * fanout &&
+                           flags & FOR_EACH_NOTE_YIELD_SUBTREES) {
+                               /* invoke callback with subtree */
+                               unsigned int path_len =
+                                       l->key_sha1[19] * 2 + fanout;
+                               assert(path_len < 40 + 19);
+                               construct_path_with_fanout(l->key_sha1, fanout,
+                                                          path);
+                               /* Create trailing slash, if needed */
+                               if (path[path_len - 1] != '/')
+                                       path[path_len++] = '/';
+                               path[path_len] = '\0';
+                               ret = fn(l->key_sha1, l->val_sha1, path,
+                                        cb_data);
+                       }
+                       if (n > fanout * 2 ||
+                           !(flags & FOR_EACH_NOTE_DONT_UNPACK_SUBTREES)) {
+                               /* unpack subtree and resume traversal */
+                               tree->a[i] = NULL;
+                               load_subtree(t, l, tree, n);
+                               free(l);
+                               goto redo;
+                       }
+                       break;
+               case PTR_TYPE_NOTE:
+                       l = (struct leaf_node *) CLR_PTR_TYPE(p);
+                       construct_path_with_fanout(l->key_sha1, fanout, path);
+                       ret = fn(l->key_sha1, l->val_sha1, path, cb_data);
+                       break;
+               }
+               if (ret)
+                       return ret;
+       }
+       return 0;
+}
+
+struct tree_write_stack {
+       struct tree_write_stack *next;
+       struct strbuf buf;
+       char path[2]; /* path to subtree in next, if any */
+};
+
+static inline int matches_tree_write_stack(struct tree_write_stack *tws,
+               const char *full_path)
+{
+       return  full_path[0] == tws->path[0] &&
+               full_path[1] == tws->path[1] &&
+               full_path[2] == '/';
+}
+
+static void write_tree_entry(struct strbuf *buf, unsigned int mode,
+               const char *path, unsigned int path_len, const
+               unsigned char *sha1)
+{
+       strbuf_addf(buf, "%o %.*s%c", mode, path_len, path, '\0');
+       strbuf_add(buf, sha1, 20);
+}
+
+static void tree_write_stack_init_subtree(struct tree_write_stack *tws,
+               const char *path)
+{
+       struct tree_write_stack *n;
+       assert(!tws->next);
+       assert(tws->path[0] == '\0' && tws->path[1] == '\0');
+       n = (struct tree_write_stack *)
+               xmalloc(sizeof(struct tree_write_stack));
+       n->next = NULL;
+       strbuf_init(&n->buf, 256 * (32 + 40)); /* assume 256 entries per tree */
+       n->path[0] = n->path[1] = '\0';
+       tws->next = n;
+       tws->path[0] = path[0];
+       tws->path[1] = path[1];
+}
+
+static int tree_write_stack_finish_subtree(struct tree_write_stack *tws)
+{
+       int ret;
+       struct tree_write_stack *n = tws->next;
+       unsigned char s[20];
+       if (n) {
+               ret = tree_write_stack_finish_subtree(n);
+               if (ret)
+                       return ret;
+               ret = write_sha1_file(n->buf.buf, n->buf.len, tree_type, s);
+               if (ret)
+                       return ret;
+               strbuf_release(&n->buf);
+               free(n);
+               tws->next = NULL;
+               write_tree_entry(&tws->buf, 040000, tws->path, 2, s);
+               tws->path[0] = tws->path[1] = '\0';
+       }
+       return 0;
+}
+
+static int write_each_note_helper(struct tree_write_stack *tws,
+               const char *path, unsigned int mode,
+               const unsigned char *sha1)
+{
+       size_t path_len = strlen(path);
+       unsigned int n = 0;
+       int ret;
+
+       /* Determine common part of tree write stack */
+       while (tws && 3 * n < path_len &&
+              matches_tree_write_stack(tws, path + 3 * n)) {
+               n++;
+               tws = tws->next;
+       }
+
+       /* tws point to last matching tree_write_stack entry */
+       ret = tree_write_stack_finish_subtree(tws);
+       if (ret)
+               return ret;
+
+       /* Start subtrees needed to satisfy path */
+       while (3 * n + 2 < path_len && path[3 * n + 2] == '/') {
+               tree_write_stack_init_subtree(tws, path + 3 * n);
+               n++;
+               tws = tws->next;
+       }
+
+       /* There should be no more directory components in the given path */
+       assert(memchr(path + 3 * n, '/', path_len - (3 * n)) == NULL);
+
+       /* Finally add given entry to the current tree object */
+       write_tree_entry(&tws->buf, mode, path + 3 * n, path_len - (3 * n),
+                        sha1);
+
+       return 0;
+}
+
+struct write_each_note_data {
+       struct tree_write_stack *root;
+       struct non_note *next_non_note;
+};
+
+static int write_each_non_note_until(const char *note_path,
+               struct write_each_note_data *d)
+{
+       struct non_note *n = d->next_non_note;
+       int cmp = 0, ret;
+       while (n && (!note_path || (cmp = strcmp(n->path, note_path)) <= 0)) {
+               if (note_path && cmp == 0)
+                       ; /* do nothing, prefer note to non-note */
+               else {
+                       ret = write_each_note_helper(d->root, n->path, n->mode,
+                                                    n->sha1);
+                       if (ret)
+                               return ret;
+               }
+               n = n->next;
+       }
+       d->next_non_note = n;
+       return 0;
+}
+
+static int write_each_note(const unsigned char *object_sha1,
+               const unsigned char *note_sha1, char *note_path,
+               void *cb_data)
+{
+       struct write_each_note_data *d =
+               (struct write_each_note_data *) cb_data;
+       size_t note_path_len = strlen(note_path);
+       unsigned int mode = 0100644;
+
+       if (note_path[note_path_len - 1] == '/') {
+               /* subtree entry */
+               note_path_len--;
+               note_path[note_path_len] = '\0';
+               mode = 040000;
+       }
+       assert(note_path_len <= 40 + 19);
+
+       /* Weave non-note entries into note entries */
+       return  write_each_non_note_until(note_path, d) ||
+               write_each_note_helper(d->root, note_path, mode, note_sha1);
+}
+
+struct note_delete_list {
+       struct note_delete_list *next;
+       const unsigned char *sha1;
+};
+
+static int prune_notes_helper(const unsigned char *object_sha1,
+               const unsigned char *note_sha1, char *note_path,
+               void *cb_data)
+{
+       struct note_delete_list **l = (struct note_delete_list **) cb_data;
+       struct note_delete_list *n;
+
+       if (has_sha1_file(object_sha1))
+               return 0; /* nothing to do for this note */
+
+       /* failed to find object => prune this note */
+       n = (struct note_delete_list *) xmalloc(sizeof(*n));
+       n->next = *l;
+       n->sha1 = object_sha1;
+       *l = n;
+       return 0;
+}
+
+int combine_notes_concatenate(unsigned char *cur_sha1,
+               const unsigned char *new_sha1)
+{
+       char *cur_msg = NULL, *new_msg = NULL, *buf;
+       unsigned long cur_len, new_len, buf_len;
+       enum object_type cur_type, new_type;
+       int ret;
+
+       /* read in both note blob objects */
+       if (!is_null_sha1(new_sha1))
+               new_msg = read_sha1_file(new_sha1, &new_type, &new_len);
+       if (!new_msg || !new_len || new_type != OBJ_BLOB) {
+               free(new_msg);
+               return 0;
+       }
+       if (!is_null_sha1(cur_sha1))
+               cur_msg = read_sha1_file(cur_sha1, &cur_type, &cur_len);
+       if (!cur_msg || !cur_len || cur_type != OBJ_BLOB) {
+               free(cur_msg);
+               free(new_msg);
+               hashcpy(cur_sha1, new_sha1);
+               return 0;
+       }
+
+       /* we will separate the notes by a newline anyway */
+       if (cur_msg[cur_len - 1] == '\n')
+               cur_len--;
+
+       /* concatenate cur_msg and new_msg into buf */
+       buf_len = cur_len + 1 + new_len;
+       buf = (char *) xmalloc(buf_len);
+       memcpy(buf, cur_msg, cur_len);
+       buf[cur_len] = '\n';
+       memcpy(buf + cur_len + 1, new_msg, new_len);
+       free(cur_msg);
+       free(new_msg);
+
+       /* create a new blob object from buf */
+       ret = write_sha1_file(buf, buf_len, blob_type, cur_sha1);
+       free(buf);
+       return ret;
+}
+
+int combine_notes_overwrite(unsigned char *cur_sha1,
+               const unsigned char *new_sha1)
+{
+       hashcpy(cur_sha1, new_sha1);
+       return 0;
+}
+
+int combine_notes_ignore(unsigned char *cur_sha1,
+               const unsigned char *new_sha1)
+{
+       return 0;
+}
+
+static int string_list_add_one_ref(const char *path, const unsigned char *sha1,
+                                  int flag, void *cb)
+{
+       struct string_list *refs = cb;
+       if (!unsorted_string_list_has_string(refs, path))
+               string_list_append(refs, path);
+       return 0;
+}
+
+void string_list_add_refs_by_glob(struct string_list *list, const char *glob)
+{
+       if (has_glob_specials(glob)) {
+               for_each_glob_ref(string_list_add_one_ref, glob, list);
+       } else {
+               unsigned char sha1[20];
+               if (get_sha1(glob, sha1))
+                       warning("notes ref %s is invalid", glob);
+               if (!unsorted_string_list_has_string(list, glob))
+                       string_list_append(list, glob);
+       }
+}
+
+void string_list_add_refs_from_colon_sep(struct string_list *list,
+                                        const char *globs)
+{
+       struct strbuf globbuf = STRBUF_INIT;
+       struct strbuf **split;
+       int i;
+
+       strbuf_addstr(&globbuf, globs);
+       split = strbuf_split(&globbuf, ':');
+
+       for (i = 0; split[i]; i++) {
+               if (!split[i]->len)
+                       continue;
+               if (split[i]->buf[split[i]->len-1] == ':')
+                       strbuf_setlen(split[i], split[i]->len-1);
+               string_list_add_refs_by_glob(list, split[i]->buf);
+       }
+
+       strbuf_list_free(split);
+       strbuf_release(&globbuf);
+}
+
+static int notes_display_config(const char *k, const char *v, void *cb)
+{
+       int *load_refs = cb;
+
+       if (*load_refs && !strcmp(k, "notes.displayref")) {
+               if (!v)
+                       config_error_nonbool(k);
+               string_list_add_refs_by_glob(&display_notes_refs, v);
+       }
+
+       return 0;
+}
+
+static const char *default_notes_ref(void)
+{
+       const char *notes_ref = NULL;
+       if (!notes_ref)
+               notes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT);
+       if (!notes_ref)
+               notes_ref = notes_ref_name; /* value of core.notesRef config */
+       if (!notes_ref)
+               notes_ref = GIT_NOTES_DEFAULT_REF;
+       return notes_ref;
+}
+
+void init_notes(struct notes_tree *t, const char *notes_ref,
+               combine_notes_fn combine_notes, int flags)
+{
+       unsigned char sha1[20], object_sha1[20];
+       unsigned mode;
+       struct leaf_node root_tree;
+
+       if (!t)
+               t = &default_notes_tree;
+       assert(!t->initialized);
+
+       if (!notes_ref)
+               notes_ref = default_notes_ref();
+
+       if (!combine_notes)
+               combine_notes = combine_notes_concatenate;
+
+       t->root = (struct int_node *) xcalloc(sizeof(struct int_node), 1);
+       t->first_non_note = NULL;
+       t->prev_non_note = NULL;
+       t->ref = notes_ref ? xstrdup(notes_ref) : NULL;
+       t->combine_notes = combine_notes;
+       t->initialized = 1;
+       t->dirty = 0;
+
+       if (flags & NOTES_INIT_EMPTY || !notes_ref ||
+           read_ref(notes_ref, object_sha1))
+               return;
+       if (get_tree_entry(object_sha1, "", sha1, &mode))
+               die("Failed to read notes tree referenced by %s (%s)",
+                   notes_ref, object_sha1);
+
+       hashclr(root_tree.key_sha1);
+       hashcpy(root_tree.val_sha1, sha1);
+       load_subtree(t, &root_tree, t->root, 0);
+}
+
+struct notes_tree **load_notes_trees(struct string_list *refs)
+{
+       struct string_list_item *item;
+       int counter = 0;
+       struct notes_tree **trees;
+       trees = xmalloc((refs->nr+1) * sizeof(struct notes_tree *));
+       for_each_string_list_item(item, refs) {
+               struct notes_tree *t = xcalloc(1, sizeof(struct notes_tree));
+               init_notes(t, item->string, combine_notes_ignore, 0);
+               trees[counter++] = t;
+       }
+       trees[counter] = NULL;
+       return trees;
+}
+
+void init_display_notes(struct display_notes_opt *opt)
+{
+       char *display_ref_env;
+       int load_config_refs = 0;
+       display_notes_refs.strdup_strings = 1;
+
+       assert(!display_notes_trees);
+
+       if (!opt || !opt->suppress_default_notes) {
+               string_list_append(&display_notes_refs, default_notes_ref());
+               display_ref_env = getenv(GIT_NOTES_DISPLAY_REF_ENVIRONMENT);
+               if (display_ref_env) {
+                       string_list_add_refs_from_colon_sep(&display_notes_refs,
+                                                           display_ref_env);
+                       load_config_refs = 0;
+               } else
+                       load_config_refs = 1;
+       }
+
+       git_config(notes_display_config, &load_config_refs);
+
+       if (opt && opt->extra_notes_refs) {
+               struct string_list_item *item;
+               for_each_string_list_item(item, opt->extra_notes_refs)
+                       string_list_add_refs_by_glob(&display_notes_refs,
+                                                    item->string);
+       }
+
+       display_notes_trees = load_notes_trees(&display_notes_refs);
+       string_list_clear(&display_notes_refs, 0);
+}
+
+void add_note(struct notes_tree *t, const unsigned char *object_sha1,
+               const unsigned char *note_sha1, combine_notes_fn combine_notes)
+{
+       struct leaf_node *l;
+
+       if (!t)
+               t = &default_notes_tree;
+       assert(t->initialized);
+       t->dirty = 1;
+       if (!combine_notes)
+               combine_notes = t->combine_notes;
+       l = (struct leaf_node *) xmalloc(sizeof(struct leaf_node));
+       hashcpy(l->key_sha1, object_sha1);
+       hashcpy(l->val_sha1, note_sha1);
+       note_tree_insert(t, t->root, 0, l, PTR_TYPE_NOTE, combine_notes);
+}
+
+int remove_note(struct notes_tree *t, const unsigned char *object_sha1)
+{
+       struct leaf_node l;
+
+       if (!t)
+               t = &default_notes_tree;
+       assert(t->initialized);
+       hashcpy(l.key_sha1, object_sha1);
+       hashclr(l.val_sha1);
+       note_tree_remove(t, t->root, 0, &l);
+       if (is_null_sha1(l.val_sha1)) // no note was removed
+               return 1;
+       t->dirty = 1;
+       return 0;
+}
+
+const unsigned char *get_note(struct notes_tree *t,
+               const unsigned char *object_sha1)
+{
+       struct leaf_node *found;
+
+       if (!t)
+               t = &default_notes_tree;
+       assert(t->initialized);
+       found = note_tree_find(t, t->root, 0, object_sha1);
+       return found ? found->val_sha1 : NULL;
+}
+
+int for_each_note(struct notes_tree *t, int flags, each_note_fn fn,
+               void *cb_data)
+{
+       if (!t)
+               t = &default_notes_tree;
+       assert(t->initialized);
+       return for_each_note_helper(t, t->root, 0, 0, flags, fn, cb_data);
+}
+
+int write_notes_tree(struct notes_tree *t, unsigned char *result)
+{
+       struct tree_write_stack root;
+       struct write_each_note_data cb_data;
+       int ret;
+
+       if (!t)
+               t = &default_notes_tree;
+       assert(t->initialized);
+
+       /* Prepare for traversal of current notes tree */
+       root.next = NULL; /* last forward entry in list is grounded */
+       strbuf_init(&root.buf, 256 * (32 + 40)); /* assume 256 entries */
+       root.path[0] = root.path[1] = '\0';
+       cb_data.root = &root;
+       cb_data.next_non_note = t->first_non_note;
+
+       /* Write tree objects representing current notes tree */
+       ret = for_each_note(t, FOR_EACH_NOTE_DONT_UNPACK_SUBTREES |
+                               FOR_EACH_NOTE_YIELD_SUBTREES,
+                       write_each_note, &cb_data) ||
+               write_each_non_note_until(NULL, &cb_data) ||
+               tree_write_stack_finish_subtree(&root) ||
+               write_sha1_file(root.buf.buf, root.buf.len, tree_type, result);
+       strbuf_release(&root.buf);
+       return ret;
+}
+
+void prune_notes(struct notes_tree *t, int flags)
+{
+       struct note_delete_list *l = NULL;
+
+       if (!t)
+               t = &default_notes_tree;
+       assert(t->initialized);
+
+       for_each_note(t, 0, prune_notes_helper, &l);
+
+       while (l) {
+               if (flags & NOTES_PRUNE_VERBOSE)
+                       printf("%s\n", sha1_to_hex(l->sha1));
+               if (!(flags & NOTES_PRUNE_DRYRUN))
+                       remove_note(t, l->sha1);
+               l = l->next;
+       }
+}
+
+void free_notes(struct notes_tree *t)
+{
+       if (!t)
+               t = &default_notes_tree;
+       if (t->root)
+               note_tree_free(t->root);
+       free(t->root);
+       while (t->first_non_note) {
+               t->prev_non_note = t->first_non_note->next;
+               free(t->first_non_note->path);
+               free(t->first_non_note);
+               t->first_non_note = t->prev_non_note;
+       }
+       free(t->ref);
+       memset(t, 0, sizeof(struct notes_tree));
+}
+
+void format_note(struct notes_tree *t, const unsigned char *object_sha1,
+               struct strbuf *sb, const char *output_encoding, int flags)
+{
+       static const char utf8[] = "utf-8";
+       const unsigned char *sha1;
+       char *msg, *msg_p;
+       unsigned long linelen, msglen;
+       enum object_type type;
+
+       if (!t)
+               t = &default_notes_tree;
+       if (!t->initialized)
+               init_notes(t, NULL, NULL, 0);
+
+       sha1 = get_note(t, object_sha1);
+       if (!sha1)
+               return;
+
+       if (!(msg = read_sha1_file(sha1, &type, &msglen)) || !msglen ||
+                       type != OBJ_BLOB) {
+               free(msg);
+               return;
+       }
+
+       if (output_encoding && *output_encoding &&
+                       strcmp(utf8, output_encoding)) {
+               char *reencoded = reencode_string(msg, output_encoding, utf8);
+               if (reencoded) {
+                       free(msg);
+                       msg = reencoded;
+                       msglen = strlen(msg);
+               }
+       }
+
+       /* we will end the annotation by a newline anyway */
+       if (msglen && msg[msglen - 1] == '\n')
+               msglen--;
+
+       if (flags & NOTES_SHOW_HEADER) {
+               const char *ref = t->ref;
+               if (!ref || !strcmp(ref, GIT_NOTES_DEFAULT_REF)) {
+                       strbuf_addstr(sb, "\nNotes:\n");
+               } else {
+                       if (!prefixcmp(ref, "refs/"))
+                               ref += 5;
+                       if (!prefixcmp(ref, "notes/"))
+                               ref += 6;
+                       strbuf_addf(sb, "\nNotes (%s):\n", ref);
+               }
+       }
+
+       for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) {
+               linelen = strchrnul(msg_p, '\n') - msg_p;
+
+               if (flags & NOTES_INDENT)
+                       strbuf_addstr(sb, "    ");
+               strbuf_add(sb, msg_p, linelen);
+               strbuf_addch(sb, '\n');
+       }
+
+       free(msg);
+}
+
+void format_display_notes(const unsigned char *object_sha1,
+                         struct strbuf *sb, const char *output_encoding, int flags)
+{
+       int i;
+       assert(display_notes_trees);
+       for (i = 0; display_notes_trees[i]; i++)
+               format_note(display_notes_trees[i], object_sha1, sb,
+                           output_encoding, flags);
+}
+
+int copy_note(struct notes_tree *t,
+             const unsigned char *from_obj, const unsigned char *to_obj,
+             int force, combine_notes_fn combine_fn)
+{
+       const unsigned char *note = get_note(t, from_obj);
+       const unsigned char *existing_note = get_note(t, to_obj);
+
+       if (!force && existing_note)
+               return 1;
+
+       if (note)
+               add_note(t, to_obj, note, combine_fn);
+       else if (existing_note)
+               add_note(t, to_obj, null_sha1, combine_fn);
+
+       return 0;
+}
diff --git a/notes.h b/notes.h
new file mode 100644 (file)
index 0000000..5106761
--- /dev/null
+++ b/notes.h
@@ -0,0 +1,271 @@
+#ifndef NOTES_H
+#define NOTES_H
+
+/*
+ * Function type for combining two notes annotating the same object.
+ *
+ * When adding a new note annotating the same object as an existing note, it is
+ * up to the caller to decide how to combine the two notes. The decision is
+ * made by passing in a function of the following form. The function accepts
+ * two SHA1s -- of the existing note and the new note, respectively. The
+ * function then combines the notes in whatever way it sees fit, and writes the
+ * resulting SHA1 into the first SHA1 argument (cur_sha1). A non-zero return
+ * value indicates failure.
+ *
+ * The two given SHA1s must both be non-NULL and different from each other.
+ *
+ * The default combine_notes function (you get this when passing NULL) is
+ * combine_notes_concatenate(), which appends the contents of the new note to
+ * the contents of the existing note.
+ */
+typedef int (*combine_notes_fn)(unsigned char *cur_sha1, const unsigned char *new_sha1);
+
+/* Common notes combinators */
+int combine_notes_concatenate(unsigned char *cur_sha1, const unsigned char *new_sha1);
+int combine_notes_overwrite(unsigned char *cur_sha1, const unsigned char *new_sha1);
+int combine_notes_ignore(unsigned char *cur_sha1, const unsigned char *new_sha1);
+
+/*
+ * Notes tree object
+ *
+ * Encapsulates the internal notes tree structure associated with a notes ref.
+ * Whenever a struct notes_tree pointer is required below, you may pass NULL in
+ * order to use the default/internal notes tree. E.g. you only need to pass a
+ * non-NULL value if you need to refer to several different notes trees
+ * simultaneously.
+ */
+extern struct notes_tree {
+       struct int_node *root;
+       struct non_note *first_non_note, *prev_non_note;
+       char *ref;
+       combine_notes_fn combine_notes;
+       int initialized;
+       int dirty;
+} default_notes_tree;
+
+/*
+ * Flags controlling behaviour of notes tree initialization
+ *
+ * Default behaviour is to initialize the notes tree from the tree object
+ * specified by the given (or default) notes ref.
+ */
+#define NOTES_INIT_EMPTY 1
+
+/*
+ * Initialize the given notes_tree with the notes tree structure at the given
+ * ref. If given ref is NULL, the value of the $GIT_NOTES_REF environment
+ * variable is used, and if that is missing, the default notes ref is used
+ * ("refs/notes/commits").
+ *
+ * If you need to re-intialize a notes_tree structure (e.g. when switching from
+ * one notes ref to another), you must first de-initialize the notes_tree
+ * structure by calling free_notes(struct notes_tree *).
+ *
+ * If you pass t == NULL, the default internal notes_tree will be initialized.
+ *
+ * The combine_notes function that is passed becomes the default combine_notes
+ * function for the given notes_tree. If NULL is passed, the default
+ * combine_notes function is combine_notes_concatenate().
+ *
+ * Precondition: The notes_tree structure is zeroed (this can be achieved with
+ * memset(t, 0, sizeof(struct notes_tree)))
+ */
+void init_notes(struct notes_tree *t, const char *notes_ref,
+               combine_notes_fn combine_notes, int flags);
+
+/*
+ * Add the given note object to the given notes_tree structure
+ *
+ * IMPORTANT: The changes made by add_note() to the given notes_tree structure
+ * are not persistent until a subsequent call to write_notes_tree() returns
+ * zero.
+ */
+void add_note(struct notes_tree *t, const unsigned char *object_sha1,
+               const unsigned char *note_sha1, combine_notes_fn combine_notes);
+
+/*
+ * Remove the given note object from the given notes_tree structure
+ *
+ * IMPORTANT: The changes made by remove_note() to the given notes_tree
+ * structure are not persistent until a subsequent call to write_notes_tree()
+ * returns zero.
+ *
+ * Return 0 if a note was removed; 1 if there was no note to remove.
+ */
+int remove_note(struct notes_tree *t, const unsigned char *object_sha1);
+
+/*
+ * Get the note object SHA1 containing the note data for the given object
+ *
+ * Return NULL if the given object has no notes.
+ */
+const unsigned char *get_note(struct notes_tree *t,
+               const unsigned char *object_sha1);
+
+/*
+ * Copy a note from one object to another in the given notes_tree.
+ *
+ * Fails if the to_obj already has a note unless 'force' is true.
+ */
+int copy_note(struct notes_tree *t,
+             const unsigned char *from_obj, const unsigned char *to_obj,
+             int force, combine_notes_fn combine_fn);
+
+/*
+ * Flags controlling behaviour of for_each_note()
+ *
+ * Default behaviour of for_each_note() is to traverse every single note object
+ * in the given notes tree, unpacking subtree entries along the way.
+ * The following flags can be used to alter the default behaviour:
+ *
+ * - DONT_UNPACK_SUBTREES causes for_each_note() NOT to unpack and recurse into
+ *   subtree entries while traversing the notes tree. This causes notes within
+ *   those subtrees NOT to be passed to the callback. Use this flag if you
+ *   don't want to traverse _all_ notes, but only want to traverse the parts
+ *   of the notes tree that have already been unpacked (this includes at least
+ *   all notes that have been added/changed).
+ *
+ * - YIELD_SUBTREES causes any subtree entries that are encountered to be
+ *   passed to the callback, before recursing into them. Subtree entries are
+ *   not note objects, but represent intermediate directories in the notes
+ *   tree. When passed to the callback, subtree entries will have a trailing
+ *   slash in their path, which the callback may use to differentiate between
+ *   note entries and subtree entries. Note that already-unpacked subtree
+ *   entries are not part of the notes tree, and will therefore not be yielded.
+ *   If this flag is used together with DONT_UNPACK_SUBTREES, for_each_note()
+ *   will yield the subtree entry, but not recurse into it.
+ */
+#define FOR_EACH_NOTE_DONT_UNPACK_SUBTREES 1
+#define FOR_EACH_NOTE_YIELD_SUBTREES 2
+
+/*
+ * Invoke the specified callback function for each note in the given notes_tree
+ *
+ * If the callback returns nonzero, the note walk is aborted, and the return
+ * value from the callback is returned from for_each_note(). Hence, a zero
+ * return value from for_each_note() indicates that all notes were walked
+ * successfully.
+ *
+ * IMPORTANT: The callback function is NOT allowed to change the notes tree.
+ * In other words, the following functions can NOT be invoked (on the current
+ * notes tree) from within the callback:
+ * - add_note()
+ * - remove_note()
+ * - free_notes()
+ */
+typedef int each_note_fn(const unsigned char *object_sha1,
+               const unsigned char *note_sha1, char *note_path,
+               void *cb_data);
+int for_each_note(struct notes_tree *t, int flags, each_note_fn fn,
+               void *cb_data);
+
+/*
+ * Write the given notes_tree structure to the object database
+ *
+ * Creates a new tree object encapsulating the current state of the given
+ * notes_tree, and stores its SHA1 into the 'result' argument.
+ *
+ * Returns zero on success, non-zero on failure.
+ *
+ * IMPORTANT: Changes made to the given notes_tree are not persistent until
+ * this function has returned zero. Please also remember to create a
+ * corresponding commit object, and update the appropriate notes ref.
+ */
+int write_notes_tree(struct notes_tree *t, unsigned char *result);
+
+/* Flags controlling the operation of prune */
+#define NOTES_PRUNE_VERBOSE 1
+#define NOTES_PRUNE_DRYRUN 2
+/*
+ * Remove all notes annotating non-existing objects from the given notes tree
+ *
+ * All notes in the given notes_tree that are associated with objects that no
+ * longer exist in the database, are removed from the notes tree.
+ *
+ * IMPORTANT: The changes made by prune_notes() to the given notes_tree
+ * structure are not persistent until a subsequent call to write_notes_tree()
+ * returns zero.
+ */
+void prune_notes(struct notes_tree *t, int flags);
+
+/*
+ * Free (and de-initialize) the given notes_tree structure
+ *
+ * IMPORTANT: Changes made to the given notes_tree since the last, successful
+ * call to write_notes_tree() will be lost.
+ */
+void free_notes(struct notes_tree *t);
+
+/* Flags controlling how notes are formatted */
+#define NOTES_SHOW_HEADER 1
+#define NOTES_INDENT 2
+
+/*
+ * Fill the given strbuf with the notes associated with the given object.
+ *
+ * If the given notes_tree structure is not initialized, it will be auto-
+ * initialized to the default value (see documentation for init_notes() above).
+ * If the given notes_tree is NULL, the internal/default notes_tree will be
+ * used instead.
+ *
+ * 'flags' is a bitwise combination of the above formatting flags.
+ */
+void format_note(struct notes_tree *t, const unsigned char *object_sha1,
+               struct strbuf *sb, const char *output_encoding, int flags);
+
+
+struct string_list;
+
+struct display_notes_opt {
+       unsigned int suppress_default_notes:1;
+       struct string_list *extra_notes_refs;
+};
+
+/*
+ * Load the notes machinery for displaying several notes trees.
+ *
+ * If 'opt' is not NULL, then it specifies additional settings for the
+ * displaying:
+ *
+ * - suppress_default_notes indicates that the notes from
+ *   core.notesRef and notes.displayRef should not be loaded.
+ *
+ * - extra_notes_refs may contain a list of globs (in the same style
+ *   as notes.displayRef) where notes should be loaded from.
+ */
+void init_display_notes(struct display_notes_opt *opt);
+
+/*
+ * Append notes for the given 'object_sha1' from all trees set up by
+ * init_display_notes() to 'sb'.  The 'flags' are a bitwise
+ * combination of
+ *
+ * - NOTES_SHOW_HEADER: add a 'Notes (refname):' header
+ *
+ * - NOTES_INDENT: indent the notes by 4 places
+ *
+ * You *must* call init_display_notes() before using this function.
+ */
+void format_display_notes(const unsigned char *object_sha1,
+                         struct strbuf *sb, const char *output_encoding, int flags);
+
+/*
+ * Load the notes tree from each ref listed in 'refs'.  The output is
+ * an array of notes_tree*, terminated by a NULL.
+ */
+struct notes_tree **load_notes_trees(struct string_list *refs);
+
+/*
+ * Add all refs that match 'glob' to the 'list'.
+ */
+void string_list_add_refs_by_glob(struct string_list *list, const char *glob);
+
+/*
+ * Add all refs from a colon-separated glob list 'globs' to the end of
+ * 'list'.  Empty components are ignored.  This helper is used to
+ * parse GIT_NOTES_DISPLAY_REF style environment variables.
+ */
+void string_list_add_refs_from_colon_sep(struct string_list *list,
+                                        const char *globs);
+
+#endif
index 7e6a92c88e7b139ec03e0ff26e97e1559a06a220..7e1f2bbed2ad7f331fefc78d759acb14050bea48 100644 (file)
--- a/object.c
+++ b/object.c
@@ -45,13 +45,14 @@ int type_from_string(const char *str)
 
 static unsigned int hash_obj(struct object *obj, unsigned int n)
 {
-       unsigned int hash = *(unsigned int *)obj->sha1;
+       unsigned int hash;
+       memcpy(&hash, obj->sha1, sizeof(unsigned int));
        return hash % n;
 }
 
 static void insert_obj_hash(struct object *obj, struct object **hash, unsigned int size)
 {
-       int j = hash_obj(obj, size);
+       unsigned int j = hash_obj(obj, size);
 
        while (hash[j]) {
                j++;
@@ -61,16 +62,16 @@ static void insert_obj_hash(struct object *obj, struct object **hash, unsigned i
        hash[j] = obj;
 }
 
-static int hashtable_index(const unsigned char *sha1)
+static unsigned int hashtable_index(const unsigned char *sha1)
 {
        unsigned int i;
        memcpy(&i, sha1, sizeof(unsigned int));
-       return (int)(i % obj_hash_size);
+       return i % obj_hash_size;
 }
 
 struct object *lookup_object(const unsigned char *sha1)
 {
-       int i;
+       unsigned int i;
        struct object *obj;
 
        if (!obj_hash)
@@ -187,13 +188,14 @@ struct object *parse_object(const unsigned char *sha1)
        unsigned long size;
        enum object_type type;
        int eaten;
-       void *buffer = read_sha1_file(sha1, &type, &size);
+       const unsigned char *repl;
+       void *buffer = read_sha1_file_repl(sha1, &type, &size, &repl);
 
        if (buffer) {
                struct object *obj;
-               if (check_sha1_signature(sha1, buffer, size, typename(type)) < 0) {
+               if (check_sha1_signature(repl, buffer, size, typename(type)) < 0) {
                        free(buffer);
-                       error("sha1 mismatch %s\n", sha1_to_hex(sha1));
+                       error("sha1 mismatch %s\n", sha1_to_hex(repl));
                        return NULL;
                }
 
@@ -209,31 +211,10 @@ 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;
+       new_list->item = item;
+       new_list->next = *list_p;
+       *list_p = new_list;
+       return new_list;
 }
 
 int object_list_contains(struct object_list *list, struct object *obj)
@@ -271,10 +252,10 @@ void add_object_array_with_mode(struct object *obj, const char *name, struct obj
 
 void object_array_remove_duplicates(struct object_array *array)
 {
-       int ref, src, dst;
+       unsigned int ref, src, dst;
        struct object_array_entry *objects = array->objects;
 
-       for (ref = 0; ref < array->nr - 1; ref++) {
+       for (ref = 0; ref + 1 < array->nr; ref++) {
                for (src = ref + 1, dst = src;
                     src < array->nr;
                     src++) {
index 89dd0c47a6c86fd3a63370c84e574e799830e1d3..4d1d61546f0441bd198cda3ad153b35a91444b72 100644 (file)
--- a/object.h
+++ b/object.h
@@ -21,6 +21,8 @@ struct object_array {
        } *objects;
 };
 
+#define OBJECT_ARRAY_INIT { 0, 0, NULL }
+
 #define TYPE_BITS   3
 #define FLAG_BITS  27
 
@@ -72,11 +74,6 @@ struct object *lookup_unknown_object(const unsigned  char *sha1);
 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);
 
 /* Object array handling .. */
index 90c33b1b84ef793bf017927026863de41e31fa2c..9d0cb9a114cb3e5b0a4a4a132b9289ef18d8ae21 100644 (file)
@@ -49,7 +49,7 @@ static int verify_packfile(struct packed_git *p,
        const unsigned char *index_base = p->index_data;
        git_SHA_CTX ctx;
        unsigned char sha1[20], *pack_sig;
-       off_t offset = 0, pack_sig_ofs = p->pack_size - 20;
+       off_t offset = 0, pack_sig_ofs = 0;
        uint32_t nr_objects, i;
        int err = 0;
        struct idx_entry *entries;
@@ -61,21 +61,23 @@ static int verify_packfile(struct packed_git *p,
         */
 
        git_SHA1_Init(&ctx);
-       while (offset < pack_sig_ofs) {
+       do {
                unsigned int remaining;
                unsigned char *in = use_pack(p, w_curs, offset, &remaining);
                offset += remaining;
+               if (!pack_sig_ofs)
+                       pack_sig_ofs = p->pack_size - 20;
                if (offset > pack_sig_ofs)
                        remaining -= (unsigned int)(offset - pack_sig_ofs);
                git_SHA1_Update(&ctx, in, remaining);
-       }
+       } while (offset < pack_sig_ofs);
        git_SHA1_Final(sha1, &ctx);
        pack_sig = use_pack(p, w_curs, pack_sig_ofs, NULL);
        if (hashcmp(sha1, pack_sig))
                err = error("%s SHA1 checksum mismatch",
                            p->pack_name);
        if (hashcmp(index_base + index_size - 40, pack_sig))
-               err = error("%s SHA1 does not match its inddex",
+               err = error("%s SHA1 does not match its index",
                            p->pack_name);
        unuse_pack(w_curs);
 
@@ -131,14 +133,13 @@ static int verify_packfile(struct packed_git *p,
        return err;
 }
 
-int verify_pack(struct packed_git *p)
+int verify_pack_index(struct packed_git *p)
 {
        off_t index_size;
        const unsigned char *index_base;
        git_SHA_CTX ctx;
        unsigned char sha1[20];
        int err = 0;
-       struct pack_window *w_curs = NULL;
 
        if (open_pack_index(p))
                return error("packfile %s index not opened", p->pack_name);
@@ -152,8 +153,18 @@ int verify_pack(struct packed_git *p)
        if (hashcmp(sha1, index_base + index_size - 20))
                err = error("Packfile index for %s SHA1 mismatch",
                            p->pack_name);
+       return err;
+}
+
+int verify_pack(struct packed_git *p)
+{
+       int err = 0;
+       struct pack_window *w_curs = NULL;
+
+       err |= verify_pack_index(p);
+       if (!p->index_data)
+               return -1;
 
-       /* Verify pack file */
        err |= verify_packfile(p, &w_curs);
        unuse_pack(&w_curs);
 
diff --git a/pack-redundant.c b/pack-redundant.c
deleted file mode 100644 (file)
index 48a12bc..0000000
+++ /dev/null
@@ -1,698 +0,0 @@
-/*
-*
-* Copyright 2005, Lukas Sandstrom <lukass@etek.chalmers.se>
-*
-* This file is licensed under the GPL v2.
-*
-*/
-
-#include "cache.h"
-#include "exec_cmd.h"
-
-#define BLKSIZE 512
-
-static const char pack_redundant_usage[] =
-"git pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>";
-
-static int load_all_packs, verbose, alt_odb;
-
-struct llist_item {
-       struct llist_item *next;
-       const 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;
-
-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,
-                                              const 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,
-                                                  const unsigned char *sha1)
-{
-       return llist_insert(list, list->back, sha1);
-}
-
-static inline struct llist_item *llist_insert_sorted_unique(struct llist *list,
-                       const unsigned char *sha1, struct llist_item *hint)
-{
-       struct llist_item *prev = NULL, *l;
-
-       l = (hint == NULL) ? list->front : hint;
-       while (l) {
-               int cmp = hashcmp(l->sha1, sha1);
-               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 = hashcmp(l->sha1, sha1);
-               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)
-{
-       unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step;
-       const unsigned char *p1_base, *p2_base;
-       struct llist_item *p1_hint = NULL, *p2_hint = NULL;
-
-       p1_base = p1->pack->index_data;
-       p2_base = p2->pack->index_data;
-       p1_base += 256 * 4 + ((p1->pack->index_version < 2) ? 4 : 8);
-       p2_base += 256 * 4 + ((p2->pack->index_version < 2) ? 4 : 8);
-       p1_step = (p1->pack->index_version < 2) ? 24 : 20;
-       p2_step = (p2->pack->index_version < 2) ? 24 : 20;
-
-       while (p1_off < p1->pack->num_objects * p1_step &&
-              p2_off < p2->pack->num_objects * p2_step)
-       {
-               int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off);
-               /* 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 += p1_step;
-                       p2_off += p2_step;
-                       continue;
-               }
-               if (cmp < 0) { /* p1 has the object, p2 doesn't */
-                       p1_off += p1_step;
-               } else { /* p2 has the object, p1 doesn't */
-                       p2_off += p2_step;
-               }
-       }
-}
-
-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;
-       unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step;
-       const unsigned char *p1_base, *p2_base;
-
-       p1_base = p1->index_data;
-       p2_base = p2->index_data;
-       p1_base += 256 * 4 + ((p1->index_version < 2) ? 4 : 8);
-       p2_base += 256 * 4 + ((p2->index_version < 2) ? 4 : 8);
-       p1_step = (p1->index_version < 2) ? 24 : 20;
-       p2_step = (p2->index_version < 2) ? 24 : 20;
-
-       while (p1_off < p1->num_objects * p1_step &&
-              p2_off < p2->num_objects * p2_step)
-       {
-               int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off);
-               /* cmp ~ p1 - p2 */
-               if (cmp == 0) {
-                       ret++;
-                       p1_off += p1_step;
-                       p2_off += p2_step;
-                       continue;
-               }
-               if (cmp < 0) { /* p1 has the object, p2 doesn't */
-                       p1_off += p1_step;
-               } else { /* p2 has the object, p1 doesn't */
-                       p2_off += p2_step;
-               }
-       }
-       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 off_t pack_set_bytecount(struct pack_list *pl)
-{
-       off_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;
-       off_t min_perm_size = 0, 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!");
-
-       /* find the permutation with the smallest size */
-       perm = perm_ok;
-       while (perm) {
-               perm_size = pack_set_bytecount(perm->pl);
-               if (!min_perm_size || 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;
-       unsigned long off = 0, step;
-       const unsigned char *base;
-
-       if (!p->pack_local && !(alt_odb || verbose))
-               return NULL;
-
-       l.pack = p;
-       llist_init(&l.all_objects);
-
-       if (open_pack_index(p))
-               return NULL;
-
-       base = p->index_data;
-       base += 256 * 4 + ((p->index_version < 2) ? 4 : 8);
-       step = (p->index_version < 2) ? 24 : 20;
-       while (off < p->num_objects * step) {
-               llist_insert_back(l.all_objects, base + off);
-               off += step;
-       }
-       /* 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", filename);
-
-       while (p) {
-               if (strstr(p->pack_name, filename))
-                       return add_pack(p);
-               p = p->next;
-       }
-       die("Filename %s not found in packed_git", 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 */
-
-       git_extract_argv0_path(argv[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!");
-
-       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;
-}
index 2c76fb181f64e10c65517224b0c09cb648db81ac..1290570260d184b9c94fdab62650d6e474b365b4 100644 (file)
@@ -60,14 +60,46 @@ static int handle_one_ref(const char *path, const unsigned char *sha1,
        return 0;
 }
 
+/*
+ * Remove empty parents, but spare refs/ and immediate subdirs.
+ * Note: munges *name.
+ */
+static void try_remove_empty_parents(char *name)
+{
+       char *p, *q;
+       int i;
+       p = name;
+       for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */
+               while (*p && *p != '/')
+                       p++;
+               /* tolerate duplicate slashes; see check_ref_format() */
+               while (*p == '/')
+                       p++;
+       }
+       for (q = p; *q; q++)
+               ;
+       while (1) {
+               while (q > p && *q != '/')
+                       q--;
+               while (q > p && *(q-1) == '/')
+                       q--;
+               if (q == p)
+                       break;
+               *q = '\0';
+               if (rmdir(git_path("%s", name)))
+                       break;
+       }
+}
+
 /* make sure nobody touched the ref, and unlink */
 static void prune_ref(struct ref_to_prune *r)
 {
        struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);
 
        if (lock) {
-               unlink(git_path("%s", r->name));
+               unlink_or_warn(git_path("%s", r->name));
                unlock_ref(lock);
+               try_remove_empty_parents(r->name);
        }
 }
 
@@ -93,8 +125,7 @@ int pack_refs(unsigned int flags)
                                       LOCK_DIE_ON_ERROR);
        cbdata.refs_file = fdopen(fd, "w");
        if (!cbdata.refs_file)
-               die("unable to create ref-pack file structure (%s)",
-                   strerror(errno));
+               die_errno("unable to create ref-pack file structure");
 
        /* perhaps other traits later as well */
        fprintf(cbdata.refs_file, "# pack-refs with: peeled \n");
@@ -103,7 +134,7 @@ int pack_refs(unsigned int flags)
        if (ferror(cbdata.refs_file))
                die("failed to write ref-pack file");
        if (fflush(cbdata.refs_file) || fsync(fd) || fclose(cbdata.refs_file))
-               die("failed to write ref-pack file (%s)", strerror(errno));
+               die_errno("failed to write ref-pack file");
        /*
         * Since the lock file was fdopen()'ed and then fclose()'ed above,
         * assign -1 to the lock file descriptor so that commit_lock_file()
@@ -111,7 +142,7 @@ int pack_refs(unsigned int flags)
         */
        packed.fd = -1;
        if (commit_lock_file(&packed) < 0)
-               die("unable to overwrite old ref-pack file (%s)", strerror(errno));
+               die_errno("unable to overwrite old ref-pack file");
        if (cbdata.flags & PACK_REFS_PRUNE)
                prune_refs(cbdata.ref_to_prune);
        return 0;
index 1de53c8934c03b46604e94cbf4237ad5ffc57f83..77a0465be6f6a79814aa3c009612736770b342a1 100644 (file)
@@ -149,8 +149,7 @@ void discard_revindex(void)
        if (pack_revindex_hashsz) {
                int i;
                for (i = 0; i < pack_revindex_hashsz; i++)
-                       if (pack_revindex[i].revindex)
-                               free(pack_revindex[i].revindex);
+                       free(pack_revindex[i].revindex);
                free(pack_revindex);
                pack_revindex_hashsz = 0;
        }
index 7053538f4cf44e15a788ab46dfb680ee85ce4fc2..a905ca4486754f099a30f90a2fcd22d0c771a070 100644 (file)
@@ -17,8 +17,8 @@ static int sha1_compare(const void *_a, const void *_b)
  * the SHA1 hash of sorted object names. The objects array passed in
  * will be sorted by SHA1 on exit.
  */
-char *write_idx_file(char *index_name, struct pack_idx_entry **objects,
-                    int nr_objects, unsigned char *sha1)
+const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects,
+                          int nr_objects, unsigned char *sha1)
 {
        struct sha1file *f;
        struct pack_idx_entry **sorted_by_sha, **list, **last;
@@ -51,7 +51,7 @@ char *write_idx_file(char *index_name, struct pack_idx_entry **objects,
                fd = open(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
        }
        if (fd < 0)
-               die("unable to create %s: %s", index_name, strerror(errno));
+               die_errno("unable to create '%s'", index_name);
        f = sha1fd(fd, index_name);
 
        /* if last object's offset is >= 2^31 we should use index V2 */
@@ -174,11 +174,11 @@ void fixup_pack_header_footer(int pack_fd,
        git_SHA1_Init(&new_sha1_ctx);
 
        if (lseek(pack_fd, 0, SEEK_SET) != 0)
-               die("Failed seeking to start of %s: %s", pack_name, strerror(errno));
+               die_errno("Failed seeking to start of '%s'", pack_name);
        if (read_in_full(pack_fd, &hdr, sizeof(hdr)) != sizeof(hdr))
-               die("Unable to reread header of %s: %s", pack_name, strerror(errno));
+               die_errno("Unable to reread header of '%s'", pack_name);
        if (lseek(pack_fd, 0, SEEK_SET) != 0)
-               die("Failed seeking to start of %s: %s", pack_name, strerror(errno));
+               die_errno("Failed seeking to start of '%s'", pack_name);
        git_SHA1_Update(&old_sha1_ctx, &hdr, sizeof(hdr));
        hdr.hdr_entries = htonl(object_count);
        git_SHA1_Update(&new_sha1_ctx, &hdr, sizeof(hdr));
@@ -195,7 +195,7 @@ void fixup_pack_header_footer(int pack_fd,
                if (!n)
                        break;
                if (n < 0)
-                       die("Failed to checksum %s: %s", pack_name, strerror(errno));
+                       die_errno("Failed to checksum '%s'", pack_name);
                git_SHA1_Update(&new_sha1_ctx, buf, n);
 
                aligned_sz -= n;
@@ -253,3 +253,30 @@ char *index_pack_lockfile(int ip_out)
        }
        return NULL;
 }
+
+/*
+ * 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"
+ */
+int encode_in_pack_object_header(enum object_type type, uintmax_t size, unsigned char *hdr)
+{
+       int n = 1;
+       unsigned char c;
+
+       if (type < OBJ_COMMIT || type > OBJ_REF_DELTA)
+               die("bad type %d", type);
+
+       c = (type << 4) | (size & 15);
+       size >>= 4;
+       while (size) {
+               *hdr++ = c | 0x80;
+               c = size & 0x7f;
+               size >>= 7;
+               n++;
+       }
+       *hdr = c;
+       return n;
+}
diff --git a/pack.h b/pack.h
index a883334b269c76d8de1395adf2b8f3d0d7e8564f..bb275762b7eb6f473f333ae40780821e383db20b 100644 (file)
--- a/pack.h
+++ b/pack.h
@@ -55,11 +55,13 @@ struct pack_idx_entry {
        off_t offset;
 };
 
-extern char *write_idx_file(char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1);
+extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1);
 extern int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, off_t offset, off_t len, unsigned int nr);
+extern int verify_pack_index(struct packed_git *);
 extern int verify_pack(struct packed_git *);
 extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t, unsigned char *, off_t);
 extern char *index_pack_lockfile(int fd);
+extern int encode_in_pack_object_header(enum object_type, uintmax_t, unsigned char *);
 
 #define PH_ERROR_EOF           (-1)
 #define PH_ERROR_PACK_SIGNATURE        (-2)
diff --git a/pager.c b/pager.c
index 4921843577e42b774457a61277b9bc3441d3ab6b..dac358f047550a07dd8d90b2e410feab3eebd534 100644 (file)
--- a/pager.c
+++ b/pager.c
@@ -2,6 +2,10 @@
 #include "run-command.h"
 #include "sigchain.h"
 
+#ifndef DEFAULT_PAGER
+#define DEFAULT_PAGER "less"
+#endif
+
 /*
  * This is split up from the rest of git so that we can do
  * something different on Windows.
@@ -9,7 +13,7 @@
 
 static int spawned_pager;
 
-#ifndef __MINGW32__
+#ifndef WIN32
 static void pager_preexec(void)
 {
        /*
@@ -21,12 +25,10 @@ static void pager_preexec(void)
        FD_ZERO(&in);
        FD_SET(0, &in);
        select(1, &in, NULL, &in, NULL);
-
-       setenv("LESS", "FRSX", 0);
 }
 #endif
 
-static const char *pager_argv[] = { "sh", "-c", NULL, NULL };
+static const char *pager_argv[] = { NULL, NULL };
 static struct child_process pager_process;
 
 static void wait_for_pager(void)
@@ -46,12 +48,14 @@ static void wait_for_pager_signal(int signo)
        raise(signo);
 }
 
-void setup_pager(void)
+const char *git_pager(int stdout_is_tty)
 {
-       const char *pager = getenv("GIT_PAGER");
+       const char *pager;
 
-       if (!isatty(1))
-               return;
+       if (!stdout_is_tty)
+               return NULL;
+
+       pager = getenv("GIT_PAGER");
        if (!pager) {
                if (!pager_program)
                        git_config(git_default_config, NULL);
@@ -60,17 +64,32 @@ void setup_pager(void)
        if (!pager)
                pager = getenv("PAGER");
        if (!pager)
-               pager = "less";
+               pager = DEFAULT_PAGER;
        else if (!*pager || !strcmp(pager, "cat"))
+               pager = NULL;
+
+       return pager;
+}
+
+void setup_pager(void)
+{
+       const char *pager = git_pager(isatty(1));
+
+       if (!pager)
                return;
 
        spawned_pager = 1; /* means we are emitting to terminal */
 
        /* spawn the pager */
-       pager_argv[2] = pager;
+       pager_argv[0] = pager;
+       pager_process.use_shell = 1;
        pager_process.argv = pager_argv;
        pager_process.in = -1;
-#ifndef __MINGW32__
+       if (!getenv("LESS")) {
+               static const char *env[] = { "LESS=FRSX", NULL };
+               pager_process.env = env;
+       }
+#ifndef WIN32
        pager_process.preexec_cb = pager_preexec;
 #endif
        if (start_command(&pager_process))
index cf71bcffd2e1e9aeacb44df488b0825f09d54255..0fa79bc31d322e2aab8fce738ca6489a35a51ca4 100644 (file)
@@ -2,6 +2,11 @@
 #include "parse-options.h"
 #include "cache.h"
 #include "commit.h"
+#include "color.h"
+
+static int parse_options_usage(struct parse_opt_ctx_t *ctx,
+                              const char * const *usagestr,
+                              const struct option *opts, int err);
 
 #define OPT_SHORT 1
 #define OPT_UNSET 2
@@ -31,11 +36,20 @@ static int get_arg(struct parse_opt_ctx_t *p, const struct option *opt,
        return 0;
 }
 
+static void fix_filename(const char *prefix, const char **file)
+{
+       if (!file || !*file || !prefix || is_absolute_path(*file)
+           || !strcmp("-", *file))
+               return;
+       *file = xstrdup(prefix_filename(prefix, strlen(prefix), *file));
+}
+
 static int get_value(struct parse_opt_ctx_t *p,
                     const struct option *opt, int flags)
 {
        const char *s, *arg;
        const int unset = flags & OPT_UNSET;
+       int err;
 
        if (unset && p->opt)
                return opterror(opt, "takes no value", flags);
@@ -50,6 +64,7 @@ static int get_value(struct parse_opt_ctx_t *p,
                        /* FALLTHROUGH */
                case OPTION_BOOLEAN:
                case OPTION_BIT:
+               case OPTION_NEGBIT:
                case OPTION_SET_INT:
                case OPTION_SET_PTR:
                        return opterror(opt, "takes no value", flags);
@@ -66,6 +81,13 @@ static int get_value(struct parse_opt_ctx_t *p,
                        *(int *)opt->value |= opt->defval;
                return 0;
 
+       case OPTION_NEGBIT:
+               if (unset)
+                       *(int *)opt->value |= opt->defval;
+               else
+                       *(int *)opt->value &= ~opt->defval;
+               return 0;
+
        case OPTION_BOOLEAN:
                *(int *)opt->value = unset ? 0 : *(int *)opt->value + 1;
                return 0;
@@ -87,6 +109,19 @@ static int get_value(struct parse_opt_ctx_t *p,
                        return get_arg(p, opt, flags, (const char **)opt->value);
                return 0;
 
+       case OPTION_FILENAME:
+               err = 0;
+               if (unset)
+                       *(const char **)opt->value = NULL;
+               else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
+                       *(const char **)opt->value = (const char *)opt->defval;
+               else
+                       err = get_arg(p, opt, flags, (const char **)opt->value);
+
+               if (!err)
+                       fix_filename(p->prefix, (const char **)opt->value);
+               return err;
+
        case OPTION_CALLBACK:
                if (unset)
                        return (*opt->callback)(opt, NULL, 1) ? (-1) : 0;
@@ -121,11 +156,33 @@ static int get_value(struct parse_opt_ctx_t *p,
 
 static int parse_short_opt(struct parse_opt_ctx_t *p, const struct option *options)
 {
+       const struct option *numopt = NULL;
+
        for (; options->type != OPTION_END; options++) {
                if (options->short_name == *p->opt) {
                        p->opt = p->opt[1] ? p->opt + 1 : NULL;
                        return get_value(p, options, OPT_SHORT);
                }
+
+               /*
+                * Handle the numerical option later, explicit one-digit
+                * options take precedence over it.
+                */
+               if (options->type == OPTION_NUMBER)
+                       numopt = options;
+       }
+       if (numopt && isdigit(*p->opt)) {
+               size_t len = 1;
+               char *arg;
+               int rc;
+
+               while (isdigit(p->opt[len]))
+                       len++;
+               arg = xmemdupz(p->opt, len);
+               p->opt = p->opt[len] ? p->opt + len : NULL;
+               rc = (*numopt->callback)(numopt, arg, 0) ? (-1) : 0;
+               free(arg);
+               return rc;
        }
        return -2;
 }
@@ -178,6 +235,9 @@ is_abbreviated:
                                abbrev_flags = flags;
                                continue;
                        }
+                       /* negation allowed? */
+                       if (options->flags & PARSE_OPT_NONEG)
+                               continue;
                        /* negated and abbreviated very much? */
                        if (!prefixcmp("no-", arg)) {
                                flags |= OPT_UNSET;
@@ -215,6 +275,25 @@ is_abbreviated:
        return -2;
 }
 
+static int parse_nodash_opt(struct parse_opt_ctx_t *p, const char *arg,
+                           const struct option *options)
+{
+       for (; options->type != OPTION_END; options++) {
+               if (!(options->flags & PARSE_OPT_NODASH))
+                       continue;
+               if ((options->flags & PARSE_OPT_OPTARG) ||
+                   !(options->flags & PARSE_OPT_NOARG))
+                       die("BUG: dashless options don't support arguments");
+               if (!(options->flags & PARSE_OPT_NONEG))
+                       die("BUG: dashless options don't support negation");
+               if (options->long_name)
+                       die("BUG: dashless options can't be long");
+               if (options->short_name == arg[0] && arg[1] == '\0')
+                       return get_value(p, options, OPT_SHORT);
+       }
+       return -2;
+}
+
 static void check_typos(const char *arg, const struct option *options)
 {
        if (strlen(arg) < 3)
@@ -235,13 +314,37 @@ static void check_typos(const char *arg, const struct option *options)
        }
 }
 
+static void parse_options_check(const struct option *opts)
+{
+       int err = 0;
+
+       for (; opts->type != OPTION_END; opts++) {
+               if ((opts->flags & PARSE_OPT_LASTARG_DEFAULT) &&
+                   (opts->flags & PARSE_OPT_OPTARG)) {
+                       if (opts->long_name) {
+                               error("`--%s` uses incompatible flags "
+                                     "LASTARG_DEFAULT and OPTARG", opts->long_name);
+                       } else {
+                               error("`-%c` uses incompatible flags "
+                                     "LASTARG_DEFAULT and OPTARG", opts->short_name);
+                       }
+                       err |= 1;
+               }
+       }
+
+       if (err)
+               exit(129);
+}
+
 void parse_options_start(struct parse_opt_ctx_t *ctx,
-                        int argc, const char **argv, int flags)
+                        int argc, const char **argv, const char *prefix,
+                        int flags)
 {
        memset(ctx, 0, sizeof(*ctx));
        ctx->argc = argc - 1;
        ctx->argv = argv + 1;
        ctx->out  = argv;
+       ctx->prefix = prefix;
        ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0);
        ctx->flags = flags;
        if ((flags & PARSE_OPT_KEEP_UNKNOWN) &&
@@ -249,8 +352,9 @@ void parse_options_start(struct parse_opt_ctx_t *ctx,
                die("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
 }
 
-static int usage_with_options_internal(const char * const *,
-                                      const struct option *, int);
+static int usage_with_options_internal(struct parse_opt_ctx_t *,
+                                      const char * const *,
+                                      const struct option *, int, int);
 
 int parse_options_step(struct parse_opt_ctx_t *ctx,
                       const struct option *options,
@@ -258,6 +362,8 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
 {
        int internal_help = !(ctx->flags & PARSE_OPT_NO_INTERNAL_HELP);
 
+       parse_options_check(options);
+
        /* we must reset ->opt, unknown short option leave it dangling */
        ctx->opt = NULL;
 
@@ -265,6 +371,8 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                const char *arg = ctx->argv[0];
 
                if (*arg != '-' || !arg[1]) {
+                       if (parse_nodash_opt(ctx, arg, options) == 0)
+                               continue;
                        if (ctx->flags & PARSE_OPT_STOP_AT_NON_OPTION)
                                break;
                        ctx->out[ctx->cpidx++] = ctx->argv[0];
@@ -274,10 +382,10 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                if (arg[1] != '-') {
                        ctx->opt = arg + 1;
                        if (internal_help && *ctx->opt == 'h')
-                               return parse_options_usage(usagestr, options);
+                               return parse_options_usage(ctx, usagestr, options, 0);
                        switch (parse_short_opt(ctx, options)) {
                        case -1:
-                               return parse_options_usage(usagestr, options);
+                               return parse_options_usage(ctx, usagestr, options, 1);
                        case -2:
                                goto unknown;
                        }
@@ -285,10 +393,10 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                                check_typos(arg + 1, options);
                        while (ctx->opt) {
                                if (internal_help && *ctx->opt == 'h')
-                                       return parse_options_usage(usagestr, options);
+                                       return parse_options_usage(ctx, usagestr, options, 0);
                                switch (parse_short_opt(ctx, options)) {
                                case -1:
-                                       return parse_options_usage(usagestr, options);
+                                       return parse_options_usage(ctx, usagestr, options, 1);
                                case -2:
                                        /* fake a short option thing to hide the fact that we may have
                                         * started to parse aggregated stuff
@@ -312,12 +420,12 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                }
 
                if (internal_help && !strcmp(arg + 2, "help-all"))
-                       return usage_with_options_internal(usagestr, options, 1);
+                       return usage_with_options_internal(ctx, usagestr, options, 1, 0);
                if (internal_help && !strcmp(arg + 2, "help"))
-                       return parse_options_usage(usagestr, options);
+                       return parse_options_usage(ctx, usagestr, options, 0);
                switch (parse_long_opt(ctx, arg + 2, options)) {
                case -1:
-                       return parse_options_usage(usagestr, options);
+                       return parse_options_usage(ctx, usagestr, options, 1);
                case -2:
                        goto unknown;
                }
@@ -338,12 +446,13 @@ int parse_options_end(struct parse_opt_ctx_t *ctx)
        return ctx->cpidx + ctx->argc;
 }
 
-int parse_options(int argc, const char **argv, const struct option *options,
-                 const char * const usagestr[], int flags)
+int parse_options(int argc, const char **argv, const char *prefix,
+                 const struct option *options, const char * const usagestr[],
+                 int flags)
 {
        struct parse_opt_ctx_t ctx;
 
-       parse_options_start(&ctx, argc, argv, flags);
+       parse_options_start(&ctx, argc, argv, prefix, flags);
        switch (parse_options_step(&ctx, options, usagestr)) {
        case PARSE_OPT_HELP:
                exit(129);
@@ -361,97 +470,92 @@ int parse_options(int argc, const char **argv, const struct option *options,
        return parse_options_end(&ctx);
 }
 
+static int usage_argh(const struct option *opts, FILE *outfile)
+{
+       const char *s;
+       int literal = (opts->flags & PARSE_OPT_LITERAL_ARGHELP) || !opts->argh;
+       if (opts->flags & PARSE_OPT_OPTARG)
+               if (opts->long_name)
+                       s = literal ? "[=%s]" : "[=<%s>]";
+               else
+                       s = literal ? "[%s]" : "[<%s>]";
+       else
+               s = literal ? " %s" : " <%s>";
+       return fprintf(outfile, s, opts->argh ? opts->argh : "...");
+}
+
 #define USAGE_OPTS_WIDTH 24
 #define USAGE_GAP         2
 
-int usage_with_options_internal(const char * const *usagestr,
-                               const struct option *opts, int full)
+static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
+                                      const char * const *usagestr,
+                                      const struct option *opts, int full, int err)
 {
+       FILE *outfile = err ? stderr : stdout;
+
        if (!usagestr)
                return PARSE_OPT_HELP;
 
-       fprintf(stderr, "usage: %s\n", *usagestr++);
+       if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)
+               fprintf(outfile, "cat <<\\EOF\n");
+
+       fprintf(outfile, "usage: %s\n", *usagestr++);
        while (*usagestr && **usagestr)
-               fprintf(stderr, "   or: %s\n", *usagestr++);
+               fprintf(outfile, "   or: %s\n", *usagestr++);
        while (*usagestr) {
-               fprintf(stderr, "%s%s\n",
+               fprintf(outfile, "%s%s\n",
                                **usagestr ? "    " : "",
                                *usagestr);
                usagestr++;
        }
 
        if (opts->type != OPTION_GROUP)
-               fputc('\n', stderr);
+               fputc('\n', outfile);
 
        for (; opts->type != OPTION_END; opts++) {
                size_t pos;
                int pad;
 
                if (opts->type == OPTION_GROUP) {
-                       fputc('\n', stderr);
+                       fputc('\n', outfile);
                        if (*opts->help)
-                               fprintf(stderr, "%s\n", opts->help);
+                               fprintf(outfile, "%s\n", opts->help);
                        continue;
                }
                if (!full && (opts->flags & PARSE_OPT_HIDDEN))
                        continue;
 
-               pos = fprintf(stderr, "    ");
-               if (opts->short_name)
-                       pos += fprintf(stderr, "-%c", opts->short_name);
+               pos = fprintf(outfile, "    ");
+               if (opts->short_name && !(opts->flags & PARSE_OPT_NEGHELP)) {
+                       if (opts->flags & PARSE_OPT_NODASH)
+                               pos += fprintf(outfile, "%c", opts->short_name);
+                       else
+                               pos += fprintf(outfile, "-%c", opts->short_name);
+               }
                if (opts->long_name && opts->short_name)
-                       pos += fprintf(stderr, ", ");
+                       pos += fprintf(outfile, ", ");
                if (opts->long_name)
-                       pos += fprintf(stderr, "--%s", opts->long_name);
+                       pos += fprintf(outfile, "--%s%s",
+                               (opts->flags & PARSE_OPT_NEGHELP) ?  "no-" : "",
+                               opts->long_name);
+               if (opts->type == OPTION_NUMBER)
+                       pos += fprintf(outfile, "-NUM");
 
-               switch (opts->type) {
-               case OPTION_ARGUMENT:
-                       break;
-               case OPTION_INTEGER:
-                       if (opts->flags & PARSE_OPT_OPTARG)
-                               if (opts->long_name)
-                                       pos += fprintf(stderr, "[=<n>]");
-                               else
-                                       pos += fprintf(stderr, "[<n>]");
-                       else
-                               pos += fprintf(stderr, " <n>");
-                       break;
-               case OPTION_CALLBACK:
-                       if (opts->flags & PARSE_OPT_NOARG)
-                               break;
-                       /* FALLTHROUGH */
-               case OPTION_STRING:
-                       if (opts->argh) {
-                               if (opts->flags & PARSE_OPT_OPTARG)
-                                       if (opts->long_name)
-                                               pos += fprintf(stderr, "[=<%s>]", opts->argh);
-                                       else
-                                               pos += fprintf(stderr, "[<%s>]", opts->argh);
-                               else
-                                       pos += fprintf(stderr, " <%s>", opts->argh);
-                       } else {
-                               if (opts->flags & PARSE_OPT_OPTARG)
-                                       if (opts->long_name)
-                                               pos += fprintf(stderr, "[=...]");
-                                       else
-                                               pos += fprintf(stderr, "[...]");
-                               else
-                                       pos += fprintf(stderr, " ...");
-                       }
-                       break;
-               default: /* OPTION_{BIT,BOOLEAN,SET_INT,SET_PTR} */
-                       break;
-               }
+               if (!(opts->flags & PARSE_OPT_NOARG))
+                       pos += usage_argh(opts, outfile);
 
                if (pos <= USAGE_OPTS_WIDTH)
                        pad = USAGE_OPTS_WIDTH - pos;
                else {
-                       fputc('\n', stderr);
+                       fputc('\n', outfile);
                        pad = USAGE_OPTS_WIDTH;
                }
-               fprintf(stderr, "%*s%s\n", pad + USAGE_GAP, "", opts->help);
+               fprintf(outfile, "%*s%s\n", pad + USAGE_GAP, "", opts->help);
        }
-       fputc('\n', stderr);
+       fputc('\n', outfile);
+
+       if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)
+               fputs("EOF\n", outfile);
 
        return PARSE_OPT_HELP;
 }
@@ -459,14 +563,23 @@ int usage_with_options_internal(const char * const *usagestr,
 void usage_with_options(const char * const *usagestr,
                        const struct option *opts)
 {
-       usage_with_options_internal(usagestr, opts, 0);
+       usage_with_options_internal(NULL, usagestr, opts, 0, 1);
        exit(129);
 }
 
-int parse_options_usage(const char * const *usagestr,
-                       const struct option *opts)
+void usage_msg_opt(const char *msg,
+                  const char * const *usagestr,
+                  const struct option *options)
+{
+       fprintf(stderr, "%s\n\n", msg);
+       usage_with_options(usagestr, options);
+}
+
+static int parse_options_usage(struct parse_opt_ctx_t *ctx,
+                              const char * const *usagestr,
+                              const struct option *opts, int err)
 {
-       return usage_with_options_internal(usagestr, opts, 0);
+       return usage_with_options_internal(ctx, usagestr, opts, 0, err);
 }
 
 
@@ -499,6 +612,21 @@ int parse_opt_approxidate_cb(const struct option *opt, const char *arg,
        return 0;
 }
 
+int parse_opt_color_flag_cb(const struct option *opt, const char *arg,
+                           int unset)
+{
+       int value;
+
+       if (!arg)
+               arg = unset ? "never" : (const char *)opt->defval;
+       value = git_config_colorbool(NULL, arg, -1);
+       if (value < 0)
+               return opterror(opt,
+                       "expects \"always\", \"auto\", or \"never\"", 0);
+       *(int *)opt->value = value;
+       return 0;
+}
+
 int parse_opt_verbosity_cb(const struct option *opt, const char *arg,
                           int unset)
 {
@@ -537,14 +665,24 @@ int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
        return 0;
 }
 
-/*
- * This should really be OPTION_FILENAME type as a part of
- * parse_options that take prefix to do this while parsing.
- */
-extern const char *parse_options_fix_filename(const char *prefix, const char *file)
+int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
 {
-       if (!file || !prefix || is_absolute_path(file) || !strcmp("-", file))
-               return file;
-       return prefix_filename(prefix, strlen(prefix), file);
+       int *target = opt->value;
+       *target = unset ? 2 : 1;
+       return 0;
 }
 
+int parse_options_concat(struct option *dst, size_t dst_size, struct option *src)
+{
+       int i, j;
+
+       for (i = 0; i < dst_size; i++)
+               if (dst[i].type == OPTION_END)
+                       break;
+       for (j = 0; i < dst_size; i++, j++) {
+               dst[i] = src[j];
+               if (src[j].type == OPTION_END)
+                       return 0;
+       }
+       return -1;
+}
index b54eec128bcda1a8b4c4a2c33f2ce799c506ac23..d982f0f1bf181f00f54861eb768504007490e95d 100644 (file)
@@ -6,8 +6,10 @@ enum parse_opt_type {
        OPTION_END,
        OPTION_ARGUMENT,
        OPTION_GROUP,
+       OPTION_NUMBER,
        /* options with no arguments */
        OPTION_BIT,
+       OPTION_NEGBIT,
        OPTION_BOOLEAN, /* _INCR would have been a better name */
        OPTION_SET_INT,
        OPTION_SET_PTR,
@@ -15,6 +17,7 @@ enum parse_opt_type {
        OPTION_STRING,
        OPTION_INTEGER,
        OPTION_CALLBACK,
+       OPTION_FILENAME
 };
 
 enum parse_opt_flags {
@@ -22,7 +25,7 @@ enum parse_opt_flags {
        PARSE_OPT_STOP_AT_NON_OPTION = 2,
        PARSE_OPT_KEEP_ARGV0 = 4,
        PARSE_OPT_KEEP_UNKNOWN = 8,
-       PARSE_OPT_NO_INTERNAL_HELP = 16,
+       PARSE_OPT_NO_INTERNAL_HELP = 16
 };
 
 enum parse_opt_option_flags {
@@ -31,6 +34,10 @@ enum parse_opt_option_flags {
        PARSE_OPT_NONEG   = 4,
        PARSE_OPT_HIDDEN  = 8,
        PARSE_OPT_LASTARG_DEFAULT = 16,
+       PARSE_OPT_NODASH = 32,
+       PARSE_OPT_LITERAL_ARGHELP = 64,
+       PARSE_OPT_NEGHELP = 128,
+       PARSE_OPT_SHELL_EVAL = 256
 };
 
 struct option;
@@ -62,10 +69,22 @@ typedef int parse_opt_cb(const struct option *, const char *arg, int unset);
  * `flags`::
  *   mask of parse_opt_option_flags.
  *   PARSE_OPT_OPTARG: says that the argument is optional (not for BOOLEANs)
- *   PARSE_OPT_NOARG: says that this option takes no argument, for CALLBACKs
+ *   PARSE_OPT_NOARG: says that this option does not take an argument
  *   PARSE_OPT_NONEG: says that this option cannot be negated
- *   PARSE_OPT_HIDDEN this option is skipped in the default usage, showed in
- *                    the long one.
+ *   PARSE_OPT_HIDDEN: this option is skipped in the default usage, and
+ *                     shown only in the full usage.
+ *   PARSE_OPT_LASTARG_DEFAULT: says that this option will take the default
+ *                             value if no argument is given when the option
+ *                             is last on the command line. If the option is
+ *                             not last it will require an argument.
+ *                             Should not be used with PARSE_OPT_OPTARG.
+ *   PARSE_OPT_NODASH: this option doesn't start with a dash.
+ *   PARSE_OPT_LITERAL_ARGHELP: says that argh shouldn't be enclosed in brackets
+ *                             (i.e. '<argh>') in the help message.
+ *                             Useful for options with multiple parameters.
+ *   PARSE_OPT_NEGHELP: says that the long option should always be shown with
+ *                             the --no prefix in the usage message. Sometimes
+ *                             useful for users of OPTION_NEGBIT.
  *
  * `callback`::
  *   pointer to the callback to use for OPTION_CALLBACK.
@@ -90,37 +109,59 @@ struct option {
 };
 
 #define OPT_END()                   { OPTION_END }
-#define OPT_ARGUMENT(l, h)          { OPTION_ARGUMENT, 0, (l), NULL, NULL, (h) }
+#define OPT_ARGUMENT(l, h)          { OPTION_ARGUMENT, 0, (l), NULL, NULL, \
+                                     (h), PARSE_OPT_NOARG}
 #define OPT_GROUP(h)                { OPTION_GROUP, 0, NULL, NULL, NULL, (h) }
-#define OPT_BIT(s, l, v, h, b)      { OPTION_BIT, (s), (l), (v), NULL, (h), 0, NULL, (b) }
-#define OPT_BOOLEAN(s, l, v, h)     { OPTION_BOOLEAN, (s), (l), (v), NULL, (h) }
-#define OPT_SET_INT(s, l, v, h, i)  { OPTION_SET_INT, (s), (l), (v), NULL, (h), 0, NULL, (i) }
-#define OPT_SET_PTR(s, l, v, h, p)  { OPTION_SET_PTR, (s), (l), (v), NULL, (h), 0, NULL, (p) }
-#define OPT_INTEGER(s, l, v, h)     { OPTION_INTEGER, (s), (l), (v), NULL, (h) }
+#define OPT_BIT(s, l, v, h, b)      { OPTION_BIT, (s), (l), (v), NULL, (h), \
+                                     PARSE_OPT_NOARG, NULL, (b) }
+#define OPT_NEGBIT(s, l, v, h, b)   { OPTION_NEGBIT, (s), (l), (v), NULL, \
+                                     (h), PARSE_OPT_NOARG, NULL, (b) }
+#define OPT_BOOLEAN(s, l, v, h)     { OPTION_BOOLEAN, (s), (l), (v), NULL, \
+                                     (h), PARSE_OPT_NOARG }
+#define OPT_SET_INT(s, l, v, h, i)  { OPTION_SET_INT, (s), (l), (v), NULL, \
+                                     (h), PARSE_OPT_NOARG, NULL, (i) }
+#define OPT_SET_PTR(s, l, v, h, p)  { OPTION_SET_PTR, (s), (l), (v), NULL, \
+                                     (h), PARSE_OPT_NOARG, NULL, (p) }
+#define OPT_INTEGER(s, l, v, h)     { OPTION_INTEGER, (s), (l), (v), "n", (h) }
 #define OPT_STRING(s, l, v, a, h)   { OPTION_STRING,  (s), (l), (v), (a), (h) }
+#define OPT_UYN(s, l, v, h)         { OPTION_CALLBACK, (s), (l), (v), NULL, \
+                                     (h), PARSE_OPT_NOARG, &parse_opt_tertiary }
 #define OPT_DATE(s, l, v, h) \
        { OPTION_CALLBACK, (s), (l), (v), "time",(h), 0, \
          parse_opt_approxidate_cb }
 #define OPT_CALLBACK(s, l, v, a, h, f) \
        { OPTION_CALLBACK, (s), (l), (v), (a), (h), 0, (f) }
+#define OPT_NUMBER_CALLBACK(v, h, f) \
+       { OPTION_NUMBER, 0, NULL, (v), NULL, (h), \
+         PARSE_OPT_NOARG | PARSE_OPT_NONEG, (f) }
+#define OPT_FILENAME(s, l, v, h)    { OPTION_FILENAME, (s), (l), (v), \
+                                      "FILE", (h) }
+#define OPT_COLOR_FLAG(s, l, v, h) \
+       { OPTION_CALLBACK, (s), (l), (v), "when", (h), PARSE_OPT_OPTARG, \
+               parse_opt_color_flag_cb, (intptr_t)"always" }
+
 
 /* parse_options() will filter out the processed options and leave the
  * non-option arguments in argv[].
  * Returns the number of arguments left in argv[].
  */
-extern int parse_options(int argc, const char **argv,
+extern int parse_options(int argc, const char **argv, const char *prefix,
                          const struct option *options,
                          const char * const usagestr[], int flags);
 
 extern NORETURN void usage_with_options(const char * const *usagestr,
                                         const struct option *options);
 
+extern NORETURN void usage_msg_opt(const char *msg,
+                                  const char * const *usagestr,
+                                  const struct option *options);
+
 /*----- incremental advanced APIs -----*/
 
 enum {
        PARSE_OPT_HELP = -1,
        PARSE_OPT_DONE,
-       PARSE_OPT_UNKNOWN,
+       PARSE_OPT_UNKNOWN
 };
 
 /*
@@ -134,13 +175,12 @@ struct parse_opt_ctx_t {
        int argc, cpidx;
        const char *opt;
        int flags;
+       const char *prefix;
 };
 
-extern int parse_options_usage(const char * const *usagestr,
-                              const struct option *opts);
-
 extern void parse_options_start(struct parse_opt_ctx_t *ctx,
-                               int argc, const char **argv, int flags);
+                               int argc, const char **argv, const char *prefix,
+                               int flags);
 
 extern int parse_options_step(struct parse_opt_ctx_t *ctx,
                              const struct option *options,
@@ -148,12 +188,15 @@ extern int parse_options_step(struct parse_opt_ctx_t *ctx,
 
 extern int parse_options_end(struct parse_opt_ctx_t *ctx);
 
+extern int parse_options_concat(struct option *dst, size_t, struct option *src);
 
 /*----- some often used options -----*/
 extern int parse_opt_abbrev_cb(const struct option *, const char *, int);
 extern int parse_opt_approxidate_cb(const struct option *, const char *, int);
+extern int parse_opt_color_flag_cb(const struct option *, const char *, int);
 extern int parse_opt_verbosity_cb(const struct option *, const char *, int);
 extern int parse_opt_with_commit(const struct option *, const char *, int);
+extern int parse_opt_tertiary(const struct option *, const char *, int);
 
 #define OPT__VERBOSE(var)  OPT_BOOLEAN('v', "verbose", (var), "be verbose")
 #define OPT__QUIET(var)    OPT_BOOLEAN('q', "quiet",   (var), "be quiet")
@@ -167,7 +210,7 @@ extern int parse_opt_with_commit(const struct option *, const char *, int);
        { OPTION_CALLBACK, 0, "abbrev", (var), "n", \
          "use <n> digits to display SHA-1s", \
          PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 }
-
-extern const char *parse_options_fix_filename(const char *prefix, const char *file);
+#define OPT__COLOR(var, h) \
+       OPT_COLOR_FLAG(0, "color", (var), (h))
 
 #endif
index ed9db81fa82c812c9ffa07f5a40540dbb15da0d3..d218faa02bd12b0e6a0df298a6a0e5787e46d93f 100644 (file)
@@ -2,7 +2,7 @@
  * patch-delta.c:
  * recreate a buffer from a source and the delta produced by diff-delta.c
  *
- * (C) 2005 Nicolas Pitre <nico@cam.org>
+ * (C) 2005 Nicolas Pitre <nico@fluxnic.net>
  *
  * 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
@@ -33,8 +33,7 @@ void *patch_delta(const void *src_buf, unsigned long src_size,
 
        /* now the result size */
        size = get_delta_hdr_size(&data, top);
-       dst_buf = xmalloc(size + 1);
-       dst_buf[size] = 0;
+       dst_buf = xmallocz(size);
 
        out = dst_buf;
        while (data < top) {
@@ -44,7 +43,7 @@ void *patch_delta(const void *src_buf, unsigned long src_size,
                        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 & 0x08) cp_off |= ((unsigned) *data++ << 24);
                        if (cmd & 0x10) cp_size = *data++;
                        if (cmd & 0x20) cp_size |= (*data++ << 8);
                        if (cmd & 0x40) cp_size |= (*data++ << 16);
diff --git a/patch-id.c b/patch-id.c
deleted file mode 100644 (file)
index 0df4cb0..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-#include "cache.h"
-#include "exec_cmd.h"
-
-static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c)
-{
-       unsigned char result[20];
-       char name[50];
-
-       if (!patchlen)
-               return;
-
-       git_SHA1_Final(result, c);
-       memcpy(name, sha1_to_hex(id), 41);
-       printf("%s %s\n", sha1_to_hex(result), name);
-       git_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];
-       git_SHA_CTX ctx;
-       int patchlen = 0;
-
-       git_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;
-               else if (!memcmp(line, "commit ", 7))
-                       p += 7;
-
-               if (!get_sha1_hex(p, n)) {
-                       flush_current_id(patchlen, sha1, &ctx);
-                       hashcpy(sha1, n);
-                       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;
-               git_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);
-
-       git_extract_argv0_path(argv[0]);
-
-       generate_id_list();
-       return 0;
-}
diff --git a/path.c b/path.c
index 8a0a6741fd664f98f2883348c0a755d60616035b..a2c9d1e24afb9d1250c846791607c49d82e9565e 100644 (file)
--- a/path.c
+++ b/path.c
@@ -11,6 +11,7 @@
  * which is what it's designed for.
  */
 #include "cache.h"
+#include "strbuf.h"
 
 static char bad_path[] = "/bad-path/";
 
@@ -121,6 +122,44 @@ char *git_path(const char *fmt, ...)
        return cleanup_path(pathname);
 }
 
+char *git_path_submodule(const char *path, const char *fmt, ...)
+{
+       char *pathname = get_pathname();
+       struct strbuf buf = STRBUF_INIT;
+       const char *git_dir;
+       va_list args;
+       unsigned len;
+
+       len = strlen(path);
+       if (len > PATH_MAX-100)
+               return bad_path;
+
+       strbuf_addstr(&buf, path);
+       if (len && path[len-1] != '/')
+               strbuf_addch(&buf, '/');
+       strbuf_addstr(&buf, ".git");
+
+       git_dir = read_gitfile_gently(buf.buf);
+       if (git_dir) {
+               strbuf_reset(&buf);
+               strbuf_addstr(&buf, git_dir);
+       }
+       strbuf_addch(&buf, '/');
+
+       if (buf.len >= PATH_MAX)
+               return bad_path;
+       memcpy(pathname, buf.buf, buf.len + 1);
+
+       strbuf_release(&buf);
+       len = strlen(pathname);
+
+       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)
@@ -139,6 +178,101 @@ int git_mkstemp(char *path, size_t len, const char *template)
        return mkstemp(path);
 }
 
+/* git_mkstemps() - create tmp file with suffix honoring TMPDIR variable. */
+int git_mkstemps(char *path, size_t len, const char *template, int suffix_len)
+{
+       const char *tmp;
+       size_t n;
+
+       tmp = getenv("TMPDIR");
+       if (!tmp)
+               tmp = "/tmp";
+       n = snprintf(path, len, "%s/%s", tmp, template);
+       if (len <= n) {
+               errno = ENAMETOOLONG;
+               return -1;
+       }
+       return mkstemps(path, suffix_len);
+}
+
+/* Adapted from libiberty's mkstemp.c. */
+
+#undef TMP_MAX
+#define TMP_MAX 16384
+
+int git_mkstemps_mode(char *pattern, int suffix_len, int mode)
+{
+       static const char letters[] =
+               "abcdefghijklmnopqrstuvwxyz"
+               "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+               "0123456789";
+       static const int num_letters = 62;
+       uint64_t value;
+       struct timeval tv;
+       char *template;
+       size_t len;
+       int fd, count;
+
+       len = strlen(pattern);
+
+       if (len < 6 + suffix_len) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (strncmp(&pattern[len - 6 - suffix_len], "XXXXXX", 6)) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       /*
+        * Replace pattern's XXXXXX characters with randomness.
+        * Try TMP_MAX different filenames.
+        */
+       gettimeofday(&tv, NULL);
+       value = ((size_t)(tv.tv_usec << 16)) ^ tv.tv_sec ^ getpid();
+       template = &pattern[len - 6 - suffix_len];
+       for (count = 0; count < TMP_MAX; ++count) {
+               uint64_t v = value;
+               /* Fill in the random bits. */
+               template[0] = letters[v % num_letters]; v /= num_letters;
+               template[1] = letters[v % num_letters]; v /= num_letters;
+               template[2] = letters[v % num_letters]; v /= num_letters;
+               template[3] = letters[v % num_letters]; v /= num_letters;
+               template[4] = letters[v % num_letters]; v /= num_letters;
+               template[5] = letters[v % num_letters]; v /= num_letters;
+
+               fd = open(pattern, O_CREAT | O_EXCL | O_RDWR, mode);
+               if (fd > 0)
+                       return fd;
+               /*
+                * Fatal error (EPERM, ENOSPC etc).
+                * It doesn't make sense to loop.
+                */
+               if (errno != EEXIST)
+                       break;
+               /*
+                * This is a random value.  It is only necessary that
+                * the next TMP_MAX values generated by adding 7777 to
+                * VALUE are different with (module 2^32).
+                */
+               value += 7777;
+       }
+       /* We return the null string if we can't find a unique file name.  */
+       pattern[0] = '\0';
+       return -1;
+}
+
+int git_mkstemp_mode(char *pattern, int mode)
+{
+       /* mkstemp is just mkstemps with no suffix */
+       return git_mkstemps_mode(pattern, 0, mode);
+}
+
+int gitmkstemps(char *pattern, int suffix_len)
+{
+       return git_mkstemps_mode(pattern, suffix_len, 0600);
+}
 
 int validate_headref(const char *path)
 {
@@ -191,43 +325,51 @@ int validate_headref(const char *path)
        return -1;
 }
 
-static char *user_path(char *buf, char *path, int sz)
+static struct passwd *getpw_str(const char *username, size_t len)
 {
        struct passwd *pw;
-       char *slash;
-       int len, baselen;
+       char *username_z = xmalloc(len + 1);
+       memcpy(username_z, username, len);
+       username_z[len] = '\0';
+       pw = getpwnam(username_z);
+       free(username_z);
+       return pw;
+}
 
-       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 = '/';
+/*
+ * Return a string with ~ and ~user expanded via getpw*.  If buf != NULL,
+ * then it is a newly allocated string. Returns NULL on getpw failure or
+ * if path is NULL.
+ */
+char *expand_user_path(const char *path)
+{
+       struct strbuf user_path = STRBUF_INIT;
+       const char *first_slash = strchrnul(path, '/');
+       const char *to_copy = path;
+
+       if (path == NULL)
+               goto return_null;
+       if (path[0] == '~') {
+               const char *username = path + 1;
+               size_t username_len = first_slash - username;
+               if (username_len == 0) {
+                       const char *home = getenv("HOME");
+                       if (!home)
+                               goto return_null;
+                       strbuf_add(&user_path, home, strlen(home));
+               } else {
+                       struct passwd *pw = getpw_str(username, username_len);
+                       if (!pw)
+                               goto return_null;
+                       strbuf_add(&user_path, pw->pw_dir, strlen(pw->pw_dir));
                }
-               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--;
+               to_copy = first_slash;
        }
-       if (slash && slash[1]) {
-               len = strlen(slash);
-               if (sz <= baselen + len)
-                       return NULL;
-               memcpy(buf + baselen, slash, len + 1);
-       }
-       return buf;
+       strbuf_add(&user_path, to_copy, strlen(to_copy));
+       return strbuf_detach(&user_path, NULL);
+return_null:
+       strbuf_release(&user_path);
+       return NULL;
 }
 
 /*
@@ -275,8 +417,18 @@ char *enter_repo(char *path, int strict)
                if (PATH_MAX <= len)
                        return NULL;
                if (path[0] == '~') {
-                       if (!user_path(used_path, path, PATH_MAX))
+                       char *newpath = expand_user_path(path);
+                       if (!newpath || (PATH_MAX - 10 < strlen(newpath))) {
+                               free(newpath);
                                return NULL;
+                       }
+                       /*
+                        * Copy back into the static buffer. A pity
+                        * since newpath was not bounded, but other
+                        * branches of the if are limited by PATH_MAX
+                        * anyway.
+                        */
+                       strcpy(used_path, newpath); free(newpath);
                        strcpy(validated_path, path);
                        path = used_path;
                }
@@ -303,7 +455,7 @@ char *enter_repo(char *path, int strict)
 
        if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 &&
            validate_headref("HEAD") == 0) {
-               setenv(GIT_DIR_ENVIRONMENT, ".", 1);
+               set_git_dir(".");
                check_repository_format();
                return path;
        }
@@ -361,17 +513,38 @@ int set_shared_perm(const char *path, int mode)
 const char *make_relative_path(const char *abs, const char *base)
 {
        static char buf[PATH_MAX + 1];
-       int baselen;
-       if (!base)
-               return abs;
-       baselen = strlen(base);
-       if (prefixcmp(abs, base))
+       int i = 0, j = 0;
+
+       if (!base || !base[0])
                return abs;
-       if (abs[baselen] == '/')
-               baselen++;
-       else if (base[baselen - 1] != '/')
+       while (base[i]) {
+               if (is_dir_sep(base[i])) {
+                       if (!is_dir_sep(abs[j]))
+                               return abs;
+                       while (is_dir_sep(base[i]))
+                               i++;
+                       while (is_dir_sep(abs[j]))
+                               j++;
+                       continue;
+               } else if (abs[j] != base[i]) {
+                       return abs;
+               }
+               i++;
+               j++;
+       }
+       if (
+           /* "/foo" is a prefix of "/foo" */
+           abs[j] &&
+           /* "/foo" is not a prefix of "/foobar" */
+           !is_dir_sep(base[i-1]) && !is_dir_sep(abs[j])
+          )
                return abs;
-       strcpy(buf, abs + baselen);
+       while (is_dir_sep(abs[j]))
+               j++;
+       if (!abs[j])
+               strcpy(buf, ".");
+       else
+               strcpy(buf, abs + j);
        return buf;
 }
 
@@ -548,3 +721,57 @@ char *strip_path_suffix(const char *path, const char *suffix)
                return NULL;
        return xstrndup(path, chomp_trailing_dir_sep(path, path_len));
 }
+
+int daemon_avoid_alias(const 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 canonicalization.
+        *
+        * 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;
+               }
+       }
+}
+
+int offset_1st_component(const char *path)
+{
+       if (has_dos_drive_prefix(path))
+               return 2 + is_dir_sep(path[2]);
+       return is_dir_sep(path[0]);
+}
index 291ff5b53c1883ee8fce67fbb7e2b32393ef0800..6cb0dd19344ba1169b23b7e4949033f8a9088a00 100644 (file)
@@ -172,7 +172,7 @@ sub repository {
        }
 
        if (defined $opts{Directory}) {
-               -d $opts{Directory} or throw Error::Simple("Directory not found: $!");
+               -d $opts{Directory} or throw Error::Simple("Directory not found: $opts{Directory} $!");
 
                my $search = Git->repository(WorkingCopy => $opts{Directory});
                my $dir;
@@ -185,7 +185,7 @@ sub repository {
 
                if ($dir) {
                        $dir =~ m#^/# or $dir = $opts{Directory} . '/' . $dir;
-                       $opts{Repository} = $dir;
+                       $opts{Repository} = abs_path($dir);
 
                        # If --git-dir went ok, this shouldn't die either.
                        my $prefix = $search->command_oneline('rev-parse', '--show-prefix');
@@ -204,14 +204,14 @@ sub repository {
                        $dir = $opts{Directory};
 
                        unless (-d "$dir/refs" and -d "$dir/objects" and -e "$dir/HEAD") {
-                               # Mimick git-rev-parse --git-dir error message:
+                               # Mimic git-rev-parse --git-dir error message:
                                throw Error::Simple("fatal: Not a git repository: $dir");
                        }
                        my $search = Git->repository(Repository => $dir);
                        try {
                                $search->command('symbolic-ref', 'HEAD');
                        } catch Git::Error::Command with {
-                               # Mimick git-rev-parse --git-dir error message:
+                               # Mimic git-rev-parse --git-dir error message:
                                throw Error::Simple("fatal: Not a git repository: $dir");
                        }
 
@@ -545,7 +545,7 @@ sub wc_chdir {
                or throw Error::Simple("bare repository");
 
        -d $self->wc_path().'/'.$subdir
-               or throw Error::Simple("subdir not found: $!");
+               or throw Error::Simple("subdir not found: $subdir $!");
        # Of course we will not "hold" the subdirectory so anyone
        # can delete it now and we will never know. But at least we tried.
 
@@ -842,7 +842,7 @@ sub _open_hash_and_insert_object_if_needed {
 
        ($self->{hash_object_pid}, $self->{hash_object_in},
         $self->{hash_object_out}, $self->{hash_object_ctx}) =
-               command_bidi_pipe(qw(hash-object -w --stdin-paths));
+               command_bidi_pipe(qw(hash-object -w --stdin-paths --no-filters));
 }
 
 sub _close_hash_and_insert_object {
@@ -1280,6 +1280,8 @@ sub _cmd_exec {
        my ($self, @args) = @_;
        if ($self) {
                $self->repo_path() and $ENV{'GIT_DIR'} = $self->repo_path();
+               $self->repo_path() and $self->wc_path()
+                       and $ENV{'GIT_WORK_TREE'} = $self->wc_path();
                $self->wc_path() and chdir($self->wc_path());
                $self->wc_subdir() and chdir($self->wc_subdir());
        }
index e3dd1a5547c471208c445d77263ee46e64b37451..a2ffb6402d45420dff4dcd545dfa08b57305d8cd 100644 (file)
@@ -29,16 +29,16 @@ $(makfile): ../GIT-CFLAGS Makefile
        '$(PERL_PATH_SQ)' -MError -e 'exit($$Error::VERSION < 0.15009)' || \
        echo '  cp private-Error.pm blib/lib/Error.pm' >> $@
        echo install: >> $@
-       echo '  mkdir -p "$(instdir_SQ)"' >> $@
-       echo '  $(RM) "$(instdir_SQ)/Git.pm"; cp Git.pm "$(instdir_SQ)"' >> $@
-       echo '  $(RM) "$(instdir_SQ)/Error.pm"' >> $@
+       echo '  mkdir -p "$$(DESTDIR)$(instdir_SQ)"' >> $@
+       echo '  $(RM) "$$(DESTDIR)$(instdir_SQ)/Git.pm"; cp Git.pm "$$(DESTDIR)$(instdir_SQ)"' >> $@
+       echo '  $(RM) "$$(DESTDIR)$(instdir_SQ)/Error.pm"' >> $@
        '$(PERL_PATH_SQ)' -MError -e 'exit($$Error::VERSION < 0.15009)' || \
-       echo '  cp private-Error.pm "$(instdir_SQ)/Error.pm"' >> $@
+       echo '  cp private-Error.pm "$$(DESTDIR)$(instdir_SQ)/Error.pm"' >> $@
        echo instlibdir: >> $@
        echo '  echo $(instdir_SQ)' >> $@
 else
 $(makfile): Makefile.PL ../GIT-CFLAGS
-       $(PERL_PATH) $< PREFIX='$(prefix_SQ)'
+       $(PERL_PATH) $< PREFIX='$(prefix_SQ)' INSTALL_BASE=''
 endif
 
 # this is just added comfort for calling make directly in perl dir
index 320253eb8e91eb1d914aa4e34f7d3af4649b9b39..0b9deca2cc6ef77897a23b2096a7acdd577c2482 100644 (file)
@@ -5,6 +5,14 @@ sub MY::postamble {
 instlibdir:
        @echo '$(INSTALLSITELIB)'
 
+ifneq (,$(DESTDIR))
+ifeq (0,$(shell expr '$(MM_VERSION)' '>' 6.10))
+$(error ExtUtils::MakeMaker version "$(MM_VERSION)" is older than 6.11 and so \
+       is likely incompatible with the DESTDIR mechanism.  Try setting \
+       NO_PERL_MAKEMAKER=1 instead)
+endif
+endif
+
 MAKE_FRAG
 }
 
index f5d00863a6234c16db33637d19fefd2014780e87..295ba2b16c8675d8c5c5d0a5ee6e61e044ce99dd 100644 (file)
@@ -28,7 +28,7 @@ ssize_t safe_write(int fd, const void *buf, ssize_t n)
                }
                if (!ret)
                        die("write error (disk full?)");
-               die("write error (%s)", strerror(errno));
+               die_errno("write error");
        }
        return nn;
 }
@@ -42,17 +42,19 @@ void packet_flush(int fd)
        safe_write(fd, "0000", 4);
 }
 
+void packet_buf_flush(struct strbuf *buf)
+{
+       strbuf_add(buf, "0000", 4);
+}
+
 #define hex(a) (hexchar[(a) & 15])
-void packet_write(int fd, const char *fmt, ...)
+static char buffer[1000];
+static unsigned format_packet(const char *fmt, va_list args)
 {
-       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;
@@ -60,27 +62,45 @@ void packet_write(int fd, const char *fmt, ...)
        buffer[1] = hex(n >> 8);
        buffer[2] = hex(n >> 4);
        buffer[3] = hex(n);
+       return n;
+}
+
+void packet_write(int fd, const char *fmt, ...)
+{
+       va_list args;
+       unsigned n;
+
+       va_start(args, fmt);
+       n = format_packet(fmt, args);
+       va_end(args);
        safe_write(fd, buffer, n);
 }
 
+void packet_buf_write(struct strbuf *buf, const char *fmt, ...)
+{
+       va_list args;
+       unsigned n;
+
+       va_start(args, fmt);
+       n = format_packet(fmt, args);
+       va_end(args);
+       strbuf_add(buf, buffer, n);
+}
+
 static void safe_read(int fd, void *buffer, unsigned size)
 {
        ssize_t ret = read_in_full(fd, buffer, size);
        if (ret < 0)
-               die("read error (%s)", strerror(errno));
+               die_errno("read error");
        else if (ret < size)
                die("The remote end hung up unexpectedly");
 }
 
-int packet_read_line(int fd, char *buffer, unsigned size)
+static int packet_length(const char *linelen)
 {
        int n;
-       unsigned len;
-       char linelen[4];
+       int len = 0;
 
-       safe_read(fd, linelen, 4);
-
-       len = 0;
        for (n = 0; n < 4; n++) {
                unsigned char c = linelen[n];
                len <<= 4;
@@ -96,8 +116,20 @@ int packet_read_line(int fd, char *buffer, unsigned size)
                        len += c - 'A' + 10;
                        continue;
                }
-               die("protocol error: bad line length character");
+               return -1;
        }
+       return len;
+}
+
+int packet_read_line(int fd, char *buffer, unsigned size)
+{
+       int len;
+       char linelen[4];
+
+       safe_read(fd, linelen, 4);
+       len = packet_length(linelen);
+       if (len < 0)
+               die("protocol error: bad line length character: %.4s", linelen);
        if (!len)
                return 0;
        len -= 4;
@@ -107,3 +139,31 @@ int packet_read_line(int fd, char *buffer, unsigned size)
        buffer[len] = 0;
        return len;
 }
+
+int packet_get_line(struct strbuf *out,
+       char **src_buf, size_t *src_len)
+{
+       int len;
+
+       if (*src_len < 4)
+               return -1;
+       len = packet_length(*src_buf);
+       if (len < 0)
+               return -1;
+       if (!len) {
+               *src_buf += 4;
+               *src_len -= 4;
+               return 0;
+       }
+       if (*src_len < len)
+               return -2;
+
+       *src_buf += 4;
+       *src_len -= 4;
+       len -= 4;
+
+       strbuf_add(out, *src_buf, len);
+       *src_buf += len;
+       *src_len -= len;
+       return len;
+}
index 9df653f6f5afe720870658d7093bddbf3e66beaf..1e5dcfe87c568eaa7746091b1eb059834f4913bb 100644 (file)
@@ -2,14 +2,18 @@
 #define PKTLINE_H
 
 #include "git-compat-util.h"
+#include "strbuf.h"
 
 /*
  * Silly packetized line writing interface
  */
 void packet_flush(int fd);
 void packet_write(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
+void packet_buf_flush(struct strbuf *buf);
+void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
 
 int packet_read_line(int fd, char *buffer, unsigned size);
+int packet_get_line(struct strbuf *out, char **src_buf, size_t *src_len);
 ssize_t safe_write(int, const void *, ssize_t);
 
 #endif
index 88edc5f8a9d5384e19426e6adb40e08b34d3adf2..e3d0bda31a98372eb9b6a8c2cd0fd65917a9dbde 100644 (file)
@@ -34,7 +34,9 @@ static void *preload_thread(void *_data)
        struct thread_data *p = _data;
        struct index_state *index = p->index;
        struct cache_entry **cep = index->cache + p->offset;
+       struct cache_def cache;
 
+       memset(&cache, 0, sizeof(cache));
        nr = p->nr;
        if (nr + p->offset > index->cache_nr)
                nr = index->cache_nr - p->offset;
@@ -45,10 +47,14 @@ static void *preload_thread(void *_data)
 
                if (ce_stage(ce))
                        continue;
+               if (S_ISGITLINK(ce->ce_mode))
+                       continue;
                if (ce_uptodate(ce))
                        continue;
                if (!ce_path_match(ce, p->pathspec))
                        continue;
+               if (threaded_has_symlink_leading_path(&cache, ce->name, ce_namelen(ce)))
+                       continue;
                if (lstat(ce->name, &st))
                        continue;
                if (ie_match_stat(index, ce, &st, CE_MATCH_RACY_IS_DIRTY))
@@ -72,7 +78,7 @@ static void preload_index(struct index_state *index, const char **pathspec)
        if (threads > MAX_PARALLEL)
                threads = MAX_PARALLEL;
        offset = 0;
-       work = (index->cache_nr + threads - 1) / threads;
+       work = DIV_ROUND_UP(index->cache_nr, threads);
        for (i = 0; i < threads; i++) {
                struct thread_data *p = data+i;
                p->index = index;
index a0ef356558f4cdb148010f1b47dbd3fcc363d8ba..f85444b27d4815547f7cef542291b1f6616f77d5 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -6,9 +6,22 @@
 #include "string-list.h"
 #include "mailmap.h"
 #include "log-tree.h"
+#include "notes.h"
 #include "color.h"
+#include "reflog-walk.h"
 
 static char *user_format;
+static struct cmt_fmt_map {
+       const char *name;
+       enum cmit_fmt format;
+       int is_tformat;
+       int is_alias;
+       const char *user_format;
+} *commit_formats;
+static size_t builtin_formats_len;
+static size_t commit_formats_len;
+static size_t commit_formats_alloc;
+static struct cmt_fmt_map *find_commit_format(const char *sought);
 
 static void save_user_format(struct rev_info *rev, const char *cp, int is_tformat)
 {
@@ -19,22 +32,118 @@ static void save_user_format(struct rev_info *rev, const char *cp, int is_tforma
        rev->commit_format = CMIT_FMT_USERFORMAT;
 }
 
-void get_commit_format(const char *arg, struct rev_info *rev)
+static int git_pretty_formats_config(const char *var, const char *value, void *cb)
 {
+       struct cmt_fmt_map *commit_format = NULL;
+       const char *name;
+       const char *fmt;
        int i;
-       static 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 },
+
+       if (prefixcmp(var, "pretty."))
+               return 0;
+
+       name = var + strlen("pretty.");
+       for (i = 0; i < builtin_formats_len; i++) {
+               if (!strcmp(commit_formats[i].name, name))
+                       return 0;
+       }
+
+       for (i = builtin_formats_len; i < commit_formats_len; i++) {
+               if (!strcmp(commit_formats[i].name, name)) {
+                       commit_format = &commit_formats[i];
+                       break;
+               }
+       }
+
+       if (!commit_format) {
+               ALLOC_GROW(commit_formats, commit_formats_len+1,
+                          commit_formats_alloc);
+               commit_format = &commit_formats[commit_formats_len];
+               memset(commit_format, 0, sizeof(*commit_format));
+               commit_formats_len++;
+       }
+
+       commit_format->name = xstrdup(name);
+       commit_format->format = CMIT_FMT_USERFORMAT;
+       git_config_string(&fmt, var, value);
+       if (!prefixcmp(fmt, "format:") || !prefixcmp(fmt, "tformat:")) {
+               commit_format->is_tformat = fmt[0] == 't';
+               fmt = strchr(fmt, ':') + 1;
+       } else if (strchr(fmt, '%'))
+               commit_format->is_tformat = 1;
+       else
+               commit_format->is_alias = 1;
+       commit_format->user_format = fmt;
+
+       return 0;
+}
+
+static void setup_commit_formats(void)
+{
+       struct cmt_fmt_map builtin_formats[] = {
+               { "raw",        CMIT_FMT_RAW,           0 },
+               { "medium",     CMIT_FMT_MEDIUM,        0 },
+               { "short",      CMIT_FMT_SHORT,         0 },
+               { "email",      CMIT_FMT_EMAIL,         0 },
+               { "fuller",     CMIT_FMT_FULLER,        0 },
+               { "full",       CMIT_FMT_FULL,          0 },
+               { "oneline",    CMIT_FMT_ONELINE,       1 }
        };
+       commit_formats_len = ARRAY_SIZE(builtin_formats);
+       builtin_formats_len = commit_formats_len;
+       ALLOC_GROW(commit_formats, commit_formats_len, commit_formats_alloc);
+       memcpy(commit_formats, builtin_formats,
+              sizeof(*builtin_formats)*ARRAY_SIZE(builtin_formats));
+
+       git_config(git_pretty_formats_config, NULL);
+}
+
+static struct cmt_fmt_map *find_commit_format_recursive(const char *sought,
+                                                       const char *original,
+                                                       int num_redirections)
+{
+       struct cmt_fmt_map *found = NULL;
+       size_t found_match_len = 0;
+       int i;
+
+       if (num_redirections >= commit_formats_len)
+               die("invalid --pretty format: "
+                   "'%s' references an alias which points to itself",
+                   original);
+
+       for (i = 0; i < commit_formats_len; i++) {
+               size_t match_len;
+
+               if (prefixcmp(commit_formats[i].name, sought))
+                       continue;
+
+               match_len = strlen(commit_formats[i].name);
+               if (found == NULL || found_match_len > match_len) {
+                       found = &commit_formats[i];
+                       found_match_len = match_len;
+               }
+       }
+
+       if (found && found->is_alias) {
+               found = find_commit_format_recursive(found->user_format,
+                                                    original,
+                                                    num_redirections+1);
+       }
+
+       return found;
+}
+
+static struct cmt_fmt_map *find_commit_format(const char *sought)
+{
+       if (!commit_formats)
+               setup_commit_formats();
+
+       return find_commit_format_recursive(sought, sought, 0);
+}
+
+void get_commit_format(const char *arg, struct rev_info *rev)
+{
+       struct cmt_fmt_map *commit_format;
 
        rev->use_terminator = 0;
        if (!arg || !*arg) {
@@ -45,21 +154,22 @@ void get_commit_format(const char *arg, struct rev_info *rev)
                save_user_format(rev, strchr(arg, ':') + 1, arg[0] == 't');
                return;
        }
-       for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {
-               if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len) &&
-                   !strncmp(arg, cmt_fmts[i].n, strlen(arg))) {
-                       if (cmt_fmts[i].v == CMIT_FMT_ONELINE)
-                               rev->use_terminator = 1;
-                       rev->commit_format = cmt_fmts[i].v;
-                       return;
-               }
-       }
+
        if (strchr(arg, '%')) {
                save_user_format(rev, arg, 1);
                return;
        }
 
-       die("invalid --pretty format: %s", arg);
+       commit_format = find_commit_format(arg);
+       if (!commit_format)
+               die("invalid --pretty format: %s", arg);
+
+       rev->commit_format = commit_format->format;
+       rev->use_terminator = commit_format->is_tformat;
+       if (commit_format->format == CMIT_FMT_USERFORMAT) {
+               save_user_format(rev, commit_format->user_format,
+                                commit_format->is_tformat);
+       }
 }
 
 /*
@@ -81,11 +191,23 @@ static int get_one_line(const char *msg)
 }
 
 /* High bit set, or ISO-2022-INT */
-int non_ascii(int ch)
+static int non_ascii(int ch)
 {
        return !isascii(ch) || ch == '\033';
 }
 
+int has_non_ascii(const char *s)
+{
+       int ch;
+       if (!s)
+               return 0;
+       while ((ch = *s++) != '\0') {
+               if (non_ascii(ch))
+                       return 1;
+       }
+       return 0;
+}
+
 static int is_rfc2047_special(char ch)
 {
        return (non_ascii(ch) || (ch == '=') || (ch == '?') || (ch == '_'));
@@ -284,7 +406,7 @@ static char *replace_encoding_header(char *buf, const char *encoding)
 static char *logmsg_reencode(const struct commit *commit,
                             const char *output_encoding)
 {
-       static const char *utf8 = "utf-8";
+       static const char *utf8 = "UTF-8";
        const char *use_encoding;
        char *encoding;
        char *out;
@@ -430,9 +552,10 @@ struct chunk {
 
 struct format_commit_context {
        const struct commit *commit;
-       enum date_mode dmode;
+       const struct pretty_print_context *pretty_ctx;
        unsigned commit_header_parsed:1;
        unsigned commit_message_parsed:1;
+       size_t width, indent1, indent2;
 
        /* These offsets are relative to the start of the commit message. */
        struct chunk author;
@@ -446,6 +569,7 @@ struct format_commit_context {
        struct chunk abbrev_commit_hash;
        struct chunk abbrev_tree_hash;
        struct chunk abbrev_parent_hashes;
+       size_t wrap_start;
 };
 
 static int add_again(struct strbuf *sb, struct chunk *chunk)
@@ -571,7 +695,7 @@ static void format_decoration(struct strbuf *sb, const struct commit *commit)
        struct name_decoration *d;
        const char *prefix = " (";
 
-       load_ref_decorations();
+       load_ref_decorations(DECORATE_SHORT_REFS);
        d = lookup_decoration(&name_decoration, &commit->object);
        while (d) {
                strbuf_addstr(sb, prefix);
@@ -583,8 +707,37 @@ static void format_decoration(struct strbuf *sb, const struct commit *commit)
                strbuf_addch(sb, ')');
 }
 
-static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
-                               void *context)
+static void strbuf_wrap(struct strbuf *sb, size_t pos,
+                       size_t width, size_t indent1, size_t indent2)
+{
+       struct strbuf tmp = STRBUF_INIT;
+
+       if (pos)
+               strbuf_add(&tmp, sb->buf, pos);
+       strbuf_add_wrapped_text(&tmp, sb->buf + pos,
+                               (int) indent1, (int) indent2, (int) width);
+       strbuf_swap(&tmp, sb);
+       strbuf_release(&tmp);
+}
+
+static void rewrap_message_tail(struct strbuf *sb,
+                               struct format_commit_context *c,
+                               size_t new_width, size_t new_indent1,
+                               size_t new_indent2)
+{
+       if (c->width == new_width && c->indent1 == new_indent1 &&
+           c->indent2 == new_indent2)
+               return;
+       if (c->wrap_start < sb->len)
+               strbuf_wrap(sb, c->wrap_start, c->width, c->indent1, c->indent2);
+       c->wrap_start = sb->len;
+       c->width = new_width;
+       c->indent1 = new_indent1;
+       c->indent2 = new_indent2;
+}
+
+static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
+                               void *context)
 {
        struct format_commit_context *c = context;
        const struct commit *commit = c->commit;
@@ -633,6 +786,30 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
                        return 3;
                } else
                        return 0;
+       case 'w':
+               if (placeholder[1] == '(') {
+                       unsigned long width = 0, indent1 = 0, indent2 = 0;
+                       char *next;
+                       const char *start = placeholder + 2;
+                       const char *end = strchr(start, ')');
+                       if (!end)
+                               return 0;
+                       if (end > start) {
+                               width = strtoul(start, &next, 10);
+                               if (*next == ',') {
+                                       indent1 = strtoul(next + 1, &next, 10);
+                                       if (*next == ',') {
+                                               indent2 = strtoul(next + 1,
+                                                                &next, 10);
+                                       }
+                               }
+                               if (*next != ')')
+                                       return 0;
+                       }
+                       rewrap_message_tail(sb, c, width, indent1, indent2);
+                       return end - placeholder + 1;
+               } else
+                       return 0;
        }
 
        /* these depend on the commit */
@@ -647,7 +824,7 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
                if (add_again(sb, &c->abbrev_commit_hash))
                        return 1;
                strbuf_addstr(sb, find_unique_abbrev(commit->object.sha1,
-                                                    DEFAULT_ABBREV));
+                                                    c->pretty_ctx->abbrev));
                c->abbrev_commit_hash.len = sb->len - c->abbrev_commit_hash.off;
                return 1;
        case 'T':               /* tree hash */
@@ -657,7 +834,7 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
                if (add_again(sb, &c->abbrev_tree_hash))
                        return 1;
                strbuf_addstr(sb, find_unique_abbrev(commit->tree->object.sha1,
-                                                    DEFAULT_ABBREV));
+                                                    c->pretty_ctx->abbrev));
                c->abbrev_tree_hash.len = sb->len - c->abbrev_tree_hash.off;
                return 1;
        case 'P':               /* parent hashes */
@@ -674,7 +851,8 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
                        if (p != commit->parents)
                                strbuf_addch(sb, ' ');
                        strbuf_addstr(sb, find_unique_abbrev(
-                                       p->item->object.sha1, DEFAULT_ABBREV));
+                                       p->item->object.sha1,
+                                       c->pretty_ctx->abbrev));
                }
                c->abbrev_parent_hashes.len = sb->len -
                                              c->abbrev_parent_hashes.off;
@@ -689,6 +867,30 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
        case 'd':
                format_decoration(sb, commit);
                return 1;
+       case 'g':               /* reflog info */
+               switch(placeholder[1]) {
+               case 'd':       /* reflog selector */
+               case 'D':
+                       if (c->pretty_ctx->reflog_info)
+                               get_reflog_selector(sb,
+                                                   c->pretty_ctx->reflog_info,
+                                                   c->pretty_ctx->date_mode,
+                                                   (placeholder[1] == 'd'));
+                       return 2;
+               case 's':       /* reflog message */
+                       if (c->pretty_ctx->reflog_info)
+                               get_reflog_message(sb, c->pretty_ctx->reflog_info);
+                       return 2;
+               }
+               return 0;       /* unknown %g placeholder */
+       case 'N':
+               if (c->pretty_ctx->show_notes) {
+                       format_display_notes(commit->object.sha1, sb,
+                                   git_log_output_encoding ? git_log_output_encoding
+                                                           : git_commit_encoding, 0);
+                       return 1;
+               }
+               return 0;
        }
 
        /* For the rest we have to parse the commit header. */
@@ -699,14 +901,18 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
        case 'a':       /* author ... */
                return format_person_part(sb, placeholder[1],
                                   msg + c->author.off, c->author.len,
-                                  c->dmode);
+                                  c->pretty_ctx->date_mode);
        case 'c':       /* committer ... */
                return format_person_part(sb, placeholder[1],
                                   msg + c->committer.off, c->committer.len,
-                                  c->dmode);
+                                  c->pretty_ctx->date_mode);
        case 'e':       /* encoding */
                strbuf_add(sb, msg + c->encoding.off, c->encoding.len);
                return 1;
+       case 'B':       /* raw body */
+               /* message_off is always left at the initial newline */
+               strbuf_addstr(sb, msg + c->message_off + 1);
+               return 1;
        }
 
        /* Now we need to parse the commit message. */
@@ -727,16 +933,92 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
        return 0;       /* unknown placeholder */
 }
 
+static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
+                                void *context)
+{
+       int consumed;
+       size_t orig_len;
+       enum {
+               NO_MAGIC,
+               ADD_LF_BEFORE_NON_EMPTY,
+               DEL_LF_BEFORE_EMPTY,
+               ADD_SP_BEFORE_NON_EMPTY
+       } magic = NO_MAGIC;
+
+       switch (placeholder[0]) {
+       case '-':
+               magic = DEL_LF_BEFORE_EMPTY;
+               break;
+       case '+':
+               magic = ADD_LF_BEFORE_NON_EMPTY;
+               break;
+       case ' ':
+               magic = ADD_SP_BEFORE_NON_EMPTY;
+               break;
+       default:
+               break;
+       }
+       if (magic != NO_MAGIC)
+               placeholder++;
+
+       orig_len = sb->len;
+       consumed = format_commit_one(sb, placeholder, context);
+       if (magic == NO_MAGIC)
+               return consumed;
+
+       if ((orig_len == sb->len) && magic == DEL_LF_BEFORE_EMPTY) {
+               while (sb->len && sb->buf[sb->len - 1] == '\n')
+                       strbuf_setlen(sb, sb->len - 1);
+       } else if (orig_len != sb->len) {
+               if (magic == ADD_LF_BEFORE_NON_EMPTY)
+                       strbuf_insert(sb, orig_len, "\n", 1);
+               else if (magic == ADD_SP_BEFORE_NON_EMPTY)
+                       strbuf_insert(sb, orig_len, " ", 1);
+       }
+       return consumed + 1;
+}
+
+static size_t userformat_want_item(struct strbuf *sb, const char *placeholder,
+                                  void *context)
+{
+       struct userformat_want *w = context;
+
+       if (*placeholder == '+' || *placeholder == '-' || *placeholder == ' ')
+               placeholder++;
+
+       switch (*placeholder) {
+       case 'N':
+               w->notes = 1;
+               break;
+       }
+       return 0;
+}
+
+void userformat_find_requirements(const char *fmt, struct userformat_want *w)
+{
+       struct strbuf dummy = STRBUF_INIT;
+
+       if (!fmt) {
+               if (!user_format)
+                       return;
+               fmt = user_format;
+       }
+       strbuf_expand(&dummy, user_format, userformat_want_item, w);
+       strbuf_release(&dummy);
+}
+
 void format_commit_message(const struct commit *commit,
-                          const void *format, struct strbuf *sb,
-                          enum date_mode dmode)
+                          const char *format, struct strbuf *sb,
+                          const struct pretty_print_context *pretty_ctx)
 {
        struct format_commit_context context;
 
        memset(&context, 0, sizeof(context));
        context.commit = commit;
-       context.dmode = dmode;
+       context.pretty_ctx = pretty_ctx;
+       context.wrap_start = sb->len;
        strbuf_expand(sb, format, format_commit_item, &context);
+       rewrap_message_tail(sb, &context, 0, 0, 0);
 }
 
 static void pp_header(enum cmit_fmt fmt,
@@ -881,25 +1163,25 @@ char *reencode_commit_message(const struct commit *commit, const char **encoding
                    ? git_log_output_encoding
                    : git_commit_encoding);
        if (!encoding)
-               encoding = "utf-8";
+               encoding = "UTF-8";
        if (encoding_p)
                *encoding_p = encoding;
        return logmsg_reencode(commit, encoding);
 }
 
 void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
-                        struct strbuf *sb, int abbrev,
-                        const char *subject, const char *after_subject,
-                        enum date_mode dmode, int need_8bit_cte)
+                        struct strbuf *sb,
+                        const struct pretty_print_context *context)
 {
        unsigned long beginning_of_body;
        int indent = 4;
        const char *msg = commit->buffer;
        char *reencoded;
        const char *encoding;
+       int need_8bit_cte = context->need_8bit_cte;
 
        if (fmt == CMIT_FMT_USERFORMAT) {
-               format_commit_message(commit, user_format, sb, dmode);
+               format_commit_message(commit, user_format, sb, context);
                return;
        }
 
@@ -934,8 +1216,9 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
                }
        }
 
-       pp_header(fmt, abbrev, dmode, encoding, commit, &msg, sb);
-       if (fmt != CMIT_FMT_ONELINE && !subject) {
+       pp_header(fmt, context->abbrev, context->date_mode, encoding,
+                 commit, &msg, sb);
+       if (fmt != CMIT_FMT_ONELINE && !context->subject) {
                strbuf_addch(sb, '\n');
        }
 
@@ -944,8 +1227,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
 
        /* These formats treat the title line specially. */
        if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
-               pp_title_line(fmt, &msg, sb, subject,
-                             after_subject, encoding, need_8bit_cte);
+               pp_title_line(fmt, &msg, sb, context->subject,
+                             context->after_subject, encoding, need_8bit_cte);
 
        beginning_of_body = sb->len;
        if (fmt != CMIT_FMT_ONELINE)
@@ -963,5 +1246,10 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
         */
        if (fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)
                strbuf_addch(sb, '\n');
+
+       if (context->show_notes)
+               format_display_notes(commit->object.sha1, sb, encoding,
+                                    NOTES_SHOW_HEADER | NOTES_INDENT);
+
        free(reencoded);
 }
index 621c34edc2a3a6a9bceb0f708ed5b0df05145fb5..3971f49f4ddaa774806bd1717379a1edd22ba17c 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Simple text-based progress display module for GIT
  *
- * Copyright (c) 2007 by Nicolas Pitre <nico@cam.org>
+ * Copyright (c) 2007 by Nicolas Pitre <nico@fluxnic.net>
  *
  * 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
@@ -131,7 +131,13 @@ static void throughput_string(struct throughput *tp, off_t total,
        } else {
                l -= snprintf(tp->display, l, ", %u bytes", (int)total);
        }
-       if (rate)
+
+       if (rate > 1 << 10) {
+               int x = rate + 5;  /* for rounding */
+               snprintf(tp->display + sizeof(tp->display) - l, l,
+                        " | %u.%2.2u MiB/s",
+                        x >> 10, ((x & ((1 << 10) - 1)) * 100) >> 10);
+       } else if (rate)
                snprintf(tp->display + sizeof(tp->display) - l, l,
                         " | %u KiB/s", rate);
 }
diff --git a/quote.c b/quote.c
index 7a49fcf69671646a0d3ba6de6478cfc6767c31fe..63d3b018183abc05a5231dfd7e134dd7394f7a9b 100644 (file)
--- a/quote.c
+++ b/quote.c
@@ -72,7 +72,7 @@ void sq_quote_argv(struct strbuf *dst, const char** argv, size_t maxlen)
        }
 }
 
-char *sq_dequote_step(char *arg, char **next)
+static char *sq_dequote_step(char *arg, char **next)
 {
        char *dst = arg;
        char *src = arg;
@@ -213,7 +213,7 @@ static size_t quote_c_style_counted(const char *name, ssize_t maxlen,
                int ch;
 
                len = next_quote_pos(p, maxlen);
-               if (len == maxlen || !p[len])
+               if (len == maxlen || (maxlen < 0 && !p[len]))
                        break;
 
                if (!no_dq && p == name)
@@ -223,6 +223,8 @@ static size_t quote_c_style_counted(const char *name, ssize_t maxlen,
                EMIT('\\');
                p += len;
                ch = (unsigned char)*p++;
+               if (maxlen >= 0)
+                       maxlen -= len + 1;
                if (sq_lookup[ch] >= ' ') {
                        EMIT(sq_lookup[ch]);
                } else {
@@ -272,8 +274,8 @@ void write_name_quoted(const char *name, FILE *fp, int terminator)
        fputc(terminator, fp);
 }
 
-extern void write_name_quotedpfx(const char *pfx, size_t pfxlen,
-                                 const char *name, FILE *fp, int terminator)
+void write_name_quotedpfx(const char *pfx, size_t pfxlen,
+                         const char *name, FILE *fp, int terminator)
 {
        int needquote = 0;
 
@@ -293,42 +295,75 @@ extern void write_name_quotedpfx(const char *pfx, size_t pfxlen,
        fputc(terminator, fp);
 }
 
-/* quote path as relative to the given prefix */
-char *quote_path_relative(const char *in, int len,
-                         struct strbuf *out, const char *prefix)
+static const char *path_relative(const char *in, int len,
+                                struct strbuf *sb, const char *prefix,
+                                int prefix_len);
+
+void write_name_quoted_relative(const char *name, size_t len,
+                               const char *prefix, size_t prefix_len,
+                               FILE *fp, int terminator)
 {
-       int needquote;
+       struct strbuf sb = STRBUF_INIT;
+
+       name = path_relative(name, len, &sb, prefix, prefix_len);
+       write_name_quoted(name, fp, terminator);
+
+       strbuf_release(&sb);
+}
+
+/*
+ * Give path as relative to prefix.
+ *
+ * The strbuf may or may not be used, so do not assume it contains the
+ * returned path.
+ */
+static const char *path_relative(const char *in, int len,
+                                struct strbuf *sb, const char *prefix,
+                                int prefix_len)
+{
+       int off, i;
 
        if (len < 0)
                len = strlen(in);
+       if (prefix && prefix_len < 0)
+               prefix_len = strlen(prefix);
+
+       off = 0;
+       i = 0;
+       while (i < prefix_len && i < len && prefix[i] == in[i]) {
+               if (prefix[i] == '/')
+                       off = i + 1;
+               i++;
+       }
+       in += off;
+       len -= off;
+
+       if (i >= prefix_len)
+               return in;
 
-       /* "../" prefix itself does not need quoting, but "in" might. */
-       needquote = next_quote_pos(in, len) < len;
-       strbuf_setlen(out, 0);
-       strbuf_grow(out, len);
-
-       if (needquote)
-               strbuf_addch(out, '"');
-       if (prefix) {
-               int off = 0;
-               while (prefix[off] && off < len && prefix[off] == in[off])
-                       if (prefix[off] == '/') {
-                               prefix += off + 1;
-                               in += off + 1;
-                               len -= off + 1;
-                               off = 0;
-                       } else
-                               off++;
-
-               for (; *prefix; prefix++)
-                       if (*prefix == '/')
-                               strbuf_addstr(out, "../");
+       strbuf_reset(sb);
+       strbuf_grow(sb, len);
+
+       while (i < prefix_len) {
+               if (prefix[i] == '/')
+                       strbuf_addstr(sb, "../");
+               i++;
        }
+       strbuf_add(sb, in, len);
+
+       return sb->buf;
+}
 
-       quote_c_style_counted (in, len, out, NULL, 1);
+/* quote path as relative to the given prefix */
+char *quote_path_relative(const char *in, int len,
+                         struct strbuf *out, const char *prefix)
+{
+       struct strbuf sb = STRBUF_INIT;
+       const char *rel = path_relative(in, len, &sb, prefix, -1);
+       strbuf_reset(out);
+       quote_c_style_counted(rel, strlen(rel), out, NULL, 0);
+       strbuf_release(&sb);
 
-       if (needquote)
-               strbuf_addch(out, '"');
        if (!out->len)
                strbuf_addstr(out, "./");
 
diff --git a/quote.h b/quote.h
index 66730f2bff3cee42bc7c670e2a6d7da240db1d08..38003bff5f97a11e051b106522a8548d43d24f6c 100644 (file)
--- a/quote.h
+++ b/quote.h
@@ -45,7 +45,6 @@ extern char *sq_dequote(char *);
  * next argument that should be passed as first parameter. When there
  * is no more argument to be dequoted, "next" is updated to point to NULL.
  */
-extern char *sq_dequote_step(char *arg, char **next);
 extern int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc);
 
 extern int unquote_c_style(struct strbuf *, const char *quoted, const char **endp);
@@ -55,9 +54,12 @@ extern void quote_two_c_style(struct strbuf *, const char *, const char *, int);
 extern void write_name_quoted(const char *name, FILE *, int terminator);
 extern void write_name_quotedpfx(const char *pfx, size_t pfxlen,
                                  const char *name, FILE *, int terminator);
+extern void write_name_quoted_relative(const char *name, size_t len,
+               const char *prefix, size_t prefix_len,
+               FILE *fp, int terminator);
 
 /* quote path as relative to the given prefix */
-char *quote_path_relative(const char *in, int len,
+extern char *quote_path_relative(const char *in, int len,
                          struct strbuf *out, const char *prefix);
 
 /* quoting as a string literal for other languages */
index b515fa2de332cc570a8a32861bd8d6491b61133e..a03fabf060081e9fa5e0d753cd0d9cf4bca5062e 100644 (file)
@@ -90,7 +90,7 @@ static void walk_commit_list(struct rev_info *revs)
 {
        int i;
        struct commit *commit;
-       struct object_array objects = { 0, 0, NULL };
+       struct object_array objects = OBJECT_ARRAY_INIT;
 
        /* Walk all commits, process their trees */
        while ((commit = get_revision(revs)) != NULL)
index 3f587110cb9d7be1890b7db68a0bdac35d48cd35..1f42473e8070a05ada8c56b0d60537227a5223ec 100644 (file)
 #include "dir.h"
 #include "tree.h"
 #include "commit.h"
-#include "diff.h"
-#include "diffcore.h"
-#include "revision.h"
 #include "blob.h"
+#include "resolve-undo.h"
+
+static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
 
 /* Index extensions.
  *
@@ -26,6 +26,7 @@
 
 #define CACHE_EXT(s) ( (s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]) )
 #define CACHE_EXT_TREE 0x54524545      /* "TREE" */
+#define CACHE_EXT_RESOLVE_UNDO 0x52455543 /* "REUC" */
 
 struct index_state the_index;
 
@@ -156,7 +157,7 @@ static int ce_modified_check_fs(struct cache_entry *ce, struct stat *st)
        return 0;
 }
 
-int is_empty_blob_sha1(const unsigned char *sha1)
+static int is_empty_blob_sha1(const unsigned char *sha1)
 {
        static const unsigned char empty_blob_sha1[20] = {
                0xe6,0x9d,0xe2,0x9b,0xb2,0xd1,0xd6,0x43,0x4b,0x8b,
@@ -259,12 +260,17 @@ int ie_match_stat(const struct index_state *istate,
 {
        unsigned int changed;
        int ignore_valid = options & CE_MATCH_IGNORE_VALID;
+       int ignore_skip_worktree = options & CE_MATCH_IGNORE_SKIP_WORKTREE;
        int assume_racy_is_modified = options & CE_MATCH_RACY_IS_DIRTY;
 
        /*
         * If it's marked as always valid in the index, it's
         * valid whatever the checked-out copy says.
+        *
+        * skip-worktree has the same effect with higher precedence
         */
+       if (!ignore_skip_worktree && ce_skip_worktree(ce))
+               return 0;
        if (!ignore_valid && (ce->ce_flags & CE_VALID))
                return 0;
 
@@ -449,6 +455,7 @@ int remove_index_entry_at(struct index_state *istate, int pos)
 {
        struct cache_entry *ce = istate->cache[pos];
 
+       record_resolve_undo(istate, ce);
        remove_name_hash(ce);
        istate->cache_changed = 1;
        istate->cache_nr--;
@@ -564,7 +571,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
        int size, namelen, was_same;
        mode_t st_mode = st->st_mode;
        struct cache_entry *ce, *alias;
-       unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_RACY_IS_DIRTY;
+       unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE|CE_MATCH_RACY_IS_DIRTY;
        int verbose = flags & (ADD_CACHE_VERBOSE | ADD_CACHE_PRETEND);
        int pretend = flags & ADD_CACHE_PRETEND;
        int intent_only = flags & ADD_CACHE_INTENT;
@@ -605,7 +612,8 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
        if (alias && !ce_stage(alias) && !ie_match_stat(istate, alias, st, ce_option)) {
                /* Nothing changed, really */
                free(ce);
-               ce_mark_uptodate(alias);
+               if (!S_ISGITLINK(alias->ce_mode))
+                       ce_mark_uptodate(alias);
                alias->ce_flags |= CE_ADDED;
                return 0;
        }
@@ -638,7 +646,7 @@ int add_file_to_index(struct index_state *istate, const char *path, int flags)
 {
        struct stat st;
        if (lstat(path, &st))
-               die("%s: unable to stat (%s)", path, strerror(errno));
+               die_errno("unable to stat '%s'", path);
        return add_to_index(istate, path, &st, flags);
 }
 
@@ -1000,14 +1008,20 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
        struct cache_entry *updated;
        int changed, size;
        int ignore_valid = options & CE_MATCH_IGNORE_VALID;
+       int ignore_skip_worktree = options & CE_MATCH_IGNORE_SKIP_WORKTREE;
 
        if (ce_uptodate(ce))
                return ce;
 
        /*
-        * CE_VALID means the user promised us that the change to
-        * the work tree does not matter and told us not to worry.
+        * CE_VALID or CE_SKIP_WORKTREE means the user promised us
+        * that the change to the work tree does not matter and told
+        * us not to worry.
         */
+       if (!ignore_skip_worktree && ce_skip_worktree(ce)) {
+               ce_mark_uptodate(ce);
+               return ce;
+       }
        if (!ignore_valid && (ce->ce_flags & CE_VALID)) {
                ce_mark_uptodate(ce);
                return ce;
@@ -1037,7 +1051,8 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
                         * because CE_UPTODATE flag is in-core only;
                         * we are not going to write this change out.
                         */
-                       ce_mark_uptodate(ce);
+                       if (!S_ISGITLINK(ce->ce_mode))
+                               ce_mark_uptodate(ce);
                        return ce;
                }
        }
@@ -1065,7 +1080,18 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
        return updated;
 }
 
-int refresh_index(struct index_state *istate, unsigned int flags, const char **pathspec, char *seen)
+static void show_file(const char * fmt, const char * name, int in_porcelain,
+                     int * first, char *header_msg)
+{
+       if (in_porcelain && *first && header_msg) {
+               printf("%s\n", header_msg);
+               *first=0;
+       }
+       printf(fmt, name);
+}
+
+int refresh_index(struct index_state *istate, unsigned int flags, const char **pathspec,
+                 char *seen, char *header_msg)
 {
        int i;
        int has_errors = 0;
@@ -1074,11 +1100,14 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
        int quiet = (flags & REFRESH_QUIET) != 0;
        int not_new = (flags & REFRESH_IGNORE_MISSING) != 0;
        int ignore_submodules = (flags & REFRESH_IGNORE_SUBMODULES) != 0;
+       int first = 1;
+       int in_porcelain = (flags & REFRESH_IN_PORCELAIN);
        unsigned int options = really ? CE_MATCH_IGNORE_VALID : 0;
-       const char *needs_update_message;
+       const char *needs_update_fmt;
+       const char *needs_merge_fmt;
 
-       needs_update_message = ((flags & REFRESH_SAY_CHANGED)
-                               ? "locally modified" : "needs update");
+       needs_update_fmt = (in_porcelain ? "M\t%s\n" : "%s: needs update\n");
+       needs_merge_fmt = (in_porcelain ? "U\t%s\n" : "%s: needs merge\n");
        for (i = 0; i < istate->cache_nr; i++) {
                struct cache_entry *ce, *new;
                int cache_errno = 0;
@@ -1094,7 +1123,7 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
                        i--;
                        if (allow_unmerged)
                                continue;
-                       printf("%s: needs merge\n", ce->name);
+                       show_file(needs_merge_fmt, ce->name, in_porcelain, &first, header_msg);
                        has_errors = 1;
                        continue;
                }
@@ -1117,7 +1146,7 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
                        }
                        if (quiet)
                                continue;
-                       printf("%s: %s\n", ce->name, needs_update_message);
+                       show_file(needs_update_fmt, ce->name, in_porcelain, &first, header_msg);
                        has_errors = 1;
                        continue;
                }
@@ -1127,7 +1156,7 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
        return has_errors;
 }
 
-struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really)
+static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really)
 {
        return refresh_cache_ent(&the_index, ce, really, NULL);
 }
@@ -1156,6 +1185,9 @@ static int read_index_extension(struct index_state *istate,
        case CACHE_EXT_TREE:
                istate->cache_tree = cache_tree_read(data, sz);
                break;
+       case CACHE_EXT_RESOLVE_UNDO:
+               istate->resolve_undo = resolve_undo_read(data, sz);
+               break;
        default:
                if (*ext < 'A' || 'Z' < *ext)
                        return error("index uses %.4s extension, which we do not understand",
@@ -1251,11 +1283,11 @@ int read_index_from(struct index_state *istate, const char *path)
        if (fd < 0) {
                if (errno == ENOENT)
                        return 0;
-               die("index file open failed (%s)", strerror(errno));
+               die_errno("index file open failed");
        }
 
        if (fstat(fd, &st))
-               die("cannot stat the open index (%s)", strerror(errno));
+               die_errno("cannot stat the open index");
 
        errno = EINVAL;
        mmap_size = xsize_t(st.st_size);
@@ -1265,7 +1297,7 @@ int read_index_from(struct index_state *istate, const char *path)
        mmap = xmmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
        close(fd);
        if (mmap == MAP_FAILED)
-               die("unable to map index file");
+               die_errno("unable to map index file");
 
        hdr = mmap;
        if (verify_hdr(hdr, mmap_size) < 0)
@@ -1308,7 +1340,7 @@ int read_index_from(struct index_state *istate, const char *path)
                 * extension name (4-byte) and section length
                 * in 4-byte network byte order.
                 */
-               unsigned long extsize;
+               uint32_t extsize;
                memcpy(&extsize, (char *)mmap + src_offset + 4, 4);
                extsize = ntohl(extsize);
                if (read_index_extension(istate,
@@ -1335,6 +1367,7 @@ int is_index_unborn(struct index_state *istate)
 
 int discard_index(struct index_state *istate)
 {
+       resolve_undo_clear_index(istate);
        istate->cache_nr = 0;
        istate->cache_changed = 0;
        istate->timestamp.sec = 0;
@@ -1483,6 +1516,7 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce)
        int size = ondisk_ce_size(ce);
        struct ondisk_cache_entry *ondisk = xcalloc(1, size);
        char *name;
+       int result;
 
        ondisk->ctime.sec = htonl(ce->ce_ctime.sec);
        ondisk->mtime.sec = htonl(ce->ce_mtime.sec);
@@ -1506,7 +1540,9 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce)
                name = ondisk->name;
        memcpy(name, ce->name, ce_namelen(ce));
 
-       return ce_write(c, fd, ondisk, size);
+       result = ce_write(c, fd, ondisk, size);
+       free(ondisk);
+       return result;
 }
 
 int write_index(struct index_state *istate, int newfd)
@@ -1560,6 +1596,17 @@ int write_index(struct index_state *istate, int newfd)
                if (err)
                        return -1;
        }
+       if (istate->resolve_undo) {
+               struct strbuf sb = STRBUF_INIT;
+
+               resolve_undo_write(&sb, istate->resolve_undo);
+               err = write_index_ext_header(&c, newfd, CACHE_EXT_RESOLVE_UNDO,
+                                            sb.len) < 0
+                       || ce_write(&c, newfd, sb.buf, sb.len) < 0;
+               strbuf_release(&sb);
+               if (err)
+                       return -1;
+       }
 
        if (ce_flush(&c, newfd) || fstat(newfd, &st))
                return -1;
@@ -1592,9 +1639,8 @@ int read_index_unmerged(struct index_state *istate)
                len = strlen(ce->name);
                size = cache_entry_size(len);
                new_ce = xcalloc(1, size);
-               hashcpy(new_ce->sha1, ce->sha1);
                memcpy(new_ce->name, ce->name, len);
-               new_ce->ce_flags = create_ce_flags(len, 0);
+               new_ce->ce_flags = create_ce_flags(len, 0) | CE_CONFLICTED;
                new_ce->ce_mode = ce->ce_mode;
                if (add_index_entry(istate, new_ce, 0))
                        return error("%s: cannot drop to stage #0",
@@ -1604,81 +1650,6 @@ int read_index_unmerged(struct index_state *istate)
        return unmerged;
 }
 
-struct update_callback_data
-{
-       int flags;
-       int add_errors;
-};
-
-static void update_callback(struct diff_queue_struct *q,
-                           struct diff_options *opt, void *cbdata)
-{
-       int i;
-       struct update_callback_data *data = cbdata;
-
-       for (i = 0; i < q->nr; i++) {
-               struct diff_filepair *p = q->queue[i];
-               const char *path = p->one->path;
-               switch (p->status) {
-               default:
-                       die("unexpected diff status %c", p->status);
-               case DIFF_STATUS_UNMERGED:
-                       /*
-                        * ADD_CACHE_IGNORE_REMOVAL is unset if "git
-                        * add -u" is calling us, In such a case, a
-                        * missing work tree file needs to be removed
-                        * if there is an unmerged entry at stage #2,
-                        * but such a diff record is followed by
-                        * another with DIFF_STATUS_DELETED (and if
-                        * there is no stage #2, we won't see DELETED
-                        * nor MODIFIED).  We can simply continue
-                        * either way.
-                        */
-                       if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL))
-                               continue;
-                       /*
-                        * Otherwise, it is "git add path" is asking
-                        * to explicitly add it; we fall through.  A
-                        * missing work tree file is an error and is
-                        * caught by add_file_to_index() in such a
-                        * case.
-                        */
-               case DIFF_STATUS_MODIFIED:
-               case DIFF_STATUS_TYPE_CHANGED:
-                       if (add_file_to_index(&the_index, path, data->flags)) {
-                               if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
-                                       die("updating files failed");
-                               data->add_errors++;
-                       }
-                       break;
-               case DIFF_STATUS_DELETED:
-                       if (data->flags & ADD_CACHE_IGNORE_REMOVAL)
-                               break;
-                       if (!(data->flags & ADD_CACHE_PRETEND))
-                               remove_file_from_index(&the_index, path);
-                       if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
-                               printf("remove '%s'\n", path);
-                       break;
-               }
-       }
-}
-
-int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
-{
-       struct update_callback_data data;
-       struct rev_info rev;
-       init_revisions(&rev, prefix);
-       setup_revisions(0, NULL, &rev, NULL);
-       rev.prune_data = pathspec;
-       rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
-       rev.diffopt.format_callback = update_callback;
-       data.flags = flags;
-       data.add_errors = 0;
-       rev.diffopt.format_callback_data = &data;
-       run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
-       return !!data.add_errors;
-}
-
 /*
  * Returns 1 if the path is an "other" path with respect to
  * the index; that is, the path is not mentioned in the index at all,
index 5623ea6b48a2f355e8866eabb2805b51b4127a4a..4879615cad7527dc5346cd1a85bd56b9d11e052b 100644 (file)
@@ -8,6 +8,7 @@
 
 struct complete_reflogs {
        char *ref;
+       const char *short_ref;
        struct reflog_info {
                unsigned char osha1[20], nsha1[20];
                char *email;
@@ -161,7 +162,7 @@ int add_reflog_for_walk(struct reflog_walk_info *info,
        } else
                recno = 0;
 
-       item = string_list_lookup(branch, &info->complete_reflogs);
+       item = string_list_lookup(&info->complete_reflogs, branch);
        if (item)
                reflogs = item->util;
        else {
@@ -189,7 +190,7 @@ int add_reflog_for_walk(struct reflog_walk_info *info,
                }
                if (!reflogs || reflogs->nr == 0)
                        return -1;
-               string_list_insert(branch, &info->complete_reflogs)->util
+               string_list_insert(&info->complete_reflogs, branch)->util
                        = reflogs;
        }
 
@@ -241,36 +242,74 @@ void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit)
        commit->object.flags &= ~(ADDED | SEEN | SHOWN);
 }
 
-void show_reflog_message(struct reflog_walk_info *info, int oneline,
+void get_reflog_selector(struct strbuf *sb,
+                        struct reflog_walk_info *reflog_info,
+                        enum date_mode dmode,
+                        int shorten)
+{
+       struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
+       struct reflog_info *info;
+       const char *printed_ref;
+
+       if (!commit_reflog)
+               return;
+
+       if (shorten) {
+               if (!commit_reflog->reflogs->short_ref)
+                       commit_reflog->reflogs->short_ref
+                               = shorten_unambiguous_ref(commit_reflog->reflogs->ref, 0);
+               printed_ref = commit_reflog->reflogs->short_ref;
+       } else {
+               printed_ref = commit_reflog->reflogs->ref;
+       }
+
+       strbuf_addf(sb, "%s@{", printed_ref);
+       if (commit_reflog->flag || dmode) {
+               info = &commit_reflog->reflogs->items[commit_reflog->recno+1];
+               strbuf_addstr(sb, show_date(info->timestamp, info->tz, dmode));
+       } else {
+               strbuf_addf(sb, "%d", commit_reflog->reflogs->nr
+                           - 2 - commit_reflog->recno);
+       }
+
+       strbuf_addch(sb, '}');
+}
+
+void get_reflog_message(struct strbuf *sb,
+                       struct reflog_walk_info *reflog_info)
+{
+       struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
+       struct reflog_info *info;
+       size_t len;
+
+       if (!commit_reflog)
+               return;
+
+       info = &commit_reflog->reflogs->items[commit_reflog->recno+1];
+       len = strlen(info->message);
+       if (len > 0)
+               len--; /* strip away trailing newline */
+       strbuf_add(sb, info->message, len);
+}
+
+void show_reflog_message(struct reflog_walk_info *reflog_info, int oneline,
        enum date_mode dmode)
 {
-       if (info && info->last_commit_reflog) {
-               struct commit_reflog *commit_reflog = info->last_commit_reflog;
+       if (reflog_info && reflog_info->last_commit_reflog) {
+               struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
                struct reflog_info *info;
+               struct strbuf selector = STRBUF_INIT;
 
                info = &commit_reflog->reflogs->items[commit_reflog->recno+1];
+               get_reflog_selector(&selector, reflog_info, dmode, 0);
                if (oneline) {
-                       printf("%s@{", commit_reflog->reflogs->ref);
-                       if (commit_reflog->flag || dmode)
-                               printf("%s", show_date(info->timestamp,
-                                                      info->tz,
-                                                      dmode));
-                       else
-                               printf("%d", commit_reflog->reflogs->nr
-                                      - 2 - commit_reflog->recno);
-                       printf("}: %s", info->message);
+                       printf("%s: %s", selector.buf, info->message);
                }
                else {
-                       printf("Reflog: %s@{", commit_reflog->reflogs->ref);
-                       if (commit_reflog->flag || dmode)
-                               printf("%s", show_date(info->timestamp,
-                                                       info->tz,
-                                                       dmode));
-                       else
-                               printf("%d", commit_reflog->reflogs->nr
-                                      - 2 - commit_reflog->recno);
-                       printf("} (%s)\nReflog message: %s",
-                              info->email, info->message);
+                       printf("Reflog: %s (%s)\nReflog message: %s",
+                              selector.buf, info->email, info->message);
                }
+
+               strbuf_release(&selector);
        }
 }
index 74c90964bd95dcd97a5acde83c83b2f3b61c2441..7bd2cd4c4e5cd9d51e645509eeacc49a83a44ba7 100644 (file)
@@ -3,6 +3,8 @@
 
 #include "cache.h"
 
+struct reflog_walk_info;
+
 extern void init_reflog_walk(struct reflog_walk_info** info);
 extern int add_reflog_for_walk(struct reflog_walk_info *info,
                struct commit *commit, const char *name);
@@ -10,5 +12,11 @@ extern void fake_reflog_parent(struct reflog_walk_info *info,
                struct commit *commit);
 extern void show_reflog_message(struct reflog_walk_info *info, int,
                enum date_mode);
+extern void get_reflog_message(struct strbuf *sb,
+               struct reflog_walk_info *reflog_info);
+extern void get_reflog_selector(struct strbuf *sb,
+               struct reflog_walk_info *reflog_info,
+               enum date_mode dmode,
+               int shorten);
 
 #endif
diff --git a/refs.c b/refs.c
index e65a3b4c4ef57863a1055108d2598777cabc2c8d..e3c05110e58ca5684f9c43b1e1a5eb2587c96828 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -6,6 +6,7 @@
 
 /* ISSYMREF=01 and ISPACKED=02 are public interfaces */
 #define REF_KNOWS_PEELED 04
+#define REF_BROKEN 010
 
 struct ref_list {
        struct ref_list *next;
@@ -156,7 +157,7 @@ static struct cached_refs {
        char did_packed;
        struct ref_list *loose;
        struct ref_list *packed;
-} cached_refs;
+} cached_refs, submodule_refs;
 static struct ref_list *current_ref;
 
 static struct ref_list *extra_refs;
@@ -228,23 +229,45 @@ void clear_extra_refs(void)
        extra_refs = NULL;
 }
 
-static struct ref_list *get_packed_refs(void)
+static struct ref_list *get_packed_refs(const char *submodule)
 {
-       if (!cached_refs.did_packed) {
-               FILE *f = fopen(git_path("packed-refs"), "r");
-               cached_refs.packed = NULL;
+       const char *packed_refs_file;
+       struct cached_refs *refs;
+
+       if (submodule) {
+               packed_refs_file = git_path_submodule(submodule, "packed-refs");
+               refs = &submodule_refs;
+               free_ref_list(refs->packed);
+       } else {
+               packed_refs_file = git_path("packed-refs");
+               refs = &cached_refs;
+       }
+
+       if (!refs->did_packed || submodule) {
+               FILE *f = fopen(packed_refs_file, "r");
+               refs->packed = NULL;
                if (f) {
-                       read_packed_refs(f, &cached_refs);
+                       read_packed_refs(f, refs);
                        fclose(f);
                }
-               cached_refs.did_packed = 1;
+               refs->did_packed = 1;
        }
-       return cached_refs.packed;
+       return refs->packed;
 }
 
-static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
+static struct ref_list *get_ref_dir(const char *submodule, const char *base,
+                                   struct ref_list *list)
 {
-       DIR *dir = opendir(git_path("%s", base));
+       DIR *dir;
+       const char *path;
+
+       if (submodule)
+               path = git_path_submodule(submodule, "%s", base);
+       else
+               path = git_path("%s", base);
+
+
+       dir = opendir(path);
 
        if (dir) {
                struct dirent *de;
@@ -260,6 +283,7 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
                        struct stat st;
                        int flag;
                        int namelen;
+                       const char *refdir;
 
                        if (de->d_name[0] == '.')
                                continue;
@@ -269,14 +293,27 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
                        if (has_extension(de->d_name, ".lock"))
                                continue;
                        memcpy(ref + baselen, de->d_name, namelen+1);
-                       if (stat(git_path("%s", ref), &st) < 0)
+                       refdir = submodule
+                               ? git_path_submodule(submodule, "%s", ref)
+                               : git_path("%s", ref);
+                       if (stat(refdir, &st) < 0)
                                continue;
                        if (S_ISDIR(st.st_mode)) {
-                               list = get_ref_dir(ref, list);
+                               list = get_ref_dir(submodule, ref, list);
                                continue;
                        }
-                       if (!resolve_ref(ref, sha1, 1, &flag))
+                       if (submodule) {
                                hashclr(sha1);
+                               flag = 0;
+                               if (resolve_gitlink_ref(submodule, ref, sha1) < 0) {
+                                       hashclr(sha1);
+                                       flag |= REF_BROKEN;
+                               }
+                       } else
+                               if (!resolve_ref(ref, sha1, 1, &flag)) {
+                                       hashclr(sha1);
+                                       flag |= REF_BROKEN;
+                               }
                        list = add_ref(ref, sha1, flag, list, NULL);
                }
                free(ref);
@@ -286,6 +323,7 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
 }
 
 struct warn_if_dangling_data {
+       FILE *fp;
        const char *refname;
        const char *msg_fmt;
 };
@@ -304,20 +342,30 @@ static int warn_if_dangling_symref(const char *refname, const unsigned char *sha
        if (!resolves_to || strcmp(resolves_to, d->refname))
                return 0;
 
-       printf(d->msg_fmt, refname);
+       fprintf(d->fp, d->msg_fmt, refname);
        return 0;
 }
 
-void warn_dangling_symref(const char *msg_fmt, const char *refname)
+void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
 {
-       struct warn_if_dangling_data data = { refname, msg_fmt };
+       struct warn_if_dangling_data data;
+
+       data.fp = fp;
+       data.refname = refname;
+       data.msg_fmt = msg_fmt;
        for_each_rawref(warn_if_dangling_symref, &data);
 }
 
-static struct ref_list *get_loose_refs(void)
+static struct ref_list *get_loose_refs(const char *submodule)
 {
+       if (submodule) {
+               free_ref_list(submodule_refs.loose);
+               submodule_refs.loose = get_ref_dir(submodule, "refs", NULL);
+               return submodule_refs.loose;
+       }
+
        if (!cached_refs.did_loose) {
-               cached_refs.loose = get_ref_dir("refs", NULL);
+               cached_refs.loose = get_ref_dir(NULL, "refs", NULL);
                cached_refs.did_loose = 1;
        }
        return cached_refs.loose;
@@ -451,7 +499,7 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
                git_snpath(path, sizeof(path), "%s", ref);
                /* Special case: non-existing file. */
                if (lstat(path, &st) < 0) {
-                       struct ref_list *list = get_packed_refs();
+                       struct ref_list *list = get_packed_refs(NULL);
                        while (list) {
                                if (!strcmp(ref, list->name)) {
                                        hashcpy(sha1, list->sha1);
@@ -518,6 +566,13 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
        return ref;
 }
 
+/* The argument to filter_refs */
+struct ref_filter {
+       const char *pattern;
+       each_ref_fn *fn;
+       void *cb_data;
+};
+
 int read_ref(const char *ref, unsigned char *sha1)
 {
        if (resolve_ref(ref, sha1, 1, NULL))
@@ -531,9 +586,10 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim,
 {
        if (strncmp(base, entry->name, trim))
                return 0;
+
        if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
-               if (is_null_sha1(entry->sha1))
-                       return 0;
+               if (entry->flag & REF_BROKEN)
+                       return 0; /* ignore dangling symref */
                if (!has_sha1_file(entry->sha1)) {
                        error("%s does not point to a valid object!", entry->name);
                        return 0;
@@ -543,6 +599,15 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim,
        return fn(entry->name + trim, entry->sha1, entry->flag, cb_data);
 }
 
+static int filter_refs(const char *ref, const unsigned char *sha, int flags,
+       void *data)
+{
+       struct ref_filter *filter = (struct ref_filter *)data;
+       if (fnmatch(filter->pattern, ref, 0))
+               return 0;
+       return filter->fn(ref, sha, flags, filter->cb_data);
+}
+
 int peel_ref(const char *ref, unsigned char *sha1)
 {
        int flag;
@@ -563,7 +628,7 @@ int peel_ref(const char *ref, unsigned char *sha1)
                return -1;
 
        if ((flag & REF_ISPACKED)) {
-               struct ref_list *list = get_packed_refs();
+               struct ref_list *list = get_packed_refs(NULL);
 
                while (list) {
                        if (!strcmp(list->name, ref)) {
@@ -590,12 +655,12 @@ fallback:
        return -1;
 }
 
-static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
-                          int flags, void *cb_data)
+static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn fn,
+                          int trim, int flags, void *cb_data)
 {
        int retval = 0;
-       struct ref_list *packed = get_packed_refs();
-       struct ref_list *loose = get_loose_refs();
+       struct ref_list *packed = get_packed_refs(submodule);
+       struct ref_list *loose = get_loose_refs(submodule);
 
        struct ref_list *extra;
 
@@ -632,24 +697,54 @@ end_each:
        return retval;
 }
 
-int head_ref(each_ref_fn fn, void *cb_data)
+
+static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)
 {
        unsigned char sha1[20];
        int flag;
 
+       if (submodule) {
+               if (resolve_gitlink_ref(submodule, "HEAD", sha1) == 0)
+                       return fn("HEAD", sha1, 0, cb_data);
+
+               return 0;
+       }
+
        if (resolve_ref("HEAD", sha1, 1, &flag))
                return fn("HEAD", sha1, flag, cb_data);
+
        return 0;
 }
 
+int head_ref(each_ref_fn fn, void *cb_data)
+{
+       return do_head_ref(NULL, fn, cb_data);
+}
+
+int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+{
+       return do_head_ref(submodule, fn, cb_data);
+}
+
 int for_each_ref(each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref("refs/", fn, 0, 0, cb_data);
+       return do_for_each_ref(NULL, "refs/", fn, 0, 0, cb_data);
+}
+
+int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+{
+       return do_for_each_ref(submodule, "refs/", fn, 0, 0, cb_data);
 }
 
 int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref(prefix, fn, strlen(prefix), 0, cb_data);
+       return do_for_each_ref(NULL, prefix, fn, strlen(prefix), 0, cb_data);
+}
+
+int for_each_ref_in_submodule(const char *submodule, const char *prefix,
+               each_ref_fn fn, void *cb_data)
+{
+       return do_for_each_ref(submodule, prefix, fn, strlen(prefix), 0, cb_data);
 }
 
 int for_each_tag_ref(each_ref_fn fn, void *cb_data)
@@ -657,19 +752,74 @@ int for_each_tag_ref(each_ref_fn fn, void *cb_data)
        return for_each_ref_in("refs/tags/", fn, cb_data);
 }
 
+int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+{
+       return for_each_ref_in_submodule(submodule, "refs/tags/", fn, cb_data);
+}
+
 int for_each_branch_ref(each_ref_fn fn, void *cb_data)
 {
        return for_each_ref_in("refs/heads/", fn, cb_data);
 }
 
+int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+{
+       return for_each_ref_in_submodule(submodule, "refs/heads/", fn, cb_data);
+}
+
 int for_each_remote_ref(each_ref_fn fn, void *cb_data)
 {
        return for_each_ref_in("refs/remotes/", fn, cb_data);
 }
 
+int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+{
+       return for_each_ref_in_submodule(submodule, "refs/remotes/", fn, cb_data);
+}
+
+int for_each_replace_ref(each_ref_fn fn, void *cb_data)
+{
+       return do_for_each_ref(NULL, "refs/replace/", fn, 13, 0, cb_data);
+}
+
+int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
+       const char *prefix, void *cb_data)
+{
+       struct strbuf real_pattern = STRBUF_INIT;
+       struct ref_filter filter;
+       int ret;
+
+       if (!prefix && prefixcmp(pattern, "refs/"))
+               strbuf_addstr(&real_pattern, "refs/");
+       else if (prefix)
+               strbuf_addstr(&real_pattern, prefix);
+       strbuf_addstr(&real_pattern, pattern);
+
+       if (!has_glob_specials(pattern)) {
+               /* Append implied '/' '*' if not present. */
+               if (real_pattern.buf[real_pattern.len - 1] != '/')
+                       strbuf_addch(&real_pattern, '/');
+               /* No need to check for '*', there is none. */
+               strbuf_addch(&real_pattern, '*');
+       }
+
+       filter.pattern = real_pattern.buf;
+       filter.fn = fn;
+       filter.cb_data = cb_data;
+       ret = for_each_ref(filter_refs, &filter);
+
+       strbuf_release(&real_pattern);
+       return ret;
+}
+
+int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data)
+{
+       return for_each_glob_ref_in(fn, pattern, NULL, cb_data);
+}
+
 int for_each_rawref(each_ref_fn fn, void *cb_data)
 {
-       return do_for_each_ref("refs/", fn, 0,
+       return do_for_each_ref(NULL, "refs/", fn, 0,
                               DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
 }
 
@@ -682,12 +832,13 @@ int for_each_rawref(each_ref_fn fn, void *cb_data)
  * - it has ASCII control character, "~", "^", ":" or SP, anywhere, or
  * - it ends with a "/".
  * - it ends with ".lock"
+ * - it contains a "\" (backslash)
  */
 
 static inline int bad_ref_char(int ch)
 {
        if (((unsigned) ch) <= ' ' ||
-           ch == '~' || ch == '^' || ch == ':')
+           ch == '~' || ch == '^' || ch == ':' || ch == '\\')
                return 1;
        /* 2.13 Pattern Matching Notation */
        if (ch == '?' || ch == '[') /* Unsupported */
@@ -750,9 +901,8 @@ int check_ref_format(const char *ref)
        }
 }
 
-const char *prettify_ref(const struct ref *ref)
+const char *prettify_refname(const char *name)
 {
-       const char *name = ref->name;
        return name + (
                !prefixcmp(name, "refs/heads/") ? 11 :
                !prefixcmp(name, "refs/tags/") ? 10 :
@@ -820,7 +970,7 @@ static int remove_empty_directories(const char *file)
        strbuf_init(&path, 20);
        strbuf_addstr(&path, file);
 
-       result = remove_dir_recursively(&path, 1);
+       result = remove_dir_recursively(&path, REMOVE_DIR_EMPTY_ONLY);
 
        strbuf_release(&path);
 
@@ -893,8 +1043,10 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
         * name is a proper prefix of our refname.
         */
        if (missing &&
-            !is_refname_available(ref, NULL, get_packed_refs(), 0))
+            !is_refname_available(ref, NULL, get_packed_refs(NULL), 0)) {
+               last_errno = ENOTDIR;
                goto error_return;
+       }
 
        lock->lk = xcalloc(1, sizeof(struct lock_file));
 
@@ -954,7 +1106,7 @@ static int repack_without_ref(const char *refname)
        int fd;
        int found = 0;
 
-       packed_ref_list = get_packed_refs();
+       packed_ref_list = get_packed_refs(NULL);
        for (list = packed_ref_list; list; list = list->next) {
                if (!strcmp(refname, list->name)) {
                        found = 1;
@@ -964,8 +1116,10 @@ static int repack_without_ref(const char *refname)
        if (!found)
                return 0;
        fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
-       if (fd < 0)
+       if (fd < 0) {
+               unable_to_lock_error(git_path("packed-refs"), errno);
                return error("cannot delete '%s' from packed refs", refname);
+       }
 
        for (list = packed_ref_list; list; list = list->next) {
                char line[PATH_MAX + 100];
@@ -1002,12 +1156,10 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
                } else {
                        path = git_path("%s", refname);
                }
-               err = unlink(path);
-               if (err && errno != ENOENT) {
+               err = unlink_or_warn(path);
+               if (err && errno != ENOENT)
                        ret = 1;
-                       error("unlink(%s) failed: %s",
-                             path, strerror(errno));
-               }
+
                if (!(delopt & REF_NODEREF))
                        lock->lk->filename[i] = '.';
        }
@@ -1017,15 +1169,21 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
         */
        ret |= repack_without_ref(refname);
 
-       err = unlink(git_path("logs/%s", lock->ref_name));
-       if (err && errno != ENOENT)
-               warning("unlink(%s) failed: %s",
-                       git_path("logs/%s", lock->ref_name), strerror(errno));
+       unlink_or_warn(git_path("logs/%s", lock->ref_name));
        invalidate_cached_refs();
        unlock_ref(lock);
        return ret;
 }
 
+/*
+ * People using contrib's git-new-workdir have .git/logs/refs ->
+ * /some/other/path/.git/logs/refs, and that may live on another device.
+ *
+ * IOW, to avoid cross device rename errors, the temporary renamed log must
+ * live into logs/refs.
+ */
+#define TMP_RENAMED_LOG  "logs/refs/.tmp-renamed-log"
+
 int rename_ref(const char *oldref, const char *newref, const char *logmsg)
 {
        static const char renamed_ref[] = "RENAMED-REF";
@@ -1046,10 +1204,10 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
        if (!symref)
                return error("refname %s not found", oldref);
 
-       if (!is_refname_available(newref, oldref, get_packed_refs(), 0))
+       if (!is_refname_available(newref, oldref, get_packed_refs(NULL), 0))
                return 1;
 
-       if (!is_refname_available(newref, oldref, get_loose_refs(), 0))
+       if (!is_refname_available(newref, oldref, get_loose_refs(NULL), 0))
                return 1;
 
        lock = lock_ref_sha1_basic(renamed_ref, NULL, 0, NULL);
@@ -1059,8 +1217,8 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
        if (write_ref_sha1(lock, orig_sha1, logmsg))
                return error("unable to save current sha1 in %s", renamed_ref);
 
-       if (log && rename(git_path("logs/%s", oldref), git_path("tmp-renamed-log")))
-               return error("unable to move logfile logs/%s to tmp-renamed-log: %s",
+       if (log && rename(git_path("logs/%s", oldref), git_path(TMP_RENAMED_LOG)))
+               return error("unable to move logfile logs/%s to "TMP_RENAMED_LOG": %s",
                        oldref, strerror(errno));
 
        if (delete_ref(oldref, orig_sha1, REF_NODEREF)) {
@@ -1086,7 +1244,7 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
        }
 
  retry:
-       if (log && rename(git_path("tmp-renamed-log"), git_path("logs/%s", newref))) {
+       if (log && rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newref))) {
                if (errno==EISDIR || errno==ENOTDIR) {
                        /*
                         * rename(a, b) when b is an existing
@@ -1099,7 +1257,7 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
                        }
                        goto retry;
                } else {
-                       error("unable to move logfile tmp-renamed-log to logs/%s: %s",
+                       error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s",
                                newref, strerror(errno));
                        goto rollback;
                }
@@ -1139,8 +1297,8 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
                error("unable to restore logfile %s from %s: %s",
                        oldref, newref, strerror(errno));
        if (!logmoved && log &&
-           rename(git_path("tmp-renamed-log"), git_path("logs/%s", oldref)))
-               error("unable to restore logfile %s from tmp-renamed-log: %s",
+           rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", oldref)))
+               error("unable to restore logfile %s from "TMP_RENAMED_LOG": %s",
                        oldref, strerror(errno));
 
        return 1;
@@ -1198,51 +1356,65 @@ static int copy_msg(char *buf, const char *msg)
        return cp - buf;
 }
 
-static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
-                        const unsigned char *new_sha1, const char *msg)
+int log_ref_setup(const char *ref_name, char *logfile, int bufsize)
 {
-       int logfd, written, oflags = O_APPEND | O_WRONLY;
-       unsigned maxlen, len;
-       int msglen;
-       char log_file[PATH_MAX];
-       char *logrec;
-       const char *committer;
-
-       if (log_all_ref_updates < 0)
-               log_all_ref_updates = !is_bare_repository();
-
-       git_snpath(log_file, sizeof(log_file), "logs/%s", ref_name);
+       int logfd, oflags = O_APPEND | O_WRONLY;
 
+       git_snpath(logfile, bufsize, "logs/%s", ref_name);
        if (log_all_ref_updates &&
            (!prefixcmp(ref_name, "refs/heads/") ||
             !prefixcmp(ref_name, "refs/remotes/") ||
+            !prefixcmp(ref_name, "refs/notes/") ||
             !strcmp(ref_name, "HEAD"))) {
-               if (safe_create_leading_directories(log_file) < 0)
+               if (safe_create_leading_directories(logfile) < 0)
                        return error("unable to create directory for %s",
-                                    log_file);
+                                    logfile);
                oflags |= O_CREAT;
        }
 
-       logfd = open(log_file, oflags, 0666);
+       logfd = open(logfile, oflags, 0666);
        if (logfd < 0) {
                if (!(oflags & O_CREAT) && errno == ENOENT)
                        return 0;
 
                if ((oflags & O_CREAT) && errno == EISDIR) {
-                       if (remove_empty_directories(log_file)) {
+                       if (remove_empty_directories(logfile)) {
                                return error("There are still logs under '%s'",
-                                            log_file);
+                                            logfile);
                        }
-                       logfd = open(log_file, oflags, 0666);
+                       logfd = open(logfile, oflags, 0666);
                }
 
                if (logfd < 0)
                        return error("Unable to append to %s: %s",
-                                    log_file, strerror(errno));
+                                    logfile, strerror(errno));
        }
 
-       adjust_shared_perm(log_file);
+       adjust_shared_perm(logfile);
+       close(logfd);
+       return 0;
+}
 
+static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
+                        const unsigned char *new_sha1, const char *msg)
+{
+       int logfd, result, written, oflags = O_APPEND | O_WRONLY;
+       unsigned maxlen, len;
+       int msglen;
+       char log_file[PATH_MAX];
+       char *logrec;
+       const char *committer;
+
+       if (log_all_ref_updates < 0)
+               log_all_ref_updates = !is_bare_repository();
+
+       result = log_ref_setup(ref_name, log_file, sizeof(log_file));
+       if (result)
+               return result;
+
+       logfd = open(log_file, oflags);
+       if (logfd < 0)
+               return 0;
        msglen = msg ? strlen(msg) : 0;
        committer = git_committer_info(0);
        maxlen = strlen(committer) + msglen + 100;
@@ -1381,7 +1553,7 @@ int create_symref(const char *ref_target, const char *refs_heads_master,
        if (adjust_shared_perm(git_HEAD)) {
                error("Unable to fix permissions on %s", lockpath);
        error_unlink_return:
-               unlink(lockpath);
+               unlink_or_warn(lockpath);
        error_free_return:
                free(git_HEAD);
                return -1;
@@ -1421,7 +1593,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
        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));
+               die_errno("Unable to read log '%s'", logfile);
        fstat(logfd, &st);
        if (!st.st_size)
                die("Log %s is empty.", logfile);
@@ -1515,7 +1687,7 @@ int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long ofs,
 {
        const char *logfile;
        FILE *logfp;
-       char buf[1024];
+       struct strbuf sb = STRBUF_INIT;
        int ret = 0;
 
        logfile = git_path("logs/%s", ref);
@@ -1528,22 +1700,24 @@ int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long ofs,
                if (fstat(fileno(logfp), &statbuf) ||
                    statbuf.st_size < ofs ||
                    fseek(logfp, -ofs, SEEK_END) ||
-                   fgets(buf, sizeof(buf), logfp))
+                   strbuf_getwholeline(&sb, logfp, '\n')) {
+                       fclose(logfp);
+                       strbuf_release(&sb);
                        return -1;
+               }
        }
 
-       while (fgets(buf, sizeof(buf), logfp)) {
+       while (!strbuf_getwholeline(&sb, logfp, '\n')) {
                unsigned char osha1[20], nsha1[20];
                char *email_end, *message;
                unsigned long timestamp;
-               int len, tz;
+               int tz;
 
                /* old SP new SP name <email> SP time TAB msg LF */
-               len = strlen(buf);
-               if (len < 83 || buf[len-1] != '\n' ||
-                   get_sha1_hex(buf, osha1) || buf[40] != ' ' ||
-                   get_sha1_hex(buf + 41, nsha1) || buf[81] != ' ' ||
-                   !(email_end = strchr(buf + 82, '>')) ||
+               if (sb.len < 83 || sb.buf[sb.len - 1] != '\n' ||
+                   get_sha1_hex(sb.buf, osha1) || sb.buf[40] != ' ' ||
+                   get_sha1_hex(sb.buf + 41, nsha1) || sb.buf[81] != ' ' ||
+                   !(email_end = strchr(sb.buf + 82, '>')) ||
                    email_end[1] != ' ' ||
                    !(timestamp = strtoul(email_end + 2, &message, 10)) ||
                    !message || message[0] != ' ' ||
@@ -1557,11 +1731,13 @@ int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long ofs,
                        message += 6;
                else
                        message += 7;
-               ret = fn(osha1, nsha1, buf+82, timestamp, tz, message, cb_data);
+               ret = fn(osha1, nsha1, sb.buf + 82, timestamp, tz, message,
+                        cb_data);
                if (ret)
                        break;
        }
        fclose(logfp);
+       strbuf_release(&sb);
        return ret;
 }
 
diff --git a/refs.h b/refs.h
index 29d17a48e4a2923bc72337deb1ef64cf7b467381..5e7a9a59f5f6b687d15eb37a11696433bdc8f15c 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -24,11 +24,27 @@ extern int for_each_ref_in(const char *, each_ref_fn, void *);
 extern int for_each_tag_ref(each_ref_fn, void *);
 extern int for_each_branch_ref(each_ref_fn, void *);
 extern int for_each_remote_ref(each_ref_fn, void *);
+extern int for_each_replace_ref(each_ref_fn, void *);
+extern int for_each_glob_ref(each_ref_fn, const char *pattern, void *);
+extern int for_each_glob_ref_in(each_ref_fn, const char *pattern, const char* prefix, void *);
+
+extern int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
+extern int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
+extern int for_each_ref_in_submodule(const char *submodule, const char *prefix,
+               each_ref_fn fn, void *cb_data);
+extern int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
+extern int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
+extern int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
+
+static inline const char *has_glob_specials(const char *pattern)
+{
+       return strpbrk(pattern, "?*[");
+}
 
 /* can be used to learn about broken ref and symref */
 extern int for_each_rawref(each_ref_fn, void *);
 
-extern void warn_dangling_symref(const char *msg_fmt, const char *refname);
+extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname);
 
 /*
  * Extra refs will be listed by for_each_ref() before any actual refs
@@ -60,6 +76,9 @@ 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);
 
+/** Setup reflog before using. **/
+int log_ref_setup(const char *ref_name, char *logfile, int bufsize);
+
 /** Reads log for the value of ref during at_time. **/
 extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt);
 
@@ -80,7 +99,7 @@ extern int for_each_reflog(each_ref_fn, void *);
 #define CHECK_REF_FORMAT_WILDCARD (-3)
 extern int check_ref_format(const char *target);
 
-extern const char *prettify_ref(const struct ref *ref);
+extern const char *prettify_refname(const char *refname);
 extern char *shorten_unambiguous_ref(const char *ref, int strict);
 
 /** rename ref, return 0 on success **/
diff --git a/remote-curl.c b/remote-curl.c
new file mode 100644 (file)
index 0000000..04d4813
--- /dev/null
@@ -0,0 +1,862 @@
+#include "cache.h"
+#include "remote.h"
+#include "strbuf.h"
+#include "walker.h"
+#include "http.h"
+#include "exec_cmd.h"
+#include "run-command.h"
+#include "pkt-line.h"
+#include "sideband.h"
+
+static struct remote *remote;
+static const char *url; /* always ends with a trailing slash */
+
+struct options {
+       int verbosity;
+       unsigned long depth;
+       unsigned progress : 1,
+               followtags : 1,
+               dry_run : 1,
+               thin : 1;
+};
+static struct options options;
+
+static int set_option(const char *name, const char *value)
+{
+       if (!strcmp(name, "verbosity")) {
+               char *end;
+               int v = strtol(value, &end, 10);
+               if (value == end || *end)
+                       return -1;
+               options.verbosity = v;
+               return 0;
+       }
+       else if (!strcmp(name, "progress")) {
+               if (!strcmp(value, "true"))
+                       options.progress = 1;
+               else if (!strcmp(value, "false"))
+                       options.progress = 0;
+               else
+                       return -1;
+               return 0;
+       }
+       else if (!strcmp(name, "depth")) {
+               char *end;
+               unsigned long v = strtoul(value, &end, 10);
+               if (value == end || *end)
+                       return -1;
+               options.depth = v;
+               return 0;
+       }
+       else if (!strcmp(name, "followtags")) {
+               if (!strcmp(value, "true"))
+                       options.followtags = 1;
+               else if (!strcmp(value, "false"))
+                       options.followtags = 0;
+               else
+                       return -1;
+               return 0;
+       }
+       else if (!strcmp(name, "dry-run")) {
+               if (!strcmp(value, "true"))
+                       options.dry_run = 1;
+               else if (!strcmp(value, "false"))
+                       options.dry_run = 0;
+               else
+                       return -1;
+               return 0;
+       }
+       else {
+               return 1 /* unsupported */;
+       }
+}
+
+struct discovery {
+       const char *service;
+       char *buf_alloc;
+       char *buf;
+       size_t len;
+       unsigned proto_git : 1;
+};
+static struct discovery *last_discovery;
+
+static void free_discovery(struct discovery *d)
+{
+       if (d) {
+               if (d == last_discovery)
+                       last_discovery = NULL;
+               free(d->buf_alloc);
+               free(d);
+       }
+}
+
+static struct discovery* discover_refs(const char *service)
+{
+       struct strbuf buffer = STRBUF_INIT;
+       struct discovery *last = last_discovery;
+       char *refs_url;
+       int http_ret, is_http = 0, proto_git_candidate = 1;
+
+       if (last && !strcmp(service, last->service))
+               return last;
+       free_discovery(last);
+
+       strbuf_addf(&buffer, "%sinfo/refs", url);
+       if (!prefixcmp(url, "http://") || !prefixcmp(url, "https://")) {
+               is_http = 1;
+               if (!strchr(url, '?'))
+                       strbuf_addch(&buffer, '?');
+               else
+                       strbuf_addch(&buffer, '&');
+               strbuf_addf(&buffer, "service=%s", service);
+       }
+       refs_url = strbuf_detach(&buffer, NULL);
+
+       http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE);
+
+       /* try again with "plain" url (no ? or & appended) */
+       if (http_ret != HTTP_OK) {
+               free(refs_url);
+               strbuf_reset(&buffer);
+
+               proto_git_candidate = 0;
+               strbuf_addf(&buffer, "%sinfo/refs", url);
+               refs_url = strbuf_detach(&buffer, NULL);
+
+               http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE);
+       }
+
+       switch (http_ret) {
+       case HTTP_OK:
+               break;
+       case HTTP_MISSING_TARGET:
+               die("%s not found: did you run git update-server-info on the"
+                   " server?", refs_url);
+       case HTTP_NOAUTH:
+               die("Authentication failed");
+       default:
+               http_error(refs_url, http_ret);
+               die("HTTP request failed");
+       }
+
+       last= xcalloc(1, sizeof(*last_discovery));
+       last->service = service;
+       last->buf_alloc = strbuf_detach(&buffer, &last->len);
+       last->buf = last->buf_alloc;
+
+       if (is_http && proto_git_candidate
+               && 5 <= last->len && last->buf[4] == '#') {
+               /* smart HTTP response; validate that the service
+                * pkt-line matches our request.
+                */
+               struct strbuf exp = STRBUF_INIT;
+
+               if (packet_get_line(&buffer, &last->buf, &last->len) <= 0)
+                       die("%s has invalid packet header", refs_url);
+               if (buffer.len && buffer.buf[buffer.len - 1] == '\n')
+                       strbuf_setlen(&buffer, buffer.len - 1);
+
+               strbuf_addf(&exp, "# service=%s", service);
+               if (strbuf_cmp(&exp, &buffer))
+                       die("invalid server response; got '%s'", buffer.buf);
+               strbuf_release(&exp);
+
+               /* The header can include additional metadata lines, up
+                * until a packet flush marker.  Ignore these now, but
+                * in the future we might start to scan them.
+                */
+               strbuf_reset(&buffer);
+               while (packet_get_line(&buffer, &last->buf, &last->len) > 0)
+                       strbuf_reset(&buffer);
+
+               last->proto_git = 1;
+       }
+
+       free(refs_url);
+       strbuf_release(&buffer);
+       last_discovery = last;
+       return last;
+}
+
+static int write_discovery(int in, int out, void *data)
+{
+       struct discovery *heads = data;
+       int err = 0;
+       if (write_in_full(out, heads->buf, heads->len) != heads->len)
+               err = 1;
+       close(out);
+       return err;
+}
+
+static struct ref *parse_git_refs(struct discovery *heads)
+{
+       struct ref *list = NULL;
+       struct async async;
+
+       memset(&async, 0, sizeof(async));
+       async.proc = write_discovery;
+       async.data = heads;
+       async.out = -1;
+
+       if (start_async(&async))
+               die("cannot start thread to parse advertised refs");
+       get_remote_heads(async.out, &list, 0, NULL, 0, NULL);
+       close(async.out);
+       if (finish_async(&async))
+               die("ref parsing thread failed");
+       return list;
+}
+
+static struct ref *parse_info_refs(struct discovery *heads)
+{
+       char *data, *start, *mid;
+       char *ref_name;
+       int i = 0;
+
+       struct ref *refs = NULL;
+       struct ref *ref = NULL;
+       struct ref *last_ref = NULL;
+
+       data = heads->buf;
+       start = NULL;
+       mid = data;
+       while (i < heads->len) {
+               if (!start) {
+                       start = &data[i];
+               }
+               if (data[i] == '\t')
+                       mid = &data[i];
+               if (data[i] == '\n') {
+                       data[i] = 0;
+                       ref_name = mid + 1;
+                       ref = xmalloc(sizeof(struct ref) +
+                                     strlen(ref_name) + 1);
+                       memset(ref, 0, sizeof(struct ref));
+                       strcpy(ref->name, ref_name);
+                       get_sha1_hex(start, ref->old_sha1);
+                       if (!refs)
+                               refs = ref;
+                       if (last_ref)
+                               last_ref->next = ref;
+                       last_ref = ref;
+                       start = NULL;
+               }
+               i++;
+       }
+
+       ref = alloc_ref("HEAD");
+       if (!http_fetch_ref(url, ref) &&
+           !resolve_remote_symref(ref, refs)) {
+               ref->next = refs;
+               refs = ref;
+       } else {
+               free(ref);
+       }
+
+       return refs;
+}
+
+static struct ref *get_refs(int for_push)
+{
+       struct discovery *heads;
+
+       if (for_push)
+               heads = discover_refs("git-receive-pack");
+       else
+               heads = discover_refs("git-upload-pack");
+
+       if (heads->proto_git)
+               return parse_git_refs(heads);
+       return parse_info_refs(heads);
+}
+
+static void output_refs(struct ref *refs)
+{
+       struct ref *posn;
+       for (posn = refs; posn; posn = posn->next) {
+               if (posn->symref)
+                       printf("@%s %s\n", posn->symref, posn->name);
+               else
+                       printf("%s %s\n", sha1_to_hex(posn->old_sha1), posn->name);
+       }
+       printf("\n");
+       fflush(stdout);
+       free_refs(refs);
+}
+
+struct rpc_state {
+       const char *service_name;
+       const char **argv;
+       char *service_url;
+       char *hdr_content_type;
+       char *hdr_accept;
+       char *buf;
+       size_t alloc;
+       size_t len;
+       size_t pos;
+       int in;
+       int out;
+       struct strbuf result;
+       unsigned gzip_request : 1;
+       unsigned initial_buffer : 1;
+};
+
+static size_t rpc_out(void *ptr, size_t eltsize,
+               size_t nmemb, void *buffer_)
+{
+       size_t max = eltsize * nmemb;
+       struct rpc_state *rpc = buffer_;
+       size_t avail = rpc->len - rpc->pos;
+
+       if (!avail) {
+               rpc->initial_buffer = 0;
+               avail = packet_read_line(rpc->out, rpc->buf, rpc->alloc);
+               if (!avail)
+                       return 0;
+               rpc->pos = 0;
+               rpc->len = avail;
+       }
+
+       if (max < avail)
+               avail = max;
+       memcpy(ptr, rpc->buf + rpc->pos, avail);
+       rpc->pos += avail;
+       return avail;
+}
+
+#ifndef NO_CURL_IOCTL
+static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp)
+{
+       struct rpc_state *rpc = clientp;
+
+       switch (cmd) {
+       case CURLIOCMD_NOP:
+               return CURLIOE_OK;
+
+       case CURLIOCMD_RESTARTREAD:
+               if (rpc->initial_buffer) {
+                       rpc->pos = 0;
+                       return CURLIOE_OK;
+               }
+               fprintf(stderr, "Unable to rewind rpc post data - try increasing http.postBuffer\n");
+               return CURLIOE_FAILRESTART;
+
+       default:
+               return CURLIOE_UNKNOWNCMD;
+       }
+}
+#endif
+
+static size_t rpc_in(const void *ptr, size_t eltsize,
+               size_t nmemb, void *buffer_)
+{
+       size_t size = eltsize * nmemb;
+       struct rpc_state *rpc = buffer_;
+       write_or_die(rpc->in, ptr, size);
+       return size;
+}
+
+static int post_rpc(struct rpc_state *rpc)
+{
+       struct active_request_slot *slot;
+       struct slot_results results;
+       struct curl_slist *headers = NULL;
+       int use_gzip = rpc->gzip_request;
+       char *gzip_body = NULL;
+       int err = 0, large_request = 0;
+
+       /* Try to load the entire request, if we can fit it into the
+        * allocated buffer space we can use HTTP/1.0 and avoid the
+        * chunked encoding mess.
+        */
+       while (1) {
+               size_t left = rpc->alloc - rpc->len;
+               char *buf = rpc->buf + rpc->len;
+               int n;
+
+               if (left < LARGE_PACKET_MAX) {
+                       large_request = 1;
+                       use_gzip = 0;
+                       break;
+               }
+
+               n = packet_read_line(rpc->out, buf, left);
+               if (!n)
+                       break;
+               rpc->len += n;
+       }
+
+       slot = get_active_slot();
+       slot->results = &results;
+
+       curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
+       curl_easy_setopt(slot->curl, CURLOPT_POST, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, rpc->service_url);
+       curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "");
+
+       headers = curl_slist_append(headers, rpc->hdr_content_type);
+       headers = curl_slist_append(headers, rpc->hdr_accept);
+
+       if (large_request) {
+               /* The request body is large and the size cannot be predicted.
+                * We must use chunked encoding to send it.
+                */
+               headers = curl_slist_append(headers, "Expect: 100-continue");
+               headers = curl_slist_append(headers, "Transfer-Encoding: chunked");
+               rpc->initial_buffer = 1;
+               curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out);
+               curl_easy_setopt(slot->curl, CURLOPT_INFILE, rpc);
+#ifndef NO_CURL_IOCTL
+               curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, rpc_ioctl);
+               curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, rpc);
+#endif
+               if (options.verbosity > 1) {
+                       fprintf(stderr, "POST %s (chunked)\n", rpc->service_name);
+                       fflush(stderr);
+               }
+
+       } else if (use_gzip && 1024 < rpc->len) {
+               /* The client backend isn't giving us compressed data so
+                * we can try to deflate it ourselves, this may save on.
+                * the transfer time.
+                */
+               size_t size;
+               z_stream stream;
+               int ret;
+
+               memset(&stream, 0, sizeof(stream));
+               ret = deflateInit2(&stream, Z_BEST_COMPRESSION,
+                               Z_DEFLATED, (15 + 16),
+                               8, Z_DEFAULT_STRATEGY);
+               if (ret != Z_OK)
+                       die("cannot deflate request; zlib init error %d", ret);
+               size = deflateBound(&stream, rpc->len);
+               gzip_body = xmalloc(size);
+
+               stream.next_in = (unsigned char *)rpc->buf;
+               stream.avail_in = rpc->len;
+               stream.next_out = (unsigned char *)gzip_body;
+               stream.avail_out = size;
+
+               ret = deflate(&stream, Z_FINISH);
+               if (ret != Z_STREAM_END)
+                       die("cannot deflate request; zlib deflate error %d", ret);
+
+               ret = deflateEnd(&stream);
+               if (ret != Z_OK)
+                       die("cannot deflate request; zlib end error %d", ret);
+
+               size = stream.total_out;
+
+               headers = curl_slist_append(headers, "Content-Encoding: gzip");
+               curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, gzip_body);
+               curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, size);
+
+               if (options.verbosity > 1) {
+                       fprintf(stderr, "POST %s (gzip %lu to %lu bytes)\n",
+                               rpc->service_name,
+                               (unsigned long)rpc->len, (unsigned long)size);
+                       fflush(stderr);
+               }
+       } else {
+               /* We know the complete request size in advance, use the
+                * more normal Content-Length approach.
+                */
+               curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, rpc->buf);
+               curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, rpc->len);
+               if (options.verbosity > 1) {
+                       fprintf(stderr, "POST %s (%lu bytes)\n",
+                               rpc->service_name, (unsigned long)rpc->len);
+                       fflush(stderr);
+               }
+       }
+
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, rpc_in);
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, rpc);
+
+       slot->curl_result = curl_easy_perform(slot->curl);
+       finish_active_slot(slot);
+
+       if (results.curl_result != CURLE_OK) {
+               err |= error("RPC failed; result=%d, HTTP code = %ld",
+                       results.curl_result, results.http_code);
+       }
+
+       curl_slist_free_all(headers);
+       free(gzip_body);
+       return err;
+}
+
+static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
+{
+       const char *svc = rpc->service_name;
+       struct strbuf buf = STRBUF_INIT;
+       struct child_process client;
+       int err = 0;
+
+       memset(&client, 0, sizeof(client));
+       client.in = -1;
+       client.out = -1;
+       client.git_cmd = 1;
+       client.argv = rpc->argv;
+       if (start_command(&client))
+               exit(1);
+       if (heads)
+               write_or_die(client.in, heads->buf, heads->len);
+
+       rpc->alloc = http_post_buffer;
+       rpc->buf = xmalloc(rpc->alloc);
+       rpc->in = client.in;
+       rpc->out = client.out;
+       strbuf_init(&rpc->result, 0);
+
+       strbuf_addf(&buf, "%s%s", url, svc);
+       rpc->service_url = strbuf_detach(&buf, NULL);
+
+       strbuf_addf(&buf, "Content-Type: application/x-%s-request", svc);
+       rpc->hdr_content_type = strbuf_detach(&buf, NULL);
+
+       strbuf_addf(&buf, "Accept: application/x-%s-result", svc);
+       rpc->hdr_accept = strbuf_detach(&buf, NULL);
+
+       while (!err) {
+               int n = packet_read_line(rpc->out, rpc->buf, rpc->alloc);
+               if (!n)
+                       break;
+               rpc->pos = 0;
+               rpc->len = n;
+               err |= post_rpc(rpc);
+       }
+
+       close(client.in);
+       client.in = -1;
+       strbuf_read(&rpc->result, client.out, 0);
+
+       close(client.out);
+       client.out = -1;
+
+       err |= finish_command(&client);
+       free(rpc->service_url);
+       free(rpc->hdr_content_type);
+       free(rpc->hdr_accept);
+       free(rpc->buf);
+       strbuf_release(&buf);
+       return err;
+}
+
+static int fetch_dumb(int nr_heads, struct ref **to_fetch)
+{
+       struct walker *walker;
+       char **targets = xmalloc(nr_heads * sizeof(char*));
+       int ret, i;
+
+       if (options.depth)
+               die("dumb http transport does not support --depth");
+       for (i = 0; i < nr_heads; i++)
+               targets[i] = xstrdup(sha1_to_hex(to_fetch[i]->old_sha1));
+
+       walker = get_http_walker(url);
+       walker->get_all = 1;
+       walker->get_tree = 1;
+       walker->get_history = 1;
+       walker->get_verbosely = options.verbosity >= 3;
+       walker->get_recover = 0;
+       ret = walker_fetch(walker, nr_heads, targets, NULL, NULL);
+       walker_free(walker);
+
+       for (i = 0; i < nr_heads; i++)
+               free(targets[i]);
+       free(targets);
+
+       return ret ? error("Fetch failed.") : 0;
+}
+
+static int fetch_git(struct discovery *heads,
+       int nr_heads, struct ref **to_fetch)
+{
+       struct rpc_state rpc;
+       char *depth_arg = NULL;
+       const char **argv;
+       int argc = 0, i, err;
+
+       argv = xmalloc((15 + nr_heads) * sizeof(char*));
+       argv[argc++] = "fetch-pack";
+       argv[argc++] = "--stateless-rpc";
+       argv[argc++] = "--lock-pack";
+       if (options.followtags)
+               argv[argc++] = "--include-tag";
+       if (options.thin)
+               argv[argc++] = "--thin";
+       if (options.verbosity >= 3) {
+               argv[argc++] = "-v";
+               argv[argc++] = "-v";
+       }
+       if (!options.progress)
+               argv[argc++] = "--no-progress";
+       if (options.depth) {
+               struct strbuf buf = STRBUF_INIT;
+               strbuf_addf(&buf, "--depth=%lu", options.depth);
+               depth_arg = strbuf_detach(&buf, NULL);
+               argv[argc++] = depth_arg;
+       }
+       argv[argc++] = url;
+       for (i = 0; i < nr_heads; i++) {
+               struct ref *ref = to_fetch[i];
+               if (!ref->name || !*ref->name)
+                       die("cannot fetch by sha1 over smart http");
+               argv[argc++] = ref->name;
+       }
+       argv[argc++] = NULL;
+
+       memset(&rpc, 0, sizeof(rpc));
+       rpc.service_name = "git-upload-pack",
+       rpc.argv = argv;
+       rpc.gzip_request = 1;
+
+       err = rpc_service(&rpc, heads);
+       if (rpc.result.len)
+               safe_write(1, rpc.result.buf, rpc.result.len);
+       strbuf_release(&rpc.result);
+       free(argv);
+       free(depth_arg);
+       return err;
+}
+
+static int fetch(int nr_heads, struct ref **to_fetch)
+{
+       struct discovery *d = discover_refs("git-upload-pack");
+       if (d->proto_git)
+               return fetch_git(d, nr_heads, to_fetch);
+       else
+               return fetch_dumb(nr_heads, to_fetch);
+}
+
+static void parse_fetch(struct strbuf *buf)
+{
+       struct ref **to_fetch = NULL;
+       struct ref *list_head = NULL;
+       struct ref **list = &list_head;
+       int alloc_heads = 0, nr_heads = 0;
+
+       do {
+               if (!prefixcmp(buf->buf, "fetch ")) {
+                       char *p = buf->buf + strlen("fetch ");
+                       char *name;
+                       struct ref *ref;
+                       unsigned char old_sha1[20];
+
+                       if (strlen(p) < 40 || get_sha1_hex(p, old_sha1))
+                               die("protocol error: expected sha/ref, got %s'", p);
+                       if (p[40] == ' ')
+                               name = p + 41;
+                       else if (!p[40])
+                               name = "";
+                       else
+                               die("protocol error: expected sha/ref, got %s'", p);
+
+                       ref = alloc_ref(name);
+                       hashcpy(ref->old_sha1, old_sha1);
+
+                       *list = ref;
+                       list = &ref->next;
+
+                       ALLOC_GROW(to_fetch, nr_heads + 1, alloc_heads);
+                       to_fetch[nr_heads++] = ref;
+               }
+               else
+                       die("http transport does not support %s", buf->buf);
+
+               strbuf_reset(buf);
+               if (strbuf_getline(buf, stdin, '\n') == EOF)
+                       return;
+               if (!*buf->buf)
+                       break;
+       } while (1);
+
+       if (fetch(nr_heads, to_fetch))
+               exit(128); /* error already reported */
+       free_refs(list_head);
+       free(to_fetch);
+
+       printf("\n");
+       fflush(stdout);
+       strbuf_reset(buf);
+}
+
+static int push_dav(int nr_spec, char **specs)
+{
+       const char **argv = xmalloc((10 + nr_spec) * sizeof(char*));
+       int argc = 0, i;
+
+       argv[argc++] = "http-push";
+       argv[argc++] = "--helper-status";
+       if (options.dry_run)
+               argv[argc++] = "--dry-run";
+       if (options.verbosity > 1)
+               argv[argc++] = "--verbose";
+       argv[argc++] = url;
+       for (i = 0; i < nr_spec; i++)
+               argv[argc++] = specs[i];
+       argv[argc++] = NULL;
+
+       if (run_command_v_opt(argv, RUN_GIT_CMD))
+               die("git-%s failed", argv[0]);
+       free(argv);
+       return 0;
+}
+
+static int push_git(struct discovery *heads, int nr_spec, char **specs)
+{
+       struct rpc_state rpc;
+       const char **argv;
+       int argc = 0, i, err;
+
+       argv = xmalloc((10 + nr_spec) * sizeof(char*));
+       argv[argc++] = "send-pack";
+       argv[argc++] = "--stateless-rpc";
+       argv[argc++] = "--helper-status";
+       if (options.thin)
+               argv[argc++] = "--thin";
+       if (options.dry_run)
+               argv[argc++] = "--dry-run";
+       if (options.verbosity > 1)
+               argv[argc++] = "--verbose";
+       argv[argc++] = url;
+       for (i = 0; i < nr_spec; i++)
+               argv[argc++] = specs[i];
+       argv[argc++] = NULL;
+
+       memset(&rpc, 0, sizeof(rpc));
+       rpc.service_name = "git-receive-pack",
+       rpc.argv = argv;
+
+       err = rpc_service(&rpc, heads);
+       if (rpc.result.len)
+               safe_write(1, rpc.result.buf, rpc.result.len);
+       strbuf_release(&rpc.result);
+       free(argv);
+       return err;
+}
+
+static int push(int nr_spec, char **specs)
+{
+       struct discovery *heads = discover_refs("git-receive-pack");
+       int ret;
+
+       if (heads->proto_git)
+               ret = push_git(heads, nr_spec, specs);
+       else
+               ret = push_dav(nr_spec, specs);
+       free_discovery(heads);
+       return ret;
+}
+
+static void parse_push(struct strbuf *buf)
+{
+       char **specs = NULL;
+       int alloc_spec = 0, nr_spec = 0, i;
+
+       do {
+               if (!prefixcmp(buf->buf, "push ")) {
+                       ALLOC_GROW(specs, nr_spec + 1, alloc_spec);
+                       specs[nr_spec++] = xstrdup(buf->buf + 5);
+               }
+               else
+                       die("http transport does not support %s", buf->buf);
+
+               strbuf_reset(buf);
+               if (strbuf_getline(buf, stdin, '\n') == EOF)
+                       return;
+               if (!*buf->buf)
+                       break;
+       } while (1);
+
+       if (push(nr_spec, specs))
+               exit(128); /* error already reported */
+       for (i = 0; i < nr_spec; i++)
+               free(specs[i]);
+       free(specs);
+
+       printf("\n");
+       fflush(stdout);
+}
+
+int main(int argc, const char **argv)
+{
+       struct strbuf buf = STRBUF_INIT;
+       int nongit;
+
+       git_extract_argv0_path(argv[0]);
+       setup_git_directory_gently(&nongit);
+       if (argc < 2) {
+               fprintf(stderr, "Remote needed\n");
+               return 1;
+       }
+
+       options.verbosity = 1;
+       options.progress = !!isatty(2);
+       options.thin = 1;
+
+       remote = remote_get(argv[1]);
+
+       if (argc > 2) {
+               end_url_with_slash(&buf, argv[2]);
+       } else {
+               end_url_with_slash(&buf, remote->url[0]);
+       }
+
+       url = strbuf_detach(&buf, NULL);
+
+       http_init(remote);
+
+       do {
+               if (strbuf_getline(&buf, stdin, '\n') == EOF)
+                       break;
+               if (!prefixcmp(buf.buf, "fetch ")) {
+                       if (nongit)
+                               die("Fetch attempted without a local repo");
+                       parse_fetch(&buf);
+
+               } else if (!strcmp(buf.buf, "list") || !prefixcmp(buf.buf, "list ")) {
+                       int for_push = !!strstr(buf.buf + 4, "for-push");
+                       output_refs(get_refs(for_push));
+
+               } else if (!prefixcmp(buf.buf, "push ")) {
+                       parse_push(&buf);
+
+               } else if (!prefixcmp(buf.buf, "option ")) {
+                       char *name = buf.buf + strlen("option ");
+                       char *value = strchr(name, ' ');
+                       int result;
+
+                       if (value)
+                               *value++ = '\0';
+                       else
+                               value = "true";
+
+                       result = set_option(name, value);
+                       if (!result)
+                               printf("ok\n");
+                       else if (result < 0)
+                               printf("error invalid value\n");
+                       else
+                               printf("unsupported\n");
+                       fflush(stdout);
+
+               } else if (!strcmp(buf.buf, "capabilities")) {
+                       printf("fetch\n");
+                       printf("option\n");
+                       printf("push\n");
+                       printf("\n");
+                       fflush(stdout);
+               } else {
+                       return 1;
+               }
+               strbuf_reset(&buf);
+       } while (1);
+
+       http_cleanup();
+
+       return 0;
+}
index d66e2f3c93dc72a7112ce101278ae937cc914320..9143ec7a1772c45b6c3d4c84f6fc000040400dee 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -6,6 +6,7 @@
 #include "revision.h"
 #include "dir.h"
 #include "tag.h"
+#include "string-list.h"
 
 static struct refspec s_tag_refspec = {
        0,
@@ -28,6 +29,11 @@ struct rewrite {
        int instead_of_nr;
        int instead_of_alloc;
 };
+struct rewrites {
+       struct rewrite **rewrite;
+       int rewrite_alloc;
+       int rewrite_nr;
+};
 
 static struct remote **remotes;
 static int remotes_alloc;
@@ -41,14 +47,18 @@ static struct branch *current_branch;
 static const char *default_remote_name;
 static int explicit_default_remote_name;
 
-static struct rewrite **rewrite;
-static int rewrite_alloc;
-static int rewrite_nr;
+static struct rewrites rewrites;
+static struct rewrites rewrites_push;
 
 #define BUF_SIZE (2048)
 static char buffer[BUF_SIZE];
 
-static const char *alias_url(const char *url)
+static int valid_remote(const struct remote *remote)
+{
+       return (!!remote->url) || (!!remote->foreign_vcs);
+}
+
+static const char *alias_url(const char *url, struct rewrites *r)
 {
        int i, j;
        char *ret;
@@ -57,14 +67,14 @@ static const char *alias_url(const char *url)
 
        longest = NULL;
        longest_i = -1;
-       for (i = 0; i < rewrite_nr; i++) {
-               if (!rewrite[i])
+       for (i = 0; i < r->rewrite_nr; i++) {
+               if (!r->rewrite[i])
                        continue;
-               for (j = 0; j < rewrite[i]->instead_of_nr; j++) {
-                       if (!prefixcmp(url, rewrite[i]->instead_of[j].s) &&
+               for (j = 0; j < r->rewrite[i]->instead_of_nr; j++) {
+                       if (!prefixcmp(url, r->rewrite[i]->instead_of[j].s) &&
                            (!longest ||
-                            longest->len < rewrite[i]->instead_of[j].len)) {
-                               longest = &(rewrite[i]->instead_of[j]);
+                            longest->len < r->rewrite[i]->instead_of[j].len)) {
+                               longest = &(r->rewrite[i]->instead_of[j]);
                                longest_i = i;
                        }
                }
@@ -72,10 +82,10 @@ static const char *alias_url(const char *url)
        if (!longest)
                return url;
 
-       ret = xmalloc(rewrite[longest_i]->baselen +
+       ret = xmalloc(r->rewrite[longest_i]->baselen +
                     (strlen(url) - longest->len) + 1);
-       strcpy(ret, rewrite[longest_i]->base);
-       strcpy(ret + rewrite[longest_i]->baselen, url + longest->len);
+       strcpy(ret, r->rewrite[longest_i]->base);
+       strcpy(ret + r->rewrite[longest_i]->baselen, url + longest->len);
        return ret;
 }
 
@@ -101,9 +111,23 @@ static void add_url(struct remote *remote, const char *url)
        remote->url[remote->url_nr++] = url;
 }
 
+static void add_pushurl(struct remote *remote, const char *pushurl)
+{
+       ALLOC_GROW(remote->pushurl, remote->pushurl_nr + 1, remote->pushurl_alloc);
+       remote->pushurl[remote->pushurl_nr++] = pushurl;
+}
+
+static void add_pushurl_alias(struct remote *remote, const char *url)
+{
+       const char *pushurl = alias_url(url, &rewrites_push);
+       if (pushurl != url)
+               add_pushurl(remote, pushurl);
+}
+
 static void add_url_alias(struct remote *remote, const char *url)
 {
-       add_url(remote, alias_url(url));
+       add_url(remote, alias_url(url, &rewrites));
+       add_pushurl_alias(remote, url);
 }
 
 static struct remote *make_remote(const char *name, int len)
@@ -163,22 +187,22 @@ static struct branch *make_branch(const char *name, int len)
        return ret;
 }
 
-static struct rewrite *make_rewrite(const char *base, int len)
+static struct rewrite *make_rewrite(struct rewrites *r, const char *base, int len)
 {
        struct rewrite *ret;
        int i;
 
-       for (i = 0; i < rewrite_nr; i++) {
+       for (i = 0; i < r->rewrite_nr; i++) {
                if (len
-                   ? (len == rewrite[i]->baselen &&
-                      !strncmp(base, rewrite[i]->base, len))
-                   : !strcmp(base, rewrite[i]->base))
-                       return rewrite[i];
+                   ? (len == r->rewrite[i]->baselen &&
+                      !strncmp(base, r->rewrite[i]->base, len))
+                   : !strcmp(base, r->rewrite[i]->base))
+                       return r->rewrite[i];
        }
 
-       ALLOC_GROW(rewrite, rewrite_nr + 1, rewrite_alloc);
+       ALLOC_GROW(r->rewrite, r->rewrite_nr + 1, r->rewrite_alloc);
        ret = xcalloc(1, sizeof(struct rewrite));
-       rewrite[rewrite_nr++] = ret;
+       r->rewrite[r->rewrite_nr++] = ret;
        if (len) {
                ret->base = xstrndup(base, len);
                ret->baselen = len;
@@ -301,7 +325,7 @@ static void read_branches_file(struct remote *remote)
                strbuf_addstr(&branch, "HEAD:");
        }
        add_url_alias(remote, p);
-       add_fetch_refspec(remote, strbuf_detach(&branch, 0));
+       add_fetch_refspec(remote, strbuf_detach(&branch, NULL));
        /*
         * Cogito compatible push: push current HEAD to remote #branch
         * (master if missing)
@@ -312,7 +336,7 @@ static void read_branches_file(struct remote *remote)
                strbuf_addf(&branch, ":refs/heads/%s", frag);
        else
                strbuf_addstr(&branch, ":refs/heads/master");
-       add_push_refspec(remote, strbuf_detach(&branch, 0));
+       add_push_refspec(remote, strbuf_detach(&branch, NULL));
        remote->fetch_tags = 1; /* always auto-follow */
 }
 
@@ -349,8 +373,13 @@ static int handle_config(const char *key, const char *value, void *cb)
                subkey = strrchr(name, '.');
                if (!subkey)
                        return 0;
-               rewrite = make_rewrite(name, subkey - name);
                if (!strcmp(subkey, ".insteadof")) {
+                       rewrite = make_rewrite(&rewrites, name, subkey - name);
+                       if (!value)
+                               return config_error_nonbool(key);
+                       add_instead_of(rewrite, xstrdup(value));
+               } else if (!strcmp(subkey, ".pushinsteadof")) {
+                       rewrite = make_rewrite(&rewrites_push, name, subkey - name);
                        if (!value)
                                return config_error_nonbool(key);
                        add_instead_of(rewrite, xstrdup(value));
@@ -373,12 +402,18 @@ static int handle_config(const char *key, const char *value, void *cb)
                remote->mirror = git_config_bool(key, value);
        else if (!strcmp(subkey, ".skipdefaultupdate"))
                remote->skip_default_update = git_config_bool(key, value);
-
+       else if (!strcmp(subkey, ".skipfetchall"))
+               remote->skip_default_update = git_config_bool(key, value);
        else if (!strcmp(subkey, ".url")) {
                const char *v;
                if (git_config_string(&v, key, value))
                        return -1;
                add_url(remote, v);
+       } else if (!strcmp(subkey, ".pushurl")) {
+               const char *v;
+               if (git_config_string(&v, key, value))
+                       return -1;
+               add_pushurl(remote, v);
        } else if (!strcmp(subkey, ".push")) {
                const char *v;
                if (git_config_string(&v, key, value))
@@ -408,9 +443,13 @@ static int handle_config(const char *key, const char *value, void *cb)
        } else if (!strcmp(subkey, ".tagopt")) {
                if (!strcmp(value, "--no-tags"))
                        remote->fetch_tags = -1;
+               else if (!strcmp(value, "--tags"))
+                       remote->fetch_tags = 2;
        } else if (!strcmp(subkey, ".proxy")) {
                return git_config_string((const char **)&remote->http_proxy,
                                         key, value);
+       } else if (!strcmp(subkey, ".vcs")) {
+               return git_config_string(&remote->foreign_vcs, key, value);
        }
        return 0;
 }
@@ -419,10 +458,17 @@ static void alias_all_urls(void)
 {
        int i, j;
        for (i = 0; i < remotes_nr; i++) {
+               int add_pushurl_aliases;
                if (!remotes[i])
                        continue;
+               for (j = 0; j < remotes[i]->pushurl_nr; j++) {
+                       remotes[i]->pushurl[j] = alias_url(remotes[i]->pushurl[j], &rewrites);
+               }
+               add_pushurl_aliases = remotes[i]->pushurl_nr == 0;
                for (j = 0; j < remotes[i]->url_nr; j++) {
-                       remotes[i]->url[j] = alias_url(remotes[i]->url[j]);
+                       if (add_pushurl_aliases)
+                               add_pushurl_alias(remotes[i], remotes[i]->url[j]);
+                       remotes[i]->url[j] = alias_url(remotes[i]->url[j], &rewrites);
                }
        }
 }
@@ -432,7 +478,7 @@ static void read_config(void)
        unsigned char sha1[20];
        const char *head_ref;
        int flag;
-       if (default_remote_name) // did this already
+       if (default_remote_name) /* did this already */
                return;
        default_remote_name = xstrdup("origin");
        current_branch = NULL;
@@ -613,10 +659,9 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
 
 int valid_fetch_refspec(const char *fetch_refspec_str)
 {
-       const char *fetch_refspec[] = { fetch_refspec_str };
        struct refspec *refspec;
 
-       refspec = parse_refspec_internal(1, fetch_refspec, 1, 1);
+       refspec = parse_refspec_internal(1, &fetch_refspec_str, 1, 1);
        free_refspecs(refspec, 1);
        return !!refspec;
 }
@@ -631,6 +676,16 @@ static struct refspec *parse_push_refspec(int nr_refspec, const char **refspec)
        return parse_refspec_internal(nr_refspec, refspec, 0, 0);
 }
 
+void free_refspec(int nr_refspec, struct refspec *refspec)
+{
+       int i;
+       for (i = 0; i < nr_refspec; i++) {
+               free(refspec[i].src);
+               free(refspec[i].dst);
+       }
+       free(refspec);
+}
+
 static int valid_remote_nick(const char *name)
 {
        if (!name[0] || is_dot_or_dotdot(name))
@@ -653,14 +708,14 @@ struct remote *remote_get(const char *name)
 
        ret = make_remote(name, 0);
        if (valid_remote_nick(name)) {
-               if (!ret->url)
+               if (!valid_remote(ret))
                        read_remotes_file(ret);
-               if (!ret->url)
+               if (!valid_remote(ret))
                        read_branches_file(ret);
        }
-       if (name_given && !ret->url)
+       if (name_given && !valid_remote(ret))
                add_url_alias(ret, name);
-       if (!ret->url)
+       if (!valid_remote(ret))
                return NULL;
        ret->fetch = parse_fetch_refspec(ret->fetch_refspec_nr, ret->fetch_refspec);
        ret->push = parse_push_refspec(ret->push_refspec_nr, ret->push_refspec);
@@ -699,29 +754,33 @@ int for_each_remote(each_remote_fn fn, void *priv)
 
 void ref_remove_duplicates(struct ref *ref_map)
 {
-       struct ref **posn;
-       struct ref *next;
-       for (; ref_map; ref_map = ref_map->next) {
+       struct string_list refs = STRING_LIST_INIT_NODUP;
+       struct string_list_item *item = NULL;
+       struct ref *prev = NULL, *next = NULL;
+       for (; ref_map; prev = ref_map, ref_map = next) {
+               next = ref_map->next;
                if (!ref_map->peer_ref)
                        continue;
-               posn = &ref_map->next;
-               while (*posn) {
-                       if ((*posn)->peer_ref &&
-                           !strcmp((*posn)->peer_ref->name,
-                                   ref_map->peer_ref->name)) {
-                               if (strcmp((*posn)->name, ref_map->name))
-                                       die("%s tracks both %s and %s",
-                                           ref_map->peer_ref->name,
-                                           (*posn)->name, ref_map->name);
-                               next = (*posn)->next;
-                               free((*posn)->peer_ref);
-                               free(*posn);
-                               *posn = next;
-                       } else {
-                               posn = &(*posn)->next;
-                       }
+
+               item = string_list_lookup(&refs, ref_map->peer_ref->name);
+               if (item) {
+                       if (strcmp(((struct ref *)item->util)->name,
+                                  ref_map->name))
+                               die("%s tracks both %s and %s",
+                                   ref_map->peer_ref->name,
+                                   ((struct ref *)item->util)->name,
+                                   ref_map->name);
+                       prev->next = ref_map->next;
+                       free(ref_map->peer_ref);
+                       free(ref_map);
+                       ref_map = prev; /* skip this; we freed it */
+                       continue;
                }
+
+               item = string_list_insert(&refs, ref_map->peer_ref->name);
+               item->util = ref_map;
        }
+       string_list_clear(&refs, 0);
 }
 
 int remote_has_url(struct remote *remote, const char *url)
@@ -769,6 +828,23 @@ static int match_name_with_pattern(const char *key, const char *name,
        return ret;
 }
 
+char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
+                    const char *name)
+{
+       int i;
+       char *ret = NULL;
+       for (i = 0; i < nr_refspec; i++) {
+               struct refspec *refspec = refspecs + i;
+               if (refspec->pattern) {
+                       if (match_name_with_pattern(refspec->src, name,
+                                                   refspec->dst, &ret))
+                               return ret;
+               } else if (!strcmp(refspec->src, name))
+                       return strdup(refspec->dst);
+       }
+       return NULL;
+}
+
 int remote_find_tracking(struct remote *remote, struct refspec *refspec)
 {
        int find_src = refspec->src == NULL;
@@ -1024,7 +1100,7 @@ static int match_explicit(struct ref *src, struct ref *dst,
        case 0:
                if (!memcmp(dst_value, "refs/", 5))
                        matched_dst = make_linked_ref(dst_value, dst_tail);
-               else if((dst_guess = guess_ref(dst_value, matched_src)))
+               else if ((dst_guess = guess_ref(dst_value, matched_src)))
                        matched_dst = make_linked_ref(dst_guess, dst_tail);
                else
                        error("unable to push to unqualified destination: %s\n"
@@ -1085,26 +1161,35 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,
                return NULL;
 }
 
+static struct ref **tail_ref(struct ref **head)
+{
+       struct ref **tail = head;
+       while (*tail)
+               tail = &((*tail)->next);
+       return tail;
+}
+
 /*
  * Note. This is used only by "push"; refspec matching rules for
  * push and fetch are subtly different, so do not try to reuse it
  * without thinking.
  */
-int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
+int match_refs(struct ref *src, struct ref **dst,
               int nr_refspec, const char **refspec, int flags)
 {
        struct refspec *rs;
        int send_all = flags & MATCH_REFS_ALL;
        int send_mirror = flags & MATCH_REFS_MIRROR;
        int errs;
-       static const char *default_refspec[] = { ":", 0 };
+       static const char *default_refspec[] = { ":", NULL };
+       struct ref **dst_tail = tail_ref(dst);
 
        if (!nr_refspec) {
                nr_refspec = 1;
                refspec = default_refspec;
        }
        rs = parse_push_refspec(nr_refspec, (const char **) refspec);
-       errs = match_explicit_refs(src, dst, dst_tail, rs, nr_refspec);
+       errs = match_explicit_refs(src, *dst, &dst_tail, rs, nr_refspec);
 
        /* pick the remainder */
        for ( ; src; src = src->next) {
@@ -1134,7 +1219,7 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
                                                     dst_side, &dst_name))
                                die("Didn't think it matches any more");
                }
-               dst_peer = find_ref_by_name(dst, dst_name);
+               dst_peer = find_ref_by_name(*dst, dst_name);
                if (dst_peer) {
                        if (dst_peer->peer_ref)
                                /* We're already sending something to this ref. */
@@ -1150,7 +1235,7 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
                                goto free_name;
 
                        /* Create a new one and link it */
-                       dst_peer = make_linked_ref(dst_name, dst_tail);
+                       dst_peer = make_linked_ref(dst_name, &dst_tail);
                        hashcpy(dst_peer->new_sha1, src->new_sha1);
                }
                dst_peer->peer_ref = copy_ref(src);
@@ -1163,6 +1248,56 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
        return 0;
 }
 
+void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
+       int force_update)
+{
+       struct ref *ref;
+
+       for (ref = remote_refs; ref; ref = ref->next) {
+               if (ref->peer_ref)
+                       hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
+               else if (!send_mirror)
+                       continue;
+
+               ref->deletion = is_null_sha1(ref->new_sha1);
+               if (!ref->deletion &&
+                       !hashcmp(ref->old_sha1, ref->new_sha1)) {
+                       ref->status = REF_STATUS_UPTODATE;
+                       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.
+                *
+                * (4) regardless of all of the above, removing :B is
+                *     always allowed.
+                */
+
+               ref->nonfastforward =
+                       !ref->deletion &&
+                       !is_null_sha1(ref->old_sha1) &&
+                       (!has_sha1_file(ref->old_sha1)
+                         || !ref_newer(ref->new_sha1, ref->old_sha1));
+
+               if (ref->nonfastforward && !ref->force && !force_update) {
+                       ref->status = REF_STATUS_REJECT_NONFASTFORWARD;
+                       continue;
+               }
+       }
+}
+
 struct branch *branch_get(const char *name)
 {
        struct branch *ret;
@@ -1254,7 +1389,7 @@ struct ref *get_remote_ref(const struct ref *remote_refs, const char *name)
 
 static struct ref *get_local_ref(const char *name)
 {
-       if (!name)
+       if (!name || name[0] == '\0')
                return NULL;
 
        if (!prefixcmp(name, "refs/"))
@@ -1399,13 +1534,13 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
        base = branch->merge[0]->dst;
        if (!resolve_ref(base, sha1, 1, NULL))
                return 0;
-       theirs = lookup_commit(sha1);
+       theirs = lookup_commit_reference(sha1);
        if (!theirs)
                return 0;
 
        if (!resolve_ref(branch->refname, sha1, 1, NULL))
                return 0;
-       ours = lookup_commit(sha1);
+       ours = lookup_commit_reference(sha1);
        if (!ours)
                return 0;
 
@@ -1542,3 +1677,42 @@ struct ref *guess_remote_head(const struct ref *head,
 
        return list;
 }
+
+struct stale_heads_info {
+       struct remote *remote;
+       struct string_list *ref_names;
+       struct ref **stale_refs_tail;
+};
+
+static int get_stale_heads_cb(const char *refname,
+       const unsigned char *sha1, int flags, void *cb_data)
+{
+       struct stale_heads_info *info = cb_data;
+       struct refspec refspec;
+       memset(&refspec, 0, sizeof(refspec));
+       refspec.dst = (char *)refname;
+       if (!remote_find_tracking(info->remote, &refspec)) {
+               if (!((flags & REF_ISSYMREF) ||
+                   string_list_has_string(info->ref_names, refspec.src))) {
+                       struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail);
+                       hashcpy(ref->new_sha1, sha1);
+               }
+       }
+       return 0;
+}
+
+struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map)
+{
+       struct ref *ref, *stale_refs = NULL;
+       struct string_list ref_names = STRING_LIST_INIT_NODUP;
+       struct stale_heads_info info;
+       info.remote = remote;
+       info.ref_names = &ref_names;
+       info.stale_refs_tail = &stale_refs;
+       for (ref = fetch_map; ref; ref = ref->next)
+               string_list_append(&ref_names, ref->name);
+       sort_string_list(&ref_names);
+       for_each_ref(get_stale_heads_cb, &info);
+       string_list_clear(&ref_names, 0);
+       return stale_refs;
+}
index 99706a89bc6011c01fcd661d8bad4b26f59b0ca7..888d7c15de2eacc56d869a96d4463aefca7a7a06 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -11,10 +11,16 @@ struct remote {
        const char *name;
        int origin;
 
+       const char *foreign_vcs;
+
        const char **url;
        int url_nr;
        int url_alloc;
 
+       const char **pushurl;
+       int pushurl_nr;
+       int pushurl_alloc;
+
        const char **push_refspec;
        struct refspec *push;
        int push_refspec_nr;
@@ -85,8 +91,15 @@ void ref_remove_duplicates(struct ref *ref_map);
 int valid_fetch_refspec(const char *refspec);
 struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec);
 
-int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
+void free_refspec(int nr_refspec, struct refspec *refspec);
+
+char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
+                    const char *name);
+
+int match_refs(struct ref *src, struct ref **dst,
               int nr_refspec, const char **refspec, int all);
+void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
+       int force_update);
 
 /*
  * Given a list of the remote refs and the specification of things to
@@ -132,7 +145,7 @@ int branch_merge_matches(struct branch *, int n, const char *);
 enum match_refs_flags {
        MATCH_REFS_NONE         = 0,
        MATCH_REFS_ALL          = (1 << 0),
-       MATCH_REFS_MIRROR       = (1 << 1),
+       MATCH_REFS_MIRROR       = (1 << 1)
 };
 
 /* Reporting of tracking info */
@@ -150,4 +163,7 @@ struct ref *guess_remote_head(const struct ref *head,
                              const struct ref *refs,
                              int all);
 
+/* Return refs which no longer exist on remote */
+struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map);
+
 #endif
diff --git a/replace_object.c b/replace_object.c
new file mode 100644 (file)
index 0000000..eb59604
--- /dev/null
@@ -0,0 +1,114 @@
+#include "cache.h"
+#include "sha1-lookup.h"
+#include "refs.h"
+
+static struct replace_object {
+       unsigned char sha1[2][20];
+} **replace_object;
+
+static int replace_object_alloc, replace_object_nr;
+
+static const unsigned char *replace_sha1_access(size_t index, void *table)
+{
+       struct replace_object **replace = table;
+       return replace[index]->sha1[0];
+}
+
+static int replace_object_pos(const unsigned char *sha1)
+{
+       return sha1_pos(sha1, replace_object, replace_object_nr,
+                       replace_sha1_access);
+}
+
+static int register_replace_object(struct replace_object *replace,
+                                  int ignore_dups)
+{
+       int pos = replace_object_pos(replace->sha1[0]);
+
+       if (0 <= pos) {
+               if (ignore_dups)
+                       free(replace);
+               else {
+                       free(replace_object[pos]);
+                       replace_object[pos] = replace;
+               }
+               return 1;
+       }
+       pos = -pos - 1;
+       if (replace_object_alloc <= ++replace_object_nr) {
+               replace_object_alloc = alloc_nr(replace_object_alloc);
+               replace_object = xrealloc(replace_object,
+                                         sizeof(*replace_object) *
+                                         replace_object_alloc);
+       }
+       if (pos < replace_object_nr)
+               memmove(replace_object + pos + 1,
+                       replace_object + pos,
+                       (replace_object_nr - pos - 1) *
+                       sizeof(*replace_object));
+       replace_object[pos] = replace;
+       return 0;
+}
+
+static int register_replace_ref(const char *refname,
+                               const unsigned char *sha1,
+                               int flag, void *cb_data)
+{
+       /* Get sha1 from refname */
+       const char *slash = strrchr(refname, '/');
+       const char *hash = slash ? slash + 1 : refname;
+       struct replace_object *repl_obj = xmalloc(sizeof(*repl_obj));
+
+       if (strlen(hash) != 40 || get_sha1_hex(hash, repl_obj->sha1[0])) {
+               free(repl_obj);
+               warning("bad replace ref name: %s", refname);
+               return 0;
+       }
+
+       /* Copy sha1 from the read ref */
+       hashcpy(repl_obj->sha1[1], sha1);
+
+       /* Register new object */
+       if (register_replace_object(repl_obj, 1))
+               die("duplicate replace ref: %s", refname);
+
+       return 0;
+}
+
+static void prepare_replace_object(void)
+{
+       static int replace_object_prepared;
+
+       if (replace_object_prepared)
+               return;
+
+       for_each_replace_ref(register_replace_ref, NULL);
+       replace_object_prepared = 1;
+}
+
+/* We allow "recursive" replacement. Only within reason, though */
+#define MAXREPLACEDEPTH 5
+
+const unsigned char *lookup_replace_object(const unsigned char *sha1)
+{
+       int pos, depth = MAXREPLACEDEPTH;
+       const unsigned char *cur = sha1;
+
+       if (!read_replace_refs)
+               return sha1;
+
+       prepare_replace_object();
+
+       /* Try to recursively replace the object */
+       do {
+               if (--depth < 0)
+                       die("replace depth too high for object %s",
+                           sha1_to_hex(sha1));
+
+               pos = replace_object_pos(cur);
+               if (0 <= pos)
+                       cur = replace_object[pos]->sha1[1];
+       } while (0 <= pos);
+
+       return cur;
+}
index 713c6e16ac536eaa667cc9f22768670be129483c..861ca7c815b4857f1fde399617860e5a179e2ca3 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -1,8 +1,11 @@
 #include "cache.h"
 #include "string-list.h"
 #include "rerere.h"
-#include "xdiff/xdiff.h"
 #include "xdiff-interface.h"
+#include "dir.h"
+#include "resolve-undo.h"
+#include "ll-merge.h"
+#include "attr.h"
 
 /* if rerere_enabled == -1, fall back to detection of .git/rr-cache */
 static int rerere_enabled = -1;
@@ -43,7 +46,7 @@ static void read_rr(struct string_list *rr)
                        ; /* do nothing */
                if (i == sizeof(buf))
                        die("filename too long");
-               string_list_insert(buf, rr)->util = name;
+               string_list_insert(rr, buf)->util = name;
        }
        fclose(in);
 }
@@ -61,7 +64,7 @@ static int write_rr(struct string_list *rr, int out_fd)
                path = rr->items[i].string;
                length = strlen(path) + 1;
                if (write_in_full(out_fd, rr->items[i].util, 40) != 40 ||
-                   write_in_full(out_fd, "\t", 1) != 1 ||
+                   write_str_in_full(out_fd, "\t") != 1 ||
                    write_in_full(out_fd, path, length) != length)
                        die("unable to write rerere record");
        }
@@ -83,61 +86,106 @@ static inline void ferr_puts(const char *s, FILE *fp, int *err)
        ferr_write(s, strlen(s), fp, err);
 }
 
-static int handle_file(const char *path,
-        unsigned char *sha1, const char *output)
+struct rerere_io {
+       int (*getline)(struct strbuf *, struct rerere_io *);
+       FILE *output;
+       int wrerror;
+       /* some more stuff */
+};
+
+static void rerere_io_putstr(const char *str, struct rerere_io *io)
+{
+       if (io->output)
+               ferr_puts(str, io->output, &io->wrerror);
+}
+
+static void rerere_io_putconflict(int ch, int size, struct rerere_io *io)
+{
+       char buf[64];
+
+       while (size) {
+               if (size < sizeof(buf) - 2) {
+                       memset(buf, ch, size);
+                       buf[size] = '\n';
+                       buf[size + 1] = '\0';
+                       size = 0;
+               } else {
+                       int sz = sizeof(buf) - 1;
+                       if (size <= sz)
+                               sz -= (sz - size) + 1;
+                       memset(buf, ch, sz);
+                       buf[sz] = '\0';
+                       size -= sz;
+               }
+               rerere_io_putstr(buf, io);
+       }
+}
+
+static void rerere_io_putmem(const char *mem, size_t sz, struct rerere_io *io)
+{
+       if (io->output)
+               ferr_write(mem, sz, io->output, &io->wrerror);
+}
+
+struct rerere_io_file {
+       struct rerere_io io;
+       FILE *input;
+};
+
+static int rerere_file_getline(struct strbuf *sb, struct rerere_io *io_)
+{
+       struct rerere_io_file *io = (struct rerere_io_file *)io_;
+       return strbuf_getwholeline(sb, io->input, '\n');
+}
+
+static int is_cmarker(char *buf, int marker_char, int marker_size, int want_sp)
+{
+       while (marker_size--)
+               if (*buf++ != marker_char)
+                       return 0;
+       if (want_sp && *buf != ' ')
+               return 0;
+       return isspace(*buf);
+}
+
+static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_size)
 {
        git_SHA_CTX ctx;
-       char buf[1024];
        int hunk_no = 0;
        enum {
-               RR_CONTEXT = 0, RR_SIDE_1, RR_SIDE_2, RR_ORIGINAL,
+               RR_CONTEXT = 0, RR_SIDE_1, RR_SIDE_2, RR_ORIGINAL
        } hunk = RR_CONTEXT;
        struct strbuf one = STRBUF_INIT, two = STRBUF_INIT;
-       FILE *f = fopen(path, "r");
-       FILE *out = NULL;
-       int wrerror = 0;
-
-       if (!f)
-               return error("Could not open %s", path);
-
-       if (output) {
-               out = fopen(output, "w");
-               if (!out) {
-                       fclose(f);
-                       return error("Could not write %s", output);
-               }
-       }
+       struct strbuf buf = STRBUF_INIT;
 
        if (sha1)
                git_SHA1_Init(&ctx);
 
-       while (fgets(buf, sizeof(buf), f)) {
-               if (!prefixcmp(buf, "<<<<<<< ")) {
+       while (!io->getline(&buf, io)) {
+               if (is_cmarker(buf.buf, '<', marker_size, 1)) {
                        if (hunk != RR_CONTEXT)
                                goto bad;
                        hunk = RR_SIDE_1;
-               } else if (!prefixcmp(buf, "|||||||") && isspace(buf[7])) {
+               } else if (is_cmarker(buf.buf, '|', marker_size, 0)) {
                        if (hunk != RR_SIDE_1)
                                goto bad;
                        hunk = RR_ORIGINAL;
-               } else if (!prefixcmp(buf, "=======") && isspace(buf[7])) {
+               } else if (is_cmarker(buf.buf, '=', marker_size, 0)) {
                        if (hunk != RR_SIDE_1 && hunk != RR_ORIGINAL)
                                goto bad;
                        hunk = RR_SIDE_2;
-               } else if (!prefixcmp(buf, ">>>>>>> ")) {
+               } else if (is_cmarker(buf.buf, '>', marker_size, 1)) {
                        if (hunk != RR_SIDE_2)
                                goto bad;
                        if (strbuf_cmp(&one, &two) > 0)
                                strbuf_swap(&one, &two);
                        hunk_no++;
                        hunk = RR_CONTEXT;
-                       if (out) {
-                               ferr_puts("<<<<<<<\n", out, &wrerror);
-                               ferr_write(one.buf, one.len, out, &wrerror);
-                               ferr_puts("=======\n", out, &wrerror);
-                               ferr_write(two.buf, two.len, out, &wrerror);
-                               ferr_puts(">>>>>>>\n", out, &wrerror);
-                       }
+                       rerere_io_putconflict('<', marker_size, io);
+                       rerere_io_putmem(one.buf, one.len, io);
+                       rerere_io_putconflict('=', marker_size, io);
+                       rerere_io_putmem(two.buf, two.len, io);
+                       rerere_io_putconflict('>', marker_size, io);
                        if (sha1) {
                                git_SHA1_Update(&ctx, one.buf ? one.buf : "",
                                            one.len + 1);
@@ -147,13 +195,13 @@ static int handle_file(const char *path,
                        strbuf_reset(&one);
                        strbuf_reset(&two);
                } else if (hunk == RR_SIDE_1)
-                       strbuf_addstr(&one, buf);
+                       strbuf_addstr(&one, buf.buf);
                else if (hunk == RR_ORIGINAL)
                        ; /* discard */
                else if (hunk == RR_SIDE_2)
-                       strbuf_addstr(&two, buf);
-               else if (out)
-                       ferr_puts(buf, out, &wrerror);
+                       strbuf_addstr(&two, buf.buf);
+               else
+                       rerere_io_putstr(buf.buf, io);
                continue;
        bad:
                hunk = 99; /* force error exit */
@@ -161,26 +209,142 @@ static int handle_file(const char *path,
        }
        strbuf_release(&one);
        strbuf_release(&two);
+       strbuf_release(&buf);
 
-       fclose(f);
-       if (wrerror)
-               error("There were errors while writing %s (%s)",
-                     path, strerror(wrerror));
-       if (out && fclose(out))
-               wrerror = error("Failed to flush %s: %s",
-                               path, strerror(errno));
        if (sha1)
                git_SHA1_Final(sha1, &ctx);
-       if (hunk != RR_CONTEXT) {
+       if (hunk != RR_CONTEXT)
+               return -1;
+       return hunk_no;
+}
+
+static int handle_file(const char *path, unsigned char *sha1, const char *output)
+{
+       int hunk_no = 0;
+       struct rerere_io_file io;
+       int marker_size = ll_merge_marker_size(path);
+
+       memset(&io, 0, sizeof(io));
+       io.io.getline = rerere_file_getline;
+       io.input = fopen(path, "r");
+       io.io.wrerror = 0;
+       if (!io.input)
+               return error("Could not open %s", path);
+
+       if (output) {
+               io.io.output = fopen(output, "w");
+               if (!io.io.output) {
+                       fclose(io.input);
+                       return error("Could not write %s", output);
+               }
+       }
+
+       hunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size);
+
+       fclose(io.input);
+       if (io.io.wrerror)
+               error("There were errors while writing %s (%s)",
+                     path, strerror(io.io.wrerror));
+       if (io.io.output && fclose(io.io.output))
+               io.io.wrerror = error("Failed to flush %s: %s",
+                                     path, strerror(errno));
+
+       if (hunk_no < 0) {
                if (output)
-                       unlink(output);
+                       unlink_or_warn(output);
                return error("Could not parse conflict hunks in %s", path);
        }
-       if (wrerror)
+       if (io.io.wrerror)
                return -1;
        return hunk_no;
 }
 
+struct rerere_io_mem {
+       struct rerere_io io;
+       struct strbuf input;
+};
+
+static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_)
+{
+       struct rerere_io_mem *io = (struct rerere_io_mem *)io_;
+       char *ep;
+       size_t len;
+
+       strbuf_release(sb);
+       if (!io->input.len)
+               return -1;
+       ep = strchrnul(io->input.buf, '\n');
+       if (*ep == '\n')
+               ep++;
+       len = ep - io->input.buf;
+       strbuf_add(sb, io->input.buf, len);
+       strbuf_remove(&io->input, 0, len);
+       return 0;
+}
+
+static int handle_cache(const char *path, unsigned char *sha1, const char *output)
+{
+       mmfile_t mmfile[3];
+       mmbuffer_t result = {NULL, 0};
+       struct cache_entry *ce;
+       int pos, len, i, hunk_no;
+       struct rerere_io_mem io;
+       int marker_size = ll_merge_marker_size(path);
+
+       /*
+        * Reproduce the conflicted merge in-core
+        */
+       len = strlen(path);
+       pos = cache_name_pos(path, len);
+       if (0 <= pos)
+               return -1;
+       pos = -pos - 1;
+
+       for (i = 0; i < 3; i++) {
+               enum object_type type;
+               unsigned long size;
+
+               mmfile[i].size = 0;
+               mmfile[i].ptr = NULL;
+               if (active_nr <= pos)
+                       break;
+               ce = active_cache[pos++];
+               if (ce_namelen(ce) != len || memcmp(ce->name, path, len)
+                   || ce_stage(ce) != i + 1)
+                       break;
+               mmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size);
+               mmfile[i].size = size;
+       }
+       for (i = 0; i < 3; i++) {
+               if (!mmfile[i].ptr && !mmfile[i].size)
+                       mmfile[i].ptr = xstrdup("");
+       }
+       /*
+        * NEEDSWORK: handle conflicts from merges with
+        * merge.renormalize set, too
+        */
+       ll_merge(&result, path, &mmfile[0], NULL,
+                &mmfile[1], "ours",
+                &mmfile[2], "theirs", 0);
+       for (i = 0; i < 3; i++)
+               free(mmfile[i].ptr);
+
+       memset(&io, 0, sizeof(io));
+       io.io.getline = rerere_mem_getline;
+       if (output)
+               io.io.output = fopen(output, "w");
+       else
+               io.io.output = NULL;
+       strbuf_init(&io.input, 0);
+       strbuf_attach(&io.input, result.ptr, result.size, result.size);
+
+       hunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size);
+       strbuf_release(&io.input);
+       if (io.io.output)
+               fclose(io.io.output);
+       return hunk_no;
+}
+
 static int find_conflict(struct string_list *conflict)
 {
        int i;
@@ -194,7 +358,7 @@ static int find_conflict(struct string_list *conflict)
                    ce_same_name(e2, e3) &&
                    S_ISREG(e2->ce_mode) &&
                    S_ISREG(e3->ce_mode)) {
-                       string_list_insert((const char *)e2->name, conflict);
+                       string_list_insert(conflict, (const char *)e2->name);
                        i++; /* skip over both #2 and #3 */
                }
        }
@@ -204,21 +368,27 @@ static int find_conflict(struct string_list *conflict)
 static int merge(const char *name, const char *path)
 {
        int ret;
-       mmfile_t cur, base, other;
+       mmfile_t cur = {NULL, 0}, base = {NULL, 0}, other = {NULL, 0};
        mmbuffer_t result = {NULL, 0};
-       xpparam_t xpp = {XDF_NEED_MINIMAL};
 
        if (handle_file(path, NULL, rerere_path(name, "thisimage")) < 0)
                return 1;
 
        if (read_mmfile(&cur, rerere_path(name, "thisimage")) ||
                        read_mmfile(&base, rerere_path(name, "preimage")) ||
-                       read_mmfile(&other, rerere_path(name, "postimage")))
-               return 1;
-       ret = xdl_merge(&base, &cur, "", &other, "",
-                       &xpp, XDL_MERGE_ZEALOUS, &result);
+                       read_mmfile(&other, rerere_path(name, "postimage"))) {
+               ret = 1;
+               goto out;
+       }
+       ret = ll_merge(&result, path, &base, NULL, &cur, "", &other, "", 0);
        if (!ret) {
-               FILE *f = fopen(path, "w");
+               FILE *f;
+
+               if (utime(rerere_path(name, "postimage"), NULL) < 0)
+                       warning("failed utime() on %s: %s",
+                                       rerere_path(name, "postimage"),
+                                       strerror(errno));
+               f = fopen(path, "w");
                if (!f)
                        return error("Could not open %s: %s", path,
                                     strerror(errno));
@@ -229,6 +399,7 @@ static int merge(const char *name, const char *path)
                                     strerror(errno));
        }
 
+out:
        free(cur.ptr);
        free(base.ptr);
        free(other.ptr);
@@ -265,8 +436,8 @@ static int update_paths(struct string_list *update)
 
 static int do_plain_rerere(struct string_list *rr, int fd)
 {
-       struct string_list conflict = { NULL, 0, 0, 1 };
-       struct string_list update = { NULL, 0, 0, 1 };
+       struct string_list conflict = STRING_LIST_INIT_DUP;
+       struct string_list update = STRING_LIST_INIT_DUP;
        int i;
 
        find_conflict(&conflict);
@@ -288,7 +459,7 @@ static int do_plain_rerere(struct string_list *rr, int fd)
                        if (ret < 1)
                                continue;
                        hex = xstrdup(sha1_to_hex(sha1));
-                       string_list_insert(path, rr)->util = hex;
+                       string_list_insert(rr, path)->util = hex;
                        if (mkdir(git_path("rr-cache/%s", hex), 0755))
                                continue;
                        handle_file(path, NULL, rerere_path(hex, "preimage"));
@@ -310,7 +481,7 @@ static int do_plain_rerere(struct string_list *rr, int fd)
                if (has_rerere_resolution(name)) {
                        if (!merge(name, path)) {
                                if (rerere_autoupdate)
-                                       string_list_insert(path, &update);
+                                       string_list_insert(&update, path);
                                fprintf(stderr,
                                        "%s '%s' using previous resolution.\n",
                                        rerere_autoupdate
@@ -367,7 +538,7 @@ static int is_rerere_enabled(void)
        return 1;
 }
 
-int setup_rerere(struct string_list *merge_rr)
+int setup_rerere(struct string_list *merge_rr, int flags)
 {
        int fd;
 
@@ -375,6 +546,8 @@ int setup_rerere(struct string_list *merge_rr)
        if (!is_rerere_enabled())
                return -1;
 
+       if (flags & (RERERE_AUTOUPDATE|RERERE_NOAUTOUPDATE))
+               rerere_autoupdate = !!(flags & RERERE_AUTOUPDATE);
        merge_rr_path = git_pathdup("MERGE_RR");
        fd = hold_lock_file_for_update(&write_lock, merge_rr_path,
                                       LOCK_DIE_ON_ERROR);
@@ -382,13 +555,62 @@ int setup_rerere(struct string_list *merge_rr)
        return fd;
 }
 
-int rerere(void)
+int rerere(int flags)
 {
-       struct string_list merge_rr = { NULL, 0, 0, 1 };
+       struct string_list merge_rr = STRING_LIST_INIT_DUP;
        int fd;
 
-       fd = setup_rerere(&merge_rr);
+       fd = setup_rerere(&merge_rr, flags);
        if (fd < 0)
                return 0;
        return do_plain_rerere(&merge_rr, fd);
 }
+
+static int rerere_forget_one_path(const char *path, struct string_list *rr)
+{
+       const char *filename;
+       char *hex;
+       unsigned char sha1[20];
+       int ret;
+
+       ret = handle_cache(path, sha1, NULL);
+       if (ret < 1)
+               return error("Could not parse conflict hunks in '%s'", path);
+       hex = xstrdup(sha1_to_hex(sha1));
+       filename = rerere_path(hex, "postimage");
+       if (unlink(filename))
+               return (errno == ENOENT
+                       ? error("no remembered resolution for %s", path)
+                       : error("cannot unlink %s: %s", filename, strerror(errno)));
+
+       handle_cache(path, sha1, rerere_path(hex, "preimage"));
+       fprintf(stderr, "Updated preimage for '%s'\n", path);
+
+
+       string_list_insert(rr, path)->util = hex;
+       fprintf(stderr, "Forgot resolution for %s\n", path);
+       return 0;
+}
+
+int rerere_forget(const char **pathspec)
+{
+       int i, fd;
+       struct string_list conflict = STRING_LIST_INIT_DUP;
+       struct string_list merge_rr = STRING_LIST_INIT_DUP;
+
+       if (read_cache() < 0)
+               return error("Could not read index");
+
+       fd = setup_rerere(&merge_rr, RERERE_NOAUTOUPDATE);
+
+       unmerge_cache(pathspec);
+       find_conflict(&conflict);
+       for (i = 0; i < conflict.nr; i++) {
+               struct string_list_item *it = &conflict.items[i];
+               if (!match_pathspec(pathspec, it->string, strlen(it->string),
+                                   0, NULL))
+                       continue;
+               rerere_forget_one_path(it->string, &merge_rr);
+       }
+       return write_rr(&merge_rr, fd);
+}
index 13313f3f2b2cc6a8d895305b7ac92c12c1753682..eaa9004dcdbb2f0dfd446e1ac68237c6dff18530 100644 (file)
--- a/rerere.h
+++ b/rerere.h
@@ -3,9 +3,16 @@
 
 #include "string-list.h"
 
-extern int setup_rerere(struct string_list *);
-extern int rerere(void);
+#define RERERE_AUTOUPDATE   01
+#define RERERE_NOAUTOUPDATE 02
+
+extern int setup_rerere(struct string_list *, int);
+extern int rerere(int);
 extern const char *rerere_path(const char *hex, const char *file);
 extern int has_rerere_resolution(const char *hex);
+extern int rerere_forget(const char **);
+
+#define OPT_RERERE_AUTOUPDATE(v) OPT_UYN(0, "rerere-autoupdate", (v), \
+       "update the index with reused conflict resolution if possible")
 
 #endif
diff --git a/resolve-undo.c b/resolve-undo.c
new file mode 100644 (file)
index 0000000..72b4612
--- /dev/null
@@ -0,0 +1,172 @@
+#include "cache.h"
+#include "dir.h"
+#include "resolve-undo.h"
+#include "string-list.h"
+
+/* The only error case is to run out of memory in string-list */
+void record_resolve_undo(struct index_state *istate, struct cache_entry *ce)
+{
+       struct string_list_item *lost;
+       struct resolve_undo_info *ui;
+       struct string_list *resolve_undo;
+       int stage = ce_stage(ce);
+
+       if (!stage)
+               return;
+
+       if (!istate->resolve_undo) {
+               resolve_undo = xcalloc(1, sizeof(*resolve_undo));
+               resolve_undo->strdup_strings = 1;
+               istate->resolve_undo = resolve_undo;
+       }
+       resolve_undo = istate->resolve_undo;
+       lost = string_list_insert(resolve_undo, ce->name);
+       if (!lost->util)
+               lost->util = xcalloc(1, sizeof(*ui));
+       ui = lost->util;
+       hashcpy(ui->sha1[stage - 1], ce->sha1);
+       ui->mode[stage - 1] = ce->ce_mode;
+}
+
+void resolve_undo_write(struct strbuf *sb, struct string_list *resolve_undo)
+{
+       struct string_list_item *item;
+       for_each_string_list_item(item, resolve_undo) {
+               struct resolve_undo_info *ui = item->util;
+               int i;
+
+               if (!ui)
+                       continue;
+               strbuf_addstr(sb, item->string);
+               strbuf_addch(sb, 0);
+               for (i = 0; i < 3; i++)
+                       strbuf_addf(sb, "%o%c", ui->mode[i], 0);
+               for (i = 0; i < 3; i++) {
+                       if (!ui->mode[i])
+                               continue;
+                       strbuf_add(sb, ui->sha1[i], 20);
+               }
+       }
+}
+
+struct string_list *resolve_undo_read(const char *data, unsigned long size)
+{
+       struct string_list *resolve_undo;
+       size_t len;
+       char *endptr;
+       int i;
+
+       resolve_undo = xcalloc(1, sizeof(*resolve_undo));
+       resolve_undo->strdup_strings = 1;
+
+       while (size) {
+               struct string_list_item *lost;
+               struct resolve_undo_info *ui;
+
+               len = strlen(data) + 1;
+               if (size <= len)
+                       goto error;
+               lost = string_list_insert(resolve_undo, data);
+               if (!lost->util)
+                       lost->util = xcalloc(1, sizeof(*ui));
+               ui = lost->util;
+               size -= len;
+               data += len;
+
+               for (i = 0; i < 3; i++) {
+                       ui->mode[i] = strtoul(data, &endptr, 8);
+                       if (!endptr || endptr == data || *endptr)
+                               goto error;
+                       len = (endptr + 1) - (char*)data;
+                       if (size <= len)
+                               goto error;
+                       size -= len;
+                       data += len;
+               }
+
+               for (i = 0; i < 3; i++) {
+                       if (!ui->mode[i])
+                               continue;
+                       if (size < 20)
+                               goto error;
+                       hashcpy(ui->sha1[i], (const unsigned char *)data);
+                       size -= 20;
+                       data += 20;
+               }
+       }
+       return resolve_undo;
+
+error:
+       string_list_clear(resolve_undo, 1);
+       error("Index records invalid resolve-undo information");
+       return NULL;
+}
+
+void resolve_undo_clear_index(struct index_state *istate)
+{
+       struct string_list *resolve_undo = istate->resolve_undo;
+       if (!resolve_undo)
+               return;
+       string_list_clear(resolve_undo, 1);
+       free(resolve_undo);
+       istate->resolve_undo = NULL;
+       istate->cache_changed = 1;
+}
+
+int unmerge_index_entry_at(struct index_state *istate, int pos)
+{
+       struct cache_entry *ce;
+       struct string_list_item *item;
+       struct resolve_undo_info *ru;
+       int i, err = 0;
+
+       if (!istate->resolve_undo)
+               return pos;
+
+       ce = istate->cache[pos];
+       if (ce_stage(ce)) {
+               /* already unmerged */
+               while ((pos < istate->cache_nr) &&
+                      ! strcmp(istate->cache[pos]->name, ce->name))
+                       pos++;
+               return pos - 1; /* return the last entry processed */
+       }
+       item = string_list_lookup(istate->resolve_undo, ce->name);
+       if (!item)
+               return pos;
+       ru = item->util;
+       if (!ru)
+               return pos;
+       remove_index_entry_at(istate, pos);
+       for (i = 0; i < 3; i++) {
+               struct cache_entry *nce;
+               if (!ru->mode[i])
+                       continue;
+               nce = make_cache_entry(ru->mode[i], ru->sha1[i],
+                                      ce->name, i + 1, 0);
+               if (add_index_entry(istate, nce, ADD_CACHE_OK_TO_ADD)) {
+                       err = 1;
+                       error("cannot unmerge '%s'", ce->name);
+               }
+       }
+       if (err)
+               return pos;
+       free(ru);
+       item->util = NULL;
+       return unmerge_index_entry_at(istate, pos);
+}
+
+void unmerge_index(struct index_state *istate, const char **pathspec)
+{
+       int i;
+
+       if (!istate->resolve_undo)
+               return;
+
+       for (i = 0; i < istate->cache_nr; i++) {
+               struct cache_entry *ce = istate->cache[i];
+               if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL))
+                       continue;
+               i = unmerge_index_entry_at(istate, i);
+       }
+}
diff --git a/resolve-undo.h b/resolve-undo.h
new file mode 100644 (file)
index 0000000..8458769
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef RESOLVE_UNDO_H
+#define RESOLVE_UNDO_H
+
+struct resolve_undo_info {
+       unsigned int mode[3];
+       unsigned char sha1[3][20];
+};
+
+extern void record_resolve_undo(struct index_state *, struct cache_entry *);
+extern void resolve_undo_write(struct strbuf *, struct string_list *);
+extern struct string_list *resolve_undo_read(const char *, unsigned long);
+extern void resolve_undo_clear_index(struct index_state *);
+extern int unmerge_index_entry_at(struct index_state *, int);
+extern void unmerge_index(struct index_state *, const char **);
+
+#endif
index 18b7ebbbd599417462b5c4275c2581b91ebeca6a..b1c18906badc5fc4ce3af48daa7c226365e3c08f 100644 (file)
@@ -12,6 +12,7 @@
 #include "patch-ids.h"
 #include "decorate.h"
 #include "log-tree.h"
+#include "string-list.h"
 
 volatile show_early_output_fn_t show_early_output;
 
@@ -133,11 +134,21 @@ void mark_parents_uninteresting(struct commit *commit)
 static void add_pending_object_with_mode(struct rev_info *revs, struct object *obj, const char *name, unsigned mode)
 {
        if (revs->no_walk && (obj->flags & UNINTERESTING))
-               die("object ranges do not make sense when not walking revisions");
-       if (revs->reflog_info && obj->type == OBJ_COMMIT &&
-                       add_reflog_for_walk(revs->reflog_info,
-                               (struct commit *)obj, name))
-               return;
+               revs->no_walk = 0;
+       if (revs->reflog_info && obj->type == OBJ_COMMIT) {
+               struct strbuf buf = STRBUF_INIT;
+               int len = interpret_branch_name(name, &buf);
+               int st;
+
+               if (0 < len && name[len] && buf.len)
+                       strbuf_addstr(&buf, name + len);
+               st = add_reflog_for_walk(revs->reflog_info,
+                                        (struct commit *)obj,
+                                        buf.buf[0] ? buf.buf: name);
+               strbuf_release(&buf);
+               if (st)
+                       return;
+       }
        add_object_array_with_mode(obj, name, &revs->pending, mode);
 }
 
@@ -256,34 +267,23 @@ static int everybody_uninteresting(struct commit_list *orig)
 
 /*
  * The goal is to get REV_TREE_NEW as the result only if the
- * diff consists of all '+' (and no other changes), and
- * REV_TREE_DIFFERENT otherwise (of course if the trees are
- * the same we want REV_TREE_SAME).  That means that once we
- * get to REV_TREE_DIFFERENT, we do not have to look any further.
+ * diff consists of all '+' (and no other changes), REV_TREE_OLD
+ * if the whole diff is removal of old data, and otherwise
+ * REV_TREE_DIFFERENT (of course if the trees are the same we
+ * want REV_TREE_SAME).
+ * That means that once we get to REV_TREE_DIFFERENT, we do not
+ * have to look any further.
  */
 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 *fullpath)
+                   const char *fullpath, unsigned dirty_submodule)
 {
-       int diff = REV_TREE_DIFFERENT;
+       int diff = addremove == '+' ? REV_TREE_NEW : REV_TREE_OLD;
 
-       /*
-        * 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;
+       tree_difference |= diff;
        if (tree_difference == REV_TREE_DIFFERENT)
                DIFF_OPT_SET(options, HAS_CHANGES);
 }
@@ -292,7 +292,8 @@ 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 *fullpath)
+                const char *fullpath,
+                unsigned old_dirty_submodule, unsigned new_dirty_submodule)
 {
        tree_difference = REV_TREE_DIFFERENT;
        DIFF_OPT_SET(options, HAS_CHANGES);
@@ -305,6 +306,8 @@ static int rev_compare_tree(struct rev_info *revs, struct commit *parent, struct
 
        if (!t1)
                return REV_TREE_NEW;
+       if (!t2)
+               return REV_TREE_OLD;
 
        if (revs->simplify_by_decoration) {
                /*
@@ -323,8 +326,7 @@ static int rev_compare_tree(struct rev_info *revs, struct commit *parent, struct
                if (!revs->prune_data)
                        return REV_TREE_SAME;
        }
-       if (!t2)
-               return REV_TREE_DIFFERENT;
+
        tree_difference = REV_TREE_SAME;
        DIFF_OPT_CLR(&revs->pruning, HAS_CHANGES);
        if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "",
@@ -429,6 +431,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
                                p->parents = NULL;
                        }
                /* fallthrough */
+               case REV_TREE_OLD:
                case REV_TREE_DIFFERENT:
                        tree_changed = 1;
                        pp = &parent->next;
@@ -545,6 +548,9 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs)
                        right_count++;
        }
 
+       if (!left_count || !right_count)
+               return;
+
        left_first = left_count < right_count;
        init_patch_ids(&ids);
        if (revs->diffopt.nr_paths) {
@@ -640,6 +646,93 @@ static int still_interesting(struct commit_list *src, unsigned long date, int sl
        return slop-1;
 }
 
+/*
+ * "rev-list --ancestry-path A..B" computes commits that are ancestors
+ * of B but not ancestors of A but further limits the result to those
+ * that are descendants of A.  This takes the list of bottom commits and
+ * the result of "A..B" without --ancestry-path, and limits the latter
+ * further to the ones that can reach one of the commits in "bottom".
+ */
+static void limit_to_ancestry(struct commit_list *bottom, struct commit_list *list)
+{
+       struct commit_list *p;
+       struct commit_list *rlist = NULL;
+       int made_progress;
+
+       /*
+        * Reverse the list so that it will be likely that we would
+        * process parents before children.
+        */
+       for (p = list; p; p = p->next)
+               commit_list_insert(p->item, &rlist);
+
+       for (p = bottom; p; p = p->next)
+               p->item->object.flags |= TMP_MARK;
+
+       /*
+        * Mark the ones that can reach bottom commits in "list",
+        * in a bottom-up fashion.
+        */
+       do {
+               made_progress = 0;
+               for (p = rlist; p; p = p->next) {
+                       struct commit *c = p->item;
+                       struct commit_list *parents;
+                       if (c->object.flags & (TMP_MARK | UNINTERESTING))
+                               continue;
+                       for (parents = c->parents;
+                            parents;
+                            parents = parents->next) {
+                               if (!(parents->item->object.flags & TMP_MARK))
+                                       continue;
+                               c->object.flags |= TMP_MARK;
+                               made_progress = 1;
+                               break;
+                       }
+               }
+       } while (made_progress);
+
+       /*
+        * NEEDSWORK: decide if we want to remove parents that are
+        * not marked with TMP_MARK from commit->parents for commits
+        * in the resulting list.  We may not want to do that, though.
+        */
+
+       /*
+        * The ones that are not marked with TMP_MARK are uninteresting
+        */
+       for (p = list; p; p = p->next) {
+               struct commit *c = p->item;
+               if (c->object.flags & TMP_MARK)
+                       continue;
+               c->object.flags |= UNINTERESTING;
+       }
+
+       /* We are done with the TMP_MARK */
+       for (p = list; p; p = p->next)
+               p->item->object.flags &= ~TMP_MARK;
+       for (p = bottom; p; p = p->next)
+               p->item->object.flags &= ~TMP_MARK;
+       free_commit_list(rlist);
+}
+
+/*
+ * Before walking the history, keep the set of "negative" refs the
+ * caller has asked to exclude.
+ *
+ * This is used to compute "rev-list --ancestry-path A..B", as we need
+ * to filter the result of "A..B" further to the ones that can actually
+ * reach A.
+ */
+static struct commit_list *collect_bottom_commits(struct commit_list *list)
+{
+       struct commit_list *elem, *bottom = NULL;
+       for (elem = list; elem; elem = elem->next)
+               if (elem->item->object.flags & UNINTERESTING)
+                       commit_list_insert(elem->item, &bottom);
+       return bottom;
+}
+
 static int limit_list(struct rev_info *revs)
 {
        int slop = SLOP;
@@ -647,6 +740,13 @@ static int limit_list(struct rev_info *revs)
        struct commit_list *list = revs->commits;
        struct commit_list *newlist = NULL;
        struct commit_list **p = &newlist;
+       struct commit_list *bottom = NULL;
+
+       if (revs->ancestry_path) {
+               bottom = collect_bottom_commits(list);
+               if (!bottom)
+                       die("--ancestry-path given but there are no bottom commits");
+       }
 
        while (list) {
                struct commit_list *entry = list;
@@ -688,6 +788,11 @@ static int limit_list(struct rev_info *revs)
        if (revs->cherry_pick)
                cherry_pick_list(newlist, revs);
 
+       if (bottom) {
+               limit_to_ancestry(bottom, newlist);
+               free_commit_list(bottom);
+       }
+
        revs->commits = newlist;
        return 0;
 }
@@ -708,13 +813,19 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, int flag,
        return 0;
 }
 
-static void handle_refs(struct rev_info *revs, unsigned flags,
-               int (*for_each)(each_ref_fn, void *))
+static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs,
+       unsigned flags)
+{
+       cb->all_revs = revs;
+       cb->all_flags = flags;
+}
+
+static void handle_refs(const char *submodule, struct rev_info *revs, unsigned flags,
+               int (*for_each)(const char *, each_ref_fn, void *))
 {
        struct all_refs_cb cb;
-       cb.all_revs = revs;
-       cb.all_flags = flags;
-       for_each(handle_one_ref, &cb);
+       init_all_refs_cb(&cb, revs, flags);
+       for_each(submodule, handle_one_ref, &cb);
 }
 
 static void handle_one_reflog_commit(unsigned char *sha1, void *cb_data)
@@ -800,7 +911,7 @@ void init_revisions(struct rev_info *revs, const char *prefix)
        revs->ignore_merges = 1;
        revs->simplify_history = 1;
        DIFF_OPT_SET(&revs->pruning, RECURSIVE);
-       DIFF_OPT_SET(&revs->pruning, QUIET);
+       DIFF_OPT_SET(&revs->pruning, QUICK);
        revs->pruning.add_remove = file_add_remove;
        revs->pruning.change = file_change;
        revs->lifo = 1;
@@ -815,6 +926,7 @@ void init_revisions(struct rev_info *revs, const char *prefix)
 
        revs->grep_filter.status_only = 1;
        revs->grep_filter.pattern_tail = &(revs->grep_filter.pattern_list);
+       revs->grep_filter.header_tail = &(revs->grep_filter.header_list);
        revs->grep_filter.regflags = REG_NEWLINE;
 
        diff_setup(&revs->diffopt);
@@ -962,21 +1074,59 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
        return 0;
 }
 
-void read_revisions_from_stdin(struct rev_info *revs)
+static void read_pathspec_from_stdin(struct rev_info *revs, struct strbuf *sb, const char ***prune_data)
 {
-       char line[1000];
+       const char **prune = *prune_data;
+       int prune_nr;
+       int prune_alloc;
 
-       while (fgets(line, sizeof(line), stdin) != NULL) {
-               int len = strlen(line);
-               if (len && line[len - 1] == '\n')
-                       line[--len] = '\0';
+       /* count existing ones */
+       if (!prune)
+               prune_nr = 0;
+       else
+               for (prune_nr = 0; prune[prune_nr]; prune_nr++)
+                       ;
+       prune_alloc = prune_nr; /* not really, but we do not know */
+
+       while (strbuf_getwholeline(sb, stdin, '\n') != EOF) {
+               int len = sb->len;
+               if (len && sb->buf[len - 1] == '\n')
+                       sb->buf[--len] = '\0';
+               ALLOC_GROW(prune, prune_nr+1, prune_alloc);
+               prune[prune_nr++] = xstrdup(sb->buf);
+       }
+       if (prune) {
+               ALLOC_GROW(prune, prune_nr+1, prune_alloc);
+               prune[prune_nr] = NULL;
+       }
+       *prune_data = prune;
+}
+
+static void read_revisions_from_stdin(struct rev_info *revs, const char ***prune)
+{
+       struct strbuf sb;
+       int seen_dashdash = 0;
+
+       strbuf_init(&sb, 1000);
+       while (strbuf_getwholeline(&sb, stdin, '\n') != EOF) {
+               int len = sb.len;
+               if (len && sb.buf[len - 1] == '\n')
+                       sb.buf[--len] = '\0';
                if (!len)
                        break;
-               if (line[0] == '-')
+               if (sb.buf[0] == '-') {
+                       if (len == 2 && sb.buf[1] == '-') {
+                               seen_dashdash = 1;
+                               break;
+                       }
                        die("options not supported in --stdin mode");
-               if (handle_revision_arg(line, revs, 0, 1))
-                       die("bad revision '%s'", line);
+               }
+               if (handle_revision_arg(sb.buf, revs, 0, 1))
+                       die("bad revision '%s'", sb.buf);
        }
+       if (seen_dashdash)
+               read_pathspec_from_stdin(revs, &sb, prune);
+       strbuf_release(&sb);
 }
 
 static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what)
@@ -998,45 +1148,64 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                               int *unkc, const char **unkv)
 {
        const char *arg = argv[0];
+       const char *optarg;
+       int argcount;
 
        /* pseudo revision arguments */
        if (!strcmp(arg, "--all") || !strcmp(arg, "--branches") ||
            !strcmp(arg, "--tags") || !strcmp(arg, "--remotes") ||
            !strcmp(arg, "--reflog") || !strcmp(arg, "--not") ||
-           !strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk"))
+           !strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk") ||
+           !strcmp(arg, "--bisect"))
        {
                unkv[(*unkc)++] = arg;
                return 1;
        }
 
-       if (!prefixcmp(arg, "--max-count=")) {
-               revs->max_count = atoi(arg + 12);
-       } else if (!prefixcmp(arg, "--skip=")) {
-               revs->skip_count = atoi(arg + 7);
+       if ((argcount = parse_long_opt("max-count", argv, &optarg))) {
+               revs->max_count = atoi(optarg);
+               revs->no_walk = 0;
+               return argcount;
+       } else if ((argcount = parse_long_opt("skip", argv, &optarg))) {
+               revs->skip_count = atoi(optarg);
+               return argcount;
        } else if ((*arg == '-') && isdigit(arg[1])) {
        /* accept -<digit>, like traditional "head" */
                revs->max_count = atoi(arg + 1);
+               revs->no_walk = 0;
        } else if (!strcmp(arg, "-n")) {
                if (argc <= 1)
                        return error("-n requires an argument");
                revs->max_count = atoi(argv[1]);
+               revs->no_walk = 0;
                return 2;
        } else if (!prefixcmp(arg, "-n")) {
                revs->max_count = atoi(arg + 2);
-       } else if (!prefixcmp(arg, "--max-age=")) {
-               revs->max_age = atoi(arg + 10);
-       } else if (!prefixcmp(arg, "--since=")) {
-               revs->max_age = approxidate(arg + 8);
-       } else if (!prefixcmp(arg, "--after=")) {
-               revs->max_age = approxidate(arg + 8);
-       } else if (!prefixcmp(arg, "--min-age=")) {
-               revs->min_age = atoi(arg + 10);
-       } else if (!prefixcmp(arg, "--before=")) {
-               revs->min_age = approxidate(arg + 9);
-       } else if (!prefixcmp(arg, "--until=")) {
-               revs->min_age = approxidate(arg + 8);
+               revs->no_walk = 0;
+       } else if ((argcount = parse_long_opt("max-age", argv, &optarg))) {
+               revs->max_age = atoi(optarg);
+               return argcount;
+       } else if ((argcount = parse_long_opt("since", argv, &optarg))) {
+               revs->max_age = approxidate(optarg);
+               return argcount;
+       } else if ((argcount = parse_long_opt("after", argv, &optarg))) {
+               revs->max_age = approxidate(optarg);
+               return argcount;
+       } else if ((argcount = parse_long_opt("min-age", argv, &optarg))) {
+               revs->min_age = atoi(optarg);
+               return argcount;
+       } else if ((argcount = parse_long_opt("before", argv, &optarg))) {
+               revs->min_age = approxidate(optarg);
+               return argcount;
+       } else if ((argcount = parse_long_opt("until", argv, &optarg))) {
+               revs->min_age = approxidate(optarg);
+               return argcount;
        } else if (!strcmp(arg, "--first-parent")) {
                revs->first_parent_only = 1;
+       } else if (!strcmp(arg, "--ancestry-path")) {
+               revs->ancestry_path = 1;
+               revs->simplify_history = 0;
+               revs->limited = 1;
        } else if (!strcmp(arg, "-g") || !strcmp(arg, "--walk-reflogs")) {
                init_reflog_walk(&revs->reflog_info);
        } else if (!strcmp(arg, "--default")) {
@@ -1061,7 +1230,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->simplify_by_decoration = 1;
                revs->limited = 1;
                revs->prune = 1;
-               load_ref_decorations();
+               load_ref_decorations(DECORATE_SHORT_REFS);
        } else if (!strcmp(arg, "--date-order")) {
                revs->lifo = 0;
                revs->topo_order = 1;
@@ -1086,12 +1255,16 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->show_all = 1;
        } else if (!strcmp(arg, "--remove-empty")) {
                revs->remove_empty_trees = 1;
+       } else if (!strcmp(arg, "--merges")) {
+               revs->merges_only = 1;
        } else if (!strcmp(arg, "--no-merges")) {
                revs->no_merges = 1;
        } else if (!strcmp(arg, "--boundary")) {
                revs->boundary = 1;
        } else if (!strcmp(arg, "--left-right")) {
                revs->left_right = 1;
+       } else if (!strcmp(arg, "--count")) {
+               revs->count = 1;
        } else if (!strcmp(arg, "--cherry-pick")) {
                revs->cherry_pick = 1;
                revs->limited = 1;
@@ -1129,13 +1302,46 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->verbose_header = 1;
        } else if (!strcmp(arg, "--pretty")) {
                revs->verbose_header = 1;
+               revs->pretty_given = 1;
                get_commit_format(arg+8, revs);
        } else if (!prefixcmp(arg, "--pretty=") || !prefixcmp(arg, "--format=")) {
+               /*
+                * Detached form ("--pretty X" as opposed to "--pretty=X")
+                * not allowed, since the argument is optional.
+                */
                revs->verbose_header = 1;
+               revs->pretty_given = 1;
                get_commit_format(arg+9, revs);
+       } else if (!strcmp(arg, "--show-notes")) {
+               revs->show_notes = 1;
+               revs->show_notes_given = 1;
+       } else if (!prefixcmp(arg, "--show-notes=")) {
+               struct strbuf buf = STRBUF_INIT;
+               revs->show_notes = 1;
+               revs->show_notes_given = 1;
+               if (!revs->notes_opt.extra_notes_refs)
+                       revs->notes_opt.extra_notes_refs = xcalloc(1, sizeof(struct string_list));
+               if (!prefixcmp(arg+13, "refs/"))
+                       /* happy */;
+               else if (!prefixcmp(arg+13, "notes/"))
+                       strbuf_addstr(&buf, "refs/");
+               else
+                       strbuf_addstr(&buf, "refs/notes/");
+               strbuf_addstr(&buf, arg+13);
+               string_list_append(revs->notes_opt.extra_notes_refs,
+                                  strbuf_detach(&buf, NULL));
+       } else if (!strcmp(arg, "--no-notes")) {
+               revs->show_notes = 0;
+               revs->show_notes_given = 1;
+       } else if (!strcmp(arg, "--standard-notes")) {
+               revs->show_notes_given = 1;
+               revs->notes_opt.suppress_default_notes = 0;
+       } else if (!strcmp(arg, "--no-standard-notes")) {
+               revs->notes_opt.suppress_default_notes = 1;
        } else if (!strcmp(arg, "--oneline")) {
                revs->verbose_header = 1;
                get_commit_format("oneline", revs);
+               revs->pretty_given = 1;
                revs->abbrev_commit = 1;
        } else if (!strcmp(arg, "--graph")) {
                revs->topo_order = 1;
@@ -1166,20 +1372,26 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->simplify_history = 0;
        } else if (!strcmp(arg, "--relative-date")) {
                revs->date_mode = DATE_RELATIVE;
-       } else if (!strncmp(arg, "--date=", 7)) {
-               revs->date_mode = parse_date_format(arg + 7);
+               revs->date_mode_explicit = 1;
+       } else if ((argcount = parse_long_opt("date", argv, &optarg))) {
+               revs->date_mode = parse_date_format(optarg);
+               revs->date_mode_explicit = 1;
+               return argcount;
        } else if (!strcmp(arg, "--log-size")) {
                revs->show_log_size = 1;
        }
        /*
         * Grepping the commit log
         */
-       else if (!prefixcmp(arg, "--author=")) {
-               add_header_grep(revs, GREP_HEADER_AUTHOR, arg+9);
-       } else if (!prefixcmp(arg, "--committer=")) {
-               add_header_grep(revs, GREP_HEADER_COMMITTER, arg+12);
-       } else if (!prefixcmp(arg, "--grep=")) {
-               add_message_grep(revs, arg+7);
+       else if ((argcount = parse_long_opt("author", argv, &optarg))) {
+               add_header_grep(revs, GREP_HEADER_AUTHOR, optarg);
+               return argcount;
+       } else if ((argcount = parse_long_opt("committer", argv, &optarg))) {
+               add_header_grep(revs, GREP_HEADER_COMMITTER, optarg);
+               return argcount;
+       } else if ((argcount = parse_long_opt("grep", argv, &optarg))) {
+               add_message_grep(revs, optarg);
+               return argcount;
        } else if (!strcmp(arg, "--extended-regexp") || !strcmp(arg, "-E")) {
                revs->grep_filter.regflags |= REG_EXTENDED;
        } else if (!strcmp(arg, "--regexp-ignore-case") || !strcmp(arg, "-i")) {
@@ -1188,12 +1400,12 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->grep_filter.fixed = 1;
        } else if (!strcmp(arg, "--all-match")) {
                revs->grep_filter.all_match = 1;
-       } else if (!prefixcmp(arg, "--encoding=")) {
-               arg += 11;
-               if (strcmp(arg, "none"))
-                       git_log_output_encoding = xstrdup(arg);
+       } else if ((argcount = parse_long_opt("encoding", argv, &optarg))) {
+               if (strcmp(optarg, "none"))
+                       git_log_output_encoding = xstrdup(optarg);
                else
                        git_log_output_encoding = "";
+               return argcount;
        } else if (!strcmp(arg, "--reverse")) {
                revs->reverse ^= 1;
        } else if (!strcmp(arg, "--children")) {
@@ -1223,6 +1435,44 @@ void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx,
        ctx->argc -= n;
 }
 
+static int for_each_bad_bisect_ref(const char *submodule, each_ref_fn fn, void *cb_data)
+{
+       return for_each_ref_in_submodule(submodule, "refs/bisect/bad", fn, cb_data);
+}
+
+static int for_each_good_bisect_ref(const char *submodule, each_ref_fn fn, void *cb_data)
+{
+       return for_each_ref_in_submodule(submodule, "refs/bisect/good", fn, cb_data);
+}
+
+static void append_prune_data(const char ***prune_data, const char **av)
+{
+       const char **prune = *prune_data;
+       int prune_nr;
+       int prune_alloc;
+
+       if (!prune) {
+               *prune_data = av;
+               return;
+       }
+
+       /* count existing ones */
+       for (prune_nr = 0; prune[prune_nr]; prune_nr++)
+               ;
+       prune_alloc = prune_nr; /* not really, but we do not know */
+
+       while (*av) {
+               ALLOC_GROW(prune, prune_nr+1, prune_alloc);
+               prune[prune_nr++] = *av;
+               av++;
+       }
+       if (prune) {
+               ALLOC_GROW(prune, prune_nr+1, prune_alloc);
+               prune[prune_nr] = NULL;
+       }
+       *prune_data = prune;
+}
+
 /*
  * Parse revision information, filling in the "rev_info" structure,
  * and removing the used arguments from the argument list.
@@ -1230,9 +1480,16 @@ void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx,
  * 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 setup_revisions(int argc, const char **argv, struct rev_info *revs, struct setup_revision_opt *opt)
 {
-       int i, flags, left, seen_dashdash;
+       int i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0;
+       const char **prune_data = NULL;
+       const char *submodule = NULL;
+       const char *optarg;
+       int argcount;
+
+       if (opt)
+               submodule = opt->submodule;
 
        /* First, search for "--" */
        seen_dashdash = 0;
@@ -1243,33 +1500,65 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                argv[i] = NULL;
                argc = i;
                if (argv[i + 1])
-                       revs->prune_data = get_pathspec(revs->prefix, argv + i + 1);
+                       prune_data = argv + i + 1;
                seen_dashdash = 1;
                break;
        }
 
        /* Second, deal with arguments and options */
        flags = 0;
+       read_from_stdin = 0;
        for (left = i = 1; i < argc; i++) {
                const char *arg = argv[i];
                if (*arg == '-') {
                        int opts;
 
                        if (!strcmp(arg, "--all")) {
-                               handle_refs(revs, flags, for_each_ref);
-                               handle_refs(revs, flags, head_ref);
+                               handle_refs(submodule, revs, flags, for_each_ref_submodule);
+                               handle_refs(submodule, revs, flags, head_ref_submodule);
                                continue;
                        }
                        if (!strcmp(arg, "--branches")) {
-                               handle_refs(revs, flags, for_each_branch_ref);
+                               handle_refs(submodule, revs, flags, for_each_branch_ref_submodule);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--bisect")) {
+                               handle_refs(submodule, revs, flags, for_each_bad_bisect_ref);
+                               handle_refs(submodule, revs, flags ^ UNINTERESTING, for_each_good_bisect_ref);
+                               revs->bisect = 1;
                                continue;
                        }
                        if (!strcmp(arg, "--tags")) {
-                               handle_refs(revs, flags, for_each_tag_ref);
+                               handle_refs(submodule, revs, flags, for_each_tag_ref_submodule);
                                continue;
                        }
                        if (!strcmp(arg, "--remotes")) {
-                               handle_refs(revs, flags, for_each_remote_ref);
+                               handle_refs(submodule, revs, flags, for_each_remote_ref_submodule);
+                               continue;
+                       }
+                       if ((argcount = parse_long_opt("glob", argv + i, &optarg))) {
+                               struct all_refs_cb cb;
+                               i += argcount - 1;
+                               init_all_refs_cb(&cb, revs, flags);
+                               for_each_glob_ref(handle_one_ref, optarg, &cb);
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--branches=")) {
+                               struct all_refs_cb cb;
+                               init_all_refs_cb(&cb, revs, flags);
+                               for_each_glob_ref_in(handle_one_ref, arg + 11, "refs/heads/", &cb);
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--tags=")) {
+                               struct all_refs_cb cb;
+                               init_all_refs_cb(&cb, revs, flags);
+                               for_each_glob_ref_in(handle_one_ref, arg + 7, "refs/tags/", &cb);
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--remotes=")) {
+                               struct all_refs_cb cb;
+                               init_all_refs_cb(&cb, revs, flags);
+                               for_each_glob_ref_in(handle_one_ref, arg + 10, "refs/remotes/", &cb);
                                continue;
                        }
                        if (!strcmp(arg, "--reflog")) {
@@ -1288,6 +1577,16 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                revs->no_walk = 0;
                                continue;
                        }
+                       if (!strcmp(arg, "--stdin")) {
+                               if (revs->disable_stdin) {
+                                       argv[left++] = arg;
+                                       continue;
+                               }
+                               if (read_from_stdin++)
+                                       die("--stdin given twice?");
+                               read_revisions_from_stdin(revs, &prune_data);
+                               continue;
+                       }
 
                        opts = handle_revision_opt(revs, argc - i, argv + i, &left, argv);
                        if (opts > 0) {
@@ -1313,17 +1612,23 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                        for (j = i; j < argc; j++)
                                verify_filename(revs->prefix, argv[j]);
 
-                       revs->prune_data = get_pathspec(revs->prefix,
-                                                       argv + i);
+                       append_prune_data(&prune_data, argv + i);
                        break;
                }
+               else
+                       got_rev_arg = 1;
        }
 
+       if (prune_data)
+               revs->prune_data = get_pathspec(revs->prefix, prune_data);
+
        if (revs->def == NULL)
-               revs->def = def;
+               revs->def = opt ? opt->def : NULL;
+       if (opt && opt->tweak)
+               opt->tweak(revs, opt);
        if (revs->show_merge)
                prepare_show_merge(revs);
-       if (revs->def && !revs->pending.nr) {
+       if (revs->def && !revs->pending.nr && !got_rev_arg) {
                unsigned char sha1[20];
                struct object *object;
                unsigned mode;
@@ -1354,11 +1659,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                if (!revs->full_diff)
                        diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
        }
-       if (revs->combine_merges) {
+       if (revs->combine_merges)
                revs->ignore_merges = 0;
-               if (revs->dense_combined_merges && !revs->diffopt.output_format)
-                       revs->diffopt.output_format = DIFF_FORMAT_PATCH;
-       }
        revs->diffopt.abbrev = revs->abbrev;
        if (diff_setup_done(&revs->diffopt) < 0)
                die("diff_setup_done failed");
@@ -1613,7 +1915,7 @@ int prepare_revision_walk(struct rev_info *revs)
 enum rewrite_result {
        rewrite_one_ok,
        rewrite_one_noparents,
-       rewrite_one_error,
+       rewrite_one_error
 };
 
 static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp)
@@ -1659,7 +1961,7 @@ static int rewrite_parents(struct rev_info *revs, struct commit *commit)
 
 static int commit_match(struct commit *commit, struct rev_info *opt)
 {
-       if (!opt->grep_filter.pattern_list)
+       if (!opt->grep_filter.pattern_list && !opt->grep_filter.header_list)
                return 1;
        return grep_buffer(&opt->grep_filter,
                           NULL, /* we say nothing, not even filename */
@@ -1671,7 +1973,7 @@ static inline int want_ancestry(struct rev_info *revs)
        return (revs->rewrite_parents || revs->children.name);
 }
 
-enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
+enum commit_action get_commit_action(struct rev_info *revs, struct commit *commit)
 {
        if (commit->object.flags & SHOWN)
                return commit_ignore;
@@ -1685,6 +1987,8 @@ enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
                return commit_ignore;
        if (revs->no_merges && commit->parents && commit->parents->next)
                return commit_ignore;
+       if (revs->merges_only && !(commit->parents && commit->parents->next))
+               return commit_ignore;
        if (!commit_match(commit, revs))
                return commit_ignore;
        if (revs->prune && revs->dense) {
@@ -1697,12 +2001,23 @@ enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
                        if (!commit->parents || !commit->parents->next)
                                return commit_ignore;
                }
-               if (want_ancestry(revs) && rewrite_parents(revs, commit) < 0)
-                       return commit_error;
        }
        return commit_show;
 }
 
+enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
+{
+       enum commit_action action = get_commit_action(revs, commit);
+
+       if (action == commit_show &&
+           !revs->show_all &&
+           revs->prune && revs->dense && want_ancestry(revs)) {
+               if (rewrite_parents(revs, commit) < 0)
+                       return commit_error;
+       }
+       return action;
+}
+
 static struct commit *get_revision_1(struct rev_info *revs)
 {
        if (!revs->commits)
index be39e7d38643817cced4e07a89b6db5201f2b747..05659c64acd7fe8eb7be011cee6174e397e57baf 100644 (file)
@@ -3,6 +3,7 @@
 
 #include "parse-options.h"
 #include "grep.h"
+#include "notes.h"
 
 #define SEEN           (1u<<0)
 #define UNINTERESTING   (1u<<1)
 #define SYMMETRIC_LEFT (1u<<8)
 #define ALL_REV_FLAGS  ((1u<<9)-1)
 
+#define DECORATE_SHORT_REFS    1
+#define DECORATE_FULL_REFS     2
+
 struct rev_info;
 struct log_info;
+struct string_list;
 
 struct rev_info {
        /* Starting list */
@@ -36,6 +41,7 @@ struct rev_info {
        unsigned int    dense:1,
                        prune:1,
                        no_merges:1,
+                       merges_only:1,
                        no_walk:1,
                        show_all:1,
                        remove_empty_trees:1,
@@ -51,6 +57,7 @@ struct rev_info {
                        limited:1,
                        unpacked:1,
                        boundary:2,
+                       count:1,
                        left_right:1,
                        rewrite_parents:1,
                        print_parents:1,
@@ -59,6 +66,8 @@ struct rev_info {
                        reverse:1,
                        reverse_output_stage:1,
                        cherry_pick:1,
+                       bisect:1,
+                       ancestry_path:1,
                        first_parent_only:1;
 
        /* Diff flags */
@@ -75,9 +84,15 @@ struct rev_info {
        /* Format info */
        unsigned int    shown_one:1,
                        show_merge:1,
+                       show_notes:1,
+                       show_notes_given:1,
+                       pretty_given:1,
                        abbrev_commit:1,
                        use_terminator:1,
-                       missing_newline:1;
+                       missing_newline:1,
+                       date_mode_explicit:1;
+       unsigned int    disable_stdin:1;
+
        enum date_mode date_mode;
 
        unsigned int    abbrev;
@@ -115,20 +130,32 @@ struct rev_info {
        struct reflog_walk_info *reflog_info;
        struct decoration children;
        struct decoration merge_simplification;
+
+       /* notes-specific options: which refs to show */
+       struct display_notes_opt notes_opt;
+
+       /* commit counts */
+       int count_left;
+       int count_right;
 };
 
 #define REV_TREE_SAME          0
-#define REV_TREE_NEW           1
-#define REV_TREE_DIFFERENT     2
+#define REV_TREE_NEW           1       /* Only new files */
+#define REV_TREE_OLD           2       /* Only files removed */
+#define REV_TREE_DIFFERENT     3       /* Mixed changes */
 
 /* revision.c */
-void read_revisions_from_stdin(struct rev_info *revs);
-
 typedef void (*show_early_output_fn_t)(struct rev_info *, struct commit_list *);
 extern volatile show_early_output_fn_t show_early_output;
 
+struct setup_revision_opt {
+       const char *def;
+       void (*tweak)(struct rev_info *, struct setup_revision_opt *);
+       const char *submodule;
+};
+
 extern void init_revisions(struct rev_info *revs, const char *prefix);
-extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def);
+extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct setup_revision_opt *);
 extern void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx,
                                 const struct option *options,
                                 const char * const usagestr[]);
@@ -163,6 +190,7 @@ enum commit_action {
        commit_error
 };
 
+extern enum commit_action get_commit_action(struct rev_info *revs, struct commit *commit);
 extern enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit);
 
 #endif
index eb2efc33073512efd14b65e67db8cf814ca3fa33..2a1041ef6599c84fff6a8d9faf5dea23a2af3ab0 100644 (file)
@@ -8,17 +8,138 @@ static inline void close_pair(int fd[2])
        close(fd[1]);
 }
 
+#ifndef WIN32
 static inline void dup_devnull(int to)
 {
        int fd = open("/dev/null", O_RDWR);
        dup2(fd, to);
        close(fd);
 }
+#endif
+
+static const char **prepare_shell_cmd(const char **argv)
+{
+       int argc, nargc = 0;
+       const char **nargv;
+
+       for (argc = 0; argv[argc]; argc++)
+               ; /* just counting */
+       /* +1 for NULL, +3 for "sh -c" plus extra $0 */
+       nargv = xmalloc(sizeof(*nargv) * (argc + 1 + 3));
+
+       if (argc < 1)
+               die("BUG: shell command is empty");
+
+       if (strcspn(argv[0], "|&;<>()$`\\\"' \t\n*?[#~=%") != strlen(argv[0])) {
+               nargv[nargc++] = "sh";
+               nargv[nargc++] = "-c";
+
+               if (argc < 2)
+                       nargv[nargc++] = argv[0];
+               else {
+                       struct strbuf arg0 = STRBUF_INIT;
+                       strbuf_addf(&arg0, "%s \"$@\"", argv[0]);
+                       nargv[nargc++] = strbuf_detach(&arg0, NULL);
+               }
+       }
+
+       for (argc = 0; argv[argc]; argc++)
+               nargv[nargc++] = argv[argc];
+       nargv[nargc] = NULL;
+
+       return nargv;
+}
+
+#ifndef WIN32
+static int execv_shell_cmd(const char **argv)
+{
+       const char **nargv = prepare_shell_cmd(argv);
+       trace_argv_printf(nargv, "trace: exec:");
+       execvp(nargv[0], (char **)nargv);
+       free(nargv);
+       return -1;
+}
+#endif
+
+#ifndef WIN32
+static int child_err = 2;
+static int child_notifier = -1;
+
+static void notify_parent(void)
+{
+       ssize_t unused;
+       unused = write(child_notifier, "", 1);
+}
+
+static NORETURN void die_child(const char *err, va_list params)
+{
+       char msg[4096];
+       ssize_t unused;
+       int len = vsnprintf(msg, sizeof(msg), err, params);
+       if (len > sizeof(msg))
+               len = sizeof(msg);
+
+       unused = write(child_err, "fatal: ", 7);
+       unused = write(child_err, msg, len);
+       unused = write(child_err, "\n", 1);
+       exit(128);
+}
+#endif
+
+static inline void set_cloexec(int fd)
+{
+       int flags = fcntl(fd, F_GETFD);
+       if (flags >= 0)
+               fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
+}
+
+static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure)
+{
+       int status, code = -1;
+       pid_t waiting;
+       int failed_errno = 0;
+
+       while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
+               ;       /* nothing */
+
+       if (waiting < 0) {
+               failed_errno = errno;
+               error("waitpid for %s failed: %s", argv0, strerror(errno));
+       } else if (waiting != pid) {
+               error("waitpid is confused (%s)", argv0);
+       } else if (WIFSIGNALED(status)) {
+               code = WTERMSIG(status);
+               error("%s died of signal %d", argv0, code);
+               /*
+                * This return value is chosen so that code & 0xff
+                * mimics the exit code that a POSIX shell would report for
+                * a program that died from this signal.
+                */
+               code -= 128;
+       } else if (WIFEXITED(status)) {
+               code = WEXITSTATUS(status);
+               /*
+                * Convert special exit code when execvp failed.
+                */
+               if (code == 127) {
+                       code = -1;
+                       failed_errno = ENOENT;
+                       if (!silent_exec_failure)
+                               error("cannot run %s: %s", argv0,
+                                       strerror(ENOENT));
+               }
+       } else {
+               error("waitpid is confused (%s)", argv0);
+       }
+       errno = failed_errno;
+       return code;
+}
 
 int start_command(struct child_process *cmd)
 {
        int need_in, need_out, need_err;
        int fdin[2], fdout[2], fderr[2];
+       int failed_errno = failed_errno;
 
        /*
         * In case of errors we must keep the promise to close FDs
@@ -28,9 +149,10 @@ int start_command(struct child_process *cmd)
        need_in = !cmd->no_stdin && cmd->in < 0;
        if (need_in) {
                if (pipe(fdin) < 0) {
+                       failed_errno = errno;
                        if (cmd->out > 0)
                                close(cmd->out);
-                       return -ERR_RUN_COMMAND_PIPE;
+                       goto fail_pipe;
                }
                cmd->in = fdin[1];
        }
@@ -40,11 +162,12 @@ int start_command(struct child_process *cmd)
                && cmd->out < 0;
        if (need_out) {
                if (pipe(fdout) < 0) {
+                       failed_errno = errno;
                        if (need_in)
                                close_pair(fdin);
                        else if (cmd->in)
                                close(cmd->in);
-                       return -ERR_RUN_COMMAND_PIPE;
+                       goto fail_pipe;
                }
                cmd->out = fdout[0];
        }
@@ -52,6 +175,7 @@ int start_command(struct child_process *cmd)
        need_err = !cmd->no_stderr && cmd->err < 0;
        if (need_err) {
                if (pipe(fderr) < 0) {
+                       failed_errno = errno;
                        if (need_in)
                                close_pair(fdin);
                        else if (cmd->in)
@@ -60,17 +184,42 @@ int start_command(struct child_process *cmd)
                                close_pair(fdout);
                        else if (cmd->out)
                                close(cmd->out);
-                       return -ERR_RUN_COMMAND_PIPE;
+fail_pipe:
+                       error("cannot create pipe for %s: %s",
+                               cmd->argv[0], strerror(failed_errno));
+                       errno = failed_errno;
+                       return -1;
                }
                cmd->err = fderr[0];
        }
 
        trace_argv_printf(cmd->argv, "trace: run_command:");
 
-#ifndef __MINGW32__
+#ifndef WIN32
+{
+       int notify_pipe[2];
+       if (pipe(notify_pipe))
+               notify_pipe[0] = notify_pipe[1] = -1;
+
        fflush(NULL);
        cmd->pid = fork();
        if (!cmd->pid) {
+               /*
+                * Redirect the channel to write syscall error messages to
+                * before redirecting the process's stderr so that all die()
+                * in subsequent call paths use the parent's stderr.
+                */
+               if (cmd->no_stderr || need_err) {
+                       child_err = dup(2);
+                       set_cloexec(child_err);
+               }
+               set_die_routine(die_child);
+
+               close(notify_pipe[0]);
+               set_cloexec(notify_pipe[1]);
+               child_notifier = notify_pipe[1];
+               atexit(notify_parent);
+
                if (cmd->no_stdin)
                        dup_devnull(0);
                else if (need_in) {
@@ -86,6 +235,9 @@ int start_command(struct child_process *cmd)
                else if (need_err) {
                        dup2(fderr[1], 2);
                        close_pair(fderr);
+               } else if (cmd->err > 1) {
+                       dup2(cmd->err, 2);
+                       close(cmd->err);
                }
 
                if (cmd->no_stdout)
@@ -101,8 +253,8 @@ int start_command(struct child_process *cmd)
                }
 
                if (cmd->dir && chdir(cmd->dir))
-                       die("exec %s: cd to %s failed (%s)", cmd->argv[0],
-                           cmd->dir, strerror(errno));
+                       die_errno("exec '%s': cd to '%s' failed", cmd->argv[0],
+                           cmd->dir);
                if (cmd->env) {
                        for (; *cmd->env; cmd->env++) {
                                if (strchr(*cmd->env, '='))
@@ -111,68 +263,99 @@ int start_command(struct child_process *cmd)
                                        unsetenv(*cmd->env);
                        }
                }
-               if (cmd->preexec_cb)
+               if (cmd->preexec_cb) {
+                       /*
+                        * We cannot predict what the pre-exec callback does.
+                        * Forgo parent notification.
+                        */
+                       close(child_notifier);
+                       child_notifier = -1;
+
                        cmd->preexec_cb();
+               }
                if (cmd->git_cmd) {
                        execv_git_cmd(cmd->argv);
+               } else if (cmd->use_shell) {
+                       execv_shell_cmd(cmd->argv);
                } else {
                        execvp(cmd->argv[0], (char *const*) cmd->argv);
                }
-               trace_printf("trace: exec '%s' failed: %s\n", cmd->argv[0],
-                               strerror(errno));
-               exit(127);
+               /*
+                * Do not check for cmd->silent_exec_failure; the parent
+                * process will check it when it sees this exit code.
+                */
+               if (errno == ENOENT)
+                       exit(127);
+               else
+                       die_errno("cannot exec '%s'", cmd->argv[0]);
+       }
+       if (cmd->pid < 0)
+               error("cannot fork() for %s: %s", cmd->argv[0],
+                       strerror(failed_errno = errno));
+
+       /*
+        * Wait for child's execvp. If the execvp succeeds (or if fork()
+        * failed), EOF is seen immediately by the parent. Otherwise, the
+        * child process sends a single byte.
+        * Note that use of this infrastructure is completely advisory,
+        * therefore, we keep error checks minimal.
+        */
+       close(notify_pipe[1]);
+       if (read(notify_pipe[0], &notify_pipe[1], 1) == 1) {
+               /*
+                * At this point we know that fork() succeeded, but execvp()
+                * failed. Errors have been reported to our stderr.
+                */
+               wait_or_whine(cmd->pid, cmd->argv[0],
+                             cmd->silent_exec_failure);
+               failed_errno = errno;
+               cmd->pid = -1;
        }
+       close(notify_pipe[0]);
+}
 #else
-       int s0 = -1, s1 = -1, s2 = -1;  /* backups of stdin, stdout, stderr */
+{
+       int fhin = 0, fhout = 1, fherr = 2;
        const char **sargv = cmd->argv;
        char **env = environ;
 
-       if (cmd->no_stdin) {
-               s0 = dup(0);
-               dup_devnull(0);
-       } else if (need_in) {
-               s0 = dup(0);
-               dup2(fdin[0], 0);
-       } else if (cmd->in) {
-               s0 = dup(0);
-               dup2(cmd->in, 0);
-       }
-
-       if (cmd->no_stderr) {
-               s2 = dup(2);
-               dup_devnull(2);
-       } else if (need_err) {
-               s2 = dup(2);
-               dup2(fderr[1], 2);
-       }
-
-       if (cmd->no_stdout) {
-               s1 = dup(1);
-               dup_devnull(1);
-       } else if (cmd->stdout_to_stderr) {
-               s1 = dup(1);
-               dup2(2, 1);
-       } else if (need_out) {
-               s1 = dup(1);
-               dup2(fdout[1], 1);
-       } else if (cmd->out > 1) {
-               s1 = dup(1);
-               dup2(cmd->out, 1);
-       }
+       if (cmd->no_stdin)
+               fhin = open("/dev/null", O_RDWR);
+       else if (need_in)
+               fhin = dup(fdin[0]);
+       else if (cmd->in)
+               fhin = dup(cmd->in);
+
+       if (cmd->no_stderr)
+               fherr = open("/dev/null", O_RDWR);
+       else if (need_err)
+               fherr = dup(fderr[1]);
+       else if (cmd->err > 2)
+               fherr = dup(cmd->err);
+
+       if (cmd->no_stdout)
+               fhout = open("/dev/null", O_RDWR);
+       else if (cmd->stdout_to_stderr)
+               fhout = dup(fherr);
+       else if (need_out)
+               fhout = dup(fdout[1]);
+       else if (cmd->out > 1)
+               fhout = dup(cmd->out);
 
-       if (cmd->dir)
-               die("chdir in start_command() not implemented");
-       if (cmd->env) {
-               env = copy_environ();
-               for (; *cmd->env; cmd->env++)
-                       env = env_setenv(env, *cmd->env);
-       }
+       if (cmd->env)
+               env = make_augmented_environ(cmd->env);
 
        if (cmd->git_cmd) {
                cmd->argv = prepare_git_cmd(cmd->argv);
+       } else if (cmd->use_shell) {
+               cmd->argv = prepare_shell_cmd(cmd->argv);
        }
 
-       cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env);
+       cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env, cmd->dir,
+                                 fhin, fhout, fherr);
+       failed_errno = errno;
+       if (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT))
+               error("cannot spawn %s: %s", cmd->argv[0], strerror(errno));
 
        if (cmd->env)
                free_environ(env);
@@ -180,16 +363,16 @@ int start_command(struct child_process *cmd)
                free(cmd->argv);
 
        cmd->argv = sargv;
-       if (s0 >= 0)
-               dup2(s0, 0), close(s0);
-       if (s1 >= 0)
-               dup2(s1, 1), close(s1);
-       if (s2 >= 0)
-               dup2(s2, 2), close(s2);
+       if (fhin != 0)
+               close(fhin);
+       if (fhout != 1)
+               close(fhout);
+       if (fherr != 2)
+               close(fherr);
+}
 #endif
 
        if (cmd->pid < 0) {
-               int err = errno;
                if (need_in)
                        close_pair(fdin);
                else if (cmd->in)
@@ -200,9 +383,10 @@ int start_command(struct child_process *cmd)
                        close(cmd->out);
                if (need_err)
                        close_pair(fderr);
-               return err == ENOENT ?
-                       -ERR_RUN_COMMAND_EXEC :
-                       -ERR_RUN_COMMAND_FORK;
+               else if (cmd->err)
+                       close(cmd->err);
+               errno = failed_errno;
+               return -1;
        }
 
        if (need_in)
@@ -217,44 +401,15 @@ int start_command(struct child_process *cmd)
 
        if (need_err)
                close(fderr[1]);
+       else if (cmd->err)
+               close(cmd->err);
 
        return 0;
 }
 
-static int wait_or_whine(pid_t pid)
-{
-       for (;;) {
-               int status, code;
-               pid_t waiting = waitpid(pid, &status, 0);
-
-               if (waiting < 0) {
-                       if (errno == EINTR)
-                               continue;
-                       error("waitpid failed (%s)", strerror(errno));
-                       return -ERR_RUN_COMMAND_WAITPID;
-               }
-               if (waiting != 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);
-               switch (code) {
-               case 127:
-                       return -ERR_RUN_COMMAND_EXEC;
-               case 0:
-                       return 0;
-               default:
-                       return -code;
-               }
-       }
-}
-
 int finish_command(struct child_process *cmd)
 {
-       return wait_or_whine(cmd->pid);
+       return wait_or_whine(cmd->pid, cmd->argv[0], cmd->silent_exec_failure);
 }
 
 int run_command(struct child_process *cmd)
@@ -274,6 +429,8 @@ static void prepare_run_command_v_opt(struct child_process *cmd,
        cmd->no_stdin = opt & RUN_COMMAND_NO_STDIN ? 1 : 0;
        cmd->git_cmd = opt & RUN_GIT_CMD ? 1 : 0;
        cmd->stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0;
+       cmd->silent_exec_failure = opt & RUN_SILENT_EXEC_FAILURE ? 1 : 0;
+       cmd->use_shell = opt & RUN_USING_SHELL ? 1 : 0;
 }
 
 int run_command_v_opt(const char **argv, int opt)
@@ -292,65 +449,158 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const
        return run_command(&cmd);
 }
 
-#ifdef __MINGW32__
-static __stdcall unsigned run_thread(void *data)
+#ifndef NO_PTHREADS
+static pthread_t main_thread;
+static int main_thread_set;
+static pthread_key_t async_key;
+
+static void *run_thread(void *data)
 {
        struct async *async = data;
-       return async->proc(async->fd_for_proc, async->data);
+       intptr_t ret;
+
+       pthread_setspecific(async_key, async);
+       ret = async->proc(async->proc_in, async->proc_out, async->data);
+       return (void *)ret;
+}
+
+static NORETURN void die_async(const char *err, va_list params)
+{
+       vreportf("fatal: ", err, params);
+
+       if (!pthread_equal(main_thread, pthread_self())) {
+               struct async *async = pthread_getspecific(async_key);
+               if (async->proc_in >= 0)
+                       close(async->proc_in);
+               if (async->proc_out >= 0)
+                       close(async->proc_out);
+               pthread_exit((void *)128);
+       }
+
+       exit(128);
 }
 #endif
 
 int start_async(struct async *async)
 {
-       int pipe_out[2];
+       int need_in, need_out;
+       int fdin[2], fdout[2];
+       int proc_in, proc_out;
+
+       need_in = async->in < 0;
+       if (need_in) {
+               if (pipe(fdin) < 0) {
+                       if (async->out > 0)
+                               close(async->out);
+                       return error("cannot create pipe: %s", strerror(errno));
+               }
+               async->in = fdin[1];
+       }
+
+       need_out = async->out < 0;
+       if (need_out) {
+               if (pipe(fdout) < 0) {
+                       if (need_in)
+                               close_pair(fdin);
+                       else if (async->in)
+                               close(async->in);
+                       return error("cannot create pipe: %s", strerror(errno));
+               }
+               async->out = fdout[0];
+       }
+
+       if (need_in)
+               proc_in = fdin[0];
+       else if (async->in)
+               proc_in = async->in;
+       else
+               proc_in = -1;
 
-       if (pipe(pipe_out) < 0)
-               return error("cannot create pipe: %s", strerror(errno));
-       async->out = pipe_out[0];
+       if (need_out)
+               proc_out = fdout[1];
+       else if (async->out)
+               proc_out = async->out;
+       else
+               proc_out = -1;
 
-#ifndef __MINGW32__
+#ifdef NO_PTHREADS
        /* Flush stdio before fork() to avoid cloning buffers */
        fflush(NULL);
 
        async->pid = fork();
        if (async->pid < 0) {
                error("fork (async) failed: %s", strerror(errno));
-               close_pair(pipe_out);
-               return -1;
+               goto error;
        }
        if (!async->pid) {
-               close(pipe_out[0]);
-               exit(!!async->proc(pipe_out[1], async->data));
+               if (need_in)
+                       close(fdin[1]);
+               if (need_out)
+                       close(fdout[0]);
+               exit(!!async->proc(proc_in, proc_out, async->data));
        }
-       close(pipe_out[1]);
+
+       if (need_in)
+               close(fdin[0]);
+       else if (async->in)
+               close(async->in);
+
+       if (need_out)
+               close(fdout[1]);
+       else if (async->out)
+               close(async->out);
 #else
-       async->fd_for_proc = pipe_out[1];
-       async->tid = (HANDLE) _beginthreadex(NULL, 0, run_thread, async, 0, NULL);
-       if (!async->tid) {
-               error("cannot create thread: %s", strerror(errno));
-               close_pair(pipe_out);
-               return -1;
+       if (!main_thread_set) {
+               /*
+                * We assume that the first time that start_async is called
+                * it is from the main thread.
+                */
+               main_thread_set = 1;
+               main_thread = pthread_self();
+               pthread_key_create(&async_key, NULL);
+               set_die_routine(die_async);
+       }
+
+       if (proc_in >= 0)
+               set_cloexec(proc_in);
+       if (proc_out >= 0)
+               set_cloexec(proc_out);
+       async->proc_in = proc_in;
+       async->proc_out = proc_out;
+       {
+               int err = pthread_create(&async->tid, NULL, run_thread, async);
+               if (err) {
+                       error("cannot create thread: %s", strerror(err));
+                       goto error;
+               }
        }
 #endif
        return 0;
+
+error:
+       if (need_in)
+               close_pair(fdin);
+       else if (async->in)
+               close(async->in);
+
+       if (need_out)
+               close_pair(fdout);
+       else if (async->out)
+               close(async->out);
+       return -1;
 }
 
 int finish_async(struct async *async)
 {
-#ifndef __MINGW32__
-       int ret = 0;
-
-       if (wait_or_whine(async->pid))
-               ret = error("waitpid (async) failed");
+#ifdef NO_PTHREADS
+       return wait_or_whine(async->pid, "child process", 0);
 #else
-       DWORD ret = 0;
-       if (WaitForSingleObject(async->tid, INFINITE) != WAIT_OBJECT_0)
-               ret = error("waiting for thread failed: %lu", GetLastError());
-       else if (!GetExitCodeThread(async->tid, &ret))
-               ret = error("cannot get thread exit code: %lu", GetLastError());
-       CloseHandle(async->tid);
+       void *ret = (void *)(intptr_t)(-1);
+
+       if (pthread_join(async->tid, &ret))
+               error("pthread_join failed");
+       return (int)(intptr_t)ret;
 #endif
-       return ret;
 }
 
 int run_hook(const char *index_file, const char *name, ...)
@@ -385,15 +635,7 @@ int run_hook(const char *index_file, const char *name, ...)
                hook.env = env;
        }
 
-       ret = start_command(&hook);
+       ret = run_command(&hook);
        free(argv);
-       if (ret) {
-               warning("Could not spawn %s", argv[0]);
-               return ret;
-       }
-       ret = finish_command(&hook);
-       if (ret == -ERR_RUN_COMMAND_WAITPID_SIGNAL)
-               warning("%s exited due to uncaught signal", argv[0]);
-
        return ret;
 }
index e3455028435eab958d5f86a3e86249f1704b9c1b..56491b9f2344541c02bd0da2928a535f11193bd8 100644 (file)
@@ -1,16 +1,9 @@
 #ifndef RUN_COMMAND_H
 #define RUN_COMMAND_H
 
-enum {
-       ERR_RUN_COMMAND_FORK = 10000,
-       ERR_RUN_COMMAND_EXEC,
-       ERR_RUN_COMMAND_PIPE,
-       ERR_RUN_COMMAND_WAITPID,
-       ERR_RUN_COMMAND_WAITPID_WRONG_PID,
-       ERR_RUN_COMMAND_WAITPID_SIGNAL,
-       ERR_RUN_COMMAND_WAITPID_NOEXIT,
-};
-#define IS_RUN_COMMAND_ERR(x) (-(x) >= ERR_RUN_COMMAND_FORK)
+#ifndef NO_PTHREADS
+#include <pthread.h>
+#endif
 
 struct child_process {
        const char **argv;
@@ -29,7 +22,7 @@ struct child_process {
         * - Specify > 0 to set a channel to a particular FD as follows:
         *     .in: a readable FD, becomes child's stdin
         *     .out: a writable FD, becomes child's stdout/stderr
-        *     .err > 0 not supported
+        *     .err: a writable FD, becomes child's stderr
         *   The specified FD is closed by start_command(), even in case
         *   of errors!
         */
@@ -42,7 +35,9 @@ struct child_process {
        unsigned no_stdout:1;
        unsigned no_stderr:1;
        unsigned git_cmd:1; /* if this is to be git sub-command */
+       unsigned silent_exec_failure:1;
        unsigned stdout_to_stderr:1;
+       unsigned use_shell:1;
        void (*preexec_cb)(void);
 };
 
@@ -55,6 +50,8 @@ extern int run_hook(const char *index_file, const char *name, ...);
 #define RUN_COMMAND_NO_STDIN 1
 #define RUN_GIT_CMD         2  /*If this is to be git sub-command */
 #define RUN_COMMAND_STDOUT_TO_STDERR 4
+#define RUN_SILENT_EXEC_FAILURE 8
+#define RUN_USING_SHELL 16
 int run_command_v_opt(const char **argv, int opt);
 
 /*
@@ -73,17 +70,20 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const
  */
 struct async {
        /*
-        * proc writes to fd and closes it;
+        * proc reads from in; closes it before return
+        * proc writes to out; closes it before return
         * returns 0 on success, non-zero on failure
         */
-       int (*proc)(int fd, void *data);
+       int (*proc)(int in, int out, void *data);
        void *data;
+       int in;         /* caller writes here and closes it */
        int out;        /* caller reads from here and closes it */
-#ifndef __MINGW32__
+#ifdef NO_PTHREADS
        pid_t pid;
 #else
-       HANDLE tid;
-       int fd_for_proc;
+       pthread_t tid;
+       int proc_in;
+       int proc_out;
 #endif
 };
 
index 83d76c7e35183ded9eec5a56c59d4da549c0b584..60b4ba66eb8cac3378326378dc4e0cbdb88162ac 100644 (file)
@@ -3,10 +3,14 @@
 
 struct send_pack_args {
        unsigned verbose:1,
+               quiet:1,
+               porcelain:1,
                send_mirror:1,
                force_update:1,
                use_thin_pack:1,
-               dry_run:1;
+               use_ofs_delta:1,
+               dry_run:1,
+               stateless_rpc:1;
 };
 
 int send_pack(struct send_pack_args *args,
index 906ce5b27242ca1e415a5410135d58f1c000e8b0..9ec744e9f2da294a21ac02b46f0ea0b35889ae54 100644 (file)
@@ -113,11 +113,8 @@ static int read_pack_info_file(const char *infofile)
                                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;
@@ -246,7 +243,7 @@ int update_server_info(int force)
        errs = errs | update_info_packs(force);
 
        /* remove leftover rev-cache file if there is any */
-       unlink(git_path("info/rev-cache"));
+       unlink_or_warn(git_path("info/rev-cache"));
 
        return errs;
 }
diff --git a/setup.c b/setup.c
index ebd60de9ce5b52f348819a6a390c15b8dc08d2ff..a3b76de2bb1c18e2f3b8cdf5799c3c650b16d027 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -18,11 +18,15 @@ const char *prefix_path(const char *prefix, int len, const char *path)
        if (normalize_path_copy(sanitized, sanitized))
                goto error_out;
        if (is_absolute_path(orig)) {
+               size_t root_len, len, total;
                const char *work_tree = get_git_work_tree();
-               size_t len = strlen(work_tree);
-               size_t total = strlen(sanitized) + 1;
+               if (!work_tree)
+                       goto error_out;
+               len = strlen(work_tree);
+               root_len = offset_1st_component(work_tree);
+               total = strlen(sanitized) + 1;
                if (strncmp(sanitized, work_tree, len) ||
-                   (sanitized[len] != '\0' && sanitized[len] != '/')) {
+                   (len > root_len && sanitized[len] != '\0' && sanitized[len] != '/')) {
                error_out:
                        die("'%s' is outside repository", orig);
                }
@@ -41,7 +45,7 @@ const char *prefix_path(const char *prefix, int len, const char *path)
 const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
 {
        static char path[PATH_MAX];
-#ifndef __MINGW32__
+#ifndef WIN32
        if (!pfx || !*pfx || is_absolute_path(arg))
                return arg;
        memcpy(path, pfx, pfx_len);
@@ -61,6 +65,31 @@ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
        return path;
 }
 
+int check_filename(const char *prefix, const char *arg)
+{
+       const char *name;
+       struct stat st;
+
+       name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;
+       if (!lstat(name, &st))
+               return 1; /* file exists */
+       if (errno == ENOENT || errno == ENOTDIR)
+               return 0; /* file does not exist */
+       die_errno("failed to stat '%s'", arg);
+}
+
+static void NORETURN die_verify_filename(const char *prefix, const char *arg)
+{
+       unsigned char sha1[20];
+       unsigned mode;
+       /* try a detailed diagnostic ... */
+       get_sha1_with_mode_1(arg, sha1, &mode, 0, prefix);
+       /* ... or fall back the most general message. */
+       die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
+           "Use '--' to separate paths from revisions", arg);
+
+}
+
 /*
  * Verify a filename that we got as an argument for a pathspec
  * entry. Note that a filename that begins with "-" never verifies
@@ -70,18 +99,11 @@ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
  */
 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))
+       if (check_filename(prefix, arg))
                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));
+       die_verify_filename(prefix, arg);
 }
 
 /*
@@ -91,19 +113,14 @@ void verify_filename(const char *prefix, const char *arg)
  */
 void verify_non_filename(const char *prefix, const char *arg)
 {
-       const char *name;
-       struct stat st;
-
        if (!is_inside_work_tree() || is_inside_git_dir())
                return;
        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 && errno != ENOTDIR)
-               die("'%s': %s", arg, strerror(errno));
+       if (!check_filename(prefix, arg))
+               return;
+       die("ambiguous argument '%s': both revision and filename\n"
+           "Use '--' to separate filenames from revisions", arg);
 }
 
 const char **get_pathspec(const char *prefix, const char **pathspec)
@@ -153,6 +170,8 @@ static int is_git_directory(const char *suspect)
        char path[PATH_MAX];
        size_t len = strlen(suspect);
 
+       if (PATH_MAX <= len + strlen("/objects"))
+               die("Too long path: %.*s", 60, suspect);
        strcpy(path, suspect);
        if (getenv(DB_ENVIRONMENT)) {
                if (access(getenv(DB_ENVIRONMENT), X_OK))
@@ -190,7 +209,7 @@ int is_inside_work_tree(void)
 }
 
 /*
- * set_work_tree() is only ever called if you set GIT_DIR explicitely.
+ * set_work_tree() is only ever called if you set GIT_DIR explicitly.
  * The old behaviour (which we retain here) is to set the work tree root
  * to the cwd, unless overridden by the config, the command line, or
  * GIT_WORK_TREE.
@@ -247,6 +266,8 @@ static int check_repository_format_gently(int *nongit_ok)
 const char *read_gitfile_gently(const char *path)
 {
        char *buf;
+       char *dir;
+       const char *slash;
        struct stat st;
        int fd;
        size_t len;
@@ -257,7 +278,7 @@ const char *read_gitfile_gently(const char *path)
                return NULL;
        fd = open(path, O_RDONLY);
        if (fd < 0)
-               die("Error opening %s: %s", path, strerror(errno));
+               die_errno("Error opening '%s'", path);
        buf = xmalloc(st.st_size + 1);
        len = read_in_full(fd, buf, st.st_size);
        close(fd);
@@ -271,18 +292,141 @@ const char *read_gitfile_gently(const char *path)
        if (len < 9)
                die("No path in gitfile: %s", path);
        buf[len] = '\0';
-       if (!is_git_directory(buf + 8))
-               die("Not a git repository: %s", buf + 8);
-       path = make_absolute_path(buf + 8);
+       dir = buf + 8;
+
+       if (!is_absolute_path(dir) && (slash = strrchr(path, '/'))) {
+               size_t pathlen = slash+1 - path;
+               size_t dirlen = pathlen + len - 8;
+               dir = xmalloc(dirlen + 1);
+               strncpy(dir, path, pathlen);
+               strncpy(dir + pathlen, buf + 8, len - 8);
+               dir[dirlen] = '\0';
+               free(buf);
+               buf = dir;
+       }
+
+       if (!is_git_directory(dir))
+               die("Not a git repository: %s", dir);
+       path = make_absolute_path(dir);
+
        free(buf);
        return path;
 }
 
+static const char *setup_explicit_git_dir(const char *gitdirenv,
+                               const char *work_tree_env, int *nongit_ok)
+{
+       static char buffer[1024 + 1];
+       const char *retval;
+
+       if (PATH_MAX - 40 < strlen(gitdirenv))
+               die("'$%s' too big", GIT_DIR_ENVIRONMENT);
+       if (!is_git_directory(gitdirenv)) {
+               if (nongit_ok) {
+                       *nongit_ok = 1;
+                       return NULL;
+               }
+               die("Not a git repository: '%s'", gitdirenv);
+       }
+       if (!work_tree_env) {
+               retval = set_work_tree(gitdirenv);
+               /* config may override worktree */
+               if (check_repository_format_gently(nongit_ok))
+                       return NULL;
+               return retval;
+       }
+       if (check_repository_format_gently(nongit_ok))
+               return NULL;
+       retval = get_relative_cwd(buffer, sizeof(buffer) - 1,
+                       get_git_work_tree());
+       if (!retval || !*retval)
+               return NULL;
+       set_git_dir(make_absolute_path(gitdirenv));
+       if (chdir(work_tree_env) < 0)
+               die_errno ("Could not chdir to '%s'", work_tree_env);
+       strcat(buffer, "/");
+       return retval;
+}
+
+static int cwd_contains_git_dir(const char **gitfile_dirp)
+{
+       const char *gitfile_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT);
+       *gitfile_dirp = gitfile_dir;
+       if (gitfile_dir) {
+               if (set_git_dir(gitfile_dir))
+                       die("Repository setup failed");
+               return 1;
+       }
+       return is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT);
+}
+
+static const char *setup_discovered_git_dir(const char *work_tree_env,
+               int offset, int len, char *cwd, int *nongit_ok)
+{
+       int root_len;
+
+       inside_git_dir = 0;
+       if (!work_tree_env)
+               inside_work_tree = 1;
+       root_len = offset_1st_component(cwd);
+       git_work_tree_cfg = xstrndup(cwd, offset > root_len ? offset : root_len);
+       if (check_repository_format_gently(nongit_ok))
+               return NULL;
+       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;
+}
+
+static const char *setup_bare_git_dir(const char *work_tree_env,
+               int offset, int len, char *cwd, int *nongit_ok)
+{
+       int root_len;
+
+       inside_git_dir = 1;
+       if (!work_tree_env)
+               inside_work_tree = 0;
+       if (offset != len) {
+               if (chdir(cwd))
+                       die_errno("Cannot come back to cwd");
+               root_len = offset_1st_component(cwd);
+               cwd[offset > root_len ? offset : root_len] = '\0';
+               set_git_dir(cwd);
+       } else
+               set_git_dir(".");
+       check_repository_format_gently(nongit_ok);
+       return NULL;
+}
+
+static const char *setup_nongit(const char *cwd, int *nongit_ok)
+{
+       if (!nongit_ok)
+               die("Not a git repository (or any of the parent directories): %s", DEFAULT_GIT_DIR_ENVIRONMENT);
+       if (chdir(cwd))
+               die_errno("Cannot come back to cwd");
+       *nongit_ok = 1;
+       return NULL;
+}
+
+static dev_t get_device_or_die(const char *path, const char *prefix)
+{
+       struct stat buf;
+       if (stat(path, &buf))
+               die_errno("failed to stat '%s%s%s'",
+                               prefix ? prefix : "",
+                               prefix ? "/" : "", path);
+       return buf.st_dev;
+}
+
 /*
  * We cannot decide in this function whether we are in the work tree or
  * not, since the config can only be read _after_ this function was called.
  */
-const char *setup_git_directory_gently(int *nongit_ok)
+static const char *setup_git_directory_gently_1(int *nongit_ok)
 {
        const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);
        const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);
@@ -290,6 +434,8 @@ const char *setup_git_directory_gently(int *nongit_ok)
        const char *gitdirenv;
        const char *gitfile_dir;
        int len, offset, ceil_offset;
+       dev_t current_device = 0;
+       int one_filesystem = 1;
 
        /*
         * Let's assume that we are in a git repository.
@@ -305,41 +451,11 @@ const char *setup_git_directory_gently(int *nongit_ok)
         * validation.
         */
        gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
-       if (gitdirenv) {
-               if (PATH_MAX - 40 < strlen(gitdirenv))
-                       die("'$%s' too big", GIT_DIR_ENVIRONMENT);
-               if (is_git_directory(gitdirenv)) {
-                       static char buffer[1024 + 1];
-                       const char *retval;
-
-                       if (!work_tree_env) {
-                               retval = set_work_tree(gitdirenv);
-                               /* config may override worktree */
-                               if (check_repository_format_gently(nongit_ok))
-                                       return NULL;
-                               return retval;
-                       }
-                       if (check_repository_format_gently(nongit_ok))
-                               return NULL;
-                       retval = get_relative_cwd(buffer, sizeof(buffer) - 1,
-                                       get_git_work_tree());
-                       if (!retval || !*retval)
-                               return NULL;
-                       set_git_dir(make_absolute_path(gitdirenv));
-                       if (chdir(work_tree_env) < 0)
-                               die ("Could not chdir to %s", work_tree_env);
-                       strcat(buffer, "/");
-                       return retval;
-               }
-               if (nongit_ok) {
-                       *nongit_ok = 1;
-                       return NULL;
-               }
-               die("Not a git repository: '%s'", gitdirenv);
-       }
+       if (gitdirenv)
+               return setup_explicit_git_dir(gitdirenv, work_tree_env, nongit_ok);
 
        if (!getcwd(cwd, sizeof(cwd)-1))
-               die("Unable to read current working directory");
+               die_errno("Unable to read current working directory");
 
        ceil_offset = longest_ancestor_length(cwd, env_ceiling_dirs);
        if (ceil_offset < 0 && has_dos_drive_prefix(cwd))
@@ -357,55 +473,48 @@ const char *setup_git_directory_gently(int *nongit_ok)
         *   etc.
         */
        offset = len = strlen(cwd);
+       one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0);
+       if (one_filesystem)
+               current_device = get_device_or_die(".", NULL);
        for (;;) {
-               gitfile_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT);
-               if (gitfile_dir) {
-                       if (set_git_dir(gitfile_dir))
-                               die("Repository setup failed");
-                       break;
-               }
-               if (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))
-                       break;
-               if (is_git_directory(".")) {
-                       inside_git_dir = 1;
-                       if (!work_tree_env)
-                               inside_work_tree = 0;
-                       if (offset != len) {
-                               cwd[offset] = '\0';
-                               setenv(GIT_DIR_ENVIRONMENT, cwd, 1);
-                       } else
-                               setenv(GIT_DIR_ENVIRONMENT, ".", 1);
-                       check_repository_format_gently(nongit_ok);
-                       return NULL;
-               }
+               if (cwd_contains_git_dir(&gitfile_dir))
+                       return setup_discovered_git_dir(work_tree_env, offset,
+                                                       len, cwd, nongit_ok);
+               if (is_git_directory("."))
+                       return setup_bare_git_dir(work_tree_env, offset,
+                                                       len, cwd, nongit_ok);
                while (--offset > ceil_offset && cwd[offset] != '/');
-               if (offset <= ceil_offset) {
-                       if (nongit_ok) {
-                               if (chdir(cwd))
-                                       die("Cannot come back to cwd");
-                               *nongit_ok = 1;
-                               return NULL;
+               if (offset <= ceil_offset)
+                       return setup_nongit(cwd, nongit_ok);
+               if (one_filesystem) {
+                       dev_t parent_device = get_device_or_die("..", cwd);
+                       if (parent_device != current_device) {
+                               if (nongit_ok) {
+                                       if (chdir(cwd))
+                                               die_errno("Cannot come back to cwd");
+                                       *nongit_ok = 1;
+                                       return NULL;
+                               }
+                               cwd[offset] = '\0';
+                               die("Not a git repository (or any parent up to mount parent %s)\n"
+                               "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).", cwd);
                        }
-                       die("Not a git repository (or any of the parent directories): %s", DEFAULT_GIT_DIR_ENVIRONMENT);
                }
-               if (chdir(".."))
-                       die("Cannot change to %s/..: %s", cwd, strerror(errno));
+               if (chdir("..")) {
+                       cwd[offset] = '\0';
+                       die_errno("Cannot change to '%s/..'", cwd);
+               }
        }
+}
 
-       inside_git_dir = 0;
-       if (!work_tree_env)
-               inside_work_tree = 1;
-       git_work_tree_cfg = xstrndup(cwd, offset);
-       if (check_repository_format_gently(nongit_ok))
-               return NULL;
-       if (offset == len)
-               return NULL;
+const char *setup_git_directory_gently(int *nongit_ok)
+{
+       const char *prefix;
 
-       /* Make "offset" point to past the '/', and add a '/' at the end */
-       offset++;
-       cwd[len++] = '/';
-       cwd[len] = 0;
-       return cwd + offset;
+       prefix = setup_git_directory_gently_1(nongit_ok);
+       if (startup_info)
+               startup_info->have_repository = !nongit_ok || !*nongit_ok;
+       return prefix;
 }
 
 int git_config_perm(const char *var, const char *value)
@@ -484,6 +593,12 @@ int check_repository_format(void)
        return check_repository_format_gently(NULL);
 }
 
+/*
+ * Returns the "prefix", a path to the current working directory
+ * relative to the work tree root, or NULL, if the current working
+ * directory is not a strict subdirectory of the work tree root. The
+ * prefix always ends with a '/' character.
+ */
 const char *setup_git_directory(void)
 {
        const char *retval = setup_git_directory_gently(NULL);
@@ -493,10 +608,10 @@ const char *setup_git_directory(void)
                static char buffer[PATH_MAX + 1];
                char *rel;
                if (retval && chdir(retval))
-                       die ("Could not jump back into original cwd");
+                       die_errno ("Could not jump back into original cwd");
                rel = get_relative_cwd(buffer, PATH_MAX, get_git_work_tree());
                if (rel && *rel && chdir(get_git_work_tree()))
-                       die ("Could not jump to working directory");
+                       die_errno ("Could not jump to working directory");
                return rel && *rel ? strcat(rel, "/") : NULL;
        }
 
index 28bd9082fc9f4b79519f6bef8204600072c07027..0cd9435619f1e0637584289b45d52c1cdd8a9460 100644 (file)
@@ -35,61 +35,6 @@ static size_t sz_fmt(size_t s) { return s; }
 
 const unsigned char null_sha1[20];
 
-const signed char hexval_table[256] = {
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 00-07 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 08-0f */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 10-17 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 18-1f */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 20-27 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 28-2f */
-         0,  1,  2,  3,  4,  5,  6,  7,                /* 30-37 */
-         8,  9, -1, -1, -1, -1, -1, -1,                /* 38-3f */
-        -1, 10, 11, 12, 13, 14, 15, -1,                /* 40-47 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 48-4f */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 50-57 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 58-5f */
-        -1, 10, 11, 12, 13, 14, 15, -1,                /* 60-67 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 68-67 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 70-77 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 78-7f */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 80-87 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 88-8f */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 90-97 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 98-9f */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* a0-a7 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* a8-af */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* b0-b7 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* b8-bf */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* c0-c7 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* c8-cf */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* d0-d7 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* d8-df */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* e0-e7 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* e8-ef */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* f0-f7 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* f8-ff */
-};
-
-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;
-}
-
-static inline int offset_1st_component(const char *path)
-{
-       if (has_dos_drive_prefix(path))
-               return 2 + (path[2] == '/');
-       return *path == '/';
-}
-
 int safe_create_leading_directories(char *path)
 {
        char *pos = path + offset_1st_component(path);
@@ -133,24 +78,6 @@ int safe_create_leading_directories_const(const char *path)
        return result;
 }
 
-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;
@@ -175,20 +102,22 @@ static void fill_sha1_path(char *pathbuf, const unsigned char *sha1)
  */
 char *sha1_file_name(const unsigned char *sha1)
 {
-       static char *name, *base;
+       static char buf[PATH_MAX];
+       const char *objdir;
+       int len;
 
-       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;
+       objdir = get_object_directory();
+       len = strlen(objdir);
+
+       /* '/' + sha1(2) + '/' + sha1(38) + '\0' */
+       if (len + 43 > PATH_MAX)
+               die("insanely long object directory %s", objdir);
+       memcpy(buf, objdir, len);
+       buf[len] = '/';
+       buf[len+3] = '/';
+       buf[len+42] = '\0';
+       fill_sha1_path(buf + len + 1, sha1);
+       return buf;
 }
 
 static char *sha1_get_pack_name(const unsigned char *sha1,
@@ -672,6 +601,14 @@ void unuse_pack(struct pack_window **w_cursor)
        }
 }
 
+void close_pack_index(struct packed_git *p)
+{
+       if (p->index_data) {
+               munmap((void *)p->index_data, p->index_size);
+               p->index_data = NULL;
+       }
+}
+
 /*
  * This is used by git-repack in case a newly created pack happens to
  * contain the same set of objects as an existing one.  In that case
@@ -693,8 +630,7 @@ void free_pack_by_name(const char *pack_name)
                        close_pack_windows(p);
                        if (p->pack_fd != -1)
                                close(p->pack_fd);
-                       if (p->index_data)
-                               munmap((void *)p->index_data, p->index_size);
+                       close_pack_index(p);
                        free(p->bad_object_sha1);
                        *pp = p->next;
                        free(p);
@@ -720,6 +656,8 @@ static int open_packed_git_1(struct packed_git *p)
                return error("packfile %s index unavailable", p->pack_name);
 
        p->pack_fd = open(p->pack_name, O_RDONLY);
+       while (p->pack_fd < 0 && errno == EMFILE && unuse_one_window(p, -1))
+               p->pack_fd = open(p->pack_name, O_RDONLY);
        if (p->pack_fd < 0 || fstat(p->pack_fd, &st))
                return -1;
 
@@ -902,9 +840,8 @@ struct packed_git *add_packed_git(const char *path, int path_len, int local)
        return p;
 }
 
-struct packed_git *parse_pack_index(unsigned char *sha1)
+struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path)
 {
-       const char *idx_path = sha1_pack_index_name(sha1);
        const char *path = sha1_pack_name(sha1);
        struct packed_git *p = alloc_packed_git(strlen(path) + 1);
 
@@ -937,6 +874,8 @@ static void prepare_packed_git_one(char *objdir, int local)
        sprintf(path, "%s/pack", objdir);
        len = strlen(path);
        dir = opendir(path);
+       while (!dir && errno == EMFILE && unuse_one_window(packed_git, -1))
+               dir = opendir(path);
        if (!dir) {
                if (errno != ENOENT)
                        error("unable to open object pack directory: %s: %s",
@@ -1158,8 +1097,7 @@ unsigned long unpack_object_header_buffer(const unsigned char *buf,
                unsigned long len, enum object_type *type, unsigned long *sizep)
 {
        unsigned shift;
-       unsigned char c;
-       unsigned long size;
+       unsigned long size, c;
        unsigned long used = 0;
 
        c = buf[used++];
@@ -1167,7 +1105,7 @@ unsigned long unpack_object_header_buffer(const unsigned char *buf,
        size = c & 15;
        shift = 4;
        while (c & 0x80) {
-               if (len <= used || sizeof(long) * 8 <= shift) {
+               if (len <= used || bitsizeof(long) <= shift) {
                        error("bad object header");
                        return 0;
                }
@@ -1229,7 +1167,7 @@ static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned lon
 static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size, const unsigned char *sha1)
 {
        int bytes = strlen(buffer) + 1;
-       unsigned char *buf = xmalloc(1+size);
+       unsigned char *buf = xmallocz(size);
        unsigned long n;
        int status = Z_OK;
 
@@ -1257,7 +1195,6 @@ static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size
                while (status == Z_OK)
                        status = git_inflate(stream, Z_FINISH);
        }
-       buf[size] = 0;
        if (status == Z_STREAM_END && !stream->avail_in) {
                git_inflate_end(stream);
                return buf;
@@ -1580,17 +1517,18 @@ static void *unpack_compressed_entry(struct packed_git *p,
        z_stream stream;
        unsigned char *buffer, *in;
 
-       buffer = xmalloc(size + 1);
-       buffer[size] = 0;
+       buffer = xmallocz(size);
        memset(&stream, 0, sizeof(stream));
        stream.next_out = buffer;
-       stream.avail_out = size;
+       stream.avail_out = size + 1;
 
        git_inflate_init(&stream);
        do {
                in = use_pack(p, w_curs, curpos, &stream.avail_in);
                stream.next_in = in;
                st = git_inflate(&stream, Z_FINISH);
+               if (!stream.avail_out)
+                       break; /* the payload is larger than it should be */
                curpos += stream.next_in - in;
        } while (st == Z_OK || st == Z_BUF_ERROR);
        git_inflate_end(&stream);
@@ -2141,13 +2079,35 @@ static void *read_object(const unsigned char *sha1, enum object_type *type,
        return read_packed_sha1(sha1, type, size);
 }
 
-void *read_sha1_file(const unsigned char *sha1, enum object_type *type,
-                    unsigned long *size)
+void *read_sha1_file_repl(const unsigned char *sha1,
+                         enum object_type *type,
+                         unsigned long *size,
+                         const unsigned char **replacement)
 {
-       void *data = read_object(sha1, type, size);
+       const unsigned char *repl = lookup_replace_object(sha1);
+       void *data = read_object(repl, type, size);
+       char *path;
+
+       /* die if we replaced an object with one that does not exist */
+       if (!data && repl != sha1)
+               die("replacement %s not found for %s",
+                   sha1_to_hex(repl), sha1_to_hex(sha1));
+
        /* legacy behavior is to die on corrupted objects */
-       if (!data && (has_loose_object(sha1) || has_packed_and_bad(sha1)))
-               die("object %s is corrupted", sha1_to_hex(sha1));
+       if (!data) {
+               if (has_loose_object(repl)) {
+                       path = sha1_file_name(sha1);
+                       die("loose object %s (stored in %s) is corrupted", sha1_to_hex(repl), path);
+               }
+               if (has_packed_and_bad(repl)) {
+                       path = sha1_pack_name(sha1);
+                       die("packed object %s (stored in %s) is corrupted", sha1_to_hex(repl), path);
+               }
+       }
+
+       if (replacement)
+               *replacement = repl;
+
        return data;
 }
 
@@ -2247,7 +2207,7 @@ int move_temp_to_file(const char *tmpfile, const char *filename)
                        goto out;
                ret = errno;
        }
-       unlink(tmpfile);
+       unlink_or_warn(tmpfile);
        if (ret) {
                if (ret != EEXIST) {
                        return error("unable to write sha1 filename %s: %s\n", filename, strerror(ret));
@@ -2256,7 +2216,7 @@ int move_temp_to_file(const char *tmpfile, const char *filename)
        }
 
 out:
-       if (set_shared_perm(filename, (S_IFREG|0444)))
+       if (adjust_shared_perm(filename))
                return error("unable to set permission to '%s'", filename);
        return 0;
 }
@@ -2283,7 +2243,7 @@ static void close_sha1_file(int fd)
        if (fsync_object_files)
                fsync_or_die(fd, "sha1 file");
        if (close(fd) != 0)
-               die("error when closing sha1 file (%s)", strerror(errno));
+               die_errno("error when closing sha1 file");
 }
 
 /* Size of directory component, including the ending '/' */
@@ -2312,7 +2272,7 @@ static int create_tmpfile(char *buffer, size_t bufsiz, const char *filename)
        }
        memcpy(buffer, filename, dirlen);
        strcpy(buffer + dirlen, "tmp_obj_XXXXXX");
-       fd = mkstemp(buffer);
+       fd = git_mkstemp_mode(buffer, 0444);
        if (fd < 0 && dirlen && errno == ENOENT) {
                /* Make sure the directory exists */
                memcpy(buffer, filename, dirlen);
@@ -2322,23 +2282,26 @@ static int create_tmpfile(char *buffer, size_t bufsiz, const char *filename)
 
                /* Try again */
                strcpy(buffer + dirlen - 1, "/tmp_obj_XXXXXX");
-               fd = mkstemp(buffer);
+               fd = git_mkstemp_mode(buffer, 0444);
        }
        return fd;
 }
 
 static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
-                             void *buf, unsigned long len, time_t mtime)
+                             const void *buf, unsigned long len, time_t mtime)
 {
        int fd, ret;
-       size_t size;
-       unsigned char *compressed;
+       unsigned char compressed[4096];
        z_stream stream;
+       git_SHA_CTX c;
+       unsigned char parano_sha1[20];
        char *filename;
        static char tmpfile[PATH_MAX];
 
        filename = sha1_file_name(sha1);
        fd = create_tmpfile(tmpfile, sizeof(tmpfile), filename);
+       while (fd < 0 && errno == EMFILE && unuse_one_window(packed_git, -1))
+               fd = create_tmpfile(tmpfile, sizeof(tmpfile), filename);
        if (fd < 0) {
                if (errno == EACCES)
                        return error("insufficient permission for adding an object to repository database %s\n", get_object_directory());
@@ -2349,36 +2312,40 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
        /* Set it up */
        memset(&stream, 0, sizeof(stream));
        deflateInit(&stream, zlib_compression_level);
-       size = 8 + deflateBound(&stream, len+hdrlen);
-       compressed = xmalloc(size);
-
-       /* Compress it */
        stream.next_out = compressed;
-       stream.avail_out = size;
+       stream.avail_out = sizeof(compressed);
+       git_SHA1_Init(&c);
 
        /* First header.. */
        stream.next_in = (unsigned char *)hdr;
        stream.avail_in = hdrlen;
        while (deflate(&stream, 0) == Z_OK)
                /* nothing */;
+       git_SHA1_Update(&c, hdr, hdrlen);
 
        /* Then the data itself.. */
-       stream.next_in = buf;
+       stream.next_in = (void *)buf;
        stream.avail_in = len;
-       ret = deflate(&stream, Z_FINISH);
+       do {
+               unsigned char *in0 = stream.next_in;
+               ret = deflate(&stream, Z_FINISH);
+               git_SHA1_Update(&c, in0, stream.next_in - in0);
+               if (write_buffer(fd, compressed, stream.next_out - compressed) < 0)
+                       die("unable to write sha1 file");
+               stream.next_out = compressed;
+               stream.avail_out = sizeof(compressed);
+       } while (ret == Z_OK);
+
        if (ret != Z_STREAM_END)
                die("unable to deflate new object %s (%d)", sha1_to_hex(sha1), ret);
-
        ret = deflateEnd(&stream);
        if (ret != Z_OK)
                die("deflateEnd on object %s failed (%d)", sha1_to_hex(sha1), ret);
+       git_SHA1_Final(parano_sha1, &c);
+       if (hashcmp(sha1, parano_sha1) != 0)
+               die("confused by unstable object source data for %s", sha1_to_hex(sha1));
 
-       size = stream.total_out;
-
-       if (write_buffer(fd, compressed, size) < 0)
-               die("unable to write sha1 file");
        close_sha1_file(fd);
-       free(compressed);
 
        if (mtime) {
                struct utimbuf utb;
@@ -2392,7 +2359,7 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
        return move_temp_to_file(tmpfile, filename);
 }
 
-int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
+int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
 {
        unsigned char sha1[20];
        char hdr[32];
@@ -2438,14 +2405,6 @@ int has_pack_index(const unsigned char *sha1)
        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;
@@ -2490,6 +2449,8 @@ static int index_mem(unsigned char *sha1, void *buf, size_t size,
        return ret;
 }
 
+#define SMALL_FILE_SIZE (32*1024)
+
 int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
             enum object_type type, const char *path)
 {
@@ -2504,12 +2465,21 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
                else
                        ret = -1;
                strbuf_release(&sbuf);
-       } else if (size) {
+       } else if (!size) {
+               ret = index_mem(sha1, NULL, size, write_object, type, path);
+       } else if (size <= SMALL_FILE_SIZE) {
+               char *buf = xmalloc(size);
+               if (size == read_in_full(fd, buf, size))
+                       ret = index_mem(sha1, buf, size, write_object, type,
+                                       path);
+               else
+                       ret = error("short read %s", strerror(errno));
+               free(buf);
+       } else {
                void *buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
                ret = index_mem(sha1, buf, size, write_object, type, path);
                munmap(buf, size);
-       } else
-               ret = index_mem(sha1, NULL, size, write_object, type, path);
+       }
        close(fd);
        return ret;
 }
@@ -2564,3 +2534,13 @@ int read_pack_header(int fd, struct pack_header *header)
                return PH_ERROR_PROTOCOL;
        return 0;
 }
+
+void assert_sha1_type(const unsigned char *sha1, enum object_type expect)
+{
+       enum object_type type = sha1_object_info(sha1, NULL);
+       if (type < 0)
+               die("%s is not a valid object", sha1_to_hex(sha1));
+       if (type != expect)
+               die("%s is not a valid '%s' object", sha1_to_hex(sha1),
+                   typename(expect));
+}
index 904bcd96a54a1cc33386a56a16d07dce34cbb90b..7b7e61719fa63e41917b41240061c68e3cbabf01 100644 (file)
@@ -5,6 +5,7 @@
 #include "blob.h"
 #include "tree-walk.h"
 #include "refs.h"
+#include "remote.h"
 
 static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
 {
@@ -240,7 +241,8 @@ static int ambiguous_path(const char *path, int len)
 
 /*
  * *string and *len will only be substituted, and *string returned (for
- * later free()ing) if the string passed in is of the form @{-<n>}.
+ * later free()ing) if the string passed in is a magic short-hand form
+ * to name a branch.
  */
 static char *substitute_branch_name(const char **string, int *len)
 {
@@ -278,8 +280,7 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
                                *ref = xstrdup(r);
                        if (!warn_ambiguous_refs)
                                break;
-               } else if ((flag & REF_ISSYMREF) &&
-                          (len != 4 || strcmp(str, "HEAD")))
+               } else if ((flag & REF_ISSYMREF) && strcmp(fullref, "HEAD"))
                        warning("ignoring dangling symref %s.", fullref);
        }
        free(last_branch);
@@ -323,11 +324,25 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
        return logs_found;
 }
 
+static inline int upstream_mark(const char *string, int len)
+{
+       const char *suffix[] = { "@{upstream}", "@{u}" };
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(suffix); i++) {
+               int suffix_len = strlen(suffix[i]);
+               if (suffix_len <= len
+                   && !memcmp(string, suffix[i], suffix_len))
+                       return suffix_len;
+       }
+       return 0;
+}
+
 static int get_sha1_1(const char *name, int len, unsigned char *sha1);
 
 static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 {
-       static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
+       static const char *warn_msg = "refname '%.*s' is ambiguous.";
        char *real_ref = NULL;
        int refs_found = 0;
        int at, reflog_len;
@@ -340,8 +355,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
        if (len && str[len-1] == '}') {
                for (at = len-2; at >= 0; at--) {
                        if (str[at] == '@' && str[at+1] == '{') {
-                               reflog_len = (len-1) - (at+2);
-                               len = at;
+                               if (!upstream_mark(str + at, len - at)) {
+                                       reflog_len = (len-1) - (at+2);
+                                       len = at;
+                               }
                                break;
                        }
                }
@@ -373,7 +390,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
                return -1;
 
        if (warn_ambiguous_refs && refs_found > 1)
-               fprintf(stderr, warning, len, str);
+               warning(warn_msg, len, str);
 
        if (reflog_len) {
                int nth, i;
@@ -381,6 +398,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
                unsigned long co_time;
                int co_tz, co_cnt;
 
+               /* a @{-N} placed anywhere except the start is an error */
+               if (str[at+2] == '-')
+                       return -1;
+
                /* Is it asking for N-th entry, or approxidate? */
                for (i = nth = 0; 0 <= nth && i < reflog_len; i++) {
                        char ch = str[at+2+i];
@@ -395,21 +416,24 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
                } else if (0 <= nth)
                        at_time = 0;
                else {
+                       int errors = 0;
                        char *tmp = xstrndup(str + at + 2, reflog_len);
-                       at_time = approxidate(tmp);
+                       at_time = approxidate_careful(tmp, &errors);
                        free(tmp);
+                       if (errors)
+                               return -1;
                }
                if (read_ref_at(real_ref, at_time, nth, sha1, NULL,
                                &co_time, &co_tz, &co_cnt)) {
                        if (at_time)
-                               fprintf(stderr,
-                                       "warning: Log for '%.*s' only goes "
-                                       "back to %s.\n", len, str,
+                               warning("Log for '%.*s' only goes "
+                                       "back to %s.", len, str,
                                        show_date(co_time, co_tz, DATE_RFC2822));
-                       else
-                               fprintf(stderr,
-                                       "warning: Log for '%.*s' only has "
-                                       "%d entries.\n", len, str, co_cnt);
+                       else {
+                               free(real_ref);
+                               die("Log for '%.*s' only has %d entries.",
+                                   len, str, co_cnt);
+                       }
                }
        }
 
@@ -635,6 +659,16 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1)
        return get_short_sha1(name, len, sha1, 0);
 }
 
+/*
+ * This interprets names like ':/Initial revision of "git"' by searching
+ * through history and returning the first commit whose message starts
+ * the given regular expression.
+ *
+ * For future extension, ':/!' is reserved. If you want to match a message
+ * beginning with a '!', you have to repeat the exclamation mark.
+ */
+#define ONELINE_SEEN (1u<<20)
+
 static int handle_one_ref(const char *path,
                const unsigned char *sha1, int flag, void *cb_data)
 {
@@ -650,30 +684,26 @@ static int handle_one_ref(const char *path,
        if (object->type != OBJ_COMMIT)
                return 0;
        insert_by_date((struct commit *)object, list);
+       object->flags |= ONELINE_SEEN;
        return 0;
 }
 
-/*
- * This interprets names like ':/Initial revision of "git"' by searching
- * through history and returning the first commit whose message starts
- * with the given string.
- *
- * For future extension, ':/!' is reserved. If you want to match a message
- * beginning with a '!', you have to repeat the exclamation mark.
- */
-
-#define ONELINE_SEEN (1u<<20)
 static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
 {
        struct commit_list *list = NULL, *backup = NULL, *l;
        int retval = -1;
        char *temp_commit_buffer = NULL;
+       regex_t regex;
 
        if (prefix[0] == '!') {
                if (prefix[1] != '!')
                        die ("Invalid search pattern: %s", prefix);
                prefix++;
        }
+
+       if (regcomp(&regex, prefix, REG_EXTENDED))
+               die("Invalid search pattern: %s", prefix);
+
        for_each_ref(handle_one_ref, &list);
        for (l = list; l; l = l->next)
                commit_list_insert(l->item, &backup);
@@ -697,12 +727,13 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
                }
                if (!(p = strstr(p, "\n\n")))
                        continue;
-               if (!prefixcmp(p + 2, prefix)) {
+               if (!regexec(&regex, p + 2, 0, NULL, 0)) {
                        hashcpy(sha1, commit->object.sha1);
                        retval = 0;
                        break;
                }
        }
+       regfree(&regex);
        free(temp_commit_buffer);
        free_commit_list(list);
        for (l = backup; l; l = l->next)
@@ -740,17 +771,10 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
 }
 
 /*
- * This reads "@{-N}" syntax, finds the name of the Nth previous
- * branch we were on, and places the name of the branch in the given
- * buf and returns the number of characters parsed if successful.
- *
- * If the input is not of the accepted format, it returns a negative
- * number to signal an error.
- *
- * If the input was ok but there are not N branch switches in the
- * reflog, it returns 0.
+ * Parse @{-N} syntax, return the number of characters parsed
+ * if successful; otherwise signal an error with negative value.
  */
-int interpret_branch_name(const char *name, struct strbuf *buf)
+static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf)
 {
        long nth;
        int i, retval;
@@ -777,8 +801,6 @@ int interpret_branch_name(const char *name, struct strbuf *buf)
        for_each_recent_reflog_ent("HEAD", grab_nth_branch_switch, 40960, &cb);
        if (cb.cnt < nth) {
                cb.cnt = 0;
-               for (i = 0; i < nth; i++)
-                       strbuf_release(&cb.buf[i]);
                for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
        }
        if (cb.cnt < nth)
@@ -796,23 +818,244 @@ release_return:
        return retval;
 }
 
+int get_sha1_mb(const char *name, unsigned char *sha1)
+{
+       struct commit *one, *two;
+       struct commit_list *mbs;
+       unsigned char sha1_tmp[20];
+       const char *dots;
+       int st;
+
+       dots = strstr(name, "...");
+       if (!dots)
+               return get_sha1(name, sha1);
+       if (dots == name)
+               st = get_sha1("HEAD", sha1_tmp);
+       else {
+               struct strbuf sb;
+               strbuf_init(&sb, dots - name);
+               strbuf_add(&sb, name, dots - name);
+               st = get_sha1(sb.buf, sha1_tmp);
+               strbuf_release(&sb);
+       }
+       if (st)
+               return st;
+       one = lookup_commit_reference_gently(sha1_tmp, 0);
+       if (!one)
+               return -1;
+
+       if (get_sha1(dots[3] ? (dots + 3) : "HEAD", sha1_tmp))
+               return -1;
+       two = lookup_commit_reference_gently(sha1_tmp, 0);
+       if (!two)
+               return -1;
+       mbs = get_merge_bases(one, two, 1);
+       if (!mbs || mbs->next)
+               st = -1;
+       else {
+               st = 0;
+               hashcpy(sha1, mbs->item->object.sha1);
+       }
+       free_commit_list(mbs);
+       return st;
+}
+
+/*
+ * This reads short-hand syntax that not only evaluates to a commit
+ * object name, but also can act as if the end user spelled the name
+ * of the branch from the command line.
+ *
+ * - "@{-N}" finds the name of the Nth previous branch we were on, and
+ *   places the name of the branch in the given buf and returns the
+ *   number of characters parsed if successful.
+ *
+ * - "<branch>@{upstream}" finds the name of the other ref that
+ *   <branch> is configured to merge with (missing <branch> defaults
+ *   to the current branch), and places the name of the branch in the
+ *   given buf and returns the number of characters parsed if
+ *   successful.
+ *
+ * If the input is not of the accepted format, it returns a negative
+ * number to signal an error.
+ *
+ * If the input was ok but there are not N branch switches in the
+ * reflog, it returns 0.
+ */
+int interpret_branch_name(const char *name, struct strbuf *buf)
+{
+       char *cp;
+       struct branch *upstream;
+       int namelen = strlen(name);
+       int len = interpret_nth_prior_checkout(name, buf);
+       int tmp_len;
+
+       if (!len)
+               return len; /* syntax Ok, not enough switches */
+       if (0 < len && len == namelen)
+               return len; /* consumed all */
+       else if (0 < len) {
+               /* we have extra data, which might need further processing */
+               struct strbuf tmp = STRBUF_INIT;
+               int used = buf->len;
+               int ret;
+
+               strbuf_add(buf, name + len, namelen - len);
+               ret = interpret_branch_name(buf->buf, &tmp);
+               /* that data was not interpreted, remove our cruft */
+               if (ret < 0) {
+                       strbuf_setlen(buf, used);
+                       return len;
+               }
+               strbuf_reset(buf);
+               strbuf_addbuf(buf, &tmp);
+               strbuf_release(&tmp);
+               /* tweak for size of {-N} versus expanded ref name */
+               return ret - used + len;
+       }
+
+       cp = strchr(name, '@');
+       if (!cp)
+               return -1;
+       tmp_len = upstream_mark(cp, namelen - (cp - name));
+       if (!tmp_len)
+               return -1;
+       len = cp + tmp_len - name;
+       cp = xstrndup(name, cp - name);
+       upstream = branch_get(*cp ? cp : NULL);
+       if (!upstream
+           || !upstream->merge
+           || !upstream->merge[0]->dst)
+               return error("No upstream branch found for '%s'", cp);
+       free(cp);
+       cp = shorten_unambiguous_ref(upstream->merge[0]->dst, 0);
+       strbuf_reset(buf);
+       strbuf_addstr(buf, cp);
+       free(cp);
+       return len;
+}
+
 /*
  * 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)
 {
-       unsigned unused;
-       return get_sha1_with_mode(name, sha1, &unused);
+       struct object_context unused;
+       return get_sha1_with_context(name, sha1, &unused);
+}
+
+/* Must be called only when object_name:filename doesn't exist. */
+static void diagnose_invalid_sha1_path(const char *prefix,
+                                      const char *filename,
+                                      const unsigned char *tree_sha1,
+                                      const char *object_name)
+{
+       struct stat st;
+       unsigned char sha1[20];
+       unsigned mode;
+
+       if (!prefix)
+               prefix = "";
+
+       if (!lstat(filename, &st))
+               die("Path '%s' exists on disk, but not in '%s'.",
+                   filename, object_name);
+       if (errno == ENOENT || errno == ENOTDIR) {
+               char *fullname = xmalloc(strlen(filename)
+                                            + strlen(prefix) + 1);
+               strcpy(fullname, prefix);
+               strcat(fullname, filename);
+
+               if (!get_tree_entry(tree_sha1, fullname,
+                                   sha1, &mode)) {
+                       die("Path '%s' exists, but not '%s'.\n"
+                           "Did you mean '%s:%s'?",
+                           fullname,
+                           filename,
+                           object_name,
+                           fullname);
+               }
+               die("Path '%s' does not exist in '%s'",
+                   filename, object_name);
+       }
+}
+
+/* Must be called only when :stage:filename doesn't exist. */
+static void diagnose_invalid_index_path(int stage,
+                                       const char *prefix,
+                                       const char *filename)
+{
+       struct stat st;
+       struct cache_entry *ce;
+       int pos;
+       unsigned namelen = strlen(filename);
+       unsigned fullnamelen;
+       char *fullname;
+
+       if (!prefix)
+               prefix = "";
+
+       /* Wrong stage number? */
+       pos = cache_name_pos(filename, namelen);
+       if (pos < 0)
+               pos = -pos - 1;
+       if (pos < active_nr) {
+               ce = active_cache[pos];
+               if (ce_namelen(ce) == namelen &&
+                   !memcmp(ce->name, filename, namelen))
+                       die("Path '%s' is in the index, but not at stage %d.\n"
+                           "Did you mean ':%d:%s'?",
+                           filename, stage,
+                           ce_stage(ce), filename);
+       }
+
+       /* Confusion between relative and absolute filenames? */
+       fullnamelen = namelen + strlen(prefix);
+       fullname = xmalloc(fullnamelen + 1);
+       strcpy(fullname, prefix);
+       strcat(fullname, filename);
+       pos = cache_name_pos(fullname, fullnamelen);
+       if (pos < 0)
+               pos = -pos - 1;
+       if (pos < active_nr) {
+               ce = active_cache[pos];
+               if (ce_namelen(ce) == fullnamelen &&
+                   !memcmp(ce->name, fullname, fullnamelen))
+                       die("Path '%s' is in the index, but not '%s'.\n"
+                           "Did you mean ':%d:%s'?",
+                           fullname, filename,
+                           ce_stage(ce), fullname);
+       }
+
+       if (!lstat(filename, &st))
+               die("Path '%s' exists on disk, but not in the index.", filename);
+       if (errno == ENOENT || errno == ENOTDIR)
+               die("Path '%s' does not exist (neither on disk nor in the index).",
+                   filename);
+
+       free(fullname);
+}
+
+
+int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, int gently, const char *prefix)
+{
+       struct object_context oc;
+       int ret;
+       ret = get_sha1_with_context_1(name, sha1, &oc, gently, prefix);
+       *mode = oc.mode;
+       return ret;
 }
 
-int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
+int get_sha1_with_context_1(const char *name, unsigned char *sha1,
+                           struct object_context *oc,
+                           int gently, const char *prefix)
 {
        int ret, bracket_depth;
        int namelen = strlen(name);
        const char *cp;
 
-       *mode = S_IFINVALID;
+       memset(oc, 0, sizeof(*oc));
+       oc->mode = S_IFINVALID;
        ret = get_sha1_1(name, namelen, sha1);
        if (!ret)
                return ret;
@@ -835,6 +1078,11 @@ int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
                        cp = name + 3;
                }
                namelen = namelen - (cp - name);
+
+               strncpy(oc->path, cp,
+                       sizeof(oc->path));
+               oc->path[sizeof(oc->path)-1] = '\0';
+
                if (!active_cache)
                        read_cache();
                pos = cache_name_pos(cp, namelen);
@@ -847,11 +1095,12 @@ int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
                                break;
                        if (ce_stage(ce) == stage) {
                                hashcpy(sha1, ce->sha1);
-                               *mode = ce->ce_mode;
                                return 0;
                        }
                        pos++;
                }
+               if (!gently)
+                       diagnose_invalid_index_path(stage, prefix, cp);
                return -1;
        }
        for (cp = name, bracket_depth = 0; *cp; cp++) {
@@ -864,9 +1113,30 @@ int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
        }
        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,
-                                             mode);
+               char *object_name = NULL;
+               if (!gently) {
+                       object_name = xmalloc(cp-name+1);
+                       strncpy(object_name, name, cp-name);
+                       object_name[cp-name] = '\0';
+               }
+               if (!get_sha1_1(name, cp-name, tree_sha1)) {
+                       const char *filename = cp+1;
+                       ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode);
+                       if (!gently) {
+                               diagnose_invalid_sha1_path(prefix, filename,
+                                                          tree_sha1, object_name);
+                               free(object_name);
+                       }
+                       hashcpy(oc->tree, tree_sha1);
+                       strncpy(oc->path, filename,
+                               sizeof(oc->path));
+                       oc->path[sizeof(oc->path)-1] = '\0';
+
+                       return ret;
+               } else {
+                       if (!gently)
+                               die("Invalid object name '%s'.", object_name);
+               }
        }
        return ret;
 }
index 4d90eda19efe0a80c1cb39e8897ab3ed5e6fcf56..a0363dea203d2a06e985bb4e140ffc675428cbf8 100644 (file)
--- a/shallow.c
+++ b/shallow.c
@@ -47,7 +47,7 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth,
 {
        int i = 0, cur_depth = 0;
        struct commit_list *result = NULL;
-       struct object_array stack = {0, 0, NULL};
+       struct object_array stack = OBJECT_ARRAY_INIT;
        struct commit *commit = NULL;
 
        while (commit || i < heads->nr || stack.nr) {
diff --git a/shell.c b/shell.c
index b968be79f487f8d93e205fd0a844a206e24f21f8..e4864e04da3b0e237342c2ca0548c0ec0082c171 100644 (file)
--- a/shell.c
+++ b/shell.c
@@ -60,7 +60,7 @@ int main(int argc, char **argv)
        while (devnull_fd >= 0 && devnull_fd <= 2)
                devnull_fd = dup(devnull_fd);
        if (devnull_fd == -1)
-               die("opening /dev/null failed (%s)", strerror(errno));
+               die_errno("opening /dev/null failed");
        close (devnull_fd);
 
        /*
index bc02cc29ef0d5f640ab390614def995f30fe4691..de4f86fb970e15491f44dfe38b7d7d6fdc3be9ad 100644 (file)
@@ -12,6 +12,7 @@ struct shortlog {
        int in1;
        int in2;
        int user_format;
+       int abbrev;
 
        char *common_repo_prefix;
        int email;
index 45bb535773fd9c36f73502df9462f7de800009c8..4c0ac138aff7b5add73e17fc9c3c4e409fd918e2 100644 (file)
@@ -1,6 +1,9 @@
 #include "cache.h"
 #include "pack.h"
 
+static const char show_index_usage[] =
+"git show-index < <packed archive index>";
+
 int main(int argc, char **argv)
 {
        int i;
@@ -8,6 +11,8 @@ int main(int argc, char **argv)
        unsigned int version;
        static unsigned int top_index[256];
 
+       if (argc != 1)
+               usage(show_index_usage);
        if (fread(top_index, 2 * 4, 1, stdin) != 1)
                die("unable to read header");
        if (top_index[0] == htonl(PACK_IDX_SIGNATURE)) {
@@ -43,7 +48,7 @@ int main(int argc, char **argv)
                        unsigned char sha1[20];
                        uint32_t crc;
                        uint32_t off;
-               } *entries = xmalloc(nr * sizeof(entries[0]));
+               } *entries = malloc(nr * sizeof(entries[0]));
                for (i = 0; i < nr; i++)
                        if (fread(entries[i].sha1, 20, 1, stdin) != 1)
                                die("unable to read sha1 %u/%u", i, nr);
index 899b1ff36619d0077dced9da77f538ab98396a83..d5ffa1c8919a6db750606c78a1b44d8618fa35a5 100644 (file)
@@ -135,9 +135,14 @@ ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet
                n = sz;
                if (packet_max - 5 < n)
                        n = packet_max - 5;
-               sprintf(hdr, "%04x", n + 5);
-               hdr[4] = band;
-               safe_write(fd, hdr, 5);
+               if (0 <= band) {
+                       sprintf(hdr, "%04x", n + 5);
+                       hdr[4] = band;
+                       safe_write(fd, hdr, 5);
+               } else {
+                       sprintf(hdr, "%04x", n + 4);
+                       safe_write(fd, hdr, 4);
+               }
                safe_write(fd, p, n);
                p += n;
                sz -= n;
index a88496030b7053a543173c299bd9f54b923db2ec..bc3a0802ea7e7b1743602972de182391b4bf0b3f 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -10,6 +10,15 @@ int prefixcmp(const char *str, const char *prefix)
                        return (unsigned char)*prefix - (unsigned char)*str;
 }
 
+int suffixcmp(const char *str, const char *suffix)
+{
+       int len = strlen(str), suflen = strlen(suffix);
+       if (len < suflen)
+               return -1;
+       else
+               return strcmp(str + len - suflen, suffix);
+}
+
 /*
  * Used as the default ->buf value, so that people can always assume
  * buf is non NULL and ->buf is NUL terminated even for a freshly
@@ -91,13 +100,6 @@ void strbuf_ltrim(struct strbuf *sb)
        sb->buf[sb->len] = '\0';
 }
 
-void strbuf_tolower(struct strbuf *sb)
-{
-       int i;
-       for (i = 0; i < sb->len; i++)
-               sb->buf[i] = tolower(sb->buf[i]);
-}
-
 struct strbuf **strbuf_split(const struct strbuf *sb, int delim)
 {
        int alloc = 2, pos = 0;
@@ -227,6 +229,12 @@ void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn,
                        break;
                format = percent + 1;
 
+               if (*format == '%') {
+                       strbuf_addch(sb, '%');
+                       format++;
+                       continue;
+               }
+
                consumed = fn(sb, format, context);
                if (consumed)
                        format += consumed;
@@ -251,6 +259,17 @@ size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder,
        return 0;
 }
 
+void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src)
+{
+       int i, len = src->len;
+
+       for (i = 0; i < len; i++) {
+               if (src->buf[i] == '%')
+                       strbuf_addch(dst, '%');
+               strbuf_addch(dst, src->buf[i]);
+       }
+}
+
 size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f)
 {
        size_t res;
@@ -260,7 +279,7 @@ size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f)
        res = fread(sb->buf + sb->len, 1, size, f);
        if (res > 0)
                strbuf_setlen(sb, sb->len + res);
-       else if (res < 0 && oldalloc == 0)
+       else if (oldalloc == 0)
                strbuf_release(sb);
        return res;
 }
@@ -322,7 +341,7 @@ int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint)
        return -1;
 }
 
-int strbuf_getline(struct strbuf *sb, FILE *fp, int term)
+int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)
 {
        int ch;
 
@@ -332,10 +351,10 @@ int strbuf_getline(struct strbuf *sb, FILE *fp, int term)
 
        strbuf_reset(sb);
        while ((ch = fgetc(fp)) != EOF) {
-               if (ch == term)
-                       break;
                strbuf_grow(sb, 1);
                sb->buf[sb->len++] = ch;
+               if (ch == term)
+                       break;
        }
        if (ch == EOF && sb->len == 0)
                return EOF;
@@ -344,6 +363,15 @@ int strbuf_getline(struct strbuf *sb, FILE *fp, int term)
        return 0;
 }
 
+int strbuf_getline(struct strbuf *sb, FILE *fp, int term)
+{
+       if (strbuf_getwholeline(sb, fp, term))
+               return EOF;
+       if (sb->buf[sb->len-1] == term)
+               strbuf_setlen(sb, sb->len-1);
+       return 0;
+}
+
 int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint)
 {
        int fd, len;
index eaa8704d5fa6e85d33953db77dd03de3cb462df4..fac2dbc24fc654e58840727204043a198d065921 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -81,7 +81,6 @@ extern void strbuf_trim(struct strbuf *);
 extern void strbuf_rtrim(struct strbuf *);
 extern void strbuf_ltrim(struct strbuf *);
 extern int strbuf_cmp(const struct strbuf *, const struct strbuf *);
-extern void strbuf_tolower(struct strbuf *);
 
 extern struct strbuf **strbuf_split(const struct strbuf *, int delim);
 extern void strbuf_list_free(struct strbuf **);
@@ -105,6 +104,7 @@ static inline void strbuf_addstr(struct strbuf *sb, const char *s) {
        strbuf_add(sb, s, strlen(s));
 }
 static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) {
+       strbuf_grow(sb, sb2->len);
        strbuf_add(sb, sb2->buf, sb2->len);
 }
 extern void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len);
@@ -116,8 +116,9 @@ struct strbuf_expand_dict_entry {
        const char *value;
 };
 extern size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, void *context);
+extern void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src);
 
-__attribute__((format(printf,2,3)))
+__attribute__((format (printf,2,3)))
 extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);
 
 extern size_t strbuf_fread(struct strbuf *, size_t, FILE *);
@@ -126,6 +127,7 @@ extern ssize_t strbuf_read(struct strbuf *, int fd, size_t hint);
 extern int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint);
 extern int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint);
 
+extern int strbuf_getwholeline(struct strbuf *, FILE *, int);
 extern int strbuf_getline(struct strbuf *, FILE *, int);
 
 extern void stripspace(struct strbuf *buf, int skip_comments);
index 1ac536e638dbbc02deb2d4e4a607cc52f7f7c108..9b023a25841cb7a9c1faecc30c484eba0e16d6af 100644 (file)
@@ -51,13 +51,13 @@ static int add_entry(int insert_at, struct string_list *list, const char *string
        return index;
 }
 
-struct string_list_item *string_list_insert(const char *string, struct string_list *list)
+struct string_list_item *string_list_insert(struct string_list *list, const char *string)
 {
-       return string_list_insert_at_index(-1, string, list);
+       return string_list_insert_at_index(list, -1, string);
 }
 
-struct string_list_item *string_list_insert_at_index(int insert_at,
-                                                    const char *string, struct string_list *list)
+struct string_list_item *string_list_insert_at_index(struct string_list *list,
+                                                    int insert_at, const char *string)
 {
        int index = add_entry(insert_at, list, string);
 
@@ -84,7 +84,7 @@ int string_list_find_insert_index(const struct string_list *list, const char *st
        return index;
 }
 
-struct string_list_item *string_list_lookup(const char *string, struct string_list *list)
+struct string_list_item *string_list_lookup(struct string_list *list, const char *string)
 {
        int exact_match, i = get_entry_index(list, string, &exact_match);
        if (!exact_match)
@@ -92,8 +92,8 @@ struct string_list_item *string_list_lookup(const char *string, struct string_li
        return list->items + i;
 }
 
-int for_each_string_list(string_list_each_func_t fn,
-                        struct string_list *list, void *cb_data)
+int for_each_string_list(struct string_list *list,
+                        string_list_each_func_t fn, void *cb_data)
 {
        int i, ret = 0;
        for (i = 0; i < list->nr; i++)
@@ -139,7 +139,7 @@ void string_list_clear_func(struct string_list *list, string_list_clear_func_t c
 }
 
 
-void print_string_list(const char *text, const struct string_list *p)
+void print_string_list(const struct string_list *p, const char *text)
 {
        int i;
        if ( text )
@@ -148,7 +148,7 @@ void print_string_list(const char *text, const struct string_list *p)
                printf("%s:%p\n", p->items[i].string, p->items[i].util);
 }
 
-struct string_list_item *string_list_append(const char *string, struct string_list *list)
+struct string_list_item *string_list_append(struct string_list *list, const char *string)
 {
        ALLOC_GROW(list->items, list->nr + 1, list->alloc);
        list->items[list->nr].string =
@@ -168,12 +168,19 @@ void sort_string_list(struct string_list *list)
        qsort(list->items, list->nr, sizeof(*list->items), cmp_items);
 }
 
-int unsorted_string_list_has_string(struct string_list *list, const char *string)
+struct string_list_item *unsorted_string_list_lookup(struct string_list *list,
+                                                    const char *string)
 {
        int i;
        for (i = 0; i < list->nr; i++)
                if (!strcmp(string, list->items[i].string))
-                       return 1;
-       return 0;
+                       return list->items + i;
+       return NULL;
+}
+
+int unsorted_string_list_has_string(struct string_list *list,
+                                   const char *string)
+{
+       return unsorted_string_list_lookup(list, string) != NULL;
 }
 
index 14bbc477decc32618139e912fda22e585d815159..494693898b89fe2f8145b483ea7e366e9b1192c1 100644 (file)
@@ -1,5 +1,5 @@
-#ifndef PATH_LIST_H
-#define PATH_LIST_H
+#ifndef STRING_LIST_H
+#define STRING_LIST_H
 
 struct string_list_item {
        char *string;
@@ -12,7 +12,10 @@ struct string_list
        unsigned int strdup_strings:1;
 };
 
-void print_string_list(const char *text, const struct string_list *p);
+#define STRING_LIST_INIT_NODUP { NULL, 0, 0, 0 }
+#define STRING_LIST_INIT_DUP   { NULL, 0, 0, 1 }
+
+void print_string_list(const struct string_list *p, const char *text);
 void string_list_clear(struct string_list *list, int free_util);
 
 /* Use this function to call a custom clear function on each util pointer */
@@ -20,23 +23,26 @@ void string_list_clear(struct string_list *list, int free_util);
 typedef void (*string_list_clear_func_t)(void *p, const char *str);
 void string_list_clear_func(struct string_list *list, string_list_clear_func_t clearfunc);
 
-/* Use this function to iterate over each item */
+/* Use this function or the macro below to iterate over each item */
 typedef int (*string_list_each_func_t)(struct string_list_item *, void *);
-int for_each_string_list(string_list_each_func_t,
-                        struct string_list *list, void *cb_data);
+int for_each_string_list(struct string_list *list,
+                        string_list_each_func_t, void *cb_data);
+#define for_each_string_list_item(item,list) \
+       for (item = (list)->items; item < (list)->items + (list)->nr; ++item)
 
 /* Use these functions only on sorted lists: */
 int string_list_has_string(const struct string_list *list, const char *string);
 int string_list_find_insert_index(const struct string_list *list, const char *string,
                                  int negative_existing_index);
-struct string_list_item *string_list_insert(const char *string, struct string_list *list);
-struct string_list_item *string_list_insert_at_index(int insert_at,
-                                                    const char *string, struct string_list *list);
-struct string_list_item *string_list_lookup(const char *string, struct string_list *list);
+struct string_list_item *string_list_insert(struct string_list *list, const char *string);
+struct string_list_item *string_list_insert_at_index(struct string_list *list,
+                                                    int insert_at, const char *string);
+struct string_list_item *string_list_lookup(struct string_list *list, const char *string);
 
 /* Use these functions only on unsorted lists: */
-struct string_list_item *string_list_append(const char *string, struct string_list *list);
+struct string_list_item *string_list_append(struct string_list *list, const char *string);
 void sort_string_list(struct string_list *list);
 int unsorted_string_list_has_string(struct string_list *list, const char *string);
-
-#endif /* PATH_LIST_H */
+struct string_list_item *unsorted_string_list_lookup(struct string_list *list,
+                                                    const char *string);
+#endif /* STRING_LIST_H */
diff --git a/submodule.c b/submodule.c
new file mode 100644 (file)
index 0000000..91a4758
--- /dev/null
@@ -0,0 +1,459 @@
+#include "cache.h"
+#include "submodule.h"
+#include "dir.h"
+#include "diff.h"
+#include "commit.h"
+#include "revision.h"
+#include "run-command.h"
+#include "diffcore.h"
+#include "refs.h"
+#include "string-list.h"
+
+struct string_list config_name_for_path;
+struct string_list config_ignore_for_name;
+
+static int add_submodule_odb(const char *path)
+{
+       struct strbuf objects_directory = STRBUF_INIT;
+       struct alternate_object_database *alt_odb;
+       int ret = 0;
+       const char *git_dir;
+
+       strbuf_addf(&objects_directory, "%s/.git", path);
+       git_dir = read_gitfile_gently(objects_directory.buf);
+       if (git_dir) {
+               strbuf_reset(&objects_directory);
+               strbuf_addstr(&objects_directory, git_dir);
+       }
+       strbuf_addstr(&objects_directory, "/objects/");
+       if (!is_directory(objects_directory.buf)) {
+               ret = -1;
+               goto done;
+       }
+       /* avoid adding it twice */
+       for (alt_odb = alt_odb_list; alt_odb; alt_odb = alt_odb->next)
+               if (alt_odb->name - alt_odb->base == objects_directory.len &&
+                               !strncmp(alt_odb->base, objects_directory.buf,
+                                       objects_directory.len))
+                       goto done;
+
+       alt_odb = xmalloc(objects_directory.len + 42 + sizeof(*alt_odb));
+       alt_odb->next = alt_odb_list;
+       strcpy(alt_odb->base, objects_directory.buf);
+       alt_odb->name = alt_odb->base + objects_directory.len;
+       alt_odb->name[2] = '/';
+       alt_odb->name[40] = '\0';
+       alt_odb->name[41] = '\0';
+       alt_odb_list = alt_odb;
+       prepare_alt_odb();
+done:
+       strbuf_release(&objects_directory);
+       return ret;
+}
+
+void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
+                                            const char *path)
+{
+       struct string_list_item *path_option, *ignore_option;
+       path_option = unsorted_string_list_lookup(&config_name_for_path, path);
+       if (path_option) {
+               ignore_option = unsorted_string_list_lookup(&config_ignore_for_name, path_option->util);
+               if (ignore_option)
+                       handle_ignore_submodules_arg(diffopt, ignore_option->util);
+       }
+}
+
+static int submodule_config(const char *var, const char *value, void *cb)
+{
+       if (!prefixcmp(var, "submodule."))
+               return parse_submodule_config_option(var, value);
+       return 0;
+}
+
+void gitmodules_config(void)
+{
+       const char *work_tree = get_git_work_tree();
+       if (work_tree) {
+               struct strbuf gitmodules_path = STRBUF_INIT;
+               strbuf_addstr(&gitmodules_path, work_tree);
+               strbuf_addstr(&gitmodules_path, "/.gitmodules");
+               git_config_from_file(submodule_config, gitmodules_path.buf, NULL);
+               strbuf_release(&gitmodules_path);
+       }
+}
+
+int parse_submodule_config_option(const char *var, const char *value)
+{
+       int len;
+       struct string_list_item *config;
+       struct strbuf submodname = STRBUF_INIT;
+
+       var += 10;              /* Skip "submodule." */
+
+       len = strlen(var);
+       if ((len > 5) && !strcmp(var + len - 5, ".path")) {
+               strbuf_add(&submodname, var, len - 5);
+               config = unsorted_string_list_lookup(&config_name_for_path, value);
+               if (config)
+                       free(config->util);
+               else
+                       config = string_list_append(&config_name_for_path, xstrdup(value));
+               config->util = strbuf_detach(&submodname, NULL);
+               strbuf_release(&submodname);
+       } else if ((len > 7) && !strcmp(var + len - 7, ".ignore")) {
+               if (strcmp(value, "untracked") && strcmp(value, "dirty") &&
+                   strcmp(value, "all") && strcmp(value, "none")) {
+                       warning("Invalid parameter \"%s\" for config option \"submodule.%s.ignore\"", value, var);
+                       return 0;
+               }
+
+               strbuf_add(&submodname, var, len - 7);
+               config = unsorted_string_list_lookup(&config_ignore_for_name, submodname.buf);
+               if (config)
+                       free(config->util);
+               else
+                       config = string_list_append(&config_ignore_for_name,
+                                                   strbuf_detach(&submodname, NULL));
+               strbuf_release(&submodname);
+               config->util = xstrdup(value);
+               return 0;
+       }
+       return 0;
+}
+
+void handle_ignore_submodules_arg(struct diff_options *diffopt,
+                                 const char *arg)
+{
+       DIFF_OPT_CLR(diffopt, IGNORE_SUBMODULES);
+       DIFF_OPT_CLR(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
+       DIFF_OPT_CLR(diffopt, IGNORE_DIRTY_SUBMODULES);
+
+       if (!strcmp(arg, "all"))
+               DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES);
+       else if (!strcmp(arg, "untracked"))
+               DIFF_OPT_SET(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
+       else if (!strcmp(arg, "dirty"))
+               DIFF_OPT_SET(diffopt, IGNORE_DIRTY_SUBMODULES);
+       else if (strcmp(arg, "none"))
+               die("bad --ignore-submodules argument: %s", arg);
+}
+
+void show_submodule_summary(FILE *f, const char *path,
+               unsigned char one[20], unsigned char two[20],
+               unsigned dirty_submodule,
+               const char *del, const char *add, const char *reset)
+{
+       struct rev_info rev;
+       struct commit *commit, *left = left, *right = right;
+       struct commit_list *merge_bases, *list;
+       const char *message = NULL;
+       struct strbuf sb = STRBUF_INIT;
+       static const char *format = "  %m %s";
+       int fast_forward = 0, fast_backward = 0;
+
+       if (is_null_sha1(two))
+               message = "(submodule deleted)";
+       else if (add_submodule_odb(path))
+               message = "(not checked out)";
+       else if (is_null_sha1(one))
+               message = "(new submodule)";
+       else if (!(left = lookup_commit_reference(one)) ||
+                !(right = lookup_commit_reference(two)))
+               message = "(commits not present)";
+
+       if (!message) {
+               init_revisions(&rev, NULL);
+               setup_revisions(0, NULL, &rev, NULL);
+               rev.left_right = 1;
+               rev.first_parent_only = 1;
+               left->object.flags |= SYMMETRIC_LEFT;
+               add_pending_object(&rev, &left->object, path);
+               add_pending_object(&rev, &right->object, path);
+               merge_bases = get_merge_bases(left, right, 1);
+               if (merge_bases) {
+                       if (merge_bases->item == left)
+                               fast_forward = 1;
+                       else if (merge_bases->item == right)
+                               fast_backward = 1;
+               }
+               for (list = merge_bases; list; list = list->next) {
+                       list->item->object.flags |= UNINTERESTING;
+                       add_pending_object(&rev, &list->item->object,
+                               sha1_to_hex(list->item->object.sha1));
+               }
+               if (prepare_revision_walk(&rev))
+                       message = "(revision walker failed)";
+       }
+
+       if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
+               fprintf(f, "Submodule %s contains untracked content\n", path);
+       if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
+               fprintf(f, "Submodule %s contains modified content\n", path);
+
+       if (!hashcmp(one, two)) {
+               strbuf_release(&sb);
+               return;
+       }
+
+       strbuf_addf(&sb, "Submodule %s %s..", path,
+                       find_unique_abbrev(one, DEFAULT_ABBREV));
+       if (!fast_backward && !fast_forward)
+               strbuf_addch(&sb, '.');
+       strbuf_addf(&sb, "%s", find_unique_abbrev(two, DEFAULT_ABBREV));
+       if (message)
+               strbuf_addf(&sb, " %s\n", message);
+       else
+               strbuf_addf(&sb, "%s:\n", fast_backward ? " (rewind)" : "");
+       fwrite(sb.buf, sb.len, 1, f);
+
+       if (!message) {
+               while ((commit = get_revision(&rev))) {
+                       struct pretty_print_context ctx = {0};
+                       ctx.date_mode = rev.date_mode;
+                       strbuf_setlen(&sb, 0);
+                       if (commit->object.flags & SYMMETRIC_LEFT) {
+                               if (del)
+                                       strbuf_addstr(&sb, del);
+                       }
+                       else if (add)
+                               strbuf_addstr(&sb, add);
+                       format_commit_message(commit, format, &sb, &ctx);
+                       if (reset)
+                               strbuf_addstr(&sb, reset);
+                       strbuf_addch(&sb, '\n');
+                       fprintf(f, "%s", sb.buf);
+               }
+               clear_commit_marks(left, ~0);
+               clear_commit_marks(right, ~0);
+       }
+       strbuf_release(&sb);
+}
+
+unsigned is_submodule_modified(const char *path, int ignore_untracked)
+{
+       ssize_t len;
+       struct child_process cp;
+       const char *argv[] = {
+               "status",
+               "--porcelain",
+               NULL,
+               NULL,
+       };
+       struct strbuf buf = STRBUF_INIT;
+       unsigned dirty_submodule = 0;
+       const char *line, *next_line;
+       const char *git_dir;
+
+       strbuf_addf(&buf, "%s/.git", path);
+       git_dir = read_gitfile_gently(buf.buf);
+       if (!git_dir)
+               git_dir = buf.buf;
+       if (!is_directory(git_dir)) {
+               strbuf_release(&buf);
+               /* The submodule is not checked out, so it is not modified */
+               return 0;
+
+       }
+       strbuf_reset(&buf);
+
+       if (ignore_untracked)
+               argv[2] = "-uno";
+
+       memset(&cp, 0, sizeof(cp));
+       cp.argv = argv;
+       cp.env = local_repo_env;
+       cp.git_cmd = 1;
+       cp.no_stdin = 1;
+       cp.out = -1;
+       cp.dir = path;
+       if (start_command(&cp))
+               die("Could not run git status --porcelain");
+
+       len = strbuf_read(&buf, cp.out, 1024);
+       line = buf.buf;
+       while (len > 2) {
+               if ((line[0] == '?') && (line[1] == '?')) {
+                       dirty_submodule |= DIRTY_SUBMODULE_UNTRACKED;
+                       if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
+                               break;
+               } else {
+                       dirty_submodule |= DIRTY_SUBMODULE_MODIFIED;
+                       if (ignore_untracked ||
+                           (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED))
+                               break;
+               }
+               next_line = strchr(line, '\n');
+               if (!next_line)
+                       break;
+               next_line++;
+               len -= (next_line - line);
+               line = next_line;
+       }
+       close(cp.out);
+
+       if (finish_command(&cp))
+               die("git status --porcelain failed");
+
+       strbuf_release(&buf);
+       return dirty_submodule;
+}
+
+static int find_first_merges(struct object_array *result, const char *path,
+               struct commit *a, struct commit *b)
+{
+       int i, j;
+       struct object_array merges;
+       struct commit *commit;
+       int contains_another;
+
+       char merged_revision[42];
+       const char *rev_args[] = { "rev-list", "--merges", "--ancestry-path",
+                                  "--all", merged_revision, NULL };
+       struct rev_info revs;
+       struct setup_revision_opt rev_opts;
+
+       memset(&merges, 0, sizeof(merges));
+       memset(result, 0, sizeof(struct object_array));
+       memset(&rev_opts, 0, sizeof(rev_opts));
+
+       /* get all revisions that merge commit a */
+       snprintf(merged_revision, sizeof(merged_revision), "^%s",
+                       sha1_to_hex(a->object.sha1));
+       init_revisions(&revs, NULL);
+       rev_opts.submodule = path;
+       setup_revisions(sizeof(rev_args)/sizeof(char *)-1, rev_args, &revs, &rev_opts);
+
+       /* save all revisions from the above list that contain b */
+       if (prepare_revision_walk(&revs))
+               die("revision walk setup failed");
+       while ((commit = get_revision(&revs)) != NULL) {
+               struct object *o = &(commit->object);
+               if (in_merge_bases(b, &commit, 1))
+                       add_object_array(o, NULL, &merges);
+       }
+
+       /* Now we've got all merges that contain a and b. Prune all
+        * merges that contain another found merge and save them in
+        * result.
+        */
+       for (i = 0; i < merges.nr; i++) {
+               struct commit *m1 = (struct commit *) merges.objects[i].item;
+
+               contains_another = 0;
+               for (j = 0; j < merges.nr; j++) {
+                       struct commit *m2 = (struct commit *) merges.objects[j].item;
+                       if (i != j && in_merge_bases(m2, &m1, 1)) {
+                               contains_another = 1;
+                               break;
+                       }
+               }
+
+               if (!contains_another)
+                       add_object_array(merges.objects[i].item,
+                                        merges.objects[i].name, result);
+       }
+
+       free(merges.objects);
+       return result->nr;
+}
+
+static void print_commit(struct commit *commit)
+{
+       struct strbuf sb = STRBUF_INIT;
+       struct pretty_print_context ctx = {0};
+       ctx.date_mode = DATE_NORMAL;
+       format_commit_message(commit, " %h: %m %s", &sb, &ctx);
+       fprintf(stderr, "%s\n", sb.buf);
+       strbuf_release(&sb);
+}
+
+#define MERGE_WARNING(path, msg) \
+       warning("Failed to merge submodule %s (%s)", path, msg);
+
+int merge_submodule(unsigned char result[20], const char *path,
+                   const unsigned char base[20], const unsigned char a[20],
+                   const unsigned char b[20])
+{
+       struct commit *commit_base, *commit_a, *commit_b;
+       int parent_count;
+       struct object_array merges;
+
+       int i;
+
+       /* store a in result in case we fail */
+       hashcpy(result, a);
+
+       /* we can not handle deletion conflicts */
+       if (is_null_sha1(base))
+               return 0;
+       if (is_null_sha1(a))
+               return 0;
+       if (is_null_sha1(b))
+               return 0;
+
+       if (add_submodule_odb(path)) {
+               MERGE_WARNING(path, "not checked out");
+               return 0;
+       }
+
+       if (!(commit_base = lookup_commit_reference(base)) ||
+           !(commit_a = lookup_commit_reference(a)) ||
+           !(commit_b = lookup_commit_reference(b))) {
+               MERGE_WARNING(path, "commits not present");
+               return 0;
+       }
+
+       /* check whether both changes are forward */
+       if (!in_merge_bases(commit_base, &commit_a, 1) ||
+           !in_merge_bases(commit_base, &commit_b, 1)) {
+               MERGE_WARNING(path, "commits don't follow merge-base");
+               return 0;
+       }
+
+       /* Case #1: a is contained in b or vice versa */
+       if (in_merge_bases(commit_a, &commit_b, 1)) {
+               hashcpy(result, b);
+               return 1;
+       }
+       if (in_merge_bases(commit_b, &commit_a, 1)) {
+               hashcpy(result, a);
+               return 1;
+       }
+
+       /*
+        * Case #2: There are one or more merges that contain a and b in
+        * the submodule. If there is only one, then present it as a
+        * suggestion to the user, but leave it marked unmerged so the
+        * user needs to confirm the resolution.
+        */
+
+       /* find commit which merges them */
+       parent_count = find_first_merges(&merges, path, commit_a, commit_b);
+       switch (parent_count) {
+       case 0:
+               MERGE_WARNING(path, "merge following commits not found");
+               break;
+
+       case 1:
+               MERGE_WARNING(path, "not fast-forward");
+               fprintf(stderr, "Found a possible merge resolution "
+                               "for the submodule:\n");
+               print_commit((struct commit *) merges.objects[0].item);
+               fprintf(stderr,
+                       "If this is correct simply add it to the index "
+                       "for example\n"
+                       "by using:\n\n"
+                       "  git update-index --cacheinfo 160000 %s \"%s\"\n\n"
+                       "which will accept this suggestion.\n",
+                       sha1_to_hex(merges.objects[0].item->sha1), path);
+               break;
+
+       default:
+               MERGE_WARNING(path, "multiple merges found");
+               for (i = 0; i < merges.nr; i++)
+                       print_commit((struct commit *) merges.objects[i].item);
+       }
+
+       free(merges.objects);
+       return 0;
+}
diff --git a/submodule.h b/submodule.h
new file mode 100644 (file)
index 0000000..386f410
--- /dev/null
@@ -0,0 +1,19 @@
+#ifndef SUBMODULE_H
+#define SUBMODULE_H
+
+struct diff_options;
+
+void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
+               const char *path);
+void gitmodules_config();
+int parse_submodule_config_option(const char *var, const char *value);
+void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *);
+void show_submodule_summary(FILE *f, const char *path,
+               unsigned char one[20], unsigned char two[20],
+               unsigned dirty_submodule,
+               const char *del, const char *add, const char *reset);
+unsigned is_submodule_modified(const char *path, int ignore_untracked);
+int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],
+                   const unsigned char a[20], const unsigned char b[20]);
+
+#endif
index 1d6b35b858020300f502e2a9341b82d4fa8a61fd..88601200114f857a57214a9bf0d02ffd39a83504 100644 (file)
@@ -32,19 +32,13 @@ static int longest_path_match(const char *name_a, int len_a,
        return match_len;
 }
 
-static struct cache_def {
-       char path[PATH_MAX + 1];
-       int len;
-       int flags;
-       int track_flags;
-       int prefix_len_stat_func;
-} cache;
+static struct cache_def default_cache;
 
-static inline void reset_lstat_cache(void)
+static inline void reset_lstat_cache(struct cache_def *cache)
 {
-       cache.path[0] = '\0';
-       cache.len = 0;
-       cache.flags = 0;
+       cache->path[0] = '\0';
+       cache->len = 0;
+       cache->flags = 0;
        /*
         * The track_flags and prefix_len_stat_func members is only
         * set by the safeguard rule inside lstat_cache()
@@ -70,23 +64,23 @@ static inline void reset_lstat_cache(void)
  * of the prefix, where the cache should use the stat() function
  * instead of the lstat() function to test each path component.
  */
-static int lstat_cache(const char *name, int len,
+static int lstat_cache(struct cache_def *cache, const char *name, int len,
                       int track_flags, int prefix_len_stat_func)
 {
        int match_len, last_slash, last_slash_dir, previous_slash;
        int match_flags, ret_flags, save_flags, max_len, ret;
        struct stat st;
 
-       if (cache.track_flags != track_flags ||
-           cache.prefix_len_stat_func != prefix_len_stat_func) {
+       if (cache->track_flags != track_flags ||
+           cache->prefix_len_stat_func != prefix_len_stat_func) {
                /*
                 * As a safeguard rule we clear the cache if the
                 * values of track_flags and/or prefix_len_stat_func
                 * does not match with the last supplied values.
                 */
-               reset_lstat_cache();
-               cache.track_flags = track_flags;
-               cache.prefix_len_stat_func = prefix_len_stat_func;
+               reset_lstat_cache(cache);
+               cache->track_flags = track_flags;
+               cache->prefix_len_stat_func = prefix_len_stat_func;
                match_len = last_slash = 0;
        } else {
                /*
@@ -94,10 +88,14 @@ static int lstat_cache(const char *name, int len,
                 * the 2 "excluding" path types.
                 */
                match_len = last_slash =
-                       longest_path_match(name, len, cache.path, cache.len,
+                       longest_path_match(name, len, cache->path, cache->len,
                                           &previous_slash);
-               match_flags = cache.flags & track_flags & (FL_NOENT|FL_SYMLINK);
-               if (match_flags && match_len == cache.len)
+               match_flags = cache->flags & track_flags & (FL_NOENT|FL_SYMLINK);
+
+               if (!(track_flags & FL_FULLPATH) && match_len == len)
+                       match_len = last_slash = previous_slash;
+
+               if (match_flags && match_len == cache->len)
                        return match_flags;
                /*
                 * If we now have match_len > 0, we would know that
@@ -121,18 +119,18 @@ static int lstat_cache(const char *name, int len,
        max_len = len < PATH_MAX ? len : PATH_MAX;
        while (match_len < max_len) {
                do {
-                       cache.path[match_len] = name[match_len];
+                       cache->path[match_len] = name[match_len];
                        match_len++;
                } while (match_len < max_len && name[match_len] != '/');
                if (match_len >= max_len && !(track_flags & FL_FULLPATH))
                        break;
                last_slash = match_len;
-               cache.path[last_slash] = '\0';
+               cache->path[last_slash] = '\0';
 
                if (last_slash <= prefix_len_stat_func)
-                       ret = stat(cache.path, &st);
+                       ret = stat(cache->path, &st);
                else
-                       ret = lstat(cache.path, &st);
+                       ret = lstat(cache->path, &st);
 
                if (ret) {
                        ret_flags = FL_LSTATERR;
@@ -156,9 +154,9 @@ static int lstat_cache(const char *name, int len,
         */
        save_flags = ret_flags & track_flags & (FL_NOENT|FL_SYMLINK);
        if (save_flags && last_slash > 0 && last_slash <= PATH_MAX) {
-               cache.path[last_slash] = '\0';
-               cache.len = last_slash;
-               cache.flags = save_flags;
+               cache->path[last_slash] = '\0';
+               cache->len = last_slash;
+               cache->flags = save_flags;
        } else if ((track_flags & FL_DIR) &&
                   last_slash_dir > 0 && last_slash_dir <= PATH_MAX) {
                /*
@@ -172,53 +170,31 @@ static int lstat_cache(const char *name, int len,
                 * can still cache the path components before the last
                 * one (the found symlink or non-existing component).
                 */
-               cache.path[last_slash_dir] = '\0';
-               cache.len = last_slash_dir;
-               cache.flags = FL_DIR;
+               cache->path[last_slash_dir] = '\0';
+               cache->len = last_slash_dir;
+               cache->flags = FL_DIR;
        } else {
-               reset_lstat_cache();
+               reset_lstat_cache(cache);
        }
        return ret_flags;
 }
 
-/*
- * Invalidate the given 'name' from the cache, if 'name' matches
- * completely with the cache.
- */
-void invalidate_lstat_cache(const char *name, int len)
-{
-       int match_len, previous_slash;
-
-       match_len = longest_path_match(name, len, cache.path, cache.len,
-                                      &previous_slash);
-       if (len == match_len) {
-               if ((cache.track_flags & FL_DIR) && previous_slash > 0) {
-                       cache.path[previous_slash] = '\0';
-                       cache.len = previous_slash;
-                       cache.flags = FL_DIR;
-               } else
-                       reset_lstat_cache();
-       }
-}
+#define USE_ONLY_LSTAT  0
 
 /*
- * Completely clear the contents of the cache
+ * Return non-zero if path 'name' has a leading symlink component
  */
-void clear_lstat_cache(void)
+int threaded_has_symlink_leading_path(struct cache_def *cache, const char *name, int len)
 {
-       reset_lstat_cache();
+       return lstat_cache(cache, name, len, FL_SYMLINK|FL_DIR, USE_ONLY_LSTAT) & FL_SYMLINK;
 }
 
-#define USE_ONLY_LSTAT  0
-
 /*
  * Return non-zero if path 'name' has a leading symlink component
  */
 int has_symlink_leading_path(const char *name, int len)
 {
-       return lstat_cache(name, len,
-                          FL_SYMLINK|FL_DIR, USE_ONLY_LSTAT) &
-               FL_SYMLINK;
+       return threaded_has_symlink_leading_path(&default_cache, name, len);
 }
 
 /*
@@ -227,7 +203,8 @@ int has_symlink_leading_path(const char *name, int len)
  */
 int has_symlink_or_noent_leading_path(const char *name, int len)
 {
-       return lstat_cache(name, len,
+       struct cache_def *cache = &default_cache;       /* FIXME */
+       return lstat_cache(cache, name, len,
                           FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT) &
                (FL_SYMLINK|FL_NOENT);
 }
@@ -241,7 +218,8 @@ int has_symlink_or_noent_leading_path(const char *name, int len)
  */
 int has_dirs_only_path(const char *name, int len, int prefix_len)
 {
-       return lstat_cache(name, len,
+       struct cache_def *cache = &default_cache;       /* FIXME */
+       return lstat_cache(cache, name, len,
                           FL_DIR|FL_FULLPATH, prefix_len) &
                FL_DIR;
 }
@@ -263,7 +241,6 @@ static void do_remove_scheduled_dirs(int new_len)
                         removal.path[removal.len] != '/');
        }
        removal.len = new_len;
-       return;
 }
 
 void schedule_dir_for_removal(const char *name, int len)
@@ -296,11 +273,9 @@ void schedule_dir_for_removal(const char *name, int len)
                       last_slash - match_len);
                removal.len = last_slash;
        }
-       return;
 }
 
 void remove_scheduled_dirs(void)
 {
        do_remove_scheduled_dirs(0);
-       return;
 }
index 7dcbb232cd876cb7b976443cc586f60a94ab92bf..4e731dc1e3bef53903f030ee7c63fe7ef7324cb1 100644 (file)
@@ -1,2 +1,3 @@
 /trash directory*
 /test-results
+/.prove
index bf816fc8505508c91999175ad6544a67febabb33..c7baefb7eac27fff31e3e78c7862eb2bf73d9068 100644 (file)
@@ -3,8 +3,12 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
+-include ../config.mak.autogen
+-include ../config.mak
+
 #GIT_TEST_OPTS=--verbose --debug
 SHELL_PATH ?= $(SHELL)
+PERL_PATH ?= /usr/bin/perl
 TAR ?= $(TAR)
 RM ?= rm -f
 
@@ -25,13 +29,17 @@ pre-clean:
 
 clean:
        $(RM) -r 'trash directory'.* test-results
+       $(RM) -r valgrind/bin
+       $(RM) .prove
 
 aggregate-results-and-cleanup: $(T)
        $(MAKE) aggregate-results
        $(MAKE) clean
 
 aggregate-results:
-       '$(SHELL_PATH_SQ)' ./aggregate-results.sh test-results/t*-*
+       for f in test-results/t*-*.counts; do \
+               echo "$$f"; \
+       done | '$(SHELL_PATH_SQ)' ./aggregate-results.sh
 
 # we can test NO_OPTIMIZE_COMMITS independently of LC_ALL
 full-svn-test:
@@ -41,4 +49,42 @@ full-svn-test:
 valgrind:
        GIT_TEST_OPTS=--valgrind $(MAKE)
 
-.PHONY: pre-clean $(T) aggregate-results clean valgrind
+# Smoke testing targets
+-include ../GIT-VERSION-FILE
+uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo unknown')
+uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo unknown')
+
+test-results:
+       mkdir -p test-results
+
+test-results/git-smoke.tar.gz: test-results
+       $(PERL_PATH) ./harness \
+               --archive="test-results/git-smoke.tar.gz" \
+               $(T)
+
+smoke: test-results/git-smoke.tar.gz
+
+SMOKE_UPLOAD_FLAGS =
+ifdef SMOKE_USERNAME
+       SMOKE_UPLOAD_FLAGS += -F username="$(SMOKE_USERNAME)" -F password="$(SMOKE_PASSWORD)"
+endif
+ifdef SMOKE_COMMENT
+       SMOKE_UPLOAD_FLAGS += -F comments="$(SMOKE_COMMENT)"
+endif
+ifdef SMOKE_TAGS
+       SMOKE_UPLOAD_FLAGS += -F tags="$(SMOKE_TAGS)"
+endif
+
+smoke_report: smoke
+       curl \
+               -H "Expect: " \
+               -F project=Git \
+               -F architecture="$(uname_M)" \
+               -F platform="$(uname_S)" \
+               -F revision="$(GIT_VERSION)" \
+               -F report_file=@test-results/git-smoke.tar.gz \
+               $(SMOKE_UPLOAD_FLAGS) \
+               http://smoke.git.nix.is/app/projects/process_add_report/1 \
+       | grep -v ^Redirecting
+
+.PHONY: pre-clean $(T) aggregate-results clean valgrind smoke smoke_report
index d8f6c7de6d27e27a33982e82893baa3bb5ffd7fd..a1eb7c8720ad3e91f78fca6d58acd336c71171a0 100644 (file)
--- a/t/README
+++ b/t/README
@@ -18,25 +18,48 @@ 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 in an empty repo.
-    *   ok 2: .git/objects should have 256 subdirectories.
-    *   ok 3: git-update-index without --add should fail adding.
+    ok 1 - .git/objects should be empty after git init in an empty repo.
+    ok 2 - .git/objects should have 3 subdirectories.
+    ok 3 - success is reported like this
     ...
-    *   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)
+    ok 43 - very long name in the index handled sanely
+    # fixed 1 known breakage(s)
+    # still have 1 known breakage(s)
+    # passed all remaining 42 test(s)
+    1..43
+    *** t0001-init.sh ***
+    ok 1 - plain
+    ok 2 - plain with GIT_WORK_TREE
+    ok 3 - plain bare
+
+Since the tests all output TAP (see http://testanything.org) they can
+be run with any TAP harness. Here's an example of parallel testing
+powered by a recent version of prove(1):
+
+    $ prove --timer --jobs 15 ./t[0-9]*.sh
+    [19:17:33] ./t0005-signals.sh ................................... ok       36 ms
+    [19:17:33] ./t0022-crlf-rename.sh ............................... ok       69 ms
+    [19:17:33] ./t0024-crlf-archive.sh .............................. ok      154 ms
+    [19:17:33] ./t0004-unwritable.sh ................................ ok      289 ms
+    [19:17:33] ./t0002-gitfile.sh ................................... ok      480 ms
+    ===(     102;0  25/?  6/?  5/?  16/?  1/?  4/?  2/?  1/?  3/?  1... )===
+
+prove and other harnesses come with a lot of useful options. The
+--state option in particular is very useful:
+
+    # Repeat until no more failures
+    $ prove -j 15 --state=failed,save ./t[0-9]*.sh
+
+You can also run each test individually from command line, like this:
+
+    $ sh ./t3010-ls-files-killed-modified.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.
+    ok 4 - git ls-files -m to show modified files.
+    ok 5 - validate git ls-files -m output.
+    # passed all 5 test(s)
+    1..5
 
 You can pass --verbose (or -v), --debug (or -d), and --immediate
 (or -i) command line argument to the test, or by setting GIT_TEST_OPTS
@@ -75,6 +98,34 @@ appropriately before running "make".
        As the names depend on the tests' file names, it is safe to
        run the tests with this option in parallel.
 
+--with-dashes::
+       By default tests are run without dashed forms of
+       commands (like git-commit) in the PATH (it only uses
+       wrappers from ../bin-wrappers).  Use this option to include
+       the build directory (..) in the PATH, which contains all
+       the dashed forms of commands.  This option is currently
+       implied by other options like --valgrind and
+       GIT_TEST_INSTALLED.
+
+--root=<directory>::
+       Create "trash" directories used to store all temporary data during
+       testing under <directory>, instead of the t/ directory.
+       Using this option with a RAM-based filesystem (such as tmpfs)
+       can massively speed up the test suite.
+
+You can also set the GIT_TEST_INSTALLED environment variable to
+the bindir of an existing git installation to test that installation.
+You still need to have built this git sandbox, from which various
+test-* support programs, templates, and perl libraries are used.
+If your installed git is incomplete, it will silently test parts of
+your built version instead.
+
+When using GIT_TEST_INSTALLED, you can also set GIT_TEST_EXEC_PATH to
+override the location of the dashed-form subcommands (what
+GIT_EXEC_PATH would be used for during normal operation).
+GIT_TEST_EXEC_PATH defaults to `$GIT_TEST_INSTALLED/git --exec-path`.
+
+
 Skipping Tests
 --------------
 
@@ -170,15 +221,128 @@ 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 directory'
-   if you must know, but I do not think you care.
+ - Creates an empty test directory with an empty .git/objects database
+   and chdir(2) into it.  This directory is 't/trash
+   directory.$test_name_without_dotsh', with t/ subject to change by
+   the --root option documented above.
 
  - 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.
 
+Do's, don'ts & things to keep in mind
+-------------------------------------
+
+Here are a few examples of things you probably should and shouldn't do
+when writing tests.
+
+Do:
+
+ - Put all code inside test_expect_success and other assertions.
+
+   Even code that isn't a test per se, but merely some setup code
+   should be inside a test assertion.
+
+ - Chain your test assertions
+
+   Write test code like this:
+
+       git merge foo &&
+       git push bar &&
+       test ...
+
+   Instead of:
+
+       git merge hla
+       git push gh
+       test ...
+
+   That way all of the commands in your tests will succeed or fail. If
+   you must ignore the return value of something (e.g., the return
+   after unsetting a variable that was already unset is unportable) it's
+   best to indicate so explicitly with a semicolon:
+
+       unset HLAGH;
+       git merge hla &&
+       git push gh &&
+       test ...
+
+ - Check the test coverage for your tests. See the "Test coverage"
+   below.
+
+   Don't blindly follow test coverage metrics, they're a good way to
+   spot if you've missed something. If a new function you added
+   doesn't have any coverage you're probably doing something wrong,
+   but having 100% coverage doesn't necessarily mean that you tested
+   everything.
+
+   Tests that are likely to smoke out future regressions are better
+   than tests that just inflate the coverage metrics.
+
+Don't:
+
+ - exit() within a <script> part.
+
+   The harness will catch this as a programming error of the test.
+   Use test_done instead if you need to stop the tests early (see
+   "Skipping tests" below).
+
+ - Break the TAP output
+
+   The raw output from your test may be interpreted by a TAP harness. TAP
+   harnesses will ignore everything they don't know about, but don't step
+   on their toes in these areas:
+
+   - Don't print lines like "$x..$y" where $x and $y are integers.
+
+   - Don't print lines that begin with "ok" or "not ok".
+
+   TAP harnesses expect a line that begins with either "ok" and "not
+   ok" to signal a test passed or failed (and our harness already
+   produces such lines), so your script shouldn't emit such lines to
+   their output.
+
+   You can glean some further possible issues from the TAP grammar
+   (see http://search.cpan.org/perldoc?TAP::Parser::Grammar#TAP_Grammar)
+   but the best indication is to just run the tests with prove(1),
+   it'll complain if anything is amiss.
+
+Keep in mind:
+
+ - Inside <script> part, the standard output and standard error
+   streams are discarded, and the test harness only reports "ok" or
+   "not ok" to the end user running the tests. Under --verbose, they
+   are shown to help debugging the tests.
+
+
+Skipping tests
+--------------
+
+If you need to skip tests you should do so be using the three-arg form
+of the test_* functions (see the "Test harness library" section
+below), e.g.:
+
+    test_expect_success PERL 'I need Perl' "
+        '$PERL_PATH' -e 'hlagh() if unf_unf()'
+    "
+
+The advantage of skipping tests like this is that platforms that don't
+have the PERL and other optional dependencies get an indication of how
+many tests they're missing.
+
+If the test code is too hairy for that (i.e. does a lot of setup work
+outside test assertions) you can also skip all remaining tests by
+setting skip_all and immediately call test_done:
+
+       if ! test_have_prereq PERL
+       then
+           skip_all='skipping perl interface tests, perl not available'
+           test_done
+       fi
+
+The string you give to skip_all will be used as an explanation for why
+the test was skipped.
 
 End with test_done
 ------------------
@@ -194,9 +358,9 @@ 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>
+ - test_expect_success [<prereq>] <message> <script>
 
-   This takes two strings as parameter, and evaluates the
+   Usually takes two strings as parameter, and evaluates the
    <script>.  If it yields success, test is considered
    successful.  <message> should state what it is testing.
 
@@ -206,7 +370,20 @@ library for your script to use.
            'git-write-tree should be able to write an empty tree.' \
            'tree=$(git-write-tree)'
 
- - test_expect_failure <message> <script>
+   If you supply three parameters the first will be taken to be a
+   prerequisite, see the test_set_prereq and test_have_prereq
+   documentation below:
+
+       test_expect_success TTY 'git --paginate rev-list uses a pager' \
+           ' ... '
+
+   You can also supply a comma-separated list of prerequisites, in the
+   rare case where your test depends on more than one:
+
+       test_expect_success PERL,PYTHON 'yo dawg' \
+           ' test $(perl -E 'print eval "1 +" . qx[python -c "print 2"]') == "4" '
+
+ - test_expect_failure [<prereq>] <message> <script>
 
    This is NOT the opposite of test_expect_success, but is used
    to mark a test that demonstrates a known breakage.  Unlike
@@ -215,6 +392,16 @@ library for your script to use.
    success and "still broken" on failure.  Failures from these
    tests won't cause -i (immediate) to stop.
 
+   Like test_expect_success this function can optionally use a three
+   argument invocation with a prerequisite as the first argument.
+
+ - test_expect_code [<prereq>] <code> <message> <script>
+
+   Analogous to test_expect_success, but pass the test if it exits
+   with a given exit <code>
+
+ test_expect_code 1 'Merge with d/f conflicts' 'git merge "merge msg" B master'
+
  - test_debug <script>
 
    This takes a single argument, <script>, and evaluates it only
@@ -247,6 +434,134 @@ library for your script to use.
    Merges the given rev using the given message.  Like test_commit,
    creates a tag and calls test_tick before committing.
 
+ - test_set_prereq SOME_PREREQ
+
+   Set a test prerequisite to be used later with test_have_prereq. The
+   test-lib will set some prerequisites for you, see the
+   "Prerequisites" section below for a full list of these.
+
+   Others you can set yourself and use later with either
+   test_have_prereq directly, or the three argument invocation of
+   test_expect_success and test_expect_failure.
+
+ - test_have_prereq SOME PREREQ
+
+   Check if we have a prerequisite previously set with
+   test_set_prereq. The most common use of this directly is to skip
+   all the tests if we don't have some essential prerequisite:
+
+       if ! test_have_prereq PERL
+       then
+           skip_all='skipping perl interface tests, perl not available'
+           test_done
+       fi
+
+ - test_external [<prereq>] <message> <external> <script>
+
+   Execute a <script> with an <external> interpreter (like perl). This
+   was added for tests like t9700-perl-git.sh which do most of their
+   work in an external test script.
+
+       test_external \
+           'GitwebCache::*FileCache*' \
+           "$PERL_PATH" "$TEST_DIRECTORY"/t9503/test_cache_interface.pl
+
+   If the test is outputting its own TAP you should set the
+   test_external_has_tap variable somewhere before calling the first
+   test_external* function. See t9700-perl-git.sh for an example.
+
+       # The external test will outputs its own plan
+       test_external_has_tap=1
+
+ - test_external_without_stderr [<prereq>] <message> <external> <script>
+
+   Like test_external but fail if there's any output on stderr,
+   instead of checking the exit code.
+
+       test_external_without_stderr \
+           'Perl API' \
+           "$PERL_PATH" "$TEST_DIRECTORY"/t9700/test.pl
+
+ - test_must_fail <git-command>
+
+   Run a git command and ensure it fails in a controlled way.  Use
+   this instead of "! <git-command>".  When git-command dies due to a
+   segfault, test_must_fail diagnoses it as an error; "! <git-command>"
+   treats it as just another expected failure, which would let such a
+   bug go unnoticed.
+
+ - test_might_fail <git-command>
+
+   Similar to test_must_fail, but tolerate success, too.  Use this
+   instead of "<git-command> || :" to catch failures due to segv.
+
+ - test_cmp <expected> <actual>
+
+   Check whether the content of the <actual> file matches the
+   <expected> file.  This behaves like "cmp" but produces more
+   helpful output when the test is run with "-v" option.
+
+ - test_path_is_file <file> [<diagnosis>]
+   test_path_is_dir <dir> [<diagnosis>]
+   test_path_is_missing <path> [<diagnosis>]
+
+   Check whether a file/directory exists or doesn't. <diagnosis> will
+   be displayed if the test fails.
+
+ - test_when_finished <script>
+
+   Prepend <script> to a list of commands to run to clean up
+   at the end of the current test.  If some clean-up command
+   fails, the test will not pass.
+
+   Example:
+
+       test_expect_success 'branch pointing to non-commit' '
+               git rev-parse HEAD^{tree} >.git/refs/heads/invalid &&
+               test_when_finished "git update-ref -d refs/heads/invalid" &&
+               ...
+       '
+
+Prerequisites
+-------------
+
+These are the prerequisites that the test library predefines with
+test_have_prereq.
+
+See the prereq argument to the test_* functions in the "Test harness
+library" section above and the "test_have_prereq" function for how to
+use these, and "test_set_prereq" for how to define your own.
+
+ - PERL & PYTHON
+
+   Git wasn't compiled with NO_PERL=YesPlease or
+   NO_PYTHON=YesPlease. Wrap any tests that need Perl or Python in
+   these.
+
+ - POSIXPERM
+
+   The filesystem supports POSIX style permission bits.
+
+ - BSLASHPSPEC
+
+   Backslashes in pathspec are not directory separators. This is not
+   set on Windows. See 6fd1106a for details.
+
+ - EXECKEEPSPID
+
+   The process retains the same pid across exec(2). See fb9a2bea for
+   details.
+
+ - SYMLINKS
+
+   The filesystem we're on supports symbolic links. E.g. a FAT
+   filesystem doesn't support these. See 704a3143 for details.
+
+ - SANITY
+
+   Test is not run by root user, and an attempt to write to an
+   unwritable file is expected to fail correctly.
+
 Tips for Writing Tests
 ----------------------
 
@@ -273,3 +588,115 @@ 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.
+
+Test coverage
+-------------
+
+You can use the coverage tests to find code paths that are not being
+used or properly exercised yet.
+
+To do that, run the coverage target at the top-level (not in the t/
+directory):
+
+    make coverage
+
+That'll compile Git with GCC's coverage arguments, and generate a test
+report with gcov after the tests finish. Running the coverage tests
+can take a while, since running the tests in parallel is incompatible
+with GCC's coverage mode.
+
+After the tests have run you can generate a list of untested
+functions:
+
+    make coverage-untested-functions
+
+You can also generate a detailed per-file HTML report using the
+Devel::Cover module. To install it do:
+
+   # On Debian or Ubuntu:
+   sudo aptitude install libdevel-cover-perl
+
+   # From the CPAN with cpanminus
+   curl -L http://cpanmin.us | perl - --sudo --self-upgrade
+   cpanm --sudo Devel::Cover
+
+Then, at the top-level:
+
+    make cover_db_html
+
+That'll generate a detailed cover report in the "cover_db_html"
+directory, which you can then copy to a webserver, or inspect locally
+in a browser.
+
+Smoke testing
+-------------
+
+The Git test suite has support for smoke testing. Smoke testing is
+when you submit the results of a test run to a central server for
+analysis and aggregation.
+
+Running a smoke tester is an easy and valuable way of contributing to
+Git development, particularly if you have access to an uncommon OS on
+obscure hardware.
+
+After building Git you can generate a smoke report like this in the
+"t" directory:
+
+    make clean smoke
+
+You can also pass arguments via the environment. This should make it
+faster:
+
+    GIT_TEST_OPTS='--root=/dev/shm' TEST_JOBS=10 make clean smoke
+
+The "smoke" target will run the Git test suite with Perl's
+"TAP::Harness" module, and package up the results in a .tar.gz archive
+with "TAP::Harness::Archive". The former is included with Perl v5.10.1
+or later, but you'll need to install the latter from the CPAN. See the
+"Test coverage" section above for how you might do that.
+
+Once the "smoke" target finishes you'll see a message like this:
+
+    TAP Archive created at <path to git>/t/test-results/git-smoke.tar.gz
+
+To upload the smoke report you need to have curl(1) installed, then
+do:
+
+    make smoke_report
+
+To upload the report anonymously. Hopefully that'll return something
+like "Reported #7 added.".
+
+If you're going to be uploading reports frequently please request a
+user account by E-Mailing gitsmoke@v.nix.is. Once you have a username
+and password you'll be able to do:
+
+    SMOKE_USERNAME=<username> SMOKE_PASSWORD=<password> make smoke_report
+
+You can also add an additional comment to attach to the report, and/or
+a comma separated list of tags:
+
+    SMOKE_USERNAME=<username> SMOKE_PASSWORD=<password> \
+        SMOKE_COMMENT=<comment> SMOKE_TAGS=<tags> \
+        make smoke_report
+
+Once the report is uploaded it'll be made available at
+http://smoke.git.nix.is, here's an overview of Recent Smoke Reports
+for Git:
+
+    http://smoke.git.nix.is/app/projects/smoke_reports/1
+
+The reports will also be mirrored to GitHub every few hours:
+
+    http://github.com/gitsmoke/smoke-reports
+
+The Smolder SQLite database is also mirrored and made available for
+download:
+
+    http://github.com/gitsmoke/smoke-database
+
+Note that the database includes hashed (with crypt()) user passwords
+and E-Mail addresses. Don't use a valuable password for the smoke
+service if you have an account, or an E-Mail address you don't want to
+be publicly known. The user accounts are just meant to be convenient
+labels, they're not meant to be secure.
index d5bab75d7da49ebb53e368d67f6b867f5417a125..d206b7c4cfa4f7d61d151cebc35a0f296fbe4c6b 100755 (executable)
@@ -6,7 +6,7 @@ failed=0
 broken=0
 total=0
 
-for file
+while read file
 do
        while read type value
        do
index 4bddeb591ecc17ec532164d0d6cf1ad1a54eb996..75a35fcd0699940aea51534b5fe954847bed7bb9 100644 (file)
@@ -1,7 +1,5 @@
 :
 
-_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.
diff --git a/t/gitweb-lib.sh b/t/gitweb-lib.sh
new file mode 100644 (file)
index 0000000..81ef2a0
--- /dev/null
@@ -0,0 +1,88 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Jakub Narebski
+#
+
+gitweb_init () {
+       safe_pwd="$(perl -MPOSIX=getcwd -e 'print quotemeta(getcwd)')"
+       cat >gitweb_config.perl <<EOF
+#!/usr/bin/perl
+
+# gitweb configuration for tests
+
+our \$version = 'current';
+our \$GIT = 'git';
+our \$projectroot = "$safe_pwd";
+our \$project_maxdepth = 8;
+our \$home_link_str = 'projects';
+our \$site_name = '[localhost]';
+our \$site_header = '';
+our \$site_footer = '';
+our \$home_text = 'indextext.html';
+our @stylesheets = ('file:///$TEST_DIRECTORY/../gitweb/static/gitweb.css');
+our \$logo = 'file:///$TEST_DIRECTORY/../gitweb/static/git-logo.png';
+our \$favicon = 'file:///$TEST_DIRECTORY/../gitweb/static/git-favicon.png';
+our \$projects_list = '';
+our \$export_ok = '';
+our \$strict_export = '';
+our \$maxload = undef;
+
+EOF
+
+       cat >.git/description <<EOF
+$0 test repository
+EOF
+}
+
+gitweb_run () {
+       GATEWAY_INTERFACE='CGI/1.1'
+       HTTP_ACCEPT='*/*'
+       REQUEST_METHOD='GET'
+       SCRIPT_NAME="$TEST_DIRECTORY/../gitweb/gitweb.perl"
+       QUERY_STRING=""$1""
+       PATH_INFO=""$2""
+       export GATEWAY_INTERFACE HTTP_ACCEPT REQUEST_METHOD \
+               SCRIPT_NAME QUERY_STRING PATH_INFO
+
+       GITWEB_CONFIG=$(pwd)/gitweb_config.perl
+       export GITWEB_CONFIG
+
+       # some of git commands write to STDERR on error, but this is not
+       # written to web server logs, so we are not interested in that:
+       # we are interested only in properly formatted errors/warnings
+       rm -f gitweb.log &&
+       perl -- "$SCRIPT_NAME" \
+               >gitweb.output 2>gitweb.log &&
+       perl -w -e '
+               open O, ">gitweb.headers";
+               while (<>) {
+                       print O;
+                       last if (/^\r$/ || /^$/);
+               }
+               open O, ">gitweb.body";
+               while (<>) {
+                       print O;
+               }
+               close O;
+       ' gitweb.output &&
+       if grep '^[[]' gitweb.log >/dev/null 2>&1; then false; else true; fi
+
+       # gitweb.log is left for debugging
+       # gitweb.output is used to parse HTTP output
+       # gitweb.headers contains only HTTP headers
+       # gitweb.body contains body of message, without headers
+}
+
+. ./test-lib.sh
+
+if ! test_have_prereq PERL; then
+       skip_all='skipping gitweb tests, perl not available'
+       test_done
+fi
+
+perl -MEncode -e 'decode_utf8("", Encode::FB_CROAK)' >/dev/null 2>&1 || {
+    skip_all='skipping gitweb tests, perl version is too old'
+    test_done
+}
+
+gitweb_init
diff --git a/t/harness b/t/harness
new file mode 100755 (executable)
index 0000000..f5c02f4
--- /dev/null
+++ b/t/harness
@@ -0,0 +1,21 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Getopt::Long ();
+use TAP::Harness::Archive;
+
+Getopt::Long::Parser->new(
+       config => [ qw/ pass_through / ],
+)->getoptions(
+       'jobs:1'    => \(my $jobs = $ENV{TEST_JOBS}),
+       'archive=s' => \my $archive,
+) or die "$0: Couldn't getoptions()";
+
+TAP::Harness::Archive->new({
+       jobs             => $jobs,
+       archive          => $archive,
+       ($ENV{GIT_TEST_OPTS}
+        ? (test_args    => [ split /\s+/, $ENV{GIT_TEST_OPTS} ])
+        : ()),
+       extra_properties => {},
+})->runtests(@ARGV);
diff --git a/t/lib-cvs.sh b/t/lib-cvs.sh
new file mode 100644 (file)
index 0000000..44263ad
--- /dev/null
@@ -0,0 +1,78 @@
+#!/bin/sh
+
+. ./test-lib.sh
+
+unset CVS_SERVER
+
+if ! type cvs >/dev/null 2>&1
+then
+       skip_all='skipping cvsimport tests, cvs not found'
+       test_done
+fi
+
+CVS="cvs -f"
+export CVS
+
+cvsps_version=`cvsps -h 2>&1 | sed -ne 's/cvsps version //p'`
+case "$cvsps_version" in
+2.1 | 2.2*)
+       ;;
+'')
+       skip_all='skipping cvsimport tests, cvsps not found'
+       test_done
+       ;;
+*)
+       skip_all='skipping cvsimport tests, unsupported cvsps version'
+       test_done
+       ;;
+esac
+
+setup_cvs_test_repository () {
+       CVSROOT="$(pwd)/.cvsroot" &&
+       cp -r "$TEST_DIRECTORY/$1/cvsroot" "$CVSROOT" &&
+       export CVSROOT
+}
+
+test_cvs_co () {
+       # Usage: test_cvs_co BRANCH_NAME
+       rm -rf module-cvs-"$1"
+       if [ "$1" = "master" ]
+       then
+               $CVS co -P -d module-cvs-"$1" -A module
+       else
+               $CVS co -P -d module-cvs-"$1" -r "$1" module
+       fi
+}
+
+test_git_co () {
+       # Usage: test_git_co BRANCH_NAME
+       (cd module-git && git checkout "$1")
+}
+
+test_cmp_branch_file () {
+       # Usage: test_cmp_branch_file BRANCH_NAME PATH
+       # The branch must already be checked out of CVS and git.
+       test_cmp module-cvs-"$1"/"$2" module-git/"$2"
+}
+
+test_cmp_branch_tree () {
+       # Usage: test_cmp_branch_tree BRANCH_NAME
+       # Check BRANCH_NAME out of CVS and git and make sure that all
+       # of the files and directories are identical.
+
+       test_cvs_co "$1" &&
+       test_git_co "$1" &&
+       (
+               cd module-cvs-"$1"
+               find . -type d -name CVS -prune -o -type f -print
+       ) | sort >module-cvs-"$1".list &&
+       (
+               cd module-git
+               find . -type d -name .git -prune -o -type f -print
+       ) | sort >module-git-"$1".list &&
+       test_cmp module-cvs-"$1".list module-git-"$1".list &&
+       cat module-cvs-"$1".list | while read f
+       do
+               test_cmp_branch_file "$1" "$f" || return 1
+       done
+}
index 773d47cf3cb704d7976185738a2b9840c159a33c..92d6d319428223de9205d4d162e3806aedee855b 100644 (file)
@@ -5,29 +5,31 @@ git_svn_id=git""-svn-id
 
 if test -n "$NO_SVN_TESTS"
 then
-       say 'skipping git svn tests, NO_SVN_TESTS defined'
+       skip_all='skipping git svn tests, NO_SVN_TESTS defined'
        test_done
 fi
 if ! test_have_prereq PERL; then
-       say 'skipping git svn tests, perl not available'
+       skip_all='skipping git svn tests, perl not available'
        test_done
 fi
 
 GIT_DIR=$PWD/.git
-GIT_SVN_DIR=$GIT_DIR/svn/git-svn
+GIT_SVN_DIR=$GIT_DIR/svn/refs/remotes/git-svn
 SVN_TREE=$GIT_SVN_DIR/svn-tree
 
 svn >/dev/null 2>&1
 if test $? -ne 1
 then
-    say 'skipping git svn tests, svn not found'
+    skip_all='skipping git svn tests, svn not found'
     test_done
 fi
 
 svnrepo=$PWD/svnrepo
 export svnrepo
+svnconf=$PWD/svnconf
+export svnconf
 
-perl -w -e "
+"$PERL_PATH" -w -e "
 use SVN::Core;
 use SVN::Repos;
 \$SVN::Core::VERSION gt '1.1.0' or exit(42);
@@ -37,13 +39,12 @@ x=$?
 if test $x -ne 0
 then
        if test $x -eq 42; then
-               err='Perl SVN libraries must be >= 1.1.0'
+               skip_all='Perl SVN libraries must be >= 1.1.0'
        elif test $x -eq 41; then
-               err='svnadmin failed to create fsfs repository'
+               skip_all='svnadmin failed to create fsfs repository'
        else
-               err='Perl SVN libraries not found or unusable, skipping test'
+               skip_all='Perl SVN libraries not found or unusable'
        fi
-       say "$err"
        test_done
 fi
 
@@ -54,6 +55,19 @@ poke() {
        test-chmtime +1 "$1"
 }
 
+# We need this, because we should pass empty configuration directory to
+# the 'svn commit' to avoid automated property changes and other stuff
+# that could be set from user's configuration files in ~/.subversion.
+svn_cmd () {
+       [ -d "$svnconf" ] || mkdir "$svnconf"
+       orig_svncmd="$1"; shift
+       if [ -z "$orig_svncmd" ]; then
+               svn
+               return
+       fi
+       svn "$orig_svncmd" --config-dir "$svnconf" "$@"
+}
+
 for d in \
        "$SVN_HTTPD_PATH" \
        /usr/sbin/apache2 \
@@ -115,7 +129,7 @@ stop_httpd () {
 }
 
 convert_to_rev_db () {
-       perl -w -- - "$@" <<\EOF
+       "$PERL_PATH" -w -- - "$@" <<\EOF
 use strict;
 @ARGV == 2 or die "Usage: convert_to_rev_db <input> <output>";
 open my $wr, '+>', $ARGV[1] or die "$!: couldn't open: $ARGV[1]";
@@ -143,7 +157,7 @@ EOF
 require_svnserve () {
     if test -z "$SVNSERVE_PORT"
     then
-        say 'skipping svnserve test. (set $SVNSERVE_PORT to enable)'
+       skip_all='skipping svnserve test. (set $SVNSERVE_PORT to enable)'
         test_done
     fi
 }
index cde659d14ac599e78bb4cd12a2ad88eab179549e..e733f6516fdf98e48d5c7cd36ecc89d36166eea1 100644 (file)
@@ -5,23 +5,35 @@
 
 if test -z "$GIT_TEST_HTTPD"
 then
-       say "skipping test, network testing disabled by default"
-       say "(define GIT_TEST_HTTPD to enable)"
+       skip_all="Network testing disabled (define GIT_TEST_HTTPD to enable)"
        test_done
 fi
 
 HTTPD_PARA=""
 
+for DEFAULT_HTTPD_PATH in '/usr/sbin/httpd' '/usr/sbin/apache2'
+do
+       if test -x "$DEFAULT_HTTPD_PATH"
+       then
+               break
+       fi
+done
+
+for DEFAULT_HTTPD_MODULE_PATH in '/usr/libexec/apache2' \
+                                '/usr/lib/apache2/modules' \
+                                '/usr/lib64/httpd/modules' \
+                                '/usr/lib/httpd/modules'
+do
+       if test -d "$DEFAULT_HTTPD_MODULE_PATH"
+       then
+               break
+       fi
+done
+
 case $(uname) in
        Darwin)
-               DEFAULT_HTTPD_PATH='/usr/sbin/httpd'
-               DEFAULT_HTTPD_MODULE_PATH='/usr/libexec/apache2'
                HTTPD_PARA="$HTTPD_PARA -DDarwin"
        ;;
-       *)
-               DEFAULT_HTTPD_PATH='/usr/sbin/apache2'
-               DEFAULT_HTTPD_MODULE_PATH='/usr/lib/apache2/modules'
-       ;;
 esac
 
 LIB_HTTPD_PATH=${LIB_HTTPD_PATH-"$DEFAULT_HTTPD_PATH"}
@@ -33,7 +45,7 @@ HTTPD_DOCUMENT_ROOT_PATH=$HTTPD_ROOT_PATH/www
 
 if ! test -x "$LIB_HTTPD_PATH"
 then
-       say "skipping test, no web server found at '$LIB_HTTPD_PATH'"
+       skip_all="skipping test, no web server found at '$LIB_HTTPD_PATH'"
        test_done
 fi
 
@@ -46,7 +58,12 @@ then
        then
                if ! test $HTTPD_VERSION -ge 2
                then
-                       say "skipping test, at least Apache version 2 is required"
+                       skip_all="skipping test, at least Apache version 2 is required"
+                       test_done
+               fi
+               if ! test -d "$DEFAULT_HTTPD_MODULE_PATH"
+               then
+                       skip_all="Apache module directory not found.  Skipping tests."
                        test_done
                fi
 
@@ -93,14 +110,16 @@ prepare_httpd() {
 start_httpd() {
        prepare_httpd >&3 2>&4
 
-       trap 'stop_httpd; die' EXIT
+       trap 'code=$?; stop_httpd; (exit $code); die' EXIT
 
        "$LIB_HTTPD_PATH" -d "$HTTPD_ROOT_PATH" \
                -f "$TEST_PATH/apache.conf" $HTTPD_PARA \
                -c "Listen 127.0.0.1:$LIB_HTTPD_PORT" -k start \
                >&3 2>&4
-       if ! test $? = 0; then
-               say "skipping test, web server setup failed"
+       if test $? -ne 0
+       then
+               skip_all="skipping test, web server setup failed"
+               trap 'die' EXIT
                test_done
        fi
 }
@@ -111,3 +130,32 @@ stop_httpd() {
        "$LIB_HTTPD_PATH" -d "$HTTPD_ROOT_PATH" \
                -f "$TEST_PATH/apache.conf" $HTTPD_PARA -k stop
 }
+
+test_http_push_nonff() {
+       REMOTE_REPO=$1
+       LOCAL_REPO=$2
+       BRANCH=$3
+
+       test_expect_success 'non-fast-forward push fails' '
+               cd "$REMOTE_REPO" &&
+               HEAD=$(git rev-parse --verify HEAD) &&
+
+               cd "$LOCAL_REPO" &&
+               git checkout $BRANCH &&
+               echo "changed" > path2 &&
+               git commit -a -m path2 --amend &&
+
+               test_must_fail git push -v origin >output 2>&1 &&
+               (cd "$REMOTE_REPO" &&
+                test $HEAD = $(git rev-parse --verify HEAD))
+       '
+
+       test_expect_success 'non-fast-forward push show ref status' '
+               grep "^ ! \[rejected\][ ]*$BRANCH -> $BRANCH (non-fast-forward)$" output
+       '
+
+       test_expect_success 'non-fast-forward push shows help message' '
+               grep "To prevent you from losing history, non-fast-forward updates were rejected" \
+                       output
+       '
+}
index 21aa42f1c613538ba26c0b613cc0d1b001bc52b9..4961505d1dd99da529b43abd42522e6a005961b1 100644 (file)
@@ -8,6 +8,33 @@ ErrorLog error.log
 <IfModule !mod_log_config.c>
        LoadModule log_config_module modules/mod_log_config.so
 </IfModule>
+<IfModule !mod_alias.c>
+       LoadModule alias_module modules/mod_alias.so
+</IfModule>
+<IfModule !mod_cgi.c>
+       LoadModule cgi_module modules/mod_cgi.so
+</IfModule>
+<IfModule !mod_env.c>
+       LoadModule env_module modules/mod_env.so
+</IfModule>
+
+Alias /dumb/ www/
+
+<Location /smart/>
+       SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
+       SetEnv GIT_HTTP_EXPORT_ALL
+</Location>
+<Location /smart_noexport/>
+       SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
+</Location>
+ScriptAlias /smart/ ${GIT_EXEC_PATH}/git-http-backend/
+ScriptAlias /smart_noexport/ ${GIT_EXEC_PATH}/git-http-backend/
+<Directory ${GIT_EXEC_PATH}>
+       Options None
+</Directory>
+<Files ${GIT_EXEC_PATH}/git-http-backend>
+       Options ExecCGI
+</Files>
 
 <IfDefine SSL>
 LoadModule ssl_module modules/mod_ssl.so
@@ -26,7 +53,7 @@ SSLEngine On
        LoadModule dav_fs_module modules/mod_dav_fs.so
 
        DAVLockDB DAVLock
-       <Location />
+       <Location /dumb/>
                Dav on
        </Location>
 </IfDefine>
diff --git a/t/lib-pager.sh b/t/lib-pager.sh
new file mode 100644 (file)
index 0000000..ba03eab
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+test_expect_success 'determine default pager' '
+       test_might_fail git config --unset core.pager &&
+       less=$(
+               unset PAGER GIT_PAGER;
+               git var GIT_PAGER
+       ) &&
+       test -n "$less"
+'
+
+if expr "$less" : '[a-z][a-z]*$' >/dev/null
+then
+       test_set_prereq SIMPLEPAGER
+fi
diff --git a/t/lib-patch-mode.sh b/t/lib-patch-mode.sh
new file mode 100644 (file)
index 0000000..06c3c91
--- /dev/null
@@ -0,0 +1,38 @@
+: included from t2016 and others
+
+. ./test-lib.sh
+
+set_state () {
+       echo "$3" > "$1" &&
+       git add "$1" &&
+       echo "$2" > "$1"
+}
+
+save_state () {
+       noslash="$(echo "$1" | tr / _)" &&
+       cat "$1" > _worktree_"$noslash" &&
+       git show :"$1" > _index_"$noslash"
+}
+
+set_and_save_state () {
+       set_state "$@" &&
+       save_state "$1"
+}
+
+verify_state () {
+       test "$(cat "$1")" = "$2" &&
+       test "$(git show :"$1")" = "$3"
+}
+
+verify_saved_state () {
+       noslash="$(echo "$1" | tr / _)" &&
+       verify_state "$1" "$(cat _worktree_"$noslash")" "$(cat _index_"$noslash")"
+}
+
+save_head () {
+       git rev-parse HEAD > _head
+}
+
+verify_saved_head () {
+       test "$(cat _head)" = "$(git rev-parse HEAD)"
+}
diff --git a/t/lib-prereq-FILEMODE.sh b/t/lib-prereq-FILEMODE.sh
new file mode 100644 (file)
index 0000000..bce5a4c
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+if test "$(git config --bool core.filemode)" = false
+then
+       say 'filemode disabled on the filesystem'
+else
+       test_set_prereq FILEMODE
+fi
index 260a231933a3cc9ce1f96eeb5952f34d7dc18d72..6ccf7970916b58748aedcce7e583eed2dee782d3 100644 (file)
@@ -2,21 +2,33 @@
 
 # After setting the fake editor with this function, you can
 #
-# - override the commit message with $FAKE_COMMIT_MESSAGE,
+# - override the commit message with $FAKE_COMMIT_MESSAGE
 # - amend the commit message with $FAKE_COMMIT_AMEND
 # - check that non-commit messages have a certain line count with $EXPECT_COUNT
-# - rewrite a rebase -i script with $FAKE_LINES in the form
+# - check the commit count in the commit message header with $EXPECT_HEADER_COUNT
+# - rewrite a rebase -i script as directed by $FAKE_LINES.
+#   $FAKE_LINES consists of a sequence of words separated by spaces.
+#   The following word combinations are possible:
 #
-#      "[<lineno1>] [<lineno2>]..."
+#   "<lineno>" -- add a "pick" line with the SHA1 taken from the
+#       specified line.
 #
-#   If a line number is prefixed with "squash" or "edit", the respective line's
-#   command will be replaced with the specified one.
+#   "<cmd> <lineno>" -- add a line with the specified command
+#       ("squash", "fixup", "edit", or "reword") and the SHA1 taken
+#       from the specified line.
+#
+#   "#" -- Add a comment line.
+#
+#   ">" -- Add a blank line.
 
 set_fake_editor () {
        echo "#!$SHELL_PATH" >fake-editor.sh
        cat >> fake-editor.sh <<\EOF
 case "$1" in
 */COMMIT_EDITMSG)
+       test -z "$EXPECT_HEADER_COUNT" ||
+               test "$EXPECT_HEADER_COUNT" = "$(sed -n '1s/^# This is a combination of \(.*\) commits\./\1/p' < "$1")" ||
+               exit
        test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
        test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1"
        exit
@@ -28,19 +40,26 @@ test -z "$EXPECT_COUNT" ||
 test -z "$FAKE_LINES" && exit
 grep -v '^#' < "$1" > "$1".tmp
 rm -f "$1"
+echo 'rebase -i script before editing:'
 cat "$1".tmp
 action=pick
 for line in $FAKE_LINES; do
        case $line in
-       squash|edit)
+       squash|fixup|edit|reword)
                action="$line";;
+       exec*)
+               echo "$line" | sed 's/_/ /g' >> "$1";;
+       "#")
+               echo '# comment' >> "$1";;
+       ">")
+               echo >> "$1";;
        *)
-               echo sed -n "${line}s/^pick/$action/p"
-               sed -n "${line}p" < "$1".tmp
                sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1"
                action=pick;;
        esac
 done
+echo 'rebase -i script after editing:'
+cat "$1"
 EOF
 
        test_set_editor "$(pwd)/fake-editor.sh"
diff --git a/t/lib-t6000.sh b/t/lib-t6000.sh
new file mode 100644 (file)
index 0000000..ea25dd8
--- /dev/null
@@ -0,0 +1,127 @@
+: included from 6002 and others
+
+[ -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
+
+       GIT_AUTHOR_EMAIL="$_author"
+       export GIT_AUTHOR_EMAIL
+       "$@"
+       if test -z "$_save"
+       then
+               unset GIT_AUTHOR_EMAIL
+       else
+               GIT_AUTHOR_EMAIL="$_save"
+               export GIT_AUTHOR_EMAIL
+       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
+    GIT_COMMITTER_DATE="$_date"
+    export GIT_COMMITTER_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
+               test_cmp $_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()
+{
+       perl -pe '
+               s/[^A-Za-z0-9.]/-/g;
+               s/-+/-/g;
+               s/-$//;
+               s/^-//;
+               y/A-Z/a-z/;
+       '
+}
+
+
+# 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\""
+}
index f4ca4fc85c6b52a2ba919528284f2b668e6bd3d2..f688bd3ef5034e32983685cfae940df303b377cc 100755 (executable)
@@ -54,9 +54,40 @@ test_expect_success 'success is reported like this' '
 test_expect_failure 'pretend we have a known breakage' '
     false
 '
+
+test_expect_success 'pretend we have fixed a known breakage (run in sub test-lib)' "
+    mkdir passing-todo &&
+    (cd passing-todo &&
+    cat >passing-todo.sh <<EOF &&
+#!$SHELL_PATH
+
+test_description='A passing TODO test
+
+This is run in a sub test-lib so that we do not get incorrect passing
+metrics
+'
+
+# Point to the t/test-lib.sh, which isn't in ../ as usual
+TEST_DIRECTORY=\"$TEST_DIRECTORY\"
+. \"\$TEST_DIRECTORY\"/test-lib.sh
+
 test_expect_failure 'pretend we have fixed a known breakage' '
     :
 '
+
+test_done
+EOF
+    chmod +x passing-todo.sh &&
+    ./passing-todo.sh >out 2>err &&
+    ! test -s err &&
+cat >expect <<EOF &&
+ok 1 - pretend we have fixed a known breakage # TODO known breakage
+# fixed 1 known breakage(s)
+# passed all 1 test(s)
+1..1
+EOF
+    test_cmp expect out)
+"
 test_set_prereq HAVEIT
 haveit=no
 test_expect_success HAVEIT 'test runs if prerequisite is satisfied' '
@@ -73,6 +104,48 @@ then
        exit 1
 fi
 
+test_set_prereq HAVETHIS
+haveit=no
+test_expect_success HAVETHIS,HAVEIT 'test runs if prerequisites are satisfied' '
+    test_have_prereq HAVEIT &&
+    test_have_prereq HAVETHIS &&
+    haveit=yes
+'
+donthaveit=yes
+test_expect_success HAVEIT,DONTHAVEIT 'unmet prerequisites causes test to be skipped' '
+    donthaveit=no
+'
+donthaveiteither=yes
+test_expect_success DONTHAVEIT,HAVEIT 'unmet prerequisites causes test to be skipped' '
+    donthaveiteither=no
+'
+if test $haveit$donthaveit$donthaveiteither != yesyesyes
+then
+       say "bug in test framework: multiple prerequisite tags do not work reliably"
+       exit 1
+fi
+
+clean=no
+test_expect_success 'tests clean up after themselves' '
+    test_when_finished clean=yes
+'
+
+cleaner=no
+test_expect_code 1 'tests clean up even after a failure' '
+    test_when_finished cleaner=yes &&
+    (exit 1)
+'
+
+if test $clean$cleaner != yesyes
+then
+       say "bug in test framework: cleanup commands do not work reliably"
+       exit 1
+fi
+
+test_expect_code 2 'failure to clean up causes the test to fail' '
+    test_when_finished "(exit 2)"
+'
+
 ################################################################
 # Basics of the basics
 
@@ -280,7 +353,7 @@ $expectfilter >expected <<\EOF
 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'
+    'git diff-files >current && test_cmp current expected >/dev/null'
 
 test_expect_success \
     'git update-index --refresh should succeed.' \
index e3d846420dc09e7b24876b291b9e546ac0628ed3..7fe8883ae060947da804abb5b287fb94d19e87c4 100755 (executable)
@@ -167,10 +167,25 @@ test_expect_success 'init with --template (blank)' '
        ! test -f template-blank/.git/info/exclude
 '
 
+test_expect_success 'init with init.templatedir set' '
+       mkdir templatedir-source &&
+       echo Content >templatedir-source/file &&
+       (
+               test_config="${HOME}/.gitconfig" &&
+               git config -f "$test_config"  init.templatedir "${HOME}/templatedir-source" &&
+               mkdir templatedir-set &&
+               cd templatedir-set &&
+               unset GIT_CONFIG_NOGLOBAL &&
+               unset GIT_TEMPLATE_DIR &&
+               NO_SET_GIT_TEMPLATE_DIR=t &&
+               export NO_SET_GIT_TEMPLATE_DIR &&
+               git init
+       ) &&
+       test_cmp templatedir-source/file templatedir-set/.git/file
+'
+
 test_expect_success 'init --bare/--shared overrides system/global config' '
        (
-               HOME="`pwd`" &&
-               export HOME &&
                test_config="$HOME"/.gitconfig &&
                unset GIT_CONFIG_NOGLOBAL &&
                git config -f "$test_config" core.bare false &&
@@ -186,8 +201,6 @@ test_expect_success 'init --bare/--shared overrides system/global config' '
 
 test_expect_success 'init honors global core.sharedRepository' '
        (
-               HOME="`pwd`" &&
-               export HOME &&
                test_config="$HOME"/.gitconfig &&
                unset GIT_CONFIG_NOGLOBAL &&
                git config -f "$test_config" core.sharedRepository 0666 &&
@@ -208,4 +221,101 @@ test_expect_success 'init rejects insanely long --template' '
        )
 '
 
+test_expect_success 'init creates a new directory' '
+       rm -fr newdir &&
+       (
+               git init newdir &&
+               test -d newdir/.git/refs
+       )
+'
+
+test_expect_success 'init creates a new bare directory' '
+       rm -fr newdir &&
+       (
+               git init --bare newdir &&
+               test -d newdir/refs
+       )
+'
+
+test_expect_success 'init recreates a directory' '
+       rm -fr newdir &&
+       (
+               mkdir newdir &&
+               git init newdir &&
+               test -d newdir/.git/refs
+       )
+'
+
+test_expect_success 'init recreates a new bare directory' '
+       rm -fr newdir &&
+       (
+               mkdir newdir &&
+               git init --bare newdir &&
+               test -d newdir/refs
+       )
+'
+
+test_expect_success 'init creates a new deep directory' '
+       rm -fr newdir &&
+       git init newdir/a/b/c &&
+       test -d newdir/a/b/c/.git/refs
+'
+
+test_expect_success POSIXPERM 'init creates a new deep directory (umask vs. shared)' '
+       rm -fr newdir &&
+       (
+               # Leading directories should honor umask while
+               # the repository itself should follow "shared"
+               umask 002 &&
+               git init --bare --shared=0660 newdir/a/b/c &&
+               test -d newdir/a/b/c/refs &&
+               ls -ld newdir/a newdir/a/b > lsab.out &&
+               ! grep -v "^drwxrw[sx]r-x" lsab.out &&
+               ls -ld newdir/a/b/c > lsc.out &&
+               ! grep -v "^drwxrw[sx]---" lsc.out
+       )
+'
+
+test_expect_success 'init notices EEXIST (1)' '
+       rm -fr newdir &&
+       (
+               >newdir &&
+               test_must_fail git init newdir &&
+               test -f newdir
+       )
+'
+
+test_expect_success 'init notices EEXIST (2)' '
+       rm -fr newdir &&
+       (
+               mkdir newdir &&
+               >newdir/a
+               test_must_fail git init newdir/a/b &&
+               test -f newdir/a
+       )
+'
+
+test_expect_success POSIXPERM,SANITY 'init notices EPERM' '
+       rm -fr newdir &&
+       (
+               mkdir newdir &&
+               chmod -w newdir &&
+               test_must_fail git init newdir/a/b
+       )
+'
+
+test_expect_success 'init creates a new bare directory with global --bare' '
+       rm -rf newdir &&
+       git --bare init newdir &&
+       test -d newdir/refs
+'
+
+test_expect_success 'init prefers command line to GIT_DIR' '
+       rm -rf newdir &&
+       mkdir otherdir &&
+       GIT_DIR=otherdir git --bare init newdir &&
+       test -d newdir/refs &&
+       ! test -d otherdir/refs
+'
+
 test_done
index 1c77192eb318d007689089eaf42f4f939c2f9ee4..de38c7f7aab16a0c2766ef020a3fc03fadece555 100755 (executable)
@@ -20,8 +20,12 @@ test_expect_success 'setup' '
 
        mkdir -p a/b/d a/c &&
        (
+               echo "[attr]notest !test"
                echo "f test=f"
                echo "a/i test=a/i"
+               echo "onoff test -test"
+               echo "offon -test test"
+               echo "no notest"
        ) >.gitattributes &&
        (
                echo "g test=a/g" &&
@@ -30,6 +34,7 @@ test_expect_success 'setup' '
        (
                echo "h test=a/b/h" &&
                echo "d/* test=a/b/d/*"
+               echo "d/yes notest"
        ) >a/b/.gitattributes
 
 '
@@ -43,7 +48,12 @@ test_expect_success 'attribute test' '
        attr_check a/b/g a/b/g &&
        attr_check b/g unspecified &&
        attr_check a/b/h a/b/h &&
-       attr_check a/b/d/g "a/b/d/*"
+       attr_check a/b/d/g "a/b/d/*" &&
+       attr_check onoff unset &&
+       attr_check offon set &&
+       attr_check no unspecified &&
+       attr_check a/b/d/no "a/b/d/*" &&
+       attr_check a/b/d/yes unspecified
 
 '
 
@@ -58,6 +68,11 @@ a/b/g: test: a/b/g
 b/g: test: unspecified
 a/b/h: test: a/b/h
 a/b/d/g: test: a/b/d/*
+onoff: test: unset
+offon: test: set
+no: test: unspecified
+a/b/d/no: test: a/b/d/*
+a/b/d/yes: test: unspecified
 EOF
 
        sed -e "s/:.*//" < expect | git check-attr --stdin test > actual &&
index 2342ac5788a9976b591cb78593279f092d1dc2f6..385b1265de53829053bae40f111a0bf30004b98b 100755 (executable)
@@ -15,7 +15,7 @@ test_expect_success setup '
 
 '
 
-test_expect_success POSIXPERM 'write-tree should notice unwritable repository' '
+test_expect_success POSIXPERM,SANITY 'write-tree should notice unwritable repository' '
 
        (
                chmod a-w .git/objects .git/objects/?? &&
@@ -27,7 +27,7 @@ test_expect_success POSIXPERM 'write-tree should notice unwritable repository' '
 
 '
 
-test_expect_success POSIXPERM 'commit should notice unwritable repository' '
+test_expect_success POSIXPERM,SANITY 'commit should notice unwritable repository' '
 
        (
                chmod a-w .git/objects .git/objects/?? &&
@@ -39,7 +39,7 @@ test_expect_success POSIXPERM 'commit should notice unwritable repository' '
 
 '
 
-test_expect_success POSIXPERM 'update-index should notice unwritable repository' '
+test_expect_success POSIXPERM,SANITY 'update-index should notice unwritable repository' '
 
        (
                echo 6O >file &&
@@ -52,7 +52,7 @@ test_expect_success POSIXPERM 'update-index should notice unwritable repository'
 
 '
 
-test_expect_success POSIXPERM 'add should notice unwritable repository' '
+test_expect_success POSIXPERM,SANITY 'add should notice unwritable repository' '
 
        (
                echo b >file &&
index 09f855af3e9cded903c828f3f946a0c2403ddcdf..93e58c00e886db22b5ebf86af103efc4e65ec832 100755 (executable)
@@ -13,6 +13,7 @@ test_expect_success 'sigchain works' '
        test-sigchain >actual
        case "$?" in
        143) true ;; # POSIX w/ SIGTERM=15
+       271) true ;; # ksh w/ SIGTERM=15
          3) true ;; # Windows
          *) false ;;
        esac &&
diff --git a/t/t0006-date.sh b/t/t0006-date.sh
new file mode 100755 (executable)
index 0000000..1d4d0a5
--- /dev/null
@@ -0,0 +1,78 @@
+#!/bin/sh
+
+test_description='test date parsing and printing'
+. ./test-lib.sh
+
+# arbitrary reference time: 2009-08-30 19:20:00
+TEST_DATE_NOW=1251660000; export TEST_DATE_NOW
+
+check_show() {
+       t=$(($TEST_DATE_NOW - $1))
+       echo "$t -> $2" >expect
+       test_expect_${3:-success} "relative date ($2)" "
+       test-date show $t >actual &&
+       test_cmp expect actual
+       "
+}
+
+check_show 5 '5 seconds ago'
+check_show 300 '5 minutes ago'
+check_show 18000 '5 hours ago'
+check_show 432000 '5 days ago'
+check_show 1728000 '3 weeks ago'
+check_show 13000000 '5 months ago'
+check_show 37500000 '1 year, 2 months ago'
+check_show 55188000 '1 year, 9 months ago'
+check_show 630000000 '20 years ago'
+check_show 31449600 '12 months ago'
+
+check_parse() {
+       echo "$1 -> $2" >expect
+       test_expect_${4:-success} "parse date ($1${3:+ TZ=$3})" "
+       TZ=${3:-$TZ} test-date parse '$1' >actual &&
+       test_cmp expect actual
+       "
+}
+
+check_parse 2008 bad
+check_parse 2008-02 bad
+check_parse 2008-02-14 bad
+check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 +0000'
+check_parse '2008-02-14 20:30:45 -0500' '2008-02-14 20:30:45 -0500'
+check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 -0500' EST5
+
+check_approxidate() {
+       echo "$1 -> $2 +0000" >expect
+       test_expect_${3:-success} "parse approxidate ($1)" "
+       test-date approxidate '$1' >actual &&
+       test_cmp expect actual
+       "
+}
+
+check_approxidate now '2009-08-30 19:20:00'
+check_approxidate '5 seconds ago' '2009-08-30 19:19:55'
+check_approxidate 5.seconds.ago '2009-08-30 19:19:55'
+check_approxidate 10.minutes.ago '2009-08-30 19:10:00'
+check_approxidate yesterday '2009-08-29 19:20:00'
+check_approxidate 3.days.ago '2009-08-27 19:20:00'
+check_approxidate 3.weeks.ago '2009-08-09 19:20:00'
+check_approxidate 3.months.ago '2009-05-30 19:20:00'
+check_approxidate 2.years.3.months.ago '2007-05-30 19:20:00'
+
+check_approxidate '6am yesterday' '2009-08-29 06:00:00'
+check_approxidate '6pm yesterday' '2009-08-29 18:00:00'
+check_approxidate '3:00' '2009-08-30 03:00:00'
+check_approxidate '15:00' '2009-08-30 15:00:00'
+check_approxidate 'noon today' '2009-08-30 12:00:00'
+check_approxidate 'noon yesterday' '2009-08-29 12:00:00'
+
+check_approxidate 'last tuesday' '2009-08-25 19:20:00'
+check_approxidate 'July 5th' '2009-07-05 19:20:00'
+check_approxidate '06/05/2009' '2009-06-05 19:20:00'
+check_approxidate '06.05.2009' '2009-05-06 19:20:00'
+
+check_approxidate 'Jun 6, 5AM' '2009-06-06 05:00:00'
+check_approxidate '5AM Jun 6' '2009-06-06 05:00:00'
+check_approxidate '6AM, June 7, 2009' '2009-06-07 06:00:00'
+
+test_done
index 4e72b53140bd35db87a6c873eda9e75e896e1cdd..234a94f3e6311c529b5bb476ee5a6a2ea705017f 100755 (executable)
@@ -4,21 +4,8 @@ test_description='CRLF conversion'
 
 . ./test-lib.sh
 
-q_to_nul () {
-       perl -pe 'y/Q/\000/'
-}
-
-q_to_cr () {
-       tr Q '\015'
-}
-
-append_cr () {
-       sed -e 's/$/Q/' | tr Q '\015'
-}
-
-remove_cr () {
-       tr '\015' Q <"$1" | grep Q >/dev/null &&
-       tr '\015' Q <"$1" | sed -ne 's/Q$//p'
+has_cr() {
+       tr '\015' Q <"$1" | grep Q >/dev/null
 }
 
 test_expect_success setup '
@@ -156,7 +143,7 @@ test_expect_success 'checkout with autocrlf=true' '
 
        for f in one dir/two
        do
-               remove_cr "$f" >tmp && mv -f tmp $f &&
+               remove_cr <"$f" >tmp && mv -f tmp $f &&
                git update-index -- $f || {
                        echo "Eh? $f"
                        false
@@ -180,7 +167,7 @@ test_expect_success 'checkout with autocrlf=input' '
 
        for f in one dir/two
        do
-               if remove_cr "$f" >/dev/null
+               if has_cr "$f"
                then
                        echo "Eh? $f"
                        false
@@ -245,7 +232,7 @@ test_expect_success 'apply patch (autocrlf=true)' '
        git read-tree --reset -u HEAD &&
 
        git apply patch.file &&
-       test "$patched" = "`remove_cr one | git hash-object --stdin`" || {
+       test "$patched" = "`remove_cr <one | git hash-object --stdin`" || {
                echo "Eh?  apply without index"
                false
        }
@@ -272,7 +259,7 @@ test_expect_success 'apply patch --index (autocrlf=true)' '
 
        git apply --index patch.file &&
        test "$patched" = `git rev-parse :one` &&
-       test "$patched" = "`remove_cr one | git hash-object --stdin`" || {
+       test "$patched" = "`remove_cr <one | git hash-object --stdin`" || {
                echo "Eh?  apply with --index"
                false
        }
@@ -285,7 +272,7 @@ test_expect_success '.gitattributes says two is binary' '
        git config core.autocrlf true &&
        git read-tree --reset -u HEAD &&
 
-       if remove_cr dir/two >/dev/null
+       if has_cr dir/two
        then
                echo "Huh?"
                false
@@ -293,7 +280,7 @@ test_expect_success '.gitattributes says two is binary' '
                : happy
        fi &&
 
-       if remove_cr one >/dev/null
+       if has_cr one
        then
                : happy
        else
@@ -301,7 +288,7 @@ test_expect_success '.gitattributes says two is binary' '
                false
        fi &&
 
-       if remove_cr three >/dev/null
+       if has_cr three
        then
                echo "Huh?"
                false
@@ -316,7 +303,7 @@ test_expect_success '.gitattributes says two is input' '
        echo "two crlf=input" >.gitattributes &&
        git read-tree --reset -u HEAD &&
 
-       if remove_cr dir/two >/dev/null
+       if has_cr dir/two
        then
                echo "Huh?"
                false
@@ -331,7 +318,7 @@ test_expect_success '.gitattributes says two and three are text' '
        echo "t* crlf" >.gitattributes &&
        git read-tree --reset -u HEAD &&
 
-       if remove_cr dir/two >/dev/null
+       if has_cr dir/two
        then
                : happy
        else
@@ -339,7 +326,7 @@ test_expect_success '.gitattributes says two and three are text' '
                false
        fi &&
 
-       if remove_cr three >/dev/null
+       if has_cr three
        then
                : happy
        else
@@ -357,14 +344,14 @@ test_expect_success 'in-tree .gitattributes (1)' '
        rm -rf tmp one dir .gitattributes patch.file three &&
        git read-tree --reset -u HEAD &&
 
-       if remove_cr one >/dev/null
+       if has_cr one
        then
                echo "Eh? one should not have CRLF"
                false
        else
                : happy
        fi &&
-       remove_cr three >/dev/null || {
+       has_cr three || {
                echo "Eh? three should still have CRLF"
                false
        }
@@ -376,14 +363,14 @@ test_expect_success 'in-tree .gitattributes (2)' '
        git read-tree --reset HEAD &&
        git checkout-index -f -q -u -a &&
 
-       if remove_cr one >/dev/null
+       if has_cr one
        then
                echo "Eh? one should not have CRLF"
                false
        else
                : happy
        fi &&
-       remove_cr three >/dev/null || {
+       has_cr three || {
                echo "Eh? three should still have CRLF"
                false
        }
@@ -396,14 +383,14 @@ test_expect_success 'in-tree .gitattributes (3)' '
        git checkout-index -u .gitattributes &&
        git checkout-index -u one dir/two three &&
 
-       if remove_cr one >/dev/null
+       if has_cr one
        then
                echo "Eh? one should not have CRLF"
                false
        else
                : happy
        fi &&
-       remove_cr three >/dev/null || {
+       has_cr three || {
                echo "Eh? three should still have CRLF"
                false
        }
@@ -416,14 +403,14 @@ test_expect_success 'in-tree .gitattributes (4)' '
        git checkout-index -u one dir/two three &&
        git checkout-index -u .gitattributes &&
 
-       if remove_cr one >/dev/null
+       if has_cr one
        then
                echo "Eh? one should not have CRLF"
                false
        else
                : happy
        fi &&
-       remove_cr three >/dev/null || {
+       has_cr three || {
                echo "Eh? three should still have CRLF"
                false
        }
@@ -456,7 +443,7 @@ test_expect_success 'checkout when deleting .gitattributes' '
 
        git checkout master~1 &&
        git checkout master &&
-       remove_cr .file2 >/dev/null
+       has_cr .file2
 
 '
 
@@ -466,5 +453,57 @@ test_expect_success 'invalid .gitattributes (must not crash)' '
        git diff
 
 '
+# Some more tests here to add new autocrlf functionality.
+# We want to have a known state here, so start a bit from scratch
+
+test_expect_success 'setting up for new autocrlf tests' '
+       git config core.autocrlf false &&
+       git config core.safecrlf false &&
+       rm -rf .????* * &&
+       for w in I am all LF; do echo $w; done >alllf &&
+       for w in Oh here is CRLFQ in text; do echo $w; done | q_to_cr >mixed &&
+       for w in I am all CRLF; do echo $w; done | append_cr >allcrlf &&
+       git add -A . &&
+       git commit -m "alllf, allcrlf and mixed only" &&
+       git tag -a -m "message" autocrlf-checkpoint
+'
+
+test_expect_success 'report no change after setting autocrlf' '
+       git config core.autocrlf true &&
+       touch * &&
+       git diff --exit-code
+'
+
+test_expect_success 'files are clean after checkout' '
+       rm * &&
+       git checkout -f &&
+       git diff --exit-code
+'
+
+cr_to_Q_no_NL () {
+    tr '\015' Q | tr -d '\012'
+}
+
+test_expect_success 'LF only file gets CRLF with autocrlf' '
+       test "$(cr_to_Q_no_NL < alllf)" = "IQamQallQLFQ"
+'
+
+test_expect_success 'Mixed file is still mixed with autocrlf' '
+       test "$(cr_to_Q_no_NL < mixed)" = "OhhereisCRLFQintext"
+'
+
+test_expect_success 'CRLF only file has CRLF with autocrlf' '
+       test "$(cr_to_Q_no_NL < allcrlf)" = "IQamQallQCRLFQ"
+'
+
+test_expect_success 'New CRLF file gets LF in repo' '
+       tr -d "\015" < alllf | append_cr > alllf2 &&
+       git add alllf2 &&
+       git commit -m "alllf2 added" &&
+       git config core.autocrlf false &&
+       rm * &&
+       git checkout -f &&
+       test_cmp alllf alllf2
+'
 
 test_done
index 8fc39d77cec6168dae930beef785597dace24aa3..828e35baf72d94908ad1f30dbd2e1aa6f9376e69 100755 (executable)
@@ -4,7 +4,8 @@ test_description='blob conversion via gitattributes'
 
 . ./test-lib.sh
 
-cat <<\EOF >rot13.sh
+cat <<EOF >rot13.sh
+#!$SHELL_PATH
 tr \
   'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' \
   'nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM'
@@ -64,17 +65,21 @@ test_expect_success expanded_in_repo '
                echo "\$Id:NoSpaceAtFront \$"
                echo "\$Id:NoSpaceAtEitherEnd\$"
                echo "\$Id: NoTerminatingSymbol"
+               echo "\$Id: Foreign Commit With Spaces \$"
+               echo "\$Id: NoTerminatingSymbolAtEOF"
        } > expanded-keywords &&
 
        {
                echo "File with expanded keywords"
-               echo "\$Id: 4f21723e7b15065df7de95bd46c8ba6fb1818f4c \$"
-               echo "\$Id: 4f21723e7b15065df7de95bd46c8ba6fb1818f4c \$"
-               echo "\$Id: 4f21723e7b15065df7de95bd46c8ba6fb1818f4c \$"
-               echo "\$Id: 4f21723e7b15065df7de95bd46c8ba6fb1818f4c \$"
-               echo "\$Id: 4f21723e7b15065df7de95bd46c8ba6fb1818f4c \$"
-               echo "\$Id: 4f21723e7b15065df7de95bd46c8ba6fb1818f4c \$"
+               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
+               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
+               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
+               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
+               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
+               echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
                echo "\$Id: NoTerminatingSymbol"
+               echo "\$Id: Foreign Commit With Spaces \$"
+               echo "\$Id: NoTerminatingSymbolAtEOF"
        } > expected-output &&
 
        git add expanded-keywords &&
index f1e1d48869a25c6ab1d0b570eb4c0e28afc5fa05..7af3fbcc7b9e5e61e41205accaa6d81e85cae6cf 100755 (executable)
@@ -12,7 +12,7 @@ test_expect_success setup '
        test_tick &&
        git commit -m Initial &&
 
-       sed -e "s/\$/\r/" "$TEST_DIRECTORY"/t0022-crlf-rename.sh >elpmas &&
+       append_cr <"$TEST_DIRECTORY"/t0022-crlf-rename.sh >elpmas &&
        git add elpmas &&
        rm -f sample &&
 
diff --git a/t/t0025-crlf-auto.sh b/t/t0025-crlf-auto.sh
new file mode 100755 (executable)
index 0000000..f5f67a6
--- /dev/null
@@ -0,0 +1,155 @@
+#!/bin/sh
+
+test_description='CRLF conversion'
+
+. ./test-lib.sh
+
+has_cr() {
+       tr '\015' Q <"$1" | grep Q >/dev/null
+}
+
+test_expect_success setup '
+
+       git config core.autocrlf false &&
+
+       for w in Hello world how are you; do echo $w; done >one &&
+       for w in I am very very fine thank you; do echo ${w}Q; done | q_to_cr >two &&
+       for w in Oh here is a QNUL byte how alarming; do echo ${w}; done | q_to_nul >three &&
+       git add . &&
+
+       git commit -m initial &&
+
+       one=`git rev-parse HEAD:one` &&
+       two=`git rev-parse HEAD:two` &&
+       three=`git rev-parse HEAD:three` &&
+
+       echo happy.
+'
+
+test_expect_success 'default settings cause no changes' '
+
+       rm -f .gitattributes tmp one two three &&
+       git read-tree --reset -u HEAD &&
+
+       ! has_cr one &&
+       has_cr two &&
+       onediff=`git diff one` &&
+       twodiff=`git diff two` &&
+       threediff=`git diff three` &&
+       test -z "$onediff" -a -z "$twodiff" -a -z "$threediff"
+'
+
+test_expect_success 'crlf=true causes a CRLF file to be normalized' '
+
+       # Backwards compatibility check
+       rm -f .gitattributes tmp one two three &&
+       echo "two crlf" > .gitattributes &&
+       git read-tree --reset -u HEAD &&
+
+       # Note, "normalized" means that git will normalize it if added
+       has_cr two &&
+       twodiff=`git diff two` &&
+       test -n "$twodiff"
+'
+
+test_expect_success 'text=true causes a CRLF file to be normalized' '
+
+       rm -f .gitattributes tmp one two three &&
+       echo "two text" > .gitattributes &&
+       git read-tree --reset -u HEAD &&
+
+       # Note, "normalized" means that git will normalize it if added
+       has_cr two &&
+       twodiff=`git diff two` &&
+       test -n "$twodiff"
+'
+
+test_expect_success 'eol=crlf gives a normalized file CRLFs with autocrlf=false' '
+
+       rm -f .gitattributes tmp one two three &&
+       git config core.autocrlf false &&
+       echo "one eol=crlf" > .gitattributes &&
+       git read-tree --reset -u HEAD &&
+
+       has_cr one &&
+       onediff=`git diff one` &&
+       test -z "$onediff"
+'
+
+test_expect_success 'eol=crlf gives a normalized file CRLFs with autocrlf=input' '
+
+       rm -f .gitattributes tmp one two three &&
+       git config core.autocrlf input &&
+       echo "one eol=crlf" > .gitattributes &&
+       git read-tree --reset -u HEAD &&
+
+       has_cr one &&
+       onediff=`git diff one` &&
+       test -z "$onediff"
+'
+
+test_expect_success 'eol=lf gives a normalized file LFs with autocrlf=true' '
+
+       rm -f .gitattributes tmp one two three &&
+       git config core.autocrlf true &&
+       echo "one eol=lf" > .gitattributes &&
+       git read-tree --reset -u HEAD &&
+
+       ! has_cr one &&
+       onediff=`git diff one` &&
+       test -z "$onediff"
+'
+
+test_expect_success 'autocrlf=true does not normalize CRLF files' '
+
+       rm -f .gitattributes tmp one two three &&
+       git config core.autocrlf true &&
+       git read-tree --reset -u HEAD &&
+
+       has_cr one &&
+       has_cr two &&
+       onediff=`git diff one` &&
+       twodiff=`git diff two` &&
+       threediff=`git diff three` &&
+       test -z "$onediff" -a -z "$twodiff" -a -z "$threediff"
+'
+
+test_expect_success 'text=auto, autocrlf=true _does_ normalize CRLF files' '
+
+       rm -f .gitattributes tmp one two three &&
+       git config core.autocrlf true &&
+       echo "* text=auto" > .gitattributes &&
+       git read-tree --reset -u HEAD &&
+
+       has_cr one &&
+       has_cr two &&
+       onediff=`git diff one` &&
+       twodiff=`git diff two` &&
+       threediff=`git diff three` &&
+       test -z "$onediff" -a -n "$twodiff" -a -z "$threediff"
+'
+
+test_expect_success 'text=auto, autocrlf=true does not normalize binary files' '
+
+       rm -f .gitattributes tmp one two three &&
+       git config core.autocrlf true &&
+       echo "* text=auto" > .gitattributes &&
+       git read-tree --reset -u HEAD &&
+
+       ! has_cr three &&
+       threediff=`git diff three` &&
+       test -z "$threediff"
+'
+
+test_expect_success 'eol=crlf _does_ normalize binary files' '
+
+       rm -f .gitattributes tmp one two three &&
+       echo "three eol=crlf" > .gitattributes &&
+       git read-tree --reset -u HEAD &&
+
+       has_cr three &&
+       threediff=`git diff three` &&
+       test -z "$threediff"
+'
+
+test_done
diff --git a/t/t0026-eol-config.sh b/t/t0026-eol-config.sh
new file mode 100755 (executable)
index 0000000..f37ac8f
--- /dev/null
@@ -0,0 +1,83 @@
+#!/bin/sh
+
+test_description='CRLF conversion'
+
+. ./test-lib.sh
+
+has_cr() {
+       tr '\015' Q <"$1" | grep Q >/dev/null
+}
+
+test_expect_success setup '
+
+       git config core.autocrlf false &&
+
+       echo "one text" > .gitattributes
+
+       for w in Hello world how are you; do echo $w; done >one &&
+       for w in I am very very fine thank you; do echo $w; done >two &&
+       git add . &&
+
+       git commit -m initial &&
+
+       one=`git rev-parse HEAD:one` &&
+       two=`git rev-parse HEAD:two` &&
+
+       echo happy.
+'
+
+test_expect_success 'eol=lf puts LFs in normalized file' '
+
+       rm -f .gitattributes tmp one two &&
+       git config core.eol lf &&
+       git read-tree --reset -u HEAD &&
+
+       ! has_cr one &&
+       ! has_cr two &&
+       onediff=`git diff one` &&
+       twodiff=`git diff two` &&
+       test -z "$onediff" -a -z "$twodiff"
+'
+
+test_expect_success 'eol=crlf puts CRLFs in normalized file' '
+
+       rm -f .gitattributes tmp one two &&
+       git config core.eol crlf &&
+       git read-tree --reset -u HEAD &&
+
+       has_cr one &&
+       ! has_cr two &&
+       onediff=`git diff one` &&
+       twodiff=`git diff two` &&
+       test -z "$onediff" -a -z "$twodiff"
+'
+
+test_expect_success 'autocrlf=true overrides eol=lf' '
+
+       rm -f .gitattributes tmp one two &&
+       git config core.eol lf &&
+       git config core.autocrlf true &&
+       git read-tree --reset -u HEAD &&
+
+       has_cr one &&
+       has_cr two &&
+       onediff=`git diff one` &&
+       twodiff=`git diff two` &&
+       test -z "$onediff" -a -z "$twodiff"
+'
+
+test_expect_success 'autocrlf=true overrides unset eol' '
+
+       rm -f .gitattributes tmp one two &&
+       git config --unset-all core.eol &&
+       git config core.autocrlf true &&
+       git read-tree --reset -u HEAD &&
+
+       has_cr one &&
+       has_cr two &&
+       onediff=`git diff one` &&
+       twodiff=`git diff two` &&
+       test -z "$onediff" -a -z "$twodiff"
+'
+
+test_done
index e38241c80a6625c9b5b89340b9d0c5a2667bf345..20924506af886c8d0ce28f3fcb2742214d80020f 100755 (executable)
@@ -7,17 +7,19 @@ test_description='our own option parser'
 
 . ./test-lib.sh
 
-cat > expect.err << EOF
+cat > expect << EOF
 usage: test-parse-options <options>
 
     -b, --boolean         get a boolean
     -4, --or4             bitwise-or boolean with ...0100
+    --neg-or4             same as --no-or4
 
     -i, --integer <n>     get a integer
     -j <n>                get a integer, too
     --set23               set integer to 23
     -t <time>             get timestamp of <time>
     -L, --length <str>    get length of <str>
+    -F, --file <FILE>     set file to <FILE>
 
 String options
     -s, --string <string>
@@ -29,6 +31,10 @@ String options
 
 Magic arguments
     --quux                means --quux
+    -NUM                  set integer to NUM
+    +                     same as -b
+    --ambiguous           positive ambiguity
+    --no-ambiguous        negative ambiguity
 
 Standard options
     --abbrev[=<n>]        use <n> digits to display SHA-1s
@@ -40,10 +46,12 @@ EOF
 
 test_expect_success 'test help' '
        test_must_fail test-parse-options -h > output 2> output.err &&
-       test ! -s output &&
-       test_cmp expect.err output.err
+       test ! -s output.err &&
+       test_cmp expect output
 '
 
+mv expect expect.err
+
 cat > expect << EOF
 boolean: 2
 integer: 1729
@@ -53,10 +61,12 @@ abbrev: 7
 verbose: 2
 quiet: no
 dry run: yes
+file: prefix/my.file
 EOF
 
 test_expect_success 'short options' '
-       test-parse-options -s123 -b -i 1729 -b -vv -n > output 2> output.err &&
+       test-parse-options -s123 -b -i 1729 -b -vv -n -F my.file \
+       > output 2> output.err &&
        test_cmp expect output &&
        test ! -s output.err
 '
@@ -70,11 +80,12 @@ abbrev: 10
 verbose: 2
 quiet: no
 dry run: no
+file: prefix/fi.le
 EOF
 
 test_expect_success 'long options' '
        test-parse-options --boolean --integer 1729 --boolean --string2=321 \
-               --verbose --verbose --no-dry-run --abbrev=10 \
+               --verbose --verbose --no-dry-run --abbrev=10 --file fi.le\
                > output 2> output.err &&
        test ! -s output.err &&
        test_cmp expect output
@@ -84,6 +95,8 @@ test_expect_success 'missing required value' '
        test-parse-options -s;
        test $? = 129 &&
        test-parse-options --string;
+       test $? = 129 &&
+       test-parse-options --file;
        test $? = 129
 '
 
@@ -96,6 +109,7 @@ abbrev: 7
 verbose: 0
 quiet: no
 dry run: no
+file: (not set)
 arg 00: a1
 arg 01: b1
 arg 02: --boolean
@@ -117,6 +131,7 @@ abbrev: 7
 verbose: 0
 quiet: no
 dry run: no
+file: (not set)
 EOF
 
 test_expect_success 'unambiguously abbreviated option' '
@@ -145,6 +160,7 @@ abbrev: 7
 verbose: 0
 quiet: no
 dry run: no
+file: (not set)
 EOF
 
 test_expect_success 'non ambiguous option (after two options it abbreviates)' '
@@ -172,6 +188,7 @@ abbrev: 7
 verbose: 0
 quiet: no
 dry run: no
+file: (not set)
 arg 00: --quux
 EOF
 
@@ -190,6 +207,7 @@ abbrev: 7
 verbose: 0
 quiet: yes
 dry run: no
+file: (not set)
 arg 00: foo
 EOF
 
@@ -210,6 +228,7 @@ abbrev: 7
 verbose: 0
 quiet: no
 dry run: no
+file: (not set)
 EOF
 
 test_expect_success 'OPT_CALLBACK() and OPT_BIT() work' '
@@ -237,6 +256,7 @@ abbrev: 7
 verbose: 0
 quiet: no
 dry run: no
+file: (not set)
 EOF
 
 test_expect_success 'OPT_BIT() and OPT_SET_INT() work' '
@@ -245,7 +265,76 @@ test_expect_success 'OPT_BIT() and OPT_SET_INT() work' '
        test_cmp expect output
 '
 
-# --or4
-# --no-or4
+test_expect_success 'OPT_NEGBIT() and OPT_SET_INT() work' '
+       test-parse-options --set23 -bbbbb --neg-or4 > output 2> output.err &&
+       test ! -s output.err &&
+       test_cmp expect output
+'
+
+cat > expect <<EOF
+boolean: 6
+integer: 0
+timestamp: 0
+string: (not set)
+abbrev: 7
+verbose: 0
+quiet: no
+dry run: no
+file: (not set)
+EOF
+
+test_expect_success 'OPT_BIT() works' '
+       test-parse-options -bb --or4 > output 2> output.err &&
+       test ! -s output.err &&
+       test_cmp expect output
+'
+
+test_expect_success 'OPT_NEGBIT() works' '
+       test-parse-options -bb --no-neg-or4 > output 2> output.err &&
+       test ! -s output.err &&
+       test_cmp expect output
+'
+
+test_expect_success 'OPT_BOOLEAN() with PARSE_OPT_NODASH works' '
+       test-parse-options + + + + + + > output 2> output.err &&
+       test ! -s output.err &&
+       test_cmp expect output
+'
+
+cat > expect <<EOF
+boolean: 0
+integer: 12345
+timestamp: 0
+string: (not set)
+abbrev: 7
+verbose: 0
+quiet: no
+dry run: no
+file: (not set)
+EOF
+
+test_expect_success 'OPT_NUMBER_CALLBACK() works' '
+       test-parse-options -12345 > output 2> output.err &&
+       test ! -s output.err &&
+       test_cmp expect output
+'
+
+cat >expect <<EOF
+boolean: 0
+integer: 0
+timestamp: 0
+string: (not set)
+abbrev: 7
+verbose: 0
+quiet: no
+dry run: no
+file: (not set)
+EOF
+
+test_expect_success 'negation of OPT_NONEG flags is not ambiguous' '
+       test-parse-options --no-ambig >output 2>output.err &&
+       test ! -s output.err &&
+       test_cmp expect output
+'
 
 test_done
index 89282ccf7a1a73d4b5ee085c4236b1204dc502c8..41df6bcf279a1abc4462e63866076094cfbdedd8 100755 (executable)
@@ -108,13 +108,17 @@ $test_case 'merge (case change)' '
 
 '
 
-$test_case 'add (with different case)' '
+
+
+test_expect_failure 'add (with different case)' '
 
        git reset --hard initial &&
        rm camelcase &&
        echo 1 >CamelCase &&
        git add CamelCase &&
-       test $(git ls-files | grep -i camelcase | wc -l) = 1
+       camel=$(git ls-files | grep -i camelcase) &&
+       test $(echo "$camel" | wc -l) = 1 &&
+       test "z$(git cat-file blob :$camel)" = z1
 
 '
 
diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh
new file mode 100755 (executable)
index 0000000..10b26e4
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Ilari Liusvaara
+#
+
+test_description='Test run command'
+
+. ./test-lib.sh
+
+test_expect_success 'start_command reports ENOENT' '
+       test-run-command start-command-ENOENT ./does-not-exist
+'
+
+test_done
diff --git a/t/t0080-vcs-svn.sh b/t/t0080-vcs-svn.sh
new file mode 100755 (executable)
index 0000000..d3225ad
--- /dev/null
@@ -0,0 +1,171 @@
+#!/bin/sh
+
+test_description='check infrastructure for svn importer'
+
+. ./test-lib.sh
+uint32_max=4294967295
+
+test_expect_success 'obj pool: store data' '
+       cat <<-\EOF >expected &&
+       0
+       1
+       EOF
+
+       test-obj-pool <<-\EOF >actual &&
+       alloc one 16
+       set one 13
+       test one 13
+       reset one
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'obj pool: NULL is offset ~0' '
+       echo "$uint32_max" >expected &&
+       echo null one | test-obj-pool >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'obj pool: out-of-bounds access' '
+       cat <<-EOF >expected &&
+       0
+       0
+       $uint32_max
+       $uint32_max
+       16
+       20
+       $uint32_max
+       EOF
+
+       test-obj-pool <<-\EOF >actual &&
+       alloc one 16
+       alloc two 16
+       offset one 20
+       offset two 20
+       alloc one 5
+       offset one 20
+       free one 1
+       offset one 20
+       reset one
+       reset two
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'obj pool: high-water mark' '
+       cat <<-\EOF >expected &&
+       0
+       0
+       10
+       20
+       20
+       20
+       EOF
+
+       test-obj-pool <<-\EOF >actual &&
+       alloc one 10
+       committed one
+       alloc one 10
+       commit one
+       committed one
+       alloc one 10
+       free one 20
+       committed one
+       reset one
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'line buffer' '
+       echo HELLO >expected1 &&
+       printf "%s\n" "" HELLO >expected2 &&
+       echo >expected3 &&
+       printf "%s\n" "" Q | q_to_nul >expected4 &&
+       printf "%s\n" foo "" >expected5 &&
+       printf "%s\n" "" foo >expected6 &&
+
+       test-line-buffer <<-\EOF >actual1 &&
+       5
+       HELLO
+       EOF
+
+       test-line-buffer <<-\EOF >actual2 &&
+       0
+
+       5
+       HELLO
+       EOF
+
+       q_to_nul <<-\EOF |
+       1
+       Q
+       EOF
+       test-line-buffer >actual3 &&
+
+       q_to_nul <<-\EOF |
+       0
+
+       1
+       Q
+       EOF
+       test-line-buffer >actual4 &&
+
+       test-line-buffer <<-\EOF >actual5 &&
+       5
+       foo
+       EOF
+
+       test-line-buffer <<-\EOF >actual6 &&
+       0
+
+       5
+       foo
+       EOF
+
+       test_cmp expected1 actual1 &&
+       test_cmp expected2 actual2 &&
+       test_cmp expected3 actual3 &&
+       test_cmp expected4 actual4 &&
+       test_cmp expected5 actual5 &&
+       test_cmp expected6 actual6
+'
+
+test_expect_success 'string pool' '
+       echo a does not equal b >expected.differ &&
+       echo a equals a >expected.match &&
+       echo equals equals equals >expected.matchmore &&
+
+       test-string-pool "a,--b" >actual.differ &&
+       test-string-pool "a,a" >actual.match &&
+       test-string-pool "equals-equals" >actual.matchmore &&
+       test_must_fail test-string-pool a,a,a &&
+       test_must_fail test-string-pool a &&
+
+       test_cmp expected.differ actual.differ &&
+       test_cmp expected.match actual.match &&
+       test_cmp expected.matchmore actual.matchmore
+'
+
+test_expect_success 'treap sort' '
+       cat <<-\EOF >unsorted &&
+       68
+       12
+       13
+       13
+       68
+       13
+       13
+       21
+       10
+       11
+       12
+       13
+       13
+       EOF
+       sort unsorted >expected &&
+
+       test-treap <unsorted >actual &&
+       test_cmp expected actual
+'
+
+test_done
diff --git a/t/t0101-at-syntax.sh b/t/t0101-at-syntax.sh
new file mode 100755 (executable)
index 0000000..a1998b5
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+test_description='various @{whatever} syntax tests'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       test_commit one &&
+       test_commit two
+'
+
+check_at() {
+       echo "$2" >expect &&
+       git log -1 --format=%s "$1" >actual &&
+       test_cmp expect actual
+}
+
+test_expect_success '@{0} shows current' '
+       check_at @{0} two
+'
+
+test_expect_success '@{1} shows old' '
+       check_at @{1} one
+'
+
+test_expect_success '@{now} shows current' '
+       check_at @{now} two
+'
+
+test_expect_success '@{2001-09-17} (before the first commit) shows old' '
+       check_at @{2001-09-17} one
+'
+
+test_expect_success 'silly approxidates work' '
+       check_at @{3.hot.dogs.on.2001-09-17} one
+'
+
+test_expect_success 'notice misspelled upstream' '
+       test_must_fail git log -1 --format=%s @{usptream}
+'
+
+test_expect_success 'complain about total nonsense' '
+       test_must_fail git log -1 --format=%s @{utter.bogosity}
+'
+
+test_done
index 22ba7a5442c587f4536aad5668df43661231de56..4f171722d9e1dda7668f54b4104267eb70d91c67 100755 (executable)
@@ -126,9 +126,6 @@ cat >expected <<\EOF
 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 &&
     test_cmp expected current
index 271bc4e17f0c12cda550ffa4f54f1ad7555b3bed..93ca84f9e6d3dfd859d8bd4941f4d2c5a354ab6c 100755 (executable)
@@ -5,7 +5,7 @@
 
 test_description='Two way merge with read-tree -m $H $M
 
-This test tries two-way merge (aka fast forward with carry forward).
+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
@@ -26,8 +26,6 @@ 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;' \
@@ -51,7 +49,7 @@ check_cache_at () {
 }
 
 cat >bozbar-old <<\EOF
-This is a sample file used in two-way fast forward merge
+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
@@ -300,7 +298,7 @@ test_expect_success \
      echo gnusto gnusto >bozbar &&
      if read_tree_twoway $treeH $treeM; then false; else :; fi'
 
-# This fails with straight two-way fast forward.
+# This fails with straight two-way fast-forward.
 test_expect_success \
     '22 - local change cache updated.' \
     'rm -f .git/index &&
@@ -361,7 +359,7 @@ test_expect_success \
 
 test_expect_success \
     'a/b (untracked) vs a, plus c/d case test.' \
-    '! git read-tree -u -m "$treeH" "$treeM" &&
+    'test_must_fail git read-tree -u -m "$treeH" "$treeM" &&
      git ls-files --stage &&
      test -f a/b'
 
@@ -392,4 +390,20 @@ test_expect_success \
      git ls-files --stage | tee >treeMcheck.out &&
      test_cmp treeM.out treeMcheck.out'
 
+test_expect_success '-m references the correct modified tree' '
+       echo >file-a &&
+       echo >file-b &&
+       git add file-a file-b &&
+       git commit -a -m "test for correct modified tree"
+       git branch initial-mod &&
+       echo b >file-b &&
+       git commit -a -m "B" &&
+       echo a >file-a &&
+       git add file-a &&
+       git ls-tree $(git write-tree) file-a >expect &&
+       git read-tree -m HEAD initial-mod &&
+       git ls-tree $(git write-tree) file-a >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 5e40cec530df07a8b7c088d31b28ac2d39abdc1b..0241329a08af0be642131ffe4b619231d9ea3844 100755 (executable)
@@ -10,8 +10,6 @@ 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 '1{/^diff --git /d;}' \
index f19b4a2a4afa89fd1242d1acccb1e999e6a88c6d..eb8e3d447613ce7383f53a7a09039c01d72f35ef 100755 (executable)
@@ -177,7 +177,7 @@ test_expect_success SYMLINKS 'funny symlink in work tree' '
 
 '
 
-test_expect_success SYMLINKS 'funny symlink in work tree, un-unlink-able' '
+test_expect_success SYMLINKS,SANITY 'funny symlink in work tree, un-unlink-able' '
 
        rm -fr a b &&
        git reset --hard &&
index fd98e445bf2e74284709df54d2cd2d3f0006b19c..dd32432d626e4f3d192c2bbe4824772025bb08b1 100755 (executable)
@@ -65,10 +65,6 @@ test_expect_success "Can't use --path with --stdin-paths" '
        echo example | test_must_fail git hash-object --stdin-paths --path=foo
 '
 
-test_expect_success "Can't use --stdin-paths with --no-filters" '
-       echo example | test_must_fail git hash-object --stdin-paths --no-filters
-'
-
 test_expect_success "Can't use --path with --no-filters" '
        test_must_fail git hash-object --no-filters --path=foo
 '
@@ -141,6 +137,20 @@ test_expect_success 'check that --no-filters option works' '
        git config --unset core.autocrlf
 '
 
+test_expect_success 'check that --no-filters option works with --stdin-paths' '
+       echo fooQ | tr Q "\\015" >file0 &&
+       cp file0 file1 &&
+       echo "file0 -crlf" >.gitattributes &&
+       echo "file1 crlf" >>.gitattributes &&
+       git config core.autocrlf true &&
+       file0_sha=$(git hash-object file0) &&
+       file1_sha=$(git hash-object file1) &&
+       test "$file0_sha" != "$file1_sha" &&
+       nofilters_file1=$(echo "file1" | git hash-object --stdin-paths --no-filters) &&
+       test "$file0_sha" = "$nofilters_file1" &&
+       git config --unset core.autocrlf
+'
+
 pop_repo
 
 for args in "-w --stdin" "--stdin -w"; do
diff --git a/t/t1009-read-tree-new-index.sh b/t/t1009-read-tree-new-index.sh
new file mode 100755 (executable)
index 0000000..59b3aa4
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+test_description='test read-tree into a fresh index file'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       echo one >a &&
+       git add a &&
+       git commit -m initial
+'
+
+test_expect_success 'non-existent index file' '
+       rm -f new-index &&
+       GIT_INDEX_FILE=new-index git read-tree master
+'
+
+test_expect_success 'empty index file' '
+       rm -f new-index &&
+       > new-index &&
+       GIT_INDEX_FILE=new-index git read-tree master
+'
+
+test_done
+
diff --git a/t/t1010-mktree.sh b/t/t1010-mktree.sh
new file mode 100755 (executable)
index 0000000..b946f87
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+test_description='git mktree'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       for d in a a. a0
+       do
+               mkdir "$d" && echo "$d/one" >"$d/one" &&
+               git add "$d"
+       done &&
+       echo zero >one &&
+       git update-index --add --info-only one &&
+       git write-tree --missing-ok >tree.missing &&
+       git ls-tree $(cat tree.missing) >top.missing &&
+       git ls-tree -r $(cat tree.missing) >all.missing &&
+       echo one >one &&
+       git add one &&
+       git write-tree >tree &&
+       git ls-tree $(cat tree) >top &&
+       git ls-tree -r $(cat tree) >all &&
+       test_tick &&
+       git commit -q -m one &&
+       H=$(git rev-parse HEAD) &&
+       git update-index --add --cacheinfo 160000 $H sub &&
+       test_tick &&
+       git commit -q -m two &&
+       git rev-parse HEAD^{tree} >tree.withsub &&
+       git ls-tree HEAD >top.withsub &&
+       git ls-tree -r HEAD >all.withsub
+'
+
+test_expect_success 'ls-tree piped to mktree (1)' '
+       git mktree <top >actual &&
+       test_cmp tree actual
+'
+
+test_expect_success 'ls-tree piped to mktree (2)' '
+       git mktree <top.withsub >actual &&
+       test_cmp tree.withsub actual
+'
+
+test_expect_success 'ls-tree output in wrong order given to mktree (1)' '
+       perl -e "print reverse <>" <top |
+       git mktree >actual &&
+       test_cmp tree actual
+'
+
+test_expect_success 'ls-tree output in wrong order given to mktree (2)' '
+       perl -e "print reverse <>" <top.withsub |
+       git mktree >actual &&
+       test_cmp tree.withsub actual
+'
+
+test_expect_success 'allow missing object with --missing' '
+       git mktree --missing <top.missing >actual &&
+       test_cmp tree.missing actual
+'
+
+test_expect_success 'mktree refuses to read ls-tree -r output (1)' '
+       test_must_fail git mktree <all >actual
+'
+
+test_expect_success 'mktree refuses to read ls-tree -r output (2)' '
+       test_must_fail git mktree <all.withsub >actual
+'
+
+test_done
diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh
new file mode 100755 (executable)
index 0000000..9a07de1
--- /dev/null
@@ -0,0 +1,179 @@
+#!/bin/sh
+
+test_description='sparse checkout tests
+
+* (tag: removed, master) removed
+| D    sub/added
+* (HEAD, tag: top) modified and added
+| M    init.t
+| A    sub/added
+* (tag: init) init
+  A    init.t
+'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       cat >expected <<-\EOF &&
+       100644 77f0ba1734ed79d12881f81b36ee134de6a3327b 0       init.t
+       100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0       sub/added
+       EOF
+       cat >expected.swt <<-\EOF &&
+       H init.t
+       H sub/added
+       EOF
+
+       test_commit init &&
+       echo modified >>init.t &&
+       mkdir sub &&
+       touch sub/added &&
+       git add init.t sub/added &&
+       git commit -m "modified and added" &&
+       git tag top &&
+       git rm sub/added &&
+       git commit -m removed &&
+       git tag removed &&
+       git checkout top &&
+       git ls-files --stage >result &&
+       test_cmp expected result
+'
+
+test_expect_success 'read-tree without .git/info/sparse-checkout' '
+       git read-tree -m -u HEAD &&
+       git ls-files --stage >result &&
+       test_cmp expected result &&
+       git ls-files -t >result &&
+       test_cmp expected.swt result
+'
+
+test_expect_success 'read-tree with .git/info/sparse-checkout but disabled' '
+       echo >.git/info/sparse-checkout
+       git read-tree -m -u HEAD &&
+       git ls-files -t >result &&
+       test_cmp expected.swt result &&
+       test -f init.t &&
+       test -f sub/added
+'
+
+test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse-checkout and enabled' '
+       git config core.sparsecheckout true &&
+       echo >.git/info/sparse-checkout &&
+       git read-tree --no-sparse-checkout -m -u HEAD &&
+       git ls-files -t >result &&
+       test_cmp expected.swt result &&
+       test -f init.t &&
+       test -f sub/added
+'
+
+test_expect_success 'read-tree with empty .git/info/sparse-checkout' '
+       git config core.sparsecheckout true &&
+       echo >.git/info/sparse-checkout &&
+       test_must_fail git read-tree -m -u HEAD &&
+       git ls-files --stage >result &&
+       test_cmp expected result &&
+       git ls-files -t >result &&
+       test_cmp expected.swt result &&
+       test -f init.t &&
+       test -f sub/added
+'
+
+test_expect_success 'match directories with trailing slash' '
+       cat >expected.swt-noinit <<-\EOF &&
+       S init.t
+       H sub/added
+       EOF
+
+       echo sub/ > .git/info/sparse-checkout &&
+       git read-tree -m -u HEAD &&
+       git ls-files -t > result &&
+       test_cmp expected.swt-noinit result &&
+       test ! -f init.t &&
+       test -f sub/added
+'
+
+test_expect_failure 'match directories without trailing slash' '
+       echo init.t >.git/info/sparse-checkout &&
+       echo sub >>.git/info/sparse-checkout &&
+       git read-tree -m -u HEAD &&
+       git ls-files -t >result &&
+       test_cmp expected.swt result &&
+       test ! -f init.t &&
+       test -f sub/added
+'
+
+test_expect_success 'checkout area changes' '
+       cat >expected.swt-nosub <<-\EOF &&
+       H init.t
+       S sub/added
+       EOF
+
+       echo init.t >.git/info/sparse-checkout &&
+       git read-tree -m -u HEAD &&
+       git ls-files -t >result &&
+       test_cmp expected.swt-nosub result &&
+       test -f init.t &&
+       test ! -f sub/added
+'
+
+test_expect_success 'read-tree updates worktree, absent case' '
+       echo sub/added >.git/info/sparse-checkout &&
+       git checkout -f top &&
+       git read-tree -m -u HEAD^ &&
+       test ! -f init.t
+'
+
+test_expect_success 'read-tree updates worktree, dirty case' '
+       echo sub/added >.git/info/sparse-checkout &&
+       git checkout -f top &&
+       echo dirty >init.t &&
+       git read-tree -m -u HEAD^ &&
+       grep -q dirty init.t &&
+       rm init.t
+'
+
+test_expect_success 'read-tree removes worktree, dirty case' '
+       echo init.t >.git/info/sparse-checkout &&
+       git checkout -f top &&
+       echo dirty >added &&
+       git read-tree -m -u HEAD^ &&
+       grep -q dirty added
+'
+
+test_expect_success 'read-tree adds to worktree, absent case' '
+       echo init.t >.git/info/sparse-checkout &&
+       git checkout -f removed &&
+       git read-tree -u -m HEAD^ &&
+       test ! -f sub/added
+'
+
+test_expect_success 'read-tree adds to worktree, dirty case' '
+       echo init.t >.git/info/sparse-checkout &&
+       git checkout -f removed &&
+       mkdir sub &&
+       echo dirty >sub/added &&
+       git read-tree -u -m HEAD^ &&
+       grep -q dirty sub/added
+'
+
+test_expect_success 'index removal and worktree narrowing at the same time' '
+       >empty &&
+       echo init.t >.git/info/sparse-checkout &&
+       echo sub/added >>.git/info/sparse-checkout &&
+       git checkout -f top &&
+       echo init.t >.git/info/sparse-checkout &&
+       git checkout removed &&
+       git ls-files sub/added >result &&
+       test ! -f sub/added &&
+       test_cmp empty result
+'
+
+test_expect_success 'read-tree --reset removes outside worktree' '
+       >empty &&
+       echo init.t >.git/info/sparse-checkout &&
+       git checkout -f top &&
+       git reset --hard removed &&
+       git ls-files sub/added >result &&
+       test_cmp empty result
+'
+
+test_done
diff --git a/t/t1012-read-tree-df.sh b/t/t1012-read-tree-df.sh
new file mode 100755 (executable)
index 0000000..9811d46
--- /dev/null
@@ -0,0 +1,102 @@
+#!/bin/sh
+
+test_description='read-tree D/F conflict corner cases'
+
+. ./test-lib.sh
+
+maketree () {
+       (
+               rm -f .git/index .git/index.lock &&
+               git clean -d -f -f -q -x &&
+               name="$1" &&
+               shift &&
+               for it
+               do
+                       path=$(expr "$it" : '\([^:]*\)') &&
+                       mkdir -p $(dirname "$path") &&
+                       echo "$it" >"$path" &&
+                       git update-index --add "$path" || exit
+               done &&
+               git tag "$name" $(git write-tree)
+       )
+}
+
+settree () {
+       rm -f .git/index .git/index.lock &&
+       git clean -d -f -f -q -x &&
+       git read-tree "$1" &&
+       git checkout-index -f -q -u -a &&
+       git update-index --refresh
+}
+
+checkindex () {
+       git ls-files -s |
+       sed "s|^[0-7][0-7]* $_x40 \([0-3]\)     |\1 |" >current &&
+       cat >expect &&
+       test_cmp expect current
+}
+
+test_expect_success setup '
+       maketree O-000 a/b-2/c/d a/b/c/d a/x &&
+       maketree A-000 a/b-2/c/d a/b/c/d a/x &&
+       maketree A-001 a/b-2/c/d a/b/c/d a/b/c/e a/x &&
+       maketree B-000 a/b-2/c/d a/b     a/x &&
+
+       maketree O-010 t-0     t/1  t/2 t=3 &&
+       maketree A-010 t-0 t            t=3 &&
+       maketree B-010         t/1:     t=3: &&
+
+       maketree O-020 ds/dma/ioat.c ds/dma/ioat_dca.c &&
+       maketree A-020 ds/dma/ioat/Makefile ds/dma/ioat/registers.h &&
+       :
+'
+
+test_expect_success '3-way (1)' '
+       settree A-000 &&
+       git read-tree -m -u O-000 A-000 B-000 &&
+       checkindex <<-EOF
+       3 a/b
+       0 a/b-2/c/d
+       1 a/b/c/d
+       2 a/b/c/d
+       0 a/x
+       EOF
+'
+
+test_expect_success '3-way (2)' '
+       settree A-001 &&
+       git read-tree -m -u O-000 A-001 B-000 &&
+       checkindex <<-EOF
+       3 a/b
+       0 a/b-2/c/d
+       1 a/b/c/d
+       2 a/b/c/d
+       2 a/b/c/e
+       0 a/x
+       EOF
+'
+
+test_expect_success '3-way (3)' '
+       settree A-010 &&
+       git read-tree -m -u O-010 A-010 B-010 &&
+       checkindex <<-EOF
+       2 t
+       1 t-0
+       2 t-0
+       1 t/1
+       3 t/1
+       1 t/2
+       0 t=3
+       EOF
+'
+
+test_expect_success '2-way (1)' '
+       settree O-020 &&
+       git read-tree -m -u O-020 A-020 &&
+       checkindex <<-EOF
+       0 ds/dma/ioat/Makefile
+       0 ds/dma/ioat/registers.h
+       EOF
+'
+
+test_done
index 210e594f6f3c83cc1b0c423a0f692380ff57176b..a3ac33801a28fe7bd2ce67d4d4ae306c1050f5c1 100755 (executable)
@@ -16,123 +16,133 @@ test_expect_success setup '
        cp one original.one &&
        cp dir/two original.two
 '
-HERE=`pwd`
 LF='
 '
 
 test_expect_success 'update-index and ls-files' '
-       cd "$HERE" &&
        git update-index --add one &&
        case "`git ls-files`" in
-       one) echo ok one ;;
+       one) echo pass one ;;
        *) echo bad one; exit 1 ;;
        esac &&
-       cd dir &&
-       git update-index --add two &&
+       (
+               cd dir &&
+               git update-index --add two &&
+               case "`git ls-files`" in
+               two) echo pass two ;;
+               *) echo bad two; exit 1 ;;
+               esac
+       ) &&
        case "`git ls-files`" in
-       two) echo ok two ;;
-       *) echo bad two; exit 1 ;;
-       esac &&
-       cd .. &&
-       case "`git ls-files`" in
-       dir/two"$LF"one) echo ok both ;;
+       dir/two"$LF"one) echo pass both ;;
        *) echo bad; exit 1 ;;
        esac
 '
 
 test_expect_success 'cat-file' '
-       cd "$HERE" &&
        two=`git ls-files -s dir/two` &&
        two=`expr "$two" : "[0-7]* \\([0-9a-f]*\\)"` &&
        echo "$two" &&
        git cat-file -p "$two" >actual &&
        cmp dir/two actual &&
-       cd dir &&
-       git cat-file -p "$two" >actual &&
-       cmp two actual
+       (
+               cd dir &&
+               git cat-file -p "$two" >actual &&
+               cmp two actual
+       )
 '
 rm -f actual dir/actual
 
 test_expect_success 'diff-files' '
-       cd "$HERE" &&
        echo a >>one &&
        echo d >>dir/two &&
        case "`git diff-files --name-only`" in
-       dir/two"$LF"one) echo ok top ;;
+       dir/two"$LF"one) echo pass top ;;
        *) echo bad top; exit 1 ;;
        esac &&
        # diff should not omit leading paths
-       cd dir &&
-       case "`git diff-files --name-only`" in
-       dir/two"$LF"one) echo ok subdir ;;
-       *) echo bad subdir; exit 1 ;;
-       esac &&
-       case "`git diff-files --name-only .`" in
-       dir/two) echo ok subdir limited ;;
-       *) echo bad subdir limited; exit 1 ;;
-       esac
+       (
+               cd dir &&
+               case "`git diff-files --name-only`" in
+               dir/two"$LF"one) echo pass subdir ;;
+               *) echo bad subdir; exit 1 ;;
+               esac &&
+               case "`git diff-files --name-only .`" in
+               dir/two) echo pass subdir limited ;;
+               *) echo bad subdir limited; exit 1 ;;
+               esac
+       )
 '
 
 test_expect_success 'write-tree' '
-       cd "$HERE" &&
        top=`git write-tree` &&
        echo $top &&
-       cd dir &&
-       sub=`git write-tree` &&
-       echo $sub &&
-       test "z$top" = "z$sub"
+       (
+               cd dir &&
+               sub=`git write-tree` &&
+               echo $sub &&
+               test "z$top" = "z$sub"
+       )
 '
 
 test_expect_success 'checkout-index' '
-       cd "$HERE" &&
        git checkout-index -f -u one &&
        cmp one original.one &&
-       cd dir &&
-       git checkout-index -f -u two &&
-       cmp two ../original.two
+       (
+               cd dir &&
+               git checkout-index -f -u two &&
+               cmp two ../original.two
+       )
 '
 
 test_expect_success 'read-tree' '
-       cd "$HERE" &&
        rm -f one dir/two &&
        tree=`git write-tree` &&
        git read-tree --reset -u "$tree" &&
        cmp one original.one &&
        cmp dir/two original.two &&
-       cd dir &&
-       rm -f two &&
-       git read-tree --reset -u "$tree" &&
-       cmp two ../original.two &&
-       cmp ../one ../original.one
+       (
+               cd dir &&
+               rm -f two &&
+               git read-tree --reset -u "$tree" &&
+               cmp two ../original.two &&
+               cmp ../one ../original.one
+       )
 '
 
 test_expect_success 'no file/rev ambiguity check inside .git' '
-       cd "$HERE" &&
        git commit -a -m 1 &&
-       cd "$HERE"/.git &&
-       git show -s HEAD
+       (
+               cd .git &&
+               git show -s HEAD
+       )
 '
 
 test_expect_success 'no file/rev ambiguity check inside a bare repo' '
-       cd "$HERE" &&
        git clone -s --bare .git foo.git &&
-       cd foo.git && GIT_DIR=. git show -s HEAD
+       (
+               cd foo.git &&
+               GIT_DIR=. git show -s HEAD
+       )
 '
 
 # This still does not work as it should...
 : test_expect_success 'no file/rev ambiguity check inside a bare repo' '
-       cd "$HERE" &&
        git clone -s --bare .git foo.git &&
-       cd foo.git && git show -s HEAD
+       (
+               cd foo.git &&
+               git show -s HEAD
+       )
 '
 
 test_expect_success SYMLINKS 'detection should not be fooled by a symlink' '
-       cd "$HERE" &&
        rm -fr foo.git &&
        git clone -s .git another &&
        ln -s another yetanother &&
-       cd yetanother/.git &&
-       git show -s HEAD
+       (
+               cd yetanother/.git &&
+               git show -s HEAD
+       )
 '
 
 test_done
index 67e637b7810190e1b7d12dab70cd41d83db0e442..ab55eda158bb5a6ecad77302aa2fe17bd6e92be3 100755 (executable)
@@ -7,14 +7,18 @@ test_description='A simple turial in the form of a test case'
 
 . ./test-lib.sh
 
-echo "Hello World" > hello
-echo "Silly example" > example
+test_expect_success 'blob'  '
+       echo "Hello World" > hello &&
+       echo "Silly example" > example &&
 
-git update-index --add hello example
+       git update-index --add hello example &&
 
-test_expect_success 'blob' "test blob = \"$(git cat-file -t 557db03)\""
+       test blob = "$(git cat-file -t 557db03)"
+'
 
-test_expect_success 'blob 557db03' "test \"Hello World\" = \"$(git cat-file blob 557db03)\""
+test_expect_success 'blob 557db03' '
+       test "Hello World" = "$(git cat-file blob 557db03)"
+'
 
 echo "It's a new day for git" >>hello
 cat > diff.expect << EOF
@@ -26,25 +30,35 @@ index 557db03..263414f 100644
  Hello World
 +It's a new day for git
 EOF
-git diff-files -p > diff.output
-test_expect_success 'git diff-files -p' 'cmp diff.expect diff.output'
-git diff > 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"
+test_expect_success 'git diff-files -p' '
+       git diff-files -p > diff.output &&
+       test_cmp diff.expect diff.output
+'
 
-output="$(echo "Initial commit" | git commit-tree $(git write-tree) 2>&1 > .git/refs/heads/master)"
+test_expect_success 'git diff' '
+       git diff > diff.output &&
+       test_cmp diff.expect diff.output
+'
 
-git diff-index -p HEAD > diff.output
-test_expect_success 'git diff-index -p HEAD' 'cmp diff.expect diff.output'
+test_expect_success 'tree' '
+       tree=$(git write-tree 2>/dev/null)
+       test 8988da15d077d4829fc51d8544c097def6644dbb = $tree
+'
 
-git diff HEAD > diff.output
-test_expect_success 'git diff HEAD' 'cmp diff.expect diff.output'
+test_expect_success 'git diff-index -p HEAD' '
+       test_tick &&
+       tree=$(git write-tree) &&
+       commit=$(echo "Initial commit" | git commit-tree $tree) &&
+       git update-ref HEAD $commit &&
+       git diff-index -p HEAD > diff.output &&
+       test_cmp diff.expect diff.output
+'
 
-#rm hello
-#test_expect_success 'git read-tree --reset HEAD' "git read-tree --reset HEAD ; test \"hello: needs update\" = \"$(git update-index --refresh)\""
+test_expect_success 'git diff HEAD' '
+       git diff HEAD > diff.output &&
+       test_cmp diff.expect diff.output
+'
 
 cat > whatchanged.expect << EOF
 commit VARIABLE
@@ -69,39 +83,47 @@ index 0000000..557db03
 +Hello World
 EOF
 
-git whatchanged -p --root | \
-       sed -e "1s/^\(.\{7\}\).\{40\}/\1VARIABLE/" \
+test_expect_success 'git whatchanged -p --root' '
+       git whatchanged -p --root |
+               sed -e "1s/^\(.\{7\}\).\{40\}/\1VARIABLE/" \
                -e "2,3s/^\(.\{8\}\).*$/\1VARIABLE/" \
-> whatchanged.output
-test_expect_success 'git whatchanged -p --root' 'cmp whatchanged.expect whatchanged.output'
-
-git tag my-first-tag
-test_expect_success 'git tag my-first-tag' 'cmp .git/refs/heads/master .git/refs/tags/my-first-tag'
+       > whatchanged.output &&
+       test_cmp whatchanged.expect whatchanged.output
+'
 
-# TODO: test git clone
+test_expect_success 'git tag my-first-tag' '
+       git tag my-first-tag &&
+       test_cmp .git/refs/heads/master .git/refs/tags/my-first-tag
+'
 
-git checkout -b mybranch
-test_expect_success 'git checkout -b mybranch' 'cmp .git/refs/heads/master .git/refs/heads/mybranch'
+test_expect_success 'git checkout -b mybranch' '
+       git checkout -b mybranch &&
+       test_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'
+test_expect_success 'git branch' '
+       git branch > branch.output &&
+       test_cmp branch.expect branch.output
+'
 
-git checkout mybranch
-echo "Work, work, work" >>hello
-git commit -m 'Some work.' -i hello
+test_expect_success 'git resolve now fails' '
+       git checkout mybranch &&
+       echo "Work, work, work" >>hello &&
+       test_tick &&
+       git commit -m "Some work." -i hello &&
 
-git checkout master
+       git checkout master &&
 
-echo "Play, play, play" >>hello
-echo "Lots of fun" >>example
-git commit -m 'Some fun.' -i hello example
+       echo "Play, play, play" >>hello &&
+       echo "Lots of fun" >>example &&
+       test_tick &&
+       git commit -m "Some fun." -i hello example &&
 
-test_expect_success 'git resolve now fails' '
        test_must_fail git merge -m "Merge work in mybranch" mybranch
 '
 
@@ -112,52 +134,132 @@ 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.
+* [master] Merge work in mybranch
  ! [mybranch] Some work.
 --
--  [master] Merged "mybranch" changes.
+-  [master] Merge work in mybranch
 *+ [mybranch] Some work.
+*  [master^] Some fun.
 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
+test_expect_success 'git show-branch' '
+       test_tick &&
+       git commit -m "Merge work in mybranch" -i hello &&
+       git show-branch --topo-order --more=1 master mybranch \
+               > show-branch.output &&
+       test_cmp show-branch.expect show-branch.output
+'
 
 cat > resolve.expect << EOF
-Updating from VARIABLE to VARIABLE
+Updating VARIABLE..VARIABLE
+FASTFORWARD (no commit created; -m option ignored)
  example |    1 +
  hello   |    1 +
  2 files changed, 2 insertions(+), 0 deletions(-)
 EOF
 
-git merge -s "Merge upstream changes." master | \
-       sed -e "1s/[0-9a-f]\{40\}/VARIABLE/g" >resolve.output
-test_expect_success 'git resolve' 'cmp resolve.expect resolve.output'
+test_expect_success 'git resolve' '
+       git checkout mybranch &&
+       git merge -m "Merge upstream changes." master |
+               sed -e "1s/[0-9a-f]\{7\}/VARIABLE/g" \
+               -e "s/^Fast[- ]forward /FASTFORWARD /" >resolve.output &&
+       test_cmp resolve.expect resolve.output
+'
 
 cat > show-branch2.expect << EOF
-! [master] Merged "mybranch" changes.
- * [mybranch] Merged "mybranch" changes.
+! [master] Merge work in mybranch
+ * [mybranch] Merge work in mybranch
+--
+-- [master] Merge work in mybranch
+EOF
+
+test_expect_success 'git show-branch (part 2)' '
+       git show-branch --topo-order master mybranch > show-branch2.output &&
+       test_cmp show-branch2.expect show-branch2.output
+'
+
+cat > show-branch3.expect << EOF
+! [master] Merge work in mybranch
+ * [mybranch] Merge work in mybranch
+--
+-- [master] Merge work in mybranch
++* [master^2] Some work.
++* [master^] Some fun.
+EOF
+
+test_expect_success 'git show-branch (part 3)' '
+       git show-branch --topo-order --more=2 master mybranch \
+               > show-branch3.output &&
+       test_cmp show-branch3.expect show-branch3.output
+'
+
+test_expect_success 'rewind to "Some fun." and "Some work."' '
+       git checkout mybranch &&
+       git reset --hard master^2 &&
+       git checkout master &&
+       git reset --hard master^
+'
+
+cat > show-branch4.expect << EOF
+* [master] Some fun.
+ ! [mybranch] Some work.
 --
--- [master] Merged "mybranch" changes.
+*  [master] Some fun.
+ + [mybranch] Some work.
+*+ [master^] Initial commit
+EOF
+
+test_expect_success 'git show-branch (part 4)' '
+       git show-branch --topo-order > show-branch4.output &&
+       test_cmp show-branch4.expect show-branch4.output
+'
+
+test_expect_success 'manual merge' '
+       mb=$(git merge-base HEAD mybranch) &&
+       git name-rev --name-only --tags $mb > name-rev.output &&
+       test "my-first-tag" = $(cat name-rev.output) &&
+
+       git read-tree -m -u $mb HEAD mybranch
+'
+
+cat > ls-files.expect << EOF
+100644 7f8b141b65fdcee47321e399a2598a235a032422 0      example
+100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1      hello
+100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2      hello
+100644 cc44c73eb783565da5831b4d820c962954019b69 3      hello
+EOF
+
+test_expect_success 'git ls-files --stage' '
+       git ls-files --stage > ls-files.output &&
+       test_cmp ls-files.expect ls-files.output
+'
+
+cat > ls-files-unmerged.expect << EOF
+100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1      hello
+100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2      hello
+100644 cc44c73eb783565da5831b4d820c962954019b69 3      hello
 EOF
 
-git show-branch --topo-order master mybranch > show-branch2.output
-test_expect_success 'git show-branch' 'cmp show-branch2.expect show-branch2.output'
+test_expect_success 'git ls-files --unmerged' '
+       git ls-files --unmerged > ls-files-unmerged.output &&
+       test_cmp ls-files-unmerged.expect ls-files-unmerged.output
+'
 
-# TODO: test git fetch
+test_expect_success 'git-merge-index' '
+       test_must_fail git merge-index git-merge-one-file hello
+'
 
-# TODO: test git push
+test_expect_success 'git ls-files --stage (part 2)' '
+       git ls-files --stage > ls-files.output2 &&
+       test_cmp ls-files.expect ls-files.output2
+'
 
 test_expect_success 'git repack' 'git repack'
 test_expect_success 'git prune-packed' 'git prune-packed'
 test_expect_success '-> only packed objects' '
-       ! find -type f .git/objects/[0-9a-f][0-9a-f]
+       git prune && # Remove conflict marked blobs
+       test $(find .git/objects/[0-9a-f][0-9a-f] -type f -print 2>/dev/null | wc -l) = 0
 '
 
 test_done
index 43ea283242c4afc25e53d4a8c894140793649717..074f2f2e3e57f76b01f16502998e95b493676711 100755 (executable)
@@ -398,6 +398,17 @@ test_expect_success 'alternative GIT_CONFIG' 'cmp output expect'
 test_expect_success 'alternative GIT_CONFIG (--file)' \
        'git config --file other-config -l > output && cmp output expect'
 
+test_expect_success 'refer config from subdirectory' '
+       mkdir x &&
+       (
+               cd x &&
+               echo strasse >expect
+               git config --get --file ../other-config ein.bahn >actual &&
+               test_cmp expect actual
+       )
+
+'
+
 GIT_CONFIG=other-config git config anwohner.park ausweis
 
 cat > expect << EOF
@@ -459,6 +470,28 @@ EOF
 
 test_expect_success "rename succeeded" "test_cmp expect .git/config"
 
+cat >> .git/config << EOF
+[branch "vier"] z = 1
+EOF
+
+test_expect_success "rename a section with a var on the same line" \
+       'git config --rename-section branch.vier branch.zwei'
+
+cat > expect << EOF
+# Hallo
+       #Bello
+[branch "zwei"]
+       x = 1
+[branch "zwei"]
+       y = 1
+[branch "drei"]
+weird
+[branch "zwei"]
+       z = 1
+EOF
+
+test_expect_success "rename succeeded" "test_cmp expect .git/config"
+
 cat >> .git/config << EOF
   [branch "zwei"] a = 1 [branch "vier"]
 EOF
@@ -661,6 +694,56 @@ test_expect_success 'set --bool-or-int' '
 
 rm .git/config
 
+cat >expect <<\EOF
+[path]
+       home = ~/
+       normal = /dev/null
+       trailingtilde = foo~
+EOF
+
+test_expect_success 'set --path' '
+       git config --path path.home "~/" &&
+       git config --path path.normal "/dev/null" &&
+       git config --path path.trailingtilde "foo~" &&
+       test_cmp expect .git/config'
+
+if test "${HOME+set}"
+then
+       test_set_prereq HOMEVAR
+fi
+
+cat >expect <<EOF
+$HOME/
+/dev/null
+foo~
+EOF
+
+test_expect_success HOMEVAR 'get --path' '
+       git config --get --path path.home > result &&
+       git config --get --path path.normal >> result &&
+       git config --get --path path.trailingtilde >> result &&
+       test_cmp expect result
+'
+
+cat >expect <<\EOF
+/dev/null
+foo~
+EOF
+
+test_expect_success 'get --path copes with unset $HOME' '
+       (
+               unset HOME;
+               test_must_fail git config --get --path path.home \
+                       >result 2>msg &&
+               git config --get --path path.normal >>result &&
+               git config --get --path path.trailingtilde >>result
+       ) &&
+       grep "[Ff]ailed to expand.*~/" msg &&
+       test_cmp expect result
+'
+
+rm .git/config
+
 git config quote.leading " test"
 git config quote.ending "test "
 git config quote.semicolon "test;test"
@@ -733,6 +816,11 @@ echo >>result
 
 test_expect_success '--null --get-regexp' 'cmp result expect'
 
+test_expect_success 'inner whitespace kept verbatim' '
+       git config section.val "foo       bar" &&
+       test "z$(git config section.val)" = "zfoo         bar"
+'
+
 test_expect_success SYMLINKS 'symlinked configuration' '
 
        ln -s notyet myconfig &&
@@ -758,4 +846,12 @@ test_expect_success 'check split_cmdline return' "
        test_must_fail git merge master
        "
 
+test_expect_success 'git -c "key=value" support' '
+       test "z$(git -c name=value config name)" = zvalue &&
+       test "z$(git -c core.name=value config core.name)" = zvalue &&
+       test "z$(git -c CamelCase=value config camelcase)" = zvalue &&
+       test "z$(git -c flag config --bool flag)" = ztrue &&
+       test_must_fail git -c core.name=value config name
+'
+
 test_done
index 8d305b43725f8cf60e7ee802df1923feb98eeae5..a6bf1bf4d6182f0883ea858e28aa8a9b3dd79024 100755 (executable)
@@ -7,41 +7,64 @@ test_description='Test repository version check'
 
 . ./test-lib.sh
 
-cat >test.patch <<EOF
-diff --git a/test.txt b/test.txt
-new file mode 100644
---- /dev/null
-+++ b/test.txt
-@@ -0,0 +1 @@
-+123
-EOF
+test_expect_success 'setup' '
+       cat >test.patch <<-\EOF &&
+       diff --git a/test.txt b/test.txt
+       new file mode 100644
+       --- /dev/null
+       +++ b/test.txt
+       @@ -0,0 +1 @@
+       +123
+       EOF
 
-test_create_repo "test"
-test_create_repo "test2"
-
-GIT_CONFIG=test2/.git/config git config core.repositoryformatversion 99 || exit 1
+       test_create_repo "test" &&
+       test_create_repo "test2" &&
+       GIT_CONFIG=test2/.git/config git config core.repositoryformatversion 99
+'
 
 test_expect_success 'gitdir selection on normal repos' '
-       (test "$(git config core.repositoryformatversion)" = 0 &&
-       cd test &&
-       test "$(git config core.repositoryformatversion)" = 0)'
+       echo 0 >expect &&
+       git config core.repositoryformatversion >actual &&
+       (
+               cd test &&
+               git config core.repositoryformatversion >../actual2
+       ) &&
+       test_cmp expect actual &&
+       test_cmp expect actual2
+'
 
-# Make sure it would stop at test2, not trash
 test_expect_success 'gitdir selection on unsupported repo' '
-       (cd test2 &&
-       test "$(git config core.repositoryformatversion)" = 99)'
+       # Make sure it would stop at test2, not trash
+       echo 99 >expect &&
+       (
+               cd test2 &&
+               git config core.repositoryformatversion >../actual
+       )
+       test_cmp expect actual
+'
 
 test_expect_success 'gitdir not required mode' '
-       (git apply --stat test.patch &&
-       cd test && git apply --stat ../test.patch &&
-       cd ../test2 && git apply --stat ../test.patch)'
-
-test_expect_success 'gitdir required mode on normal repos' '
-       (git apply --check --index test.patch &&
-       cd test && git apply --check --index ../test.patch)'
+       git apply --stat test.patch &&
+       (
+               cd test &&
+               git apply --stat ../test.patch
+       ) &&
+       (
+               cd test2 &&
+               git apply --stat ../test.patch
+       )
+'
 
-test_expect_success 'gitdir required mode on unsupported repo' '
-       (cd test2 && test_must_fail git apply --check --index ../test.patch)
+test_expect_success 'gitdir required mode' '
+       git apply --check --index test.patch &&
+       (
+               cd test &&
+               git apply --check --index ../test.patch
+       ) &&
+       (
+               cd test2 &&
+               test_must_fail git apply --check --index ../test.patch
+       )
 '
 
 test_done
diff --git a/t/t1304-default-acl.sh b/t/t1304-default-acl.sh
new file mode 100755 (executable)
index 0000000..b5d89a2
--- /dev/null
@@ -0,0 +1,61 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Matthieu Moy
+#
+
+test_description='Test repository with default ACL'
+
+# Create the test repo with restrictive umask
+# => this must come before . ./test-lib.sh
+umask 077
+
+. ./test-lib.sh
+
+# We need an arbitrary other user give permission to using ACLs. root
+# is a good candidate: exists on all unices, and it has permission
+# anyway, so we don't create a security hole running the testsuite.
+
+setfacl_out="$(setfacl -m u:root:rwx . 2>&1)"
+setfacl_ret=$?
+
+if test $setfacl_ret != 0
+then
+       say "Unable to use setfacl (output: '$setfacl_out'; return code: '$setfacl_ret')"
+else
+       test_set_prereq SETFACL
+fi
+
+check_perms_and_acl () {
+       test -r "$1" &&
+       getfacl "$1" > actual &&
+       grep -q "user:root:rwx" actual &&
+       grep -q "user:${LOGNAME}:rwx" actual &&
+       egrep "mask::?r--" actual > /dev/null 2>&1 &&
+       grep -q "group::---" actual || false
+}
+
+dirs_to_set="./ .git/ .git/objects/ .git/objects/pack/"
+
+test_expect_success SETFACL 'Setup test repo' '
+       setfacl -m d:u::rwx,d:g::---,d:o:---,d:m:rwx $dirs_to_set &&
+       setfacl -m m:rwx               $dirs_to_set &&
+       setfacl -m u:root:rwx          $dirs_to_set &&
+       setfacl -m d:u:"$LOGNAME":rwx  $dirs_to_set &&
+       setfacl -m d:u:root:rwx        $dirs_to_set &&
+
+       touch file.txt &&
+       git add file.txt &&
+       git commit -m "init"
+'
+
+test_expect_success SETFACL 'Objects creation does not break ACLs with restrictive umask' '
+       # SHA1 for empty blob
+       check_perms_and_acl .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
+'
+
+test_expect_success SETFACL 'git gc does not break ACLs with restrictive umask' '
+       git gc &&
+       check_perms_and_acl .git/objects/pack/*.pack
+'
+
+test_done
diff --git a/t/t1402-check-ref-format.sh b/t/t1402-check-ref-format.sh
new file mode 100755 (executable)
index 0000000..782e75d
--- /dev/null
@@ -0,0 +1,78 @@
+#!/bin/sh
+
+test_description='Test git check-ref-format'
+
+. ./test-lib.sh
+
+valid_ref() {
+       test_expect_success "ref name '$1' is valid" \
+               "git check-ref-format '$1'"
+}
+invalid_ref() {
+       test_expect_success "ref name '$1' is not valid" \
+               "test_must_fail git check-ref-format '$1'"
+}
+
+valid_ref 'heads/foo'
+invalid_ref 'foo'
+valid_ref 'foo/bar/baz'
+valid_ref 'refs///heads/foo'
+invalid_ref 'heads/foo/'
+invalid_ref './foo'
+invalid_ref '.refs/foo'
+invalid_ref 'heads/foo..bar'
+invalid_ref 'heads/foo?bar'
+valid_ref 'foo./bar'
+invalid_ref 'heads/foo.lock'
+valid_ref 'heads/foo@bar'
+invalid_ref 'heads/v@{ation'
+invalid_ref 'heads/foo\bar'
+
+test_expect_success "check-ref-format --branch @{-1}" '
+       T=$(git write-tree) &&
+       sha1=$(echo A | git commit-tree $T) &&
+       git update-ref refs/heads/master $sha1 &&
+       git update-ref refs/remotes/origin/master $sha1
+       git checkout master &&
+       git checkout origin/master &&
+       git checkout master &&
+       refname=$(git check-ref-format --branch @{-1}) &&
+       test "$refname" = "$sha1" &&
+       refname2=$(git check-ref-format --branch @{-2}) &&
+       test "$refname2" = master'
+
+test_expect_success 'check-ref-format --branch from subdir' '
+       mkdir subdir &&
+
+       T=$(git write-tree) &&
+       sha1=$(echo A | git commit-tree $T) &&
+       git update-ref refs/heads/master $sha1 &&
+       git update-ref refs/remotes/origin/master $sha1
+       git checkout master &&
+       git checkout origin/master &&
+       git checkout master &&
+       refname=$(
+               cd subdir &&
+               git check-ref-format --branch @{-1}
+       ) &&
+       test "$refname" = "$sha1"
+'
+
+valid_ref_normalized() {
+       test_expect_success "ref name '$1' simplifies to '$2'" "
+               refname=\$(git check-ref-format --print '$1') &&
+               test \"\$refname\" = '$2'"
+}
+invalid_ref_normalized() {
+       test_expect_success "check-ref-format --print rejects '$1'" "
+               test_must_fail git check-ref-format --print '$1'"
+}
+
+valid_ref_normalized 'heads/foo' 'heads/foo'
+valid_ref_normalized 'refs///heads/foo' 'refs/heads/foo'
+invalid_ref_normalized 'foo'
+invalid_ref_normalized 'heads/foo/../bar'
+invalid_ref_normalized 'heads/./foo'
+invalid_ref_normalized 'heads\foo'
+
+test_done
index 80af6b9b7ea50652dff6804b589d134207e9258f..25046c42081c8412b56c6b739dfd3a67ab700810 100755 (executable)
@@ -214,4 +214,45 @@ test_expect_success 'delete' '
 
 '
 
+test_expect_success 'rewind2' '
+
+       test_tick && git reset --hard HEAD~2 &&
+       loglen=$(wc -l <.git/logs/refs/heads/master) &&
+       test $loglen = 4
+
+'
+
+test_expect_success '--expire=never' '
+
+       git reflog expire --verbose \
+               --expire=never \
+               --expire-unreachable=never \
+               --all &&
+       loglen=$(wc -l <.git/logs/refs/heads/master) &&
+       test $loglen = 4
+
+'
+
+test_expect_success 'gc.reflogexpire=never' '
+
+       git config gc.reflogexpire never &&
+       git config gc.reflogexpireunreachable never &&
+       git reflog expire --verbose --all &&
+       loglen=$(wc -l <.git/logs/refs/heads/master) &&
+       test $loglen = 4
+'
+
+test_expect_success 'gc.reflogexpire=false' '
+
+       git config gc.reflogexpire false &&
+       git config gc.reflogexpireunreachable false &&
+       git reflog expire --verbose --all &&
+       loglen=$(wc -l <.git/logs/refs/heads/master) &&
+       test $loglen = 4 &&
+
+       git config --unset gc.reflogexpire &&
+       git config --unset gc.reflogexpireunreachable
+
+'
+
 test_done
index c18ed8edf994f3d701ab1d01c2e05b2585174d31..ba25ff354d6fc4998237b1145737faf6c836966e 100755 (executable)
@@ -64,4 +64,13 @@ test_expect_success 'using --date= shows reflog date (oneline)' '
        test_cmp expect actual
 '
 
+: >expect
+test_expect_success 'empty reflog file' '
+       git branch empty &&
+       : >.git/logs/refs/heads/empty &&
+
+       git log -g empty >actual &&
+       test_cmp expect actual
+'
+
 test_done
index a22632f483e2068642aa09a5bc28fde01d2294b9..1be415e334330b2fea398cc58a0f054b6b876da1 100755 (executable)
@@ -1,19 +1,23 @@
 #!/bin/sh
 
-test_description='git fsck random collection of tests'
+test_description='git fsck random collection of tests
+
+* (HEAD) B
+* (master) A
+'
 
 . ./test-lib.sh
 
 test_expect_success setup '
+       git config gc.auto 0 &&
+       git config i18n.commitencoding ISO-8859-1 &&
        test_commit A fileA one &&
+       git config --unset i18n.commitencoding &&
        git checkout HEAD^0 &&
        test_commit B fileB two &&
        git tag -d A B &&
-       git reflog expire --expire=now --all
-'
-
-test_expect_success 'HEAD is part of refs' '
-       test 0 = $(git fsck | wc -l)
+       git reflog expire --expire=now --all &&
+       >empty
 '
 
 test_expect_success 'loose objects borrowed from alternate are not missing' '
@@ -23,76 +27,132 @@ test_expect_success 'loose objects borrowed from alternate are not missing' '
                git init &&
                echo ../../../.git/objects >.git/objects/info/alternates &&
                test_commit C fileC one &&
-               git fsck >out &&
-               ! grep "missing blob" out
-       )
+               git fsck >../out 2>&1
+       ) &&
+       {
+               grep -v dangling out >actual ||
+               :
+       } &&
+       test_cmp empty actual
+'
+
+test_expect_success 'HEAD is part of refs, valid objects appear valid' '
+       git fsck >actual 2>&1 &&
+       test_cmp empty actual
 '
 
 # Corruption tests follow.  Make sure to remove all traces of the
 # specific corruption you test afterwards, lest a later test trip over
 # it.
 
+test_expect_success 'setup: helpers for corruption tests' '
+       sha1_file() {
+               echo "$*" | sed "s#..#.git/objects/&/#"
+       } &&
+
+       remove_object() {
+               file=$(sha1_file "$*") &&
+               test -e "$file" &&
+               rm -f "$file"
+       }
+'
+
 test_expect_success 'object with bad sha1' '
        sha=$(echo blob | git hash-object -w --stdin) &&
-       echo $sha &&
        old=$(echo $sha | sed "s+^..+&/+") &&
        new=$(dirname $old)/ffffffffffffffffffffffffffffffffffffff &&
        sha="$(dirname $new)$(basename $new)"
        mv .git/objects/$old .git/objects/$new &&
+       test_when_finished "remove_object $sha" &&
        git update-index --add --cacheinfo 100644 $sha foo &&
+       test_when_finished "git read-tree -u --reset HEAD" &&
        tree=$(git write-tree) &&
+       test_when_finished "remove_object $tree" &&
        cmt=$(echo bogus | git commit-tree $tree) &&
+       test_when_finished "remove_object $cmt" &&
        git update-ref refs/heads/bogus $cmt &&
-       (git fsck 2>out; true) &&
-       grep "$sha.*corrupt" out &&
-       rm -f .git/objects/$new &&
-       git update-ref -d refs/heads/bogus &&
-       git read-tree -u --reset HEAD
+       test_when_finished "git update-ref -d refs/heads/bogus" &&
+
+       test_might_fail git fsck 2>out &&
+       cat out &&
+       grep "$sha.*corrupt" out
 '
 
 test_expect_success 'branch pointing to non-commit' '
-       git rev-parse HEAD^{tree} > .git/refs/heads/invalid &&
+       git rev-parse HEAD^{tree} >.git/refs/heads/invalid &&
+       test_when_finished "git update-ref -d refs/heads/invalid" &&
        git fsck 2>out &&
-       grep "not a commit" out &&
-       git update-ref -d refs/heads/invalid
+       cat out &&
+       grep "not a commit" out
 '
 
-cat > invalid-tag <<EOF
-object ffffffffffffffffffffffffffffffffffffffff
-type commit
-tag invalid
-tagger T A Gger <tagger@example.com> 1234567890 -0000
-
-This is an invalid tag.
-EOF
-
-test_expect_failure 'tag pointing to nonexistent' '
-       tag=$(git hash-object -w --stdin < invalid-tag) &&
-       echo $tag > .git/refs/tags/invalid &&
-       git fsck --tags 2>out &&
+test_expect_success 'email without @ is okay' '
+       git cat-file commit HEAD >basis &&
+       sed "s/@/AT/" basis >okay &&
+       new=$(git hash-object -t commit -w --stdin <okay) &&
+       test_when_finished "remove_object $new" &&
+       git update-ref refs/heads/bogus "$new" &&
+       test_when_finished "git update-ref -d refs/heads/bogus" &&
+       git fsck 2>out &&
        cat out &&
-       grep "could not load tagged object" out &&
-       rm .git/refs/tags/invalid
+       ! grep "commit $new" out
 '
 
-cat > wrong-tag <<EOF
-object $(echo blob | git hash-object -w --stdin)
-type commit
-tag wrong
-tagger T A Gger <tagger@example.com> 1234567890 -0000
-
-This is an invalid tag.
-EOF
+test_expect_success 'email with embedded > is not okay' '
+       git cat-file commit HEAD >basis &&
+       sed "s/@[a-z]/&>/" basis >bad-email &&
+       new=$(git hash-object -t commit -w --stdin <bad-email) &&
+       test_when_finished "remove_object $new" &&
+       git update-ref refs/heads/bogus "$new" &&
+       test_when_finished "git update-ref -d refs/heads/bogus" &&
+       git fsck 2>out &&
+       cat out &&
+       grep "error in commit $new" out
+'
 
-test_expect_failure 'tag pointing to something else than its type' '
-       tag=$(git hash-object -w --stdin < wrong-tag) &&
-       echo $tag > .git/refs/tags/wrong &&
-       git fsck --tags 2>out &&
+test_expect_success 'tag pointing to nonexistent' '
+       cat >invalid-tag <<-\EOF
+       object ffffffffffffffffffffffffffffffffffffffff
+       type commit
+       tag invalid
+       tagger T A Gger <tagger@example.com> 1234567890 -0000
+
+       This is an invalid tag.
+       EOF
+
+       tag=$(git hash-object -t tag -w --stdin <invalid-tag) &&
+       test_when_finished "remove_object $tag" &&
+       echo $tag >.git/refs/tags/invalid &&
+       test_when_finished "git update-ref -d refs/tags/invalid" &&
+       test_must_fail git fsck --tags >out &&
        cat out &&
-       grep "some sane error message" out &&
-       rm .git/refs/tags/wrong
+       grep "broken link" out
 '
 
+test_expect_success 'tag pointing to something else than its type' '
+       sha=$(echo blob | git hash-object -w --stdin) &&
+       test_when_finished "remove_object $sha" &&
+       cat >wrong-tag <<-EOF &&
+       object $sha
+       type commit
+       tag wrong
+       tagger T A Gger <tagger@example.com> 1234567890 -0000
+
+       This is an invalid tag.
+       EOF
+
+       tag=$(git hash-object -t tag -w --stdin <wrong-tag) &&
+       test_when_finished "remove_object $tag" &&
+       echo $tag >.git/refs/tags/wrong &&
+       test_when_finished "git update-ref -d refs/tags/wrong" &&
+       test_must_fail git fsck --tags 2>out &&
+       cat out &&
+       grep "error in tag.*broken links" out
+'
 
+test_expect_success 'cleaned up' '
+       git fsck >actual 2>&1 &&
+       test_cmp empty actual
+'
 
 test_done
index f6a6f839a18de4c3775ea965f164d0d20f2bbe9b..2c8f01f6684cbe492132575209f79371ad761d87 100755 (executable)
 test_description='test separate work tree'
 . ./test-lib.sh
 
-test_rev_parse() {
-       name=$1
-       shift
-
-       test_expect_success "$name: is-bare-repository" \
-       "test '$1' = \"\$(git rev-parse --is-bare-repository)\""
-       shift
-       [ $# -eq 0 ] && return
-
-       test_expect_success "$name: is-inside-git-dir" \
-       "test '$1' = \"\$(git rev-parse --is-inside-git-dir)\""
-       shift
-       [ $# -eq 0 ] && return
-
-       test_expect_success "$name: is-inside-work-tree" \
-       "test '$1' = \"\$(git rev-parse --is-inside-work-tree)\""
-       shift
-       [ $# -eq 0 ] && return
-
-       test_expect_success "$name: prefix" \
-       "test '$1' = \"\$(git rev-parse --show-prefix)\""
-       shift
-       [ $# -eq 0 ] && return
-}
-
-EMPTY_TREE=$(git write-tree)
-mkdir -p work/sub/dir || exit 1
-mv .git repo.git || exit 1
-
-say "core.worktree = relative path"
-GIT_DIR=repo.git
-GIT_CONFIG="$(pwd)"/$GIT_DIR/config
-export GIT_DIR GIT_CONFIG
-unset GIT_WORK_TREE
-git config core.worktree ../work
-test_rev_parse 'outside'      false false false
-cd work || exit 1
-GIT_DIR=../repo.git
-GIT_CONFIG="$(pwd)"/$GIT_DIR/config
-test_rev_parse 'inside'       false false true ''
-cd sub/dir || exit 1
-GIT_DIR=../../../repo.git
-GIT_CONFIG="$(pwd)"/$GIT_DIR/config
-test_rev_parse 'subdirectory' false false true sub/dir/
-cd ../../.. || exit 1
-
-say "core.worktree = absolute path"
-GIT_DIR=$(pwd)/repo.git
-GIT_CONFIG=$GIT_DIR/config
-git config core.worktree "$(pwd)/work"
-test_rev_parse 'outside'      false false false
-cd work || exit 1
-test_rev_parse 'inside'       false false true ''
-cd sub/dir || exit 1
-test_rev_parse 'subdirectory' false false true sub/dir/
-cd ../../.. || exit 1
-
-say "GIT_WORK_TREE=relative path (override core.worktree)"
-GIT_DIR=$(pwd)/repo.git
-GIT_CONFIG=$GIT_DIR/config
-git config core.worktree non-existent
-GIT_WORK_TREE=work
-export GIT_WORK_TREE
-test_rev_parse 'outside'      false false false
-cd work || exit 1
-GIT_WORK_TREE=.
-test_rev_parse 'inside'       false false true ''
-cd sub/dir || exit 1
-GIT_WORK_TREE=../..
-test_rev_parse 'subdirectory' false false true sub/dir/
-cd ../../.. || exit 1
-
-mv work repo.git/work
-
-say "GIT_WORK_TREE=absolute path, work tree below git dir"
-GIT_DIR=$(pwd)/repo.git
-GIT_CONFIG=$GIT_DIR/config
-GIT_WORK_TREE=$(pwd)/repo.git/work
-test_rev_parse 'outside'              false false false
-cd repo.git || exit 1
-test_rev_parse 'in repo.git'              false true  false
-cd objects || exit 1
-test_rev_parse 'in repo.git/objects'      false true  false
-cd ../work || exit 1
-test_rev_parse 'in repo.git/work'         false true true ''
-cd sub/dir || exit 1
-test_rev_parse 'in repo.git/sub/dir' false true true sub/dir/
-cd ../../../.. || exit 1
-
-test_expect_success 'repo finds its work tree' '
-       (cd repo.git &&
-        : > work/sub/dir/untracked &&
-        test sub/dir/untracked = "$(git ls-files --others)")
-'
-
-test_expect_success 'repo finds its work tree from work tree, too' '
-       (cd repo.git/work/sub/dir &&
-        : > tracked &&
-        git --git-dir=../../.. add tracked &&
-        cd ../../.. &&
-        test sub/dir/tracked = "$(git ls-files)")
+test_expect_success 'setup' '
+       EMPTY_TREE=$(git write-tree) &&
+       EMPTY_BLOB=$(git hash-object -t blob --stdin </dev/null) &&
+       CHANGED_BLOB=$(echo changed | git hash-object -t blob --stdin) &&
+       ZEROES=0000000000000000000000000000000000000000 &&
+       EMPTY_BLOB7=$(echo $EMPTY_BLOB | sed "s/\(.......\).*/\1/") &&
+       CHANGED_BLOB7=$(echo $CHANGED_BLOB | sed "s/\(.......\).*/\1/") &&
+
+       mkdir -p work/sub/dir &&
+       mkdir -p work2 &&
+       mv .git repo.git
+'
+
+test_expect_success 'setup: helper for testing rev-parse' '
+       test_rev_parse() {
+               echo $1 >expected.bare &&
+               echo $2 >expected.inside-git &&
+               echo $3 >expected.inside-worktree &&
+               if test $# -ge 4
+               then
+                       echo $4 >expected.prefix
+               fi &&
+
+               git rev-parse --is-bare-repository >actual.bare &&
+               git rev-parse --is-inside-git-dir >actual.inside-git &&
+               git rev-parse --is-inside-work-tree >actual.inside-worktree &&
+               if test $# -ge 4
+               then
+                       git rev-parse --show-prefix >actual.prefix
+               fi &&
+
+               test_cmp expected.bare actual.bare &&
+               test_cmp expected.inside-git actual.inside-git &&
+               test_cmp expected.inside-worktree actual.inside-worktree &&
+               if test $# -ge 4
+               then
+                       # rev-parse --show-prefix should output
+                       # a single newline when at the top of the work tree,
+                       # but we test for that separately.
+                       test -z "$4" && ! test -s actual.prefix ||
+                       test_cmp expected.prefix actual.prefix
+               fi
+       }
+'
+
+test_expect_success 'setup: core.worktree = relative path' '
+       unset GIT_WORK_TREE;
+       GIT_DIR=repo.git &&
+       GIT_CONFIG="$(pwd)"/$GIT_DIR/config &&
+       export GIT_DIR GIT_CONFIG &&
+       git config core.worktree ../work
+'
+
+test_expect_success 'outside' '
+       test_rev_parse false false false
+'
+
+test_expect_success 'inside work tree' '
+       (
+               cd work &&
+               GIT_DIR=../repo.git &&
+               GIT_CONFIG="$(pwd)"/$GIT_DIR/config &&
+               test_rev_parse false false true ""
+       )
+'
+
+test_expect_failure 'empty prefix is actually written out' '
+       echo >expected &&
+       (
+               cd work &&
+               GIT_DIR=../repo.git &&
+               GIT_CONFIG="$(pwd)"/$GIT_DIR/config &&
+               git rev-parse --show-prefix >../actual
+       ) &&
+       test_cmp expected actual
+'
+
+test_expect_success 'subdir of work tree' '
+       (
+               cd work/sub/dir &&
+               GIT_DIR=../../../repo.git &&
+               GIT_CONFIG="$(pwd)"/$GIT_DIR/config &&
+               test_rev_parse false false true sub/dir/
+       )
+'
+
+test_expect_success 'setup: core.worktree = absolute path' '
+       unset GIT_WORK_TREE;
+       GIT_DIR=$(pwd)/repo.git &&
+       GIT_CONFIG=$GIT_DIR/config &&
+       export GIT_DIR GIT_CONFIG &&
+       git config core.worktree "$(pwd)/work"
+'
+
+test_expect_success 'outside' '
+       test_rev_parse false false false &&
+       (
+               cd work2 &&
+               test_rev_parse false false false
+       )
+'
+
+test_expect_success 'inside work tree' '
+       (
+               cd work &&
+               test_rev_parse false false true ""
+       )
+'
+
+test_expect_success 'subdir of work tree' '
+       (
+               cd work/sub/dir &&
+               test_rev_parse false false true sub/dir/
+       )
+'
+
+test_expect_success 'setup: GIT_WORK_TREE=relative (override core.worktree)' '
+       GIT_DIR=$(pwd)/repo.git &&
+       GIT_CONFIG=$GIT_DIR/config &&
+       git config core.worktree non-existent &&
+       GIT_WORK_TREE=work &&
+       export GIT_DIR GIT_CONFIG GIT_WORK_TREE
+'
+
+test_expect_success 'outside' '
+       test_rev_parse false false false &&
+       (
+               cd work2 &&
+               test_rev_parse false false false
+       )
+'
+
+test_expect_success 'inside work tree' '
+       (
+               cd work &&
+               GIT_WORK_TREE=. &&
+               test_rev_parse false false true ""
+       )
+'
+
+test_expect_success 'subdir of work tree' '
+       (
+               cd work/sub/dir &&
+               GIT_WORK_TREE=../.. &&
+               test_rev_parse false false true sub/dir/
+       )
+'
+
+test_expect_success 'setup: GIT_WORK_TREE=absolute, below git dir' '
+       mv work repo.git/work &&
+       mv work2 repo.git/work2 &&
+       GIT_DIR=$(pwd)/repo.git &&
+       GIT_CONFIG=$GIT_DIR/config &&
+       GIT_WORK_TREE=$(pwd)/repo.git/work &&
+       export GIT_DIR GIT_CONFIG GIT_WORK_TREE
+'
+
+test_expect_success 'outside' '
+       echo outside &&
+       test_rev_parse false false false
+'
+
+test_expect_success 'in repo.git' '
+       (
+               cd repo.git &&
+               test_rev_parse false true false
+       ) &&
+       (
+               cd repo.git/objects &&
+               test_rev_parse false true false
+       ) &&
+       (
+               cd repo.git/work2 &&
+               test_rev_parse false true false
+       )
+'
+
+test_expect_success 'inside work tree' '
+       (
+               cd repo.git/work &&
+               test_rev_parse false true true ""
+       )
+'
+
+test_expect_success 'subdir of work tree' '
+       (
+               cd repo.git/work/sub/dir &&
+               test_rev_parse false true true sub/dir/
+       )
+'
+
+test_expect_success 'find work tree from repo' '
+       echo sub/dir/untracked >expected &&
+       cat <<-\EOF >repo.git/work/.gitignore &&
+       expected.*
+       actual.*
+       .gitignore
+       EOF
+       >repo.git/work/sub/dir/untracked &&
+       (
+               cd repo.git &&
+               git ls-files --others --exclude-standard >../actual
+       ) &&
+       test_cmp expected actual
+'
+
+test_expect_success 'find work tree from work tree' '
+       echo sub/dir/tracked >expected &&
+       >repo.git/work/sub/dir/tracked &&
+       (
+               cd repo.git/work/sub/dir &&
+               git --git-dir=../../.. add tracked
+       ) &&
+       (
+               cd repo.git &&
+               git ls-files >../actual
+       ) &&
+       test_cmp expected actual
 '
 
 test_expect_success '_gently() groks relative GIT_DIR & GIT_WORK_TREE' '
-       (cd repo.git/work/sub/dir &&
-       GIT_DIR=../../.. GIT_WORK_TREE=../.. GIT_PAGER= \
+       (
+               cd repo.git/work/sub/dir &&
+               GIT_DIR=../../.. &&
+               GIT_WORK_TREE=../.. &&
+               GIT_PAGER= &&
+               export GIT_DIR GIT_WORK_TREE GIT_PAGER &&
+
                git diff --exit-code tracked &&
-       echo changed > tracked &&
-       ! GIT_DIR=../../.. GIT_WORK_TREE=../.. GIT_PAGER= \
-               git diff --exit-code tracked)
-'
-cat > diff-index-cached.expected <<\EOF
-:000000 100644 0000000000000000000000000000000000000000 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 A     sub/dir/tracked
-EOF
-cat > diff-index.expected <<\EOF
-:000000 100644 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 A     sub/dir/tracked
-EOF
-
-
-test_expect_success 'git diff-index' '
-       GIT_DIR=repo.git GIT_WORK_TREE=repo.git/work git diff-index $EMPTY_TREE > result &&
-       test_cmp diff-index.expected result &&
-       GIT_DIR=repo.git git diff-index --cached $EMPTY_TREE > result &&
-       test_cmp diff-index-cached.expected result
-'
-cat >diff-files.expected <<\EOF
-:100644 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0000000000000000000000000000000000000000 M     sub/dir/tracked
-EOF
-
-test_expect_success 'git diff-files' '
-       GIT_DIR=repo.git GIT_WORK_TREE=repo.git/work git diff-files > result &&
-       test_cmp diff-files.expected result
-'
-
-cat >diff-TREE.expected <<\EOF
-diff --git a/sub/dir/tracked b/sub/dir/tracked
-new file mode 100644
-index 0000000..5ea2ed4
---- /dev/null
-+++ b/sub/dir/tracked
-@@ -0,0 +1 @@
-+changed
-EOF
-cat >diff-TREE-cached.expected <<\EOF
-diff --git a/sub/dir/tracked b/sub/dir/tracked
-new file mode 100644
-index 0000000..e69de29
-EOF
-cat >diff-FILES.expected <<\EOF
-diff --git a/sub/dir/tracked b/sub/dir/tracked
-index e69de29..5ea2ed4 100644
---- a/sub/dir/tracked
-+++ b/sub/dir/tracked
-@@ -0,0 +1 @@
-+changed
-EOF
-
-test_expect_success 'git diff' '
-       GIT_DIR=repo.git GIT_WORK_TREE=repo.git/work git diff $EMPTY_TREE > result &&
-       test_cmp diff-TREE.expected result &&
-       GIT_DIR=repo.git git diff --cached $EMPTY_TREE > result &&
-       test_cmp diff-TREE-cached.expected result &&
-       GIT_DIR=repo.git GIT_WORK_TREE=repo.git/work git diff > result &&
-       test_cmp diff-FILES.expected result
+               echo changed >tracked &&
+               test_must_fail git diff --exit-code tracked
+       )
+'
+
+test_expect_success 'diff-index respects work tree under .git dir' '
+       cat >diff-index-cached.expected <<-EOF &&
+       :000000 100644 $ZEROES $EMPTY_BLOB A    sub/dir/tracked
+       EOF
+       cat >diff-index.expected <<-EOF &&
+       :000000 100644 $ZEROES $ZEROES A        sub/dir/tracked
+       EOF
+
+       (
+               GIT_DIR=repo.git &&
+               GIT_WORK_TREE=repo.git/work &&
+               export GIT_DIR GIT_WORK_TREE &&
+               git diff-index $EMPTY_TREE >diff-index.actual &&
+               git diff-index --cached $EMPTY_TREE >diff-index-cached.actual
+       ) &&
+       test_cmp diff-index.expected diff-index.actual &&
+       test_cmp diff-index-cached.expected diff-index-cached.actual
+'
+
+test_expect_success 'diff-files respects work tree under .git dir' '
+       cat >diff-files.expected <<-EOF &&
+       :100644 100644 $EMPTY_BLOB $ZEROES M    sub/dir/tracked
+       EOF
+
+       (
+               GIT_DIR=repo.git &&
+               GIT_WORK_TREE=repo.git/work &&
+               export GIT_DIR GIT_WORK_TREE &&
+               git diff-files >diff-files.actual
+       ) &&
+       test_cmp diff-files.expected diff-files.actual
+'
+
+test_expect_success 'git diff respects work tree under .git dir' '
+       cat >diff-TREE.expected <<-EOF &&
+       diff --git a/sub/dir/tracked b/sub/dir/tracked
+       new file mode 100644
+       index 0000000..$CHANGED_BLOB7
+       --- /dev/null
+       +++ b/sub/dir/tracked
+       @@ -0,0 +1 @@
+       +changed
+       EOF
+       cat >diff-TREE-cached.expected <<-EOF &&
+       diff --git a/sub/dir/tracked b/sub/dir/tracked
+       new file mode 100644
+       index 0000000..$EMPTY_BLOB7
+       EOF
+       cat >diff-FILES.expected <<-EOF &&
+       diff --git a/sub/dir/tracked b/sub/dir/tracked
+       index $EMPTY_BLOB7..$CHANGED_BLOB7 100644
+       --- a/sub/dir/tracked
+       +++ b/sub/dir/tracked
+       @@ -0,0 +1 @@
+       +changed
+       EOF
+
+       (
+               GIT_DIR=repo.git &&
+               GIT_WORK_TREE=repo.git/work &&
+               export GIT_DIR GIT_WORK_TREE &&
+               git diff $EMPTY_TREE >diff-TREE.actual &&
+               git diff --cached $EMPTY_TREE >diff-TREE-cached.actual &&
+               git diff >diff-FILES.actual
+       ) &&
+       test_cmp diff-TREE.expected diff-TREE.actual &&
+       test_cmp diff-TREE-cached.expected diff-TREE-cached.actual &&
+       test_cmp diff-FILES.expected diff-FILES.actual
 '
 
 test_expect_success 'git grep' '
-       (cd repo.git/work/sub &&
-       GIT_DIR=../.. GIT_WORK_TREE=.. git grep -l changed | grep dir/tracked)
+       echo dir/tracked >expected.grep &&
+       (
+               cd repo.git/work/sub &&
+               GIT_DIR=../.. &&
+               GIT_WORK_TREE=.. &&
+               export GIT_DIR GIT_WORK_TREE &&
+               git grep -l changed >../../../actual.grep
+       ) &&
+       test_cmp expected.grep actual.grep
+'
+
+test_expect_success 'git commit' '
+       (
+               cd repo.git &&
+               GIT_DIR=. GIT_WORK_TREE=work git commit -a -m done
+       )
+'
+
+test_expect_success 'absolute pathspec should fail gracefully' '
+       (
+               cd repo.git &&
+               test_might_fail git config --unset core.worktree &&
+               test_must_fail git log HEAD -- /home
+       )
+'
+
+test_expect_success 'make_relative_path handles double slashes in GIT_DIR' '
+       >dummy_file
+       echo git --git-dir="$(pwd)//repo.git" --work-tree="$(pwd)" add dummy_file &&
+       git --git-dir="$(pwd)//repo.git" --work-tree="$(pwd)" add dummy_file
 '
 
 test_done
index 997002d4c40dd8e66e3be5a701e3d99bab1c57c4..b3195c47070b35d555f8cfae90497a2d00d10ca7 100755 (executable)
@@ -3,7 +3,8 @@
 test_description='test git rev-parse --parseopt'
 . ./test-lib.sh
 
-cat > expect.err <<EOF
+cat > expect <<\END_EXPECT
+cat <<\EOF
 usage: some-command [options] <args>...
 
     some-command does foo and bar!
@@ -19,9 +20,9 @@ Extras
     --extra1              line above used to cause a segfault but no longer does
 
 EOF
+END_EXPECT
 
-test_expect_success 'test --parseopt help output' '
-       git rev-parse --parseopt -- -h 2> output.err <<EOF
+cat > optionspec << EOF
 some-command [options] <args>...
 
 some-command does foo and bar!
@@ -37,7 +38,65 @@ C?        option C with an optional argument
 Extras
 extra1    line above used to cause a segfault but no longer does
 EOF
-       test_cmp expect.err output.err
+
+test_expect_success 'test --parseopt help output' '
+       git rev-parse --parseopt -- -h > output < optionspec
+       test_cmp expect output
+'
+
+cat > expect <<EOF
+set -- --foo --bar 'ham' -- 'arg'
+EOF
+
+test_expect_success 'test --parseopt' '
+       git rev-parse --parseopt -- --foo --bar=ham arg < optionspec > output &&
+       test_cmp expect output
+'
+
+test_expect_success 'test --parseopt with mixed options and arguments' '
+       git rev-parse --parseopt -- --foo arg --bar=ham < optionspec > output &&
+       test_cmp expect output
+'
+
+cat > expect <<EOF
+set -- --foo -- 'arg' '--bar=ham'
+EOF
+
+test_expect_success 'test --parseopt with --' '
+       git rev-parse --parseopt -- --foo -- arg --bar=ham < optionspec > output &&
+       test_cmp expect output
+'
+
+test_expect_success 'test --parseopt --stop-at-non-option' '
+       git rev-parse --parseopt --stop-at-non-option -- --foo arg --bar=ham < optionspec > output &&
+       test_cmp expect output
+'
+
+cat > expect <<EOF
+set -- --foo -- '--' 'arg' '--bar=ham'
+EOF
+
+test_expect_success 'test --parseopt --keep-dashdash' '
+       git rev-parse --parseopt --keep-dashdash -- --foo -- arg --bar=ham < optionspec > output &&
+       test_cmp expect output
+'
+
+cat >expect <<EOF
+set -- --foo -- '--' 'arg' '--spam=ham'
+EOF
+
+test_expect_success 'test --parseopt --keep-dashdash --stop-at-non-option with --' '
+       git rev-parse --parseopt --keep-dashdash --stop-at-non-option -- --foo -- arg --spam=ham <optionspec >output &&
+       test_cmp expect output
+'
+
+cat > expect <<EOF
+set -- --foo -- 'arg' '--spam=ham'
+EOF
+
+test_expect_success 'test --parseopt --keep-dashdash --stop-at-non-option without --' '
+       git rev-parse --parseopt --keep-dashdash --stop-at-non-option -- --foo arg --spam=ham <optionspec >output &&
+       test_cmp expect output
 '
 
 test_done
index cc6539494737fff8a02460b6cdca3fccad8f770f..100f857b168b1d8b88ea1a1e7e31ba42a2beee6c 100755 (executable)
@@ -104,4 +104,15 @@ test_expect_success 'use --default' '
        test_must_fail git rev-parse --verify --default bar
 '
 
+test_expect_success 'master@{n} for various n' '
+       N=$(git reflog | wc -l) &&
+       Nm1=$((N-1)) &&
+       Np1=$((N+1)) &&
+       git rev-parse --verify master@{0} &&
+       git rev-parse --verify master@{1} &&
+       git rev-parse --verify master@{$Nm1} &&
+       test_must_fail git rev-parse --verify master@{$N} &&
+       test_must_fail git rev-parse --verify master@{$Np1}
+'
+
 test_done
diff --git a/t/t1506-rev-parse-diagnosis.sh b/t/t1506-rev-parse-diagnosis.sh
new file mode 100755 (executable)
index 0000000..0eeeb0e
--- /dev/null
@@ -0,0 +1,78 @@
+#!/bin/sh
+
+test_description='test git rev-parse diagnosis for invalid argument'
+
+exec </dev/null
+
+. ./test-lib.sh
+
+HASH_file=
+
+test_expect_success 'set up basic repo' '
+       echo one > file.txt &&
+       mkdir subdir &&
+       echo two > subdir/file.txt &&
+       echo three > subdir/file2.txt &&
+       git add . &&
+       git commit -m init &&
+       echo four > index-only.txt &&
+       git add index-only.txt &&
+       echo five > disk-only.txt
+'
+
+test_expect_success 'correct file objects' '
+       HASH_file=$(git rev-parse HEAD:file.txt) &&
+       git rev-parse HEAD:subdir/file.txt &&
+       git rev-parse :index-only.txt &&
+       (cd subdir &&
+        git rev-parse HEAD:subdir/file2.txt &&
+        test $HASH_file = $(git rev-parse HEAD:file.txt) &&
+        test $HASH_file = $(git rev-parse :file.txt) &&
+        test $HASH_file = $(git rev-parse :0:file.txt) )
+'
+
+test_expect_success 'incorrect revision id' '
+       test_must_fail git rev-parse foobar:file.txt 2>error &&
+       grep "Invalid object name '"'"'foobar'"'"'." error &&
+       test_must_fail git rev-parse foobar 2> error &&
+       grep "unknown revision or path not in the working tree." error
+'
+
+test_expect_success 'incorrect file in sha1:path' '
+       test_must_fail git rev-parse HEAD:nothing.txt 2> error &&
+       grep "fatal: Path '"'"'nothing.txt'"'"' does not exist in '"'"'HEAD'"'"'" error &&
+       test_must_fail git rev-parse HEAD:index-only.txt 2> error &&
+       grep "fatal: Path '"'"'index-only.txt'"'"' exists on disk, but not in '"'"'HEAD'"'"'." error &&
+       (cd subdir &&
+        test_must_fail git rev-parse HEAD:file2.txt 2> error &&
+        grep "Did you mean '"'"'HEAD:subdir/file2.txt'"'"'?" error )
+'
+
+test_expect_success 'incorrect file in :path and :N:path' '
+       test_must_fail git rev-parse :nothing.txt 2> error &&
+       grep "fatal: Path '"'"'nothing.txt'"'"' does not exist (neither on disk nor in the index)." error &&
+       test_must_fail git rev-parse :1:nothing.txt 2> error &&
+       grep "Path '"'"'nothing.txt'"'"' does not exist (neither on disk nor in the index)." error &&
+       test_must_fail git rev-parse :1:file.txt 2> error &&
+       grep "Did you mean '"'"':0:file.txt'"'"'?" error &&
+       (cd subdir &&
+        test_must_fail git rev-parse :1:file.txt 2> error &&
+        grep "Did you mean '"'"':0:file.txt'"'"'?" error &&
+        test_must_fail git rev-parse :file2.txt 2> error &&
+        grep "Did you mean '"'"':0:subdir/file2.txt'"'"'?" error &&
+        test_must_fail git rev-parse :2:file2.txt 2> error &&
+        grep "Did you mean '"'"':0:subdir/file2.txt'"'"'?" error) &&
+       test_must_fail git rev-parse :disk-only.txt 2> error &&
+       grep "fatal: Path '"'"'disk-only.txt'"'"' exists on disk, but not in the index." error
+'
+
+test_expect_success 'invalid @{n} reference' '
+       test_must_fail git rev-parse master@{99999} >output 2>error &&
+       test -z "$(cat output)" &&
+       grep "fatal: Log for [^ ]* only has [0-9][0-9]* entries." error  &&
+       test_must_fail git rev-parse --verify master@{99999} >output 2>error &&
+       test -z "$(cat output)" &&
+       grep "fatal: Log for [^ ]* only has [0-9][0-9]* entries." error
+'
+
+test_done
diff --git a/t/t1507-rev-parse-upstream.sh b/t/t1507-rev-parse-upstream.sh
new file mode 100755 (executable)
index 0000000..8c8dfda
--- /dev/null
@@ -0,0 +1,139 @@
+#!/bin/sh
+
+test_description='test <branch>@{upstream} syntax'
+
+. ./test-lib.sh
+
+
+test_expect_success 'setup' '
+
+       test_commit 1 &&
+       git checkout -b side &&
+       test_commit 2 &&
+       git checkout master &&
+       git clone . clone &&
+       test_commit 3 &&
+       (cd clone &&
+        test_commit 4 &&
+        git branch --track my-side origin/side)
+
+'
+
+full_name () {
+       (cd clone &&
+        git rev-parse --symbolic-full-name "$@")
+}
+
+commit_subject () {
+       (cd clone &&
+        git show -s --pretty=format:%s "$@")
+}
+
+test_expect_success '@{upstream} resolves to correct full name' '
+       test refs/remotes/origin/master = "$(full_name @{upstream})"
+'
+
+test_expect_success '@{u} resolves to correct full name' '
+       test refs/remotes/origin/master = "$(full_name @{u})"
+'
+
+test_expect_success 'my-side@{upstream} resolves to correct full name' '
+       test refs/remotes/origin/side = "$(full_name my-side@{u})"
+'
+
+test_expect_success 'my-side@{u} resolves to correct commit' '
+       git checkout side &&
+       test_commit 5 &&
+       (cd clone && git fetch) &&
+       test 2 = "$(commit_subject my-side)" &&
+       test 5 = "$(commit_subject my-side@{u})"
+'
+
+test_expect_success 'not-tracking@{u} fails' '
+       test_must_fail full_name non-tracking@{u} &&
+       (cd clone && git checkout --no-track -b non-tracking) &&
+       test_must_fail full_name non-tracking@{u}
+'
+
+test_expect_success '<branch>@{u}@{1} resolves correctly' '
+       test_commit 6 &&
+       (cd clone && git fetch) &&
+       test 5 = $(commit_subject my-side@{u}@{1})
+'
+
+test_expect_success '@{u} without specifying branch fails on a detached HEAD' '
+       git checkout HEAD^0 &&
+       test_must_fail git rev-parse @{u}
+'
+
+test_expect_success 'checkout -b new my-side@{u} forks from the same' '
+(
+       cd clone &&
+       git checkout -b new my-side@{u} &&
+       git rev-parse --symbolic-full-name my-side@{u} >expect &&
+       git rev-parse --symbolic-full-name new@{u} >actual &&
+       test_cmp expect actual
+)
+'
+
+test_expect_success 'merge my-side@{u} records the correct name' '
+(
+       sq="'\''" &&
+       cd clone || exit
+       git checkout master || exit
+       git branch -D new ;# can fail but is ok
+       git branch -t new my-side@{u} &&
+       git merge -s ours new@{u} &&
+       git show -s --pretty=format:%s >actual &&
+       echo "Merge remote branch ${sq}origin/side${sq}" >expect &&
+       test_cmp expect actual
+)
+'
+
+test_expect_success 'branch -d other@{u}' '
+       git checkout -t -b other master &&
+       git branch -d @{u} &&
+       git for-each-ref refs/heads/master >actual &&
+       >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'checkout other@{u}' '
+       git branch -f master HEAD &&
+       git checkout -t -b another master &&
+       git checkout @{u} &&
+       git symbolic-ref HEAD >actual &&
+       echo refs/heads/master >expect &&
+       test_cmp expect actual
+'
+
+cat >expect <<EOF
+commit 8f489d01d0cc65c3b0f09504ec50b5ed02a70bd5
+Reflog: master@{0} (C O Mitter <committer@example.com>)
+Reflog message: branch: Created from HEAD
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:15:13 2005 -0700
+
+    3
+EOF
+test_expect_success 'log -g other@{u}' '
+       git log -1 -g other@{u} >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<EOF
+commit 8f489d01d0cc65c3b0f09504ec50b5ed02a70bd5
+Reflog: master@{Thu Apr 7 15:17:13 2005 -0700} (C O Mitter <committer@example.com>)
+Reflog message: branch: Created from HEAD
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:15:13 2005 -0700
+
+    3
+EOF
+
+test_expect_success 'log -g other@{u}@{now}' '
+       git log -1 -g other@{u}@{now} >actual &&
+       test_cmp expect actual
+'
+
+test_done
diff --git a/t/t1508-at-combinations.sh b/t/t1508-at-combinations.sh
new file mode 100755 (executable)
index 0000000..d5d6244
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+test_description='test various @{X} syntax combinations together'
+. ./test-lib.sh
+
+check() {
+test_expect_${3:-success} "$1 = $2" "
+       echo '$2' >expect &&
+       git log -1 --format=%s '$1' >actual &&
+       test_cmp expect actual
+"
+}
+nonsense() {
+test_expect_${2:-success} "$1 is nonsensical" "
+       test_must_fail git log -1 '$1'
+"
+}
+fail() {
+       "$@" failure
+}
+
+test_expect_success 'setup' '
+       test_commit master-one &&
+       test_commit master-two &&
+       git checkout -b upstream-branch &&
+       test_commit upstream-one &&
+       test_commit upstream-two &&
+       git checkout -b old-branch &&
+       test_commit old-one &&
+       test_commit old-two &&
+       git checkout -b new-branch &&
+       test_commit new-one &&
+       test_commit new-two &&
+       git config branch.old-branch.remote . &&
+       git config branch.old-branch.merge refs/heads/master &&
+       git config branch.new-branch.remote . &&
+       git config branch.new-branch.merge refs/heads/upstream-branch
+'
+
+check HEAD new-two
+check "@{1}" new-one
+check "@{-1}" old-two
+check "@{-1}@{1}" old-one
+check "@{u}" upstream-two
+check "@{u}@{1}" upstream-one
+check "@{-1}@{u}" master-two
+check "@{-1}@{u}@{1}" master-one
+nonsense "@{u}@{-1}"
+nonsense "@{1}@{u}"
+
+test_done
diff --git a/t/t1509-root-worktree.sh b/t/t1509-root-worktree.sh
new file mode 100755 (executable)
index 0000000..7f60fd0
--- /dev/null
@@ -0,0 +1,249 @@
+#!/bin/sh
+
+test_description='Test Git when git repository is located at root
+
+This test requires write access in root. Do not bother if you do not
+have a throwaway chroot or VM.
+
+Script t1509/prepare-chroot.sh may help you setup chroot, then you
+can chroot in and execute this test from there.
+'
+
+. ./test-lib.sh
+
+test_cmp_val() {
+       echo "$1" > expected
+       echo "$2" > result
+       test_cmp expected result
+}
+
+test_vars() {
+       test_expect_success "$1: gitdir" '
+               test_cmp_val "'"$2"'" "$(git rev-parse --git-dir)"
+       '
+
+       test_expect_success "$1: worktree" '
+               test_cmp_val "'"$3"'" "$(git rev-parse --show-toplevel)"
+       '
+
+       test_expect_success "$1: prefix" '
+               test_cmp_val "'"$4"'" "$(git rev-parse --show-prefix)"
+       '
+}
+
+test_foobar_root() {
+       test_expect_success 'add relative' '
+               test -z "$(cd / && git ls-files)" &&
+               git add foo/foome &&
+               git add foo/bar/barme &&
+               git add me &&
+               ( cd / && git ls-files --stage ) > result &&
+               test_cmp /ls.expected result &&
+               rm "$(git rev-parse --git-dir)/index"
+       '
+
+       test_expect_success 'add absolute' '
+               test -z "$(cd / && git ls-files)" &&
+               git add /foo/foome &&
+               git add /foo/bar/barme &&
+               git add /me &&
+               ( cd / && git ls-files --stage ) > result &&
+               test_cmp /ls.expected result &&
+               rm "$(git rev-parse --git-dir)/index"
+       '
+
+}
+
+test_foobar_foo() {
+       test_expect_success 'add relative' '
+               test -z "$(cd / && git ls-files)" &&
+               git add foome &&
+               git add bar/barme &&
+               git add ../me &&
+               ( cd / && git ls-files --stage ) > result &&
+               test_cmp /ls.expected result &&
+               rm "$(git rev-parse --git-dir)/index"
+       '
+
+       test_expect_success 'add absolute' '
+               test -z "$(cd / && git ls-files)" &&
+               git add /foo/foome &&
+               git add /foo/bar/barme &&
+               git add /me &&
+               ( cd / && git ls-files --stage ) > result &&
+               test_cmp /ls.expected result &&
+               rm "$(git rev-parse --git-dir)/index"
+       '
+}
+
+test_foobar_foobar() {
+       test_expect_success 'add relative' '
+               test -z "$(cd / && git ls-files)" &&
+               git add ../foome &&
+               git add barme &&
+               git add ../../me &&
+               ( cd / && git ls-files --stage ) > result &&
+               test_cmp /ls.expected result &&
+               rm "$(git rev-parse --git-dir)/index"
+       '
+
+       test_expect_success 'add absolute' '
+               test -z "$(cd / && git ls-files)" &&
+               git add /foo/foome &&
+               git add /foo/bar/barme &&
+               git add /me &&
+               ( cd / && git ls-files --stage ) > result &&
+               test_cmp /ls.expected result &&
+               rm "$(git rev-parse --git-dir)/index"
+       '
+}
+
+if ! test_have_prereq POSIXPERM || ! [ -w / ]; then
+       skip_all="Dangerous test skipped. Read this test if you want to execute it"
+       test_done
+fi
+
+if [ "$IKNOWWHATIAMDOING" != "YES" ]; then
+       skip_all="You must set env var IKNOWWHATIAMDOING=YES in order to run this test"
+       test_done
+fi
+
+if [ "$UID" = 0 ]; then
+       skip_all="No you can't run this with root"
+       test_done
+fi
+
+ONE_SHA1=d00491fd7e5bb6fa28c517a0bb32b8b506539d4d
+
+test_expect_success 'setup' '
+       rm -rf /foo
+       mkdir /foo &&
+       mkdir /foo/bar &&
+       echo 1 > /foo/foome &&
+       echo 1 > /foo/bar/barme &&
+       echo 1 > /me
+'
+
+say "GIT_DIR absolute, GIT_WORK_TREE set"
+
+test_expect_success 'go to /' 'cd /'
+
+cat >ls.expected <<EOF
+100644 $ONE_SHA1 0     foo/bar/barme
+100644 $ONE_SHA1 0     foo/foome
+100644 $ONE_SHA1 0     me
+EOF
+
+export GIT_DIR="$TRASH_DIRECTORY/.git"
+export GIT_WORK_TREE=/
+
+test_vars 'abs gitdir, root' "$GIT_DIR" "/" ""
+test_foobar_root
+
+test_expect_success 'go to /foo' 'cd /foo'
+
+test_vars 'abs gitdir, foo' "$GIT_DIR" "/" "foo/"
+test_foobar_foo
+
+test_expect_success 'go to /foo/bar' 'cd /foo/bar'
+
+test_vars 'abs gitdir, foo/bar' "$GIT_DIR" "/" "foo/bar/"
+test_foobar_foobar
+
+say "GIT_DIR relative, GIT_WORK_TREE set"
+
+test_expect_success 'go to /' 'cd /'
+
+export GIT_DIR="$(echo $TRASH_DIRECTORY|sed 's,^/,,')/.git"
+export GIT_WORK_TREE=/
+
+test_vars 'rel gitdir, root' "$GIT_DIR" "/" ""
+test_foobar_root
+
+test_expect_success 'go to /foo' 'cd /foo'
+
+export GIT_DIR="../$TRASH_DIRECTORY/.git"
+export GIT_WORK_TREE=/
+
+test_vars 'rel gitdir, foo' "$TRASH_DIRECTORY/.git" "/" "foo/"
+test_foobar_foo
+
+test_expect_success 'go to /foo/bar' 'cd /foo/bar'
+
+export GIT_DIR="../../$TRASH_DIRECTORY/.git"
+export GIT_WORK_TREE=/
+
+test_vars 'rel gitdir, foo/bar' "$TRASH_DIRECTORY/.git" "/" "foo/bar/"
+test_foobar_foobar
+
+say "GIT_DIR relative, GIT_WORK_TREE relative"
+
+test_expect_success 'go to /' 'cd /'
+
+export GIT_DIR="$(echo $TRASH_DIRECTORY|sed 's,^/,,')/.git"
+export GIT_WORK_TREE=.
+
+test_vars 'rel gitdir, root' "$GIT_DIR" "/" ""
+test_foobar_root
+
+test_expect_success 'go to /' 'cd /foo'
+
+export GIT_DIR="../$TRASH_DIRECTORY/.git"
+export GIT_WORK_TREE=..
+
+test_vars 'rel gitdir, foo' "$TRASH_DIRECTORY/.git" "/" "foo/"
+test_foobar_foo
+
+test_expect_success 'go to /foo/bar' 'cd /foo/bar'
+
+export GIT_DIR="../../$TRASH_DIRECTORY/.git"
+export GIT_WORK_TREE=../..
+
+test_vars 'rel gitdir, foo/bar' "$TRASH_DIRECTORY/.git" "/" "foo/bar/"
+test_foobar_foobar
+
+say ".git at root"
+
+unset GIT_DIR
+unset GIT_WORK_TREE
+
+test_expect_success 'go to /' 'cd /'
+test_expect_success 'setup' '
+       rm -rf /.git
+       echo "Initialized empty Git repository in /.git/" > expected &&
+       git init > result &&
+       test_cmp expected result
+'
+
+test_vars 'auto gitdir, root' ".git" "/" ""
+test_foobar_root
+
+test_expect_success 'go to /foo' 'cd /foo'
+test_vars 'auto gitdir, foo' "/.git" "/" "foo/"
+test_foobar_foo
+
+test_expect_success 'go to /foo/bar' 'cd /foo/bar'
+test_vars 'auto gitdir, foo/bar' "/.git" "/" "foo/bar/"
+test_foobar_foobar
+
+test_expect_success 'cleanup' 'rm -rf /.git'
+
+say "auto bare gitdir"
+
+# DESTROYYYYY!!!!!
+test_expect_success 'setup' '
+       rm -rf /refs /objects /info /hooks
+       rm /*
+       cd / &&
+       echo "Initialized empty Git repository in /" > expected &&
+       git init --bare > result &&
+       test_cmp expected result
+'
+
+test_vars 'auto gitdir, root' "." "" ""
+
+test_expect_success 'go to /foo' 'cd /foo'
+
+test_vars 'auto gitdir, root' "/" "" ""
+
+test_done
diff --git a/t/t1509/excludes b/t/t1509/excludes
new file mode 100644 (file)
index 0000000..d4d21d3
--- /dev/null
@@ -0,0 +1,14 @@
+*.o
+*~
+*.bak
+*.c
+*.h
+.git
+contrib
+Documentation
+git-gui
+gitk-git
+gitweb
+t/t4013
+t/t5100
+t/t5515
diff --git a/t/t1509/prepare-chroot.sh b/t/t1509/prepare-chroot.sh
new file mode 100755 (executable)
index 0000000..c5334a8
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+die() {
+       echo >&2 "$@"
+       exit 1
+}
+
+xmkdir() {
+       while [ -n "$1" ]; do
+               [ -d "$1" ] || mkdir "$1" || die "Unable to mkdir $1"
+               shift
+       done
+}
+
+R="$1"
+
+[ -n "$R" ] || die "Usage: prepare-chroot.sh <root>"
+[ -x git ] || die "This script needs to be executed at git source code's top directory"
+[ -x /bin/busybox ] || die "You need busybox"
+
+xmkdir "$R" "$R/bin" "$R/etc" "$R/lib" "$R/dev"
+[ -c "$R/dev/null" ] || die "/dev/null is missing. Do mknod $R/dev/null c 1 3 && chmod 666 $R/dev/null"
+echo "root:x:0:0:root:/:/bin/sh" > "$R/etc/passwd"
+echo "$(id -nu):x:$(id -u):$(id -g)::$(pwd)/t:/bin/sh" >> "$R/etc/passwd"
+echo "root::0:root" > "$R/etc/group"
+echo "$(id -ng)::$(id -g):$(id -nu)" >> "$R/etc/group"
+
+[ -x "$R/bin/busybox" ] || cp /bin/busybox "$R/bin/busybox"
+[ -x "$R/bin/sh" ] || ln -s /bin/busybox "$R/bin/sh"
+[ -x "$R/bin/su" ] || ln -s /bin/busybox "$R/bin/su"
+
+mkdir -p "$R$(pwd)"
+rsync --exclude-from t/t1509/excludes -Ha . "$R$(pwd)"
+ldd git | grep '/' | sed 's,.*\s\(/[^ ]*\).*,\1,' | while read i; do
+       mkdir -p "$R$(dirname $i)"
+       cp "$i" "$R/$i"
+done
+echo "Execute this in root: 'chroot $R /bin/su - $(id -nu)'"
index f7e1a735ec8699616280a086f59dc50c078bfaa7..de3edb5d571ea83263f5133e751704ab0ba580c8 100755 (executable)
@@ -48,4 +48,13 @@ test_expect_success \
     'git checkout-index conflicting paths.' \
     'test -f path0 && test -d path1 && test -f path1/file1'
 
+test_expect_success SYMLINKS 'checkout-index -f twice with --prefix' '
+       mkdir -p tar/get &&
+       ln -s tar/get there &&
+       echo first &&
+       git checkout-index -a -f --prefix=there/ &&
+       echo second &&
+       git checkout-index -a -f --prefix=there/
+'
+
 test_done
index 20f33436d00077b64dcc855fc263cd5d0efcca38..a74ee227b80391b7d8e2b86159a8374663cb674d 100755 (executable)
@@ -6,13 +6,7 @@ test_description='git checkout to switch between branches with symlink<->dir'
 
 . ./test-lib.sh
 
-if ! test_have_prereq SYMLINKS
-then
-       say "symbolic links not supported - skipping tests"
-       test_done
-fi
-
-test_expect_success setup '
+test_expect_success SYMLINKS setup '
 
        mkdir frotz &&
        echo hello >frotz/filfre &&
@@ -38,16 +32,18 @@ test_expect_success setup '
 
 '
 
-test_expect_success 'switch from symlink to dir' '
+test_expect_success SYMLINKS 'switch from symlink to dir' '
 
        git checkout master
 
 '
 
-rm -fr frotz xyzzy nitfol &&
-git checkout -f master || exit
+test_expect_success SYMLINKS 'Remove temporary directories & switch to master' '
+       rm -fr frotz xyzzy nitfol &&
+       git checkout -f master
+'
 
-test_expect_success 'switch from dir to symlink' '
+test_expect_success SYMLINKS 'switch from dir to symlink' '
 
        git checkout side
 
index 87b30a268c66848726baec5676e73b8db686806d..b44de9dc623fc923941b61719f445170602e21be 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='checkout can switch to last branch'
+test_description='checkout can switch to last branch and merge base'
 
 . ./test-lib.sh
 
@@ -91,4 +91,29 @@ test_expect_success 'switch to twelfth from the last' '
        test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch13"
 '
 
+test_expect_success 'merge base test setup' '
+       git checkout -b another other &&
+       echo "hello again" >>world &&
+       git add world &&
+       git commit -m third
+'
+
+test_expect_success 'another...master' '
+       git checkout another &&
+       git checkout another...master &&
+       test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)"
+'
+
+test_expect_success '...master' '
+       git checkout another &&
+       git checkout ...master &&
+       test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)"
+'
+
+test_expect_success 'master...' '
+       git checkout another &&
+       git checkout master... &&
+       test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)"
+'
+
 test_done
index fda3f0af7eb0fcaf7d0d61ff10a22e2dedaa416b..70edbb33e26c0ee6d5cec81d2417c8a830d6fab1 100755 (executable)
@@ -39,4 +39,27 @@ test_expect_success '"checkout <submodule>" updates the index only' '
        git diff-files --quiet
 '
 
+test_expect_success '"checkout <submodule>" honors diff.ignoreSubmodules' '
+       git config diff.ignoreSubmodules dirty &&
+       echo x> submodule/untracked &&
+       git checkout HEAD >actual 2>&1 &&
+       ! test -s actual
+'
+
+test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .gitmodules' '
+       git config diff.ignoreSubmodules none &&
+       git config -f .gitmodules submodule.submodule.path submodule &&
+       git config -f .gitmodules submodule.submodule.ignore untracked &&
+       git checkout HEAD >actual 2>&1 &&
+       ! test -s actual
+'
+
+test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .git/config' '
+       git config -f .gitmodules submodule.submodule.ignore none &&
+       git config submodule.submodule.path submodule &&
+       git config submodule.submodule.ignore all &&
+       git checkout HEAD >actual 2>&1 &&
+       ! test -s actual
+'
+
 test_done
diff --git a/t/t2014-switch.sh b/t/t2014-switch.sh
new file mode 100755 (executable)
index 0000000..ccfb147
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+test_description='Peter MacMillan'
+. ./test-lib.sh
+
+test_expect_success setup '
+       echo Hello >file &&
+       git add file &&
+       test_tick &&
+       git commit -m V1 &&
+       echo Hello world >file &&
+       git add file &&
+       git checkout -b other
+'
+
+test_expect_success 'check all changes are staged' '
+       git diff --exit-code
+'
+
+test_expect_success 'second commit' '
+       git commit -m V2
+'
+
+test_expect_success 'check' '
+       git diff --cached --exit-code
+'
+
+test_done
diff --git a/t/t2015-checkout-unborn.sh b/t/t2015-checkout-unborn.sh
new file mode 100755 (executable)
index 0000000..c551d39
--- /dev/null
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+test_description='checkout from unborn branch protects contents'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       mkdir parent &&
+       (cd parent &&
+        git init &&
+        echo content >file &&
+        git add file &&
+        git commit -m base
+       ) &&
+       git fetch parent master:origin
+'
+
+test_expect_success 'checkout from unborn preserves untracked files' '
+       echo precious >expect &&
+       echo precious >file &&
+       test_must_fail git checkout -b new origin &&
+       test_cmp expect file
+'
+
+test_expect_success 'checkout from unborn preserves index contents' '
+       echo precious >expect &&
+       echo precious >file &&
+       git add file &&
+       test_must_fail git checkout -b new origin &&
+       test_cmp expect file &&
+       git show :file >file &&
+       test_cmp expect file
+'
+
+test_expect_success 'checkout from unborn merges identical index contents' '
+       echo content >file &&
+       git add file &&
+       git checkout -b new origin
+'
+
+test_done
diff --git a/t/t2016-checkout-patch.sh b/t/t2016-checkout-patch.sh
new file mode 100755 (executable)
index 0000000..a463b13
--- /dev/null
@@ -0,0 +1,115 @@
+#!/bin/sh
+
+test_description='git checkout --patch'
+
+. ./lib-patch-mode.sh
+
+test_expect_success PERL 'setup' '
+       mkdir dir &&
+       echo parent > dir/foo &&
+       echo dummy > bar &&
+       git add bar dir/foo &&
+       git commit -m initial &&
+       test_tick &&
+       test_commit second dir/foo head &&
+       set_and_save_state bar bar_work bar_index &&
+       save_head
+'
+
+# note: bar sorts before dir/foo, so the first 'n' is always to skip 'bar'
+
+test_expect_success PERL 'saying "n" does nothing' '
+       set_and_save_state dir/foo work head &&
+       (echo n; echo n) | git checkout -p &&
+       verify_saved_state bar &&
+       verify_saved_state dir/foo
+'
+
+test_expect_success PERL 'git checkout -p' '
+       (echo n; echo y) | git checkout -p &&
+       verify_saved_state bar &&
+       verify_state dir/foo head head
+'
+
+test_expect_success PERL 'git checkout -p with staged changes' '
+       set_state dir/foo work index
+       (echo n; echo y) | git checkout -p &&
+       verify_saved_state bar &&
+       verify_state dir/foo index index
+'
+
+test_expect_success PERL 'git checkout -p HEAD with NO staged changes: abort' '
+       set_and_save_state dir/foo work head &&
+       (echo n; echo y; echo n) | git checkout -p HEAD &&
+       verify_saved_state bar &&
+       verify_saved_state dir/foo
+'
+
+test_expect_success PERL 'git checkout -p HEAD with NO staged changes: apply' '
+       (echo n; echo y; echo y) | git checkout -p HEAD &&
+       verify_saved_state bar &&
+       verify_state dir/foo head head
+'
+
+test_expect_success PERL 'git checkout -p HEAD with change already staged' '
+       set_state dir/foo index index &&
+       # the third n is to get out in case it mistakenly does not apply
+       (echo n; echo y; echo n) | git checkout -p HEAD &&
+       verify_saved_state bar &&
+       verify_state dir/foo head head
+'
+
+test_expect_success PERL 'git checkout -p HEAD^' '
+       # the third n is to get out in case it mistakenly does not apply
+       (echo n; echo y; echo n) | git checkout -p HEAD^ &&
+       verify_saved_state bar &&
+       verify_state dir/foo parent parent
+'
+
+test_expect_success PERL 'git checkout -p handles deletion' '
+       set_state dir/foo work index &&
+       rm dir/foo &&
+       (echo n; echo y) | git checkout -p &&
+       verify_saved_state bar &&
+       verify_state dir/foo index index
+'
+
+# The idea in the rest is that bar sorts first, so we always say 'y'
+# first and if the path limiter fails it'll apply to bar instead of
+# dir/foo.  There's always an extra 'n' to reject edits to dir/foo in
+# the failure case (and thus get out of the loop).
+
+test_expect_success PERL 'path limiting works: dir' '
+       set_state dir/foo work head &&
+       (echo y; echo n) | git checkout -p dir &&
+       verify_saved_state bar &&
+       verify_state dir/foo head head
+'
+
+test_expect_success PERL 'path limiting works: -- dir' '
+       set_state dir/foo work head &&
+       (echo y; echo n) | git checkout -p -- dir &&
+       verify_saved_state bar &&
+       verify_state dir/foo head head
+'
+
+test_expect_success PERL 'path limiting works: HEAD^ -- dir' '
+       # the third n is to get out in case it mistakenly does not apply
+       (echo y; echo n; echo n) | git checkout -p HEAD^ -- dir &&
+       verify_saved_state bar &&
+       verify_state dir/foo parent parent
+'
+
+test_expect_success PERL 'path limiting works: foo inside dir' '
+       set_state dir/foo work head &&
+       # the third n is to get out in case it mistakenly does not apply
+       (echo y; echo n; echo n) | (cd dir && git checkout -p foo) &&
+       verify_saved_state bar &&
+       verify_state dir/foo head head
+'
+
+test_expect_success PERL 'none of this moved HEAD' '
+       verify_saved_head
+'
+
+test_done
diff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh
new file mode 100755 (executable)
index 0000000..2d2f63f
--- /dev/null
@@ -0,0 +1,119 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Erick Mattos
+#
+
+test_description='git checkout --orphan
+
+Main Tests for --orphan functionality.'
+
+. ./test-lib.sh
+
+TEST_FILE=foo
+
+test_expect_success 'Setup' '
+       echo "Initial" >"$TEST_FILE" &&
+       git add "$TEST_FILE" &&
+       git commit -m "First Commit"
+       test_tick &&
+       echo "State 1" >>"$TEST_FILE" &&
+       git add "$TEST_FILE" &&
+       test_tick &&
+       git commit -m "Second Commit"
+'
+
+test_expect_success '--orphan creates a new orphan branch from HEAD' '
+       git checkout --orphan alpha &&
+       test_must_fail git rev-parse --verify HEAD &&
+       test "refs/heads/alpha" = "$(git symbolic-ref HEAD)" &&
+       test_tick &&
+       git commit -m "Third Commit" &&
+       test_must_fail git rev-parse --verify HEAD^ &&
+       git diff-tree --quiet master alpha
+'
+
+test_expect_success '--orphan creates a new orphan branch from <start_point>' '
+       git checkout master &&
+       git checkout --orphan beta master^ &&
+       test_must_fail git rev-parse --verify HEAD &&
+       test "refs/heads/beta" = "$(git symbolic-ref HEAD)" &&
+       test_tick &&
+       git commit -m "Fourth Commit" &&
+       test_must_fail git rev-parse --verify HEAD^ &&
+       git diff-tree --quiet master^ beta
+'
+
+test_expect_success '--orphan must be rejected with -b' '
+       git checkout master &&
+       test_must_fail git checkout --orphan new -b newer &&
+       test refs/heads/master = "$(git symbolic-ref HEAD)"
+'
+
+test_expect_success '--orphan must be rejected with -t' '
+       git checkout master &&
+       test_must_fail git checkout --orphan new -t master &&
+       test refs/heads/master = "$(git symbolic-ref HEAD)"
+'
+
+test_expect_success '--orphan ignores branch.autosetupmerge' '
+       git checkout master &&
+       git config branch.autosetupmerge always &&
+       git checkout --orphan gamma &&
+       test -z "$(git config branch.gamma.merge)" &&
+       test refs/heads/gamma = "$(git symbolic-ref HEAD)" &&
+       test_must_fail git rev-parse --verify HEAD^
+'
+
+test_expect_success '--orphan makes reflog by default' '
+       git checkout master &&
+       git config --unset core.logAllRefUpdates &&
+       git checkout --orphan delta &&
+       test_must_fail git rev-parse --verify delta@{0} &&
+       git commit -m Delta &&
+       git rev-parse --verify delta@{0}
+'
+
+test_expect_success '--orphan does not make reflog when core.logAllRefUpdates = false' '
+       git checkout master &&
+       git config core.logAllRefUpdates false &&
+       git checkout --orphan epsilon &&
+       test_must_fail git rev-parse --verify epsilon@{0} &&
+       git commit -m Epsilon &&
+       test_must_fail git rev-parse --verify epsilon@{0}
+'
+
+test_expect_success '--orphan with -l makes reflog when core.logAllRefUpdates = false' '
+       git checkout master &&
+       git checkout -l --orphan zeta &&
+       test_must_fail git rev-parse --verify zeta@{0} &&
+       git commit -m Zeta &&
+       git rev-parse --verify zeta@{0}
+'
+
+test_expect_success 'giving up --orphan not committed when -l and core.logAllRefUpdates = false deletes reflog' '
+       git checkout master &&
+       git checkout -l --orphan eta &&
+       test_must_fail git rev-parse --verify eta@{0} &&
+       git checkout master &&
+       test_must_fail git rev-parse --verify eta@{0}
+'
+
+test_expect_success '--orphan is rejected with an existing name' '
+       git checkout master &&
+       test_must_fail git checkout --orphan master &&
+       test refs/heads/master = "$(git symbolic-ref HEAD)"
+'
+
+test_expect_success '--orphan refuses to switch if a merge is needed' '
+       git checkout master &&
+       git reset --hard &&
+       echo local >>"$TEST_FILE" &&
+       cat "$TEST_FILE" >"$TEST_FILE.saved" &&
+       test_must_fail git checkout --orphan new master^ &&
+       test refs/heads/master = "$(git symbolic-ref HEAD)" &&
+       test_cmp "$TEST_FILE" "$TEST_FILE.saved" &&
+       git diff-index --quiet --cached HEAD &&
+       git reset --hard
+'
+
+test_done
diff --git a/t/t2018-checkout-branch.sh b/t/t2018-checkout-branch.sh
new file mode 100755 (executable)
index 0000000..fa69016
--- /dev/null
@@ -0,0 +1,172 @@
+#!/bin/sh
+
+test_description='checkout '
+
+. ./test-lib.sh
+
+# Arguments: <branch> <sha> [<checkout options>]
+#
+# Runs "git checkout" to switch to <branch>, testing that
+#
+#   1) we are on the specified branch, <branch>;
+#   2) HEAD is <sha>; if <sha> is not specified, the old HEAD is used.
+#
+# If <checkout options> is not specified, "git checkout" is run with -b.
+do_checkout() {
+       exp_branch=$1 &&
+       exp_ref="refs/heads/$exp_branch" &&
+
+       # if <sha> is not specified, use HEAD.
+       exp_sha=${2:-$(git rev-parse --verify HEAD)} &&
+
+       # default options for git checkout: -b
+       if [ -z "$3" ]; then
+               opts="-b"
+       else
+               opts="$3"
+       fi
+
+       git checkout $opts $exp_branch $exp_sha &&
+
+       test $exp_ref = $(git rev-parse --symbolic-full-name HEAD) &&
+       test $exp_sha = $(git rev-parse --verify HEAD)
+}
+
+test_dirty_unmergeable() {
+       ! git diff --exit-code >/dev/null
+}
+
+setup_dirty_unmergeable() {
+       echo >>file1 change2
+}
+
+test_dirty_mergeable() {
+       ! git diff --cached --exit-code >/dev/null
+}
+
+setup_dirty_mergeable() {
+       echo >file2 file2 &&
+       git add file2
+}
+
+test_expect_success 'setup' '
+       test_commit initial file1 &&
+       HEAD1=$(git rev-parse --verify HEAD) &&
+
+       test_commit change1 file1 &&
+       HEAD2=$(git rev-parse --verify HEAD) &&
+
+       git branch -m branch1
+'
+
+test_expect_success 'checkout -b to a new branch, set to HEAD' '
+       do_checkout branch2
+'
+
+test_expect_success 'checkout -b to a new branch, set to an explicit ref' '
+       git checkout branch1 &&
+       git branch -D branch2 &&
+
+       do_checkout branch2 $HEAD1
+'
+
+test_expect_success 'checkout -b to a new branch with unmergeable changes fails' '
+       git checkout branch1 &&
+
+       # clean up from previous test
+       git branch -D branch2 &&
+
+       setup_dirty_unmergeable &&
+       test_must_fail do_checkout branch2 $HEAD1 &&
+       test_dirty_unmergeable
+'
+
+test_expect_success 'checkout -f -b to a new branch with unmergeable changes discards changes' '
+       # still dirty and on branch1
+       do_checkout branch2 $HEAD1 "-f -b" &&
+       test_must_fail test_dirty_unmergeable
+'
+
+test_expect_success 'checkout -b to a new branch preserves mergeable changes' '
+       git checkout branch1 &&
+
+       # clean up from previous test
+       git branch -D branch2 &&
+
+       setup_dirty_mergeable &&
+       do_checkout branch2 $HEAD1 &&
+       test_dirty_mergeable
+'
+
+test_expect_success 'checkout -f -b to a new branch with mergeable changes discards changes' '
+       # clean up from previous test
+       git reset --hard &&
+
+       git checkout branch1 &&
+
+       # clean up from previous test
+       git branch -D branch2 &&
+
+       setup_dirty_mergeable &&
+       do_checkout branch2 $HEAD1 "-f -b" &&
+       test_must_fail test_dirty_mergeable
+'
+
+test_expect_success 'checkout -b to an existing branch fails' '
+       git reset --hard HEAD &&
+
+       test_must_fail do_checkout branch2 $HEAD2
+'
+
+test_expect_success 'checkout -B to an existing branch resets branch to HEAD' '
+       git checkout branch1 &&
+
+       do_checkout branch2 "" -B
+'
+
+test_expect_success 'checkout -B to an existing branch from detached HEAD resets branch to HEAD' '
+       git checkout $(git rev-parse --verify HEAD) &&
+
+       do_checkout branch2 "" -B
+'
+
+test_expect_success 'checkout -B to an existing branch with an explicit ref resets branch to that ref' '
+       git checkout branch1 &&
+
+       do_checkout branch2 $HEAD1 -B
+'
+
+test_expect_success 'checkout -B to an existing branch with unmergeable changes fails' '
+       git checkout branch1 &&
+
+       setup_dirty_unmergeable &&
+       test_must_fail do_checkout branch2 $HEAD1 -B &&
+       test_dirty_unmergeable
+'
+
+test_expect_success 'checkout -f -B to an existing branch with unmergeable changes discards changes' '
+       # still dirty and on branch1
+       do_checkout branch2 $HEAD1 "-f -B" &&
+       test_must_fail test_dirty_unmergeable
+'
+
+test_expect_success 'checkout -B to an existing branch preserves mergeable changes' '
+       git checkout branch1 &&
+
+       setup_dirty_mergeable &&
+       do_checkout branch2 $HEAD1 -B &&
+       test_dirty_mergeable
+'
+
+test_expect_success 'checkout -f -B to an existing branch with mergeable changes discards changes' '
+       # clean up from previous test
+       git reset --hard &&
+
+       git checkout branch1 &&
+
+       setup_dirty_mergeable &&
+       do_checkout branch2 $HEAD1 "-f -B" &&
+       test_must_fail test_dirty_mergeable
+'
+
+test_done
diff --git a/t/t2030-unresolve-info.sh b/t/t2030-unresolve-info.sh
new file mode 100755 (executable)
index 0000000..cb7effe
--- /dev/null
@@ -0,0 +1,170 @@
+#!/bin/sh
+
+test_description='undoing resolution'
+
+. ./test-lib.sh
+
+check_resolve_undo () {
+       msg=$1
+       shift
+       while case $# in
+       0)      break ;;
+       1|2|3)  die "Bug in check-resolve-undo test" ;;
+       esac
+       do
+               path=$1
+               shift
+               for stage in 1 2 3
+               do
+                       sha1=$1
+                       shift
+                       case "$sha1" in
+                       '') continue ;;
+                       esac
+                       sha1=$(git rev-parse --verify "$sha1")
+                       printf "100644 %s %s\t%s\n" $sha1 $stage $path
+               done
+       done >"$msg.expect" &&
+       git ls-files --resolve-undo >"$msg.actual" &&
+       test_cmp "$msg.expect" "$msg.actual"
+}
+
+prime_resolve_undo () {
+       git reset --hard &&
+       git checkout second^0 &&
+       test_tick &&
+       test_must_fail git merge third^0 &&
+       echo merge does not leave anything &&
+       check_resolve_undo empty &&
+       echo different >fi/le &&
+       git add fi/le &&
+       echo resolving records &&
+       check_resolve_undo recorded fi/le initial:fi/le second:fi/le third:fi/le
+}
+
+test_expect_success setup '
+       mkdir fi &&
+       test_commit initial fi/le first &&
+       git branch side &&
+       git branch another &&
+       test_commit second fi/le second &&
+       git checkout side &&
+       test_commit third fi/le third &&
+       git checkout another &&
+       test_commit fourth fi/le fourth &&
+       git checkout master
+'
+
+test_expect_success 'add records switch clears' '
+       prime_resolve_undo &&
+       test_tick &&
+       git commit -m merged &&
+       echo committing keeps &&
+       check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le &&
+       git checkout second^0 &&
+       echo switching clears &&
+       check_resolve_undo cleared
+'
+
+test_expect_success 'rm records reset clears' '
+       prime_resolve_undo &&
+       test_tick &&
+       git commit -m merged &&
+       echo committing keeps &&
+       check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le &&
+
+       echo merge clears upfront &&
+       test_must_fail git merge fourth^0 &&
+       check_resolve_undo nuked &&
+
+       git rm -f fi/le &&
+       echo resolving records &&
+       check_resolve_undo recorded fi/le initial:fi/le HEAD:fi/le fourth:fi/le &&
+
+       git reset --hard &&
+       echo resetting discards &&
+       check_resolve_undo discarded
+'
+
+test_expect_success 'plumbing clears' '
+       prime_resolve_undo &&
+       test_tick &&
+       git commit -m merged &&
+       echo committing keeps &&
+       check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le &&
+
+       echo plumbing clear &&
+       git update-index --clear-resolve-undo &&
+       check_resolve_undo cleared
+'
+
+test_expect_success 'add records checkout -m undoes' '
+       prime_resolve_undo &&
+       git diff HEAD &&
+       git checkout --conflict=merge fi/le &&
+       echo checkout used the record and removed it &&
+       check_resolve_undo removed &&
+       echo the index and the work tree is unmerged again &&
+       git diff >actual &&
+       grep "^++<<<<<<<" actual
+'
+
+test_expect_success 'unmerge with plumbing' '
+       prime_resolve_undo &&
+       git update-index --unresolve fi/le &&
+       git ls-files -u >actual &&
+       test $(wc -l <actual) = 3
+'
+
+test_expect_success 'rerere and rerere forget' '
+       mkdir .git/rr-cache &&
+       prime_resolve_undo &&
+       echo record the resolution &&
+       git rerere &&
+       rerere_id=$(cd .git/rr-cache && echo */postimage) &&
+       rerere_id=${rerere_id%/postimage} &&
+       test -f .git/rr-cache/$rerere_id/postimage &&
+       git checkout -m fi/le &&
+       echo resurrect the conflict &&
+       grep "^=======" fi/le &&
+       echo reresolve the conflict &&
+       git rerere &&
+       test "z$(cat fi/le)" = zdifferent &&
+       echo register the resolution again &&
+       git add fi/le &&
+       check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le &&
+       test -z "$(git ls-files -u)" &&
+       git rerere forget fi/le &&
+       ! test -f .git/rr-cache/$rerere_id/postimage &&
+       tr "\0" "\n" <.git/MERGE_RR >actual &&
+       echo "$rerere_id        fi/le" >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'rerere and rerere forget (subdirectory)' '
+       rm -fr .git/rr-cache &&
+       mkdir .git/rr-cache &&
+       prime_resolve_undo &&
+       echo record the resolution &&
+       (cd fi && git rerere) &&
+       rerere_id=$(cd .git/rr-cache && echo */postimage) &&
+       rerere_id=${rerere_id%/postimage} &&
+       test -f .git/rr-cache/$rerere_id/postimage &&
+       (cd fi && git checkout -m le) &&
+       echo resurrect the conflict &&
+       grep "^=======" fi/le &&
+       echo reresolve the conflict &&
+       (cd fi && git rerere) &&
+       test "z$(cat fi/le)" = zdifferent &&
+       echo register the resolution again &&
+       (cd fi && git add le) &&
+       check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le &&
+       test -z "$(git ls-files -u)" &&
+       (cd fi && git rerere forget le) &&
+       ! test -f .git/rr-cache/$rerere_id/postimage &&
+       tr "\0" "\n" <.git/MERGE_RR >actual &&
+       echo "$rerere_id        fi/le" >expect &&
+       test_cmp expect actual
+'
+
+test_done
index 648184fd983512be57b46fb5903b42e4de5e4704..76ad7c344c0546603fb198a9ffb907d6142cbe84 100755 (executable)
@@ -63,10 +63,10 @@ cat > expected <<\EOF
 EOF
 test_expect_success 'update-index --update from subdir' \
        'echo not so happy >file2 &&
-       cd dir1 &&
+       (cd dir1 &&
        cat ../file2 >file3 &&
-       git update-index --again &&
-       cd .. &&
+       git update-index --again
+       ) &&
        git ls-files -s >current &&
        cmp current expected'
 
index 1ed44ee503f9ecfb5222a9bce3f42ff2aa8127bc..4d0d0a35156ee1a7604f0ea719e93d3f765a2f91 100755 (executable)
@@ -24,7 +24,7 @@ git update-index symlink'
 test_expect_success \
 'the index entry must still be a symbolic link' '
 case "`git ls-files --stage --cached symlink`" in
-120000" "*symlink) echo ok;;
+120000" "*symlink) echo pass;;
 *) echo fail; git ls-files --stage --cached symlink; (exit 1);;
 esac'
 
diff --git a/t/t2104-update-index-skip-worktree.sh b/t/t2104-update-index-skip-worktree.sh
new file mode 100755 (executable)
index 0000000..1d0879b
--- /dev/null
@@ -0,0 +1,57 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Nguyễn Thái Ngọc Duy
+#
+
+test_description='skip-worktree bit test'
+
+. ./test-lib.sh
+
+cat >expect.full <<EOF
+H 1
+H 2
+H sub/1
+H sub/2
+EOF
+
+cat >expect.skip <<EOF
+S 1
+H 2
+S sub/1
+H sub/2
+EOF
+
+test_expect_success 'setup' '
+       mkdir sub &&
+       touch ./1 ./2 sub/1 sub/2 &&
+       git add 1 2 sub/1 sub/2 &&
+       git ls-files -t | test_cmp expect.full -
+'
+
+test_expect_success 'index is at version 2' '
+       test "$(test-index-version < .git/index)" = 2
+'
+
+test_expect_success 'update-index --skip-worktree' '
+       git update-index --skip-worktree 1 sub/1 &&
+       git ls-files -t | test_cmp expect.skip -
+'
+
+test_expect_success 'index is at version 3 after having some skip-worktree entries' '
+       test "$(test-index-version < .git/index)" = 3
+'
+
+test_expect_success 'ls-files -t' '
+       git ls-files -t | test_cmp expect.skip -
+'
+
+test_expect_success 'update-index --no-skip-worktree' '
+       git update-index --no-skip-worktree 1 sub/1 &&
+       git ls-files -t | test_cmp expect.full -
+'
+
+test_expect_success 'index version is back to 2 when there is no skip-worktree entry' '
+       test "$(test-index-version < .git/index)" = 2
+'
+
+test_done
diff --git a/t/t2105-update-index-gitfile.sh b/t/t2105-update-index-gitfile.sh
new file mode 100755 (executable)
index 0000000..a7f3d47
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Brad King
+#
+
+test_description='git update-index for gitlink to .git file.
+'
+
+. ./test-lib.sh
+
+test_expect_success 'submodule with absolute .git file' '
+       mkdir sub1 &&
+       (cd sub1 &&
+        git init &&
+        REAL="$(pwd)/.real" &&
+        mv .git "$REAL" &&
+        echo "gitdir: $REAL" >.git &&
+        test_commit first)
+'
+
+test_expect_success 'add gitlink to absolute .git file' '
+       git update-index --add -- sub1
+'
+
+test_expect_success 'submodule with relative .git file' '
+       mkdir sub2 &&
+       (cd sub2 &&
+        git init &&
+        mv .git .real &&
+        echo "gitdir: .real" >.git &&
+        test_commit first)
+'
+
+test_expect_success 'add gitlink to relative .git file' '
+       git update-index --add -- sub2
+'
+
+test_done
diff --git a/t/t2106-update-index-assume-unchanged.sh b/t/t2106-update-index-assume-unchanged.sh
new file mode 100755 (executable)
index 0000000..99d858c
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+test_description='git update-index --assume-unchanged test.
+'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' \
+       ': >file &&
+        git add file &&
+        git commit -m initial &&
+        git branch other &&
+        echo upstream >file &&
+        git add file &&
+        git commit -m upstream'
+
+test_expect_success 'do not switch branches with dirty file' \
+       'git reset --hard &&
+        git checkout other &&
+        echo dirt >file &&
+        git update-index --assume-unchanged file &&
+        test_must_fail git checkout master'
+
+test_done
index 912075063b9946d38a9ff72621cb80bcf2c05399..2ad2819a345af53ff6ab0b7c28fa313f1a0a5956 100755 (executable)
@@ -176,4 +176,9 @@ test_expect_success 'add -u resolves unmerged paths' '
 
 '
 
+test_expect_success '"add -u non-existent" should fail' '
+       test_must_fail git add -u non-existent &&
+       ! (git ls-files | grep "non-existent")
+'
+
 test_done
diff --git a/t/t2204-add-ignored.sh b/t/t2204-add-ignored.sh
new file mode 100755 (executable)
index 0000000..24afdab
--- /dev/null
@@ -0,0 +1,79 @@
+#!/bin/sh
+
+test_description='giving ignored paths to git add'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       mkdir sub dir dir/sub &&
+       echo sub >.gitignore &&
+       echo ign >>.gitignore &&
+       for p in . sub dir dir/sub
+       do
+               >"$p/ign" &&
+               >"$p/file" || exit 1
+       done
+'
+
+for i in file dir/file dir 'd*'
+do
+       test_expect_success "no complaints for unignored $i" '
+               rm -f .git/index &&
+               git add "$i" &&
+               git ls-files "$i" >out &&
+               test -s out
+       '
+done
+
+for i in ign dir/ign dir/sub dir/sub/*ign sub/file sub sub/*
+do
+       test_expect_success "complaints for ignored $i" '
+               rm -f .git/index &&
+               test_must_fail git add "$i" 2>err &&
+               git ls-files "$i" >out &&
+               ! test -s out &&
+               grep -e "Use -f if" err &&
+               cat err
+       '
+
+       test_expect_success "complaints for ignored $i with unignored file" '
+               rm -f .git/index &&
+               test_must_fail git add "$i" file 2>err &&
+               git ls-files "$i" >out &&
+               ! test -s out &&
+               grep -e "Use -f if" err &&
+               cat err
+       '
+done
+
+for i in sub sub/*
+do
+       test_expect_success "complaints for ignored $i in dir" '
+               rm -f .git/index &&
+               (
+                       cd dir &&
+                       test_must_fail git add "$i" 2>err &&
+                       git ls-files "$i" >out &&
+                       ! test -s out &&
+                       grep -e "Use -f if" err &&
+                       cat err
+               )
+       '
+done
+
+for i in ign file
+do
+       test_expect_success "complaints for ignored $i in sub" '
+               rm -f .git/index &&
+               (
+                       cd sub &&
+                       test_must_fail git add "$i" 2>err &&
+                       git ls-files "$i" >out &&
+                       ! test -s out &&
+                       grep -e "Use -f if" err &&
+                       cat err
+               )
+       '
+done
+
+test_done
index 3b01ad2e4de152d23bbe8267d3b1a5cafd615e88..9965bc5c92445160895ae3ea426c5caf1b3458b3 100755 (executable)
@@ -8,7 +8,7 @@ test_cd_to_toplevel () {
        test_expect_success $3 "$2" '
                (
                        cd '"'$1'"' &&
-                       . git-sh-setup &&
+                       . "$(git --exec-path)"/git-sh-setup &&
                        cd_to_toplevel &&
                        [ "$(pwd -P)" = "$TOPLEVEL" ]
                )
index 86291e839942e6842bf1d2b40f2d7f7c1d8d4a9f..2eec0118c4235c0aa9d85cb7112e1f72b49c5c5f 100755 (executable)
@@ -17,57 +17,52 @@ filesystem.
 '
 . ./test-lib.sh
 
-date >path0
-if test_have_prereq SYMLINKS
-then
-       ln -s xyzzy path1
-else
-       date > path1
-fi
-mkdir path2 path3 path4
-date >path2/file2
-date >path2-junk
-date >path3/file3
-date >path3-junk
-git update-index --add path3-junk path3/file3
-
-cat >expected1 <<EOF
-expected1
-expected2
-expected3
-output
-path0
-path1
-path2-junk
-path2/file2
-EOF
-sed -e 's|path2/file2|path2/|' <expected1 >expected2
-cat <expected2 >expected3
-echo path4/ >>expected2
-
-test_expect_success \
-    'git ls-files --others to show output.' \
-    'git ls-files --others >output'
-
-test_expect_success \
-    'git ls-files --others should pick up symlinks.' \
-    'test_cmp expected1 output'
+test_expect_success 'setup ' '
+       date >path0 &&
+       if test_have_prereq SYMLINKS
+       then
+               ln -s xyzzy path1
+       else
+               date >path1
+       fi &&
+       mkdir path2 path3 path4 &&
+       date >path2/file2 &&
+       date >path2-junk &&
+       date >path3/file3 &&
+       date >path3-junk &&
+       git update-index --add path3-junk path3/file3
+'
 
-test_expect_success \
-    'git ls-files --others --directory to show output.' \
-    'git ls-files --others --directory >output'
+test_expect_success 'setup: expected output' '
+       cat >expected1 <<-\EOF &&
+       expected1
+       expected2
+       expected3
+       output
+       path0
+       path1
+       path2-junk
+       path2/file2
+       EOF
 
+       sed -e "s|path2/file2|path2/|" <expected1 >expected2 &&
+       cp expected2 expected3 &&
+       echo path4/ >>expected2
+'
 
-test_expect_success \
-    'git ls-files --others --directory should not get confused.' \
-    'test_cmp expected2 output'
+test_expect_success 'ls-files --others' '
+       git ls-files --others >output &&
+       test_cmp expected1 output
+'
 
-test_expect_success \
-    'git ls-files --others --directory --no-empty-directory to show output.' \
-    'git ls-files --others --directory --no-empty-directory >output'
+test_expect_success 'ls-files --others --directory' '
+       git ls-files --others --directory >output &&
+       test_cmp expected2 output
+'
 
-test_expect_success \
-    '--no-empty-directory hides empty directory' \
-    'test_cmp expected3 output'
+test_expect_success '--no-empty-directory hides empty directory' '
+       git ls-files --others --directory --no-empty-directory >output &&
+       test_cmp expected3 output
+'
 
 test_done
index c65bca838881938e2f924cfe62b2c303dbd5b1cd..6d2f2b67ee8d03e1f1dc4874da100cb2e179b6d1 100755 (executable)
@@ -64,6 +64,8 @@ two/*.4
 echo '!*.2
 !*.8' >one/two/.gitignore
 
+allignores='.gitignore one/.gitignore one/two/.gitignore'
+
 test_expect_success \
     'git ls-files --others with various exclude options.' \
     'git ls-files --others \
@@ -85,6 +87,26 @@ test_expect_success \
        >output &&
      test_cmp expect output'
 
+test_expect_success 'setup skip-worktree gitignore' '
+       git add $allignores &&
+       git update-index --skip-worktree $allignores &&
+       rm $allignores
+'
+
+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 &&
+     test_cmp expect output'
+
+test_expect_success 'restore gitignore' '
+       git checkout $allignores &&
+       rm .git/index
+'
+
 cat > excludes-file <<\EOF
 *.[1-8]
 e*
@@ -153,4 +175,43 @@ test_expect_success 'negated exclude matches can override previous ones' '
        grep "^a.1" output
 '
 
+test_expect_success 'subdirectory ignore (setup)' '
+       mkdir -p top/l1/l2 &&
+       (
+               cd top &&
+               git init &&
+               echo /.gitignore >.gitignore &&
+               echo l1 >>.gitignore &&
+               echo l2 >l1/.gitignore &&
+               >l1/l2/l1
+       )
+'
+
+test_expect_success 'subdirectory ignore (toplevel)' '
+       (
+               cd top &&
+               git ls-files -o --exclude-standard
+       ) >actual &&
+       >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'subdirectory ignore (l1/l2)' '
+       (
+               cd top/l1/l2 &&
+               git ls-files -o --exclude-standard
+       ) >actual &&
+       >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'subdirectory ignore (l1)' '
+       (
+               cd top/l1 &&
+               git ls-files -o --exclude-standard
+       ) >actual &&
+       >expect &&
+       test_cmp expect actual
+'
+
 test_done
index f4066cbc090a8fd0f6a528eed65d16d705c1bb18..a7d8187169a36f708bd7dc39d97604dd9cc21565 100755 (executable)
@@ -11,9 +11,11 @@ line.
 '
 . ./test-lib.sh
 
-touch foo bar
-git update-index --add foo bar
-git commit -m "add foo bar"
+test_expect_success 'setup' '
+       touch foo bar &&
+       git update-index --add foo bar &&
+       git commit -m "add foo bar"
+'
 
 test_expect_success \
     'git ls-files --error-unmatch should fail with unmatched path.' \
index 0de613dc53d85c01f6d122834e094503a2736507..e66e550b2449e76435a0bf16c6a9889c0794f858 100755 (executable)
@@ -22,6 +22,9 @@ test_expect_success 'setup 1' '
        git branch df-2 &&
        git branch df-3 &&
        git branch remove &&
+       git branch submod &&
+       git branch copy &&
+       git branch rename &&
 
        echo hello >>a &&
        cp a d/e &&
@@ -236,6 +239,33 @@ test_expect_success 'setup 6' '
        test_cmp expected actual
 '
 
+test_expect_success 'setup 7' '
+
+       git checkout submod &&
+       git rm d/e &&
+       test_tick &&
+       git commit -m "remove d/e" &&
+       git update-index --add --cacheinfo 160000 $c1 d &&
+       test_tick &&
+       git commit -m "make d/ a submodule"
+'
+
+test_expect_success 'setup 8' '
+       git checkout rename &&
+       git mv a e &&
+       git add e &&
+       test_tick &&
+       git commit -m "rename a->e"
+'
+
+test_expect_success 'setup 9' '
+       git checkout copy &&
+       cp a e &&
+       git add e &&
+       test_tick &&
+       git commit -m "copy a->e"
+'
+
 test_expect_success 'merge-recursive simple' '
 
        rm -fr [abcd] &&
@@ -276,8 +306,13 @@ test_expect_success 'fail if the index has unresolved entries' '
 
        test_must_fail git merge "$c5" &&
        test_must_fail git merge "$c5" 2> out &&
-       grep "You are in the middle of a conflicted merge" out
-
+       grep "not possible because you have unmerged files" out &&
+       git add -u &&
+       test_must_fail git merge "$c5" 2> out &&
+       grep "You have not concluded your merge" out &&
+       rm -f .git/MERGE_HEAD &&
+       test_must_fail git merge "$c5" 2> out &&
+       grep "Your local changes to the following files would be overwritten by merge:" out
 '
 
 test_expect_success 'merge-recursive remove conflict' '
@@ -546,4 +581,38 @@ test_expect_success 'merge removes empty directories' '
        test_must_fail test -d d
 '
 
+test_expect_failure 'merge-recursive simple w/submodule' '
+
+       git checkout submod &&
+       git merge remove
+'
+
+test_expect_failure 'merge-recursive simple w/submodule result' '
+
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o5 0      a"
+               echo "100644 $o0 0      c"
+               echo "160000 $c1 0      d"
+       ) >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'merge-recursive copy vs. rename' '
+       git checkout -f copy &&
+       git merge rename &&
+       ( git ls-tree -r HEAD && git ls-files -s ) >actual &&
+       (
+               echo "100644 blob $o0   b"
+               echo "100644 blob $o0   c"
+               echo "100644 blob $o0   d/e"
+               echo "100644 blob $o0   e"
+               echo "100644 $o0 0      b"
+               echo "100644 $o0 0      c"
+               echo "100644 $o0 0      d/e"
+               echo "100644 $o0 0      e"
+       ) >expected &&
+       test_cmp expected actual
+'
+
 test_done
diff --git a/t/t3031-merge-criscross.sh b/t/t3031-merge-criscross.sh
new file mode 100755 (executable)
index 0000000..7f41607
--- /dev/null
@@ -0,0 +1,95 @@
+#!/bin/sh
+
+test_description='merge-recursive backend test'
+
+. ./test-lib.sh
+
+#         A      <- create some files
+#        / \
+#       B   C    <- cause rename/delete conflicts between B and C
+#      /     \
+#     |\     /|
+#     | D   E |
+#     |  \ /  |
+#     |   X   |
+#     |  / \  |
+#     | /   \ |
+#     |/     \|
+#     F       G  <- merge E into B, D into C
+#      \     /
+#       \   /
+#        \ /
+#         H      <- recursive merge crashes
+#
+
+# initialize
+test_expect_success 'setup repo with criss-cross history' '
+       mkdir data &&
+
+       # create a bunch of files
+       n=1 &&
+       while test $n -le 10
+       do
+               echo $n > data/$n &&
+               n=$(($n+1)) ||
+               break
+       done &&
+
+       # check them in
+       git add data &&
+       git commit -m A &&
+       git branch A &&
+
+       # a file in one branch
+       git checkout -b B A &&
+       git rm data/9 &&
+       git add data &&
+       git commit -m B &&
+
+       # with a branch off of it
+       git branch D &&
+
+       # put some commits on D
+       git checkout D &&
+       echo testD > data/testD &&
+       git add data &&
+       git commit -m D &&
+
+       # back up to the top, create another branch and cause
+       # a rename conflict with the file we deleted earlier
+       git checkout -b C A &&
+       git mv data/9 data/new-9 &&
+       git add data &&
+       git commit -m C &&
+
+       # with a branch off of it
+       git branch E &&
+
+       # put a commit on E
+       git checkout E &&
+       echo testE > data/testE &&
+       git add data &&
+       git commit -m E &&
+
+       # now, merge E into B
+       git checkout B &&
+       test_must_fail git merge E &&
+       # force-resolve
+       git add data &&
+       git commit -m F &&
+       git branch F &&
+
+       # and merge D into C
+       git checkout C &&
+       test_must_fail git merge D &&
+       # force-resolve
+       git add data &&
+       git commit -m G &&
+       git branch G
+'
+
+test_expect_success 'recursive merge between F and G, causes segfault' '
+       git merge F
+'
+
+test_done
index 3ce501bb9794900b99fbbf2f2ccfbb330f7947a7..61c1f53d1b75302be60396d2e01ccd5da5b6e5b4 100755 (executable)
@@ -53,17 +53,15 @@ test_expect_success setup '
        git add .
 '
 
-# We have to run from a sub-directory to trigger prune_path
-# Then we finally get to run our --with-tree test
-cd sub
-
 test_expect_success 'git -ls-files --with-tree should succeed from subdir' '
-
-       git ls-files --with-tree=HEAD~1 >../output
-
+       # We have to run from a sub-directory to trigger prune_path
+       # Then we finally get to run our --with-tree test
+       (
+               cd sub &&
+               git ls-files --with-tree=HEAD~1 >../output
+       )
 '
 
-cd ..
 test_expect_success \
     'git -ls-files --with-tree should add entries from named tree.' \
     'test_cmp expected output'
index ee60d03fe8a582100e9df5511f6fea8900bcc543..81d90b66c50e75323a44aaf5e2e067d5a3569e6a 100755 (executable)
@@ -43,8 +43,6 @@ test_expect_success \
      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
     test_cmp expected check
@@ -167,4 +165,13 @@ test_expect_success \
 EOF
      test_output'
 
+test_expect_success \
+    'ls-tree with one path a prefix of the other' \
+    'git ls-tree $tree path2/baz path2/bazbo >current &&
+     make_expected <<\EOF &&
+040000 tree X  path2/baz
+120000 blob X  path2/bazbo
+EOF
+     test_output'
+
 test_done
index 51cb4a30f571772399697bc073153b7e956853bf..026f9f89d916fdd0547c58aed7bcba7f06850023 100755 (executable)
@@ -21,35 +21,32 @@ 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_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
+'
+
 test_output () {
-    sed -e "s/ $_x40   / X     /" <current >check
-    test_cmp expected check
+       sed -e "s/ $_x40        / X     /" <current >check &&
+       test_cmp expected check
 }
 
-test_expect_success \
-    'ls-tree plain' \
-    'git ls-tree $tree >current &&
-     cat >expected <<\EOF &&
+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
@@ -57,13 +54,13 @@ test_expect_success \
 040000 tree X  path2
 040000 tree X  path3
 EOF
-     test_output'
+       test_output
+'
 
 # Recursive does not show tree nodes anymore...
-test_expect_success \
-    'ls-tree recursive' \
-    'git ls-tree -r $tree >current &&
-     cat >expected <<\EOF &&
+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
@@ -72,68 +69,71 @@ test_expect_success \
 100644 blob X  path3/1.txt
 100644 blob X  path3/2.txt
 EOF
-     test_output'
+       test_output
+'
 
-test_expect_success \
-    'ls-tree filter 1.txt' \
-    'git ls-tree $tree 1.txt >current &&
-     cat >expected <<\EOF &&
+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_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 &&
+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_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 &&
+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'
+       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 &&
+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'
+       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 path3// >current &&
-     cat >expected <<\EOF &&
+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 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_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_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 &&
+       >expected &&
+       test_output
+'
 
 test_expect_success 'ls-tree filter is leading path match' '
        git ls-tree $tree pa path3/a >current &&
@@ -141,4 +141,89 @@ test_expect_success 'ls-tree filter is leading path match' '
        test_output
 '
 
+test_expect_success 'ls-tree --full-name' '
+       (
+               cd path0 &&
+               git ls-tree --full-name $tree a
+       ) >current &&
+       cat >expected <<\EOF &&
+040000 tree X  path0/a
+EOF
+       test_output
+'
+
+test_expect_success 'ls-tree --full-tree' '
+       (
+               cd path1/b/c &&
+               git ls-tree --full-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
+'
+
+test_expect_success 'ls-tree --full-tree -r' '
+       (
+               cd path3/ &&
+               git ls-tree --full-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 --abbrev=5' '
+       git ls-tree --abbrev=5 $tree >current &&
+       sed -e "s/ $_x05[0-9a-f]*       / X     /" <current >check &&
+       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_cmp expected check
+'
+
+test_expect_success 'ls-tree --name-only' '
+       git ls-tree --name-only $tree >current &&
+       cat >expected <<\EOF &&
+1.txt
+2.txt
+path0
+path1
+path2
+path3
+EOF
+       test_output
+'
+
+test_expect_success 'ls-tree --name-only -r' '
+       git ls-tree --name-only -r $tree >current &&
+       cat >expected <<\EOF &&
+1.txt
+2.txt
+path0/a/b/c/1.txt
+path1/b/c/1.txt
+path2/1.txt
+path3/1.txt
+path3/2.txt
+EOF
+       test_output
+'
+
 test_done
index d59a9b4aef5fc53e42ec95a3b96a18fb67aa82cb..f54a533456d74a3eb2f745dbc77c8ad5a5ae960f 100755 (executable)
@@ -43,7 +43,7 @@ test_expect_success \
      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_cmp expect .git/logs/refs/heads/d/e/f'
 
 test_expect_success \
     'git branch -d d/e/f should delete a branch and a log' \
@@ -222,7 +222,28 @@ test_expect_success \
      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_cmp expect .git/logs/refs/heads/g/h/i'
+
+test_expect_success 'checkout -b makes reflog by default' '
+       git checkout master &&
+       git config --unset core.logAllRefUpdates &&
+       git checkout -b alpha &&
+       git rev-parse --verify alpha@{0}
+'
+
+test_expect_success 'checkout -b does not make reflog when core.logAllRefUpdates = false' '
+       git checkout master &&
+       git config core.logAllRefUpdates false &&
+       git checkout -b beta &&
+       test_must_fail git rev-parse --verify beta@{0}
+'
+
+test_expect_success 'checkout -b with -l makes reflog when core.logAllRefUpdates = false' '
+       git checkout master &&
+       git checkout -lb gamma &&
+       git config --unset core.logAllRefUpdates &&
+       git rev-parse --verify gamma@{0}
+'
 
 test_expect_success 'avoid ambiguous track' '
        git config branch.autosetupmerge true &&
@@ -468,4 +489,30 @@ test_expect_success 'detect misconfigured autosetuprebase (no value)' '
        git config --unset branch.autosetuprebase
 '
 
+test_expect_success 'attempt to delete a branch without base and unmerged to HEAD' '
+       git checkout my9 &&
+       git config --unset branch.my8.merge &&
+       test_must_fail git branch -d my8
+'
+
+test_expect_success 'attempt to delete a branch merged to its base' '
+       # we are on my9 which is the initial commit; traditionally
+       # we would not have allowed deleting my8 that is not merged
+       # to my9, but it is set to track master that already has my8
+       git config branch.my8.merge refs/heads/master &&
+       git branch -d my8
+'
+
+test_expect_success 'attempt to delete a branch merged to its base' '
+       git checkout master &&
+       echo Third >>A &&
+       git commit -m "Third commit" A &&
+       git branch -t my10 my9 &&
+       git branch -f my10 HEAD^ &&
+       # we are on master which is at the third commit, and my10
+       # is behind us, so traditionally we would have allowed deleting
+       # it; but my10 is set to track my9 that is further behind.
+       test_must_fail git branch -d my10
+'
+
 test_done
index 7fe4a6ecb05df0fbfb825fbb08207f7672e1775f..0a5d5e669fac2e3be513df4f27c1f0a6560796db 100755 (executable)
@@ -56,4 +56,12 @@ test_expect_success 'show-branch with more than 8 branches' '
 
 '
 
+test_expect_success 'show-branch with showbranch.default' '
+       for i in $numbers; do
+               git config --add showbranch.default branch$i
+       done &&
+       git show-branch >out &&
+       test_cmp expect out
+'
+
 test_done
index 413019acafc98646a61e77960527f042a8f96ac6..cd04361df811d329e269ac1d5eb0b0f03baa74d8 100755 (executable)
@@ -28,7 +28,7 @@ test_expect_success \
      SHA1=`cat .git/refs/heads/a` &&
      echo "$SHA1 refs/heads/a" >expect &&
      git show-ref a >result &&
-     diff expect result'
+     test_cmp expect result'
 
 test_expect_success \
     'see if a branch still exists when packed' \
@@ -37,7 +37,7 @@ test_expect_success \
      rm -f .git/refs/heads/b &&
      echo "$SHA1 refs/heads/b" >expect &&
      git show-ref b >result &&
-     diff expect result'
+     test_cmp expect result'
 
 test_expect_success 'git branch c/d should barf if branch c exists' '
      git branch c &&
@@ -52,7 +52,7 @@ test_expect_success \
      git pack-refs --all --prune &&
      echo "$SHA1 refs/heads/e" >expect &&
      git show-ref e >result &&
-     diff expect result'
+     test_cmp expect result'
 
 test_expect_success 'see if git pack-refs --prune remove ref files' '
      git branch f &&
@@ -60,6 +60,12 @@ test_expect_success 'see if git pack-refs --prune remove ref files' '
      ! test -f .git/refs/heads/f
 '
 
+test_expect_success 'see if git pack-refs --prune removes empty dirs' '
+     git branch r/s/t &&
+     git pack-refs --all --prune &&
+     ! test -e .git/refs/heads/r
+'
+
 test_expect_success \
     'git branch g should work when git branch g/h has been deleted' \
     'git branch g/h &&
@@ -109,7 +115,7 @@ test_expect_success 'pack, prune and repack' '
        git show-ref >all-of-them &&
        git pack-refs &&
        git show-ref >again &&
-       diff all-of-them again
+       test_cmp all-of-them again
 '
 
 test_done
index db46d53e8271c0410a0dbf53a3560a8b635e2853..f39a261d806d567a3aadee892c58f6be02c64e0a 100755 (executable)
@@ -24,19 +24,25 @@ EOF
 cat 2>/dev/null >"$p1" "$p0"
 echo 'Foo Bar Baz' >"$p2"
 
-test -f "$p1" && cmp "$p0" "$p1" || {
+if test -f "$p1" && cmp "$p0" "$p1"
+then
+    test_set_prereq TABS_IN_FILENAMES
+else
        # 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
-}
+       say 'Your filesystem does not allow tabs in filenames'
+fi
 
+test_expect_success TABS_IN_FILENAMES 'setup expect' "
 echo 'just space
 no-funny' >expected
-test_expect_success 'git ls-files no-funny' \
+"
+
+test_expect_success TABS_IN_FILENAMES 'git ls-files no-funny' \
        'git update-index --add "$p0" "$p2" &&
        git ls-files >current &&
        test_cmp expected current'
 
+test_expect_success TABS_IN_FILENAMES 'setup expect' '
 t0=`git write-tree`
 echo "$t0" >t0
 
@@ -45,18 +51,24 @@ just space
 no-funny
 "tabs\t,\" (dq) and spaces"
 EOF
-test_expect_success 'git ls-files with-funny' \
+'
+
+test_expect_success TABS_IN_FILENAMES 'git ls-files with-funny' \
        'git update-index --add "$p1" &&
        git ls-files >current &&
        test_cmp expected current'
 
+test_expect_success TABS_IN_FILENAMES 'setup expect' "
 echo 'just space
 no-funny
-tabs   ," (dq) and spaces' >expected
-test_expect_success 'git ls-files -z with-funny' \
+tabs   ,\" (dq) and spaces' >expected
+"
+
+test_expect_success TABS_IN_FILENAMES 'git ls-files -z with-funny' \
        'git ls-files -z | perl -pe y/\\000/\\012/ >current &&
        test_cmp expected current'
 
+test_expect_success TABS_IN_FILENAMES 'setup expect' '
 t1=`git write-tree`
 echo "$t1" >t1
 
@@ -65,60 +77,78 @@ just space
 no-funny
 "tabs\t,\" (dq) and spaces"
 EOF
-test_expect_success 'git ls-tree with funny' \
+'
+
+test_expect_success TABS_IN_FILENAMES 'git ls-tree with funny' \
        'git ls-tree -r $t1 | sed -e "s/^[^     ]*      //" >current &&
         test_cmp expected current'
 
+test_expect_success TABS_IN_FILENAMES 'setup expect' '
 cat > expected <<\EOF
 A      "tabs\t,\" (dq) and spaces"
 EOF
-test_expect_success 'git diff-index with-funny' \
+'
+
+test_expect_success TABS_IN_FILENAMES 'git diff-index with-funny' \
        'git diff-index --name-status $t0 >current &&
        test_cmp expected current'
 
-test_expect_success 'git diff-tree with-funny' \
+test_expect_success TABS_IN_FILENAMES 'git diff-tree with-funny' \
        'git diff-tree --name-status $t0 $t1 >current &&
        test_cmp expected current'
 
+test_expect_success TABS_IN_FILENAMES 'setup expect' "
 echo 'A
-tabs   ," (dq) and spaces' >expected
-test_expect_success 'git diff-index -z with-funny' \
+tabs   ,\" (dq) and spaces' >expected
+"
+
+test_expect_success TABS_IN_FILENAMES 'git diff-index -z with-funny' \
        'git diff-index -z --name-status $t0 | perl -pe y/\\000/\\012/ >current &&
        test_cmp expected current'
 
-test_expect_success 'git diff-tree -z with-funny' \
+test_expect_success TABS_IN_FILENAMES 'git diff-tree -z with-funny' \
        'git diff-tree -z --name-status $t0 $t1 | perl -pe y/\\000/\\012/ >current &&
        test_cmp expected current'
 
+test_expect_success TABS_IN_FILENAMES 'setup expect' '
 cat > expected <<\EOF
 CNUM   no-funny        "tabs\t,\" (dq) and spaces"
 EOF
-test_expect_success 'git diff-tree -C with-funny' \
+'
+
+test_expect_success TABS_IN_FILENAMES '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 &&
        test_cmp expected current'
 
+test_expect_success TABS_IN_FILENAMES 'setup expect' '
 cat > expected <<\EOF
 RNUM   no-funny        "tabs\t,\" (dq) and spaces"
 EOF
-test_expect_success 'git diff-tree delete with-funny' \
+'
+
+test_expect_success TABS_IN_FILENAMES '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 &&
        test_cmp expected current'
 
+test_expect_success TABS_IN_FILENAMES 'setup expect' '
 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' \
+'
+
+test_expect_success TABS_IN_FILENAMES 'git diff-tree delete with-funny' \
        'git diff-index -M -p $t0 |
         sed -e "s/index [0-9]*%/index NUM%/" >current &&
         test_cmp expected current'
 
-chmod +x "$p1"
+test_expect_success TABS_IN_FILENAMES 'setup expect' '
+chmod +x "$p1" &&
 cat > expected <<\EOF
 diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
 old mode 100644
@@ -127,31 +157,39 @@ similarity index NUM%
 rename from no-funny
 rename to "tabs\t,\" (dq) and spaces"
 EOF
-test_expect_success 'git diff-tree delete with-funny' \
+'
+
+test_expect_success TABS_IN_FILENAMES 'git diff-tree delete with-funny' \
        'git diff-index -M -p $t0 |
         sed -e "s/index [0-9]*%/index NUM%/" >current &&
         test_cmp expected current'
 
+test_expect_success TABS_IN_FILENAMES 'setup expect' '
 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' \
+'
+
+test_expect_success TABS_IN_FILENAMES 'git diff-tree rename with-funny applied' \
        'git diff-index -M -p $t0 |
         git apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
         test_cmp expected current'
 
+test_expect_success TABS_IN_FILENAMES 'setup expect' '
 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' \
+'
+
+test_expect_success TABS_IN_FILENAMES 'git diff-tree delete with-funny applied' \
        'git diff-index -p $t0 |
         git apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
         test_cmp expected current'
 
-test_expect_success 'git apply non-git diff' \
+test_expect_success TABS_IN_FILENAMES 'git apply non-git diff' \
        'git diff-index -p $t0 |
         sed -ne "/^[-+@]/p" |
         git apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
new file mode 100755 (executable)
index 0000000..a2b79a0
--- /dev/null
@@ -0,0 +1,1064 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E. Schindelin
+#
+
+test_description='Test commit notes'
+
+. ./test-lib.sh
+
+cat > fake_editor.sh << \EOF
+#!/bin/sh
+echo "$MSG" > "$1"
+echo "$MSG" >& 2
+EOF
+chmod a+x fake_editor.sh
+GIT_EDITOR=./fake_editor.sh
+export GIT_EDITOR
+
+test_expect_success 'cannot annotate non-existing HEAD' '
+       (MSG=3 && export MSG && test_must_fail git notes add)
+'
+
+test_expect_success setup '
+       : > a1 &&
+       git add a1 &&
+       test_tick &&
+       git commit -m 1st &&
+       : > a2 &&
+       git add a2 &&
+       test_tick &&
+       git commit -m 2nd
+'
+
+test_expect_success 'need valid notes ref' '
+       (MSG=1 GIT_NOTES_REF=/ && export MSG GIT_NOTES_REF &&
+        test_must_fail git notes add) &&
+       (MSG=2 GIT_NOTES_REF=/ && export MSG GIT_NOTES_REF &&
+        test_must_fail git notes show)
+'
+
+test_expect_success 'refusing to add notes in refs/heads/' '
+       (MSG=1 GIT_NOTES_REF=refs/heads/bogus &&
+        export MSG GIT_NOTES_REF &&
+        test_must_fail git notes add)
+'
+
+test_expect_success 'refusing to edit notes in refs/remotes/' '
+       (MSG=1 GIT_NOTES_REF=refs/remotes/bogus &&
+        export MSG GIT_NOTES_REF &&
+        test_must_fail git notes edit)
+'
+
+# 1 indicates caught gracefully by die, 128 means git-show barked
+test_expect_success 'handle empty notes gracefully' '
+       git notes show ; test 1 = $?
+'
+
+test_expect_success 'show non-existent notes entry with %N' '
+       for l in A B
+       do
+               echo "$l"
+       done >expect &&
+       git show -s --format='A%n%NB' >output &&
+       test_cmp expect output
+'
+
+test_expect_success 'create notes' '
+       git config core.notesRef refs/notes/commits &&
+       MSG=b4 git notes add &&
+       test ! -f .git/NOTES_EDITMSG &&
+       test 1 = $(git ls-tree refs/notes/commits | wc -l) &&
+       test b4 = $(git notes show) &&
+       git show HEAD^ &&
+       test_must_fail git notes show HEAD^
+'
+
+test_expect_success 'show notes entry with %N' '
+       for l in A b4 B
+       do
+               echo "$l"
+       done >expect &&
+       git show -s --format='A%n%NB' >output &&
+       test_cmp expect output
+'
+
+cat >expect <<EOF
+d423f8c refs/notes/commits@{0}: notes: Notes added by 'git notes add'
+EOF
+
+test_expect_success 'create reflog entry' '
+       git reflog show refs/notes/commits >output &&
+       test_cmp expect output
+'
+
+test_expect_success 'edit existing notes' '
+       MSG=b3 git notes edit &&
+       test ! -f .git/NOTES_EDITMSG &&
+       test 1 = $(git ls-tree refs/notes/commits | wc -l) &&
+       test b3 = $(git notes show) &&
+       git show HEAD^ &&
+       test_must_fail git notes show HEAD^
+'
+
+test_expect_success 'cannot add note where one exists' '
+       ! MSG=b2 git notes add &&
+       test ! -f .git/NOTES_EDITMSG &&
+       test 1 = $(git ls-tree refs/notes/commits | wc -l) &&
+       test b3 = $(git notes show) &&
+       git show HEAD^ &&
+       test_must_fail git notes show HEAD^
+'
+
+test_expect_success 'can overwrite existing note with "git notes add -f"' '
+       MSG=b1 git notes add -f &&
+       test ! -f .git/NOTES_EDITMSG &&
+       test 1 = $(git ls-tree refs/notes/commits | wc -l) &&
+       test b1 = $(git notes show) &&
+       git show HEAD^ &&
+       test_must_fail git notes show HEAD^
+'
+
+cat > expect << EOF
+commit 268048bfb8a1fb38e703baceb8ab235421bf80c5
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:14:13 2005 -0700
+
+    2nd
+
+Notes:
+    b1
+EOF
+
+test_expect_success 'show notes' '
+       ! (git cat-file commit HEAD | grep b1) &&
+       git log -1 > output &&
+       test_cmp expect output
+'
+
+test_expect_success 'create multi-line notes (setup)' '
+       : > a3 &&
+       git add a3 &&
+       test_tick &&
+       git commit -m 3rd &&
+       MSG="b3
+c3c3c3c3
+d3d3d3" git notes add
+'
+
+cat > expect-multiline << EOF
+commit 1584215f1d29c65e99c6c6848626553fdd07fd75
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:15:13 2005 -0700
+
+    3rd
+
+Notes:
+    b3
+    c3c3c3c3
+    d3d3d3
+EOF
+
+printf "\n" >> expect-multiline
+cat expect >> expect-multiline
+
+test_expect_success 'show multi-line notes' '
+       git log -2 > output &&
+       test_cmp expect-multiline output
+'
+test_expect_success 'create -F notes (setup)' '
+       : > a4 &&
+       git add a4 &&
+       test_tick &&
+       git commit -m 4th &&
+       echo "xyzzy" > note5 &&
+       git notes add -F note5
+'
+
+cat > expect-F << EOF
+commit 15023535574ded8b1a89052b32673f84cf9582b8
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:16:13 2005 -0700
+
+    4th
+
+Notes:
+    xyzzy
+EOF
+
+printf "\n" >> expect-F
+cat expect-multiline >> expect-F
+
+test_expect_success 'show -F notes' '
+       git log -3 > output &&
+       test_cmp expect-F output
+'
+
+cat >expect << EOF
+commit 15023535574ded8b1a89052b32673f84cf9582b8
+tree e070e3af51011e47b183c33adf9736736a525709
+parent 1584215f1d29c65e99c6c6848626553fdd07fd75
+author A U Thor <author@example.com> 1112912173 -0700
+committer C O Mitter <committer@example.com> 1112912173 -0700
+
+    4th
+EOF
+test_expect_success 'git log --pretty=raw does not show notes' '
+       git log -1 --pretty=raw >output &&
+       test_cmp expect output
+'
+
+cat >>expect <<EOF
+
+Notes:
+    xyzzy
+EOF
+test_expect_success 'git log --show-notes' '
+       git log -1 --pretty=raw --show-notes >output &&
+       test_cmp expect output
+'
+
+test_expect_success 'git log --no-notes' '
+       git log -1 --no-notes >output &&
+       ! grep xyzzy output
+'
+
+test_expect_success 'git format-patch does not show notes' '
+       git format-patch -1 --stdout >output &&
+       ! grep xyzzy output
+'
+
+test_expect_success 'git format-patch --show-notes does show notes' '
+       git format-patch --show-notes -1 --stdout >output &&
+       grep xyzzy output
+'
+
+for pretty in \
+       "" --pretty --pretty=raw --pretty=short --pretty=medium \
+       --pretty=full --pretty=fuller --pretty=format:%s --oneline
+do
+       case "$pretty" in
+       "") p= not= negate="" ;;
+       ?*) p="$pretty" not=" not" negate="!" ;;
+       esac
+       test_expect_success "git show $pretty does$not show notes" '
+               git show $p >output &&
+               eval "$negate grep xyzzy output"
+       '
+done
+
+test_expect_success 'create -m notes (setup)' '
+       : > a5 &&
+       git add a5 &&
+       test_tick &&
+       git commit -m 5th &&
+       git notes add -m spam -m "foo
+bar
+baz"
+'
+
+whitespace="    "
+cat > expect-m << EOF
+commit bd1753200303d0a0344be813e504253b3d98e74d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:17:13 2005 -0700
+
+    5th
+
+Notes:
+    spam
+$whitespace
+    foo
+    bar
+    baz
+EOF
+
+printf "\n" >> expect-m
+cat expect-F >> expect-m
+
+test_expect_success 'show -m notes' '
+       git log -4 > output &&
+       test_cmp expect-m output
+'
+
+test_expect_success 'remove note with add -f -F /dev/null (setup)' '
+       git notes add -f -F /dev/null
+'
+
+cat > expect-rm-F << EOF
+commit bd1753200303d0a0344be813e504253b3d98e74d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:17:13 2005 -0700
+
+    5th
+EOF
+
+printf "\n" >> expect-rm-F
+cat expect-F >> expect-rm-F
+
+test_expect_success 'verify note removal with -F /dev/null' '
+       git log -4 > output &&
+       test_cmp expect-rm-F output &&
+       test_must_fail git notes show
+'
+
+test_expect_success 'do not create empty note with -m "" (setup)' '
+       git notes add -m ""
+'
+
+test_expect_success 'verify non-creation of note with -m ""' '
+       git log -4 > output &&
+       test_cmp expect-rm-F output &&
+       test_must_fail git notes show
+'
+
+cat > expect-combine_m_and_F << EOF
+foo
+
+xyzzy
+
+bar
+
+zyxxy
+
+baz
+EOF
+
+test_expect_success 'create note with combination of -m and -F' '
+       echo "xyzzy" > note_a &&
+       echo "zyxxy" > note_b &&
+       git notes add -m "foo" -F note_a -m "bar" -F note_b -m "baz" &&
+       git notes show > output &&
+       test_cmp expect-combine_m_and_F output
+'
+
+test_expect_success 'remove note with "git notes remove" (setup)' '
+       git notes remove HEAD^ &&
+       git notes remove
+'
+
+cat > expect-rm-remove << EOF
+commit bd1753200303d0a0344be813e504253b3d98e74d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:17:13 2005 -0700
+
+    5th
+
+commit 15023535574ded8b1a89052b32673f84cf9582b8
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:16:13 2005 -0700
+
+    4th
+EOF
+
+printf "\n" >> expect-rm-remove
+cat expect-multiline >> expect-rm-remove
+
+test_expect_success 'verify note removal with "git notes remove"' '
+       git log -4 > output &&
+       test_cmp expect-rm-remove output &&
+       test_must_fail git notes show HEAD^
+'
+
+cat > expect << EOF
+c18dc024e14f08d18d14eea0d747ff692d66d6a3 1584215f1d29c65e99c6c6848626553fdd07fd75
+c9c6af7f78bc47490dbf3e822cf2f3c24d4b9061 268048bfb8a1fb38e703baceb8ab235421bf80c5
+EOF
+
+test_expect_success 'removing non-existing note should not create new commit' '
+       git rev-parse --verify refs/notes/commits > before_commit &&
+       test_must_fail git notes remove HEAD^ &&
+       git rev-parse --verify refs/notes/commits > after_commit &&
+       test_cmp before_commit after_commit
+'
+
+test_expect_success 'list notes with "git notes list"' '
+       git notes list > output &&
+       test_cmp expect output
+'
+
+test_expect_success 'list notes with "git notes"' '
+       git notes > output &&
+       test_cmp expect output
+'
+
+cat > expect << EOF
+c18dc024e14f08d18d14eea0d747ff692d66d6a3
+EOF
+
+test_expect_success 'list specific note with "git notes list <object>"' '
+       git notes list HEAD^^ > output &&
+       test_cmp expect output
+'
+
+cat > expect << EOF
+EOF
+
+test_expect_success 'listing non-existing notes fails' '
+       test_must_fail git notes list HEAD > output &&
+       test_cmp expect output
+'
+
+cat > expect << EOF
+Initial set of notes
+
+More notes appended with git notes append
+EOF
+
+test_expect_success 'append to existing note with "git notes append"' '
+       git notes add -m "Initial set of notes" &&
+       git notes append -m "More notes appended with git notes append" &&
+       git notes show > output &&
+       test_cmp expect output
+'
+
+cat > expect_list << EOF
+c18dc024e14f08d18d14eea0d747ff692d66d6a3 1584215f1d29c65e99c6c6848626553fdd07fd75
+c9c6af7f78bc47490dbf3e822cf2f3c24d4b9061 268048bfb8a1fb38e703baceb8ab235421bf80c5
+4b6ad22357cc8a1296720574b8d2fbc22fab0671 bd1753200303d0a0344be813e504253b3d98e74d
+EOF
+
+test_expect_success '"git notes list" does not expand to "git notes list HEAD"' '
+       git notes list > output &&
+       test_cmp expect_list output
+'
+
+test_expect_success 'appending empty string does not change existing note' '
+       git notes append -m "" &&
+       git notes show > output &&
+       test_cmp expect output
+'
+
+test_expect_success 'git notes append == add when there is no existing note' '
+       git notes remove HEAD &&
+       test_must_fail git notes list HEAD &&
+       git notes append -m "Initial set of notes
+
+More notes appended with git notes append" &&
+       git notes show > output &&
+       test_cmp expect output
+'
+
+test_expect_success 'appending empty string to non-existing note does not create note' '
+       git notes remove HEAD &&
+       test_must_fail git notes list HEAD &&
+       git notes append -m "" &&
+       test_must_fail git notes list HEAD
+'
+
+test_expect_success 'create other note on a different notes ref (setup)' '
+       : > a6 &&
+       git add a6 &&
+       test_tick &&
+       git commit -m 6th &&
+       GIT_NOTES_REF="refs/notes/other" git notes add -m "other note"
+'
+
+cat > expect-other << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:18:13 2005 -0700
+
+    6th
+
+Notes (other):
+    other note
+EOF
+
+cat > expect-not-other << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:18:13 2005 -0700
+
+    6th
+EOF
+
+test_expect_success 'Do not show note on other ref by default' '
+       git log -1 > output &&
+       test_cmp expect-not-other output
+'
+
+test_expect_success 'Do show note when ref is given in GIT_NOTES_REF' '
+       GIT_NOTES_REF="refs/notes/other" git log -1 > output &&
+       test_cmp expect-other output
+'
+
+test_expect_success 'Do show note when ref is given in core.notesRef config' '
+       git config core.notesRef "refs/notes/other" &&
+       git log -1 > output &&
+       test_cmp expect-other output
+'
+
+test_expect_success 'Do not show note when core.notesRef is overridden' '
+       GIT_NOTES_REF="refs/notes/wrong" git log -1 > output &&
+       test_cmp expect-not-other output
+'
+
+cat > expect-both << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:18:13 2005 -0700
+
+    6th
+
+Notes:
+    order test
+
+Notes (other):
+    other note
+
+commit bd1753200303d0a0344be813e504253b3d98e74d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:17:13 2005 -0700
+
+    5th
+
+Notes:
+    replacement for deleted note
+EOF
+
+test_expect_success 'Show all notes when notes.displayRef=refs/notes/*' '
+       GIT_NOTES_REF=refs/notes/commits git notes add \
+               -m"replacement for deleted note" HEAD^ &&
+       GIT_NOTES_REF=refs/notes/commits git notes add -m"order test" &&
+       git config --unset core.notesRef &&
+       git config notes.displayRef "refs/notes/*" &&
+       git log -2 > output &&
+       test_cmp expect-both output
+'
+
+test_expect_success 'core.notesRef is implicitly in notes.displayRef' '
+       git config core.notesRef refs/notes/commits &&
+       git config notes.displayRef refs/notes/other &&
+       git log -2 > output &&
+       test_cmp expect-both output
+'
+
+test_expect_success 'notes.displayRef can be given more than once' '
+       git config --unset core.notesRef &&
+       git config notes.displayRef refs/notes/commits &&
+       git config --add notes.displayRef refs/notes/other &&
+       git log -2 > output &&
+       test_cmp expect-both output
+'
+
+cat > expect-both-reversed << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:18:13 2005 -0700
+
+    6th
+
+Notes (other):
+    other note
+
+Notes:
+    order test
+EOF
+
+test_expect_success 'notes.displayRef respects order' '
+       git config core.notesRef refs/notes/other &&
+       git config --unset-all notes.displayRef &&
+       git config notes.displayRef refs/notes/commits &&
+       git log -1 > output &&
+       test_cmp expect-both-reversed output
+'
+
+test_expect_success 'GIT_NOTES_DISPLAY_REF works' '
+       git config --unset-all core.notesRef &&
+       git config --unset-all notes.displayRef &&
+       GIT_NOTES_DISPLAY_REF=refs/notes/commits:refs/notes/other \
+               git log -2 > output &&
+       test_cmp expect-both output
+'
+
+cat > expect-none << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:18:13 2005 -0700
+
+    6th
+
+commit bd1753200303d0a0344be813e504253b3d98e74d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:17:13 2005 -0700
+
+    5th
+EOF
+
+test_expect_success 'GIT_NOTES_DISPLAY_REF overrides config' '
+       git config notes.displayRef "refs/notes/*" &&
+       GIT_NOTES_REF= GIT_NOTES_DISPLAY_REF= git log -2 > output &&
+       test_cmp expect-none output
+'
+
+test_expect_success '--show-notes=* adds to GIT_NOTES_DISPLAY_REF' '
+       GIT_NOTES_REF= GIT_NOTES_DISPLAY_REF= git log --show-notes=* -2 > output &&
+       test_cmp expect-both output
+'
+
+cat > expect-commits << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:18:13 2005 -0700
+
+    6th
+
+Notes:
+    order test
+EOF
+
+test_expect_success '--no-standard-notes' '
+       git log --no-standard-notes --show-notes=commits -1 > output &&
+       test_cmp expect-commits output
+'
+
+test_expect_success '--standard-notes' '
+       git log --no-standard-notes --show-notes=commits \
+               --standard-notes -2 > output &&
+       test_cmp expect-both output
+'
+
+test_expect_success '--show-notes=ref accumulates' '
+       git log --show-notes=other --show-notes=commits \
+                --no-standard-notes -1 > output &&
+       test_cmp expect-both-reversed output
+'
+
+test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' '
+       git config core.notesRef refs/notes/other &&
+       echo "Note on a tree" > expect
+       git notes add -m "Note on a tree" HEAD: &&
+       git notes show HEAD: > actual &&
+       test_cmp expect actual &&
+       echo "Note on a blob" > expect
+       filename=$(git ls-tree --name-only HEAD | head -n1) &&
+       git notes add -m "Note on a blob" HEAD:$filename &&
+       git notes show HEAD:$filename > actual &&
+       test_cmp expect actual &&
+       echo "Note on a tag" > expect
+       git tag -a -m "This is an annotated tag" foobar HEAD^ &&
+       git notes add -m "Note on a tag" foobar &&
+       git notes show foobar > actual &&
+       test_cmp expect actual
+'
+
+cat > expect << EOF
+commit 2ede89468182a62d0bde2583c736089bcf7d7e92
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:19:13 2005 -0700
+
+    7th
+
+Notes (other):
+    other note
+EOF
+
+test_expect_success 'create note from other note with "git notes add -C"' '
+       : > a7 &&
+       git add a7 &&
+       test_tick &&
+       git commit -m 7th &&
+       git notes add -C $(git notes list HEAD^) &&
+       git log -1 > actual &&
+       test_cmp expect actual &&
+       test "$(git notes list HEAD)" = "$(git notes list HEAD^)"
+'
+
+test_expect_success 'create note from non-existing note with "git notes add -C" fails' '
+       : > a8 &&
+       git add a8 &&
+       test_tick &&
+       git commit -m 8th &&
+       test_must_fail git notes add -C deadbeef &&
+       test_must_fail git notes list HEAD
+'
+
+cat > expect << EOF
+commit 016e982bad97eacdbda0fcbd7ce5b0ba87c81f1b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:21:13 2005 -0700
+
+    9th
+
+Notes (other):
+    yet another note
+EOF
+
+test_expect_success 'create note from other note with "git notes add -c"' '
+       : > a9 &&
+       git add a9 &&
+       test_tick &&
+       git commit -m 9th &&
+       MSG="yet another note" git notes add -c $(git notes list HEAD^^) &&
+       git log -1 > actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'create note from non-existing note with "git notes add -c" fails' '
+       : > a10 &&
+       git add a10 &&
+       test_tick &&
+       git commit -m 10th &&
+       (
+               MSG="yet another note" &&
+               export MSG &&
+               test_must_fail git notes add -c deadbeef
+       ) &&
+       test_must_fail git notes list HEAD
+'
+
+cat > expect << EOF
+commit 016e982bad97eacdbda0fcbd7ce5b0ba87c81f1b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:21:13 2005 -0700
+
+    9th
+
+Notes (other):
+    yet another note
+$whitespace
+    yet another note
+EOF
+
+test_expect_success 'append to note from other note with "git notes append -C"' '
+       git notes append -C $(git notes list HEAD^) HEAD^ &&
+       git log -1 HEAD^ > actual &&
+       test_cmp expect actual
+'
+
+cat > expect << EOF
+commit ffed603236bfa3891c49644257a83598afe8ae5a
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:22:13 2005 -0700
+
+    10th
+
+Notes (other):
+    other note
+EOF
+
+test_expect_success 'create note from other note with "git notes append -c"' '
+       MSG="other note" git notes append -c $(git notes list HEAD^) &&
+       git log -1 > actual &&
+       test_cmp expect actual
+'
+
+cat > expect << EOF
+commit ffed603236bfa3891c49644257a83598afe8ae5a
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:22:13 2005 -0700
+
+    10th
+
+Notes (other):
+    other note
+$whitespace
+    yet another note
+EOF
+
+test_expect_success 'append to note from other note with "git notes append -c"' '
+       MSG="yet another note" git notes append -c $(git notes list HEAD) &&
+       git log -1 > actual &&
+       test_cmp expect actual
+'
+
+cat > expect << EOF
+commit 6352c5e33dbcab725fe0579be16aa2ba8eb369be
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:23:13 2005 -0700
+
+    11th
+
+Notes (other):
+    other note
+$whitespace
+    yet another note
+EOF
+
+test_expect_success 'copy note with "git notes copy"' '
+       : > a11 &&
+       git add a11 &&
+       test_tick &&
+       git commit -m 11th &&
+       git notes copy HEAD^ HEAD &&
+       git log -1 > actual &&
+       test_cmp expect actual &&
+       test "$(git notes list HEAD)" = "$(git notes list HEAD^)"
+'
+
+test_expect_success 'prevent overwrite with "git notes copy"' '
+       test_must_fail git notes copy HEAD~2 HEAD &&
+       git log -1 > actual &&
+       test_cmp expect actual &&
+       test "$(git notes list HEAD)" = "$(git notes list HEAD^)"
+'
+
+cat > expect << EOF
+commit 6352c5e33dbcab725fe0579be16aa2ba8eb369be
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:23:13 2005 -0700
+
+    11th
+
+Notes (other):
+    yet another note
+$whitespace
+    yet another note
+EOF
+
+test_expect_success 'allow overwrite with "git notes copy -f"' '
+       git notes copy -f HEAD~2 HEAD &&
+       git log -1 > actual &&
+       test_cmp expect actual &&
+       test "$(git notes list HEAD)" = "$(git notes list HEAD~2)"
+'
+
+test_expect_success 'cannot copy note from object without notes' '
+       : > a12 &&
+       git add a12 &&
+       test_tick &&
+       git commit -m 12th &&
+       : > a13 &&
+       git add a13 &&
+       test_tick &&
+       git commit -m 13th &&
+       test_must_fail git notes copy HEAD^ HEAD
+'
+
+cat > expect << EOF
+commit e5d4fb5698d564ab8c73551538ecaf2b0c666185
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:25:13 2005 -0700
+
+    13th
+
+Notes (other):
+    yet another note
+$whitespace
+    yet another note
+
+commit 7038787dfe22a14c3867ce816dbba39845359719
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:24:13 2005 -0700
+
+    12th
+
+Notes (other):
+    other note
+$whitespace
+    yet another note
+EOF
+
+test_expect_success 'git notes copy --stdin' '
+       (echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
+       echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
+       git notes copy --stdin &&
+       git log -2 > output &&
+       test_cmp expect output &&
+       test "$(git notes list HEAD)" = "$(git notes list HEAD~2)" &&
+       test "$(git notes list HEAD^)" = "$(git notes list HEAD~3)"
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+commit be28d8b4d9951ad940d229ee3b0b9ee3b1ec273d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:26:13 2005 -0700
+
+    14th
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (unconfigured)' '
+       test_commit 14th &&
+       test_commit 15th &&
+       (echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
+       echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
+       git notes copy --for-rewrite=foo &&
+       git log -2 > output &&
+       test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    yet another note
+$whitespace
+    yet another note
+
+commit be28d8b4d9951ad940d229ee3b0b9ee3b1ec273d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:26:13 2005 -0700
+
+    14th
+
+Notes (other):
+    other note
+$whitespace
+    yet another note
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (enabled)' '
+       git config notes.rewriteMode overwrite &&
+       git config notes.rewriteRef "refs/notes/*" &&
+       (echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
+       echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
+       git notes copy --for-rewrite=foo &&
+       git log -2 > output &&
+       test_cmp expect output
+'
+
+test_expect_success 'git notes copy --for-rewrite (disabled)' '
+       git config notes.rewrite.bar false &&
+       echo $(git rev-parse HEAD~3) $(git rev-parse HEAD) |
+       git notes copy --for-rewrite=bar &&
+       git log -2 > output &&
+       test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    a fresh note
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (overwrite)' '
+       git notes add -f -m"a fresh note" HEAD^ &&
+       echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+       git notes copy --for-rewrite=foo &&
+       git log -1 > output &&
+       test_cmp expect output
+'
+
+test_expect_success 'git notes copy --for-rewrite (ignore)' '
+       git config notes.rewriteMode ignore &&
+       echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+       git notes copy --for-rewrite=foo &&
+       git log -1 > output &&
+       test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    a fresh note
+    another fresh note
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (append)' '
+       git notes add -f -m"another fresh note" HEAD^ &&
+       git config notes.rewriteMode concatenate &&
+       echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+       git notes copy --for-rewrite=foo &&
+       git log -1 > output &&
+       test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    a fresh note
+    another fresh note
+    append 1
+    append 2
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (append two to one)' '
+       git notes add -f -m"append 1" HEAD^ &&
+       git notes add -f -m"append 2" HEAD^^ &&
+       (echo $(git rev-parse HEAD^) $(git rev-parse HEAD);
+       echo $(git rev-parse HEAD^^) $(git rev-parse HEAD)) |
+       git notes copy --for-rewrite=foo &&
+       git log -1 > output &&
+       test_cmp expect output
+'
+
+test_expect_success 'git notes copy --for-rewrite (append empty)' '
+       git notes remove HEAD^ &&
+       echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+       git notes copy --for-rewrite=foo &&
+       git log -1 > output &&
+       test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    replacement note 1
+EOF
+
+test_expect_success 'GIT_NOTES_REWRITE_MODE works' '
+       git notes add -f -m"replacement note 1" HEAD^ &&
+       echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+       GIT_NOTES_REWRITE_MODE=overwrite git notes copy --for-rewrite=foo &&
+       git log -1 > output &&
+       test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    replacement note 2
+EOF
+
+test_expect_success 'GIT_NOTES_REWRITE_REF works' '
+       git config notes.rewriteMode overwrite &&
+       git notes add -f -m"replacement note 2" HEAD^ &&
+       git config --unset-all notes.rewriteRef &&
+       echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+       GIT_NOTES_REWRITE_REF=refs/notes/commits:refs/notes/other \
+               git notes copy --for-rewrite=foo &&
+       git log -1 > output &&
+       test_cmp expect output
+'
+
+test_expect_success 'GIT_NOTES_REWRITE_REF overrides config' '
+       git config notes.rewriteRef refs/notes/other &&
+       git notes add -f -m"replacement note 3" HEAD^ &&
+       echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+       GIT_NOTES_REWRITE_REF= git notes copy --for-rewrite=foo &&
+       git log -1 > output &&
+       test_cmp expect output
+'
+
+test_expect_success 'git notes copy diagnoses too many or too few parameters' '
+       test_must_fail git notes copy &&
+       test_must_fail git notes copy one two three
+'
+
+test_done
diff --git a/t/t3302-notes-index-expensive.sh b/t/t3302-notes-index-expensive.sh
new file mode 100755 (executable)
index 0000000..e35d781
--- /dev/null
@@ -0,0 +1,126 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E. Schindelin
+#
+
+test_description='Test commit notes index (expensive!)'
+
+. ./test-lib.sh
+
+test_set_prereq NOT_EXPENSIVE
+test -n "$GIT_NOTES_TIMING_TESTS" && test_set_prereq EXPENSIVE
+test -x /usr/bin/time && test_set_prereq USR_BIN_TIME
+
+create_repo () {
+       number_of_commits=$1
+       nr=0
+       test -d .git || {
+       git init &&
+       (
+               while [ $nr -lt $number_of_commits ]; do
+                       nr=$(($nr+1))
+                       mark=$(($nr+$nr))
+                       notemark=$(($mark+1))
+                       test_tick &&
+                       cat <<INPUT_END &&
+commit refs/heads/master
+mark :$mark
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+commit #$nr
+COMMIT
+
+M 644 inline file
+data <<EOF
+file in commit #$nr
+EOF
+
+blob
+mark :$notemark
+data <<EOF
+note for commit #$nr
+EOF
+
+INPUT_END
+
+                       echo "N :$notemark :$mark" >> note_commit
+               done &&
+               test_tick &&
+               cat <<INPUT_END &&
+commit refs/notes/commits
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes
+COMMIT
+
+INPUT_END
+
+               cat note_commit
+       ) |
+       git fast-import --quiet &&
+       git config core.notesRef refs/notes/commits
+       }
+}
+
+test_notes () {
+       count=$1 &&
+       git config core.notesRef refs/notes/commits &&
+       git log | grep "^    " > output &&
+       i=$count &&
+       while [ $i -gt 0 ]; do
+               echo "    commit #$i" &&
+               echo "    note for commit #$i" &&
+               i=$(($i-1));
+       done > expect &&
+       test_cmp expect output
+}
+
+cat > time_notes << \EOF
+       mode=$1
+       i=1
+       while [ $i -lt $2 ]; do
+               case $1 in
+               no-notes)
+                       GIT_NOTES_REF=non-existing; export GIT_NOTES_REF
+               ;;
+               notes)
+                       unset GIT_NOTES_REF
+               ;;
+               esac
+               git log >/dev/null
+               i=$(($i+1))
+       done
+EOF
+
+time_notes () {
+       for mode in no-notes notes
+       do
+               echo $mode
+               /usr/bin/time "$SHELL_PATH" ../time_notes $mode $1
+       done
+}
+
+do_tests () {
+       pr=$1
+       count=$2
+
+       test_expect_success $pr 'setup / mkdir' '
+               mkdir $count &&
+               cd $count
+       '
+
+       test_expect_success $pr "setup $count" "create_repo $count"
+
+       test_expect_success $pr 'notes work' "test_notes $count"
+
+       test_expect_success USR_BIN_TIME,$pr 'notes timing with /usr/bin/time' "time_notes 100"
+
+       test_expect_success $pr 'teardown / cd ..' 'cd ..'
+}
+
+do_tests NOT_EXPENSIVE 10
+for count in 100 1000 10000; do
+       do_tests EXPENSIVE $count
+done
+
+test_done
diff --git a/t/t3303-notes-subtrees.sh b/t/t3303-notes-subtrees.sh
new file mode 100755 (executable)
index 0000000..75ec187
--- /dev/null
@@ -0,0 +1,194 @@
+#!/bin/sh
+
+test_description='Test commit notes organized in subtrees'
+
+. ./test-lib.sh
+
+number_of_commits=100
+
+start_note_commit () {
+       test_tick &&
+       cat <<INPUT_END
+commit refs/notes/commits
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes
+COMMIT
+
+from refs/notes/commits^0
+deleteall
+INPUT_END
+
+}
+
+verify_notes () {
+       git log | grep "^    " > output &&
+       i=$number_of_commits &&
+       while [ $i -gt 0 ]; do
+               echo "    commit #$i" &&
+               echo "    note for commit #$i" &&
+               i=$(($i-1));
+       done > expect &&
+       test_cmp expect output
+}
+
+test_expect_success "setup: create $number_of_commits commits" '
+
+       (
+               nr=0 &&
+               while [ $nr -lt $number_of_commits ]; do
+                       nr=$(($nr+1)) &&
+                       test_tick &&
+                       cat <<INPUT_END
+commit refs/heads/master
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+commit #$nr
+COMMIT
+
+M 644 inline file
+data <<EOF
+file in commit #$nr
+EOF
+
+INPUT_END
+
+               done &&
+               test_tick &&
+               cat <<INPUT_END
+commit refs/notes/commits
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+no notes
+COMMIT
+
+deleteall
+
+INPUT_END
+
+       ) |
+       git fast-import --quiet &&
+       git config core.notesRef refs/notes/commits
+'
+
+test_sha1_based () {
+       (
+               start_note_commit &&
+               nr=$number_of_commits &&
+               git rev-list refs/heads/master |
+               while read sha1; do
+                       note_path=$(echo "$sha1" | sed "$1")
+                       cat <<INPUT_END &&
+M 100644 inline $note_path
+data <<EOF
+note for commit #$nr
+EOF
+
+INPUT_END
+
+                       nr=$(($nr-1))
+               done
+       ) |
+       git fast-import --quiet
+}
+
+test_expect_success 'test notes in 2/38-fanout' 'test_sha1_based "s|^..|&/|"'
+test_expect_success 'verify notes in 2/38-fanout' 'verify_notes'
+
+test_expect_success 'test notes in 2/2/36-fanout' 'test_sha1_based "s|^\(..\)\(..\)|\1/\2/|"'
+test_expect_success 'verify notes in 2/2/36-fanout' 'verify_notes'
+
+test_expect_success 'test notes in 2/2/2/34-fanout' 'test_sha1_based "s|^\(..\)\(..\)\(..\)|\1/\2/\3/|"'
+test_expect_success 'verify notes in 2/2/2/34-fanout' 'verify_notes'
+
+test_same_notes () {
+       (
+               start_note_commit &&
+               nr=$number_of_commits &&
+               git rev-list refs/heads/master |
+               while read sha1; do
+                       first_note_path=$(echo "$sha1" | sed "$1")
+                       second_note_path=$(echo "$sha1" | sed "$2")
+                       cat <<INPUT_END &&
+M 100644 inline $second_note_path
+data <<EOF
+note for commit #$nr
+EOF
+
+M 100644 inline $first_note_path
+data <<EOF
+note for commit #$nr
+EOF
+
+INPUT_END
+
+                       nr=$(($nr-1))
+               done
+       ) |
+       git fast-import --quiet
+}
+
+test_expect_success 'test same notes in no fanout and 2/38-fanout' 'test_same_notes "s|^..|&/|" ""'
+test_expect_success 'verify same notes in no fanout and 2/38-fanout' 'verify_notes'
+
+test_expect_success 'test same notes in no fanout and 2/2/36-fanout' 'test_same_notes "s|^\(..\)\(..\)|\1/\2/|" ""'
+test_expect_success 'verify same notes in no fanout and 2/2/36-fanout' 'verify_notes'
+
+test_expect_success 'test same notes in 2/38-fanout and 2/2/36-fanout' 'test_same_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^..|&/|"'
+test_expect_success 'verify same notes in 2/38-fanout and 2/2/36-fanout' 'verify_notes'
+
+test_expect_success 'test same notes in 2/2/2/34-fanout and 2/2/36-fanout' 'test_same_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^\(..\)\(..\)\(..\)|\1/\2/\3/|"'
+test_expect_success 'verify same notes in 2/2/2/34-fanout and 2/2/36-fanout' 'verify_notes'
+
+test_concatenated_notes () {
+       (
+               start_note_commit &&
+               nr=$number_of_commits &&
+               git rev-list refs/heads/master |
+               while read sha1; do
+                       first_note_path=$(echo "$sha1" | sed "$1")
+                       second_note_path=$(echo "$sha1" | sed "$2")
+                       cat <<INPUT_END &&
+M 100644 inline $second_note_path
+data <<EOF
+second note for commit #$nr
+EOF
+
+M 100644 inline $first_note_path
+data <<EOF
+first note for commit #$nr
+EOF
+
+INPUT_END
+
+                       nr=$(($nr-1))
+               done
+       ) |
+       git fast-import --quiet
+}
+
+verify_concatenated_notes () {
+    git log | grep "^    " > output &&
+    i=$number_of_commits &&
+    while [ $i -gt 0 ]; do
+        echo "    commit #$i" &&
+        echo "    first note for commit #$i" &&
+        echo "    second note for commit #$i" &&
+        i=$(($i-1));
+    done > expect &&
+    test_cmp expect output
+}
+
+test_expect_success 'test notes in no fanout concatenated with 2/38-fanout' 'test_concatenated_notes "s|^..|&/|" ""'
+test_expect_success 'verify notes in no fanout concatenated with 2/38-fanout' 'verify_concatenated_notes'
+
+test_expect_success 'test notes in no fanout concatenated with 2/2/36-fanout' 'test_concatenated_notes "s|^\(..\)\(..\)|\1/\2/|" ""'
+test_expect_success 'verify notes in no fanout concatenated with 2/2/36-fanout' 'verify_concatenated_notes'
+
+test_expect_success 'test notes in 2/38-fanout concatenated with 2/2/36-fanout' 'test_concatenated_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^..|&/|"'
+test_expect_success 'verify notes in 2/38-fanout concatenated with 2/2/36-fanout' 'verify_concatenated_notes'
+
+test_expect_success 'test notes in 2/2/36-fanout concatenated with 2/2/2/34-fanout' 'test_concatenated_notes "s|^\(..\)\(..\)\(..\)|\1/\2/\3/|" "s|^\(..\)\(..\)|\1/\2/|"'
+test_expect_success 'verify notes in 2/2/36-fanout concatenated with 2/2/2/34-fanout' 'verify_concatenated_notes'
+
+test_done
diff --git a/t/t3304-notes-mixed.sh b/t/t3304-notes-mixed.sh
new file mode 100755 (executable)
index 0000000..1709e8c
--- /dev/null
@@ -0,0 +1,206 @@
+#!/bin/sh
+
+test_description='Test notes trees that also contain non-notes'
+
+. ./test-lib.sh
+
+number_of_commits=100
+
+start_note_commit () {
+       test_tick &&
+       cat <<INPUT_END
+commit refs/notes/commits
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes
+COMMIT
+
+from refs/notes/commits^0
+deleteall
+INPUT_END
+
+}
+
+verify_notes () {
+       git log | grep "^    " > output &&
+       i=$number_of_commits &&
+       while [ $i -gt 0 ]; do
+               echo "    commit #$i" &&
+               echo "    note for commit #$i" &&
+               i=$(($i-1));
+       done > expect &&
+       test_cmp expect output
+}
+
+test_expect_success "setup: create a couple of commits" '
+
+       test_tick &&
+       cat <<INPUT_END >input &&
+commit refs/heads/master
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+commit #1
+COMMIT
+
+M 644 inline file
+data <<EOF
+file in commit #1
+EOF
+
+INPUT_END
+
+       test_tick &&
+       cat <<INPUT_END >>input &&
+commit refs/heads/master
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+commit #2
+COMMIT
+
+M 644 inline file
+data <<EOF
+file in commit #2
+EOF
+
+INPUT_END
+       git fast-import --quiet <input
+'
+
+test_expect_success "create a notes tree with both notes and non-notes" '
+
+       commit1=$(git rev-parse refs/heads/master^) &&
+       commit2=$(git rev-parse refs/heads/master) &&
+       test_tick &&
+       cat <<INPUT_END >input &&
+commit refs/notes/commits
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes commit #1
+COMMIT
+
+N inline $commit1
+data <<EOF
+note for commit #1
+EOF
+
+N inline $commit2
+data <<EOF
+note for commit #2
+EOF
+
+INPUT_END
+       test_tick &&
+       cat <<INPUT_END >>input &&
+commit refs/notes/commits
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes commit #2
+COMMIT
+
+M 644 inline foobar/non-note.txt
+data <<EOF
+A non-note in a notes tree
+EOF
+
+N inline $commit2
+data <<EOF
+edited note for commit #2
+EOF
+
+INPUT_END
+       test_tick &&
+       cat <<INPUT_END >>input &&
+commit refs/notes/commits
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes commit #3
+COMMIT
+
+N inline $commit1
+data <<EOF
+edited note for commit #1
+EOF
+
+M 644 inline deadbeef
+data <<EOF
+non-note with SHA1-like name
+EOF
+
+M 644 inline de/adbeef
+data <<EOF
+another non-note with SHA1-like name
+EOF
+
+M 644 inline de/adbeefdeadbeefdeadbeefdeadbeefdeadbeef
+data <<EOF
+This is actually a valid note, albeit to a non-existing object.
+It is needed in order to trigger the "mishandling" of the dead/beef non-note.
+EOF
+
+M 644 inline dead/beef
+data <<EOF
+yet another non-note with SHA1-like name
+EOF
+
+INPUT_END
+       git fast-import --quiet <input &&
+       git config core.notesRef refs/notes/commits
+'
+
+cat >expect <<EXPECT_END
+    commit #2
+    edited note for commit #2
+    commit #1
+    edited note for commit #1
+EXPECT_END
+
+test_expect_success "verify contents of notes" '
+
+       git log | grep "^    " > actual &&
+       test_cmp expect actual
+'
+
+cat >expect_nn1 <<EXPECT_END
+A non-note in a notes tree
+EXPECT_END
+cat >expect_nn2 <<EXPECT_END
+non-note with SHA1-like name
+EXPECT_END
+cat >expect_nn3 <<EXPECT_END
+another non-note with SHA1-like name
+EXPECT_END
+cat >expect_nn4 <<EXPECT_END
+yet another non-note with SHA1-like name
+EXPECT_END
+
+test_expect_success "verify contents of non-notes" '
+
+       git cat-file -p refs/notes/commits:foobar/non-note.txt > actual_nn1 &&
+       test_cmp expect_nn1 actual_nn1 &&
+       git cat-file -p refs/notes/commits:deadbeef > actual_nn2 &&
+       test_cmp expect_nn2 actual_nn2 &&
+       git cat-file -p refs/notes/commits:de/adbeef > actual_nn3 &&
+       test_cmp expect_nn3 actual_nn3 &&
+       git cat-file -p refs/notes/commits:dead/beef > actual_nn4 &&
+       test_cmp expect_nn4 actual_nn4
+'
+
+test_expect_success "git-notes preserves non-notes" '
+
+       test_tick &&
+       git notes add -f -m "foo bar"
+'
+
+test_expect_success "verify contents of non-notes after git-notes" '
+
+       git cat-file -p refs/notes/commits:foobar/non-note.txt > actual_nn1 &&
+       test_cmp expect_nn1 actual_nn1 &&
+       git cat-file -p refs/notes/commits:deadbeef > actual_nn2 &&
+       test_cmp expect_nn2 actual_nn2 &&
+       git cat-file -p refs/notes/commits:de/adbeef > actual_nn3 &&
+       test_cmp expect_nn3 actual_nn3 &&
+       git cat-file -p refs/notes/commits:dead/beef > actual_nn4 &&
+       test_cmp expect_nn4 actual_nn4
+'
+
+test_done
diff --git a/t/t3305-notes-fanout.sh b/t/t3305-notes-fanout.sh
new file mode 100755 (executable)
index 0000000..b1ea64b
--- /dev/null
@@ -0,0 +1,95 @@
+#!/bin/sh
+
+test_description='Test that adding/removing many notes triggers automatic fanout restructuring'
+
+. ./test-lib.sh
+
+test_expect_success 'creating many notes with git-notes' '
+       num_notes=300 &&
+       i=0 &&
+       while test $i -lt $num_notes
+       do
+               i=$(($i + 1)) &&
+               test_tick &&
+               echo "file for commit #$i" > file &&
+               git add file &&
+               git commit -q -m "commit #$i" &&
+               git notes add -m "note #$i" || return 1
+       done
+'
+
+test_expect_success 'many notes created correctly with git-notes' '
+       git log | grep "^    " > output &&
+       i=300 &&
+       while test $i -gt 0
+       do
+               echo "    commit #$i" &&
+               echo "    note #$i" &&
+               i=$(($i - 1));
+       done > expect &&
+       test_cmp expect output
+'
+
+test_expect_success 'many notes created with git-notes triggers fanout' '
+       # Expect entire notes tree to have a fanout == 1
+       git ls-tree -r --name-only refs/notes/commits |
+       while read path
+       do
+               case "$path" in
+               ??/??????????????????????????????????????)
+                       : true
+                       ;;
+               *)
+                       echo "Invalid path \"$path\"" &&
+                       return 1
+                       ;;
+               esac
+       done
+'
+
+test_expect_success 'deleting most notes with git-notes' '
+       num_notes=250 &&
+       i=0 &&
+       git rev-list HEAD |
+       while read sha1
+       do
+               i=$(($i + 1)) &&
+               if test $i -gt $num_notes
+               then
+                       break
+               fi &&
+               test_tick &&
+               git notes remove "$sha1"
+       done
+'
+
+test_expect_success 'most notes deleted correctly with git-notes' '
+       git log HEAD~250 | grep "^    " > output &&
+       i=50 &&
+       while test $i -gt 0
+       do
+               echo "    commit #$i" &&
+               echo "    note #$i" &&
+               i=$(($i - 1));
+       done > expect &&
+       test_cmp expect output
+'
+
+test_expect_success 'deleting most notes triggers fanout consolidation' '
+       # Expect entire notes tree to have a fanout == 0
+       git ls-tree -r --name-only refs/notes/commits |
+       while read path
+       do
+               case "$path" in
+               ????????????????????????????????????????)
+                       : true
+                       ;;
+               *)
+                       echo "Invalid path \"$path\"" &&
+                       return 1
+                       ;;
+               esac
+       done
+'
+
+test_done
diff --git a/t/t3306-notes-prune.sh b/t/t3306-notes-prune.sh
new file mode 100755 (executable)
index 0000000..c428217
--- /dev/null
@@ -0,0 +1,138 @@
+#!/bin/sh
+
+test_description='Test git notes prune'
+
+. ./test-lib.sh
+
+test_expect_success 'setup: create a few commits with notes' '
+
+       : > file1 &&
+       git add file1 &&
+       test_tick &&
+       git commit -m 1st &&
+       git notes add -m "Note #1" &&
+       : > file2 &&
+       git add file2 &&
+       test_tick &&
+       git commit -m 2nd &&
+       git notes add -m "Note #2" &&
+       : > file3 &&
+       git add file3 &&
+       test_tick &&
+       git commit -m 3rd &&
+       git notes add -m "Note #3"
+'
+
+cat > expect <<END_OF_LOG
+commit 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:15:13 2005 -0700
+
+    3rd
+
+Notes:
+    Note #3
+
+commit 08341ad9e94faa089d60fd3f523affb25c6da189
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:14:13 2005 -0700
+
+    2nd
+
+Notes:
+    Note #2
+
+commit ab5f302035f2e7aaf04265f08b42034c23256e1f
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:13:13 2005 -0700
+
+    1st
+
+Notes:
+    Note #1
+END_OF_LOG
+
+test_expect_success 'verify commits and notes' '
+
+       git log > actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'remove some commits' '
+
+       git reset --hard HEAD~1 &&
+       git reflog expire --expire=now HEAD &&
+       git gc --prune=now
+'
+
+test_expect_success 'verify that commits are gone' '
+
+       test_must_fail git cat-file -p 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 &&
+       git cat-file -p 08341ad9e94faa089d60fd3f523affb25c6da189 &&
+       git cat-file -p ab5f302035f2e7aaf04265f08b42034c23256e1f
+'
+
+test_expect_success 'verify that notes are still present' '
+
+       git notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 &&
+       git notes show 08341ad9e94faa089d60fd3f523affb25c6da189 &&
+       git notes show ab5f302035f2e7aaf04265f08b42034c23256e1f
+'
+
+test_expect_success 'prune -n does not remove notes' '
+
+       git notes list > expect &&
+       git notes prune -n &&
+       git notes list > actual &&
+       test_cmp expect actual
+'
+
+cat > expect <<EOF
+5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29
+EOF
+
+test_expect_success 'prune -n lists prunable notes' '
+
+
+       git notes prune -n > actual &&
+       test_cmp expect actual
+'
+
+
+test_expect_success 'prune notes' '
+
+       git notes prune
+'
+
+test_expect_success 'verify that notes are gone' '
+
+       test_must_fail git notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 &&
+       git notes show 08341ad9e94faa089d60fd3f523affb25c6da189 &&
+       git notes show ab5f302035f2e7aaf04265f08b42034c23256e1f
+'
+
+test_expect_success 'remove some commits' '
+
+       git reset --hard HEAD~1 &&
+       git reflog expire --expire=now HEAD &&
+       git gc --prune=now
+'
+
+cat > expect <<EOF
+08341ad9e94faa089d60fd3f523affb25c6da189
+EOF
+
+test_expect_success 'prune -v notes' '
+
+       git notes prune -v > actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'verify that notes are gone' '
+
+       test_must_fail git notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 &&
+       test_must_fail git notes show 08341ad9e94faa089d60fd3f523affb25c6da189 &&
+       git notes show ab5f302035f2e7aaf04265f08b42034c23256e1f
+'
+
+test_done
diff --git a/t/t3307-notes-man.sh b/t/t3307-notes-man.sh
new file mode 100755 (executable)
index 0000000..3269f2e
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+test_description='Examples from the git-notes man page
+
+Make sure the manual is not full of lies.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       test_commit A &&
+       test_commit B &&
+       test_commit C
+'
+
+test_expect_success 'example 1: notes to add an Acked-by line' '
+       cat <<-\EOF >expect &&
+           B
+
+       Notes:
+           Acked-by: A C Ker <acker@example.com>
+       EOF
+       git notes add -m "Acked-by: A C Ker <acker@example.com>" B &&
+       git show -s B^{commit} >log &&
+       tail -n 4 log >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'example 2: binary notes' '
+       cp "$TEST_DIRECTORY"/test4012.png .
+       git checkout B &&
+       blob=$(git hash-object -w test4012.png) &&
+       git notes --ref=logo add -C "$blob" &&
+       git notes --ref=logo copy B C &&
+       git notes --ref=logo show C >actual &&
+       test_cmp test4012.png actual
+'
+
+test_done
index 6e391a370208c9a0a6e73facc87f6a0e5081b929..349eebd54268c927249322dc22fc478869860f7c 100755 (executable)
 # Copyright (c) 2005 Amos Waterland
 #
 
-test_description='git rebase should not destroy author information
+test_description='git rebase assorted tests
 
-This test runs git rebase and checks that the author information is not lost.
+This test runs git rebase and checks that the author information is not lost
+among other things.
 '
 . ./test-lib.sh
 
-GIT_AUTHOR_EMAIL=bogus_email_address
-export GIT_AUTHOR_EMAIL
-
-test_expect_success \
-    'prepare repository with topic branches' \
-    'git config core.logAllRefUpdates true &&
-     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 -b side my-topic-branch &&
-     echo Side >> C &&
-     git add C &&
-     git commit -m "Add C" &&
-     git checkout -b nonlinear my-topic-branch &&
-     echo Edit >> B &&
-     git add B &&
-     git commit -m "Modify B" &&
-     git merge side &&
-     git checkout -b upstream-merged-nonlinear &&
-     git merge master &&
-     git checkout -f my-topic-branch &&
-     git tag topic
+GIT_AUTHOR_NAME=author@name
+GIT_AUTHOR_EMAIL=bogus@email@address
+export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
+
+test_expect_success 'prepare repository with topic branches' '
+       git config core.logAllRefUpdates true &&
+       echo First >A &&
+       git update-index --add A &&
+       git commit -m "Add A." &&
+       git checkout -b force-3way &&
+       echo Dummy >Y &&
+       git update-index --add Y &&
+       git commit -m "Add Y." &&
+       git checkout -b filemove &&
+       git reset --soft master &&
+       mkdir D &&
+       git mv A D/A &&
+       git commit -m "Move A." &&
+       git checkout -b my-topic-branch master &&
+       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 -b side my-topic-branch &&
+       echo Side >>C &&
+       git add C &&
+       git commit -m "Add C" &&
+       git checkout -b nonlinear my-topic-branch &&
+       echo Edit >>B &&
+       git add B &&
+       git commit -m "Modify B" &&
+       git merge side &&
+       git checkout -b upstream-merged-nonlinear &&
+       git merge master &&
+       git checkout -f my-topic-branch &&
+       git tag topic
+'
+
+test_expect_success 'rebase on dirty worktree' '
+       echo dirty >>A &&
+       test_must_fail git rebase master
+'
+
+test_expect_success 'rebase on dirty cache' '
+       git add A &&
+       test_must_fail git rebase master
 '
 
 test_expect_success 'rebase against master' '
-     git rebase master'
+       git reset --hard HEAD &&
+       git rebase master
+'
+
+test_expect_success 'rebase against master twice' '
+       git rebase master >out &&
+       grep "Current branch my-topic-branch is up to date" out
+'
+
+test_expect_success 'rebase against master twice with --force' '
+       git rebase --force-rebase master >out &&
+       grep "Current branch my-topic-branch is up to date, rebase forced" out
+'
+
+test_expect_success 'rebase against master twice from another branch' '
+       git checkout my-topic-branch^ &&
+       git rebase master my-topic-branch >out &&
+       grep "Current branch my-topic-branch is up to date" out
+'
+
+test_expect_success 'rebase fast-forward to master' '
+       git checkout my-topic-branch^ &&
+       git rebase my-topic-branch >out &&
+       grep "Fast-forwarded HEAD to my-topic-branch" out
+'
+
+test_expect_success 'the rebase operation should not have destroyed author information' '
+       ! (git log | grep "Author:" | grep "<>")
+'
 
-test_expect_success \
-    'the rebase operation should not have destroyed author information' \
-    '! (git log | grep "Author:" | grep "<>")'
+test_expect_success 'the rebase operation should not have destroyed author information (2)' "
+       git log -1 |
+       grep 'Author: $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>'
+"
 
 test_expect_success 'HEAD was detached during rebase' '
-     test $(git rev-parse HEAD@{1}) != $(git rev-parse my-topic-branch@{1})
+       test $(git rev-parse HEAD@{1}) != $(git rev-parse my-topic-branch@{1})
 '
 
 test_expect_success 'rebase after merge master' '
-     git reset --hard topic &&
-     git merge master &&
-     git rebase master &&
-     ! (git show | grep "^Merge:")
+       git reset --hard topic &&
+       git merge master &&
+       git rebase master &&
+       ! (git show | grep "^Merge:")
 '
 
 test_expect_success 'rebase of history with merges is linearized' '
-     git checkout nonlinear &&
-     test 4 = $(git rev-list master.. | wc -l) &&
-     git rebase master &&
-     test 3 = $(git rev-list master.. | wc -l)
+       git checkout nonlinear &&
+       test 4 = $(git rev-list master.. | wc -l) &&
+       git rebase master &&
+       test 3 = $(git rev-list master.. | wc -l)
 '
 
-test_expect_success \
-    'rebase of history with merges after upstream merge is linearized' '
-     git checkout upstream-merged-nonlinear &&
-     test 5 = $(git rev-list master.. | wc -l) &&
-     git rebase master &&
-     test 3 = $(git rev-list master.. | wc -l)
+test_expect_success 'rebase of history with merges after upstream merge is linearized' '
+       git checkout upstream-merged-nonlinear &&
+       test 5 = $(git rev-list master.. | wc -l) &&
+       git rebase master &&
+       test 3 = $(git rev-list master.. | wc -l)
 '
 
 test_expect_success 'rebase a single mode change' '
-     git checkout master &&
-     echo 1 > X &&
-     git add X &&
-     test_tick &&
-     git commit -m prepare &&
-     git checkout -b modechange HEAD^ &&
-     echo 1 > X &&
-     git add X &&
-     test_chmod +x A &&
-     test_tick &&
-     git commit -m modechange &&
-     GIT_TRACE=1 git rebase master
+       git checkout master &&
+       echo 1 >X &&
+       git add X &&
+       test_tick &&
+       git commit -m prepare &&
+       git checkout -b modechange HEAD^ &&
+       echo 1 >X &&
+       git add X &&
+       test_chmod +x A &&
+       test_tick &&
+       git commit -m modechange &&
+       GIT_TRACE=1 git rebase master
+'
+
+test_expect_success 'rebase is not broken by diff.renames' '
+       git config diff.renames copies &&
+       test_when_finished "git config --unset diff.renames" &&
+       git checkout filemove &&
+       GIT_TRACE=1 git rebase force-3way
+'
+
+test_expect_success 'setup: recover' '
+       test_might_fail git rebase --abort &&
+       git reset --hard &&
+       git checkout modechange
 '
 
 test_expect_success 'Show verbose error when HEAD could not be detached' '
-     : > B &&
-     test_must_fail git rebase topic 2> output.err > output.out &&
-     grep "Untracked working tree file .B. would be overwritten" output.err
+       >B &&
+       test_must_fail git rebase topic 2>output.err >output.out &&
+       grep "The following untracked working tree files would be overwritten by checkout:" output.err &&
+       grep B output.err
+'
+rm -f B
+
+test_expect_success 'dump usage when upstream arg is missing' '
+       git checkout -b usage topic &&
+       test_must_fail git rebase 2>error1 &&
+       grep "[Uu]sage" error1 &&
+       test_must_fail git rebase --abort 2>error2 &&
+       grep "No rebase in progress" error2 &&
+       test_must_fail git rebase --onto master 2>error3 &&
+       grep "[Uu]sage" error3 &&
+       ! grep "can.t shift" error3
+'
+
+test_expect_success 'rebase -q is quiet' '
+       git checkout -b quiet topic &&
+       git rebase -q master >output.out 2>&1 &&
+       test ! -s output.out
+'
+
+test_expect_success 'Rebase a commit that sprinkles CRs in' '
+       (
+               echo "One"
+               echo "TwoQ"
+               echo "Three"
+               echo "FQur"
+               echo "Five"
+       ) | q_to_cr >CR &&
+       git add CR &&
+       test_tick &&
+       git commit -a -m "A file with a line with CR" &&
+       git tag file-with-cr &&
+       git checkout HEAD^0 &&
+       git rebase --onto HEAD^^ HEAD^ &&
+       git diff --exit-code file-with-cr:CR HEAD:CR
+'
+
+test_expect_success 'rebase can copy notes' '
+       git config notes.rewrite.rebase true &&
+       git config notes.rewriteRef "refs/notes/*" &&
+       test_commit n1 &&
+       test_commit n2 &&
+       test_commit n3 &&
+       git notes add -m"a note" n3 &&
+       git rebase --onto n1 n2 &&
+       test "a note" = "$(git notes show HEAD)"
+'
+
+test_expect_success 'rebase -m can copy notes' '
+       git reset --hard n3 &&
+       git rebase -m --onto n1 n2 &&
+       test "a note" = "$(git notes show HEAD)"
 '
 
 test_done
index 7b7d07269ae35f56dd02a223f350ae0da97bae85..2bea65634a1790ceecf824309c1fbb66250f58d5 100755 (executable)
@@ -74,6 +74,15 @@ test_expect_success 'rebase the other way' '
        git rebase --merge side
 '
 
+test_expect_success 'rebase -Xtheirs' '
+       git checkout -b conflicting master~2 &&
+       echo "AB $T" >> original &&
+       git commit -mconflicting original &&
+       git rebase -Xtheirs master &&
+       grep AB original &&
+       ! grep 11 original
+'
+
 test_expect_success 'merge and rebase should match' '
        git diff-tree -r test-rebase test-merge >difference &&
        if test -s difference
index c32ff6682b932b3d9b270de7260a2671d8d07403..7d20a74c5ca1331ff241d1596bcb114e48a5907d 100755 (executable)
@@ -10,62 +10,123 @@ that the result still makes sense.
 '
 . ./test-lib.sh
 
-. ../lib-rebase.sh
+. "$TEST_DIRECTORY"/lib-rebase.sh
 
 set_fake_editor
 
-# set up two branches like this:
+# Set up the repository like this:
 #
-# A - B - C - D - E
-#   \
-#     F - G - H
-#       \
-#         I
+#     one - two - three - four (conflict-branch)
+#   /
+# A - B - C - D - E            (master)
+# | \
+# |   F - G - H                (branch1)
+# |     \
+# |\      I                    (branch2)
+# | \
+# |   J - K - L - M            (no-conflict-branch)
+#  \
+#    N - O - P                 (no-ff-branch)
 #
-# where B, D and G touch the same file.
+# where A, B, D and G all touch file1, and one, two, three, four all
+# touch file "conflict".
+#
+# WARNING: Modifications to the initial repository can change the SHA ID used
+# in the expect2 file for the 'stop on conflicting pick' test.
+
 
 test_expect_success 'setup' '
-       : > file1 &&
-       git add file1 &&
-       test_tick &&
-       git commit -m A &&
-       git tag A &&
-       echo 1 > file1 &&
-       test_tick &&
-       git commit -m B file1 &&
-       : > file2 &&
-       git add file2 &&
-       test_tick &&
-       git commit -m C &&
-       echo 2 > file1 &&
-       test_tick &&
-       git commit -m D file1 &&
-       : > file3 &&
-       git add file3 &&
-       test_tick &&
-       git commit -m E &&
+       test_commit A file1 &&
+       test_commit B file1 &&
+       test_commit C file2 &&
+       test_commit D file1 &&
+       test_commit E file3 &&
        git checkout -b branch1 A &&
-       : > file4 &&
-       git add file4 &&
-       test_tick &&
-       git commit -m F &&
-       git tag F &&
-       echo 3 > file1 &&
-       test_tick &&
-       git commit -m G file1 &&
-       : > file5 &&
-       git add file5 &&
-       test_tick &&
-       git commit -m H &&
+       test_commit F file4 &&
+       test_commit G file1 &&
+       test_commit H file5 &&
        git checkout -b branch2 F &&
-       : > file6 &&
-       git add file6 &&
-       test_tick &&
-       git commit -m I &&
-       git tag I
+       test_commit I file6
+       git checkout -b conflict-branch A &&
+       for n in one two three four
+       do
+               test_commit $n conflict
+       done &&
+       git checkout -b no-conflict-branch A &&
+       for n in J K L M
+       do
+               test_commit $n file$n
+       done &&
+       git checkout -b no-ff-branch A &&
+       for n in N O P
+       do
+               test_commit $n file$n
+       done
+'
+
+# "exec" commands are ran with the user shell by default, but this may
+# be non-POSIX. For example, if SHELL=zsh then ">file" doesn't work
+# to create a file. Unseting SHELL avoids such non-portable behavior
+# in tests.
+SHELL=
+
+test_expect_success 'rebase -i with the exec command' '
+       git checkout master &&
+       (
+       FAKE_LINES="1 exec_>touch-one
+               2 exec_>touch-two exec_false exec_>touch-three
+               3 4 exec_>\"touch-file__name_with_spaces\";_>touch-after-semicolon 5" &&
+       export FAKE_LINES &&
+       test_must_fail git rebase -i A
+       ) &&
+       test_path_is_file touch-one &&
+       test_path_is_file touch-two &&
+       test_path_is_missing touch-three " (should have stopped before)" &&
+       test $(git rev-parse C) = $(git rev-parse HEAD) || {
+               echo "Stopped at wrong revision:"
+               echo "($(git describe --tags HEAD) instead of C)"
+               false
+       } &&
+       git rebase --continue &&
+       test_path_is_file touch-three &&
+       test_path_is_file "touch-file  name with spaces" &&
+       test_path_is_file touch-after-semicolon &&
+       test $(git rev-parse master) = $(git rev-parse HEAD) || {
+               echo "Stopped at wrong revision:"
+               echo "($(git describe --tags HEAD) instead of master)"
+               false
+       } &&
+       rm -f touch-*
+'
+
+test_expect_success 'rebase -i with the exec command runs from tree root' '
+       git checkout master &&
+       mkdir subdir && (cd subdir &&
+       FAKE_LINES="1 exec_>touch-subdir" \
+               git rebase -i HEAD^
+       ) &&
+       test_path_is_file touch-subdir &&
+       rm -fr subdir
+'
+
+test_expect_success 'rebase -i with the exec command checks tree cleanness' '
+       git checkout master &&
+       (
+       FAKE_LINES="exec_echo_foo_>file1 1" &&
+       export FAKE_LINES &&
+       test_must_fail git rebase -i HEAD^
+       ) &&
+       test $(git rev-parse master^) = $(git rev-parse HEAD) || {
+               echo "Stopped at wrong revision:"
+               echo "($(git describe --tags HEAD) instead of master^)"
+               false
+       } &&
+       git reset --hard &&
+       git rebase --continue
 '
 
 test_expect_success 'no changes are a nop' '
+       git checkout branch2 &&
        git rebase -i F &&
        test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" &&
        test $(git rev-parse I) = $(git rev-parse HEAD)
@@ -111,19 +172,20 @@ test_expect_success 'exchange two commits' '
 
 cat > expect << EOF
 diff --git a/file1 b/file1
-index e69de29..00750ed 100644
+index f70f10e..fd79235 100644
 --- a/file1
 +++ b/file1
-@@ -0,0 +1 @@
-+3
+@@ -1 +1 @@
+-A
++G
 EOF
 
 cat > expect2 << EOF
-<<<<<<< HEAD:file1
-2
+<<<<<<< HEAD
+D
 =======
-3
->>>>>>> b7ca976... G:file1
+G
+>>>>>>> 5d18e54... G
 EOF
 
 test_expect_success 'stop on conflicting pick' '
@@ -142,7 +204,18 @@ test_expect_success 'abort' '
        git rebase --abort &&
        test $(git rev-parse new-branch1) = $(git rev-parse HEAD) &&
        test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" &&
-       ! test -d .git/rebase-merge
+       test_path_is_missing .git/rebase-merge
+'
+
+test_expect_success 'abort with error when new base cannot be checked out' '
+       git rm --cached file1 &&
+       git commit -m "remove file in base" &&
+       test_must_fail git rebase -i master > output 2>&1 &&
+       grep "The following untracked working tree files would be overwritten by checkout:" \
+               output &&
+       grep "file1" output &&
+       test_path_is_missing .git/rebase-merge &&
+       git reset --hard HEAD^
 '
 
 test_expect_success 'retain authorship' '
@@ -161,7 +234,8 @@ test_expect_success 'squash' '
        test_tick &&
        GIT_AUTHOR_NAME="Nitfol" git commit -m "nitfol" file7 &&
        echo "******************************" &&
-       FAKE_LINES="1 squash 2" git rebase -i --onto master HEAD~2 &&
+       FAKE_LINES="1 squash 2" EXPECT_HEADER_COUNT=2 \
+               git rebase -i --onto master HEAD~2 &&
        test B = $(cat file7) &&
        test $(git rev-parse HEAD^) = $(git rev-parse master)
 '
@@ -179,6 +253,12 @@ test_expect_success '-p handles "no changes" gracefully' '
        test $HEAD = $(git rev-parse HEAD)
 '
 
+test_expect_failure 'exchange two commits with -p' '
+       FAKE_LINES="2 1" git rebase -i -p HEAD~2 &&
+       test H = $(git cat-file commit HEAD^ | sed -ne \$p) &&
+       test G = $(git cat-file commit HEAD | sed -ne \$p)
+'
+
 test_expect_success 'preserve merges with -p' '
        git checkout -b to-be-preserved master^ &&
        : > unrelated-file &&
@@ -256,30 +336,113 @@ test_expect_success 'verbose flag is heeded, even after --continue' '
 test_expect_success 'multi-squash only fires up editor once' '
        base=$(git rev-parse HEAD~4) &&
        FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 squash 2 squash 3 squash 4" \
+               EXPECT_HEADER_COUNT=4 \
                git rebase -i $base &&
        test $base = $(git rev-parse HEAD^) &&
        test 1 = $(git show | grep ONCE | wc -l)
 '
 
+test_expect_success 'multi-fixup does not fire up editor' '
+       git checkout -b multi-fixup E &&
+       base=$(git rev-parse HEAD~4) &&
+       FAKE_COMMIT_AMEND="NEVER" FAKE_LINES="1 fixup 2 fixup 3 fixup 4" \
+               git rebase -i $base &&
+       test $base = $(git rev-parse HEAD^) &&
+       test 0 = $(git show | grep NEVER | wc -l) &&
+       git checkout to-be-rebased &&
+       git branch -D multi-fixup
+'
+
+test_expect_success 'commit message used after conflict' '
+       git checkout -b conflict-fixup conflict-branch &&
+       base=$(git rev-parse HEAD~4) &&
+       (
+               FAKE_LINES="1 fixup 3 fixup 4" &&
+               export FAKE_LINES &&
+               test_must_fail git rebase -i $base
+       ) &&
+       echo three > conflict &&
+       git add conflict &&
+       FAKE_COMMIT_AMEND="ONCE" EXPECT_HEADER_COUNT=2 \
+               git rebase --continue &&
+       test $base = $(git rev-parse HEAD^) &&
+       test 1 = $(git show | grep ONCE | wc -l) &&
+       git checkout to-be-rebased &&
+       git branch -D conflict-fixup
+'
+
+test_expect_success 'commit message retained after conflict' '
+       git checkout -b conflict-squash conflict-branch &&
+       base=$(git rev-parse HEAD~4) &&
+       (
+               FAKE_LINES="1 fixup 3 squash 4" &&
+               export FAKE_LINES &&
+               test_must_fail git rebase -i $base
+       ) &&
+       echo three > conflict &&
+       git add conflict &&
+       FAKE_COMMIT_AMEND="TWICE" EXPECT_HEADER_COUNT=2 \
+               git rebase --continue &&
+       test $base = $(git rev-parse HEAD^) &&
+       test 2 = $(git show | grep TWICE | wc -l) &&
+       git checkout to-be-rebased &&
+       git branch -D conflict-squash
+'
+
+cat > expect-squash-fixup << EOF
+B
+
+D
+
+ONCE
+EOF
+
+test_expect_success 'squash and fixup generate correct log messages' '
+       git checkout -b squash-fixup E &&
+       base=$(git rev-parse HEAD~4) &&
+       FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 fixup 2 squash 3 fixup 4" \
+               EXPECT_HEADER_COUNT=4 \
+               git rebase -i $base &&
+       git cat-file commit HEAD | sed -e 1,/^\$/d > actual-squash-fixup &&
+       test_cmp expect-squash-fixup actual-squash-fixup &&
+       git checkout to-be-rebased &&
+       git branch -D squash-fixup
+'
+
+test_expect_success 'squash ignores comments' '
+       git checkout -b skip-comments E &&
+       base=$(git rev-parse HEAD~4) &&
+       FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="# 1 # squash 2 # squash 3 # squash 4 #" \
+               EXPECT_HEADER_COUNT=4 \
+               git rebase -i $base &&
+       test $base = $(git rev-parse HEAD^) &&
+       test 1 = $(git show | grep ONCE | wc -l) &&
+       git checkout to-be-rebased &&
+       git branch -D skip-comments
+'
+
+test_expect_success 'squash ignores blank lines' '
+       git checkout -b skip-blank-lines E &&
+       base=$(git rev-parse HEAD~4) &&
+       FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="> 1 > squash 2 > squash 3 > squash 4 >" \
+               EXPECT_HEADER_COUNT=4 \
+               git rebase -i $base &&
+       test $base = $(git rev-parse HEAD^) &&
+       test 1 = $(git show | grep ONCE | wc -l) &&
+       git checkout to-be-rebased &&
+       git branch -D skip-blank-lines
+'
+
 test_expect_success 'squash works as expected' '
-       for n in one two three four
-       do
-               echo $n >> file$n &&
-               git add file$n &&
-               git commit -m $n
-       done &&
+       git checkout -b squash-works no-conflict-branch &&
        one=$(git rev-parse HEAD~3) &&
-       FAKE_LINES="1 squash 3 2" git rebase -i HEAD~3 &&
+       FAKE_LINES="1 squash 3 2" EXPECT_HEADER_COUNT=2 \
+               git rebase -i HEAD~3 &&
        test $one = $(git rev-parse HEAD~2)
 '
 
 test_expect_success 'interrupted squash works as expected' '
-       for n in one two three four
-       do
-               echo $n >> conflict &&
-               git add conflict &&
-               git commit -m $n
-       done &&
+       git checkout -b interrupted-squash conflict-branch &&
        one=$(git rev-parse HEAD~3) &&
        (
                FAKE_LINES="1 squash 3 2" &&
@@ -296,12 +459,7 @@ test_expect_success 'interrupted squash works as expected' '
 '
 
 test_expect_success 'interrupted squash works as expected (case 2)' '
-       for n in one two three four
-       do
-               echo $n >> conflict &&
-               git add conflict &&
-               git commit -m $n
-       done &&
+       git checkout -b interrupted-squash2 conflict-branch &&
        one=$(git rev-parse HEAD~3) &&
        (
                FAKE_LINES="3 squash 1 2" &&
@@ -470,4 +628,92 @@ test_expect_success 'avoid unnecessary reset' '
        test 123456789 = $MTIME
 '
 
+test_expect_success 'reword' '
+       git checkout -b reword-branch master &&
+       FAKE_LINES="1 2 3 reword 4" FAKE_COMMIT_MESSAGE="E changed" git rebase -i A &&
+       git show HEAD | grep "E changed" &&
+       test $(git rev-parse master) != $(git rev-parse HEAD) &&
+       test $(git rev-parse master^) = $(git rev-parse HEAD^) &&
+       FAKE_LINES="1 2 reword 3 4" FAKE_COMMIT_MESSAGE="D changed" git rebase -i A &&
+       git show HEAD^ | grep "D changed" &&
+       FAKE_LINES="reword 1 2 3 4" FAKE_COMMIT_MESSAGE="B changed" git rebase -i A &&
+       git show HEAD~3 | grep "B changed" &&
+       FAKE_LINES="1 reword 2 3 4" FAKE_COMMIT_MESSAGE="C changed" git rebase -i A &&
+       git show HEAD~2 | grep "C changed"
+'
+
+test_expect_success 'rebase -i can copy notes' '
+       git config notes.rewrite.rebase true &&
+       git config notes.rewriteRef "refs/notes/*" &&
+       test_commit n1 &&
+       test_commit n2 &&
+       test_commit n3 &&
+       git notes add -m"a note" n3 &&
+       git rebase --onto n1 n2 &&
+       test "a note" = "$(git notes show HEAD)"
+'
+
+cat >expect <<EOF
+an earlier note
+a note
+EOF
+
+test_expect_success 'rebase -i can copy notes over a fixup' '
+       git reset --hard n3 &&
+       git notes add -m"an earlier note" n2 &&
+       GIT_NOTES_REWRITE_MODE=concatenate FAKE_LINES="1 fixup 2" git rebase -i n1 &&
+       git notes show > output &&
+       test_cmp expect output
+'
+
+test_expect_success 'rebase while detaching HEAD' '
+       git symbolic-ref HEAD &&
+       grandparent=$(git rev-parse HEAD~2) &&
+       test_tick &&
+       FAKE_LINES="2 1" git rebase -i HEAD~2 HEAD^0 &&
+       test $grandparent = $(git rev-parse HEAD~2) &&
+       test_must_fail git symbolic-ref HEAD
+'
+
+test_tick # Ensure that the rebased commits get a different timestamp.
+test_expect_success 'always cherry-pick with --no-ff' '
+       git checkout no-ff-branch &&
+       git tag original-no-ff-branch &&
+       git rebase -i --no-ff A &&
+       touch empty &&
+       for p in 0 1 2
+       do
+               test ! $(git rev-parse HEAD~$p) = $(git rev-parse original-no-ff-branch~$p) &&
+               git diff HEAD~$p original-no-ff-branch~$p > out &&
+               test_cmp empty out
+       done &&
+       test $(git rev-parse HEAD~3) = $(git rev-parse original-no-ff-branch~3) &&
+       git diff HEAD~3 original-no-ff-branch~3 > out &&
+       test_cmp empty out
+'
+
+test_expect_success 'set up commits with funny messages' '
+       git checkout -b funny A &&
+       echo >>file1 &&
+       test_tick &&
+       git commit -a -m "end with slash\\" &&
+       echo >>file1 &&
+       test_tick &&
+       git commit -a -m "something (\000) that looks like octal" &&
+       echo >>file1 &&
+       test_tick &&
+       git commit -a -m "something (\n) that looks like a newline" &&
+       echo >>file1 &&
+       test_tick &&
+       git commit -a -m "another commit"
+'
+
+test_expect_success 'rebase-i history with funny messages' '
+       git rev-list A..funny >expect &&
+       test_tick &&
+       FAKE_LINES="1 2 3 4" git rebase -i A &&
+       git rev-list A.. >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 2999e78937f31a45e9e2ea925f69ac00f157503f..fbb3f2e0dfcf1a0673dbd2022a4ed843990fce52 100755 (executable)
@@ -38,7 +38,7 @@ testrebase() {
                # Clean up the state from the previous one
                git reset --hard pre-rebase &&
                test_must_fail git rebase$type master &&
-               test -d "$dotest" &&
+               test_path_is_dir "$dotest" &&
                git rebase --abort &&
                test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) &&
                test ! -d "$dotest"
@@ -49,7 +49,7 @@ testrebase() {
                # Clean up the state from the previous one
                git reset --hard pre-rebase &&
                test_must_fail git rebase$type master &&
-               test -d "$dotest" &&
+               test_path_is_dir "$dotest" &&
                test_must_fail git rebase --skip &&
                test $(git rev-parse HEAD) = $(git rev-parse master) &&
                git rebase --abort &&
@@ -62,7 +62,7 @@ testrebase() {
                # Clean up the state from the previous one
                git reset --hard pre-rebase &&
                test_must_fail git rebase$type master &&
-               test -d "$dotest" &&
+               test_path_is_dir "$dotest" &&
                echo c > a &&
                echo d >> a &&
                git add a &&
index e12cd578e8663ad8717aecde310bb0b6a500c2a2..2062b858bbcb63a715d87f3b12adfd0e9ceb3a67 100755 (executable)
@@ -32,8 +32,8 @@ test_expect_success rebase '
 
        git checkout side &&
        git rebase master &&
-       git cat-file commit HEAD | sed -e "1,/^$/d" >actual &&
-       git cat-file commit side@{1} | sed -e "1,/^$/d" >expect &&
+       git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+       git cat-file commit side@{1} | sed -e "1,/^\$/d" >expect &&
        test_cmp expect actual
 
 '
index e6c832780fbe00afe5c513f0f5e4c87d7020e2a4..74161a42ec34c2d33780a80425ec1be5a4028f03 100755 (executable)
@@ -32,33 +32,34 @@ export GIT_AUTHOR_EMAIL
 test_expect_success 'setup for merge-preserving rebase' \
        'echo First > A &&
        git add A &&
-       git-commit -m "Add A1" &&
+       git commit -m "Add A1" &&
        git checkout -b topic &&
        echo Second > B &&
        git add B &&
-       git-commit -m "Add B1" &&
+       git commit -m "Add B1" &&
        git checkout -f master &&
        echo Third >> A &&
-       git-commit -a -m "Modify A2" &&
+       git commit -a -m "Modify A2" &&
 
        git clone ./. clone1 &&
-       cd clone1 &&
+       (cd clone1 &&
        git checkout -b topic origin/topic &&
-       git merge origin/master &&
-       cd .. &&
+       git merge origin/master
+       ) &&
 
        echo Fifth > B &&
        git add B &&
        git commit -m "Add different B" &&
 
        git clone ./. clone2 &&
-       cd clone2 &&
-       git checkout -b topic origin/topic &&
-       test_must_fail git merge origin/master &&
-       echo Resolved > B &&
-       git add B &&
-       git commit -m "Merge origin/master into topic" &&
-       cd .. &&
+       (
+               cd clone2 &&
+               git checkout -b topic origin/topic &&
+               test_must_fail git merge origin/master &&
+               echo Resolved >B &&
+               git add B &&
+               git commit -m "Merge origin/master into topic"
+       ) &&
 
        git checkout topic &&
        echo Fourth >> B &&
@@ -71,7 +72,7 @@ test_expect_success 'rebase -p fakes interactive rebase' '
        git fetch &&
        git rebase -p origin/topic &&
        test 1 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) &&
-       test 1 = $(git rev-list --all --pretty=oneline | grep "Merge commit" | wc -l)
+       test 1 = $(git rev-list --all --pretty=oneline | grep "Merge remote branch " | wc -l)
        )
 '
 
index c49143a1a45d6949253e2daf0e57f60029041e60..6f73b95558c85264d073d42f67c407967c4d5c69 100755 (executable)
@@ -43,11 +43,11 @@ test_expect_success 'setup' '
 # G2 = same changes as G
 test_expect_success 'skip same-resolution merges with -p' '
        git checkout H &&
-       ! git merge E &&
+       test_must_fail git merge E &&
        test_commit L file1 23 &&
        git checkout I &&
        test_commit G2 file1 3 &&
-       ! git merge E &&
+       test_must_fail git merge E &&
        test_commit J file1 23 &&
        test_commit K file7 file7 &&
        git rebase -i -p L &&
@@ -65,11 +65,11 @@ test_expect_success 'skip same-resolution merges with -p' '
 # G2 = different changes as G
 test_expect_success 'keep different-resolution merges with -p' '
        git checkout H &&
-       ! git merge E &&
+       test_must_fail git merge E &&
        test_commit L2 file1 23 &&
        git checkout I &&
        test_commit G3 file1 4 &&
-       ! git merge E &&
+       test_must_fail git merge E &&
        test_commit J2 file1 24 &&
        test_commit K2 file7 file7 &&
        test_must_fail git rebase -i -p L2 &&
index 6533505218a51db369d03357603c2ad1926ce448..14a23cd8726fbb33df480133ed0466177916b7fb 100755 (executable)
@@ -10,7 +10,7 @@ a merge to before the merge.
 '
 . ./test-lib.sh
 
-. ../lib-rebase.sh
+. "$TEST_DIRECTORY"/lib-rebase.sh
 
 set_fake_editor
 
diff --git a/t/t3414-rebase-preserve-onto.sh b/t/t3414-rebase-preserve-onto.sh
new file mode 100755 (executable)
index 0000000..ee0a6cc
--- /dev/null
@@ -0,0 +1,80 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Greg Price
+#
+
+test_description='git rebase -p should respect --onto
+
+In a rebase with --onto, we should rewrite all the commits that
+aren'"'"'t on top of $ONTO, even if they are on top of $UPSTREAM.
+'
+. ./test-lib.sh
+
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+# Set up branches like this:
+# A1---B1---E1---F1---G1
+#  \    \             /
+#   \    \--C1---D1--/
+#    H1
+
+test_expect_success 'setup' '
+       test_commit A1 &&
+       test_commit B1 &&
+       test_commit C1 &&
+       test_commit D1 &&
+       git reset --hard B1 &&
+       test_commit E1 &&
+       test_commit F1 &&
+       test_merge G1 D1 &&
+       git reset --hard A1 &&
+       test_commit H1
+'
+
+# Now rebase merge G1 from both branches' base B1, both should move:
+# A1---B1---E1---F1---G1
+#  \    \             /
+#   \    \--C1---D1--/
+#    \
+#     H1---E2---F2---G2
+#      \             /
+#       \--C2---D2--/
+
+test_expect_success 'rebase from B1 onto H1' '
+       git checkout G1 &&
+       git rebase -p --onto H1 B1 &&
+       test "$(git rev-parse HEAD^1^1^1)" = "$(git rev-parse H1)" &&
+       test "$(git rev-parse HEAD^2^1^1)" = "$(git rev-parse H1)"
+'
+
+# On the other hand if rebase from E1 which is within one branch,
+# then the other branch stays:
+# A1---B1---E1---F1---G1
+#  \    \             /
+#   \    \--C1---D1--/
+#    \             \
+#     H1-----F3-----G3
+
+test_expect_success 'rebase from E1 onto H1' '
+       git checkout G1 &&
+       git rebase -p --onto H1 E1 &&
+       test "$(git rev-parse HEAD^1^1)" = "$(git rev-parse H1)" &&
+       test "$(git rev-parse HEAD^2)" = "$(git rev-parse D1)"
+'
+
+# And the same if we rebase from a commit in the second-parent branch.
+# A1---B1---E1---F1----G1
+#  \    \          \   /
+#   \    \--C1---D1-\-/
+#    \               \
+#     H1------D3------G4
+
+test_expect_success 'rebase from C1 onto H1' '
+       git checkout G1 &&
+       git rev-list --first-parent --pretty=oneline C1..G1 &&
+       git rebase -p --onto H1 C1 &&
+       test "$(git rev-parse HEAD^2^1)" = "$(git rev-parse H1)" &&
+       test "$(git rev-parse HEAD^1)" = "$(git rev-parse F1)"
+'
+
+test_done
diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh
new file mode 100755 (executable)
index 0000000..37cb89a
--- /dev/null
@@ -0,0 +1,97 @@
+#!/bin/sh
+
+test_description='auto squash'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       echo 0 >file0 &&
+       git add . &&
+       test_tick &&
+       git commit -m "initial commit" &&
+       echo 0 >file1 &&
+       echo 2 >file2 &&
+       git add . &&
+       test_tick &&
+       git commit -m "first commit" &&
+       echo 3 >file3 &&
+       git add . &&
+       test_tick &&
+       git commit -m "second commit" &&
+       git tag base
+'
+
+test_auto_fixup() {
+       git reset --hard base &&
+       echo 1 >file1 &&
+       git add -u &&
+       test_tick &&
+       git commit -m "fixup! first"
+
+       git tag $1 &&
+       test_tick &&
+       git rebase $2 -i HEAD^^^ &&
+       git log --oneline >actual &&
+       test 3 = $(wc -l <actual) &&
+       git diff --exit-code $1 &&
+       test 1 = "$(git cat-file blob HEAD^:file1)" &&
+       test 1 = $(git cat-file commit HEAD^ | grep first | wc -l)
+}
+
+test_expect_success 'auto fixup (option)' '
+       test_auto_fixup final-fixup-option --autosquash
+'
+
+test_expect_success 'auto fixup (config)' '
+       git config rebase.autosquash true &&
+       test_auto_fixup final-fixup-config-true &&
+       test_must_fail test_auto_fixup fixup-config-true-no --no-autosquash &&
+       git config rebase.autosquash false &&
+       test_must_fail test_auto_fixup final-fixup-config-false
+'
+
+test_auto_squash() {
+       git reset --hard base &&
+       echo 1 >file1 &&
+       git add -u &&
+       test_tick &&
+       git commit -m "squash! first"
+
+       git tag $1 &&
+       test_tick &&
+       git rebase $2 -i HEAD^^^ &&
+       git log --oneline >actual &&
+       test 3 = $(wc -l <actual) &&
+       git diff --exit-code $1 &&
+       test 1 = "$(git cat-file blob HEAD^:file1)" &&
+       test 2 = $(git cat-file commit HEAD^ | grep first | wc -l)
+}
+
+test_expect_success 'auto squash (option)' '
+       test_auto_squash final-squash --autosquash
+'
+
+test_expect_success 'auto squash (config)' '
+       git config rebase.autosquash true &&
+       test_auto_squash final-squash-config-true &&
+       test_must_fail test_auto_squash squash-config-true-no --no-autosquash &&
+       git config rebase.autosquash false &&
+       test_must_fail test_auto_squash final-squash-config-false
+'
+
+test_expect_success 'misspelled auto squash' '
+       git reset --hard base &&
+       echo 1 >file1 &&
+       git add -u &&
+       test_tick &&
+       git commit -m "squash! forst"
+       git tag final-missquash &&
+       test_tick &&
+       git rebase --autosquash -i HEAD^^^ &&
+       git log --oneline >actual &&
+       test 4 = $(wc -l <actual) &&
+       git diff --exit-code final-missquash &&
+       test 0 = $(git rev-list final-missquash...HEAD | wc -l)
+'
+
+test_done
diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh
new file mode 100755 (executable)
index 0000000..ddf2f64
--- /dev/null
@@ -0,0 +1,105 @@
+#!/bin/sh
+
+test_description='git rebase --onto A...B'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-rebase.sh"
+
+# Rebase only the tip commit of "topic" on merge base between "master"
+# and "topic".  Cannot do this for "side" with "master" because there
+# is no single merge base.
+#
+#
+#          F---G topic                             G'
+#         /                                       /
+# A---B---C---D---E master      -->       A---B---C---D---E
+#      \   \ /
+#      \   x
+#       \ / \
+#        H---I---J---K side
+
+test_expect_success setup '
+       test_commit A &&
+       test_commit B &&
+       git branch side &&
+       test_commit C &&
+       git branch topic &&
+       git checkout side &&
+       test_commit H &&
+       git checkout master &&
+       test_tick &&
+       git merge H &&
+       git tag D &&
+       test_commit E &&
+       git checkout topic &&
+       test_commit F &&
+       test_commit G &&
+       git checkout side &&
+       test_tick &&
+       git merge C &&
+       git tag I &&
+       test_commit J &&
+       test_commit K
+'
+
+test_expect_success 'rebase --onto master...topic' '
+       git reset --hard &&
+       git checkout topic &&
+       git reset --hard G &&
+
+       git rebase --onto master...topic F &&
+       git rev-parse HEAD^1 >actual &&
+       git rev-parse C^0 >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'rebase --onto master...' '
+       git reset --hard &&
+       git checkout topic &&
+       git reset --hard G &&
+
+       git rebase --onto master... F &&
+       git rev-parse HEAD^1 >actual &&
+       git rev-parse C^0 >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'rebase --onto master...side' '
+       git reset --hard &&
+       git checkout side &&
+       git reset --hard K &&
+
+       test_must_fail git rebase --onto master...side J
+'
+
+test_expect_success 'rebase -i --onto master...topic' '
+       git reset --hard &&
+       git checkout topic &&
+       git reset --hard G &&
+       set_fake_editor &&
+       EXPECT_COUNT=1 git rebase -i --onto master...topic F &&
+       git rev-parse HEAD^1 >actual &&
+       git rev-parse C^0 >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'rebase -i --onto master...' '
+       git reset --hard &&
+       git checkout topic &&
+       git reset --hard G &&
+       set_fake_editor &&
+       EXPECT_COUNT=1 git rebase -i --onto master... F &&
+       git rev-parse HEAD^1 >actual &&
+       git rev-parse C^0 >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'rebase -i --onto master...side' '
+       git reset --hard &&
+       git checkout side &&
+       git reset --hard K &&
+
+       test_must_fail git rebase -i --onto master...side J
+'
+
+test_done
diff --git a/t/t3417-rebase-whitespace-fix.sh b/t/t3417-rebase-whitespace-fix.sh
new file mode 100755 (executable)
index 0000000..220a740
--- /dev/null
@@ -0,0 +1,126 @@
+#!/bin/sh
+
+test_description='git rebase --whitespace=fix
+
+This test runs git rebase --whitespace=fix and make sure that it works.
+'
+
+. ./test-lib.sh
+
+# prepare initial revision of "file" with a blank line at the end
+cat >file <<EOF
+a
+b
+c
+
+EOF
+
+# expected contents in "file" after rebase
+cat >expect-first <<EOF
+a
+b
+c
+EOF
+
+# prepare second revision of "file"
+cat >second <<EOF
+a
+b
+c
+
+d
+e
+f
+
+
+
+
+EOF
+
+# expected contents in second revision after rebase
+cat >expect-second <<EOF
+a
+b
+c
+
+d
+e
+f
+EOF
+
+test_expect_success 'blank line at end of file; extend at end of file' '
+       git commit --allow-empty -m "Initial empty commit" &&
+       git add file && git commit -m first &&
+       mv second file &&
+       git add file && git commit -m second &&
+       git rebase --whitespace=fix HEAD^^ &&
+       git diff --exit-code HEAD^:file expect-first &&
+       test_cmp file expect-second
+'
+
+# prepare third revision of "file"
+sed -e's/Z//' >third <<EOF
+a
+b
+c
+
+d
+e
+f
+    Z
+ Z
+h
+i
+j
+k
+l
+EOF
+
+sed -e's/ //g' <third >expect-third
+
+test_expect_success 'two blanks line at end of file; extend at end of file' '
+       cp third file && git add file && git commit -m third &&
+       git rebase --whitespace=fix HEAD^^ &&
+       git diff --exit-code HEAD^:file expect-second &&
+       test_cmp file expect-third
+'
+
+test_expect_success 'same, but do not remove trailing spaces' '
+       git config core.whitespace "-blank-at-eol" &&
+       git reset --hard HEAD^ &&
+       cp third file && git add file && git commit -m third &&
+       git rebase --whitespace=fix HEAD^^
+       git diff --exit-code HEAD^:file expect-second &&
+       test_cmp file third
+'
+
+sed -e's/Z//' >beginning <<EOF
+a
+                   Z
+       Z
+EOF
+
+cat >expect-beginning <<EOF
+a
+
+
+1
+2
+3
+4
+5
+EOF
+
+test_expect_success 'at beginning of file' '
+       git config core.whitespace "blank-at-eol" &&
+       cp beginning file &&
+       git commit -m beginning file &&
+       for i in 1 2 3 4 5; do
+               echo $i
+       done >> file &&
+       git commit -m more file &&
+       git rebase --whitespace=fix HEAD^^ &&
+       test_cmp file expect-beginning
+'
+
+test_done
diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh
new file mode 100755 (executable)
index 0000000..3b0d273
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+test_description='git rebase --continue tests'
+
+. ./test-lib.sh
+
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+set_fake_editor
+
+test_expect_success 'setup' '
+       test_commit "commit-new-file-F1" F1 1 &&
+       test_commit "commit-new-file-F2" F2 2 &&
+
+       git checkout -b topic HEAD^ &&
+       test_commit "commit-new-file-F2-on-topic-branch" F2 22 &&
+
+       git checkout master
+'
+
+test_expect_success 'interactive rebase --continue works with touched file' '
+       rm -fr .git/rebase-* &&
+       git reset --hard &&
+       git checkout master &&
+
+       FAKE_LINES="edit 1" git rebase -i HEAD^ &&
+       test-chmtime =-60 F1 &&
+       git rebase --continue
+'
+
+test_expect_success 'non-interactive rebase --continue works with touched file' '
+       rm -fr .git/rebase-* &&
+       git reset --hard &&
+       git checkout master &&
+
+       test_must_fail git rebase --onto master master topic &&
+       echo "Resolved" >F2 &&
+       git add F2 &&
+       test-chmtime =-60 F1 &&
+       git rebase --continue
+'
+
+test_done
index dadbbc2a9f9b70a4e33f5aa825b8f9fe14eec124..f038f34b7c03b419b9341770a6924767a0b8e8d7 100755 (executable)
@@ -17,17 +17,19 @@ 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 &&
+     test_tick &&
      git commit -m "Add A." &&
 
      git checkout -b my-topic-branch &&
 
      echo Second > B &&
      git update-index --add B &&
+     test_tick &&
      git commit -m "Add B." &&
 
-     sleep 2 &&
      echo AnotherSecond > C &&
      git update-index --add C &&
+     test_tick &&
      git commit -m "Add C." &&
 
      git checkout -f master &&
@@ -35,6 +37,7 @@ test_expect_success \
 
      echo Third >> A &&
      git update-index A &&
+     test_tick &&
      git commit -m "Modify A." &&
 
      expr "$(echo $(git cherry master my-topic-branch) )" : "+ [^ ]* + .*"
index bb4cf00d78637b3bdf0c4e3da99291e0ab3c718f..bc7aedd0484ed5ad1115cac0f22943445d658f47 100755 (executable)
@@ -41,13 +41,32 @@ test_expect_success setup '
        git tag rename2
 '
 
+test_expect_success 'cherry-pick --nonsense' '
+
+       pos=$(git rev-parse HEAD) &&
+       git diff --exit-code HEAD &&
+       test_must_fail git cherry-pick --nonsense 2>msg &&
+       git diff --exit-code HEAD "$pos" &&
+       grep '[Uu]sage:' msg
+'
+
+test_expect_success 'revert --nonsense' '
+
+       pos=$(git rev-parse HEAD) &&
+       git diff --exit-code HEAD &&
+       test_must_fail git revert --nonsense 2>msg &&
+       git diff --exit-code HEAD "$pos" &&
+       grep '[Uu]sage:' msg
+'
+
 test_expect_success 'cherry-pick after renaming branch' '
 
        git checkout rename2 &&
        git cherry-pick added &&
        test $(git rev-parse HEAD^) = $(git rev-parse rename2) &&
        test -f opos &&
-       grep "Add extra line at the end" opos
+       grep "Add extra line at the end" opos &&
+       git reflog -1 | grep cherry-pick
 
 '
 
@@ -57,7 +76,8 @@ test_expect_success 'revert after renaming branch' '
        git revert added &&
        test $(git rev-parse HEAD^) = $(git rev-parse rename1) &&
        test -f spoo &&
-       ! grep "Add extra line at the end" spoo
+       ! grep "Add extra line at the end" spoo &&
+       git reflog -1 | grep revert
 
 '
 
@@ -66,7 +86,7 @@ test_expect_success 'revert forbidden on dirty working tree' '
        echo content >extra_file &&
        git add extra_file &&
        test_must_fail git revert HEAD 2>errors &&
-       grep "Dirty index" errors
+       grep "Your local changes would be overwritten by " errors
 
 '
 
index 9aaeabd972ffd3009fb62b9884516cc876bbb35f..c10b28cf5731705b437793a58f5acd6c605ad579 100755 (executable)
@@ -13,15 +13,33 @@ test_expect_success setup '
 
        git checkout -b empty-branch &&
        test_tick &&
-       git commit --allow-empty -m "empty"
+       git commit --allow-empty -m "empty" &&
+
+       echo third >> file1 &&
+       git add file1 &&
+       test_tick &&
+       git commit --allow-empty-message -m ""
 
 '
 
-test_expect_code 1 'cherry-pick an empty commit' '
+test_expect_success 'cherry-pick an empty commit' '
+       git checkout master && {
+               git cherry-pick empty-branch^
+               test "$?" = 1
+       }
+'
 
-       git checkout master &&
-       git cherry-pick empty-branch
+test_expect_success 'index lockfile was removed' '
+
+       test ! -f .git/index.lock
+
+'
 
+test_expect_success 'cherry-pick a commit with an empty message' '
+       git checkout master && {
+               git cherry-pick empty-branch
+               test "$?" = 1
+       }
 '
 
 test_expect_success 'index lockfile was removed' '
diff --git a/t/t3506-cherry-pick-ff.sh b/t/t3506-cherry-pick-ff.sh
new file mode 100755 (executable)
index 0000000..e17ae71
--- /dev/null
@@ -0,0 +1,98 @@
+#!/bin/sh
+
+test_description='test cherry-picking with --ff option'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       echo first > file1 &&
+       git add file1 &&
+       test_tick &&
+       git commit -m "first" &&
+       git tag first &&
+
+       git checkout -b other &&
+       echo second >> file1 &&
+       git add file1 &&
+       test_tick &&
+       git commit -m "second" &&
+       git tag second
+'
+
+test_expect_success 'cherry-pick using --ff fast forwards' '
+       git checkout master &&
+       git reset --hard first &&
+       test_tick &&
+       git cherry-pick --ff second &&
+       test "$(git rev-parse --verify HEAD)" = "$(git rev-parse --verify second)"
+'
+
+test_expect_success 'cherry-pick not using --ff does not fast forwards' '
+       git checkout master &&
+       git reset --hard first &&
+       test_tick &&
+       git cherry-pick second &&
+       test "$(git rev-parse --verify HEAD)" != "$(git rev-parse --verify second)"
+'
+
+#
+# We setup the following graph:
+#
+#            B---C
+#           /   /
+#      first---A
+#
+# (This has been taken from t3502-cherry-pick-merge.sh)
+#
+test_expect_success 'merge setup' '
+       git checkout master &&
+       git reset --hard first &&
+       echo new line >A &&
+       git add A &&
+       test_tick &&
+       git commit -m "add line to A" A &&
+       git tag A &&
+       git checkout -b side first &&
+       echo new line >B &&
+       git add B &&
+       test_tick &&
+       git commit -m "add line to B" B &&
+       git tag B &&
+       git checkout master &&
+       git merge side &&
+       git tag C &&
+       git checkout -b new A
+'
+
+test_expect_success 'cherry-pick a non-merge with --ff and -m should fail' '
+       git reset --hard A -- &&
+       test_must_fail git cherry-pick --ff -m 1 B &&
+       git diff --exit-code A --
+'
+
+test_expect_success 'cherry pick a merge with --ff but without -m should fail' '
+       git reset --hard A -- &&
+       test_must_fail git cherry-pick --ff C &&
+       git diff --exit-code A --
+'
+
+test_expect_success 'cherry pick with --ff a merge (1)' '
+       git reset --hard A -- &&
+       git cherry-pick --ff -m 1 C &&
+       git diff --exit-code C &&
+       test "$(git rev-parse --verify HEAD)" = "$(git rev-parse --verify C)"
+'
+
+test_expect_success 'cherry pick with --ff a merge (2)' '
+       git reset --hard B -- &&
+       git cherry-pick --ff -m 2 C &&
+       git diff --exit-code C &&
+       test "$(git rev-parse --verify HEAD)" = "$(git rev-parse --verify C)"
+'
+
+test_expect_success 'cherry pick a merge relative to nonexistent parent with --ff should fail' '
+       git reset --hard B -- &&
+       test_must_fail git cherry-pick --ff -m 3 C
+'
+
+test_done
diff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh
new file mode 100755 (executable)
index 0000000..607bf25
--- /dev/null
@@ -0,0 +1,218 @@
+#!/bin/sh
+
+test_description='test cherry-pick and revert with conflicts
+
+  -
+  + picked: rewrites foo to c
+  + base: rewrites foo to b
+  + initial: writes foo as a, unrelated as unrelated
+
+'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+       echo unrelated >unrelated &&
+       git add unrelated &&
+       test_commit initial foo a &&
+       test_commit base foo b &&
+       test_commit picked foo c &&
+       git config advice.detachedhead false
+
+'
+
+test_expect_success 'failed cherry-pick does not advance HEAD' '
+
+       git checkout -f initial^0 &&
+       git read-tree -u --reset HEAD &&
+       git clean -d -f -f -q -x &&
+
+       git update-index --refresh &&
+       git diff-index --exit-code HEAD &&
+
+       head=$(git rev-parse HEAD) &&
+       test_must_fail git cherry-pick picked &&
+       newhead=$(git rev-parse HEAD) &&
+
+       test "$head" = "$newhead"
+'
+
+test_expect_success 'advice from failed cherry-pick' "
+       git checkout -f initial^0 &&
+       git read-tree -u --reset HEAD &&
+       git clean -d -f -f -q -x &&
+
+       git update-index --refresh &&
+       git diff-index --exit-code HEAD &&
+
+       picked=\$(git rev-parse --short picked) &&
+       cat <<-EOF >expected &&
+       error: could not apply \$picked... picked
+       hint: after resolving the conflicts, mark the corrected paths
+       hint: with 'git add <paths>' or 'git rm <paths>'
+       hint: and commit the result with 'git commit -c \$picked'
+       EOF
+       test_must_fail git cherry-pick picked 2>actual &&
+
+       test_cmp expected actual
+"
+
+test_expect_success 'failed cherry-pick produces dirty index' '
+
+       git checkout -f initial^0 &&
+       git read-tree -u --reset HEAD &&
+       git clean -d -f -f -q -x &&
+
+       git update-index --refresh &&
+       git diff-index --exit-code HEAD &&
+
+       test_must_fail git cherry-pick picked &&
+
+       test_must_fail git update-index --refresh -q &&
+       test_must_fail git diff-index --exit-code HEAD
+'
+
+test_expect_success 'failed cherry-pick registers participants in index' '
+
+       git read-tree -u --reset HEAD &&
+       git clean -d -f -f -q -x &&
+       {
+               git checkout base -- foo &&
+               git ls-files --stage foo &&
+               git checkout initial -- foo &&
+               git ls-files --stage foo &&
+               git checkout picked -- foo &&
+               git ls-files --stage foo
+       } > stages &&
+       sed "
+               1 s/ 0  / 1     /
+               2 s/ 0  / 2     /
+               3 s/ 0  / 3     /
+       " < stages > expected &&
+       git checkout -f initial^0 &&
+
+       git update-index --refresh &&
+       git diff-index --exit-code HEAD &&
+
+       test_must_fail git cherry-pick picked &&
+       git ls-files --stage --unmerged > actual &&
+
+       test_cmp expected actual
+'
+
+test_expect_success 'failed cherry-pick describes conflict in work tree' '
+
+       git checkout -f initial^0 &&
+       git read-tree -u --reset HEAD &&
+       git clean -d -f -f -q -x &&
+       cat <<-EOF > expected &&
+       <<<<<<< HEAD
+       a
+       =======
+       c
+       >>>>>>> objid picked
+       EOF
+
+       git update-index --refresh &&
+       git diff-index --exit-code HEAD &&
+
+       test_must_fail git cherry-pick picked &&
+
+       sed "s/[a-f0-9]*\.\.\./objid/" foo > actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'diff3 -m style' '
+
+       git config merge.conflictstyle diff3 &&
+       git checkout -f initial^0 &&
+       git read-tree -u --reset HEAD &&
+       git clean -d -f -f -q -x &&
+       cat <<-EOF > expected &&
+       <<<<<<< HEAD
+       a
+       ||||||| parent of objid picked
+       b
+       =======
+       c
+       >>>>>>> objid picked
+       EOF
+
+       git update-index --refresh &&
+       git diff-index --exit-code HEAD &&
+
+       test_must_fail git cherry-pick picked &&
+
+       sed "s/[a-f0-9]*\.\.\./objid/" foo > actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'revert also handles conflicts sanely' '
+
+       git config --unset merge.conflictstyle &&
+       git read-tree -u --reset HEAD &&
+       git clean -d -f -f -q -x &&
+       cat <<-EOF > expected &&
+       <<<<<<< HEAD
+       a
+       =======
+       b
+       >>>>>>> parent of objid picked
+       EOF
+       {
+               git checkout picked -- foo &&
+               git ls-files --stage foo &&
+               git checkout initial -- foo &&
+               git ls-files --stage foo &&
+               git checkout base -- foo &&
+               git ls-files --stage foo
+       } > stages &&
+       sed "
+               1 s/ 0  / 1     /
+               2 s/ 0  / 2     /
+               3 s/ 0  / 3     /
+       " < stages > expected-stages &&
+       git checkout -f initial^0 &&
+
+       git update-index --refresh &&
+       git diff-index --exit-code HEAD &&
+
+       head=$(git rev-parse HEAD) &&
+       test_must_fail git revert picked &&
+       newhead=$(git rev-parse HEAD) &&
+       git ls-files --stage --unmerged > actual-stages &&
+
+       test "$head" = "$newhead" &&
+       test_must_fail git update-index --refresh -q &&
+       test_must_fail git diff-index --exit-code HEAD &&
+       test_cmp expected-stages actual-stages &&
+       sed "s/[a-f0-9]*\.\.\./objid/" foo > actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'revert conflict, diff3 -m style' '
+       git config merge.conflictstyle diff3 &&
+       git checkout -f initial^0 &&
+       git read-tree -u --reset HEAD &&
+       git clean -d -f -f -q -x &&
+       cat <<-EOF > expected &&
+       <<<<<<< HEAD
+       a
+       ||||||| objid picked
+       c
+       =======
+       b
+       >>>>>>> parent of objid picked
+       EOF
+
+       git update-index --refresh &&
+       git diff-index --exit-code HEAD &&
+
+       test_must_fail git revert picked &&
+
+       sed "s/[a-f0-9]*\.\.\./objid/" foo > actual &&
+       test_cmp expected actual
+'
+
+test_done
diff --git a/t/t3508-cherry-pick-many-commits.sh b/t/t3508-cherry-pick-many-commits.sh
new file mode 100755 (executable)
index 0000000..8e09fd0
--- /dev/null
@@ -0,0 +1,159 @@
+#!/bin/sh
+
+test_description='test cherry-picking many commits'
+
+. ./test-lib.sh
+
+check_head_differs_from() {
+       head=$(git rev-parse --verify HEAD) &&
+       arg=$(git rev-parse --verify "$1") &&
+       test "$head" != "$arg"
+}
+
+check_head_equals() {
+       head=$(git rev-parse --verify HEAD) &&
+       arg=$(git rev-parse --verify "$1") &&
+       test "$head" = "$arg"
+}
+
+test_expect_success setup '
+       echo first > file1 &&
+       git add file1 &&
+       test_tick &&
+       git commit -m "first" &&
+       git tag first &&
+
+       git checkout -b other &&
+       for val in second third fourth
+       do
+               echo $val >> file1 &&
+               git add file1 &&
+               test_tick &&
+               git commit -m "$val" &&
+               git tag $val
+       done
+'
+
+test_expect_success 'cherry-pick first..fourth works' '
+       cat <<-\EOF >expected &&
+       [master OBJID] second
+        Author: A U Thor <author@example.com>
+        1 files changed, 1 insertions(+), 0 deletions(-)
+       [master OBJID] third
+        Author: A U Thor <author@example.com>
+        1 files changed, 1 insertions(+), 0 deletions(-)
+       [master OBJID] fourth
+        Author: A U Thor <author@example.com>
+        1 files changed, 1 insertions(+), 0 deletions(-)
+       EOF
+
+       git checkout -f master &&
+       git reset --hard first &&
+       test_tick &&
+       git cherry-pick first..fourth >actual &&
+       git diff --quiet other &&
+       git diff --quiet HEAD other &&
+
+       sed -e "s/$_x05[0-9a-f][0-9a-f]/OBJID/" <actual >actual.fuzzy &&
+       test_cmp expected actual.fuzzy &&
+       check_head_differs_from fourth
+'
+
+test_expect_success 'cherry-pick --strategy resolve first..fourth works' '
+       cat <<-\EOF >expected &&
+       Trying simple merge.
+       [master OBJID] second
+        Author: A U Thor <author@example.com>
+        1 files changed, 1 insertions(+), 0 deletions(-)
+       Trying simple merge.
+       [master OBJID] third
+        Author: A U Thor <author@example.com>
+        1 files changed, 1 insertions(+), 0 deletions(-)
+       Trying simple merge.
+       [master OBJID] fourth
+        Author: A U Thor <author@example.com>
+        1 files changed, 1 insertions(+), 0 deletions(-)
+       EOF
+
+       git checkout -f master &&
+       git reset --hard first &&
+       test_tick &&
+       git cherry-pick --strategy resolve first..fourth >actual &&
+       git diff --quiet other &&
+       git diff --quiet HEAD other &&
+       sed -e "s/$_x05[0-9a-f][0-9a-f]/OBJID/" <actual >actual.fuzzy &&
+       test_cmp expected actual.fuzzy &&
+       check_head_differs_from fourth
+'
+
+test_expect_success 'cherry-pick --ff first..fourth works' '
+       git checkout -f master &&
+       git reset --hard first &&
+       test_tick &&
+       git cherry-pick --ff first..fourth &&
+       git diff --quiet other &&
+       git diff --quiet HEAD other &&
+       check_head_equals fourth
+'
+
+test_expect_success 'cherry-pick -n first..fourth works' '
+       git checkout -f master &&
+       git reset --hard first &&
+       test_tick &&
+       git cherry-pick -n first..fourth &&
+       git diff --quiet other &&
+       git diff --cached --quiet other &&
+       git diff --quiet HEAD first
+'
+
+test_expect_success 'revert first..fourth works' '
+       git checkout -f master &&
+       git reset --hard fourth &&
+       test_tick &&
+       git revert first..fourth &&
+       git diff --quiet first &&
+       git diff --cached --quiet first &&
+       git diff --quiet HEAD first
+'
+
+test_expect_success 'revert ^first fourth works' '
+       git checkout -f master &&
+       git reset --hard fourth &&
+       test_tick &&
+       git revert ^first fourth &&
+       git diff --quiet first &&
+       git diff --cached --quiet first &&
+       git diff --quiet HEAD first
+'
+
+test_expect_success 'revert fourth fourth~1 fourth~2 works' '
+       git checkout -f master &&
+       git reset --hard fourth &&
+       test_tick &&
+       git revert fourth fourth~1 fourth~2 &&
+       git diff --quiet first &&
+       git diff --cached --quiet first &&
+       git diff --quiet HEAD first
+'
+
+test_expect_success 'cherry-pick -3 fourth works' '
+       git checkout -f master &&
+       git reset --hard first &&
+       test_tick &&
+       git cherry-pick -3 fourth &&
+       git diff --quiet other &&
+       git diff --quiet HEAD other &&
+       check_head_differs_from fourth
+'
+
+test_expect_success 'cherry-pick --stdin works' '
+       git checkout -f master &&
+       git reset --hard first &&
+       test_tick &&
+       git rev-list --reverse first..fourth | git cherry-pick --stdin &&
+       git diff --quiet other &&
+       git diff --quiet HEAD other &&
+       check_head_differs_from fourth
+'
+
+test_done
diff --git a/t/t3509-cherry-pick-merge-df.sh b/t/t3509-cherry-pick-merge-df.sh
new file mode 100755 (executable)
index 0000000..a5ccdbf
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+test_description='Test cherry-pick with directory/file conflicts'
+. ./test-lib.sh
+
+test_expect_success SYMLINKS 'Setup rename across paths each below D/F conflicts' '
+       mkdir a &&
+       >a/f &&
+       git add a &&
+       git commit -m a &&
+
+       mkdir b &&
+       ln -s ../a b/a &&
+       git add b &&
+       git commit -m b &&
+
+       git checkout -b branch &&
+       rm b/a &&
+       mv a b/a &&
+       ln -s b/a a &&
+       git add . &&
+       git commit -m swap &&
+
+       >f1 &&
+       git add f1 &&
+       git commit -m f1
+'
+
+test_expect_success SYMLINKS 'Cherry-pick succeeds with rename across D/F conflicts' '
+       git reset --hard &&
+       git checkout master^0 &&
+       git cherry-pick branch
+'
+
+test_done
index 76b1bb45456a18a8c1c33256695396cc2b65a3a9..b26cabd571503696a2bfa356e4f6ac46febf74ba 100755 (executable)
@@ -28,22 +28,6 @@ embedded' &&
      git commit -m 'add files with tabs and newlines'
 "
 
-# Determine rm behavior
-# Later we will try removing an unremovable path to make sure
-# git rm barfs, but if the test is run as root that cannot be
-# arranged.
-: >test-file
-chmod a-w .
-rm -f test-file 2>/dev/null
-if test -f test-file
-then
-       test_set_prereq RO_DIR
-else
-       say 'skipping removal failure test (perhaps running as root?)'
-fi
-chmod 775 .
-rm -f test-file
-
 test_expect_success \
     'Pre-check that foo exists and is in index before git rm foo' \
     '[ -f foo ] && git ls-files --error-unmatch foo'
@@ -271,4 +255,12 @@ test_expect_success 'choking "git rm" should not let it die with cruft' '
        test "$status" != 0
 '
 
+test_expect_success 'rm removes subdirectories recursively' '
+       mkdir -p dir/subdir/subsubdir &&
+       echo content >dir/subdir/subsubdir/file &&
+       git add dir/subdir/subsubdir/file &&
+       git rm -f dir/subdir/subsubdir/file &&
+       ! test -d dir
+'
+
 test_done
index 050de42ef4148a730c30520ccaad5e9871e536bd..ec7108358e7b7f54f0b5e7530c8368e4a56576af 100755 (executable)
@@ -26,7 +26,7 @@ test_expect_success \
         chmod 755 xfoo1 &&
         git add xfoo1 &&
         case "`git ls-files --stage xfoo1`" in
-        100644" "*xfoo1) echo ok;;
+        100644" "*xfoo1) echo pass;;
         *) echo fail; git ls-files --stage xfoo1; (exit 1);;
         esac'
 
@@ -35,7 +35,7 @@ test_expect_success SYMLINKS 'git add: filemode=0 should not get confused by sym
        ln -s foo xfoo1 &&
        git add xfoo1 &&
        case "`git ls-files --stage xfoo1`" in
-       120000" "*xfoo1) echo ok;;
+       120000" "*xfoo1) echo pass;;
        *) echo fail; git ls-files --stage xfoo1; (exit 1);;
        esac
 '
@@ -47,7 +47,7 @@ test_expect_success \
         chmod 755 xfoo2 &&
         git update-index --add xfoo2 &&
         case "`git ls-files --stage xfoo2`" in
-        100644" "*xfoo2) echo ok;;
+        100644" "*xfoo2) echo pass;;
         *) echo fail; git ls-files --stage xfoo2; (exit 1);;
         esac'
 
@@ -56,7 +56,7 @@ test_expect_success SYMLINKS 'git add: filemode=0 should not get confused by sym
        ln -s foo xfoo2 &&
        git update-index --add xfoo2 &&
        case "`git ls-files --stage xfoo2`" in
-       120000" "*xfoo2) echo ok;;
+       120000" "*xfoo2) echo pass;;
        *) echo fail; git ls-files --stage xfoo2; (exit 1);;
        esac
 '
@@ -67,7 +67,7 @@ test_expect_success SYMLINKS \
         ln -s xfoo2 xfoo3 &&
         git update-index --add xfoo3 &&
         case "`git ls-files --stage xfoo3`" in
-        120000" "*xfoo3) echo ok;;
+        120000" "*xfoo3) echo pass;;
         *) echo fail; git ls-files --stage xfoo3; (exit 1);;
         esac'
 
@@ -172,14 +172,14 @@ test_expect_success 'git add --refresh' '
        test -z "`git diff-index HEAD -- foo`" &&
        git read-tree HEAD &&
        case "`git diff-index HEAD -- foo`" in
-       :100644" "*"M   foo") echo ok;;
+       :100644" "*"M   foo") echo pass;;
        *) echo fail; (exit 1);;
        esac &&
        git add --refresh -- foo &&
        test -z "`git diff-index HEAD -- foo`"
 '
 
-test_expect_success POSIXPERM 'git add should fail atomically upon an unreadable file' '
+test_expect_success POSIXPERM,SANITY 'git add should fail atomically upon an unreadable file' '
        git reset --hard &&
        date >foo1 &&
        date >foo2 &&
@@ -190,7 +190,7 @@ test_expect_success POSIXPERM 'git add should fail atomically upon an unreadable
 
 rm -f foo2
 
-test_expect_success POSIXPERM 'git add --ignore-errors' '
+test_expect_success POSIXPERM,SANITY 'git add --ignore-errors' '
        git reset --hard &&
        date >foo1 &&
        date >foo2 &&
@@ -201,7 +201,7 @@ test_expect_success POSIXPERM 'git add --ignore-errors' '
 
 rm -f foo2
 
-test_expect_success POSIXPERM 'git add (add.ignore-errors)' '
+test_expect_success POSIXPERM,SANITY 'git add (add.ignore-errors)' '
        git config add.ignore-errors 1 &&
        git reset --hard &&
        date >foo1 &&
@@ -212,7 +212,7 @@ test_expect_success POSIXPERM 'git add (add.ignore-errors)' '
 '
 rm -f foo2
 
-test_expect_success POSIXPERM 'git add (add.ignore-errors = false)' '
+test_expect_success POSIXPERM,SANITY 'git add (add.ignore-errors = false)' '
        git config add.ignore-errors 0 &&
        git reset --hard &&
        date >foo1 &&
@@ -221,6 +221,19 @@ test_expect_success POSIXPERM 'git add (add.ignore-errors = false)' '
        test_must_fail git add --verbose . &&
        ! ( git ls-files foo1 | grep foo1 )
 '
+rm -f foo2
+
+test_expect_success POSIXPERM,SANITY '--no-ignore-errors overrides config' '
+       git config add.ignore-errors 1 &&
+       git reset --hard &&
+       date >foo1 &&
+       date >foo2 &&
+       chmod 0 foo2 &&
+       test_must_fail git add --verbose --no-ignore-errors . &&
+       ! ( git ls-files foo1 | grep foo1 ) &&
+       git config add.ignore-errors 0
+'
+rm -f foo2
 
 test_expect_success BSLASHPSPEC "git add 'fo\\[ou\\]bar' ignores foobar" '
        git reset --hard &&
@@ -230,4 +243,49 @@ test_expect_success BSLASHPSPEC "git add 'fo\\[ou\\]bar' ignores foobar" '
        ! ( git ls-files foobar | grep foobar )
 '
 
+test_expect_success 'git add to resolve conflicts on otherwise ignored path' '
+       git reset --hard &&
+       H=$(git rev-parse :1/2/a) &&
+       (
+               echo "100644 $H 1       track-this"
+               echo "100644 $H 3       track-this"
+       ) | git update-index --index-info &&
+       echo track-this >>.gitignore &&
+       echo resolved >track-this &&
+       git add track-this
+'
+
+test_expect_success '"add non-existent" should fail' '
+       test_must_fail git add non-existent &&
+       ! (git ls-files | grep "non-existent")
+'
+
+test_expect_success 'git add --dry-run of existing changed file' "
+       echo new >>track-this &&
+       git add --dry-run track-this >actual 2>&1 &&
+       echo \"add 'track-this'\" | test_cmp - actual
+"
+
+test_expect_success 'git add --dry-run of non-existing file' "
+       echo ignored-file >>.gitignore &&
+       test_must_fail git add --dry-run track-this ignored-file >actual 2>&1 &&
+       echo \"fatal: pathspec 'ignored-file' did not match any files\" | test_cmp - actual
+"
+
+cat >expect.err <<\EOF
+The following paths are ignored by one of your .gitignore files:
+ignored-file
+Use -f if you really want to add them.
+fatal: no files added
+EOF
+cat >expect.out <<\EOF
+add 'track-this'
+EOF
+
+test_expect_success 'git add --dry-run --ignore-missing of non-existing file' '
+       test_must_fail git add --dry-run --ignore-missing track-this ignored-file >actual.out 2>actual.err &&
+       test_cmp expect.out actual.out &&
+       test_cmp expect.err actual.err
+'
+
 test_done
index dfc65601aa2171bfc9321753a3d90db112d81f72..d6327e7c74cd0cd3d433a71b1e9bb89eaf088ed5 100755 (executable)
@@ -2,22 +2,20 @@
 
 test_description='add -i basic tests'
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-prereq-FILEMODE.sh
 
-if ! test_have_prereq PERL; then
-       say 'skipping git add -i tests, perl not available'
-       test_done
-fi
-
-test_expect_success 'setup (initial)' '
+test_expect_success PERL 'setup (initial)' '
        echo content >file &&
        git add file &&
        echo more >>file &&
        echo lines >>file
 '
-test_expect_success 'status works (initial)' '
+test_expect_success PERL 'status works (initial)' '
        git add -i </dev/null >output &&
        grep "+1/-0 *+2/-0 file" output
 '
+
+test_expect_success PERL 'setup expected' '
 cat >expected <<EOF
 new file mode 100644
 index 0000000..d95f3ad
@@ -26,19 +24,21 @@ index 0000000..d95f3ad
 @@ -0,0 +1 @@
 +content
 EOF
-test_expect_success 'diff works (initial)' '
+'
+
+test_expect_success PERL 'diff works (initial)' '
        (echo d; echo 1) | git add -i >output &&
        sed -ne "/new file/,/content/p" <output >diff &&
        test_cmp expected diff
 '
-test_expect_success 'revert works (initial)' '
+test_expect_success PERL 'revert works (initial)' '
        git add file &&
        (echo r; echo 1) | git add -i &&
        git ls-files >output &&
        ! grep . output
 '
 
-test_expect_success 'setup (commit)' '
+test_expect_success PERL 'setup (commit)' '
        echo baseline >file &&
        git add file &&
        git commit -m commit &&
@@ -47,10 +47,12 @@ test_expect_success 'setup (commit)' '
        echo more >>file &&
        echo lines >>file
 '
-test_expect_success 'status works (commit)' '
+test_expect_success PERL 'status works (commit)' '
        git add -i </dev/null >output &&
        grep "+1/-0 *+2/-0 file" output
 '
+
+test_expect_success PERL 'setup expected' '
 cat >expected <<EOF
 index 180b47c..b6f2c08 100644
 --- a/file
@@ -59,60 +61,79 @@ index 180b47c..b6f2c08 100644
  baseline
 +content
 EOF
-test_expect_success 'diff works (commit)' '
+'
+
+test_expect_success PERL 'diff works (commit)' '
        (echo d; echo 1) | git add -i >output &&
        sed -ne "/^index/,/content/p" <output >diff &&
        test_cmp expected diff
 '
-test_expect_success 'revert works (commit)' '
+test_expect_success PERL 'revert works (commit)' '
        git add file &&
        (echo r; echo 1) | git add -i &&
        git add -i </dev/null >output &&
        grep "unchanged *+3/-0 file" output
 '
 
+
+test_expect_success PERL 'setup expected' '
 cat >expected <<EOF
 EOF
-cat >fake_editor.sh <<EOF
-EOF
-chmod a+x fake_editor.sh
-test_set_editor "$(pwd)/fake_editor.sh"
-test_expect_success 'dummy edit works' '
+'
+
+test_expect_success PERL 'setup fake editor' '
+       cat >fake_editor.sh <<EOF
+       EOF
+       chmod a+x fake_editor.sh &&
+       test_set_editor "$(pwd)/fake_editor.sh" &&
+'
+
+test_expect_success PERL 'dummy edit works' '
        (echo e; echo a) | git add -p &&
        git diff > diff &&
        test_cmp expected diff
 '
 
+test_expect_success PERL 'setup patch' '
 cat >patch <<EOF
 @@ -1,1 +1,4 @@
  this
 +patch
--doesn't
+-does not
  apply
 EOF
-echo "#!$SHELL_PATH" >fake_editor.sh
-cat >>fake_editor.sh <<\EOF
+'
+
+test_expect_success PERL 'setup fake editor' '
+       echo "#!$SHELL_PATH" >fake_editor.sh &&
+       cat >>fake_editor.sh <<\EOF &&
 mv -f "$1" oldpatch &&
 mv -f patch "$1"
 EOF
-chmod a+x fake_editor.sh
-test_set_editor "$(pwd)/fake_editor.sh"
-test_expect_success 'bad edit rejected' '
+       chmod a+x fake_editor.sh &&
+       test_set_editor "$(pwd)/fake_editor.sh"
+'
+
+test_expect_success PERL 'bad edit rejected' '
        git reset &&
        (echo e; echo n; echo d) | git add -p >output &&
        grep "hunk does not apply" output
 '
 
+test_expect_success PERL 'setup patch' '
 cat >patch <<EOF
 this patch
 is garbage
 EOF
-test_expect_success 'garbage edit rejected' '
+'
+
+test_expect_success PERL 'garbage edit rejected' '
        git reset &&
        (echo e; echo n; echo d) | git add -p >output &&
        grep "hunk does not apply" output
 '
 
+test_expect_success PERL 'setup patch' '
 cat >patch <<EOF
 @@ -1,0 +1,0 @@
  baseline
@@ -120,6 +141,9 @@ cat >patch <<EOF
 +newcontent
 +lines
 EOF
+'
+
+test_expect_success PERL 'setup expected' '
 cat >expected <<EOF
 diff --git a/file b/file
 index b5dd6c9..f910ae9 100644
@@ -132,20 +156,29 @@ index b5dd6c9..f910ae9 100644
 +more
  lines
 EOF
-test_expect_success 'real edit works' '
+'
+
+test_expect_success PERL 'real edit works' '
        (echo e; echo n; echo d) | git add -p &&
        git diff >output &&
        test_cmp expected output
 '
 
-if test "$(git config --bool core.filemode)" = false
-then
-       say 'skipping filemode tests (filesystem does not properly support modes)'
-else
-       test_set_prereq FILEMODE
-fi
+test_expect_success PERL 'skip files similarly as commit -a' '
+       git reset &&
+       echo file >.gitignore &&
+       echo changed >file &&
+       echo y | git add -p file &&
+       git diff >output &&
+       git reset &&
+       git commit -am commit &&
+       git diff >expected &&
+       test_cmp expected output &&
+       git reset --hard HEAD^
+'
+rm -f .gitignore
 
-test_expect_success FILEMODE 'patch does not affect mode' '
+test_expect_success PERL,FILEMODE 'patch does not affect mode' '
        git reset --hard &&
        echo content >>file &&
        chmod +x file &&
@@ -154,7 +187,7 @@ test_expect_success FILEMODE 'patch does not affect mode' '
        git diff file | grep "new mode"
 '
 
-test_expect_success FILEMODE 'stage mode but not hunk' '
+test_expect_success PERL,FILEMODE 'stage mode but not hunk' '
        git reset --hard &&
        echo content >>file &&
        chmod +x file &&
@@ -163,6 +196,103 @@ test_expect_success FILEMODE 'stage mode but not hunk' '
        git diff          file | grep "+content"
 '
 
+
+test_expect_success PERL,FILEMODE 'stage mode and hunk' '
+       git reset --hard &&
+       echo content >>file &&
+       chmod +x file &&
+       printf "y\\ny\\n" | git add -p &&
+       git diff --cached file | grep "new mode" &&
+       git diff --cached file | grep "+content" &&
+       test -z "$(git diff file)"
+'
+
 # end of tests disabled when filemode is not usable
 
+test_expect_success PERL 'setup again' '
+       git reset --hard &&
+       test_chmod +x file &&
+       echo content >>file
+'
+
+# Write the patch file with a new line at the top and bottom
+test_expect_success PERL 'setup patch' '
+cat >patch <<EOF
+index 180b47c..b6f2c08 100644
+--- a/file
++++ b/file
+@@ -1,2 +1,4 @@
++firstline
+ baseline
+ content
++lastline
+EOF
+'
+
+# Expected output, similar to the patch but w/ diff at the top
+test_expect_success PERL 'setup expected' '
+cat >expected <<EOF
+diff --git a/file b/file
+index b6f2c08..61b9053 100755
+--- a/file
++++ b/file
+@@ -1,2 +1,4 @@
++firstline
+ baseline
+ content
++lastline
+EOF
+'
+
+# Test splitting the first patch, then adding both
+test_expect_success PERL 'add first line works' '
+       git commit -am "clear local changes" &&
+       git apply patch &&
+       (echo s; echo y; echo y) | git add -p file &&
+       git diff --cached > diff &&
+       test_cmp expected diff
+'
+
+test_expect_success PERL 'setup expected' '
+cat >expected <<EOF
+diff --git a/non-empty b/non-empty
+deleted file mode 100644
+index d95f3ad..0000000
+--- a/non-empty
++++ /dev/null
+@@ -1 +0,0 @@
+-content
+EOF
+'
+
+test_expect_success PERL 'deleting a non-empty file' '
+       git reset --hard &&
+       echo content >non-empty &&
+       git add non-empty &&
+       git commit -m non-empty &&
+       rm non-empty &&
+       echo y | git add -p non-empty &&
+       git diff --cached >diff &&
+       test_cmp expected diff
+'
+
+test_expect_success PERL 'setup expected' '
+cat >expected <<EOF
+diff --git a/empty b/empty
+deleted file mode 100644
+index e69de29..0000000
+EOF
+'
+
+test_expect_success PERL 'deleting an empty file' '
+       git reset --hard &&
+       > empty &&
+       git add empty &&
+       git commit -m empty &&
+       rm empty &&
+       echo y | git add -p empty &&
+       git diff --cached >diff &&
+       test_cmp expected diff
+'
+
 test_done
diff --git a/t/t3702-add-edit.sh b/t/t3702-add-edit.sh
new file mode 100755 (executable)
index 0000000..4ee47cc
--- /dev/null
@@ -0,0 +1,121 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E. Schindelin
+#
+
+test_description='add -e basic tests'
+. ./test-lib.sh
+
+
+cat > file << EOF
+LO, praise of the prowess of people-kings
+of spear-armed Danes, in days long sped,
+we have heard, and what honor the athelings won!
+Oft Scyld the Scefing from squadroned foes,
+from many a tribe, the mead-bench tore,
+awing the earls. Since erst he lay
+friendless, a foundling, fate repaid him:
+for he waxed under welkin, in wealth he throve,
+till before him the folk, both far and near,
+who house by the whale-path, heard his mandate,
+gave him gifts:  a good king he!
+EOF
+
+cat > second-part << EOF
+To him an heir was afterward born,
+a son in his halls, whom heaven sent
+to favor the folk, feeling their woe
+that erst they had lacked an earl for leader
+so long a while; the Lord endowed him,
+the Wielder of Wonder, with world's renown.
+EOF
+
+test_expect_success 'setup' '
+
+       git add file &&
+       test_tick &&
+       git commit -m initial file
+
+'
+
+cat > expected-patch << EOF
+diff --git a/file b/file
+index b9834b5..9020acb 100644
+--- a/file
++++ b/file
+@@ -1,11 +1,6 @@
+-LO, praise of the prowess of people-kings
+-of spear-armed Danes, in days long sped,
+-we have heard, and what honor the athelings won!
+-Oft Scyld the Scefing from squadroned foes,
+-from many a tribe, the mead-bench tore,
+-awing the earls. Since erst he lay
+-friendless, a foundling, fate repaid him:
+-for he waxed under welkin, in wealth he throve,
+-till before him the folk, both far and near,
+-who house by the whale-path, heard his mandate,
+-gave him gifts:  a good king he!
++To him an heir was afterward born,
++a son in his halls, whom heaven sent
++to favor the folk, feeling their woe
++that erst they had lacked an earl for leader
++so long a while; the Lord endowed him,
++the Wielder of Wonder, with world's renown.
+EOF
+
+cat > patch << EOF
+diff --git a/file b/file
+index b9834b5..ef6e94c 100644
+--- a/file
++++ b/file
+@@ -3,1 +3,333 @@ of spear-armed Danes, in days long sped,
+ we have heard, and what honor the athelings won!
++
+ Oft Scyld the Scefing from squadroned foes,
+@@ -2,7 +1,5 @@ awing the earls. Since erst he lay
+ friendless, a foundling, fate repaid him:
++
+ for he waxed under welkin, in wealth he throve,
+EOF
+
+cat > expected << EOF
+diff --git a/file b/file
+index b9834b5..ef6e94c 100644
+--- a/file
++++ b/file
+@@ -1,10 +1,12 @@
+ LO, praise of the prowess of people-kings
+ of spear-armed Danes, in days long sped,
+ we have heard, and what honor the athelings won!
++
+ Oft Scyld the Scefing from squadroned foes,
+ from many a tribe, the mead-bench tore,
+ awing the earls. Since erst he lay
+ friendless, a foundling, fate repaid him:
++
+ for he waxed under welkin, in wealth he throve,
+ till before him the folk, both far and near,
+ who house by the whale-path, heard his mandate,
+EOF
+
+echo "#!$SHELL_PATH" >fake-editor.sh
+cat >> fake-editor.sh <<\EOF
+mv -f "$1" orig-patch &&
+mv -f patch "$1"
+EOF
+
+test_set_editor "$(pwd)/fake-editor.sh"
+chmod a+x fake-editor.sh
+
+test_expect_success 'add -e' '
+
+       cp second-part file &&
+       git add -e &&
+       test_cmp second-part file &&
+       test_cmp orig-patch expected-patch &&
+       git diff --cached > out &&
+       test_cmp out expected
+
+'
+
+test_done
index 6fb027ba57eeb328ac48ec78ff5f685fd94a6f4b..8eb47942e2d7f9624058b3347f011db591a13434 100755 (executable)
@@ -22,10 +22,12 @@ check_verify_failure () {
 ###########################################################
 # first create a commit, so we have a valid object/type
 # for the tag.
-echo Hello >A
-git update-index --add A
-git commit -m "Initial commit"
-head=$(git rev-parse --verify HEAD)
+test_expect_success 'setup' '
+       echo Hello >A &&
+       git update-index --add A &&
+       git commit -m "Initial commit" &&
+       head=$(git rev-parse --verify HEAD)
+'
 
 ############################################################
 #  1. length check
index 784c31aec99d90b69186079ddb66350d9f4a8827..256c4c970145aa9f59e58ee1b0da4c6281b6d9e5 100755 (executable)
@@ -9,7 +9,15 @@ test_description='commit and log output encodings'
 
 compare_with () {
        git show -s $1 | sed -e '1,/^$/d' -e 's/^    //' >current &&
-       test_cmp current "$2"
+       case "$3" in
+       '')
+               test_cmp "$2" current ;;
+       ?*)
+               iconv -f "$3" -t UTF-8 >current.utf8 <current &&
+               iconv -f "$3" -t UTF-8 >expect.utf8 <"$2" &&
+               test_cmp expect.utf8 current.utf8
+               ;;
+       esac
 }
 
 test_expect_success setup '
@@ -26,7 +34,7 @@ test_expect_success 'no encoding header for base case' '
        test z = "z$E"
 '
 
-for H in ISO-8859-1 EUCJP ISO-2022-JP
+for H in ISO8859-1 eucJP ISO-2022-JP
 do
        test_expect_success "$H setup" '
                git config i18n.commitencoding $H &&
@@ -36,7 +44,7 @@ do
        '
 done
 
-for H in ISO-8859-1 EUCJP ISO-2022-JP
+for H in ISO8859-1 eucJP ISO-2022-JP
 do
        test_expect_success "check encoding header for $H" '
                E=$(git cat-file commit '$H' | sed -ne "s/^encoding //p") &&
@@ -53,14 +61,14 @@ test_expect_success 'config to remove customization' '
        else
                test z = "z$Z"
        fi &&
-       git config i18n.commitencoding utf-8
+       git config i18n.commitencoding UTF-8
 '
 
-test_expect_success 'ISO-8859-1 should be shown in UTF-8 now' '
-       compare_with ISO-8859-1 "$TEST_DIRECTORY"/t3900/1-UTF-8.txt
+test_expect_success 'ISO8859-1 should be shown in UTF-8 now' '
+       compare_with ISO8859-1 "$TEST_DIRECTORY"/t3900/1-UTF-8.txt
 '
 
-for H in EUCJP ISO-2022-JP
+for H in eucJP ISO-2022-JP
 do
        test_expect_success "$H should be shown in UTF-8 now" '
                compare_with '$H' "$TEST_DIRECTORY"/t3900/2-UTF-8.txt
@@ -78,7 +86,7 @@ test_expect_success 'config to add customization' '
        fi
 '
 
-for H in ISO-8859-1 EUCJP ISO-2022-JP
+for H in ISO8859-1 eucJP ISO-2022-JP
 do
        test_expect_success "$H should be shown in itself now" '
                git config i18n.commitencoding '$H' &&
@@ -87,32 +95,38 @@ do
 done
 
 test_expect_success 'config to tweak customization' '
-       git config i18n.logoutputencoding utf-8
+       git config i18n.logoutputencoding UTF-8
 '
 
-test_expect_success 'ISO-8859-1 should be shown in UTF-8 now' '
-       compare_with ISO-8859-1 "$TEST_DIRECTORY"/t3900/1-UTF-8.txt
+test_expect_success 'ISO8859-1 should be shown in UTF-8 now' '
+       compare_with ISO8859-1 "$TEST_DIRECTORY"/t3900/1-UTF-8.txt
 '
 
-for H in EUCJP ISO-2022-JP
+for H in eucJP ISO-2022-JP
 do
        test_expect_success "$H should be shown in UTF-8 now" '
                compare_with '$H' "$TEST_DIRECTORY"/t3900/2-UTF-8.txt
        '
 done
 
-for J in EUCJP ISO-2022-JP
+for J in eucJP ISO-2022-JP
 do
+       if test "$J" = ISO-2022-JP
+       then
+               ICONV=$J
+       else
+               ICONV=
+       fi
        git config i18n.logoutputencoding $J
-       for H in EUCJP ISO-2022-JP
+       for H in eucJP ISO-2022-JP
        do
                test_expect_success "$H should be shown in $J now" '
-                       compare_with '$H' "$TEST_DIRECTORY"/t3900/'$J'.txt
+                       compare_with '$H' "$TEST_DIRECTORY"/t3900/'$J'.txt $ICONV
                '
        done
 done
 
-for H in ISO-8859-1 EUCJP ISO-2022-JP
+for H in ISO8859-1 eucJP ISO-2022-JP
 do
        test_expect_success "No conversion with $H" '
                compare_with "--encoding=none '$H'" "$TEST_DIRECTORY"/t3900/'$H'.txt
diff --git a/t/t3900/EUCJP.txt b/t/t3900/EUCJP.txt
deleted file mode 100644 (file)
index 546f2aa..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-¤Ï¤ì¤Ò¤Û¤Õ
-
-¤·¤Æ¤¤¤ë¤Î¤¬¡¢¤¤¤ë¤Î¤Ç¡£
-ßÀÉͤۤì¤×¤ê¤Ý¤ì¤Þ¤Ó¤°¤ê¤í¤Ø¡£
diff --git a/t/t3900/ISO-8859-1.txt b/t/t3900/ISO-8859-1.txt
deleted file mode 100644 (file)
index 7cbef0e..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-ÄËÑÏÖ
-
-Ábçdèfg
diff --git a/t/t3900/ISO8859-1.txt b/t/t3900/ISO8859-1.txt
new file mode 100644 (file)
index 0000000..7cbef0e
--- /dev/null
@@ -0,0 +1,3 @@
+ÄËÑÏÖ
+
+Ábçdèfg
diff --git a/t/t3900/eucJP.txt b/t/t3900/eucJP.txt
new file mode 100644 (file)
index 0000000..546f2aa
--- /dev/null
@@ -0,0 +1,4 @@
+¤Ï¤ì¤Ò¤Û¤Õ
+
+¤·¤Æ¤¤¤ë¤Î¤¬¡¢¤¤¤ë¤Î¤Ç¡£
+ßÀÉͤۤì¤×¤ê¤Ý¤ì¤Þ¤Ó¤°¤ê¤í¤Ø¡£
index 7655da3f8d5e68f293ae5afe2d58dd41b1396f37..31a5770b34466c9ed52a9f00bac27b71fd6e0deb 100755 (executable)
@@ -17,9 +17,9 @@ check_encoding () {
                git cat-file commit HEAD~$j |
                case "$header" in
                8859)
-                       grep "^encoding ISO-8859-1" ;;
+                       grep "^encoding ISO8859-1" ;;
                *)
-                       ! grep "^encoding ISO-8859-1" ;;
+                       grep "^encoding ISO8859-1"; test "$?" != 0 ;;
                esac || {
                        bad=1
                        break
@@ -55,7 +55,7 @@ test_expect_success setup '
        git commit -s -m "Second on side" &&
 
        # the second one on the side branch is ISO-8859-1
-       git config i18n.commitencoding ISO-8859-1 &&
+       git config i18n.commitencoding ISO8859-1 &&
        # use author and committer name in ISO-8859-1 to match it.
        . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
        test_tick &&
@@ -68,14 +68,14 @@ test_expect_success setup '
 '
 
 test_expect_success 'format-patch output (ISO-8859-1)' '
-       git config i18n.logoutputencoding ISO-8859-1 &&
+       git config i18n.logoutputencoding ISO8859-1 &&
 
        git format-patch --stdout master..HEAD^ >out-l1 &&
        git format-patch --stdout HEAD^ >out-l2 &&
-       grep "^Content-Type: text/plain; charset=ISO-8859-1" out-l1 &&
-       grep "^From: =?ISO-8859-1?q?=C1=E9=ED=20=F3=FA?=" out-l1 &&
-       grep "^Content-Type: text/plain; charset=ISO-8859-1" out-l2 &&
-       grep "^From: =?ISO-8859-1?q?=C1=E9=ED=20=F3=FA?=" out-l2
+       grep "^Content-Type: text/plain; charset=ISO8859-1" out-l1 &&
+       grep "^From: =?ISO8859-1?q?=C1=E9=ED=20=F3=FA?=" out-l1 &&
+       grep "^Content-Type: text/plain; charset=ISO8859-1" out-l2 &&
+       grep "^From: =?ISO8859-1?q?=C1=E9=ED=20=F3=FA?=" out-l2
 '
 
 test_expect_success 'format-patch output (UTF-8)' '
@@ -110,7 +110,7 @@ test_expect_success 'rebase (U/U)' '
 
 test_expect_success 'rebase (U/L)' '
        git config i18n.commitencoding UTF-8 &&
-       git config i18n.logoutputencoding ISO-8859-1 &&
+       git config i18n.logoutputencoding ISO8859-1 &&
        . "$TEST_DIRECTORY"/t3901-utf8.txt &&
 
        git reset --hard side &&
@@ -121,8 +121,8 @@ test_expect_success 'rebase (U/L)' '
 
 test_expect_success 'rebase (L/L)' '
        # In this test we want ISO-8859-1 encoded commits as the result
-       git config i18n.commitencoding ISO-8859-1 &&
-       git config i18n.logoutputencoding ISO-8859-1 &&
+       git config i18n.commitencoding ISO8859-1 &&
+       git config i18n.logoutputencoding ISO8859-1 &&
        . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
 
        git reset --hard side &&
@@ -134,7 +134,7 @@ test_expect_success 'rebase (L/L)' '
 test_expect_success 'rebase (L/U)' '
        # This is pathological -- use UTF-8 as intermediate form
        # to get ISO-8859-1 results.
-       git config i18n.commitencoding ISO-8859-1 &&
+       git config i18n.commitencoding ISO8859-1 &&
        git config i18n.logoutputencoding UTF-8 &&
        . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
 
@@ -162,8 +162,8 @@ test_expect_success 'cherry-pick(U/U)' '
 test_expect_success 'cherry-pick(L/L)' '
        # Both the commitencoding and logoutputencoding is set to ISO-8859-1
 
-       git config i18n.commitencoding ISO-8859-1 &&
-       git config i18n.logoutputencoding ISO-8859-1 &&
+       git config i18n.commitencoding ISO8859-1 &&
+       git config i18n.logoutputencoding ISO8859-1 &&
        . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
 
        git reset --hard master &&
@@ -178,7 +178,7 @@ test_expect_success 'cherry-pick(U/L)' '
        # Commitencoding is set to UTF-8 but logoutputencoding is ISO-8859-1
 
        git config i18n.commitencoding UTF-8 &&
-       git config i18n.logoutputencoding ISO-8859-1 &&
+       git config i18n.logoutputencoding ISO8859-1 &&
        . "$TEST_DIRECTORY"/t3901-utf8.txt &&
 
        git reset --hard master &&
@@ -193,7 +193,7 @@ test_expect_success 'cherry-pick(L/U)' '
        # Again, the commitencoding is set to ISO-8859-1 but
        # logoutputencoding is set to UTF-8.
 
-       git config i18n.commitencoding ISO-8859-1 &&
+       git config i18n.commitencoding ISO8859-1 &&
        git config i18n.logoutputencoding UTF-8 &&
        . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
 
@@ -218,7 +218,7 @@ test_expect_success 'rebase --merge (U/U)' '
 
 test_expect_success 'rebase --merge (U/L)' '
        git config i18n.commitencoding UTF-8 &&
-       git config i18n.logoutputencoding ISO-8859-1 &&
+       git config i18n.logoutputencoding ISO8859-1 &&
        . "$TEST_DIRECTORY"/t3901-utf8.txt &&
 
        git reset --hard side &&
@@ -229,8 +229,8 @@ test_expect_success 'rebase --merge (U/L)' '
 
 test_expect_success 'rebase --merge (L/L)' '
        # In this test we want ISO-8859-1 encoded commits as the result
-       git config i18n.commitencoding ISO-8859-1 &&
-       git config i18n.logoutputencoding ISO-8859-1 &&
+       git config i18n.commitencoding ISO8859-1 &&
+       git config i18n.logoutputencoding ISO8859-1 &&
        . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
 
        git reset --hard side &&
@@ -242,7 +242,7 @@ test_expect_success 'rebase --merge (L/L)' '
 test_expect_success 'rebase --merge (L/U)' '
        # This is pathological -- use UTF-8 as intermediate form
        # to get ISO-8859-1 results.
-       git config i18n.commitencoding ISO-8859-1 &&
+       git config i18n.commitencoding ISO8859-1 &&
        git config i18n.logoutputencoding UTF-8 &&
        . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
 
index 58680524258037eb0c17f7e2929d58ed1c16b574..7d4946984121b68ce1fe64dd3f5f363a53c63255 100755 (executable)
@@ -15,24 +15,27 @@ LF='
 DQ='"'
 
 echo foo 2>/dev/null > "Name and an${HT}HT"
-test -f "Name and an${HT}HT" || {
-       # 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
-}
+if ! test -f "Name and an${HT}HT"
+then
+       # FAT/NTFS does not allow tabs in filenames
+       say 'Your filesystem does not allow tabs in filenames'
+else
+       test_set_prereq TABS_IN_FILENAMES
+fi
 
 for_each_name () {
        for name in \
            Name "Name and a${LF}LF" "Name and an${HT}HT" "Name${DQ}" \
            "$FN$HT$GN" "$FN$LF$GN" "$FN $GN" "$FN$GN" "$FN$DQ$GN" \
-           "With SP in it"
+           "With SP in it" "$FN/file"
        do
                eval "$1"
        done
 }
 
-test_expect_success setup '
+test_expect_success TABS_IN_FILENAMES 'setup' '
 
+       mkdir "$FN" &&
        for_each_name "echo initial >\"\$name\""
        git add . &&
        git commit -q -m Initial &&
@@ -44,6 +47,7 @@ test_expect_success setup '
 
 '
 
+test_expect_success TABS_IN_FILENAMES 'setup expected files' '
 cat >expect.quoted <<\EOF
 Name
 "Name and a\nLF"
@@ -54,6 +58,7 @@ With SP in it
 "\346\277\261\351\207\216\n\347\264\224"
 "\346\277\261\351\207\216 \347\264\224"
 "\346\277\261\351\207\216\"\347\264\224"
+"\346\277\261\351\207\216/file"
 "\346\277\261\351\207\216\347\264\224"
 EOF
 
@@ -67,67 +72,83 @@ With SP in it
 "濱野\n純"
 濱野 純
 "濱野\"純"
+濱野/file
 濱野純
 EOF
+'
 
-test_expect_success 'check fully quoted output from ls-files' '
+test_expect_success TABS_IN_FILENAMES 'check fully quoted output from ls-files' '
 
        git ls-files >current && test_cmp expect.quoted current
 
 '
 
-test_expect_success 'check fully quoted output from diff-files' '
+test_expect_success TABS_IN_FILENAMES 'check fully quoted output from diff-files' '
 
        git diff --name-only >current &&
        test_cmp expect.quoted current
 
 '
 
-test_expect_success 'check fully quoted output from diff-index' '
+test_expect_success TABS_IN_FILENAMES 'check fully quoted output from diff-index' '
 
        git diff --name-only HEAD >current &&
        test_cmp expect.quoted current
 
 '
 
-test_expect_success 'check fully quoted output from diff-tree' '
+test_expect_success TABS_IN_FILENAMES 'check fully quoted output from diff-tree' '
 
        git diff --name-only HEAD^ HEAD >current &&
        test_cmp expect.quoted current
 
 '
 
-test_expect_success 'setting core.quotepath' '
+test_expect_success TABS_IN_FILENAMES 'check fully quoted output from ls-tree' '
+
+       git ls-tree --name-only -r HEAD >current &&
+       test_cmp expect.quoted current
+
+'
+
+test_expect_success TABS_IN_FILENAMES 'setting core.quotepath' '
 
        git config --bool core.quotepath false
 
 '
 
-test_expect_success 'check fully quoted output from ls-files' '
+test_expect_success TABS_IN_FILENAMES 'check fully quoted output from ls-files' '
 
        git ls-files >current && test_cmp expect.raw current
 
 '
 
-test_expect_success 'check fully quoted output from diff-files' '
+test_expect_success TABS_IN_FILENAMES 'check fully quoted output from diff-files' '
 
        git diff --name-only >current &&
        test_cmp expect.raw current
 
 '
 
-test_expect_success 'check fully quoted output from diff-index' '
+test_expect_success TABS_IN_FILENAMES 'check fully quoted output from diff-index' '
 
        git diff --name-only HEAD >current &&
        test_cmp expect.raw current
 
 '
 
-test_expect_success 'check fully quoted output from diff-tree' '
+test_expect_success TABS_IN_FILENAMES 'check fully quoted output from diff-tree' '
 
        git diff --name-only HEAD^ HEAD >current &&
        test_cmp expect.raw current
 
 '
 
+test_expect_success TABS_IN_FILENAMES 'check fully quoted output from ls-tree' '
+
+       git ls-tree --name-only -r HEAD >current &&
+       test_cmp expect.raw current
+
+'
+
 test_done
index 7484cbede6ccd3ecb56a3ebff734740cb543c0a4..a283dca25fe9b5d75841626337e0e68244fdbbf2 100755 (executable)
@@ -69,9 +69,10 @@ test_expect_success 'apply stashed changes (including index)' '
 test_expect_success 'unstashing in a subdirectory' '
        git reset --hard HEAD &&
        mkdir subdir &&
-       cd subdir &&
-       git stash apply &&
-       cd ..
+       (
+               cd subdir &&
+               git stash apply
+       )
 '
 
 test_expect_success 'drop top stash' '
@@ -81,7 +82,7 @@ test_expect_success 'drop top stash' '
        git stash &&
        git stash drop &&
        git stash list > stashlist2 &&
-       diff stashlist1 stashlist2 &&
+       test_cmp stashlist1 stashlist2 &&
        git stash apply &&
        test 3 = $(cat file) &&
        test 1 = $(git show :file) &&
@@ -177,4 +178,317 @@ test_expect_success 'stash branch' '
        test 0 = $(git stash list | wc -l)
 '
 
+test_expect_success 'apply -q is quiet' '
+       echo foo > file &&
+       git stash &&
+       git stash apply -q > output.out 2>&1 &&
+       test ! -s output.out
+'
+
+test_expect_success 'save -q is quiet' '
+       git stash save --quiet > output.out 2>&1 &&
+       test ! -s output.out
+'
+
+test_expect_success 'pop -q is quiet' '
+       git stash pop -q > output.out 2>&1 &&
+       test ! -s output.out
+'
+
+test_expect_success 'pop -q --index works and is quiet' '
+       echo foo > file &&
+       git add file &&
+       git stash save --quiet &&
+       git stash pop -q --index > output.out 2>&1 &&
+       test foo = "$(git show :file)" &&
+       test ! -s output.out
+'
+
+test_expect_success 'drop -q is quiet' '
+       git stash &&
+       git stash drop -q > output.out 2>&1 &&
+       test ! -s output.out
+'
+
+test_expect_success 'stash -k' '
+       echo bar3 > file &&
+       echo bar4 > file2 &&
+       git add file2 &&
+       git stash -k &&
+       test bar,bar4 = $(cat file),$(cat file2)
+'
+
+test_expect_success 'stash --invalid-option' '
+       echo bar5 > file &&
+       echo bar6 > file2 &&
+       git add file2 &&
+       test_must_fail git stash --invalid-option &&
+       test_must_fail git stash save --invalid-option &&
+       test bar5,bar6 = $(cat file),$(cat file2) &&
+       git stash -- -message-starting-with-dash &&
+       test bar,bar2 = $(cat file),$(cat file2)
+'
+
+test_expect_success 'stash an added file' '
+       git reset --hard &&
+       echo new >file3 &&
+       git add file3 &&
+       git stash save "added file" &&
+       ! test -r file3 &&
+       git stash apply &&
+       test new = "$(cat file3)"
+'
+
+test_expect_success 'stash rm then recreate' '
+       git reset --hard &&
+       git rm file &&
+       echo bar7 >file &&
+       git stash save "rm then recreate" &&
+       test bar = "$(cat file)" &&
+       git stash apply &&
+       test bar7 = "$(cat file)"
+'
+
+test_expect_success 'stash rm and ignore' '
+       git reset --hard &&
+       git rm file &&
+       echo file >.gitignore &&
+       git stash save "rm and ignore" &&
+       test bar = "$(cat file)" &&
+       test file = "$(cat .gitignore)"
+       git stash apply &&
+       ! test -r file &&
+       test file = "$(cat .gitignore)"
+'
+
+test_expect_success 'stash rm and ignore (stage .gitignore)' '
+       git reset --hard &&
+       git rm file &&
+       echo file >.gitignore &&
+       git add .gitignore &&
+       git stash save "rm and ignore (stage .gitignore)" &&
+       test bar = "$(cat file)" &&
+       ! test -r .gitignore
+       git stash apply &&
+       ! test -r file &&
+       test file = "$(cat .gitignore)"
+'
+
+test_expect_success SYMLINKS 'stash file to symlink' '
+       git reset --hard &&
+       rm file &&
+       ln -s file2 file &&
+       git stash save "file to symlink" &&
+       test -f file &&
+       test bar = "$(cat file)" &&
+       git stash apply &&
+       case "$(ls -l file)" in *" file -> file2") :;; *) false;; esac
+'
+
+test_expect_success SYMLINKS 'stash file to symlink (stage rm)' '
+       git reset --hard &&
+       git rm file &&
+       ln -s file2 file &&
+       git stash save "file to symlink (stage rm)" &&
+       test -f file &&
+       test bar = "$(cat file)" &&
+       git stash apply &&
+       case "$(ls -l file)" in *" file -> file2") :;; *) false;; esac
+'
+
+test_expect_success SYMLINKS 'stash file to symlink (full stage)' '
+       git reset --hard &&
+       rm file &&
+       ln -s file2 file &&
+       git add file &&
+       git stash save "file to symlink (full stage)" &&
+       test -f file &&
+       test bar = "$(cat file)" &&
+       git stash apply &&
+       case "$(ls -l file)" in *" file -> file2") :;; *) false;; esac
+'
+
+# This test creates a commit with a symlink used for the following tests
+
+test_expect_success SYMLINKS 'stash symlink to file' '
+       git reset --hard &&
+       ln -s file filelink &&
+       git add filelink &&
+       git commit -m "Add symlink" &&
+       rm filelink &&
+       cp file filelink &&
+       git stash save "symlink to file" &&
+       test -h filelink &&
+       case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac &&
+       git stash apply &&
+       ! test -h filelink &&
+       test bar = "$(cat file)"
+'
+
+test_expect_success SYMLINKS 'stash symlink to file (stage rm)' '
+       git reset --hard &&
+       git rm filelink &&
+       cp file filelink &&
+       git stash save "symlink to file (stage rm)" &&
+       test -h filelink &&
+       case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac &&
+       git stash apply &&
+       ! test -h filelink &&
+       test bar = "$(cat file)"
+'
+
+test_expect_success SYMLINKS 'stash symlink to file (full stage)' '
+       git reset --hard &&
+       rm filelink &&
+       cp file filelink &&
+       git add filelink &&
+       git stash save "symlink to file (full stage)" &&
+       test -h filelink &&
+       case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac &&
+       git stash apply &&
+       ! test -h filelink &&
+       test bar = "$(cat file)"
+'
+
+test_expect_failure 'stash directory to file' '
+       git reset --hard &&
+       mkdir dir &&
+       echo foo >dir/file &&
+       git add dir/file &&
+       git commit -m "Add file in dir" &&
+       rm -fr dir &&
+       echo bar >dir &&
+       git stash save "directory to file" &&
+       test -d dir &&
+       test foo = "$(cat dir/file)" &&
+       test_must_fail git stash apply &&
+       test bar = "$(cat dir)" &&
+       git reset --soft HEAD^
+'
+
+test_expect_failure 'stash file to directory' '
+       git reset --hard &&
+       rm file &&
+       mkdir file &&
+       echo foo >file/file &&
+       git stash save "file to directory" &&
+       test -f file &&
+       test bar = "$(cat file)" &&
+       git stash apply &&
+       test -f file/file &&
+       test foo = "$(cat file/file)"
+'
+
+test_expect_success 'stash branch - no stashes on stack, stash-like argument' '
+       git stash clear &&
+       test_when_finished "git reset --hard HEAD" &&
+       git reset --hard &&
+       echo foo >> file &&
+       STASH_ID=$(git stash create) &&
+       git reset --hard &&
+       git stash branch stash-branch ${STASH_ID} &&
+       test_when_finished "git reset --hard HEAD && git checkout master && git branch -D stash-branch" &&
+       test $(git ls-files --modified | wc -l) -eq 1
+'
+
+test_expect_success 'stash branch - stashes on stack, stash-like argument' '
+       git stash clear &&
+       test_when_finished "git reset --hard HEAD" &&
+       git reset --hard &&
+       echo foo >> file &&
+       git stash &&
+       test_when_finished "git stash drop" &&
+       echo bar >> file &&
+       STASH_ID=$(git stash create) &&
+       git reset --hard &&
+       git stash branch stash-branch ${STASH_ID} &&
+       test_when_finished "git reset --hard HEAD && git checkout master && git branch -D stash-branch" &&
+       test $(git ls-files --modified | wc -l) -eq 1
+'
+
+test_expect_success 'stash show - stashes on stack, stash-like argument' '
+       git stash clear &&
+       test_when_finished "git reset --hard HEAD" &&
+       git reset --hard &&
+       echo foo >> file &&
+       git stash &&
+       test_when_finished "git stash drop" &&
+       echo bar >> file &&
+       STASH_ID=$(git stash create) &&
+       git reset --hard &&
+       git stash show ${STASH_ID}
+'
+test_expect_success 'stash show - no stashes on stack, stash-like argument' '
+       git stash clear &&
+       test_when_finished "git reset --hard HEAD" &&
+       git reset --hard &&
+       echo foo >> file &&
+       STASH_ID=$(git stash create) &&
+       git reset --hard &&
+       git stash show ${STASH_ID}
+'
+
+test_expect_success 'stash drop - fail early if specified stash is not a stash reference' '
+       git stash clear &&
+       test_when_finished "git reset --hard HEAD && git stash clear" &&
+       git reset --hard &&
+       echo foo > file &&
+       git stash &&
+       echo bar > file &&
+       git stash &&
+       test_must_fail git stash drop $(git rev-parse stash@{0}) &&
+       git stash pop &&
+       test bar = "$(cat file)" &&
+       git reset --hard HEAD
+'
+
+test_expect_success 'stash pop - fail early if specified stash is not a stash reference' '
+       git stash clear &&
+       test_when_finished "git reset --hard HEAD && git stash clear" &&
+       git reset --hard &&
+       echo foo > file &&
+       git stash &&
+       echo bar > file &&
+       git stash &&
+       test_must_fail git stash pop $(git rev-parse stash@{0}) &&
+       git stash pop &&
+       test bar = "$(cat file)" &&
+       git reset --hard HEAD
+'
+
+test_expect_success 'ref with non-existant reflog' '
+       git stash clear &&
+       echo bar5 > file &&
+       echo bar6 > file2 &&
+       git add file2 &&
+       git stash &&
+       ! "git rev-parse --quiet --verify does-not-exist" &&
+       test_must_fail git stash drop does-not-exist &&
+       test_must_fail git stash drop does-not-exist@{0} &&
+       test_must_fail git stash pop does-not-exist &&
+       test_must_fail git stash pop does-not-exist@{0} &&
+       test_must_fail git stash apply does-not-exist &&
+       test_must_fail git stash apply does-not-exist@{0} &&
+       test_must_fail git stash show does-not-exist &&
+       test_must_fail git stash show does-not-exist@{0} &&
+       test_must_fail git stash branch tmp does-not-exist &&
+       test_must_fail git stash branch tmp does-not-exist@{0} &&
+       git stash drop
+'
+
+test_expect_success 'invalid ref of the form stash@{n}, n >= N' '
+       git stash clear &&
+       test_must_fail git stash drop stash@{0} &&
+       echo bar5 > file &&
+       echo bar6 > file2 &&
+       git add file2 &&
+       git stash &&
+       test_must_fail git drop stash@{1} &&
+       test_must_fail git pop stash@{1} &&
+       test_must_fail git apply stash@{1} &&
+       test_must_fail git show stash@{1} &&
+       test_must_fail git branch tmp stash@{1} &&
+       git stash drop
+'
+
 test_done
diff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh
new file mode 100755 (executable)
index 0000000..d1819ca
--- /dev/null
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+test_description='git checkout --patch'
+. ./lib-patch-mode.sh
+
+test_expect_success PERL 'setup' '
+       mkdir dir &&
+       echo parent > dir/foo &&
+       echo dummy > bar &&
+       git add bar dir/foo &&
+       git commit -m initial &&
+       test_tick &&
+       test_commit second dir/foo head &&
+       echo index > dir/foo &&
+       git add dir/foo &&
+       set_and_save_state bar bar_work bar_index &&
+       save_head
+'
+
+# note: bar sorts before dir, so the first 'n' is always to skip 'bar'
+
+test_expect_success PERL 'saying "n" does nothing' '
+       set_state dir/foo work index
+       (echo n; echo n) | test_must_fail git stash save -p &&
+       verify_state dir/foo work index &&
+       verify_saved_state bar
+'
+
+test_expect_success PERL 'git stash -p' '
+       (echo n; echo y) | git stash save -p &&
+       verify_state dir/foo head index &&
+       verify_saved_state bar &&
+       git reset --hard &&
+       git stash apply &&
+       verify_state dir/foo work head &&
+       verify_state bar dummy dummy
+'
+
+test_expect_success PERL 'git stash -p --no-keep-index' '
+       set_state dir/foo work index &&
+       set_state bar bar_work bar_index &&
+       (echo n; echo y) | git stash save -p --no-keep-index &&
+       verify_state dir/foo head head &&
+       verify_state bar bar_work dummy &&
+       git reset --hard &&
+       git stash apply --index &&
+       verify_state dir/foo work index &&
+       verify_state bar dummy bar_index
+'
+
+test_expect_success PERL 'none of this moved HEAD' '
+       verify_saved_head
+'
+
+test_done
index 18695ce8218a7b383258eeb0bad84b4d4bde45be..73441a516572dcf826582c04f3fe9d3ad0c1a88c 100755 (executable)
@@ -135,7 +135,7 @@ cmp_diff_files_output () {
     # filesystem.
     sed <"$2" >.test-tmp \
        -e '/^:000000 /d;s/'$x40'\( [MCRNDU][0-9]*\)    /'$z40'\1       /' &&
-    diff "$1" .test-tmp
+    test_cmp "$1" .test-tmp
 }
 
 test_expect_success \
index a4da1196a93a00502c8945a14e3aafd628efda53..92a65f4852b8eca089fb9de617e0cdc8b55e1475 100755 (executable)
@@ -12,13 +12,7 @@ by an edit for them.
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/diff-lib.sh
 
-if ! test_have_prereq SYMLINKS
-then
-       say 'Symbolic links not supported, skipping tests.'
-       test_done
-fi
-
-test_expect_success \
+test_expect_success SYMLINKS \
     'prepare reference tree' \
     'echo xyzzy | tr -d '\\\\'012 >yomin &&
      ln -s xyzzy frotz &&
@@ -26,7 +20,7 @@ test_expect_success \
     tree=$(git write-tree) &&
     echo $tree'
 
-test_expect_success \
+test_expect_success SYMLINKS \
     'prepare work tree' \
     'mv frotz rezrov &&
      rm -f yomin &&
@@ -40,8 +34,9 @@ test_expect_success \
 # rezrov and nitfol are rename/copy of frotz and bozbar should be
 # a new creation.
 
-GIT_DIFF_OPTS=--unified=0 git diff-index -M -p $tree >current
-cat >expected <<\EOF
+test_expect_success SYMLINKS 'setup diff output' "
+    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
@@ -65,8 +60,9 @@ deleted file mode 100644
 -xyzzy
 \ No newline at end of file
 EOF
+"
 
-test_expect_success \
+test_expect_success SYMLINKS \
     'validate diff output' \
     'compare_diff_patch current expected'
 
index 8c1b81e248bced2ccb5e4ff0067996462e89deb8..ff8c2f7532aa6fd748dbbdc0322384576f3a35ed 100755 (executable)
@@ -20,8 +20,6 @@ test_expect_success \
     'test_chmod +x rezrov &&
      git diff-index $tree >current'
 
-_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
-_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
 sed -e 's/\(:100644 100755\) \('"$_x40"'\) \2 /\1 X X /' <current >check
 echo ":100644 100755 X X M     rezrov" >expected
 
index 42072d724ef67c51e6a9adcf9bf8e3ca1757e053..11502b750997aba0ecfb6aa5b07c9aad1c45d592 100755 (executable)
@@ -9,32 +9,36 @@ test_description='Rename interaction with pathspec.
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash
 
-test_expect_success \
-    'prepare reference tree' \
-    'mkdir path0 path1 &&
-     cp "$TEST_DIRECTORY"/../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'
+test_expect_success 'prepare reference tree' '
+       mkdir path0 path1 &&
+       cp "$TEST_DIRECTORY"/../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'
+test_expect_success 'copy detection' '
+       git diff-index -C --find-copies-harder $tree >current &&
+       compare_diff_raw current expected
+'
+
+test_expect_success 'copy detection, cached' '
+       git diff-index -C --find-copies-harder --cached $tree >current &&
+       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.
@@ -42,49 +46,45 @@ test_expect_success \
 # 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'
+test_expect_success 'copy, limited to a subtree' '
+       git diff-index -C --find-copies-harder $tree path1 >current &&
+       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'
+test_expect_success 'rename detection' '
+       git diff-index -C --find-copies-harder $tree >current &&
+       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_expect_success 'rename, limited to a subtree' '
+       git diff-index -C --find-copies-harder $tree path1 >current &&
+       compare_diff_raw current expected
+'
 
 test_done
index d7e327cc5bc5984546032fb085fb581de5755e11..6f6948925fed1b99f37ec94dd7e4aa2075eb6bb7 100755 (executable)
@@ -9,12 +9,6 @@ test_description='Test diff of symlinks.
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/diff-lib.sh
 
-if ! test_have_prereq SYMLINKS
-then
-       say 'Symbolic links not supported, skipping tests.'
-       test_done
-fi
-
 cat > expected << EOF
 diff --git a/frotz b/frotz
 new file mode 120000
@@ -26,7 +20,7 @@ index 0000000..7c465af
 \ No newline at end of file
 EOF
 
-test_expect_success \
+test_expect_success SYMLINKS \
     'diff new symlink' \
     'ln -s xyzzy frotz &&
     git update-index &&
@@ -35,7 +29,7 @@ test_expect_success \
     GIT_DIFF_OPTS=--unified=0 git diff-index -M -p $tree > current &&
     compare_diff_patch current expected'
 
-test_expect_success \
+test_expect_success SYMLINKS \
     'diff unchanged symlink' \
     'tree=$(git write-tree) &&
     git update-index frotz &&
@@ -52,9 +46,9 @@ index 7c465af..0000000
 \ No newline at end of file
 EOF
 
-test_expect_success \
+test_expect_success SYMLINKS \
     'diff removed symlink' \
-    'rm frotz &&
+    'mv frotz frotz2 &&
     git diff-index -M -p $tree > current &&
     compare_diff_patch current expected'
 
@@ -62,10 +56,9 @@ cat > expected << EOF
 diff --git a/frotz b/frotz
 EOF
 
-test_expect_success \
+test_expect_success SYMLINKS \
     'diff identical, but newly created symlink' \
-    'sleep 3 &&
-    ln -s xyzzy frotz &&
+    'ln -s xyzzy frotz &&
     git diff-index -M -p $tree > current &&
     compare_diff_patch current expected'
 
@@ -81,14 +74,14 @@ index 7c465af..df1db54 120000
 \ No newline at end of file
 EOF
 
-test_expect_success \
+test_expect_success SYMLINKS \
     'diff different symlink' \
     'rm frotz &&
     ln -s yxyyz frotz &&
     git diff-index -M -p $tree > current &&
     compare_diff_patch current expected'
 
-test_expect_success \
+test_expect_success SYMLINKS \
     'diff symlinks with non-existing targets' \
     'ln -s narf pinky &&
     ln -s take\ over brain &&
index f64aa48d24f2f5704d07b9285c94ba983f7d92ac..bc46563afc607e6fba9acbd2773d88207c878981 100755 (executable)
@@ -77,10 +77,6 @@ test_expect_success 'apply binary patch' \
         tree1=`git write-tree` &&
         test "$tree1" = "$tree0"'
 
-q_to_nul() {
-       perl -pe 'y/Q/\000/'
-}
-
 nul_to_q() {
        perl -pe 'y/\000/Q/'
 }
index 8b33321f8c44dc7f8a08fa144bca4f10444ceaa1..19857f4326aa491a4c7d292900a0a766e7c45f80 100755 (executable)
@@ -204,9 +204,14 @@ log --root --patch-with-stat --summary master
 log --root -c --patch-with-stat --summary master
 # improved by Timo's patch
 log --root --cc --patch-with-stat --summary master
+log -p --first-parent master
+log -m -p --first-parent master
+log -m -p master
 log -SF master
+log -S F master
 log -SF -p master
 log --decorate --all
+log --decorate=full --all
 
 rev-list --parents HEAD
 rev-list --children HEAD
@@ -234,6 +239,9 @@ show initial
 show --root initial
 show side
 show master
+show -c master
+show -m master
+show --first-parent master
 show --stat side
 show --stat --summary side
 show --patch-with-stat side
@@ -275,4 +283,8 @@ diff master master^ side
 diff --dirstat master~1 master~2
 EOF
 
+test_expect_success 'log -S requires an argument' '
+       test_must_fail git log -S
+'
+
 test_done
index 8dab4bf93ebd5f3e5ee6e899890d6e53af9ab967..1f0f9ad44b241e57e867c0676b73f37a1dc93d60 100644 (file)
@@ -18,6 +18,9 @@ A U Thor (2):
  create mode 100644 file1
  delete mode 100644 file2
 
+-- 
+g-i-t--v-e-r-s-i-o-n
+
 From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
 From: A U Thor <author@example.com>
 Date: Mon, 26 Jun 2006 00:01:00 +0000
diff --git a/t/t4013/diff.log_--decorate=full_--all b/t/t4013/diff.log_--decorate=full_--all
new file mode 100644 (file)
index 0000000..d155e0b
--- /dev/null
@@ -0,0 +1,34 @@
+$ git log --decorate=full --all
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD, refs/heads/master)
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a (refs/heads/side)
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a (refs/heads/initial)
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+$
index 12da8ac07de4423cf4736aced96bab8150f83ef0..fd7c3e64396b4ea57c3b03d2e120580205263462 100644 (file)
@@ -1,12 +1,12 @@
 $ git log --decorate --all
-commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (refs/heads/master)
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD, master)
 Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
     Merge branch 'side'
 
-commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a (refs/heads/side)
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a (side)
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:03:00 2006 +0000
 
@@ -26,7 +26,7 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     
     This is the second commit.
 
-commit 444ac553ac7612cc88969031b02b3767fb8a353a (refs/heads/initial)
+commit 444ac553ac7612cc88969031b02b3767fb8a353a (initial)
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:00:00 2006 +0000
 
diff --git a/t/t4013/diff.log_-S_F_master b/t/t4013/diff.log_-S_F_master
new file mode 100644 (file)
index 0000000..978d2b4
--- /dev/null
@@ -0,0 +1,7 @@
+$ git log -S F master
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+$
diff --git a/t/t4013/diff.log_-m_-p_--first-parent_master b/t/t4013/diff.log_-m_-p_--first-parent_master
new file mode 100644 (file)
index 0000000..7a0073f
--- /dev/null
@@ -0,0 +1,100 @@
+$ git log -m -p --first-parent master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+diff --git a/dir/sub b/dir/sub
+index cead32e..992913c 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -4,3 +4,5 @@ C
+ D
+ E
+ F
++1
++2
+diff --git a/file0 b/file0
+index b414108..10a8a9f 100644
+--- a/file0
++++ b/file0
+@@ -4,3 +4,6 @@
+ 4
+ 5
+ 6
++A
++B
++C
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+$
diff --git a/t/t4013/diff.log_-m_-p_master b/t/t4013/diff.log_-m_-p_master
new file mode 100644 (file)
index 0000000..9ca62a0
--- /dev/null
@@ -0,0 +1,200 @@
+$ git log -m -p master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0)
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+diff --git a/dir/sub b/dir/sub
+index cead32e..992913c 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -4,3 +4,5 @@ C
+ D
+ E
+ F
++1
++2
+diff --git a/file0 b/file0
+index b414108..10a8a9f 100644
+--- a/file0
++++ b/file0
+@@ -4,3 +4,6 @@
+ 4
+ 5
+ 6
++A
++B
++C
+
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a)
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+diff --git a/dir/sub b/dir/sub
+index 7289e35..992913c 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,4 +1,8 @@
+ A
+ B
++C
++D
++E
++F
+ 1
+ 2
+diff --git a/file0 b/file0
+index f4615da..10a8a9f 100644
+--- a/file0
++++ b/file0
+@@ -1,6 +1,9 @@
+ 1
+ 2
+ 3
++4
++5
++6
+ A
+ B
+ C
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+diff --git a/file3 b/file3
+deleted file mode 100644
+index 7289e35..0000000
+--- a/file3
++++ /dev/null
+@@ -1,4 +0,0 @@
+-A
+-B
+-1
+-2
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+$
diff --git a/t/t4013/diff.log_-p_--first-parent_master b/t/t4013/diff.log_-p_--first-parent_master
new file mode 100644 (file)
index 0000000..3fc896d
--- /dev/null
@@ -0,0 +1,78 @@
+$ git log -p --first-parent master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+$
diff --git a/t/t4013/diff.show_--first-parent_master b/t/t4013/diff.show_--first-parent_master
new file mode 100644 (file)
index 0000000..3dcbe47
--- /dev/null
@@ -0,0 +1,30 @@
+$ git show --first-parent master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+diff --git a/dir/sub b/dir/sub
+index cead32e..992913c 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -4,3 +4,5 @@ C
+ D
+ E
+ F
++1
++2
+diff --git a/file0 b/file0
+index b414108..10a8a9f 100644
+--- a/file0
++++ b/file0
+@@ -4,3 +4,6 @@
+ 4
+ 5
+ 6
++A
++B
++C
+$
diff --git a/t/t4013/diff.show_-c_master b/t/t4013/diff.show_-c_master
new file mode 100644 (file)
index 0000000..81aba8d
--- /dev/null
@@ -0,0 +1,36 @@
+$ git show -c master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+diff --combined dir/sub
+index cead32e,7289e35..992913c
+--- a/dir/sub
++++ b/dir/sub
+@@@ -1,6 -1,4 +1,8 @@@
+  A
+  B
+ +C
+ +D
+ +E
+ +F
++ 1
++ 2
+diff --combined file0
+index b414108,f4615da..10a8a9f
+--- a/file0
++++ b/file0
+@@@ -1,6 -1,6 +1,9 @@@
+  1
+  2
+  3
+ +4
+ +5
+ +6
++ A
++ B
++ C
+$
diff --git a/t/t4013/diff.show_-m_master b/t/t4013/diff.show_-m_master
new file mode 100644 (file)
index 0000000..4ea2ee4
--- /dev/null
@@ -0,0 +1,93 @@
+$ git show -m master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0)
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+diff --git a/dir/sub b/dir/sub
+index cead32e..992913c 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -4,3 +4,5 @@ C
+ D
+ E
+ F
++1
++2
+diff --git a/file0 b/file0
+index b414108..10a8a9f 100644
+--- a/file0
++++ b/file0
+@@ -4,3 +4,6 @@
+ 4
+ 5
+ 6
++A
++B
++C
+
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a)
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+diff --git a/dir/sub b/dir/sub
+index 7289e35..992913c 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,4 +1,8 @@
+ A
+ B
++C
++D
++E
++F
+ 1
+ 2
+diff --git a/file0 b/file0
+index f4615da..10a8a9f 100644
+--- a/file0
++++ b/file0
+@@ -1,6 +1,9 @@
+ 1
+ 2
+ 3
++4
++5
++6
+ A
+ B
+ C
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+diff --git a/file3 b/file3
+deleted file mode 100644
+index 7289e35..0000000
+--- a/file3
++++ /dev/null
+@@ -1,4 +0,0 @@
+-A
+-B
+-1
+-2
+$
index 11061ddd5bd122a1e180509c018e1b8bea42ddc3..f87434b9f8e0d520813389e6ea7c94f74222767b 100755 (executable)
@@ -93,9 +93,9 @@ test_expect_success 'extra headers' '
        git config --add format.headers "Cc: S. E. Cipient <scipient@example.com>
 " &&
        git format-patch --stdout master..side > patch2 &&
-       sed -e "/^$/q" patch2 > hdrs2 &&
-       grep "^To: R. E. Cipient <rcipient@example.com>$" hdrs2 &&
-       grep "^Cc: S. E. Cipient <scipient@example.com>$" hdrs2
+       sed -e "/^\$/q" patch2 > hdrs2 &&
+       grep "^To: R. E. Cipient <rcipient@example.com>\$" hdrs2 &&
+       grep "^Cc: S. E. Cipient <scipient@example.com>\$" hdrs2
 
 '
 
@@ -104,9 +104,9 @@ test_expect_success 'extra headers without newlines' '
        git config --replace-all format.headers "To: R. E. Cipient <rcipient@example.com>" &&
        git config --add format.headers "Cc: S. E. Cipient <scipient@example.com>" &&
        git format-patch --stdout master..side >patch3 &&
-       sed -e "/^$/q" patch3 > hdrs3 &&
-       grep "^To: R. E. Cipient <rcipient@example.com>$" hdrs3 &&
-       grep "^Cc: S. E. Cipient <scipient@example.com>$" hdrs3
+       sed -e "/^\$/q" patch3 > hdrs3 &&
+       grep "^To: R. E. Cipient <rcipient@example.com>\$" hdrs3 &&
+       grep "^Cc: S. E. Cipient <scipient@example.com>\$" hdrs3
 
 '
 
@@ -115,32 +115,84 @@ test_expect_success 'extra headers with multiple To:s' '
        git config --replace-all format.headers "To: R. E. Cipient <rcipient@example.com>" &&
        git config --add format.headers "To: S. E. Cipient <scipient@example.com>" &&
        git format-patch --stdout master..side > patch4 &&
-       sed -e "/^$/q" patch4 > hdrs4 &&
-       grep "^To: R. E. Cipient <rcipient@example.com>,$" hdrs4 &&
-       grep "^ *S. E. Cipient <scipient@example.com>$" hdrs4
+       sed -e "/^\$/q" patch4 > hdrs4 &&
+       grep "^To: R. E. Cipient <rcipient@example.com>,\$" hdrs4 &&
+       grep "^ *S. E. Cipient <scipient@example.com>\$" hdrs4
 '
 
 test_expect_success 'additional command line cc' '
 
        git config --replace-all format.headers "Cc: R. E. Cipient <rcipient@example.com>" &&
-       git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^$/q" >patch5 &&
-       grep "^Cc: R. E. Cipient <rcipient@example.com>,$" patch5 &&
-       grep "^ *S. E. Cipient <scipient@example.com>$" patch5
+       git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch5 &&
+       grep "^Cc: R. E. Cipient <rcipient@example.com>,\$" patch5 &&
+       grep "^ *S. E. Cipient <scipient@example.com>\$" patch5
 '
 
 test_expect_success 'command line headers' '
 
        git config --unset-all format.headers &&
-       git format-patch --add-header="Cc: R. E. Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^$/q" >patch6 &&
-       grep "^Cc: R. E. Cipient <rcipient@example.com>$" patch6
+       git format-patch --add-header="Cc: R. E. Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch6 &&
+       grep "^Cc: R. E. Cipient <rcipient@example.com>\$" patch6
 '
 
 test_expect_success 'configuration headers and command line headers' '
 
        git config --replace-all format.headers "Cc: R. E. Cipient <rcipient@example.com>" &&
-       git format-patch --add-header="Cc: S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^$/q" >patch7 &&
-       grep "^Cc: R. E. Cipient <rcipient@example.com>,$" patch7 &&
-       grep "^ *S. E. Cipient <scipient@example.com>$" patch7
+       git format-patch --add-header="Cc: S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch7 &&
+       grep "^Cc: R. E. Cipient <rcipient@example.com>,\$" patch7 &&
+       grep "^ *S. E. Cipient <scipient@example.com>\$" patch7
+'
+
+test_expect_success 'command line To: header' '
+
+       git config --unset-all format.headers &&
+       git format-patch --to="R. E. Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
+       grep "^To: R. E. Cipient <rcipient@example.com>\$" patch8
+'
+
+test_expect_success 'configuration To: header' '
+
+       git config format.to "R. E. Cipient <rcipient@example.com>" &&
+       git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
+       grep "^To: R. E. Cipient <rcipient@example.com>\$" patch9
+'
+
+test_expect_success '--no-to overrides config.to' '
+
+       git config --replace-all format.to \
+               "R. E. Cipient <rcipient@example.com>" &&
+       git format-patch --no-to --stdout master..side |
+       sed -e "/^\$/q" >patch10 &&
+       ! grep "^To: R. E. Cipient <rcipient@example.com>\$" patch10
+'
+
+test_expect_success '--no-to and --to replaces config.to' '
+
+       git config --replace-all format.to \
+               "Someone <someone@out.there>" &&
+       git format-patch --no-to --to="Someone Else <else@out.there>" \
+               --stdout master..side |
+       sed -e "/^\$/q" >patch11 &&
+       ! grep "^To: Someone <someone@out.there>\$" patch11 &&
+       grep "^To: Someone Else <else@out.there>\$" patch11
+'
+
+test_expect_success '--no-cc overrides config.cc' '
+
+       git config --replace-all format.cc \
+               "C. E. Cipient <rcipient@example.com>" &&
+       git format-patch --no-cc --stdout master..side |
+       sed -e "/^\$/q" >patch12 &&
+       ! grep "^Cc: C. E. Cipient <rcipient@example.com>\$" patch12
+'
+
+test_expect_success '--no-add-headers overrides config.headers' '
+
+       git config --replace-all format.headers \
+               "Header1: B. E. Cipient <rcipient@example.com>" &&
+       git format-patch --no-add-headers --stdout master..side |
+       sed -e "/^\$/q" >patch13 &&
+       ! grep "^Header1: B. E. Cipient <rcipient@example.com>\$" patch13
 '
 
 test_expect_success 'multiple files' '
@@ -406,9 +458,9 @@ test_expect_success 'cover-letter inherits diff options' '
        git mv file foo &&
        git commit -m foo &&
        git format-patch --cover-letter -1 &&
-       ! grep "file => foo .* 0 *$" 0000-cover-letter.patch &&
+       ! grep "file => foo .* 0 *\$" 0000-cover-letter.patch &&
        git format-patch --cover-letter -1 -M &&
-       grep "file => foo .* 0 *$" 0000-cover-letter.patch
+       grep "file => foo .* 0 *\$" 0000-cover-letter.patch
 
 '
 
@@ -425,7 +477,7 @@ EOF
 test_expect_success 'shortlog of cover-letter wraps overly-long onelines' '
 
        git format-patch --cover-letter -2 &&
-       sed -e "1,/A U Thor/d" -e "/^$/q" < 0000-cover-letter.patch > output &&
+       sed -e "1,/A U Thor/d" -e "/^\$/q" < 0000-cover-letter.patch > output &&
        test_cmp expect output
 
 '
@@ -450,7 +502,28 @@ EOF
 test_expect_success 'format-patch respects -U' '
 
        git format-patch -U4 -2 &&
-       sed -e "1,/^$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output &&
+       sed -e "1,/^\$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output &&
+       test_cmp expect output
+
+'
+
+cat > expect << EOF
+
+diff --git a/file b/file
+index 40f36c6..2dc5c23 100644
+--- a/file
++++ b/file
+@@ -14,3 +14,19 @@ C
+ D
+ E
+ F
++5
+EOF
+
+test_expect_success 'format-patch -p suppresses stat' '
+
+       git format-patch -p -2 &&
+       sed -e "1,/^\$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output &&
        test_cmp expect output
 
 '
@@ -493,16 +566,103 @@ test_expect_success 'format-patch from a subdirectory (2)' '
 '
 
 test_expect_success 'format-patch from a subdirectory (3)' '
-       here="$TEST_DIRECTORY/$test" &&
        rm -f 0* &&
        filename=$(
                rm -rf sub &&
                mkdir -p sub/dir &&
                cd sub/dir &&
-               git format-patch -1 -o "$here"
+               git format-patch -1 -o "$TRASH_DIRECTORY"
        ) &&
        basename=$(expr "$filename" : ".*/\(.*\)") &&
        test -f "$basename"
 '
 
+test_expect_success 'format-patch --in-reply-to' '
+       git format-patch -1 --stdout --in-reply-to "baz@foo.bar" > patch8 &&
+       grep "^In-Reply-To: <baz@foo.bar>" patch8 &&
+       grep "^References: <baz@foo.bar>" patch8
+'
+
+test_expect_success 'format-patch --signoff' '
+       git format-patch -1 --signoff --stdout |
+       grep "^Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
+'
+
+echo "fatal: --name-only does not make sense" > expect.name-only
+echo "fatal: --name-status does not make sense" > expect.name-status
+echo "fatal: --check does not make sense" > expect.check
+
+test_expect_success 'options no longer allowed for format-patch' '
+       test_must_fail git format-patch --name-only 2> output &&
+       test_cmp expect.name-only output &&
+       test_must_fail git format-patch --name-status 2> output &&
+       test_cmp expect.name-status output &&
+       test_must_fail git format-patch --check 2> output &&
+       test_cmp expect.check output'
+
+test_expect_success 'format-patch --numstat should produce a patch' '
+       git format-patch --numstat --stdout master..side > output &&
+       test 6 = $(grep "^diff --git a/" output | wc -l)'
+
+test_expect_success 'format-patch -- <path>' '
+       git format-patch master..side -- file 2>error &&
+       ! grep "Use .--" error
+'
+
+test_expect_success 'format-patch --ignore-if-in-upstream HEAD' '
+       git format-patch --ignore-if-in-upstream HEAD
+'
+
+test_expect_success 'format-patch --signature' '
+       git format-patch --stdout --signature="my sig" -1 >output &&
+       grep "my sig" output
+'
+
+test_expect_success 'format-patch with format.signature config' '
+       git config format.signature "config sig" &&
+       git format-patch --stdout -1 >output &&
+       grep "config sig" output
+'
+
+test_expect_success 'format-patch --signature overrides format.signature' '
+       git config format.signature "config sig" &&
+       git format-patch --stdout --signature="overrides" -1 >output &&
+       ! grep "config sig" output &&
+       grep "overrides" output
+'
+
+test_expect_success 'format-patch --no-signature ignores format.signature' '
+       git config format.signature "config sig" &&
+       git format-patch --stdout --signature="my sig" --no-signature \
+               -1 >output &&
+       ! grep "config sig" output &&
+       ! grep "my sig" output &&
+       ! grep "^-- \$" output
+'
+
+test_expect_success 'format-patch --signature --cover-letter' '
+       git config --unset-all format.signature &&
+       git format-patch --stdout --signature="my sig" --cover-letter \
+               -1 >output &&
+       grep "my sig" output &&
+       test 2 = $(grep "my sig" output | wc -l)
+'
+
+test_expect_success 'format.signature="" supresses signatures' '
+       git config format.signature "" &&
+       git format-patch --stdout -1 >output &&
+       ! grep "^-- \$" output
+'
+
+test_expect_success 'format-patch --no-signature supresses signatures' '
+       git config --unset-all format.signature &&
+       git format-patch --stdout --no-signature -1 >output &&
+       ! grep "^-- \$" output
+'
+
+test_expect_success 'format-patch --signature="" supresses signatures' '
+       git format-patch --signature="" -1 >output &&
+       ! grep "^-- \$" output
+'
+
 test_done
index 6d13da30dad5a78fb17a01e86ef33072ea9e6250..935d101fe8d880ff426452ca6f611ce8d30b8bc1 100755 (executable)
@@ -93,8 +93,6 @@ git diff > out
 test_expect_success 'another test, without options' 'test_cmp expect out'
 
 cat << EOF > expect
-diff --git a/x b/x
-index d99af23..8b32fb5 100644
 EOF
 git diff -w > out
 test_expect_success 'another test, with -w' 'test_cmp expect out'
@@ -354,6 +352,48 @@ test_expect_success 'check tabs and spaces as indentation (indent-with-non-tab:
 
 '
 
+test_expect_success 'check tabs as indentation (tab-in-indent: off)' '
+
+       git config core.whitespace "-tab-in-indent" &&
+       echo "  foo ();" > x &&
+       git diff --check
+
+'
+
+test_expect_success 'check tabs as indentation (tab-in-indent: on)' '
+
+       git config core.whitespace "tab-in-indent" &&
+       echo "  foo ();" > x &&
+       test_must_fail git diff --check
+
+'
+
+test_expect_success 'check tabs and spaces as indentation (tab-in-indent: on)' '
+
+       git config core.whitespace "tab-in-indent" &&
+       echo "                  foo ();" > x &&
+       test_must_fail git diff --check
+
+'
+
+test_expect_success 'check tab-in-indent and indent-with-non-tab conflict' '
+
+       git config core.whitespace "tab-in-indent,indent-with-non-tab" &&
+       echo "foo ();" > x &&
+       test_must_fail git diff --check
+
+'
+
+test_expect_success 'check tab-in-indent excluded from wildcard whitespace attribute' '
+
+       git config --unset core.whitespace &&
+       echo "x whitespace" > .gitattributes &&
+       echo "    foo ();" > x &&
+       git diff --check &&
+       rm -f .gitattributes
+
+'
+
 test_expect_success 'line numbers in --check output are correct' '
 
        echo "" > x &&
@@ -362,10 +402,17 @@ test_expect_success 'line numbers in --check output are correct' '
 
 '
 
-test_expect_success 'checkdiff detects trailing blank lines' '
+test_expect_success 'checkdiff detects new trailing blank lines (1)' '
        echo "foo();" >x &&
        echo "" >>x &&
-       git diff --check | grep "ends with blank"
+       git diff --check | grep "new blank line"
+'
+
+test_expect_success 'checkdiff detects new trailing blank lines (2)' '
+       { echo a; echo b; echo; echo; } >x &&
+       git add x &&
+       { echo a; echo; echo; echo; echo; } >x &&
+       git diff --check | grep "new blank line"
 '
 
 test_expect_success 'checkdiff allows new blank lines' '
@@ -379,6 +426,55 @@ test_expect_success 'checkdiff allows new blank lines' '
        git diff --check
 '
 
+cat <<EOF >expect
+EOF
+test_expect_success 'whitespace-only changes not reported' '
+       git reset --hard &&
+       echo >x "hello world" &&
+       git add x &&
+       git commit -m "hello 1" &&
+       echo >x "hello  world" &&
+       git diff -b >actual &&
+       test_cmp expect actual
+'
+
+cat <<EOF >expect
+diff --git a/x b/z
+similarity index NUM%
+rename from x
+rename to z
+index 380c32a..a97b785 100644
+EOF
+test_expect_success 'whitespace-only changes reported across renames' '
+       git reset --hard &&
+       for i in 1 2 3 4 5 6 7 8 9; do echo "$i$i$i$i$i$i"; done >x &&
+       git add x &&
+       git commit -m "base" &&
+       sed -e "5s/^/ /" x >z &&
+       git rm x &&
+       git add z &&
+       git diff -w -M --cached |
+       sed -e "/^similarity index /s/[0-9][0-9]*/NUM/" >actual &&
+       test_cmp expect actual
+'
+
+cat >expected <<\EOF
+diff --git a/empty b/void
+similarity index 100%
+rename from empty
+rename to void
+EOF
+
+test_expect_success 'rename empty' '
+       git reset --hard &&
+       >empty &&
+       git add empty &&
+       git commit -m empty &&
+       git mv empty void &&
+       git diff -w --cached -M >current &&
+       test_cmp expected current
+'
+
 test_expect_success 'combined diff with autocrlf conversion' '
 
        git reset --hard &&
index 55eb5f83f17c0ebfb537d390fd3913b7c89d65f4..ab0c2f0574f915296b885a21c6151a6947bdae7e 100755 (executable)
@@ -13,12 +13,14 @@ P1='pathname        with HT'
 P2='pathname with SP'
 P3='pathname
 with LF'
-: 2>/dev/null >"$P1" && test -f "$P1" && rm -f "$P1" || {
-       say 'Your filesystem does not allow tabs in filenames, test skipped.'
-       test_done
-}
+if : 2>/dev/null >"$P1" && test -f "$P1" && rm -f "$P1"
+then
+       test_set_prereq TABS_IN_FILENAMES
+else
+       say 'Your filesystem does not allow tabs in filenames'
+fi
 
-test_expect_success setup '
+test_expect_success TABS_IN_FILENAMES setup '
        echo P0.0 >"$P0.0" &&
        echo P0.1 >"$P0.1" &&
        echo P0.2 >"$P0.2" &&
@@ -38,6 +40,7 @@ test_expect_success setup '
        :
 '
 
+test_expect_success TABS_IN_FILENAMES 'setup expected files' '
 cat >expect <<\EOF
  rename pathname.1 => "Rpathname\twith HT.0" (100%)
  rename pathname.3 => "Rpathname\nwith LF.0" (100%)
@@ -47,11 +50,14 @@ cat >expect <<\EOF
  rename pathname.0 => Rpathname.0 (100%)
  rename "pathname\twith HT.0" => Rpathname.1 (100%)
 EOF
-test_expect_success 'git diff --summary -M HEAD' '
+'
+
+test_expect_success TABS_IN_FILENAMES 'git diff --summary -M HEAD' '
        git diff --summary -M HEAD >actual &&
        test_cmp expect actual
 '
 
+test_expect_success TABS_IN_FILENAMES 'setup expected files' '
 cat >expect <<\EOF
  pathname.1 => "Rpathname\twith HT.0"            |    0
  pathname.3 => "Rpathname\nwith LF.0"            |    0
@@ -62,7 +68,9 @@ cat >expect <<\EOF
  "pathname\twith HT.0" => Rpathname.1            |    0
  7 files changed, 0 insertions(+), 0 deletions(-)
 EOF
-test_expect_success 'git diff --stat -M HEAD' '
+'
+
+test_expect_success TABS_IN_FILENAMES 'git diff --stat -M HEAD' '
        git diff --stat -M HEAD >actual &&
        test_cmp expect actual
 '
index 60dd2014d5ae5d5e9e168b8b60278d90ef93cc53..61589853df55e063fbe6489fc9c6effc4a9f33b6 100755 (executable)
@@ -5,6 +5,9 @@ test_description='Return value of diffs'
 . ./test-lib.sh
 
 test_expect_success 'setup' '
+       echo "1 " >a &&
+       git add . &&
+       git commit -m zeroth &&
        echo 1 >a &&
        git add . &&
        git commit -m first &&
@@ -13,6 +16,18 @@ test_expect_success 'setup' '
        git commit -a -m second
 '
 
+test_expect_success 'git diff --quiet -w  HEAD^^ HEAD^' '
+       git diff --quiet -w HEAD^^ HEAD^
+'
+
+test_expect_success 'git diff --quiet HEAD^^ HEAD^' '
+       test_must_fail git diff --quiet HEAD^^ HEAD^
+'
+
+test_expect_success 'git diff --quiet -w  HEAD^ HEAD' '
+       test_must_fail git diff --quiet -w HEAD^ HEAD
+'
+
 test_expect_success 'git diff-tree HEAD^ HEAD' '
        git diff-tree --exit-code HEAD^ HEAD
        test $? = 1
@@ -105,7 +120,6 @@ test_expect_success '--check with --no-pager returns 2 for dirty difference' '
 
 '
 
-
 test_expect_success 'check should test not just the last line' '
        echo "" >>a &&
        git --no-pager diff --check
@@ -127,4 +141,26 @@ test_expect_success 'check detects leftover conflict markers' '
        git reset --hard
 '
 
+test_expect_success 'check honors conflict marker length' '
+       git reset --hard &&
+       echo ">>>>>>> boo" >>b &&
+       echo "======" >>a &&
+       git diff --check a &&
+       (
+               git diff --check b
+               test $? = 2
+       ) &&
+       git reset --hard &&
+       echo ">>>>>>>> boo" >>b &&
+       echo "========" >>a &&
+       git diff --check &&
+       echo "b conflict-marker-size=8" >.gitattributes &&
+       (
+               git diff --check b
+               test $? = 2
+       ) &&
+       git diff --check a &&
+       git reset --hard
+'
+
 test_done
index 5b10e976a3fd0c554ff2fa14004e69343a9ec634..c8e19372b00f128646b809e1f5723d88b58a3f30 100755 (executable)
@@ -32,13 +32,18 @@ EOF
 
 sed 's/beer\\/beer,\\/' < Beer.java > Beer-correct.java
 
-builtin_patterns="bibtex cpp html java objc pascal php python ruby tex"
+builtin_patterns="bibtex cpp csharp html java objc pascal php python ruby tex"
 for p in $builtin_patterns
 do
        test_expect_success "builtin $p pattern compiles" '
                echo "*.java diff=$p" > .gitattributes &&
-               ! ( git diff --no-index Beer.java Beer-correct.java 2>&1 |
-                       grep "fatal" > /dev/null )
+               ! { git diff --no-index Beer.java Beer-correct.java 2>&1 |
+                       grep "fatal" > /dev/null; }
+       '
+       test_expect_success "builtin $p wordRegex pattern compiles" '
+               ! { git diff --no-index --word-diff \
+                       Beer.java Beer-correct.java 2>&1 |
+                       grep "fatal" > /dev/null; }
        '
 done
 
index 84a1fe31151c2af38554eaca8f03e2c1e2e7848f..f6d1f1ebab406fcd4f405178ec151149754500b0 100755 (executable)
@@ -20,11 +20,27 @@ test_expect_success setup '
 
 blue_grep='7;34m' ;# ESC [ 7 ; 3 4 m
 
+printf "\033[%s" "$blue_grep" >check-grep
+if (grep "$blue_grep" <check-grep | grep "$blue_grep") >/dev/null 2>&1
+then
+       grep_a=grep
+elif (grep -a "$blue_grep" <check-grep | grep -a "$blue_grep") >/dev/null 2>&1
+then
+       grep_a='grep -a'
+else
+       grep_a=grep ;# expected to fail...
+fi
+rm -f check-grep
+
+prepare_output () {
+       git diff --color >output
+       $grep_a "$blue_grep" output >error
+       $grep_a -v "$blue_grep" output >normal
+}
+
 test_expect_success default '
 
-       git diff --color >output
-       grep "$blue_grep" output >error
-       grep -v "$blue_grep" output >normal
+       prepare_output
 
        grep Eight normal >/dev/null &&
        grep HT error >/dev/null &&
@@ -37,9 +53,7 @@ test_expect_success default '
 test_expect_success 'without -trail' '
 
        git config core.whitespace -trail
-       git diff --color >output
-       grep "$blue_grep" output >error
-       grep -v "$blue_grep" output >normal
+       prepare_output
 
        grep Eight normal >/dev/null &&
        grep HT error >/dev/null &&
@@ -53,9 +67,7 @@ test_expect_success 'without -trail (attribute)' '
 
        git config --unset core.whitespace
        echo "F whitespace=-trail" >.gitattributes
-       git diff --color >output
-       grep "$blue_grep" output >error
-       grep -v "$blue_grep" output >normal
+       prepare_output
 
        grep Eight normal >/dev/null &&
        grep HT error >/dev/null &&
@@ -69,9 +81,7 @@ test_expect_success 'without -space' '
 
        rm -f .gitattributes
        git config core.whitespace -space
-       git diff --color >output
-       grep "$blue_grep" output >error
-       grep -v "$blue_grep" output >normal
+       prepare_output
 
        grep Eight normal >/dev/null &&
        grep HT normal >/dev/null &&
@@ -85,9 +95,7 @@ test_expect_success 'without -space (attribute)' '
 
        git config --unset core.whitespace
        echo "F whitespace=-space" >.gitattributes
-       git diff --color >output
-       grep "$blue_grep" output >error
-       grep -v "$blue_grep" output >normal
+       prepare_output
 
        grep Eight normal >/dev/null &&
        grep HT normal >/dev/null &&
@@ -101,9 +109,7 @@ test_expect_success 'with indent-non-tab only' '
 
        rm -f .gitattributes
        git config core.whitespace indent,-trailing,-space
-       git diff --color >output
-       grep "$blue_grep" output >error
-       grep -v "$blue_grep" output >normal
+       prepare_output
 
        grep Eight error >/dev/null &&
        grep HT normal >/dev/null &&
@@ -117,9 +123,7 @@ test_expect_success 'with indent-non-tab only (attribute)' '
 
        git config --unset core.whitespace
        echo "F whitespace=indent,-trailing,-space" >.gitattributes
-       git diff --color >output
-       grep "$blue_grep" output >error
-       grep -v "$blue_grep" output >normal
+       prepare_output
 
        grep Eight error >/dev/null &&
        grep HT normal >/dev/null &&
@@ -133,9 +137,7 @@ test_expect_success 'with cr-at-eol' '
 
        rm -f .gitattributes
        git config core.whitespace cr-at-eol
-       git diff --color >output
-       grep "$blue_grep" output >error
-       grep -v "$blue_grep" output >normal
+       prepare_output
 
        grep Eight normal >/dev/null &&
        grep HT error >/dev/null &&
@@ -149,9 +151,7 @@ test_expect_success 'with cr-at-eol (attribute)' '
 
        git config --unset core.whitespace
        echo "F whitespace=trailing,cr-at-eol" >.gitattributes
-       git diff --color >output
-       grep "$blue_grep" output >error
-       grep -v "$blue_grep" output >normal
+       prepare_output
 
        grep Eight normal >/dev/null &&
        grep HT error >/dev/null &&
@@ -165,7 +165,7 @@ test_expect_success 'trailing empty lines (1)' '
 
        rm -f .gitattributes &&
        test_must_fail git diff --check >output &&
-       grep "ends with blank lines." output &&
+       grep "new blank line at" output &&
        grep "trailing whitespace" output
 
 '
@@ -190,4 +190,13 @@ test_expect_success 'do not color trailing cr in context' '
 
 '
 
+test_expect_success 'color new trailing blank lines' '
+       { echo a; echo b; echo; echo; } >x &&
+       git add x &&
+       { echo a; echo; echo; echo; echo c; echo; echo; echo; echo; } >x &&
+       git diff --color x >output &&
+       cnt=$($grep_a "${blue_grep}" output | wc -l) &&
+       test $cnt = 2
+'
+
 test_done
index 0720001281db6aeb5a3b6bb46cd6914ad7d78d33..a7602cf923d95a51643753ce44fa65cf406aba9c 100755 (executable)
@@ -136,6 +136,15 @@ test_expect_success 'GIT_EXTERNAL_DIFF with more than one changed files' '
        GIT_EXTERNAL_DIFF=echo git diff
 '
 
+test_expect_success 'GIT_EXTERNAL_DIFF generates pretty paths' '
+       touch file.ext &&
+       git add file.ext &&
+       echo with extension > file.ext &&
+       GIT_EXTERNAL_DIFF=echo git diff file.ext | grep ......_file\.ext &&
+       git update-index --force-remove file.ext &&
+       rm file.ext
+'
+
 echo "#!$SHELL_PATH" >fake-diff.sh
 cat >> fake-diff.sh <<\EOF
 cat $2 >> crlfed.txt
@@ -157,7 +166,7 @@ test_expect_success 'diff --cached' '
        git update-index --assume-unchanged file &&
        echo second >file &&
        git diff --cached >actual &&
-       test_cmp ../t4020/diff.NUL actual
+       test_cmp "$TEST_DIRECTORY"/t4020/diff.NUL actual
 '
 
 test_done
index 390af2389f3b9559fcfebe1e21a24317562ab7af..709b3231ca8d9da631727b4aadfb2f46049d37e9 100755 (executable)
@@ -86,6 +86,13 @@ test_expect_success 'format.numbered && --no-numbered' '
 
 '
 
+test_expect_success 'format.numbered && --keep-subject' '
+
+       git format-patch --keep-subject --stdout HEAD^ >patch4a &&
+       grep "^Subject: Third" patch4a
+
+'
+
 test_expect_success 'format.numbered = auto' '
 
        git config format.numbered auto
@@ -108,4 +115,10 @@ test_expect_success 'format.numbered = auto && --no-numbered' '
 
 '
 
+test_expect_success '--start-number && --numbered' '
+
+       git format-patch --start-number 3 --numbered --stdout HEAD~1 > patch8 &&
+       grep "^Subject: \[PATCH 3/3\]" patch8
+'
+
 test_done
index 9bdf6596d82878f709a375084095697124a97609..5d20acf436558da6c11214f431beb503bc89459a 100755 (executable)
@@ -4,13 +4,7 @@ test_description='typechange rename detection'
 
 . ./test-lib.sh
 
-if ! test_have_prereq SYMLINKS
-then
-       say 'Symbolic links not supported, skipping tests.'
-       test_done
-fi
-
-test_expect_success setup '
+test_expect_success SYMLINKS setup '
 
        rm -f foo bar &&
        cat "$TEST_DIRECTORY"/../COPYING >foo &&
@@ -56,7 +50,7 @@ test_expect_success setup '
 
 '
 
-test_expect_success 'cross renames to be detected for regular files' '
+test_expect_success SYMLINKS 'cross renames to be detected for regular files' '
 
        git diff-tree five six -r --name-status -B -M | sort >actual &&
        {
@@ -67,7 +61,7 @@ test_expect_success 'cross renames to be detected for regular files' '
 
 '
 
-test_expect_success 'cross renames to be detected for typechange' '
+test_expect_success SYMLINKS 'cross renames to be detected for typechange' '
 
        git diff-tree one two -r --name-status -B -M | sort >actual &&
        {
@@ -78,7 +72,7 @@ test_expect_success 'cross renames to be detected for typechange' '
 
 '
 
-test_expect_success 'moves and renames' '
+test_expect_success SYMLINKS 'moves and renames' '
 
        git diff-tree three four -r --name-status -B -M | sort >actual &&
        {
index b61e5169f4e9e8d9f87b9ea16e71dfcf1fb9f340..d5ccdd0cf8061e797e88185bfddb0864f73291dd 100755 (executable)
@@ -8,14 +8,13 @@ test_description='Test diff/status color escape codes'
 
 color()
 {
-       git config diff.color.new "$1" &&
-       test "`git config --get-color diff.color.new`" = "\e$2"
+       actual=$(git config --get-color no.such.slot "$1") &&
+       test "$actual" = "\e$2"
 }
 
 invalid_color()
 {
-       git config diff.color.new "$1" &&
-       test -z "`git config --get-color diff.color.new 2>/dev/null`"
+       test_must_fail git config --get-color no.such.slot "$1"
 }
 
 test_expect_success 'reset' '
@@ -42,6 +41,14 @@ test_expect_success 'fg bg attr' '
        color "blue red ul" "[4;34;41m"
 '
 
+test_expect_success 'fg bg attr...' '
+       color "blue bold dim ul blink reverse" "[1;2;4;5;7;34m"
+'
+
+test_expect_success 'long color specification' '
+       color "254 255 bold dim ul blink reverse" "[1;2;4;5;7;38;5;254;48;5;255m"
+'
+
 test_expect_success '256 colors' '
        color "254 bold 255" "[1;38;5;254;48;5;255m"
 '
@@ -66,4 +73,21 @@ test_expect_success 'extra character after attribute' '
        invalid_color "dimX"
 '
 
+test_expect_success 'unknown color slots are ignored (diff)' '
+       git config --unset diff.color.new
+       git config color.diff.nosuchslotwilleverbedefined white &&
+       git diff --color
+'
+
+test_expect_success 'unknown color slots are ignored (branch)' '
+       git config color.branch.nosuchslotwilleverbedefined white &&
+       git branch -a
+'
+
+test_expect_success 'unknown color slots are ignored (status)' '
+       git config color.status.nosuchslotwilleverbedefined white || exit
+       git status
+       case $? in 0|1) : ok ;; *) false ;; esac
+'
+
 test_done
index 5cf8924b2163ec55328ce270911a23cdcf52634a..d99814ac641c93383d7199f4419083bedcfc9ea8 100755 (executable)
@@ -32,7 +32,8 @@ test_expect_success setup '
                cd sub &&
                git rev-list HEAD
        ) &&
-       echo ":160000 160000 $3 $_z40 M sub" >expect
+       echo ":160000 160000 $3 $_z40 M sub" >expect &&
+       subtip=$3 subprev=$2
 '
 
 test_expect_success 'git diff --raw HEAD' '
@@ -50,6 +51,261 @@ test_expect_success 'git diff-files --raw' '
        test_cmp expect actual.files
 '
 
+expect_from_to () {
+       printf "%sSubproject commit %s\n+Subproject commit %s\n" \
+               "-" "$1" "$2"
+}
+
+test_expect_success 'git diff HEAD' '
+       git diff HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subtip $subprev &&
+       test_cmp expect.body actual.body
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (work tree)' '
+       echo >>sub/world &&
+       git diff HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subtip $subprev-dirty &&
+       test_cmp expect.body actual.body
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (index)' '
+       (
+               cd sub &&
+               git reset --hard &&
+               echo >>world &&
+               git add world
+       ) &&
+       git diff HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subtip $subprev-dirty &&
+       test_cmp expect.body actual.body
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (untracked)' '
+       (
+               cd sub &&
+               git reset --hard &&
+               git clean -qfdx &&
+               >cruft
+       ) &&
+       git diff HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subtip $subprev-dirty &&
+       test_cmp expect.body actual.body
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match)' '
+       git commit -m "x" sub &&
+       echo >>sub/world &&
+       git diff HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subprev $subprev-dirty &&
+       test_cmp expect.body actual.body &&
+       git diff --ignore-submodules HEAD >actual2 &&
+       ! test -s actual2 &&
+       git diff --ignore-submodules=untracked HEAD >actual3 &&
+       sed -e "1,/^@@/d" actual3 >actual3.body &&
+       expect_from_to >expect.body $subprev $subprev-dirty &&
+       test_cmp expect.body actual3.body &&
+       git diff --ignore-submodules=dirty HEAD >actual4 &&
+       ! test -s actual4
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match) [.git/config]' '
+       git config diff.ignoreSubmodules all &&
+       git diff HEAD >actual &&
+       ! test -s actual &&
+       git config submodule.subname.ignore none &&
+       git config submodule.subname.path sub &&
+       git diff HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subprev $subprev-dirty &&
+       test_cmp expect.body actual.body &&
+       git config submodule.subname.ignore all &&
+       git diff HEAD >actual2 &&
+       ! test -s actual2 &&
+       git config submodule.subname.ignore untracked &&
+       git diff HEAD >actual3 &&
+       sed -e "1,/^@@/d" actual3 >actual3.body &&
+       expect_from_to >expect.body $subprev $subprev-dirty &&
+       test_cmp expect.body actual3.body &&
+       git config submodule.subname.ignore dirty &&
+       git diff HEAD >actual4 &&
+       ! test -s actual4 &&
+       git diff HEAD --ignore-submodules=none >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subprev $subprev-dirty &&
+       test_cmp expect.body actual.body &&
+       git config --remove-section submodule.subname &&
+       git config --unset diff.ignoreSubmodules
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match) [.gitmodules]' '
+       git config diff.ignoreSubmodules dirty &&
+       git diff HEAD >actual &&
+       ! test -s actual &&
+       git config --add -f .gitmodules submodule.subname.ignore none &&
+       git config --add -f .gitmodules submodule.subname.path sub &&
+       git diff HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subprev $subprev-dirty &&
+       test_cmp expect.body actual.body &&
+       git config -f .gitmodules submodule.subname.ignore all &&
+       git config -f .gitmodules submodule.subname.path sub &&
+       git diff HEAD >actual2 &&
+       ! test -s actual2 &&
+       git config -f .gitmodules submodule.subname.ignore untracked &&
+       git diff HEAD >actual3 &&
+       sed -e "1,/^@@/d" actual3 >actual3.body &&
+       expect_from_to >expect.body $subprev $subprev-dirty &&
+       test_cmp expect.body actual3.body &&
+       git config -f .gitmodules submodule.subname.ignore dirty &&
+       git diff HEAD >actual4 &&
+       ! test -s actual4 &&
+       git config submodule.subname.ignore none &&
+       git config submodule.subname.path sub &&
+       git diff HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subprev $subprev-dirty &&
+       test_cmp expect.body actual.body &&
+       git config --remove-section submodule.subname &&
+       git config --remove-section -f .gitmodules submodule.subname &&
+       git config --unset diff.ignoreSubmodules &&
+       rm .gitmodules
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (index, refs match)' '
+       (
+               cd sub &&
+               git reset --hard &&
+               echo >>world &&
+               git add world
+       ) &&
+       git diff HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subprev $subprev-dirty &&
+       test_cmp expect.body actual.body
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match)' '
+       (
+               cd sub &&
+               git reset --hard &&
+               git clean -qfdx &&
+               >cruft
+       ) &&
+       git diff HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subprev $subprev-dirty &&
+       test_cmp expect.body actual.body &&
+       git diff --ignore-submodules=all HEAD >actual2 &&
+       ! test -s actual2 &&
+       git diff --ignore-submodules=untracked HEAD >actual3 &&
+       ! test -s actual3 &&
+       git diff --ignore-submodules=dirty HEAD >actual4 &&
+       ! test -s actual4
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match) [.git/config]' '
+       git config submodule.subname.ignore all &&
+       git config submodule.subname.path sub &&
+       git diff HEAD >actual2 &&
+       ! test -s actual2 &&
+       git config submodule.subname.ignore untracked &&
+       git diff HEAD >actual3 &&
+       ! test -s actual3 &&
+       git config submodule.subname.ignore dirty &&
+       git diff HEAD >actual4 &&
+       ! test -s actual4 &&
+       git diff --ignore-submodules=none HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subprev $subprev-dirty &&
+       test_cmp expect.body actual.body &&
+       git config --remove-section submodule.subname
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match) [.gitmodules]' '
+       git config --add -f .gitmodules submodule.subname.ignore all &&
+       git config --add -f .gitmodules submodule.subname.path sub &&
+       git diff HEAD >actual2 &&
+       ! test -s actual2 &&
+       git config -f .gitmodules submodule.subname.ignore untracked &&
+       git diff HEAD >actual3 &&
+       ! test -s actual3 &&
+       git config -f .gitmodules submodule.subname.ignore dirty &&
+       git diff HEAD >actual4 &&
+       ! test -s actual4 &&
+       git config submodule.subname.ignore none &&
+       git config submodule.subname.path sub &&
+       git diff HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subprev $subprev-dirty &&
+       test_cmp expect.body actual.body &&
+       git config --remove-section submodule.subname &&
+       git config --remove-section -f .gitmodules submodule.subname &&
+       rm .gitmodules
+'
+
+test_expect_success 'git diff between submodule commits' '
+       git diff HEAD^..HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subtip $subprev &&
+       test_cmp expect.body actual.body &&
+       git diff --ignore-submodules=dirty HEAD^..HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subtip $subprev &&
+       test_cmp expect.body actual.body &&
+       git diff --ignore-submodules HEAD^..HEAD >actual &&
+       ! test -s actual
+'
+
+test_expect_success 'git diff between submodule commits [.git/config]' '
+       git diff HEAD^..HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subtip $subprev &&
+       test_cmp expect.body actual.body &&
+       git config submodule.subname.ignore dirty &&
+       git config submodule.subname.path sub &&
+       git diff HEAD^..HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subtip $subprev &&
+       test_cmp expect.body actual.body &&
+       git config submodule.subname.ignore all &&
+       git diff HEAD^..HEAD >actual &&
+       ! test -s actual &&
+       git diff --ignore-submodules=dirty HEAD^..HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subtip $subprev &&
+       git config --remove-section submodule.subname
+'
+
+test_expect_success 'git diff between submodule commits [.gitmodules]' '
+       git diff HEAD^..HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subtip $subprev &&
+       test_cmp expect.body actual.body &&
+       git config --add -f .gitmodules submodule.subname.ignore dirty &&
+       git config --add -f .gitmodules submodule.subname.path sub &&
+       git diff HEAD^..HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subtip $subprev &&
+       test_cmp expect.body actual.body &&
+       git config -f .gitmodules submodule.subname.ignore all &&
+       git diff HEAD^..HEAD >actual &&
+       ! test -s actual &&
+       git config submodule.subname.ignore dirty &&
+       git config submodule.subname.path sub &&
+       git diff  HEAD^..HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subtip $subprev &&
+       git config --remove-section submodule.subname &&
+       git config --remove-section -f .gitmodules submodule.subname &&
+       rm .gitmodules
+'
+
 test_expect_success 'git diff (empty submodule dir)' '
        : >empty &&
        rm -rf sub/* sub/.git &&
index a3f0897a52ce2147388baeac6fc64d3b8501b516..88c5619ae7471ab0d3286259d88c437ae3953b4a 100755 (executable)
@@ -48,7 +48,7 @@ test_expect_success 'file is considered binary by plumbing' '
 
 test_expect_success 'setup textconv filters' '
        echo file diff=foo >.gitattributes &&
-       git config diff.foo.textconv "$PWD"/hexdump &&
+       git config diff.foo.textconv "\"$(pwd)\""/hexdump &&
        git config diff.fail.textconv false
 '
 
index a894c6062271c53b688830a3230d1c78fb561d70..7e7b307a24606131b4880817a0056af11973f3d2 100755 (executable)
@@ -54,7 +54,7 @@ chmod +x dump
 
 test_expect_success 'setup textconv' '
        echo file diff=foo >.gitattributes &&
-       git config diff.foo.textconv "$PWD"/dump
+       git config diff.foo.textconv "\"$(pwd)\""/dump
 '
 
 test_expect_success 'rewrite diff respects textconv' '
index 4508effcaacd3dbc8fadb13c7be0d631f4c946f1..6f7548c3a144ecada6642eccee6b0f687baafb2d 100755 (executable)
@@ -8,21 +8,13 @@ test_expect_success setup '
 
        git config diff.color.old red
        git config diff.color.new green
+       git config diff.color.func magenta
 
 '
 
-decrypt_color () {
-       sed \
-               -e 's/.\[1m/<WHITE>/g' \
-               -e 's/.\[31m/<RED>/g' \
-               -e 's/.\[32m/<GREEN>/g' \
-               -e 's/.\[36m/<BROWN>/g' \
-               -e 's/.\[m/<RESET>/g'
-}
-
 word_diff () {
        test_must_fail git diff --no-index "$@" pre post > output &&
-       decrypt_color < output > output.decrypted &&
+       test_decode_color <output >output.decrypted &&
        test_cmp expect output.decrypted
 }
 
@@ -47,9 +39,9 @@ cat > expect <<\EOF
 <WHITE>index 330b04f..5ed8eff 100644<RESET>
 <WHITE>--- a/pre<RESET>
 <WHITE>+++ b/post<RESET>
-<BROWN>@@ -1,3 +1,7 @@<RESET>
+<CYAN>@@ -1,3 +1,7 @@<RESET>
 <RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
-<RESET>
+
 a = b + c<RESET>
 
 <GREEN>aa = a<RESET>
@@ -63,14 +55,121 @@ test_expect_success 'word diff with runs of whitespace' '
 
 '
 
+test_expect_success '--word-diff=color' '
+
+       word_diff --word-diff=color
+
+'
+
+test_expect_success '--color --word-diff=color' '
+
+       word_diff --color --word-diff=color
+
+'
+
+sed 's/#.*$//' > expect <<EOF
+diff --git a/pre b/post
+index 330b04f..5ed8eff 100644
+--- a/pre
++++ b/post
+@@ -1,3 +1,7 @@
+-h(4)
++h(4),hh[44]
+~
+ # significant space
+~
+ a = b + c
+~
+~
++aa = a
+~
+~
++aeff = aeff * ( aaa )
+~
+EOF
+
+test_expect_success '--word-diff=porcelain' '
+
+       word_diff --word-diff=porcelain
+
+'
+
+cat > expect <<EOF
+diff --git a/pre b/post
+index 330b04f..5ed8eff 100644
+--- a/pre
++++ b/post
+@@ -1,3 +1,7 @@
+[-h(4)-]{+h(4),hh[44]+}
+
+a = b + c
+
+{+aa = a+}
+
+{+aeff = aeff * ( aaa )+}
+EOF
+
+test_expect_success '--word-diff=plain' '
+
+       word_diff --word-diff=plain
+
+'
+
+test_expect_success '--word-diff=plain --no-color' '
+
+       word_diff --word-diff=plain --no-color
+
+'
+
+cat > expect <<EOF
+<WHITE>diff --git a/pre b/post<RESET>
+<WHITE>index 330b04f..5ed8eff 100644<RESET>
+<WHITE>--- a/pre<RESET>
+<WHITE>+++ b/post<RESET>
+<CYAN>@@ -1,3 +1,7 @@<RESET>
+<RED>[-h(4)-]<RESET><GREEN>{+h(4),hh[44]+}<RESET>
+
+a = b + c<RESET>
+
+<GREEN>{+aa = a+}<RESET>
+
+<GREEN>{+aeff = aeff * ( aaa )+}<RESET>
+EOF
+
+test_expect_success '--word-diff=plain --color' '
+
+       word_diff --word-diff=plain --color
+
+'
+
 cat > expect <<\EOF
 <WHITE>diff --git a/pre b/post<RESET>
 <WHITE>index 330b04f..5ed8eff 100644<RESET>
 <WHITE>--- a/pre<RESET>
 <WHITE>+++ b/post<RESET>
-<BROWN>@@ -1,3 +1,7 @@<RESET>
+<CYAN>@@ -1 +1 @@<RESET>
+<RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
+<CYAN>@@ -3,0 +4,4 @@<RESET> <RESET><MAGENTA>a = b + c<RESET>
+
+<GREEN>aa = a<RESET>
+
+<GREEN>aeff = aeff * ( aaa )<RESET>
+EOF
+
+test_expect_success 'word diff without context' '
+
+       word_diff --color-words --unified=0
+
+'
+
+cat > expect <<\EOF
+<WHITE>diff --git a/pre b/post<RESET>
+<WHITE>index 330b04f..5ed8eff 100644<RESET>
+<WHITE>--- a/pre<RESET>
+<WHITE>+++ b/post<RESET>
+<CYAN>@@ -1,3 +1,7 @@<RESET>
 h(4),<GREEN>hh<RESET>[44]
-<RESET>
+
 a = b + c<RESET>
 
 <GREEN>aa = a<RESET>
@@ -104,9 +203,9 @@ cat > expect <<\EOF
 <WHITE>index 330b04f..5ed8eff 100644<RESET>
 <WHITE>--- a/pre<RESET>
 <WHITE>+++ b/post<RESET>
-<BROWN>@@ -1,3 +1,7 @@<RESET>
+<CYAN>@@ -1,3 +1,7 @@<RESET>
 h(4)<GREEN>,hh[44]<RESET>
-<RESET>
+
 a = b + c<RESET>
 
 <GREEN>aa = a<RESET>
@@ -131,6 +230,25 @@ test_expect_success 'command-line overrides config' '
        word_diff --color-words="[a-z]+"
 '
 
+cat > expect <<\EOF
+<WHITE>diff --git a/pre b/post<RESET>
+<WHITE>index 330b04f..5ed8eff 100644<RESET>
+<WHITE>--- a/pre<RESET>
+<WHITE>+++ b/post<RESET>
+<CYAN>@@ -1,3 +1,7 @@<RESET>
+h(4),<GREEN>{+hh+}<RESET>[44]
+
+a = b + c<RESET>
+
+<GREEN>{+aa = a+}<RESET>
+
+<GREEN>{+aeff = aeff * ( aaa+}<RESET> )
+EOF
+
+test_expect_success 'command-line overrides config: --word-diff-regex' '
+       word_diff --color --word-diff-regex="[a-z]+"
+'
+
 cp expect.non-whitespace-is-word expect
 
 test_expect_success '.gitattributes override config' '
@@ -146,9 +264,9 @@ cat > expect <<\EOF
 <WHITE>index 330b04f..5ed8eff 100644<RESET>
 <WHITE>--- a/pre<RESET>
 <WHITE>+++ b/post<RESET>
-<BROWN>@@ -1,3 +1,7 @@<RESET>
+<CYAN>@@ -1,3 +1,7 @@<RESET>
 h(4),<GREEN>hh[44<RESET>]
-<RESET>
+
 a = b + c<RESET>
 
 <GREEN>aa = a<RESET>
@@ -168,7 +286,7 @@ cat > expect <<\EOF
 <WHITE>index c29453b..be22f37 100644<RESET>
 <WHITE>--- a/pre<RESET>
 <WHITE>+++ b/post<RESET>
-<BROWN>@@ -1 +1 @@<RESET>
+<CYAN>@@ -1 +1 @@<RESET>
 aaa (aaa) <GREEN>aaa<RESET>
 EOF
 
@@ -187,7 +305,7 @@ cat > expect <<\EOF
 <WHITE>index 289cb9d..2d06f37 100644<RESET>
 <WHITE>--- a/pre<RESET>
 <WHITE>+++ b/post<RESET>
-<BROWN>@@ -1 +1 @@<RESET>
+<CYAN>@@ -1 +1 @@<RESET>
 (<RED>:<RESET>
 EOF
 
@@ -197,4 +315,20 @@ test_expect_success 'test when words are only removed at the end' '
 
 '
 
+cat > expect <<\EOF
+diff --git a/pre b/post
+index 289cb9d..2d06f37 100644
+--- a/pre
++++ b/post
+@@ -1 +1 @@
+-(:
++(
+EOF
+
+test_expect_success '--word-diff=none' '
+
+       word_diff --word-diff=plain --word-diff=none
+
+'
+
 test_done
diff --git a/t/t4037-diff-r-t-dirs.sh b/t/t4037-diff-r-t-dirs.sh
new file mode 100755 (executable)
index 0000000..f5ce3b2
--- /dev/null
@@ -0,0 +1,53 @@
+#!/bin/sh
+
+test_description='diff -r -t shows directory additions and deletions'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       mkdir dc dr dt &&
+       >dc/1 &&
+       >dr/2 &&
+       >dt/3 &&
+       >fc &&
+       >fr &&
+       >ft &&
+       git add . &&
+       test_tick &&
+       git commit -m initial &&
+
+       rm -fr dt dr ft fr &&
+       mkdir da ft &&
+       for p in dc/1 da/4 dt ft/5 fc
+       do
+               echo hello >$p || exit
+       done &&
+       git add -u &&
+       git add . &&
+       test_tick &&
+       git commit -m second
+'
+
+cat >expect <<\EOF
+A      da
+A      da/4
+M      dc
+M      dc/1
+D      dr
+D      dr/2
+A      dt
+D      dt
+D      dt/3
+M      fc
+D      fr
+D      ft
+A      ft
+A      ft/5
+EOF
+
+test_expect_success verify '
+       git diff-tree -r -t --name-status HEAD^ HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_done
diff --git a/t/t4038-diff-combined.sh b/t/t4038-diff-combined.sh
new file mode 100755 (executable)
index 0000000..40277c7
--- /dev/null
@@ -0,0 +1,92 @@
+#!/bin/sh
+
+test_description='combined diff'
+
+. ./test-lib.sh
+
+setup_helper () {
+       one=$1 branch=$2 side=$3 &&
+
+       git branch $side $branch &&
+       for l in $one two three fyra
+       do
+               echo $l
+       done >file &&
+       git add file &&
+       test_tick &&
+       git commit -m $branch &&
+       git checkout $side &&
+       for l in $one two three quatro
+       do
+               echo $l
+       done >file &&
+       git add file &&
+       test_tick &&
+       git commit -m $side &&
+       test_must_fail git merge $branch &&
+       for l in $one three four
+       do
+               echo $l
+       done >file &&
+       git add file &&
+       test_tick &&
+       git commit -m "merge $branch into $side"
+}
+
+verify_helper () {
+       it=$1 &&
+
+       # Ignore lines that were removed only from the other parent
+       sed -e '
+               1,/^@@@/d
+               /^ -/d
+               s/^\(.\)./\1/
+       ' "$it" >"$it.actual.1" &&
+       sed -e '
+               1,/^@@@/d
+               /^- /d
+               s/^.\(.\)/\1/
+       ' "$it" >"$it.actual.2" &&
+
+       git diff "$it^" "$it" -- | sed -e '1,/^@@/d' >"$it.expect.1" &&
+       test_cmp "$it.expect.1" "$it.actual.1" &&
+
+       git diff "$it^2" "$it" -- | sed -e '1,/^@@/d' >"$it.expect.2" &&
+       test_cmp "$it.expect.2" "$it.actual.2"
+}
+
+test_expect_success setup '
+       >file &&
+       git add file &&
+       test_tick &&
+       git commit -m initial &&
+
+       git branch withone &&
+       git branch sansone &&
+
+       git checkout withone &&
+       setup_helper one withone sidewithone &&
+
+       git checkout sansone &&
+       setup_helper "" sansone sidesansone
+'
+
+test_expect_success 'check combined output (1)' '
+       git show sidewithone -- >sidewithone &&
+       verify_helper sidewithone
+'
+
+test_expect_success 'check combined output (2)' '
+       git show sidesansone -- >sidesansone &&
+       verify_helper sidesansone
+'
+
+test_expect_success 'diagnose truncated file' '
+       >file &&
+       git add file &&
+       git commit --amend -C HEAD &&
+       git show >out &&
+       grep "diff --cc file" out
+'
+
+test_done
diff --git a/t/t4039-diff-assume-unchanged.sh b/t/t4039-diff-assume-unchanged.sh
new file mode 100755 (executable)
index 0000000..9d9498b
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+test_description='diff with assume-unchanged entries'
+
+. ./test-lib.sh
+
+# external diff has been tested in t4020-diff-external.sh
+
+test_expect_success 'setup' '
+       echo zero > zero &&
+       git add zero &&
+       git commit -m zero &&
+       echo one > one &&
+       echo two > two &&
+       git add one two &&
+       git commit -m onetwo &&
+       git update-index --assume-unchanged one &&
+       echo borked >> one &&
+       test "$(git ls-files -v one)" = "h one"
+'
+
+test_expect_success 'diff-index does not examine assume-unchanged entries' '
+       git diff-index HEAD^ -- one | grep -q 5626abf0f72e58d7a153368ba57db4c673c0e171
+'
+
+test_expect_success 'diff-files does not examine assume-unchanged entries' '
+       rm one &&
+       test -z "$(git diff-files -- one)"
+'
+
+test_done
diff --git a/t/t4040-whitespace-status.sh b/t/t4040-whitespace-status.sh
new file mode 100755 (executable)
index 0000000..a30b03b
--- /dev/null
@@ -0,0 +1,63 @@
+#!/bin/sh
+
+test_description='diff --exit-code with whitespace'
+. ./test-lib.sh
+
+test_expect_success setup '
+       mkdir a b &&
+       echo >c &&
+       echo >a/d &&
+       echo >b/e &&
+       git add . &&
+       test_tick &&
+       git commit -m initial &&
+       echo " " >a/d &&
+       test_tick &&
+       git commit -a -m second &&
+       echo "  " >a/d &&
+       echo " " >b/e &&
+       git add a/d
+'
+
+test_expect_success 'diff-tree --exit-code' '
+       test_must_fail git diff --exit-code HEAD^ HEAD &&
+       test_must_fail git diff-tree --exit-code HEAD^ HEAD
+'
+
+test_expect_success 'diff-tree -b --exit-code' '
+       git diff -b --exit-code HEAD^ HEAD &&
+       git diff-tree -b -p --exit-code HEAD^ HEAD &&
+       git diff-tree -b --exit-code HEAD^ HEAD
+'
+
+test_expect_success 'diff-index --cached --exit-code' '
+       test_must_fail git diff --cached --exit-code HEAD &&
+       test_must_fail git diff-index --cached --exit-code HEAD
+'
+
+test_expect_success 'diff-index -b -p --cached --exit-code' '
+       git diff -b --cached --exit-code HEAD &&
+       git diff-index -b -p --cached --exit-code HEAD
+'
+
+test_expect_success 'diff-index --exit-code' '
+       test_must_fail git diff --exit-code HEAD &&
+       test_must_fail git diff-index --exit-code HEAD
+'
+
+test_expect_success 'diff-index -b -p --exit-code' '
+       git diff -b --exit-code HEAD &&
+       git diff-index -b -p --exit-code HEAD
+'
+
+test_expect_success 'diff-files --exit-code' '
+       test_must_fail git diff --exit-code &&
+       test_must_fail git diff-files --exit-code
+'
+
+test_expect_success 'diff-files -b -p --exit-code' '
+       git diff -b --exit-code &&
+       git diff-files -b -p --exit-code
+'
+
+test_done
diff --git a/t/t4041-diff-submodule-option.sh b/t/t4041-diff-submodule-option.sh
new file mode 100755 (executable)
index 0000000..995bdfa
--- /dev/null
@@ -0,0 +1,429 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Jens Lehmann, based on t7401 by Ping Yin
+#
+
+test_description='Support for verbose submodule differences in git diff
+
+This test tries to verify the sanity of the --submodule option of git diff.
+'
+
+. ./test-lib.sh
+
+add_file () {
+       sm=$1
+       shift
+       owd=$(pwd)
+       cd "$sm"
+       for name; do
+               echo "$name" > "$name" &&
+               git add "$name" &&
+               test_tick &&
+               git commit -m "Add $name"
+       done >/dev/null
+       git rev-parse --verify HEAD | cut -c1-7
+       cd "$owd"
+}
+commit_file () {
+       test_tick &&
+       git commit "$@" -m "Commit $*" >/dev/null
+}
+
+test_create_repo sm1 &&
+add_file . foo >/dev/null
+
+head1=$(add_file sm1 foo1 foo2)
+
+test_expect_success 'added submodule' "
+       git add sm1 &&
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 0000000...$head1 (new submodule)
+EOF
+"
+
+commit_file sm1 &&
+head2=$(add_file sm1 foo3)
+
+test_expect_success 'modified submodule(forward)' "
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head1..$head2:
+  > Add foo3
+EOF
+"
+
+test_expect_success 'modified submodule(forward)' "
+       git diff --submodule=log >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head1..$head2:
+  > Add foo3
+EOF
+"
+
+test_expect_success 'modified submodule(forward) --submodule' "
+       git diff --submodule >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head1..$head2:
+  > Add foo3
+EOF
+"
+
+fullhead1=$(cd sm1; git rev-list --max-count=1 $head1)
+fullhead2=$(cd sm1; git rev-list --max-count=1 $head2)
+test_expect_success 'modified submodule(forward) --submodule=short' "
+       git diff --submodule=short >actual &&
+       diff actual - <<-EOF
+diff --git a/sm1 b/sm1
+index $head1..$head2 160000
+--- a/sm1
++++ b/sm1
+@@ -1 +1 @@
+-Subproject commit $fullhead1
++Subproject commit $fullhead2
+EOF
+"
+
+commit_file sm1 &&
+head3=$(
+       cd sm1 &&
+       git reset --hard HEAD~2 >/dev/null &&
+       git rev-parse --verify HEAD | cut -c1-7
+)
+
+test_expect_success 'modified submodule(backward)' "
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head2..$head3 (rewind):
+  < Add foo3
+  < Add foo2
+EOF
+"
+
+head4=$(add_file sm1 foo4 foo5) &&
+head4_full=$(GIT_DIR=sm1/.git git rev-parse --verify HEAD)
+test_expect_success 'modified submodule(backward and forward)' "
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head2...$head4:
+  > Add foo5
+  > Add foo4
+  < Add foo3
+  < Add foo2
+EOF
+"
+
+commit_file sm1 &&
+mv sm1 sm1-bak &&
+echo sm1 >sm1 &&
+head5=$(git hash-object sm1 | cut -c1-7) &&
+git add sm1 &&
+rm -f sm1 &&
+mv sm1-bak sm1
+
+test_expect_success 'typechanged submodule(submodule->blob), --cached' "
+       git diff --submodule=log --cached >actual &&
+       diff actual - <<-EOF
+Submodule sm1 41fbea9...0000000 (submodule deleted)
+diff --git a/sm1 b/sm1
+new file mode 100644
+index 0000000..9da5fb8
+--- /dev/null
++++ b/sm1
+@@ -0,0 +1 @@
++sm1
+EOF
+"
+
+test_expect_success 'typechanged submodule(submodule->blob)' "
+       git diff --submodule=log >actual &&
+       diff actual - <<-EOF
+diff --git a/sm1 b/sm1
+deleted file mode 100644
+index 9da5fb8..0000000
+--- a/sm1
++++ /dev/null
+@@ -1 +0,0 @@
+-sm1
+Submodule sm1 0000000...$head4 (new submodule)
+EOF
+"
+
+rm -rf sm1 &&
+git checkout-index sm1
+test_expect_success 'typechanged submodule(submodule->blob)' "
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head4...0000000 (submodule deleted)
+diff --git a/sm1 b/sm1
+new file mode 100644
+index 0000000..$head5
+--- /dev/null
++++ b/sm1
+@@ -0,0 +1 @@
++sm1
+EOF
+"
+
+rm -f sm1 &&
+test_create_repo sm1 &&
+head6=$(add_file sm1 foo6 foo7)
+fullhead6=$(cd sm1; git rev-list --max-count=1 $head6)
+test_expect_success 'nonexistent commit' "
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head4...$head6 (commits not present)
+EOF
+"
+
+commit_file
+test_expect_success 'typechanged submodule(blob->submodule)' "
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+diff --git a/sm1 b/sm1
+deleted file mode 100644
+index $head5..0000000
+--- a/sm1
++++ /dev/null
+@@ -1 +0,0 @@
+-sm1
+Submodule sm1 0000000...$head6 (new submodule)
+EOF
+"
+
+commit_file sm1 &&
+test_expect_success 'submodule is up to date' "
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+EOF
+"
+
+test_expect_success 'submodule contains untracked content' "
+       echo new > sm1/new-file &&
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 contains untracked content
+EOF
+"
+
+test_expect_success 'submodule contains untracked content (untracked ignored)' "
+       git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual &&
+       ! test -s actual
+"
+
+test_expect_success 'submodule contains untracked content (dirty ignored)' "
+       git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual &&
+       ! test -s actual
+"
+
+test_expect_success 'submodule contains untracked content (all ignored)' "
+       git diff-index -p --ignore-submodules=all --submodule=log HEAD >actual &&
+       ! test -s actual
+"
+
+test_expect_success 'submodule contains untracked and modifed content' "
+       echo new > sm1/foo6 &&
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 contains untracked content
+Submodule sm1 contains modified content
+EOF
+"
+
+test_expect_success 'submodule contains untracked and modifed content (untracked ignored)' "
+       echo new > sm1/foo6 &&
+       git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 contains modified content
+EOF
+"
+
+test_expect_success 'submodule contains untracked and modifed content (dirty ignored)' "
+       echo new > sm1/foo6 &&
+       git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual &&
+       ! test -s actual
+"
+
+test_expect_success 'submodule contains untracked and modifed content (all ignored)' "
+       echo new > sm1/foo6 &&
+       git diff-index -p --ignore-submodules --submodule=log HEAD >actual &&
+       ! test -s actual
+"
+
+test_expect_success 'submodule contains modifed content' "
+       rm -f sm1/new-file &&
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 contains modified content
+EOF
+"
+
+(cd sm1; git commit -mchange foo6 >/dev/null) &&
+head8=$(cd sm1; git rev-parse --verify HEAD | cut -c1-7) &&
+test_expect_success 'submodule is modified' "
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head6..$head8:
+  > change
+EOF
+"
+
+test_expect_success 'modified submodule contains untracked content' "
+       echo new > sm1/new-file &&
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 contains untracked content
+Submodule sm1 $head6..$head8:
+  > change
+EOF
+"
+
+test_expect_success 'modified submodule contains untracked content (untracked ignored)' "
+       git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head6..$head8:
+  > change
+EOF
+"
+
+test_expect_success 'modified submodule contains untracked content (dirty ignored)' "
+       git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head6..$head8:
+  > change
+EOF
+"
+
+test_expect_success 'modified submodule contains untracked content (all ignored)' "
+       git diff-index -p --ignore-submodules=all --submodule=log HEAD >actual &&
+       ! test -s actual
+"
+
+test_expect_success 'modified submodule contains untracked and modifed content' "
+       echo modification >> sm1/foo6 &&
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 contains untracked content
+Submodule sm1 contains modified content
+Submodule sm1 $head6..$head8:
+  > change
+EOF
+"
+
+test_expect_success 'modified submodule contains untracked and modifed content (untracked ignored)' "
+       echo modification >> sm1/foo6 &&
+       git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 contains modified content
+Submodule sm1 $head6..$head8:
+  > change
+EOF
+"
+
+test_expect_success 'modified submodule contains untracked and modifed content (dirty ignored)' "
+       echo modification >> sm1/foo6 &&
+       git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head6..$head8:
+  > change
+EOF
+"
+
+test_expect_success 'modified submodule contains untracked and modifed content (all ignored)' "
+       echo modification >> sm1/foo6 &&
+       git diff-index -p --ignore-submodules --submodule=log HEAD >actual &&
+       ! test -s actual
+"
+
+test_expect_success 'modified submodule contains modifed content' "
+       rm -f sm1/new-file &&
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 contains modified content
+Submodule sm1 $head6..$head8:
+  > change
+EOF
+"
+
+rm -rf sm1
+test_expect_success 'deleted submodule' "
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head6...0000000 (submodule deleted)
+EOF
+"
+
+test_create_repo sm2 &&
+head7=$(add_file sm2 foo8 foo9) &&
+git add sm2
+
+test_expect_success 'multiple submodules' "
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head6...0000000 (submodule deleted)
+Submodule sm2 0000000...$head7 (new submodule)
+EOF
+"
+
+test_expect_success 'path filter' "
+       git diff-index -p --submodule=log HEAD sm2 >actual &&
+       diff actual - <<-EOF
+Submodule sm2 0000000...$head7 (new submodule)
+EOF
+"
+
+commit_file sm2
+test_expect_success 'given commit' "
+       git diff-index -p --submodule=log HEAD^ >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head6...0000000 (submodule deleted)
+Submodule sm2 0000000...$head7 (new submodule)
+EOF
+"
+
+test_expect_success 'given commit --submodule' "
+       git diff-index -p --submodule HEAD^ >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head6...0000000 (submodule deleted)
+Submodule sm2 0000000...$head7 (new submodule)
+EOF
+"
+
+fullhead7=$(cd sm2; git rev-list --max-count=1 $head7)
+
+test_expect_success 'given commit --submodule=short' "
+       git diff-index -p --submodule=short HEAD^ >actual &&
+       diff actual - <<-EOF
+diff --git a/sm1 b/sm1
+deleted file mode 160000
+index $head6..0000000
+--- a/sm1
++++ /dev/null
+@@ -1 +0,0 @@
+-Subproject commit $fullhead6
+diff --git a/sm2 b/sm2
+new file mode 160000
+index 0000000..$head7
+--- /dev/null
++++ b/sm2
+@@ -0,0 +1 @@
++Subproject commit $fullhead7
+EOF
+"
+
+test_expect_success 'setup .git file for sm2' '
+       (cd sm2 &&
+        REAL="$(pwd)/../.real" &&
+        mv .git "$REAL"
+        echo "gitdir: $REAL" >.git)
+'
+
+test_expect_success 'diff --submodule with .git file' '
+       git diff --submodule HEAD^ >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head6...0000000 (submodule deleted)
+Submodule sm2 0000000...$head7 (new submodule)
+EOF
+'
+
+test_done
diff --git a/t/t4042-diff-textconv-caching.sh b/t/t4042-diff-textconv-caching.sh
new file mode 100755 (executable)
index 0000000..91f8198
--- /dev/null
@@ -0,0 +1,109 @@
+#!/bin/sh
+
+test_description='test textconv caching'
+. ./test-lib.sh
+
+cat >helper <<'EOF'
+#!/bin/sh
+sed 's/^/converted: /' "$@" >helper.out
+cat helper.out
+EOF
+chmod +x helper
+
+test_expect_success 'setup' '
+       echo foo content 1 >foo.bin &&
+       echo bar content 1 >bar.bin &&
+       git add . &&
+       git commit -m one &&
+       echo foo content 2 >foo.bin &&
+       echo bar content 2 >bar.bin &&
+       git commit -a -m two &&
+       echo "*.bin diff=magic" >.gitattributes &&
+       git config diff.magic.textconv ./helper &&
+       git config diff.magic.cachetextconv true
+'
+
+cat >expect <<EOF
+diff --git a/bar.bin b/bar.bin
+index fcf9166..28283d5 100644
+--- a/bar.bin
++++ b/bar.bin
+@@ -1 +1 @@
+-converted: bar content 1
++converted: bar content 2
+diff --git a/foo.bin b/foo.bin
+index d5b9fe3..1345db2 100644
+--- a/foo.bin
++++ b/foo.bin
+@@ -1 +1 @@
+-converted: foo content 1
++converted: foo content 2
+EOF
+
+test_expect_success 'first textconv works' '
+       git diff HEAD^ HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'cached textconv produces same output' '
+       git diff HEAD^ HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'cached textconv does not run helper' '
+       rm -f helper.out &&
+       git diff HEAD^ HEAD >actual &&
+       test_cmp expect actual &&
+       ! test -r helper.out
+'
+
+cat >expect <<EOF
+diff --git a/bar.bin b/bar.bin
+index fcf9166..28283d5 100644
+--- a/bar.bin
++++ b/bar.bin
+@@ -1,2 +1,2 @@
+ converted: other
+-converted: bar content 1
++converted: bar content 2
+diff --git a/foo.bin b/foo.bin
+index d5b9fe3..1345db2 100644
+--- a/foo.bin
++++ b/foo.bin
+@@ -1,2 +1,2 @@
+ converted: other
+-converted: foo content 1
++converted: foo content 2
+EOF
+test_expect_success 'changing textconv invalidates cache' '
+       echo other >other &&
+       git config diff.magic.textconv "./helper other" &&
+       git diff HEAD^ HEAD >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<EOF
+diff --git a/bar.bin b/bar.bin
+index fcf9166..28283d5 100644
+--- a/bar.bin
++++ b/bar.bin
+@@ -1,2 +1,2 @@
+ converted: other
+-converted: bar content 1
++converted: bar content 2
+diff --git a/foo.bin b/foo.bin
+index d5b9fe3..1345db2 100644
+--- a/foo.bin
++++ b/foo.bin
+@@ -1 +1 @@
+-converted: foo content 1
++converted: foo content 2
+EOF
+test_expect_success 'switching diff driver produces correct results' '
+       git config diff.moremagic.textconv ./helper &&
+       echo foo.bin diff=moremagic >>.gitattributes &&
+       git diff HEAD^ HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_done
diff --git a/t/t4043-diff-rename-binary.sh b/t/t4043-diff-rename-binary.sh
new file mode 100755 (executable)
index 0000000..0601281
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Jakub Narebski, Christian Couder
+#
+
+test_description='Move a binary file'
+
+. ./test-lib.sh
+
+
+test_expect_success 'prepare repository' '
+       git init &&
+       echo foo > foo &&
+       echo "barQ" | q_to_nul > bar &&
+       git add . &&
+       git commit -m "Initial commit"
+'
+
+test_expect_success 'move the files into a "sub" directory' '
+       mkdir sub &&
+       git mv bar foo sub/ &&
+       git commit -m "Moved to sub/"
+'
+
+cat > expected <<\EOF
+ bar => sub/bar |  Bin 5 -> 5 bytes
+ foo => sub/foo |    0
+ 2 files changed, 0 insertions(+), 0 deletions(-)
+
+diff --git a/bar b/sub/bar
+similarity index 100%
+rename from bar
+rename to sub/bar
+diff --git a/foo b/sub/foo
+similarity index 100%
+rename from foo
+rename to sub/foo
+EOF
+
+test_expect_success 'git show -C -C report renames' '
+       git show -C -C --raw --binary --stat | tail -n 12 > current &&
+       test_cmp expected current
+'
+
+test_done
diff --git a/t/t4044-diff-index-unique-abbrev.sh b/t/t4044-diff-index-unique-abbrev.sh
new file mode 100755 (executable)
index 0000000..d5ce72b
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+test_description='test unique sha1 abbreviation on "index from..to" line'
+. ./test-lib.sh
+
+cat >expect_initial <<EOF
+100644 blob 51d2738463ea4ca66f8691c91e33ce64b7d41bb1   foo
+EOF
+
+cat >expect_update <<EOF
+100644 blob 51d2738efb4ad8a1e40bed839ab8e116f0a15e47   foo
+EOF
+
+test_expect_success 'setup' '
+       echo 4827 > foo &&
+       git add foo &&
+       git commit -m "initial" &&
+       git cat-file -p HEAD: > actual &&
+       test_cmp expect_initial actual &&
+       echo 11742 > foo &&
+       git commit -a -m "update" &&
+       git cat-file -p HEAD: > actual &&
+       test_cmp expect_update actual
+'
+
+cat >expect <<EOF
+index 51d27384..51d2738e 100644
+EOF
+
+test_expect_success 'diff does not produce ambiguous index line' '
+       git diff HEAD^..HEAD | grep index > actual &&
+       test_cmp expect actual
+'
+
+test_done
diff --git a/t/t4045-diff-relative.sh b/t/t4045-diff-relative.sh
new file mode 100755 (executable)
index 0000000..8a3c63b
--- /dev/null
@@ -0,0 +1,61 @@
+#!/bin/sh
+
+test_description='diff --relative tests'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       git commit --allow-empty -m empty &&
+       echo content >file1 &&
+       mkdir subdir &&
+       echo other content >subdir/file2 &&
+       git add . &&
+       git commit -m one
+'
+
+check_diff() {
+expect=$1; shift
+cat >expected <<EOF
+diff --git a/$expect b/$expect
+new file mode 100644
+index 0000000..25c05ef
+--- /dev/null
++++ b/$expect
+@@ -0,0 +1 @@
++other content
+EOF
+test_expect_success "-p $*" "
+       git diff -p $* HEAD^ >actual &&
+       test_cmp expected actual
+"
+}
+
+check_stat() {
+expect=$1; shift
+cat >expected <<EOF
+ $expect |    1 +
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+EOF
+test_expect_success "--stat $*" "
+       git diff --stat $* HEAD^ >actual &&
+       test_cmp expected actual
+"
+}
+
+check_raw() {
+expect=$1; shift
+cat >expected <<EOF
+:000000 100644 0000000000000000000000000000000000000000 25c05ef3639d2d270e7fe765a67668f098092bc5 A     $expect
+EOF
+test_expect_success "--raw $*" "
+       git diff --no-abbrev --raw $* HEAD^ >actual &&
+       test_cmp expected actual
+"
+}
+
+for type in diff stat raw; do
+       check_$type file2 --relative=subdir/
+       check_$type file2 --relative=subdir
+       check_$type dir/file2 --relative=sub
+done
+
+test_done
index 1597965241c3566aa3a14e986fd5a630fb348479..e3ea3d5114b8f6a09b79c9a1323c563da0c7b170 100755 (executable)
@@ -7,6 +7,7 @@ test_description='git apply handling copy/rename patch.
 
 '
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-prereq-FILEMODE.sh
 
 # setup
 
@@ -31,13 +32,6 @@ test_expect_success setup \
 test_expect_success apply \
     'git apply --index --stat --summary --apply test-patch'
 
-if test "$(git config --bool core.filemode)" = false
-then
-       say 'filemode disabled on the filesystem'
-else
-       test_set_prereq FILEMODE
-fi
-
 test_expect_success FILEMODE validate \
            'test -f bar && ls -l bar | grep "^-..x......"'
 
index ad4cc1a7576d41131d291426d80c329ff838aa26..9692f16f3581f261c4c10a29f03751990adb7897 100755 (executable)
@@ -20,23 +20,25 @@ 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
-perl -pe 'y/x/\000/' <file1 >file3
-cat file3 >file4
-git add file2
-perl -pe 'y/\000/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 --binary master binary >BF.diff
-git diff-tree -p --binary -C master binary >CF.diff
+test_expect_success 'setup' "
+       git update-index --add --remove file1 file2 file4 &&
+       git commit -m 'Initial Version' 2>/dev/null &&
+
+       git checkout -b binary &&
+       perl -pe 'y/x/\000/' <file1 >file3 &&
+       cat file3 >file4 &&
+       git add file2 &&
+       perl -pe 'y/\000/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 --binary master binary >BF.diff &&
+       git diff-tree -p --binary -C master binary >CF.diff
+"
 
 test_expect_success 'stat binary diff -- should not fail.' \
        'git checkout master
index 0e3ce3611d9e83ab290ce034f2439961864ce30a..c617c2a33d8e8ac1dc7e049f9056ca6025fbf852 100755 (executable)
@@ -134,4 +134,13 @@ test_expect_success 'two lines' '
 
 '
 
+test_expect_success 'apply patch with 3 context lines matching at end' '
+       { echo a; echo b; echo c; echo d; } >file &&
+       git add file &&
+       echo e >>file &&
+       git diff >patch &&
+       >file &&
+       test_must_fail git apply patch
+'
+
 test_done
diff --git a/t/t4107-apply-ignore-whitespace.sh b/t/t4107-apply-ignore-whitespace.sh
new file mode 100755 (executable)
index 0000000..b04fc8f
--- /dev/null
@@ -0,0 +1,185 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Giuseppe Bilotta
+#
+
+test_description='git-apply --ignore-whitespace.
+
+'
+. ./test-lib.sh
+
+# This primes main.c file that indents without using HT at all.
+# Various patches with HT and other spaces are attempted in the test.
+
+cat > patch1.patch <<\EOF
+diff --git a/main.c b/main.c
+new file mode 100644
+--- /dev/null
++++ b/main.c
+@@ -0,0 +1,22 @@
++#include <stdio.h>
++
++void print_int(int num);
++int func(int num);
++
++int main() {
++       int i;
++
++       for (i = 0; i < 10; i++) {
++               print_int(func(i)); /* stuff */
++       }
++
++       return 0;
++}
++
++int func(int num) {
++       return num * num;
++}
++
++void print_int(int num) {
++       printf("%d", num);
++}
+EOF
+
+# Since whitespace is very significant and we want to prevent whitespace
+# mangling when creating this test from a patch, we protect 'fixable'
+# whitespace by replacing spaces with Z and replacing them at patch
+# creation time, hence the sed trick.
+
+# This patch will fail unless whitespace differences are being ignored
+
+sed -e 's/Z/ /g' > patch2.patch <<\EOF
+diff --git a/main.c b/main.c
+--- a/main.c
++++ b/main.c
+@@ -10,6 +10,8 @@
+Z              print_int(func(i)); /* stuff */
+Z      }
+Z
++      printf("\n");
++
+Z      return 0;
+Z}
+Z
+EOF
+
+# This patch will fail even if whitespace differences are being ignored,
+# because of the missing string at EOL. TODO: this testcase should be
+# improved by creating a line that has the same hash with and without
+# the final string.
+
+sed -e 's/Z/ /g' > patch3.patch <<\EOF
+diff --git a/main.c b/main.c
+--- a/main.c
++++ b/main.c
+@@ -10,3 +10,4 @@
+Z      for (i = 0; i < 10; i++) {
+Z              print_int(func(i));Z
++              /* stuff */
+Z      }
+EOF
+
+# This patch will fail even if whitespace differences are being ignored,
+# because of the missing EOL at EOF.
+
+sed -e 's/Z/ /g' > patch4.patch <<\EOF
+diff --git a/main.c b/main.c
+--- a/main.c
++++ b/main.c
+@@ -21,1 +21,1 @@
+-      };Z
+\ No newline at end of file
++      };
+EOF
+
+# This patch will fail unless whitespace differences are being ignored.
+
+sed -e 's/Z/ /g' > patch5.patch <<\EOF
+diff --git a/main.c b/main.c
+--- a/main.c
++++ b/main.c
+@@ -2,2 +2,3 @@
+Z      void print_int(int num);
++      /* a comment */
+Z      int func(int num);
+EOF
+
+# And this is how the final output should be.  Patches introduce
+# HTs but the original SP indents are mostly kept.
+
+sed -e 's/T/   /g' > main.c.final <<\EOF
+#include <stdio.h>
+
+void print_int(int num);
+T/* a comment */
+int func(int num);
+
+int main() {
+       int i;
+
+       for (i = 0; i < 10; i++) {
+               print_int(func(i)); /* stuff */
+       }
+
+Tprintf("\n");
+
+       return 0;
+}
+
+int func(int num) {
+       return num * num;
+}
+
+void print_int(int num) {
+       printf("%d", num);
+}
+EOF
+
+test_expect_success 'file creation' '
+       git apply patch1.patch
+'
+
+test_expect_success 'patch2 fails (retab)' '
+       test_must_fail git apply patch2.patch
+'
+
+test_expect_success 'patch2 applies with --ignore-whitespace' '
+       git apply --ignore-whitespace patch2.patch
+'
+
+test_expect_success 'patch2 reverse applies with --ignore-space-change' '
+       git apply -R --ignore-space-change patch2.patch
+'
+
+git config apply.ignorewhitespace change
+
+test_expect_success 'patch2 applies (apply.ignorewhitespace = change)' '
+       git apply patch2.patch
+'
+
+test_expect_success 'patch3 fails (missing string at EOL)' '
+       test_must_fail git apply patch3.patch
+'
+
+test_expect_success 'patch4 fails (missing EOL at EOF)' '
+       test_must_fail git apply patch4.patch
+'
+
+test_expect_success 'patch5 applies (leading whitespace)' '
+       git apply patch5.patch
+'
+
+test_expect_success 'patches do not mangle whitespace' '
+       test_cmp main.c main.c.final
+'
+
+test_expect_success 're-create file (with --ignore-whitespace)' '
+       rm -f main.c &&
+       git apply patch1.patch
+'
+
+test_expect_success 'patch5 fails (--no-ignore-whitespace)' '
+       test_must_fail git apply --no-ignore-whitespace patch5.patch
+'
+
+test_done
diff --git a/t/t4111-apply-subdir.sh b/t/t4111-apply-subdir.sh
new file mode 100755 (executable)
index 0000000..a52d94a
--- /dev/null
@@ -0,0 +1,142 @@
+#!/bin/sh
+
+test_description='patching from inconvenient places'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       cat >patch <<-\EOF &&
+       diff file.orig file
+       --- a/file.orig
+       +++ b/file
+       @@ -1 +1,2 @@
+        1
+       +2
+       EOF
+       patch="$(pwd)/patch" &&
+
+       echo 1 >preimage &&
+       printf "%s\n" 1 2 >postimage &&
+       echo 3 >other &&
+
+       test_tick &&
+       git commit --allow-empty -m basis
+'
+
+test_expect_success 'setup: subdir' '
+       reset_subdir() {
+               git reset &&
+               mkdir -p sub/dir/b &&
+               mkdir -p objects &&
+               cp "$1" file &&
+               cp "$1" objects/file &&
+               cp "$1" sub/dir/file &&
+               cp "$1" sub/dir/b/file &&
+               git add file sub/dir/file sub/dir/b/file objects/file &&
+               cp "$2" file &&
+               cp "$2" sub/dir/file &&
+               cp "$2" sub/dir/b/file &&
+               cp "$2" objects/file &&
+               test_might_fail git update-index --refresh -q
+       }
+'
+
+test_expect_success 'apply from subdir of toplevel' '
+       cp postimage expected &&
+       reset_subdir other preimage &&
+       (
+               cd sub/dir &&
+               git apply "$patch"
+       ) &&
+       test_cmp expected sub/dir/file
+'
+
+test_expect_success 'apply --cached from subdir of toplevel' '
+       cp postimage expected &&
+       cp other expected.working &&
+       reset_subdir preimage other &&
+       (
+               cd sub/dir &&
+               git apply --cached "$patch"
+       ) &&
+       git show :sub/dir/file >actual &&
+       test_cmp expected actual &&
+       test_cmp expected.working sub/dir/file
+'
+
+test_expect_success 'apply --index from subdir of toplevel' '
+       cp postimage expected &&
+       reset_subdir preimage other &&
+       (
+               cd sub/dir &&
+               test_must_fail git apply --index "$patch"
+       ) &&
+       reset_subdir other preimage &&
+       (
+               cd sub/dir &&
+               test_must_fail git apply --index "$patch"
+       ) &&
+       reset_subdir preimage preimage &&
+       (
+               cd sub/dir &&
+               git apply --index "$patch"
+       ) &&
+       git show :sub/dir/file >actual &&
+       test_cmp expected actual &&
+       test_cmp expected sub/dir/file
+'
+
+test_expect_success 'apply from .git dir' '
+       cp postimage expected &&
+       cp preimage .git/file &&
+       cp preimage .git/objects/file
+       (
+               cd .git &&
+               git apply "$patch"
+       ) &&
+       test_cmp expected .git/file
+'
+
+test_expect_success 'apply from subdir of .git dir' '
+       cp postimage expected &&
+       cp preimage .git/file &&
+       cp preimage .git/objects/file
+       (
+               cd .git/objects &&
+               git apply "$patch"
+       ) &&
+       test_cmp expected .git/objects/file
+'
+
+test_expect_success 'apply --cached from .git dir' '
+       cp postimage expected &&
+       cp other expected.working &&
+       cp other .git/file &&
+       reset_subdir preimage other &&
+       (
+               cd .git &&
+               git apply --cached "$patch"
+       ) &&
+       git show :file >actual &&
+       test_cmp expected actual &&
+       test_cmp expected.working file &&
+       test_cmp expected.working .git/file
+'
+
+test_expect_success 'apply --cached from subdir of .git dir' '
+       cp postimage expected &&
+       cp preimage expected.subdir &&
+       cp other .git/file &&
+       cp other .git/objects/file &&
+       reset_subdir preimage other &&
+       (
+               cd .git/objects &&
+               git apply --cached "$patch"
+       ) &&
+       git show :file >actual &&
+       git show :objects/file >actual.subdir &&
+       test_cmp expected actual &&
+       test_cmp expected.subdir actual.subdir
+'
+
+test_done
index 99ec13dd531c71299681acc3eb678b490ff68707..f12826fb09729d5753f3f6c9511dab5062f0aae1 100755 (executable)
@@ -9,13 +9,7 @@ test_description='git apply should not get confused with type changes.
 
 . ./test-lib.sh
 
-if ! test_have_prereq SYMLINKS
-then
-       say 'Symbolic links not supported, skipping tests.'
-       test_done
-fi
-
-test_expect_success 'setup repository and commits' '
+test_expect_success SYMLINKS 'setup repository and commits' '
        echo "hello world" > foo &&
        echo "hi planet" > bar &&
        git update-index --add foo bar &&
@@ -48,7 +42,7 @@ test_expect_success 'setup repository and commits' '
        git branch foo-baz-renamed-from-foo
        '
 
-test_expect_success 'file renamed from foo to foo/baz' '
+test_expect_success SYMLINKS 'file renamed from foo to foo/baz' '
        git checkout -f initial &&
        git diff-tree -M -p HEAD foo-baz-renamed-from-foo > patch &&
        git apply --index < patch
@@ -56,7 +50,7 @@ test_expect_success 'file renamed from foo to foo/baz' '
 test_debug 'cat patch'
 
 
-test_expect_success 'file renamed from foo/baz to foo' '
+test_expect_success SYMLINKS 'file renamed from foo/baz to foo' '
        git checkout -f foo-baz-renamed-from-foo &&
        git diff-tree -M -p HEAD initial > patch &&
        git apply --index < patch
@@ -64,7 +58,7 @@ test_expect_success 'file renamed from foo/baz to foo' '
 test_debug 'cat patch'
 
 
-test_expect_success 'directory becomes file' '
+test_expect_success SYMLINKS 'directory becomes file' '
        git checkout -f foo-becomes-a-directory &&
        git diff-tree -p HEAD initial > patch &&
        git apply --index < patch
@@ -72,7 +66,7 @@ test_expect_success 'directory becomes file' '
 test_debug 'cat patch'
 
 
-test_expect_success 'file becomes directory' '
+test_expect_success SYMLINKS 'file becomes directory' '
        git checkout -f initial &&
        git diff-tree -p HEAD foo-becomes-a-directory > patch &&
        git apply --index < patch
@@ -80,7 +74,7 @@ test_expect_success 'file becomes directory' '
 test_debug 'cat patch'
 
 
-test_expect_success 'file becomes symlink' '
+test_expect_success SYMLINKS 'file becomes symlink' '
        git checkout -f initial &&
        git diff-tree -p HEAD foo-symlinked-to-bar > patch &&
        git apply --index < patch
@@ -88,21 +82,21 @@ test_expect_success 'file becomes symlink' '
 test_debug 'cat patch'
 
 
-test_expect_success 'symlink becomes file' '
+test_expect_success SYMLINKS 'symlink becomes file' '
        git checkout -f foo-symlinked-to-bar &&
        git diff-tree -p HEAD foo-back-to-file > patch &&
        git apply --index < patch
        '
 test_debug 'cat patch'
 
-test_expect_success 'binary file becomes symlink' '
+test_expect_success SYMLINKS 'binary file becomes symlink' '
        git checkout -f foo-becomes-binary &&
        git diff-tree -p --binary HEAD foo-symlinked-to-bar > patch &&
        git apply --index < patch
        '
 test_debug 'cat patch'
 
-test_expect_success 'symlink becomes binary file' '
+test_expect_success SYMLINKS 'symlink becomes binary file' '
        git checkout -f foo-symlinked-to-bar &&
        git diff-tree -p --binary HEAD foo-becomes-binary > patch &&
        git apply --index < patch
@@ -110,7 +104,7 @@ test_expect_success 'symlink becomes binary file' '
 test_debug 'cat patch'
 
 
-test_expect_success 'symlink becomes directory' '
+test_expect_success SYMLINKS 'symlink becomes directory' '
        git checkout -f foo-symlinked-to-bar &&
        git diff-tree -p HEAD foo-becomes-a-directory > patch &&
        git apply --index < patch
@@ -118,7 +112,7 @@ test_expect_success 'symlink becomes directory' '
 test_debug 'cat patch'
 
 
-test_expect_success 'directory becomes symlink' '
+test_expect_success SYMLINKS 'directory becomes symlink' '
        git checkout -f foo-becomes-a-directory &&
        git diff-tree -p HEAD foo-symlinked-to-bar > patch &&
        git apply --index < patch
index b852e5898009bca0205c231033f8f72f48962b81..7674dd2ec9d6f14a0f2181ebf415378521f90fa4 100755 (executable)
@@ -9,13 +9,7 @@ test_description='git apply symlinks and partial files
 
 . ./test-lib.sh
 
-if ! test_have_prereq SYMLINKS
-then
-       say 'Symbolic links not supported, skipping tests.'
-       test_done
-fi
-
-test_expect_success setup '
+test_expect_success SYMLINKS setup '
 
        ln -s path1/path2/path3/path4/path5 link1 &&
        git add link? &&
@@ -34,7 +28,7 @@ test_expect_success setup '
 
 '
 
-test_expect_success 'apply symlink patch' '
+test_expect_success SYMLINKS 'apply symlink patch' '
 
        git checkout side &&
        git apply patch &&
@@ -43,7 +37,7 @@ test_expect_success 'apply symlink patch' '
 
 '
 
-test_expect_success 'apply --index symlink patch' '
+test_expect_success SYMLINKS 'apply --index symlink patch' '
 
        git checkout -f side &&
        git apply --index patch &&
index 83d4ba679850c2ae2548bcfcce3f22227fcde8c7..2b2d00b33410282b6decd0af9118ee3f79489e34 100755 (executable)
@@ -10,16 +10,50 @@ test_description='git apply -p handling.'
 test_expect_success setup '
        mkdir sub &&
        echo A >sub/file1 &&
-       cp sub/file1 file1 &&
+       cp sub/file1 file1.saved &&
        git add sub/file1 &&
        echo B >sub/file1 &&
        git diff >patch.file &&
-       rm sub/file1 &&
-       rmdir sub
+       git checkout -- sub/file1 &&
+       git mv sub süb &&
+       echo B >süb/file1 &&
+       git diff >patch.escaped &&
+       grep "[\]" patch.escaped &&
+       rm süb/file1 &&
+       rmdir süb
 '
 
 test_expect_success 'apply git diff with -p2' '
+       cp file1.saved file1 &&
        git apply -p2 patch.file
 '
 
+test_expect_success 'apply with too large -p' '
+       cp file1.saved file1 &&
+       test_must_fail git apply --stat -p3 patch.file 2>err &&
+       grep "removing 3 leading" err
+'
+
+test_expect_success 'apply (-p2) traditional diff with funny filenames' '
+       cat >patch.quotes <<-\EOF &&
+       diff -u "a/"sub/file1 "b/"sub/file1
+       --- "a/"sub/file1
+       +++ "b/"sub/file1
+       @@ -1 +1 @@
+       -A
+       +B
+       EOF
+       echo B >expected &&
+
+       cp file1.saved file1 &&
+       git apply -p2 patch.quotes &&
+       test_cmp expected file1
+'
+
+test_expect_success 'apply with too large -p and fancy filename' '
+       cp file1.saved file1 &&
+       test_must_fail git apply --stat -p3 patch.escaped 2>err &&
+       grep "removing 3 leading" err
+'
+
 test_done
index 0d3c1d5dd5c0f35f9cc44eab4fcba5ba2e36ddd7..39407376ba7da1cb8256bcd3041a83147cd9eee5 100755 (executable)
@@ -3,12 +3,6 @@
 test_description='apply to deeper directory without getting fooled with symlink'
 . ./test-lib.sh
 
-if ! test_have_prereq SYMLINKS
-then
-       say 'Symbolic links not supported, skipping tests.'
-       test_done
-fi
-
 lecho () {
        for l_
        do
@@ -16,7 +10,7 @@ lecho () {
        done
 }
 
-test_expect_success setup '
+test_expect_success SYMLINKS setup '
 
        mkdir -p arch/i386/boot arch/x86_64 &&
        lecho 1 2 3 4 5 >arch/i386/boot/Makefile &&
@@ -37,7 +31,7 @@ test_expect_success setup '
 
 '
 
-test_expect_success apply '
+test_expect_success SYMLINKS apply '
 
        git checkout test &&
        git diff --exit-code test &&
@@ -46,7 +40,7 @@ test_expect_success apply '
 
 '
 
-test_expect_success 'check result' '
+test_expect_success SYMLINKS 'check result' '
 
        git diff --exit-code master &&
        git diff --exit-code --cached master &&
index f83322e513b96bb90e71ce39340515c6be0db186..8a676a5dcd113418c2bd4ea4aa885fddd5951a3a 100755 (executable)
@@ -11,21 +11,22 @@ prepare_test_file () {
        #       !  trailing-space
        #       @  space-before-tab
        #       #  indent-with-non-tab
+       #       %  tab-in-indent
        sed -e "s/_/ /g" -e "s/>/       /" <<-\EOF
                An_SP in an ordinary line>and a HT.
-               >A HT.
-               _>A SP and a HT (@).
-               _>_A SP, a HT and a SP (@).
+               >A HT (%).
+               _>A SP and a HT (@%).
+               _>_A SP, a HT and a SP (@%).
                _______Seven SP.
                ________Eight SP (#).
-               _______>Seven SP and a HT (@).
-               ________>Eight SP and a HT (@#).
-               _______>_Seven SP, a HT and a SP (@).
-               ________>_Eight SP, a HT and a SP (@#).
+               _______>Seven SP and a HT (@%).
+               ________>Eight SP and a HT (@#%).
+               _______>_Seven SP, a HT and a SP (@%).
+               ________>_Eight SP, a HT and a SP (@#%).
                _______________Fifteen SP (#).
-               _______________>Fifteen SP and a HT (@#).
+               _______________>Fifteen SP and a HT (@#%).
                ________________Sixteen SP (#).
-               ________________>Sixteen SP and a HT (@#).
+               ________________>Sixteen SP and a HT (@#%).
                _____a__Five SP, a non WS, two SP.
                A line with a (!) trailing SP_
                A line with a (!) trailing HT>
@@ -39,12 +40,11 @@ apply_patch () {
 }
 
 test_fix () {
-
        # fix should not barf
        apply_patch --whitespace=fix || return 1
 
        # find touched lines
-       diff file target | sed -n -e "s/^> //p" >fixed
+       $DIFF file target | sed -n -e "s/^> //p" >fixed
 
        # the changed lines are all expeced to change
        fixed_cnt=$(wc -l <fixed)
@@ -85,14 +85,14 @@ test_expect_success setup '
 test_expect_success 'whitespace=nowarn, default rule' '
 
        apply_patch --whitespace=nowarn &&
-       diff file target
+       test_cmp file target
 
 '
 
 test_expect_success 'whitespace=warn, default rule' '
 
        apply_patch --whitespace=warn &&
-       diff file target
+       test_cmp file target
 
 '
 
@@ -108,7 +108,7 @@ test_expect_success 'whitespace=error-all, no rule' '
 
        git config core.whitespace -trailing,-space-before,-indent &&
        apply_patch --whitespace=error-all &&
-       diff file target
+       test_cmp file target
 
 '
 
@@ -117,7 +117,7 @@ test_expect_success 'whitespace=error-all, no rule (attribute)' '
        git config --unset core.whitespace &&
        echo "target -whitespace" >.gitattributes &&
        apply_patch --whitespace=error-all &&
-       diff file target
+       test_cmp file target
 
 '
 
@@ -130,22 +130,322 @@ do
                for i in - ''
                do
                        case "$i" in '') ti='#' ;; *) ti= ;; esac
-                       rule=${t}trailing,${s}space,${i}indent
-
-                       rm -f .gitattributes
-                       test_expect_success "rule=$rule" '
-                               git config core.whitespace "$rule" &&
-                               test_fix "$tt$ts$ti"
-                       '
-
-                       test_expect_success "rule=$rule (attributes)" '
-                               git config --unset core.whitespace &&
-                               echo "target whitespace=$rule" >.gitattributes &&
-                               test_fix "$tt$ts$ti"
-                       '
-
+                       for h in - ''
+                       do
+                               [ -z "$h$i" ] && continue
+                               case "$h" in '') th='%' ;; *) th= ;; esac
+                               rule=${t}trailing,${s}space,${i}indent,${h}tab
+
+                               rm -f .gitattributes
+                               test_expect_success "rule=$rule" '
+                                       git config core.whitespace "$rule" &&
+                                       test_fix "$tt$ts$ti$th"
+                               '
+
+                               test_expect_success "rule=$rule (attributes)" '
+                                       git config --unset core.whitespace &&
+                                       echo "target whitespace=$rule" >.gitattributes &&
+                                       test_fix "$tt$ts$ti$th"
+                               '
+
+                       done
                done
        done
 done
 
+create_patch () {
+       sed -e "s/_/ /" <<-\EOF
+               diff --git a/target b/target
+               index e69de29..8bd6648 100644
+               --- a/target
+               +++ b/target
+               @@ -0,0 +1,3 @@
+               +An empty line follows
+               +
+               +A line with trailing whitespace and no newline_
+               \ No newline at end of file
+       EOF
+}
+
+test_expect_success 'trailing whitespace & no newline at the end of file' '
+       >target &&
+       create_patch >patch-file &&
+       git apply --whitespace=fix patch-file &&
+       grep "newline$" target &&
+       grep "^$" target
+'
+
+test_expect_success 'blank at EOF with --whitespace=fix (1)' '
+       : these can fail depending on what we did before
+       git config --unset core.whitespace
+       rm -f .gitattributes
+
+       { echo a; echo b; echo c; } >one &&
+       git add one &&
+       { echo a; echo b; echo c; } >expect &&
+       { cat expect; echo; } >one &&
+       git diff -- one >patch &&
+
+       git checkout one &&
+       git apply --whitespace=fix patch &&
+       test_cmp expect one
+'
+
+test_expect_success 'blank at EOF with --whitespace=fix (2)' '
+       { echo a; echo b; echo c; } >one &&
+       git add one &&
+       { echo a; echo c; } >expect &&
+       { cat expect; echo; echo; } >one &&
+       git diff -- one >patch &&
+
+       git checkout one &&
+       git apply --whitespace=fix patch &&
+       test_cmp expect one
+'
+
+test_expect_success 'blank at EOF with --whitespace=fix (3)' '
+       { echo a; echo b; echo; } >one &&
+       git add one &&
+       { echo a; echo c; echo; } >expect &&
+       { cat expect; echo; echo; } >one &&
+       git diff -- one >patch &&
+
+       git checkout one &&
+       git apply --whitespace=fix patch &&
+       test_cmp expect one
+'
+
+test_expect_success 'blank at end of hunk, not at EOF with --whitespace=fix' '
+       { echo a; echo b; echo; echo; echo; echo; echo; echo d; } >one &&
+       git add one &&
+       { echo a; echo c; echo; echo; echo; echo; echo; echo; echo d; } >expect &&
+       cp expect one &&
+       git diff -- one >patch &&
+
+       git checkout one &&
+       git apply --whitespace=fix patch &&
+       test_cmp expect one
+'
+
+test_expect_success 'blank at EOF with --whitespace=warn' '
+       { echo a; echo b; echo c; } >one &&
+       git add one &&
+       echo >>one &&
+       cat one >expect &&
+       git diff -- one >patch &&
+
+       git checkout one &&
+       git apply --whitespace=warn patch 2>error &&
+       test_cmp expect one &&
+       grep "new blank line at EOF" error
+'
+
+test_expect_success 'blank at EOF with --whitespace=error' '
+       { echo a; echo b; echo c; } >one &&
+       git add one &&
+       cat one >expect &&
+       echo >>one &&
+       git diff -- one >patch &&
+
+       git checkout one &&
+       test_must_fail git apply --whitespace=error patch 2>error &&
+       test_cmp expect one &&
+       grep "new blank line at EOF" error
+'
+
+test_expect_success 'blank but not empty at EOF' '
+       { echo a; echo b; echo c; } >one &&
+       git add one &&
+       echo "   " >>one &&
+       cat one >expect &&
+       git diff -- one >patch &&
+
+       git checkout one &&
+       git apply --whitespace=warn patch 2>error &&
+       test_cmp expect one &&
+       grep "new blank line at EOF" error
+'
+
+test_expect_success 'applying beyond EOF requires one non-blank context line' '
+       { echo; echo; echo; echo; } >one &&
+       git add one &&
+       { echo b; } >>one &&
+       git diff -- one >patch &&
+
+       git checkout one &&
+       { echo a; echo; } >one &&
+       cp one expect &&
+       test_must_fail git apply --whitespace=fix patch &&
+       test_cmp one expect &&
+       test_must_fail git apply --ignore-space-change --whitespace=fix patch &&
+       test_cmp one expect
+'
+
+test_expect_success 'tons of blanks at EOF should not apply' '
+       for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16; do
+               echo; echo; echo; echo;
+       done >one &&
+       git add one &&
+       echo a >>one &&
+       git diff -- one >patch &&
+
+       >one &&
+       test_must_fail git apply --whitespace=fix patch &&
+       test_must_fail git apply --ignore-space-change --whitespace=fix patch
+'
+
+test_expect_success 'missing blank line at end with --whitespace=fix' '
+       echo a >one &&
+       echo >>one &&
+       git add one &&
+       echo b >>one &&
+       cp one expect &&
+       git diff -- one >patch &&
+       echo a >one &&
+       cp one saved-one &&
+       test_must_fail git apply patch &&
+       git apply --whitespace=fix patch &&
+       test_cmp one expect &&
+       mv saved-one one &&
+       git apply --ignore-space-change --whitespace=fix patch &&
+       test_cmp one expect
+'
+
+test_expect_success 'two missing blank lines at end with --whitespace=fix' '
+       { echo a; echo; echo b; echo c; } >one &&
+       cp one no-blank-lines &&
+       { echo; echo; } >>one &&
+       git add one &&
+       echo d >>one &&
+       cp one expect &&
+       echo >>one &&
+       git diff -- one >patch &&
+       cp no-blank-lines one &&
+       test_must_fail git apply patch &&
+       git apply --whitespace=fix patch &&
+       test_cmp one expect &&
+       mv no-blank-lines one &&
+       test_must_fail git apply patch &&
+       git apply --ignore-space-change --whitespace=fix patch &&
+       test_cmp one expect
+'
+
+test_expect_success 'missing blank line at end, insert before end, --whitespace=fix' '
+       { echo a; echo; } >one &&
+       git add one &&
+       { echo b; echo a; echo; } >one &&
+       cp one expect &&
+       git diff -- one >patch &&
+       echo a >one &&
+       test_must_fail git apply patch &&
+       git apply --whitespace=fix patch &&
+       test_cmp one expect
+'
+
+test_expect_success 'shrink file with tons of missing blanks at end of file' '
+       { echo a; echo b; echo c; } >one &&
+       cp one no-blank-lines &&
+       for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16; do
+               echo; echo; echo; echo;
+       done >>one &&
+       git add one &&
+       echo a >one &&
+       cp one expect &&
+       git diff -- one >patch &&
+       cp no-blank-lines one &&
+       test_must_fail git apply patch &&
+       git apply --whitespace=fix patch &&
+       test_cmp one expect &&
+       mv no-blank-lines one &&
+       git apply --ignore-space-change --whitespace=fix patch &&
+       test_cmp one expect
+'
+
+test_expect_success 'missing blanks at EOF must only match blank lines' '
+       { echo a; echo b; } >one &&
+       git add one &&
+       { echo c; echo d; } >>one &&
+       git diff -- one >patch &&
+
+       echo a >one &&
+       test_must_fail git apply patch
+       test_must_fail git apply --whitespace=fix patch &&
+       test_must_fail git apply --ignore-space-change --whitespace=fix patch
+'
+
+sed -e's/Z//' >one <<EOF
+a
+b
+c
+                     Z
+EOF
+
+test_expect_success 'missing blank line should match context line with spaces' '
+       git add one &&
+       echo d >>one &&
+       git diff -- one >patch &&
+       { echo a; echo b; echo c; } >one &&
+       cp one expect &&
+       { echo; echo d; } >>expect &&
+       git add one &&
+
+       git apply --whitespace=fix patch &&
+       test_cmp one expect
+'
+
+sed -e's/Z//' >one <<EOF
+a
+b
+c
+                     Z
+EOF
+
+test_expect_success 'same, but with the --ignore-space-option' '
+       git add one &&
+       echo d >>one &&
+       cp one expect &&
+       git diff -- one >patch &&
+       { echo a; echo b; echo c; } >one &&
+       git add one &&
+
+       git checkout-index -f one &&
+       git apply --ignore-space-change --whitespace=fix patch &&
+       test_cmp one expect
+'
+
+test_expect_success 'same, but with CR-LF line endings && cr-at-eol set' '
+       git config core.whitespace cr-at-eol &&
+       printf "a\r\n" >one &&
+       printf "b\r\n" >>one &&
+       printf "c\r\n" >>one &&
+       cp one save-one &&
+       printf "                 \r\n" >>one
+       git add one &&
+       printf "d\r\n" >>one &&
+       cp one expect &&
+       git diff -- one >patch &&
+       mv save-one one &&
+
+       git apply --ignore-space-change --whitespace=fix patch &&
+       test_cmp one expect
+'
+
+test_expect_success 'same, but with CR-LF line endings && cr-at-eol unset' '
+       git config --unset core.whitespace &&
+       printf "a\r\n" >one &&
+       printf "b\r\n" >>one &&
+       printf "c\r\n" >>one &&
+       cp one save-one &&
+       printf "                 \r\n" >>one
+       git add one &&
+       cp one expect &&
+       printf "d\r\n" >>one &&
+       git diff -- one >patch &&
+       mv save-one one &&
+       echo d >>expect &&
+
+       git apply --ignore-space-change --whitespace=fix patch &&
+       test_cmp one expect
+'
+
 test_done
index 3b471b641ba2d3274dd1ab89948485ff8ce4dfdb..9671de799949f8f67f906cd3db907e3a53cf4eef 100755 (executable)
@@ -37,11 +37,11 @@ test_expect_success setup '
        # patch-2 is the same as patch-1 but is based
        # on a version that already has whitespace fixed,
        # and does not introduce whitespace breakages.
-       sed -e "s/ $//" patch-1 >patch-2 &&
+       sed -e "s/ \$//" patch-1 >patch-2 &&
 
        # If all whitespace breakages are fixed the contents
        # should look like file-fixed
-       sed -e "s/ $//" file-1 >file-fixed
+       sed -e "s/ \$//" file-1 >file-fixed
 
 '
 
index 3a8202ea9311b1c90158ad0d115dda985060fdeb..77200c0b2d969d621623f3be8621a3f9925d50a5 100755 (executable)
@@ -27,7 +27,7 @@ test_expect_success 'apply same filename with independent changes' '
        cp same_fn same_fn2 &&
        git reset --hard &&
        git apply patch0 &&
-       diff same_fn same_fn2
+       test_cmp same_fn same_fn2
 '
 
 test_expect_success 'apply same filename with overlapping changes' '
@@ -40,7 +40,7 @@ test_expect_success 'apply same filename with overlapping changes' '
        cp same_fn same_fn2 &&
        git reset --hard &&
        git apply patch0 &&
-       diff same_fn same_fn2
+       test_cmp same_fn same_fn2
 '
 
 test_expect_success 'apply same new filename after rename' '
@@ -54,7 +54,7 @@ test_expect_success 'apply same new filename after rename' '
        cp new_fn new_fn2 &&
        git reset --hard &&
        git apply --index patch1 &&
-       diff new_fn new_fn2
+       test_cmp new_fn new_fn2
 '
 
 test_expect_success 'apply same old filename after rename -- should fail.' '
index 8f6aea48d84621ae3b7304636452c724a4bbe5b6..6cc741a634b0352c54fe8e5f61f1e99543909b8c 100755 (executable)
@@ -57,6 +57,23 @@ test_expect_success 'apply --directory (new file)' '
        test content = $(cat some/sub/dir/newfile)
 '
 
+cat > patch << EOF
+diff --git a/c/newfile2 b/c/newfile2
+new file mode 100644
+index 0000000..d95f3ad
+--- /dev/null
++++ b/c/newfile2
+@@ -0,0 +1 @@
++content
+EOF
+
+test_expect_success 'apply --directory -p (new file)' '
+       git reset --hard initial &&
+       git apply -p2 --directory=some/sub/dir/ --index patch &&
+       test content = $(git show :some/sub/dir/newfile2) &&
+       test content = $(cat some/sub/dir/newfile2)
+'
+
 cat > patch << EOF
 diff --git a/delfile b/delfile
 deleted file mode 100644
index fc7af0493102f12438d59ac5ed6e3e96eda8c841..0d36ebdc8653d7d22ab5831c1f154db452e4558e 100755 (executable)
@@ -3,13 +3,7 @@
 test_description='applying patch with mode bits'
 
 . ./test-lib.sh
-
-if test "$(git config --bool core.filemode)" = false
-then
-       say 'filemode disabled on the filesystem'
-else
-       test_set_prereq FILEMODE
-fi
+. "$TEST_DIRECTORY"/lib-prereq-FILEMODE.sh
 
 test_expect_success setup '
        echo original >file &&
diff --git a/t/t4131-apply-fake-ancestor.sh b/t/t4131-apply-fake-ancestor.sh
new file mode 100755 (executable)
index 0000000..94373ca
--- /dev/null
@@ -0,0 +1,42 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Stephen Boyd
+#
+
+test_description='git apply --build-fake-ancestor handling.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       test_commit 1 &&
+       test_commit 2 &&
+       mkdir sub &&
+       test_commit 3 sub/3 &&
+       test_commit 4
+'
+
+test_expect_success 'apply --build-fake-ancestor' '
+       git checkout 2 &&
+       echo "A" > 1.t &&
+       git diff > 1.patch &&
+       git reset --hard &&
+       git checkout 1 &&
+       git apply --build-fake-ancestor 1.ancestor 1.patch
+'
+
+test_expect_success 'apply --build-fake-ancestor in a subdirectory' '
+       git checkout 3 &&
+       echo "C" > sub/3.t &&
+       git diff > 3.patch &&
+       git reset --hard &&
+       git checkout 4 &&
+       (
+               cd sub &&
+               git apply --build-fake-ancestor 3.ancestor ../3.patch &&
+               test -f 3.ancestor
+       ) &&
+       git apply --build-fake-ancestor 3.ancestor 3.patch &&
+       test_cmp sub/3.ancestor 3.ancestor
+'
+
+test_done
diff --git a/t/t4132-apply-removal.sh b/t/t4132-apply-removal.sh
new file mode 100755 (executable)
index 0000000..bb1ffe3
--- /dev/null
@@ -0,0 +1,95 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Junio C Hamano
+
+test_description='git-apply notices removal patches generated by GNU diff'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       cat <<-EOF >c &&
+       diff -ruN a/file b/file
+       --- a/file      TS0
+       +++ b/file      TS1
+       @@ -0,0 +1 @@
+       +something
+       EOF
+
+       cat <<-EOF >d &&
+       diff -ruN a/file b/file
+       --- a/file      TS0
+       +++ b/file      TS1
+       @@ -1 +0,0 @@
+       -something
+       EOF
+
+       timeWest="1982-09-16 07:00:00.000000000 -0800" &&
+        timeGMT="1982-09-16 15:00:00.000000000 +0000" &&
+       timeEast="1982-09-17 00:00:00.000000000 +0900" &&
+
+       epocWest="1969-12-31 16:00:00.000000000 -0800" &&
+        epocGMT="1970-01-01 00:00:00.000000000 +0000" &&
+       epocEast="1970-01-01 09:00:00.000000000 +0900" &&
+
+       sed -e "s/TS0/$epocWest/" -e "s/TS1/$timeWest/" <c >createWest.patch &&
+       sed -e "s/TS0/$epocEast/" -e "s/TS1/$timeEast/" <c >createEast.patch &&
+       sed -e "s/TS0/$epocGMT/" -e "s/TS1/$timeGMT/" <c >createGMT.patch &&
+
+       sed -e "s/TS0/$timeWest/" -e "s/TS1/$timeWest/" <c >addWest.patch &&
+       sed -e "s/TS0/$timeEast/" -e "s/TS1/$timeEast/" <c >addEast.patch &&
+       sed -e "s/TS0/$timeGMT/" -e "s/TS1/$timeGMT/" <c >addGMT.patch &&
+
+       sed -e "s/TS0/$timeWest/" -e "s/TS1/$timeWest/" <d >emptyWest.patch &&
+       sed -e "s/TS0/$timeEast/" -e "s/TS1/$timeEast/" <d >emptyEast.patch &&
+       sed -e "s/TS0/$timeGMT/" -e "s/TS1/$timeGMT/" <d >emptyGMT.patch &&
+
+       sed -e "s/TS0/$timeWest/" -e "s/TS1/$epocWest/" <d >removeWest.patch &&
+       sed -e "s/TS0/$timeEast/" -e "s/TS1/$epocEast/" <d >removeEast.patch &&
+       sed -e "s/TS0/$timeGMT/" -e "s/TS1/$epocGMT/" <d >removeGMT.patch &&
+
+       echo something >something &&
+       >empty
+'
+
+for patch in *.patch
+do
+       test_expect_success "test $patch" '
+               rm -f file .git/index &&
+               case "$patch" in
+               create*)
+                       # must be able to create
+                       git apply --index $patch &&
+                       test_cmp file something &&
+                       # must notice the file is already there
+                       >file &&
+                       git add file &&
+                       test_must_fail git apply $patch
+                       ;;
+               add*)
+                       # must be able to create or patch
+                       git apply $patch &&
+                       test_cmp file something &&
+                       >file &&
+                       git apply $patch &&
+                       test_cmp file something
+                       ;;
+               empty*)
+                       # must leave an empty file
+                       cat something >file &&
+                       git add file &&
+                       git apply --index $patch &&
+                       test -f file &&
+                       test_cmp empty file
+                       ;;
+               remove*)
+                       # must remove the file
+                       cat something >file &&
+                       git add file &&
+                       git apply --index $patch &&
+                       ! test -f file
+                       ;;
+               esac
+       '
+done
+
+test_done
diff --git a/t/t4133-apply-filenames.sh b/t/t4133-apply-filenames.sh
new file mode 100755 (executable)
index 0000000..3421807
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Andreas Gruenbacher
+#
+
+test_description='git apply filename consistency check'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       cat > bad1.patch <<EOF
+diff --git a/f b/f
+new file mode 100644
+index 0000000..d00491f
+--- /dev/null
++++ b/f-blah
+@@ -0,0 +1 @@
++1
+EOF
+       cat > bad2.patch <<EOF
+diff --git a/f b/f
+deleted file mode 100644
+index d00491f..0000000
+--- b/f-blah
++++ /dev/null
+@@ -1 +0,0 @@
+-1
+EOF
+'
+
+test_expect_success 'apply diff with inconsistent filenames in headers' '
+       test_must_fail git apply bad1.patch 2>err
+       grep "inconsistent new filename" err
+       test_must_fail git apply bad2.patch 2>err
+       grep "inconsistent old filename" err
+'
+
+test_done
diff --git a/t/t4134-apply-submodule.sh b/t/t4134-apply-submodule.sh
new file mode 100755 (executable)
index 0000000..1b82f93
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Peter Collingbourne
+#
+
+test_description='git apply submodule tests'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       cat > create-sm.patch <<EOF
+diff --git a/dir/sm b/dir/sm
+new file mode 160000
+index 0000000..0123456
+--- /dev/null
++++ b/dir/sm
+@@ -0,0 +1 @@
++Subproject commit 0123456789abcdef0123456789abcdef01234567
+EOF
+       cat > remove-sm.patch <<EOF
+diff --git a/dir/sm b/dir/sm
+deleted file mode 160000
+index 0123456..0000000
+--- a/dir/sm
++++ /dev/null
+@@ -1 +0,0 @@
+-Subproject commit 0123456789abcdef0123456789abcdef01234567
+EOF
+'
+
+test_expect_success 'removing a submodule also removes all leading subdirectories' '
+       git apply --index create-sm.patch &&
+       test -d dir/sm &&
+       git apply --index remove-sm.patch &&
+       test \! -d dir
+'
+
+test_done
diff --git a/t/t4135-apply-weird-filenames.sh b/t/t4135-apply-weird-filenames.sh
new file mode 100755 (executable)
index 0000000..1e5aad5
--- /dev/null
@@ -0,0 +1,75 @@
+#!/bin/sh
+
+test_description='git apply with weird postimage filenames'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       vector=$TEST_DIRECTORY/t4135 &&
+
+       test_tick &&
+       git commit --allow-empty -m preimage &&
+       git tag preimage &&
+
+       reset_preimage() {
+               git checkout -f preimage^0 &&
+               git read-tree -u --reset HEAD &&
+               git update-index --refresh
+       } &&
+
+       test_when_finished "rm -f \"tab embedded.txt\"" &&
+       test_when_finished "rm -f '\''\"quoteembedded\".txt'\''" &&
+       if touch -- "tab        embedded.txt" '\''"quoteembedded".txt'\''
+       then
+               test_set_prereq FUNNYNAMES
+       fi
+'
+
+try_filename() {
+       desc=$1
+       postimage=$2
+       prereq=${3:-}
+       exp1=${4:-success}
+       exp2=${5:-success}
+       exp3=${6:-success}
+
+       test_expect_$exp1 $prereq "$desc, git-style file creation patch" "
+               echo postimage >expected &&
+               reset_preimage &&
+               rm -f '$postimage' &&
+               git apply -v \"\$vector\"/'git-$desc.diff' &&
+               test_cmp expected '$postimage'
+       "
+
+       test_expect_$exp2 $prereq "$desc, traditional patch" "
+               echo postimage >expected &&
+               reset_preimage &&
+               echo preimage >'$postimage' &&
+               git apply -v \"\$vector\"/'diff-$desc.diff' &&
+               test_cmp expected '$postimage'
+       "
+
+       test_expect_$exp3 $prereq "$desc, traditional file creation patch" "
+               echo postimage >expected &&
+               reset_preimage &&
+               rm -f '$postimage' &&
+               git apply -v \"\$vector\"/'add-$desc.diff' &&
+               test_cmp expected '$postimage'
+       "
+}
+
+try_filename 'plain'            'postimage.txt'
+try_filename 'with spaces'      'post image.txt'
+try_filename 'with tab'         'post  image.txt' FUNNYNAMES
+try_filename 'with backslash'   'post\image.txt' BSLASHPSPEC
+try_filename 'with quote'       '"postimage".txt' FUNNYNAMES success failure success
+
+test_expect_success 'whitespace-damaged traditional patch' '
+       echo postimage >expected &&
+       reset_preimage &&
+       rm -f postimage.txt &&
+       git apply -v "$vector/damaged.diff" &&
+       test_cmp expected postimage.txt
+'
+
+test_done
diff --git a/t/t4135/.gitignore b/t/t4135/.gitignore
new file mode 100644 (file)
index 0000000..3e58e65
--- /dev/null
@@ -0,0 +1,3 @@
+/file-creation/
+/trad-creation/
+/trad-modification/
diff --git a/t/t4135/add-plain.diff b/t/t4135/add-plain.diff
new file mode 100644 (file)
index 0000000..cf5970a
--- /dev/null
@@ -0,0 +1,5 @@
+diff -pruN a/postimage.txt b/postimage.txt
+--- a/postimage.txt    1969-12-31 18:00:00.000000000 -0600
++++ b/postimage.txt    2010-08-18 20:13:31.484002255 -0500
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/add-with backslash.diff b/t/t4135/add-with backslash.diff
new file mode 100644 (file)
index 0000000..c6861e1
--- /dev/null
@@ -0,0 +1,5 @@
+diff -pruN a/post\image.txt b/post\image.txt
+--- a/post\image.txt   1969-12-31 18:00:00.000000000 -0600
++++ b/post\image.txt   2010-08-18 20:13:31.692002255 -0500
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/add-with quote.diff b/t/t4135/add-with quote.diff
new file mode 100644 (file)
index 0000000..866de78
--- /dev/null
@@ -0,0 +1,5 @@
+diff -pruN a/"postimage".txt b/"postimage".txt
+--- a/"postimage".txt  1969-12-31 18:00:00.000000000 -0600
++++ b/"postimage".txt  2010-08-18 20:13:31.756002255 -0500
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/add-with spaces.diff b/t/t4135/add-with spaces.diff
new file mode 100644 (file)
index 0000000..a9a1212
--- /dev/null
@@ -0,0 +1,5 @@
+diff -pruN a/post image.txt b/post image.txt
+--- a/post image.txt   1969-12-31 18:00:00.000000000 -0600
++++ b/post image.txt   2010-08-18 20:13:31.556002255 -0500
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/add-with tab.diff b/t/t4135/add-with tab.diff
new file mode 100644 (file)
index 0000000..bb67cb7
--- /dev/null
@@ -0,0 +1,5 @@
+diff -pruN a/post      image.txt b/post        image.txt
+--- a/post     image.txt       1969-12-31 18:00:00.000000000 -0600
++++ b/post     image.txt       2010-08-18 20:13:31.628002255 -0500
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/damaged.diff b/t/t4135/damaged.diff
new file mode 100644 (file)
index 0000000..68f7ede
--- /dev/null
@@ -0,0 +1,5 @@
+diff -pruN a/postimage.txt b/postimage.txt
+--- a/postimage.txt     1969-12-31 18:00:00.000000000 -0600
++++ b/postimage.txt     2010-08-18 20:13:31.484002255 -0500
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/diff-plain.diff b/t/t4135/diff-plain.diff
new file mode 100644 (file)
index 0000000..acedcfa
--- /dev/null
@@ -0,0 +1,5 @@
+--- postimage.txt.orig 2010-08-18 20:13:31.432002255 -0500
++++ postimage.txt      2010-08-18 20:13:31.432002255 -0500
+@@ -1 +1 @@
+-preimage
++postimage
diff --git a/t/t4135/diff-with backslash.diff b/t/t4135/diff-with backslash.diff
new file mode 100644 (file)
index 0000000..9068a61
--- /dev/null
@@ -0,0 +1,5 @@
+--- post\image.txt.orig        2010-08-18 20:13:31.680002255 -0500
++++ post\image.txt     2010-08-18 20:13:31.680002255 -0500
+@@ -1 +1 @@
+-preimage
++postimage
diff --git a/t/t4135/diff-with quote.diff b/t/t4135/diff-with quote.diff
new file mode 100644 (file)
index 0000000..c8e8cc1
--- /dev/null
@@ -0,0 +1,5 @@
+--- "postimage".txt.orig       2010-08-18 20:13:31.744002255 -0500
++++ "postimage".txt    2010-08-18 20:13:31.744002255 -0500
+@@ -1 +1 @@
+-preimage
++postimage
diff --git a/t/t4135/diff-with spaces.diff b/t/t4135/diff-with spaces.diff
new file mode 100644 (file)
index 0000000..3512056
--- /dev/null
@@ -0,0 +1,5 @@
+--- post image.txt.orig        2010-08-18 20:13:31.544002255 -0500
++++ post image.txt     2010-08-18 20:13:31.544002255 -0500
+@@ -1 +1 @@
+-preimage
++postimage
diff --git a/t/t4135/diff-with tab.diff b/t/t4135/diff-with tab.diff
new file mode 100644 (file)
index 0000000..4e6d9b2
--- /dev/null
@@ -0,0 +1,5 @@
+--- post       image.txt.orig  2010-08-18 20:13:31.616002255 -0500
++++ post       image.txt       2010-08-18 20:13:31.616002255 -0500
+@@ -1 +1 @@
+-preimage
++postimage
diff --git a/t/t4135/git-plain.diff b/t/t4135/git-plain.diff
new file mode 100644 (file)
index 0000000..db47d1a
--- /dev/null
@@ -0,0 +1,7 @@
+diff --git a/postimage.txt b/postimage.txt
+new file mode 100644
+index 0000000..eff0c54
+--- /dev/null
++++ b/postimage.txt
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/git-with backslash.diff b/t/t4135/git-with backslash.diff
new file mode 100644 (file)
index 0000000..0e84a10
--- /dev/null
@@ -0,0 +1,7 @@
+diff --git "a/post\\image.txt" "b/post\\image.txt"
+new file mode 100644
+index 0000000..eff0c54
+--- /dev/null
++++ "b/post\\image.txt"
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/git-with quote.diff b/t/t4135/git-with quote.diff
new file mode 100644 (file)
index 0000000..bdbea8a
--- /dev/null
@@ -0,0 +1,7 @@
+diff --git "a/\"postimage\".txt" "b/\"postimage\".txt"
+new file mode 100644
+index 0000000..eff0c54
+--- /dev/null
++++ "b/\"postimage\".txt"
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/git-with spaces.diff b/t/t4135/git-with spaces.diff
new file mode 100644 (file)
index 0000000..baaa810
--- /dev/null
@@ -0,0 +1,7 @@
+diff --git a/post image.txt b/post image.txt
+new file mode 100644
+index 0000000..eff0c54
+--- /dev/null
++++ b/post image.txt   
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/git-with tab.diff b/t/t4135/git-with tab.diff
new file mode 100644 (file)
index 0000000..cca3c92
--- /dev/null
@@ -0,0 +1,7 @@
+diff --git "a/post\timage.txt" "b/post\timage.txt"
+new file mode 100644
+index 0000000..eff0c54
+--- /dev/null
++++ "b/post\timage.txt"
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/make-patches b/t/t4135/make-patches
new file mode 100755 (executable)
index 0000000..f5f45dd
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+do_filename() {
+       desc=$1
+       postimage=$2
+
+       rm -fr file-creation &&
+       git init file-creation &&
+       (
+               cd file-creation &&
+               git commit --allow-empty -m init &&
+               echo postimage >"$postimage" &&
+               git add -N "$postimage" &&
+               git diff HEAD >"../git-$desc.diff"
+       ) &&
+
+       rm -fr trad-modification &&
+       mkdir trad-modification &&
+       (
+               cd trad-modification &&
+               echo preimage >"$postimage.orig" &&
+               echo postimage >"$postimage" &&
+               ! diff -u "$postimage.orig" "$postimage" >"../diff-$desc.diff"
+       ) &&
+
+       rm -fr trad-creation &&
+       mkdir trad-creation &&
+       (
+               cd trad-creation &&
+               mkdir a b &&
+               echo postimage >"b/$postimage" &&
+               ! diff -pruN a b >"../add-$desc.diff"
+       )
+}
+
+do_filename plain postimage.txt &&
+do_filename 'with spaces' 'post image.txt' &&
+do_filename 'with tab' 'post   image.txt' &&
+do_filename 'with backslash' 'post\image.txt' &&
+do_filename 'with quote' '"postimage".txt' &&
+expand add-plain.diff >damaged.diff ||
+{
+       echo >&2 Failed. &&
+       exit 1
+}
index d6ebbaebe2c258a4694cfb709809e13d24084231..1c3d8ed548e629689517661cd1fc6c21d98ccc80 100755 (executable)
@@ -4,66 +4,71 @@ test_description='git am running'
 
 . ./test-lib.sh
 
-cat >msg <<EOF
-second
-
-Lorem ipsum dolor sit amet, consectetuer sadipscing elitr, sed diam nonumy
-eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
-voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita
-kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem
-ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
-tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
-vero eos et accusam et justo duo dolores et ea rebum.
-
-       Duis autem vel eum iriure dolor in hendrerit in vulputate velit
-       esse molestie consequat, vel illum dolore eu feugiat nulla facilisis
-       at vero eros et accumsan et iusto odio dignissim qui blandit
-       praesent luptatum zzril delenit augue duis dolore te feugait nulla
-       facilisi.
-
-
-Lorem ipsum dolor sit amet,
-consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut
-laoreet dolore magna aliquam erat volutpat.
-
-  git
-  ---
-  +++
-
-Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit
-lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure
-dolor in hendrerit in vulputate velit esse molestie consequat, vel illum
-dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio
-dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te
-feugait nulla facilisi.
-EOF
-
-cat >failmail <<EOF
-From foo@example.com Fri May 23 10:43:49 2008
-From:  foo@example.com
-To:    bar@example.com
-Subject: Re: [RFC/PATCH] git-foo.sh
-Date:  Fri, 23 May 2008 05:23:42 +0200
-
-Sometimes we have to find out that there's nothing left.
-
-EOF
-
-cat >pine <<EOF
-From MAILER-DAEMON Fri May 23 10:43:49 2008
-Date: 23 May 2008 05:23:42 +0200
-From: Mail System Internal Data <MAILER-DAEMON@example.com>
-Subject: DON'T DELETE THIS MESSAGE -- FOLDER INTERNAL DATA
-Message-ID: <foo-0001@example.com>
-
-This text is part of the internal format of your mail folder, and is not
-a real message.  It is created automatically by the mail system software.
-If deleted, important folder data will be lost, and it will be re-created
-with the data reset to initial values.
-
-EOF
-
-echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >expected
+test_expect_success 'setup: messages' '
+       cat >msg <<-\EOF &&
+       second
+
+       Lorem ipsum dolor sit amet, consectetuer sadipscing elitr, sed diam nonumy
+       eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
+       voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita
+       kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem
+       ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
+       tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
+       vero eos et accusam et justo duo dolores et ea rebum.
+
+       EOF
+       q_to_tab <<-\EOF >>msg &&
+       QDuis autem vel eum iriure dolor in hendrerit in vulputate velit
+       Qesse molestie consequat, vel illum dolore eu feugiat nulla facilisis
+       Qat vero eros et accumsan et iusto odio dignissim qui blandit
+       Qpraesent luptatum zzril delenit augue duis dolore te feugait nulla
+       Qfacilisi.
+       EOF
+       cat >>msg <<-\EOF &&
+
+       Lorem ipsum dolor sit amet,
+       consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut
+       laoreet dolore magna aliquam erat volutpat.
+
+         git
+         ---
+         +++
+
+       Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit
+       lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure
+       dolor in hendrerit in vulputate velit esse molestie consequat, vel illum
+       dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio
+       dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te
+       feugait nulla facilisi.
+       EOF
+
+       cat >failmail <<-\EOF &&
+       From foo@example.com Fri May 23 10:43:49 2008
+       From:   foo@example.com
+       To:     bar@example.com
+       Subject: Re: [RFC/PATCH] git-foo.sh
+       Date:   Fri, 23 May 2008 05:23:42 +0200
+
+       Sometimes we have to find out that there'\''s nothing left.
+
+       EOF
+
+       cat >pine <<-\EOF &&
+       From MAILER-DAEMON Fri May 23 10:43:49 2008
+       Date: 23 May 2008 05:23:42 +0200
+       From: Mail System Internal Data <MAILER-DAEMON@example.com>
+       Subject: DON'\''T DELETE THIS MESSAGE -- FOLDER INTERNAL DATA
+       Message-ID: <foo-0001@example.com>
+
+       This text is part of the internal format of your mail folder, and is not
+       a real message.  It is created automatically by the mail system software.
+       If deleted, important folder data will be lost, and it will be re-created
+       with the data reset to initial values.
+
+       EOF
+
+       signoff="Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
+'
 
 test_expect_success setup '
        echo hello >file &&
@@ -71,62 +76,121 @@ test_expect_success setup '
        test_tick &&
        git commit -m first &&
        git tag first &&
+
        echo world >>file &&
        git add file &&
        test_tick &&
        git commit -s -F msg &&
        git tag second &&
+
        git format-patch --stdout first >patch1 &&
+       {
+               echo "X-Fake-Field: Line One" &&
+               echo "X-Fake-Field: Line Two" &&
+               echo "X-Fake-Field: Line Three" &&
+               git format-patch --stdout first | sed -e "1d"
+       } > patch1.eml &&
+       {
+               echo "X-Fake-Field: Line One" &&
+               echo "X-Fake-Field: Line Two" &&
+               echo "X-Fake-Field: Line Three" &&
+               git format-patch --stdout first | sed -e "1d"
+       } | append_cr >patch1-crlf.eml &&
+
        sed -n -e "3,\$p" msg >file &&
        git add file &&
        test_tick &&
        git commit -m third &&
+
        git format-patch --stdout first >patch2 &&
+
        git checkout -b lorem &&
        sed -n -e "11,\$p" msg >file &&
        head -n 9 msg >>file &&
        test_tick &&
        git commit -a -m "moved stuff" &&
+
        echo goodbye >another &&
        git add another &&
        test_tick &&
        git commit -m "added another file" &&
-       git format-patch --stdout master >lorem-move.patch
-'
 
-# reset time
-unset test_tick
-test_tick
+       git format-patch --stdout master >lorem-move.patch &&
+
+       git checkout -b rename &&
+       git mv file renamed &&
+       git commit -m "renamed a file" &&
+
+       git format-patch -M --stdout lorem >rename.patch &&
+
+       git reset --soft lorem^ &&
+       git commit -m "renamed a file and added another" &&
+
+       git format-patch -M --stdout lorem^ >rename-add.patch &&
+
+       # reset time
+       unset test_tick &&
+       test_tick
+'
 
 test_expect_success 'am applies patch correctly' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
        git checkout first &&
        test_tick &&
        git am <patch1 &&
        ! test -d .git/rebase-apply &&
-       test -z "$(git diff second)" &&
+       git diff --exit-code second &&
        test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
        test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
 '
 
-GIT_AUTHOR_NAME="Another Thor"
-GIT_AUTHOR_EMAIL="a.thor@example.com"
-GIT_COMMITTER_NAME="Co M Miter"
-GIT_COMMITTER_EMAIL="c.miter@example.com"
-export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL
+test_expect_success 'am applies patch e-mail not in a mbox' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout first &&
+       git am patch1.eml &&
+       ! test -d .git/rebase-apply &&
+       git diff --exit-code second &&
+       test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
+       test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
+'
+
+test_expect_success 'am applies patch e-mail not in a mbox with CRLF' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout first &&
+       git am patch1-crlf.eml &&
+       ! test -d .git/rebase-apply &&
+       git diff --exit-code second &&
+       test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
+       test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
+'
+
+test_expect_success 'setup: new author and committer' '
+       GIT_AUTHOR_NAME="Another Thor" &&
+       GIT_AUTHOR_EMAIL="a.thor@example.com" &&
+       GIT_COMMITTER_NAME="Co M Miter" &&
+       GIT_COMMITTER_EMAIL="c.miter@example.com" &&
+       export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL
+'
 
 compare () {
-       test "$(git cat-file commit "$2" | grep "^$1 ")" = \
-            "$(git cat-file commit "$3" | grep "^$1 ")"
+       a=$(git cat-file commit "$2" | grep "^$1 ") &&
+       b=$(git cat-file commit "$3" | grep "^$1 ") &&
+       test "$a" = "$b"
 }
 
 test_expect_success 'am changes committer and keeps author' '
        test_tick &&
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
        git checkout first &&
        git am patch2 &&
        ! test -d .git/rebase-apply &&
        test "$(git rev-parse master^^)" = "$(git rev-parse HEAD^^)" &&
-       test -z "$(git diff master..HEAD)" &&
-       test -z "$(git diff master^..HEAD^)" &&
+       git diff --exit-code master..HEAD &&
+       git diff --exit-code master^..HEAD^ &&
        compare author master HEAD &&
        compare author master^ HEAD^ &&
        test "$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" = \
@@ -134,41 +198,55 @@ test_expect_success 'am changes committer and keeps author' '
 '
 
 test_expect_success 'am --signoff adds Signed-off-by: line' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
        git checkout -b master2 first &&
        git am --signoff <patch2 &&
+       printf "%s\n" "$signoff" >expected &&
        echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >>expected &&
        git cat-file commit HEAD^ | grep "Signed-off-by:" >actual &&
-       test_cmp actual expected &&
+       test_cmp expected actual &&
        echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >expected &&
        git cat-file commit HEAD | grep "Signed-off-by:" >actual &&
-       test_cmp actual expected
+       test_cmp expected actual
 '
 
 test_expect_success 'am stays in branch' '
-       test "refs/heads/master2" = "$(git symbolic-ref HEAD)"
+       echo refs/heads/master2 >expected &&
+       git symbolic-ref HEAD >actual &&
+       test_cmp expected actual
 '
 
 test_expect_success 'am --signoff does not add Signed-off-by: line if already there' '
        git format-patch --stdout HEAD^ >patch3 &&
        sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2," patch3 >patch4
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
        git checkout HEAD^ &&
        git am --signoff patch4 &&
-       test "$(git cat-file commit HEAD | grep -c "^Signed-off-by:")" -eq 1
+       git cat-file commit HEAD >actual &&
+       test $(grep -c "^Signed-off-by:" actual) -eq 1
 '
 
 test_expect_success 'am without --keep removes Re: and [PATCH] stuff' '
-       test "$(git rev-parse HEAD)" = "$(git rev-parse master2)"
+       git rev-parse HEAD >expected &&
+       git rev-parse master2 >actual &&
+       test_cmp expected actual
 '
 
 test_expect_success 'am --keep really keeps the subject' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
        git checkout HEAD^ &&
        git am --keep patch4 &&
        ! test -d .git/rebase-apply &&
-       git cat-file commit HEAD |
-               fgrep "Re: Re: Re: [PATCH 1/5 v2] third"
+       git cat-file commit HEAD >actual &&
+       grep "Re: Re: Re: \[PATCH 1/5 v2\] third" actual
 '
 
 test_expect_success 'am -3 falls back to 3-way merge' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
        git checkout -b lorem2 master2 &&
        sed -n -e "3,\$p" msg >file &&
        head -n 9 msg >>file &&
@@ -177,23 +255,75 @@ test_expect_success 'am -3 falls back to 3-way merge' '
        git commit -m "copied stuff" &&
        git am -3 lorem-move.patch &&
        ! test -d .git/rebase-apply &&
-       test -z "$(git diff lorem)"
+       git diff --exit-code lorem
+'
+
+test_expect_success 'am can rename a file' '
+       grep "^rename from" rename.patch &&
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout lorem^0 &&
+       git am rename.patch &&
+       ! test -d .git/rebase-apply &&
+       git update-index --refresh &&
+       git diff --exit-code rename
+'
+
+test_expect_success 'am -3 can rename a file' '
+       grep "^rename from" rename.patch &&
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout lorem^0 &&
+       git am -3 rename.patch &&
+       ! test -d .git/rebase-apply &&
+       git update-index --refresh &&
+       git diff --exit-code rename
+'
+
+test_expect_success 'am -3 can rename a file after falling back to 3-way merge' '
+       grep "^rename from" rename-add.patch &&
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout lorem^0 &&
+       git am -3 rename-add.patch &&
+       ! test -d .git/rebase-apply &&
+       git update-index --refresh &&
+       git diff --exit-code rename
+'
+
+test_expect_success 'am -3 -q is quiet' '
+       rm -fr .git/rebase-apply &&
+       git checkout -f lorem2 &&
+       git reset master2 --hard &&
+       sed -n -e "3,\$p" msg >file &&
+       head -n 9 msg >>file &&
+       git add file &&
+       test_tick &&
+       git commit -m "copied stuff" &&
+       git am -3 -q lorem-move.patch >output.out 2>&1 &&
+       ! test -s output.out
 '
 
 test_expect_success 'am pauses on conflict' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
        git checkout lorem2^^ &&
        test_must_fail git am lorem-move.patch &&
        test -d .git/rebase-apply
 '
 
 test_expect_success 'am --skip works' '
+       echo goodbye >expected &&
        git am --skip &&
        ! test -d .git/rebase-apply &&
-       test -z "$(git diff lorem2^^ -- file)" &&
-       test goodbye = "$(cat another)"
+       git diff --exit-code lorem2^^ -- file &&
+       test_cmp expected another
 '
 
 test_expect_success 'am --resolved works' '
+       echo goodbye >expected &&
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
        git checkout lorem2^^ &&
        test_must_fail git am lorem-move.patch &&
        test -d .git/rebase-apply &&
@@ -201,22 +331,29 @@ test_expect_success 'am --resolved works' '
        git add file &&
        git am --resolved &&
        ! test -d .git/rebase-apply &&
-       test goodbye = "$(cat another)"
+       test_cmp expected another
 '
 
 test_expect_success 'am takes patches from a Pine mailbox' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
        git checkout first &&
        cat pine patch1 | git am &&
        ! test -d .git/rebase-apply &&
-       test -z "$(git diff master^..HEAD)"
+       git diff --exit-code master^..HEAD
 '
 
 test_expect_success 'am fails on mail without patch' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
        test_must_fail git am <failmail &&
-       rm -r .git/rebase-apply/
+       git am --abort &&
+       ! test -d .git/rebase-apply
 '
 
 test_expect_success 'am fails on empty patch' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
        echo "---" >>failmail &&
        test_must_fail git am <failmail &&
        git am --skip &&
@@ -225,28 +362,34 @@ test_expect_success 'am fails on empty patch' '
 
 test_expect_success 'am works from stdin in subdirectory' '
        rm -fr subdir &&
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
        git checkout first &&
        (
                mkdir -p subdir &&
                cd subdir &&
                git am <../patch1
        ) &&
-       test -z "$(git diff second)"
+       git diff --exit-code second
 '
 
 test_expect_success 'am works from file (relative path given) in subdirectory' '
        rm -fr subdir &&
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
        git checkout first &&
        (
                mkdir -p subdir &&
                cd subdir &&
                git am ../patch1
        ) &&
-       test -z "$(git diff second)"
+       git diff --exit-code second
 '
 
 test_expect_success 'am works from file (absolute path given) in subdirectory' '
        rm -fr subdir &&
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
        git checkout first &&
        P=$(pwd) &&
        (
@@ -254,27 +397,31 @@ test_expect_success 'am works from file (absolute path given) in subdirectory' '
                cd subdir &&
                git am "$P/patch1"
        ) &&
-       test -z "$(git diff second)"
+       git diff --exit-code second
 '
 
 test_expect_success 'am --committer-date-is-author-date' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
        git checkout first &&
        test_tick &&
        git am --committer-date-is-author-date patch1 &&
-       git cat-file commit HEAD | sed -e "/^$/q" >head1 &&
-       at=$(sed -ne "/^author /s/.*> //p" head1) &&
-       ct=$(sed -ne "/^committer /s/.*> //p" head1) &&
-       test "$at" = "$ct"
+       git cat-file commit HEAD | sed -e "/^\$/q" >head1 &&
+       sed -ne "/^author /s/.*> //p" head1 >at &&
+       sed -ne "/^committer /s/.*> //p" head1 >ct &&
+       test_cmp at ct
 '
 
 test_expect_success 'am without --committer-date-is-author-date' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
        git checkout first &&
        test_tick &&
        git am patch1 &&
-       git cat-file commit HEAD | sed -e "/^$/q" >head1 &&
-       at=$(sed -ne "/^author /s/.*> //p" head1) &&
-       ct=$(sed -ne "/^committer /s/.*> //p" head1) &&
-       test "$at" != "$ct"
+       git cat-file commit HEAD | sed -e "/^\$/q" >head1 &&
+       sed -ne "/^author /s/.*> //p" head1 >at &&
+       sed -ne "/^committer /s/.*> //p" head1 >ct &&
+       ! test_cmp at ct
 '
 
 # This checks for +0000 because TZ is set to UTC and that should
@@ -282,27 +429,52 @@ test_expect_success 'am without --committer-date-is-author-date' '
 # by test_tick that uses -0700 timezone; if this feature does not
 # work, we will see that instead of +0000.
 test_expect_success 'am --ignore-date' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
        git checkout first &&
        test_tick &&
        git am --ignore-date patch1 &&
-       git cat-file commit HEAD | sed -e "/^$/q" >head1 &&
-       at=$(sed -ne "/^author /s/.*> //p" head1) &&
-       echo "$at" | grep "+0000"
+       git cat-file commit HEAD | sed -e "/^\$/q" >head1 &&
+       sed -ne "/^author /s/.*> //p" head1 >at &&
+       grep "+0000" at
 '
 
 test_expect_success 'am into an unborn branch' '
+       git rev-parse first^{tree} >expected &&
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
        rm -fr subdir &&
-       mkdir -p subdir &&
+       mkdir subdir &&
        git format-patch --numbered-files -o subdir -1 first &&
        (
                cd subdir &&
                git init &&
                git am 1
        ) &&
-       result=$(
-               cd subdir && git rev-parse HEAD^{tree}
+       (
+               cd subdir &&
+               git rev-parse HEAD^{tree} >../actual
        ) &&
-       test "z$result" = "z$(git rev-parse first^{tree})"
+       test_cmp expected actual
+'
+
+test_expect_success 'am newline in subject' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout first &&
+       test_tick &&
+       sed -e "s/second/second \\\n foo/" patch1 >patchnl &&
+       git am <patchnl >output.out 2>&1 &&
+       grep "^Applying: second \\\n foo$" output.out
+'
+
+test_expect_success 'am -q is quiet' '
+       rm -fr .git/rebase-apply &&
+       git reset --hard &&
+       git checkout first &&
+       test_tick &&
+       git am -q <patch1 >output.out 2>&1 &&
+       ! test -s output.out
 '
 
 test_done
index 2b912d77283fbd3fc02a7d9d6bca660e4193b8c3..b55c4117884744db8eda17e42fe05e0e65216215 100755 (executable)
@@ -47,7 +47,7 @@ do
                test_must_fail git am$with3 --skip >output &&
                test "$(grep "^Applying" output)" = "Applying: 6" &&
                test_cmp file-2-expect file-2 &&
-               test ! -f .git/rr-cache/MERGE_RR
+               test ! -f .git/MERGE_RR
        '
 
        test_expect_success "am --abort goes back after failed am$with3" '
@@ -57,7 +57,7 @@ do
                test_cmp expect actual &&
                test_cmp file-2-expect file-2 &&
                git diff-index --exit-code --cached HEAD &&
-               test ! -f .git/rr-cache/MERGE_RR
+               test ! -f .git/MERGE_RR
        '
 
 done
index a6bc028a57115729d38e4b228cd259880d0bf6f8..36255d608a7af7d85f479986e302138401f25a8d 100755 (executable)
 #
 
 test_description='git rerere
+
+! [fifth] version1
+ ! [first] first
+  ! [fourth] version1
+   ! [master] initial
+    ! [second] prefer first over second
+     ! [third] version2
+------
+     + [third] version2
++      [fifth] version1
+  +    [fourth] version1
++ +  + [third^] third
+    -  [second] prefer first over second
+ +  +  [first] first
+    +  [second^] second
+++++++ [master] initial
 '
 
 . ./test-lib.sh
 
-cat > a1 << EOF
-Some title
-==========
-Whether 'tis nobler in the mind to suffer
-The slings and arrows of outrageous fortune,
-Or to take arms against a sea of troubles,
-And by opposing end them? To die: to sleep;
-No more; and by a sleep to say we end
-The heart-ache and the thousand natural shocks
-That flesh is heir to, 'tis a consummation
-Devoutly to be wish'd.
-EOF
-
-git add a1
-git commit -q -a -m initial
-
-git checkout -b first
-cat >> a1 << EOF
-Some title
-==========
-To die, to sleep;
-To sleep: perchance to dream: ay, there's the rub;
-For in that sleep of death what dreams may come
-When we have shuffled off this mortal coil,
-Must give us pause: there's the respect
-That makes calamity of so long life;
-EOF
-git commit -q -a -m first
-
-git checkout -b second master
-git show first:a1 |
-sed -e 's/To die, t/To die! T/' -e 's/Some title/Some Title/' > a1
-echo "* END *" >>a1
-git commit -q -a -m second
+test_expect_success 'setup' '
+       cat >a1 <<-\EOF &&
+       Some title
+       ==========
+       Whether '\''tis nobler in the mind to suffer
+       The slings and arrows of outrageous fortune,
+       Or to take arms against a sea of troubles,
+       And by opposing end them? To die: to sleep;
+       No more; and by a sleep to say we end
+       The heart-ache and the thousand natural shocks
+       That flesh is heir to, '\''tis a consummation
+       Devoutly to be wish'\''d.
+       EOF
+
+       git add a1 &&
+       test_tick &&
+       git commit -q -a -m initial &&
+
+       cat >>a1 <<-\EOF &&
+       Some title
+       ==========
+       To die, to sleep;
+       To sleep: perchance to dream: ay, there'\''s the rub;
+       For in that sleep of death what dreams may come
+       When we have shuffled off this mortal coil,
+       Must give us pause: there'\''s the respect
+       That makes calamity of so long life;
+       EOF
+
+       git checkout -b first &&
+       test_tick &&
+       git commit -q -a -m first &&
+
+       git checkout -b second master &&
+       git show first:a1 |
+       sed -e "s/To die, t/To die! T/" -e "s/Some title/Some Title/" >a1 &&
+       echo "* END *" >>a1 &&
+       test_tick &&
+       git commit -q -a -m second
+'
 
 test_expect_success 'nothing recorded without rerere' '
-       (rm -rf .git/rr-cache; git config rerere.enabled false) &&
+       rm -rf .git/rr-cache &&
+       git config rerere.enabled false &&
        test_must_fail git merge first &&
        ! test -d .git/rr-cache
 '
 
-# activate rerere, old style
-test_expect_success 'conflicting merge' '
+test_expect_success 'activate rerere, old style (conflicting merge)' '
        git reset --hard &&
        mkdir .git/rr-cache &&
-       git config --unset rerere.enabled &&
-       test_must_fail git merge first
-'
+       test_might_fail git config --unset rerere.enabled &&
+       test_must_fail git merge first &&
 
-sha1=$(perl -pe 's/    .*//' .git/MERGE_RR)
-rr=.git/rr-cache/$sha1
-test_expect_success 'recorded preimage' "grep ^=======$ $rr/preimage"
+       sha1=$(perl -pe "s/     .*//" .git/MERGE_RR) &&
+       rr=.git/rr-cache/$sha1 &&
+       grep "^=======\$" $rr/preimage &&
+       ! test -f $rr/postimage &&
+       ! test -f $rr/thisimage
+'
 
 test_expect_success 'rerere.enabled works, too' '
        rm -rf .git/rr-cache &&
        git config rerere.enabled true &&
        git reset --hard &&
        test_must_fail git merge first &&
+
+       sha1=$(perl -pe "s/     .*//" .git/MERGE_RR) &&
+       rr=.git/rr-cache/$sha1 &&
        grep ^=======$ $rr/preimage
 '
 
-test_expect_success 'no postimage or thisimage yet' \
-       "test ! -f $rr/postimage -a ! -f $rr/thisimage"
+test_expect_success 'set up rr-cache' '
+       rm -rf .git/rr-cache &&
+       git config rerere.enabled true &&
+       git reset --hard &&
+       test_must_fail git merge first &&
+       sha1=$(perl -pe "s/     .*//" .git/MERGE_RR) &&
+       rr=.git/rr-cache/$sha1
+'
 
-test_expect_success 'preimage has right number of lines' '
+test_expect_success 'rr-cache looks sane' '
+       # no postimage or thisimage yet
+       ! test -f $rr/postimage &&
+       ! test -f $rr/thisimage &&
 
+       # preimage has right number of lines
        cnt=$(sed -ne "/^<<<<<<</,/^>>>>>>>/p" $rr/preimage | wc -l) &&
+       echo $cnt &&
        test $cnt = 13
-
 '
 
-git show first:a1 > a1
-
-cat > expect << EOF
---- a/a1
-+++ b/a1
-@@ -1,4 +1,4 @@
--Some Title
-+Some title
- ==========
- Whether 'tis nobler in the mind to suffer
- The slings and arrows of outrageous fortune,
-@@ -8,21 +8,11 @@
- The heart-ache and the thousand natural shocks
- That flesh is heir to, 'tis a consummation
- Devoutly to be wish'd.
--<<<<<<<
--Some Title
--==========
--To die! To sleep;
--=======
- Some title
- ==========
- To die, to sleep;
-->>>>>>>
- To sleep: perchance to dream: ay, there's the rub;
- For in that sleep of death what dreams may come
- When we have shuffled off this mortal coil,
- Must give us pause: there's the respect
- That makes calamity of so long life;
--<<<<<<<
--=======
--* END *
-->>>>>>>
-EOF
-git rerere diff > out
-
-test_expect_success 'rerere diff' 'test_cmp expect out'
-
-cat > expect << EOF
-a1
-EOF
-
-git rerere status > out
-
-test_expect_success 'rerere status' 'test_cmp expect out'
-
-test_expect_success 'commit succeeds' \
-       "git commit -q -a -m 'prefer first over second'"
-
-test_expect_success 'recorded postimage' "test -f $rr/postimage"
-
-test_expect_success 'another conflicting merge' '
-       git checkout -b third master &&
-       git show second^:a1 | sed "s/To die: t/To die! T/" > a1 &&
-       git commit -q -a -m third &&
-       test_must_fail git pull . first
+test_expect_success 'rerere diff' '
+       git show first:a1 >a1 &&
+       cat >expect <<-\EOF &&
+       --- a/a1
+       +++ b/a1
+       @@ -1,4 +1,4 @@
+       -Some Title
+       +Some title
+        ==========
+        Whether '\''tis nobler in the mind to suffer
+        The slings and arrows of outrageous fortune,
+       @@ -8,21 +8,11 @@
+        The heart-ache and the thousand natural shocks
+        That flesh is heir to, '\''tis a consummation
+        Devoutly to be wish'\''d.
+       -<<<<<<<
+       -Some Title
+       -==========
+       -To die! To sleep;
+       -=======
+        Some title
+        ==========
+        To die, to sleep;
+       ->>>>>>>
+        To sleep: perchance to dream: ay, there'\''s the rub;
+        For in that sleep of death what dreams may come
+        When we have shuffled off this mortal coil,
+        Must give us pause: there'\''s the respect
+        That makes calamity of so long life;
+       -<<<<<<<
+       -=======
+       -* END *
+       ->>>>>>>
+       EOF
+       git rerere diff >out &&
+       test_cmp expect out
 '
 
-git show first:a1 | sed 's/To die: t/To die! T/' > expect
-test_expect_success 'rerere kicked in' "! grep ^=======$ a1"
+test_expect_success 'rerere status' '
+       echo a1 >expect &&
+       git rerere status >out &&
+       test_cmp expect out
+'
 
-test_expect_success 'rerere prefers first change' 'test_cmp a1 expect'
+test_expect_success 'first postimage wins' '
+       git show first:a1 | sed "s/To die: t/To die! T/" >expect &&
 
-rm $rr/postimage
-echo "$sha1    a1" | perl -pe 'y/\012/\000/' > .git/MERGE_RR
+       git commit -q -a -m "prefer first over second" &&
+       test -f $rr/postimage &&
 
-test_expect_success 'rerere clear' 'git rerere clear'
+       oldmtimepost=$(test-chmtime -v -60 $rr/postimage | cut -f 1) &&
 
-test_expect_success 'clear removed the directory' "test ! -d $rr"
+       git checkout -b third master &&
+       git show second^:a1 | sed "s/To die: t/To die! T/" >a1 &&
+       git commit -q -a -m third &&
 
-mkdir $rr
-echo Hello > $rr/preimage
-echo World > $rr/postimage
+       test_must_fail git pull . first &&
+       # rerere kicked in
+       ! grep "^=======\$" a1 &&
+       test_cmp expect a1
+'
 
-sha2=4000000000000000000000000000000000000000
-rr2=.git/rr-cache/$sha2
-mkdir $rr2
-echo Hello > $rr2/preimage
+test_expect_success 'rerere updates postimage timestamp' '
+       newmtimepost=$(test-chmtime -v +0 $rr/postimage | cut -f 1) &&
+       test $oldmtimepost -lt $newmtimepost
+'
 
-almost_15_days_ago=$((60-15*86400))
-just_over_15_days_ago=$((-1-15*86400))
-almost_60_days_ago=$((60-60*86400))
-just_over_60_days_ago=$((-1-60*86400))
+test_expect_success 'rerere clear' '
+       rm $rr/postimage &&
+       echo "$sha1     a1" | perl -pe "y/\012/\000/" >.git/MERGE_RR &&
+       git rerere clear &&
+       ! test -d $rr
+'
 
-test-chmtime =$almost_60_days_ago $rr/preimage
-test-chmtime =$almost_15_days_ago $rr2/preimage
+test_expect_success 'set up for garbage collection tests' '
+       mkdir -p $rr &&
+       echo Hello >$rr/preimage &&
+       echo World >$rr/postimage &&
 
-test_expect_success 'garbage collection (part1)' 'git rerere gc'
+       sha2=4000000000000000000000000000000000000000 &&
+       rr2=.git/rr-cache/$sha2 &&
+       mkdir $rr2 &&
+       echo Hello >$rr2/preimage &&
 
-test_expect_success 'young records still live' \
-       "test -f $rr/preimage && test -f $rr2/preimage"
+       almost_15_days_ago=$((60-15*86400)) &&
+       just_over_15_days_ago=$((-1-15*86400)) &&
+       almost_60_days_ago=$((60-60*86400)) &&
+       just_over_60_days_ago=$((-1-60*86400)) &&
 
-test-chmtime =$just_over_60_days_ago $rr/preimage
-test-chmtime =$just_over_15_days_ago $rr2/preimage
+       test-chmtime =$just_over_60_days_ago $rr/preimage &&
+       test-chmtime =$almost_60_days_ago $rr/postimage &&
+       test-chmtime =$almost_15_days_ago $rr2/preimage
+'
 
-test_expect_success 'garbage collection (part2)' 'git rerere gc'
+test_expect_success 'gc preserves young or recently used records' '
+       git rerere gc &&
+       test -f $rr/preimage &&
+       test -f $rr2/preimage
+'
 
-test_expect_success 'old records rest in peace' \
-       "test ! -f $rr/preimage && test ! -f $rr2/preimage"
+test_expect_success 'old records rest in peace' '
+       test-chmtime =$just_over_60_days_ago $rr/postimage &&
+       test-chmtime =$just_over_15_days_ago $rr2/preimage &&
+       git rerere gc &&
+       ! test -f $rr/preimage &&
+       ! test -f $rr2/preimage
+'
 
-test_expect_success 'file2 added differently in two branches' '
+test_expect_success 'setup: file2 added differently in two branches' '
        git reset --hard &&
+
        git checkout -b fourth &&
-       echo Hallo > file2 &&
+       echo Hallo >file2 &&
        git add file2 &&
+       test_tick &&
        git commit -m version1 &&
+
        git checkout third &&
-       echo Bello > file2 &&
+       echo Bello >file2 &&
        git add file2 &&
+       test_tick &&
        git commit -m version2 &&
+
        test_must_fail git merge fourth &&
-       echo Cello > file2 &&
+       echo Cello >file2 &&
        git add file2 &&
        git commit -m resolution
 '
 
 test_expect_success 'resolution was recorded properly' '
+       echo Cello >expected &&
+
        git reset --hard HEAD~2 &&
        git checkout -b fifth &&
-       echo Hallo > file3 &&
+
+       echo Hallo >file3 &&
        git add file3 &&
+       test_tick &&
        git commit -m version1 &&
+
        git checkout third &&
-       echo Bello > file3 &&
+       echo Bello >file3 &&
        git add file3 &&
+       test_tick &&
        git commit -m version2 &&
        git tag version2 &&
+
        test_must_fail git merge fifth &&
-       test Cello = "$(cat file3)" &&
-       test 0 != $(git ls-files -u | wc -l)
+       test_cmp expected file3 &&
+       test_must_fail git update-index --refresh
 '
 
 test_expect_success 'rerere.autoupdate' '
-       git config rerere.autoupdate true
+       git config rerere.autoupdate true &&
        git reset --hard &&
        git checkout version2 &&
        test_must_fail git merge fifth &&
-       test 0 = $(git ls-files -u | wc -l)
+       git update-index --refresh
+'
+
+test_expect_success 'merge --rerere-autoupdate' '
+       test_might_fail git config --unset rerere.autoupdate &&
+       git reset --hard &&
+       git checkout version2 &&
+       test_must_fail git merge --rerere-autoupdate fifth &&
+       git update-index --refresh
+'
+
+test_expect_success 'merge --no-rerere-autoupdate' '
+       headblob=$(git rev-parse version2:file3) &&
+       mergeblob=$(git rev-parse fifth:file3) &&
+       cat >expected <<-EOF &&
+       100644 $headblob 2      file3
+       100644 $mergeblob 3     file3
+       EOF
+
+       git config rerere.autoupdate true &&
+       git reset --hard &&
+       git checkout version2 &&
+       test_must_fail git merge --no-rerere-autoupdate fifth &&
+       git ls-files -u >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'set up an unresolved merge' '
+       headblob=$(git rev-parse version2:file3) &&
+       mergeblob=$(git rev-parse fifth:file3) &&
+       cat >expected.unresolved <<-EOF &&
+       100644 $headblob 2      file3
+       100644 $mergeblob 3     file3
+       EOF
+
+       test_might_fail git config --unset rerere.autoupdate &&
+       git reset --hard &&
+       git checkout version2 &&
+       fifth=$(git rev-parse fifth) &&
+       echo "$fifth            branch 'fifth' of ." |
+       git fmt-merge-msg >msg &&
+       ancestor=$(git merge-base version2 fifth) &&
+       test_must_fail git merge-recursive "$ancestor" -- HEAD fifth &&
+
+       git ls-files --stage >failedmerge &&
+       cp file3 file3.conflict &&
+
+       git ls-files -u >actual &&
+       test_cmp expected.unresolved actual
+'
+
+test_expect_success 'explicit rerere' '
+       test_might_fail git config --unset rerere.autoupdate &&
+       git rm -fr --cached . &&
+       git update-index --index-info <failedmerge &&
+       cp file3.conflict file3 &&
+       test_must_fail git update-index --refresh -q &&
+
+       git rerere &&
+       git ls-files -u >actual &&
+       test_cmp expected.unresolved actual
+'
+
+test_expect_success 'explicit rerere with autoupdate' '
+       git config rerere.autoupdate true &&
+       git rm -fr --cached . &&
+       git update-index --index-info <failedmerge &&
+       cp file3.conflict file3 &&
+       test_must_fail git update-index --refresh -q &&
+
+       git rerere &&
+       git update-index --refresh
+'
+
+test_expect_success 'explicit rerere --rerere-autoupdate overrides' '
+       git config rerere.autoupdate false &&
+       git rm -fr --cached . &&
+       git update-index --index-info <failedmerge &&
+       cp file3.conflict file3 &&
+       git rerere &&
+       git ls-files -u >actual1 &&
+
+       git rm -fr --cached . &&
+       git update-index --index-info <failedmerge &&
+       cp file3.conflict file3 &&
+       git rerere --rerere-autoupdate &&
+       git update-index --refresh &&
+
+       git rm -fr --cached . &&
+       git update-index --index-info <failedmerge &&
+       cp file3.conflict file3 &&
+       git rerere --rerere-autoupdate --no-rerere-autoupdate &&
+       git ls-files -u >actual2 &&
+
+       git rm -fr --cached . &&
+       git update-index --index-info <failedmerge &&
+       cp file3.conflict file3 &&
+       git rerere --rerere-autoupdate --no-rerere-autoupdate --rerere-autoupdate &&
+       git update-index --refresh &&
+
+       test_cmp expected.unresolved actual1 &&
+       test_cmp expected.unresolved actual2
+'
+
+test_expect_success 'rerere --no-no-rerere-autoupdate' '
+       git rm -fr --cached . &&
+       git update-index --index-info <failedmerge &&
+       cp file3.conflict file3 &&
+       test_must_fail git rerere --no-no-rerere-autoupdate 2>err &&
+       grep [Uu]sage err &&
+       test_must_fail git update-index --refresh
+'
 
+test_expect_success 'rerere -h' '
+       test_must_fail git rerere -h >help &&
+       grep [Uu]sage help
 '
 
 test_done
index 405b97119175a1c0fa75a9db30c6b1ab076cc44e..cdb70b4b3356eeb45bb6e5ac62d1f82eb6b3ccdc 100755 (executable)
@@ -8,30 +8,93 @@ test_description='git shortlog
 
 . ./test-lib.sh
 
-echo 1 > a1
-git add a1
-tree=$(git write-tree)
-commit=$( (echo "Test"; echo) | git commit-tree $tree )
-git update-ref HEAD $commit
+test_expect_success 'setup' '
+       echo 1 >a1 &&
+       git add a1 &&
+       tree=$(git write-tree) &&
+       commit=$(printf "%s\n" "Test" "" | git commit-tree "$tree") &&
+       git update-ref HEAD "$commit" &&
 
-echo 2 > a1
-git commit --quiet -m "This is a very, very long first line for the commit message to see if it is wrapped correctly" a1
+       echo 2 >a1 &&
+       git commit --quiet -m "This is a very, very long first line for the commit message to see if it is wrapped correctly" a1 &&
 
-# test if the wrapping is still valid when replacing all i's by treble clefs.
-echo 3 > a1
-git commit --quiet -m "$(echo "This is a very, very long first line for the commit message to see if it is wrapped correctly" | sed "s/i/1234/g" | tr 1234 '\360\235\204\236')" a1
+       # test if the wrapping is still valid
+       # when replacing all is by treble clefs.
+       echo 3 >a1 &&
+       git commit --quiet -m "$(
+               echo "This is a very, very long first line for the commit message to see if it is wrapped correctly" |
+               sed "s/i/1234/g" |
+               tr 1234 "\360\235\204\236")" a1 &&
 
-# now fsck up the utf8
-git config i18n.commitencoding non-utf-8
-echo 4 > a1
-git commit --quiet -m "$(echo "This is a very, very long first line for the commit message to see if it is wrapped correctly" | sed "s/i/1234/g" | tr 1234 '\370\235\204\236')" a1
+       # now fsck up the utf8
+       git config i18n.commitencoding non-utf-8 &&
+       echo 4 >a1 &&
+       git commit --quiet -m "$(
+               echo "This is a very, very long first line for the commit message to see if it is wrapped correctly" |
+               sed "s/i/1234/g" |
+               tr 1234 "\370\235\204\236")" a1 &&
 
-echo 5 > a1
-git commit --quiet -m "a                                                               12      34      56      78" a1
+       echo 5 >a1 &&
+       git commit --quiet -m "a                                                                12      34      56      78" a1
 
-git shortlog -w HEAD > out
+       echo 6 >a1 &&
+       git commit --quiet -m "Commit by someone else" \
+               --author="Someone else <not!me>" a1 &&
 
-cat > expect << EOF
+       cat >expect.template <<-\EOF
+       A U Thor (5):
+             SUBJECT
+             SUBJECT
+             SUBJECT
+             SUBJECT
+             SUBJECT
+
+       Someone else (1):
+             SUBJECT
+
+       EOF
+'
+
+fuzz() {
+       file=$1 &&
+       sed "
+                       s/$_x40/OBJECT_NAME/g
+                       s/$_x05/OBJID/g
+                       s/^ \{6\}[CTa].*/      SUBJECT/g
+                       s/^ \{8\}[^ ].*/        CONTINUATION/g
+               " <"$file" >"$file.fuzzy" &&
+       sed "/CONTINUATION/ d" <"$file.fuzzy"
+}
+
+test_expect_success 'default output format' '
+       git shortlog HEAD >log &&
+       fuzz log >log.predictable &&
+       test_cmp expect.template log.predictable
+'
+
+test_expect_success 'pretty format' '
+       sed s/SUBJECT/OBJECT_NAME/ expect.template >expect &&
+       git shortlog --format="%H" HEAD >log &&
+       fuzz log >log.predictable &&
+       test_cmp expect log.predictable
+'
+
+test_expect_success '--abbrev' '
+       sed s/SUBJECT/OBJID/ expect.template >expect &&
+       git shortlog --format="%h" --abbrev=5 HEAD >log &&
+       fuzz log >log.predictable &&
+       test_cmp expect log.predictable
+'
+
+test_expect_success 'output from user-defined format is re-wrapped' '
+       sed "s/SUBJECT/two lines/" expect.template >expect &&
+       git shortlog --format="two%nlines" HEAD >log &&
+       fuzz log >log.predictable &&
+       test_cmp expect log.predictable
+'
+
+test_expect_success 'shortlog wrapping' '
+       cat >expect <<\EOF &&
 A U Thor (5):
       Test
       This is a very, very long first line for the commit message to see if
@@ -43,13 +106,46 @@ A U Thor (5):
       a                                                                12      34
          56    78
 
+Someone else (1):
+      Commit by someone else
+
 EOF
+       git shortlog -w HEAD >out &&
+       test_cmp expect out
+'
 
-test_expect_success 'shortlog wrapping' 'test_cmp expect out'
+test_expect_success 'shortlog from non-git directory' '
+       git log HEAD >log &&
+       GIT_DIR=non-existing git shortlog -w <log >out &&
+       test_cmp expect out
+'
+
+iconvfromutf8toiso88591() {
+       printf "%s" "$*" | iconv -f UTF-8 -t ISO8859-1
+}
 
-git log HEAD > log
-GIT_DIR=non-existing git shortlog -w < log > out
+DSCHO="Jöhännës \"Dschö\" Schindëlin"
+DSCHOE="$DSCHO <Johannes.Schindelin@gmx.de>"
+MSG1="set a1 to 2 and some non-ASCII chars: Äßø"
+MSG2="set a1 to 3 and some non-ASCII chars: áæï"
+cat > expect << EOF
+$DSCHO (2):
+      $MSG1
+      $MSG2
+
+EOF
 
-test_expect_success 'shortlog from non-git directory' 'test_cmp expect out'
+test_expect_success 'shortlog encoding' '
+       git reset --hard "$commit" &&
+       git config --unset i18n.commitencoding &&
+       echo 2 > a1 &&
+       git commit --quiet -m "$MSG1" --author="$DSCHOE" a1 &&
+       git config i18n.commitencoding "ISO8859-1" &&
+       echo 3 > a1 &&
+       git commit --quiet -m "$(iconvfromutf8toiso88591 "$MSG2")" \
+               --author="$(iconvfromutf8toiso88591 "$DSCHOE")" a1 &&
+       git config --unset i18n.commitencoding &&
+       git shortlog HEAD~2.. > out &&
+test_cmp expect out'
 
 test_done
index 64502e2be762a82b65a72af6847d5a3b79303d45..2e513569477bb5efc389a2088a4d896620e50b8f 100755 (executable)
@@ -63,6 +63,27 @@ test_expect_success 'format' '
        test_cmp expect actual
 '
 
+cat > expect << EOF
+ This is
+  the sixth
+  commit.
+ This is
+  the fifth
+  commit.
+EOF
+
+test_expect_success 'format %w(12,1,2)' '
+
+       git log -2 --format="%w(12,1,2)This is the %s commit." > actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'format %w(,1,2)' '
+
+       git log -2 --format="%w(,1,2)This is%nthe %s%ncommit." > actual &&
+       test_cmp expect actual
+'
+
 cat > expect << EOF
 804a787 sixth
 394ef78 fifth
@@ -79,13 +100,11 @@ test_expect_success 'oneline' '
 
 test_expect_success 'diff-filter=A' '
 
-       actual=$(git log --pretty="format:%s" --diff-filter=A HEAD) &&
-       expect=$(echo fifth ; echo fourth ; echo third ; echo initial) &&
-       test "$actual" = "$expect" || {
-               echo Oops
-               echo "Actual: $actual"
-               false
-       }
+       git log --pretty="format:%s" --diff-filter=A HEAD > actual &&
+       git log --pretty="format:%s" --diff-filter A HEAD > actual-separate &&
+       printf "fifth\nfourth\nthird\ninitial" > expect &&
+       test_cmp expect actual &&
+       test_cmp expect actual-separate
 
 '
 
@@ -149,6 +168,26 @@ test_expect_success 'git log --follow' '
 
 '
 
+cat > expect << EOF
+804a787 sixth
+394ef78 fifth
+5d31159 fourth
+EOF
+test_expect_success 'git log --no-walk <commits> sorts by commit time' '
+       git log --no-walk --oneline 5d31159 804a787 394ef78 > actual &&
+       test_cmp expect actual
+'
+
+cat > expect << EOF
+5d31159 fourth
+804a787 sixth
+394ef78 fifth
+EOF
+test_expect_success 'git show <commits> leaves list of commits as given' '
+       git show --oneline -s 5d31159 804a787 394ef78 > actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'setup case sensitivity tests' '
        echo case >one &&
        test_tick &&
@@ -162,6 +201,13 @@ test_expect_success 'log --grep' '
        test_cmp expect actual
 '
 
+test_expect_success 'log --grep option parsing' '
+       echo second >expect &&
+       git log -1 --pretty="tformat:%s" --grep sec >actual &&
+       test_cmp expect actual &&
+       test_must_fail git log -1 --pretty="tformat:%s" --grep
+'
+
 test_expect_success 'log -i --grep' '
        echo Second >expect &&
        git log -1 --pretty="tformat:%s" -i --grep=sec >actual &&
@@ -214,7 +260,7 @@ EOF
 
 test_expect_success 'log --graph with merge' '
        git log --graph --date-order --pretty=tformat:%s |
-               sed "s/ *$//" >actual &&
+               sed "s/ *\$//" >actual &&
        test_cmp expect actual
 '
 
@@ -274,7 +320,7 @@ EOF
 test_expect_success 'log --graph with full output' '
        git log --graph --date-order --pretty=short |
                git name-rev --name-only --stdin |
-               sed "s/Merge:.*/Merge: A B/;s/ *$//" >actual &&
+               sed "s/Merge:.*/Merge: A B/;s/ *\$//" >actual &&
        test_cmp expect actual
 '
 
@@ -300,11 +346,11 @@ test_expect_success 'set up more tangled history' '
 '
 
 cat > expect <<\EOF
-*   Merge branch 'reach'
+*   Merge commit 'reach'
 |\
 | \
 |  \
-*-. \   Merge branches 'octopus-a' and 'octopus-b'
+*-. \   Merge commit 'octopus-a'; commit 'octopus-b'
 |\ \ \
 * | | | seventh
 | | * | octopus-b
@@ -324,14 +370,12 @@ cat > expect <<\EOF
 * | | |   Merge branch 'side'
 |\ \ \ \
 | * | | | side-2
-| | | |/
-| | |/|
+| | |_|/
 | |/| |
 | * | | side-1
 * | | | Second
 * | | | sixth
-| | |/
-| |/|
+| |_|/
 |/| |
 * | | fifth
 * | | fourth
@@ -344,9 +388,70 @@ EOF
 
 test_expect_success 'log --graph with merge' '
        git log --graph --date-order --pretty=tformat:%s |
-               sed "s/ *$//" >actual &&
+               sed "s/ *\$//" >actual &&
        test_cmp expect actual
 '
 
-test_done
+test_expect_success 'log.decorate configuration' '
+       git config --unset-all log.decorate || :
+
+       git log --oneline >expect.none &&
+       git log --oneline --decorate >expect.short &&
+       git log --oneline --decorate=full >expect.full &&
+
+       echo "[log] decorate" >>.git/config &&
+       git log --oneline >actual &&
+       test_cmp expect.short actual &&
+
+       git config --unset-all log.decorate &&
+       git config log.decorate true &&
+       git log --oneline >actual &&
+       test_cmp expect.short actual &&
+       git log --oneline --decorate=full >actual &&
+       test_cmp expect.full actual &&
+       git log --oneline --decorate=no >actual &&
+       test_cmp expect.none actual &&
+
+       git config --unset-all log.decorate &&
+       git config log.decorate no &&
+       git log --oneline >actual &&
+       test_cmp expect.none actual &&
+       git log --oneline --decorate >actual &&
+       test_cmp expect.short actual &&
+       git log --oneline --decorate=full >actual &&
+       test_cmp expect.full actual &&
+
+       git config --unset-all log.decorate &&
+       git config log.decorate short &&
+       git log --oneline >actual &&
+       test_cmp expect.short actual &&
+       git log --oneline --no-decorate >actual &&
+       test_cmp expect.none actual &&
+       git log --oneline --decorate=full >actual &&
+       test_cmp expect.full actual &&
+
+       git config --unset-all log.decorate &&
+       git config log.decorate full &&
+       git log --oneline >actual &&
+       test_cmp expect.full actual &&
+       git log --oneline --no-decorate >actual &&
+       test_cmp expect.none actual &&
+       git log --oneline --decorate >actual &&
+       test_cmp expect.short actual
 
+'
+
+test_expect_success 'show added path under "--follow -M"' '
+       # This tests for a regression introduced in v1.7.2-rc0~103^2~2
+       test_create_repo regression &&
+       (
+               cd regression &&
+               test_commit needs-another-commit &&
+               test_commit foo.bar &&
+               git log -M --follow -p foo.bar.t &&
+               git log -M --follow --stat foo.bar.t &&
+               git log -M --follow --name-only foo.bar.t
+       )
+'
+
+test_done
index 04f7bae8503f7605f1403f55d0bf4d9cd146913d..68e2652814c6a52265407b0fdfb70162eb634d53 100755 (executable)
@@ -18,6 +18,11 @@ test_expect_success 'patch-id output is well-formed' '
        grep "^[a-f0-9]\{40\} $(git rev-parse HEAD)$" output
 '
 
+calc_patch_id () {
+       git patch-id |
+               sed "s# .*##" > patch-id_"$1"
+}
+
 get_patch_id () {
        git log -p -1 "$1" | git patch-id |
                sed "s# .*##" > patch-id_"$1"
@@ -35,4 +40,27 @@ test_expect_success 'patch-id detects inequality' '
        ! test_cmp patch-id_master patch-id_notsame
 '
 
+test_expect_success 'patch-id supports git-format-patch output' '
+       get_patch_id master &&
+       git checkout same &&
+       git format-patch -1 --stdout | calc_patch_id same &&
+       test_cmp patch-id_master patch-id_same &&
+       set `git format-patch -1 --stdout | git patch-id` &&
+       test "$2" = `git rev-parse HEAD`
+'
+
+test_expect_success 'whitespace is irrelevant in footer' '
+       get_patch_id master &&
+       git checkout same &&
+       git format-patch -1 --stdout | sed "s/ \$//" | calc_patch_id same &&
+       test_cmp patch-id_master patch-id_same
+'
+
+test_expect_success 'patch-id supports git-format-patch MIME output' '
+       get_patch_id master &&
+       git checkout same &&
+       git format-patch -1 --attach --stdout | calc_patch_id same &&
+       test_cmp patch-id_master patch-id_same
+'
+
 test_done
diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh
new file mode 100755 (executable)
index 0000000..cb9f2bd
--- /dev/null
@@ -0,0 +1,74 @@
+#!/bin/sh
+#
+# Copyright (c) 2010, Will Palmer
+#
+
+test_description='Test pretty formats'
+. ./test-lib.sh
+
+test_expect_success 'set up basic repos' '
+       >foo &&
+       >bar &&
+       git add foo &&
+       test_tick &&
+       git commit -m initial &&
+       git add bar &&
+       test_tick &&
+       git commit -m "add bar"
+'
+
+test_expect_success 'alias builtin format' '
+       git log --pretty=oneline >expected &&
+       git config pretty.test-alias oneline &&
+       git log --pretty=test-alias >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'alias masking builtin format' '
+       git log --pretty=oneline >expected &&
+       git config pretty.oneline "%H" &&
+       git log --pretty=oneline >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'alias user-defined format' '
+       git log --pretty="format:%h" >expected &&
+       git config pretty.test-alias "format:%h" &&
+       git log --pretty=test-alias >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'alias user-defined tformat' '
+       git log --pretty="tformat:%h" >expected &&
+       git config pretty.test-alias "tformat:%h" &&
+       git log --pretty=test-alias >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'alias non-existant format' '
+       git config pretty.test-alias format-that-will-never-exist &&
+       test_must_fail git log --pretty=test-alias
+'
+
+test_expect_success 'alias of an alias' '
+       git log --pretty="tformat:%h" >expected &&
+       git config pretty.test-foo "tformat:%h" &&
+       git config pretty.test-bar test-foo &&
+       git log --pretty=test-bar >actual && test_cmp expected actual
+'
+
+test_expect_success 'alias masking an alias' '
+       git log --pretty=format:"Two %H" >expected &&
+       git config pretty.duplicate "format:One %H" &&
+       git config --add pretty.duplicate "format:Two %H" &&
+       git log --pretty=duplicate >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'alias loop' '
+       git config pretty.test-foo test-bar &&
+       git config pretty.test-bar test-foo &&
+       test_must_fail git log --pretty=test-foo
+'
+
+test_done
diff --git a/t/t4206-log-follow-harder-copies.sh b/t/t4206-log-follow-harder-copies.sh
new file mode 100755 (executable)
index 0000000..ad29e65
--- /dev/null
@@ -0,0 +1,56 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Bo Yang
+#
+
+test_description='Test --follow should always find copies hard in git log.
+
+'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/diff-lib.sh
+
+echo >path0 'Line 1
+Line 2
+Line 3
+'
+
+test_expect_success \
+    'add a file path0 and commit.' \
+    'git add path0 &&
+     git commit -m "Add path0"'
+
+echo >path0 'New line 1
+New line 2
+New line 3
+'
+test_expect_success \
+    'Change path0.' \
+    'git add path0 &&
+     git commit -m "Change path0"'
+
+cat <path0 >path1
+test_expect_success \
+    'copy path0 to path1.' \
+    'git add path1 &&
+     git commit -m "Copy path1 from path0"'
+
+test_expect_success \
+    'find the copy path0 -> path1 harder' \
+    'git log --follow --name-status --pretty="format:%s"  path1 > current'
+
+cat >expected <<\EOF
+Copy path1 from path0
+C100   path0   path1
+
+Change path0
+M      path0
+
+Add path0
+A      path0
+EOF
+
+test_expect_success \
+    'validate the output.' \
+    'compare_diff_patch current expected'
+
+test_done
diff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh
new file mode 100755 (executable)
index 0000000..bbde31b
--- /dev/null
@@ -0,0 +1,66 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Nazri Ramliy
+#
+
+test_description='Test for "git log --decorate" colors'
+
+. ./test-lib.sh
+
+get_color ()
+{
+       git config --get-color no.such.slot "$1"
+}
+
+test_expect_success setup '
+       git config diff.color.commit yellow &&
+       git config color.decorate.branch green &&
+       git config color.decorate.remoteBranch red &&
+       git config color.decorate.tag "reverse bold yellow" &&
+       git config color.decorate.stash magenta &&
+       git config color.decorate.HEAD cyan &&
+
+       c_reset=$(get_color reset) &&
+
+       c_commit=$(get_color yellow) &&
+       c_branch=$(get_color green) &&
+       c_remoteBranch=$(get_color red) &&
+       c_tag=$(get_color "reverse bold yellow") &&
+       c_stash=$(get_color magenta) &&
+       c_HEAD=$(get_color cyan) &&
+
+       test_commit A &&
+       git clone . other &&
+       (
+               cd other &&
+               test_commit A1
+       ) &&
+
+       git remote add -f other ./other &&
+       test_commit B &&
+       git tag v1.0 &&
+       echo >>A.t &&
+       git stash save Changes to A.t
+'
+
+cat >expected <<EOF
+${c_commit}COMMIT_ID (${c_HEAD}HEAD${c_reset}${c_commit},\
+ ${c_tag}tag: v1.0${c_reset}${c_commit},\
+ ${c_tag}tag: B${c_reset}${c_commit},\
+ ${c_branch}master${c_reset}${c_commit})${c_reset} B
+${c_commit}COMMIT_ID (${c_tag}tag: A1${c_reset}${c_commit},\
+ ${c_remoteBranch}other/master${c_reset}${c_commit})${c_reset} A1
+${c_commit}COMMIT_ID (${c_stash}refs/stash${c_reset}${c_commit})${c_reset}\
+ On master: Changes to A.t
+${c_commit}COMMIT_ID (${c_tag}tag: A${c_reset}${c_commit})${c_reset} A
+EOF
+
+# We want log to show all, but the second parent to refs/stash is irrelevant
+# to this test since it does not contain any decoration, hence --first-parent
+test_expect_success 'Commit Decorations Colored Correctly' '
+       git log --first-parent --abbrev=10 --all --decorate --oneline --color=always |
+       sed "s/[0-9a-f]\{10,10\}/COMMIT_ID/" >out &&
+       test_cmp expected out
+'
+
+test_done
diff --git a/t/t4253-am-keep-cr-dos.sh b/t/t4253-am-keep-cr-dos.sh
new file mode 100755 (executable)
index 0000000..735e55d
--- /dev/null
@@ -0,0 +1,96 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Stefan-W. Hahn
+#
+
+test_description='git-am mbox with dos line ending.
+
+'
+. ./test-lib.sh
+
+# Three patches which will be added as files with dos line ending.
+
+cat >file1 <<\EOF
+line 1
+EOF
+
+cat >file1a <<\EOF
+line 1
+line 4
+EOF
+
+cat >file2 <<\EOF
+line 1
+line 2
+EOF
+
+cat >file3 <<\EOF
+line 1
+line 2
+line 3
+EOF
+
+test_expect_success 'setup repository with dos files' '
+       append_cr <file1 >file &&
+       git add file &&
+       git commit -m Initial &&
+       git tag initial &&
+       append_cr <file2 >file &&
+       git commit -a -m Second &&
+       append_cr <file3 >file &&
+       git commit -a -m Third
+'
+
+test_expect_success 'am with dos files without --keep-cr' '
+       git checkout -b dosfiles initial &&
+       git format-patch -k initial..master &&
+       test_must_fail git am -k -3 000*.patch &&
+       git am --abort &&
+       rm -rf .git/rebase-apply 000*.patch
+'
+
+test_expect_success 'am with dos files with --keep-cr' '
+       git checkout -b dosfiles-keep-cr initial &&
+       git format-patch -k --stdout initial..master | git am --keep-cr -k -3 &&
+       git diff --exit-code master
+'
+
+test_expect_success 'am with dos files config am.keepcr' '
+       git config am.keepcr 1 &&
+       git checkout -b dosfiles-conf-keepcr initial &&
+       git format-patch -k --stdout initial..master | git am -k -3 &&
+       git diff --exit-code master
+'
+
+test_expect_success 'am with dos files config am.keepcr overriden by --no-keep-cr' '
+       git config am.keepcr 1 &&
+       git checkout -b dosfiles-conf-keepcr-override initial &&
+       git format-patch -k initial..master &&
+       test_must_fail git am -k -3 --no-keep-cr 000*.patch &&
+       git am --abort &&
+       rm -rf .git/rebase-apply 000*.patch
+'
+
+test_expect_success 'am with dos files with --keep-cr continue' '
+       git checkout -b dosfiles-keep-cr-continue initial &&
+       git format-patch -k initial..master &&
+       append_cr <file1a >file &&
+       git commit -m "different patch" file &&
+       test_must_fail git am --keep-cr -k -3 000*.patch &&
+       append_cr <file2 >file &&
+       git add file &&
+       git am -3 --resolved &&
+       git diff --exit-code master
+'
+
+test_expect_success 'am with unix files config am.keepcr overriden by --no-keep-cr' '
+       git config am.keepcr 1 &&
+       git checkout -b unixfiles-conf-keepcr-override initial &&
+       cp -f file1 file &&
+       git commit -m "line ending to unix" file &&
+       git format-patch -k initial..master &&
+       git am -k -3 --no-keep-cr 000*.patch &&
+       git diff --exit-code -w master
+'
+
+test_done
diff --git a/t/t4300-merge-tree.sh b/t/t4300-merge-tree.sh
new file mode 100755 (executable)
index 0000000..46c3fe7
--- /dev/null
@@ -0,0 +1,257 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Will Palmer
+#
+
+test_description='git merge-tree'
+. ./test-lib.sh
+
+test_expect_success setup '
+       test_commit "initial" "initial-file" "initial"
+'
+
+test_expect_success 'file add A, !B' '
+       cat >expected <<\EXPECTED &&
+added in remote
+  their  100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+@@ -0,0 +1 @@
++AAA
+EXPECTED
+
+       git reset --hard initial &&
+       test_commit "add-a-not-b" "ONE" "AAA" &&
+       git merge-tree initial initial add-a-not-b >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'file add !A, B' '
+       cat >expected <<\EXPECTED &&
+added in local
+  our    100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+EXPECTED
+
+       git reset --hard initial &&
+       test_commit "add-not-a-b" "ONE" "AAA" &&
+       git merge-tree initial add-not-a-b initial >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'file add A, B (same)' '
+       cat >expected <<\EXPECTED &&
+added in both
+  our    100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+  their  100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+EXPECTED
+
+       git reset --hard initial &&
+       test_commit "add-a-b-same-A" "ONE" "AAA" &&
+       git reset --hard initial &&
+       test_commit "add-a-b-same-B" "ONE" "AAA" &&
+       git merge-tree initial add-a-b-same-A add-a-b-same-B >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'file add A, B (different)' '
+       cat >expected <<\EXPECTED &&
+added in both
+  our    100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+  their  100644 ba629238ca89489f2b350e196ca445e09d8bb834 ONE
+@@ -1 +1,5 @@
++<<<<<<< .our
+ AAA
++=======
++BBB
++>>>>>>> .their
+EXPECTED
+
+       git reset --hard initial &&
+       test_commit "add-a-b-diff-A" "ONE" "AAA" &&
+       git reset --hard initial &&
+       test_commit "add-a-b-diff-B" "ONE" "BBB" &&
+       git merge-tree initial add-a-b-diff-A add-a-b-diff-B >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'file change A, !B' '
+       cat >expected <<\EXPECTED &&
+EXPECTED
+
+       git reset --hard initial &&
+       test_commit "change-a-not-b" "initial-file" "BBB" &&
+       git merge-tree initial change-a-not-b initial >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'file change !A, B' '
+       cat >expected <<\EXPECTED &&
+merged
+  result 100644 ba629238ca89489f2b350e196ca445e09d8bb834 initial-file
+  our    100644 e79c5e8f964493290a409888d5413a737e8e5dd5 initial-file
+@@ -1 +1 @@
+-initial
++BBB
+EXPECTED
+
+       git reset --hard initial &&
+       test_commit "change-not-a-b" "initial-file" "BBB" &&
+       git merge-tree initial initial change-not-a-b >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'file change A, B (same)' '
+       cat >expected <<\EXPECTED &&
+EXPECTED
+
+       git reset --hard initial &&
+       test_commit "change-a-b-same-A" "initial-file" "AAA" &&
+       git reset --hard initial &&
+       test_commit "change-a-b-same-B" "initial-file" "AAA" &&
+       git merge-tree initial change-a-b-same-A change-a-b-same-B >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'file change A, B (different)' '
+       cat >expected <<\EXPECTED &&
+changed in both
+  base   100644 e79c5e8f964493290a409888d5413a737e8e5dd5 initial-file
+  our    100644 43d5a8ed6ef6c00ff775008633f95787d088285d initial-file
+  their  100644 ba629238ca89489f2b350e196ca445e09d8bb834 initial-file
+@@ -1 +1,5 @@
++<<<<<<< .our
+ AAA
++=======
++BBB
++>>>>>>> .their
+EXPECTED
+
+       git reset --hard initial &&
+       test_commit "change-a-b-diff-A" "initial-file" "AAA" &&
+       git reset --hard initial &&
+       test_commit "change-a-b-diff-B" "initial-file" "BBB" &&
+       git merge-tree initial change-a-b-diff-A change-a-b-diff-B >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'file change A, B (mixed)' '
+       cat >expected <<\EXPECTED &&
+changed in both
+  base   100644 f4f1f998c7776568c4ff38f516d77fef9399b5a7 ONE
+  our    100644 af14c2c3475337c73759d561ef70b59e5c731176 ONE
+  their  100644 372d761493f524d44d59bd24700c3bdf914c973c ONE
+@@ -7,7 +7,11 @@
+ AAA
+ AAA
+ AAA
++<<<<<<< .our
+ BBB
++=======
++CCC
++>>>>>>> .their
+ AAA
+ AAA
+ AAA
+EXPECTED
+
+       git reset --hard initial &&
+       test_commit "change-a-b-mix-base" "ONE" "
+AAA
+AAA
+AAA
+AAA
+AAA
+AAA
+AAA
+AAA
+AAA
+AAA
+AAA
+AAA
+AAA
+AAA
+AAA" &&
+       test_commit "change-a-b-mix-A" "ONE" \
+               "$(sed -e "1{s/AAA/BBB/;}" -e "10{s/AAA/BBB/;}" <ONE)" &&
+       git reset --hard change-a-b-mix-base &&
+       test_commit "change-a-b-mix-B" "ONE" \
+               "$(sed -e "1{s/AAA/BBB/;}" -e "10{s/AAA/CCC/;}" <ONE)" &&
+       git merge-tree change-a-b-mix-base change-a-b-mix-A change-a-b-mix-B \
+               >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'file remove A, !B' '
+       cat >expected <<\EXPECTED &&
+removed in local
+  base   100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+  their  100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+EXPECTED
+
+       git reset --hard initial &&
+       test_commit "rm-a-not-b-base" "ONE" "AAA" &&
+       git rm ONE &&
+       git commit -m "rm-a-not-b" &&
+       git tag "rm-a-not-b" &&
+       git merge-tree rm-a-not-b-base rm-a-not-b rm-a-not-b-base >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'file remove !A, B' '
+       cat >expected <<\EXPECTED &&
+removed in remote
+  base   100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+  our    100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+@@ -1 +0,0 @@
+-AAA
+EXPECTED
+
+       git reset --hard initial &&
+       test_commit "rm-not-a-b-base" "ONE" "AAA" &&
+       git rm ONE &&
+       git commit -m "rm-not-a-b" &&
+       git tag "rm-not-a-b" &&
+       git merge-tree rm-a-not-b-base rm-a-not-b-base rm-a-not-b >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'file change A, remove B' '
+       cat >expected <<\EXPECTED &&
+removed in remote
+  base   100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+  our    100644 ba629238ca89489f2b350e196ca445e09d8bb834 ONE
+@@ -1 +0,0 @@
+-BBB
+EXPECTED
+
+       git reset --hard initial &&
+       test_commit "change-a-rm-b-base" "ONE" "AAA" &&
+       test_commit "change-a-rm-b-A" "ONE" "BBB" &&
+       git reset --hard change-a-rm-b-base &&
+       git rm ONE &&
+       git commit -m "change-a-rm-b-B" &&
+       git tag "change-a-rm-b-B" &&
+       git merge-tree change-a-rm-b-base change-a-rm-b-A change-a-rm-b-B \
+               >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'file remove A, change B' '
+       cat >expected <<\EXPECTED &&
+removed in local
+  base   100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+  their  100644 ba629238ca89489f2b350e196ca445e09d8bb834 ONE
+EXPECTED
+
+       git reset --hard initial &&
+       test_commit "rm-a-change-b-base" "ONE" "AAA" &&
+
+       git rm ONE &&
+       git commit -m "rm-a-change-b-A" &&
+       git tag "rm-a-change-b-A" &&
+       git reset --hard rm-a-change-b-base &&
+       test_commit "rm-a-change-b-B" "ONE" "BBB" &&
+       git merge-tree rm-a-change-b-base rm-a-change-b-A rm-a-change-b-B \
+               >actual &&
+       test_cmp expected actual
+'
+
+test_done
index abb41b07ef1985d53c2186617e6b2fcf7e7fe033..27bfba55bd9631a666ee382b343c3c04af1ad26d 100755 (executable)
@@ -94,6 +94,10 @@ test_expect_success 'git archive with --output' \
     'git archive --output=b4.tar HEAD &&
     test_cmp b.tar b4.tar'
 
+test_expect_success 'git archive --remote' \
+    'git archive --remote=. HEAD >b5.tar &&
+    test_cmp b.tar b5.tar'
+
 test_expect_success \
     'validate file modification time' \
     'mkdir extract &&
@@ -185,6 +189,16 @@ test_expect_success 'git archive --format=zip with --output' \
     'git archive --format=zip --output=d2.zip HEAD &&
     test_cmp d.zip d2.zip'
 
+test_expect_success 'git archive with --output, inferring format' '
+       git archive --output=d3.zip HEAD &&
+       test_cmp d.zip d3.zip
+'
+
+test_expect_success 'git archive with --output, override inferred format' '
+       git archive --format=tar --output=d4.zip HEAD &&
+       test_cmp b.tar d4.zip
+'
+
 $UNZIP -v >/dev/null 2>&1
 if [ $? -eq 127 ]; then
        say "Skipping ZIP tests, because unzip was not found"
@@ -226,4 +240,16 @@ test_expect_success \
     'git archive --list outside of a git repo' \
     'GIT_DIR=some/non-existing/directory git archive --list'
 
+test_expect_success 'git-archive --prefix=olde-' '
+       git archive --prefix=olde- >h.tar HEAD &&
+       (
+               mkdir h &&
+               cd h &&
+               "$TAR" xf - <../h.tar
+       ) &&
+       test -d h/olde-a &&
+       test -d h/olde-a/bin &&
+       test -f h/olde-a/bin/sh
+'
+
 test_done
index 426b319bd36257331f07d5aee1a20ed7e515408a..02d4d2284d8bcf9637dbd6a670e376b4ae9f7af1 100755 (executable)
@@ -4,7 +4,7 @@ test_description='git archive attribute tests'
 
 . ./test-lib.sh
 
-SUBSTFORMAT=%H%n
+SUBSTFORMAT='%H (%h)%n'
 
 test_expect_exists() {
        test_expect_success " $1 exists" "test -e $1"
index e70ea94a1368dc045469808d30c717aa2b8bb158..ebc36c1758372f484055b62080d3ce81ae7c69b4 100755 (executable)
@@ -11,18 +11,30 @@ test_expect_success 'split sample box' \
        'git mailsplit -o. "$TEST_DIRECTORY"/t5100/sample.mbox >last &&
        last=`cat last` &&
        echo total is $last &&
-       test `cat last` = 13'
+       test `cat last` = 16'
+
+check_mailinfo () {
+       mail=$1 opt=$2
+       mo="$mail$opt"
+       git mailinfo -u $opt msg$mo patch$mo <$mail >info$mo &&
+       test_cmp "$TEST_DIRECTORY"/t5100/msg$mo msg$mo &&
+       test_cmp "$TEST_DIRECTORY"/t5100/patch$mo patch$mo &&
+       test_cmp "$TEST_DIRECTORY"/t5100/info$mo info$mo
+}
+
 
 for mail in `echo 00*`
 do
        test_expect_success "mailinfo $mail" '
-               git mailinfo -u msg$mail patch$mail <$mail >info$mail &&
-               echo msg &&
-               test_cmp "$TEST_DIRECTORY"/t5100/msg$mail msg$mail &&
-               echo patch &&
-               test_cmp "$TEST_DIRECTORY"/t5100/patch$mail patch$mail &&
-               echo info &&
-               test_cmp "$TEST_DIRECTORY"/t5100/info$mail info$mail
+               check_mailinfo $mail "" &&
+               if test -f "$TEST_DIRECTORY"/t5100/msg$mail--scissors
+               then
+                       check_mailinfo $mail --scissors
+               fi &&
+               if test -f "$TEST_DIRECTORY"/t5100/msg$mail--no-inbody-headers
+               then
+                       check_mailinfo $mail --no-inbody-headers
+               fi
        '
 done
 
diff --git a/t/t5100/.gitattributes b/t/t5100/.gitattributes
new file mode 100644 (file)
index 0000000..c93f514
--- /dev/null
@@ -0,0 +1,4 @@
+msg*   encoding=UTF-8
+info*  encoding=UTF-8
+rfc2047-info-* encoding=UTF-8
+sample.mbox    encoding=UTF-8
diff --git a/t/t5100/0010 b/t/t5100/0010
deleted file mode 100644 (file)
index f5892c9..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-From b9704a518e21158433baa2cc2d591fea687967f6 Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Lukas=20Sandstr=C3=B6m?= <lukass@etek.chalmers.se>
-Date: Thu, 10 Jul 2008 23:41:33 +0200
-Subject: Re: discussion that lead to this patch
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-[PATCH] git-mailinfo: Fix getting the subject from the body
-
-"Subject: " isn't in the static array "header", and thus
-memcmp("Subject: ", header[i], 7) will never match.
-
-Signed-off-by: Lukas Sandström <lukass@etek.chalmers.se>
-Signed-off-by: Junio C Hamano <gitster@pobox.com>
----
- builtin-mailinfo.c |    2 +-
- 1 files changed, 1 insertions(+), 1 deletions(-)
-
-diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
-index 962aa34..2d1520f 100644
---- a/builtin-mailinfo.c
-+++ b/builtin-mailinfo.c
-@@ -334,7 +334,7 @@ static int check_header(char *line, unsigned linesize, char **hdr_data, int over
-               return 1;
-       if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) {
-               for (i = 0; header[i]; i++) {
--                      if (!memcmp("Subject: ", header[i], 9)) {
-+                      if (!memcmp("Subject", header[i], 7)) {
-                               if (! handle_header(line, hdr_data[i], 0)) {
-                                       return 1;
-                               }
--- 
-1.5.6.2.455.g1efb2
-
diff --git a/t/t5100/info0014 b/t/t5100/info0014
new file mode 100644 (file)
index 0000000..08566b3
--- /dev/null
@@ -0,0 +1,5 @@
+Author: Junio Hamano
+Email: junkio@cox.net
+Subject: BLAH ONE
+Date: Thu, 20 Aug 2009 17:18:22 -0700
+
diff --git a/t/t5100/info0014--scissors b/t/t5100/info0014--scissors
new file mode 100644 (file)
index 0000000..ab9c8d0
--- /dev/null
@@ -0,0 +1,5 @@
+Author: Junio C Hamano
+Email: gitster@pobox.com
+Subject: Teach mailinfo to ignore everything before -- >8 -- mark
+Date: Thu, 20 Aug 2009 17:18:22 -0700
+
diff --git a/t/t5100/info0015 b/t/t5100/info0015
new file mode 100644 (file)
index 0000000..0114f10
--- /dev/null
@@ -0,0 +1,5 @@
+Author: 
+Email: 
+Subject: check bogus body header (from)
+Date: Fri, 9 Jun 2006 00:44:16 -0700
+
diff --git a/t/t5100/info0015--no-inbody-headers b/t/t5100/info0015--no-inbody-headers
new file mode 100644 (file)
index 0000000..c4d8d77
--- /dev/null
@@ -0,0 +1,5 @@
+Author: A U Thor
+Email: a.u.thor@example.com
+Subject: check bogus body header (from)
+Date: Fri, 9 Jun 2006 00:44:16 -0700
+
diff --git a/t/t5100/info0016 b/t/t5100/info0016
new file mode 100644 (file)
index 0000000..38ccd0d
--- /dev/null
@@ -0,0 +1,5 @@
+Author: A U Thor
+Email: a.u.thor@example.com
+Subject: check bogus body header (date)
+Date: bogus 
+
diff --git a/t/t5100/info0016--no-inbody-headers b/t/t5100/info0016--no-inbody-headers
new file mode 100644 (file)
index 0000000..f4857d4
--- /dev/null
@@ -0,0 +1,5 @@
+Author: A U Thor
+Email: a.u.thor@example.com
+Subject: check bogus body header (date)
+Date: Fri, 9 Jun 2006 00:44:16 -0700
+
diff --git a/t/t5100/msg0014 b/t/t5100/msg0014
new file mode 100644 (file)
index 0000000..62e5cd2
--- /dev/null
@@ -0,0 +1,18 @@
+In real life, we will see a discussion that inspired this patch
+discussing related and unrelated things around >8 scissors mark
+in this part of the message.
+
+Subject: [PATCH] BLAH TWO
+
+And then we will see the scissors.
+
+ This line is not a scissors mark -- >8 -- but talks about it.
+ - - >8 - - please remove everything above this line - - >8 - -
+
+Subject: [PATCH] Teach mailinfo to ignore everything before -- >8 -- mark
+From: Junio C Hamano <gitster@pobox.com>
+
+This teaches mailinfo the scissors -- >8 -- mark; the command ignores
+everything before it in the message body.
+
+Signed-off-by: Junio C Hamano <gitster@pobox.com>
diff --git a/t/t5100/msg0014--scissors b/t/t5100/msg0014--scissors
new file mode 100644 (file)
index 0000000..259c6a4
--- /dev/null
@@ -0,0 +1,4 @@
+This teaches mailinfo the scissors -- >8 -- mark; the command ignores
+everything before it in the message body.
+
+Signed-off-by: Junio C Hamano <gitster@pobox.com>
diff --git a/t/t5100/msg0015 b/t/t5100/msg0015
new file mode 100644 (file)
index 0000000..4abb3d5
--- /dev/null
@@ -0,0 +1,2 @@
+  - a list
+  - of stuff
diff --git a/t/t5100/msg0015--no-inbody-headers b/t/t5100/msg0015--no-inbody-headers
new file mode 100644 (file)
index 0000000..be5115b
--- /dev/null
@@ -0,0 +1,3 @@
+From: bogosity
+  - a list
+  - of stuff
diff --git a/t/t5100/msg0016 b/t/t5100/msg0016
new file mode 100644 (file)
index 0000000..0d9adad
--- /dev/null
@@ -0,0 +1,2 @@
+and some content
+
diff --git a/t/t5100/msg0016--no-inbody-headers b/t/t5100/msg0016--no-inbody-headers
new file mode 100644 (file)
index 0000000..1063f51
--- /dev/null
@@ -0,0 +1,4 @@
+Date: bogus
+
+and some content
+
diff --git a/t/t5100/patch0014 b/t/t5100/patch0014
new file mode 100644 (file)
index 0000000..124efd2
--- /dev/null
@@ -0,0 +1,64 @@
+---
+ builtin-mailinfo.c |   37 ++++++++++++++++++++++++++++++++++++-
+ 1 files changed, 36 insertions(+), 1 deletions(-)
+
+diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
+index b0b5d8f..461c47e 100644
+--- a/builtin-mailinfo.c
++++ b/builtin-mailinfo.c
+@@ -712,6 +712,34 @@ static inline int patchbreak(const struct strbuf *line)
+       return 0;
+ }
++static int scissors(const struct strbuf *line)
++{
++      size_t i, len = line->len;
++      int scissors_dashes_seen = 0;
++      const char *buf = line->buf;
++
++      for (i = 0; i < len; i++) {
++              if (isspace(buf[i]))
++                      continue;
++              if (buf[i] == '-') {
++                      scissors_dashes_seen |= 02;
++                      continue;
++              }
++              if (i + 1 < len && !memcmp(buf + i, ">8", 2)) {
++                      scissors_dashes_seen |= 01;
++                      i++;
++                      continue;
++              }
++              if (i + 7 < len && !memcmp(buf + i, "cut here", 8)) {
++                      i += 7;
++                      continue;
++              }
++              /* everything else --- not scissors */
++              break;
++      }
++      return scissors_dashes_seen == 03;
++}
++
+ static int handle_commit_msg(struct strbuf *line)
+ {
+       static int still_looking = 1;
+@@ -723,10 +751,17 @@ static int handle_commit_msg(struct strbuf *line)
+               strbuf_ltrim(line);
+               if (!line->len)
+                       return 0;
+-              if ((still_looking = check_header(line, s_hdr_data, 0)) != 0)
++              still_looking = check_header(line, s_hdr_data, 0);
++              if (still_looking)
+                       return 0;
+       }
++      if (scissors(line)) {
++              fseek(cmitmsg, 0L, SEEK_SET);
++              still_looking = 1;
++              return 0;
++      }
++
+       /* normalize the log message to UTF-8. */
+       if (metainfo_charset)
+               convert_to_utf8(line, charset.buf);
+-- 
+1.6.4.1
diff --git a/t/t5100/patch0014--scissors b/t/t5100/patch0014--scissors
new file mode 100644 (file)
index 0000000..124efd2
--- /dev/null
@@ -0,0 +1,64 @@
+---
+ builtin-mailinfo.c |   37 ++++++++++++++++++++++++++++++++++++-
+ 1 files changed, 36 insertions(+), 1 deletions(-)
+
+diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
+index b0b5d8f..461c47e 100644
+--- a/builtin-mailinfo.c
++++ b/builtin-mailinfo.c
+@@ -712,6 +712,34 @@ static inline int patchbreak(const struct strbuf *line)
+       return 0;
+ }
++static int scissors(const struct strbuf *line)
++{
++      size_t i, len = line->len;
++      int scissors_dashes_seen = 0;
++      const char *buf = line->buf;
++
++      for (i = 0; i < len; i++) {
++              if (isspace(buf[i]))
++                      continue;
++              if (buf[i] == '-') {
++                      scissors_dashes_seen |= 02;
++                      continue;
++              }
++              if (i + 1 < len && !memcmp(buf + i, ">8", 2)) {
++                      scissors_dashes_seen |= 01;
++                      i++;
++                      continue;
++              }
++              if (i + 7 < len && !memcmp(buf + i, "cut here", 8)) {
++                      i += 7;
++                      continue;
++              }
++              /* everything else --- not scissors */
++              break;
++      }
++      return scissors_dashes_seen == 03;
++}
++
+ static int handle_commit_msg(struct strbuf *line)
+ {
+       static int still_looking = 1;
+@@ -723,10 +751,17 @@ static int handle_commit_msg(struct strbuf *line)
+               strbuf_ltrim(line);
+               if (!line->len)
+                       return 0;
+-              if ((still_looking = check_header(line, s_hdr_data, 0)) != 0)
++              still_looking = check_header(line, s_hdr_data, 0);
++              if (still_looking)
+                       return 0;
+       }
++      if (scissors(line)) {
++              fseek(cmitmsg, 0L, SEEK_SET);
++              still_looking = 1;
++              return 0;
++      }
++
+       /* normalize the log message to UTF-8. */
+       if (metainfo_charset)
+               convert_to_utf8(line, charset.buf);
+-- 
+1.6.4.1
diff --git a/t/t5100/patch0015 b/t/t5100/patch0015
new file mode 100644 (file)
index 0000000..ad64848
--- /dev/null
@@ -0,0 +1,8 @@
+---
+diff --git a/foo b/foo
+index e69de29..d95f3ad 100644
+--- a/foo
++++ b/foo
+@@ -0,0 +1 @@
++content
+
diff --git a/t/t5100/patch0015--no-inbody-headers b/t/t5100/patch0015--no-inbody-headers
new file mode 100644 (file)
index 0000000..ad64848
--- /dev/null
@@ -0,0 +1,8 @@
+---
+diff --git a/foo b/foo
+index e69de29..d95f3ad 100644
+--- a/foo
++++ b/foo
+@@ -0,0 +1 @@
++content
+
diff --git a/t/t5100/patch0016 b/t/t5100/patch0016
new file mode 100644 (file)
index 0000000..ad64848
--- /dev/null
@@ -0,0 +1,8 @@
+---
+diff --git a/foo b/foo
+index e69de29..d95f3ad 100644
+--- a/foo
++++ b/foo
+@@ -0,0 +1 @@
++content
+
diff --git a/t/t5100/patch0016--no-inbody-headers b/t/t5100/patch0016--no-inbody-headers
new file mode 100644 (file)
index 0000000..ad64848
--- /dev/null
@@ -0,0 +1,8 @@
+---
+diff --git a/foo b/foo
+index e69de29..d95f3ad 100644
+--- a/foo
++++ b/foo
+@@ -0,0 +1 @@
++content
+
index 3ca2470da2ef8e082bfbb210ee9acf651b6588d6..1fc224810da6f02fbb4407bd030ab5aee3f3ef50 100644 (file)
@@ -1,48 +1,48 @@
 From nobody Mon Sep 17 00:00:00 2001
 From: =?US-ASCII?Q?Keith_Moore?= <moore@cs.utk.edu>
-To: =?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= <keld@dkuug.dk>
-CC: =?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>
-Subject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=
- =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=
+To: =?ISO8859-1?Q?Keld_J=F8rn_Simonsen?= <keld@dkuug.dk>
+CC: =?ISO8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>
+Subject: =?ISO8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=
+ =?ISO8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=
 
 From nobody Mon Sep 17 00:00:00 2001
-From: =?ISO-8859-1?Q?Olle_J=E4rnefors?= <ojarnef@admin.kth.se>
+From: =?ISO8859-1?Q?Olle_J=E4rnefors?= <ojarnef@admin.kth.se>
 To: ietf-822@dimacs.rutgers.edu, ojarnef@admin.kth.se
 Subject: Time for ISO 10646?
 
 From nobody Mon Sep 17 00:00:00 2001
 To: Dave Crocker <dcrocker@mordor.stanford.edu>
 Cc: ietf-822@dimacs.rutgers.edu, paf@comsol.se
-From: =?ISO-8859-1?Q?Patrik_F=E4ltstr=F6m?= <paf@nada.kth.se>
+From: =?ISO8859-1?Q?Patrik_F=E4ltstr=F6m?= <paf@nada.kth.se>
 Subject: Re: RFC-HDR care and feeding
 
 From nobody Mon Sep 17 00:00:00 2001
 From: Nathaniel Borenstein <nsb@thumper.bellcore.com>
-      (=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?=)
+      (=?ISO8859-8?b?7eXs+SDv4SDp7Oj08A==?=)
 To: Greg Vaudreuil <gvaudre@NRI.Reston.VA.US>, Ned Freed
    <ned@innosoft.com>, Keith Moore <moore@cs.utk.edu>
 Subject: Test of new header generator
 MIME-Version: 1.0
-Content-type: text/plain; charset=ISO-8859-1
+Content-type: text/plain; charset=ISO8859-1
 
 From nobody Mon Sep 17 00:00:00 2001
-Subject: (=?ISO-8859-1?Q?a?=)
+Subject: (=?ISO8859-1?Q?a?=)
 
 From nobody Mon Sep 17 00:00:00 2001
-Subject: (=?ISO-8859-1?Q?a?= b)
+Subject: (=?ISO8859-1?Q?a?= b)
 
 From nobody Mon Sep 17 00:00:00 2001
-Subject: (=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=)
+Subject: (=?ISO8859-1?Q?a?= =?ISO8859-1?Q?b?=)
 
 From nobody Mon Sep 17 00:00:00 2001
-Subject: (=?ISO-8859-1?Q?a?=  =?ISO-8859-1?Q?b?=)
+Subject: (=?ISO8859-1?Q?a?=  =?ISO8859-1?Q?b?=)
 
 From nobody Mon Sep 17 00:00:00 2001
-Subject: (=?ISO-8859-1?Q?a?=
-    =?ISO-8859-1?Q?b?=)
+Subject: (=?ISO8859-1?Q?a?=
+    =?ISO8859-1?Q?b?=)
 
 From nobody Mon Sep 17 00:00:00 2001
-Subject: (=?ISO-8859-1?Q?a_b?=)
+Subject: (=?ISO8859-1?Q?a_b?=)
 
 From nobody Mon Sep 17 00:00:00 2001
-Subject: (=?ISO-8859-1?Q?a?= =?ISO-8859-2?Q?_b?=)
+Subject: (=?ISO8859-1?Q?a?= =?ISO8859-2?Q?_b?=)
index c5ad206b40e1fcf79019cebdfd848d72c17cefcc..de1031241d99a66db0653875ea163785691d12d6 100644 (file)
@@ -99,7 +99,7 @@ index 9123cdc..918dcf8 100644
 From nobody Sat Aug 27 23:07:49 2005
 Path: news.gmane.org!not-for-mail
 Message-ID: <20050721.091036.01119516.yoshfuji@linux-ipv6.org>
-From: YOSHIFUJI Hideaki / =?iso-2022-jp?B?GyRCNUhGIzFRTEAbKEI=?= 
+From: YOSHIFUJI Hideaki / =?ISO-2022-JP?B?GyRCNUhGIzFRTEAbKEI=?= 
        <yoshfuji@linux-ipv6.org>
 Newsgroups: gmane.comp.version-control.git
 Subject: [PATCH 1/2] GIT: Try all addresses for given remote name
@@ -218,7 +218,7 @@ GPG-FP  : 9022 65EB 1ECF 3AD1 0BDF  80D8 4807 F894 E062 0EEA
 From nobody Sat Aug 27 23:07:49 2005
 Path: news.gmane.org!not-for-mail
 Message-ID: <u5tacjjdpxq.fsf@lysator.liu.se>
-From: =?iso-8859-1?Q?David_K=E5gedal?= <davidk@lysator.liu.se>
+From: =?ISO8859-1?Q?David_K=E5gedal?= <davidk@lysator.liu.se>
 Newsgroups: gmane.comp.version-control.git
 Subject: [PATCH] Fixed two bugs in git-cvsimport-script.
 Date: Mon, 15 Aug 2005 20:18:25 +0200
@@ -226,7 +226,7 @@ Lines: 83
 Approved: news@gmane.org
 NNTP-Posting-Host: main.gmane.org
 Mime-Version: 1.0
-Content-Type: text/plain; charset=iso-8859-1
+Content-Type: text/plain; charset=ISO8859-1
 Content-Transfer-Encoding: QUOTED-PRINTABLE
 X-Trace: sea.gmane.org 1124130247 31839 80.91.229.2 (15 Aug 2005 18:24:07 GMT)
 X-Complaints-To: usenet@sea.gmane.org
@@ -476,7 +476,7 @@ MIME-Version: 1.0
 Content-Type: multipart/mixed; boundary="=-=-="
 
 --=-=-=
-Content-Type: text/plain; charset=iso-8859-15
+Content-Type: text/plain; charset=ISO8859-15
 Content-Transfer-Encoding: quoted-printable
 
 Here comes a commit log message, and
@@ -561,3 +561,125 @@ From: <a.u.thor@example.com> (A U Thor)
 Date: Fri, 9 Jun 2006 00:44:16 -0700
 Subject: [PATCH] a patch
 
+From nobody Mon Sep 17 00:00:00 2001
+From: Junio Hamano <junkio@cox.net>
+Date: Thu, 20 Aug 2009 17:18:22 -0700
+Subject: Why doesn't git-am does not like >8 scissors mark?
+
+Subject: [PATCH] BLAH ONE
+
+In real life, we will see a discussion that inspired this patch
+discussing related and unrelated things around >8 scissors mark
+in this part of the message.
+
+Subject: [PATCH] BLAH TWO
+
+And then we will see the scissors.
+
+ This line is not a scissors mark -- >8 -- but talks about it.
+ - - >8 - - please remove everything above this line - - >8 - -
+
+Subject: [PATCH] Teach mailinfo to ignore everything before -- >8 -- mark
+From: Junio C Hamano <gitster@pobox.com>
+
+This teaches mailinfo the scissors -- >8 -- mark; the command ignores
+everything before it in the message body.
+
+Signed-off-by: Junio C Hamano <gitster@pobox.com>
+---
+ builtin-mailinfo.c |   37 ++++++++++++++++++++++++++++++++++++-
+ 1 files changed, 36 insertions(+), 1 deletions(-)
+
+diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
+index b0b5d8f..461c47e 100644
+--- a/builtin-mailinfo.c
++++ b/builtin-mailinfo.c
+@@ -712,6 +712,34 @@ static inline int patchbreak(const struct strbuf *line)
+       return 0;
+ }
++static int scissors(const struct strbuf *line)
++{
++      size_t i, len = line->len;
++      int scissors_dashes_seen = 0;
++      const char *buf = line->buf;
++
++      for (i = 0; i < len; i++) {
++              if (isspace(buf[i]))
++                      continue;
++              if (buf[i] == '-') {
++                      scissors_dashes_seen |= 02;
++                      continue;
++              }
++              if (i + 1 < len && !memcmp(buf + i, ">8", 2)) {
++                      scissors_dashes_seen |= 01;
++                      i++;
++                      continue;
++              }
++              if (i + 7 < len && !memcmp(buf + i, "cut here", 8)) {
++                      i += 7;
++                      continue;
++              }
++              /* everything else --- not scissors */
++              break;
++      }
++      return scissors_dashes_seen == 03;
++}
++
+ static int handle_commit_msg(struct strbuf *line)
+ {
+       static int still_looking = 1;
+@@ -723,10 +751,17 @@ static int handle_commit_msg(struct strbuf *line)
+               strbuf_ltrim(line);
+               if (!line->len)
+                       return 0;
+-              if ((still_looking = check_header(line, s_hdr_data, 0)) != 0)
++              still_looking = check_header(line, s_hdr_data, 0);
++              if (still_looking)
+                       return 0;
+       }
++      if (scissors(line)) {
++              fseek(cmitmsg, 0L, SEEK_SET);
++              still_looking = 1;
++              return 0;
++      }
++
+       /* normalize the log message to UTF-8. */
+       if (metainfo_charset)
+               convert_to_utf8(line, charset.buf);
+-- 
+1.6.4.1
+From nobody Mon Sep 17 00:00:00 2001
+From: A U Thor <a.u.thor@example.com>
+Subject: check bogus body header (from)
+Date: Fri, 9 Jun 2006 00:44:16 -0700
+
+From: bogosity
+  - a list
+  - of stuff
+---
+diff --git a/foo b/foo
+index e69de29..d95f3ad 100644
+--- a/foo
++++ b/foo
+@@ -0,0 +1 @@
++content
+
+From nobody Mon Sep 17 00:00:00 2001
+From: A U Thor <a.u.thor@example.com>
+Subject: check bogus body header (date)
+Date: Fri, 9 Jun 2006 00:44:16 -0700
+
+Date: bogus
+
+and some content
+
+---
+diff --git a/foo b/foo
+index e69de29..d95f3ad 100644
+--- a/foo
++++ b/foo
+@@ -0,0 +1 @@
++content
+
diff --git a/t/t5150-request-pull.sh b/t/t5150-request-pull.sh
new file mode 100755 (executable)
index 0000000..9cc0a42
--- /dev/null
@@ -0,0 +1,228 @@
+#!/bin/sh
+
+test_description='Test workflows involving pull request.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+
+       git init --bare upstream.git &&
+       git init --bare downstream.git &&
+       git clone upstream.git upstream-private &&
+       git clone downstream.git local &&
+
+       trash_url="file://$TRASH_DIRECTORY" &&
+       downstream_url="$trash_url/downstream.git/" &&
+       upstream_url="$trash_url/upstream.git/" &&
+
+       (
+               cd upstream-private &&
+               cat <<-\EOT >mnemonic.txt &&
+               Thirtey days hath November,
+               Aprile, June, and September:
+               EOT
+               git add mnemonic.txt &&
+               test_tick &&
+               git commit -m "\"Thirty days\", a reminder of month lengths" &&
+               git tag -m "version 1" -a initial &&
+               git push --tags origin master
+       ) &&
+       (
+               cd local &&
+               git remote add upstream "$trash_url/upstream.git" &&
+               git fetch upstream &&
+               git pull upstream master &&
+               cat <<-\EOT >>mnemonic.txt &&
+               Of twyecescore-eightt is but eine,
+               And all the remnante be thrycescore-eine.
+               O’course Leap yare comes an’pynes,
+               Ev’rie foure yares, gote it ryghth.
+               An’twyecescore-eight is but twyecescore-nyne.
+               EOT
+               git add mnemonic.txt &&
+               test_tick &&
+               git commit -m "More detail" &&
+               git tag -m "version 2" -a full &&
+               git checkout -b simplify HEAD^ &&
+               mv mnemonic.txt mnemonic.standard &&
+               cat <<-\EOT >mnemonic.clarified &&
+               Thirty days has September,
+               All the rest I can’t remember.
+               EOT
+               git add -N mnemonic.standard mnemonic.clarified &&
+               git commit -a -m "Adapt to use modern, simpler English
+
+But keep the old version, too, in case some people prefer it." &&
+               git checkout master
+       )
+
+'
+
+test_expect_success 'setup: two scripts for reading pull requests' '
+
+       downstream_url_for_sed=$(
+               printf "%s\n" "$downstream_url" |
+               sed -e '\''s/\\/\\\\/g'\'' -e '\''s/[[/.*^$]/\\&/g'\''
+       ) &&
+
+       cat <<-\EOT >read-request.sed &&
+       #!/bin/sed -nf
+       / in the git repository at:$/!d
+       n
+       /^$/ n
+       s/^[    ]*\(.*\) \([^ ]*\)/please pull\
+       \1\
+       \2/p
+       q
+       EOT
+
+       cat <<-EOT >fuzz.sed
+       #!/bin/sed -nf
+       s/$_x40/OBJECT_NAME/g
+       s/A U Thor/AUTHOR/g
+       s/[-0-9]\{10\} [:0-9]\{8\} [-+][0-9]\{4\}/DATE/g
+       s/        [^ ].*/        SUBJECT/g
+       s/  [^ ].* (DATE)/  SUBJECT (DATE)/g
+       s/$downstream_url_for_sed/URL/g
+       s/for-upstream/BRANCH/g
+       s/mnemonic.txt/FILENAME/g
+       /^ FILENAME | *[0-9]* [-+]*\$/ b diffstat
+       /^AUTHOR ([0-9]*):\$/ b shortlog
+       p
+       b
+       : diffstat
+       n
+       / [0-9]* files changed/ {
+               a\\
+       DIFFSTAT
+               b
+       }
+       b diffstat
+       : shortlog
+       /^        [a-zA-Z]/ n
+       /^[a-zA-Z]* ([0-9]*):\$/ n
+       /^\$/ N
+       /^\n[a-zA-Z]* ([0-9]*):\$/!{
+               a\\
+       SHORTLOG
+               D
+       }
+       n
+       b shortlog
+       EOT
+
+'
+
+test_expect_success 'pull request when forgot to push' '
+
+       rm -fr downstream.git &&
+       git init --bare downstream.git &&
+       (
+               cd local &&
+               git checkout initial &&
+               git merge --ff-only master &&
+               test_must_fail git request-pull initial "$downstream_url" \
+                       2>../err
+       ) &&
+       grep "No branch of.*is at:\$" err &&
+       grep "Are you sure you pushed" err
+
+'
+
+test_expect_success 'pull request after push' '
+
+       rm -fr downstream.git &&
+       git init --bare downstream.git &&
+       (
+               cd local &&
+               git checkout initial &&
+               git merge --ff-only master &&
+               git push origin master:for-upstream &&
+               git request-pull initial origin >../request
+       ) &&
+       sed -nf read-request.sed <request >digest &&
+       cat digest &&
+       {
+               read task &&
+               read repository &&
+               read branch
+       } <digest &&
+       (
+               cd upstream-private &&
+               git checkout initial &&
+               git pull --ff-only "$repository" "$branch"
+       ) &&
+       test "$branch" = for-upstream &&
+       test_cmp local/mnemonic.txt upstream-private/mnemonic.txt
+
+'
+
+test_expect_success 'request names an appropriate branch' '
+
+       rm -fr downstream.git &&
+       git init --bare downstream.git &&
+       (
+               cd local &&
+               git checkout initial &&
+               git merge --ff-only master &&
+               git push --tags origin master simplify &&
+               git push origin master:for-upstream &&
+               git request-pull initial "$downstream_url" >../request
+       ) &&
+       sed -nf read-request.sed <request >digest &&
+       cat digest &&
+       {
+               read task &&
+               read repository &&
+               read branch
+       } <digest &&
+       {
+               test "$branch" = master ||
+               test "$branch" = for-upstream
+       }
+
+'
+
+test_expect_success 'pull request format' '
+
+       rm -fr downstream.git &&
+       git init --bare downstream.git &&
+       cat <<-\EOT >expect &&
+       The following changes since commit OBJECT_NAME:
+
+         SUBJECT (DATE)
+
+       are available in the git repository at:
+         URL BRANCH
+
+       SHORTLOG
+
+       DIFFSTAT
+       EOT
+       (
+               cd local &&
+               git checkout initial &&
+               git merge --ff-only master &&
+               git push origin master:for-upstream &&
+               git request-pull initial "$downstream_url" >../request
+       ) &&
+       <request sed -nf fuzz.sed >request.fuzzy &&
+       test_cmp expect request.fuzzy
+
+'
+
+test_expect_success 'request-pull ignores OPTIONS_KEEPDASHDASH poison' '
+
+       (
+               cd local &&
+               OPTIONS_KEEPDASHDASH=Yes &&
+               export OPTIONS_KEEPDASHDASH &&
+               git checkout initial &&
+               git merge --ff-only master &&
+               git push origin master:for-upstream &&
+               git request-pull -- initial "$downstream_url" >../request
+       )
+
+'
+
+test_done
index e2aa254eae2ea930f29c4735f3f3d11bc790c455..bbb9c1251d97bdaf5bca8f8260e481c3cac56dea 100755 (executable)
@@ -16,7 +16,9 @@ test_expect_success \
      perl -e "print \"a\" x 4096;" > a &&
      perl -e "print \"b\" x 4096;" > b &&
      perl -e "print \"c\" x 4096;" > c &&
-     git update-index --add a b c &&
+     test-genrandom "seed a" 2097152 > a_big &&
+     test-genrandom "seed b" 2097152 > b_big &&
+     git update-index --add a a_big b b_big c &&
      cat c >d && echo foo >>d && git update-index --add d &&
      tree=`git write-tree` &&
      commit=`git commit-tree $tree </dev/null` && {
@@ -145,7 +147,7 @@ test_expect_success \
            git cat-file $t $object || return 1
         done <obj-list
     } >current &&
-    diff expect current'
+    test_cmp expect current'
 
 test_expect_success \
     'use packed deltified (REF_DELTA) objects' \
@@ -160,7 +162,7 @@ test_expect_success \
            git cat-file $t $object || return 1
         done <obj-list
     } >current &&
-    diff expect current'
+    test_cmp expect current'
 
 test_expect_success \
     'use packed deltified (OFS_DELTA) objects' \
@@ -175,7 +177,7 @@ test_expect_success \
            git cat-file $t $object || return 1
         done <obj-list
     } >current &&
-    diff expect current'
+    test_cmp expect current'
 
 unset GIT_OBJECT_DIRECTORY
 
@@ -280,26 +282,8 @@ test_expect_success \
 
      :'
 
-test_expect_success \
-    'fake a SHA1 hash collision' \
-    'test -f   .git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67 &&
-     cp -f     .git/objects/9d/235ed07cd19811a6ceb342de82f190e49c9f68 \
-               .git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67'
-
-test_expect_success \
-    'make sure index-pack detects the SHA1 collision' \
-    'test_must_fail git index-pack -o bad.idx test-3.pack 2>msg &&
-     grep "SHA1 COLLISION FOUND" msg'
-
-test_expect_success \
-    'honor pack.packSizeLimit' \
-    'git config pack.packSizeLimit 200 &&
-     packname_4=$(git pack-objects test-4 <obj-list) &&
-     test 3 = $(ls test-4-*.pack | wc -l)'
-
 test_expect_success 'unpacking with --strict' '
 
-       git config --unset pack.packsizelimit &&
        for j in a b c d e f g
        do
                for i in 0 1 2 3 4 5 6 7 8 9
@@ -392,10 +376,42 @@ test_expect_success 'index-pack with --strict' '
        )
 '
 
-test_expect_success 'tolerate absurdly small packsizelimit' '
-       git config pack.packSizeLimit 2 &&
-       packname_9=$(git pack-objects test-9 <obj-list) &&
-       test $(wc -l <obj-list) = $(ls test-9-*.pack | wc -l)
+test_expect_success 'honor pack.packSizeLimit' '
+       git config pack.packSizeLimit 3m &&
+       packname_10=$(git pack-objects test-10 <obj-list) &&
+       test 2 = $(ls test-10-*.pack | wc -l)
+'
+
+test_expect_success 'verify resulting packs' '
+       git verify-pack test-10-*.pack
+'
+
+test_expect_success 'tolerate packsizelimit smaller than biggest object' '
+       git config pack.packSizeLimit 1 &&
+       packname_11=$(git pack-objects test-11 <obj-list) &&
+       test 5 = $(ls test-11-*.pack | wc -l)
 '
 
+test_expect_success 'verify resulting packs' '
+       git verify-pack test-11-*.pack
+'
+
+#
+# WARNING!
+#
+# The following test is destructive.  Please keep the next
+# two tests at the end of this file.
+#
+
+test_expect_success \
+    'fake a SHA1 hash collision' \
+    'test -f   .git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67 &&
+     cp -f     .git/objects/9d/235ed07cd19811a6ceb342de82f190e49c9f68 \
+               .git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67'
+
+test_expect_success \
+    'make sure index-pack detects the SHA1 collision' \
+    'test_must_fail git index-pack -o bad.idx test-3.pack 2>msg &&
+     grep "SHA1 COLLISION FOUND" msg'
+
 test_done
index 4360e77d317bfdaf7b40795859b4a023a5b89c13..fb3a270822c9a4ac2e4f79bfa71c784291859d4f 100755 (executable)
@@ -74,7 +74,7 @@ if msg=$(git verify-pack -v "test-3-${pack3}.pack" 2>&1) ||
 then
        test_set_prereq OFF64_T
 else
-       say "skipping tests concerning 64-bit offsets"
+       say "skipping tests concerning 64-bit offsets"
 fi
 
 test_expect_success OFF64_T \
index 5132d4130968691bb115f5fc08cd0d2fd64ce669..5f6cd4f3332f600888c402d2f1b72ddc8840b8cf 100755 (executable)
@@ -275,4 +275,13 @@ test_expect_success \
      git cat-file blob $blob_2 > /dev/null &&
      git cat-file blob $blob_3 > /dev/null'
 
+test_expect_success \
+    'corrupting header to have too small output buffer fails unpack' \
+    'create_new_pack &&
+     git prune-packed &&
+     printf "\262\001" | do_corrupt_object $blob_1 0 &&
+     test_must_fail git cat-file blob $blob_1 > /dev/null &&
+     test_must_fail git cat-file blob $blob_2 > /dev/null &&
+     test_must_fail git cat-file blob $blob_3 > /dev/null'
+
 test_done
index 55ed7c7935c007573761842cea5978a784c865b3..e2ed13dba2705b15d6a1f623589acce134749fab 100755 (executable)
@@ -6,6 +6,17 @@
 test_description='prune'
 . ./test-lib.sh
 
+day=$((60*60*24))
+week=$(($day*7))
+
+add_blob() {
+       before=$(git count-objects | sed "s/ .*//") &&
+       BLOB=$(echo aleph_0 | git hash-object -w --stdin) &&
+       BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") &&
+       test $((1 + $before)) = $(git count-objects | sed "s/ .*//") &&
+       test -f $BLOB_FILE
+}
+
 test_expect_success setup '
 
        : > file &&
@@ -31,11 +42,7 @@ test_expect_success 'prune stale packs' '
 
 test_expect_success 'prune --expire' '
 
-       before=$(git count-objects | sed "s/ .*//") &&
-       BLOB=$(echo aleph | git hash-object -w --stdin) &&
-       BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") &&
-       test $((1 + $before)) = $(git count-objects | sed "s/ .*//") &&
-       test -f $BLOB_FILE &&
+       add_blob &&
        git prune --expire=1.hour.ago &&
        test $((1 + $before)) = $(git count-objects | sed "s/ .*//") &&
        test -f $BLOB_FILE &&
@@ -48,16 +55,12 @@ test_expect_success 'prune --expire' '
 
 test_expect_success 'gc: implicit prune --expire' '
 
-       before=$(git count-objects | sed "s/ .*//") &&
-       BLOB=$(echo aleph_0 | git hash-object -w --stdin) &&
-       BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") &&
-       test $((1 + $before)) = $(git count-objects | sed "s/ .*//") &&
-       test -f $BLOB_FILE &&
-       test-chmtime =-$((86400*14-30)) $BLOB_FILE &&
+       add_blob &&
+       test-chmtime =-$((2*$week-30)) $BLOB_FILE &&
        git gc &&
        test $((1 + $before)) = $(git count-objects | sed "s/ .*//") &&
        test -f $BLOB_FILE &&
-       test-chmtime =-$((86400*14+1)) $BLOB_FILE &&
+       test-chmtime =-$((2*$week+1)) $BLOB_FILE &&
        git gc &&
        test $before = $(git count-objects | sed "s/ .*//") &&
        ! test -f $BLOB_FILE
@@ -114,12 +117,8 @@ test_expect_success 'prune: do not prune heads listed as an argument' '
 
 test_expect_success 'gc --no-prune' '
 
-       before=$(git count-objects | sed "s/ .*//") &&
-       BLOB=$(echo aleph_0 | git hash-object -w --stdin) &&
-       BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") &&
-       test $((1 + $before)) = $(git count-objects | sed "s/ .*//") &&
-       test -f $BLOB_FILE &&
-       test-chmtime =-$((86400*5001)) $BLOB_FILE &&
+       add_blob &&
+       test-chmtime =-$((5001*$day)) $BLOB_FILE &&
        git config gc.pruneExpire 2.days.ago &&
        git gc --no-prune &&
        test 1 = $(git count-objects | sed "s/ .*//") &&
@@ -140,9 +139,8 @@ test_expect_success 'gc respects gc.pruneExpire' '
 
 test_expect_success 'gc --prune=<date>' '
 
-       BLOB=$(echo aleph_0 | git hash-object -w --stdin) &&
-       BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") &&
-       test-chmtime =-$((86400*5001)) $BLOB_FILE &&
+       add_blob &&
+       test-chmtime =-$((5001*$day)) $BLOB_FILE &&
        git gc --prune=5002.days.ago &&
        test -f $BLOB_FILE &&
        git gc --prune=5000.days.ago &&
@@ -150,4 +148,50 @@ test_expect_success 'gc --prune=<date>' '
 
 '
 
+test_expect_success 'gc --prune=never' '
+
+       add_blob &&
+       git gc --prune=never &&
+       test -f $BLOB_FILE &&
+       git gc --prune=now &&
+       test ! -f $BLOB_FILE
+
+'
+
+test_expect_success 'gc respects gc.pruneExpire=never' '
+
+       git config gc.pruneExpire never &&
+       add_blob &&
+       git gc &&
+       test -f $BLOB_FILE &&
+       git config gc.pruneExpire now &&
+       git gc &&
+       test ! -f $BLOB_FILE
+
+'
+
+test_expect_success 'prune --expire=never' '
+
+       add_blob &&
+       git prune --expire=never &&
+       test -f $BLOB_FILE &&
+       git prune &&
+       test ! -f $BLOB_FILE
+
+'
+
+test_expect_success 'gc: prune old objects after local clone' '
+       add_blob &&
+       test-chmtime =-$((2*$week+1)) $BLOB_FILE &&
+       git clone --no-hardlinks . aclone &&
+       (
+               cd aclone &&
+               test 1 = $(git count-objects | sed "s/ .*//") &&
+               test -f $BLOB_FILE &&
+               git gc --prune &&
+               test 0 = $(git count-objects | sed "s/ .*//") &&
+               ! test -f $BLOB_FILE
+       )
+'
+
 test_done
index f2d5581b12f7d70c9f346da75dced81e12bd4c7f..5bcf0b867adf3f57018a15ab8828713b986aaac6 100755 (executable)
@@ -32,7 +32,7 @@ test_expect_success setup '
        done &&
        git update-ref HEAD "$commit" &&
        git clone ./. victim &&
-       ( cd victim && git log ) &&
+       ( cd victim && git config receive.denyCurrentBranch warn && git log ) &&
        git update-ref HEAD "$zero" &&
        parent=$zero &&
        i=0 &&
@@ -94,6 +94,29 @@ test_expect_success 'refuse deleting push with denyDeletes' '
        test_must_fail git send-pack ./victim :extra master
 '
 
+test_expect_success 'cannot override denyDeletes with git -c send-pack' '
+       (
+               cd victim &&
+               test_might_fail git branch -D extra &&
+               git config receive.denyDeletes true &&
+               git branch extra master
+       ) &&
+       test_must_fail git -c receive.denyDeletes=false \
+                                       send-pack ./victim :extra master
+'
+
+test_expect_success 'override denyDeletes with git -c receive-pack' '
+       (
+               cd victim &&
+               test_might_fail git branch -D extra &&
+               git config receive.denyDeletes true &&
+               git branch extra master
+       ) &&
+       git send-pack \
+               --receive-pack="git -c receive.denyDeletes=false receive-pack" \
+               ./victim :extra master
+'
+
 test_expect_success 'denyNonFastforwards trumps --force' '
        (
            cd victim &&
@@ -129,6 +152,7 @@ rewound_push_setup() {
            cd parent &&
            git init &&
            echo one >file && git add file && git commit -m one &&
+           git config receive.denyCurrentBranch warn &&
            echo two >file && git commit -a -m two
        ) &&
        git clone parent child &&
@@ -190,16 +214,11 @@ test_expect_success 'pushing wildcard refspecs respects forcing' '
        test "$parent_head" = "$child_head"
 '
 
-test_expect_success 'warn pushing to delete current branch' '
+test_expect_success 'deny pushing to delete current branch' '
        rewound_push_setup &&
        (
            cd child &&
-           git send-pack ../parent :refs/heads/master 2>errs
-       ) &&
-       grep "warning: to refuse deleting" child/errs &&
-       (
-               cd parent &&
-               test_must_fail git rev-parse --verify master
+           test_must_fail git send-pack ../parent :refs/heads/master 2>errs
        )
 '
 
index 64f66c94f36538b1c7d20045fc4233aa0b9d9a0d..17bcb0b04096eabb29c13a025fd2afe8d1e4623b 100755 (executable)
@@ -17,22 +17,22 @@ test_expect_success setup '
        commit1=$(echo modify | git commit-tree $tree1 -p $commit0) &&
        git update-ref refs/heads/master $commit0 &&
        git update-ref refs/heads/tofail $commit1 &&
-       git clone ./. victim &&
-       GIT_DIR=victim/.git git update-ref refs/heads/tofail $commit1 &&
+       git clone --bare ./. victim.git &&
+       GIT_DIR=victim.git git update-ref refs/heads/tofail $commit1 &&
        git update-ref refs/heads/master $commit1 &&
        git update-ref refs/heads/tofail $commit0
 '
 
-cat >victim/.git/hooks/pre-receive <<'EOF'
+cat >victim.git/hooks/pre-receive <<'EOF'
 #!/bin/sh
 printf %s "$@" >>$GIT_DIR/pre-receive.args
 cat - >$GIT_DIR/pre-receive.stdin
 echo STDOUT pre-receive
 echo STDERR pre-receive >&2
 EOF
-chmod u+x victim/.git/hooks/pre-receive
+chmod u+x victim.git/hooks/pre-receive
 
-cat >victim/.git/hooks/update <<'EOF'
+cat >victim.git/hooks/update <<'EOF'
 #!/bin/sh
 echo "$@" >>$GIT_DIR/update.args
 read x; printf %s "$x" >$GIT_DIR/update.stdin
@@ -40,77 +40,77 @@ echo STDOUT update $1
 echo STDERR update $1 >&2
 test "$1" = refs/heads/master || exit
 EOF
-chmod u+x victim/.git/hooks/update
+chmod u+x victim.git/hooks/update
 
-cat >victim/.git/hooks/post-receive <<'EOF'
+cat >victim.git/hooks/post-receive <<'EOF'
 #!/bin/sh
 printf %s "$@" >>$GIT_DIR/post-receive.args
 cat - >$GIT_DIR/post-receive.stdin
 echo STDOUT post-receive
 echo STDERR post-receive >&2
 EOF
-chmod u+x victim/.git/hooks/post-receive
+chmod u+x victim.git/hooks/post-receive
 
-cat >victim/.git/hooks/post-update <<'EOF'
+cat >victim.git/hooks/post-update <<'EOF'
 #!/bin/sh
 echo "$@" >>$GIT_DIR/post-update.args
 read x; printf %s "$x" >$GIT_DIR/post-update.stdin
 echo STDOUT post-update
 echo STDERR post-update >&2
 EOF
-chmod u+x victim/.git/hooks/post-update
+chmod u+x victim.git/hooks/post-update
 
 test_expect_success push '
-       test_must_fail git send-pack --force ./victim/.git \
+       test_must_fail git send-pack --force ./victim.git \
                master tofail >send.out 2>send.err
 '
 
 test_expect_success 'updated as expected' '
-       test $(GIT_DIR=victim/.git git rev-parse master) = $commit1 &&
-       test $(GIT_DIR=victim/.git git rev-parse tofail) = $commit1
+       test $(GIT_DIR=victim.git git rev-parse master) = $commit1 &&
+       test $(GIT_DIR=victim.git git rev-parse tofail) = $commit1
 '
 
 test_expect_success 'hooks ran' '
-       test -f victim/.git/pre-receive.args &&
-       test -f victim/.git/pre-receive.stdin &&
-       test -f victim/.git/update.args &&
-       test -f victim/.git/update.stdin &&
-       test -f victim/.git/post-receive.args &&
-       test -f victim/.git/post-receive.stdin &&
-       test -f victim/.git/post-update.args &&
-       test -f victim/.git/post-update.stdin
+       test -f victim.git/pre-receive.args &&
+       test -f victim.git/pre-receive.stdin &&
+       test -f victim.git/update.args &&
+       test -f victim.git/update.stdin &&
+       test -f victim.git/post-receive.args &&
+       test -f victim.git/post-receive.stdin &&
+       test -f victim.git/post-update.args &&
+       test -f victim.git/post-update.stdin
 '
 
 test_expect_success 'pre-receive hook input' '
        (echo $commit0 $commit1 refs/heads/master;
         echo $commit1 $commit0 refs/heads/tofail
-       ) | test_cmp - victim/.git/pre-receive.stdin
+       ) | test_cmp - victim.git/pre-receive.stdin
 '
 
 test_expect_success 'update hook arguments' '
        (echo refs/heads/master $commit0 $commit1;
         echo refs/heads/tofail $commit1 $commit0
-       ) | test_cmp - victim/.git/update.args
+       ) | test_cmp - victim.git/update.args
 '
 
 test_expect_success 'post-receive hook input' '
        echo $commit0 $commit1 refs/heads/master |
-       test_cmp - victim/.git/post-receive.stdin
+       test_cmp - victim.git/post-receive.stdin
 '
 
 test_expect_success 'post-update hook arguments' '
        echo refs/heads/master |
-       test_cmp - victim/.git/post-update.args
+       test_cmp - victim.git/post-update.args
 '
 
 test_expect_success 'all hook stdin is /dev/null' '
-       ! test -s victim/.git/update.stdin &&
-       ! test -s victim/.git/post-update.stdin
+       ! test -s victim.git/update.stdin &&
+       ! test -s victim.git/post-update.stdin
 '
 
 test_expect_success 'all *-receive hook args are empty' '
-       ! test -s victim/.git/pre-receive.args &&
-       ! test -s victim/.git/post-receive.args
+       ! test -s victim.git/pre-receive.args &&
+       ! test -s victim.git/post-receive.args
 '
 
 test_expect_success 'send-pack produced no output' '
@@ -118,20 +118,21 @@ test_expect_success 'send-pack produced no output' '
 '
 
 cat <<EOF >expect
-STDOUT pre-receive
-STDERR pre-receive
-STDOUT update refs/heads/master
-STDERR update refs/heads/master
-STDOUT update refs/heads/tofail
-STDERR update refs/heads/tofail
-STDOUT post-receive
-STDERR post-receive
-STDOUT post-update
-STDERR post-update
+remote: STDOUT pre-receive
+remote: STDERR pre-receive
+remote: STDOUT update refs/heads/master
+remote: STDERR update refs/heads/master
+remote: STDOUT update refs/heads/tofail
+remote: STDERR update refs/heads/tofail
+remote: error: hook declined to update refs/heads/tofail
+remote: STDOUT post-receive
+remote: STDERR post-receive
+remote: STDOUT post-update
+remote: STDERR post-update
 EOF
 test_expect_success 'send-pack stderr contains hook messages' '
-       grep ^STD send.err >actual &&
-       test_cmp - actual <expect
+       grep ^remote: send.err | sed "s/ *\$//" >actual &&
+       test_cmp expect actual
 '
 
 test_done
index 5858b868ed6ff05d458996cf8c359a7148591582..d05a9138b46eec072750142f912a41caa3e23d83 100755 (executable)
@@ -7,19 +7,19 @@ test_description='Test the post-checkout hook.'
 . ./test-lib.sh
 
 test_expect_success setup '
-        echo Data for commit0. >a &&
-        echo Data for commit0. >b &&
-        git update-index --add a &&
-        git update-index --add b &&
-        tree0=$(git write-tree) &&
-        commit0=$(echo setup | git commit-tree $tree0) &&
-        git update-ref refs/heads/master $commit0 &&
-        git clone ./. clone1 &&
-        git clone ./. clone2 &&
-        GIT_DIR=clone2/.git git branch -a new2 &&
-        echo Data for commit1. >clone2/b &&
-        GIT_DIR=clone2/.git git add clone2/b &&
-        GIT_DIR=clone2/.git git commit -m new2
+       echo Data for commit0. >a &&
+       echo Data for commit0. >b &&
+       git update-index --add a &&
+       git update-index --add b &&
+       tree0=$(git write-tree) &&
+       commit0=$(echo setup | git commit-tree $tree0) &&
+       git update-ref refs/heads/master $commit0 &&
+       git clone ./. clone1 &&
+       git clone ./. clone2 &&
+       GIT_DIR=clone2/.git git branch new2 &&
+       echo Data for commit1. >clone2/b &&
+       GIT_DIR=clone2/.git git add clone2/b &&
+       GIT_DIR=clone2/.git git commit -m new2
 '
 
 for clone in 1 2; do
index cb9aacc7bc62e2ecfdca0dc7f6071e5330fe09d0..4bda18a662da5a0c36ee007c37d9ded6be3b1832 100755 (executable)
@@ -8,6 +8,7 @@ test_expect_success setup '
 
        >file1 && git add file1 && test_tick &&
        git commit -m Initial &&
+       git config receive.denyCurrentBranch warn &&
 
        mkdir another && (
                cd another &&
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
new file mode 100755 (executable)
index 0000000..552da65
--- /dev/null
@@ -0,0 +1,199 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Thomas Rast
+#
+
+test_description='Test the post-rewrite hook.'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       test_commit A foo A &&
+       test_commit B foo B &&
+       test_commit C foo C &&
+       test_commit D foo D
+'
+
+mkdir .git/hooks
+
+cat >.git/hooks/post-rewrite <<EOF
+#!/bin/sh
+echo \$@ > "$TRASH_DIRECTORY"/post-rewrite.args
+cat > "$TRASH_DIRECTORY"/post-rewrite.data
+EOF
+chmod u+x .git/hooks/post-rewrite
+
+clear_hook_input () {
+       rm -f post-rewrite.args post-rewrite.data
+}
+
+verify_hook_input () {
+       test_cmp "$TRASH_DIRECTORY"/post-rewrite.args expected.args &&
+       test_cmp "$TRASH_DIRECTORY"/post-rewrite.data expected.data
+}
+
+test_expect_success 'git commit --amend' '
+       clear_hook_input &&
+       echo "D new message" > newmsg &&
+       oldsha=$(git rev-parse HEAD^0) &&
+       git commit -Fnewmsg --amend &&
+       echo amend > expected.args &&
+       echo $oldsha $(git rev-parse HEAD^0) > expected.data &&
+       verify_hook_input
+'
+
+test_expect_success 'git commit --amend --no-post-rewrite' '
+       clear_hook_input &&
+       echo "D new message again" > newmsg &&
+       git commit --no-post-rewrite -Fnewmsg --amend &&
+       test ! -f post-rewrite.args &&
+       test ! -f post-rewrite.data
+'
+
+test_expect_success 'git rebase' '
+       git reset --hard D &&
+       clear_hook_input &&
+       test_must_fail git rebase --onto A B &&
+       echo C > foo &&
+       git add foo &&
+       git rebase --continue &&
+       echo rebase >expected.args &&
+       cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+       verify_hook_input
+'
+
+test_expect_success 'git rebase --skip' '
+       git reset --hard D &&
+       clear_hook_input &&
+       test_must_fail git rebase --onto A B &&
+       test_must_fail git rebase --skip &&
+       echo D > foo &&
+       git add foo &&
+       git rebase --continue &&
+       echo rebase >expected.args &&
+       cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+       verify_hook_input
+'
+
+test_expect_success 'git rebase -m' '
+       git reset --hard D &&
+       clear_hook_input &&
+       test_must_fail git rebase -m --onto A B &&
+       echo C > foo &&
+       git add foo &&
+       git rebase --continue &&
+       echo rebase >expected.args &&
+       cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+       verify_hook_input
+'
+
+test_expect_success 'git rebase -m --skip' '
+       git reset --hard D &&
+       clear_hook_input &&
+       test_must_fail git rebase --onto A B &&
+       test_must_fail git rebase --skip &&
+       echo D > foo &&
+       git add foo &&
+       git rebase --continue &&
+       echo rebase >expected.args &&
+       cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+       verify_hook_input
+'
+
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+set_fake_editor
+
+# Helper to work around the lack of one-shot exporting for
+# test_must_fail (as it is a shell function)
+test_fail_interactive_rebase () {
+       (
+               FAKE_LINES="$1" &&
+               shift &&
+               export FAKE_LINES &&
+               test_must_fail git rebase -i "$@"
+       )
+}
+
+test_expect_success 'git rebase -i (unchanged)' '
+       git reset --hard D &&
+       clear_hook_input &&
+       test_fail_interactive_rebase "1 2" --onto A B &&
+       echo C > foo &&
+       git add foo &&
+       git rebase --continue &&
+       echo rebase >expected.args &&
+       cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+       verify_hook_input
+'
+
+test_expect_success 'git rebase -i (skip)' '
+       git reset --hard D &&
+       clear_hook_input &&
+       test_fail_interactive_rebase "2" --onto A B &&
+       echo D > foo &&
+       git add foo &&
+       git rebase --continue &&
+       echo rebase >expected.args &&
+       cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+       verify_hook_input
+'
+
+test_expect_success 'git rebase -i (squash)' '
+       git reset --hard D &&
+       clear_hook_input &&
+       test_fail_interactive_rebase "1 squash 2" --onto A B &&
+       echo C > foo &&
+       git add foo &&
+       git rebase --continue &&
+       echo rebase >expected.args &&
+       cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+       verify_hook_input
+'
+
+test_expect_success 'git rebase -i (fixup without conflict)' '
+       git reset --hard D &&
+       clear_hook_input &&
+       FAKE_LINES="1 fixup 2" git rebase -i B &&
+       echo rebase >expected.args &&
+       cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+       verify_hook_input
+'
+
+test_expect_success 'git rebase -i (double edit)' '
+       git reset --hard D &&
+       clear_hook_input &&
+       FAKE_LINES="edit 1 edit 2" git rebase -i B &&
+       git rebase --continue &&
+       echo something > foo &&
+       git add foo &&
+       git rebase --continue &&
+       echo rebase >expected.args &&
+       cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+       verify_hook_input
+'
+
+test_done
index c450f33f333e6f1c367f8f350dfd78f8f44a0fee..18376d66081759c6a4959a2d8bc47ca441364660 100755 (executable)
@@ -3,9 +3,8 @@
 # Copyright (c) 2005 Johannes Schindelin
 #
 
-test_description='Testing multi_ack pack fetching
+test_description='Testing multi_ack pack fetching'
 
-'
 . ./test-lib.sh
 
 # Test fetch-pack/upload-pack pair.
@@ -13,77 +12,60 @@ test_description='Testing multi_ack pack fetching
 # Some convenience functions
 
 add () {
-       name=$1
-       text="$@"
-       branch=`echo $name | sed -e 's/^\(.\).*$/\1/'`
-       parents=""
+       name=$1 &&
+       text="$@" &&
+       branch=`echo $name | sed -e 's/^\(.\).*$/\1/'` &&
+       parents="" &&
 
-       shift
+       shift &&
        while test $1; do
-               parents="$parents -p $1"
+               parents="$parents -p $1" &&
                shift
-       done
+       done &&
 
-       echo "$text" > test.txt
-       git update-index --add test.txt
-       tree=$(git write-tree)
+       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)
-       eval "$name=$commit; export $name"
-       echo $commit > .git/refs/heads/$branch
+       test_tick &&
+       commit=$(echo "$text" | git commit-tree $tree $parents) &&
+       eval "$name=$commit; export $name" &&
+       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 --full > fsck.txt 2>&1'
-
-       test_expect_success 'check downloaded results' \
-       'mv .git/objects/pack/pack-* . &&
-        p=`ls -1 pack-*.pack` &&
-        git unpack-objects <$p &&
-        git fsck --full'
-
-       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 ..
+       number=$1 &&
+       heads=$2 &&
+       count=$3 &&
+       test_expect_success "$number pull" '
+               (
+                       cd client &&
+                       git fetch-pack -k -v .. $heads &&
+
+                       case "$heads" in
+                           *A*)
+                                   echo $ATIP > .git/refs/heads/A;;
+                       esac &&
+                       case "$heads" in *B*)
+                           echo $BTIP > .git/refs/heads/B;;
+                       esac &&
+                       git symbolic-ref HEAD refs/heads/`echo $heads \
+                               | sed -e "s/^\(.\).*$/\1/"` &&
+
+                       git fsck --full &&
+
+                       mv .git/objects/pack/pack-* . &&
+                       p=`ls -1 pack-*.pack` &&
+                       git unpack-objects <$p &&
+                       git fsck --full &&
+
+                       idx=`echo pack-*.idx` &&
+                       pack_count=`git show-index <$idx | wc -l` &&
+                       test $pack_count = $count &&
+                       rm -f pack-*
+               )
+       '
 }
 
 # Here begins the actual testing
@@ -94,89 +76,176 @@ pull_to_client () {
 
 # client pulls A20, B1. Then tracks only B. Then pulls A.
 
-(
+test_expect_success 'setup' '
        mkdir client &&
-       cd client &&
-       git init 2>> log2.txt &&
-       git config transfer.unpacklimit 0
-)
-
-add A1
-
-prev=1; cur=2; while [ $cur -le 10 ]; do
-       add A$cur $(eval echo \$A$prev)
-       prev=$cur
-       cur=$(($cur+1))
-done
+       (
+               cd client &&
+               git init &&
+               git config transfer.unpacklimit 0
+       ) &&
+       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
+'
 
-add B1 $A1
+pull_to_client 1st "B A" $((11*3))
 
-echo $ATIP > .git/refs/heads/A
-echo $BTIP > .git/refs/heads/B
-git symbolic-ref HEAD refs/heads/B
+test_expect_success 'post 1st pull setup' '
+       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 1st "B A" $((11*3))
+pull_to_client 2nd "B" $((64*3))
 
-add A11 $A10
+pull_to_client 3rd "A" $((1*3))
 
-prev=1; cur=2; while [ $cur -le 65 ]; do
-       add B$cur $(eval echo \$B$prev)
-       prev=$cur
-       cur=$(($cur+1))
-done
+test_expect_success 'clone shallow' '
+       git clone --depth 2 "file://$(pwd)/." shallow
+'
 
-pull_to_client 2nd "B" $((64*3))
+test_expect_success 'clone shallow object count' '
+       (
+               cd shallow &&
+               git count-objects -v
+       ) > count.shallow &&
+       grep "^in-pack: 18" count.shallow
+'
 
-pull_to_client 3rd "A" $((1*3)) # old fails
+test_expect_success 'clone shallow object count (part 2)' '
+       sed -e "/^in-pack:/d" -e "/^packs:/d" -e "/^size-pack:/d" \
+           -e "/: 0$/d" count.shallow > count_output &&
+       ! test -s count_output
+'
 
-test_expect_success "clone shallow" 'git clone --depth 2 "file://$(pwd)/." shallow'
+test_expect_success 'fsck in shallow repo' '
+       (
+               cd shallow &&
+               git fsck --full
+       )
+'
 
-(cd shallow; git count-objects -v) > count.shallow
+test_expect_success 'simple fetch in shallow repo' '
+       (
+               cd shallow &&
+               git fetch
+       )
+'
 
-test_expect_success "clone shallow object count" \
-       "test \"in-pack: 18\" = \"$(grep in-pack count.shallow)\""
+test_expect_success 'no changes expected' '
+       (
+               cd shallow &&
+               git count-objects -v
+       ) > count.shallow.2 &&
+       cmp count.shallow count.shallow.2
+'
 
-count_output () {
-       sed -e '/^in-pack:/d' -e '/^packs:/d' -e '/^size-pack:/d' -e '/: 0$/d' "$1"
-}
+test_expect_success 'fetch same depth in shallow repo' '
+       (
+               cd shallow &&
+               git fetch --depth=2
+       )
+'
 
-test_expect_success "clone shallow object count (part 2)" '
-       test -z "$(count_output count.shallow)"
+test_expect_success 'no changes expected' '
+       (
+               cd shallow &&
+               git count-objects -v
+       ) > count.shallow.3 &&
+       cmp count.shallow count.shallow.3
 '
 
-test_expect_success "fsck in shallow repo" \
-       "(cd shallow; git fsck --full)"
+test_expect_success 'add two more' '
+       add B66 $B65 &&
+       add B67 $B66
+'
 
-#test_done; exit
+test_expect_success 'pull in shallow repo' '
+       (
+               cd shallow &&
+               git pull .. B
+       )
+'
 
-add B66 $B65
-add B67 $B66
+test_expect_success 'clone shallow object count' '
+       (
+               cd shallow &&
+               git count-objects -v
+       ) > count.shallow &&
+       grep "^count: 6" count.shallow
+'
 
-test_expect_success "pull in shallow repo" \
-       "(cd shallow; git pull .. B)"
+test_expect_success 'add two more (part 2)' '
+       add B68 $B67 &&
+       add B69 $B68
+'
 
-(cd shallow; git count-objects -v) > count.shallow
-test_expect_success "clone shallow object count" \
-       "test \"count: 6\" = \"$(grep count count.shallow)\""
+test_expect_success 'deepening pull in shallow repo' '
+       (
+               cd shallow &&
+               git pull --depth 4 .. B
+       )
+'
 
-add B68 $B67
-add B69 $B68
+test_expect_success 'clone shallow object count' '
+       (
+               cd shallow &&
+               git count-objects -v
+       ) > count.shallow &&
+       grep "^count: 12" count.shallow
+'
 
-test_expect_success "deepening pull in shallow repo" \
-       "(cd shallow; git pull --depth 4 .. B)"
+test_expect_success 'deepening fetch in shallow repo' '
+       (
+               cd shallow &&
+               git fetch --depth 4 .. A:A
+       )
+'
 
-(cd shallow; git count-objects -v) > count.shallow
-test_expect_success "clone shallow object count" \
-       "test \"count: 12\" = \"$(grep count count.shallow)\""
+test_expect_success 'clone shallow object count' '
+       (
+               cd shallow &&
+               git count-objects -v
+       ) > count.shallow &&
+       grep "^count: 18" count.shallow
+'
 
-test_expect_success "deepening fetch in shallow repo" \
-       "(cd shallow; git fetch --depth 4 .. A:A)"
+test_expect_success 'pull in shallow repo with missing merge base' '
+       (
+               cd shallow &&
+               test_must_fail git pull --depth 4 .. A
+       )
+'
 
-(cd shallow; git count-objects -v) > count.shallow
-test_expect_success "clone shallow object count" \
-       "test \"count: 18\" = \"$(grep count count.shallow)\""
+test_expect_success 'additional simple shallow deepenings' '
+       (
+               cd shallow &&
+               git fetch --depth=8 &&
+               git fetch --depth=10 &&
+               git fetch --depth=11
+       )
+'
 
-test_expect_success "pull in shallow repo with missing merge base" \
-       "(cd shallow && test_must_fail git pull --depth 4 .. A)"
+test_expect_success 'clone shallow object count' '
+       (
+               cd shallow &&
+               git count-objects -v
+       ) > count.shallow &&
+       grep "^count: 52" count.shallow
+'
 
 test_done
index 16eadd6b68664884836976aafb6dcbb582603c09..1037a723fe74756f241346a077f4f3682dbbf45d 100755 (executable)
@@ -119,4 +119,24 @@ test_expect_success 'quickfetch should not copy from alternate' '
 
 '
 
+test_expect_success 'quickfetch should handle ~1000 refs (on Windows)' '
+
+       git gc &&
+       head=$(git rev-parse HEAD) &&
+       branchprefix="$head refs/heads/branch" &&
+       for i in 0 1 2 3 4 5 6 7 8 9; do
+               for j in 0 1 2 3 4 5 6 7 8 9; do
+                       for k in 0 1 2 3 4 5 6 7 8 9; do
+                               echo "$branchprefix$i$j$k" >> .git/packed-refs
+                       done
+               done
+       done &&
+       (
+               cd cloned &&
+               git fetch &&
+               git fetch
+       )
+
+'
+
 test_done
index d5db75d826c8584e1d7f0f5ef298021fecd6f055..8a298a655fa007fbd9a43d6212b53f749c980eb6 100755 (executable)
@@ -7,7 +7,10 @@ test_description='test automatic tag following'
 case $(uname -s) in
 *MINGW*)
        say "GIT_DEBUG_SEND_PACK not supported - skipping tests"
-       test_done
+       ;;
+*)
+       test_set_prereq NOT_MINGW
+       ;;
 esac
 
 # End state of the repository:
@@ -19,7 +22,7 @@ esac
 #     \   C - origin/cat    \
 #      origin/master         master
 
-test_expect_success setup '
+test_expect_success NOT_MINGW setup '
        test_tick &&
        echo ichi >file &&
        git add file &&
@@ -42,12 +45,15 @@ test_expect_success setup '
 
 U=UPLOAD_LOG
 
+test_expect_success NOT_MINGW 'setup expect' '
 cat - <<EOF >expect
 #S
 want $A
 #E
 EOF
-test_expect_success 'fetch A (new commit : 1 connection)' '
+'
+
+test_expect_success NOT_MINGW 'fetch A (new commit : 1 connection)' '
        rm -f $U
        (
                cd cloned &&
@@ -59,7 +65,7 @@ test_expect_success 'fetch A (new commit : 1 connection)' '
        test_cmp expect actual
 '
 
-test_expect_success "create tag T on A, create C on branch cat" '
+test_expect_success NOT_MINGW "create tag T on A, create C on branch cat" '
        git tag -a -m tag1 tag1 $A &&
        T=$(git rev-parse --verify tag1) &&
 
@@ -71,13 +77,16 @@ test_expect_success "create tag T on A, create C on branch cat" '
        git checkout master
 '
 
+test_expect_success NOT_MINGW 'setup expect' '
 cat - <<EOF >expect
 #S
 want $C
 want $T
 #E
 EOF
-test_expect_success 'fetch C, T (new branch, tag : 1 connection)' '
+'
+
+test_expect_success NOT_MINGW 'fetch C, T (new branch, tag : 1 connection)' '
        rm -f $U
        (
                cd cloned &&
@@ -91,7 +100,7 @@ test_expect_success 'fetch C, T (new branch, tag : 1 connection)' '
        test_cmp expect actual
 '
 
-test_expect_success "create commits O, B, tag S on B" '
+test_expect_success NOT_MINGW "create commits O, B, tag S on B" '
        test_tick &&
        echo O >file &&
        git add file &&
@@ -107,13 +116,16 @@ test_expect_success "create commits O, B, tag S on B" '
        S=$(git rev-parse --verify tag2)
 '
 
+test_expect_success NOT_MINGW 'setup expect' '
 cat - <<EOF >expect
 #S
 want $B
 want $S
 #E
 EOF
-test_expect_success 'fetch B, S (commit and tag : 1 connection)' '
+'
+
+test_expect_success NOT_MINGW 'fetch B, S (commit and tag : 1 connection)' '
        rm -f $U
        (
                cd cloned &&
@@ -127,13 +139,16 @@ test_expect_success 'fetch B, S (commit and tag : 1 connection)' '
        test_cmp expect actual
 '
 
+test_expect_success NOT_MINGW 'setup expect' '
 cat - <<EOF >expect
 #S
 want $B
 want $S
 #E
 EOF
-test_expect_success 'new clone fetch master and tags' '
+'
+
+test_expect_success NOT_MINGW 'new clone fetch master and tags' '
        git branch -D cat
        rm -f $U
        (
index 5ec668d6d8ac22f161549e5592a49bfdb0f11081..5d1c66ea7166b0b4d631b681f344e0eefb84d899 100755 (executable)
@@ -110,17 +110,18 @@ test_expect_success 'remove remote' '
 test_expect_success 'remove remote protects non-remote branches' '
 (
        cd test &&
-       (cat >expect1 <<EOF
+       cat >expect1 <<EOF
 Note: A non-remote branch was not removed; to delete it, use:
   git branch -d master
 EOF
-    cat >expect2 <<EOF
+       } &&
+       { cat >expect2 <<EOF
 Note: Non-remote branches were not removed; to delete them, use:
   git branch -d foobranch
   git branch -d master
 EOF
-) &&
-       git tag footag
+       } &&
+       git tag footag &&
        git config --add remote.oops.fetch "+refs/*:refs/*" &&
        git remote rm oops 2>actual1 &&
        git branch foobranch &&
@@ -135,7 +136,8 @@ EOF
 
 cat > test/expect << EOF
 * remote origin
-  URL: $(pwd)/one
+  Fetch URL: $(pwd)/one
+  Push  URL: $(pwd)/one
   HEAD branch: master
   Remote branches:
     master new (next fetch will store in remotes/origin)
@@ -151,12 +153,13 @@ cat > test/expect << EOF
     master pushes to master   (local out of date)
     master pushes to upstream (create)
 * remote two
-  URL: ../two
+  Fetch URL: ../two
+  Push  URL: ../three
   HEAD branch (remote HEAD is ambiguous, may be one of the following):
     another
     master
   Local refs configured for 'git push':
-    ahead  forces to master  (fast forwardable)
+    ahead  forces to master  (fast-forwardable)
     master pushes to another (up to date)
 EOF
 
@@ -173,6 +176,7 @@ test_expect_success 'show' '
         git branch --track rebase origin/master &&
         git branch -d -r origin/master &&
         git config --add remote.two.url ../two &&
+        git config --add remote.two.pushurl ../three &&
         git config branch.rebase.rebase true &&
         git config branch.octopus.merge "topic-a topic-b topic-c" &&
         (cd ../one &&
@@ -191,7 +195,8 @@ test_expect_success 'show' '
 
 cat > test/expect << EOF
 * remote origin
-  URL: $(pwd)/one
+  Fetch URL: $(pwd)/one
+  Push  URL: $(pwd)/one
   HEAD branch: (not queried)
   Remote branches: (status not queried)
     master
@@ -315,6 +320,69 @@ test_expect_success 'add alt && prune' '
         git rev-parse --verify refs/remotes/origin/side2)
 '
 
+cat >test/expect <<\EOF
+some-tag
+EOF
+
+test_expect_success 'add with reachable tags (default)' '
+       (cd one &&
+        >foobar &&
+        git add foobar &&
+        git commit -m "Foobar" &&
+        git tag -a -m "Foobar tag" foobar-tag &&
+        git reset --hard HEAD~1 &&
+        git tag -a -m "Some tag" some-tag) &&
+       (mkdir add-tags &&
+        cd add-tags &&
+        git init &&
+        git remote add -f origin ../one &&
+        git tag -l some-tag >../test/output &&
+        git tag -l foobar-tag >>../test/output &&
+        test_must_fail git config remote.origin.tagopt) &&
+       test_cmp test/expect test/output
+'
+
+cat >test/expect <<\EOF
+some-tag
+foobar-tag
+--tags
+EOF
+
+test_expect_success 'add --tags' '
+       (rm -rf add-tags &&
+        mkdir add-tags &&
+        cd add-tags &&
+        git init &&
+        git remote add -f --tags origin ../one &&
+        git tag -l some-tag >../test/output &&
+        git tag -l foobar-tag >>../test/output &&
+        git config remote.origin.tagopt >>../test/output) &&
+       test_cmp test/expect test/output
+'
+
+cat >test/expect <<\EOF
+--no-tags
+EOF
+
+test_expect_success 'add --no-tags' '
+       (rm -rf add-tags &&
+        mkdir add-no-tags &&
+        cd add-no-tags &&
+        git init &&
+        git remote add -f --no-tags origin ../one &&
+        git tag -l some-tag >../test/output &&
+        git tag -l foobar-tag >../test/output &&
+        git config remote.origin.tagopt >>../test/output) &&
+       (cd one &&
+        git tag -d some-tag foobar-tag) &&
+       test_cmp test/expect test/output
+'
+
+test_expect_success 'reject --no-no-tags' '
+       (cd add-no-tags &&
+        test_must_fail git remote add -f --no-no-tags neworigin ../one)
+'
+
 cat > one/expect << EOF
   apis/master
   apis/side
@@ -361,6 +429,17 @@ test_expect_success 'update with arguments' '
 
 '
 
+test_expect_success 'update --prune' '
+
+       (cd one &&
+        git branch -m side2 side3) &&
+       (cd test &&
+        git remote update --prune &&
+        (cd ../one && git branch -m side3 side2) &&
+        git rev-parse refs/remotes/origin/side3 &&
+        test_must_fail git rev-parse refs/remotes/origin/side2)
+'
+
 cat > one/expect << EOF
   apis/master
   apis/side
@@ -404,6 +483,20 @@ test_expect_success 'update default (overridden, with funny whitespace)' '
 
 '
 
+test_expect_success 'update (with remotes.default defined)' '
+
+       (cd one &&
+        for b in $(git branch -r)
+        do
+               git branch -r -d $b || break
+        done &&
+        git config remotes.default "drosophila" &&
+        git remote update &&
+        git branch -r > output &&
+        test_cmp expect output)
+
+'
+
 test_expect_success '"remote show" does not show symbolic refs' '
 
        git clone one three &&
@@ -478,15 +571,15 @@ test_expect_success 'remote prune to cause a dangling symref' '
        (
                cd seven &&
                git remote prune origin
-       ) 2>err &&
+       ) >err 2>&1 &&
        grep "has become dangling" err &&
 
-       : And the dangling symref will not cause other annoying errors
+       : And the dangling symref will not cause other annoying errors &&
        (
                cd seven &&
                git branch -a
        ) 2>err &&
-       ! grep "points nowhere" err
+       ! grep "points nowhere" err &&
        (
                cd seven &&
                test_must_fail git branch nomore origin
@@ -494,5 +587,281 @@ test_expect_success 'remote prune to cause a dangling symref' '
        grep "dangling symref" err
 '
 
-test_done
+test_expect_success 'show empty remote' '
 
+       test_create_repo empty &&
+       git clone empty empty-clone &&
+       (
+               cd empty-clone &&
+               git remote show origin
+       )
+'
+
+test_expect_success 'remote set-branches requires a remote' '
+       test_must_fail git remote set-branches &&
+       test_must_fail git remote set-branches --add
+'
+
+test_expect_success 'remote set-branches' '
+       echo "+refs/heads/*:refs/remotes/scratch/*" >expect.initial &&
+       sort <<-\EOF >expect.add &&
+       +refs/heads/*:refs/remotes/scratch/*
+       +refs/heads/other:refs/remotes/scratch/other
+       EOF
+       sort <<-\EOF >expect.replace &&
+       +refs/heads/maint:refs/remotes/scratch/maint
+       +refs/heads/master:refs/remotes/scratch/master
+       +refs/heads/next:refs/remotes/scratch/next
+       EOF
+       sort <<-\EOF >expect.add-two &&
+       +refs/heads/maint:refs/remotes/scratch/maint
+       +refs/heads/master:refs/remotes/scratch/master
+       +refs/heads/next:refs/remotes/scratch/next
+       +refs/heads/pu:refs/remotes/scratch/pu
+       +refs/heads/t/topic:refs/remotes/scratch/t/topic
+       EOF
+       sort <<-\EOF >expect.setup-ffonly &&
+       refs/heads/master:refs/remotes/scratch/master
+       +refs/heads/next:refs/remotes/scratch/next
+       EOF
+       sort <<-\EOF >expect.respect-ffonly &&
+       refs/heads/master:refs/remotes/scratch/master
+       +refs/heads/next:refs/remotes/scratch/next
+       +refs/heads/pu:refs/remotes/scratch/pu
+       EOF
+
+       git clone .git/ setbranches &&
+       (
+               cd setbranches &&
+               git remote rename origin scratch &&
+               git config --get-all remote.scratch.fetch >config-result &&
+               sort <config-result >../actual.initial &&
+
+               git remote set-branches scratch --add other &&
+               git config --get-all remote.scratch.fetch >config-result &&
+               sort <config-result >../actual.add &&
+
+               git remote set-branches scratch maint master next &&
+               git config --get-all remote.scratch.fetch >config-result &&
+               sort <config-result >../actual.replace &&
+
+               git remote set-branches --add scratch pu t/topic &&
+               git config --get-all remote.scratch.fetch >config-result &&
+               sort <config-result >../actual.add-two &&
+
+               git config --unset-all remote.scratch.fetch &&
+               git config remote.scratch.fetch \
+                       refs/heads/master:refs/remotes/scratch/master &&
+               git config --add remote.scratch.fetch \
+                       +refs/heads/next:refs/remotes/scratch/next &&
+               git config --get-all remote.scratch.fetch >config-result &&
+               sort <config-result >../actual.setup-ffonly &&
+
+               git remote set-branches --add scratch pu &&
+               git config --get-all remote.scratch.fetch >config-result &&
+               sort <config-result >../actual.respect-ffonly
+       ) &&
+       test_cmp expect.initial actual.initial &&
+       test_cmp expect.add actual.add &&
+       test_cmp expect.replace actual.replace &&
+       test_cmp expect.add-two actual.add-two &&
+       test_cmp expect.setup-ffonly actual.setup-ffonly &&
+       test_cmp expect.respect-ffonly actual.respect-ffonly
+'
+
+test_expect_success 'remote set-branches with --mirror' '
+       echo "+refs/*:refs/*" >expect.initial &&
+       echo "+refs/heads/master:refs/heads/master" >expect.replace &&
+       git clone --mirror .git/ setbranches-mirror &&
+       (
+               cd setbranches-mirror &&
+               git remote rename origin scratch &&
+               git config --get-all remote.scratch.fetch >../actual.initial &&
+
+               git remote set-branches scratch heads/master &&
+               git config --get-all remote.scratch.fetch >../actual.replace
+       ) &&
+       test_cmp expect.initial actual.initial &&
+       test_cmp expect.replace actual.replace
+'
+
+test_expect_success 'new remote' '
+       git remote add someremote foo &&
+       echo foo >expect &&
+       git config --get-all remote.someremote.url >actual &&
+       cmp expect actual
+'
+
+test_expect_success 'remote set-url bar' '
+       git remote set-url someremote bar &&
+       echo bar >expect &&
+       git config --get-all remote.someremote.url >actual &&
+       cmp expect actual
+'
+
+test_expect_success 'remote set-url baz bar' '
+       git remote set-url someremote baz bar &&
+       echo baz >expect &&
+       git config --get-all remote.someremote.url >actual &&
+       cmp expect actual
+'
+
+test_expect_success 'remote set-url zot bar' '
+       test_must_fail git remote set-url someremote zot bar &&
+       echo baz >expect &&
+       git config --get-all remote.someremote.url >actual &&
+       cmp expect actual
+'
+
+test_expect_success 'remote set-url --push zot baz' '
+       test_must_fail git remote set-url --push someremote zot baz &&
+       echo "YYY" >expect &&
+       echo baz >>expect &&
+       test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+'
+
+test_expect_success 'remote set-url --push zot' '
+       git remote set-url --push someremote zot &&
+       echo zot >expect &&
+       echo "YYY" >>expect &&
+       echo baz >>expect &&
+       git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+'
+
+test_expect_success 'remote set-url --push qux zot' '
+       git remote set-url --push someremote qux zot &&
+       echo qux >expect &&
+       echo "YYY" >>expect &&
+       echo baz >>expect &&
+       git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+'
+
+test_expect_success 'remote set-url --push foo qu+x' '
+       git remote set-url --push someremote foo qu+x &&
+       echo foo >expect &&
+       echo "YYY" >>expect &&
+       echo baz >>expect &&
+       git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+'
+
+test_expect_success 'remote set-url --push --add aaa' '
+       git remote set-url --push --add someremote aaa &&
+       echo foo >expect &&
+       echo aaa >>expect &&
+       echo "YYY" >>expect &&
+       echo baz >>expect &&
+       git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+'
+
+test_expect_success 'remote set-url --push bar aaa' '
+       git remote set-url --push someremote bar aaa &&
+       echo foo >expect &&
+       echo bar >>expect &&
+       echo "YYY" >>expect &&
+       echo baz >>expect &&
+       git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+'
+
+test_expect_success 'remote set-url --push --delete bar' '
+       git remote set-url --push --delete someremote bar &&
+       echo foo >expect &&
+       echo "YYY" >>expect &&
+       echo baz >>expect &&
+       git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+'
+
+test_expect_success 'remote set-url --push --delete foo' '
+       git remote set-url --push --delete someremote foo &&
+       echo "YYY" >expect &&
+       echo baz >>expect &&
+       test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+'
+
+test_expect_success 'remote set-url --add bbb' '
+       git remote set-url --add someremote bbb &&
+       echo "YYY" >expect &&
+       echo baz >>expect &&
+       echo bbb >>expect &&
+       test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+'
+
+test_expect_success 'remote set-url --delete .*' '
+       test_must_fail git remote set-url --delete someremote .\* &&
+       echo "YYY" >expect &&
+       echo baz >>expect &&
+       echo bbb >>expect &&
+       test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+'
+
+test_expect_success 'remote set-url --delete bbb' '
+       git remote set-url --delete someremote bbb &&
+       echo "YYY" >expect &&
+       echo baz >>expect &&
+       test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+'
+
+test_expect_success 'remote set-url --delete baz' '
+       test_must_fail git remote set-url --delete someremote baz &&
+       echo "YYY" >expect &&
+       echo baz >>expect &&
+       test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+'
+
+test_expect_success 'remote set-url --add ccc' '
+       git remote set-url --add someremote ccc &&
+       echo "YYY" >expect &&
+       echo baz >>expect &&
+       echo ccc >>expect &&
+       test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+'
+
+test_expect_success 'remote set-url --delete baz' '
+       git remote set-url --delete someremote baz &&
+       echo "YYY" >expect &&
+       echo ccc >>expect &&
+       test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+'
+
+test_done
index 2a1806b0b4427abb410d0310ba8c1ec52f8bde58..b7b7ddaa403d7a06c0203b89e484d56d20863f4a 100755 (executable)
@@ -51,7 +51,7 @@ test_expect_success 'nonexistant group produces error' '
        ! repo_fetched two
 '
 
-test_expect_success 'updating group updates all members' '
+test_expect_success 'updating group updates all members (remote update)' '
        mark group-all &&
        update_repos &&
        git config --add remotes.all one &&
@@ -61,7 +61,15 @@ test_expect_success 'updating group updates all members' '
        repo_fetched two
 '
 
-test_expect_success 'updating group does not update non-members' '
+test_expect_success 'updating group updates all members (fetch)' '
+       mark fetch-group-all &&
+       update_repos &&
+       git fetch all &&
+       repo_fetched one &&
+       repo_fetched two
+'
+
+test_expect_success 'updating group does not update non-members (remote update)' '
        mark group-some &&
        update_repos &&
        git config --add remotes.some one &&
@@ -70,6 +78,15 @@ test_expect_success 'updating group does not update non-members' '
        ! repo_fetched two
 '
 
+test_expect_success 'updating group does not update non-members (fetch)' '
+       mark fetch-group-some &&
+       update_repos &&
+       git config --add remotes.some one &&
+       git remote update some &&
+       repo_fetched one &&
+       ! repo_fetched two
+'
+
 test_expect_success 'updating remote name updates that remote' '
        mark remote-name &&
        update_repos &&
index bee3424fed770aacac6dbcdf4ba58bfd7bf5c2da..9a884751ec73ff721d658ebb69cbfe1f0270be85 100755 (executable)
@@ -9,6 +9,11 @@ test_description='Per branch config variables affects "git fetch".
 
 D=`pwd`
 
+test_bundle_object_count () {
+       git verify-pack -v "$1" >verify.out &&
+       test "$2" = $(grep '^[0-9a-f]\{40\} ' verify.out | wc -l)
+}
+
 test_expect_success setup '
        echo >file original &&
        git add file &&
@@ -16,27 +21,30 @@ test_expect_success setup '
 
 test_expect_success "clone and setup child repos" '
        git clone . one &&
-       cd one &&
-       echo >file updated by one &&
-       git commit -a -m "updated by one" &&
-       cd .. &&
+       (
+               cd one &&
+               echo >file updated by one &&
+               git commit -a -m "updated by one"
+       ) &&
        git clone . two &&
-       cd two &&
-       git config branch.master.remote one &&
-       git config remote.one.url ../one/.git/ &&
-       git config remote.one.fetch refs/heads/master:refs/heads/one &&
-       cd .. &&
+       (
+               cd two &&
+               git config branch.master.remote one &&
+               git config remote.one.url ../one/.git/ &&
+               git config remote.one.fetch refs/heads/master:refs/heads/one
+       ) &&
        git clone . three &&
-       cd three &&
-       git config branch.master.remote two &&
-       git config branch.master.merge refs/heads/one &&
-       mkdir -p .git/remotes &&
-       {
-               echo "URL: ../two/.git/"
-               echo "Pull: refs/heads/master:refs/heads/two"
-               echo "Pull: refs/heads/one:refs/heads/one"
-       } >.git/remotes/two &&
-       cd .. &&
+       (
+               cd three &&
+               git config branch.master.remote two &&
+               git config branch.master.merge refs/heads/one &&
+               mkdir -p .git/remotes &&
+               {
+                       echo "URL: ../two/.git/"
+                       echo "Pull: refs/heads/master:refs/heads/two"
+                       echo "Pull: refs/heads/one:refs/heads/one"
+               } >.git/remotes/two
+       ) &&
        git clone . bundle &&
        git clone . seven
 '
@@ -66,7 +74,7 @@ test_expect_success "fetch test for-merge" '
                echo "$one_in_two       "
        } >expected &&
        cut -f -2 .git/FETCH_HEAD >actual &&
-       diff expected actual'
+       test_cmp expected actual'
 
 test_expect_success 'fetch tags when there is no tags' '
 
@@ -146,6 +154,7 @@ test_expect_success 'unbundle 1' '
        test_must_fail git fetch "$D/bundle1" master:master
 '
 
+
 test_expect_success 'bundle 1 has only 3 files ' '
        cd "$D" &&
        (
@@ -156,8 +165,7 @@ test_expect_success 'bundle 1 has only 3 files ' '
                cat
        ) <bundle1 >bundle.pack &&
        git index-pack bundle.pack &&
-       verify=$(git verify-pack -v bundle.pack) &&
-       test 4 = $(echo "$verify" | wc -l)
+       test_bundle_object_count bundle.pack 3
 '
 
 test_expect_success 'unbundle 2' '
@@ -180,7 +188,7 @@ test_expect_success 'bundle does not prerequisite objects' '
                cat
        ) <bundle3 >bundle.pack &&
        git index-pack bundle.pack &&
-       test 4 = $(git verify-pack -v bundle.pack | wc -l)
+       test_bundle_object_count bundle.pack 3
 '
 
 test_expect_success 'bundle should be able to create a full history' '
@@ -235,6 +243,38 @@ test_expect_success 'fetch with a non-applying branch.<name>.merge' '
        git fetch blub
 '
 
+# URL supplied to fetch does not match the url of the configured branch's remote
+test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge [1]' '
+       one_head=$(cd one && git rev-parse HEAD) &&
+       this_head=$(git rev-parse HEAD) &&
+       git update-ref -d FETCH_HEAD &&
+       git fetch one &&
+       test $one_head = "$(git rev-parse --verify FETCH_HEAD)" &&
+       test $this_head = "$(git rev-parse --verify HEAD)"
+'
+
+# URL supplied to fetch matches the url of the configured branch's remote and
+# the merge spec matches the branch the remote HEAD points to
+test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge [2]' '
+       one_ref=$(cd one && git symbolic-ref HEAD) &&
+       git config branch.master.remote blub &&
+       git config branch.master.merge "$one_ref" &&
+       git update-ref -d FETCH_HEAD &&
+       git fetch one &&
+       test $one_head = "$(git rev-parse --verify FETCH_HEAD)" &&
+       test $this_head = "$(git rev-parse --verify HEAD)"
+'
+
+# URL supplied to fetch matches the url of the configured branch's remote, but
+# the merge spec does not match the branch the remote HEAD points to
+test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge [3]' '
+       git config branch.master.merge "${one_ref}_not" &&
+       git update-ref -d FETCH_HEAD &&
+       git fetch one &&
+       test $one_head = "$(git rev-parse --verify FETCH_HEAD)" &&
+       test $this_head = "$(git rev-parse --verify HEAD)"
+'
+
 # the strange name is: a\!'b
 test_expect_success 'quoting of a strangely named repo' '
        test_must_fail git fetch "a\\!'\''b" > result 2>&1 &&
@@ -336,4 +376,22 @@ test_expect_success 'fetch into the current branch with --update-head-ok' '
 
 '
 
+test_expect_success 'fetch --dry-run' '
+
+       rm -f .git/FETCH_HEAD &&
+       git fetch --dry-run . &&
+       ! test -f .git/FETCH_HEAD
+'
+
+test_expect_success "should be able to fetch with duplicate refspecs" '
+        mkdir dups &&
+        cd dups &&
+        git init &&
+        git config branch.master.remote three &&
+        git config remote.three.url ../three/.git &&
+        git config remote.three.fetch +refs/heads/*:refs/remotes/origin/* &&
+        git config --add remote.three.fetch +refs/heads/*:refs/remotes/origin/* &&
+        git fetch three
+'
+
 test_done
index 1dd8eed5bb3cb0f320a8f0780452e52fa7d8da16..d1912351db7da4a7c457fd44895b5ea076c84e7e 100755 (executable)
@@ -49,4 +49,78 @@ test_expect_success 'ls-remote self' '
 
 '
 
+test_expect_success 'dies when no remote specified and no default remotes found' '
+
+       test_must_fail git ls-remote
+
+'
+
+test_expect_success 'use "origin" when no remote specified' '
+
+       URL="$(pwd)/.git" &&
+       echo "From $URL" >exp_err &&
+
+       git remote add origin "$URL" &&
+       git ls-remote 2>actual_err >actual &&
+
+       test_cmp exp_err actual_err &&
+       test_cmp expected.all actual
+
+'
+
+test_expect_success 'suppress "From <url>" with -q' '
+
+       git ls-remote -q 2>actual_err &&
+       test_must_fail test_cmp exp_err actual_err
+
+'
+
+test_expect_success 'use branch.<name>.remote if possible' '
+
+       #
+       # Test that we are indeed using branch.<name>.remote, not "origin", even
+       # though the "origin" remote has been set.
+       #
+
+       # setup a new remote to differentiate from "origin"
+       git clone . other.git &&
+       (
+               cd other.git &&
+               echo "$(git rev-parse HEAD)     HEAD"
+               git show-ref    | sed -e "s/ /  /"
+       ) >exp &&
+
+       URL="other.git" &&
+       echo "From $URL" >exp_err &&
+
+       git remote add other $URL &&
+       git config branch.master.remote other &&
+
+       git ls-remote 2>actual_err >actual &&
+       test_cmp exp_err actual_err &&
+       test_cmp exp actual
+
+'
+
+cat >exp <<EOF
+fatal: 'refs*master' does not appear to be a git repository
+fatal: The remote end hung up unexpectedly
+EOF
+test_expect_success 'confuses pattern as remote when no remote specified' '
+       #
+       # Do not expect "git ls-remote <pattern>" to work; ls-remote, correctly,
+       # confuses <pattern> for <remote>. Although ugly, this behaviour is akin
+       # to the confusion of refspecs for remotes by git-fetch and git-push,
+       # eg:
+       #
+       #   $ git fetch branch
+       #
+
+       # We could just as easily have used "master"; the "*" emphasizes its
+       # role as a pattern.
+       test_must_fail git ls-remote refs*master >actual 2>&1 &&
+       test_cmp exp actual
+
+'
+
 test_done
diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh
new file mode 100755 (executable)
index 0000000..b737332
--- /dev/null
@@ -0,0 +1,154 @@
+#!/bin/sh
+
+test_description='fetch --all works correctly'
+
+. ./test-lib.sh
+
+setup_repository () {
+       mkdir "$1" && (
+       cd "$1" &&
+       git init &&
+       >file &&
+       git add file &&
+       test_tick &&
+       git commit -m "Initial" &&
+       git checkout -b side &&
+       >elif &&
+       git add elif &&
+       test_tick &&
+       git commit -m "Second" &&
+       git checkout master
+       )
+}
+
+test_expect_success setup '
+       setup_repository one &&
+       setup_repository two &&
+       (
+               cd two && git branch another
+       ) &&
+       git clone --mirror two three
+       git clone one test
+'
+
+cat > test/expect << EOF
+  one/master
+  one/side
+  origin/HEAD -> origin/master
+  origin/master
+  origin/side
+  three/another
+  three/master
+  three/side
+  two/another
+  two/master
+  two/side
+EOF
+
+test_expect_success 'git fetch --all' '
+       (cd test &&
+        git remote add one ../one &&
+        git remote add two ../two &&
+        git remote add three ../three &&
+        git fetch --all &&
+        git branch -r > output &&
+        test_cmp expect output)
+'
+
+test_expect_success 'git fetch --all should continue if a remote has errors' '
+       (git clone one test2 &&
+        cd test2 &&
+        git remote add bad ../non-existing &&
+        git remote add one ../one &&
+        git remote add two ../two &&
+        git remote add three ../three &&
+        test_must_fail git fetch --all &&
+        git branch -r > output &&
+        test_cmp ../test/expect output)
+'
+
+test_expect_success 'git fetch --all does not allow non-option arguments' '
+       (cd test &&
+        test_must_fail git fetch --all origin &&
+        test_must_fail git fetch --all origin master)
+'
+
+cat > expect << EOF
+  origin/HEAD -> origin/master
+  origin/master
+  origin/side
+  three/another
+  three/master
+  three/side
+EOF
+
+test_expect_success 'git fetch --multiple (but only one remote)' '
+       (git clone one test3 &&
+        cd test3 &&
+        git remote add three ../three &&
+        git fetch --multiple three &&
+        git branch -r > output &&
+        test_cmp ../expect output)
+'
+
+cat > expect << EOF
+  one/master
+  one/side
+  two/another
+  two/master
+  two/side
+EOF
+
+test_expect_success 'git fetch --multiple (two remotes)' '
+       (git clone one test4 &&
+        cd test4 &&
+        git remote rm origin &&
+        git remote add one ../one &&
+        git remote add two ../two &&
+        git fetch --multiple one two &&
+        git branch -r > output &&
+        test_cmp ../expect output)
+'
+
+test_expect_success 'git fetch --multiple (bad remote names)' '
+       (cd test4 &&
+        test_must_fail git fetch --multiple four)
+'
+
+
+test_expect_success 'git fetch --all (skipFetchAll)' '
+       (cd test4 &&
+        for b in $(git branch -r)
+        do
+               git branch -r -d $b || break
+        done &&
+        git remote add three ../three &&
+        git config remote.three.skipFetchAll true &&
+        git fetch --all &&
+        git branch -r > output &&
+        test_cmp ../expect output)
+'
+
+cat > expect << EOF
+  one/master
+  one/side
+  three/another
+  three/master
+  three/side
+  two/another
+  two/master
+  two/side
+EOF
+
+test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' '
+       (cd test4 &&
+        for b in $(git branch -r)
+        do
+               git branch -r -d $b || break
+        done &&
+        git fetch --multiple one two three &&
+        git branch -r > output &&
+        test_cmp ../expect output)
+'
+
+test_done
index 89649e7a9b2dc138bb9618d4e5770e76f62dc7e0..b11da79c9cafebb5af572bd8e9f85dfc6f3c3f77 100755 (executable)
@@ -12,6 +12,7 @@ mk_empty () {
        (
                cd testrepo &&
                git init &&
+               git config receive.denyCurrentBranch warn &&
                mv .git/hooks .git/hooks-disabled
        )
 }
@@ -63,13 +64,13 @@ check_push_result () {
 
 test_expect_success setup '
 
-       >path1 &&
+       >path1 &&
        git add path1 &&
        test_tick &&
        git commit -a -m repo &&
        the_first_commit=$(git show-ref -s --verify refs/heads/master) &&
 
-       >path2 &&
+       >path2 &&
        git add path2 &&
        test_tick &&
        git commit -a -m second &&
@@ -122,6 +123,23 @@ test_expect_success 'fetch with insteadOf' '
        )
 '
 
+test_expect_success 'fetch with pushInsteadOf (should not rewrite)' '
+       mk_empty &&
+       (
+               TRASH=$(pwd)/ &&
+               cd testrepo &&
+               git config "url.trash/.pushInsteadOf" "$TRASH" &&
+               git config remote.up.url "$TRASH." &&
+               git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" &&
+               git fetch up &&
+
+               r=$(git show-ref -s --verify refs/remotes/origin/master) &&
+               test "z$r" = "z$the_commit" &&
+
+               test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+       )
+'
+
 test_expect_success 'push without wildcard' '
        mk_empty &&
 
@@ -162,6 +180,36 @@ test_expect_success 'push with insteadOf' '
        )
 '
 
+test_expect_success 'push with pushInsteadOf' '
+       mk_empty &&
+       TRASH="$(pwd)/" &&
+       git config "url.$TRASH.pushInsteadOf" trash/ &&
+       git push trash/testrepo refs/heads/master:refs/remotes/origin/master &&
+       (
+               cd testrepo &&
+               r=$(git show-ref -s --verify refs/remotes/origin/master) &&
+               test "z$r" = "z$the_commit" &&
+
+               test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+       )
+'
+
+test_expect_success 'push with pushInsteadOf and explicit pushurl (pushInsteadOf should not rewrite)' '
+       mk_empty &&
+       TRASH="$(pwd)/" &&
+       git config "url.trash2/.pushInsteadOf" trash/ &&
+       git config remote.r.url trash/wrong &&
+       git config remote.r.pushurl "$TRASH/testrepo" &&
+       git push r refs/heads/master:refs/remotes/origin/master &&
+       (
+               cd testrepo &&
+               r=$(git show-ref -s --verify refs/remotes/origin/master) &&
+               test "z$r" = "z$the_commit" &&
+
+               test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+       )
+'
+
 test_expect_success 'push with matching heads' '
 
        mk_test heads/master &&
@@ -419,11 +467,26 @@ test_expect_success 'push with config remote.*.push = HEAD' '
 git config --remove-section remote.there
 git config --remove-section branch.master
 
+test_expect_success 'push with config remote.*.pushurl' '
+
+       mk_test heads/master &&
+       git checkout master &&
+       git config remote.there.url test2repo &&
+       git config remote.there.pushurl testrepo &&
+       git push there &&
+       check_push_result $the_commit heads/master
+'
+
+# clean up the cruft left with the previous one
+git config --remove-section remote.there
+
 test_expect_success 'push with dry-run' '
 
        mk_test heads/master &&
-       (cd testrepo &&
-        old_commit=$(git show-ref -s --verify refs/heads/master)) &&
+       (
+               cd testrepo &&
+               old_commit=$(git show-ref -s --verify refs/heads/master)
+       ) &&
        git push --dry-run testrepo &&
        check_push_result $old_commit heads/master
 '
@@ -432,10 +495,13 @@ test_expect_success 'push updates local refs' '
 
        mk_test heads/master &&
        mk_child child &&
-       (cd child &&
+       (
+               cd child &&
                git pull .. master &&
                git push &&
-       test $(git rev-parse master) = $(git rev-parse remotes/origin/master))
+               test $(git rev-parse master) = \
+                       $(git rev-parse remotes/origin/master)
+       )
 
 '
 
@@ -445,10 +511,13 @@ test_expect_success 'push updates up-to-date local refs' '
        mk_child child1 &&
        mk_child child2 &&
        (cd child1 && git pull .. master && git push) &&
-       (cd child2 &&
+       (
+               cd child2 &&
                git pull ../child1 master &&
                git push &&
-       test $(git rev-parse master) = $(git rev-parse remotes/origin/master))
+               test $(git rev-parse master) = \
+                       $(git rev-parse remotes/origin/master)
+       )
 
 '
 
@@ -456,9 +525,11 @@ test_expect_success 'push preserves up-to-date packed refs' '
 
        mk_test heads/master &&
        mk_child child &&
-       (cd child &&
+       (
+               cd child &&
                git push &&
-       ! test -f .git/refs/remotes/origin/master)
+               ! test -f .git/refs/remotes/origin/master
+       )
 
 '
 
@@ -467,13 +538,15 @@ test_expect_success 'push does not update local refs on failure' '
        mk_test heads/master &&
        mk_child child &&
        mkdir testrepo/.git/hooks &&
-       echo exit 1 >testrepo/.git/hooks/pre-receive &&
+       echo "#!/no/frobnication/today" >testrepo/.git/hooks/pre-receive &&
        chmod +x testrepo/.git/hooks/pre-receive &&
-       (cd child &&
+       (
+               cd child &&
                git pull .. master
                test_must_fail git push &&
                test $(git rev-parse master) != \
-                       $(git rev-parse remotes/origin/master))
+                       $(git rev-parse remotes/origin/master)
+       )
 
 '
 
@@ -486,36 +559,69 @@ test_expect_success 'allow deleting an invalid remote ref' '
 
 '
 
+test_expect_success 'allow deleting a ref using --delete' '
+       mk_test heads/master &&
+       (cd testrepo && git config receive.denyDeleteCurrent warn) &&
+       git push testrepo --delete master &&
+       (cd testrepo && test_must_fail git rev-parse --verify refs/heads/master)
+'
+
+test_expect_success 'allow deleting a tag using --delete' '
+       mk_test heads/master &&
+       git tag -a -m dummy_message deltag heads/master &&
+       git push testrepo --tags &&
+       (cd testrepo && git rev-parse --verify -q refs/tags/deltag) &&
+       git push testrepo --delete tag deltag &&
+       (cd testrepo && test_must_fail git rev-parse --verify refs/tags/deltag)
+'
+
+test_expect_success 'push --delete without args aborts' '
+       mk_test heads/master &&
+       test_must_fail git push testrepo --delete
+'
+
+test_expect_success 'push --delete refuses src:dest refspecs' '
+       mk_test heads/master &&
+       test_must_fail git push testrepo --delete master:foo
+'
+
 test_expect_success 'warn on push to HEAD of non-bare repository' '
        mk_test heads/master
-       (cd testrepo &&
+       (
+               cd testrepo &&
                git checkout master &&
-               git config receive.denyCurrentBranch warn) &&
+               git config receive.denyCurrentBranch warn
+       ) &&
        git push testrepo master 2>stderr &&
        grep "warning: updating the current branch" stderr
 '
 
 test_expect_success 'deny push to HEAD of non-bare repository' '
        mk_test heads/master
-       (cd testrepo &&
+       (
+               cd testrepo &&
                git checkout master &&
-               git config receive.denyCurrentBranch true) &&
+               git config receive.denyCurrentBranch true
+       ) &&
        test_must_fail git push testrepo master
 '
 
 test_expect_success 'allow push to HEAD of bare repository (bare)' '
        mk_test heads/master
-       (cd testrepo &&
+       (
+               cd testrepo &&
                git checkout master &&
                git config receive.denyCurrentBranch true &&
-               git config core.bare true) &&
+               git config core.bare true
+       ) &&
        git push testrepo master 2>stderr &&
        ! grep "warning: updating the current branch" stderr
 '
 
 test_expect_success 'allow push to HEAD of non-bare repository (config)' '
        mk_test heads/master
-       (cd testrepo &&
+       (
+               cd testrepo &&
                git checkout master &&
                git config receive.denyCurrentBranch false
        ) &&
@@ -528,7 +634,8 @@ test_expect_success 'fetch with branches' '
        git branch second $the_first_commit &&
        git checkout second &&
        echo ".." > testrepo/.git/branches/branch1 &&
-       (cd testrepo &&
+       (
+               cd testrepo &&
                git fetch branch1 &&
                r=$(git show-ref -s --verify refs/heads/branch1) &&
                test "z$r" = "z$the_commit" &&
@@ -540,7 +647,8 @@ test_expect_success 'fetch with branches' '
 test_expect_success 'fetch with branches containing #' '
        mk_empty &&
        echo "..#second" > testrepo/.git/branches/branch2 &&
-       (cd testrepo &&
+       (
+               cd testrepo &&
                git fetch branch2 &&
                r=$(git show-ref -s --verify refs/heads/branch2) &&
                test "z$r" = "z$the_first_commit" &&
@@ -554,7 +662,8 @@ test_expect_success 'push with branches' '
        git checkout second &&
        echo "testrepo" > .git/branches/branch1 &&
        git push branch1 &&
-       (cd testrepo &&
+       (
+               cd testrepo &&
                r=$(git show-ref -s --verify refs/heads/master) &&
                test "z$r" = "z$the_first_commit" &&
                test 1 = $(git for-each-ref refs/heads | wc -l)
@@ -565,7 +674,8 @@ test_expect_success 'push with branches containing #' '
        mk_empty &&
        echo "testrepo#branch3" > .git/branches/branch2 &&
        git push branch2 &&
-       (cd testrepo &&
+       (
+               cd testrepo &&
                r=$(git show-ref -s --verify refs/heads/branch3) &&
                test "z$r" = "z$the_first_commit" &&
                test 1 = $(git for-each-ref refs/heads | wc -l)
@@ -573,4 +683,103 @@ test_expect_success 'push with branches containing #' '
        git checkout master
 '
 
+test_expect_success 'push into aliased refs (consistent)' '
+       mk_test heads/master &&
+       mk_child child1 &&
+       mk_child child2 &&
+       (
+               cd child1 &&
+               git branch foo &&
+               git symbolic-ref refs/heads/bar refs/heads/foo
+               git config receive.denyCurrentBranch false
+       ) &&
+       (
+               cd child2 &&
+               >path2 &&
+               git add path2 &&
+               test_tick &&
+               git commit -a -m child2 &&
+               git branch foo &&
+               git branch bar &&
+               git push ../child1 foo bar
+       )
+'
+
+test_expect_success 'push into aliased refs (inconsistent)' '
+       mk_test heads/master &&
+       mk_child child1 &&
+       mk_child child2 &&
+       (
+               cd child1 &&
+               git branch foo &&
+               git symbolic-ref refs/heads/bar refs/heads/foo
+               git config receive.denyCurrentBranch false
+       ) &&
+       (
+               cd child2 &&
+               >path2 &&
+               git add path2 &&
+               test_tick &&
+               git commit -a -m child2 &&
+               git branch foo &&
+               >path3 &&
+               git add path3 &&
+               test_tick &&
+               git commit -a -m child2 &&
+               git branch bar &&
+               test_must_fail git push ../child1 foo bar 2>stderr &&
+               grep "refusing inconsistent update" stderr
+       )
+'
+
+test_expect_success 'push --porcelain' '
+       mk_empty &&
+       echo >.git/foo  "To testrepo" &&
+       echo >>.git/foo "*      refs/heads/master:refs/remotes/origin/master    [new branch]"  &&
+       echo >>.git/foo "Done" &&
+       git push >.git/bar --porcelain  testrepo refs/heads/master:refs/remotes/origin/master &&
+       (
+               cd testrepo &&
+               r=$(git show-ref -s --verify refs/remotes/origin/master) &&
+               test "z$r" = "z$the_commit" &&
+               test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+       ) &&
+       test_cmp .git/foo .git/bar
+'
+
+test_expect_success 'push --porcelain bad url' '
+       mk_empty &&
+       test_must_fail git push >.git/bar --porcelain asdfasdfasd refs/heads/master:refs/remotes/origin/master &&
+       test_must_fail grep -q Done .git/bar
+'
+
+test_expect_success 'push --porcelain rejected' '
+       mk_empty &&
+       git push testrepo refs/heads/master:refs/remotes/origin/master &&
+       (cd testrepo &&
+               git reset --hard origin/master^
+               git config receive.denyCurrentBranch true) &&
+
+       echo >.git/foo  "To testrepo"  &&
+       echo >>.git/foo "!      refs/heads/master:refs/heads/master     [remote rejected] (branch is currently checked out)" &&
+
+       test_must_fail git push >.git/bar --porcelain  testrepo refs/heads/master:refs/heads/master &&
+       test_cmp .git/foo .git/bar
+'
+
+test_expect_success 'push --porcelain --dry-run rejected' '
+       mk_empty &&
+       git push testrepo refs/heads/master:refs/remotes/origin/master &&
+       (cd testrepo &&
+               git reset --hard origin/master
+               git config receive.denyCurrentBranch true) &&
+
+       echo >.git/foo  "To testrepo"  &&
+       echo >>.git/foo "!      refs/heads/master^:refs/heads/master    [rejected] (non-fast-forward)" &&
+       echo >>.git/foo "Done" &&
+
+       test_must_fail git push >.git/bar --porcelain  --dry-run testrepo refs/heads/master^:refs/heads/master &&
+       test_cmp .git/foo .git/bar
+'
+
 test_done
index ea49dedbf8867694d83cd550c8212ff107361920..e2ad2605084194868fc23e23fec1636af622b47f 100755 (executable)
@@ -19,7 +19,8 @@ mk_repo_pair () {
        mkdir mirror &&
        (
                cd mirror &&
-               git init
+               git init &&
+               git config receive.denyCurrentBranch warn
        ) &&
        mkdir master &&
        (
index c6bc65faa06adeaced0733064fb09eb82add585e..c2060bb870f35a3819fabb19c97e921e0ae43349 100755 (executable)
@@ -22,7 +22,7 @@ test_expect_success setup '
        git commit -a -m next
 '
 
-test_expect_success 'non fast forward fetch' '
+test_expect_success 'non-fast-forward fetch' '
 
        test_must_fail git fetch . master:side
 
index 725771fac167ea5aac8cf65b916c96918b0f5e0d..0b489f5b1227268c050c1256d105d57d871f5698 100755 (executable)
@@ -4,6 +4,11 @@ test_description='pulling into void'
 
 . ./test-lib.sh
 
+modify () {
+       sed -e "$1" <"$2" >"$2.x" &&
+       mv "$2.x" "$2"
+}
+
 D=`pwd`
 
 test_expect_success setup '
@@ -26,7 +31,7 @@ cd "$D"
 test_expect_success 'checking the results' '
        test -f file &&
        test -f cloned/file &&
-       diff file cloned/file
+       test_cmp file cloned/file
 '
 
 test_expect_success 'pulling into void using master:master' '
@@ -92,20 +97,47 @@ test_expect_success '--rebase with rebased upstream' '
 
        git remote add -f me . &&
        git checkout copy &&
+       git tag copy-orig &&
        git reset --hard HEAD^ &&
        echo conflicting modification > file &&
        git commit -m conflict file &&
        git checkout to-rebase &&
        echo file > file2 &&
        git commit -m to-rebase file2 &&
+       git tag to-rebase-orig &&
        git pull --rebase me copy &&
        test "conflicting modification" = "$(cat file)" &&
        test file = $(cat file2)
 
 '
 
+test_expect_success '--rebase with rebased default upstream' '
+
+       git update-ref refs/remotes/me/copy copy-orig &&
+       git checkout --track -b to-rebase2 me/copy &&
+       git reset --hard to-rebase-orig &&
+       git pull --rebase &&
+       test "conflicting modification" = "$(cat file)" &&
+       test file = $(cat file2)
+
+'
+
+test_expect_success 'rebased upstream + fetch + pull --rebase' '
+
+       git update-ref refs/remotes/me/copy copy-orig &&
+       git reset --hard to-rebase-orig &&
+       git checkout --track -b to-rebase3 me/copy &&
+       git reset --hard to-rebase-orig &&
+       git fetch &&
+       git pull --rebase &&
+       test "conflicting modification" = "$(cat file)" &&
+       test file = "$(cat file2)"
+
+'
+
 test_expect_success 'pull --rebase dies early with dirty working directory' '
 
+       git checkout to-rebase &&
        git update-ref refs/remotes/me/copy copy^ &&
        COPY=$(git rev-parse --verify me/copy) &&
        git rebase --onto $COPY copy &&
@@ -122,4 +154,72 @@ test_expect_success 'pull --rebase dies early with dirty working directory' '
 
 '
 
+test_expect_success 'pull --rebase works on branch yet to be born' '
+       git rev-parse master >expect &&
+       mkdir empty_repo &&
+       (cd empty_repo &&
+        git init &&
+        git pull --rebase .. master &&
+        git rev-parse HEAD >../actual
+       ) &&
+       test_cmp expect actual
+'
+
+test_expect_success 'setup for detecting upstreamed changes' '
+       mkdir src &&
+       (cd src &&
+        git init &&
+        printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" > stuff &&
+        git add stuff &&
+        git commit -m "Initial revision"
+       ) &&
+       git clone src dst &&
+       (cd src &&
+        modify s/5/43/ stuff &&
+        git commit -a -m "5->43" &&
+        modify s/6/42/ stuff &&
+        git commit -a -m "Make it bigger"
+       ) &&
+       (cd dst &&
+        modify s/5/43/ stuff &&
+        git commit -a -m "Independent discovery of 5->43"
+       )
+'
+
+test_expect_success 'git pull --rebase detects upstreamed changes' '
+       (cd dst &&
+        git pull --rebase &&
+        test -z "$(git ls-files -u)"
+       )
+'
+
+test_expect_success 'setup for avoiding reapplying old patches' '
+       (cd dst &&
+        test_might_fail git rebase --abort &&
+        git reset --hard origin/master
+       ) &&
+       git clone --bare src src-replace.git &&
+       rm -rf src &&
+       mv src-replace.git src &&
+       (cd dst &&
+        modify s/2/22/ stuff &&
+        git commit -a -m "Change 2" &&
+        modify s/3/33/ stuff &&
+        git commit -a -m "Change 3" &&
+        modify s/4/44/ stuff &&
+        git commit -a -m "Change 4" &&
+        git push &&
+
+        modify s/44/55/ stuff &&
+        git commit --amend -a -m "Modified Change 4"
+       )
+'
+
+test_expect_success 'git pull --rebase does not reapply old patches' '
+       (cd dst &&
+        test_must_fail git pull --rebase &&
+        test 1 = $(find .git/rebase-apply -name "000*" | wc -l)
+       )
+'
+
 test_done
index 83e2e8ab800a2f664085b7350272727748512e42..1b06691bb41586b3bc564841b720ce710f927a1c 100755 (executable)
@@ -4,8 +4,6 @@ test_description='pull options'
 
 . ./test-lib.sh
 
-D=`pwd`
-
 test_expect_success 'setup' '
        mkdir parent &&
        (cd parent && git init &&
@@ -13,48 +11,83 @@ test_expect_success 'setup' '
         git commit -m one)
 '
 
-cd "$D"
-
 test_expect_success 'git pull -q' '
        mkdir clonedq &&
-       cd clonedq &&
-       git pull -q "$D/parent" >out 2>err &&
-       test ! -s out
+       (cd clonedq && git init &&
+       git pull -q "../parent" >out 2>err &&
+       test ! -s err &&
+       test ! -s out)
 '
 
-cd "$D"
-
 test_expect_success 'git pull' '
        mkdir cloned &&
-       cd cloned &&
-       git pull "$D/parent" >out 2>err &&
-       test -s out
+       (cd cloned && git init &&
+       git pull "../parent" >out 2>err &&
+       test -s err &&
+       test ! -s out)
 '
-cd "$D"
 
 test_expect_success 'git pull -v' '
        mkdir clonedv &&
-       cd clonedv &&
-       git pull -v "$D/parent" >out 2>err &&
-       test -s out
+       (cd clonedv && git init &&
+       git pull -v "../parent" >out 2>err &&
+       test -s err &&
+       test ! -s out)
 '
 
-cd "$D"
-
 test_expect_success 'git pull -v -q' '
        mkdir clonedvq &&
-       cd clonedvq &&
-       git pull -v -q "$D/parent" >out 2>err &&
-       test ! -s out
+       (cd clonedvq && git init &&
+       git pull -v -q "../parent" >out 2>err &&
+       test ! -s out &&
+       test ! -s err)
 '
 
-cd "$D"
-
 test_expect_success 'git pull -q -v' '
        mkdir clonedqv &&
-       cd clonedqv &&
-       git pull -q -v "$D/parent" >out 2>err &&
-       test -s out
+       (cd clonedqv && git init &&
+       git pull -q -v "../parent" >out 2>err &&
+       test ! -s out &&
+       test -s err)
+'
+
+test_expect_success 'git pull --force' '
+       mkdir clonedoldstyle &&
+       (cd clonedoldstyle && git init &&
+       cat >>.git/config <<-\EOF &&
+       [remote "one"]
+               url = ../parent
+               fetch = refs/heads/master:refs/heads/mirror
+       [remote "two"]
+               url = ../parent
+               fetch = refs/heads/master:refs/heads/origin
+       [branch "master"]
+               remote = two
+               merge = refs/heads/master
+       EOF
+       git pull two &&
+       test_commit A &&
+       git branch -f origin &&
+       git pull --all --force
+       )
+'
+
+test_expect_success 'git pull --all' '
+       mkdir clonedmulti &&
+       (cd clonedmulti && git init &&
+       cat >>.git/config <<-\EOF &&
+       [remote "one"]
+               url = ../parent
+               fetch = refs/heads/*:refs/remotes/one/*
+       [remote "two"]
+               url = ../parent
+               fetch = refs/heads/*:refs/remotes/two/*
+       [branch "master"]
+               remote = one
+               merge = refs/heads/master
+       EOF
+       git pull --all
+       )
 '
 
 test_done
index 86bbd7d024ff6b1ee775ec737c550d54f9371ad7..8e9b204e02a3d204af08463d2229ca0f319cdc42 100755 (executable)
@@ -4,12 +4,6 @@ test_description='pulling from symlinked subdir'
 
 . ./test-lib.sh
 
-if ! test_have_prereq SYMLINKS
-then
-       say 'Symbolic links not supported, skipping tests.'
-       test_done
-fi
-
 # The scenario we are building:
 #
 #   trash\ directory/
@@ -20,17 +14,23 @@ fi
 #
 # The working directory is subdir-link.
 
-mkdir subdir
-echo file >subdir/file
-git add subdir/file
-git commit -q -m file
-git clone -q . clone-repo
-ln -s clone-repo/subdir/ subdir-link
-
+test_expect_success SYMLINKS setup '
+       mkdir subdir &&
+       echo file >subdir/file &&
+       git add subdir/file &&
+       git commit -q -m file &&
+       git clone -q . clone-repo &&
+       ln -s clone-repo/subdir/ subdir-link &&
+       (
+               cd clone-repo &&
+               git config receive.denyCurrentBranch warn
+       ) &&
+       git config receive.denyCurrentBranch warn
+'
 
 # Demonstrate that things work if we just avoid the symlink
 #
-test_expect_success 'pulling from real subdir' '
+test_expect_success SYMLINKS 'pulling from real subdir' '
        (
                echo real >subdir/file &&
                git commit -m real subdir/file &&
@@ -58,7 +58,7 @@ test_expect_success 'pulling from real subdir' '
 # directory.  A POSIX shell's "cd" works a little differently
 # than chdir() in C; "cd -P" is much closer to chdir().
 #
-test_expect_success 'pulling from symlinked subdir' '
+test_expect_success SYMLINKS 'pulling from symlinked subdir' '
        (
                echo link >subdir/file &&
                git commit -m link subdir/file &&
@@ -71,7 +71,7 @@ test_expect_success 'pulling from symlinked subdir' '
 # Prove that the remote end really is a repo, and other commands
 # work fine in this context.  It's just that "git pull" breaks.
 #
-test_expect_success 'pushing from symlinked subdir' '
+test_expect_success SYMLINKS 'pushing from symlinked subdir' '
        (
                cd subdir-link/ &&
                echo push >file &&
diff --git a/t/t5523-push-upstream.sh b/t/t5523-push-upstream.sh
new file mode 100755 (executable)
index 0000000..00da707
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+test_description='push with --set-upstream'
+. ./test-lib.sh
+
+test_expect_success 'setup bare parent' '
+       git init --bare parent &&
+       git remote add upstream parent
+'
+
+test_expect_success 'setup local commit' '
+       echo content >file &&
+       git add file &&
+       git commit -m one
+'
+
+check_config() {
+       (echo $2; echo $3) >expect.$1
+       (git config branch.$1.remote
+        git config branch.$1.merge) >actual.$1
+       test_cmp expect.$1 actual.$1
+}
+
+test_expect_success 'push -u master:master' '
+       git push -u upstream master:master &&
+       check_config master upstream refs/heads/master
+'
+
+test_expect_success 'push -u master:other' '
+       git push -u upstream master:other &&
+       check_config master upstream refs/heads/other
+'
+
+test_expect_success 'push -u --dry-run master:otherX' '
+       git push -u --dry-run upstream master:otherX &&
+       check_config master upstream refs/heads/other
+'
+
+test_expect_success 'push -u master2:master2' '
+       git branch master2 &&
+       git push -u upstream master2:master2 &&
+       check_config master2 upstream refs/heads/master2
+'
+
+test_expect_success 'push -u master2:other2' '
+       git push -u upstream master2:other2 &&
+       check_config master2 upstream refs/heads/other2
+'
+
+test_expect_success 'push -u :master2' '
+       git push -u upstream :master2 &&
+       check_config master2 upstream refs/heads/other2
+'
+
+test_expect_success 'push -u --all' '
+       git branch all1 &&
+       git branch all2 &&
+       git push -u --all &&
+       check_config all1 upstream refs/heads/all1 &&
+       check_config all2 upstream refs/heads/all2
+'
+
+test_expect_success 'push -u HEAD' '
+       git checkout -b headbranch &&
+       git push -u upstream HEAD &&
+       check_config headbranch upstream refs/heads/headbranch
+'
+
+test_done
diff --git a/t/t5524-pull-msg.sh b/t/t5524-pull-msg.sh
new file mode 100755 (executable)
index 0000000..8cccecc
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+test_description='git pull message generation'
+
+. ./test-lib.sh
+
+dollar='$Dollar'
+
+test_expect_success setup '
+       test_commit initial afile original &&
+       git clone . cloned &&
+       (
+               cd cloned &&
+               echo added >bfile &&
+               git add bfile &&
+               test_tick &&
+               git commit -m "add bfile"
+       ) &&
+       test_tick && test_tick &&
+       echo "original $dollar" >afile &&
+       git add afile &&
+       git commit -m "do not clobber $dollar signs"
+'
+
+test_expect_success pull '
+(
+       cd cloned &&
+       git pull --log &&
+       git log -2 &&
+       git cat-file commit HEAD >result &&
+       grep Dollar result
+)
+'
+
+test_done
diff --git a/t/t5525-fetch-tagopt.sh b/t/t5525-fetch-tagopt.sh
new file mode 100755 (executable)
index 0000000..4fbf7a1
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+test_description='tagopt variable affects "git fetch" and is overridden by commandline.'
+
+. ./test-lib.sh
+
+setup_clone () {
+       git clone --mirror . $1 &&
+       git remote add remote_$1 $1 &&
+       (cd $1 &&
+       git tag tag_$1)
+}
+
+test_expect_success setup '
+       test_commit test &&
+       setup_clone one &&
+       git config remote.remote_one.tagopt --no-tags &&
+       setup_clone two &&
+       git config remote.remote_two.tagopt --tags
+       '
+
+test_expect_success "fetch with tagopt=--no-tags does not get tag" '
+       git fetch remote_one &&
+       test_must_fail git show-ref tag_one
+       '
+
+test_expect_success "fetch --tags with tagopt=--no-tags gets tag" '
+       git fetch --tags remote_one &&
+       git show-ref tag_one
+       '
+
+test_expect_success "fetch --no-tags with tagopt=--tags does not get tag" '
+       git fetch --no-tags remote_two &&
+       test_must_fail git show-ref tag_two
+       '
+
+test_expect_success "fetch with tagopt=--tags gets tag" '
+       git fetch remote_two &&
+       git show-ref tag_two
+       '
+test_done
index f5102b902a4fa0505fee13aa18d38a211cdb42cb..6b2a5f4a65659b7fcfcd9d096a0b43473e1cf1d6 100755 (executable)
@@ -30,11 +30,12 @@ test_expect_success 'fsck fails' '
        test_must_fail git fsck
 '
 
-test_expect_success 'upload-pack fails due to error in pack-objects' '
+test_expect_success 'upload-pack fails due to error in pack-objects packing' '
 
-       ! echo "0032want $(git rev-parse HEAD)
-00000009done
-0000" | git upload-pack . > /dev/null 2> output.err &&
+       printf "0032want %s\n00000009done\n0000" \
+               $(git rev-parse HEAD) >input &&
+       test_must_fail git upload-pack . <input >/dev/null 2>output.err &&
+       grep "unable to read" output.err &&
        grep "pack-objects died" output.err
 '
 
@@ -50,10 +51,31 @@ test_expect_success 'fsck fails' '
 '
 test_expect_success 'upload-pack fails due to error in rev-list' '
 
-       ! echo "0032want $(git rev-parse HEAD)
-00000009done
-0000" | git upload-pack . > /dev/null 2> output.err &&
-       grep "waitpid (async) failed" output.err
+       printf "0032want %s\n0034shallow %s00000009done\n0000" \
+               $(git rev-parse HEAD) $(git rev-parse HEAD^) >input &&
+       test_must_fail git upload-pack . <input >/dev/null 2>output.err &&
+       # pack-objects survived
+       grep "Total.*, reused" output.err &&
+       # but there was an error, which must have been in rev-list
+       grep "bad tree object" output.err
+'
+
+test_expect_success 'upload-pack error message when bad ref requested' '
+
+       printf "0045want %s multi_ack_detailed\n00000009done\n0000" \
+               "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" >input &&
+       test_must_fail git upload-pack . <input >output 2>output.err &&
+       grep -q "not our ref" output.err &&
+       ! grep -q multi_ack_detailed output.err
+'
+
+test_expect_success 'upload-pack fails due to error in pack-objects enumeration' '
+
+       printf "0032want %s\n00000009done\n0000" \
+               $(git rev-parse HEAD) >input &&
+       test_must_fail git upload-pack . <input >/dev/null 2>output.err &&
+       grep "bad tree object" output.err &&
+       grep "pack-objects died" output.err
 '
 
 test_expect_success 'create empty repository' '
diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh
new file mode 100755 (executable)
index 0000000..65d8d47
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+test_description='unpack-objects'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       mkdir pub.git &&
+       GIT_DIR=pub.git git init --bare
+       GIT_DIR=pub.git git config receive.fsckobjects true &&
+       mkdir work &&
+       (
+               cd work &&
+               git init &&
+               mkdir -p gar/bage &&
+               (
+                       cd gar/bage &&
+                       git init &&
+                       >junk &&
+                       git add junk &&
+                       git commit -m "Initial junk"
+               ) &&
+               git add gar/bage &&
+               git commit -m "Initial superproject"
+       )
+'
+
+test_expect_success push '
+       (
+               cd work &&
+               git push ../pub.git master
+       )
+'
+
+test_done
index 5fe479e1c2362ba05d059805d412b605aa7a83aa..a266ca56361347fe751495e1531b5a26e4733493 100755 (executable)
@@ -3,23 +3,22 @@
 # Copyright (c) 2008 Clemens Buchacher <drizzd@aon.at>
 #
 
-test_description='test http-push
+test_description='test WebDAV http-push
 
 This test runs various sanity checks on http-push.'
 
 . ./test-lib.sh
 
-ROOT_PATH="$PWD"
-LIB_HTTPD_DAV=t
-LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5540'}
-
 if git http-push > /dev/null 2>&1 || [ $? -eq 128 ]
 then
-       say "skipping test, USE_CURL_MULTI is not defined"
+       skip_all="skipping test, USE_CURL_MULTI is not defined"
        test_done
 fi
 
+LIB_HTTPD_DAV=t
+LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5540'}
 . "$TEST_DIRECTORY"/lib-httpd.sh
+ROOT_PATH="$PWD"
 start_httpd
 
 test_expect_success 'setup remote repository' '
@@ -36,16 +35,17 @@ test_expect_success 'setup remote repository' '
        cd test_repo.git &&
        git --bare update-server-info &&
        mv hooks/post-update.sample hooks/post-update &&
+       ORIG_HEAD=$(git rev-parse --verify HEAD) &&
        cd - &&
        mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH"
 '
 
 test_expect_success 'clone remote repository' '
        cd "$ROOT_PATH" &&
-       git clone $HTTPD_URL/test_repo.git test_repo_clone
+       git clone $HTTPD_URL/dumb/test_repo.git test_repo_clone
 '
 
-test_expect_failure 'push to remote repository with packed refs' '
+test_expect_success 'push to remote repository with packed refs' '
        cd "$ROOT_PATH"/test_repo_clone &&
        : >path2 &&
        git add path2 &&
@@ -57,16 +57,56 @@ test_expect_failure 'push to remote repository with packed refs' '
         test $HEAD = $(git rev-parse --verify HEAD))
 '
 
-test_expect_success ' push to remote repository with unpacked refs' '
+test_expect_success 'push already up-to-date' '
+       git push
+'
+
+test_expect_success 'push to remote repository with unpacked refs' '
        (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git &&
         rm packed-refs &&
-        git update-ref refs/heads/master \
-               0c973ae9bd51902a28466f3850b543fa66a6aaf4) &&
+        git update-ref refs/heads/master $ORIG_HEAD &&
+        git --bare update-server-info) &&
        git push &&
        (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git &&
         test $HEAD = $(git rev-parse --verify HEAD))
 '
 
+test_expect_success 'http-push fetches unpacked objects' '
+       cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \
+               "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_unpacked.git &&
+
+       git clone $HTTPD_URL/dumb/test_repo_unpacked.git \
+               "$ROOT_PATH"/fetch_unpacked &&
+
+       # By reset, we force git to retrieve the object
+       (cd "$ROOT_PATH"/fetch_unpacked &&
+        git reset --hard HEAD^ &&
+        git remote rm origin &&
+        git reflog expire --expire=0 --all &&
+        git prune &&
+        git push -f -v $HTTPD_URL/dumb/test_repo_unpacked.git master)
+'
+
+test_expect_success 'http-push fetches packed objects' '
+       cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \
+               "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_packed.git &&
+
+       git clone $HTTPD_URL/dumb/test_repo_packed.git \
+               "$ROOT_PATH"/test_repo_clone_packed &&
+
+       (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_packed.git &&
+        git --bare repack &&
+        git --bare prune-packed) &&
+
+       # By reset, we force git to retrieve the packed object
+       (cd "$ROOT_PATH"/test_repo_clone_packed &&
+        git reset --hard HEAD^ &&
+        git remote rm origin &&
+        git reflog expire --expire=0 --all &&
+        git prune &&
+        git push -f -v $HTTPD_URL/dumb/test_repo_packed.git master)
+'
+
 test_expect_success 'create and delete remote branch' '
        cd "$ROOT_PATH"/test_repo_clone &&
        git checkout -b dev &&
@@ -75,10 +115,7 @@ test_expect_success 'create and delete remote branch' '
        test_tick &&
        git commit -m dev &&
        git push origin dev &&
-       git fetch &&
        git push origin :dev &&
-       git branch -d -r origin/dev &&
-       git fetch &&
        test_must_fail git show-ref --verify refs/remotes/origin/dev
 '
 
@@ -100,6 +137,9 @@ test_expect_success 'PUT and MOVE sends object to URLs with SHA-1 hash suffix' '
 
 '
 
+test_http_push_nonff "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \
+       "$ROOT_PATH"/test_repo_clone master
+
 stop_httpd
 
 test_done
diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh
new file mode 100755 (executable)
index 0000000..b0c2a2c
--- /dev/null
@@ -0,0 +1,141 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Clemens Buchacher <drizzd@aon.at>
+#
+
+test_description='test smart pushing over http via http-backend'
+. ./test-lib.sh
+
+if test -n "$NO_CURL"; then
+       skip_all='skipping test, git built without http support'
+       test_done
+fi
+
+ROOT_PATH="$PWD"
+LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5541'}
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+test_expect_success 'setup remote repository' '
+       cd "$ROOT_PATH" &&
+       mkdir test_repo &&
+       cd test_repo &&
+       git init &&
+       : >path1 &&
+       git add path1 &&
+       test_tick &&
+       git commit -m initial &&
+       cd - &&
+       git clone --bare test_repo test_repo.git &&
+       cd test_repo.git &&
+       git config http.receivepack true &&
+       ORIG_HEAD=$(git rev-parse --verify HEAD) &&
+       cd - &&
+       mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH"
+'
+
+cat >exp <<EOF
+GET  /smart/test_repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
+POST /smart/test_repo.git/git-upload-pack HTTP/1.1 200
+EOF
+test_expect_success 'no empty path components' '
+       # In the URL, add a trailing slash, and see if git appends yet another
+       # slash.
+       cd "$ROOT_PATH" &&
+       git clone $HTTPD_URL/smart/test_repo.git/ test_repo_clone &&
+
+       sed -e "
+               s/^.* \"//
+               s/\"//
+               s/ [1-9][0-9]*\$//
+               s/^GET /GET  /
+       " >act <"$HTTPD_ROOT_PATH"/access.log &&
+
+       # Clear the log, so that it does not affect the "used receive-pack
+       # service" test which reads the log too.
+       #
+       # We do this before the actual comparison to ensure the log is cleared.
+       echo > "$HTTPD_ROOT_PATH"/access.log &&
+
+       test_cmp exp act
+'
+
+test_expect_success 'clone remote repository' '
+       rm -rf test_repo_clone &&
+       git clone $HTTPD_URL/smart/test_repo.git test_repo_clone
+'
+
+test_expect_success 'push to remote repository' '
+       cd "$ROOT_PATH"/test_repo_clone &&
+       : >path2 &&
+       git add path2 &&
+       test_tick &&
+       git commit -m path2 &&
+       HEAD=$(git rev-parse --verify HEAD) &&
+       git push &&
+       (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git &&
+        test $HEAD = $(git rev-parse --verify HEAD))
+'
+
+test_expect_success 'push already up-to-date' '
+       git push
+'
+
+test_expect_success 'create and delete remote branch' '
+       cd "$ROOT_PATH"/test_repo_clone &&
+       git checkout -b dev &&
+       : >path3 &&
+       git add path3 &&
+       test_tick &&
+       git commit -m dev &&
+       git push origin dev &&
+       git push origin :dev &&
+       test_must_fail git show-ref --verify refs/remotes/origin/dev
+'
+
+cat >exp <<EOF
+
+GET  /smart/test_repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
+POST /smart/test_repo.git/git-upload-pack HTTP/1.1 200
+GET  /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
+POST /smart/test_repo.git/git-receive-pack HTTP/1.1 200
+GET  /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
+GET  /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
+POST /smart/test_repo.git/git-receive-pack HTTP/1.1 200
+GET  /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
+POST /smart/test_repo.git/git-receive-pack HTTP/1.1 200
+EOF
+test_expect_success 'used receive-pack service' '
+       sed -e "
+               s/^.* \"//
+               s/\"//
+               s/ [1-9][0-9]*\$//
+               s/^GET /GET  /
+       " >act <"$HTTPD_ROOT_PATH"/access.log &&
+       test_cmp exp act
+'
+
+test_http_push_nonff "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \
+       "$ROOT_PATH"/test_repo_clone master
+
+test_expect_success 'push fails for non-fast-forward refs unmatched by remote helper' '
+       # create a dissimilarly-named remote ref so that git is unable to match the
+       # two refs (viz. local, remote) unless an explicit refspec is provided.
+       git push origin master:retsam
+
+       echo "change changed" > path2 &&
+       git commit -a -m path2 --amend &&
+
+       # push master too; this ensures there is at least one '"'push'"' command to
+       # the remote helper and triggers interaction with the helper.
+       test_must_fail git push -v origin +master master:retsam >output 2>&1 &&
+
+       grep "^ + [a-f0-9]*\.\.\.[a-f0-9]* *master -> master (forced update)$" output &&
+       grep "^ ! \[rejected\] *master -> retsam (non-fast-forward)$" output &&
+
+       grep "To prevent you from losing history, non-fast-forward updates were rejected" \
+               output
+'
+
+stop_httpd
+test_done
index 05b1b62cb66956bca57e1fbacac534993637bdd9..2fb48d09edb47129d9cea8b9686e46a6d8f35615 100755 (executable)
@@ -1,10 +1,10 @@
 #!/bin/sh
 
-test_description='test fetching over http'
+test_description='test dumb fetching over http via static file'
 . ./test-lib.sh
 
 if test -n "$NO_CURL"; then
-       say 'skipping test, git built without http support'
+       skip_all='skipping test, git built without http support'
        test_done
 fi
 
@@ -30,7 +30,7 @@ test_expect_success 'create http-accessible bare repository' '
 '
 
 test_expect_success 'clone http repository' '
-       git clone $HTTPD_URL/repo.git clone &&
+       git clone $HTTPD_URL/dumb/repo.git clone &&
        test_cmp file clone/file
 '
 
@@ -53,5 +53,50 @@ test_expect_success 'http remote detects correct HEAD' '
        )
 '
 
+test_expect_success 'fetch packed objects' '
+       cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
+       (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
+        git --bare repack &&
+        git --bare prune-packed
+       ) &&
+       git clone $HTTPD_URL/dumb/repo_pack.git
+'
+
+test_expect_success 'fetch notices corrupt pack' '
+       cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git &&
+       (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git &&
+        p=`ls objects/pack/pack-*.pack` &&
+        chmod u+w $p &&
+        printf %0256d 0 | dd of=$p bs=256 count=1 seek=1 conv=notrunc
+       ) &&
+       mkdir repo_bad1.git &&
+       (cd repo_bad1.git &&
+        git --bare init &&
+        test_must_fail git --bare fetch $HTTPD_URL/dumb/repo_bad1.git &&
+        test 0 = `ls objects/pack/pack-*.pack | wc -l`
+       )
+'
+
+test_expect_success 'fetch notices corrupt idx' '
+       cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad2.git &&
+       (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad2.git &&
+        p=`ls objects/pack/pack-*.idx` &&
+        chmod u+w $p &&
+        printf %0256d 0 | dd of=$p bs=256 count=1 seek=1 conv=notrunc
+       ) &&
+       mkdir repo_bad2.git &&
+       (cd repo_bad2.git &&
+        git --bare init &&
+        test_must_fail git --bare fetch $HTTPD_URL/dumb/repo_bad2.git &&
+        test 0 = `ls objects/pack | wc -l`
+       )
+'
+
+test_expect_success 'did not use upload-pack service' '
+       grep '/git-upload-pack' <"$HTTPD_ROOT_PATH"/access.log >act
+       : >exp
+       test_cmp exp act
+'
+
 stop_httpd
 test_done
diff --git a/t/t5551-http-fetch.sh b/t/t5551-http-fetch.sh
new file mode 100755 (executable)
index 0000000..fd19121
--- /dev/null
@@ -0,0 +1,105 @@
+#!/bin/sh
+
+test_description='test smart fetching over http via http-backend'
+. ./test-lib.sh
+
+if test -n "$NO_CURL"; then
+       skip_all='skipping test, git built without http support'
+       test_done
+fi
+
+LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5551'}
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+test_expect_success 'setup repository' '
+       echo content >file &&
+       git add file &&
+       git commit -m one
+'
+
+test_expect_success 'create http-accessible bare repository' '
+       mkdir "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+       (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+        git --bare init
+       ) &&
+       git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+       git push public master:master
+'
+
+cat >exp <<EOF
+> GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1
+> Accept: */*
+> Pragma: no-cache
+< HTTP/1.1 200 OK
+< Pragma: no-cache
+< Cache-Control: no-cache, max-age=0, must-revalidate
+< Content-Type: application/x-git-upload-pack-advertisement
+> POST /smart/repo.git/git-upload-pack HTTP/1.1
+> Accept-Encoding: deflate, gzip
+> Content-Type: application/x-git-upload-pack-request
+> Accept: application/x-git-upload-pack-result
+> Content-Length: xxx
+< HTTP/1.1 200 OK
+< Pragma: no-cache
+< Cache-Control: no-cache, max-age=0, must-revalidate
+< Content-Type: application/x-git-upload-pack-result
+EOF
+test_expect_success 'clone http repository' '
+       GIT_CURL_VERBOSE=1 git clone --quiet $HTTPD_URL/smart/repo.git clone 2>err &&
+       test_cmp file clone/file &&
+       tr '\''\015'\'' Q <err |
+       sed -e "
+               s/Q\$//
+               /^[*] /d
+               /^$/d
+               /^< $/d
+
+               /^[^><]/{
+                       s/^/> /
+               }
+
+               /^> User-Agent: /d
+               /^> Host: /d
+               /^> POST /,$ {
+                       /^> Accept: [*]\\/[*]/d
+               }
+               s/^> Content-Length: .*/> Content-Length: xxx/
+               /^> 00..want /d
+               /^> 00.*done/d
+
+               /^< Server: /d
+               /^< Expires: /d
+               /^< Date: /d
+               /^< Content-Length: /d
+               /^< Transfer-Encoding: /d
+       " >act &&
+       test_cmp exp act
+'
+
+test_expect_success 'fetch changes via http' '
+       echo content >>file &&
+       git commit -a -m two &&
+       git push public
+       (cd clone && git pull) &&
+       test_cmp file clone/file
+'
+
+cat >exp <<EOF
+GET  /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
+POST /smart/repo.git/git-upload-pack HTTP/1.1 200
+GET  /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
+POST /smart/repo.git/git-upload-pack HTTP/1.1 200
+EOF
+test_expect_success 'used upload-pack service' '
+       sed -e "
+               s/^.* \"//
+               s/\"//
+               s/ [1-9][0-9]*\$//
+               s/^GET /GET  /
+       " >act <"$HTTPD_ROOT_PATH"/access.log &&
+       test_cmp exp act
+'
+
+stop_httpd
+test_done
diff --git a/t/t5560-http-backend-noserver.sh b/t/t5560-http-backend-noserver.sh
new file mode 100755 (executable)
index 0000000..44885b8
--- /dev/null
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+test_description='test git-http-backend-noserver'
+. ./test-lib.sh
+
+HTTPD_DOCUMENT_ROOT_PATH="$TRASH_DIRECTORY"
+
+run_backend() {
+       echo "$2" |
+       QUERY_STRING="${1#*\?}" \
+       GIT_PROJECT_ROOT="$HTTPD_DOCUMENT_ROOT_PATH" \
+       PATH_INFO="${1%%\?*}" \
+       git http-backend >act.out 2>act.err
+}
+
+GET() {
+       export REQUEST_METHOD="GET" &&
+       run_backend "/repo.git/$1" &&
+       unset REQUEST_METHOD &&
+       if ! grep "Status" act.out >act
+       then
+               printf "Status: 200 OK\r\n" >act
+       fi
+       printf "Status: $2\r\n" >exp &&
+       test_cmp exp act
+}
+
+POST() {
+       export REQUEST_METHOD="POST" &&
+       export CONTENT_TYPE="application/x-$1-request" &&
+       run_backend "/repo.git/$1" "$2" &&
+       unset REQUEST_METHOD &&
+       unset CONTENT_TYPE &&
+       if ! grep "Status" act.out >act
+       then
+               printf "Status: 200 OK\r\n" >act
+       fi
+       printf "Status: $3\r\n" >exp &&
+       test_cmp exp act
+}
+
+log_div() {
+       return 0
+}
+
+. "$TEST_DIRECTORY"/t556x_common
+
+expect_aliased() {
+       export REQUEST_METHOD="GET" &&
+       if test $1 = 0; then
+               run_backend "$2"
+       else
+               run_backend "$2" &&
+               echo "fatal: '$2': aliased" >exp.err &&
+               test_cmp exp.err act.err
+       fi
+       unset REQUEST_METHOD
+}
+
+test_expect_success 'http-backend blocks bad PATH_INFO' '
+       config http.getanyfile true &&
+
+       expect_aliased 0 /repo.git/HEAD &&
+
+       expect_aliased 1 /repo.git/../HEAD &&
+       expect_aliased 1 /../etc/passwd &&
+       expect_aliased 1 ../etc/passwd &&
+       expect_aliased 1 /etc//passwd &&
+       expect_aliased 1 /etc/./passwd &&
+       expect_aliased 1 //domain/data.txt
+'
+
+test_done
diff --git a/t/t5561-http-backend.sh b/t/t5561-http-backend.sh
new file mode 100755 (executable)
index 0000000..b5d7fbc
--- /dev/null
@@ -0,0 +1,149 @@
+#!/bin/sh
+
+test_description='test git-http-backend'
+. ./test-lib.sh
+
+if test -n "$NO_CURL"; then
+       skip_all='skipping test, git built without http support'
+       test_done
+fi
+
+LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5561'}
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+GET() {
+       curl --include "$HTTPD_URL/$SMART/repo.git/$1" >out 2>/dev/null &&
+       tr '\015' Q <out |
+       sed '
+               s/Q$//
+               1q
+       ' >act &&
+       echo "HTTP/1.1 $2" >exp &&
+       test_cmp exp act
+}
+
+POST() {
+       curl --include --data "$2" \
+       --header "Content-Type: application/x-$1-request" \
+       "$HTTPD_URL/smart/repo.git/$1" >out 2>/dev/null &&
+       tr '\015' Q <out |
+       sed '
+               s/Q$//
+               1q
+       ' >act &&
+       echo "HTTP/1.1 $3" >exp &&
+       test_cmp exp act
+}
+
+log_div() {
+       echo >>"$HTTPD_ROOT_PATH"/access.log
+       echo "###  $1" >>"$HTTPD_ROOT_PATH"/access.log
+       echo "###" >>"$HTTPD_ROOT_PATH"/access.log
+}
+
+. "$TEST_DIRECTORY"/t556x_common
+
+cat >exp <<EOF
+
+###  refs/heads/master
+###
+GET  /smart/repo.git/refs/heads/master HTTP/1.1 404 -
+
+###  getanyfile default
+###
+GET  /smart/repo.git/HEAD HTTP/1.1 200
+GET  /smart/repo.git/info/refs HTTP/1.1 200
+GET  /smart/repo.git/objects/info/packs HTTP/1.1 200
+GET  /smart/repo.git/objects/info/alternates HTTP/1.1 200 -
+GET  /smart/repo.git/objects/info/http-alternates HTTP/1.1 200 -
+GET  /smart/repo.git/$LOOSE_URL HTTP/1.1 200
+GET  /smart/repo.git/$PACK_URL HTTP/1.1 200
+GET  /smart/repo.git/$IDX_URL HTTP/1.1 200
+
+###  no git-daemon-export-ok
+###
+GET  /smart_noexport/repo.git/HEAD HTTP/1.1 404 -
+GET  /smart_noexport/repo.git/info/refs HTTP/1.1 404 -
+GET  /smart_noexport/repo.git/objects/info/packs HTTP/1.1 404 -
+GET  /smart_noexport/repo.git/objects/info/alternates HTTP/1.1 404 -
+GET  /smart_noexport/repo.git/objects/info/http-alternates HTTP/1.1 404 -
+GET  /smart_noexport/repo.git/$LOOSE_URL HTTP/1.1 404 -
+GET  /smart_noexport/repo.git/$PACK_URL HTTP/1.1 404 -
+GET  /smart_noexport/repo.git/$IDX_URL HTTP/1.1 404 -
+
+###  git-daemon-export-ok
+###
+GET  /smart_noexport/repo.git/HEAD HTTP/1.1 200
+GET  /smart_noexport/repo.git/info/refs HTTP/1.1 200
+GET  /smart_noexport/repo.git/objects/info/packs HTTP/1.1 200
+GET  /smart_noexport/repo.git/objects/info/alternates HTTP/1.1 200 -
+GET  /smart_noexport/repo.git/objects/info/http-alternates HTTP/1.1 200 -
+GET  /smart_noexport/repo.git/$LOOSE_URL HTTP/1.1 200
+GET  /smart_noexport/repo.git/$PACK_URL HTTP/1.1 200
+GET  /smart_noexport/repo.git/$IDX_URL HTTP/1.1 200
+
+###  getanyfile true
+###
+GET  /smart/repo.git/HEAD HTTP/1.1 200
+GET  /smart/repo.git/info/refs HTTP/1.1 200
+GET  /smart/repo.git/objects/info/packs HTTP/1.1 200
+GET  /smart/repo.git/objects/info/alternates HTTP/1.1 200 -
+GET  /smart/repo.git/objects/info/http-alternates HTTP/1.1 200 -
+GET  /smart/repo.git/$LOOSE_URL HTTP/1.1 200
+GET  /smart/repo.git/$PACK_URL HTTP/1.1 200
+GET  /smart/repo.git/$IDX_URL HTTP/1.1 200
+
+###  getanyfile false
+###
+GET  /smart/repo.git/HEAD HTTP/1.1 403 -
+GET  /smart/repo.git/info/refs HTTP/1.1 403 -
+GET  /smart/repo.git/objects/info/packs HTTP/1.1 403 -
+GET  /smart/repo.git/objects/info/alternates HTTP/1.1 403 -
+GET  /smart/repo.git/objects/info/http-alternates HTTP/1.1 403 -
+GET  /smart/repo.git/$LOOSE_URL HTTP/1.1 403 -
+GET  /smart/repo.git/$PACK_URL HTTP/1.1 403 -
+GET  /smart/repo.git/$IDX_URL HTTP/1.1 403 -
+
+###  uploadpack default
+###
+GET  /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
+POST /smart/repo.git/git-upload-pack HTTP/1.1 200 -
+
+###  uploadpack true
+###
+GET  /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
+POST /smart/repo.git/git-upload-pack HTTP/1.1 200 -
+
+###  uploadpack false
+###
+GET  /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 403 -
+POST /smart/repo.git/git-upload-pack HTTP/1.1 403 -
+
+###  receivepack default
+###
+GET  /smart/repo.git/info/refs?service=git-receive-pack HTTP/1.1 403 -
+POST /smart/repo.git/git-receive-pack HTTP/1.1 403 -
+
+###  receivepack true
+###
+GET  /smart/repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
+POST /smart/repo.git/git-receive-pack HTTP/1.1 200 -
+
+###  receivepack false
+###
+GET  /smart/repo.git/info/refs?service=git-receive-pack HTTP/1.1 403 -
+POST /smart/repo.git/git-receive-pack HTTP/1.1 403 -
+EOF
+test_expect_success 'server request log matches test results' '
+       sed -e "
+               s/^.* \"//
+               s/\"//
+               s/ [1-9][0-9]*\$//
+               s/^GET /GET  /
+       " >act <"$HTTPD_ROOT_PATH"/access.log &&
+       test_cmp exp act
+'
+
+stop_httpd
+test_done
diff --git a/t/t556x_common b/t/t556x_common
new file mode 100755 (executable)
index 0000000..be024e5
--- /dev/null
@@ -0,0 +1,122 @@
+#!/bin/sh
+
+find_file() {
+       cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+       find $1 -type f |
+       sed -e 1q
+}
+
+config() {
+       git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" config $1 $2
+}
+
+test_expect_success 'setup repository' '
+       echo content >file &&
+       git add file &&
+       git commit -m one &&
+
+       mkdir "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+       (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+        git --bare init &&
+        : >objects/info/alternates &&
+        : >objects/info/http-alternates
+       ) &&
+       git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+       git push public master:master &&
+
+       (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+        git repack -a -d
+       ) &&
+
+       echo other >file &&
+       git add file &&
+       git commit -m two &&
+       git push public master:master &&
+
+       LOOSE_URL=$(find_file objects/??) &&
+       PACK_URL=$(find_file objects/pack/*.pack) &&
+       IDX_URL=$(find_file objects/pack/*.idx)
+'
+
+get_static_files() {
+       GET HEAD "$1" &&
+       GET info/refs "$1" &&
+       GET objects/info/packs "$1" &&
+       GET objects/info/alternates "$1" &&
+       GET objects/info/http-alternates "$1" &&
+       GET $LOOSE_URL "$1" &&
+       GET $PACK_URL "$1" &&
+       GET $IDX_URL "$1"
+}
+
+SMART=smart
+export GIT_HTTP_EXPORT_ALL=1
+test_expect_success 'direct refs/heads/master not found' '
+       log_div "refs/heads/master"
+       GET refs/heads/master "404 Not Found"
+'
+test_expect_success 'static file is ok' '
+       log_div "getanyfile default"
+       get_static_files "200 OK"
+'
+SMART=smart_noexport
+unset GIT_HTTP_EXPORT_ALL
+test_expect_success 'no export by default' '
+       log_div "no git-daemon-export-ok"
+       get_static_files "404 Not Found"
+'
+test_expect_success 'export if git-daemon-export-ok' '
+       log_div "git-daemon-export-ok"
+        (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+        touch git-daemon-export-ok
+       ) &&
+        get_static_files "200 OK"
+'
+SMART=smart
+export GIT_HTTP_EXPORT_ALL=1
+test_expect_success 'static file if http.getanyfile true is ok' '
+       log_div "getanyfile true"
+       config http.getanyfile true &&
+       get_static_files "200 OK"
+'
+test_expect_success 'static file if http.getanyfile false fails' '
+       log_div "getanyfile false"
+       config http.getanyfile false &&
+       get_static_files "403 Forbidden"
+'
+
+test_expect_success 'http.uploadpack default enabled' '
+       log_div "uploadpack default"
+       GET info/refs?service=git-upload-pack "200 OK"  &&
+       POST git-upload-pack 0000 "200 OK"
+'
+test_expect_success 'http.uploadpack true' '
+       log_div "uploadpack true"
+       config http.uploadpack true &&
+       GET info/refs?service=git-upload-pack "200 OK" &&
+       POST git-upload-pack 0000 "200 OK"
+'
+test_expect_success 'http.uploadpack false' '
+       log_div "uploadpack false"
+       config http.uploadpack false &&
+       GET info/refs?service=git-upload-pack "403 Forbidden" &&
+       POST git-upload-pack 0000 "403 Forbidden"
+'
+
+test_expect_success 'http.receivepack default disabled' '
+       log_div "receivepack default"
+       GET info/refs?service=git-receive-pack "403 Forbidden"  &&
+       POST git-receive-pack 0000 "403 Forbidden"
+'
+test_expect_success 'http.receivepack true' '
+       log_div "receivepack true"
+       config http.receivepack true &&
+       GET info/refs?service=git-receive-pack "200 OK" &&
+       POST git-receive-pack 0000 "200 OK"
+'
+test_expect_success 'http.receivepack false' '
+       log_div "receivepack false"
+       config http.receivepack false &&
+       GET info/refs?service=git-receive-pack "403 Forbidden" &&
+       POST git-receive-pack 0000 "403 Forbidden"
+'
index 2335d8bc850b4b0010fcb9ac64a25d0c205e8a42..987e0c846309a8a49dbe31ce292edd55342217b3 100755 (executable)
@@ -34,7 +34,7 @@ test_expect_success 'clone with excess parameters (2)' '
 test_expect_success 'output from clone' '
        rm -fr dst &&
        git clone -n "file://$(pwd)/src" dst >output &&
-       test $(grep Initialized output | wc -l) = 1
+       test $(grep Clon output | wc -l) = 1
 '
 
 test_expect_success 'clone does not keep pack' '
@@ -149,11 +149,13 @@ test_expect_success 'clone a void' '
        (
                cd src-0 && git init
        ) &&
-       git clone src-0 target-6 &&
+       git clone "file://$(pwd)/src-0" target-6 2>err-6 &&
+       ! grep "fatal:" err-6 &&
        (
                cd src-0 && test_commit A
        ) &&
-       git clone src-0 target-7 &&
+       git clone "file://$(pwd)/src-0" target-7 2>err-7 &&
+       ! grep "fatal:" err-7 &&
        # There is no reason to insist they are bit-for-bit
        # identical, but this test should suffice for now.
        test_cmp target-6/.git/config target-7/.git/config
@@ -161,8 +163,6 @@ test_expect_success 'clone a void' '
 
 test_expect_success 'clone respects global branch.autosetuprebase' '
        (
-               HOME=$(pwd) &&
-               export HOME &&
                test_config="$HOME/.gitconfig" &&
                unset GIT_CONFIG_NOGLOBAL &&
                git config -f "$test_config" branch.autosetuprebase remote &&
@@ -174,4 +174,22 @@ test_expect_success 'clone respects global branch.autosetuprebase' '
        )
 '
 
+test_expect_success 'respect url-encoding of file://' '
+       git init x+y &&
+       git clone "file://$PWD/x+y" xy-url-1 &&
+       git clone "file://$PWD/x%2By" xy-url-2
+'
+
+test_expect_success 'do not query-string-decode + in URLs' '
+       rm -rf x+y &&
+       git init "x y" &&
+       test_must_fail git clone "file://$PWD/x+y" xy-no-plus
+'
+
+test_expect_success 'do not respect url-encoding of non-url path' '
+       git init x+y &&
+       test_must_fail git clone x%2By xy-regular &&
+       git clone x+y xy-regular
+'
+
 test_done
index 1c109160690d273451f7a089be42e45f36a3b5bb..895f5595aee9341276e79497b9c4a8736c78e5e7 100755 (executable)
@@ -48,7 +48,7 @@ test_expect_success 'that reference gets used' \
 'cd C &&
 echo "0 objects, 0 kilobytes" > expected &&
 git count-objects > current &&
-diff expected current'
+test_cmp expected current'
 
 cd "$base_dir"
 
@@ -75,7 +75,7 @@ cd "$base_dir"
 test_expect_success 'that reference gets used' \
 'cd D && echo "0 objects, 0 kilobytes" > expected &&
 git count-objects > current &&
-diff expected current'
+test_cmp expected current'
 
 cd "$base_dir"
 
@@ -100,7 +100,7 @@ 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'
+test_cmp expected current'
 
 cd "$base_dir"
 
@@ -116,7 +116,7 @@ test_expect_success 'check objects expected to exist locally' \
 'cd D &&
 echo "5 objects" > expected &&
 git count-objects | cut -d, -f1 > current &&
-diff expected current'
+test_cmp expected current'
 
 cd "$base_dir"
 
index 19b5c0d552fa8b4665b5e396833264e258365b28..8b4c356cd21846025d84a434077bfdc8ee2bab57 100755 (executable)
@@ -119,7 +119,9 @@ test_expect_success 'bundle clone with nonexistent HEAD' '
 test_expect_success 'clone empty repository' '
        cd "$D" &&
        mkdir empty &&
-       (cd empty && git init) &&
+       (cd empty &&
+        git init &&
+        git config receive.denyCurrentBranch warn) &&
        git clone empty empty-clone &&
        test_tick &&
        (cd empty-clone
index 27825f5f31ca2310e08fd2555589aa878728b292..02cb02472322e8fd6c31548d30950c937e301e5f 100755 (executable)
@@ -27,7 +27,8 @@ test_expect_success 'redirected clone' '
 '
 test_expect_success 'redirected clone -v' '
 
-       git clone -v "file://$(pwd)/parent" clone-redirected-v >out 2>err &&
+       git clone --progress "file://$(pwd)/parent" clone-redirected-progress \
+               >out 2>err &&
        test -s err
 
 '
index a8f4419e610c4329cefc556684a7212f1405e104..728ccd88c3d69f06bff2c3593ddc325f9186402c 100755 (executable)
@@ -30,4 +30,27 @@ test_expect_success 'tags can be excluded by rev-list options' '
 
 '
 
+test_expect_success 'die if bundle file cannot be created' '
+
+       mkdir adir &&
+       test_must_fail git bundle create adir --all
+
+'
+
+test_expect_failure 'bundle --stdin' '
+
+       echo master | git bundle create stdin-bundle.bdl --stdin &&
+       git ls-remote stdin-bundle.bdl >output &&
+       grep master output
+
+'
+
+test_expect_failure 'bundle --stdin <rev-list options>' '
+
+       echo master | git bundle create hybrid-bundle.bdl --stdin tag &&
+       git ls-remote hybrid-bundle.bdl >output &&
+       grep master output
+
+'
+
 test_done
index 9f52154cac1bc8d35a279180767ab998ec612170..e9783c341a97afdd58aa2adddc8c6db4adb54bee 100755 (executable)
@@ -3,16 +3,18 @@
 test_description='Test cloning a repository larger than 2 gigabyte'
 . ./test-lib.sh
 
-test -z "$GIT_TEST_CLONE_2GB" &&
-say "Skipping expensive 2GB clone test; enable it with GIT_TEST_CLONE_2GB=t" &&
-test_done &&
-exit
+if test -z "$GIT_TEST_CLONE_2GB"
+then
+       say 'Skipping expensive 2GB clone test; enable it with GIT_TEST_CLONE_2GB=t'
+else
+       test_set_prereq CLONE_2GB
+fi
 
-test_expect_success 'setup' '
+test_expect_success CLONE_2GB 'setup' '
 
        git config pack.compression 0 &&
        git config pack.depth 0 &&
-       blobsize=$((20*1024*1024)) &&
+       blobsize=$((100*1024*1024)) &&
        blobcount=$((2*1024*1024*1024/$blobsize+1)) &&
        i=1 &&
        (while test $i -le $blobcount
@@ -31,14 +33,20 @@ test_expect_success 'setup' '
         echo "data 5" &&
         echo ">2gb" &&
         cat commit) |
-       git fast-import &&
+       git fast-import --big-file-threshold=2 &&
        test ! -f exit-status
 
 '
 
-test_expect_success 'clone' '
+test_expect_success CLONE_2GB 'clone - bare' '
 
-       git clone --bare --no-hardlinks . clone
+       git clone --bare --no-hardlinks . clone-bare
+
+'
+
+test_expect_success CLONE_2GB 'clone - with worktree, file:// protocol' '
+
+       git clone file://. clone-wt
 
 '
 
diff --git a/t/t5706-clone-branch.sh b/t/t5706-clone-branch.sh
new file mode 100755 (executable)
index 0000000..f3f9a76
--- /dev/null
@@ -0,0 +1,68 @@
+#!/bin/sh
+
+test_description='clone --branch option'
+. ./test-lib.sh
+
+check_HEAD() {
+       echo refs/heads/"$1" >expect &&
+       git symbolic-ref HEAD >actual &&
+       test_cmp expect actual
+}
+
+check_file() {
+       echo "$1" >expect &&
+       test_cmp expect file
+}
+
+test_expect_success 'setup' '
+       mkdir parent &&
+       (cd parent && git init &&
+        echo one >file && git add file && git commit -m one &&
+        git checkout -b two &&
+        echo two >file && git add file && git commit -m two &&
+        git checkout master)
+'
+
+test_expect_success 'vanilla clone chooses HEAD' '
+       git clone parent clone &&
+       (cd clone &&
+        check_HEAD master &&
+        check_file one
+       )
+'
+
+test_expect_success 'clone -b chooses specified branch' '
+       git clone -b two parent clone-two &&
+       (cd clone-two &&
+        check_HEAD two &&
+        check_file two
+       )
+'
+
+test_expect_success 'clone -b sets up tracking' '
+       (cd clone-two &&
+        echo origin >expect &&
+        git config branch.two.remote >actual &&
+        echo refs/heads/two >>expect &&
+        git config branch.two.merge >>actual &&
+        test_cmp expect actual
+       )
+'
+
+test_expect_success 'clone -b does not munge remotes/origin/HEAD' '
+       (cd clone-two &&
+        echo refs/remotes/origin/master >expect &&
+        git symbolic-ref refs/remotes/origin/HEAD >actual &&
+        test_cmp expect actual
+       )
+'
+
+test_expect_success 'clone -b with bogus branch chooses HEAD' '
+       git clone -b bogus parent clone-bogus &&
+       (cd clone-bogus &&
+        check_HEAD master &&
+        check_file one
+       )
+'
+
+test_done
diff --git a/t/t5800-remote-helpers.sh b/t/t5800-remote-helpers.sh
new file mode 100755 (executable)
index 0000000..1fb6380
--- /dev/null
@@ -0,0 +1,80 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Sverre Rabbelier
+#
+
+test_description='Test remote-helper import and export commands'
+
+. ./test-lib.sh
+
+if test_have_prereq PYTHON && "$PYTHON_PATH" -c '
+import sys
+if sys.hexversion < 0x02040000:
+    sys.exit(1)
+'
+then
+    # Requires Python 2.4 or newer
+       test_set_prereq PYTHON_24
+fi
+
+test_expect_success PYTHON_24 'setup repository' '
+       git init --bare server/.git &&
+       git clone server public &&
+       (cd public &&
+        echo content >file &&
+        git add file &&
+        git commit -m one &&
+        git push origin master)
+'
+
+test_expect_success PYTHON_24 'cloning from local repo' '
+       git clone "testgit::${PWD}/server" localclone &&
+       test_cmp public/file localclone/file
+'
+
+test_expect_success PYTHON_24 'cloning from remote repo' '
+       git clone "testgit::file://${PWD}/server" clone &&
+       test_cmp public/file clone/file
+'
+
+test_expect_success PYTHON_24 'create new commit on remote' '
+       (cd public &&
+        echo content >>file &&
+        git commit -a -m two &&
+        git push)
+'
+
+test_expect_success PYTHON_24 'pulling from local repo' '
+       (cd localclone && git pull) &&
+       test_cmp public/file localclone/file
+'
+
+test_expect_success PYTHON_24 'pulling from remote remote' '
+       (cd clone && git pull) &&
+       test_cmp public/file clone/file
+'
+
+test_expect_success PYTHON_24 'pushing to local repo' '
+       (cd localclone &&
+       echo content >>file &&
+       git commit -a -m three &&
+       git push) &&
+       HEAD=$(git --git-dir=localclone/.git rev-parse --verify HEAD) &&
+       test $HEAD = $(git --git-dir=server/.git rev-parse --verify HEAD)
+'
+
+test_expect_success PYTHON_24 'synch with changes from localclone' '
+       (cd clone &&
+        git pull)
+'
+
+test_expect_success PYTHON_24 'pushing remote local repo' '
+       (cd clone &&
+       echo content >>file &&
+       git commit -a -m four &&
+       git push) &&
+       HEAD=$(git --git-dir=clone/.git rev-parse --verify HEAD) &&
+       test $HEAD = $(git --git-dir=server/.git rev-parse --verify HEAD)
+'
+
+test_done
diff --git a/t/t6000lib.sh b/t/t6000lib.sh
deleted file mode 100755 (executable)
index f55627b..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-[ -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
-
-       GIT_AUTHOR_EMAIL="$_author"
-       export GIT_AUTHOR_EMAIL
-       "$@"
-       if test -z "$_save"
-       then
-               unset GIT_AUTHOR_EMAIL
-       else
-               GIT_AUTHOR_EMAIL="$_save"
-               export GIT_AUTHOR_EMAIL
-       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
-    GIT_COMMITTER_DATE="$_date"
-    export GIT_COMMITTER_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()
-{
-       perl -pe '
-               s/[^A-Za-z0-9.]/-/g;
-               s/-+/-/g;
-               s/-$//;
-               s/^-//;
-               y/A-Z/a-z/;
-       '
-}
-
-
-# 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\""
-}
index b2131cdacd93e0b62f4ef8fdc62b6a81c6aef6fc..fc57e7d3fd69c60144ee3fb3f66b252a67369b42 100755 (executable)
@@ -84,7 +84,7 @@ check () {
                git rev-list --parents --pretty=raw $arg |
                sed -n -e 's/^commit //p' >test.actual
        fi
-       diff test.expect test.actual
+       test_cmp test.expect test.actual
 }
 
 for type in basic parents parents-raw
index b4e8fbaa5e6f2a56094c05ca505630669a51e101..fb07536a0f5b7e1000cf4945f55be3305df88e90 100755 (executable)
@@ -5,7 +5,7 @@
 test_description='Tests git rev-list --bisect functionality'
 
 . ./test-lib.sh
-. "$TEST_DIRECTORY"/t6000lib.sh # t6xxx specific functions
+. "$TEST_DIRECTORY"/lib-t6000.sh # t6xxx specific functions
 
 # usage: test_bisection max-diff bisect-option head ^prune...
 #
index 2c73f2da7b0a1f560bfd41376b587d1c91b18615..e4c52b0214b5028e8c3db035dc96ca3285e61506 100755 (executable)
@@ -6,7 +6,7 @@
 test_description='Tests git rev-list --topo-order functionality'
 
 . ./test-lib.sh
-. "$TEST_DIRECTORY"/t6000lib.sh # t6xxx specific functions
+. "$TEST_DIRECTORY"/lib-t6000.sh # t6xxx specific functions
 
 list_duplicates()
 {
index 59d1f6283bec5cf7740d988dfd090c1463389c71..cccacd4add48524abd4a3021f8048a6c45c245ea 100755 (executable)
@@ -19,6 +19,13 @@ test_cmp expect.$1 output.$1
 "
 }
 
+test_format percent %%h <<'EOF'
+commit 131a310eb913d107dd3c09a65d1651175898735d
+%h
+commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+%h
+EOF
+
 test_format hash %H%n%h <<'EOF'
 commit 131a310eb913d107dd3c09a65d1651175898735d
 131a310eb913d107dd3c09a65d1651175898735d
@@ -94,6 +101,15 @@ commit 131a310eb913d107dd3c09a65d1651175898735d
 commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
 EOF
 
+test_format raw-body %B <<'EOF'
+commit 131a310eb913d107dd3c09a65d1651175898735d
+changed foo
+
+commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+added foo
+
+EOF
+
 test_format colors %Credfoo%Cgreenbar%Cbluebaz%Cresetxyzzy <<'EOF'
 commit 131a310eb913d107dd3c09a65d1651175898735d
 \e[31mfoo\e[32mbar\e[34mbaz\e[mxyzzy
@@ -162,4 +178,94 @@ test_expect_success 'empty email' '
        }
 '
 
+test_expect_success 'del LF before empty (1)' '
+       git show -s --pretty=format:"%s%n%-b%nThanks%n" HEAD^^ >actual &&
+       test $(wc -l <actual) = 2
+'
+
+test_expect_success 'del LF before empty (2)' '
+       git show -s --pretty=format:"%s%n%-b%nThanks%n" HEAD >actual &&
+       test $(wc -l <actual) = 6 &&
+       grep "^$" actual
+'
+
+test_expect_success 'add LF before non-empty (1)' '
+       git show -s --pretty=format:"%s%+b%nThanks%n" HEAD^^ >actual &&
+       test $(wc -l <actual) = 2
+'
+
+test_expect_success 'add LF before non-empty (2)' '
+       git show -s --pretty=format:"%s%+b%nThanks%n" HEAD >actual &&
+       test $(wc -l <actual) = 6 &&
+       grep "^$" actual
+'
+
+test_expect_success 'add SP before non-empty (1)' '
+       git show -s --pretty=format:"%s% bThanks" HEAD^^ >actual &&
+       test $(wc -w <actual) = 2
+'
+
+test_expect_success 'add SP before non-empty (2)' '
+       git show -s --pretty=format:"%s% sThanks" HEAD^^ >actual &&
+       test $(wc -w <actual) = 4
+'
+
+test_expect_success '--abbrev' '
+       echo SHORT SHORT SHORT >expect2 &&
+       echo LONG LONG LONG >expect3 &&
+       git log -1 --format="%h %h %h" HEAD >actual1 &&
+       git log -1 --abbrev=5 --format="%h %h %h" HEAD >actual2 &&
+       git log -1 --abbrev=5 --format="%H %H %H" HEAD >actual3 &&
+       sed -e "s/$_x40/LONG/g" -e "s/$_x05/SHORT/g" <actual2 >fuzzy2 &&
+       sed -e "s/$_x40/LONG/g" -e "s/$_x05/SHORT/g" <actual3 >fuzzy3 &&
+       test_cmp expect2 fuzzy2 &&
+       test_cmp expect3 fuzzy3 &&
+       ! test_cmp actual1 actual2
+'
+
+test_expect_success '%H is not affected by --abbrev-commit' '
+       git log -1 --format=%H --abbrev-commit --abbrev=20 HEAD >actual &&
+       len=$(wc -c <actual) &&
+       test $len = 41
+'
+
+test_expect_success '%h is not affected by --abbrev-commit' '
+       git log -1 --format=%h --abbrev-commit --abbrev=20 HEAD >actual &&
+       len=$(wc -c <actual) &&
+       test $len = 21
+'
+
+test_expect_success '"%h %gD: %gs" is same as git-reflog' '
+       git reflog >expect &&
+       git log -g --format="%h %gD: %gs" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '"%h %gD: %gs" is same as git-reflog (with date)' '
+       git reflog --date=raw >expect &&
+       git log -g --format="%h %gD: %gs" --date=raw >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '"%h %gD: %gs" is same as git-reflog (with --abbrev)' '
+       git reflog --abbrev=13 --date=raw >expect &&
+       git log -g --abbrev=13 --format="%h %gD: %gs" --date=raw >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '%gd shortens ref name' '
+       echo "master@{0}" >expect.gd-short &&
+       git log -g -1 --format=%gd refs/heads/master >actual.gd-short &&
+       test_cmp expect.gd-short actual.gd-short
+'
+
+test_expect_success 'oneline with empty message' '
+       git commit -m "dummy" --allow-empty &&
+       git commit -m "dummy" --allow-empty &&
+       git filter-branch --msg-filter "sed -e s/dummy//" HEAD^^.. &&
+       git rev-list --oneline HEAD >test.txt &&
+       test $(git rev-list --oneline HEAD | wc -l) -eq 5 &&
+       test $(git rev-list --oneline --graph HEAD | wc -l) -eq 5
+'
+
 test_done
index 4b8611ce2092f9062aeaeb62ce19a9121d5be537..b565638e92655c12c841c29a108516a8b13cf8d2 100755 (executable)
@@ -32,6 +32,23 @@ test_expect_success setup '
        git tag B
 '
 
+cat >expect <<EOF
+<tags/B
+>tags/C
+EOF
+
+test_expect_success '--left-right' '
+       git rev-list --left-right B...C > actual &&
+       git name-rev --stdin --name-only --refs="*tags/*" \
+               < actual > actual.named &&
+       test_cmp actual.named expect
+'
+
+test_expect_success '--count' '
+       git rev-list --count B...C > actual &&
+       test "$(cat actual)" = 2
+'
+
 test_expect_success '--cherry-pick foo comes up empty' '
        test -z "$(git rev-list --left-right --cherry-pick B...C -- foo)"
 '
@@ -54,4 +71,16 @@ test_expect_success '--cherry-pick with independent, but identical branches' '
                HEAD...master -- foo)"
 '
 
+cat >expect <<EOF
+1      2
+EOF
+
+# Insert an extra commit to break the symmetry
+test_expect_success '--count --left-right' '
+       git checkout branch &&
+       test_commit D &&
+       git rev-list --count --left-right B...D > actual &&
+       test_cmp expect actual
+'
+
 test_done
index 04e4b7c5c2aa4f7c6922ebc5c9236962ab5d175c..62197a3d35f257dee6545d0f7768f21242e696ca 100755 (executable)
 # Copyright (c) 2005 Junio C Hamano
 #
 
-test_description='Merge base computation.
+test_description='Merge base and parent list computation.
 '
 
 . ./test-lib.sh
 
-T=$(git write-tree)
-
-M=1130000000
-Z=+0000
-
-GIT_COMMITTER_EMAIL=git@comm.iter.xz
-GIT_COMMITTER_NAME='C O Mmiter'
-GIT_AUTHOR_NAME='A U Thor'
-GIT_AUTHOR_EMAIL=git@au.thor.xz
-export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
-
-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
-}
-
-#  E---D---C---B---A
-#  \'-_         \   \
-#   \  `---------G   \
-#    \                \
-#     F----------------H
-
-# 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"'
-
-# Setup for second test to demonstrate that relying on timestamps in a
-# distributed SCM to provide a _consistent_ partial ordering of commits
-# leads to insanity.
-#
-#               Relative
-# Structure     timestamps
-#
-#   PL  PR        +4  +4
-#  /  \/  \      /  \/  \
-# L2  C2  R2    +3  -1  +3
-# |   |   |     |   |   |
-# L1  C1  R1    +2  -2  +2
-# |   |   |     |   |   |
-# L0  C0  R0    +1  -3  +1
-#   \ |  /        \ |  /
-#     S             0
-#
-# The left and right chains of commits can be of any length and complexity as
-# long as all of the timestamps are greater than that of S.
+test_expect_success 'setup' '
+       T=$(git write-tree) &&
+
+       M=1130000000 &&
+       Z=+0000 &&
+
+       GIT_COMMITTER_EMAIL=git@comm.iter.xz &&
+       GIT_COMMITTER_NAME="C O Mmiter" &&
+       GIT_AUTHOR_NAME="A U Thor" &&
+       GIT_AUTHOR_EMAIL=git@au.thor.xz &&
+       export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL &&
+
+       doit() {
+               OFFSET=$1 &&
+               NAME=$2 &&
+               shift 2 &&
+
+               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
+       }
+'
+
+test_expect_success 'set up G and H' '
+       # E---D---C---B---A
+       # \"-_         \   \
+       #  \  `---------G   \
+       #   \                \
+       #    F----------------H
+       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 'merge-base G H' '
+       git name-rev $B >expected &&
+
+       MB=$(git merge-base G H) &&
+       git name-rev "$MB" >actual.single &&
 
-S=$(doit  0 S)
+       MB=$(git merge-base --all G H) &&
+       git name-rev "$MB" >actual.all &&
 
-C0=$(doit -3 C0 $S)
-C1=$(doit -2 C1 $C0)
-C2=$(doit -1 C2 $C1)
+       MB=$(git show-branch --merge-base G H) &&
+       git name-rev "$MB" >actual.sb &&
 
-L0=$(doit  1 L0 $S)
-L1=$(doit  2 L1 $L0)
-L2=$(doit  3 L2 $L1)
+       test_cmp expected actual.single &&
+       test_cmp expected actual.all &&
+       test_cmp expected actual.sb
+'
+
+test_expect_success 'merge-base/show-branch --independent' '
+       git name-rev "$H" >expected1 &&
+       git name-rev "$H" "$G" >expected2 &&
 
-R0=$(doit  1 R0 $S)
-R1=$(doit  2 R1 $R0)
-R2=$(doit  3 R2 $R1)
+       parents=$(git merge-base --independent H) &&
+       git name-rev $parents >actual1.mb &&
+       parents=$(git merge-base --independent A H G) &&
+       git name-rev $parents >actual2.mb &&
 
-PL=$(doit  4 PL $L2 $C2)
-PR=$(doit  4 PR $C2 $R2)
+       parents=$(git show-branch --independent H) &&
+       git name-rev $parents >actual1.sb &&
+       parents=$(git show-branch --independent A H G) &&
+       git name-rev $parents >actual2.sb &&
 
-test_expect_success 'compute merge-base (single)' \
-    'MB=$(git merge-base PL PR) &&
-     expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/C2"'
+       test_cmp expected1 actual1.mb &&
+       test_cmp expected2 actual2.mb &&
+       test_cmp expected1 actual1.sb &&
+       test_cmp expected2 actual2.sb
+'
 
-test_expect_success 'compute merge-base (all)' \
-    'MB=$(git merge-base --all PL PR) &&
-     expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/C2"'
+test_expect_success 'unsynchronized clocks' '
+       # This test is to demonstrate that relying on timestamps in a distributed
+       # SCM to provide a _consistent_ partial ordering of commits leads to
+       # insanity.
+       #
+       #               Relative
+       # Structure     timestamps
+       #
+       #   PL  PR        +4  +4
+       #  /  \/  \      /  \/  \
+       # L2  C2  R2    +3  -1  +3
+       # |   |   |     |   |   |
+       # L1  C1  R1    +2  -2  +2
+       # |   |   |     |   |   |
+       # L0  C0  R0    +1  -3  +1
+       #   \ |  /        \ |  /
+       #     S             0
+       #
+       # The left and right chains of commits can be of any length and complexity as
+       # long as all of the timestamps are greater than that of S.
+
+       S=$(doit  0 S) &&
+
+       C0=$(doit -3 C0 $S) &&
+       C1=$(doit -2 C1 $C0) &&
+       C2=$(doit -1 C2 $C1) &&
+
+       L0=$(doit  1 L0 $S) &&
+       L1=$(doit  2 L1 $L0) &&
+       L2=$(doit  3 L2 $L1) &&
+
+       R0=$(doit  1 R0 $S) &&
+       R1=$(doit  2 R1 $R0) &&
+       R2=$(doit  3 R2 $R1) &&
+
+       PL=$(doit  4 PL $L2 $C2) &&
+       PR=$(doit  4 PR $C2 $R2)
+
+       git name-rev $C2 >expected &&
+
+       MB=$(git merge-base PL PR) &&
+       git name-rev "$MB" >actual.single &&
+
+       MB=$(git merge-base --all PL PR) &&
+       git name-rev "$MB" >actual.all &&
+
+       test_cmp expected actual.single &&
+       test_cmp expected actual.all
+'
 
-# Another set to demonstrate base between one commit and a merge
-# in the documentation.
+test_expect_success '--independent with unsynchronized clocks' '
+       IB=$(doit 0 IB) &&
+       I1=$(doit -10 I1 $IB) &&
+       I2=$(doit  -9 I2 $I1) &&
+       I3=$(doit  -8 I3 $I2) &&
+       I4=$(doit  -7 I4 $I3) &&
+       I5=$(doit  -6 I5 $I4) &&
+       I6=$(doit  -5 I6 $I5) &&
+       I7=$(doit  -4 I7 $I6) &&
+       I8=$(doit  -3 I8 $I7) &&
+       IH=$(doit  -2 IH $I8) &&
+
+       echo $IH >expected &&
+       git merge-base --independent IB IH >actual &&
+       test_cmp expected actual
+'
 
 test_expect_success 'merge-base for octopus-step (setup)' '
-       test_tick && git commit --allow-empty -m root && git tag MMR &&
-       test_tick && git commit --allow-empty -m 1 && git tag MM1 &&
-       test_tick && git commit --allow-empty -m o &&
-       test_tick && git commit --allow-empty -m o &&
-       test_tick && git commit --allow-empty -m o &&
-       test_tick && git commit --allow-empty -m A && git tag MMA &&
+       # Another set to demonstrate base between one commit and a merge
+       # in the documentation.
+       #
+       # * C (MMC) * B (MMB) * A  (MMA)
+       # * o       * o       * o
+       # * o       * o       * o
+       # * o       * o       * o
+       # * o       | _______/
+       # |         |/
+       # |         * 1 (MM1)
+       # | _______/
+       # |/
+       # * root (MMR)
+
+       test_commit MMR &&
+       test_commit MM1 &&
+       test_commit MM-o &&
+       test_commit MM-p &&
+       test_commit MM-q &&
+       test_commit MMA &&
        git checkout MM1 &&
-       test_tick && git commit --allow-empty -m o &&
-       test_tick && git commit --allow-empty -m o &&
-       test_tick && git commit --allow-empty -m o &&
-       test_tick && git commit --allow-empty -m B && git tag MMB &&
+       test_commit MM-r &&
+       test_commit MM-s &&
+       test_commit MM-t &&
+       test_commit MMB &&
        git checkout MMR &&
-       test_tick && git commit --allow-empty -m o &&
-       test_tick && git commit --allow-empty -m o &&
-       test_tick && git commit --allow-empty -m o &&
-       test_tick && git commit --allow-empty -m o &&
-       test_tick && git commit --allow-empty -m C && git tag MMC
+       test_commit MM-u &&
+       test_commit MM-v &&
+       test_commit MM-w &&
+       test_commit MM-x &&
+       test_commit MMC
 '
 
 test_expect_success 'merge-base A B C' '
-       MB=$(git merge-base --all MMA MMB MMC) &&
-       MM1=$(git rev-parse --verify MM1) &&
-       test "$MM1" = "$MB"
+       git rev-parse --verify MM1 >expected &&
+       git rev-parse --verify MMR >expected.sb &&
+
+       git merge-base --all MMA MMB MMC >actual &&
+       git merge-base --all --octopus MMA MMB MMC >actual.common &&
+       git show-branch --merge-base MMA MMB MMC >actual.sb &&
+
+       test_cmp expected actual &&
+       test_cmp expected.sb actual.common &&
+       test_cmp expected.sb actual.sb
 '
 
-test_expect_success 'criss-cross merge-base for octopus-step (setup)' '
+test_expect_success 'criss-cross merge-base for octopus-step' '
        git reset --hard MMR &&
-       test_tick && git commit --allow-empty -m 1 && git tag CC1 &&
+       test_commit CC1 &&
        git reset --hard E &&
-       test_tick && git commit --allow-empty -m 2 && git tag CC2 &&
-       test_tick && git merge -s ours CC1 &&
-       test_tick && git commit --allow-empty -m o &&
-       test_tick && git commit --allow-empty -m B && git tag CCB &&
+       test_commit CC2 &&
+       test_tick &&
+       git merge -s ours CC1 &&
+       test_commit CC-o &&
+       test_commit CCB &&
        git reset --hard CC1 &&
-       test_tick && git merge -s ours CC2 &&
-       test_tick && git commit --allow-empty -m A && git tag CCA
-'
+       git merge -s ours CC2 &&
+       test_commit CCA &&
+
+       git rev-parse CC1 CC2 >expected &&
+       git merge-base --all CCB CCA^^ CCA^^2 >actual &&
 
-test_expect_success 'merge-base B A^^ A^^2' '
-       MB0=$(git merge-base --all CCB CCA^^ CCA^^2 | sort) &&
-       MB1=$(git rev-parse CC1 CC2 | sort) &&
-       test "$MB0" = "$MB1"
+       sort expected >expected.sorted &&
+       sort actual >actual.sorted &&
+       test_cmp expected.sorted actual.sorted
 '
 
 test_done
index 510bb9679f29c10645c902caea020651a797aa2f..af34a1e81711ca183680c9882353bd786b55e79f 100755 (executable)
@@ -8,9 +8,6 @@ note () {
        git tag "$1"
 }
 
-_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"
-
 unnote () {
        git name-rev --tags --stdin | sed -e "s|$_x40 (tags/\([^)]*\)) |\1 |g"
 }
diff --git a/t/t6015-rev-list-show-all-parents.sh b/t/t6015-rev-list-show-all-parents.sh
new file mode 100755 (executable)
index 0000000..8b146fb
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+test_description='--show-all --parents does not rewrite TREESAME commits'
+
+. ./test-lib.sh
+
+test_expect_success 'set up --show-all --parents test' '
+       test_commit one foo.txt &&
+       commit1=`git rev-list -1 HEAD` &&
+       test_commit two bar.txt &&
+       commit2=`git rev-list -1 HEAD` &&
+       test_commit three foo.txt &&
+       commit3=`git rev-list -1 HEAD`
+       '
+
+test_expect_success '--parents rewrites TREESAME parents correctly' '
+       echo $commit3 $commit1 > expected &&
+       echo $commit1 >> expected &&
+       git rev-list --parents HEAD -- foo.txt > actual &&
+       test_cmp expected actual
+       '
+
+test_expect_success '--parents --show-all does not rewrites TREESAME parents' '
+       echo $commit3 $commit2 > expected &&
+       echo $commit2 $commit1 >> expected &&
+       echo $commit1 >> expected &&
+       git rev-list --parents --show-all HEAD -- foo.txt > actual &&
+       test_cmp expected actual
+       '
+
+test_done
diff --git a/t/t6016-rev-list-graph-simplify-history.sh b/t/t6016-rev-list-graph-simplify-history.sh
new file mode 100755 (executable)
index 0000000..27fd52b
--- /dev/null
@@ -0,0 +1,276 @@
+#!/bin/sh
+
+# There's more than one "correct" way to represent the history graphically.
+# These tests depend on the current behavior of the graphing code.  If the
+# graphing code is ever changed to draw the output differently, these tests
+# cases will need to be updated to know about the new layout.
+
+test_description='--graph and simplified history'
+
+. ./test-lib.sh
+
+test_expect_success 'set up rev-list --graph test' '
+       # 3 commits on branch A
+       test_commit A1 foo.txt &&
+       test_commit A2 bar.txt &&
+       test_commit A3 bar.txt &&
+       git branch -m master A &&
+
+       # 2 commits on branch B, started from A1
+       git checkout -b B A1 &&
+       test_commit B1 foo.txt &&
+       test_commit B2 abc.txt &&
+
+       # 2 commits on branch C, started from A2
+       git checkout -b C A2 &&
+       test_commit C1 xyz.txt &&
+       test_commit C2 xyz.txt &&
+
+       # Octopus merge B and C into branch A
+       git checkout A &&
+       git merge B C &&
+       git tag A4
+
+       test_commit A5 bar.txt &&
+
+       # More commits on C, then merge C into A
+       git checkout C &&
+       test_commit C3 foo.txt &&
+       test_commit C4 bar.txt &&
+       git checkout A &&
+       git merge -s ours C &&
+       git tag A6
+
+       test_commit A7 bar.txt &&
+
+       # Store commit names in variables for later use
+       A1=$(git rev-parse --verify A1) &&
+       A2=$(git rev-parse --verify A2) &&
+       A3=$(git rev-parse --verify A3) &&
+       A4=$(git rev-parse --verify A4) &&
+       A5=$(git rev-parse --verify A5) &&
+       A6=$(git rev-parse --verify A6) &&
+       A7=$(git rev-parse --verify A7) &&
+       B1=$(git rev-parse --verify B1) &&
+       B2=$(git rev-parse --verify B2) &&
+       C1=$(git rev-parse --verify C1) &&
+       C2=$(git rev-parse --verify C2) &&
+       C3=$(git rev-parse --verify C3) &&
+       C4=$(git rev-parse --verify C4)
+       '
+
+test_expect_success '--graph --all' '
+       rm -f expected &&
+       echo "* $A7" >> expected &&
+       echo "*   $A6" >> expected &&
+       echo "|\\  " >> expected &&
+       echo "| * $C4" >> expected &&
+       echo "| * $C3" >> expected &&
+       echo "* | $A5" >> expected &&
+       echo "| |     " >> expected &&
+       echo "|  \\    " >> expected &&
+       echo "*-. \\   $A4" >> expected &&
+       echo "|\\ \\ \\  " >> expected &&
+       echo "| | |/  " >> expected &&
+       echo "| | * $C2" >> expected &&
+       echo "| | * $C1" >> expected &&
+       echo "| * | $B2" >> expected &&
+       echo "| * | $B1" >> expected &&
+       echo "* | | $A3" >> expected &&
+       echo "| |/  " >> expected &&
+       echo "|/|   " >> expected &&
+       echo "* | $A2" >> expected &&
+       echo "|/  " >> expected &&
+       echo "* $A1" >> expected &&
+       git rev-list --graph --all > actual &&
+       test_cmp expected actual
+       '
+
+# Make sure the graph_is_interesting() code still realizes
+# that undecorated merges are interesting, even with --simplify-by-decoration
+test_expect_success '--graph --simplify-by-decoration' '
+       rm -f expected &&
+       git tag -d A4
+       echo "* $A7" >> expected &&
+       echo "*   $A6" >> expected &&
+       echo "|\\  " >> expected &&
+       echo "| * $C4" >> expected &&
+       echo "| * $C3" >> expected &&
+       echo "* | $A5" >> expected &&
+       echo "| |     " >> expected &&
+       echo "|  \\    " >> expected &&
+       echo "*-. \\   $A4" >> expected &&
+       echo "|\\ \\ \\  " >> expected &&
+       echo "| | |/  " >> expected &&
+       echo "| | * $C2" >> expected &&
+       echo "| | * $C1" >> expected &&
+       echo "| * | $B2" >> expected &&
+       echo "| * | $B1" >> expected &&
+       echo "* | | $A3" >> expected &&
+       echo "| |/  " >> expected &&
+       echo "|/|   " >> expected &&
+       echo "* | $A2" >> expected &&
+       echo "|/  " >> expected &&
+       echo "* $A1" >> expected &&
+       git rev-list --graph --all --simplify-by-decoration > actual &&
+       test_cmp expected actual
+       '
+
+# Get rid of all decorations on branch B, and graph with it simplified away
+test_expect_success '--graph --simplify-by-decoration prune branch B' '
+       rm -f expected &&
+       git tag -d B2
+       git tag -d B1
+       git branch -d B
+       echo "* $A7" >> expected &&
+       echo "*   $A6" >> expected &&
+       echo "|\\  " >> expected &&
+       echo "| * $C4" >> expected &&
+       echo "| * $C3" >> expected &&
+       echo "* | $A5" >> expected &&
+       echo "* |   $A4" >> expected &&
+       echo "|\\ \\  " >> expected &&
+       echo "| |/  " >> expected &&
+       echo "| * $C2" >> expected &&
+       echo "| * $C1" >> expected &&
+       echo "* | $A3" >> expected &&
+       echo "|/  " >> expected &&
+       echo "* $A2" >> expected &&
+       echo "* $A1" >> expected &&
+       git rev-list --graph --simplify-by-decoration --all > actual &&
+       test_cmp expected actual
+       '
+
+test_expect_success '--graph --full-history -- bar.txt' '
+       rm -f expected &&
+       git tag -d B2
+       git tag -d B1
+       git branch -d B
+       echo "* $A7" >> expected &&
+       echo "*   $A6" >> expected &&
+       echo "|\\  " >> expected &&
+       echo "| * $C4" >> expected &&
+       echo "* | $A5" >> expected &&
+       echo "* |   $A4" >> expected &&
+       echo "|\\ \\  " >> expected &&
+       echo "| |/  " >> expected &&
+       echo "* | $A3" >> expected &&
+       echo "|/  " >> expected &&
+       echo "* $A2" >> expected &&
+       git rev-list --graph --full-history --all -- bar.txt > actual &&
+       test_cmp expected actual
+       '
+
+test_expect_success '--graph --full-history --simplify-merges -- bar.txt' '
+       rm -f expected &&
+       git tag -d B2
+       git tag -d B1
+       git branch -d B
+       echo "* $A7" >> expected &&
+       echo "*   $A6" >> expected &&
+       echo "|\\  " >> expected &&
+       echo "| * $C4" >> expected &&
+       echo "* | $A5" >> expected &&
+       echo "* | $A3" >> expected &&
+       echo "|/  " >> expected &&
+       echo "* $A2" >> expected &&
+       git rev-list --graph --full-history --simplify-merges --all \
+               -- bar.txt > actual &&
+       test_cmp expected actual
+       '
+
+test_expect_success '--graph -- bar.txt' '
+       rm -f expected &&
+       git tag -d B2
+       git tag -d B1
+       git branch -d B
+       echo "* $A7" >> expected &&
+       echo "* $A5" >> expected &&
+       echo "* $A3" >> expected &&
+       echo "| * $C4" >> expected &&
+       echo "|/  " >> expected &&
+       echo "* $A2" >> expected &&
+       git rev-list --graph --all -- bar.txt > actual &&
+       test_cmp expected actual
+       '
+
+test_expect_success '--graph --sparse -- bar.txt' '
+       rm -f expected &&
+       git tag -d B2
+       git tag -d B1
+       git branch -d B
+       echo "* $A7" >> expected &&
+       echo "* $A6" >> expected &&
+       echo "* $A5" >> expected &&
+       echo "* $A4" >> expected &&
+       echo "* $A3" >> expected &&
+       echo "| * $C4" >> expected &&
+       echo "| * $C3" >> expected &&
+       echo "| * $C2" >> expected &&
+       echo "| * $C1" >> expected &&
+       echo "|/  " >> expected &&
+       echo "* $A2" >> expected &&
+       echo "* $A1" >> expected &&
+       git rev-list --graph --sparse --all -- bar.txt > actual &&
+       test_cmp expected actual
+       '
+
+test_expect_success '--graph ^C4' '
+       rm -f expected &&
+       echo "* $A7" >> expected &&
+       echo "* $A6" >> expected &&
+       echo "* $A5" >> expected &&
+       echo "*   $A4" >> expected &&
+       echo "|\\  " >> expected &&
+       echo "| * $B2" >> expected &&
+       echo "| * $B1" >> expected &&
+       echo "* $A3" >> expected &&
+       git rev-list --graph --all ^C4 > actual &&
+       test_cmp expected actual
+       '
+
+test_expect_success '--graph ^C3' '
+       rm -f expected &&
+       echo "* $A7" >> expected &&
+       echo "*   $A6" >> expected &&
+       echo "|\\  " >> expected &&
+       echo "| * $C4" >> expected &&
+       echo "* $A5" >> expected &&
+       echo "*   $A4" >> expected &&
+       echo "|\\  " >> expected &&
+       echo "| * $B2" >> expected &&
+       echo "| * $B1" >> expected &&
+       echo "* $A3" >> expected &&
+       git rev-list --graph --all ^C3 > actual &&
+       test_cmp expected actual
+       '
+
+# I don't think the ordering of the boundary commits is really
+# that important, but this test depends on it.  If the ordering ever changes
+# in the code, we'll need to update this test.
+test_expect_success '--graph --boundary ^C3' '
+       rm -f expected &&
+       echo "* $A7" >> expected &&
+       echo "*   $A6" >> expected &&
+       echo "|\\  " >> expected &&
+       echo "| * $C4" >> expected &&
+       echo "* | $A5" >> expected &&
+       echo "| |     " >> expected &&
+       echo "|  \\    " >> expected &&
+       echo "*-. \\   $A4" >> expected &&
+       echo "|\\ \\ \\  " >> expected &&
+       echo "| * | | $B2" >> expected &&
+       echo "| * | | $B1" >> expected &&
+       echo "* | | | $A3" >> expected &&
+       echo "o | | | $A2" >> expected &&
+       echo "|/ / /  " >> expected &&
+       echo "o | | $A1" >> expected &&
+       echo " / /  " >> expected &&
+       echo "| o $C3" >> expected &&
+       echo "|/  " >> expected &&
+       echo "o $C2" >> expected &&
+       git rev-list --graph --boundary --all ^C3 > actual &&
+       test_cmp expected actual
+       '
+
+test_done
diff --git a/t/t6017-rev-list-stdin.sh b/t/t6017-rev-list-stdin.sh
new file mode 100755 (executable)
index 0000000..f1c32db
--- /dev/null
@@ -0,0 +1,61 @@
+#!/bin/sh
+#
+# Copyright (c) 2009, Junio C Hamano
+#
+
+test_description='log family learns --stdin'
+
+. ./test-lib.sh
+
+check () {
+       for cmd in rev-list "log --stat"
+       do
+               for i in "$@"
+               do
+                       printf "%s\n" $i
+               done >input &&
+               test_expect_success "check $cmd $*" '
+                       git $cmd $(cat input) >expect &&
+                       git $cmd --stdin <input >actual &&
+                       sed -e "s/^/input /" input &&
+                       sed -e "s/^/output /" expect &&
+                       test_cmp expect actual
+               '
+       done
+}
+
+them='1 2 3 4 5 6 7'
+
+test_expect_success setup '
+       (
+               for i in 0 $them
+               do
+                       for j in $them
+                       do
+                               echo $i.$j >file-$j &&
+                               git add file-$j || exit
+                       done &&
+                       test_tick &&
+                       git commit -m $i || exit
+               done &&
+               for i in $them
+               do
+                       git checkout -b side-$i master~$i &&
+                       echo updated $i >file-$i &&
+                       git add file-$i &&
+                       test_tick &&
+                       git commit -m side-$i || exit
+               done
+       )
+'
+
+check master
+check side-1 ^side-4
+check side-1 ^side-7 --
+check side-1 ^side-7 -- file-1
+check side-1 ^side-7 -- file-2
+check side-3 ^side-4 -- file-3
+check side-3 ^side-2
+check side-3 ^side-2 -- file-1
+
+test_done
diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh
new file mode 100755 (executable)
index 0000000..fb8291c
--- /dev/null
@@ -0,0 +1,216 @@
+#!/bin/sh
+
+test_description='rev-list/rev-parse --glob'
+
+. ./test-lib.sh
+
+commit () {
+       test_tick &&
+       echo $1 > foo &&
+       git add foo &&
+       git commit -m "$1"
+}
+
+compare () {
+       # Split arguments on whitespace.
+       git $1 $2 >expected &&
+       git $1 $3 >actual &&
+       test_cmp expected actual
+}
+
+test_expect_success 'setup' '
+
+       commit master &&
+       git checkout -b subspace/one master &&
+       commit one &&
+       git checkout -b subspace/two master &&
+       commit two &&
+       git checkout -b subspace-x master &&
+       commit subspace-x &&
+       git checkout -b other/three master &&
+       commit three &&
+       git checkout -b someref master &&
+       commit some &&
+       git checkout master &&
+       commit master2 &&
+       git tag foo/bar master &&
+       commit master3 &&
+       git update-ref refs/remotes/foo/baz master &&
+       commit master4
+'
+
+test_expect_success 'rev-parse --glob=refs/heads/subspace/*' '
+
+       compare rev-parse "subspace/one subspace/two" "--glob=refs/heads/subspace/*"
+
+'
+
+test_expect_success 'rev-parse --glob=heads/subspace/*' '
+
+       compare rev-parse "subspace/one subspace/two" "--glob=heads/subspace/*"
+
+'
+
+test_expect_success 'rev-parse --glob=refs/heads/subspace/' '
+
+       compare rev-parse "subspace/one subspace/two" "--glob=refs/heads/subspace/"
+
+'
+
+test_expect_success 'rev-parse --glob=heads/subspace/' '
+
+       compare rev-parse "subspace/one subspace/two" "--glob=heads/subspace/"
+
+'
+
+test_expect_success 'rev-parse --glob=heads/subspace' '
+
+       compare rev-parse "subspace/one subspace/two" "--glob=heads/subspace"
+
+'
+
+test_expect_success 'rev-parse --branches=subspace/*' '
+
+       compare rev-parse "subspace/one subspace/two" "--branches=subspace/*"
+
+'
+
+test_expect_success 'rev-parse --branches=subspace/' '
+
+       compare rev-parse "subspace/one subspace/two" "--branches=subspace/"
+
+'
+
+test_expect_success 'rev-parse --branches=subspace' '
+
+       compare rev-parse "subspace/one subspace/two" "--branches=subspace"
+
+'
+
+test_expect_success 'rev-parse --glob=heads/subspace/* --glob=heads/other/*' '
+
+       compare rev-parse "subspace/one subspace/two other/three" "--glob=heads/subspace/* --glob=heads/other/*"
+
+'
+
+test_expect_success 'rev-parse --glob=heads/someref/* master' '
+
+       compare rev-parse "master" "--glob=heads/someref/* master"
+
+'
+
+test_expect_success 'rev-parse --glob=heads/*' '
+
+       compare rev-parse "master other/three someref subspace-x subspace/one subspace/two" "--glob=heads/*"
+
+'
+
+test_expect_success 'rev-parse --tags=foo' '
+
+       compare rev-parse "foo/bar" "--tags=foo"
+
+'
+
+test_expect_success 'rev-parse --remotes=foo' '
+
+       compare rev-parse "foo/baz" "--remotes=foo"
+
+'
+
+test_expect_success 'rev-list --glob=refs/heads/subspace/*' '
+
+       compare rev-list "subspace/one subspace/two" "--glob=refs/heads/subspace/*"
+
+'
+
+test_expect_success 'rev-list --glob refs/heads/subspace/*' '
+
+       compare rev-list "subspace/one subspace/two" "--glob refs/heads/subspace/*"
+
+'
+
+test_expect_success 'rev-list --glob=heads/subspace/*' '
+
+       compare rev-list "subspace/one subspace/two" "--glob=heads/subspace/*"
+
+'
+
+test_expect_success 'rev-list --glob=refs/heads/subspace/' '
+
+       compare rev-list "subspace/one subspace/two" "--glob=refs/heads/subspace/"
+
+'
+
+test_expect_success 'rev-list --glob=heads/subspace/' '
+
+       compare rev-list "subspace/one subspace/two" "--glob=heads/subspace/"
+
+'
+
+test_expect_success 'rev-list --glob=heads/subspace' '
+
+       compare rev-list "subspace/one subspace/two" "--glob=heads/subspace"
+
+'
+
+test_expect_success 'rev-list --branches=subspace/*' '
+
+       compare rev-list "subspace/one subspace/two" "--branches=subspace/*"
+
+'
+
+test_expect_success 'rev-list --branches=subspace/' '
+
+       compare rev-list "subspace/one subspace/two" "--branches=subspace/"
+
+'
+
+test_expect_success 'rev-list --branches=subspace' '
+
+       compare rev-list "subspace/one subspace/two" "--branches=subspace"
+
+'
+
+test_expect_success 'rev-list --branches' '
+
+       compare rev-list "master subspace-x someref other/three subspace/one subspace/two" "--branches"
+
+'
+
+test_expect_success 'rev-list --glob=heads/someref/* master' '
+
+       compare rev-list "master" "--glob=heads/someref/* master"
+
+'
+
+test_expect_success 'rev-list --glob=heads/subspace/* --glob=heads/other/*' '
+
+       compare rev-list "subspace/one subspace/two other/three" "--glob=heads/subspace/* --glob=heads/other/*"
+
+'
+
+test_expect_success 'rev-list --glob=heads/*' '
+
+       compare rev-list "master other/three someref subspace-x subspace/one subspace/two" "--glob=heads/*"
+
+'
+
+test_expect_success 'rev-list --tags=foo' '
+
+       compare rev-list "foo/bar" "--tags=foo"
+
+'
+
+test_expect_success 'rev-list --tags' '
+
+       compare rev-list "foo/bar" "--tags"
+
+'
+
+test_expect_success 'rev-list --remotes=foo' '
+
+       compare rev-list "foo/baz" "--remotes=foo"
+
+'
+
+test_done
diff --git a/t/t6019-rev-list-ancestry-path.sh b/t/t6019-rev-list-ancestry-path.sh
new file mode 100755 (executable)
index 0000000..7641029
--- /dev/null
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+test_description='--ancestry-path'
+
+#          D---E-------F
+#         /     \       \
+#    B---C---G---H---I---J
+#   /                     \
+#  A-------K---------------L--M
+#
+#  D..M                 == E F G H I J K L M
+#  --ancestry-path D..M == E F H I J L M
+#
+#  D..M -- M.t                 == M
+#  --ancestry-path D..M -- M.t == M
+
+. ./test-lib.sh
+
+test_merge () {
+       test_tick &&
+       git merge -s ours -m "$2" "$1" &&
+       git tag "$2"
+}
+
+test_expect_success setup '
+       test_commit A &&
+       test_commit B &&
+       test_commit C &&
+       test_commit D &&
+       test_commit E &&
+       test_commit F &&
+       git reset --hard C &&
+       test_commit G &&
+       test_merge E H &&
+       test_commit I &&
+       test_merge F J &&
+       git reset --hard A &&
+       test_commit K &&
+       test_merge J L &&
+       test_commit M
+'
+
+test_expect_success 'rev-list D..M' '
+       for c in E F G H I J K L M; do echo $c; done >expect &&
+       git rev-list --format=%s D..M |
+       sed -e "/^commit /d" |
+       sort >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'rev-list --ancestry-path D..M' '
+       for c in E F H I J L M; do echo $c; done >expect &&
+       git rev-list --ancestry-path --format=%s D..M |
+       sed -e "/^commit /d" |
+       sort >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'rev-list D..M -- M.t' '
+       echo M >expect &&
+       git rev-list --format=%s D..M -- M.t |
+       sed -e "/^commit /d" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'rev-list --ancestry-patch D..M -- M.t' '
+       echo M >expect &&
+       git rev-list --ancestry-path --format=%s D..M -- M.t |
+       sed -e "/^commit /d" >actual &&
+       test_cmp expect actual
+'
+
+test_done
index a19d49de28c457d1a5726400d25c1f731506a05f..490d3971142a87e940f3d27e7e7f068b276ff602 100755 (executable)
@@ -22,4 +22,27 @@ git commit -m "File: dir"'
 
 test_expect_code 1 'Merge with d/f conflicts' 'git merge "merge msg" B master'
 
+test_expect_success 'F/D conflict' '
+       git reset --hard &&
+       git checkout master &&
+       rm .git/index &&
+
+       mkdir before &&
+       echo FILE >before/one &&
+       echo FILE >after &&
+       git add . &&
+       git commit -m first &&
+
+       rm -f after &&
+       git mv before after &&
+       git commit -m move &&
+
+       git checkout -b para HEAD^ &&
+       echo COMPLETELY ANOTHER FILE >another &&
+       git add . &&
+       git commit -m para &&
+
+       git merge master
+'
+
 test_done
index e3f7ae8120aa2a46b25dd3830597cb863a5f5e20..b66544b76d545a396ef068438f3980b3f544efdd 100755 (executable)
@@ -280,7 +280,7 @@ test_expect_success 'updated working tree file should prevent the merge' '
                echo "BAD: should have complained"
                return 1
        }
-       diff M M.saved || {
+       test_cmp M M.saved || {
                echo "BAD: should have left M intact"
                return 1
        }
@@ -301,7 +301,7 @@ test_expect_success 'updated working tree file should prevent the merge' '
                echo "BAD: should have complained"
                return 1
        }
-       diff M M.saved || {
+       test_cmp M M.saved || {
                echo "BAD: should have left M intact"
                return 1
        }
index f8942bc8908fb92b1b11580e3554a81377dc3ba1..d486d73994cf563063b578ec5e212331e87bf67f 100755 (executable)
@@ -54,10 +54,20 @@ deduxit me super semitas jusitiae,
 EOF
 printf "propter nomen suum." >> new4.txt
 
+test_expect_success 'merge with no changes' '
+       cp orig.txt test.txt &&
+       git merge-file test.txt orig.txt orig.txt &&
+       test_cmp test.txt orig.txt
+'
+
 cp new1.txt test.txt
 test_expect_success "merge without conflict" \
        "git merge-file test.txt orig.txt new2.txt"
 
+cp new1.txt test.txt
+test_expect_success "merge without conflict (--quiet)" \
+       "git merge-file --quiet test.txt orig.txt new2.txt"
+
 cp new1.txt test2.txt
 test_expect_success "merge without conflict (missing LF at EOF)" \
        "git merge-file test2.txt orig.txt new2.txt"
@@ -140,8 +150,8 @@ test_expect_success 'binary files cannot be merged' '
        grep "Cannot merge binary files" merge.err
 '
 
-sed -e "s/deerit.$/deerit;/" -e "s/me;$/me./" < new5.txt > new6.txt
-sed -e "s/deerit.$/deerit,/" -e "s/me;$/me,/" < new5.txt > new7.txt
+sed -e "s/deerit.\$/deerit;/" -e "s/me;\$/me./" < new5.txt > new6.txt
+sed -e "s/deerit.\$/deerit,/" -e "s/me;\$/me,/" < new5.txt > new7.txt
 
 test_expect_success 'MERGE_ZEALOUS simplifies non-conflicts' '
 
@@ -171,7 +181,7 @@ et nihil mihi deerit;
 
 In loco pascuae ibi me collocavit;
 super aquam refectionis educavit me.
-|||||||
+||||||| new5.txt
 et nihil mihi deerit.
 In loco pascuae ibi me collocavit,
 super aquam refectionis educavit me;
@@ -205,4 +215,41 @@ test_expect_success '"diff3 -m" style output (2)' '
        test_cmp expect actual
 '
 
+cat >expect <<\EOF
+Dominus regit me,
+<<<<<<<<<< new8.txt
+et nihil mihi deerit;
+
+
+
+
+In loco pascuae ibi me collocavit;
+super aquam refectionis educavit me.
+|||||||||| new5.txt
+et nihil mihi deerit.
+In loco pascuae ibi me collocavit,
+super aquam refectionis educavit me;
+==========
+et nihil mihi deerit,
+
+
+
+
+In loco pascuae ibi me collocavit --
+super aquam refectionis educavit me,
+>>>>>>>>>> new9.txt
+animam meam convertit,
+deduxit me super semitas jusitiae,
+propter nomen suum.
+Nam et si ambulavero in medio umbrae mortis,
+non timebo mala, quoniam TU mecum es:
+virga tua et baculus tuus ipsa me consolata sunt.
+EOF
+
+test_expect_success 'marker size' '
+       test_must_fail git merge-file -p --marker-size=10 \
+               new8.txt new5.txt new9.txt >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 129fa3000c9543804b43e74e27eec523e328bb5c..b3fbf659c003acbed785558c21950046b8caced8 100755 (executable)
@@ -65,18 +65,18 @@ test_expect_success "combined merge conflicts" "
 "
 
 cat > expect << EOF
-<<<<<<< HEAD:a1
+<<<<<<< HEAD
 F
 =======
 G
->>>>>>> G:a1
+>>>>>>> G
 EOF
 
 test_expect_success "result contains a conflict" "test_cmp expect a1"
 
 git ls-files --stage > out
 cat > expect << EOF
-100644 da056ce14a2241509897fa68bb2b3b6e6194ef9e 1      a1
+100644 439cc46de773d8a83c77799b7cc9191c128bfcff 1      a1
 100644 cf84443e49e1b366fac938711ddf4be2d4d1d9e9 2      a1
 100644 fd7923529855d0b274795ae3349c5e0438333979 3      a1
 EOF
@@ -93,8 +93,7 @@ test_expect_success 'refuse to merge binary files' '
        git add binary-file &&
        git commit -m binary2 &&
        test_must_fail git merge F > merge.out 2> merge.err &&
-       grep "Cannot merge binary files: HEAD:binary-file vs. F:binary-file" \
-               merge.err
+       grep "Cannot merge binary files: binary-file (HEAD vs. F)" merge.err
 '
 
 test_expect_success 'mark rename/delete as unmerged' '
index 1ba0a252230a283a6bb2463d98537aab1eaf4fb8..5e439972bea358f700ded4266df31f7dc4514800 100755 (executable)
@@ -70,6 +70,18 @@ test_expect_success 'check merge result in working tree' '
 
 '
 
+test_expect_success 'retry the merge with longer context' '
+       echo text conflict-marker-size=32 >>.gitattributes &&
+       git checkout -m text &&
+       sed -ne "/^\([<=>]\)\1\1\1*/{
+               s/ .*$//
+               p
+       }" >actual text &&
+       grep ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" actual &&
+       grep "================================" actual &&
+       grep "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" actual
+'
+
 cat >./custom-merge <<\EOF
 #!/bin/sh
 
index f8f3e3ff2c00df468f5703a4e0ac31f52e42e06d..a91644e3b2ac3490cfe49d5e67c9736197cd56a1 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='merge fast forward and up to date'
+test_description='merge fast-forward and up to date'
 
 . ./test-lib.sh
 
index 5bbfa44e8d9ee3eebb18eb07e93380c802741a18..3900d9f61f84a9eab907e87fca7bb9429ea36f04 100755 (executable)
@@ -52,6 +52,7 @@ test_expect_success 'initial merge' '
        git merge -s ours --no-commit gui/master &&
        git read-tree --prefix=git-gui/ -u gui/master &&
        git commit -m "Merge git-gui as our subdirectory" &&
+       git checkout -b work &&
        git ls-files -s >actual &&
        (
                echo "100644 $o1 0      git-gui/git-gui.sh"
@@ -65,9 +66,10 @@ test_expect_success 'merge update' '
        echo git-gui2 > git-gui.sh &&
        o3=$(git hash-object git-gui.sh) &&
        git add git-gui.sh &&
+       git checkout -b master2 &&
        git commit -m "update git-gui" &&
        cd ../git &&
-       git pull -s subtree gui master &&
+       git pull -s subtree gui master2 &&
        git ls-files -s >actual &&
        (
                echo "100644 $o3 0      git-gui/git-gui.sh"
@@ -76,4 +78,47 @@ test_expect_success 'merge update' '
        test_cmp expected actual
 '
 
+test_expect_success 'initial ambiguous subtree' '
+       cd ../git &&
+       git reset --hard master &&
+       git checkout -b master2 &&
+       git merge -s ours --no-commit gui/master &&
+       git read-tree --prefix=git-gui2/ -u gui/master &&
+       git commit -m "Merge git-gui2 as our subdirectory" &&
+       git checkout -b work2 &&
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o1 0      git-gui/git-gui.sh"
+               echo "100644 $o1 0      git-gui2/git-gui.sh"
+               echo "100644 $o2 0      git.c"
+       ) >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'merge using explicit' '
+       cd ../git &&
+       git reset --hard master2 &&
+       git pull -Xsubtree=git-gui gui master2 &&
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o3 0      git-gui/git-gui.sh"
+               echo "100644 $o1 0      git-gui2/git-gui.sh"
+               echo "100644 $o2 0      git.c"
+       ) >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'merge2 using explicit' '
+       cd ../git &&
+       git reset --hard master2 &&
+       git pull -Xsubtree=git-gui2 gui master2 &&
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o1 0      git-gui/git-gui.sh"
+               echo "100644 $o3 0      git-gui2/git-gui.sh"
+               echo "100644 $o2 0      git.c"
+       ) >expected &&
+       test_cmp expected actual
+'
+
 test_done
index 54b7ea6505d8c189c6c557cffd3d6518df06fb73..3b042aacd63f77651fdaf3d10b65f4fc85669a75 100755 (executable)
@@ -175,7 +175,7 @@ test_expect_success 'bisect skip: successfull result' '
        git bisect start $HASH4 $HASH1 &&
        git bisect skip &&
        git bisect bad > my_bisect_log.txt &&
-       grep "$HASH2 is first bad commit" my_bisect_log.txt &&
+       grep "$HASH2 is the first bad commit" my_bisect_log.txt &&
        git bisect reset
 '
 
@@ -261,7 +261,7 @@ test_expect_success \
      git bisect good $HASH1 &&
      git bisect bad $HASH4 &&
      git bisect run ./test_script.sh > my_bisect_log.txt &&
-     grep "$HASH3 is first bad commit" my_bisect_log.txt &&
+     grep "$HASH3 is the first bad commit" my_bisect_log.txt &&
      git bisect reset'
 
 # We want to automatically find the commit that
@@ -274,7 +274,7 @@ test_expect_success \
      chmod +x test_script.sh &&
      git bisect start $HASH4 $HASH1 &&
      git bisect run ./test_script.sh > my_bisect_log.txt &&
-     grep "$HASH4 is first bad commit" my_bisect_log.txt &&
+     grep "$HASH4 is the first bad commit" my_bisect_log.txt &&
      git bisect reset'
 
 # $HASH1 is good, $HASH5 is bad, we skip $HASH3
@@ -287,14 +287,14 @@ test_expect_success 'bisect skip: add line and then a new test' '
        git bisect start $HASH5 $HASH1 &&
        git bisect skip &&
        git bisect good > my_bisect_log.txt &&
-       grep "$HASH5 is first bad commit" my_bisect_log.txt &&
+       grep "$HASH5 is the first bad commit" my_bisect_log.txt &&
        git bisect log > log_to_replay.txt &&
        git bisect reset
 '
 
 test_expect_success 'bisect skip and bisect replay' '
        git bisect replay log_to_replay.txt > my_bisect_log.txt &&
-       grep "$HASH5 is first bad commit" my_bisect_log.txt &&
+       grep "$HASH5 is the first bad commit" my_bisect_log.txt &&
        git bisect reset
 '
 
@@ -335,7 +335,7 @@ test_expect_success 'bisect run & skip: find first bad' '
        chmod +x test_script.sh &&
        git bisect start $HASH7 $HASH1 &&
        git bisect run ./test_script.sh > my_bisect_log.txt &&
-       grep "$HASH6 is first bad commit" my_bisect_log.txt
+       grep "$HASH6 is the first bad commit" my_bisect_log.txt
 '
 
 test_expect_success 'bisect skip only one range' '
@@ -385,7 +385,7 @@ test_expect_success 'bisect does not create a "bisect" branch' '
        rev_hash6=$(git rev-parse --verify HEAD) &&
        test "$rev_hash6" = "$HASH6" &&
        git bisect good > my_bisect_log.txt &&
-       grep "$HASH7 is first bad commit" my_bisect_log.txt &&
+       grep "$HASH7 is the first bad commit" my_bisect_log.txt &&
        git bisect reset &&
        rev_hash6=$(git rev-parse --verify bisect) &&
        test "$rev_hash6" = "$HASH6" &&
@@ -423,7 +423,7 @@ test_expect_success 'skipped merge base when good and bad are siblings' '
        grep "merge base must be tested" my_bisect_log.txt &&
        grep $HASH4 my_bisect_log.txt &&
        git bisect skip > my_bisect_log.txt 2>&1 &&
-       grep "Warning" my_bisect_log.txt &&
+       grep "warning" my_bisect_log.txt &&
        grep $SIDE_HASH6 my_bisect_log.txt &&
        git bisect reset
 '
@@ -482,28 +482,17 @@ test_expect_success 'good merge bases when good and bad are siblings' '
        git bisect reset
 '
 
-check_trace() {
-       grep "$1" "$GIT_TRACE" | grep "\^$2" | grep "$3" >/dev/null
-}
-
 test_expect_success 'optimized merge base checks' '
-       GIT_TRACE="$(pwd)/trace.log" &&
-       export GIT_TRACE &&
        git bisect start "$HASH7" "$SIDE_HASH7" > my_bisect_log.txt &&
        grep "merge base must be tested" my_bisect_log.txt &&
        grep "$HASH4" my_bisect_log.txt &&
-       check_trace "rev-list" "$HASH7" "$SIDE_HASH7" &&
        git bisect good > my_bisect_log2.txt &&
        test -f ".git/BISECT_ANCESTORS_OK" &&
        test "$HASH6" = $(git rev-parse --verify HEAD) &&
-       : > "$GIT_TRACE" &&
        git bisect bad > my_bisect_log3.txt &&
-       test_must_fail check_trace "rev-list" "$HASH6" "$SIDE_HASH7" &&
        git bisect good "$A_HASH" > my_bisect_log4.txt &&
        grep "merge base must be tested" my_bisect_log4.txt &&
-       test_must_fail test -f ".git/BISECT_ANCESTORS_OK" &&
-       check_trace "rev-list" "$HASH6" "$A_HASH" &&
-       unset GIT_TRACE
+       test_must_fail test -f ".git/BISECT_ANCESTORS_OK"
 '
 
 # This creates another side branch called "parallel" with some files
@@ -545,7 +534,7 @@ test_expect_success 'restricting bisection on one dir' '
        para1=$(git rev-parse --verify HEAD) &&
        test "$para1" = "$PARA_HASH1" &&
        git bisect bad > my_bisect_log.txt &&
-       grep "$PARA_HASH1 is first bad commit" my_bisect_log.txt
+       grep "$PARA_HASH1 is the first bad commit" my_bisect_log.txt
 '
 
 test_expect_success 'restricting bisection on one dir and a file' '
@@ -563,7 +552,24 @@ test_expect_success 'restricting bisection on one dir and a file' '
        para1=$(git rev-parse --verify HEAD) &&
        test "$para1" = "$PARA_HASH1" &&
        git bisect good > my_bisect_log.txt &&
-       grep "$PARA_HASH4 is first bad commit" my_bisect_log.txt
+       grep "$PARA_HASH4 is the first bad commit" my_bisect_log.txt
+'
+
+test_expect_success 'skipping away from skipped commit' '
+       git bisect start $PARA_HASH7 $HASH1 &&
+       para4=$(git rev-parse --verify HEAD) &&
+       test "$para4" = "$PARA_HASH4" &&
+        git bisect skip &&
+       hash7=$(git rev-parse --verify HEAD) &&
+       test "$hash7" = "$HASH7" &&
+        git bisect skip &&
+       para3=$(git rev-parse --verify HEAD) &&
+       test "$para3" = "$PARA_HASH3"
+'
+
+test_expect_success 'erroring out when using bad path parameters' '
+       test_must_fail git bisect start $PARA_HASH7 $HASH1 -- foobar 2> error.txt &&
+       grep "bad path parameters" error.txt
 '
 
 #
index 8a3304fb0b5901fb02435d3b77c3d049404f4e25..1cd649e245e49735afa539ab0e982036154c4797 100755 (executable)
@@ -2,11 +2,7 @@
 
 test_description='merge-recursive: handle file mode'
 . ./test-lib.sh
-
-if ! test "$(git config --bool core.filemode)" = false
-then
-       test_set_prereq FILEMODE
-fi
+. "$TEST_DIRECTORY"/lib-prereq-FILEMODE.sh
 
 test_expect_success 'mode change in one branch: keep changed version' '
        : >file1 &&
@@ -57,4 +53,35 @@ test_expect_success FILEMODE 'verify executable bit on file' '
        test -x file2
 '
 
+test_expect_success 'merging with triple rename across D/F conflict' '
+       git reset --hard HEAD &&
+       git checkout -b main &&
+       git rm -rf . &&
+
+       echo "just a file" >sub1 &&
+       mkdir -p sub2 &&
+       echo content1 >sub2/file1 &&
+       echo content2 >sub2/file2 &&
+       echo content3 >sub2/file3 &&
+       mkdir simple &&
+       echo base >simple/bar &&
+       git add -A &&
+       test_tick &&
+       git commit -m base &&
+
+       git checkout -b other &&
+       echo more >>simple/bar &&
+       test_tick &&
+       git commit -a -m changesimplefile &&
+
+       git checkout main &&
+       git rm sub1 &&
+       git mv sub2 sub1 &&
+       test_tick &&
+       git commit -m changefiletodir &&
+
+       test_tick &&
+       git merge other
+'
+
 test_done
index 75d9602de4d4238b4182956127540525f711d33f..e8d65eefb521cc9f2e8211918734d069381772d4 100755 (executable)
@@ -1,13 +1,5 @@
 #!/bin/sh
 
-append_cr () {
-       sed -e 's/$/Q/' | tr Q '\015'
-}
-
-remove_cr () {
-       tr '\015' Q | sed -e 's/Q$//'
-}
-
 test_description='merge conflict in crlf repo
 
                b---M
diff --git a/t/t6035-merge-dir-to-symlink.sh b/t/t6035-merge-dir-to-symlink.sh
new file mode 100755 (executable)
index 0000000..92e02d5
--- /dev/null
@@ -0,0 +1,143 @@
+#!/bin/sh
+
+test_description='merging when a directory was replaced with a symlink'
+. ./test-lib.sh
+
+test_expect_success SYMLINKS 'create a commit where dir a/b changed to symlink' '
+       mkdir -p a/b/c a/b-2/c &&
+       > a/b/c/d &&
+       > a/b-2/c/d &&
+       > a/x &&
+       git add -A &&
+       git commit -m base &&
+       git tag start &&
+       rm -rf a/b &&
+       ln -s b-2 a/b &&
+       git add -A &&
+       git commit -m "dir to symlink"
+'
+
+test_expect_success SYMLINKS 'keep a/b-2/c/d across checkout' '
+       git checkout HEAD^0 &&
+       git reset --hard master &&
+       git rm --cached a/b &&
+       git commit -m "untracked symlink remains" &&
+        git checkout start^0 &&
+        test -f a/b-2/c/d
+'
+
+test_expect_success SYMLINKS 'checkout should not have deleted a/b-2/c/d' '
+       git checkout HEAD^0 &&
+       git reset --hard master &&
+        git checkout start^0 &&
+        test -f a/b-2/c/d
+'
+
+test_expect_success SYMLINKS 'setup for merge test' '
+       git reset --hard &&
+       test -f a/b-2/c/d &&
+       echo x > a/x &&
+       git add a/x &&
+       git commit -m x &&
+       git tag baseline
+'
+
+test_expect_success SYMLINKS 'Handle D/F conflict, do not lose a/b-2/c/d in merge (resolve)' '
+       git reset --hard &&
+       git checkout baseline^0 &&
+       git merge -s resolve master &&
+       test -h a/b &&
+       test -f a/b-2/c/d
+'
+
+test_expect_success SYMLINKS 'Handle D/F conflict, do not lose a/b-2/c/d in merge (recursive)' '
+       git reset --hard &&
+       git checkout baseline^0 &&
+       git merge -s recursive master &&
+       test -h a/b &&
+       test -f a/b-2/c/d
+'
+
+test_expect_success SYMLINKS 'Handle F/D conflict, do not lose a/b-2/c/d in merge (resolve)' '
+       git reset --hard &&
+       git checkout master^0 &&
+       git merge -s resolve baseline^0 &&
+       test -h a/b &&
+       test -f a/b-2/c/d
+'
+
+test_expect_success SYMLINKS 'Handle F/D conflict, do not lose a/b-2/c/d in merge (recursive)' '
+       git reset --hard &&
+       git checkout master^0 &&
+       git merge -s recursive baseline^0 &&
+       test -h a/b &&
+       test -f a/b-2/c/d
+'
+
+test_expect_failure SYMLINKS 'do not lose untracked in merge (resolve)' '
+       git reset --hard &&
+       git checkout baseline^0 &&
+       >a/b/c/e &&
+       test_must_fail git merge -s resolve master &&
+       test -f a/b/c/e &&
+       test -f a/b-2/c/d
+'
+
+test_expect_success SYMLINKS 'do not lose untracked in merge (recursive)' '
+       git reset --hard &&
+       git checkout baseline^0 &&
+       >a/b/c/e &&
+       test_must_fail git merge -s recursive master &&
+       test -f a/b/c/e &&
+       test -f a/b-2/c/d
+'
+
+test_expect_success SYMLINKS 'do not lose modifications in merge (resolve)' '
+       git reset --hard &&
+       git checkout baseline^0 &&
+       echo more content >>a/b/c/d &&
+       test_must_fail git merge -s resolve master
+'
+
+test_expect_success SYMLINKS 'do not lose modifications in merge (recursive)' '
+       git reset --hard &&
+       git checkout baseline^0 &&
+       echo more content >>a/b/c/d &&
+       test_must_fail git merge -s recursive master
+'
+
+test_expect_success SYMLINKS 'setup a merge where dir a/b-2 changed to symlink' '
+       git reset --hard &&
+       git checkout start^0 &&
+       rm -rf a/b-2 &&
+       ln -s b a/b-2 &&
+       git add -A &&
+       git commit -m "dir a/b-2 to symlink" &&
+       git tag test2
+'
+
+test_expect_success SYMLINKS 'merge should not have D/F conflicts (resolve)' '
+       git reset --hard &&
+       git checkout baseline^0 &&
+       git merge -s resolve test2 &&
+       test -h a/b-2 &&
+       test -f a/b/c/d
+'
+
+test_expect_success SYMLINKS 'merge should not have D/F conflicts (recursive)' '
+       git reset --hard &&
+       git checkout baseline^0 &&
+       git merge -s recursive test2 &&
+       test -h a/b-2 &&
+       test -f a/b/c/d
+'
+
+test_expect_success SYMLINKS 'merge should not have F/D conflicts (recursive)' '
+       git reset --hard &&
+       git checkout -b foo test2 &&
+       git merge -s recursive baseline^0 &&
+       test -h a/b-2 &&
+       test -f a/b/c/d
+'
+
+test_done
diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh
new file mode 100755 (executable)
index 0000000..b874141
--- /dev/null
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+test_description='recursive merge corner cases'
+
+. ./test-lib.sh
+
+#
+#  L1  L2
+#   o---o
+#  / \ / \
+# o   X   ?
+#  \ / \ /
+#   o---o
+#  R1  R2
+#
+
+test_expect_success setup '
+       ten="0 1 2 3 4 5 6 7 8 9"
+       for i in $ten
+       do
+               echo line $i in a sample file
+       done >one &&
+       for i in $ten
+       do
+               echo line $i in another sample file
+       done >two &&
+       git add one two &&
+       test_tick && git commit -m initial &&
+
+       git branch L1 &&
+       git checkout -b R1 &&
+       git mv one three &&
+       test_tick && git commit -m R1 &&
+
+       git checkout L1 &&
+       git mv two three &&
+       test_tick && git commit -m L1 &&
+
+       git checkout L1^0 &&
+       test_tick && git merge -s ours R1 &&
+       git tag L2 &&
+
+       git checkout R1^0 &&
+       test_tick && git merge -s ours L1 &&
+       git tag R2
+'
+
+test_expect_success merge '
+       git reset --hard &&
+       git checkout L2^0 &&
+
+       test_must_fail git merge -s recursive R2^0
+'
+
+test_done
diff --git a/t/t6037-merge-ours-theirs.sh b/t/t6037-merge-ours-theirs.sh
new file mode 100755 (executable)
index 0000000..2cf42c7
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+test_description='Merge-recursive ours and theirs variants'
+. ./test-lib.sh
+
+test_expect_success setup '
+       for i in 1 2 3 4 5 6 7 8 9
+       do
+               echo "$i"
+       done >file &&
+       git add file &&
+       cp file elif &&
+       git commit -m initial &&
+
+       sed -e "s/1/one/" -e "s/9/nine/" >file <elif &&
+       git commit -a -m ours &&
+
+       git checkout -b side HEAD^ &&
+
+       sed -e "s/9/nueve/" >file <elif &&
+       git commit -a -m theirs &&
+
+       git checkout master^0
+'
+
+test_expect_success 'plain recursive - should conflict' '
+       git reset --hard master &&
+       test_must_fail git merge -s recursive side &&
+       grep nine file &&
+       grep nueve file &&
+       ! grep 9 file &&
+       grep one file &&
+       ! grep 1 file
+'
+
+test_expect_success 'recursive favouring theirs' '
+       git reset --hard master &&
+       git merge -s recursive -Xtheirs side &&
+       ! grep nine file &&
+       grep nueve file &&
+       ! grep 9 file &&
+       grep one file &&
+       ! grep 1 file
+'
+
+test_expect_success 'recursive favouring ours' '
+       git reset --hard master &&
+       git merge -s recursive -X ours side &&
+       grep nine file &&
+       ! grep nueve file &&
+       ! grep 9 file &&
+       grep one file &&
+       ! grep 1 file
+'
+
+test_expect_success 'pull with -X' '
+       git reset --hard master && git pull -s recursive -Xours . side &&
+       git reset --hard master && git pull -s recursive -X ours . side &&
+       git reset --hard master && git pull -s recursive -Xtheirs . side &&
+       git reset --hard master && git pull -s recursive -X theirs . side &&
+       git reset --hard master && test_must_fail git pull -s recursive -X bork . side
+'
+
+test_done
diff --git a/t/t6038-merge-text-auto.sh b/t/t6038-merge-text-auto.sh
new file mode 100755 (executable)
index 0000000..52d0dc4
--- /dev/null
@@ -0,0 +1,189 @@
+#!/bin/sh
+
+test_description='CRLF merge conflict across text=auto change
+
+* [master] remove .gitattributes
+ ! [side] add line from b
+--
+ + [side] add line from b
+*  [master] remove .gitattributes
+*  [master^] add line from a
+*  [master~2] normalize file
+*+ [side^] Initial
+'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       git config core.autocrlf false &&
+
+       echo first line | append_cr >file &&
+       echo first line >control_file &&
+       echo only line >inert_file &&
+
+       git add file control_file inert_file &&
+       test_tick &&
+       git commit -m "Initial" &&
+       git tag initial &&
+       git branch side &&
+
+       echo "* text=auto" >.gitattributes &&
+       touch file &&
+       git add .gitattributes file &&
+       test_tick &&
+       git commit -m "normalize file" &&
+
+       echo same line | append_cr >>file &&
+       echo same line >>control_file &&
+       git add file control_file &&
+       test_tick &&
+       git commit -m "add line from a" &&
+       git tag a &&
+
+       git rm .gitattributes &&
+       rm file &&
+       git checkout file &&
+       test_tick &&
+       git commit -m "remove .gitattributes" &&
+       git tag c &&
+
+       git checkout side &&
+       echo same line | append_cr >>file &&
+       echo same line >>control_file &&
+       git add file control_file &&
+       test_tick &&
+       git commit -m "add line from b" &&
+       git tag b &&
+
+       git checkout master
+'
+
+test_expect_success 'set up fuzz_conflict() helper' '
+       fuzz_conflict() {
+               sed -e "s/^\([<>=]......\) .*/\1/" "$@"
+       }
+'
+
+test_expect_success 'Merge after setting text=auto' '
+       cat <<-\EOF >expected &&
+       first line
+       same line
+       EOF
+
+       git config merge.renormalize true &&
+       git rm -fr . &&
+       rm -f .gitattributes &&
+       git reset --hard a &&
+       git merge b &&
+       test_cmp expected file
+'
+
+test_expect_success 'Merge addition of text=auto' '
+       cat <<-\EOF >expected &&
+       first line
+       same line
+       EOF
+
+       git config merge.renormalize true &&
+       git rm -fr . &&
+       rm -f .gitattributes &&
+       git reset --hard b &&
+       git merge a &&
+       test_cmp expected file
+'
+
+test_expect_success 'Detect CRLF/LF conflict after setting text=auto' '
+       q_to_cr <<-\EOF >expected &&
+       <<<<<<<
+       first line
+       same line
+       =======
+       first lineQ
+       same lineQ
+       >>>>>>>
+       EOF
+
+       git config merge.renormalize false &&
+       rm -f .gitattributes &&
+       git reset --hard a &&
+       test_must_fail git merge b &&
+       fuzz_conflict file >file.fuzzy &&
+       test_cmp expected file.fuzzy
+'
+
+test_expect_success 'Detect LF/CRLF conflict from addition of text=auto' '
+       q_to_cr <<-\EOF >expected &&
+       <<<<<<<
+       first lineQ
+       same lineQ
+       =======
+       first line
+       same line
+       >>>>>>>
+       EOF
+
+       git config merge.renormalize false &&
+       rm -f .gitattributes &&
+       git reset --hard b &&
+       test_must_fail git merge a &&
+       fuzz_conflict file >file.fuzzy &&
+       test_cmp expected file.fuzzy
+'
+
+test_expect_failure 'checkout -m after setting text=auto' '
+       cat <<-\EOF >expected &&
+       first line
+       same line
+       EOF
+
+       git config merge.renormalize true &&
+       git rm -fr . &&
+       rm -f .gitattributes &&
+       git reset --hard initial &&
+       git checkout a -- . &&
+       git checkout -m b &&
+       test_cmp expected file
+'
+
+test_expect_failure 'checkout -m addition of text=auto' '
+       cat <<-\EOF >expected &&
+       first line
+       same line
+       EOF
+
+       git config merge.renormalize true &&
+       git rm -fr . &&
+       rm -f .gitattributes file &&
+       git reset --hard initial &&
+       git checkout b -- . &&
+       git checkout -m a &&
+       test_cmp expected file
+'
+
+test_expect_failure 'cherry-pick patch from after text=auto was added' '
+       append_cr <<-\EOF >expected &&
+       first line
+       same line
+       EOF
+
+       git config merge.renormalize true &&
+       git rm -fr . &&
+       git reset --hard b &&
+       test_must_fail git cherry-pick a >err 2>&1 &&
+       grep "[Nn]othing added" err &&
+       test_cmp expected file
+'
+
+test_expect_success 'Test delete/normalize conflict' '
+       git checkout -f side &&
+       git rm -fr . &&
+       rm -f .gitattributes &&
+       git reset --hard initial &&
+       git rm file &&
+       git commit -m "remove file" &&
+       git checkout master &&
+       git reset --hard a^ &&
+       git merge side
+'
+
+test_done
index 3d6db4d386a0a009892fac490818294664d188fa..1785e178a4cb8fddd58d1b1db8062cf12825155e 100755 (executable)
@@ -69,10 +69,45 @@ test_expect_success 'status' '
                cd test &&
                git checkout b1 >/dev/null &&
                # reports nothing to commit
-               test_must_fail git status
+               test_must_fail git commit --dry-run
        ) >actual &&
        grep "have 1 and 1 different" actual
 '
 
+test_expect_success 'status when tracking lightweight tags' '
+       git checkout master &&
+       git tag light &&
+       git branch --track lighttrack light >actual &&
+       grep "set up to track" actual &&
+       git checkout lighttrack
+'
+
+test_expect_success 'status when tracking annotated tags' '
+       git checkout master &&
+       git tag -m heavy heavy &&
+       git branch --track heavytrack heavy >actual &&
+       grep "set up to track" actual &&
+       git checkout heavytrack
+'
+
+test_expect_success 'setup tracking with branch --set-upstream on existing branch' '
+       git branch from-master master &&
+       test_must_fail git config branch.from-master.merge > actual &&
+       git branch --set-upstream from-master master &&
+       git config branch.from-master.merge > actual &&
+       grep -q "^refs/heads/master$" actual
+'
 
+test_expect_success '--set-upstream does not change branch' '
+       git branch from-master2 master &&
+       test_must_fail git config branch.from-master2.merge > actual &&
+       git rev-list from-master2 &&
+       git update-ref refs/heads/from-master2 from-master2^ &&
+       git rev-parse from-master2 >expect2 &&
+       git branch --set-upstream from-master2 master &&
+       git config branch.from-master.merge > actual &&
+       git rev-parse from-master2 >actual2 &&
+       grep -q "^refs/heads/master$" actual &&
+       cmp expect2 actual2
+'
 test_done
diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh
new file mode 100755 (executable)
index 0000000..c907523
--- /dev/null
@@ -0,0 +1,234 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Christian Couder
+#
+test_description='Tests replace refs functionality'
+
+exec </dev/null
+
+. ./test-lib.sh
+
+add_and_commit_file()
+{
+    _file="$1"
+    _msg="$2"
+
+    git add $_file || return $?
+    test_tick || return $?
+    git commit --quiet -m "$_file: $_msg"
+}
+
+HASH1=
+HASH2=
+HASH3=
+HASH4=
+HASH5=
+HASH6=
+HASH7=
+
+test_expect_success 'set up buggy branch' '
+     echo "line 1" >> hello &&
+     echo "line 2" >> hello &&
+     echo "line 3" >> hello &&
+     echo "line 4" >> hello &&
+     add_and_commit_file hello "4 lines" &&
+     HASH1=$(git rev-parse --verify HEAD) &&
+     echo "line BUG" >> hello &&
+     echo "line 6" >> hello &&
+     echo "line 7" >> hello &&
+     echo "line 8" >> hello &&
+     add_and_commit_file hello "4 more lines with a BUG" &&
+     HASH2=$(git rev-parse --verify HEAD) &&
+     echo "line 9" >> hello &&
+     echo "line 10" >> hello &&
+     add_and_commit_file hello "2 more lines" &&
+     HASH3=$(git rev-parse --verify HEAD) &&
+     echo "line 11" >> hello &&
+     add_and_commit_file hello "1 more line" &&
+     HASH4=$(git rev-parse --verify HEAD) &&
+     sed -e "s/BUG/5/" hello > hello.new &&
+     mv hello.new hello &&
+     add_and_commit_file hello "BUG fixed" &&
+     HASH5=$(git rev-parse --verify HEAD) &&
+     echo "line 12" >> hello &&
+     echo "line 13" >> hello &&
+     add_and_commit_file hello "2 more lines" &&
+     HASH6=$(git rev-parse --verify HEAD)
+     echo "line 14" >> hello &&
+     echo "line 15" >> hello &&
+     echo "line 16" >> hello &&
+     add_and_commit_file hello "again 3 more lines" &&
+     HASH7=$(git rev-parse --verify HEAD)
+'
+
+test_expect_success 'replace the author' '
+     git cat-file commit $HASH2 | grep "author A U Thor" &&
+     R=$(git cat-file commit $HASH2 | sed -e "s/A U/O/" | git hash-object -t commit --stdin -w) &&
+     git cat-file commit $R | grep "author O Thor" &&
+     git update-ref refs/replace/$HASH2 $R &&
+     git show HEAD~5 | grep "O Thor" &&
+     git show $HASH2 | grep "O Thor"
+'
+
+test_expect_success 'test --no-replace-objects option' '
+     git cat-file commit $HASH2 | grep "author O Thor" &&
+     git --no-replace-objects cat-file commit $HASH2 | grep "author A U Thor" &&
+     git show $HASH2 | grep "O Thor" &&
+     git --no-replace-objects show $HASH2 | grep "A U Thor"
+'
+
+test_expect_success 'test GIT_NO_REPLACE_OBJECTS env variable' '
+     GIT_NO_REPLACE_OBJECTS=1 git cat-file commit $HASH2 | grep "author A U Thor" &&
+     GIT_NO_REPLACE_OBJECTS=1 git show $HASH2 | grep "A U Thor"
+'
+
+cat >tag.sig <<EOF
+object $HASH2
+type commit
+tag mytag
+tagger T A Gger <> 0 +0000
+
+EOF
+
+test_expect_success 'tag replaced commit' '
+     git mktag <tag.sig >.git/refs/tags/mytag 2>message
+'
+
+test_expect_success '"git fsck" works' '
+     git fsck master > fsck_master.out &&
+     grep "dangling commit $R" fsck_master.out &&
+     grep "dangling tag $(cat .git/refs/tags/mytag)" fsck_master.out &&
+     test -z "$(git fsck)"
+'
+
+test_expect_success 'repack, clone and fetch work' '
+     git repack -a -d &&
+     git clone --no-hardlinks . clone_dir &&
+     (
+         cd clone_dir &&
+         git show HEAD~5 | grep "A U Thor" &&
+         git show $HASH2 | grep "A U Thor" &&
+         git cat-file commit $R &&
+         git repack -a -d &&
+         test_must_fail git cat-file commit $R &&
+         git fetch ../ "refs/replace/*:refs/replace/*" &&
+         git show HEAD~5 | grep "O Thor" &&
+         git show $HASH2 | grep "O Thor" &&
+         git cat-file commit $R
+     )
+'
+
+test_expect_success '"git replace" listing and deleting' '
+     test "$HASH2" = "$(git replace -l)" &&
+     test "$HASH2" = "$(git replace)" &&
+     aa=${HASH2%??????????????????????????????????????} &&
+     test "$HASH2" = "$(git replace -l "$aa*")" &&
+     test_must_fail git replace -d $R &&
+     test_must_fail git replace -d &&
+     test_must_fail git replace -l -d $HASH2 &&
+     git replace -d $HASH2 &&
+     git show $HASH2 | grep "A U Thor" &&
+     test -z "$(git replace -l)"
+'
+
+test_expect_success '"git replace" replacing' '
+     git replace $HASH2 $R &&
+     git show $HASH2 | grep "O Thor" &&
+     test_must_fail git replace $HASH2 $R &&
+     git replace -f $HASH2 $R &&
+     test_must_fail git replace -f &&
+     test "$HASH2" = "$(git replace)"
+'
+
+# This creates a side branch where the bug in H2
+# does not appear because P2 is created by applying
+# H2 and squashing H5 into it.
+# P3, P4 and P6 are created by cherry-picking H3, H4
+# and H6 respectively.
+#
+# At this point, we should have the following:
+#
+#    P2--P3--P4--P6
+#   /
+# H1-H2-H3-H4-H5-H6-H7
+#
+# Then we replace H6 with P6.
+#
+test_expect_success 'create parallel branch without the bug' '
+     git replace -d $HASH2 &&
+     git show $HASH2 | grep "A U Thor" &&
+     git checkout $HASH1 &&
+     git cherry-pick $HASH2 &&
+     git show $HASH5 | git apply &&
+     git commit --amend -m "hello: 4 more lines WITHOUT the bug" hello &&
+     PARA2=$(git rev-parse --verify HEAD) &&
+     git cherry-pick $HASH3 &&
+     PARA3=$(git rev-parse --verify HEAD) &&
+     git cherry-pick $HASH4 &&
+     PARA4=$(git rev-parse --verify HEAD) &&
+     git cherry-pick $HASH6 &&
+     PARA6=$(git rev-parse --verify HEAD) &&
+     git replace $HASH6 $PARA6 &&
+     git checkout master &&
+     cur=$(git rev-parse --verify HEAD) &&
+     test "$cur" = "$HASH7" &&
+     git log --pretty=oneline | grep $PARA2 &&
+     git remote add cloned ./clone_dir
+'
+
+test_expect_success 'push to cloned repo' '
+     git push cloned $HASH6^:refs/heads/parallel &&
+     (
+         cd clone_dir &&
+         git checkout parallel &&
+         git log --pretty=oneline | grep $PARA2
+     )
+'
+
+test_expect_success 'push branch with replacement' '
+     git cat-file commit $PARA3 | grep "author A U Thor" &&
+     S=$(git cat-file commit $PARA3 | sed -e "s/A U/O/" | git hash-object -t commit --stdin -w) &&
+     git cat-file commit $S | grep "author O Thor" &&
+     git replace $PARA3 $S &&
+     git show $HASH6~2 | grep "O Thor" &&
+     git show $PARA3 | grep "O Thor" &&
+     git push cloned $HASH6^:refs/heads/parallel2 &&
+     (
+         cd clone_dir &&
+         git checkout parallel2 &&
+         git log --pretty=oneline | grep $PARA3 &&
+         git show $PARA3 | grep "A U Thor"
+     )
+'
+
+test_expect_success 'fetch branch with replacement' '
+     git branch tofetch $HASH6 &&
+     (
+         cd clone_dir &&
+         git fetch origin refs/heads/tofetch:refs/heads/parallel3
+         git log --pretty=oneline parallel3 | grep $PARA3
+         git show $PARA3 | grep "A U Thor"
+     )
+'
+
+test_expect_success 'bisect and replacements' '
+     git bisect start $HASH7 $HASH1 &&
+     test "$PARA3" = "$(git rev-parse --verify HEAD)" &&
+     git bisect reset &&
+     GIT_NO_REPLACE_OBJECTS=1 git bisect start $HASH7 $HASH1 &&
+     test "$HASH4" = "$(git rev-parse --verify HEAD)" &&
+     git bisect reset &&
+     git --no-replace-objects bisect start $HASH7 $HASH1 &&
+     test "$HASH4" = "$(git rev-parse --verify HEAD)" &&
+     git bisect reset
+'
+
+test_expect_success 'index-pack and replacements' '
+       git --no-replace-objects rev-list --objects HEAD |
+       git --no-replace-objects pack-objects test- &&
+       git index-pack test-*.pack
+'
+
+#
+#
+test_done
index f105fab98e2d493ab489d345676101fc13096c22..e673c25e943f77430d7f61b0ab9e0b21e38e6915 100755 (executable)
@@ -6,7 +6,7 @@
 test_description='Test git rev-parse with different parent options'
 
 . ./test-lib.sh
-. "$TEST_DIRECTORY"/t6000lib.sh # t6xxx specific functions
+. "$TEST_DIRECTORY"/lib-t6000.sh # t6xxx specific functions
 
 date >path0
 git update-index --add path0
index 8c7e081c53eec31d38844d8efb9b942893107b09..876d1ab7430c2054ddc69bd3712ff769de52e774 100755 (executable)
@@ -8,7 +8,7 @@ test_description='test describe
  o----o----o----o----o----.    /
        \        A    c        /
         .------------o---o---o
-                     D   e
+                   D,R   e
 '
 . ./test-lib.sh
 
@@ -34,6 +34,8 @@ test_expect_success setup '
        echo one >file && git add file && git commit -m initial &&
        one=$(git rev-parse HEAD) &&
 
+       git describe --always HEAD &&
+
        test_tick &&
        echo two >file && git add file && git commit -m second &&
        two=$(git rev-parse HEAD) &&
@@ -66,6 +68,8 @@ test_expect_success setup '
        echo D >another && git add another && git commit -m D &&
        test_tick &&
        git tag -a -m D D &&
+       test_tick &&
+       git tag -a -m R R &&
 
        test_tick &&
        echo DD >another && git commit -a -m another &&
@@ -87,15 +91,21 @@ test_expect_success setup '
 
 check_describe A-* HEAD
 check_describe A-* HEAD^
-check_describe D-* HEAD^^
+check_describe R-* HEAD^^
 check_describe A-* HEAD^^2
 check_describe B HEAD^^2^
+check_describe R-* HEAD^^^
 
 check_describe c-* --tags HEAD
 check_describe c-* --tags HEAD^
 check_describe e-* --tags HEAD^^
 check_describe c-* --tags HEAD^^2
 check_describe B --tags HEAD^^2^
+check_describe e --tags HEAD^^^
+
+check_describe heads/master --all HEAD
+check_describe tags/c-* --all HEAD^
+check_describe tags/e --all HEAD^^^
 
 check_describe B-0-* --long HEAD^^2^
 check_describe A-3-* --long HEAD^^2
@@ -123,6 +133,20 @@ test_expect_success 'rename tag Q back to A' '
 test_expect_success 'pack tag refs' 'git pack-refs'
 check_describe A-* HEAD
 
+check_describe "A-*[0-9a-f]" --dirty
+
+test_expect_success 'set-up dirty work tree' '
+       echo >>file
+'
+
+check_describe "A-*[0-9a-f]-dirty" --dirty
+
+check_describe "A-*[0-9a-f].mod" --dirty=.mod
+
+test_expect_success 'describe --dirty HEAD' '
+       test_must_fail git describe --dirty HEAD
+'
+
 test_expect_success 'set-up matching pattern tests' '
        git tag -a -m test-annotated test-annotated &&
        echo >>file &&
index 2049ab6cf844da233837b58857cfde8a2d563252..71f6cad3c2b8e8ed0b16d292f4922760901702ef 100755 (executable)
@@ -7,73 +7,76 @@ test_description='fmt-merge-msg test'
 
 . ./test-lib.sh
 
-datestamp=1151939923
-setdate () {
-       GIT_COMMITTER_DATE="$datestamp +0200"
-       GIT_AUTHOR_DATE="$datestamp +0200"
-       datestamp=`expr "$datestamp" + 1`
-       export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
-}
-
 test_expect_success setup '
        echo one >one &&
        git add one &&
-       setdate &&
+       test_tick &&
        git commit -m "Initial" &&
 
+       git clone . remote &&
+
        echo uno >one &&
        echo dos >two &&
        git add two &&
-       setdate &&
+       test_tick &&
        git commit -a -m "Second" &&
 
        git checkout -b left &&
 
-       echo $datestamp >one &&
-       setdate &&
+       echo "c1" >one &&
+       test_tick &&
        git commit -a -m "Common #1" &&
 
-       echo $datestamp >one &&
-       setdate &&
+       echo "c2" >one &&
+       test_tick &&
        git commit -a -m "Common #2" &&
 
        git branch right &&
 
-       echo $datestamp >two &&
-       setdate &&
+       echo "l3" >two &&
+       test_tick &&
        git commit -a -m "Left #3" &&
 
-       echo $datestamp >two &&
-       setdate &&
+       echo "l4" >two &&
+       test_tick &&
        git commit -a -m "Left #4" &&
 
-       echo $datestamp >two &&
-       setdate &&
+       echo "l5" >two &&
+       test_tick &&
        git commit -a -m "Left #5" &&
+       git tag tag-l5 &&
 
        git checkout right &&
 
-       echo $datestamp >three &&
+       echo "r3" >three &&
        git add three &&
-       setdate &&
+       test_tick &&
        git commit -a -m "Right #3" &&
+       git tag tag-r3 &&
 
-       echo $datestamp >three &&
-       setdate &&
+       echo "r4" >three &&
+       test_tick &&
        git commit -a -m "Right #4" &&
 
-       echo $datestamp >three &&
-       setdate &&
+       echo "r5" >three &&
+       test_tick &&
        git commit -a -m "Right #5" &&
 
-       git show-branch
-'
+       git checkout -b long &&
+       i=0 &&
+       while test $i -lt 30
+       do
+               test_commit $i one &&
+               i=$(($i+1))
+       done &&
 
-cat >expected <<\EOF
-Merge branch 'left'
-EOF
+       git show-branch &&
+
+       apos="'\''"
+'
 
-test_expect_success 'merge-msg test #1' '
+test_expect_success 'message for merging local branch' '
+       echo "Merge branch ${apos}left${apos}" >expected &&
 
        git checkout master &&
        git fetch . left &&
@@ -82,11 +85,8 @@ test_expect_success 'merge-msg test #1' '
        test_cmp expected actual
 '
 
-cat >expected <<EOF
-Merge branch 'left' of $(pwd)
-EOF
-
-test_expect_success 'merge-msg test #2' '
+test_expect_success 'message for merging external branch' '
+       echo "Merge branch ${apos}left${apos} of $(pwd)" >expected &&
 
        git checkout master &&
        git fetch "$(pwd)" left &&
@@ -95,114 +95,266 @@ test_expect_success 'merge-msg test #2' '
        test_cmp expected actual
 '
 
-cat >expected <<\EOF
-Merge branch 'left'
-
-* left:
-  Left #5
-  Left #4
-  Left #3
-  Common #2
-  Common #1
-EOF
+test_expect_success '[merge] summary/log configuration' '
+       cat >expected <<-EOF &&
+       Merge branch ${apos}left${apos}
 
-test_expect_success 'merge-msg test #3-1' '
+       * left:
+         Left #5
+         Left #4
+         Left #3
+         Common #2
+         Common #1
+       EOF
 
-       git config --unset-all merge.log
-       git config --unset-all merge.summary
        git config merge.log true &&
+       test_might_fail git config --unset-all merge.summary &&
 
        git checkout master &&
-       setdate &&
+       test_tick &&
        git fetch . left &&
 
-       git fmt-merge-msg <.git/FETCH_HEAD >actual &&
-       test_cmp expected actual
+       git fmt-merge-msg <.git/FETCH_HEAD >actual1 &&
+
+       test_might_fail git config --unset-all merge.log &&
+       git config merge.summary true &&
+
+       git checkout master &&
+       test_tick &&
+       git fetch . left &&
+
+       git fmt-merge-msg <.git/FETCH_HEAD >actual2 &&
+
+       test_cmp expected actual1 &&
+       test_cmp expected actual2
+'
+
+test_expect_success 'fmt-merge-msg -m' '
+       echo "Sync with left" >expected &&
+       cat >expected.log <<-EOF &&
+       Sync with left
+
+       * ${apos}left${apos} of $(pwd):
+         Left #5
+         Left #4
+         Left #3
+         Common #2
+         Common #1
+       EOF
+
+       test_might_fail git config --unset merge.log &&
+       test_might_fail git config --unset merge.summary &&
+       git checkout master &&
+       git fetch "$(pwd)" left &&
+       git fmt-merge-msg -m "Sync with left" <.git/FETCH_HEAD >actual &&
+       git fmt-merge-msg --log -m "Sync with left" \
+                                       <.git/FETCH_HEAD >actual.log &&
+       git config merge.log true &&
+       git fmt-merge-msg -m "Sync with left" \
+                                       <.git/FETCH_HEAD >actual.log-config &&
+       git fmt-merge-msg --no-log -m "Sync with left" \
+                                       <.git/FETCH_HEAD >actual.nolog &&
+
+       test_cmp expected actual &&
+       test_cmp expected.log actual.log &&
+       test_cmp expected.log actual.log-config &&
+       test_cmp expected actual.nolog
 '
 
-test_expect_success 'merge-msg test #3-2' '
+test_expect_success 'setup: expected shortlog for two branches' '
+       cat >expected <<-EOF
+       Merge branches ${apos}left${apos} and ${apos}right${apos}
+
+       * left:
+         Left #5
+         Left #4
+         Left #3
+         Common #2
+         Common #1
+
+       * right:
+         Right #5
+         Right #4
+         Right #3
+         Common #2
+         Common #1
+       EOF
+'
+
+test_expect_success 'shortlog for two branches' '
+       git config merge.log true &&
+       test_might_fail git config --unset-all merge.summary &&
+       git checkout master &&
+       test_tick &&
+       git fetch . left right &&
+       git fmt-merge-msg <.git/FETCH_HEAD >actual1 &&
 
-       git config --unset-all merge.log
-       git config --unset-all merge.summary
+       test_might_fail git config --unset-all merge.log &&
        git config merge.summary true &&
+       git checkout master &&
+       test_tick &&
+       git fetch . left right &&
+       git fmt-merge-msg <.git/FETCH_HEAD >actual2 &&
 
+       git config merge.log yes &&
+       test_might_fail git config --unset-all merge.summary &&
        git checkout master &&
-       setdate &&
-       git fetch . left &&
+       test_tick &&
+       git fetch . left right &&
+       git fmt-merge-msg <.git/FETCH_HEAD >actual3 &&
 
-       git fmt-merge-msg <.git/FETCH_HEAD >actual &&
+       test_might_fail git config --unset-all merge.log &&
+       git config merge.summary yes &&
+       git checkout master &&
+       test_tick &&
+       git fetch . left right &&
+       git fmt-merge-msg <.git/FETCH_HEAD >actual4 &&
+
+       test_cmp expected actual1 &&
+       test_cmp expected actual2 &&
+       test_cmp expected actual3 &&
+       test_cmp expected actual4
+'
+
+test_expect_success 'merge-msg -F' '
+       test_might_fail git config --unset-all merge.log &&
+       git config merge.summary yes &&
+       git checkout master &&
+       test_tick &&
+       git fetch . left right &&
+       git fmt-merge-msg -F .git/FETCH_HEAD >actual &&
        test_cmp expected actual
 '
 
-cat >expected <<\EOF
-Merge branches 'left' and 'right'
+test_expect_success 'merge-msg -F in subdirectory' '
+       test_might_fail git config --unset-all merge.log &&
+       git config merge.summary yes &&
+       git checkout master &&
+       test_tick &&
+       git fetch . left right &&
+       mkdir sub &&
+       cp .git/FETCH_HEAD sub/FETCH_HEAD &&
+       (
+               cd sub &&
+               git fmt-merge-msg -F FETCH_HEAD >../actual
+       ) &&
+       test_cmp expected actual
+'
+
+test_expect_success 'merge-msg with nothing to merge' '
+       test_might_fail git config --unset-all merge.log &&
+       git config merge.summary yes &&
+
+       >empty &&
 
-* left:
-  Left #5
-  Left #4
-  Left #3
-  Common #2
-  Common #1
+       (
+               cd remote &&
+               git checkout -b unrelated &&
+               test_tick &&
+               git fetch origin &&
+               git fmt-merge-msg <.git/FETCH_HEAD >../actual
+       ) &&
 
-* right:
-  Right #5
-  Right #4
-  Right #3
-  Common #2
-  Common #1
-EOF
+       test_cmp empty actual
+'
 
-test_expect_success 'merge-msg test #4-1' '
+test_expect_success 'merge-msg tag' '
+       cat >expected <<-EOF &&
+       Merge tag ${apos}tag-r3${apos}
 
-       git config --unset-all merge.log
-       git config --unset-all merge.summary
-       git config merge.log true &&
+       * tag ${apos}tag-r3${apos}:
+         Right #3
+         Common #2
+         Common #1
+       EOF
+
+       test_might_fail git config --unset-all merge.log &&
+       git config merge.summary yes &&
 
        git checkout master &&
-       setdate &&
-       git fetch . left right &&
+       test_tick &&
+       git fetch . tag tag-r3 &&
 
        git fmt-merge-msg <.git/FETCH_HEAD >actual &&
        test_cmp expected actual
 '
 
-test_expect_success 'merge-msg test #4-2' '
+test_expect_success 'merge-msg two tags' '
+       cat >expected <<-EOF &&
+       Merge tags ${apos}tag-r3${apos} and ${apos}tag-l5${apos}
 
-       git config --unset-all merge.log
-       git config --unset-all merge.summary
-       git config merge.summary true &&
+       * tag ${apos}tag-r3${apos}:
+         Right #3
+         Common #2
+         Common #1
+
+       * tag ${apos}tag-l5${apos}:
+         Left #5
+         Left #4
+         Left #3
+         Common #2
+         Common #1
+       EOF
+
+       test_might_fail git config --unset-all merge.log &&
+       git config merge.summary yes &&
 
        git checkout master &&
-       setdate &&
-       git fetch . left right &&
+       test_tick &&
+       git fetch . tag tag-r3 tag tag-l5 &&
 
        git fmt-merge-msg <.git/FETCH_HEAD >actual &&
        test_cmp expected actual
 '
 
-test_expect_success 'merge-msg test #5-1' '
+test_expect_success 'merge-msg tag and branch' '
+       cat >expected <<-EOF &&
+       Merge branch ${apos}left${apos}, tag ${apos}tag-r3${apos}
 
-       git config --unset-all merge.log
-       git config --unset-all merge.summary
-       git config merge.log yes &&
+       * tag ${apos}tag-r3${apos}:
+         Right #3
+         Common #2
+         Common #1
+
+       * left:
+         Left #5
+         Left #4
+         Left #3
+         Common #2
+         Common #1
+       EOF
+
+       test_might_fail git config --unset-all merge.log &&
+       git config merge.summary yes &&
 
        git checkout master &&
-       setdate &&
-       git fetch . left right &&
+       test_tick &&
+       git fetch . tag tag-r3 left &&
 
        git fmt-merge-msg <.git/FETCH_HEAD >actual &&
        test_cmp expected actual
 '
 
-test_expect_success 'merge-msg test #5-2' '
+test_expect_success 'merge-msg lots of commits' '
+       {
+               cat <<-EOF &&
+               Merge branch ${apos}long${apos}
 
-       git config --unset-all merge.log
-       git config --unset-all merge.summary
-       git config merge.summary yes &&
+               * long: (35 commits)
+               EOF
+
+               i=29 &&
+               while test $i -gt 9
+               do
+                       echo "  $i" &&
+                       i=$(($i-1))
+               done &&
+               echo "  ..."
+       } >expected &&
 
        git checkout master &&
-       setdate &&
-       git fetch . left right &&
+       test_tick &&
+       git fetch . long &&
 
        git fmt-merge-msg <.git/FETCH_HEAD >actual &&
        test_cmp expected actual
index 8052c86ad3516505765ab214f4801940c8cc1684..7dc8a510c7f33d048cd3268424298fdda2f8c2bb 100755 (executable)
@@ -295,6 +295,15 @@ test_expect_success 'Check short upstream format' '
        test_cmp expected actual
 '
 
+cat >expected <<EOF
+67a36f1
+EOF
+
+test_expect_success 'Check short objectname format' '
+       git for-each-ref --format="%(objectname:short)" refs/heads >actual &&
+       test_cmp expected actual
+'
+
 test_expect_success 'Check for invalid refname format' '
        test_must_fail git for-each-ref --format="%(refname:INVALID)"
 '
index 10b8f8c44befdb4eb00b3959f8b29cbebb7a22e1..65a35d94a001b555ce9e4d6c528d588339a5300b 100755 (executable)
@@ -189,6 +189,18 @@ test_expect_success 'absolute pathname outside should fail' '(
 
 )'
 
+test_expect_success 'git mv to move multiple sources into a directory' '
+       rm -fr .git && git init &&
+       mkdir dir other &&
+       >dir/a.txt &&
+       >dir/b.txt &&
+       git add dir/?.txt &&
+       git mv dir/a.txt dir/b.txt other &&
+       git ls-files >actual &&
+       { echo other/a.txt; echo other/b.txt; } >expect &&
+       test_cmp expect actual
+'
+
 test_expect_success 'git mv should not change sha1 of moved cache entry' '
 
        rm -fr .git &&
diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh
deleted file mode 100755 (executable)
index b815937..0000000
+++ /dev/null
@@ -1,178 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2006 Junio C Hamano
-#
-
-test_description='git grep various.
-'
-
-. ./test-lib.sh
-
-test_expect_success setup '
-       {
-               echo foo mmap bar
-               echo foo_mmap bar
-               echo foo_mmap bar mmap
-               echo foo mmap bar_mmap
-               echo foo_mmap bar mmap baz
-       } >file &&
-       echo x x xx x >x &&
-       echo y yy >y &&
-       echo zzz > z &&
-       mkdir t &&
-       echo test >t/t &&
-       git add file x y z t/t &&
-       test_tick &&
-       git commit -m initial
-'
-
-test_expect_success 'grep should not segfault with a bad input' '
-       test_must_fail git grep "("
-'
-
-for H in HEAD ''
-do
-       case "$H" in
-       HEAD)   HC='HEAD:' L='HEAD' ;;
-       '')     HC= L='in working tree' ;;
-       esac
-
-       test_expect_success "grep -w $L" '
-               {
-                       echo ${HC}file:1:foo mmap bar
-                       echo ${HC}file:3:foo_mmap bar mmap
-                       echo ${HC}file:4:foo mmap bar_mmap
-                       echo ${HC}file:5:foo_mmap bar mmap baz
-               } >expected &&
-               git grep -n -w -e mmap $H >actual &&
-               diff expected actual
-       '
-
-       test_expect_success "grep -w $L (x)" '
-               {
-                       echo ${HC}x:1:x x xx x
-               } >expected &&
-               git grep -n -w -e "x xx* x" $H >actual &&
-               diff expected actual
-       '
-
-       test_expect_success "grep -w $L (y-1)" '
-               {
-                       echo ${HC}y:1:y yy
-               } >expected &&
-               git grep -n -w -e "^y" $H >actual &&
-               diff expected actual
-       '
-
-       test_expect_success "grep -w $L (y-2)" '
-               : >expected &&
-               if git grep -n -w -e "^y y" $H >actual
-               then
-                       echo should not have matched
-                       cat actual
-                       false
-               else
-                       diff expected actual
-               fi
-       '
-
-       test_expect_success "grep -w $L (z)" '
-               : >expected &&
-               if git grep -n -w -e "^z" $H >actual
-               then
-                       echo should not have matched
-                       cat actual
-                       false
-               else
-                       diff expected actual
-               fi
-       '
-
-       test_expect_success "grep $L (t-1)" '
-               echo "${HC}t/t:1:test" >expected &&
-               git grep -n -e test $H >actual &&
-               diff expected actual
-       '
-
-       test_expect_success "grep $L (t-2)" '
-               echo "${HC}t:1:test" >expected &&
-               (
-                       cd t &&
-                       git grep -n -e test $H
-               ) >actual &&
-               diff expected actual
-       '
-
-       test_expect_success "grep $L (t-3)" '
-               echo "${HC}t/t:1:test" >expected &&
-               (
-                       cd t &&
-                       git grep --full-name -n -e test $H
-               ) >actual &&
-               diff expected actual
-       '
-
-       test_expect_success "grep -c $L (no /dev/null)" '
-               ! git grep -c test $H | grep /dev/null
-        '
-
-done
-
-test_expect_success 'log grep setup' '
-       echo a >>file &&
-       test_tick &&
-       GIT_AUTHOR_NAME="With * Asterisk" \
-       GIT_AUTHOR_EMAIL="xyzzy@frotz.com" \
-       git commit -a -m "second" &&
-
-       echo a >>file &&
-       test_tick &&
-       git commit -a -m "third"
-
-'
-
-test_expect_success 'log grep (1)' '
-       git log --author=author --pretty=tformat:%s >actual &&
-       ( echo third ; echo initial ) >expect &&
-       test_cmp expect actual
-'
-
-test_expect_success 'log grep (2)' '
-       git log --author=" * " -F --pretty=tformat:%s >actual &&
-       ( echo second ) >expect &&
-       test_cmp expect actual
-'
-
-test_expect_success 'log grep (3)' '
-       git log --author="^A U" --pretty=tformat:%s >actual &&
-       ( echo third ; echo initial ) >expect &&
-       test_cmp expect actual
-'
-
-test_expect_success 'log grep (4)' '
-       git log --author="frotz\.com>$" --pretty=tformat:%s >actual &&
-       ( echo second ) >expect &&
-       test_cmp expect actual
-'
-
-test_expect_success 'log grep (5)' '
-       git log --author=Thor -F --grep=Thu --pretty=tformat:%s >actual &&
-       ( echo third ; echo initial ) >expect &&
-       test_cmp expect actual
-'
-
-test_expect_success 'log grep (6)' '
-       git log --author=-0700  --pretty=tformat:%s >actual &&
-       >expect &&
-       test_cmp expect actual
-'
-
-test_expect_success 'grep with CE_VALID file' '
-       git update-index --assume-unchanged t/t &&
-       rm t/t &&
-       test "$(git grep --no-ext-grep t)" = "t/t:test" &&
-       git update-index --no-assume-unchanged t/t &&
-       git checkout t/t
-'
-
-test_done
index 329c851685b1c663ee88d45a5d21d452a293fa8e..e0227730deb88413301b37501fdb165a2c90460e 100755 (executable)
@@ -3,31 +3,34 @@
 test_description='git filter-branch'
 . ./test-lib.sh
 
-make_commit () {
-       lower=$(echo $1 | tr '[A-Z]' '[a-z]')
-       echo $lower > $lower
-       git add $lower
-       test_tick
-       git commit -m $1
-       git tag $1
-}
-
 test_expect_success 'setup' '
-       make_commit A
-       make_commit B
-       git checkout -b branch B
-       make_commit D
-       mkdir dir
-       make_commit dir/D
-       make_commit E
-       git checkout master
-       make_commit C
-       git checkout branch
-       git merge C
-       git tag F
-       make_commit G
-       make_commit H
+       test_commit A &&
+       test_commit B &&
+       git checkout -b branch B &&
+       test_commit D &&
+       mkdir dir &&
+       test_commit dir/D &&
+       test_commit E &&
+       git checkout master &&
+       test_commit C &&
+       git checkout branch &&
+       git merge C &&
+       git tag F &&
+       test_commit G &&
+       test_commit H
 '
+# * (HEAD, branch) H
+# * G
+# *   Merge commit 'C' into branch
+# |\
+# | * (master) C
+# * | E
+# * | dir/D
+# * | D
+# |/
+# * B
+# * A
+
 
 H=$(git rev-parse H)
 
@@ -65,14 +68,14 @@ test_expect_success 'Fail if commit filter fails' '
 '
 
 test_expect_success 'rewrite, renaming a specific file' '
-       git filter-branch -f --tree-filter "mv d doh || :" HEAD
+       git filter-branch -f --tree-filter "mv D.t doh || :" HEAD
 '
 
 test_expect_success 'test that the file was renamed' '
-       test d = "$(git show HEAD:doh --)" &&
-       ! test -f d &&
+       test D = "$(git show HEAD:doh --)" &&
+       ! test -f D.t &&
        test -f doh &&
-       test d = "$(cat doh)"
+       test D = "$(cat doh)"
 '
 
 test_expect_success 'rewrite, renaming a specific directory' '
@@ -80,18 +83,18 @@ test_expect_success 'rewrite, renaming a specific directory' '
 '
 
 test_expect_success 'test that the directory was renamed' '
-       test dir/d = "$(git show HEAD:diroh/d --)" &&
+       test dir/D = "$(git show HEAD:diroh/D.t --)" &&
        ! test -d dir &&
        test -d diroh &&
        ! test -d diroh/dir &&
-       test -f diroh/d &&
-       test dir/d = "$(cat diroh/d)"
+       test -f diroh/D.t &&
+       test dir/D = "$(cat diroh/D.t)"
 '
 
 git tag oldD HEAD~4
 test_expect_success 'rewrite one branch, keeping a side branch' '
        git branch modD oldD &&
-       git filter-branch -f --tree-filter "mv b boh || :" D..modD
+       git filter-branch -f --tree-filter "mv B.t boh || :" D..modD
 '
 
 test_expect_success 'common ancestor is still common (unchanged)' '
@@ -104,13 +107,13 @@ test_expect_success 'filter subdirectory only' '
        git add subdir/new &&
        test_tick &&
        git commit -m "subdir" &&
-       echo H > a &&
+       echo H > A.t &&
        test_tick &&
-       git commit -m "not subdir" a &&
+       git commit -m "not subdir" A.t &&
        echo A > subdir/new &&
        test_tick &&
        git commit -m "again subdir" subdir/new &&
-       git rm a &&
+       git rm A.t &&
        test_tick &&
        git commit -m "again not subdir" &&
        git branch sub &&
@@ -134,7 +137,7 @@ test_expect_success 'more setup' '
        git add subdir/new &&
        test_tick &&
        git commit -m "subdir on master" subdir/new &&
-       git rm a &&
+       git rm A.t &&
        test_tick &&
        git commit -m "again subdir on master" &&
        git merge branch
@@ -143,11 +146,12 @@ test_expect_success 'more setup' '
 test_expect_success 'use index-filter to move into a subdirectory' '
        git branch directorymoved &&
        git filter-branch -f --index-filter \
-                "git ls-files -s | sed \"s-\\t-&newsubdir/-\" |
+                "git ls-files -s | sed \"s-    -&newsubdir/-\" |
                  GIT_INDEX_FILE=\$GIT_INDEX_FILE.new \
                        git update-index --index-info &&
                  mv \"\$GIT_INDEX_FILE.new\" \"\$GIT_INDEX_FILE\"" directorymoved &&
-       test -z "$(git diff HEAD directorymoved:newsubdir)"'
+       git diff --exit-code HEAD directorymoved:newsubdir
+'
 
 test_expect_success 'stops when msg filter fails' '
        old=$(git rev-parse HEAD) &&
@@ -282,10 +286,85 @@ test_expect_success 'Tag name filtering allows slashes in tag names' '
 
 test_expect_success 'Prune empty commits' '
        git rev-list HEAD > expect &&
-       make_commit to_remove &&
-       git filter-branch -f --index-filter "git update-index --remove to_remove" --prune-empty HEAD &&
+       test_commit to_remove &&
+       git filter-branch -f --index-filter "git update-index --remove to_remove.t" --prune-empty HEAD &&
        git rev-list HEAD > actual &&
        test_cmp expect actual
 '
 
+test_expect_success '--remap-to-ancestor with filename filters' '
+       git checkout master &&
+       git reset --hard A &&
+       test_commit add-foo foo 1 &&
+       git branch moved-foo &&
+       test_commit add-bar bar a &&
+       git branch invariant &&
+       orig_invariant=$(git rev-parse invariant) &&
+       git branch moved-bar &&
+       test_commit change-foo foo 2 &&
+       git filter-branch -f --remap-to-ancestor \
+               moved-foo moved-bar A..master \
+               -- -- foo &&
+       test $(git rev-parse moved-foo) = $(git rev-parse moved-bar) &&
+       test $(git rev-parse moved-foo) = $(git rev-parse master^) &&
+       test $orig_invariant = $(git rev-parse invariant)
+'
+
+test_expect_success 'automatic remapping to ancestor with filename filters' '
+       git checkout master &&
+       git reset --hard A &&
+       test_commit add-foo2 foo 1 &&
+       git branch moved-foo2 &&
+       test_commit add-bar2 bar a &&
+       git branch invariant2 &&
+       orig_invariant=$(git rev-parse invariant2) &&
+       git branch moved-bar2 &&
+       test_commit change-foo2 foo 2 &&
+       git filter-branch -f \
+               moved-foo2 moved-bar2 A..master \
+               -- -- foo &&
+       test $(git rev-parse moved-foo2) = $(git rev-parse moved-bar2) &&
+       test $(git rev-parse moved-foo2) = $(git rev-parse master^) &&
+       test $orig_invariant = $(git rev-parse invariant2)
+'
+
+test_expect_success 'setup submodule' '
+       rm -fr ?* .git &&
+       git init &&
+       test_commit file &&
+       mkdir submod &&
+       submodurl="$PWD/submod" &&
+       ( cd submod &&
+         git init &&
+         test_commit file-in-submod ) &&
+       git submodule add "$submodurl" &&
+       git commit -m "added submodule" &&
+       test_commit add-file &&
+       ( cd submod && test_commit add-in-submodule ) &&
+       git add submod &&
+       git commit -m "changed submodule" &&
+       git branch original HEAD
+'
+
+orig_head=`git show-ref --hash --head HEAD`
+
+test_expect_success 'rewrite submodule with another content' '
+       git filter-branch --tree-filter "test -d submod && {
+                                        rm -rf submod &&
+                                        git rm -rf --quiet submod &&
+                                        mkdir submod &&
+                                        : > submod/file
+                                        } || :" HEAD &&
+       test $orig_head != `git show-ref --hash --head HEAD`
+'
+
+test_expect_success 'replace submodule revision' '
+       git reset --hard original &&
+       git filter-branch -f --tree-filter \
+           "if git ls-files --error-unmatch -- submod > /dev/null 2>&1
+            then git update-index --cacheinfo 160000 0123456789012345678901234567890123456789 submod
+            fi" HEAD &&
+       test $orig_head != `git show-ref --hash --head HEAD`
+'
+
 test_done
index 73dbc4360b58c95ae135577031fb8e60b3f395f7..ac943f5eeecd17dd1edc5834265df9e16e5032f2 100755 (executable)
@@ -583,7 +583,7 @@ test_expect_success \
 # subsequent tests require gpg; check if it is available
 gpg --version >/dev/null 2>/dev/null
 if [ $? -eq 127 ]; then
-       say "gpg not found - skipping tag signing and verification tests"
+       say "gpg not found - skipping tag signing and verification tests"
 else
        # As said here: http://www.gnupg.org/documentation/faqs.html#q6.19
        # the gpg version 1.0.6 didn't parse trust packets correctly, so for
index b647957d75fa0d0ce4d88c7c3c7243f31af38b4a..1b530b5022fb58ce51ad8cd44c630acdca613311 100755 (executable)
@@ -4,7 +4,21 @@ test_description='GIT_EDITOR, core.editor, and stuff'
 
 . ./test-lib.sh
 
-for i in GIT_EDITOR core_editor EDITOR VISUAL vi
+unset EDITOR VISUAL GIT_EDITOR
+
+test_expect_success 'determine default editor' '
+
+       vi=$(TERM=vt100 git var GIT_EDITOR) &&
+       test -n "$vi"
+
+'
+
+if ! expr "$vi" : '[a-z]*$' >/dev/null
+then
+       vi=
+fi
+
+for i in GIT_EDITOR core_editor EDITOR VISUAL $vi
 do
        cat >e-$i.sh <<-EOF
        #!$SHELL_PATH
@@ -12,20 +26,19 @@ do
        EOF
        chmod +x e-$i.sh
 done
-unset vi
-mv e-vi.sh vi
-unset EDITOR VISUAL GIT_EDITOR
+
+if ! test -z "$vi"
+then
+       mv e-$vi.sh $vi
+fi
 
 test_expect_success setup '
 
-       msg="Hand edited" &&
+       msg="Hand-edited" &&
+       test_commit "$msg" &&
        echo "$msg" >expect &&
-       git add vi &&
-       test_tick &&
-       git commit -m "$msg" &&
-       git show -s --pretty=oneline |
-       sed -e "s/^[0-9a-f]* //" >actual &&
-       diff actual expect
+       git show -s --format=%s > actual &&
+       test_cmp actual expect
 
 '
 
@@ -42,9 +55,19 @@ test_expect_success 'dumb should error out when falling back on vi' '
        fi
 '
 
+test_expect_success 'dumb should prefer EDITOR to VISUAL' '
+
+       EDITOR=./e-EDITOR.sh &&
+       VISUAL=./e-VISUAL.sh &&
+       export EDITOR VISUAL &&
+       git commit --amend &&
+       test "$(git show -s --format=%s)" = "Edited by EDITOR"
+
+'
+
 TERM=vt100
 export TERM
-for i in vi EDITOR VISUAL core_editor GIT_EDITOR
+for i in $vi EDITOR VISUAL core_editor GIT_EDITOR
 do
        echo "Edited by $i" >expect
        unset EDITOR VISUAL GIT_EDITOR
@@ -62,13 +85,13 @@ do
                git --exec-path=. commit --amend &&
                git show -s --pretty=oneline |
                sed -e "s/^[0-9a-f]* //" >actual &&
-               diff actual expect
+               test_cmp actual expect
        '
 done
 
 unset EDITOR VISUAL GIT_EDITOR
 git config --unset-all core.editor
-for i in vi EDITOR VISUAL core_editor GIT_EDITOR
+for i in $vi EDITOR VISUAL core_editor GIT_EDITOR
 do
        echo "Edited by $i" >expect
        case "$i" in
@@ -84,17 +107,17 @@ do
                git --exec-path=. commit --amend &&
                git show -s --pretty=oneline |
                sed -e "s/^[0-9a-f]* //" >actual &&
-               diff actual expect
+               test_cmp actual expect
        '
 done
 
-if echo 'echo space > "$1"' > "e space.sh"
+if echo 'echo space > "$1"' > "e space.sh"
 then
-       say "Skipping; FS does not support spaces in filenames"
-       test_done
+       # FS supports spaces in filenames
+       test_set_prereq SPACES_IN_FILENAMES
 fi
 
-test_expect_success 'editor with a space' '
+test_expect_success SPACES_IN_FILENAMES 'editor with a space' '
 
        chmod a+x "e space.sh" &&
        GIT_EDITOR="./e\ space.sh" git commit --amend &&
@@ -103,7 +126,7 @@ test_expect_success 'editor with a space' '
 '
 
 unset GIT_EDITOR
-test_expect_success 'core.editor with a space' '
+test_expect_success SPACES_IN_FILENAMES 'core.editor with a space' '
 
        git config core.editor \"./e\ space.sh\" &&
        git commit --amend &&
diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh
new file mode 100755 (executable)
index 0000000..fb744e3
--- /dev/null
@@ -0,0 +1,438 @@
+#!/bin/sh
+
+test_description='Test automatic use of a pager.'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-pager.sh
+
+cleanup_fail() {
+       echo >&2 cleanup failed
+       (exit 1)
+}
+
+test_expect_success 'set up terminal for tests' '
+       rm -f stdout_is_tty ||
+       cleanup_fail &&
+
+       if test -t 1
+       then
+               >stdout_is_tty
+       elif
+               test_have_prereq PERL &&
+               "$PERL_PATH" "$TEST_DIRECTORY"/t7006/test-terminal.perl \
+                       sh -c "test -t 1"
+       then
+               >test_terminal_works
+       fi
+'
+
+if test -e stdout_is_tty
+then
+       test_terminal() { "$@"; }
+       test_set_prereq TTY
+elif test -e test_terminal_works
+then
+       test_terminal() {
+               "$PERL_PATH" "$TEST_DIRECTORY"/t7006/test-terminal.perl "$@"
+       }
+       test_set_prereq TTY
+else
+       say "# no usable terminal, so skipping some tests"
+fi
+
+test_expect_success 'setup' '
+       unset GIT_PAGER GIT_PAGER_IN_USE;
+       test_might_fail git config --unset core.pager &&
+
+       PAGER="cat >paginated.out" &&
+       export PAGER &&
+
+       test_commit initial
+'
+
+test_expect_success TTY 'some commands use a pager' '
+       rm -f paginated.out ||
+       cleanup_fail &&
+
+       test_terminal git log &&
+       test -e paginated.out
+'
+
+test_expect_failure TTY 'pager runs from subdir' '
+       echo subdir/paginated.out >expected &&
+       mkdir -p subdir &&
+       rm -f paginated.out subdir/paginated.out &&
+       (
+               cd subdir &&
+               test_terminal git log
+       ) &&
+       {
+               ls paginated.out subdir/paginated.out ||
+               :
+       } >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success TTY 'some commands do not use a pager' '
+       rm -f paginated.out ||
+       cleanup_fail &&
+
+       test_terminal git rev-list HEAD &&
+       ! test -e paginated.out
+'
+
+test_expect_success 'no pager when stdout is a pipe' '
+       rm -f paginated.out ||
+       cleanup_fail &&
+
+       git log | cat &&
+       ! test -e paginated.out
+'
+
+test_expect_success 'no pager when stdout is a regular file' '
+       rm -f paginated.out ||
+       cleanup_fail &&
+
+       git log >file &&
+       ! test -e paginated.out
+'
+
+test_expect_success TTY 'git --paginate rev-list uses a pager' '
+       rm -f paginated.out ||
+       cleanup_fail &&
+
+       test_terminal git --paginate rev-list HEAD &&
+       test -e paginated.out
+'
+
+test_expect_success 'no pager even with --paginate when stdout is a pipe' '
+       rm -f file paginated.out ||
+       cleanup_fail &&
+
+       git --paginate log | cat &&
+       ! test -e paginated.out
+'
+
+test_expect_success TTY 'no pager with --no-pager' '
+       rm -f paginated.out ||
+       cleanup_fail &&
+
+       test_terminal git --no-pager log &&
+       ! test -e paginated.out
+'
+
+test_expect_success TTY 'configuration can disable pager' '
+       rm -f paginated.out &&
+       test_might_fail git config --unset pager.grep &&
+       test_terminal git grep initial &&
+       test -e paginated.out &&
+
+       rm -f paginated.out &&
+       git config pager.grep false &&
+       test_when_finished "git config --unset pager.grep" &&
+       test_terminal git grep initial &&
+       ! test -e paginated.out
+'
+
+test_expect_success TTY 'git config uses a pager if configured to' '
+       rm -f paginated.out &&
+       git config pager.config true &&
+       test_when_finished "git config --unset pager.config" &&
+       test_terminal git config --list &&
+       test -e paginated.out
+'
+
+test_expect_success TTY 'configuration can enable pager (from subdir)' '
+       rm -f paginated.out &&
+       mkdir -p subdir &&
+       git config pager.bundle true &&
+       test_when_finished "git config --unset pager.bundle" &&
+
+       git bundle create test.bundle --all &&
+       rm -f paginated.out subdir/paginated.out &&
+       (
+               cd subdir &&
+               test_terminal git bundle unbundle ../test.bundle
+       ) &&
+       {
+               test -e paginated.out ||
+               test -e subdir/paginated.out
+       }
+'
+
+# A colored commit log will begin with an appropriate ANSI escape
+# for the first color; the text "commit" comes later.
+colorful() {
+       read firstline <$1
+       ! expr "$firstline" : "[a-zA-Z]" >/dev/null
+}
+
+test_expect_success 'tests can detect color' '
+       rm -f colorful.log colorless.log ||
+       cleanup_fail &&
+
+       git log --no-color >colorless.log &&
+       git log --color >colorful.log &&
+       ! colorful colorless.log &&
+       colorful colorful.log
+'
+
+test_expect_success 'no color when stdout is a regular file' '
+       rm -f colorless.log &&
+       git config color.ui auto ||
+       cleanup_fail &&
+
+       git log >colorless.log &&
+       ! colorful colorless.log
+'
+
+test_expect_success TTY 'color when writing to a pager' '
+       rm -f paginated.out &&
+       git config color.ui auto ||
+       cleanup_fail &&
+
+       (
+               TERM=vt100 &&
+               export TERM &&
+               test_terminal git log
+       ) &&
+       colorful paginated.out
+'
+
+test_expect_success 'color when writing to a file intended for a pager' '
+       rm -f colorful.log &&
+       git config color.ui auto ||
+       cleanup_fail &&
+
+       (
+               TERM=vt100 &&
+               GIT_PAGER_IN_USE=true &&
+               export TERM GIT_PAGER_IN_USE &&
+               git log >colorful.log
+       ) &&
+       colorful colorful.log
+'
+
+if test_have_prereq SIMPLEPAGER && test_have_prereq TTY
+then
+       test_set_prereq SIMPLEPAGERTTY
+fi
+
+# Use this helper to make it easy for the caller of your
+# terminal-using function to specify whether it should fail.
+# If you write
+#
+#      your_test() {
+#              parse_args "$@"
+#
+#              $test_expectation "$cmd - behaves well" "
+#                      ...
+#                      $full_command &&
+#                      ...
+#              "
+#      }
+#
+# then your test can be used like this:
+#
+#      your_test expect_(success|failure) [test_must_fail] 'git foo'
+#
+parse_args() {
+       test_expectation="test_$1"
+       shift
+       if test "$1" = test_must_fail
+       then
+               full_command="test_must_fail test_terminal "
+               shift
+       else
+               full_command="test_terminal "
+       fi
+       cmd=$1
+       full_command="$full_command $1"
+}
+
+test_default_pager() {
+       parse_args "$@"
+
+       $test_expectation SIMPLEPAGERTTY "$cmd - default pager is used by default" "
+               unset PAGER GIT_PAGER;
+               test_might_fail git config --unset core.pager &&
+               rm -f default_pager_used ||
+               cleanup_fail &&
+
+               cat >\$less <<-\EOF &&
+               #!/bin/sh
+               wc >default_pager_used
+               EOF
+               chmod +x \$less &&
+               (
+                       PATH=.:\$PATH &&
+                       export PATH &&
+                       $full_command
+               ) &&
+               test -e default_pager_used
+       "
+}
+
+test_PAGER_overrides() {
+       parse_args "$@"
+
+       $test_expectation TTY "$cmd - PAGER overrides default pager" "
+               unset GIT_PAGER;
+               test_might_fail git config --unset core.pager &&
+               rm -f PAGER_used ||
+               cleanup_fail &&
+
+               PAGER='wc >PAGER_used' &&
+               export PAGER &&
+               $full_command &&
+               test -e PAGER_used
+       "
+}
+
+test_core_pager_overrides() {
+       if_local_config=
+       used_if_wanted='overrides PAGER'
+       test_core_pager "$@"
+}
+
+test_local_config_ignored() {
+       if_local_config='! '
+       used_if_wanted='is not used'
+       test_core_pager "$@"
+}
+
+test_core_pager() {
+       parse_args "$@"
+
+       $test_expectation TTY "$cmd - repository-local core.pager setting $used_if_wanted" "
+               unset GIT_PAGER;
+               rm -f core.pager_used ||
+               cleanup_fail &&
+
+               PAGER=wc &&
+               export PAGER &&
+               git config core.pager 'wc >core.pager_used' &&
+               $full_command &&
+               ${if_local_config}test -e core.pager_used
+       "
+}
+
+test_core_pager_subdir() {
+       if_local_config=
+       used_if_wanted='overrides PAGER'
+       test_pager_subdir_helper "$@"
+}
+
+test_no_local_config_subdir() {
+       if_local_config='! '
+       used_if_wanted='is not used'
+       test_pager_subdir_helper "$@"
+}
+
+test_pager_subdir_helper() {
+       parse_args "$@"
+
+       $test_expectation TTY "$cmd - core.pager $used_if_wanted from subdirectory" "
+               unset GIT_PAGER;
+               rm -f core.pager_used &&
+               rm -fr sub ||
+               cleanup_fail &&
+
+               PAGER=wc &&
+               stampname=\$(pwd)/core.pager_used &&
+               export PAGER stampname &&
+               git config core.pager 'wc >\"\$stampname\"' &&
+               mkdir sub &&
+               (
+                       cd sub &&
+                       $full_command
+               ) &&
+               ${if_local_config}test -e core.pager_used
+       "
+}
+
+test_GIT_PAGER_overrides() {
+       parse_args "$@"
+
+       $test_expectation TTY "$cmd - GIT_PAGER overrides core.pager" "
+               rm -f GIT_PAGER_used ||
+               cleanup_fail &&
+
+               git config core.pager wc &&
+               GIT_PAGER='wc >GIT_PAGER_used' &&
+               export GIT_PAGER &&
+               $full_command &&
+               test -e GIT_PAGER_used
+       "
+}
+
+test_doesnt_paginate() {
+       parse_args "$@"
+
+       $test_expectation TTY "no pager for '$cmd'" "
+               rm -f GIT_PAGER_used ||
+               cleanup_fail &&
+
+               GIT_PAGER='wc >GIT_PAGER_used' &&
+               export GIT_PAGER &&
+               $full_command &&
+               ! test -e GIT_PAGER_used
+       "
+}
+
+test_pager_choices() {
+       test_default_pager        expect_success "$@"
+       test_PAGER_overrides      expect_success "$@"
+       test_core_pager_overrides expect_success "$@"
+       test_core_pager_subdir    expect_success "$@"
+       test_GIT_PAGER_overrides  expect_success "$@"
+}
+
+test_expect_success 'setup: some aliases' '
+       git config alias.aliasedlog log &&
+       git config alias.true "!true"
+'
+
+test_pager_choices                       'git log'
+test_pager_choices                       'git -p log'
+test_pager_choices                       'git aliasedlog'
+
+test_default_pager        expect_success 'git -p aliasedlog'
+test_PAGER_overrides      expect_success 'git -p aliasedlog'
+test_core_pager_overrides expect_success 'git -p aliasedlog'
+test_core_pager_subdir    expect_failure 'git -p aliasedlog'
+test_GIT_PAGER_overrides  expect_success 'git -p aliasedlog'
+
+test_default_pager        expect_success 'git -p true'
+test_PAGER_overrides      expect_success 'git -p true'
+test_core_pager_overrides expect_success 'git -p true'
+test_core_pager_subdir    expect_failure 'git -p true'
+test_GIT_PAGER_overrides  expect_success 'git -p true'
+
+test_default_pager        expect_success test_must_fail 'git -p request-pull'
+test_PAGER_overrides      expect_success test_must_fail 'git -p request-pull'
+test_core_pager_overrides expect_success test_must_fail 'git -p request-pull'
+test_core_pager_subdir    expect_failure test_must_fail 'git -p request-pull'
+test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p request-pull'
+
+test_default_pager        expect_success test_must_fail 'git -p'
+test_PAGER_overrides      expect_success test_must_fail 'git -p'
+test_local_config_ignored expect_failure test_must_fail 'git -p'
+test_no_local_config_subdir expect_success test_must_fail 'git -p'
+test_GIT_PAGER_overrides  expect_success test_must_fail 'git -p'
+
+test_doesnt_paginate      expect_failure test_must_fail 'git -p nonsense'
+
+test_pager_choices                       'git shortlog'
+test_expect_success 'setup: configure shortlog not to paginate' '
+       git config pager.shortlog false
+'
+test_doesnt_paginate      expect_success 'git shortlog'
+test_no_local_config_subdir expect_success 'git shortlog'
+test_default_pager        expect_success 'git -p shortlog'
+test_core_pager_subdir    expect_success 'git -p shortlog'
+
+test_core_pager_subdir    expect_success test_must_fail \
+                                        'git -p apply </dev/null'
+
+test_done
diff --git a/t/t7006/test-terminal.perl b/t/t7006/test-terminal.perl
new file mode 100755 (executable)
index 0000000..73ff809
--- /dev/null
@@ -0,0 +1,58 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use IO::Pty;
+use File::Copy;
+
+# Run @$argv in the background with stdout redirected to $out.
+sub start_child {
+       my ($argv, $out) = @_;
+       my $pid = fork;
+       if (not defined $pid) {
+               die "fork failed: $!"
+       } elsif ($pid == 0) {
+               open STDOUT, ">&", $out;
+               close $out;
+               exec(@$argv) or die "cannot exec '$argv->[0]': $!"
+       }
+       return $pid;
+}
+
+# Wait for $pid to finish.
+sub finish_child {
+       # Simplified from wait_or_whine() in run-command.c.
+       my ($pid) = @_;
+
+       my $waiting = waitpid($pid, 0);
+       if ($waiting < 0) {
+               die "waitpid failed: $!";
+       } elsif ($? & 127) {
+               my $code = $? & 127;
+               warn "died of signal $code";
+               return $code - 128;
+       } else {
+               return $? >> 8;
+       }
+}
+
+sub xsendfile {
+       my ($out, $in) = @_;
+
+       # Note: the real sendfile() cannot read from a terminal.
+
+       # It is unspecified by POSIX whether reads
+       # from a disconnected terminal will return
+       # EIO (as in AIX 4.x, IRIX, and Linux) or
+       # end-of-file.  Either is fine.
+       copy($in, $out, 4096) or $!{EIO} or die "cannot copy from child: $!";
+}
+
+if ($#ARGV < 1) {
+       die "usage: test-terminal program args";
+}
+my $master = new IO::Pty;
+my $slave = $master->slave;
+my $pid = start_child(\@ARGV, $slave);
+close $slave;
+xsendfile(\*STDOUT, $master);
+exit(finish_child($pid));
diff --git a/t/t7008-grep-binary.sh b/t/t7008-grep-binary.sh
new file mode 100755 (executable)
index 0000000..e058d18
--- /dev/null
@@ -0,0 +1,102 @@
+#!/bin/sh
+
+test_description='git grep in binary files'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' "
+       echo 'binaryQfile' | q_to_nul >a &&
+       git add a &&
+       git commit -m.
+"
+
+test_expect_success 'git grep ina a' '
+       echo Binary file a matches >expect &&
+       git grep ina a >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git grep -ah ina a' '
+       git grep -ah ina a >actual &&
+       test_cmp a actual
+'
+
+test_expect_success 'git grep -I ina a' '
+       : >expect &&
+       test_must_fail git grep -I ina a >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git grep -c ina a' '
+       echo a:1 >expect &&
+       git grep -c ina a >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git grep -l ina a' '
+       echo a >expect &&
+       git grep -l ina a >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git grep -L bar a' '
+       echo a >expect &&
+       git grep -L bar a >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git grep -q ina a' '
+       : >expect &&
+       git grep -q ina a >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git grep -F ile a' '
+       git grep -F ile a
+'
+
+test_expect_success 'git grep -Fi iLE a' '
+       git grep -Fi iLE a
+'
+
+# This test actually passes on platforms where regexec() supports the
+# flag REG_STARTEND.
+test_expect_success 'git grep ile a' '
+       git grep ile a
+'
+
+test_expect_failure 'git grep .fi a' '
+       git grep .fi a
+'
+
+test_expect_success 'git grep -F y<NUL>f a' "
+       printf 'yQf' | q_to_nul >f &&
+       git grep -f f -F a
+"
+
+test_expect_success 'git grep -F y<NUL>x a' "
+       printf 'yQx' | q_to_nul >f &&
+       test_must_fail git grep -f f -F a
+"
+
+test_expect_success 'git grep -Fi Y<NUL>f a' "
+       printf 'YQf' | q_to_nul >f &&
+       git grep -f f -Fi a
+"
+
+test_expect_failure 'git grep -Fi Y<NUL>x a' "
+       printf 'YQx' | q_to_nul >f &&
+       test_must_fail git grep -f f -Fi a
+"
+
+test_expect_success 'git grep y<NUL>f a' "
+       printf 'yQf' | q_to_nul >f &&
+       git grep -f f a
+"
+
+test_expect_failure 'git grep y<NUL>x a' "
+       printf 'yQx' | q_to_nul >f &&
+       test_must_fail git grep -f f a
+"
+
+test_done
index d8a7c798525728ddc8fc5fa9bd8335d8d1f0a710..0335a9a158ab507b2e37e4d7642a69ba4344c0ad 100755 (executable)
@@ -103,14 +103,10 @@ test_expect_success 'git ls-files (relative #3)' '
        git add a &&
        (
                cd a/b &&
-               if git ls-files "../e/f"
-               then
-                       echo Gaah, should have failed
-                       exit 1
-               else
-                       : happy
-               fi
-       )
+               git ls-files "../e/f"
+       )  >current &&
+       echo ../e/f >expect &&
+       test_cmp expect current
 
 '
 
diff --git a/t/t7011-skip-worktree-reading.sh b/t/t7011-skip-worktree-reading.sh
new file mode 100755 (executable)
index 0000000..bb4066f
--- /dev/null
@@ -0,0 +1,163 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Nguyễn Thái Ngọc Duy
+#
+
+test_description='skip-worktree bit test'
+
+. ./test-lib.sh
+
+cat >expect.full <<EOF
+H 1
+H 2
+H init.t
+H sub/1
+H sub/2
+EOF
+
+cat >expect.skip <<EOF
+S 1
+H 2
+H init.t
+S sub/1
+H sub/2
+EOF
+
+NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+ZERO_SHA0=0000000000000000000000000000000000000000
+setup_absent() {
+       test -f 1 && rm 1
+       git update-index --remove 1 &&
+       git update-index --add --cacheinfo 100644 $NULL_SHA1 1 &&
+       git update-index --skip-worktree 1
+}
+
+test_absent() {
+       echo "100644 $NULL_SHA1 0       1" > expected &&
+       git ls-files --stage 1 > result &&
+       test_cmp expected result &&
+       test ! -f 1
+}
+
+setup_dirty() {
+       git update-index --force-remove 1 &&
+       echo dirty > 1 &&
+       git update-index --add --cacheinfo 100644 $NULL_SHA1 1 &&
+       git update-index --skip-worktree 1
+}
+
+test_dirty() {
+       echo "100644 $NULL_SHA1 0       1" > expected &&
+       git ls-files --stage 1 > result &&
+       test_cmp expected result &&
+       echo dirty > expected
+       test_cmp expected 1
+}
+
+test_expect_success 'setup' '
+       test_commit init &&
+       mkdir sub &&
+       touch ./1 ./2 sub/1 sub/2 &&
+       git add 1 2 sub/1 sub/2 &&
+       git update-index --skip-worktree 1 sub/1 &&
+       git ls-files -t > result &&
+       test_cmp expect.skip result
+'
+
+test_expect_success 'update-index' '
+       setup_absent &&
+       git update-index 1 &&
+       test_absent
+'
+
+test_expect_success 'update-index' '
+       setup_dirty &&
+       git update-index 1 &&
+       test_dirty
+'
+
+test_expect_success 'update-index --remove' '
+       setup_absent &&
+       git update-index --remove 1 &&
+       test -z "$(git ls-files 1)" &&
+       test ! -f 1
+'
+
+test_expect_success 'update-index --remove' '
+       setup_dirty &&
+       git update-index --remove 1 &&
+       test -z "$(git ls-files 1)" &&
+       echo dirty > expected &&
+       test_cmp expected 1
+'
+
+test_expect_success 'ls-files --delete' '
+       setup_absent &&
+       test -z "$(git ls-files -d)"
+'
+
+test_expect_success 'ls-files --delete' '
+       setup_dirty &&
+       test -z "$(git ls-files -d)"
+'
+
+test_expect_success 'ls-files --modified' '
+       setup_absent &&
+       test -z "$(git ls-files -m)"
+'
+
+test_expect_success 'ls-files --modified' '
+       setup_dirty &&
+       test -z "$(git ls-files -m)"
+'
+
+test_expect_success 'grep with skip-worktree file' '
+       git update-index --no-skip-worktree 1 &&
+       echo test > 1 &&
+       git update-index 1 &&
+       git update-index --skip-worktree 1 &&
+       rm 1 &&
+       test "$(git grep --no-ext-grep test)" = "1:test"
+'
+
+echo ":000000 100644 $ZERO_SHA0 $NULL_SHA1 A   1" > expected
+test_expect_success 'diff-index does not examine skip-worktree absent entries' '
+       setup_absent &&
+       git diff-index HEAD -- 1 > result &&
+       test_cmp expected result
+'
+
+test_expect_success 'diff-index does not examine skip-worktree dirty entries' '
+       setup_dirty &&
+       git diff-index HEAD -- 1 > result &&
+       test_cmp expected result
+'
+
+test_expect_success 'diff-files does not examine skip-worktree absent entries' '
+       setup_absent &&
+       test -z "$(git diff-files -- one)"
+'
+
+test_expect_success 'diff-files does not examine skip-worktree dirty entries' '
+       setup_dirty &&
+       test -z "$(git diff-files -- one)"
+'
+
+test_expect_success 'git-rm succeeds on skip-worktree absent entries' '
+       setup_absent &&
+       git rm 1
+'
+
+test_expect_success 'commit on skip-worktree absent entries' '
+       git reset &&
+       setup_absent &&
+       test_must_fail git commit -m null 1
+'
+
+test_expect_success 'commit on skip-worktree dirty entries' '
+       git reset &&
+       setup_dirty &&
+       test_must_fail git commit -m null 1
+'
+
+test_done
diff --git a/t/t7012-skip-worktree-writing.sh b/t/t7012-skip-worktree-writing.sh
new file mode 100755 (executable)
index 0000000..582d0b5
--- /dev/null
@@ -0,0 +1,146 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Nguyễn Thái Ngọc Duy
+#
+
+test_description='test worktree writing operations when skip-worktree is used'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       test_commit init &&
+       echo modified >> init.t &&
+       touch added &&
+       git add init.t added &&
+       git commit -m "modified and added" &&
+       git tag top
+'
+
+test_expect_success 'read-tree updates worktree, absent case' '
+       git checkout -f top &&
+       git update-index --skip-worktree init.t &&
+       rm init.t &&
+       git read-tree -m -u HEAD^ &&
+       echo init > expected &&
+       test_cmp expected init.t
+'
+
+test_expect_success 'read-tree updates worktree, dirty case' '
+       git checkout -f top &&
+       git update-index --skip-worktree init.t &&
+       echo dirty >> init.t &&
+       test_must_fail git read-tree -m -u HEAD^ &&
+       grep -q dirty init.t &&
+       test "$(git ls-files -t init.t)" = "S init.t" &&
+       git update-index --no-skip-worktree init.t
+'
+
+test_expect_success 'read-tree removes worktree, absent case' '
+       git checkout -f top &&
+       git update-index --skip-worktree added &&
+       rm added &&
+       git read-tree -m -u HEAD^ &&
+       test ! -f added
+'
+
+test_expect_success 'read-tree removes worktree, dirty case' '
+       git checkout -f top &&
+       git update-index --skip-worktree added &&
+       echo dirty >> added &&
+       test_must_fail git read-tree -m -u HEAD^ &&
+       grep -q dirty added &&
+       test "$(git ls-files -t added)" = "S added" &&
+       git update-index --no-skip-worktree added
+'
+
+NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+ZERO_SHA0=0000000000000000000000000000000000000000
+setup_absent() {
+       test -f 1 && rm 1
+       git update-index --remove 1 &&
+       git update-index --add --cacheinfo 100644 $NULL_SHA1 1 &&
+       git update-index --skip-worktree 1
+}
+
+test_absent() {
+       echo "100644 $NULL_SHA1 0       1" > expected &&
+       git ls-files --stage 1 > result &&
+       test_cmp expected result &&
+       test ! -f 1
+}
+
+setup_dirty() {
+       git update-index --force-remove 1 &&
+       echo dirty > 1 &&
+       git update-index --add --cacheinfo 100644 $NULL_SHA1 1 &&
+       git update-index --skip-worktree 1
+}
+
+test_dirty() {
+       echo "100644 $NULL_SHA1 0       1" > expected &&
+       git ls-files --stage 1 > result &&
+       test_cmp expected result &&
+       echo dirty > expected
+       test_cmp expected 1
+}
+
+cat >expected <<EOF
+S 1
+H 2
+H init.t
+S sub/1
+H sub/2
+EOF
+
+test_expect_success 'index setup' '
+       git checkout -f init &&
+       mkdir sub &&
+       touch ./1 ./2 sub/1 sub/2 &&
+       git add 1 2 sub/1 sub/2 &&
+       git update-index --skip-worktree 1 sub/1 &&
+       git ls-files -t > result &&
+       test_cmp expected result
+'
+
+test_expect_success 'git-add ignores worktree content' '
+       setup_absent &&
+       git add 1 &&
+       test_absent
+'
+
+test_expect_success 'git-add ignores worktree content' '
+       setup_dirty &&
+       git add 1 &&
+       test_dirty
+'
+
+test_expect_success 'git-rm fails if worktree is dirty' '
+       setup_dirty &&
+       test_must_fail git rm 1 &&
+       test_dirty
+'
+
+cat >expected <<EOF
+Would remove expected
+Would remove result
+EOF
+test_expect_success 'git-clean, absent case' '
+       setup_absent &&
+       git clean -n > result &&
+       test_cmp expected result
+'
+
+test_expect_success 'git-clean, dirty case' '
+       setup_dirty &&
+       git clean -n > result &&
+       test_cmp expected result
+'
+
+#TODO test_expect_failure 'git-apply adds file' false
+#TODO test_expect_failure 'git-apply updates file' false
+#TODO test_expect_failure 'git-apply removes file' false
+#TODO test_expect_failure 'git-mv to skip-worktree' false
+#TODO test_expect_failure 'git-mv from skip-worktree' false
+#TODO test_expect_failure 'git-checkout' false
+
+test_done
diff --git a/t/t7060-wtstatus.sh b/t/t7060-wtstatus.sh
new file mode 100755 (executable)
index 0000000..fcac472
--- /dev/null
@@ -0,0 +1,59 @@
+#!/bin/sh
+
+test_description='basic work tree status reporting'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       test_commit A &&
+       test_commit B oneside added &&
+       git checkout A^0 &&
+       test_commit C oneside created
+'
+
+test_expect_success 'A/A conflict' '
+       git checkout B^0 &&
+       test_must_fail git merge C
+'
+
+test_expect_success 'Report path with conflict' '
+       git diff --cached --name-status >actual &&
+       echo "U oneside" >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'Report new path with conflict' '
+       git diff --cached --name-status HEAD^ >actual &&
+       echo "U oneside" >expect &&
+       test_cmp expect actual
+'
+
+cat >expect <<EOF
+# On branch side
+# Unmerged paths:
+#   (use "git add/rm <file>..." as appropriate to mark resolution)
+#
+#      deleted by us:      foo
+#
+no changes added to commit (use "git add" and/or "git commit -a")
+EOF
+
+test_expect_success 'M/D conflict does not segfault' '
+       mkdir mdconflict &&
+       (
+               cd mdconflict &&
+               git init &&
+               test_commit initial foo "" &&
+               test_commit modify foo foo &&
+               git checkout -b side HEAD^ &&
+               git rm foo &&
+               git commit -m delete &&
+               test_must_fail git merge master &&
+               test_must_fail git commit --dry-run >../actual &&
+               test_cmp ../expect ../actual &&
+               git status >../actual &&
+               test_cmp ../expect ../actual
+       )
+'
+
+test_done
index e637c7d4dbdce337b4d005719c4aa86d447a70d9..b8cf2603a195af406d3606712e45fd1195c1588f 100755 (executable)
@@ -139,19 +139,19 @@ test_expect_success \
 test_expect_success \
        'resetting to HEAD with no changes should succeed and do nothing' '
        git reset --hard &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
        git reset --hard HEAD &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
        git reset --soft &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
        git reset --soft HEAD &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
        git reset --mixed &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
        git reset --mixed HEAD &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
        git reset &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
        git reset HEAD &&
                check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
 '
@@ -419,7 +419,8 @@ test_expect_success 'resetting an unmodified path is a no-op' '
 '
 
 cat > expect << EOF
-file2: locally modified
+Unstaged changes after reset:
+M      file2
 EOF
 
 test_expect_success '--mixed refreshes the index' '
index 42bf518c68e6ef07c8be1af714723b2f900a573c..1eef93c2b292c8ec649f12c826587365e5a0d0e6 100755 (executable)
@@ -11,16 +11,57 @@ test_expect_success 'setup non-bare' '
        git commit -a -m two
 '
 
+test_expect_success '"hard" reset requires a worktree' '
+       (cd .git &&
+        test_must_fail git reset --hard)
+'
+
+test_expect_success '"merge" reset requires a worktree' '
+       (cd .git &&
+        test_must_fail git reset --merge)
+'
+
+test_expect_success '"keep" reset requires a worktree' '
+       (cd .git &&
+        test_must_fail git reset --keep)
+'
+
+test_expect_success '"mixed" reset is ok' '
+       (cd .git && git reset)
+'
+
+test_expect_success '"soft" reset is ok' '
+       (cd .git && git reset --soft)
+'
+
+test_expect_success 'hard reset works with GIT_WORK_TREE' '
+       mkdir worktree &&
+       GIT_WORK_TREE=$PWD/worktree GIT_DIR=$PWD/.git git reset --hard &&
+       test_cmp file worktree/file
+'
+
 test_expect_success 'setup bare' '
        git clone --bare . bare.git &&
        cd bare.git
 '
 
-test_expect_success 'hard reset is not allowed' '
-       test_must_fail  git reset --hard HEAD^
+test_expect_success '"hard" reset is not allowed in bare' '
+       test_must_fail git reset --hard HEAD^
+'
+
+test_expect_success '"merge" reset is not allowed in bare' '
+       test_must_fail git reset --merge HEAD^
+'
+
+test_expect_success '"keep" reset is not allowed in bare' '
+       test_must_fail git reset --keep HEAD^
+'
+
+test_expect_success '"mixed" reset is not allowed in bare' '
+       test_must_fail git reset --mixed HEAD^
 '
 
-test_expect_success 'soft reset is allowed' '
+test_expect_success '"soft" reset is allowed in bare' '
        git reset --soft HEAD^ &&
        test "`git show --pretty=format:%s | head -n 1`" = "one"
 '
diff --git a/t/t7105-reset-patch.sh b/t/t7105-reset-patch.sh
new file mode 100755 (executable)
index 0000000..9891e2c
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+test_description='git reset --patch'
+. ./lib-patch-mode.sh
+
+test_expect_success PERL 'setup' '
+       mkdir dir &&
+       echo parent > dir/foo &&
+       echo dummy > bar &&
+       git add dir &&
+       git commit -m initial &&
+       test_tick &&
+       test_commit second dir/foo head &&
+       set_and_save_state bar bar_work bar_index &&
+       save_head
+'
+
+# note: bar sorts before foo, so the first 'n' is always to skip 'bar'
+
+test_expect_success PERL 'saying "n" does nothing' '
+       set_and_save_state dir/foo work work
+       (echo n; echo n) | git reset -p &&
+       verify_saved_state dir/foo &&
+       verify_saved_state bar
+'
+
+test_expect_success PERL 'git reset -p' '
+       (echo n; echo y) | git reset -p &&
+       verify_state dir/foo work head &&
+       verify_saved_state bar
+'
+
+test_expect_success PERL 'git reset -p HEAD^' '
+       (echo n; echo y) | git reset -p HEAD^ &&
+       verify_state dir/foo work parent &&
+       verify_saved_state bar
+'
+
+# The idea in the rest is that bar sorts first, so we always say 'y'
+# first and if the path limiter fails it'll apply to bar instead of
+# dir/foo.  There's always an extra 'n' to reject edits to dir/foo in
+# the failure case (and thus get out of the loop).
+
+test_expect_success PERL 'git reset -p dir' '
+       set_state dir/foo work work
+       (echo y; echo n) | git reset -p dir &&
+       verify_state dir/foo work head &&
+       verify_saved_state bar
+'
+
+test_expect_success PERL 'git reset -p -- foo (inside dir)' '
+       set_state dir/foo work work
+       (echo y; echo n) | (cd dir && git reset -p -- foo) &&
+       verify_state dir/foo work head &&
+       verify_saved_state bar
+'
+
+test_expect_success PERL 'git reset -p HEAD^ -- dir' '
+       (echo y; echo n) | git reset -p HEAD^ -- dir &&
+       verify_state dir/foo work parent &&
+       verify_saved_state bar
+'
+
+test_expect_success PERL 'none of this moved HEAD' '
+       verify_saved_head
+'
+
+
+test_done
diff --git a/t/t7110-reset-merge.sh b/t/t7110-reset-merge.sh
new file mode 100755 (executable)
index 0000000..70cdd8e
--- /dev/null
@@ -0,0 +1,295 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Christian Couder
+#
+
+test_description='Tests for "git reset" with "--merge" and "--keep" options'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+    for i in 1 2 3; do echo line $i; done >file1 &&
+    cat file1 >file2 &&
+    git add file1 file2 &&
+    test_tick &&
+    git commit -m "Initial commit" &&
+    git tag initial &&
+    echo line 4 >>file1 &&
+    cat file1 >file2 &&
+    test_tick &&
+    git commit -m "add line 4 to file1" file1 &&
+    git tag second
+'
+
+# The next test will test the following:
+#
+#           working index HEAD target         working index HEAD
+#           ----------------------------------------------------
+# file1:     C       C     C    D     --merge  D       D     D
+# file2:     C       D     D    D     --merge  C       D     D
+test_expect_success 'reset --merge is ok with changes in file it does not touch' '
+    git reset --merge HEAD^ &&
+    ! grep 4 file1 &&
+    grep 4 file2 &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
+    test -z "$(git diff --cached)"
+'
+
+test_expect_success 'reset --merge is ok when switching back' '
+    git reset --merge second &&
+    grep 4 file1 &&
+    grep 4 file2 &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
+    test -z "$(git diff --cached)"
+'
+
+# The next test will test the following:
+#
+#           working index HEAD target         working index HEAD
+#           ----------------------------------------------------
+# file1:     C       C     C    D     --keep   D       D     D
+# file2:     C       D     D    D     --keep   C       D     D
+test_expect_success 'reset --keep is ok with changes in file it does not touch' '
+    git reset --hard second &&
+    cat file1 >file2 &&
+    git reset --keep HEAD^ &&
+    ! grep 4 file1 &&
+    grep 4 file2 &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
+    test -z "$(git diff --cached)"
+'
+
+test_expect_success 'reset --keep is ok when switching back' '
+    git reset --keep second &&
+    grep 4 file1 &&
+    grep 4 file2 &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
+    test -z "$(git diff --cached)"
+'
+
+# The next test will test the following:
+#
+#           working index HEAD target         working index HEAD
+#           ----------------------------------------------------
+# file1:     B       B     C    D     --merge  D       D     D
+# file2:     C       D     D    D     --merge  C       D     D
+test_expect_success 'reset --merge discards changes added to index (1)' '
+    git reset --hard second &&
+    cat file1 >file2 &&
+    echo "line 5" >> file1 &&
+    git add file1 &&
+    git reset --merge HEAD^ &&
+    ! grep 4 file1 &&
+    ! grep 5 file1 &&
+    grep 4 file2 &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
+    test -z "$(git diff --cached)"
+'
+
+test_expect_success 'reset --merge is ok again when switching back (1)' '
+    git reset --hard initial &&
+    echo "line 5" >> file2 &&
+    git add file2 &&
+    git reset --merge second &&
+    ! grep 4 file2 &&
+    ! grep 5 file1 &&
+    grep 4 file1 &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
+    test -z "$(git diff --cached)"
+'
+
+# The next test will test the following:
+#
+#           working index HEAD target         working index HEAD
+#           ----------------------------------------------------
+# file1:     B       B     C    D     --keep   (disallowed)
+test_expect_success 'reset --keep fails with changes in index in files it touches' '
+    git reset --hard second &&
+    echo "line 5" >> file1 &&
+    git add file1 &&
+    test_must_fail git reset --keep HEAD^
+'
+
+# The next test will test the following:
+#
+#           working index HEAD target         working index HEAD
+#           ----------------------------------------------------
+# file1:     C       C     C    D     --merge  D       D     D
+# file2:     C       C     D    D     --merge  D       D     D
+test_expect_success 'reset --merge discards changes added to index (2)' '
+    git reset --hard second &&
+    echo "line 4" >> file2 &&
+    git add file2 &&
+    git reset --merge HEAD^ &&
+    ! grep 4 file2 &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
+    test -z "$(git diff)" &&
+    test -z "$(git diff --cached)"
+'
+
+test_expect_success 'reset --merge is ok again when switching back (2)' '
+    git reset --hard initial &&
+    git reset --merge second &&
+    ! grep 4 file2 &&
+    grep 4 file1 &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
+    test -z "$(git diff --cached)"
+'
+
+# The next test will test the following:
+#
+#           working index HEAD target         working index HEAD
+#           ----------------------------------------------------
+# file1:     C       C     C    D     --keep   D       D     D
+# file2:     C       C     D    D     --keep   C       D     D
+test_expect_success 'reset --keep keeps changes it does not touch' '
+    git reset --hard second &&
+    echo "line 4" >> file2 &&
+    git add file2 &&
+    git reset --keep HEAD^ &&
+    grep 4 file2 &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
+    test -z "$(git diff --cached)"
+'
+
+test_expect_success 'reset --keep keeps changes when switching back' '
+    git reset --keep second &&
+    grep 4 file2 &&
+    grep 4 file1 &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
+    test -z "$(git diff --cached)"
+'
+
+# The next test will test the following:
+#
+#           working index HEAD target         working index HEAD
+#           ----------------------------------------------------
+# file1:     A       B     B    C     --merge  (disallowed)
+test_expect_success 'reset --merge fails with changes in file it touches' '
+    git reset --hard second &&
+    echo "line 5" >> file1 &&
+    test_tick &&
+    git commit -m "add line 5" file1 &&
+    sed -e "s/line 1/changed line 1/" <file1 >file3 &&
+    mv file3 file1 &&
+    test_must_fail git reset --merge HEAD^ 2>err.log &&
+    grep file1 err.log | grep "not uptodate"
+'
+
+# The next test will test the following:
+#
+#           working index HEAD target         working index HEAD
+#           ----------------------------------------------------
+# file1:     A       B     B    C     --keep   (disallowed)
+test_expect_success 'reset --keep fails with changes in file it touches' '
+    git reset --hard second &&
+    echo "line 5" >> file1 &&
+    test_tick &&
+    git commit -m "add line 5" file1 &&
+    sed -e "s/line 1/changed line 1/" <file1 >file3 &&
+    mv file3 file1 &&
+    test_must_fail git reset --keep HEAD^ 2>err.log &&
+    grep file1 err.log | grep "not uptodate"
+'
+
+test_expect_success 'setup 3 different branches' '
+    git reset --hard second &&
+    git branch branch1 &&
+    git branch branch2 &&
+    git branch branch3 &&
+    git checkout branch1 &&
+    echo "line 5 in branch1" >> file1 &&
+    test_tick &&
+    git commit -a -m "change in branch1" &&
+    git checkout branch2 &&
+    echo "line 5 in branch2" >> file1 &&
+    test_tick &&
+    git commit -a -m "change in branch2" &&
+    git tag third &&
+    git checkout branch3 &&
+    echo a new file >file3 &&
+    rm -f file1 &&
+    git add file3 &&
+    test_tick &&
+    git commit -a -m "change in branch3"
+'
+
+# The next test will test the following:
+#
+#           working index HEAD target         working index HEAD
+#           ----------------------------------------------------
+# file1:     X       U     B    C     --merge  C       C     C
+test_expect_success '"reset --merge HEAD^" is ok with pending merge' '
+    git checkout third &&
+    test_must_fail git merge branch1 &&
+    git reset --merge HEAD^ &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
+    test -z "$(git diff --cached)" &&
+    test -z "$(git diff)"
+'
+
+# The next test will test the following:
+#
+#           working index HEAD target         working index HEAD
+#           ----------------------------------------------------
+# file1:     X       U     B    C     --keep   (disallowed)
+test_expect_success '"reset --keep HEAD^" fails with pending merge' '
+    git reset --hard third &&
+    test_must_fail git merge branch1 &&
+    test_must_fail git reset --keep HEAD^ 2>err.log &&
+    grep "middle of a merge" err.log
+'
+
+# The next test will test the following:
+#
+#           working index HEAD target         working index HEAD
+#           ----------------------------------------------------
+# file1:     X       U     B    B     --merge  B       B     B
+test_expect_success '"reset --merge HEAD" is ok with pending merge' '
+    git reset --hard third &&
+    test_must_fail git merge branch1 &&
+    git reset --merge HEAD &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse third)" &&
+    test -z "$(git diff --cached)" &&
+    test -z "$(git diff)"
+'
+
+# The next test will test the following:
+#
+#           working index HEAD target         working index HEAD
+#           ----------------------------------------------------
+# file1:     X       U     B    B     --keep   (disallowed)
+test_expect_success '"reset --keep HEAD" fails with pending merge' '
+    git reset --hard third &&
+    test_must_fail git merge branch1 &&
+    test_must_fail git reset --keep HEAD 2>err.log &&
+    grep "middle of a merge" err.log
+'
+
+test_expect_success '--merge is ok with added/deleted merge' '
+    git reset --hard third &&
+    rm -f file2 &&
+    test_must_fail git merge branch3 &&
+    ! test -f file2 &&
+    test -f file3 &&
+    git diff --exit-code file3 &&
+    git diff --exit-code branch3 file3 &&
+    git reset --merge HEAD &&
+    ! test -f file3 &&
+    ! test -f file2 &&
+    git diff --exit-code --cached
+'
+
+test_expect_success '--keep fails with added/deleted merge' '
+    git reset --hard third &&
+    rm -f file2 &&
+    test_must_fail git merge branch3 &&
+    ! test -f file2 &&
+    test -f file3 &&
+    git diff --exit-code file3 &&
+    git diff --exit-code branch3 file3 &&
+    test_must_fail git reset --keep HEAD 2>err.log &&
+    grep "middle of a merge" err.log
+'
+
+test_done
diff --git a/t/t7111-reset-table.sh b/t/t7111-reset-table.sh
new file mode 100755 (executable)
index 0000000..ce421ad
--- /dev/null
@@ -0,0 +1,121 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Christian Couder
+#
+
+test_description='Tests to check that "reset" options follow a known table'
+
+. ./test-lib.sh
+
+
+test_expect_success 'creating initial commits' '
+    test_commit E file1 &&
+    test_commit D file1 &&
+    test_commit C file1
+'
+
+while read W1 I1 H1 T opt W2 I2 H2
+do
+    test_expect_success "check: $W1 $I1 $H1 $T --$opt $W2 $I2 $H2" '
+       git reset --hard C &&
+       if test "$I1" != "$H1"
+       then
+           echo "$I1" >file1 &&
+           git add file1
+       fi &&
+       if test "$W1" != "$I1"
+       then
+           echo "$W1" >file1
+       fi &&
+       if test "$W2" != "XXXXX"
+       then
+           git reset --$opt $T &&
+           test "$(cat file1)" = "$W2" &&
+           git checkout-index -f -- file1 &&
+           test "$(cat file1)" = "$I2" &&
+           git checkout -f HEAD -- file1 &&
+           test "$(cat file1)" = "$H2"
+       else
+           test_must_fail git reset --$opt $T
+       fi
+    '
+done <<\EOF
+A B C D soft   A B D
+A B C D mixed  A D D
+A B C D hard   D D D
+A B C D merge  XXXXX
+A B C D keep   XXXXX
+A B C C soft   A B C
+A B C C mixed  A C C
+A B C C hard   C C C
+A B C C merge  XXXXX
+A B C C keep   A C C
+B B C D soft   B B D
+B B C D mixed  B D D
+B B C D hard   D D D
+B B C D merge  D D D
+B B C D keep   XXXXX
+B B C C soft   B B C
+B B C C mixed  B C C
+B B C C hard   C C C
+B B C C merge  C C C
+B B C C keep   B C C
+B C C D soft   B C D
+B C C D mixed  B D D
+B C C D hard   D D D
+B C C D merge  XXXXX
+B C C D keep   XXXXX
+B C C C soft   B C C
+B C C C mixed  B C C
+B C C C hard   C C C
+B C C C merge  B C C
+B C C C keep   B C C
+EOF
+
+test_expect_success 'setting up branches to test with unmerged entries' '
+    git reset --hard C &&
+    git branch branch1 &&
+    git branch branch2 &&
+    git checkout branch1 &&
+    test_commit B1 file1 &&
+    git checkout branch2 &&
+    test_commit B file1
+'
+
+while read W1 I1 H1 T opt W2 I2 H2
+do
+    test_expect_success "check: $W1 $I1 $H1 $T --$opt $W2 $I2 $H2" '
+       git reset --hard B &&
+       test_must_fail git merge branch1 &&
+       cat file1 >X_file1 &&
+       if test "$W2" != "XXXXX"
+       then
+           git reset --$opt $T &&
+           if test "$W2" = "X"
+           then
+               test_cmp file1 X_file1
+           else
+               test "$(cat file1)" = "$W2"
+           fi &&
+           git checkout-index -f -- file1 &&
+           test "$(cat file1)" = "$I2" &&
+           git checkout -f HEAD -- file1 &&
+           test "$(cat file1)" = "$H2"
+       else
+           test_must_fail git reset --$opt $T
+       fi
+    '
+done <<\EOF
+X U B C soft   XXXXX
+X U B C mixed  X C C
+X U B C hard   C C C
+X U B C merge  C C C
+X U B C keep   XXXXX
+X U B B soft   XXXXX
+X U B B mixed  X B B
+X U B B hard   B B B
+X U B B merge  B B B
+X U B B keep   XXXXX
+EOF
+
+test_done
index bdb808af1aefae97c297f7845a8cd3b595404e89..1337fa5a2209d489c43f0a34c95a89053d3fd8bf 100755 (executable)
@@ -11,10 +11,12 @@ Test switching across them.
   ! [master] Initial A one, A two
    * [renamer] Renamer R one->uno, M two
     ! [side] Side M one, D two, A three
-  ---
-    + [side] Side M one, D two, A three
-   *  [renamer] Renamer R one->uno, M two
-  +*+ [master] Initial A one, A two
+     ! [simple] Simple D one, M two
+  ----
+     + [simple] Simple D one, M two
+    +  [side] Side M one, D two, A three
+   *   [renamer] Renamer R one->uno, M two
+  +*++ [master] Initial A one, A two
 
 '
 
@@ -52,6 +54,11 @@ test_expect_success setup '
        git update-index --add --remove one two three &&
        git commit -m "Side M one, D two, A three" &&
 
+       git checkout -b simple master &&
+       rm -f one &&
+       fill a c e > two &&
+       git commit -a -m "Simple D one, M two" &&
+
        git checkout master
 '
 
@@ -166,19 +173,81 @@ test_expect_success 'checkout -m with merge conflict' '
        ! test -s current
 '
 
-test_expect_success 'checkout to detach HEAD' '
+test_expect_success 'format of merge conflict from checkout -m' '
+
+       git checkout -f master && git clean -f &&
+
+       fill b d > two &&
+       git checkout -m simple &&
+
+       git ls-files >current &&
+       fill same two two two >expect &&
+       test_cmp current expect &&
+
+       cat <<-EOF >expect &&
+       <<<<<<< simple
+       a
+       c
+       e
+       =======
+       b
+       d
+       >>>>>>> local
+       EOF
+       test_cmp two expect
+'
+
+test_expect_success 'checkout --merge --conflict=diff3 <branch>' '
+
+       git checkout -f master && git reset --hard && git clean -f &&
+
+       fill b d > two &&
+       git checkout --merge --conflict=diff3 simple &&
+
+       cat <<-EOF >expect &&
+       <<<<<<< simple
+       a
+       c
+       e
+       ||||||| master
+       a
+       b
+       c
+       d
+       e
+       =======
+       b
+       d
+       >>>>>>> local
+       EOF
+       test_cmp two expect
+'
 
+test_expect_success 'checkout to detach HEAD (with advice declined)' '
+
+       git config advice.detachedHead false &&
        git checkout -f renamer && git clean -f &&
        git checkout renamer^ 2>messages &&
-       (cat >messages.expect <<EOF
-Note: moving to '\''renamer^'\'' which isn'\''t a local branch
-If you want to create a new branch from this checkout, you may do so
-(now or later) by using -b with the checkout command again. Example:
-  git checkout -b <new_branch_name>
-HEAD is now at 7329388... Initial A one, A two
-EOF
-) &&
-       test_cmp messages.expect messages &&
+       grep "HEAD is now at 7329388" messages &&
+       test 1 -eq $(wc -l <messages) &&
+       H=$(git rev-parse --verify HEAD) &&
+       M=$(git show-ref -s --verify refs/heads/master) &&
+       test "z$H" = "z$M" &&
+       if git symbolic-ref HEAD >/dev/null 2>&1
+       then
+               echo "OOPS, HEAD is still symbolic???"
+               false
+       else
+               : happy
+       fi
+'
+
+test_expect_success 'checkout to detach HEAD' '
+       git config advice.detachedHead true &&
+       git checkout -f renamer && git clean -f &&
+       git checkout renamer^ 2>messages &&
+       grep "HEAD is now at 7329388" messages &&
+       test 1 -lt $(wc -l <messages) &&
        H=$(git rev-parse --verify HEAD) &&
        M=$(git show-ref -s --verify refs/heads/master) &&
        test "z$H" = "z$M" &&
@@ -469,7 +538,7 @@ test_expect_success 'checkout with --merge, in diff3 -m style' '
        (
                echo "<<<<<<< ours"
                echo ourside
-               echo "|||||||"
+               echo "||||||| base"
                echo original
                echo "======="
                echo theirside
@@ -513,7 +582,7 @@ test_expect_success 'checkout --conflict=diff3' '
        (
                echo "<<<<<<< ours"
                echo ourside
-               echo "|||||||"
+               echo "||||||| base"
                echo original
                echo "======="
                echo theirside
@@ -534,4 +603,69 @@ test_expect_success 'failing checkout -b should not break working tree' '
 
 '
 
+test_expect_success 'switch out of non-branch' '
+       git reset --hard master &&
+       git checkout master^0 &&
+       echo modified >one &&
+       test_must_fail git checkout renamer 2>error.log &&
+       ! grep "^Previous HEAD" error.log
+'
+
+(
+ echo "#!$SHELL_PATH"
+ cat <<\EOF
+O=$1 A=$2 B=$3
+cat "$A" >.tmp
+exec >"$A"
+echo '<<<<<<< filfre-theirs'
+cat "$B"
+echo '||||||| filfre-common'
+cat "$O"
+echo '======='
+cat ".tmp"
+echo '>>>>>>> filfre-ours'
+rm -f .tmp
+exit 1
+EOF
+) >filfre.sh
+chmod +x filfre.sh
+
+test_expect_success 'custom merge driver with checkout -m' '
+       git reset --hard &&
+
+       git config merge.filfre.driver "./filfre.sh %O %A %B" &&
+       git config merge.filfre.name "Feel-free merge driver" &&
+       git config merge.filfre.recursive binary &&
+       echo "arm merge=filfre" >.gitattributes &&
+
+       git checkout -b left &&
+       echo neutral >arm &&
+       git add arm .gitattributes &&
+       test_tick &&
+       git commit -m neutral &&
+       git branch right &&
+
+       echo left >arm &&
+       test_tick &&
+       git commit -a -m left &&
+       git checkout right &&
+
+       echo right >arm &&
+       test_tick &&
+       git commit -a -m right &&
+
+       test_must_fail git merge left &&
+       (
+               for t in filfre-common left right
+               do
+                       grep $t arm || exit 1
+               done
+               exit 0
+       ) &&
+
+       mv arm expect &&
+       git checkout -m arm &&
+       test_cmp expect arm
+'
+
 test_done
index 929d5d4d3b9d55f570cef1617a0716b17265c988..6c776e9bec78ab1cf960293d8c796e9de13045b5 100755 (executable)
@@ -22,6 +22,25 @@ test_expect_success 'setup' '
 
 '
 
+test_expect_success 'git clean with skip-worktree .gitignore' '
+       git update-index --skip-worktree .gitignore &&
+       rm .gitignore &&
+       mkdir -p build docs &&
+       touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
+       git clean &&
+       test -f Makefile &&
+       test -f README &&
+       test -f src/part1.c &&
+       test -f src/part2.c &&
+       test ! -f a.out &&
+       test ! -f src/part3.c &&
+       test -f docs/manual.txt &&
+       test -f obj.o &&
+       test -f build/lib.so &&
+       git update-index --no-skip-worktree .gitignore &&
+       git checkout .gitignore
+'
+
 test_expect_success 'git clean' '
 
        mkdir -p build docs &&
@@ -369,15 +388,69 @@ test_expect_success 'core.excludesfile' '
 
 '
 
-test_expect_success 'removal failure' '
+test_expect_success SANITY 'removal failure' '
 
        mkdir foo &&
        touch foo/bar &&
        (exec <foo/bar &&
         chmod 0 foo &&
-        test_must_fail git clean -f -d)
+        test_must_fail git clean -f -d &&
+        chmod 755 foo)
+'
 
+test_expect_success 'nested git work tree' '
+       rm -fr foo bar &&
+       mkdir foo bar &&
+       (
+               cd foo &&
+               git init &&
+               >hello.world
+               git add . &&
+               git commit -a -m nested
+       ) &&
+       (
+               cd bar &&
+               >goodbye.people
+       ) &&
+       git clean -f -d &&
+       test -f foo/.git/index &&
+       test -f foo/hello.world &&
+       ! test -d bar
+'
+
+test_expect_success 'force removal of nested git work tree' '
+       rm -fr foo bar &&
+       mkdir foo bar &&
+       (
+               cd foo &&
+               git init &&
+               >hello.world
+               git add . &&
+               git commit -a -m nested
+       ) &&
+       (
+               cd bar &&
+               >goodbye.people
+       ) &&
+       git clean -f -f -d &&
+       ! test -d foo &&
+       ! test -d bar
+'
+
+test_expect_success 'git clean -e' '
+       rm -fr repo &&
+       mkdir repo &&
+       (
+               cd repo &&
+               git init &&
+               touch known 1 2 3 &&
+               git add known &&
+               git clean -f -e 1 -e 2 &&
+               test -e 1 &&
+               test -e 2 &&
+               ! (test -e 3) &&
+               test -e known
+       )
 '
-chmod 755 foo
 
 test_done
index 0f2ccc6cf0123951d9bdbb880931868f29de5b4e..782b0a3ece7724bf8df7cf4d405715a42ad21d44 100755 (executable)
@@ -11,226 +11,317 @@ subcommands of git submodule.
 
 . ./test-lib.sh
 
-#
-# Test setup:
-#  -create a repository in directory init
-#  -add a couple of files
-#  -add directory init to 'superproject', this creates a DIRLINK entry
-#  -add a couple of regular files to enable testing of submodule filtering
-#  -mv init subrepo
-#  -add an entry to .gitmodules for submodule 'example'
-#
-test_expect_success 'Prepare submodule testing' '
-       : > t &&
+test_expect_success 'setup - initial commit' '
+       >t &&
        git add t &&
        git commit -m "initial commit" &&
-       git branch initial HEAD &&
+       git branch initial
+'
+
+test_expect_success 'setup - repository in init subdirectory' '
        mkdir init &&
-       cd init &&
-       git init &&
-       echo a >a &&
-       git add a &&
-       git commit -m "submodule commit 1" &&
-       git tag -a -m "rev-1" rev-1 &&
-       rev1=$(git rev-parse HEAD) &&
-       if test -z "$rev1"
-       then
-               echo "[OOPS] submodule git rev-parse returned nothing"
-               false
-       fi &&
-       cd .. &&
+       (
+               cd init &&
+               git init &&
+               echo a >a &&
+               git add a &&
+               git commit -m "submodule commit 1" &&
+               git tag -a -m "rev-1" rev-1
+       )
+'
+
+test_expect_success 'setup - commit with gitlink' '
        echo a >a &&
        echo z >z &&
        git add a init z &&
-       git commit -m "super commit 1" &&
-       mv init .subrepo &&
-       GIT_CONFIG=.gitmodules git config submodule.example.url git://example.com/init.git
+       git commit -m "super commit 1"
+'
+
+test_expect_success 'setup - hide init subdirectory' '
+       mv init .subrepo
 '
 
-test_expect_success 'Prepare submodule add testing' '
-       submodurl=$(pwd)
+test_expect_success 'setup - repository to add submodules to' '
+       git init addtest &&
+       git init addtest-ignore
+'
+
+# The 'submodule add' tests need some repository to add as a submodule.
+# The trash directory is a good one as any.
+submodurl=$TRASH_DIRECTORY
+
+listbranches() {
+       git for-each-ref --format='%(refname)' 'refs/heads/*'
+}
+
+inspect() {
+       dir=$1 &&
+       dotdot="${2:-..}" &&
+
        (
-               mkdir addtest &&
-               cd addtest &&
-               git init
+               cd "$dir" &&
+               listbranches >"$dotdot/heads" &&
+               { git symbolic-ref HEAD || :; } >"$dotdot/head" &&
+               git rev-parse HEAD >"$dotdot/head-sha1" &&
+               git update-index --refresh &&
+               git diff-files --exit-code &&
+               git clean -n -d -x >"$dotdot/untracked"
        )
-'
+}
 
 test_expect_success 'submodule add' '
+       echo "refs/heads/master" >expect &&
+       >empty &&
+
        (
                cd addtest &&
                git submodule add "$submodurl" submod &&
                git submodule init
+       ) &&
+
+       rm -f heads head untracked &&
+       inspect addtest/submod ../.. &&
+       test_cmp expect heads &&
+       test_cmp expect head &&
+       test_cmp empty untracked
+'
+
+test_expect_success 'submodule add to .gitignored path fails' '
+       (
+               cd addtest-ignore &&
+               cat <<-\EOF >expect &&
+               The following path is ignored by one of your .gitignore files:
+               submod
+               Use -f if you really want to add it.
+               EOF
+               # Does not use test_commit due to the ignore
+               echo "*" > .gitignore &&
+               git add --force .gitignore &&
+               git commit -m"Ignore everything" &&
+               ! git submodule add "$submodurl" submod >actual 2>&1 &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success 'submodule add to .gitignored path with --force' '
+       (
+               cd addtest-ignore &&
+               git submodule add --force "$submodurl" submod
        )
 '
 
 test_expect_success 'submodule add --branch' '
+       echo "refs/heads/initial" >expect-head &&
+       cat <<-\EOF >expect-heads &&
+       refs/heads/initial
+       refs/heads/master
+       EOF
+       >empty &&
+
        (
                cd addtest &&
                git submodule add -b initial "$submodurl" submod-branch &&
-               git submodule init &&
-               cd submod-branch &&
-               git branch | grep initial
-       )
+               git submodule init
+       ) &&
+
+       rm -f heads head untracked &&
+       inspect addtest/submod-branch ../.. &&
+       test_cmp expect-heads heads &&
+       test_cmp expect-head head &&
+       test_cmp empty untracked
 '
 
 test_expect_success 'submodule add with ./ in path' '
+       echo "refs/heads/master" >expect &&
+       >empty &&
+
        (
                cd addtest &&
                git submodule add "$submodurl" ././dotsubmod/./frotz/./ &&
                git submodule init
-       )
+       ) &&
+
+       rm -f heads head untracked &&
+       inspect addtest/dotsubmod/frotz ../../.. &&
+       test_cmp expect heads &&
+       test_cmp expect head &&
+       test_cmp empty untracked
 '
 
 test_expect_success 'submodule add with // in path' '
+       echo "refs/heads/master" >expect &&
+       >empty &&
+
        (
                cd addtest &&
                git submodule add "$submodurl" slashslashsubmod///frotz// &&
                git submodule init
-       )
+       ) &&
+
+       rm -f heads head untracked &&
+       inspect addtest/slashslashsubmod/frotz ../../.. &&
+       test_cmp expect heads &&
+       test_cmp expect head &&
+       test_cmp empty untracked
 '
 
 test_expect_success 'submodule add with /.. in path' '
+       echo "refs/heads/master" >expect &&
+       >empty &&
+
        (
                cd addtest &&
                git submodule add "$submodurl" dotdotsubmod/../realsubmod/frotz/.. &&
                git submodule init
-       )
+       ) &&
+
+       rm -f heads head untracked &&
+       inspect addtest/realsubmod ../.. &&
+       test_cmp expect heads &&
+       test_cmp expect head &&
+       test_cmp empty untracked
 '
 
 test_expect_success 'submodule add with ./, /.. and // in path' '
+       echo "refs/heads/master" >expect &&
+       >empty &&
+
        (
                cd addtest &&
                git submodule add "$submodurl" dot/dotslashsubmod/./../..////realsubmod2/a/b/c/d/../../../../frotz//.. &&
                git submodule init
-       )
+       ) &&
+
+       rm -f heads head untracked &&
+       inspect addtest/realsubmod2 ../.. &&
+       test_cmp expect heads &&
+       test_cmp expect head &&
+       test_cmp empty untracked
+'
+
+test_expect_success 'setup - add an example entry to .gitmodules' '
+       GIT_CONFIG=.gitmodules \
+       git config submodule.example.url git://example.com/init.git
 '
 
 test_expect_success 'status should fail for unmapped paths' '
-       if git submodule status
-       then
-               echo "[OOPS] submodule status succeeded"
-               false
-       elif ! GIT_CONFIG=.gitmodules git config submodule.example.path init
-       then
-               echo "[OOPS] git config failed to update .gitmodules"
-               false
-       fi
+       test_must_fail git submodule status
+'
+
+test_expect_success 'setup - map path in .gitmodules' '
+       cat <<\EOF >expect &&
+[submodule "example"]
+       url = git://example.com/init.git
+       path = init
+EOF
+
+       GIT_CONFIG=.gitmodules git config submodule.example.path init &&
+
+       test_cmp expect .gitmodules
 '
 
 test_expect_success 'status should only print one line' '
-       lines=$(git submodule status | wc -l) &&
-       test $lines = 1
+       git submodule status >lines &&
+       test $(wc -l <lines) = 1
+'
+
+test_expect_success 'setup - fetch commit name from submodule' '
+       rev1=$(cd .subrepo && git rev-parse HEAD) &&
+       printf "rev1: %s\n" "$rev1" &&
+       test -n "$rev1"
 '
 
 test_expect_success 'status should initially be "missing"' '
-       git submodule status | grep "^-$rev1"
+       git submodule status >lines &&
+       grep "^-$rev1" lines
 '
 
 test_expect_success 'init should register submodule url in .git/config' '
+       echo git://example.com/init.git >expect &&
+
        git submodule init &&
-       url=$(git config submodule.example.url) &&
-       if test "$url" != "git://example.com/init.git"
-       then
-               echo "[OOPS] init succeeded but submodule url is wrong"
-               false
-       elif test_must_fail git config submodule.example.url ./.subrepo
-       then
-               echo "[OOPS] init succeeded but update of url failed"
-               false
-       fi
+       git config submodule.example.url >url &&
+       git config submodule.example.url ./.subrepo &&
+
+       test_cmp expect url
 '
 
 test_expect_success 'update should fail when path is used by a file' '
+       echo hello >expect &&
+
        echo "hello" >init &&
-       if git submodule update
-       then
-               echo "[OOPS] update should have failed"
-               false
-       elif test "$(cat init)" != "hello"
-       then
-               echo "[OOPS] update failed but init file was molested"
-               false
-       else
-               rm init
-       fi
+       test_must_fail git submodule update &&
+
+       test_cmp expect init
 '
 
 test_expect_success 'update should fail when path is used by a nonempty directory' '
+       echo hello >expect &&
+
+       rm -fr init &&
        mkdir init &&
        echo "hello" >init/a &&
-       if git submodule update
-       then
-               echo "[OOPS] update should have failed"
-               false
-       elif test "$(cat init/a)" != "hello"
-       then
-               echo "[OOPS] update failed but init/a was molested"
-               false
-       else
-               rm init/a
-       fi
+
+       test_must_fail git submodule update &&
+
+       test_cmp expect init/a
 '
 
 test_expect_success 'update should work when path is an empty dir' '
-       rm -rf init &&
+       rm -fr init &&
+       rm -f head-sha1 &&
+       echo "$rev1" >expect &&
+
        mkdir init &&
        git submodule update &&
-       head=$(cd init && git rev-parse HEAD) &&
-       if test -z "$head"
-       then
-               echo "[OOPS] Failed to obtain submodule head"
-               false
-       elif test "$head" != "$rev1"
-       then
-               echo "[OOPS] Submodule head is $head but should have been $rev1"
-               false
-       fi
+
+       inspect init &&
+       test_cmp expect head-sha1
 '
 
 test_expect_success 'status should be "up-to-date" after update' '
-       git submodule status | grep "^ $rev1"
+       git submodule status >list &&
+       grep "^ $rev1" list
 '
 
 test_expect_success 'status should be "modified" after submodule commit' '
-       cd init &&
-       echo b >b &&
-       git add b &&
-       git commit -m "submodule commit 2" &&
-       rev2=$(git rev-parse HEAD) &&
-       cd .. &&
-       if test -z "$rev2"
-       then
-               echo "[OOPS] submodule git rev-parse returned nothing"
-               false
-       fi &&
-       git submodule status | grep "^+$rev2"
+       (
+               cd init &&
+               echo b >b &&
+               git add b &&
+               git commit -m "submodule commit 2"
+       ) &&
+
+       rev2=$(cd init && git rev-parse HEAD) &&
+       test -n "$rev2" &&
+       git submodule status >list &&
+
+       grep "^+$rev2" list
 '
 
 test_expect_success 'the --cached sha1 should be rev1' '
-       git submodule --cached status | grep "^+$rev1"
+       git submodule --cached status >list &&
+       grep "^+$rev1" list
 '
 
 test_expect_success 'git diff should report the SHA1 of the new submodule commit' '
-       git diff | grep "^+Subproject commit $rev2"
+       git diff >diff &&
+       grep "^+Subproject commit $rev2" diff
 '
 
 test_expect_success 'update should checkout rev1' '
+       rm -f head-sha1 &&
+       echo "$rev1" >expect &&
+
        git submodule update init &&
-       head=$(cd init && git rev-parse HEAD) &&
-       if test -z "$head"
-       then
-               echo "[OOPS] submodule git rev-parse returned nothing"
-               false
-       elif test "$head" != "$rev1"
-       then
-               echo "[OOPS] init did not checkout correct head"
-               false
-       fi
+       inspect init &&
+
+       test_cmp expect head-sha1
 '
 
 test_expect_success 'status should be "up-to-date" after update' '
-       git submodule status | grep "^ $rev1"
+       git submodule status >list &&
+       grep "^ $rev1" list
 '
 
 test_expect_success 'checkout superproject with subproject already present' '
@@ -239,6 +330,8 @@ test_expect_success 'checkout superproject with subproject already present' '
 '
 
 test_expect_success 'apply submodule diff' '
+       >empty &&
+
        git branch second &&
        (
                cd init &&
@@ -251,21 +344,24 @@ test_expect_success 'apply submodule diff' '
        git format-patch -1 --stdout >P.diff &&
        git checkout second &&
        git apply --index P.diff &&
-       D=$(git diff --cached master) &&
-       test -z "$D"
+
+       git diff --cached master >staged &&
+       test_cmp empty staged
 '
 
 test_expect_success 'update --init' '
-
        mv init init2 &&
        git config -f .gitmodules submodule.example.url "$(pwd)/init2" &&
-       git config --remove-section submodule.example
+       git config --remove-section submodule.example &&
+       test_must_fail git config submodule.example.url &&
+
        git submodule update init > update.out &&
+       cat update.out &&
        grep "not initialized" update.out &&
-       test ! -d init/.git &&
+       ! test -d init/.git &&
+
        git submodule update --init init &&
        test -d init/.git
-
 '
 
 test_expect_success 'do not add files from a submodule' '
@@ -299,6 +395,15 @@ test_expect_success 'ls-files gracefully handles trailing slash' '
 
 '
 
+test_expect_success 'moving to a commit without submodule does not leave empty dir' '
+       rm -rf init &&
+       mkdir init &&
+       git reset --hard &&
+       git checkout initial &&
+       test ! -d init &&
+       git checkout second
+'
+
 test_expect_success 'submodule <invalid-path> warns' '
 
        git submodule no-such-submodule 2> output.err &&
@@ -306,4 +411,21 @@ test_expect_success 'submodule <invalid-path> warns' '
 
 '
 
+test_expect_success 'add submodules without specifying an explicit path' '
+       mkdir repo &&
+       (
+               cd repo &&
+               git init &&
+               echo r >r &&
+               git add r &&
+               git commit -m "repo commit 1"
+       ) &&
+       git clone --bare repo/ bare.git &&
+       cd addtest &&
+       git submodule add "$submodurl/repo" &&
+       git config -f .gitmodules submodule.repo.path repo &&
+       git submodule add "$submodurl/bare.git" &&
+       git config -f .gitmodules submodule.bare.path bare
+'
+
 test_done
index 61498293b99e1cbbb4bfb4de45b6d14488aaddd0..294584452bbc3226e631ce0b81c546b04cd8d992 100755 (executable)
@@ -56,11 +56,21 @@ test_expect_success 'modified submodule(forward)' "
 EOF
 "
 
+test_expect_success 'modified submodule(forward), --files' "
+       git submodule summary --files >actual &&
+       diff actual - <<-EOF
+* sm1 $head1...$head2 (1):
+  > Add foo3
+
+EOF
+"
+
 commit_file sm1 &&
-cd sm1 &&
-git reset --hard HEAD~2 >/dev/null &&
-head3=$(git rev-parse --verify HEAD | cut -c1-7) &&
-cd ..
+head3=$(
+       cd sm1 &&
+       git reset --hard HEAD~2 >/dev/null &&
+       git rev-parse --verify HEAD | cut -c1-7
+)
 
 test_expect_success 'modified submodule(backward)' "
     git submodule summary >actual &&
@@ -114,6 +124,15 @@ test_expect_success 'typechanged submodule(submodule->blob), --cached' "
 EOF
 "
 
+test_expect_success 'typechanged submodule(submodule->blob), --files' "
+    git submodule summary --files >actual &&
+    diff actual - <<-EOF
+* sm1 $head5(blob)->$head4(submodule) (3):
+  > Add foo5
+
+EOF
+"
+
 rm -rf sm1 &&
 git checkout-index sm1
 test_expect_success 'typechanged submodule(submodule->blob)' "
@@ -195,7 +214,7 @@ EOF
 test_expect_success '--for-status' "
     git submodule summary --for-status HEAD^ >actual &&
     test_cmp actual - <<EOF
-# Modified submodules:
+# Submodule changes to be committed:
 #
 # * sm1 $head6...0000000:
 #
@@ -205,4 +224,15 @@ test_expect_success '--for-status' "
 EOF
 "
 
+test_expect_success 'fail when using --files together with --cached' "
+    test_must_fail git submodule summary --files --cached
+"
+
+test_expect_success 'should not fail in an empty repo' "
+    git init xyzzy &&
+    cd xyzzy &&
+    git submodule summary >output 2>&1 &&
+    test_cmp output /dev/null
+"
+
 test_done
index 7538756487d9b9483143b338104cd6af13397a91..02522f9627eea8166b2d4709ef3519b23c8dd3d5 100755 (executable)
@@ -14,7 +14,7 @@ test_expect_success setup '
        echo file > file &&
        git add file &&
        test_tick &&
-       git commit -m upstream
+       git commit -m upstream &&
        git clone . super &&
        git clone super submodule &&
        (cd super &&
@@ -42,7 +42,7 @@ test_expect_success 'change submodule url' '
        ) &&
        mv submodule moved-submodule &&
        (cd super &&
-        git config -f .gitmodules submodule.submodule.url ../moved-submodule
+        git config -f .gitmodules submodule.submodule.url ../moved-submodule &&
         test_tick &&
         git commit -a -m moved-submodule
        )
@@ -58,6 +58,9 @@ test_expect_success '"git submodule sync" should update submodule URLs' '
        (cd super-clone/submodule &&
         git checkout master &&
         git pull
+       ) &&
+       (cd super-clone &&
+        test -d "$(git config submodule.submodule.url)"
        )
 '
 
index 9a21f783d3a5fedaa56df26919ab8e0456872e90..7e2e258950772c91dfc04aff72ed74e95b5df884 100755 (executable)
@@ -45,7 +45,7 @@ test_expect_success setup '
         git commit -m sub-b) &&
        git add sub &&
        test_tick &&
-       git commit -m b
+       git commit -m b &&
 
        git checkout -b c a &&
        git merge -s ours b &&
@@ -54,21 +54,132 @@ test_expect_success setup '
        git merge -s ours a
 '
 
-test_expect_success 'merging with modify/modify conflict' '
+# History setup
+#
+#      b
+#    /   \
+#   a     d
+#    \   /
+#      c
+#
+# a in the main repository records to sub-a in the submodule and
+# analogous b and c. d should be automatically found by merging c into
+# b in the main repository.
+test_expect_success 'setup for merge search' '
+       mkdir merge-search &&
+       (cd merge-search &&
+       git init &&
+       mkdir sub &&
+       (cd sub &&
+        git init &&
+        echo "file-a" > file-a &&
+        git add file-a &&
+        git commit -m "sub-a" &&
+        git branch sub-a) &&
+       git add sub &&
+       git commit -m "a" &&
+       git branch a &&
+
+       git checkout -b b &&
+       (cd sub &&
+        git checkout -b sub-b &&
+        echo "file-b" > file-b &&
+        git add file-b &&
+        git commit -m "sub-b") &&
+       git commit -a -m "b" &&
+
+       git checkout -b c a &&
+       (cd sub &&
+        git checkout -b sub-c sub-a &&
+        echo "file-c" > file-c &&
+        git add file-c &&
+        git commit -m "sub-c") &&
+       git commit -a -m "c" &&
 
-       git checkout -b test1 a &&
-       test_must_fail git merge b &&
-       test -f .git/MERGE_MSG &&
-       git diff &&
-       test -n "$(git ls-files -u)"
+       git checkout -b d a &&
+       (cd sub &&
+        git checkout -b sub-d sub-b &&
+        git merge sub-c) &&
+       git commit -a -m "d" &&
+       git branch test b)
 '
 
-test_expect_success 'merging with a modify/modify conflict between merge bases' '
+test_expect_success 'merge with one side as a fast-forward of the other' '
+       (cd merge-search &&
+        git checkout -b test-forward b &&
+        git merge d &&
+        git ls-tree test-forward sub | cut -f1 | cut -f3 -d" " > actual &&
+        (cd sub &&
+         git rev-parse sub-d > ../expect) &&
+        test_cmp actual expect)
+'
 
+test_expect_success 'merging should conflict for non fast-forward' '
+       (cd merge-search &&
+        git checkout -b test-nonforward b &&
+        (cd sub &&
+         git rev-parse sub-d > ../expect) &&
+        test_must_fail git merge c 2> actual  &&
+        grep $(cat expect) actual > /dev/null &&
+        git reset --hard)
+'
+
+test_expect_success 'merging should fail for ambiguous common parent' '
+       (cd merge-search &&
+       git checkout -b test-ambiguous b &&
+       (cd sub &&
+        git checkout -b ambiguous sub-b &&
+        git merge sub-c &&
+        git rev-parse sub-d > ../expect1 &&
+        git rev-parse ambiguous > ../expect2) &&
+       test_must_fail git merge c 2> actual &&
+       grep $(cat expect1) actual > /dev/null &&
+       grep $(cat expect2) actual > /dev/null &&
+       git reset --hard)
+'
+
+# in a situation like this
+#
+# submodule tree:
+#
+#    sub-a --- sub-b --- sub-d
+#
+# main tree:
+#
+#    e (sub-a)
+#   /
+#  bb (sub-b)
+#   \
+#    f (sub-d)
+#
+# A merge between e and f should fail because one of the submodule
+# commits (sub-a) does not descend from the submodule merge-base (sub-b).
+#
+test_expect_success 'merging should fail for changes that are backwards' '
+       (cd merge-search &&
+       git checkout -b bb a &&
+       (cd sub &&
+        git checkout sub-b) &&
+       git commit -a -m "bb" &&
+
+       git checkout -b e bb &&
+       (cd sub &&
+        git checkout sub-a) &&
+       git commit -a -m "e" &&
+
+       git checkout -b f bb &&
+       (cd sub &&
+        git checkout sub-d) &&
+       git commit -a -m "f" &&
+
+       git checkout -b test-backward e &&
+       test_must_fail git merge f)
+'
+
+test_expect_success 'merging with a modify/modify conflict between merge bases' '
        git reset --hard HEAD &&
        git checkout -b test2 c &&
        git merge d
-
 '
 
 test_done
diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh
new file mode 100755 (executable)
index 0000000..bfb4975
--- /dev/null
@@ -0,0 +1,206 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Red Hat, Inc.
+#
+
+test_description='Test updating submodules
+
+This test verifies that "git submodule update" detaches the HEAD of the
+submodule and "git submodule update --rebase/--merge" does not detach the HEAD.
+'
+
+. ./test-lib.sh
+
+
+compare_head()
+{
+    sha_master=`git rev-list --max-count=1 master`
+    sha_head=`git rev-list --max-count=1 HEAD`
+
+    test "$sha_master" = "$sha_head"
+}
+
+
+test_expect_success 'setup a submodule tree' '
+       echo file > file &&
+       git add file &&
+       test_tick &&
+       git commit -m upstream &&
+       git clone . super &&
+       git clone super submodule &&
+       git clone super rebasing &&
+       git clone super merging &&
+       (cd super &&
+        git submodule add ../submodule submodule &&
+        test_tick &&
+        git commit -m "submodule" &&
+        git submodule init submodule
+       ) &&
+       (cd submodule &&
+       echo "line2" > file &&
+       git add file &&
+       git commit -m "Commit 2"
+       ) &&
+       (cd super &&
+        (cd submodule &&
+         git pull --rebase origin
+        ) &&
+        git add submodule &&
+        git commit -m "submodule update"
+       ) &&
+       (cd super &&
+        git submodule add ../rebasing rebasing &&
+        test_tick &&
+        git commit -m "rebasing"
+       ) &&
+       (cd super &&
+        git submodule add ../merging merging &&
+        test_tick &&
+        git commit -m "rebasing"
+       )
+'
+
+test_expect_success 'submodule update detaching the HEAD ' '
+       (cd super/submodule &&
+        git reset --hard HEAD~1
+       ) &&
+       (cd super &&
+        (cd submodule &&
+         compare_head
+        ) &&
+        git submodule update submodule &&
+        cd submodule &&
+        ! compare_head
+       )
+'
+
+test_expect_success 'submodule update --rebase staying on master' '
+       (cd super/submodule &&
+         git checkout master
+       ) &&
+       (cd super &&
+        (cd submodule &&
+         compare_head
+        ) &&
+        git submodule update --rebase submodule &&
+        cd submodule &&
+        compare_head
+       )
+'
+
+test_expect_success 'submodule update --merge staying on master' '
+       (cd super/submodule &&
+         git reset --hard HEAD~1
+       ) &&
+       (cd super &&
+        (cd submodule &&
+         compare_head
+        ) &&
+        git submodule update --merge submodule &&
+        cd submodule &&
+        compare_head
+       )
+'
+
+test_expect_success 'submodule update - rebase in .git/config' '
+       (cd super &&
+        git config submodule.submodule.update rebase
+       ) &&
+       (cd super/submodule &&
+         git reset --hard HEAD~1
+       ) &&
+       (cd super &&
+        (cd submodule &&
+         compare_head
+        ) &&
+        git submodule update submodule &&
+        cd submodule &&
+        compare_head
+       )
+'
+
+test_expect_success 'submodule update - checkout in .git/config but --rebase given' '
+       (cd super &&
+        git config submodule.submodule.update checkout
+       ) &&
+       (cd super/submodule &&
+         git reset --hard HEAD~1
+       ) &&
+       (cd super &&
+        (cd submodule &&
+         compare_head
+        ) &&
+        git submodule update --rebase submodule &&
+        cd submodule &&
+        compare_head
+       )
+'
+
+test_expect_success 'submodule update - merge in .git/config' '
+       (cd super &&
+        git config submodule.submodule.update merge
+       ) &&
+       (cd super/submodule &&
+         git reset --hard HEAD~1
+       ) &&
+       (cd super &&
+        (cd submodule &&
+         compare_head
+        ) &&
+        git submodule update submodule &&
+        cd submodule &&
+        compare_head
+       )
+'
+
+test_expect_success 'submodule update - checkout in .git/config but --merge given' '
+       (cd super &&
+        git config submodule.submodule.update checkout
+       ) &&
+       (cd super/submodule &&
+         git reset --hard HEAD~1
+       ) &&
+       (cd super &&
+        (cd submodule &&
+         compare_head
+        ) &&
+        git submodule update --merge submodule &&
+        cd submodule &&
+        compare_head
+       )
+'
+
+test_expect_success 'submodule update - checkout in .git/config' '
+       (cd super &&
+        git config submodule.submodule.update checkout
+       ) &&
+       (cd super/submodule &&
+         git reset --hard HEAD^
+       ) &&
+       (cd super &&
+        (cd submodule &&
+         compare_head
+        ) &&
+        git submodule update submodule &&
+        cd submodule &&
+        ! compare_head
+       )
+'
+
+test_expect_success 'submodule init picks up rebase' '
+       (cd super &&
+        git config -f .gitmodules submodule.rebasing.update rebase &&
+        git submodule init rebasing &&
+        test "rebase" = "$(git config submodule.rebasing.update)"
+       )
+'
+
+test_expect_success 'submodule init picks up merge' '
+       (cd super &&
+        git config -f .gitmodules submodule.merging.update merge &&
+        git submodule init merging &&
+        test "merge" = "$(git config submodule.merging.update)"
+       )
+'
+
+test_done
diff --git a/t/t7407-submodule-foreach.sh b/t/t7407-submodule-foreach.sh
new file mode 100755 (executable)
index 0000000..905a8ba
--- /dev/null
@@ -0,0 +1,241 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Johan Herland
+#
+
+test_description='Test "git submodule foreach"
+
+This test verifies that "git submodule foreach" correctly visits all submodules
+that are currently checked out.
+'
+
+. ./test-lib.sh
+
+
+test_expect_success 'setup a submodule tree' '
+       echo file > file &&
+       git add file &&
+       test_tick &&
+       git commit -m upstream &&
+       git clone . super &&
+       git clone super submodule &&
+       (
+               cd super &&
+               git submodule add ../submodule sub1 &&
+               git submodule add ../submodule sub2 &&
+               git submodule add ../submodule sub3 &&
+               git config -f .gitmodules --rename-section \
+                       submodule.sub1 submodule.foo1 &&
+               git config -f .gitmodules --rename-section \
+                       submodule.sub2 submodule.foo2 &&
+               git config -f .gitmodules --rename-section \
+                       submodule.sub3 submodule.foo3 &&
+               git add .gitmodules &&
+               test_tick &&
+               git commit -m "submodules" &&
+               git submodule init sub1 &&
+               git submodule init sub2 &&
+               git submodule init sub3
+       ) &&
+       (
+               cd submodule &&
+               echo different > file &&
+               git add file &&
+               test_tick &&
+               git commit -m "different"
+       ) &&
+       (
+               cd super &&
+               (
+                       cd sub3 &&
+                       git pull
+               ) &&
+               git add sub3 &&
+               test_tick &&
+               git commit -m "update sub3"
+       )
+'
+
+sub1sha1=$(cd super/sub1 && git rev-parse HEAD)
+sub3sha1=$(cd super/sub3 && git rev-parse HEAD)
+
+pwd=$(pwd)
+
+cat > expect <<EOF
+Entering 'sub1'
+$pwd/clone-foo1-sub1-$sub1sha1
+Entering 'sub3'
+$pwd/clone-foo3-sub3-$sub3sha1
+EOF
+
+test_expect_success 'test basic "submodule foreach" usage' '
+       git clone super clone &&
+       (
+               cd clone &&
+               git submodule update --init -- sub1 sub3 &&
+               git submodule foreach "echo \$toplevel-\$name-\$path-\$sha1" > ../actual &&
+               git config foo.bar zar &&
+               git submodule foreach "git config --file \"\$toplevel/.git/config\" foo.bar"
+       ) &&
+       test_cmp expect actual
+'
+
+test_expect_success 'setup nested submodules' '
+       git clone submodule nested1 &&
+       git clone submodule nested2 &&
+       git clone submodule nested3 &&
+       (
+               cd nested3 &&
+               git submodule add ../submodule submodule &&
+               test_tick &&
+               git commit -m "submodule" &&
+               git submodule init submodule
+       ) &&
+       (
+               cd nested2 &&
+               git submodule add ../nested3 nested3 &&
+               test_tick &&
+               git commit -m "nested3" &&
+               git submodule init nested3
+       ) &&
+       (
+               cd nested1 &&
+               git submodule add ../nested2 nested2 &&
+               test_tick &&
+               git commit -m "nested2" &&
+               git submodule init nested2
+       ) &&
+       (
+               cd super &&
+               git submodule add ../nested1 nested1 &&
+               test_tick &&
+               git commit -m "nested1" &&
+               git submodule init nested1
+       )
+'
+
+test_expect_success 'use "submodule foreach" to checkout 2nd level submodule' '
+       git clone super clone2 &&
+       (
+               cd clone2 &&
+               test ! -d sub1/.git &&
+               test ! -d sub2/.git &&
+               test ! -d sub3/.git &&
+               test ! -d nested1/.git &&
+               git submodule update --init &&
+               test -d sub1/.git &&
+               test -d sub2/.git &&
+               test -d sub3/.git &&
+               test -d nested1/.git &&
+               test ! -d nested1/nested2/.git &&
+               git submodule foreach "git submodule update --init" &&
+               test -d nested1/nested2/.git &&
+               test ! -d nested1/nested2/nested3/.git
+       )
+'
+
+test_expect_success 'use "foreach --recursive" to checkout all submodules' '
+       (
+               cd clone2 &&
+               git submodule foreach --recursive "git submodule update --init" &&
+               test -d nested1/nested2/nested3/.git &&
+               test -d nested1/nested2/nested3/submodule/.git
+       )
+'
+
+cat > expect <<EOF
+Entering 'nested1'
+Entering 'nested1/nested2'
+Entering 'nested1/nested2/nested3'
+Entering 'nested1/nested2/nested3/submodule'
+Entering 'sub1'
+Entering 'sub2'
+Entering 'sub3'
+EOF
+
+test_expect_success 'test messages from "foreach --recursive"' '
+       (
+               cd clone2 &&
+               git submodule foreach --recursive "true" > ../actual
+       ) &&
+       test_cmp expect actual
+'
+
+cat > expect <<EOF
+nested1-nested1
+nested2-nested2
+nested3-nested3
+submodule-submodule
+foo1-sub1
+foo2-sub2
+foo3-sub3
+EOF
+
+test_expect_success 'test "foreach --quiet --recursive"' '
+       (
+               cd clone2 &&
+               git submodule foreach -q --recursive "echo \$name-\$path" > ../actual
+       ) &&
+       test_cmp expect actual
+'
+
+test_expect_success 'use "update --recursive" to checkout all submodules' '
+       git clone super clone3 &&
+       (
+               cd clone3 &&
+               test ! -d sub1/.git &&
+               test ! -d sub2/.git &&
+               test ! -d sub3/.git &&
+               test ! -d nested1/.git &&
+               git submodule update --init --recursive &&
+               test -d sub1/.git &&
+               test -d sub2/.git &&
+               test -d sub3/.git &&
+               test -d nested1/.git &&
+               test -d nested1/nested2/.git &&
+               test -d nested1/nested2/nested3/.git &&
+               test -d nested1/nested2/nested3/submodule/.git
+       )
+'
+
+nested1sha1=$(cd clone3/nested1 && git rev-parse HEAD)
+nested2sha1=$(cd clone3/nested1/nested2 && git rev-parse HEAD)
+nested3sha1=$(cd clone3/nested1/nested2/nested3 && git rev-parse HEAD)
+submodulesha1=$(cd clone3/nested1/nested2/nested3/submodule && git rev-parse HEAD)
+sub1sha1=$(cd clone3/sub1 && git rev-parse HEAD)
+sub2sha1=$(cd clone3/sub2 && git rev-parse HEAD)
+sub3sha1=$(cd clone3/sub3 && git rev-parse HEAD)
+sub1sha1_short=$(cd clone3/sub1 && git rev-parse --short HEAD)
+sub2sha1_short=$(cd clone3/sub2 && git rev-parse --short HEAD)
+
+cat > expect <<EOF
+ $nested1sha1 nested1 (heads/master)
+ $nested2sha1 nested1/nested2 (heads/master)
+ $nested3sha1 nested1/nested2/nested3 (heads/master)
+ $submodulesha1 nested1/nested2/nested3/submodule (heads/master)
+ $sub1sha1 sub1 ($sub1sha1_short)
+ $sub2sha1 sub2 ($sub2sha1_short)
+ $sub3sha1 sub3 (heads/master)
+EOF
+
+test_expect_success 'test "status --recursive"' '
+       (
+               cd clone3 &&
+               git submodule status --recursive > ../actual
+       ) &&
+       test_cmp expect actual
+'
+
+test_expect_success 'use "git clone --recursive" to checkout all submodules' '
+       git clone --recursive super clone4 &&
+       test -d clone4/.git &&
+       test -d clone4/sub1/.git &&
+       test -d clone4/sub2/.git &&
+       test -d clone4/sub3/.git &&
+       test -d clone4/nested1/.git &&
+       test -d clone4/nested1/nested2/.git &&
+       test -d clone4/nested1/nested2/nested3/.git &&
+       test -d clone4/nested1/nested2/nested3/submodule/.git
+'
+
+test_done
diff --git a/t/t7408-submodule-reference.sh b/t/t7408-submodule-reference.sh
new file mode 100755 (executable)
index 0000000..cc16d3f
--- /dev/null
@@ -0,0 +1,81 @@
+#!/bin/sh
+#
+# Copyright (c) 2009, Red Hat Inc, Author: Michael S. Tsirkin (mst@redhat.com)
+#
+
+test_description='test clone --reference'
+. ./test-lib.sh
+
+base_dir=`pwd`
+
+U=$base_dir/UPLOAD_LOG
+
+test_expect_success 'preparing first repository' \
+'test_create_repo A && cd A &&
+echo first > file1 &&
+git add file1 &&
+git commit -m A-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 B-addition &&
+git repack -a -d &&
+git prune'
+
+cd "$base_dir"
+
+test_expect_success 'preparing supermodule' \
+'test_create_repo super && cd super &&
+echo file > file &&
+git add file &&
+git commit -m B-super-initial'
+
+cd "$base_dir"
+
+test_expect_success 'submodule add --reference' \
+'cd super && git submodule add --reference ../B "file://$base_dir/A" sub &&
+git commit -m B-super-added'
+
+cd "$base_dir"
+
+test_expect_success 'after add: existence of info/alternates' \
+'test `wc -l <super/sub/.git/objects/info/alternates` = 1'
+
+cd "$base_dir"
+
+test_expect_success 'that reference gets used with add' \
+'cd super/sub &&
+echo "0 objects, 0 kilobytes" > expected &&
+git count-objects > current &&
+diff expected current'
+
+cd "$base_dir"
+
+test_expect_success 'cloning supermodule' \
+'git clone super super-clone'
+
+cd "$base_dir"
+
+test_expect_success 'update with reference' \
+'cd super-clone && git submodule update --init --reference ../B'
+
+cd "$base_dir"
+
+test_expect_success 'after update: existence of info/alternates' \
+'test `wc -l <super-clone/sub/.git/objects/info/alternates` = 1'
+
+cd "$base_dir"
+
+test_expect_success 'that reference gets used with update' \
+'cd super-clone/sub &&
+echo "0 objects, 0 kilobytes" > expected &&
+git count-objects > current &&
+diff expected current'
+
+cd "$base_dir"
+
+test_done
index 5998baf27b5ad0fb32bf0fd42023d029a65d0e6b..aa9c577e9e306bce05cd0fe076c56017c1b97e41 100755 (executable)
@@ -150,7 +150,7 @@ EOF
 test_expect_success '--signoff' '
        echo "yet another content *narf*" >> foo &&
        echo "zort" | git commit -s -F - foo &&
-       git cat-file commit HEAD | sed "1,/^$/d" > output &&
+       git cat-file commit HEAD | sed "1,/^\$/d" > output &&
        test_cmp expect output
 '
 
@@ -183,4 +183,36 @@ test_expect_success 'commit message from stdin' '
        commit_msg_is "Log with foo word"
 '
 
+test_expect_success 'commit -F overrides -t' '
+       (
+               cd subdir &&
+               echo "-F log" > f.log &&
+               echo "-t template" > t.template &&
+               git commit --allow-empty -F f.log -t t.template
+       ) &&
+       commit_msg_is "-F log"
+'
+
+test_expect_success 'Commit without message is allowed with --allow-empty-message' '
+       echo "more content" >>foo &&
+       git add foo &&
+       >empty &&
+       git commit --allow-empty-message <empty &&
+       commit_msg_is ""
+'
+
+test_expect_success 'Commit without message is no-no without --allow-empty-message' '
+       echo "more content" >>foo &&
+       git add foo &&
+       >empty &&
+       test_must_fail git commit <empty
+'
+
+test_expect_success 'Commit a message with --allow-empty-message' '
+       echo "even more content" >>foo &&
+       git add foo &&
+       git commit --allow-empty-message -m"hello there" &&
+       commit_msg_is "hello there"
+'
+
 test_done
index e2ef53228ecc6162afdcce78109d426e154b7caa..8297cb4f1e6e2d903dfbf6fde825d2c787082e58 100755 (executable)
@@ -86,7 +86,7 @@ chmod 755 editor
 
 test_expect_success \
        "amend commit" \
-       "VISUAL=./editor git commit --amend"
+       "EDITOR=./editor git commit --amend"
 
 test_expect_success \
        "passing -m and -F" \
@@ -107,7 +107,7 @@ chmod 755 editor
 test_expect_success \
        "editing message from other commit" \
        "echo 'hula hula' >file && \
-        VISUAL=./editor git commit -c HEAD^ -a"
+        EDITOR=./editor git commit -c HEAD^ -a"
 
 test_expect_success \
        "message from stdin" \
@@ -117,7 +117,11 @@ test_expect_success \
 test_expect_success \
        "overriding author from command line" \
        "echo 'gak' >file && \
-        git commit -m 'author' --author 'Rubber Duck <rduck@convoy.org>' -a"
+        git commit -m 'author' --author 'Rubber Duck <rduck@convoy.org>' -a >output 2>&1"
+
+test_expect_success \
+       "commit --author output mentions author" \
+       "grep Rubber.Duck output"
 
 test_expect_success PERL \
        "interactive add" \
@@ -141,10 +145,10 @@ EOF
 test_expect_success \
        'editor not invoked if -F is given' '
         echo "moo" >file &&
-        VISUAL=./editor git commit -a -F msg &&
+        EDITOR=./editor git commit -a -F msg &&
         git show -s --pretty=format:"%s" | grep -q good &&
         echo "quack" >file &&
-        echo "Another good message." | VISUAL=./editor git commit -a -F - &&
+        echo "Another good message." | EDITOR=./editor git commit -a -F - &&
         git show -s --pretty=format:"%s" | grep -q good
         '
 # We could just check the head sha1, but checking each commit makes it
@@ -211,6 +215,21 @@ test_expect_success 'amend commit to fix author' '
 
 '
 
+test_expect_success 'amend commit to fix date' '
+
+       test_tick &&
+       newtick=$GIT_AUTHOR_DATE &&
+       git reset --hard &&
+       git cat-file -p HEAD |
+       sed -e "s/author.*/author $author $newtick/" \
+               -e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \
+               expected &&
+       git commit --amend --date="$newtick" &&
+       git cat-file -p HEAD > current &&
+       test_cmp expected current
+
+'
+
 test_expect_success 'sign off (1)' '
 
        echo 1 >positive &&
@@ -247,6 +266,47 @@ $existing" &&
 
 '
 
+test_expect_success 'signoff gap' '
+
+       echo 3 >positive &&
+       git add positive &&
+       alt="Alt-RFC-822-Header: Value" &&
+       git commit -s -m "welcome
+
+$alt" &&
+       git cat-file commit HEAD | sed -e "1,/^\$/d" > actual &&
+       (
+               echo welcome
+               echo
+               echo $alt
+               git var GIT_COMMITTER_IDENT |
+               sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /"
+       ) >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'signoff gap 2' '
+
+       echo 4 >positive &&
+       git add positive &&
+       alt="fixed: 34" &&
+       git commit -s -m "welcome
+
+We have now
+$alt" &&
+       git cat-file commit HEAD | sed -e "1,/^\$/d" > actual &&
+       (
+               echo welcome
+               echo
+               echo We have now
+               echo $alt
+               echo
+               git var GIT_COMMITTER_IDENT |
+               sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /"
+       ) >expected &&
+       test_cmp expected actual
+'
+
 test_expect_success 'multiple -m' '
 
        >negative &&
@@ -365,4 +425,16 @@ test_expect_success 'amend using the message from a commit named with tag' '
 
 '
 
+test_expect_success 'amend can copy notes' '
+
+       git config notes.rewrite.amend true &&
+       git config notes.rewriteRef "refs/notes/*" &&
+       test_commit foo &&
+       git notes add -m"a note" &&
+       test_tick &&
+       git commit --amend -m"new foo" &&
+       test "$(git notes show)" = "a note"
+
+'
+
 test_done
index 56cd866019dcc871e5dea684f1d879fcf2c1b884..ac2e187a5720d1ff947e58073dd6cc403ef40d5d 100755 (executable)
@@ -4,8 +4,76 @@ test_description='git commit porcelain-ish'
 
 . ./test-lib.sh
 
+# Arguments: [<prefix] [<commit message>] [<commit options>]
+check_summary_oneline() {
+       test_tick &&
+       git commit ${3+"$3"} -m "$2" | head -1 > act &&
+
+       # branch name
+       SUMMARY_PREFIX="$(git name-rev --name-only HEAD)" &&
+
+       # append the "special" prefix, like "root-commit", "detached HEAD"
+       if test -n "$1"
+       then
+               SUMMARY_PREFIX="$SUMMARY_PREFIX ($1)"
+       fi
+
+       # abbrev SHA-1
+       SUMMARY_POSTFIX="$(git log -1 --pretty='format:%h')"
+       echo "[$SUMMARY_PREFIX $SUMMARY_POSTFIX] $2" >exp &&
+
+       test_cmp exp act
+}
+
+test_expect_success 'output summary format' '
+
+       echo new >file1 &&
+       git add file1 &&
+       check_summary_oneline "root-commit" "initial" &&
+
+       echo change >>file1 &&
+       git add file1 &&
+       check_summary_oneline "" "a change"
+'
+
+test_expect_success 'output summary format for commit with an empty diff' '
+
+       check_summary_oneline "" "empty" "--allow-empty"
+'
+
+test_expect_success 'output summary format for merges' '
+
+       git checkout -b recursive-base &&
+       test_commit base file1 &&
+
+       git checkout -b recursive-a recursive-base &&
+       test_commit commit-a file1 &&
+
+       git checkout -b recursive-b recursive-base &&
+       test_commit commit-b file1 &&
+
+       # conflict
+       git checkout recursive-a &&
+       test_must_fail git merge recursive-b &&
+       # resolve the conflict
+       echo commit-a > file1 &&
+       git add file1 &&
+       check_summary_oneline "" "Merge"
+'
+
+output_tests_cleanup() {
+       # this is needed for "do not fire editor in the presence of conflicts"
+       git checkout master &&
+
+       # this is needed for the "partial removal" test to pass
+       git rm file1 &&
+       git commit -m "cleanup"
+}
+
 test_expect_success 'the basics' '
 
+       output_tests_cleanup &&
+
        echo doing partial >"commit is" &&
        mkdir not &&
        echo very much encouraged but we should >not/forbid &&
@@ -35,7 +103,7 @@ test_expect_success 'partial' '
 
 '
 
-test_expect_success 'partial modification in a subdirecotry' '
+test_expect_success 'partial modification in a subdirectory' '
 
        test_tick &&
        git commit -m "partial commit to subdirectory" not &&
@@ -258,4 +326,122 @@ test_expect_success 'Hand committing of a redundant merge removes dups' '
 
 '
 
+test_expect_success 'A single-liner subject with a token plus colon is not a footer' '
+
+       git reset --hard &&
+       git commit -s -m "hello: kitty" --allow-empty &&
+       git cat-file commit HEAD | sed -e "1,/^$/d" >actual &&
+       test $(wc -l <actual) = 3
+
+'
+
+cat >.git/FAKE_EDITOR <<EOF
+#!$SHELL_PATH
+mv "\$1" "\$1.orig"
+(
+       echo message
+       cat "\$1.orig"
+) >"\$1"
+EOF
+
+echo '## Custom template' >template
+
+clear_config () {
+       (
+               git config --unset-all "$1"
+               case $? in
+               0|5)    exit 0 ;;
+               *)      exit 1 ;;
+               esac
+       )
+}
+
+try_commit () {
+       git reset --hard &&
+       echo >>negative &&
+       GIT_EDITOR=.git/FAKE_EDITOR git commit -a $* $use_template &&
+       case "$use_template" in
+       '')
+               ! grep "^## Custom template" .git/COMMIT_EDITMSG ;;
+       *)
+               grep "^## Custom template" .git/COMMIT_EDITMSG ;;
+       esac
+}
+
+try_commit_status_combo () {
+
+       test_expect_success 'commit' '
+               clear_config commit.status &&
+               try_commit "" &&
+               grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+       '
+
+       test_expect_success 'commit' '
+               clear_config commit.status &&
+               try_commit "" &&
+               grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+       '
+
+       test_expect_success 'commit --status' '
+               clear_config commit.status &&
+               try_commit --status &&
+               grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+       '
+
+       test_expect_success 'commit --no-status' '
+               clear_config commit.status &&
+               try_commit --no-status
+               ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+       '
+
+       test_expect_success 'commit with commit.status = yes' '
+               clear_config commit.status &&
+               git config commit.status yes &&
+               try_commit "" &&
+               grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+       '
+
+       test_expect_success 'commit with commit.status = no' '
+               clear_config commit.status &&
+               git config commit.status no &&
+               try_commit "" &&
+               ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+       '
+
+       test_expect_success 'commit --status with commit.status = yes' '
+               clear_config commit.status &&
+               git config commit.status yes &&
+               try_commit --status &&
+               grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+       '
+
+       test_expect_success 'commit --no-status with commit.status = yes' '
+               clear_config commit.status &&
+               git config commit.status yes &&
+               try_commit --no-status &&
+               ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+       '
+
+       test_expect_success 'commit --status with commit.status = no' '
+               clear_config commit.status &&
+               git config commit.status no &&
+               try_commit --status &&
+               grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+       '
+
+       test_expect_success 'commit --no-status with commit.status = no' '
+               clear_config commit.status &&
+               git config commit.status no &&
+               try_commit --no-status &&
+               ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+       '
+
+}
+
+try_commit_status_combo
+
+use_template="-t template"
+
+try_commit_status_combo
+
 test_done
index d9a08aac56f8edf002a126cba83172abf5015034..3d4f85d74f6f378d76bde77e581273af010ba452 100755 (executable)
@@ -5,34 +5,186 @@ test_description='git status for submodule'
 . ./test-lib.sh
 
 test_expect_success 'setup' '
-       test_create_repo sub
-       cd sub &&
-       : >bar &&
-       git add bar &&
-       git commit -m " Add bar" &&
-       cd .. &&
-       git add sub &&
+       test_create_repo sub &&
+       (
+               cd sub &&
+               : >bar &&
+               git add bar &&
+               git commit -m " Add bar" &&
+               : >foo &&
+               git add foo &&
+               git commit -m " Add foo"
+       ) &&
+       echo output > .gitignore &&
+       git add sub .gitignore &&
        git commit -m "Add submodule sub"
 '
 
 test_expect_success 'status clean' '
-       git status |
-       grep "nothing to commit"
+       git status >output &&
+       grep "nothing to commit" output
 '
-test_expect_success 'status -a clean' '
-       git status -a |
-       grep "nothing to commit"
+
+test_expect_success 'commit --dry-run -a clean' '
+       test_must_fail git commit --dry-run -a >output &&
+       grep "nothing to commit" output
+'
+
+test_expect_success 'status with modified file in submodule' '
+       (cd sub && git reset --hard) &&
+       echo "changed" >sub/foo &&
+       git status >output &&
+       grep "modified:   sub (modified content)" output
+'
+
+test_expect_success 'status with modified file in submodule (porcelain)' '
+       (cd sub && git reset --hard) &&
+       echo "changed" >sub/foo &&
+       git status --porcelain >output &&
+       diff output - <<-\EOF
+        M sub
+       EOF
+'
+
+test_expect_success 'status with added file in submodule' '
+       (cd sub && git reset --hard && echo >foo && git add foo) &&
+       git status >output &&
+       grep "modified:   sub (modified content)" output
+'
+
+test_expect_success 'status with added file in submodule (porcelain)' '
+       (cd sub && git reset --hard && echo >foo && git add foo) &&
+       git status --porcelain >output &&
+       diff output - <<-\EOF
+        M sub
+       EOF
+'
+
+test_expect_success 'status with untracked file in submodule' '
+       (cd sub && git reset --hard) &&
+       echo "content" >sub/new-file &&
+       git status >output &&
+       grep "modified:   sub (untracked content)" output
+'
+
+test_expect_success 'status -uno with untracked file in submodule' '
+       git status -uno >output &&
+       grep "^nothing to commit" output
+'
+
+test_expect_success 'status with untracked file in submodule (porcelain)' '
+       git status --porcelain >output &&
+       diff output - <<-\EOF
+        M sub
+       EOF
+'
+
+test_expect_success 'status with added and untracked file in submodule' '
+       (cd sub && git reset --hard && echo >foo && git add foo) &&
+       echo "content" >sub/new-file &&
+       git status >output &&
+       grep "modified:   sub (modified content, untracked content)" output
+'
+
+test_expect_success 'status with added and untracked file in submodule (porcelain)' '
+       (cd sub && git reset --hard && echo >foo && git add foo) &&
+       echo "content" >sub/new-file &&
+       git status --porcelain >output &&
+       diff output - <<-\EOF
+        M sub
+       EOF
+'
+
+test_expect_success 'status with modified file in modified submodule' '
+       (cd sub && git reset --hard) &&
+       rm sub/new-file &&
+       (cd sub && echo "next change" >foo && git commit -m "next change" foo) &&
+       echo "changed" >sub/foo &&
+       git status >output &&
+       grep "modified:   sub (new commits, modified content)" output
 '
+
+test_expect_success 'status with modified file in modified submodule (porcelain)' '
+       (cd sub && git reset --hard) &&
+       echo "changed" >sub/foo &&
+       git status --porcelain >output &&
+       diff output - <<-\EOF
+        M sub
+       EOF
+'
+
+test_expect_success 'status with added file in modified submodule' '
+       (cd sub && git reset --hard && echo >foo && git add foo) &&
+       git status >output &&
+       grep "modified:   sub (new commits, modified content)" output
+'
+
+test_expect_success 'status with added file in modified submodule (porcelain)' '
+       (cd sub && git reset --hard && echo >foo && git add foo) &&
+       git status --porcelain >output &&
+       diff output - <<-\EOF
+        M sub
+       EOF
+'
+
+test_expect_success 'status with untracked file in modified submodule' '
+       (cd sub && git reset --hard) &&
+       echo "content" >sub/new-file &&
+       git status >output &&
+       grep "modified:   sub (new commits, untracked content)" output
+'
+
+test_expect_success 'status with untracked file in modified submodule (porcelain)' '
+       git status --porcelain >output &&
+       diff output - <<-\EOF
+        M sub
+       EOF
+'
+
+test_expect_success 'status with added and untracked file in modified submodule' '
+       (cd sub && git reset --hard && echo >foo && git add foo) &&
+       echo "content" >sub/new-file &&
+       git status >output &&
+       grep "modified:   sub (new commits, modified content, untracked content)" output
+'
+
+test_expect_success 'status with added and untracked file in modified submodule (porcelain)' '
+       (cd sub && git reset --hard && echo >foo && git add foo) &&
+       echo "content" >sub/new-file &&
+       git status --porcelain >output &&
+       diff output - <<-\EOF
+        M sub
+       EOF
+'
+
+test_expect_success 'setup .git file for sub' '
+       (cd sub &&
+        rm -f new-file
+        REAL="$(pwd)/../.real" &&
+        mv .git "$REAL"
+        echo "gitdir: $REAL" >.git) &&
+        echo .real >>.gitignore &&
+        git commit -m "added .real to .gitignore" .gitignore
+'
+
+test_expect_success 'status with added file in modified submodule with .git file' '
+       (cd sub && git reset --hard && echo >foo && git add foo) &&
+       git status >output &&
+       grep "modified:   sub (new commits, modified content)" output
+'
+
 test_expect_success 'rm submodule contents' '
        rm -rf sub/* sub/.git
 '
+
 test_expect_success 'status clean (empty submodule dir)' '
-       git status |
-       grep "nothing to commit"
+       git status >output &&
+       grep "nothing to commit" output
 '
+
 test_expect_success 'status -a clean (empty submodule dir)' '
-       git status -a |
-       grep "nothing to commit"
+       test_must_fail git commit --dry-run -a >output &&
+       grep "nothing to commit" output
 '
 
 test_done
index 93f875f50054225e0879843b6e29ac057ba6c020..c9300f3c8b246e3c81570532534748e510a2865f 100755 (executable)
@@ -8,26 +8,26 @@ test_description='git status'
 . ./test-lib.sh
 
 test_expect_success 'setup' '
-       : > tracked &&
-       : > modified &&
+       : >tracked &&
+       : >modified &&
        mkdir dir1 &&
-       : > dir1/tracked &&
-       : > dir1/modified &&
+       : >dir1/tracked &&
+       : >dir1/modified &&
        mkdir dir2 &&
-       : > dir1/tracked &&
-       : > dir1/modified &&
+       : >dir1/tracked &&
+       : >dir1/modified &&
        git add . &&
 
        git status >output &&
 
        test_tick &&
        git commit -m initial &&
-       : > untracked &&
-       : > dir1/untracked &&
-       : > dir2/untracked &&
-       echo 1 > dir1/modified &&
-       echo 2 > dir2/modified &&
-       echo 3 > dir2/added &&
+       : >untracked &&
+       : >dir1/untracked &&
+       : >dir2/untracked &&
+       echo 1 >dir1/modified &&
+       echo 2 >dir2/modified &&
+       echo 3 >dir2/added &&
        git add dir2/added
 '
 
@@ -37,7 +37,7 @@ test_expect_success 'status (1)' '
 
 '
 
-cat > expect << \EOF
+cat >expect <<\EOF
 # On branch master
 # Changes to be committed:
 #   (use "git reset HEAD <file>..." to unstage)
@@ -63,7 +63,72 @@ EOF
 
 test_expect_success 'status (2)' '
 
-       git status > output &&
+       git status >output &&
+       test_cmp expect output
+
+'
+
+cat >expect <<\EOF
+# On branch master
+# Changes to be committed:
+#      new file:   dir2/added
+#
+# Changed but not updated:
+#      modified:   dir1/modified
+#
+# Untracked files:
+#      dir1/untracked
+#      dir2/modified
+#      dir2/untracked
+#      expect
+#      output
+#      untracked
+EOF
+
+git config advice.statusHints false
+
+test_expect_success 'status (advice.statusHints false)' '
+
+       git status >output &&
+       test_cmp expect output
+
+'
+
+git config --unset advice.statusHints
+
+cat >expect <<\EOF
+ M dir1/modified
+A  dir2/added
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+
+test_expect_success 'status -s' '
+
+       git status -s >output &&
+       test_cmp expect output
+
+'
+
+cat >expect <<\EOF
+## master
+ M dir1/modified
+A  dir2/added
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+
+test_expect_success 'status -s -b' '
+
+       git status -s -b >output &&
        test_cmp expect output
 
 '
@@ -85,8 +150,8 @@ cat >expect <<EOF
 EOF
 test_expect_success 'status -uno' '
        mkdir dir3 &&
-       : > dir3/untracked1 &&
-       : > dir3/untracked2 &&
+       : >dir3/untracked1 &&
+       : >dir3/untracked2 &&
        git status -uno >output &&
        test_cmp expect output
 '
@@ -97,6 +162,39 @@ test_expect_success 'status (status.showUntrackedFiles no)' '
        test_cmp expect output
 '
 
+cat >expect <<EOF
+# On branch master
+# Changes to be committed:
+#      new file:   dir2/added
+#
+# Changed but not updated:
+#      modified:   dir1/modified
+#
+# Untracked files not listed
+EOF
+git config advice.statusHints false
+test_expect_success 'status -uno (advice.statusHints false)' '
+       git status -uno >output &&
+       test_cmp expect output
+'
+git config --unset advice.statusHints
+
+cat >expect << EOF
+ M dir1/modified
+A  dir2/added
+EOF
+test_expect_success 'status -s -uno' '
+       git config --unset status.showuntrackedfiles
+       git status -s -uno >output &&
+       test_cmp expect output
+'
+
+test_expect_success 'status -s (status.showUntrackedFiles no)' '
+       git config status.showuntrackedfiles no
+       git status -s >output &&
+       test_cmp expect output
+'
+
 cat >expect <<EOF
 # On branch master
 # Changes to be committed:
@@ -132,6 +230,29 @@ test_expect_success 'status (status.showUntrackedFiles normal)' '
        test_cmp expect output
 '
 
+cat >expect <<EOF
+ M dir1/modified
+A  dir2/added
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? dir3/
+?? expect
+?? output
+?? untracked
+EOF
+test_expect_success 'status -s -unormal' '
+       git config --unset status.showuntrackedfiles
+       git status -s -unormal >output &&
+       test_cmp expect output
+'
+
+test_expect_success 'status -s (status.showUntrackedFiles normal)' '
+       git config status.showuntrackedfiles normal
+       git status -s >output &&
+       test_cmp expect output
+'
+
 cat >expect <<EOF
 # On branch master
 # Changes to be committed:
@@ -169,7 +290,30 @@ test_expect_success 'status (status.showUntrackedFiles all)' '
        test_cmp expect output
 '
 
-cat > expect << \EOF
+cat >expect <<EOF
+ M dir1/modified
+A  dir2/added
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+test_expect_success 'status -s -uall' '
+       git config --unset status.showuntrackedfiles
+       git status -s -uall >output &&
+       test_cmp expect output
+'
+test_expect_success 'status -s (status.showUntrackedFiles all)' '
+       git config status.showuntrackedfiles all
+       git status -s >output &&
+       rm -rf dir3 &&
+       git config --unset status.showuntrackedfiles &&
+       test_cmp expect output
+'
+
+cat >expect <<\EOF
 # On branch master
 # Changes to be committed:
 #   (use "git reset HEAD <file>..." to unstage)
@@ -195,12 +339,182 @@ EOF
 
 test_expect_success 'status with relative paths' '
 
-       (cd dir1 && git status) > output &&
+       (cd dir1 && git status) >output &&
+       test_cmp expect output
+
+'
+
+cat >expect <<\EOF
+ M modified
+A  ../dir2/added
+?? untracked
+?? ../dir2/modified
+?? ../dir2/untracked
+?? ../expect
+?? ../output
+?? ../untracked
+EOF
+test_expect_success 'status -s with relative paths' '
+
+       (cd dir1 && git status -s) >output &&
+       test_cmp expect output
+
+'
+
+cat >expect <<\EOF
+ M dir1/modified
+A  dir2/added
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+
+test_expect_success 'status --porcelain ignores relative paths setting' '
+
+       (cd dir1 && git status --porcelain) >output &&
+       test_cmp expect output
+
+'
+
+test_expect_success 'setup unique colors' '
+
+       git config status.color.untracked blue
+
+'
+
+cat >expect <<\EOF
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#      <GREEN>new file:   dir2/added<RESET>
+#
+# Changed but not updated:
+#   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
+#
+#      <RED>modified:   dir1/modified<RESET>
+#
+# Untracked files:
+#   (use "git add <file>..." to include in what will be committed)
+#
+#      <BLUE>dir1/untracked<RESET>
+#      <BLUE>dir2/modified<RESET>
+#      <BLUE>dir2/untracked<RESET>
+#      <BLUE>expect<RESET>
+#      <BLUE>output<RESET>
+#      <BLUE>untracked<RESET>
+EOF
+
+test_expect_success 'status with color.ui' '
+
+       git config color.ui always &&
+       git status | test_decode_color >output &&
+       test_cmp expect output
+
+'
+
+test_expect_success 'status with color.status' '
+
+       git config --unset color.ui &&
+       git config color.status always &&
+       git status | test_decode_color >output &&
+       test_cmp expect output
+
+'
+
+cat >expect <<\EOF
+ <RED>M<RESET> dir1/modified
+<GREEN>A<RESET>  dir2/added
+<BLUE>??<RESET> dir1/untracked
+<BLUE>??<RESET> dir2/modified
+<BLUE>??<RESET> dir2/untracked
+<BLUE>??<RESET> expect
+<BLUE>??<RESET> output
+<BLUE>??<RESET> untracked
+EOF
+
+test_expect_success 'status -s with color.ui' '
+
+       git config --unset color.status &&
+       git config color.ui always &&
+       git status -s | test_decode_color >output &&
+       test_cmp expect output
+
+'
+
+test_expect_success 'status -s with color.status' '
+
+       git config --unset color.ui &&
+       git config color.status always &&
+       git status -s | test_decode_color >output &&
+       test_cmp expect output
+
+'
+
+cat >expect <<\EOF
+## <GREEN>master<RESET>
+ <RED>M<RESET> dir1/modified
+<GREEN>A<RESET>  dir2/added
+<BLUE>??<RESET> dir1/untracked
+<BLUE>??<RESET> dir2/modified
+<BLUE>??<RESET> dir2/untracked
+<BLUE>??<RESET> expect
+<BLUE>??<RESET> output
+<BLUE>??<RESET> untracked
+EOF
+
+test_expect_success 'status -s -b with color.status' '
+
+       git status -s -b | test_decode_color >output &&
+       test_cmp expect output
+
+'
+
+cat >expect <<\EOF
+ M dir1/modified
+A  dir2/added
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+
+test_expect_success 'status --porcelain ignores color.ui' '
+
+       git config --unset color.status &&
+       git config color.ui always &&
+       git status --porcelain | test_decode_color >output &&
+       test_cmp expect output
+
+'
+
+test_expect_success 'status --porcelain ignores color.status' '
+
+       git config --unset color.ui &&
+       git config color.status always &&
+       git status --porcelain | test_decode_color >output &&
+       test_cmp expect output
+
+'
+
+# recover unconditionally from color tests
+git config --unset color.status
+git config --unset color.ui
+
+test_expect_success 'status --porcelain ignores -b' '
+
+       git status --porcelain -b >output &&
        test_cmp expect output
 
 '
 
-cat > expect << \EOF
+cat >expect <<\EOF
 # On branch master
 # Changes to be committed:
 #   (use "git reset HEAD <file>..." to unstage)
@@ -224,10 +538,29 @@ cat > expect << \EOF
 #      untracked
 EOF
 
+
 test_expect_success 'status without relative paths' '
 
        git config status.relativePaths false
-       (cd dir1 && git status) > output &&
+       (cd dir1 && git status) >output &&
+       test_cmp expect output
+
+'
+
+cat >expect <<\EOF
+ M dir1/modified
+A  dir2/added
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+
+test_expect_success 'status -s without relative paths' '
+
+       (cd dir1 && git status -s) >output &&
        test_cmp expect output
 
 '
@@ -248,8 +581,18 @@ cat <<EOF >expect
 #      output
 #      untracked
 EOF
-test_expect_success 'status of partial commit excluding new file in index' '
-       git status dir1/modified >output &&
+test_expect_success 'dry-run of partial commit excluding new file in index' '
+       git commit --dry-run dir1/modified >output &&
+       test_cmp expect output
+'
+
+cat >expect <<EOF
+:100644 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0000000000000000000000000000000000000000 M     dir1/modified
+EOF
+test_expect_success 'status refreshes the index' '
+       touch dir2/added &&
+       git status &&
+       git diff-files >output &&
        test_cmp expect output
 '
 
@@ -298,6 +641,28 @@ test_expect_success 'status --untracked-files=all does not show submodule' '
        test_cmp expect output
 '
 
+cat >expect <<EOF
+ M dir1/modified
+A  dir2/added
+A  sm
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+test_expect_success 'status -s submodule summary is disabled by default' '
+       git status -s >output &&
+       test_cmp expect output
+'
+
+# we expect the same as the previous test
+test_expect_success 'status -s --untracked-files=all does not show submodule' '
+       git status -s --untracked-files=all >output &&
+       test_cmp expect output
+'
+
 head=$(cd sm && git rev-parse --short=7 --verify HEAD)
 
 cat >expect <<EOF
@@ -314,7 +679,7 @@ cat >expect <<EOF
 #
 #      modified:   dir1/modified
 #
-# Modified submodules:
+# Submodule changes to be committed:
 #
 # * sm 0000000...$head (1):
 #   > Add foo
@@ -335,6 +700,21 @@ test_expect_success 'status submodule summary' '
        test_cmp expect output
 '
 
+cat >expect <<EOF
+ M dir1/modified
+A  dir2/added
+A  sm
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+test_expect_success 'status -s submodule summary' '
+       git status -s >output &&
+       test_cmp expect output
+'
 
 cat >expect <<EOF
 # On branch master
@@ -358,7 +738,23 @@ EOF
 test_expect_success 'status submodule summary (clean submodule)' '
        git commit -m "commit submodule" &&
        git config status.submodulesummary 10 &&
-       test_must_fail git status >output &&
+       test_must_fail git commit --dry-run >output &&
+       test_cmp expect output &&
+       git status >output &&
+       test_cmp expect output
+'
+
+cat >expect <<EOF
+ M dir1/modified
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+test_expect_success 'status -s submodule summary (clean submodule)' '
+       git status -s >output &&
        test_cmp expect output
 '
 
@@ -376,7 +772,7 @@ cat >expect <<EOF
 #
 #      modified:   dir1/modified
 #
-# Modified submodules:
+# Submodule changes to be committed:
 #
 # * sm 0000000...$head (1):
 #   > Add foo
@@ -391,10 +787,327 @@ cat >expect <<EOF
 #      output
 #      untracked
 EOF
-test_expect_success 'status submodule summary (--amend)' '
+test_expect_success 'commit --dry-run submodule summary (--amend)' '
        git config status.submodulesummary 10 &&
-       git status --amend >output &&
+       git commit --dry-run --amend >output &&
+       test_cmp expect output
+'
+
+test_expect_success POSIXPERM,SANITY 'status succeeds in a read-only repository' '
+       (
+               chmod a-w .git &&
+               # make dir1/tracked stat-dirty
+               >dir1/tracked1 && mv -f dir1/tracked1 dir1/tracked &&
+               git status -s >output &&
+               ! grep dir1/tracked output &&
+               # make sure "status" succeeded without writing index out
+               git diff-files | grep dir1/tracked
+       )
+       status=$?
+       chmod 775 .git
+       (exit $status)
+'
+
+(cd sm && echo > bar && git add bar && git commit -q -m 'Add bar') && git add sm
+new_head=$(cd sm && git rev-parse --short=7 --verify HEAD)
+touch .gitmodules
+
+cat > expect << EOF
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#      modified:   sm
+#
+# Changed but not updated:
+#   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
+#
+#      modified:   dir1/modified
+#
+# Submodule changes to be committed:
+#
+# * sm $head...$new_head (1):
+#   > Add bar
+#
+# Untracked files:
+#   (use "git add <file>..." to include in what will be committed)
+#
+#      .gitmodules
+#      dir1/untracked
+#      dir2/modified
+#      dir2/untracked
+#      expect
+#      output
+#      untracked
+EOF
+
+test_expect_success '--ignore-submodules=untracked suppresses submodules with untracked content' '
+       echo modified > sm/untracked &&
+       git status --ignore-submodules=untracked > output &&
+       test_cmp expect output
+'
+
+test_expect_success '.gitmodules ignore=untracked suppresses submodules with untracked content' '
+       git config diff.ignoreSubmodules dirty &&
+       git status >output &&
+       test_cmp expect output &&
+       git config --add -f .gitmodules submodule.subname.ignore untracked &&
+       git config --add -f .gitmodules submodule.subname.path sm &&
+       git status > output &&
+       test_cmp expect output &&
+       git config -f .gitmodules  --remove-section submodule.subname &&
+       git config --unset diff.ignoreSubmodules
+'
+
+test_expect_success '.git/config ignore=untracked suppresses submodules with untracked content' '
+       git config --add -f .gitmodules submodule.subname.ignore none &&
+       git config --add -f .gitmodules submodule.subname.path sm &&
+       git config --add submodule.subname.ignore untracked &&
+       git config --add submodule.subname.path sm &&
+       git status > output &&
+       test_cmp expect output &&
+       git config --remove-section submodule.subname &&
+       git config --remove-section -f .gitmodules submodule.subname
+'
+
+test_expect_success '--ignore-submodules=dirty suppresses submodules with untracked content' '
+       git status --ignore-submodules=dirty > output &&
+       test_cmp expect output
+'
+
+test_expect_success '.gitmodules ignore=dirty suppresses submodules with untracked content' '
+       git config diff.ignoreSubmodules dirty &&
+       git status >output &&
+       ! test -s actual &&
+       git config --add -f .gitmodules submodule.subname.ignore dirty &&
+       git config --add -f .gitmodules submodule.subname.path sm &&
+       git status > output &&
+       test_cmp expect output &&
+       git config -f .gitmodules  --remove-section submodule.subname &&
+       git config --unset diff.ignoreSubmodules
+'
+
+test_expect_success '.git/config ignore=dirty suppresses submodules with untracked content' '
+       git config --add -f .gitmodules submodule.subname.ignore none &&
+       git config --add -f .gitmodules submodule.subname.path sm &&
+       git config --add submodule.subname.ignore dirty &&
+       git config --add submodule.subname.path sm &&
+       git status > output &&
+       test_cmp expect output &&
+       git config --remove-section submodule.subname &&
+       git config -f .gitmodules  --remove-section submodule.subname
+'
+
+test_expect_success '--ignore-submodules=dirty suppresses submodules with modified content' '
+       echo modified > sm/foo &&
+       git status --ignore-submodules=dirty > output &&
+       test_cmp expect output
+'
+
+test_expect_success '.gitmodules ignore=dirty suppresses submodules with modified content' '
+       git config --add -f .gitmodules submodule.subname.ignore dirty &&
+       git config --add -f .gitmodules submodule.subname.path sm &&
+       git status > output &&
+       test_cmp expect output &&
+       git config -f .gitmodules  --remove-section submodule.subname
+'
+
+test_expect_success '.git/config ignore=dirty suppresses submodules with modified content' '
+       git config --add -f .gitmodules submodule.subname.ignore none &&
+       git config --add -f .gitmodules submodule.subname.path sm &&
+       git config --add submodule.subname.ignore dirty &&
+       git config --add submodule.subname.path sm &&
+       git status > output &&
+       test_cmp expect output &&
+       git config --remove-section submodule.subname &&
+       git config -f .gitmodules  --remove-section submodule.subname
+'
+
+cat > expect << EOF
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#      modified:   sm
+#
+# Changed but not updated:
+#   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
+#   (commit or discard the untracked or modified content in submodules)
+#
+#      modified:   dir1/modified
+#      modified:   sm (modified content)
+#
+# Submodule changes to be committed:
+#
+# * sm $head...$new_head (1):
+#   > Add bar
+#
+# Untracked files:
+#   (use "git add <file>..." to include in what will be committed)
+#
+#      .gitmodules
+#      dir1/untracked
+#      dir2/modified
+#      dir2/untracked
+#      expect
+#      output
+#      untracked
+EOF
+
+test_expect_success "--ignore-submodules=untracked doesn't suppress submodules with modified content" '
+       git status --ignore-submodules=untracked > output &&
+       test_cmp expect output
+'
+
+test_expect_success ".gitmodules ignore=untracked doesn't suppress submodules with modified content" '
+       git config --add -f .gitmodules submodule.subname.ignore untracked &&
+       git config --add -f .gitmodules submodule.subname.path sm &&
+       git status > output &&
+       test_cmp expect output &&
+       git config -f .gitmodules  --remove-section submodule.subname
+'
+
+test_expect_success ".git/config ignore=untracked doesn't suppress submodules with modified content" '
+       git config --add -f .gitmodules submodule.subname.ignore none &&
+       git config --add -f .gitmodules submodule.subname.path sm &&
+       git config --add submodule.subname.ignore untracked &&
+       git config --add submodule.subname.path sm &&
+       git status > output &&
+       test_cmp expect output &&
+       git config --remove-section submodule.subname &&
+       git config -f .gitmodules  --remove-section submodule.subname
+'
+
+head2=$(cd sm && git commit -q -m "2nd commit" foo && git rev-parse --short=7 --verify HEAD)
+
+cat > expect << EOF
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#      modified:   sm
+#
+# Changed but not updated:
+#   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
+#
+#      modified:   dir1/modified
+#      modified:   sm (new commits)
+#
+# Submodule changes to be committed:
+#
+# * sm $head...$new_head (1):
+#   > Add bar
+#
+# Submodules changed but not updated:
+#
+# * sm $new_head...$head2 (1):
+#   > 2nd commit
+#
+# Untracked files:
+#   (use "git add <file>..." to include in what will be committed)
+#
+#      .gitmodules
+#      dir1/untracked
+#      dir2/modified
+#      dir2/untracked
+#      expect
+#      output
+#      untracked
+EOF
+
+test_expect_success "--ignore-submodules=untracked doesn't suppress submodule summary" '
+       git status --ignore-submodules=untracked > output &&
+       test_cmp expect output
+'
+
+test_expect_success ".gitmodules ignore=untracked doesn't suppress submodule summary" '
+       git config --add -f .gitmodules submodule.subname.ignore untracked &&
+       git config --add -f .gitmodules submodule.subname.path sm &&
+       git status > output &&
+       test_cmp expect output &&
+       git config -f .gitmodules  --remove-section submodule.subname
+'
+
+test_expect_success ".git/config ignore=untracked doesn't suppress submodule summary" '
+       git config --add -f .gitmodules submodule.subname.ignore none &&
+       git config --add -f .gitmodules submodule.subname.path sm &&
+       git config --add submodule.subname.ignore untracked &&
+       git config --add submodule.subname.path sm &&
+       git status > output &&
+       test_cmp expect output &&
+       git config --remove-section submodule.subname &&
+       git config -f .gitmodules  --remove-section submodule.subname
+'
+
+test_expect_success "--ignore-submodules=dirty doesn't suppress submodule summary" '
+       git status --ignore-submodules=dirty > output &&
+       test_cmp expect output
+'
+test_expect_success ".gitmodules ignore=dirty doesn't suppress submodule summary" '
+       git config --add -f .gitmodules submodule.subname.ignore dirty &&
+       git config --add -f .gitmodules submodule.subname.path sm &&
+       git status > output &&
+       test_cmp expect output &&
+       git config -f .gitmodules  --remove-section submodule.subname
+'
+
+test_expect_success ".git/config ignore=dirty doesn't suppress submodule summary" '
+       git config --add -f .gitmodules submodule.subname.ignore none &&
+       git config --add -f .gitmodules submodule.subname.path sm &&
+       git config --add submodule.subname.ignore dirty &&
+       git config --add submodule.subname.path sm &&
+       git status > output &&
+       test_cmp expect output &&
+       git config --remove-section submodule.subname &&
+       git config -f .gitmodules  --remove-section submodule.subname
+'
+
+cat > expect << EOF
+# On branch master
+# Changed but not updated:
+#   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
+#
+#      modified:   dir1/modified
+#
+# Untracked files:
+#   (use "git add <file>..." to include in what will be committed)
+#
+#      .gitmodules
+#      dir1/untracked
+#      dir2/modified
+#      dir2/untracked
+#      expect
+#      output
+#      untracked
+no changes added to commit (use "git add" and/or "git commit -a")
+EOF
+
+test_expect_success "--ignore-submodules=all suppresses submodule summary" '
+       git status --ignore-submodules=all > output &&
        test_cmp expect output
 '
 
+test_expect_failure '.gitmodules ignore=all suppresses submodule summary' '
+       git config --add -f .gitmodules submodule.subname.ignore all &&
+       git config --add -f .gitmodules submodule.subname.path sm &&
+       git status > output &&
+       test_cmp expect output &&
+       git config -f .gitmodules  --remove-section submodule.subname
+'
+
+test_expect_failure '.git/config ignore=all suppresses submodule summary' '
+       git config --add -f .gitmodules submodule.subname.ignore none &&
+       git config --add -f .gitmodules submodule.subname.path sm &&
+       git config --add submodule.subname.ignore all &&
+       git config --add submodule.subname.path sm &&
+       git status > output &&
+       test_cmp expect output &&
+       git config --remove-section submodule.subname &&
+       git config -f .gitmodules  --remove-section submodule.subname
+'
+
 test_done
diff --git a/t/t7509-commit.sh b/t/t7509-commit.sh
new file mode 100755 (executable)
index 0000000..643ab03
--- /dev/null
@@ -0,0 +1,160 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Erick Mattos
+#
+
+test_description='git commit --reset-author'
+
+. ./test-lib.sh
+
+author_header () {
+       git cat-file commit "$1" |
+       sed -n -e '/^$/q' -e '/^author /p'
+}
+
+message_body () {
+       git cat-file commit "$1" |
+       sed -e '1,/^$/d'
+}
+
+test_expect_success '-C option copies authorship and message' '
+       echo "Initial" >foo &&
+       git add foo &&
+       test_tick &&
+       git commit -m "Initial Commit" --author Frigate\ \<flying@over.world\> &&
+       git tag Initial &&
+       echo "Test 1" >>foo &&
+       test_tick &&
+       git commit -a -C Initial &&
+       author_header Initial >expect &&
+       author_header HEAD >actual &&
+       test_cmp expect actual &&
+
+       message_body Initial >expect &&
+       message_body HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '-C option copies only the message with --reset-author' '
+       echo "Test 2" >>foo &&
+       test_tick &&
+       git commit -a -C Initial --reset-author &&
+       echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect &&
+       author_header HEAD >actual
+       test_cmp expect actual &&
+
+       message_body Initial >expect &&
+       message_body HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '-c option copies authorship and message' '
+       echo "Test 3" >>foo &&
+       test_tick &&
+       EDITOR=: VISUAL=: git commit -a -c Initial &&
+       author_header Initial >expect &&
+       author_header HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '-c option copies only the message with --reset-author' '
+       echo "Test 4" >>foo &&
+       test_tick &&
+       EDITOR=: VISUAL=: git commit -a -c Initial --reset-author &&
+       echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect &&
+       author_header HEAD >actual &&
+       test_cmp expect actual &&
+
+       message_body Initial >expect &&
+       message_body HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--amend option copies authorship' '
+       git checkout Initial &&
+       echo "Test 5" >>foo &&
+       test_tick &&
+       git commit -a --amend -m "amend test" &&
+       author_header Initial >expect &&
+       author_header HEAD >actual &&
+
+       echo "amend test" >expect &&
+       message_body HEAD >actual &&
+       test_cmp expect actual
+'
+
+sha1_file() {
+       echo "$*" | sed "s#..#.git/objects/&/#"
+}
+remove_object() {
+       rm -f $(sha1_file "$*")
+}
+no_reflog() {
+       cp .git/config .git/config.saved &&
+       echo "[core] logallrefupdates = false" >>.git/config &&
+       test_when_finished "mv -f .git/config.saved .git/config" &&
+
+       if test -e .git/logs
+       then
+               mv .git/logs . &&
+               test_when_finished "mv logs .git/"
+       fi
+}
+
+test_expect_success '--amend option with empty author' '
+       git cat-file commit Initial >tmp &&
+       sed "s/author [^<]* </author  </" tmp >empty-author &&
+       no_reflog &&
+       sha=$(git hash-object -t commit -w empty-author) &&
+       test_when_finished "remove_object $sha" &&
+       git checkout $sha &&
+       test_when_finished "git checkout Initial" &&
+       echo "Empty author test" >>foo &&
+       test_tick &&
+       test_must_fail git commit -a -m "empty author" --amend 2>err &&
+       grep "empty ident" err
+'
+
+test_expect_success '--amend option with missing author' '
+       git cat-file commit Initial >tmp &&
+       sed "s/author [^<]* </author </" tmp >malformed &&
+       no_reflog &&
+       sha=$(git hash-object -t commit -w malformed) &&
+       test_when_finished "remove_object $sha" &&
+       git checkout $sha &&
+       test_when_finished "git checkout Initial" &&
+       echo "Missing author test" >>foo &&
+       test_tick &&
+       test_must_fail git commit -a -m "malformed author" --amend 2>err &&
+       grep "empty ident" err
+'
+
+test_expect_success '--reset-author makes the commit ours even with --amend option' '
+       git checkout Initial &&
+       echo "Test 6" >>foo &&
+       test_tick &&
+       git commit -a --reset-author -m "Changed again" --amend &&
+       echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect &&
+       author_header HEAD >actual &&
+       test_cmp expect actual &&
+
+       echo "Changed again" >expect &&
+       message_body HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--reset-author and --author are mutually exclusive' '
+       git checkout Initial &&
+       echo "Test 7" >>foo &&
+       test_tick &&
+       test_must_fail git commit -a --reset-author --author="Xyzzy <frotz@nitfol.xz>"
+'
+
+test_expect_success '--reset-author should be rejected without -c/-C/--amend' '
+       git checkout Initial &&
+       echo "Test 7" >>foo &&
+       test_tick &&
+       test_must_fail git commit -a --reset-author -m done
+'
+
+test_done
index e5b210bc960c8433d6758f3932a86647208297ef..b4f40e4c3a53388e72347e8e3894edd05f17dab5 100755 (executable)
 
 test_description='git merge
 
-Testing basic merge operations/option parsing.'
+Testing basic merge operations/option parsing.
+
+! [c0] commit 0
+ ! [c1] commit 1
+  ! [c2] commit 2
+   ! [c3] commit 3
+    ! [c4] c4
+     ! [c5] c5
+      ! [c6] c6
+       * [master] Merge commit 'c1'
+--------
+       - [master] Merge commit 'c1'
+ +     * [c1] commit 1
+      +  [c6] c6
+     +   [c5] c5
+    ++   [c4] c4
+   ++++  [c3] commit 3
+  +      [c2] commit 2
++++++++* [c0] commit 0
+'
 
 . ./test-lib.sh
 
-cat >file <<EOF
-1
-2
-3
-4
-5
-6
-7
-8
-9
-EOF
-
-cat >file.1 <<EOF
-1 X
-2
-3
-4
-5
-6
-7
-8
-9
-EOF
-
-cat >file.5 <<EOF
-1
-2
-3
-4
-5 X
-6
-7
-8
-9
-EOF
-
-cat >file.9 <<EOF
-1
-2
-3
-4
-5
-6
-7
-8
-9 X
-EOF
-
-cat  >result.1 <<EOF
-1 X
-2
-3
-4
-5
-6
-7
-8
-9
-EOF
-
-cat >result.1-5 <<EOF
-1 X
-2
-3
-4
-5 X
-6
-7
-8
-9
-EOF
-
-cat >result.1-5-9 <<EOF
-1 X
-2
-3
-4
-5 X
-6
-7
-8
-9 X
-EOF
-
-create_merge_msgs() {
-       echo "Merge commit 'c2'" >msg.1-5 &&
-       echo "Merge commit 'c2'; commit 'c3'" >msg.1-5-9 &&
-       echo "Squashed commit of the following:" >squash.1 &&
-       echo >>squash.1 &&
-       git log --no-merges ^HEAD c1 >>squash.1 &&
-       echo "Squashed commit of the following:" >squash.1-5 &&
-       echo >>squash.1-5 &&
-       git log --no-merges ^HEAD c2 >>squash.1-5 &&
-       echo "Squashed commit of the following:" >squash.1-5-9 &&
-       echo >>squash.1-5-9 &&
-       git log --no-merges ^HEAD c2 c3 >>squash.1-5-9 &&
-       echo > msg.nolog &&
-       echo "* commit 'c3':" >msg.log &&
-       echo "  commit 3" >>msg.log &&
-       echo >>msg.log
-}
-
-verify_diff() {
-       if ! test_cmp "$1" "$2"
-       then
-               echo "$3"
-               false
-       fi
-}
-
-verify_merge() {
-       verify_diff "$2" "$1" "[OOPS] bad merge result" &&
-       if test $(git ls-files -u | wc -l) -gt 0
-       then
-               echo "[OOPS] unmerged files"
-               false
-       fi &&
-       if test_must_fail git diff --exit-code
-       then
-               echo "[OOPS] working tree != index"
-               false
-       fi &&
-       if test -n "$3"
-       then
-               git show -s --pretty=format:%s HEAD >msg.act &&
-               verify_diff "$3" msg.act "[OOPS] bad merge message"
-       fi
-}
-
-verify_head() {
-       if test "$1" != "$(git rev-parse HEAD)"
-       then
-               echo "[OOPS] HEAD != $1"
-               false
-       fi
-}
-
-verify_parents() {
-       i=1
-       while test $# -gt 0
-       do
-               if test "$1" != "$(git rev-parse HEAD^$i)"
+test_expect_success 'set up test data and helpers' '
+       printf "%s\n" 1 2 3 4 5 6 7 8 9 >file &&
+       printf "%s\n" "1 X" 2 3 4 5 6 7 8 9 >file.1 &&
+       printf "%s\n" 1 2 3 4 "5 X" 6 7 8 9 >file.5 &&
+       printf "%s\n" 1 2 3 4 5 6 7 8 "9 X" >file.9 &&
+       printf "%s\n" "1 X" 2 3 4 5 6 7 8 9 >result.1 &&
+       printf "%s\n" "1 X" 2 3 4 "5 X" 6 7 8 9 >result.1-5 &&
+       printf "%s\n" "1 X" 2 3 4 "5 X" 6 7 8 "9 X" >result.1-5-9 &&
+
+       create_merge_msgs() {
+               echo "Merge commit '\''c2'\''" >msg.1-5 &&
+               echo "Merge commit '\''c2'\''; commit '\''c3'\''" >msg.1-5-9 &&
+               {
+                       echo "Squashed commit of the following:" &&
+                       echo &&
+                       git log --no-merges ^HEAD c1
+               } >squash.1 &&
+               {
+                       echo "Squashed commit of the following:" &&
+                       echo &&
+                       git log --no-merges ^HEAD c2
+               } >squash.1-5 &&
+               {
+                       echo "Squashed commit of the following:" &&
+                       echo &&
+                       git log --no-merges ^HEAD c2 c3
+               } >squash.1-5-9 &&
+               echo >msg.nolog &&
+               {
+                       echo "* commit '\''c3'\'':" &&
+                       echo "  commit 3" &&
+                       echo
+               } >msg.log
+       } &&
+
+       verify_merge() {
+               test_cmp "$2" "$1" &&
+               git update-index --refresh &&
+               git diff --exit-code &&
+               if test -n "$3"
                then
-                       echo "[OOPS] HEAD^$i != $1"
-                       return 1
+                       git show -s --pretty=format:%s HEAD >msg.act &&
+                       test_cmp "$3" msg.act
                fi
-               i=$(expr $i + 1)
-               shift
-       done
-}
-
-verify_mergeheads() {
-       i=1
-       if ! test -f .git/MERGE_HEAD
-       then
-               echo "[OOPS] MERGE_HEAD is missing"
-               false
-       fi &&
-       while test $# -gt 0
-       do
-               head=$(head -n $i .git/MERGE_HEAD | sed -ne \$p)
-               if test "$1" != "$head"
-               then
-                       echo "[OOPS] MERGE_HEAD $i != $1"
+       } &&
+
+       verify_head() {
+               echo "$1" >head.expected &&
+               git rev-parse HEAD >head.actual &&
+               test_cmp head.expected head.actual
+       } &&
+
+       verify_parents() {
+               printf "%s\n" "$@" >parents.expected &&
+               >parents.actual &&
+               i=1 &&
+               while test $i -le $#
+               do
+                       git rev-parse HEAD^$i >>parents.actual &&
+                       i=$(expr $i + 1) ||
                        return 1
-               fi
-               i=$(expr $i + 1)
-               shift
-       done
-}
+               done &&
+               test_cmp parents.expected parents.actual
+       } &&
 
-verify_no_mergehead() {
-       if test -f .git/MERGE_HEAD
-       then
-               echo "[OOPS] MERGE_HEAD exists"
-               false
-       fi
-}
+       verify_mergeheads() {
+               printf "%s\n" "$@" >mergehead.expected &&
+               test_cmp mergehead.expected .git/MERGE_HEAD
+       } &&
 
+       verify_no_mergehead() {
+               ! test -e .git/MERGE_HEAD
+       }
+'
 
 test_expect_success 'setup' '
        git add file &&
@@ -219,7 +133,7 @@ test_expect_success 'setup' '
        create_merge_msgs
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'test option parsing' '
        test_must_fail git merge -$ c1 &&
@@ -235,13 +149,50 @@ test_expect_success 'reject non-strategy with a git-merge-foo name' '
 '
 
 test_expect_success 'merge c0 with c1' '
+       echo "OBJID HEAD@{0}: merge c1: Fast-forward" >reflog.expected &&
+
        git reset --hard c0 &&
        git merge c1 &&
        verify_merge file result.1 &&
+       verify_head "$c1" &&
+
+       git reflog -1 >reflog.actual &&
+       sed "s/$_x05[0-9a-f]*/OBJID/g" reflog.actual >reflog.fuzzy &&
+       test_cmp reflog.expected reflog.fuzzy
+'
+
+test_debug 'git log --graph --decorate --oneline --all'
+
+test_expect_success 'merge c0 with c1 with --ff-only' '
+       git reset --hard c0 &&
+       git merge --ff-only c1 &&
+       git merge --ff-only HEAD c0 c1 &&
+       verify_merge file result.1 &&
        verify_head "$c1"
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
+
+test_expect_success 'merge from unborn branch' '
+       git checkout -f master &&
+       test_might_fail git branch -D kid &&
+
+       echo "OBJID HEAD@{0}: initial pull" >reflog.expected &&
+
+       git checkout --orphan kid &&
+       test_when_finished "git checkout -f master" &&
+       git rm -fr . &&
+       test_tick &&
+       git merge --ff-only c1 &&
+       verify_merge file result.1 &&
+       verify_head "$c1" &&
+
+       git reflog -1 >reflog.actual &&
+       sed "s/$_x05[0-9a-f][0-9a-f]/OBJID/g" reflog.actual >reflog.fuzzy &&
+       test_cmp reflog.expected reflog.fuzzy
+'
+
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c1 with c2' '
        git reset --hard c1 &&
@@ -251,7 +202,7 @@ test_expect_success 'merge c1 with c2' '
        verify_parents $c1 $c2
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c1 with c2 and c3' '
        git reset --hard c1 &&
@@ -261,7 +212,15 @@ test_expect_success 'merge c1 with c2 and c3' '
        verify_parents $c1 $c2 $c3
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
+
+test_expect_success 'failing merges with --ff-only' '
+       git reset --hard c1 &&
+       test_tick &&
+       test_must_fail git merge --ff-only c2 &&
+       test_must_fail git merge --ff-only c3 &&
+       test_must_fail git merge --ff-only c2 c3
+'
 
 test_expect_success 'merge c0 with c1 (no-commit)' '
        git reset --hard c0 &&
@@ -270,7 +229,7 @@ test_expect_success 'merge c0 with c1 (no-commit)' '
        verify_head $c1
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c1 with c2 (no-commit)' '
        git reset --hard c1 &&
@@ -280,7 +239,7 @@ test_expect_success 'merge c1 with c2 (no-commit)' '
        verify_mergeheads $c2
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c1 with c2 and c3 (no-commit)' '
        git reset --hard c1 &&
@@ -290,7 +249,7 @@ test_expect_success 'merge c1 with c2 and c3 (no-commit)' '
        verify_mergeheads $c2 $c3
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c0 with c1 (squash)' '
        git reset --hard c0 &&
@@ -298,10 +257,21 @@ test_expect_success 'merge c0 with c1 (squash)' '
        verify_merge file result.1 &&
        verify_head $c0 &&
        verify_no_mergehead &&
-       verify_diff squash.1 .git/SQUASH_MSG "[OOPS] bad squash message"
+       test_cmp squash.1 .git/SQUASH_MSG
+'
+
+test_debug 'git log --graph --decorate --oneline --all'
+
+test_expect_success 'merge c0 with c1 (squash, ff-only)' '
+       git reset --hard c0 &&
+       git merge --squash --ff-only c1 &&
+       verify_merge file result.1 &&
+       verify_head $c0 &&
+       verify_no_mergehead &&
+       test_cmp squash.1 .git/SQUASH_MSG
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c1 with c2 (squash)' '
        git reset --hard c1 &&
@@ -309,10 +279,17 @@ test_expect_success 'merge c1 with c2 (squash)' '
        verify_merge file result.1-5 &&
        verify_head $c1 &&
        verify_no_mergehead &&
-       verify_diff squash.1-5 .git/SQUASH_MSG "[OOPS] bad squash message"
+       test_cmp squash.1-5 .git/SQUASH_MSG
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
+
+test_expect_success 'unsuccesful merge of c1 with c2 (squash, ff-only)' '
+       git reset --hard c1 &&
+       test_must_fail git merge --squash --ff-only c2
+'
+
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c1 with c2 and c3 (squash)' '
        git reset --hard c1 &&
@@ -320,10 +297,10 @@ test_expect_success 'merge c1 with c2 and c3 (squash)' '
        verify_merge file result.1-5-9 &&
        verify_head $c1 &&
        verify_no_mergehead &&
-       verify_diff squash.1-5-9 .git/SQUASH_MSG "[OOPS] bad squash message"
+       test_cmp squash.1-5-9 .git/SQUASH_MSG
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c1 with c2 (no-commit in config)' '
        git reset --hard c1 &&
@@ -334,7 +311,7 @@ test_expect_success 'merge c1 with c2 (no-commit in config)' '
        verify_mergeheads $c2
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c1 with c2 (squash in config)' '
        git reset --hard c1 &&
@@ -343,10 +320,10 @@ test_expect_success 'merge c1 with c2 (squash in config)' '
        verify_merge file result.1-5 &&
        verify_head $c1 &&
        verify_no_mergehead &&
-       verify_diff squash.1-5 .git/SQUASH_MSG "[OOPS] bad squash message"
+       test_cmp squash.1-5 .git/SQUASH_MSG
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'override config option -n with --summary' '
        git reset --hard c1 &&
@@ -376,7 +353,7 @@ test_expect_success 'override config option -n with --stat' '
        fi
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'override config option --stat' '
        git reset --hard c1 &&
@@ -392,7 +369,7 @@ test_expect_success 'override config option --stat' '
        fi
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c1 with c2 (override --no-commit)' '
        git reset --hard c1 &&
@@ -403,7 +380,7 @@ test_expect_success 'merge c1 with c2 (override --no-commit)' '
        verify_parents $c1 $c2
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c1 with c2 (override --squash)' '
        git reset --hard c1 &&
@@ -414,7 +391,7 @@ test_expect_success 'merge c1 with c2 (override --squash)' '
        verify_parents $c1 $c2
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c0 with c1 (no-ff)' '
        git reset --hard c0 &&
@@ -425,13 +402,18 @@ test_expect_success 'merge c0 with c1 (no-ff)' '
        verify_parents $c0 $c1
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'combining --squash and --no-ff is refused' '
        test_must_fail git merge --squash --no-ff c1 &&
        test_must_fail git merge --no-ff --squash c1
 '
 
+test_expect_success 'combining --ff-only and --no-ff is refused' '
+       test_must_fail git merge --ff-only --no-ff c1 &&
+       test_must_fail git merge --no-ff --ff-only c1
+'
+
 test_expect_success 'merge c0 with c1 (ff overrides no-ff)' '
        git reset --hard c0 &&
        git config branch.master.mergeoptions "--no-ff" &&
@@ -444,20 +426,20 @@ test_expect_success 'merge log message' '
        git reset --hard c0 &&
        git merge --no-log c2 &&
        git show -s --pretty=format:%b HEAD >msg.act &&
-       verify_diff msg.nolog msg.act "[OOPS] bad merge log message" &&
+       test_cmp msg.nolog msg.act &&
 
        git merge --log c3 &&
        git show -s --pretty=format:%b HEAD >msg.act &&
-       verify_diff msg.log msg.act "[OOPS] bad merge log message" &&
+       test_cmp msg.log msg.act &&
 
        git reset --hard HEAD^ &&
        git config merge.log yes &&
        git merge c3 &&
        git show -s --pretty=format:%b HEAD >msg.act &&
-       verify_diff msg.log msg.act "[OOPS] bad merge log message"
+       test_cmp msg.log msg.act
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c1 with c0, c2, c0, and c1' '
        git reset --hard c1 &&
@@ -468,7 +450,7 @@ test_expect_success 'merge c1 with c0, c2, c0, and c1' '
        verify_parents $c1 $c2
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c1 with c0, c2, c0, and c1' '
        git reset --hard c1 &&
@@ -479,7 +461,7 @@ test_expect_success 'merge c1 with c0, c2, c0, and c1' '
        verify_parents $c1 $c2
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c1 with c1 and c2' '
        git reset --hard c1 &&
@@ -490,7 +472,7 @@ test_expect_success 'merge c1 with c1 and c2' '
        verify_parents $c1 $c2
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge fast-forward in a dirty tree' '
        git reset --hard c0 &&
@@ -500,49 +482,56 @@ test_expect_success 'merge fast-forward in a dirty tree' '
        git merge c2
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'in-index merge' '
        git reset --hard c0 &&
-       git merge --no-ff -s resolve c1 > out &&
+       git merge --no-ff -s resolve c1 >out &&
        grep "Wonderful." out &&
        verify_parents $c0 $c1
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'refresh the index before merging' '
        git reset --hard c1 &&
-       sleep 1 &&
-       touch file &&
+       cp file file.n && mv -f file.n file &&
        git merge c3
 '
 
-cat >expected <<EOF
-Merge branch 'c5' (early part)
+cat >expected.branch <<\EOF
+Merge branch 'c5-branch' (early part)
+EOF
+cat >expected.tag <<\EOF
+Merge commit 'c5~1'
 EOF
 
 test_expect_success 'merge early part of c2' '
        git reset --hard c3 &&
-       echo c4 > c4.c &&
+       echo c4 >c4.c &&
        git add c4.c &&
        git commit -m c4 &&
        git tag c4 &&
-       echo c5 > c5.c &&
+       echo c5 >c5.c &&
        git add c5.c &&
        git commit -m c5 &&
        git tag c5 &&
        git reset --hard c3 &&
-       echo c6 > c6.c &&
+       echo c6 >c6.c &&
        git add c6.c &&
        git commit -m c6 &&
        git tag c6 &&
+       git branch -f c5-branch c5 &&
+       git merge c5-branch~1 &&
+       git show -s --pretty=format:%s HEAD >actual.branch &&
+       git reset --keep HEAD^ &&
        git merge c5~1 &&
-       git show -s --pretty=format:%s HEAD > actual &&
-       test_cmp actual expected
+       git show -s --pretty=format:%s HEAD >actual.tag &&
+       test_cmp expected.branch actual.branch &&
+       test_cmp expected.tag actual.tag
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge --no-ff --no-commit && commit' '
        git reset --hard c0 &&
@@ -551,13 +540,13 @@ test_expect_success 'merge --no-ff --no-commit && commit' '
        verify_parents $c0 $c1
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'amending no-ff merge commit' '
        EDITOR=: git commit --amend &&
        verify_parents $c0 $c1
 '
 
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
 
 test_done
index 01e5415e943f3e16154f2f4d999f85a9d8b6ae49..2746169514f9bd1629f05eed8f9ff2fcfdba8cb5 100755 (executable)
@@ -49,4 +49,55 @@ test_expect_success 'merge c1 with c2, c3, c4, ... c29' '
        done
 '
 
+cat >expected <<\EOF
+Trying simple merge with c2
+Trying simple merge with c3
+Trying simple merge with c4
+Merge made by octopus.
+ c2.c |    1 +
+ c3.c |    1 +
+ c4.c |    1 +
+ 3 files changed, 3 insertions(+), 0 deletions(-)
+ create mode 100644 c2.c
+ create mode 100644 c3.c
+ create mode 100644 c4.c
+EOF
+
+test_expect_success 'merge output uses pretty names' '
+       git reset --hard c1 &&
+       git merge c2 c3 c4 >actual &&
+       test_cmp actual expected
+'
+
+cat >expected <<\EOF
+Already up-to-date with c4
+Trying simple merge with c5
+Merge made by octopus.
+ c5.c |    1 +
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+ create mode 100644 c5.c
+EOF
+
+test_expect_success 'merge up-to-date output uses pretty names' '
+       git merge c4 c5 >actual &&
+       test_cmp actual expected
+'
+
+cat >expected <<\EOF
+Fast-forwarding to: c1
+Trying simple merge with c2
+Merge made by octopus.
+ c1.c |    1 +
+ c2.c |    1 +
+ 2 files changed, 2 insertions(+), 0 deletions(-)
+ create mode 100644 c1.c
+ create mode 100644 c2.c
+EOF
+
+test_expect_success 'merge fast-forward output uses pretty names' '
+       git reset --hard c0 &&
+       git merge c1 c2 >actual &&
+       test_cmp actual expected
+'
+
 test_done
index de977c5e2f0b270c57bbea9daaf8e22eefdf8560..9114785ef7c850ae2d393bed3565e13f11339ba0 100755 (executable)
@@ -6,6 +6,15 @@ Testing merge when using a custom message for the merge commit.'
 
 . ./test-lib.sh
 
+create_merge_msgs() {
+       echo >exp.subject "custom message"
+
+       cp exp.subject exp.log &&
+       echo >>exp.log "" &&
+       echo >>exp.log "* commit 'c2':" &&
+       echo >>exp.log "  c2"
+}
+
 test_expect_success 'setup' '
        echo c0 > c0.c &&
        git add c0.c &&
@@ -19,19 +28,23 @@ test_expect_success 'setup' '
        echo c2 > c2.c &&
        git add c2.c &&
        git commit -m c2 &&
-       git tag c2
+       git tag c2 &&
+       create_merge_msgs
 '
 
-cat >expected <<\EOF
-custom message
 
-Merge commit 'c2'
-EOF
 test_expect_success 'merge c2 with a custom message' '
        git reset --hard c1 &&
-       git merge -m "custom message" c2 &&
-       git cat-file commit HEAD | sed -e "1,/^$/d" > actual &&
-       test_cmp expected actual
+       git merge -m "$(cat exp.subject)" c2 &&
+       git cat-file commit HEAD | sed -e "1,/^$/d" >actual &&
+       test_cmp exp.subject actual
+'
+
+test_expect_success 'merge --log appends to custom message' '
+       git reset --hard c1 &&
+       git merge --log -m "$(cat exp.subject)" c2 &&
+       git cat-file commit HEAD | sed -e "1,/^$/d" >actual &&
+       test_cmp exp.log actual
 '
 
 test_done
index 52a451dd5782ab584ba34e73abaef32d5e332537..8e8c4d72464098e1c75805e48bb5907e34e9532c 100755 (executable)
@@ -1,49 +1,93 @@
 #!/bin/sh
 
-test_description='git merge
+test_description="git merge
 
-Testing a custom strategy.'
+Testing a custom strategy.
+
+*   (HEAD, master) Merge commit 'c3'
+|\
+| * (tag: c3) c3
+* | (tag: c1) c1
+|/
+| * tag: c2) c2
+|/
+* (tag: c0) c0
+"
 
 . ./test-lib.sh
 
-cat >git-merge-theirs <<EOF
-#!$SHELL_PATH
-eval git read-tree --reset -u \\\$\$#
-EOF
-chmod +x git-merge-theirs
-PATH=.:$PATH
-export PATH
+test_expect_success 'set up custom strategy' '
+       cat >git-merge-theirs <<-EOF &&
+       #!$SHELL_PATH
+       eval git read-tree --reset -u \\\$\$#
+       EOF
+
+       chmod +x git-merge-theirs &&
+       PATH=.:$PATH &&
+       export PATH
+'
 
 test_expect_success 'setup' '
-       echo c0 >c0.c &&
-       git add c0.c &&
-       git commit -m c0 &&
-       git tag c0 &&
-       echo c1 >c1.c &&
-       git add c1.c &&
-       git commit -m c1 &&
-       git tag c1 &&
-       git reset --hard c0 &&
+       test_commit c0 c0.c &&
+       test_commit c1 c1.c &&
+       git reset --keep c0 &&
        echo c1c1 >c1.c &&
-       echo c2 >c2.c &&
-       git add c1.c c2.c &&
-       git commit -m c2 &&
-       git tag c2
+       git add c1.c &&
+       test_commit c2 c2.c &&
+       git reset --keep c0 &&
+       test_commit c3 c3.c
 '
 
 test_expect_success 'merge c2 with a custom strategy' '
        git reset --hard c1 &&
+
+       git rev-parse c1 >head.old &&
+       git rev-parse c2 >second-parent.expected &&
+       git rev-parse c2^{tree} >tree.expected &&
        git merge -s theirs c2 &&
-       test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
-       test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
-       test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
-       test "$(git rev-parse c2^{tree})" = "$(git rev-parse HEAD^{tree})" &&
+
+       git rev-parse HEAD >head.new &&
+       git rev-parse HEAD^1 >first-parent &&
+       git rev-parse HEAD^2 >second-parent &&
+       git rev-parse HEAD^{tree} >tree &&
+       git update-index --refresh &&
        git diff --exit-code &&
        git diff --exit-code c2 HEAD &&
        git diff --exit-code c2 &&
+
+       ! test_cmp head.old head.new &&
+       test_cmp head.old first-parent &&
+       test_cmp second-parent.expected second-parent &&
+       test_cmp tree.expected tree &&
        test -f c0.c &&
        grep c1c1 c1.c &&
        test -f c2.c
 '
 
+test_expect_success 'trivial merge with custom strategy' '
+       git reset --hard c1 &&
+
+       git rev-parse c1 >head.old &&
+       git rev-parse c3 >second-parent.expected &&
+       git rev-parse c3^{tree} >tree.expected &&
+       git merge -s theirs c3 &&
+
+       git rev-parse HEAD >head.new &&
+       git rev-parse HEAD^1 >first-parent &&
+       git rev-parse HEAD^2 >second-parent &&
+       git rev-parse HEAD^{tree} >tree &&
+       git update-index --refresh &&
+       git diff --exit-code &&
+       git diff --exit-code c3 HEAD &&
+       git diff --exit-code c3 &&
+
+       ! test_cmp head.old head.new &&
+       test_cmp head.old first-parent &&
+       test_cmp second-parent.expected second-parent &&
+       test_cmp tree.expected tree &&
+       test -f c0.c &&
+       ! test -e c1.c &&
+       test -f c3.c
+'
+
 test_done
index 49f4e1599acd829fdd930c1f9b5bc30ac719053d..d82349a6a8d6c12c3ca4398a18bd6c1dd42efdc9 100755 (executable)
@@ -31,7 +31,7 @@ test_expect_success 'setup' '
 test_expect_success 'will not overwrite untracked file' '
        git reset --hard c1 &&
        cat important > c2.c &&
-       ! git merge c2 &&
+       test_must_fail git merge c2 &&
        test_cmp important c2.c
 '
 
@@ -39,7 +39,7 @@ test_expect_success 'will not overwrite new file' '
        git reset --hard c1 &&
        cat important > c2.c &&
        git add c2.c &&
-       ! git merge c2 &&
+       test_must_fail git merge c2 &&
        test_cmp important c2.c
 '
 
@@ -48,7 +48,7 @@ test_expect_success 'will not overwrite staged changes' '
        cat important > c2.c &&
        git add c2.c &&
        rm c2.c &&
-       ! git merge c2 &&
+       test_must_fail git merge c2 &&
        git checkout c2.c &&
        test_cmp important c2.c
 '
@@ -58,7 +58,7 @@ test_expect_success 'will not overwrite removed file' '
        git rm c1.c &&
        git commit -m "rm c1.c" &&
        cat important > c1.c &&
-       ! git merge c1a &&
+       test_must_fail git merge c1a &&
        test_cmp important c1.c
 '
 
@@ -68,7 +68,7 @@ test_expect_success 'will not overwrite re-added file' '
        git commit -m "rm c1.c" &&
        cat important > c1.c &&
        git add c1.c &&
-       ! git merge c1a &&
+       test_must_fail git merge c1a &&
        test_cmp important c1.c
 '
 
@@ -79,7 +79,7 @@ test_expect_success 'will not overwrite removed file with staged changes' '
        cat important > c1.c &&
        git add c1.c &&
        rm c1.c &&
-       ! git merge c1a &&
+       test_must_fail git merge c1a &&
        git checkout c1.c &&
        test_cmp important c1.c
 '
diff --git a/t/t7608-merge-messages.sh b/t/t7608-merge-messages.sh
new file mode 100755 (executable)
index 0000000..28d5679
--- /dev/null
@@ -0,0 +1,60 @@
+#!/bin/sh
+
+test_description='test auto-generated merge messages'
+. ./test-lib.sh
+
+check_oneline() {
+       echo "$1" | sed "s/Q/'/g" >expect &&
+       git log -1 --pretty=tformat:%s >actual &&
+       test_cmp expect actual
+}
+
+test_expect_success 'merge local branch' '
+       test_commit master-1 &&
+       git checkout -b local-branch &&
+       test_commit branch-1 &&
+       git checkout master &&
+       test_commit master-2 &&
+       git merge local-branch &&
+       check_oneline "Merge branch Qlocal-branchQ"
+'
+
+test_expect_success 'merge octopus branches' '
+       git checkout -b octopus-a master &&
+       test_commit octopus-1 &&
+       git checkout -b octopus-b master &&
+       test_commit octopus-2 &&
+       git checkout master &&
+       git merge octopus-a octopus-b &&
+       check_oneline "Merge branches Qoctopus-aQ and Qoctopus-bQ"
+'
+
+test_expect_success 'merge tag' '
+       git checkout -b tag-branch master &&
+       test_commit tag-1 &&
+       git checkout master &&
+       test_commit master-3 &&
+       git merge tag-1 &&
+       check_oneline "Merge commit Qtag-1Q"
+'
+
+test_expect_success 'ambiguous tag' '
+       git checkout -b ambiguous master &&
+       test_commit ambiguous &&
+       git checkout master &&
+       test_commit master-4 &&
+       git merge ambiguous &&
+       check_oneline "Merge commit QambiguousQ"
+'
+
+test_expect_success 'remote branch' '
+       git checkout -b remote master &&
+       test_commit remote-1 &&
+       git update-ref refs/remotes/origin/master remote &&
+       git checkout master &&
+       test_commit master-5 &&
+       git merge origin/master &&
+       check_oneline "Merge remote branch Qorigin/masterQ"
+'
+
+test_done
diff --git a/t/t7609-merge-co-error-msgs.sh b/t/t7609-merge-co-error-msgs.sh
new file mode 100755 (executable)
index 0000000..114d2bd
--- /dev/null
@@ -0,0 +1,133 @@
+#!/bin/sh
+
+test_description='unpack-trees error messages'
+
+. ./test-lib.sh
+
+
+test_expect_success 'setup' '
+       echo one >one &&
+       git add one &&
+       git commit -a -m First &&
+
+       git checkout -b branch &&
+       echo two >two &&
+       echo three >three &&
+       echo four >four &&
+       echo five >five &&
+       git add two three four five &&
+       git commit -m Second &&
+
+       git checkout master &&
+       echo other >two &&
+       echo other >three &&
+       echo other >four &&
+       echo other >five
+'
+
+cat >expect <<\EOF
+error: The following untracked working tree files would be overwritten by merge:
+       two
+       three
+       four
+       five
+Please move or remove them before you can merge.
+EOF
+
+test_expect_success 'untracked files overwritten by merge (fast and non-fast forward)' '
+       test_must_fail git merge branch 2>out &&
+       test_cmp out expect &&
+       git commit --allow-empty -m empty &&
+       (
+               GIT_MERGE_VERBOSITY=0 &&
+               export GIT_MERGE_VERBOSITY &&
+               test_must_fail git merge branch 2>out2
+       ) &&
+       test_cmp out2 expect &&
+       git reset --hard HEAD^
+'
+
+cat >expect <<\EOF
+error: Your local changes to the following files would be overwritten by merge:
+       two
+       three
+       four
+Please, commit your changes or stash them before you can merge.
+error: The following untracked working tree files would be overwritten by merge:
+       five
+Please move or remove them before you can merge.
+EOF
+
+test_expect_success 'untracked files or local changes ovewritten by merge' '
+       git add two &&
+       git add three &&
+       git add four &&
+       test_must_fail git merge branch 2>out &&
+       test_cmp out expect
+'
+
+cat >expect <<\EOF
+error: Your local changes to the following files would be overwritten by checkout:
+       rep/two
+       rep/one
+Please, commit your changes or stash them before you can switch branches.
+EOF
+
+test_expect_success 'cannot switch branches because of local changes' '
+       git add five &&
+       mkdir rep &&
+       echo one >rep/one &&
+       echo two >rep/two &&
+       git add rep/one rep/two &&
+       git commit -m Fourth &&
+       git checkout master &&
+       echo uno >rep/one &&
+       echo dos >rep/two &&
+       test_must_fail git checkout branch 2>out &&
+       test_cmp out expect
+'
+
+cat >expect <<\EOF
+error: Your local changes to the following files would be overwritten by checkout:
+       rep/two
+       rep/one
+Please, commit your changes or stash them before you can switch branches.
+EOF
+
+test_expect_success 'not uptodate file porcelain checkout error' '
+       git add rep/one rep/two &&
+       test_must_fail git checkout branch 2>out &&
+       test_cmp out expect
+'
+
+cat >expect <<\EOF
+error: Updating the following directories would lose untracked files in it:
+       rep2
+       rep
+
+EOF
+
+test_expect_success 'not_uptodate_dir porcelain checkout error' '
+       git init uptodate &&
+       cd uptodate &&
+       mkdir rep &&
+       mkdir rep2 &&
+       touch rep/foo &&
+       touch rep2/foo &&
+       git add rep/foo rep2/foo &&
+       git commit -m init &&
+       git checkout -b branch &&
+       git rm rep -r &&
+       git rm rep2 -r &&
+       >rep &&
+       >rep2 &&
+       git add rep rep2&&
+       git commit -m "added test as a file" &&
+       git checkout master &&
+       >rep/untracked-file &&
+       >rep2/untracked-file &&
+       test_must_fail git checkout branch 2>out &&
+       test_cmp out ../expect
+'
+
+test_done
index e768c3eb2d48a9af2fc92729a6c20126f67ec866..3bd74042ef46ee8b4e4477d1c23ba0914963de33 100755 (executable)
@@ -14,6 +14,7 @@ Testing basic merge tool invocation'
 # running mergetool
 
 test_expect_success 'setup' '
+    git config rerere.enabled true &&
     echo master >file1 &&
     mkdir subdir &&
     echo master sub >subdir/file3 &&
@@ -67,23 +68,47 @@ test_expect_success 'mergetool crlf' '
 '
 
 test_expect_success 'mergetool in subdir' '
-    git checkout -b test3 branch1
-    cd subdir && (
-    test_must_fail git merge master >/dev/null 2>&1 &&
-    ( yes "" | git mergetool file3 >/dev/null 2>&1 ) &&
-    test "$(cat file3)" = "master new sub" )
+    git checkout -b test3 branch1 &&
+    (
+       cd subdir &&
+       test_must_fail git merge master >/dev/null 2>&1 &&
+       ( yes "" | git mergetool file3 >/dev/null 2>&1 ) &&
+       test "$(cat file3)" = "master new sub"
+    )
 '
 
-# We can't merge files from parent directories when running mergetool
-# from a subdir. Is this a bug?
-#
-#test_expect_failure 'mergetool in subdir' '
-#    cd subdir && (
-#    ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) &&
-#    ( yes "" | git mergetool ../file2 >/dev/null 2>&1 ) &&
-#    test "$(cat ../file1)" = "master updated" &&
-#    test "$(cat ../file2)" = "master new" &&
-#    git commit -m "branch1 resolved with mergetool - subdir" )
-#'
+test_expect_success 'mergetool on file in parent dir' '
+    (
+       cd subdir &&
+       ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) &&
+       ( yes "" | git mergetool ../file2 >/dev/null 2>&1 ) &&
+       test "$(cat ../file1)" = "master updated" &&
+       test "$(cat ../file2)" = "master new" &&
+       git commit -m "branch1 resolved with mergetool - subdir"
+    )
+'
+
+test_expect_success 'mergetool skips autoresolved' '
+    git checkout -b test4 branch1 &&
+    test_must_fail git merge master &&
+    test -n "$(git ls-files -u)" &&
+    output="$(git mergetool --no-prompt)" &&
+    test "$output" = "No files need merging" &&
+    git reset --hard
+'
+
+test_expect_success 'mergetool merges all from subdir' '
+    (
+       cd subdir &&
+       git config rerere.enabled false &&
+       test_must_fail git merge master &&
+       git mergetool --no-prompt &&
+       test "$(cat ../file1)" = "master updated" &&
+       test "$(cat ../file2)" = "master new" &&
+       test "$(cat file3)" = "master new sub" &&
+       git add ../file1 ../file2 file3 &&
+       git commit -m "branch2 resolved by mergetool from subdir"
+    )
+'
 
 test_done
index 87c9b0e121db6c8ab7074e6c4968cd06d37851fe..c2f66ff1703c35d5f5b92285e31e8df157e51642 100755 (executable)
@@ -8,6 +8,7 @@ test_expect_success 'objects in packs marked .keep are not repacked' '
        echo content1 > file1 &&
        echo content2 > file2 &&
        git add . &&
+       test_tick &&
        git commit -m initial_commit &&
        # Create two packs
        # The first pack will contain all of the objects except one
@@ -40,6 +41,7 @@ test_expect_success 'loose objects in alternate ODB are not repacked' '
        echo content3 > file3 &&
        objsha1=$(GIT_OBJECT_DIRECTORY=alt_objects git hash-object -w file3) &&
        git add file3 &&
+       test_tick &&
        git commit -m commit_file3 &&
        git repack -a -d -l &&
        git prune-packed &&
@@ -73,6 +75,7 @@ test_expect_success 'packed obs in alt ODB are repacked when local repo has pack
        rm -f .git/objects/pack/* &&
        echo new_content >> file1 &&
        git add file1 &&
+       test_tick &&
        git commit -m more_content &&
        git repack &&
        git repack -a -d &&
@@ -118,8 +121,8 @@ test_expect_success 'packed unreachable obs in alternate ODB are not loosened' '
        mv .git/objects/pack/* alt_objects/pack/ &&
        csha1=$(git rev-parse HEAD^{commit}) &&
        git reset --hard HEAD^ &&
-       sleep 1 &&
-       git reflog expire --expire=now --expire-unreachable=now --all &&
+       test_tick &&
+       git reflog expire --expire=$test_tick --expire-unreachable=$test_tick --all &&
        # The pack-objects call on the next line is equivalent to
        # git repack -A -d without the call to prune-packed
        git pack-objects --honor-pack-keep --non-empty --all --reflog \
@@ -149,5 +152,17 @@ test_expect_success 'local packed unreachable obs that exist in alternate ODB ar
        test_must_fail git show $csha1
 '
 
+test_expect_success 'objects made unreachable by grafts only are kept' '
+       test_tick &&
+       git commit --allow-empty -m "commit 4" &&
+       H0=$(git rev-parse HEAD) &&
+       H1=$(git rev-parse HEAD^) &&
+       H2=$(git rev-parse HEAD^^) &&
+       echo "$H0 $H2" > .git/info/grafts &&
+       git reflog expire --expire=$test_tick --expire-unreachable=$test_tick --all &&
+       git repack -a -d &&
+       git cat-file -t $H1
+       '
+
 test_done
 
index 5babdf26e625933268b911cc6e81f6a448f7f78d..200ab61278643e8b9deb4f624a95378e7e4c5b67 100755 (executable)
@@ -11,17 +11,20 @@ tsha1=
 test_expect_success '-A with -d option leaves unreachable objects unpacked' '
        echo content > file1 &&
        git add . &&
+       test_tick &&
        git commit -m initial_commit &&
        # create a transient branch with unique content
        git checkout -b transient_branch &&
        echo more content >> file1 &&
        # record the objects created in the database for file, commit, tree
        fsha1=$(git hash-object file1) &&
+       test_tick &&
        git commit -a -m more_content &&
        csha1=$(git rev-parse HEAD^{commit}) &&
        tsha1=$(git rev-parse HEAD^{tree}) &&
        git checkout master &&
        echo even more content >> file1 &&
+       test_tick &&
        git commit -a -m even_more_content &&
        # delete the transient branch
        git branch -D transient_branch &&
@@ -34,9 +37,11 @@ test_expect_success '-A with -d option leaves unreachable objects unpacked' '
        git show $fsha1 &&
        git show $csha1 &&
        git show $tsha1 &&
-       # now expire the reflog
-       sleep 1 &&
-       git reflog expire --expire-unreachable=now --all &&
+       # now expire the reflog, while keeping reachable ones but expiring
+       # unreachables immediately
+       test_tick &&
+       sometimeago=$(( $test_tick - 10000 )) &&
+       git reflog expire --expire=$sometimeago --expire-unreachable=$test_tick --all &&
        # and repack
        git repack -A -d -l &&
        # verify objects are retained unpacked
@@ -71,7 +76,7 @@ test_expect_success '-A without -d option leaves unreachable objects packed' '
        test 1 = $(ls -1 .git/objects/pack/pack-*.pack | wc -l) &&
        packfile=$(ls .git/objects/pack/pack-*.pack) &&
        git branch -D transient_branch &&
-       sleep 1 &&
+       test_tick &&
        git repack -A -l &&
        test ! -f "$fsha1path" &&
        test ! -f "$csha1path" &&
index ebdccf9a1eb9f9bb4918fc5adee8bdb8fd682608..58dc6f6452e69df2f91799b0ae23c620783311da 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 #
-# Copyright (c) 2009 David Aguilar
+# Copyright (c) 2009, 2010 David Aguilar
 #
 
 test_description='git-difftool
@@ -10,19 +10,19 @@ Testing basic diff tool invocation
 
 . ./test-lib.sh
 
-if ! test_have_prereq PERL; then
-       say 'skipping difftool tests, perl not available'
-       test_done
-fi
+LF='
+'
 
 remove_config_vars()
 {
        # Unset all config variables used by git-difftool
        git config --unset diff.tool
+       git config --unset diff.guitool
        git config --unset difftool.test-tool.cmd
        git config --unset difftool.prompt
        git config --unset merge.tool
        git config --unset mergetool.test-tool.cmd
+       git config --unset mergetool.prompt
        return 0
 }
 
@@ -31,11 +31,11 @@ restore_test_defaults()
        # Restores the test defaults used by several tests
        remove_config_vars
        unset GIT_DIFF_TOOL
-       unset GIT_MERGE_TOOL
        unset GIT_DIFFTOOL_PROMPT
        unset GIT_DIFFTOOL_NO_PROMPT
        git config diff.tool test-tool &&
        git config difftool.test-tool.cmd 'cat $LOCAL'
+       git config difftool.bogus-tool.cmd false
 }
 
 prompt_given()
@@ -45,7 +45,7 @@ prompt_given()
 }
 
 # Create a file on master and change it on branch
-test_expect_success 'setup' '
+test_expect_success PERL 'setup' '
        echo master >file &&
        git add file &&
        git commit -m "added file" &&
@@ -57,7 +57,7 @@ test_expect_success 'setup' '
 '
 
 # Configure a custom difftool.<tool>.cmd and use it
-test_expect_success 'custom commands' '
+test_expect_success PERL 'custom commands' '
        restore_test_defaults &&
        git config difftool.test-tool.cmd "cat \$REMOTE" &&
 
@@ -70,14 +70,34 @@ test_expect_success 'custom commands' '
 '
 
 # Ensures that git-difftool ignores bogus --tool values
-test_expect_success 'difftool ignores bad --tool values' '
-       diff=$(git difftool --no-prompt --tool=bogus-tool branch)
+test_expect_success PERL 'difftool ignores bad --tool values' '
+       diff=$(git difftool --no-prompt --tool=bad-tool branch)
        test "$?" = 1 &&
        test "$diff" = ""
 '
 
+test_expect_success PERL 'difftool honors --gui' '
+       git config merge.tool bogus-tool &&
+       git config diff.tool bogus-tool &&
+       git config diff.guitool test-tool &&
+
+       diff=$(git difftool --no-prompt --gui branch) &&
+       test "$diff" = "branch" &&
+
+       restore_test_defaults
+'
+
+test_expect_success PERL 'difftool --gui works without configured diff.guitool' '
+       git config diff.tool test-tool &&
+
+       diff=$(git difftool --no-prompt --gui branch) &&
+       test "$diff" = "branch" &&
+
+       restore_test_defaults
+'
+
 # Specify the diff tool using $GIT_DIFF_TOOL
-test_expect_success 'GIT_DIFF_TOOL variable' '
+test_expect_success PERL 'GIT_DIFF_TOOL variable' '
        git config --unset diff.tool
        GIT_DIFF_TOOL=test-tool &&
        export GIT_DIFF_TOOL &&
@@ -90,19 +110,11 @@ test_expect_success 'GIT_DIFF_TOOL variable' '
 
 # Test the $GIT_*_TOOL variables and ensure
 # that $GIT_DIFF_TOOL always wins unless --tool is specified
-test_expect_success 'GIT_DIFF_TOOL overrides' '
+test_expect_success PERL 'GIT_DIFF_TOOL overrides' '
        git config diff.tool bogus-tool &&
        git config merge.tool bogus-tool &&
 
-       GIT_MERGE_TOOL=test-tool &&
-       export GIT_MERGE_TOOL &&
-       diff=$(git difftool --no-prompt branch) &&
-       test "$diff" = "branch" &&
-       unset GIT_MERGE_TOOL &&
-
-       GIT_MERGE_TOOL=bogus-tool &&
        GIT_DIFF_TOOL=test-tool &&
-       export GIT_MERGE_TOOL &&
        export GIT_DIFF_TOOL &&
 
        diff=$(git difftool --no-prompt branch) &&
@@ -119,7 +131,7 @@ test_expect_success 'GIT_DIFF_TOOL overrides' '
 
 # Test that we don't have to pass --no-prompt to difftool
 # when $GIT_DIFFTOOL_NO_PROMPT is true
-test_expect_success 'GIT_DIFFTOOL_NO_PROMPT variable' '
+test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' '
        GIT_DIFFTOOL_NO_PROMPT=true &&
        export GIT_DIFFTOOL_NO_PROMPT &&
 
@@ -131,19 +143,19 @@ test_expect_success 'GIT_DIFFTOOL_NO_PROMPT variable' '
 
 # git-difftool supports the difftool.prompt variable.
 # Test that GIT_DIFFTOOL_PROMPT can override difftool.prompt = false
-test_expect_success 'GIT_DIFFTOOL_PROMPT variable' '
+test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' '
        git config difftool.prompt false &&
        GIT_DIFFTOOL_PROMPT=true &&
        export GIT_DIFFTOOL_PROMPT &&
 
-       prompt=$(echo | git difftool --prompt branch | tail -1) &&
+       prompt=$(echo | git difftool branch | tail -1) &&
        prompt_given "$prompt" &&
 
        restore_test_defaults
 '
 
 # Test that we don't have to pass --no-prompt when difftool.prompt is false
-test_expect_success 'difftool.prompt config variable is false' '
+test_expect_success PERL 'difftool.prompt config variable is false' '
        git config difftool.prompt false &&
 
        diff=$(git difftool branch) &&
@@ -152,8 +164,19 @@ test_expect_success 'difftool.prompt config variable is false' '
        restore_test_defaults
 '
 
+# Test that we don't have to pass --no-prompt when mergetool.prompt is false
+test_expect_success PERL 'difftool merge.prompt = false' '
+       git config --unset difftool.prompt
+       git config mergetool.prompt false &&
+
+       diff=$(git difftool branch) &&
+       test "$diff" = "branch" &&
+
+       restore_test_defaults
+'
+
 # Test that the -y flag can override difftool.prompt = true
-test_expect_success 'difftool.prompt can overridden with -y' '
+test_expect_success PERL 'difftool.prompt can overridden with -y' '
        git config difftool.prompt true &&
 
        diff=$(git difftool -y branch) &&
@@ -163,7 +186,7 @@ test_expect_success 'difftool.prompt can overridden with -y' '
 '
 
 # Test that the --prompt flag can override difftool.prompt = false
-test_expect_success 'difftool.prompt can overridden with --prompt' '
+test_expect_success PERL 'difftool.prompt can overridden with --prompt' '
        git config difftool.prompt false &&
 
        prompt=$(echo | git difftool --prompt branch | tail -1) &&
@@ -173,7 +196,7 @@ test_expect_success 'difftool.prompt can overridden with --prompt' '
 '
 
 # Test that the last flag passed on the command-line wins
-test_expect_success 'difftool last flag wins' '
+test_expect_success PERL 'difftool last flag wins' '
        diff=$(git difftool --prompt --no-prompt branch) &&
        test "$diff" = "branch" &&
 
@@ -187,7 +210,7 @@ test_expect_success 'difftool last flag wins' '
 
 # git-difftool falls back to git-mergetool config variables
 # so test that behavior here
-test_expect_success 'difftool + mergetool config variables' '
+test_expect_success PERL 'difftool + mergetool config variables' '
        remove_config_vars
        git config merge.tool test-tool &&
        git config mergetool.test-tool.cmd "cat \$LOCAL" &&
@@ -205,12 +228,44 @@ test_expect_success 'difftool + mergetool config variables' '
        restore_test_defaults
 '
 
-test_expect_success 'difftool.<tool>.path' '
+test_expect_success PERL 'difftool.<tool>.path' '
        git config difftool.tkdiff.path echo &&
        diff=$(git difftool --tool=tkdiff --no-prompt branch) &&
        git config --unset difftool.tkdiff.path &&
        lines=$(echo "$diff" | grep file | wc -l) &&
-       test "$lines" -eq 1
+       test "$lines" -eq 1 &&
+
+       restore_test_defaults
+'
+
+test_expect_success PERL 'difftool --extcmd=cat' '
+       diff=$(git difftool --no-prompt --extcmd=cat branch) &&
+       test "$diff" = branch"$LF"master
+'
+
+test_expect_success PERL 'difftool --extcmd cat' '
+       diff=$(git difftool --no-prompt --extcmd cat branch) &&
+       test "$diff" = branch"$LF"master
+'
+
+test_expect_success PERL 'difftool -x cat' '
+       diff=$(git difftool --no-prompt -x cat branch) &&
+       test "$diff" = branch"$LF"master
+'
+
+test_expect_success PERL 'difftool --extcmd echo arg1' '
+       diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"echo\ \$1\" branch)
+       test "$diff" = file
+'
+
+test_expect_success PERL 'difftool --extcmd cat arg1' '
+       diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$1\" branch)
+       test "$diff" = master
+'
+
+test_expect_success PERL 'difftool --extcmd cat arg2' '
+       diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$2\" branch)
+       test "$diff" = branch
 '
 
 test_done
diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh
new file mode 100755 (executable)
index 0000000..023f225
--- /dev/null
@@ -0,0 +1,530 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+test_description='git grep various.
+'
+
+. ./test-lib.sh
+
+cat >hello.c <<EOF
+#include <stdio.h>
+int main(int argc, const char **argv)
+{
+       printf("Hello world.\n");
+       return 0;
+       /* char ?? */
+}
+EOF
+
+test_expect_success setup '
+       {
+               echo foo mmap bar
+               echo foo_mmap bar
+               echo foo_mmap bar mmap
+               echo foo mmap bar_mmap
+               echo foo_mmap bar mmap baz
+       } >file &&
+       echo vvv >v &&
+       echo ww w >w &&
+       echo x x xx x >x &&
+       echo y yy >y &&
+       echo zzz > z &&
+       mkdir t &&
+       echo test >t/t &&
+       echo vvv >t/v &&
+       mkdir t/a &&
+       echo vvv >t/a/v &&
+       git add . &&
+       test_tick &&
+       git commit -m initial
+'
+
+test_expect_success 'grep should not segfault with a bad input' '
+       test_must_fail git grep "("
+'
+
+for H in HEAD ''
+do
+       case "$H" in
+       HEAD)   HC='HEAD:' L='HEAD' ;;
+       '')     HC= L='in working tree' ;;
+       esac
+
+       test_expect_success "grep -w $L" '
+               {
+                       echo ${HC}file:1:foo mmap bar
+                       echo ${HC}file:3:foo_mmap bar mmap
+                       echo ${HC}file:4:foo mmap bar_mmap
+                       echo ${HC}file:5:foo_mmap bar mmap baz
+               } >expected &&
+               git grep -n -w -e mmap $H >actual &&
+               test_cmp expected actual
+       '
+
+       test_expect_success "grep -w $L (w)" '
+               : >expected &&
+               test_must_fail git grep -n -w -e "^w" >actual &&
+               test_cmp expected actual
+       '
+
+       test_expect_success "grep -w $L (x)" '
+               {
+                       echo ${HC}x:1:x x xx x
+               } >expected &&
+               git grep -n -w -e "x xx* x" $H >actual &&
+               test_cmp expected actual
+       '
+
+       test_expect_success "grep -w $L (y-1)" '
+               {
+                       echo ${HC}y:1:y yy
+               } >expected &&
+               git grep -n -w -e "^y" $H >actual &&
+               test_cmp expected actual
+       '
+
+       test_expect_success "grep -w $L (y-2)" '
+               : >expected &&
+               if git grep -n -w -e "^y y" $H >actual
+               then
+                       echo should not have matched
+                       cat actual
+                       false
+               else
+                       test_cmp expected actual
+               fi
+       '
+
+       test_expect_success "grep -w $L (z)" '
+               : >expected &&
+               if git grep -n -w -e "^z" $H >actual
+               then
+                       echo should not have matched
+                       cat actual
+                       false
+               else
+                       test_cmp expected actual
+               fi
+       '
+
+       test_expect_success "grep $L (t-1)" '
+               echo "${HC}t/t:1:test" >expected &&
+               git grep -n -e test $H >actual &&
+               test_cmp expected actual
+       '
+
+       test_expect_success "grep $L (t-2)" '
+               echo "${HC}t:1:test" >expected &&
+               (
+                       cd t &&
+                       git grep -n -e test $H
+               ) >actual &&
+               test_cmp expected actual
+       '
+
+       test_expect_success "grep $L (t-3)" '
+               echo "${HC}t/t:1:test" >expected &&
+               (
+                       cd t &&
+                       git grep --full-name -n -e test $H
+               ) >actual &&
+               test_cmp expected actual
+       '
+
+       test_expect_success "grep -c $L (no /dev/null)" '
+               ! git grep -c test $H | grep /dev/null
+        '
+
+       test_expect_success "grep --max-depth -1 $L" '
+               {
+                       echo ${HC}t/a/v:1:vvv
+                       echo ${HC}t/v:1:vvv
+                       echo ${HC}v:1:vvv
+               } >expected &&
+               git grep --max-depth -1 -n -e vvv $H >actual &&
+               test_cmp expected actual
+       '
+
+       test_expect_success "grep --max-depth 0 $L" '
+               {
+                       echo ${HC}v:1:vvv
+               } >expected &&
+               git grep --max-depth 0 -n -e vvv $H >actual &&
+               test_cmp expected actual
+       '
+
+       test_expect_success "grep --max-depth 0 -- '*' $L" '
+               {
+                       echo ${HC}t/a/v:1:vvv
+                       echo ${HC}t/v:1:vvv
+                       echo ${HC}v:1:vvv
+               } >expected &&
+               git grep --max-depth 0 -n -e vvv $H -- "*" >actual &&
+               test_cmp expected actual
+       '
+
+       test_expect_success "grep --max-depth 1 $L" '
+               {
+                       echo ${HC}t/v:1:vvv
+                       echo ${HC}v:1:vvv
+               } >expected &&
+               git grep --max-depth 1 -n -e vvv $H >actual &&
+               test_cmp expected actual
+       '
+
+       test_expect_success "grep --max-depth 0 -- t $L" '
+               {
+                       echo ${HC}t/v:1:vvv
+               } >expected &&
+               git grep --max-depth 0 -n -e vvv $H -- t >actual &&
+               test_cmp expected actual
+       '
+
+done
+
+cat >expected <<EOF
+file:foo mmap bar_mmap
+EOF
+
+test_expect_success 'grep -e A --and -e B' '
+       git grep -e "foo mmap" --and -e bar_mmap >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+file:foo_mmap bar mmap
+file:foo_mmap bar mmap baz
+EOF
+
+
+test_expect_success 'grep ( -e A --or -e B ) --and -e B' '
+       git grep \( -e foo_ --or -e baz \) \
+               --and -e " mmap" >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+file:foo mmap bar
+EOF
+
+test_expect_success 'grep -e A --and --not -e B' '
+       git grep -e "foo mmap" --and --not -e bar_mmap >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'grep should ignore GREP_OPTIONS' '
+       GREP_OPTIONS=-v git grep " mmap bar\$" >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'grep -f, non-existent file' '
+       test_must_fail git grep -f patterns
+'
+
+cat >expected <<EOF
+file:foo mmap bar
+file:foo_mmap bar
+file:foo_mmap bar mmap
+file:foo mmap bar_mmap
+file:foo_mmap bar mmap baz
+EOF
+
+cat >pattern <<EOF
+mmap
+EOF
+
+test_expect_success 'grep -f, one pattern' '
+       git grep -f pattern >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+file:foo mmap bar
+file:foo_mmap bar
+file:foo_mmap bar mmap
+file:foo mmap bar_mmap
+file:foo_mmap bar mmap baz
+t/a/v:vvv
+t/v:vvv
+v:vvv
+EOF
+
+cat >patterns <<EOF
+mmap
+vvv
+EOF
+
+test_expect_success 'grep -f, multiple patterns' '
+       git grep -f patterns >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+file:foo mmap bar
+file:foo_mmap bar
+file:foo_mmap bar mmap
+file:foo mmap bar_mmap
+file:foo_mmap bar mmap baz
+t/a/v:vvv
+t/v:vvv
+v:vvv
+EOF
+
+cat >patterns <<EOF
+
+mmap
+
+vvv
+
+EOF
+
+test_expect_success 'grep -f, ignore empty lines' '
+       git grep -f patterns >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+y:y yy
+--
+z:zzz
+EOF
+
+test_expect_success 'grep -q, silently report matches' '
+       >empty &&
+       git grep -q mmap >actual &&
+       test_cmp empty actual &&
+       test_must_fail git grep -q qfwfq >actual &&
+       test_cmp empty actual
+'
+
+# Create 1024 file names that sort between "y" and "z" to make sure
+# the two files are handled by different calls to an external grep.
+# This depends on MAXARGS in builtin-grep.c being 1024 or less.
+c32="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"
+test_expect_success 'grep -C1, hunk mark between files' '
+       for a in $c32; do for b in $c32; do : >y-$a$b; done; done &&
+       git add y-?? &&
+       git grep -C1 "^[yz]" >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'grep -C1 hunk mark between files' '
+       git grep -C1 "^[yz]" >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'log grep setup' '
+       echo a >>file &&
+       test_tick &&
+       GIT_AUTHOR_NAME="With * Asterisk" \
+       GIT_AUTHOR_EMAIL="xyzzy@frotz.com" \
+       git commit -a -m "second" &&
+
+       echo a >>file &&
+       test_tick &&
+       git commit -a -m "third"
+
+'
+
+test_expect_success 'log grep (1)' '
+       git log --author=author --pretty=tformat:%s >actual &&
+       ( echo third ; echo initial ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log grep (2)' '
+       git log --author=" * " -F --pretty=tformat:%s >actual &&
+       ( echo second ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log grep (3)' '
+       git log --author="^A U" --pretty=tformat:%s >actual &&
+       ( echo third ; echo initial ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log grep (4)' '
+       git log --author="frotz\.com>$" --pretty=tformat:%s >actual &&
+       ( echo second ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log grep (5)' '
+       git log --author=Thor -F --pretty=tformat:%s >actual &&
+       ( echo third ; echo initial ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log grep (6)' '
+       git log --author=-0700  --pretty=tformat:%s >actual &&
+       >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log --grep --author implicitly uses all-match' '
+       # grep matches initial and second but not third
+       # author matches only initial and third
+       git log --author="A U Thor" --grep=s --grep=l --format=%s >actual &&
+       echo initial >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'grep with CE_VALID file' '
+       git update-index --assume-unchanged t/t &&
+       rm t/t &&
+       test "$(git grep test)" = "t/t:test" &&
+       git update-index --no-assume-unchanged t/t &&
+       git checkout t/t
+'
+
+cat >expected <<EOF
+hello.c=#include <stdio.h>
+hello.c:       return 0;
+EOF
+
+test_expect_success 'grep -p with userdiff' '
+       git config diff.custom.funcname "^#" &&
+       echo "hello.c diff=custom" >.gitattributes &&
+       git grep -p return >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+hello.c=int main(int argc, const char **argv)
+hello.c:       return 0;
+EOF
+
+test_expect_success 'grep -p' '
+       rm -f .gitattributes &&
+       git grep -p return >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+hello.c-#include <stdio.h>
+hello.c=int main(int argc, const char **argv)
+hello.c-{
+hello.c-       printf("Hello world.\n");
+hello.c:       return 0;
+EOF
+
+test_expect_success 'grep -p -B5' '
+       git grep -p -B5 return >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'grep from a subdirectory to search wider area (1)' '
+       mkdir -p s &&
+       (
+               cd s && git grep "x x x" ..
+       )
+'
+
+test_expect_success 'grep from a subdirectory to search wider area (2)' '
+       mkdir -p s &&
+       (
+               cd s || exit 1
+               ( git grep xxyyzz .. >out ; echo $? >status )
+               ! test -s out &&
+               test 1 = $(cat status)
+       )
+'
+
+cat >expected <<EOF
+hello.c:int main(int argc, const char **argv)
+EOF
+
+test_expect_success 'grep -Fi' '
+       git grep -Fi "CHAR *" >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'outside of git repository' '
+       rm -fr non &&
+       mkdir -p non/git/sub &&
+       echo hello >non/git/file1 &&
+       echo world >non/git/sub/file2 &&
+       echo ".*o*" >non/git/.gitignore &&
+       {
+               echo file1:hello &&
+               echo sub/file2:world
+       } >non/expect.full &&
+       echo file2:world >non/expect.sub
+       (
+               GIT_CEILING_DIRECTORIES="$(pwd)/non/git" &&
+               export GIT_CEILING_DIRECTORIES &&
+               cd non/git &&
+               test_must_fail git grep o &&
+               git grep --no-index o >../actual.full &&
+               test_cmp ../expect.full ../actual.full
+               cd sub &&
+               test_must_fail git grep o &&
+               git grep --no-index o >../../actual.sub &&
+               test_cmp ../../expect.sub ../../actual.sub
+       )
+'
+
+test_expect_success 'inside git repository but with --no-index' '
+       rm -fr is &&
+       mkdir -p is/git/sub &&
+       echo hello >is/git/file1 &&
+       echo world >is/git/sub/file2 &&
+       echo ".*o*" >is/git/.gitignore &&
+       {
+               echo file1:hello &&
+               echo sub/file2:world
+       } >is/expect.full &&
+       : >is/expect.empty &&
+       echo file2:world >is/expect.sub
+       (
+               cd is/git &&
+               git init &&
+               test_must_fail git grep o >../actual.full &&
+               test_cmp ../expect.empty ../actual.full &&
+               git grep --no-index o >../actual.full &&
+               test_cmp ../expect.full ../actual.full &&
+               cd sub &&
+               test_must_fail git grep o >../../actual.sub &&
+               test_cmp ../../expect.empty ../../actual.sub &&
+               git grep --no-index o >../../actual.sub &&
+               test_cmp ../../expect.sub ../../actual.sub
+       )
+'
+
+test_expect_success 'setup double-dash tests' '
+cat >double-dash <<EOF &&
+--
+->
+other
+EOF
+git add double-dash
+'
+
+cat >expected <<EOF
+double-dash:->
+EOF
+test_expect_success 'grep -- pattern' '
+       git grep -- "->" >actual &&
+       test_cmp expected actual
+'
+test_expect_success 'grep -- pattern -- pathspec' '
+       git grep -- "->" -- double-dash >actual &&
+       test_cmp expected actual
+'
+test_expect_success 'grep -e pattern -- path' '
+       git grep -e "->" -- double-dash >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+double-dash:--
+EOF
+test_expect_success 'grep -e -- -- path' '
+       git grep -e -- -- double-dash >actual &&
+       test_cmp expected actual
+'
+
+test_done
diff --git a/t/t7811-grep-open.sh b/t/t7811-grep-open.sh
new file mode 100755 (executable)
index 0000000..568a6f2
--- /dev/null
@@ -0,0 +1,168 @@
+#!/bin/sh
+
+test_description='git grep --open-files-in-pager
+'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-pager.sh
+unset PAGER GIT_PAGER
+
+test_expect_success 'setup' '
+       test_commit initial grep.h "
+enum grep_pat_token {
+       GREP_PATTERN,
+       GREP_PATTERN_HEAD,
+       GREP_PATTERN_BODY,
+       GREP_AND,
+       GREP_OPEN_PAREN,
+       GREP_CLOSE_PAREN,
+       GREP_NOT,
+       GREP_OR,
+};" &&
+
+       test_commit add-user revision.c "
+       }
+       if (seen_dashdash)
+               read_pathspec_from_stdin(revs, &sb, prune);
+       strbuf_release(&sb);
+}
+
+static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what)
+{
+       append_grep_pattern(&revs->grep_filter, ptn, \"command line\", 0, what);
+" &&
+
+       mkdir subdir &&
+       test_commit subdir subdir/grep.c "enum grep_pat_token" &&
+
+       test_commit uninteresting unrelated "hello, world" &&
+
+       echo GREP_PATTERN >untracked
+'
+
+test_expect_success SIMPLEPAGER 'git grep -O' '
+       cat >$less <<-\EOF &&
+       #!/bin/sh
+       printf "%s\n" "$@" >pager-args
+       EOF
+       chmod +x $less &&
+       cat >expect.less <<-\EOF &&
+       +/*GREP_PATTERN
+       grep.h
+       EOF
+       echo grep.h >expect.notless &&
+       >empty &&
+
+       PATH=.:$PATH git grep -O GREP_PATTERN >out &&
+       {
+               test_cmp expect.less pager-args ||
+               test_cmp expect.notless pager-args
+       } &&
+       test_cmp empty out
+'
+
+test_expect_success 'git grep -O --cached' '
+       test_must_fail git grep --cached -O GREP_PATTERN >out 2>msg &&
+       grep open-files-in-pager msg
+'
+
+test_expect_success 'git grep -O --no-index' '
+       rm -f expect.less pager-args out &&
+       cat >expect <<-\EOF &&
+       grep.h
+       untracked
+       EOF
+       >empty &&
+
+       (
+               GIT_PAGER='\''printf "%s\n" >pager-args'\'' &&
+               export GIT_PAGER &&
+               git grep --no-index -O GREP_PATTERN >out
+       ) &&
+       test_cmp expect pager-args &&
+       test_cmp empty out
+'
+
+test_expect_success 'setup: fake "less"' '
+       cat >less <<-\EOF &&
+       #!/bin/sh
+       printf "%s\n" "$@" >actual
+       EOF
+       chmod +x less
+'
+
+test_expect_success 'git grep -O jumps to line in less' '
+       cat >expect <<-\EOF &&
+       +/*GREP_PATTERN
+       grep.h
+       EOF
+       >empty &&
+
+       GIT_PAGER=./less git grep -O GREP_PATTERN >out &&
+       test_cmp expect actual &&
+       test_cmp empty out &&
+
+       git grep -O./less GREP_PATTERN >out2 &&
+       test_cmp expect actual &&
+       test_cmp empty out2
+'
+
+test_expect_success 'modified file' '
+       rm -f actual &&
+       cat >expect <<-\EOF &&
+       +/*enum grep_pat_token
+       grep.h
+       revision.c
+       subdir/grep.c
+       unrelated
+       EOF
+       >empty &&
+
+       echo "enum grep_pat_token" >unrelated &&
+       test_when_finished "git checkout HEAD unrelated" &&
+       GIT_PAGER=./less git grep -F -O "enum grep_pat_token" >out &&
+       test_cmp expect actual &&
+       test_cmp empty out
+'
+
+test_config() {
+       git config "$1" "$2" &&
+       test_when_finished "git config --unset $1"
+}
+
+test_expect_success 'copes with color settings' '
+       rm -f actual &&
+       echo grep.h >expect &&
+       test_config color.grep always &&
+       test_config color.grep.filename yellow &&
+       test_config color.grep.separator green &&
+       git grep -O'\''printf "%s\n" >actual'\'' GREP_AND &&
+       test_cmp expect actual
+'
+
+test_expect_success 'run from subdir' '
+       rm -f actual &&
+       echo grep.c >expect &&
+       >empty &&
+
+       (
+               cd subdir &&
+               export GIT_PAGER &&
+               GIT_PAGER='\''printf "%s\n" >../args'\'' &&
+               git grep -O "enum grep_pat_token" >../out &&
+               git grep -O"pwd >../dir; :" "enum grep_pat_token" >../out2
+       ) &&
+       case $(cat dir) in
+       *subdir)
+               : good
+               ;;
+       *)
+               false
+               ;;
+       esac &&
+       test_cmp expect args &&
+       test_cmp empty out &&
+       test_cmp empty out2
+'
+
+test_done
index 966bb0a61a89ed63dec085338d3c45f766a7f777..230143cf318705fb01e61f10072a096e86186934 100755 (executable)
@@ -11,7 +11,15 @@ test_expect_success setup '
        echo B B B B B >two &&
        echo C C C C C >tres &&
        echo ABC >mouse &&
-       git add one two tres mouse &&
+       for i in 1 2 3 4 5 6 7 8 9
+       do
+               echo $i
+       done >nine_lines &&
+       for i in 1 2 3 4 5 6 7 8 9 a
+       do
+               echo $i
+       done >ten_lines &&
+       git add one two tres mouse nine_lines ten_lines &&
        test_tick &&
        GIT_AUTHOR_NAME=Initial git commit -m Initial &&
 
@@ -129,4 +137,52 @@ test_expect_success 'blame wholesale copy and more' '
 
 '
 
+test_expect_success 'blame path that used to be a directory' '
+       mkdir path &&
+       echo A A A A A >path/file &&
+       echo B B B B B >path/elif &&
+       git add path &&
+       test_tick &&
+       git commit -m "path was a directory" &&
+       rm -fr path &&
+       echo A A A A A >path &&
+       git add path &&
+       test_tick &&
+       git commit -m "path is a regular file" &&
+       git blame HEAD^.. -- path
+'
+
+test_expect_success 'blame to a commit with no author name' '
+  TREE=`git rev-parse HEAD:`
+  cat >badcommit <<EOF
+tree $TREE
+author <noname> 1234567890 +0000
+committer David Reiss <dreiss@facebook.com> 1234567890 +0000
+
+some message
+EOF
+  COMMIT=`git hash-object -t commit -w badcommit`
+  git --no-pager blame $COMMIT -- uno >/dev/null
+'
+
+test_expect_success 'blame -L with invalid start' '
+       test_must_fail git blame -L5 tres 2>errors &&
+       grep "has only 2 lines" errors
+'
+
+test_expect_success 'blame -L with invalid end' '
+       test_must_fail git blame -L1,5 tres 2>errors &&
+       grep "has only 2 lines" errors
+'
+
+test_expect_success 'indent of line numbers, nine lines' '
+       git blame nine_lines >actual &&
+       test $(grep -c "  " actual) = 0
+'
+
+test_expect_success 'indent of line numbers, ten lines' '
+       git blame ten_lines >actual &&
+       test $(grep -c "  " actual) = 9
+'
+
 test_done
index fcd5c266754a2b5de3dbbf94c8ef25aebff061f2..cb390559f947fe3cd89a99b9bd473d60c7083307 100755 (executable)
@@ -4,7 +4,7 @@ test_description='git blame encoding conversion'
 . ./test-lib.sh
 
 . "$TEST_DIRECTORY"/t8005/utf8.txt
-. "$TEST_DIRECTORY"/t8005/cp1251.txt
+. "$TEST_DIRECTORY"/t8005/euc-japan.txt
 . "$TEST_DIRECTORY"/t8005/sjis.txt
 
 test_expect_success 'setup the repository' '
@@ -13,14 +13,14 @@ test_expect_success 'setup the repository' '
        git add file &&
        git commit --author "$UTF8_NAME <utf8@localhost>" -m "$UTF8_MSG" &&
 
-       echo "CP1251 LINE" >> file &&
+       echo "EUC-JAPAN LINE" >> file &&
        git add file &&
-       git config i18n.commitencoding cp1251 &&
-       git commit --author "$CP1251_NAME <cp1251@localhost>" -m "$CP1251_MSG" &&
+       git config i18n.commitencoding eucJP &&
+       git commit --author "$EUC_JAPAN_NAME <euc-japan@localhost>" -m "$EUC_JAPAN_MSG" &&
 
        echo "SJIS LINE" >> file &&
        git add file &&
-       git config i18n.commitencoding shift-jis &&
+       git config i18n.commitencoding SJIS &&
        git commit --author "$SJIS_NAME <sjis@localhost>" -m "$SJIS_MSG"
 '
 
@@ -41,17 +41,17 @@ test_expect_success \
 '
 
 cat >expected <<EOF
-author $CP1251_NAME
-summary $CP1251_MSG
-author $CP1251_NAME
-summary $CP1251_MSG
-author $CP1251_NAME
-summary $CP1251_MSG
+author $EUC_JAPAN_NAME
+summary $EUC_JAPAN_MSG
+author $EUC_JAPAN_NAME
+summary $EUC_JAPAN_MSG
+author $EUC_JAPAN_NAME
+summary $EUC_JAPAN_MSG
 EOF
 
 test_expect_success \
        'blame respects i18n.logoutputencoding' '
-       git config i18n.logoutputencoding cp1251 &&
+       git config i18n.logoutputencoding eucJP &&
        git blame --incremental file | \
                egrep "^(author|summary) " > actual &&
        test_cmp actual expected
@@ -67,8 +67,8 @@ summary $UTF8_MSG
 EOF
 
 test_expect_success \
-       'blame respects --encoding=utf-8' '
-       git blame --incremental --encoding=utf-8 file | \
+       'blame respects --encoding=UTF-8' '
+       git blame --incremental --encoding=UTF-8 file | \
                egrep "^(author|summary) " > actual &&
        test_cmp actual expected
 '
@@ -76,8 +76,8 @@ test_expect_success \
 cat >expected <<EOF
 author $SJIS_NAME
 summary $SJIS_MSG
-author $CP1251_NAME
-summary $CP1251_MSG
+author $EUC_JAPAN_NAME
+summary $EUC_JAPAN_MSG
 author $UTF8_NAME
 summary $UTF8_MSG
 EOF
diff --git a/t/t8005/cp1251.txt b/t/t8005/cp1251.txt
deleted file mode 100644 (file)
index ce41e98..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-CP1251_NAME="Èâàí Ïåòðîâè÷ Ñèäîðîâ"
-CP1251_MSG="Òåñòîâîå ñîîáùåíèå"
diff --git a/t/t8005/euc-japan.txt b/t/t8005/euc-japan.txt
new file mode 100644 (file)
index 0000000..288f040
--- /dev/null
@@ -0,0 +1,2 @@
+EUC_JAPAN_NAME="»³ÅÄ ÂÀϺ"
+EUC_JAPAN_MSG="¥Ö¥ì¡¼¥à¤Î¥Æ¥¹¥È¤Ç¤¹¡£"
index 2ccfbad207c6e96b1f4f528031d9e4938d364b92..bbdefeaced4b54f98e5d9a85ddd8e0d7346fe7e3 100644 (file)
@@ -1,2 +1,2 @@
-SJIS_NAME="\84I\84r\84p\84\84P\84u\84\84\84\82\84\80\84r\84y\84\89 \84R\84y\84t\84\80\84\82\84\80\84r"
-SJIS_MSG="\84S\84u\84\83\84\84\84\80\84r\84\80\84\84\83\84\80\84\80\84q\84\8b\84u\84~\84y\84u"
+SJIS_NAME="\8eR\93\91¾\98Y"
+SJIS_MSG="\83u\83\8c\81[\83\80\82Ì\83e\83X\83g\82Å\82·\81B"
index f46cfc56d80797740c3ec15e166add052f905fcb..4d00dbea7659ee27fda283e7e45cfb2d5f6ea4d1 100644 (file)
@@ -1,2 +1,2 @@
-UTF8_NAME="Иван Петрович Сидоров"
-UTF8_MSG="Тестовое сообщение"
+UTF8_NAME="山田 太郎"
+UTF8_MSG="ブレームのテストです。"
diff --git a/t/t8006-blame-textconv.sh b/t/t8006-blame-textconv.sh
new file mode 100755 (executable)
index 0000000..9ad96d4
--- /dev/null
@@ -0,0 +1,80 @@
+#!/bin/sh
+
+test_description='git blame textconv support'
+. ./test-lib.sh
+
+find_blame() {
+       sed -e 's/^[^(]*//'
+}
+
+cat >helper <<'EOF'
+#!/bin/sh
+sed 's/^/converted: /' "$@"
+EOF
+chmod +x helper
+
+test_expect_success 'setup ' '
+       echo test 1 >one.bin &&
+       echo test number 2 >two.bin &&
+       git add . &&
+       GIT_AUTHOR_NAME=Number1 git commit -a -m First --date="2010-01-01 18:00:00" &&
+       echo test 1 version 2 >one.bin &&
+       echo test number 2 version 2 >>two.bin &&
+       GIT_AUTHOR_NAME=Number2 git commit -a -m Second --date="2010-01-01 20:00:00"
+'
+
+cat >expected <<EOF
+(Number2 2010-01-01 20:00:00 +0000 1) test 1 version 2
+EOF
+
+test_expect_success 'no filter specified' '
+       git blame one.bin >blame &&
+       find_blame Number2 <blame >result &&
+       test_cmp expected result
+'
+
+test_expect_success 'setup textconv filters' '
+       echo "*.bin diff=test" >.gitattributes &&
+       git config diff.test.textconv ./helper &&
+       git config diff.test.cachetextconv false
+'
+
+test_expect_success 'blame with --no-textconv' '
+       git blame --no-textconv one.bin >blame &&
+       find_blame <blame> result &&
+       test_cmp expected result
+'
+
+cat >expected <<EOF
+(Number2 2010-01-01 20:00:00 +0000 1) converted: test 1 version 2
+EOF
+
+test_expect_success 'basic blame on last commit' '
+       git blame one.bin >blame &&
+       find_blame  <blame >result &&
+       test_cmp expected result
+'
+
+cat >expected <<EOF
+(Number1 2010-01-01 18:00:00 +0000 1) converted: test number 2
+(Number2 2010-01-01 20:00:00 +0000 2) converted: test number 2 version 2
+EOF
+
+test_expect_success 'blame --textconv going through revisions' '
+       git blame --textconv two.bin >blame &&
+       find_blame <blame >result &&
+       test_cmp expected result
+'
+
+test_expect_success 'make a new commit' '
+       echo "test number 2 version 3" >>two.bin &&
+       GIT_AUTHOR_NAME=Number3 git commit -a -m Third --date="2010-01-01 22:00:00"
+'
+
+test_expect_success 'blame from previous revision' '
+       git blame HEAD^ two.bin >blame &&
+       find_blame <blame >result &&
+       test_cmp expected result
+'
+
+test_done
diff --git a/t/t8007-cat-file-textconv.sh b/t/t8007-cat-file-textconv.sh
new file mode 100755 (executable)
index 0000000..38ac05e
--- /dev/null
@@ -0,0 +1,70 @@
+#!/bin/sh
+
+test_description='git cat-file textconv support'
+. ./test-lib.sh
+
+cat >helper <<'EOF'
+#!/bin/sh
+sed 's/^/converted: /' "$@"
+EOF
+chmod +x helper
+
+test_expect_success 'setup ' '
+       echo test >one.bin &&
+       git add . &&
+       GIT_AUTHOR_NAME=Number1 git commit -a -m First --date="2010-01-01 18:00:00" &&
+       echo test version 2 >one.bin &&
+       GIT_AUTHOR_NAME=Number2 git commit -a -m Second --date="2010-01-01 20:00:00"
+'
+
+cat >expected <<EOF
+fatal: git cat-file --textconv: unable to run textconv on :one.bin
+EOF
+
+test_expect_success 'no filter specified' '
+       git cat-file --textconv :one.bin 2>result
+       test_cmp expected result
+'
+
+test_expect_success 'setup textconv filters' '
+       echo "*.bin diff=test" >.gitattributes &&
+       git config diff.test.textconv ./helper &&
+       git config diff.test.cachetextconv false
+'
+
+cat >expected <<EOF
+test version 2
+EOF
+
+test_expect_success 'cat-file without --textconv' '
+       git cat-file blob :one.bin >result &&
+       test_cmp expected result
+'
+
+cat >expected <<EOF
+test
+EOF
+
+test_expect_success 'cat-file without --textconv on previous commit' '
+       git cat-file -p HEAD^:one.bin >result &&
+       test_cmp expected result
+'
+
+cat >expected <<EOF
+converted: test version 2
+EOF
+
+test_expect_success 'cat-file --textconv on last commit' '
+       git cat-file --textconv :one.bin >result &&
+       test_cmp expected result
+'
+
+cat >expected <<EOF
+converted: test
+EOF
+
+test_expect_success 'cat-file --textconv on previous commit' '
+       git cat-file --textconv HEAD^:one.bin >result &&
+       test_cmp expected result
+'
+test_done
index ce26ea4ac53a4608f6f8234b67868a9cc85be7f8..71b3df9b55d7009def5ea11ff519cae4f28dea6d 100755 (executable)
@@ -3,20 +3,17 @@
 test_description='git send-email'
 . ./test-lib.sh
 
-if ! test_have_prereq PERL; then
-       say 'skipping git send-email tests, perl not available'
-       test_done
-fi
+# May be altered later in the test
+PREREQ="PERL"
 
-PROG='git send-email'
-test_expect_success \
+test_expect_success $PREREQ \
     '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 \
+test_expect_success $PREREQ \
     'Setup helper tool' \
     '(echo "#!$SHELL_PATH"
       echo shift
@@ -36,7 +33,7 @@ clean_fake_sendmail() {
        rm -f commandline* msgtxt*
 }
 
-test_expect_success 'Extract patches' '
+test_expect_success $PREREQ 'Extract patches' '
     patches=`git format-patch -s --cc="One <one@example.com>" --cc=two@example.com -n HEAD^1`
 '
 
@@ -57,44 +54,91 @@ test_no_confirm () {
 
 # Exit immediately to prevent hang if a no-confirm test fails
 check_no_confirm () {
-       test -f no_confirm_okay || {
-               say 'No confirm test failed; skipping remaining tests to prevent hanging'
-               test_done
-       }
+       if ! test -f no_confirm_okay
+       then
+               say 'confirm test failed; skipping remaining tests to prevent hanging'
+               PREREQ="$PREREQ,CHECK_NO_CONFIRM"
+       fi
+       return 0
 }
 
-test_expect_success 'No confirm with --suppress-cc' '
-       test_no_confirm --suppress-cc=sob
+test_expect_success $PREREQ 'No confirm with --suppress-cc' '
+       test_no_confirm --suppress-cc=sob &&
+       check_no_confirm
 '
-check_no_confirm
 
-test_expect_success 'No confirm with --confirm=never' '
-       test_no_confirm --confirm=never
+
+test_expect_success $PREREQ 'No confirm with --confirm=never' '
+       test_no_confirm --confirm=never &&
+       check_no_confirm
 '
-check_no_confirm
 
 # leave sendemail.confirm set to never after this so that none of the
 # remaining tests prompt unintentionally.
-test_expect_success 'No confirm with sendemail.confirm=never' '
+test_expect_success $PREREQ 'No confirm with sendemail.confirm=never' '
        git config sendemail.confirm never &&
-       test_no_confirm --compose --subject=foo
+       test_no_confirm --compose --subject=foo &&
+       check_no_confirm
 '
-check_no_confirm
 
-test_expect_success 'Send patches' '
+test_expect_success $PREREQ 'Send patches' '
      git send-email --suppress-cc=sob --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors
 '
 
+test_expect_success $PREREQ 'setup expect' '
+cat >expected <<\EOF
+!nobody@example.com!
+!author@example.com!
+!one@example.com!
+!two@example.com!
+EOF
+'
+
+test_expect_success $PREREQ \
+    'Verify commandline' \
+    'test_cmp expected commandline1'
+
+test_expect_success $PREREQ 'Send patches with --envelope-sender' '
+    clean_fake_sendmail &&
+     git send-email --envelope-sender="Patch Contributer <patch@example.com>" --suppress-cc=sob --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors
+'
+
+test_expect_success $PREREQ 'setup expect' '
 cat >expected <<\EOF
+!patch@example.com!
+!-i!
 !nobody@example.com!
 !author@example.com!
 !one@example.com!
 !two@example.com!
 EOF
-test_expect_success \
+'
+
+test_expect_success $PREREQ \
     'Verify commandline' \
     'test_cmp expected commandline1'
 
+test_expect_success $PREREQ 'Send patches with --envelope-sender=auto' '
+    clean_fake_sendmail &&
+     git send-email --envelope-sender=auto --suppress-cc=sob --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors
+'
+
+test_expect_success $PREREQ 'setup expect' '
+cat >expected <<\EOF
+!nobody@example.com!
+!-i!
+!nobody@example.com!
+!author@example.com!
+!one@example.com!
+!two@example.com!
+EOF
+'
+
+test_expect_success $PREREQ \
+    'Verify commandline' \
+    'test_cmp expected commandline1'
+
+test_expect_success $PREREQ 'setup expect' "
 cat >expected-show-all-headers <<\EOF
 0001-Second.patch
 (mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
@@ -103,10 +147,18 @@ cat >expected-show-all-headers <<\EOF
 Dry-OK. Log says:
 Server: relay.example.com
 MAIL FROM:<from@example.com>
-RCPT TO:<to@example.com>,<cc@example.com>,<author@example.com>,<one@example.com>,<two@example.com>,<bcc@example.com>
+RCPT TO:<to@example.com>
+RCPT TO:<cc@example.com>
+RCPT TO:<author@example.com>
+RCPT TO:<one@example.com>
+RCPT TO:<two@example.com>
+RCPT TO:<bcc@example.com>
 From: Example <from@example.com>
 To: to@example.com
-Cc: cc@example.com, A <author@example.com>, One <one@example.com>, two@example.com
+Cc: cc@example.com,
+       A <author@example.com>,
+       One <one@example.com>,
+       two@example.com
 Subject: [PATCH 1/1] Second.
 Date: DATE-STRING
 Message-Id: MESSAGE-ID-STRING
@@ -116,8 +168,9 @@ References: <unique-message-id@example.com>
 
 Result: OK
 EOF
+"
 
-test_expect_success 'Show all headers' '
+test_expect_success $PREREQ 'Show all headers' '
        git send-email \
                --dry-run \
                --suppress-cc=sob \
@@ -135,7 +188,7 @@ test_expect_success 'Show all headers' '
        test_cmp expected-show-all-headers actual-show-all-headers
 '
 
-test_expect_success 'Prompting works' '
+test_expect_success $PREREQ 'Prompting works' '
        clean_fake_sendmail &&
        (echo "Example <from@example.com>"
         echo "to@example.com"
@@ -144,14 +197,33 @@ test_expect_success 'Prompting works' '
                --smtp-server="$(pwd)/fake.sendmail" \
                $patches \
                2>errors &&
-               grep "^From: Example <from@example.com>$" msgtxt1 &&
-               grep "^To: to@example.com$" msgtxt1
+               grep "^From: Example <from@example.com>\$" msgtxt1 &&
+               grep "^To: to@example.com\$" msgtxt1
 '
 
-z8=zzzzzzzz
-z64=$z8$z8$z8$z8$z8$z8$z8$z8
-z512=$z64$z64$z64$z64$z64$z64$z64$z64
-test_expect_success 'reject long lines' '
+test_expect_success $PREREQ 'cccmd works' '
+       clean_fake_sendmail &&
+       cp $patches cccmd.patch &&
+       echo cccmd--cccmd@example.com >>cccmd.patch &&
+       {
+         echo "#!$SHELL_PATH"
+         echo sed -n -e s/^cccmd--//p \"\$1\"
+       } > cccmd-sed &&
+       chmod +x cccmd-sed &&
+       git send-email \
+               --from="Example <nobody@example.com>" \
+               --to=nobody@example.com \
+               --cc-cmd=./cccmd-sed \
+               --smtp-server="$(pwd)/fake.sendmail" \
+               cccmd.patch \
+               &&
+       grep "^ cccmd@example.com" msgtxt1
+'
+
+test_expect_success $PREREQ 'reject long lines' '
+       z8=zzzzzzzz &&
+       z64=$z8$z8$z8$z8$z8$z8$z8$z8 &&
+       z512=$z64$z64$z64$z64$z64$z64$z64$z64 &&
        clean_fake_sendmail &&
        cp $patches longline.patch &&
        echo $z512$z512 >>longline.patch &&
@@ -164,33 +236,33 @@ test_expect_success 'reject long lines' '
        grep longline.patch errors
 '
 
-test_expect_success 'no patch was sent' '
+test_expect_success $PREREQ 'no patch was sent' '
        ! test -e commandline1
 '
 
-test_expect_success 'Author From: in message body' '
+test_expect_success $PREREQ 'Author From: in message body' '
        clean_fake_sendmail &&
        git send-email \
                --from="Example <nobody@example.com>" \
                --to=nobody@example.com \
                --smtp-server="$(pwd)/fake.sendmail" \
                $patches &&
-       sed "1,/^$/d" < msgtxt1 > msgbody1
+       sed "1,/^\$/d" < msgtxt1 > msgbody1
        grep "From: A <author@example.com>" msgbody1
 '
 
-test_expect_success 'Author From: not in message body' '
+test_expect_success $PREREQ 'Author From: not in message body' '
        clean_fake_sendmail &&
        git send-email \
                --from="A <author@example.com>" \
                --to=nobody@example.com \
                --smtp-server="$(pwd)/fake.sendmail" \
                $patches &&
-       sed "1,/^$/d" < msgtxt1 > msgbody1
+       sed "1,/^\$/d" < msgtxt1 > msgbody1
        ! grep "From: A <author@example.com>" msgbody1
 '
 
-test_expect_success 'allow long lines with --no-validate' '
+test_expect_success $PREREQ 'allow long lines with --no-validate' '
        git send-email \
                --from="Example <nobody@example.com>" \
                --to=nobody@example.com \
@@ -200,7 +272,7 @@ test_expect_success 'allow long lines with --no-validate' '
                2>errors
 '
 
-test_expect_success 'Invalid In-Reply-To' '
+test_expect_success $PREREQ 'Invalid In-Reply-To' '
        clean_fake_sendmail &&
        git send-email \
                --from="Example <nobody@example.com>" \
@@ -212,7 +284,7 @@ test_expect_success 'Invalid In-Reply-To' '
        ! grep "^In-Reply-To: < *>" msgtxt1
 '
 
-test_expect_success 'Valid In-Reply-To when prompting' '
+test_expect_success $PREREQ 'Valid In-Reply-To when prompting' '
        clean_fake_sendmail &&
        (echo "From Example <from@example.com>"
         echo "To Example <to@example.com>"
@@ -223,7 +295,7 @@ test_expect_success 'Valid In-Reply-To when prompting' '
        ! grep "^In-Reply-To: < *>" msgtxt1
 '
 
-test_expect_success 'setup fake editor' '
+test_expect_success $PREREQ 'setup fake editor' '
        (echo "#!$SHELL_PATH" &&
         echo "echo fake edit >>\"\$1\""
        ) >fake-editor &&
@@ -232,7 +304,7 @@ test_expect_success 'setup fake editor' '
 
 test_set_editor "$(pwd)/fake-editor"
 
-test_expect_success '--compose works' '
+test_expect_success $PREREQ '--compose works' '
        clean_fake_sendmail &&
        git send-email \
        --compose --subject foo \
@@ -243,14 +315,15 @@ test_expect_success '--compose works' '
        2>errors
 '
 
-test_expect_success 'first message is compose text' '
+test_expect_success $PREREQ 'first message is compose text' '
        grep "^fake edit" msgtxt1
 '
 
-test_expect_success 'second message is patch' '
+test_expect_success $PREREQ 'second message is patch' '
        grep "Subject:.*Second" msgtxt2
 '
 
+test_expect_success $PREREQ 'setup expect' "
 cat >expected-suppress-sob <<\EOF
 0001-Second.patch
 (mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
@@ -259,10 +332,17 @@ cat >expected-suppress-sob <<\EOF
 Dry-OK. Log says:
 Server: relay.example.com
 MAIL FROM:<from@example.com>
-RCPT TO:<to@example.com>,<cc@example.com>,<author@example.com>,<one@example.com>,<two@example.com>
+RCPT TO:<to@example.com>
+RCPT TO:<cc@example.com>
+RCPT TO:<author@example.com>
+RCPT TO:<one@example.com>
+RCPT TO:<two@example.com>
 From: Example <from@example.com>
 To: to@example.com
-Cc: cc@example.com, A <author@example.com>, One <one@example.com>, two@example.com
+Cc: cc@example.com,
+       A <author@example.com>,
+       One <one@example.com>,
+       two@example.com
 Subject: [PATCH 1/1] Second.
 Date: DATE-STRING
 Message-Id: MESSAGE-ID-STRING
@@ -270,11 +350,12 @@ X-Mailer: X-MAILER-STRING
 
 Result: OK
 EOF
+"
 
 test_suppression () {
        git send-email \
                --dry-run \
-               --suppress-cc=$1 \
+               --suppress-cc=$1 ${2+"--suppress-cc=$2"} \
                --from="Example <from@example.com>" \
                --to=to@example.com \
                --smtp-server relay.example.com \
@@ -282,15 +363,16 @@ test_suppression () {
        sed     -e "s/^\(Date:\).*/\1 DATE-STRING/" \
                -e "s/^\(Message-Id:\).*/\1 MESSAGE-ID-STRING/" \
                -e "s/^\(X-Mailer:\).*/\1 X-MAILER-STRING/" \
-               >actual-suppress-$1 &&
-       test_cmp expected-suppress-$1 actual-suppress-$1
+               >actual-suppress-$1${2+"-$2"} &&
+       test_cmp expected-suppress-$1${2+"-$2"} actual-suppress-$1${2+"-$2"}
 }
 
-test_expect_success 'sendemail.cc set' '
+test_expect_success $PREREQ 'sendemail.cc set' '
        git config sendemail.cc cc@example.com &&
        test_suppression sob
 '
 
+test_expect_success $PREREQ 'setup expect' "
 cat >expected-suppress-sob <<\EOF
 0001-Second.patch
 (mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
@@ -299,10 +381,15 @@ cat >expected-suppress-sob <<\EOF
 Dry-OK. Log says:
 Server: relay.example.com
 MAIL FROM:<from@example.com>
-RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com>
+RCPT TO:<to@example.com>
+RCPT TO:<author@example.com>
+RCPT TO:<one@example.com>
+RCPT TO:<two@example.com>
 From: Example <from@example.com>
 To: to@example.com
-Cc: A <author@example.com>, One <one@example.com>, two@example.com
+Cc: A <author@example.com>,
+       One <one@example.com>,
+       two@example.com
 Subject: [PATCH 1/1] Second.
 Date: DATE-STRING
 Message-Id: MESSAGE-ID-STRING
@@ -310,12 +397,51 @@ X-Mailer: X-MAILER-STRING
 
 Result: OK
 EOF
+"
 
-test_expect_success 'sendemail.cc unset' '
+test_expect_success $PREREQ 'sendemail.cc unset' '
        git config --unset sendemail.cc &&
        test_suppression sob
 '
 
+test_expect_success $PREREQ 'setup expect' "
+cat >expected-suppress-cccmd <<\EOF
+0001-Second.patch
+(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
+(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com'
+(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com'
+(body) Adding cc: C O Mitter <committer@example.com> from line 'Signed-off-by: C O Mitter <committer@example.com>'
+Dry-OK. Log says:
+Server: relay.example.com
+MAIL FROM:<from@example.com>
+RCPT TO:<to@example.com>
+RCPT TO:<author@example.com>
+RCPT TO:<one@example.com>
+RCPT TO:<two@example.com>
+RCPT TO:<committer@example.com>
+From: Example <from@example.com>
+To: to@example.com
+Cc: A <author@example.com>,
+       One <one@example.com>,
+       two@example.com,
+       C O Mitter <committer@example.com>
+Subject: [PATCH 1/1] Second.
+Date: DATE-STRING
+Message-Id: MESSAGE-ID-STRING
+X-Mailer: X-MAILER-STRING
+
+Result: OK
+EOF
+"
+
+test_expect_success $PREREQ 'sendemail.cccmd' '
+       echo echo cc-cmd@example.com > cccmd &&
+       chmod +x cccmd &&
+       git config sendemail.cccmd ./cccmd &&
+       test_suppression cccmd
+'
+
+test_expect_success $PREREQ 'setup expect' '
 cat >expected-suppress-all <<\EOF
 0001-Second.patch
 Dry-OK. Log says:
@@ -331,23 +457,33 @@ X-Mailer: X-MAILER-STRING
 
 Result: OK
 EOF
+'
 
-test_expect_success '--suppress-cc=all' '
+test_expect_success $PREREQ '--suppress-cc=all' '
        test_suppression all
 '
 
+test_expect_success $PREREQ 'setup expect' "
 cat >expected-suppress-body <<\EOF
 0001-Second.patch
 (mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
 (mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com'
 (mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com'
+(cc-cmd) Adding cc: cc-cmd@example.com from: './cccmd'
 Dry-OK. Log says:
 Server: relay.example.com
 MAIL FROM:<from@example.com>
-RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com>
+RCPT TO:<to@example.com>
+RCPT TO:<author@example.com>
+RCPT TO:<one@example.com>
+RCPT TO:<two@example.com>
+RCPT TO:<cc-cmd@example.com>
 From: Example <from@example.com>
 To: to@example.com
-Cc: A <author@example.com>, One <one@example.com>, two@example.com
+Cc: A <author@example.com>,
+       One <one@example.com>,
+       two@example.com,
+       cc-cmd@example.com
 Subject: [PATCH 1/1] Second.
 Date: DATE-STRING
 Message-Id: MESSAGE-ID-STRING
@@ -355,11 +491,44 @@ X-Mailer: X-MAILER-STRING
 
 Result: OK
 EOF
+"
 
-test_expect_success '--suppress-cc=body' '
+test_expect_success $PREREQ '--suppress-cc=body' '
        test_suppression body
 '
 
+test_expect_success $PREREQ 'setup expect' "
+cat >expected-suppress-body-cccmd <<\EOF
+0001-Second.patch
+(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
+(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com'
+(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com'
+Dry-OK. Log says:
+Server: relay.example.com
+MAIL FROM:<from@example.com>
+RCPT TO:<to@example.com>
+RCPT TO:<author@example.com>
+RCPT TO:<one@example.com>
+RCPT TO:<two@example.com>
+From: Example <from@example.com>
+To: to@example.com
+Cc: A <author@example.com>,
+       One <one@example.com>,
+       two@example.com
+Subject: [PATCH 1/1] Second.
+Date: DATE-STRING
+Message-Id: MESSAGE-ID-STRING
+X-Mailer: X-MAILER-STRING
+
+Result: OK
+EOF
+"
+
+test_expect_success $PREREQ '--suppress-cc=body --suppress-cc=cccmd' '
+       test_suppression body cccmd
+'
+
+test_expect_success $PREREQ 'setup expect' "
 cat >expected-suppress-sob <<\EOF
 0001-Second.patch
 (mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
@@ -368,10 +537,15 @@ cat >expected-suppress-sob <<\EOF
 Dry-OK. Log says:
 Server: relay.example.com
 MAIL FROM:<from@example.com>
-RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com>
+RCPT TO:<to@example.com>
+RCPT TO:<author@example.com>
+RCPT TO:<one@example.com>
+RCPT TO:<two@example.com>
 From: Example <from@example.com>
 To: to@example.com
-Cc: A <author@example.com>, One <one@example.com>, two@example.com
+Cc: A <author@example.com>,
+       One <one@example.com>,
+       two@example.com
 Subject: [PATCH 1/1] Second.
 Date: DATE-STRING
 Message-Id: MESSAGE-ID-STRING
@@ -379,11 +553,14 @@ X-Mailer: X-MAILER-STRING
 
 Result: OK
 EOF
+"
 
-test_expect_success '--suppress-cc=sob' '
+test_expect_success $PREREQ '--suppress-cc=sob' '
+       git config --unset sendemail.cccmd
        test_suppression sob
 '
 
+test_expect_success $PREREQ 'setup expect' "
 cat >expected-suppress-bodycc <<\EOF
 0001-Second.patch
 (mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
@@ -393,10 +570,17 @@ cat >expected-suppress-bodycc <<\EOF
 Dry-OK. Log says:
 Server: relay.example.com
 MAIL FROM:<from@example.com>
-RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com>,<committer@example.com>
+RCPT TO:<to@example.com>
+RCPT TO:<author@example.com>
+RCPT TO:<one@example.com>
+RCPT TO:<two@example.com>
+RCPT TO:<committer@example.com>
 From: Example <from@example.com>
 To: to@example.com
-Cc: A <author@example.com>, One <one@example.com>, two@example.com, C O Mitter <committer@example.com>
+Cc: A <author@example.com>,
+       One <one@example.com>,
+       two@example.com,
+       C O Mitter <committer@example.com>
 Subject: [PATCH 1/1] Second.
 Date: DATE-STRING
 Message-Id: MESSAGE-ID-STRING
@@ -404,11 +588,13 @@ X-Mailer: X-MAILER-STRING
 
 Result: OK
 EOF
+"
 
-test_expect_success '--suppress-cc=bodycc' '
+test_expect_success $PREREQ '--suppress-cc=bodycc' '
        test_suppression bodycc
 '
 
+test_expect_success $PREREQ 'setup expect' "
 cat >expected-suppress-cc <<\EOF
 0001-Second.patch
 (mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
@@ -416,10 +602,13 @@ cat >expected-suppress-cc <<\EOF
 Dry-OK. Log says:
 Server: relay.example.com
 MAIL FROM:<from@example.com>
-RCPT TO:<to@example.com>,<author@example.com>,<committer@example.com>
+RCPT TO:<to@example.com>
+RCPT TO:<author@example.com>
+RCPT TO:<committer@example.com>
 From: Example <from@example.com>
 To: to@example.com
-Cc: A <author@example.com>, C O Mitter <committer@example.com>
+Cc: A <author@example.com>,
+       C O Mitter <committer@example.com>
 Subject: [PATCH 1/1] Second.
 Date: DATE-STRING
 Message-Id: MESSAGE-ID-STRING
@@ -427,8 +616,9 @@ X-Mailer: X-MAILER-STRING
 
 Result: OK
 EOF
+"
 
-test_expect_success '--suppress-cc=cc' '
+test_expect_success $PREREQ '--suppress-cc=cc' '
        test_suppression cc
 '
 
@@ -443,23 +633,23 @@ test_confirm () {
        grep "Send this email" stdout
 }
 
-test_expect_success '--confirm=always' '
+test_expect_success $PREREQ '--confirm=always' '
        test_confirm --confirm=always --suppress-cc=all
 '
 
-test_expect_success '--confirm=auto' '
+test_expect_success $PREREQ '--confirm=auto' '
        test_confirm --confirm=auto
 '
 
-test_expect_success '--confirm=cc' '
+test_expect_success $PREREQ '--confirm=cc' '
        test_confirm --confirm=cc
 '
 
-test_expect_success '--confirm=compose' '
+test_expect_success $PREREQ '--confirm=compose' '
        test_confirm --confirm=compose --compose
 '
 
-test_expect_success 'confirm by default (due to cc)' '
+test_expect_success $PREREQ 'confirm by default (due to cc)' '
        CONFIRM=$(git config --get sendemail.confirm) &&
        git config --unset sendemail.confirm &&
        test_confirm
@@ -468,7 +658,7 @@ test_expect_success 'confirm by default (due to cc)' '
        test $ret = "0"
 '
 
-test_expect_success 'confirm by default (due to --compose)' '
+test_expect_success $PREREQ 'confirm by default (due to --compose)' '
        CONFIRM=$(git config --get sendemail.confirm) &&
        git config --unset sendemail.confirm &&
        test_confirm --suppress-cc=all --compose
@@ -477,7 +667,7 @@ test_expect_success 'confirm by default (due to --compose)' '
        test $ret = "0"
 '
 
-test_expect_success 'confirm detects EOF (inform assumes y)' '
+test_expect_success $PREREQ 'confirm detects EOF (inform assumes y)' '
        CONFIRM=$(git config --get sendemail.confirm) &&
        git config --unset sendemail.confirm &&
        rm -fr outdir &&
@@ -493,7 +683,7 @@ test_expect_success 'confirm detects EOF (inform assumes y)' '
        test $ret = "0"
 '
 
-test_expect_success 'confirm detects EOF (auto causes failure)' '
+test_expect_success $PREREQ 'confirm detects EOF (auto causes failure)' '
        CONFIRM=$(git config --get sendemail.confirm) &&
        git config sendemail.confirm auto &&
        GIT_SEND_EMAIL_NOTTY=1 &&
@@ -508,7 +698,7 @@ test_expect_success 'confirm detects EOF (auto causes failure)' '
        test $ret = "0"
 '
 
-test_expect_success 'confirm doesnt loop forever' '
+test_expect_success $PREREQ 'confirm doesnt loop forever' '
        CONFIRM=$(git config --get sendemail.confirm) &&
        git config sendemail.confirm auto &&
        GIT_SEND_EMAIL_NOTTY=1 &&
@@ -523,7 +713,7 @@ test_expect_success 'confirm doesnt loop forever' '
        test $ret = "0"
 '
 
-test_expect_success 'utf8 Cc is rfc2047 encoded' '
+test_expect_success $PREREQ 'utf8 Cc is rfc2047 encoded' '
        clean_fake_sendmail &&
        rm -fr outdir &&
        git format-patch -1 -o outdir --cc="àéìöú <utf8@example.com>" &&
@@ -532,11 +722,11 @@ test_expect_success 'utf8 Cc is rfc2047 encoded' '
        --to=nobody@example.com \
        --smtp-server="$(pwd)/fake.sendmail" \
        outdir/*.patch &&
-       grep "^Cc:" msgtxt1 |
-       grep "=?utf-8?q?=C3=A0=C3=A9=C3=AC=C3=B6=C3=BA?= <utf8@example.com>"
+       grep "^ " msgtxt1 |
+       grep "=?UTF-8?q?=C3=A0=C3=A9=C3=AC=C3=B6=C3=BA?= <utf8@example.com>"
 '
 
-test_expect_success '--compose adds MIME for utf8 body' '
+test_expect_success $PREREQ '--compose adds MIME for utf8 body' '
        clean_fake_sendmail &&
        (echo "#!$SHELL_PATH" &&
         echo "echo utf8 body: àéìöú >>\"\$1\""
@@ -550,10 +740,10 @@ test_expect_success '--compose adds MIME for utf8 body' '
          --smtp-server="$(pwd)/fake.sendmail" \
          $patches &&
        grep "^utf8 body" msgtxt1 &&
-       grep "^Content-Type: text/plain; charset=utf-8" msgtxt1
+       grep "^Content-Type: text/plain; charset=UTF-8" msgtxt1
 '
 
-test_expect_success '--compose respects user mime type' '
+test_expect_success $PREREQ '--compose respects user mime type' '
        clean_fake_sendmail &&
        (echo "#!$SHELL_PATH" &&
         echo "(echo MIME-Version: 1.0"
@@ -573,10 +763,10 @@ test_expect_success '--compose respects user mime type' '
          $patches &&
        grep "^utf8 body" msgtxt1 &&
        grep "^Content-Type: text/plain; charset=iso-8859-1" msgtxt1 &&
-       ! grep "^Content-Type: text/plain; charset=utf-8" msgtxt1
+       ! grep "^Content-Type: text/plain; charset=UTF-8" msgtxt1
 '
 
-test_expect_success '--compose adds MIME for utf8 subject' '
+test_expect_success $PREREQ '--compose adds MIME for utf8 subject' '
        clean_fake_sendmail &&
          GIT_EDITOR="\"$(pwd)/fake-editor\"" \
          git send-email \
@@ -586,10 +776,10 @@ test_expect_success '--compose adds MIME for utf8 subject' '
          --smtp-server="$(pwd)/fake.sendmail" \
          $patches &&
        grep "^fake edit" msgtxt1 &&
-       grep "^Subject: =?utf-8?q?utf8-s=C3=BCbj=C3=ABct?=" msgtxt1
+       grep "^Subject: =?UTF-8?q?utf8-s=C3=BCbj=C3=ABct?=" msgtxt1
 '
 
-test_expect_success 'detects ambiguous reference/file conflict' '
+test_expect_success $PREREQ 'detects ambiguous reference/file conflict' '
        echo master > master &&
        git add master &&
        git commit -m"add master" &&
@@ -597,7 +787,7 @@ test_expect_success 'detects ambiguous reference/file conflict' '
        grep disambiguate errors
 '
 
-test_expect_success 'feed two files' '
+test_expect_success $PREREQ 'feed two files' '
        rm -fr outdir &&
        git format-patch -2 -o outdir &&
        git send-email \
@@ -610,7 +800,7 @@ test_expect_success 'feed two files' '
        test "z$(sed -n -e 2p subjects)" = "zSubject: [PATCH 2/2] add master"
 '
 
-test_expect_success 'in-reply-to but no threading' '
+test_expect_success $PREREQ 'in-reply-to but no threading' '
        git send-email \
                --dry-run \
                --from="Example <nobody@example.com>" \
@@ -621,4 +811,225 @@ test_expect_success 'in-reply-to but no threading' '
        grep "In-Reply-To: <in-reply-id@example.com>"
 '
 
+test_expect_success $PREREQ 'no in-reply-to and no threading' '
+       git send-email \
+               --dry-run \
+               --from="Example <nobody@example.com>" \
+               --to=nobody@example.com \
+               --nothread \
+               $patches $patches >stdout &&
+       ! grep "In-Reply-To: " stdout
+'
+
+test_expect_success $PREREQ 'threading but no chain-reply-to' '
+       git send-email \
+               --dry-run \
+               --from="Example <nobody@example.com>" \
+               --to=nobody@example.com \
+               --thread \
+               --nochain-reply-to \
+               $patches $patches >stdout &&
+       grep "In-Reply-To: " stdout
+'
+
+test_expect_success $PREREQ 'warning with an implicit --chain-reply-to' '
+       git send-email \
+       --dry-run \
+       --from="Example <nobody@example.com>" \
+       --to=nobody@example.com \
+       outdir/000?-*.patch 2>errors >out &&
+       grep "no-chain-reply-to" errors
+'
+
+test_expect_success $PREREQ 'no warning with an explicit --chain-reply-to' '
+       git send-email \
+       --dry-run \
+       --from="Example <nobody@example.com>" \
+       --to=nobody@example.com \
+       --chain-reply-to \
+       outdir/000?-*.patch 2>errors >out &&
+       ! grep "no-chain-reply-to" errors
+'
+
+test_expect_success $PREREQ 'no warning with an explicit --no-chain-reply-to' '
+       git send-email \
+       --dry-run \
+       --from="Example <nobody@example.com>" \
+       --to=nobody@example.com \
+       --nochain-reply-to \
+       outdir/000?-*.patch 2>errors >out &&
+       ! grep "no-chain-reply-to" errors
+'
+
+test_expect_success $PREREQ 'no warning with sendemail.chainreplyto = false' '
+       git config sendemail.chainreplyto false &&
+       git send-email \
+       --dry-run \
+       --from="Example <nobody@example.com>" \
+       --to=nobody@example.com \
+       outdir/000?-*.patch 2>errors >out &&
+       ! grep "no-chain-reply-to" errors
+'
+
+test_expect_success $PREREQ 'no warning with sendemail.chainreplyto = true' '
+       git config sendemail.chainreplyto true &&
+       git send-email \
+       --dry-run \
+       --from="Example <nobody@example.com>" \
+       --to=nobody@example.com \
+       outdir/000?-*.patch 2>errors >out &&
+       ! grep "no-chain-reply-to" errors
+'
+
+test_expect_success $PREREQ 'sendemail.to works' '
+       git config --replace-all sendemail.to "Somebody <somebody@ex.com>" &&
+       git send-email \
+               --dry-run \
+               --from="Example <nobody@example.com>" \
+               $patches $patches >stdout &&
+       grep "To: Somebody <somebody@ex.com>" stdout
+'
+
+test_expect_success $PREREQ '--no-to overrides sendemail.to' '
+       git send-email \
+               --dry-run \
+               --from="Example <nobody@example.com>" \
+               --no-to \
+               --to=nobody@example.com \
+               $patches $patches >stdout &&
+       grep "To: nobody@example.com" stdout &&
+       ! grep "To: Somebody <somebody@ex.com>" stdout
+'
+
+test_expect_success $PREREQ 'sendemail.cc works' '
+       git config --replace-all sendemail.cc "Somebody <somebody@ex.com>" &&
+       git send-email \
+               --dry-run \
+               --from="Example <nobody@example.com>" \
+               --to=nobody@example.com \
+               $patches $patches >stdout &&
+       grep "Cc: Somebody <somebody@ex.com>" stdout
+'
+
+test_expect_success $PREREQ '--no-cc overrides sendemail.cc' '
+       git send-email \
+               --dry-run \
+               --from="Example <nobody@example.com>" \
+               --no-cc \
+               --cc=bodies@example.com \
+               --to=nobody@example.com \
+               $patches $patches >stdout &&
+       grep "Cc: bodies@example.com" stdout &&
+       ! grep "Cc: Somebody <somebody@ex.com>" stdout
+'
+
+test_expect_success $PREREQ 'sendemail.bcc works' '
+       git config --replace-all sendemail.bcc "Other <other@ex.com>" &&
+       git send-email \
+               --dry-run \
+               --from="Example <nobody@example.com>" \
+               --to=nobody@example.com \
+               --smtp-server relay.example.com \
+               $patches $patches >stdout &&
+       grep "RCPT TO:<other@ex.com>" stdout
+'
+
+test_expect_success $PREREQ '--no-bcc overrides sendemail.bcc' '
+       git send-email \
+               --dry-run \
+               --from="Example <nobody@example.com>" \
+               --no-bcc \
+               --bcc=bodies@example.com \
+               --to=nobody@example.com \
+               --smtp-server relay.example.com \
+               $patches $patches >stdout &&
+       grep "RCPT TO:<bodies@example.com>" stdout &&
+       ! grep "RCPT TO:<other@ex.com>" stdout
+'
+
+test_expect_success $PREREQ 'setup expect' '
+cat >email-using-8bit <<EOF
+From fe6ecc66ece37198fe5db91fa2fc41d9f4fe5cc4 Mon Sep 17 00:00:00 2001
+Message-Id: <bogus-message-id@example.com>
+From: author@example.com
+Date: Sat, 12 Jun 2010 15:53:58 +0200
+Subject: subject goes here
+
+Dieser deutsche Text enthält einen Umlaut!
+EOF
+'
+
+test_expect_success $PREREQ 'setup expect' '
+cat >content-type-decl <<EOF
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+EOF
+'
+
+test_expect_success $PREREQ 'asks about and fixes 8bit encodings' '
+       clean_fake_sendmail &&
+       echo |
+       git send-email --from=author@example.com --to=nobody@example.com \
+                       --smtp-server="$(pwd)/fake.sendmail" \
+                       email-using-8bit >stdout &&
+       grep "do not declare a Content-Transfer-Encoding" stdout &&
+       grep email-using-8bit stdout &&
+       grep "Which 8bit encoding" stdout &&
+       egrep "Content|MIME" msgtxt1 >actual &&
+       test_cmp actual content-type-decl
+'
+
+test_expect_success $PREREQ 'sendemail.8bitEncoding works' '
+       clean_fake_sendmail &&
+       git config sendemail.assume8bitEncoding UTF-8 &&
+       echo bogus |
+       git send-email --from=author@example.com --to=nobody@example.com \
+                       --smtp-server="$(pwd)/fake.sendmail" \
+                       email-using-8bit >stdout &&
+       egrep "Content|MIME" msgtxt1 >actual &&
+       test_cmp actual content-type-decl
+'
+
+test_expect_success $PREREQ '--8bit-encoding overrides sendemail.8bitEncoding' '
+       clean_fake_sendmail &&
+       git config sendemail.assume8bitEncoding "bogus too" &&
+       echo bogus |
+       git send-email --from=author@example.com --to=nobody@example.com \
+                       --smtp-server="$(pwd)/fake.sendmail" \
+                       --8bit-encoding=UTF-8 \
+                       email-using-8bit >stdout &&
+       egrep "Content|MIME" msgtxt1 >actual &&
+       test_cmp actual content-type-decl
+'
+
+test_expect_success $PREREQ 'setup expect' '
+cat >email-using-8bit <<EOF
+From fe6ecc66ece37198fe5db91fa2fc41d9f4fe5cc4 Mon Sep 17 00:00:00 2001
+Message-Id: <bogus-message-id@example.com>
+From: author@example.com
+Date: Sat, 12 Jun 2010 15:53:58 +0200
+Subject: Dieser Betreff enthält auch einen Umlaut!
+
+Nothing to see here.
+EOF
+'
+
+test_expect_success $PREREQ 'setup expect' '
+cat >expected <<EOF
+Subject: =?UTF-8?q?Dieser=20Betreff=20enth=C3=A4lt=20auch=20einen=20Umlaut!?=
+EOF
+'
+
+test_expect_success $PREREQ '--8bit-encoding also treats subject' '
+       clean_fake_sendmail &&
+       echo bogus |
+       git send-email --from=author@example.com --to=nobody@example.com \
+                       --smtp-server="$(pwd)/fake.sendmail" \
+                       --8bit-encoding=UTF-8 \
+                       email-using-8bit >stdout &&
+       grep "Subject" msgtxt1 >actual &&
+       test_cmp expected actual
+'
+
 test_done
diff --git a/t/t9010-svn-fe.sh b/t/t9010-svn-fe.sh
new file mode 100755 (executable)
index 0000000..a713dfc
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+test_description='check svn dumpfile importer'
+
+. ./lib-git-svn.sh
+
+test_dump() {
+       label=$1
+       dump=$2
+       test_expect_success "$dump" '
+               svnadmin create "$label-svn" &&
+               svnadmin load "$label-svn" < "$TEST_DIRECTORY/$dump" &&
+               svn_cmd export "file://$PWD/$label-svn" "$label-svnco" &&
+               git init "$label-git" &&
+               test-svn-fe "$TEST_DIRECTORY/$dump" >"$label.fe" &&
+               (
+                       cd "$label-git" &&
+                       git fast-import < ../"$label.fe"
+               ) &&
+               (
+                       cd "$label-svnco" &&
+                       git init &&
+                       git add . &&
+                       git fetch "../$label-git" master &&
+                       git diff --exit-code FETCH_HEAD
+               )
+       '
+}
+
+test_dump simple t9135/svn.dump
+
+test_done
index 4eee2e9fa6bacdc0d130a692f727885c5fa42e49..2f458f7a999a69c623052898726567b9d7ce67c9 100755 (executable)
@@ -15,24 +15,25 @@ case "$GIT_SVN_LC_ALL" in
        test_set_prereq UTF8
        ;;
 *)
-       say "UTF-8 locale not set, some tests skipped ($GIT_SVN_LC_ALL)"
+       say "UTF-8 locale not set, some tests skipped ($GIT_SVN_LC_ALL)"
        ;;
 esac
 
 test_expect_success \
     'initialize git svn' '
        mkdir import &&
-       cd import &&
-       echo foo > foo &&
-       ln -s foo foo.link
-       mkdir -p dir/a/b/c/d/e &&
-       echo "deep dir" > dir/a/b/c/d/e/file &&
-       mkdir bar &&
-       echo "zzz" > bar/zzz &&
-       echo "#!/bin/sh" > exec.sh &&
-       chmod +x exec.sh &&
-       svn import -m "import for git svn" . "$svnrepo" >/dev/null &&
-       cd .. &&
+       (
+               cd import &&
+               echo foo >foo &&
+               ln -s foo foo.link
+               mkdir -p dir/a/b/c/d/e &&
+               echo "deep dir" >dir/a/b/c/d/e/file &&
+               mkdir bar &&
+               echo "zzz" >bar/zzz &&
+               echo "#!/bin/sh" >exec.sh &&
+               chmod +x exec.sh &&
+               svn_cmd import -m "import for git svn" . "$svnrepo" >/dev/null
+       ) &&
        rm -rf import &&
        git svn init "$svnrepo"'
 
@@ -51,7 +52,7 @@ test_expect_success "$name" '
        git commit -m "$name" &&
        git svn set-tree --find-copies-harder --rmdir \
                ${remotes_git_svn}..mybranch &&
-       svn up "$SVN_TREE" &&
+       svn_cmd up "$SVN_TREE" &&
        test -d "$SVN_TREE"/dir && test ! -d "$SVN_TREE"/dir/a'
 
 
@@ -118,7 +119,7 @@ test_expect_success "$name" '
        git commit -m "$name" &&
        git svn set-tree --find-copies-harder --rmdir \
                ${remotes_git_svn}..mybranch5 &&
-       svn up "$SVN_TREE" &&
+       svn_cmd up "$SVN_TREE" &&
        test ! -x "$SVN_TREE"/exec.sh'
 
 
@@ -129,7 +130,7 @@ test_expect_success "$name" '
        git commit -m "$name" &&
        git svn set-tree --find-copies-harder --rmdir \
                ${remotes_git_svn}..mybranch5 &&
-       svn up "$SVN_TREE" &&
+       svn_cmd up "$SVN_TREE" &&
        test -x "$SVN_TREE"/exec.sh'
 
 
@@ -141,7 +142,7 @@ test_expect_success "$name" '
        git commit -m "$name" &&
        git svn set-tree --find-copies-harder --rmdir \
                ${remotes_git_svn}..mybranch5 &&
-       svn up "$SVN_TREE" &&
+       svn_cmd up "$SVN_TREE" &&
        test -L "$SVN_TREE"/exec.sh'
 
 name='new symlink is added to a file that was also just made executable'
@@ -153,7 +154,7 @@ test_expect_success "$name" '
        git commit -m "$name" &&
        git svn set-tree --find-copies-harder --rmdir \
                ${remotes_git_svn}..mybranch5 &&
-       svn up "$SVN_TREE" &&
+       svn_cmd up "$SVN_TREE" &&
        test -x "$SVN_TREE"/bar/zzz &&
        test -L "$SVN_TREE"/exec-2.sh'
 
@@ -166,7 +167,7 @@ test_expect_success "$name" '
        git commit -m "$name" &&
        git svn set-tree --find-copies-harder --rmdir \
                ${remotes_git_svn}..mybranch5 &&
-       svn up "$SVN_TREE" &&
+       svn_cmd up "$SVN_TREE" &&
        test -f "$SVN_TREE"/exec-2.sh &&
        test ! -L "$SVN_TREE"/exec-2.sh &&
        test_cmp help "$SVN_TREE"/exec-2.sh'
@@ -231,6 +232,25 @@ test_expect_success \
                               "^:refs/${remotes_git_svn}$"
         '
 
+test_expect_success 'dcommit $rev does not clobber current branch' '
+       git svn fetch -i bar &&
+       git checkout -b my-bar refs/remotes/bar &&
+       echo 1 > foo &&
+       git add foo &&
+       git commit -m "change 1" &&
+       echo 2 > foo &&
+       git add foo &&
+       git commit -m "change 2" &&
+       old_head=$(git rev-parse HEAD) &&
+       git svn dcommit -i bar HEAD^ &&
+       test $old_head = $(git rev-parse HEAD) &&
+       test refs/heads/my-bar = $(git symbolic-ref HEAD) &&
+       git log refs/remotes/bar | grep "change 1" &&
+       ! git log refs/remotes/bar | grep "change 2" &&
+       git checkout master &&
+       git branch -D my-bar
+       '
+
 test_expect_success 'able to dcommit to a subdirectory' "
        git svn fetch -i bar &&
        git checkout -b my-bar refs/remotes/bar &&
@@ -252,6 +272,17 @@ test_expect_success 'able to dcommit to a subdirectory' "
        test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\"
        "
 
+test_expect_success 'dcommit should not fail with a touched file' '
+       test_commit "commit-new-file-foo2" foo2 &&
+       test-chmtime =-60 foo &&
+       git svn dcommit
+'
+
+test_expect_success 'rebase should not fail with a touched file' '
+       test-chmtime =-60 foo &&
+       git svn rebase
+'
+
 test_expect_success 'able to set-tree to a subdirectory' "
        echo cba > d &&
        git update-index d &&
index 1e31d6ea7253ee4216347fd707d1408f97af32fa..8869f5018ecd06636dc3e722cb518af88a6919d9 100755 (executable)
@@ -48,23 +48,25 @@ EOF
        printf "\r\n" > empty_crlf
        a_empty_crlf=`git hash-object -w empty_crlf`
 
-       svn import --no-auto-props -m 'import for git svn' . "$svnrepo" >/dev/null
+       svn_cmd import --no-auto-props -m 'import for git svn' . "$svnrepo" >/dev/null
 cd ..
 
 rm -rf import
 test_expect_success 'checkout working copy from svn' 'svn co "$svnrepo" test_wc'
-test_expect_success 'setup some commits to svn' \
-       'cd test_wc &&
+test_expect_success 'setup some commits to svn' '
+       (
+               cd test_wc &&
                echo Greetings >> kw.c &&
                poke kw.c &&
-               svn commit -m "Not yet an Id" &&
+               svn_cmd commit -m "Not yet an Id" &&
                echo Hello world >> kw.c &&
                poke kw.c &&
-               svn commit -m "Modified file, but still not yet an Id" &&
-               svn propset svn:keywords Id kw.c &&
+               svn_cmd commit -m "Modified file, but still not yet an Id" &&
+               svn_cmd propset svn:keywords Id kw.c &&
                poke kw.c &&
-               svn commit -m "Propset Id" &&
-       cd ..'
+               svn_cmd commit -m "Propset Id"
+       )
+'
 
 test_expect_success 'initialize git svn' 'git svn init "$svnrepo"'
 test_expect_success 'fetch revisions from svn' 'git svn fetch'
@@ -81,18 +83,20 @@ expect='/* $Id$ */'
 got="`sed -ne 2p kw.c`"
 test_expect_success 'raw $Id$ found in kw.c' "test '$expect' = '$got'"
 
-test_expect_success "propset CR on crlf files" \
-       '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" &&
-        cd ..'
+test_expect_success "propset CR on crlf files" '
+       (
+               cd test_wc &&
+               svn_cmd propset svn:eol-style CR empty &&
+               svn_cmd propset svn:eol-style CR crlf &&
+               svn_cmd propset svn:eol-style CR ne_crlf &&
+               svn_cmd commit -m "propset CR on crlf files"
+        )
+'
 
 test_expect_success 'fetch and pull latest from svn and checkout a new wc' \
        'git svn fetch &&
         git pull . ${remotes_git_svn} &&
-        svn co "$svnrepo" new_wc'
+        svn_cmd co "$svnrepo" new_wc'
 
 for i in crlf ne_crlf lf ne_lf cr ne_cr empty_cr empty_lf empty empty_crlf
 do
@@ -106,11 +110,11 @@ cd test_wc
        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`
        test_expect_success 'Set CRLF on cr files' \
-       '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_cmd propset svn:eol-style CRLF cr &&
+        svn_cmd propset svn:eol-style CRLF ne_cr &&
+        svn_cmd propset svn:keywords Id cr &&
+        svn_cmd propset svn:keywords Id ne_cr &&
+        svn_cmd commit -m "propset CRLF on cr files"'
 cd ..
 test_expect_success 'fetch and pull latest from svn' \
        'git svn fetch && git pull . ${remotes_git_svn}'
@@ -137,17 +141,20 @@ cat > show-ignore.expect <<\EOF
 EOF
 
 test_expect_success 'test show-ignore' "
-       cd test_wc &&
-       mkdir -p deeply/nested/directory &&
-       touch deeply/nested/directory/.keep &&
-       svn add deeply &&
-       svn up &&
-       svn propset -R svn:ignore 'no-such-file*' .
-       svn commit -m 'propset svn:ignore'
-       cd .. &&
+       (
+               cd test_wc &&
+               mkdir -p deeply/nested/directory &&
+               touch deeply/nested/directory/.keep &&
+               svn_cmd add deeply &&
+               svn_cmd up &&
+               svn_cmd propset -R svn:ignore '
+no-such-file*
+' .
+               svn_cmd commit -m 'propset svn:ignore'
+       ) &&
        git svn show-ignore > show-ignore.got &&
        cmp show-ignore.expect show-ignore.got
-       "
+"
 
 cat >create-ignore.expect <<\EOF
 /no-such-file*
@@ -171,6 +178,7 @@ test_expect_success 'test create-ignore' "
        "
 
 cat >prop.expect <<\EOF
+
 no-such-file*
 
 EOF
index e2232180158cfb0e523c8ffdd3ac10bf61c8f4ee..eb70f4839ccb065425b5ad8d40b270178dedef1b 100755 (executable)
@@ -4,13 +4,14 @@ test_description='git svn rmdir'
 
 test_expect_success 'initialize repo' '
        mkdir import &&
-       cd import &&
-       mkdir -p deeply/nested/directory/number/1 &&
-       mkdir -p deeply/nested/directory/number/2 &&
-       echo foo > deeply/nested/directory/number/1/file &&
-       echo foo > deeply/nested/directory/number/2/another &&
-       svn import -m "import for git svn" . "$svnrepo" &&
-       cd ..
+       (
+               cd import &&
+               mkdir -p deeply/nested/directory/number/1 &&
+               mkdir -p deeply/nested/directory/number/2 &&
+               echo foo >deeply/nested/directory/number/1/file &&
+               echo foo >deeply/nested/directory/number/2/another &&
+               svn_cmd import -m "import for git svn" . "$svnrepo"
+       )
        '
 
 test_expect_success 'mirror via git svn' '
@@ -23,7 +24,7 @@ test_expect_success 'Try a commit on rmdir' '
        git rm -f deeply/nested/directory/number/2/another &&
        git commit -a -m "remove another" &&
        git svn set-tree --rmdir HEAD &&
-       svn ls -R "$svnrepo" | grep ^deeply/nested/directory/number/1
+       svn_cmd ls -R "$svnrepo" | grep ^deeply/nested/directory/number/1
        '
 
 
index 963dd95e4a71ccefa64b9500c490f44ea6d7c789..3413164cb127da9e8d601775769b3b049816173e 100755 (executable)
@@ -10,15 +10,15 @@ test_expect_success 'make history for tracking' '
        mkdir import &&
        mkdir import/trunk &&
        echo hello >> import/trunk/README &&
-       svn import -m initial import "$svnrepo" &&
+       svn_cmd import -m initial import "$svnrepo" &&
        rm -rf import &&
-       svn co "$svnrepo"/trunk trunk &&
+       svn_cmd co "$svnrepo"/trunk trunk &&
        echo bye bye >> trunk/README &&
-       svn rm -m "gone" "$svnrepo"/trunk &&
+       svn_cmd rm -m "gone" "$svnrepo"/trunk &&
        rm -rf trunk &&
        mkdir trunk &&
        echo "new" > trunk/FOLLOWME &&
-       svn import -m "new trunk" trunk "$svnrepo"/trunk
+       svn_cmd import -m "new trunk" trunk "$svnrepo"/trunk
 '
 
 test_expect_success 'clone repo with git' '
index ab9fa322200b333dd0222be6b712c6651f4419fb..f7f3c5ab8ef8dd822a51d6c97281ba077c511642 100755 (executable)
@@ -8,22 +8,24 @@ test_description='git svn fetching'
 
 test_expect_success 'initialize repo' '
        mkdir import &&
-       cd import &&
-       mkdir -p trunk &&
-       echo hello > trunk/readme &&
-       svn import -m "initial" . "$svnrepo" &&
-       cd .. &&
-       svn co "$svnrepo" wc &&
-       cd wc &&
-       echo world >> trunk/readme &&
-       poke trunk/readme &&
-       svn commit -m "another commit" &&
-       svn up &&
-       svn mv trunk thunk &&
-       echo goodbye >> thunk/readme &&
-       poke thunk/readme &&
-       svn commit -m "bye now" &&
-       cd ..
+       (
+               cd import &&
+               mkdir -p trunk &&
+               echo hello >trunk/readme &&
+               svn_cmd import -m "initial" . "$svnrepo"
+       ) &&
+       svn_cmd co "$svnrepo" wc &&
+       (
+               cd wc &&
+               echo world >>trunk/readme &&
+               poke trunk/readme &&
+               svn_cmd commit -m "another commit" &&
+               svn_cmd up &&
+               svn_cmd mv trunk thunk &&
+               echo goodbye >>thunk/readme &&
+               poke thunk/readme &&
+               svn_cmd commit -m "bye now"
+       )
        '
 
 test_expect_success 'init and fetch a moved directory' '
@@ -51,7 +53,7 @@ test_expect_success 'init and fetch from one svn-remote' '
         '
 
 test_expect_success 'follow deleted parent' '
-        (svn cp -m "resurrecting trunk as junk" \
+        (svn_cmd cp -m "resurrecting trunk as junk" \
                "$svnrepo"/trunk@2 "$svnrepo"/junk ||
          svn cp -m "resurrecting trunk as junk" \
                -r2 "$svnrepo"/trunk "$svnrepo"/junk) &&
@@ -83,22 +85,23 @@ test_expect_success 'follow larger parent' '
         '
 
 test_expect_success 'follow higher-level parent' '
-        svn mkdir -m "follow higher-level parent" "$svnrepo"/blob &&
-        svn co "$svnrepo"/blob blob &&
-        cd blob &&
-                echo hi > hi &&
-                svn add hi &&
-                svn commit -m "hihi" &&
-                cd ..
-        svn mkdir -m "new glob at top level" "$svnrepo"/glob &&
-        svn mv -m "move blob down a level" "$svnrepo"/blob "$svnrepo"/glob/blob &&
-        git svn init --minimize-url -i blob "$svnrepo"/glob/blob &&
+       svn mkdir -m "follow higher-level parent" "$svnrepo"/blob &&
+       svn co "$svnrepo"/blob blob &&
+       (
+               cd blob &&
+               echo hi > hi &&
+               svn add hi &&
+               svn commit -m "hihi"
+       ) &&
+       svn mkdir -m "new glob at top level" "$svnrepo"/glob &&
+       svn mv -m "move blob down a level" "$svnrepo"/blob "$svnrepo"/glob/blob &&
+       git svn init --minimize-url -i blob "$svnrepo"/glob/blob &&
         git svn fetch -i blob
         '
 
 test_expect_success 'follow deleted directory' '
-       svn mv -m "bye!" "$svnrepo"/glob/blob/hi "$svnrepo"/glob/blob/bye &&
-       svn rm -m "remove glob" "$svnrepo"/glob &&
+       svn_cmd mv -m "bye!" "$svnrepo"/glob/blob/hi "$svnrepo"/glob/blob/bye &&
+       svn_cmd rm -m "remove glob" "$svnrepo"/glob &&
        git svn init --minimize-url -i glob "$svnrepo"/glob &&
        git svn fetch -i glob &&
        test "`git cat-file blob refs/remotes/glob:blob/bye`" = hi &&
@@ -117,18 +120,23 @@ test_expect_success 'follow-parent avoids deleting relevant info' '
           import/trunk/subversion/bindings/swig/perl/t/larger-parent &&
          echo "bad delete test 2" > \
           import/trunk/subversion/bindings/swig/perl/another-larger &&
-       cd import &&
-         svn import -m "r9270 test" . "$svnrepo"/r9270 &&
-       cd .. &&
-       svn co "$svnrepo"/r9270/trunk/subversion/bindings/swig/perl r9270 &&
-       cd r9270 &&
-         svn mkdir native &&
-         svn mv t native/t &&
-         for i in a b c; do svn mv $i.pm native/$i.pm; done &&
-         echo z >> native/t/c.t &&
-         poke native/t/c.t &&
-         svn commit -m "reorg test" &&
-       cd .. &&
+       (
+               cd import &&
+               svn import -m "r9270 test" . "$svnrepo"/r9270
+       ) &&
+       svn_cmd co "$svnrepo"/r9270/trunk/subversion/bindings/swig/perl r9270 &&
+       (
+               cd r9270 &&
+               svn mkdir native &&
+               svn mv t native/t &&
+               for i in a b c
+               do
+                       svn mv $i.pm native/$i.pm
+               done &&
+               echo z >>native/t/c.t &&
+               poke native/t/c.t &&
+               svn commit -m "reorg test"
+       ) &&
        git svn init --minimize-url -i r9270-t \
          "$svnrepo"/r9270/trunk/subversion/bindings/swig/perl/native/t &&
        git svn fetch -i r9270-t &&
@@ -138,7 +146,7 @@ test_expect_success 'follow-parent avoids deleting relevant info' '
        '
 
 test_expect_success "track initial change if it was only made to parent" '
-       svn cp -m "wheee!" "$svnrepo"/r9270/trunk "$svnrepo"/r9270/drunk &&
+       svn_cmd cp -m "wheee!" "$svnrepo"/r9270/trunk "$svnrepo"/r9270/drunk &&
        git svn init --minimize-url -i r9270-d \
          "$svnrepo"/r9270/drunk/subversion/bindings/swig/perl/native/t &&
        git svn fetch -i r9270-d &&
@@ -152,31 +160,31 @@ test_expect_success "track initial change if it was only made to parent" '
 test_expect_success "follow-parent is atomic" '
        (
                cd wc &&
-               svn up &&
-               svn mkdir stunk &&
+               svn_cmd up &&
+               svn_cmd mkdir stunk &&
                echo "trunk stunk" > stunk/readme &&
-               svn add stunk/readme &&
-               svn ci -m "trunk stunk" &&
+               svn_cmd add stunk/readme &&
+               svn_cmd ci -m "trunk stunk" &&
                echo "stunk like junk" >> stunk/readme &&
-               svn ci -m "really stunk" &&
+               svn_cmd ci -m "really stunk" &&
                echo "stink stank stunk" >> stunk/readme &&
-               svn ci -m "even the grinch agrees"
+               svn_cmd ci -m "even the grinch agrees"
        ) &&
-       svn copy -m "stunk flunked" "$svnrepo"/stunk "$svnrepo"/flunk &&
+       svn_cmd copy -m "stunk flunked" "$svnrepo"/stunk "$svnrepo"/flunk &&
        { svn cp -m "early stunk flunked too" \
                "$svnrepo"/stunk@17 "$svnrepo"/flunked ||
-       svn cp -m "early stunk flunked too" \
+       svn_cmd cp -m "early stunk flunked too" \
                -r17 "$svnrepo"/stunk "$svnrepo"/flunked; } &&
        git svn init --minimize-url -i stunk "$svnrepo"/stunk &&
        git svn fetch -i stunk &&
        git update-ref refs/remotes/flunk@18 refs/remotes/stunk~2 &&
        git update-ref -d refs/remotes/stunk &&
        git config --unset svn-remote.svn.fetch stunk &&
-       mkdir -p "$GIT_DIR"/svn/flunk@18 &&
-       rev_map=$(cd "$GIT_DIR"/svn/stunk && ls .rev_map*) &&
-       dd if="$GIT_DIR"/svn/stunk/$rev_map \
-          of="$GIT_DIR"/svn/flunk@18/$rev_map bs=24 count=1 &&
-       rm -rf "$GIT_DIR"/svn/stunk &&
+       mkdir -p "$GIT_DIR"/svn/refs/remotes/flunk@18 &&
+       rev_map=$(cd "$GIT_DIR"/svn/refs/remotes/stunk && ls .rev_map*) &&
+       dd if="$GIT_DIR"/svn/refs/remotes/stunk/$rev_map \
+          of="$GIT_DIR"/svn/refs/remotes/flunk@18/$rev_map bs=24 count=1 &&
+       rm -rf "$GIT_DIR"/svn/refs/remotes/stunk &&
        git svn init --minimize-url -i flunk "$svnrepo"/flunk &&
        git svn fetch -i flunk &&
        git svn init --minimize-url -i stunk "$svnrepo"/stunk &&
@@ -192,7 +200,7 @@ test_expect_success "follow-parent is atomic" '
        '
 
 test_expect_success "track multi-parent paths" '
-       svn cp -m "resurrect /glob" "$svnrepo"/r9270 "$svnrepo"/glob &&
+       svn_cmd cp -m "resurrect /glob" "$svnrepo"/r9270 "$svnrepo"/glob &&
        git svn multi-fetch &&
        test `git cat-file commit refs/remotes/glob | \
               grep "^parent " | wc -l` -eq 2
index ba99abb6d975351cf2725e757a588a26109a9723..5d0afeae6caf03ca34d7ef883baed512bcbcec51 100755 (executable)
@@ -6,10 +6,11 @@ test_description='git svn commit-diff'
 
 test_expect_success 'initialize repo' '
        mkdir import &&
-       cd import &&
-       echo hello > readme &&
-       svn import -m "initial" . "$svnrepo" &&
-       cd .. &&
+       (
+               cd import &&
+               echo hello >readme &&
+               svn_cmd import -m "initial" . "$svnrepo"
+       ) &&
        echo hello > readme &&
        git update-index --add readme &&
        git commit -a -m "initial" &&
@@ -27,16 +28,16 @@ prev=`git rev-parse --verify HEAD^1`
 test_expect_success 'test the commit-diff command' '
        test -n "$prev" && test -n "$head" &&
        git svn commit-diff -r1 "$prev" "$head" "$svnrepo" &&
-       svn co "$svnrepo" wc &&
+       svn_cmd co "$svnrepo" wc &&
        cmp readme wc/readme
        '
 
 test_expect_success 'commit-diff to a sub-directory (with git svn config)' '
-       svn import -m "sub-directory" import "$svnrepo"/subdir &&
+       svn_cmd import -m "sub-directory" import "$svnrepo"/subdir &&
        git svn init --minimize-url "$svnrepo"/subdir &&
        git svn fetch &&
        git svn commit-diff -r3 "$prev" "$head" &&
-       svn cat "$svnrepo"/subdir/readme > readme.2 &&
+       svn_cmd cat "$svnrepo"/subdir/readme > readme.2 &&
        cmp readme readme.2
        '
 
index 6eb0fd85c86ec194062af7da8c7002f0819bcc07..f6d7ac7c5f67871ce332716561ea6aba7a6f920f 100755 (executable)
@@ -6,21 +6,23 @@ test_description='git svn commit-diff clobber'
 
 test_expect_success 'initialize repo' '
        mkdir import &&
-       cd import &&
-       echo initial > file &&
-       svn import -m "initial" . "$svnrepo" &&
-       cd .. &&
+       (
+               cd import &&
+               echo initial >file &&
+               svn_cmd import -m "initial" . "$svnrepo"
+       ) &&
        echo initial > file &&
        git update-index --add file &&
        git commit -a -m "initial"
        '
 test_expect_success 'commit change from svn side' '
-       svn co "$svnrepo" t.svn &&
-       cd t.svn &&
-       echo second line from svn >> file &&
-       poke file &&
-       svn commit -m "second line from svn" &&
-       cd .. &&
+       svn_cmd co "$svnrepo" t.svn &&
+       (
+               cd t.svn &&
+               echo second line from svn >>file &&
+               poke file &&
+               svn_cmd commit -m "second line from svn"
+       ) &&
        rm -rf t.svn
        '
 
@@ -43,12 +45,13 @@ test_expect_success 'dcommit fails to commit because of conflict' '
        git svn init "$svnrepo" &&
        git svn fetch &&
        git reset --hard refs/${remotes_git_svn} &&
-       svn co "$svnrepo" t.svn &&
-       cd t.svn &&
-       echo fourth line from svn >> file &&
-       poke file &&
-       svn commit -m "fourth line from svn" &&
-       cd .. &&
+       svn_cmd co "$svnrepo" t.svn &&
+       (
+               cd t.svn &&
+               echo fourth line from svn >>file &&
+               poke file &&
+               svn_cmd commit -m "fourth line from svn"
+       ) &&
        rm -rf t.svn &&
        echo "fourth line from git" >> file &&
        git commit -a -m "fourth line from git" &&
@@ -67,12 +70,13 @@ test_expect_success 'dcommit does the svn equivalent of an index merge' "
        "
 
 test_expect_success 'commit another change from svn side' '
-       svn co "$svnrepo" t.svn &&
-       cd t.svn &&
-               echo third line from svn >> file &&
+       svn_cmd co "$svnrepo" t.svn &&
+       (
+               cd t.svn &&
+               echo third line from svn >>file &&
                poke file &&
-               svn commit -m "third line from svn" &&
-       cd .. &&
+               svn_cmd commit -m "third line from svn"
+       ) &&
        rm -rf t.svn
        '
 
index acad16a6f0f9b3b45b4766474e15ee5019ec2ce2..289fc313fb737ac7895580fd8407cff758b085e7 100755 (executable)
@@ -6,19 +6,19 @@ test_description='git svn metadata migrations from previous versions'
 test_expect_success 'setup old-looking metadata' '
        cp "$GIT_DIR"/config "$GIT_DIR"/config-old-git-svn &&
        mkdir import &&
-       cd import &&
-               for i in trunk branches/a branches/b \
-                        tags/0.1 tags/0.2 tags/0.3; do
-                       mkdir -p $i && \
-                       echo hello >> $i/README || exit 1
-               done && \
-               svn import -m test . "$svnrepo"
-               cd .. &&
+       (
+               cd import &&
+               for i in trunk branches/a branches/b tags/0.1 tags/0.2 tags/0.3
+               do
+                       mkdir -p $i &&
+                       echo hello >>$i/README ||
+                       exit 1
+               done &&
+               svn_cmd import -m test . "$svnrepo"
+       ) &&
        git svn init "$svnrepo" &&
        git svn fetch &&
-       mv "$GIT_DIR"/svn/* "$GIT_DIR"/ &&
-       mv "$GIT_DIR"/svn/.metadata "$GIT_DIR"/ &&
-       rmdir "$GIT_DIR"/svn &&
+       rm -rf "$GIT_DIR"/svn &&
        git update-ref refs/heads/git-svn-HEAD refs/${remotes_git_svn} &&
        git update-ref refs/heads/svn-HEAD refs/${remotes_git_svn} &&
        git update-ref -d refs/${remotes_git_svn} refs/${remotes_git_svn}
@@ -56,7 +56,15 @@ test_expect_success 'initialize a multi-repository repo' '
        git config --add svn-remote.svn.fetch "branches/b:refs/remotes/b" &&
        for i in tags/0.1 tags/0.2 tags/0.3; do
                git config --add svn-remote.svn.fetch \
-                                $i:refs/remotes/$i || exit 1; done
+                                $i:refs/remotes/$i || exit 1; done &&
+       git config --get-all svn-remote.svn.fetch > fetch.out &&
+       grep "^trunk:refs/remotes/trunk$" fetch.out &&
+       grep "^branches/a:refs/remotes/a$" fetch.out &&
+       grep "^branches/b:refs/remotes/b$" fetch.out &&
+       grep "^tags/0\.1:refs/remotes/tags/0\.1$" fetch.out &&
+       grep "^tags/0\.2:refs/remotes/tags/0\.2$" fetch.out &&
+       grep "^tags/0\.3:refs/remotes/tags/0\.3$" fetch.out &&
+       grep "^:refs/${remotes_git_svn}" fetch.out
        '
 
 # refs should all be different, but the trees should all be the same:
@@ -79,36 +87,36 @@ test_expect_success 'migrate --minimize on old inited layout' '
        rm -rf "$GIT_DIR"/svn &&
        for i in `cat fetch.out`; do
                path=`expr $i : "\([^:]*\):.*$"`
-               ref=`expr $i : "[^:]*:refs/remotes/\(.*\)$"`
+               ref=`expr $i : "[^:]*:\(refs/remotes/.*\)$"`
                if test -z "$ref"; then continue; fi
                if test -n "$path"; then path="/$path"; fi
                ( mkdir -p "$GIT_DIR"/svn/$ref/info/ &&
                echo "$svnrepo"$path > "$GIT_DIR"/svn/$ref/info/url ) || exit 1;
        done &&
        git svn migrate --minimize &&
-       test -z "`git config -l |grep -v "^svn-remote\.git-svn\."`" &&
+       test -z "`git config -l | grep "^svn-remote\.git-svn\."`" &&
        git config --get-all svn-remote.svn.fetch > fetch.out &&
        grep "^trunk:refs/remotes/trunk$" fetch.out &&
        grep "^branches/a:refs/remotes/a$" fetch.out &&
        grep "^branches/b:refs/remotes/b$" fetch.out &&
        grep "^tags/0\.1:refs/remotes/tags/0\.1$" fetch.out &&
        grep "^tags/0\.2:refs/remotes/tags/0\.2$" fetch.out &&
-       grep "^tags/0\.3:refs/remotes/tags/0\.3$" fetch.out
+       grep "^tags/0\.3:refs/remotes/tags/0\.3$" fetch.out &&
        grep "^:refs/${remotes_git_svn}" fetch.out
        '
 
 test_expect_success  ".rev_db auto-converted to .rev_map.UUID" '
        git svn fetch -i trunk &&
-       test -z "$(ls "$GIT_DIR"/svn/trunk/.rev_db.* 2>/dev/null)" &&
-       expect="$(ls "$GIT_DIR"/svn/trunk/.rev_map.*)" &&
+       test -z "$(ls "$GIT_DIR"/svn/refs/remotes/trunk/.rev_db.* 2>/dev/null)" &&
+       expect="$(ls "$GIT_DIR"/svn/refs/remotes/trunk/.rev_map.*)" &&
        test -n "$expect" &&
        rev_db="$(echo $expect | sed -e "s,_map,_db,")" &&
        convert_to_rev_db "$expect" "$rev_db" &&
        rm -f "$expect" &&
        test -f "$rev_db" &&
        git svn fetch -i trunk &&
-       test -z "$(ls "$GIT_DIR"/svn/trunk/.rev_db.* 2>/dev/null)" &&
-       test ! -e "$GIT_DIR"/svn/trunk/.rev_db &&
+       test -z "$(ls "$GIT_DIR"/svn/refs/remotes/trunk/.rev_db.* 2>/dev/null)" &&
+       test ! -e "$GIT_DIR"/svn/refs/remotes/trunk/.rev_db &&
        test -f "$expect"
        '
 
index d8582b1aa5d178778623bfb4386a66f58e165c17..d732d3130299e964359784949fc97805a2888e39 100755 (executable)
@@ -14,30 +14,30 @@ test_expect_success 'test refspec globbing' '
        mkdir -p trunk/src/a trunk/src/b trunk/doc &&
        echo "hello world" > trunk/src/a/readme &&
        echo "goodbye world" > trunk/src/b/readme &&
-       svn import -m "initial" trunk "$svnrepo"/trunk &&
-       svn co "$svnrepo" tmp &&
+       svn_cmd import -m "initial" trunk "$svnrepo"/trunk &&
+       svn_cmd co "$svnrepo" tmp &&
        (
                cd tmp &&
                mkdir branches tags &&
-               svn add branches tags &&
-               svn cp trunk branches/start &&
-               svn commit -m "start a new branch" &&
-               svn up &&
+               svn_cmd add branches tags &&
+               svn_cmd cp trunk branches/start &&
+               svn_cmd commit -m "start a new branch" &&
+               svn_cmd up &&
                echo "hi" >> branches/start/src/b/readme &&
                poke branches/start/src/b/readme &&
                echo "hey" >> branches/start/src/a/readme &&
                poke branches/start/src/a/readme &&
-               svn commit -m "hi" &&
-               svn up &&
-               svn cp branches/start tags/end &&
+               svn_cmd commit -m "hi" &&
+               svn_cmd up &&
+               svn_cmd cp branches/start tags/end &&
                echo "bye" >> tags/end/src/b/readme &&
                poke tags/end/src/b/readme &&
                echo "aye" >> tags/end/src/a/readme &&
                poke tags/end/src/a/readme &&
-               svn commit -m "the end" &&
+               svn_cmd commit -m "the end" &&
                echo "byebye" >> tags/end/src/b/readme &&
                poke tags/end/src/b/readme &&
-               svn commit -m "nothing to see here"
+               svn_cmd commit -m "nothing to see here"
        ) &&
        git config --add svn-remote.svn.url "$svnrepo" &&
        git config --add svn-remote.svn.fetch \
@@ -72,7 +72,7 @@ test_expect_success 'test left-hand-side only globbing' '
                cd tmp &&
                echo "try try" >> tags/end/src/b/readme &&
                poke tags/end/src/b/readme &&
-               svn commit -m "try to try"
+               svn_cmd commit -m "try to try"
        ) &&
        git svn fetch two &&
        test `git rev-list refs/remotes/two/tags/end | wc -l` -eq 6 &&
@@ -102,7 +102,7 @@ test_expect_success 'test disallow multi-globs' '
                cd tmp &&
                echo "try try" >> tags/end/src/b/readme &&
                poke tags/end/src/b/readme &&
-               svn commit -m "try to try"
+               svn_cmd commit -m "try to try"
        ) &&
        test_must_fail git svn fetch three 2> stderr.three &&
        test_cmp expect.three stderr.three
index 8f79c3f251aed240e0ba59244a8cfd91db2369d2..c318f9f946a35f0d13b90f67dd00a6817d77d989 100755 (executable)
@@ -14,30 +14,30 @@ test_expect_success 'test refspec globbing' '
        mkdir -p trunk/src/a trunk/src/b trunk/doc &&
        echo "hello world" > trunk/src/a/readme &&
        echo "goodbye world" > trunk/src/b/readme &&
-       svn import -m "initial" trunk "$svnrepo"/trunk &&
-       svn co "$svnrepo" tmp &&
+       svn_cmd import -m "initial" trunk "$svnrepo"/trunk &&
+       svn_cmd co "$svnrepo" tmp &&
        (
                cd tmp &&
                mkdir branches branches/v1 tags &&
-               svn add branches tags &&
-               svn cp trunk branches/v1/start &&
-               svn commit -m "start a new branch" &&
-               svn up &&
+               svn_cmd add branches tags &&
+               svn_cmd cp trunk branches/v1/start &&
+               svn_cmd commit -m "start a new branch" &&
+               svn_cmd up &&
                echo "hi" >> branches/v1/start/src/b/readme &&
                poke branches/v1/start/src/b/readme &&
                echo "hey" >> branches/v1/start/src/a/readme &&
                poke branches/v1/start/src/a/readme &&
-               svn commit -m "hi" &&
-               svn up &&
-               svn cp branches/v1/start tags/end &&
+               svn_cmd commit -m "hi" &&
+               svn_cmd up &&
+               svn_cmd cp branches/v1/start tags/end &&
                echo "bye" >> tags/end/src/b/readme &&
                poke tags/end/src/b/readme &&
                echo "aye" >> tags/end/src/a/readme &&
                poke tags/end/src/a/readme &&
-               svn commit -m "the end" &&
+               svn_cmd commit -m "the end" &&
                echo "byebye" >> tags/end/src/b/readme &&
                poke tags/end/src/b/readme &&
-               svn commit -m "nothing to see here"
+               svn_cmd commit -m "nothing to see here"
        ) &&
        git config --add svn-remote.svn.url "$svnrepo" &&
        git config --add svn-remote.svn.fetch \
@@ -72,7 +72,7 @@ test_expect_success 'test left-hand-side only globbing' '
                cd tmp &&
                echo "try try" >> tags/end/src/b/readme &&
                poke tags/end/src/b/readme &&
-               svn commit -m "try to try"
+               svn_cmd commit -m "try to try"
        ) &&
        git svn fetch two &&
        test `git rev-list refs/remotes/two/tags/end | wc -l` -eq 6 &&
@@ -97,25 +97,25 @@ test_expect_success 'test another branch' '
        (
                cd tmp &&
                mkdir branches/v2 &&
-               svn add branches/v2 &&
-               svn cp trunk branches/v2/start &&
-               svn commit -m "Another versioned branch" &&
-               svn up &&
+               svn_cmd add branches/v2 &&
+               svn_cmd cp trunk branches/v2/start &&
+               svn_cmd commit -m "Another versioned branch" &&
+               svn_cmd up &&
                echo "hello" >> branches/v2/start/src/b/readme &&
                poke branches/v2/start/src/b/readme &&
                echo "howdy" >> branches/v2/start/src/a/readme &&
                poke branches/v2/start/src/a/readme &&
-               svn commit -m "Changed 2 in v2/start" &&
-               svn up &&
-               svn cp branches/v2/start tags/next &&
+               svn_cmd commit -m "Changed 2 in v2/start" &&
+               svn_cmd up &&
+               svn_cmd cp branches/v2/start tags/next &&
                echo "bye" >> tags/next/src/b/readme &&
                poke tags/next/src/b/readme &&
                echo "aye" >> tags/next/src/a/readme &&
                poke tags/next/src/a/readme &&
-               svn commit -m "adding more" &&
+               svn_cmd commit -m "adding more" &&
                echo "byebye" >> tags/next/src/b/readme &&
                poke tags/next/src/b/readme &&
-               svn commit -m "adios"
+               svn_cmd commit -m "adios"
        ) &&
        git config --add svn-remote.four.url "$svnrepo" &&
        git config --add svn-remote.four.fetch trunk:refs/remotes/four/trunk &&
@@ -151,7 +151,7 @@ test_expect_success 'test disallow multiple globs' '
                cd tmp &&
                echo "try try" >> tags/end/src/b/readme &&
                poke tags/end/src/b/readme &&
-               svn commit -m "try to try"
+               svn_cmd commit -m "try to try"
        ) &&
        test_must_fail git svn fetch three 2> stderr.three &&
        test_cmp expect.three stderr.three
index e9b6128b3fa5c3d0cbbe5abb7f3e55267263449d..e8479cec7abade29aec91b22bba69d4ab9804f21 100755 (executable)
@@ -15,7 +15,7 @@ test_description='git svn dcommit new files over svn:// test'
 require_svnserve
 
 test_expect_success 'start tracking an empty repo' '
-       svn mkdir -m "empty dir" "$svnrepo"/empty-dir &&
+       svn_cmd mkdir -m "empty dir" "$svnrepo"/empty-dir &&
        echo "[general]" > "$rawsvnrepo"/conf/svnserve.conf &&
        echo anon-access = write >> "$rawsvnrepo"/conf/svnserve.conf &&
        start_svnserve &&
index 17b2855c4f308d463ea239f355549120dab4f80f..3077851015879fc147ded9bb573d02c13967eec9 100755 (executable)
@@ -35,13 +35,14 @@ EOF
 }
 
 test_expect_success 'setup svn repository' '
-       svn co "$svnrepo" mysvnwork &&
+       svn_cmd co "$svnrepo" mysvnwork &&
        mkdir -p mysvnwork/trunk &&
-       cd mysvnwork &&
-               big_text_block >> trunk/README &&
-               svn add trunk &&
-               svn ci -m "first commit" trunk &&
-               cd ..
+       (
+               cd mysvnwork &&
+               big_text_block >>trunk/README &&
+               svn_cmd add trunk &&
+               svn_cmd ci -m "first commit" trunk
+       )
        '
 
 test_expect_success 'setup git mirror and merge' '
index 9be7aefaeea0af4d988ca656b3df0008f04a58d3..6a48e4042996549e40843186783e2215beee9cca 100755 (executable)
@@ -19,7 +19,7 @@ test_expect_success 'init and fetch repository' '
        '
 
 test_expect_success 'create file in existing ugly and empty dir' '
-       mkdir "#{bad_directory_name}" &&
+       mkdir -p "#{bad_directory_name}" &&
        echo hi > "#{bad_directory_name}/ foo" &&
        git update-index --add "#{bad_directory_name}/ foo" &&
        git commit -m "new file in ugly parent" &&
@@ -37,7 +37,7 @@ test_expect_success 'rename pretty file' '
        git update-index --add pretty &&
        git commit -m "pretty :x" &&
        git svn dcommit &&
-       mkdir regular_dir_name &&
+       mkdir -p regular_dir_name &&
        git mv pretty regular_dir_name/pretty &&
        git commit -m "moved pretty file" &&
        git svn dcommit
@@ -61,11 +61,12 @@ test_expect_success 'add a file with plus signs' '
 
 test_expect_success 'clone the repository to test rebase' '
        git svn clone "$svnrepo" test-rebase &&
-       cd test-rebase &&
-               echo test-rebase > test-rebase &&
+       (
+               cd test-rebase &&
+               echo test-rebase >test-rebase &&
                git add test-rebase &&
-               git commit -m test-rebase &&
-               cd ..
+               git commit -m test-rebase
+       )
        '
 
 test_expect_success 'make a commit to test rebase' '
index fd6d1d20463f2a4de1bd8ed739c0e977921e037c..5d477e4bdadd2168d0331e2ab809c5c86c8fa532 100755 (executable)
@@ -8,14 +8,16 @@ test_description='git svn log tests'
 
 test_expect_success 'setup repository and import' '
        mkdir import &&
-       cd import &&
-               for i in trunk branches/a branches/b \
-                        tags/0.1 tags/0.2 tags/0.3; do
-                       mkdir -p $i && \
-                       echo hello >> $i/README || exit 1
-               done && \
-               svn import -m test . "$svnrepo"
-               cd .. &&
+       (
+               cd import &&
+               for i in trunk branches/a branches/b tags/0.1 tags/0.2 tags/0.3
+               do
+                       mkdir -p $i &&
+                       echo hello >>$i/README ||
+                       exit 1
+               done &&
+               svn_cmd import -m test . "$svnrepo"
+       ) &&
        git svn init "$svnrepo" -T trunk -b branches -t tags &&
        git svn fetch &&
        git reset --hard trunk &&
index dde46cd92fba24221393b15233ed248997161caf..b7ef9e25895550e8749952e2ed54d7827737242e 100755 (executable)
@@ -16,7 +16,7 @@ cd tmp
 test_expect_success 'setup svnrepo' '
        mkdir project project/trunk project/branches project/tags &&
        echo foo > project/trunk/foo &&
-       svn import -m "$test_description" project "$svnrepo"/project &&
+       svn_cmd import -m "$test_description" project "$svnrepo"/project &&
        rm -rf project
        '
 
index 7a7c12868758d6e3bd9d1f8ec4fe32c0663115f4..63fc982c8cdbd9c19eb06bba58ad5e86da5dd03e 100755 (executable)
@@ -13,42 +13,67 @@ scary_ref='Abo-Uebernahme%20(Bug%20#994)'
 test_expect_success 'setup svnrepo' '
        mkdir project project/trunk project/branches project/tags &&
        echo foo > project/trunk/foo &&
-       svn import -m "$test_description" project "$svnrepo/pr ject" &&
+       svn_cmd import -m "$test_description" project "$svnrepo/pr ject" &&
        rm -rf project &&
-       svn cp -m "fun" "$svnrepo/pr ject/trunk" \
+       svn_cmd cp -m "fun" "$svnrepo/pr ject/trunk" \
                        "$svnrepo/pr ject/branches/fun plugin" &&
-       svn cp -m "more fun!" "$svnrepo/pr ject/branches/fun plugin" \
+       svn_cmd cp -m "more fun!" "$svnrepo/pr ject/branches/fun plugin" \
                              "$svnrepo/pr ject/branches/more fun plugin!" &&
-       svn cp -m "scary" "$svnrepo/pr ject/branches/fun plugin" \
+       svn_cmd cp -m "scary" "$svnrepo/pr ject/branches/fun plugin" \
                      "$svnrepo/pr ject/branches/$scary_uri" &&
+       svn_cmd cp -m "leading dot" "$svnrepo/pr ject/trunk" \
+                       "$svnrepo/pr ject/branches/.leading_dot" &&
+       svn_cmd cp -m "trailing dot" "$svnrepo/pr ject/trunk" \
+                       "$svnrepo/pr ject/branches/trailing_dot." &&
+       svn_cmd cp -m "trailing .lock" "$svnrepo/pr ject/trunk" \
+                       "$svnrepo/pr ject/branches/trailing_dotlock.lock" &&
+       svn_cmd cp -m "reflog" "$svnrepo/pr ject/trunk" \
+                       "$svnrepo/pr ject/branches/not-a%40{0}reflog" &&
        start_httpd
        '
 
 test_expect_success 'test clone with funky branch names' '
        git svn clone -s "$svnrepo/pr ject" project &&
-       cd project &&
+       (
+               cd project &&
                git rev-parse "refs/remotes/fun%20plugin" &&
                git rev-parse "refs/remotes/more%20fun%20plugin!" &&
                git rev-parse "refs/remotes/$scary_ref" &&
-       cd ..
+               git rev-parse "refs/remotes/%2Eleading_dot" &&
+               git rev-parse "refs/remotes/trailing_dot%2E" &&
+               git rev-parse "refs/remotes/trailing_dotlock%2Elock" &&
+               git rev-parse "refs/remotes/not-a%40{0}reflog"
+       )
        '
 
 test_expect_success 'test dcommit to funky branch' "
-       cd project &&
-       git reset --hard 'refs/remotes/more%20fun%20plugin!' &&
-       echo hello >> foo &&
-       git commit -m 'hello' -- foo &&
-       git svn dcommit &&
-       cd ..
+       (
+               cd project &&
+               git reset --hard 'refs/remotes/more%20fun%20plugin!' &&
+               echo hello >> foo &&
+               git commit -m 'hello' -- foo &&
+               git svn dcommit
+       )
        "
 
 test_expect_success 'test dcommit to scary branch' '
-       cd project &&
-       git reset --hard "refs/remotes/$scary_ref" &&
-       echo urls are scary >> foo &&
-       git commit -m "eep" -- foo &&
-       git svn dcommit &&
-       cd ..
+       (
+               cd project &&
+               git reset --hard "refs/remotes/$scary_ref" &&
+               echo urls are scary >> foo &&
+               git commit -m "eep" -- foo &&
+               git svn dcommit
+       )
+       '
+
+test_expect_success 'test dcommit to trailing_dotlock branch' '
+       (
+               cd project &&
+               git reset --hard "refs/remotes/trailing_dotlock%2Elock" &&
+               echo who names branches like this anyway? >> foo &&
+               git commit -m "bar" -- foo &&
+               git svn dcommit
+       )
        '
 
 stop_httpd
index 27dd7c273a4a1ae77a8e48b2e4b4ce1a3ae19a56..f3f397cdda4766ce6c02beac13e492d406ca14c4 100755 (executable)
@@ -7,12 +7,13 @@ test_description='git svn info'
 . ./lib-git-svn.sh
 
 # Tested with: svn, version 1.4.4 (r25188)
-v=`svn --version | sed -n -e 's/^svn, version \(1\.[0-9]*\.[0-9]*\).*$/\1/p'`
+# Tested with: svn, version 1.6.[12345689]
+v=`svn_cmd --version | sed -n -e 's/^svn, version \(1\.[0-9]*\.[0-9]*\).*$/\1/p'`
 case $v in
-1.[45].*)
+1.[456].*)
        ;;
 *)
-       say "skipping svn-info test (SVN version: $v not supported)"
+       skip_all="skipping svn-info test (SVN version: $v not supported)"
        test_done
        ;;
 esac
@@ -31,34 +32,37 @@ ptouch() {
                        my $atime = $mtime;
                        utime $atime, $mtime, $git_file;
                }
-       ' "`svn info $2 | grep '^Text Last Updated:'`" "$1"
+       ' "`svn_cmd info $2 | grep '^Text Last Updated:'`" "$1"
 }
 
 quoted_svnrepo="$(echo $svnrepo | sed 's/ /%20/')"
 
 test_expect_success 'setup repository and import' '
        mkdir info &&
-       cd info &&
-               echo FIRST > A &&
-               echo one > file &&
+       (
+               cd info &&
+               echo FIRST >A &&
+               echo one >file &&
                ln -s file symlink-file &&
                mkdir directory &&
                touch directory/.placeholder &&
                ln -s directory symlink-directory &&
-               svn import -m "initial" . "$svnrepo" &&
-       cd .. &&
-       svn co "$svnrepo" svnwc &&
-       cd svnwc &&
-               echo foo > foo &&
-               svn add foo &&
-               svn commit -m "change outside directory" &&
-               svn update &&
-       cd .. &&
+               svn_cmd import -m "initial" . "$svnrepo"
+       ) &&
+       svn_cmd co "$svnrepo" svnwc &&
+       (
+               cd svnwc &&
+               echo foo >foo &&
+               svn_cmd add foo &&
+               svn_cmd commit -m "change outside directory" &&
+               svn_cmd update
+       ) &&
        mkdir gitwc &&
-       cd gitwc &&
+       (
+               cd gitwc &&
                git svn init "$svnrepo" &&
-               git svn fetch &&
-       cd .. &&
+               git svn fetch
+       ) &&
        ptouch gitwc/file svnwc/file &&
        ptouch gitwc/directory svnwc/directory &&
        ptouch gitwc/symlink-file svnwc/symlink-file &&
@@ -137,14 +141,16 @@ test_expect_success 'info --url symlink-directory' '
 
 test_expect_success 'info added-file' "
        echo two > gitwc/added-file &&
-       cd gitwc &&
-               git add added-file &&
-       cd .. &&
+       (
+               cd gitwc &&
+               git add added-file
+       ) &&
        cp gitwc/added-file svnwc/added-file &&
        ptouch gitwc/added-file svnwc/added-file &&
-       cd svnwc &&
-               svn add added-file > /dev/null &&
-       cd .. &&
+       (
+               cd svnwc &&
+               svn_cmd add added-file > /dev/null
+       ) &&
        (cd svnwc; svn info added-file) > expected.info-added-file &&
        (cd gitwc; git svn info added-file) > actual.info-added-file &&
        test_cmp expected.info-added-file actual.info-added-file
@@ -159,12 +165,14 @@ test_expect_success 'info added-directory' "
        mkdir gitwc/added-directory svnwc/added-directory &&
        ptouch gitwc/added-directory svnwc/added-directory &&
        touch gitwc/added-directory/.placeholder &&
-       cd svnwc &&
-               svn add added-directory > /dev/null &&
-       cd .. &&
-       cd gitwc &&
-               git add added-directory &&
-       cd .. &&
+       (
+               cd svnwc &&
+               svn_cmd add added-directory > /dev/null
+       ) &&
+       (
+               cd gitwc &&
+               git add added-directory
+       ) &&
        (cd svnwc; svn info added-directory) \
                > expected.info-added-directory &&
        (cd gitwc; git svn info added-directory) \
@@ -178,14 +186,16 @@ test_expect_success 'info --url added-directory' '
        '
 
 test_expect_success 'info added-symlink-file' "
-       cd gitwc &&
+       (
+               cd gitwc &&
                ln -s added-file added-symlink-file &&
-               git add added-symlink-file &&
-       cd .. &&
-       cd svnwc &&
+               git add added-symlink-file
+       ) &&
+       (
+               cd svnwc &&
                ln -s added-file added-symlink-file &&
-               svn add added-symlink-file > /dev/null &&
-       cd .. &&
+               svn_cmd add added-symlink-file > /dev/null
+       ) &&
        ptouch gitwc/added-symlink-file svnwc/added-symlink-file &&
        (cd svnwc; svn info added-symlink-file) \
                > expected.info-added-symlink-file &&
@@ -201,14 +211,16 @@ test_expect_success 'info --url added-symlink-file' '
        '
 
 test_expect_success 'info added-symlink-directory' "
-       cd gitwc &&
+       (
+               cd gitwc &&
                ln -s added-directory added-symlink-directory &&
-               git add added-symlink-directory &&
-       cd .. &&
-       cd svnwc &&
+               git add added-symlink-directory
+       ) &&
+       (
+               cd svnwc &&
                ln -s added-directory added-symlink-directory &&
-               svn add added-symlink-directory > /dev/null &&
-       cd .. &&
+               svn_cmd add added-symlink-directory > /dev/null
+       ) &&
        ptouch gitwc/added-symlink-directory svnwc/added-symlink-directory &&
        (cd svnwc; svn info added-symlink-directory) \
                > expected.info-added-symlink-directory &&
@@ -229,12 +241,14 @@ test_expect_success 'info --url added-symlink-directory' '
 # simply reuses the Last Changed Date.
 
 test_expect_success 'info deleted-file' "
-       cd gitwc &&
-               git rm -f file > /dev/null &&
-       cd .. &&
-       cd svnwc &&
-               svn rm --force file > /dev/null &&
-       cd .. &&
+       (
+               cd gitwc &&
+               git rm -f file > /dev/null
+       ) &&
+       (
+               cd svnwc &&
+               svn_cmd rm --force file > /dev/null
+       ) &&
        (cd svnwc; svn info file) |
        sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
                > expected.info-deleted-file &&
@@ -250,12 +264,14 @@ test_expect_success 'info --url file (deleted)' '
        '
 
 test_expect_success 'info deleted-directory' "
-       cd gitwc &&
-               git rm -r -f directory > /dev/null &&
-       cd .. &&
-       cd svnwc &&
-               svn rm --force directory > /dev/null &&
-       cd .. &&
+       (
+               cd gitwc &&
+               git rm -r -f directory > /dev/null
+       ) &&
+       (
+               cd svnwc &&
+               svn_cmd rm --force directory > /dev/null
+       ) &&
        (cd svnwc; svn info directory) |
        sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
                > expected.info-deleted-directory &&
@@ -271,12 +287,14 @@ test_expect_success 'info --url directory (deleted)' '
        '
 
 test_expect_success 'info deleted-symlink-file' "
-       cd gitwc &&
-               git rm -f symlink-file > /dev/null &&
-       cd .. &&
-       cd svnwc &&
-               svn rm --force symlink-file > /dev/null &&
-       cd .. &&
+       (
+               cd gitwc &&
+               git rm -f symlink-file > /dev/null
+       ) &&
+       (
+               cd svnwc &&
+               svn_cmd rm --force symlink-file > /dev/null
+       ) &&
        (cd svnwc; svn info symlink-file) |
        sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
                > expected.info-deleted-symlink-file &&
@@ -293,12 +311,14 @@ test_expect_success 'info --url symlink-file (deleted)' '
        '
 
 test_expect_success 'info deleted-symlink-directory' "
-       cd gitwc &&
-               git rm -f symlink-directory > /dev/null &&
-       cd .. &&
-       cd svnwc &&
-               svn rm --force symlink-directory > /dev/null &&
-       cd .. &&
+       (
+               cd gitwc &&
+               git rm -f symlink-directory > /dev/null
+       ) &&
+       (
+               cd svnwc &&
+               svn_cmd rm --force symlink-directory > /dev/null
+       ) &&
        (cd svnwc; svn info symlink-directory) |
        sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
                 > expected.info-deleted-symlink-directory &&
@@ -345,9 +365,10 @@ test_expect_success 'info --url unknown-directory' '
        '
 
 test_expect_success 'info unknown-symlink-file' "
-       cd gitwc &&
-               ln -s unknown-file unknown-symlink-file &&
-       cd .. &&
+       (
+               cd gitwc &&
+               ln -s unknown-file unknown-symlink-file
+       ) &&
        (cd gitwc; test_must_fail git svn info unknown-symlink-file) \
                 2> actual.info-unknown-symlink-file &&
        grep unknown-symlink-file actual.info-unknown-symlink-file
@@ -360,9 +381,10 @@ test_expect_success 'info --url unknown-symlink-file' '
        '
 
 test_expect_success 'info unknown-symlink-directory' "
-       cd gitwc &&
-               ln -s unknown-directory unknown-symlink-directory &&
-       cd .. &&
+       (
+               cd gitwc &&
+               ln -s unknown-directory unknown-symlink-directory
+       ) &&
        (cd gitwc; test_must_fail git svn info unknown-symlink-directory) \
                 2> actual.info-unknown-symlink-directory &&
        grep unknown-symlink-directory actual.info-unknown-symlink-directory
index ef2c0523cd4566b3d1c4925ba608efc16524546a..1d92c05035f8fe68885a6338ceca4307f19e8663 100755 (executable)
@@ -9,22 +9,70 @@ test_description='git svn clone with percent escapes'
 test_expect_success 'setup svnrepo' '
        mkdir project project/trunk project/branches project/tags &&
        echo foo > project/trunk/foo &&
-       svn import -m "$test_description" project "$svnrepo/pr ject" &&
+       svn_cmd import -m "$test_description" project "$svnrepo/pr ject" &&
+       svn_cmd cp -m "branch" "$svnrepo/pr ject/trunk" \
+         "$svnrepo/pr ject/branches/b" &&
+       svn_cmd cp -m "tag" "$svnrepo/pr ject/trunk" \
+         "$svnrepo/pr ject/tags/v1" &&
        rm -rf project &&
        start_httpd
 '
 
-if test "$SVN_HTTPD_PORT" = ""
-then
-       test_expect_failure 'test clone with percent escapes - needs SVN_HTTPD_PORT set' 'false'
-else
-       test_expect_success 'test clone with percent escapes' '
-               git svn clone "$svnrepo/pr%20ject" clone &&
+test_expect_success 'test clone with percent escapes' '
+       git svn clone "$svnrepo/pr%20ject" clone &&
+       (
                cd clone &&
-                       git rev-parse refs/${remotes_git_svn} &&
-               cd ..
-       '
-fi
+               git rev-parse refs/${remotes_git_svn}
+       )
+'
+
+# SVN works either way, so should we...
+
+test_expect_success 'svn checkout with percent escapes' '
+       svn_cmd checkout "$svnrepo/pr%20ject" svn.percent &&
+       svn_cmd checkout "$svnrepo/pr%20ject/trunk" svn.percent.trunk
+'
+
+test_expect_success 'svn checkout with space' '
+       svn_cmd checkout "$svnrepo/pr ject" svn.space &&
+       svn_cmd checkout "$svnrepo/pr ject/trunk" svn.space.trunk
+'
+
+test_expect_success 'test clone trunk with percent escapes and minimize-url' '
+       git svn clone --minimize-url "$svnrepo/pr%20ject/trunk" minimize &&
+       (
+               cd minimize &&
+               git rev-parse refs/${remotes_git_svn}
+       )
+'
+
+test_expect_success 'test clone trunk with percent escapes' '
+       git svn clone "$svnrepo/pr%20ject/trunk" trunk &&
+       (
+               cd trunk &&
+               git rev-parse refs/${remotes_git_svn}
+       )
+'
+
+test_expect_success 'test clone --stdlayout with percent escapes' '
+       git svn clone --stdlayout "$svnrepo/pr%20ject" percent &&
+       (
+               cd percent &&
+               git rev-parse refs/remotes/trunk^0 &&
+               git rev-parse refs/remotes/b^0 &&
+               git rev-parse refs/remotes/tags/v1^0
+       )
+'
+
+test_expect_success 'test clone -s with unescaped space' '
+       git svn clone -s "$svnrepo/pr ject" space &&
+       (
+               cd space &&
+               git rev-parse refs/remotes/trunk^0 &&
+               git rev-parse refs/remotes/b^0 &&
+               git rev-parse refs/remotes/tags/v1^0
+       )
+'
 
 stop_httpd
 
index 1b1cf47281d10c64ac57e96701c727f19f189948..30013b7bb948640869d5c5208b2feeb08c011b73 100755 (executable)
@@ -4,12 +4,12 @@ test_description='git svn authorship'
 . ./lib-git-svn.sh
 
 test_expect_success 'setup svn repository' '
-       svn checkout "$svnrepo" work.svn &&
+       svn_cmd checkout "$svnrepo" work.svn &&
        (
                cd work.svn &&
                echo >file
-               svn add file
-               svn commit -m "first commit" file
+               svn_cmd add file
+               svn_cmd commit -m "first commit" file
        )
 '
 
@@ -74,10 +74,10 @@ test_expect_success 'interact with it via git svn' '
        # Make sure there are no svn commit messages with excess blank lines
        (
                cd work.svn &&
-               svn up &&
+               svn_cmd up &&
                
-               test $(svn log -r2:2 | wc -l) = 5 &&
-               test $(svn log -r4:4 | wc -l) = 7
+               test $(svn_cmd log -r2:2 | wc -l) = 5 &&
+               test $(svn_cmd log -r4:4 | wc -l) = 7
        )
 '
 
index cf0415274c2d2abae7a6850818f4de8063cb61a7..0ed90d982d28810c4f7a018fab3d2f8699b78d95 100755 (executable)
@@ -8,10 +8,10 @@ test_description='git svn respects rewriteRoot during rebuild'
 . ./lib-git-svn.sh
 
 mkdir import
-cd import
+(cd import
        touch foo
-       svn import -m 'import for git svn' . "$svnrepo" >/dev/null
-cd ..
+       svn_cmd import -m 'import for git svn' . "$svnrepo" >/dev/null
+)
 rm -rf import
 
 test_expect_success 'init, fetch and checkout repository' '
index 263dbf5fc276ffa052096810f59565a851245b7f..d6b076f6b7de148c8d548c7a977e4f09b4be8e3b 100755 (executable)
@@ -21,7 +21,7 @@ test_expect_success 'initialize git svn' '
        (
                cd import &&
                echo foo >foo &&
-               svn import -m "import for git svn" . "$svnrepo"
+               svn_cmd import -m "import for git svn" . "$svnrepo"
        ) &&
        rm -rf import &&
        git svn init "$svnrepo"
@@ -61,23 +61,23 @@ test_expect_success 'check resulting svn repository' '
 (
        mkdir work &&
        cd work &&
-       svn co "$svnrepo" &&
+       svn_cmd co "$svnrepo" &&
        cd svnrepo &&
 
        # Check properties from first commit.
-       test "x$(svn propget svn:executable exec1.sh)" = "x*" &&
-       test "x$(svn propget svn:mime-type exec1.sh)" = \
+       test "x$(svn_cmd propget svn:executable exec1.sh)" = "x*" &&
+       test "x$(svn_cmd propget svn:mime-type exec1.sh)" = \
             "xapplication/x-shellscript" &&
-       test "x$(svn propget svn:mime-type hello.txt)" = "xtext/plain" &&
-       test "x$(svn propget svn:eol-style hello.txt)" = "xnative" &&
-       test "x$(svn propget svn:mime-type bar)" = "x" &&
+       test "x$(svn_cmd propget svn:mime-type hello.txt)" = "xtext/plain" &&
+       test "x$(svn_cmd propget svn:eol-style hello.txt)" = "xnative" &&
+       test "x$(svn_cmd propget svn:mime-type bar)" = "x" &&
 
        # Check properties from second commit.
-       test "x$(svn propget svn:executable exec2.sh)" = "x*" &&
-       test "x$(svn propget svn:mime-type exec2.sh)" = "x" &&
-       test "x$(svn propget svn:mime-type world.txt)" = "x" &&
-       test "x$(svn propget svn:eol-style world.txt)" = "x" &&
-       test "x$(svn propget svn:mime-type zot)" = "x"
+       test "x$(svn_cmd propget svn:executable exec2.sh)" = "x*" &&
+       test "x$(svn_cmd propget svn:mime-type exec2.sh)" = "x" &&
+       test "x$(svn_cmd propget svn:mime-type world.txt)" = "x" &&
+       test "x$(svn_cmd propget svn:eol-style world.txt)" = "x" &&
+       test "x$(svn_cmd propget svn:mime-type zot)" = "x"
 )
 '
 
@@ -89,12 +89,12 @@ test_expect_success 'check renamed file' '
        git svn dcommit --config-dir=user &&
        (
                cd work/svnrepo &&
-               svn up &&
+               svn_cmd up &&
                test ! -e foo &&
                test -e foo.sh &&
-               test "x$(svn propget svn:mime-type foo.sh)" = \
+               test "x$(svn_cmd propget svn:mime-type foo.sh)" = \
                     "xapplication/x-shellscript" &&
-               test "x$(svn propget svn:eol-style foo.sh)" = "xLF"
+               test "x$(svn_cmd propget svn:eol-style foo.sh)" = "xLF"
        )
 '
 
index 475c751c1cb88ce92a18beb2f9c7362a29ae4a5b..096abd1fe5e37444a442e698a5d5734823a45798 100755 (executable)
@@ -8,30 +8,30 @@ test_expect_success 'setup svnrepo' '
        mkdir project project/trunk project/branches \
                        project/branches/v14.1 project/tags &&
        echo foo > project/trunk/foo &&
-       svn import -m "$test_description" project "$svnrepo/project" &&
+       svn_cmd import -m "$test_description" project "$svnrepo/project" &&
        rm -rf project &&
-       svn cp -m "fun" "$svnrepo/project/trunk" \
+       svn_cmd cp -m "fun" "$svnrepo/project/trunk" \
                        "$svnrepo/project/branches/v14.1/beta" &&
-       svn cp -m "more fun!" "$svnrepo/project/branches/v14.1/beta" \
+       svn_cmd cp -m "more fun!" "$svnrepo/project/branches/v14.1/beta" \
                              "$svnrepo/project/branches/v14.1/gold"
        '
 
 test_expect_success 'test clone with multi-glob in branch names' '
        git svn clone -T trunk -b branches/*/* -t tags \
                      "$svnrepo/project" project &&
-       cd project &&
+       (cd project &&
                git rev-parse "refs/remotes/v14.1/beta" &&
-               git rev-parse "refs/remotes/v14.1/gold" &&
-       cd ..
+               git rev-parse "refs/remotes/v14.1/gold"
+       )
        '
 
 test_expect_success 'test dcommit to multi-globbed branch' "
-       cd project &&
+       (cd project &&
        git reset --hard 'refs/remotes/v14.1/gold' &&
        echo hello >> foo &&
        git commit -m 'hello' -- foo &&
-       git svn dcommit &&
-       cd ..
+       git svn dcommit
+       )
        "
 
 test_done
index 87696a92dc82fad38e89a97af4a74ede84057241..2e4789d061fe084024b621708ee27e629c2a004a 100755 (executable)
@@ -9,27 +9,27 @@ test_description='git svn partial-rebuild tests'
 test_expect_success 'initialize svnrepo' '
        mkdir import &&
        (
-               cd import &&
+               (cd import &&
                mkdir trunk branches tags &&
-               cd trunk &&
-               echo foo > foo &&
-               cd .. &&
-               svn import -m "import for git-svn" . "$svnrepo" >/dev/null &&
-               svn copy "$svnrepo"/trunk "$svnrepo"/branches/a \
-                       -m "created branch a" &&
-               cd .. &&
+               (cd trunk &&
+               echo foo > foo
+               ) &&
+               svn_cmd import -m "import for git-svn" . "$svnrepo" >/dev/null &&
+               svn_cmd copy "$svnrepo"/trunk "$svnrepo"/branches/a \
+                       -m "created branch a"
+               ) &&
                rm -rf import &&
-               svn co "$svnrepo"/trunk trunk &&
-               cd trunk &&
+               svn_cmd co "$svnrepo"/trunk trunk &&
+               (cd trunk &&
                echo bar >> foo &&
-               svn ci -m "updated trunk" &&
-               cd .. &&
-               svn co "$svnrepo"/branches/a a &&
-               cd a &&
+               svn_cmd ci -m "updated trunk"
+               ) &&
+               svn_cmd co "$svnrepo"/branches/a a &&
+               (cd a &&
                echo baz >> a &&
-               svn add a &&
-               svn ci -m "updated a" &&
-               cd .. &&
+               svn_cmd add a &&
+               svn_cmd ci -m "updated a"
+               ) &&
                git svn init --stdlayout "$svnrepo"
        )
 '
@@ -41,11 +41,11 @@ test_expect_success 'import an early SVN revision into git' '
 test_expect_success 'make full git mirror of SVN' '
        mkdir mirror &&
        (
-               cd mirror &&
+               (cd mirror &&
                git init &&
                git svn init --stdlayout "$svnrepo" &&
-               git svn fetch &&
-               cd ..
+               git svn fetch
+               )
        )
 '
 
index 252daa7e1aec3c40a31db9a12a9432fbb85ef1b0..4b034a67f305622f146e66914139e58045e2457e 100755 (executable)
@@ -9,19 +9,19 @@ test_description='git svn partial-rebuild tests'
 test_expect_success 'initialize svnrepo' '
        mkdir import &&
        (
-               cd import &&
+               (cd import &&
                mkdir trunk branches tags &&
-               cd trunk &&
-               echo foo > foo &&
-               cd .. &&
-               svn import -m "import for git-svn" . "$svnrepo" >/dev/null &&
-               cd .. &&
+               (cd trunk &&
+               echo foo > foo
+               ) &&
+               svn_cmd import -m "import for git-svn" . "$svnrepo" >/dev/null
+               ) &&
                rm -rf import &&
-               svn co "$svnrepo"/trunk trunk &&
-               cd trunk &&
+               svn_cmd co "$svnrepo"/trunk trunk &&
+               (cd trunk &&
                echo bar >> foo &&
-               svn ci -m "updated trunk" &&
-               cd .. &&
+               svn_cmd ci -m "updated trunk"
+               ) &&
                rm -rf trunk
        )
 '
@@ -57,14 +57,14 @@ test_expect_success 'git svn branch tests' '
 '
 
 test_expect_success 'branch uses correct svn-remote' '
-       (svn co "$svnrepo" svn &&
+       (svn_cmd co "$svnrepo" svn &&
        cd svn &&
        mkdir mirror &&
-       svn add mirror &&
-       svn copy trunk mirror/ &&
-       svn copy tags mirror/ &&
-       svn copy branches mirror/ &&
-       svn ci -m "made mirror" ) &&
+       svn_cmd add mirror &&
+       svn_cmd copy trunk mirror/ &&
+       svn_cmd copy tags mirror/ &&
+       svn_cmd copy branches mirror/ &&
+       svn_cmd ci -m "made mirror" ) &&
        rm -rf svn &&
        git svn init -s -R mirror --prefix=mirror/ "$svnrepo"/mirror &&
        git svn fetch -R mirror &&
index 3200ab38efa03076b81b94713e74205d740e38e8..8cfdfe790f1e0bb7cd0ddb72a72a55073368ec60 100755 (executable)
@@ -14,10 +14,22 @@ compare_git_head_with () {
        test_cmp current "$1"
 }
 
+a_utf8_locale=$(locale -a | sed -n '/\.[uU][tT][fF]-*8$/{
+       p
+       q
+}')
+
+if test -n "$a_utf8_locale"
+then
+       test_set_prereq UTF8
+else
+       say "# UTF-8 locale not available, some tests are skipped"
+fi
+
 compare_svn_head_with () {
        # extract just the log message and strip out committer info.
        # don't use --limit here since svn 1.1.x doesn't have it,
-       LC_ALL=en_US.UTF-8 svn log `git svn info --url` | perl -w -e '
+       LC_ALL="$a_utf8_locale" svn log `git svn info --url` | perl -w -e '
                use bytes;
                $/ = ("-"x72) . "\n";
                my @x = <STDIN>;
@@ -29,16 +41,16 @@ compare_svn_head_with () {
        test_cmp current "$1"
 }
 
-for H in ISO-8859-1 EUCJP ISO-2022-JP
+for H in ISO8859-1 eucJP ISO-2022-JP
 do
        test_expect_success "$H setup" '
                mkdir $H &&
-               svn import -m "$H test" $H "$svnrepo"/$H &&
+               svn_cmd import -m "$H test" $H "$svnrepo"/$H &&
                git svn clone "$svnrepo"/$H $H
        '
 done
 
-for H in ISO-8859-1 EUCJP ISO-2022-JP
+for H in ISO8859-1 eucJP ISO-2022-JP
 do
        test_expect_success "$H commit on git side" '
        (
@@ -55,7 +67,7 @@ do
        '
 done
 
-for H in ISO-8859-1 EUCJP ISO-2022-JP
+for H in ISO8859-1 eucJP ISO-2022-JP
 do
        test_expect_success "$H dcommit to svn" '
        (
@@ -69,20 +81,14 @@ do
        '
 done
 
-if locale -a |grep -q en_US.utf8; then
-       test_set_prereq UTF8
-else
-       say "UTF-8 locale not available, test skipped"
-fi
-
 test_expect_success UTF8 'ISO-8859-1 should match UTF-8 in svn' '
        (
-               cd ISO-8859-1 &&
+               cd ISO8859-1 &&
                compare_svn_head_with "$TEST_DIRECTORY"/t3900/1-UTF-8.txt
        )
 '
 
-for H in EUCJP ISO-2022-JP
+for H in eucJP ISO-2022-JP
 do
        test_expect_success UTF8 "$H should match UTF-8 in svn" '
                (
index b8fb277562e7782cf538ba7011214b4cf9136484..ec0a10661409d24c77bf2061bd8a554bfb565d74 100755 (executable)
@@ -15,12 +15,12 @@ EOF
 test_expect_success 'setup svnrepo' '
        for i in aa bb cc dd
        do
-               svn mkdir -m $i --username $i "$svnrepo"/$i
+               svn_cmd mkdir -m $i --username $i "$svnrepo"/$i
        done
        '
 
 test_expect_success 'start import with incomplete authors file' '
-       ! git svn clone --authors-file=svn-authors "$svnrepo" x
+       test_must_fail git svn clone --authors-file=svn-authors "$svnrepo" x
        '
 
 test_expect_success 'imported 2 revisions successfully' '
@@ -52,18 +52,18 @@ test_expect_success 'continues to import once authors have been added' '
        '
 
 test_expect_success 'authors-file against globs' '
-       svn mkdir -m globs --username aa \
+       svn_cmd mkdir -m globs --username aa \
          "$svnrepo"/aa/trunk "$svnrepo"/aa/branches "$svnrepo"/aa/tags &&
        git svn clone --authors-file=svn-authors -s "$svnrepo"/aa aa-work &&
        for i in bb ee cc
        do
                branch="aa/branches/$i"
-               svn mkdir -m "$branch" --username $i "$svnrepo/$branch"
+               svn_cmd mkdir -m "$branch" --username $i "$svnrepo/$branch"
        done
        '
 
 test_expect_success 'fetch fails on ee' '
-       ( cd aa-work && ! git svn fetch --authors-file=../svn-authors )
+       ( cd aa-work && test_must_fail git svn fetch --authors-file=../svn-authors )
        '
 
 tmp_config_get () {
@@ -91,4 +91,25 @@ test_expect_success 'fetch continues after authors-file is fixed' '
        )
        '
 
+test_expect_success 'fresh clone with svn.authors-file in config' '
+       (
+               rm -r "$GIT_DIR" &&
+               test x = x"$(git config svn.authorsfile)" &&
+               test_config="$HOME"/.gitconfig &&
+               unset GIT_CONFIG_NOGLOBAL &&
+               unset GIT_DIR &&
+               unset GIT_CONFIG &&
+               git config --global \
+                 svn.authorsfile "$HOME"/svn-authors &&
+               test x"$HOME"/svn-authors = x"$(git config svn.authorsfile)" &&
+               git svn clone "$svnrepo" gitconfig.clone &&
+               cd gitconfig.clone &&
+               nr_ex=$(git log | grep "^Author:.*example.com" | wc -l) &&
+               nr_rev=$(git rev-list HEAD | wc -l) &&
+               test $nr_rev -eq $nr_ex
+       )
+'
+
+test_debug 'GIT_DIR=gitconfig.clone/.git git log'
+
 test_done
index 893f57ef7370a5078cce39ef7f4136342451f655..f3c30e63b7f584cddb91793b0170f004d866530d 100755 (executable)
@@ -7,19 +7,19 @@ test_description='git svn property tests'
 . ./lib-git-svn.sh
 
 test_expect_success 'setup repo with a git repo inside it' '
-       svn co "$svnrepo" s &&
+       svn_cmd co "$svnrepo" s &&
        (
                cd s &&
                git init &&
                test -f .git/HEAD &&
                > .git/a &&
                echo a > a &&
-               svn add .git a &&
-               svn commit -m "create a nested git repo" &&
-               svn up &&
+               svn_cmd add .git a &&
+               svn_cmd commit -m "create a nested git repo" &&
+               svn_cmd up &&
                echo hi >> .git/a &&
-               svn commit -m "modify .git/a" &&
-               svn up
+               svn_cmd commit -m "modify .git/a" &&
+               svn_cmd up
        )
 '
 
@@ -33,9 +33,9 @@ test_expect_success 'SVN-side change outside of .git' '
        (
                cd s &&
                echo b >> a &&
-               svn commit -m "SVN-side change outside of .git" &&
-               svn up &&
-               svn log -v | fgrep "SVN-side change outside of .git"
+               svn_cmd commit -m "SVN-side change outside of .git" &&
+               svn_cmd up &&
+               svn_cmd log -v | fgrep "SVN-side change outside of .git"
        )
 '
 
@@ -56,10 +56,10 @@ test_expect_success 'SVN-side change inside of .git' '
                git add a &&
                git commit -m "add a inside an SVN repo" &&
                git log &&
-               svn add --force .git &&
-               svn commit -m "SVN-side change inside of .git" &&
-               svn up &&
-               svn log -v | fgrep "SVN-side change inside of .git"
+               svn_cmd add --force .git &&
+               svn_cmd commit -m "SVN-side change inside of .git" &&
+               svn_cmd up &&
+               svn_cmd log -v | fgrep "SVN-side change inside of .git"
        )
 '
 
@@ -80,9 +80,9 @@ test_expect_success 'SVN-side change in and out of .git' '
                echo c >> a &&
                git add a &&
                git commit -m "add a inside an SVN repo" &&
-               svn commit -m "SVN-side change in and out of .git" &&
-               svn up &&
-               svn log -v | fgrep "SVN-side change in and out of .git"
+               svn_cmd commit -m "SVN-side change in and out of .git" &&
+               svn_cmd up &&
+               svn_cmd log -v | fgrep "SVN-side change in and out of .git"
        )
 '
 
index 71fdc4a69dde2ff0bfdb092d8e28aeba495a1dc0..09ff10cd9b0688522671cac9ee2c8886a8587947 100755 (executable)
@@ -8,19 +8,19 @@ test_description='git svn property tests'
 . ./lib-git-svn.sh
 
 test_expect_success 'setup test repository' '
-       svn co "$svnrepo" s &&
+       svn_cmd co "$svnrepo" s &&
        (
                cd s &&
                mkdir qqq www &&
                echo test_qqq > qqq/test_qqq.txt &&
                echo test_www > www/test_www.txt &&
-               svn add qqq &&
-               svn add www &&
-               svn commit -m "create some files" &&
-               svn up &&
+               svn_cmd add qqq &&
+               svn_cmd add www &&
+               svn_cmd commit -m "create some files" &&
+               svn_cmd up &&
                echo hi >> www/test_www.txt &&
-               svn commit -m "modify www/test_www.txt" &&
-               svn up
+               svn_cmd commit -m "modify www/test_www.txt" &&
+               svn_cmd up
        )
 '
 
@@ -51,9 +51,9 @@ test_expect_success 'SVN-side change outside of www' '
        (
                cd s &&
                echo b >> qqq/test_qqq.txt &&
-               svn commit -m "SVN-side change outside of www" &&
-               svn up &&
-               svn log -v | fgrep "SVN-side change outside of www"
+               svn_cmd commit -m "SVN-side change outside of www" &&
+               svn_cmd up &&
+               svn_cmd log -v | fgrep "SVN-side change outside of www"
        )
 '
 
@@ -83,9 +83,9 @@ test_expect_success 'SVN-side change inside of ignored www' '
        (
                cd s &&
                echo zaq >> www/test_www.txt
-               svn commit -m "SVN-side change inside of www/test_www.txt" &&
-               svn up &&
-               svn log -v | fgrep "SVN-side change inside of www/test_www.txt"
+               svn_cmd commit -m "SVN-side change inside of www/test_www.txt" &&
+               svn_cmd up &&
+               svn_cmd log -v | fgrep "SVN-side change inside of www/test_www.txt"
        )
 '
 
@@ -116,9 +116,9 @@ test_expect_success 'SVN-side change in and out of ignored www' '
                cd s &&
                echo cvf >> www/test_www.txt
                echo ygg >> qqq/test_qqq.txt
-               svn commit -m "SVN-side change in and out of ignored www" &&
-               svn up &&
-               svn log -v | fgrep "SVN-side change in and out of ignored www"
+               svn_cmd commit -m "SVN-side change in and out of ignored www" &&
+               svn_cmd up &&
+               svn_cmd log -v | fgrep "SVN-side change in and out of ignored www"
        )
 '
 
index 03705fa4ce832658f39f586d430fcbbf902bd16f..5280e5f1e405f2a93620b496c64e85e73181f13a 100755 (executable)
@@ -10,7 +10,12 @@ test_expect_success 'load svn dumpfile'  '
 test_expect_success 'clone using git svn' 'git svn clone -s "$svnrepo" x'
 
 test_expect_success 'test that b1 exists and is empty' '
-       (cd x && test -f b1 && ! test -s b1)
+       (
+               cd x &&
+               git reset --hard branch-c &&
+               test -f b1 &&
+               ! test -s b1
+       )
        '
 
 test_done
index fd185011b73a8e45b7863bc336f884dee8d4f980..d60da63f7aced3e13a411eee661509ae3790e68f 100755 (executable)
@@ -6,10 +6,10 @@ test_description='git svn dcommit clobber series'
 
 test_expect_success 'initialize repo' '
        mkdir import &&
-       cd import &&
+       (cd import &&
        awk "BEGIN { for (i = 1; i < 64; i++) { print i } }" > file
-       svn import -m "initial" . "$svnrepo" &&
-       cd .. &&
+       svn_cmd import -m "initial" . "$svnrepo"
+       ) &&
        git svn init "$svnrepo" &&
        git svn fetch &&
        test -e file
@@ -18,15 +18,15 @@ test_expect_success 'initialize repo' '
 test_expect_success '(supposedly) non-conflicting change from SVN' '
        test x"`sed -n -e 58p < file`" = x58 &&
        test x"`sed -n -e 61p < file`" = x61 &&
-       svn co "$svnrepo" tmp &&
-       cd tmp &&
+       svn_cmd co "$svnrepo" tmp &&
+       (cd tmp &&
                perl -i.bak -p -e "s/^58$/5588/" file &&
                perl -i.bak -p -e "s/^61$/6611/" file &&
                poke file &&
                test x"`sed -n -e 58p < file`" = x5588 &&
                test x"`sed -n -e 61p < file`" = x6611 &&
-               svn commit -m "58 => 5588, 61 => 6611" &&
-               cd ..
+               svn_cmd commit -m "58 => 5588, 61 => 6611"
+       )
        '
 
 test_expect_success 'some unrelated changes to git' "
@@ -46,7 +46,7 @@ test_expect_success 'change file but in unrelated area' "
        test x\"\`sed -n -e 7p < file\`\" = x7777 &&
        git commit -m '4 => 4444, 7 => 7777' file &&
        git svn dcommit &&
-       svn up tmp &&
+       svn_cmd up tmp &&
        cd tmp &&
                test x\"\`sed -n -e 4p < file\`\" = x4444 &&
                test x\"\`sed -n -e 7p < file\`\" = x7777 &&
diff --git a/t/t9138-git-svn-authors-prog.sh b/t/t9138-git-svn-authors-prog.sh
new file mode 100755 (executable)
index 0000000..83cc5fc
--- /dev/null
@@ -0,0 +1,83 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Eric Wong, Mark Lodato
+#
+
+test_description='git svn authors prog tests'
+
+. ./lib-git-svn.sh
+
+cat > svn-authors-prog <<'EOF'
+#!/usr/bin/perl
+$_ = shift;
+if (s/-sub$//)  {
+       print "$_ <$_\@sub.example.com>\n";
+}
+else {
+       print "$_ <$_\@example.com>\n";
+}
+EOF
+chmod +x svn-authors-prog
+
+cat > svn-authors <<'EOF'
+ff = FFFFFFF FFFFFFF <fFf@other.example.com>
+EOF
+
+test_expect_success 'setup svnrepo' '
+       for i in aa bb cc-sub dd-sub ee-foo ff
+       do
+               svn mkdir -m $i --username $i "$svnrepo"/$i
+       done
+       '
+
+test_expect_success 'import authors with prog and file' '
+       git svn clone --authors-prog=./svn-authors-prog \
+           --authors-file=svn-authors "$svnrepo" x
+       '
+
+test_expect_success 'imported 6 revisions successfully' '
+       (
+               cd x
+               test "`git rev-list refs/remotes/git-svn | wc -l`" -eq 6
+       )
+       '
+
+test_expect_success 'authors-prog ran correctly' '
+       (
+               cd x
+               git rev-list -1 --pretty=raw refs/remotes/git-svn~1 | \
+                 grep "^author ee-foo <ee-foo@example\.com> " &&
+               git rev-list -1 --pretty=raw refs/remotes/git-svn~2 | \
+                 grep "^author dd <dd@sub\.example\.com> " &&
+               git rev-list -1 --pretty=raw refs/remotes/git-svn~3 | \
+                 grep "^author cc <cc@sub\.example\.com> " &&
+               git rev-list -1 --pretty=raw refs/remotes/git-svn~4 | \
+                 grep "^author bb <bb@example\.com> " &&
+               git rev-list -1 --pretty=raw refs/remotes/git-svn~5 | \
+                 grep "^author aa <aa@example\.com> "
+       )
+       '
+
+test_expect_success 'authors-file overrode authors-prog' '
+       (
+               cd x
+               git rev-list -1 --pretty=raw refs/remotes/git-svn | \
+                 grep "^author FFFFFFF FFFFFFF <fFf@other\.example\.com> "
+       )
+       '
+
+git --git-dir=x/.git config --unset svn.authorsfile
+git --git-dir=x/.git config --unset svn.authorsprog
+
+test_expect_success 'authors-prog handled special characters in username' '
+       svn mkdir -m bad --username "xyz; touch evil" "$svnrepo"/bad &&
+       (
+               cd x &&
+               git svn --authors-prog=../svn-authors-prog fetch &&
+               git rev-list -1 --pretty=raw refs/remotes/git-svn |
+               grep "^author xyz; touch evil <xyz; touch evil@example\.com> " &&
+               ! test -f evil
+       )
+'
+
+test_done
diff --git a/t/t9139-git-svn-non-utf8-commitencoding.sh b/t/t9139-git-svn-non-utf8-commitencoding.sh
new file mode 100755 (executable)
index 0000000..22d80b0
--- /dev/null
@@ -0,0 +1,47 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Eric Wong
+
+test_description='git svn refuses to dcommit non-UTF8 messages'
+
+. ./lib-git-svn.sh
+
+# ISO-2022-JP can pass for valid UTF-8, so skipping that in this test
+
+for H in ISO8859-1 eucJP
+do
+       test_expect_success "$H setup" '
+               mkdir $H &&
+               svn_cmd import -m "$H test" $H "$svnrepo"/$H &&
+               git svn clone "$svnrepo"/$H $H
+       '
+done
+
+for H in ISO8859-1 eucJP
+do
+       test_expect_success "$H commit on git side" '
+       (
+               cd $H &&
+               git config i18n.commitencoding $H &&
+               git checkout -b t refs/remotes/git-svn &&
+               echo $H >F &&
+               git add F &&
+               git commit -a -F "$TEST_DIRECTORY"/t3900/$H.txt &&
+               E=$(git cat-file commit HEAD | sed -ne "s/^encoding //p") &&
+               test "z$E" = "z$H"
+       )
+       '
+done
+
+for H in ISO8859-1 eucJP
+do
+       test_expect_success "$H dcommit to svn" '
+       (
+               cd $H &&
+               git config --unset i18n.commitencoding &&
+               test_must_fail git svn dcommit
+       )
+       '
+done
+
+test_done
diff --git a/t/t9140-git-svn-reset.sh b/t/t9140-git-svn-reset.sh
new file mode 100755 (executable)
index 0000000..e855904
--- /dev/null
@@ -0,0 +1,66 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Ben Jackson
+#
+
+test_description='git svn reset'
+. ./lib-git-svn.sh
+
+test_expect_success 'setup test repository' '
+       svn_cmd co "$svnrepo" s &&
+       (
+               cd s &&
+               mkdir vis &&
+               echo always visible > vis/vis.txt &&
+               svn_cmd add vis &&
+               svn_cmd commit -m "create visible files" &&
+               mkdir hid &&
+               echo initially hidden > hid/hid.txt &&
+               svn_cmd add hid &&
+               svn_cmd commit -m "create initially hidden files" &&
+               svn_cmd up &&
+               echo mod >> vis/vis.txt &&
+               svn_cmd commit -m "modify vis" &&
+               svn_cmd up
+       )
+'
+
+test_expect_success 'clone SVN repository with hidden directory' '
+       git svn init "$svnrepo" g &&
+       ( cd g && git svn fetch --ignore-paths="^hid" )
+'
+
+test_expect_success 'modify hidden file in SVN repo' '
+       ( cd s &&
+         echo mod hidden >> hid/hid.txt &&
+         svn_cmd commit -m "modify hid" &&
+         svn_cmd up
+       )
+'
+
+test_expect_success 'fetch fails on modified hidden file' '
+       ( cd g &&
+         git svn find-rev refs/remotes/git-svn > ../expect &&
+         test_must_fail git svn fetch 2> ../errors &&
+         git svn find-rev refs/remotes/git-svn > ../expect2 ) &&
+       fgrep "not found in commit" errors &&
+       test_cmp expect expect2
+'
+
+test_expect_success 'reset unwinds back to r1' '
+       ( cd g &&
+         git svn reset -r1 &&
+         git svn find-rev refs/remotes/git-svn > ../expect2 ) &&
+       echo 1 >expect &&
+       test_cmp expect expect2
+'
+
+test_expect_success 'refetch succeeds not ignoring any files' '
+       ( cd g &&
+         git svn fetch &&
+         git svn rebase &&
+         fgrep "mod hidden" hid/hid.txt
+       )
+'
+
+test_done
diff --git a/t/t9141-git-svn-multiple-branches.sh b/t/t9141-git-svn-multiple-branches.sh
new file mode 100755 (executable)
index 0000000..3cd0671
--- /dev/null
@@ -0,0 +1,122 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Marc Branchaud
+#
+
+test_description='git svn multiple branch and tag paths in the svn repo'
+. ./lib-git-svn.sh
+
+test_expect_success 'setup svnrepo' '
+       mkdir   project \
+               project/trunk \
+               project/b_one \
+               project/b_two \
+               project/tags_A \
+               project/tags_B &&
+       echo 1 > project/trunk/a.file &&
+       svn_cmd import -m "$test_description" project "$svnrepo/project" &&
+       rm -rf project &&
+       svn_cmd cp -m "Branch 1" "$svnrepo/project/trunk" \
+                                "$svnrepo/project/b_one/first" &&
+       svn_cmd cp -m "Tag 1" "$svnrepo/project/trunk" \
+                             "$svnrepo/project/tags_A/1.0" &&
+       svn_cmd co "$svnrepo/project" svn_project &&
+       ( cd svn_project &&
+               echo 2 > trunk/a.file &&
+               svn_cmd ci -m "Change 1" trunk/a.file &&
+               svn_cmd cp -m "Branch 2" "$svnrepo/project/trunk" \
+                                        "$svnrepo/project/b_one/second" &&
+               svn_cmd cp -m "Tag 2" "$svnrepo/project/trunk" \
+                                     "$svnrepo/project/tags_A/2.0" &&
+               echo 3 > trunk/a.file &&
+               svn_cmd ci -m "Change 2" trunk/a.file &&
+               svn_cmd cp -m "Branch 3" "$svnrepo/project/trunk" \
+                                        "$svnrepo/project/b_two/1" &&
+               svn_cmd cp -m "Tag 3" "$svnrepo/project/trunk" \
+                                     "$svnrepo/project/tags_A/3.0" &&
+               echo 4 > trunk/a.file &&
+               svn_cmd ci -m "Change 3" trunk/a.file &&
+               svn_cmd cp -m "Branch 4" "$svnrepo/project/trunk" \
+                                        "$svnrepo/project/b_two/2" &&
+               svn_cmd cp -m "Tag 4" "$svnrepo/project/trunk" \
+                                     "$svnrepo/project/tags_A/4.0" &&
+               svn_cmd up &&
+               echo 5 > b_one/first/a.file &&
+               svn_cmd ci -m "Change 4" b_one/first/a.file &&
+               svn_cmd cp -m "Tag 5" "$svnrepo/project/b_one/first" \
+                                     "$svnrepo/project/tags_B/v5" &&
+               echo 6 > b_one/second/a.file &&
+               svn_cmd ci -m "Change 5" b_one/second/a.file &&
+               svn_cmd cp -m "Tag 6" "$svnrepo/project/b_one/second" \
+                                     "$svnrepo/project/tags_B/v6" &&
+               echo 7 > b_two/1/a.file &&
+               svn_cmd ci -m "Change 6" b_two/1/a.file &&
+               svn_cmd cp -m "Tag 7" "$svnrepo/project/b_two/1" \
+                                     "$svnrepo/project/tags_B/v7" &&
+               echo 8 > b_two/2/a.file &&
+               svn_cmd ci -m "Change 7" b_two/2/a.file &&
+               svn_cmd cp -m "Tag 8" "$svnrepo/project/b_two/2" \
+                                     "$svnrepo/project/tags_B/v8"
+       )
+'
+
+test_expect_success 'clone multiple branch and tag paths' '
+       git svn clone -T trunk \
+                     -b b_one/* --branches b_two/* \
+                     -t tags_A/* --tags tags_B \
+                     "$svnrepo/project" git_project &&
+       ( cd git_project &&
+               git rev-parse refs/remotes/first &&
+               git rev-parse refs/remotes/second &&
+               git rev-parse refs/remotes/1 &&
+               git rev-parse refs/remotes/2 &&
+               git rev-parse refs/remotes/tags/1.0 &&
+               git rev-parse refs/remotes/tags/2.0 &&
+               git rev-parse refs/remotes/tags/3.0 &&
+               git rev-parse refs/remotes/tags/4.0 &&
+               git rev-parse refs/remotes/tags/v5 &&
+               git rev-parse refs/remotes/tags/v6 &&
+               git rev-parse refs/remotes/tags/v7 &&
+               git rev-parse refs/remotes/tags/v8
+       )
+'
+
+test_expect_success 'Multiple branch or tag paths require -d' '
+       ( cd git_project &&
+               test_must_fail git svn branch -m "No new branch" Nope &&
+               test_must_fail git svn tag -m "No new tag" Tagless &&
+               test_must_fail git rev-parse refs/remotes/Nope &&
+               test_must_fail git rev-parse refs/remotes/tags/Tagless
+       ) &&
+       ( cd svn_project &&
+               svn_cmd up &&
+               test_must_fail test -d b_one/Nope &&
+               test_must_fail test -d b_two/Nope &&
+               test_must_fail test -d tags_A/Tagless &&
+               test_must_fail test -d tags_B/Tagless
+       )
+'
+
+test_expect_success 'create new branches and tags' '
+       ( cd git_project &&
+               git svn branch -m "New branch 1" -d b_one New1 ) &&
+       ( cd svn_project &&
+               svn_cmd up && test -e b_one/New1/a.file ) &&
+
+       ( cd git_project &&
+               git svn branch -m "New branch 2" -d b_two New2 ) &&
+       ( cd svn_project &&
+               svn_cmd up && test -e b_two/New2/a.file ) &&
+
+       ( cd git_project &&
+               git svn branch -t -m "New tag 1" -d tags_A Tag1 ) &&
+       ( cd svn_project &&
+               svn_cmd up && test -e tags_A/Tag1/a.file ) &&
+
+       ( cd git_project &&
+               git svn tag -m "New tag 2" -d tags_B Tag2 ) &&
+       ( cd svn_project &&
+               svn_cmd up && test -e tags_B/Tag2/a.file )
+'
+
+test_done
diff --git a/t/t9142-git-svn-shallow-clone.sh b/t/t9142-git-svn-shallow-clone.sh
new file mode 100755 (executable)
index 0000000..1236acc
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Eric Wong
+#
+
+test_description='git svn shallow clone'
+. ./lib-git-svn.sh
+
+test_expect_success 'setup test repository' '
+       svn_cmd mkdir -m "create standard layout" \
+         "$svnrepo"/trunk "$svnrepo"/branches "$svnrepo"/tags &&
+       svn_cmd cp -m "branch off trunk" \
+         "$svnrepo"/trunk "$svnrepo"/branches/a &&
+       svn_cmd co "$svnrepo"/branches/a &&
+       (
+               cd a &&
+               > foo &&
+               svn_cmd add foo &&
+               svn_cmd commit -m "add foo"
+       )
+'
+
+start_httpd
+
+test_expect_success 'clone trunk with "-r HEAD"' '
+       git svn clone -r HEAD "$svnrepo/trunk" g &&
+       ( cd g && git rev-parse --symbolic --verify HEAD )
+'
+
+stop_httpd
+
+test_done
diff --git a/t/t9143-git-svn-gc.sh b/t/t9143-git-svn-gc.sh
new file mode 100755 (executable)
index 0000000..337ea59
--- /dev/null
@@ -0,0 +1,53 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Robert Allan Zeh
+
+test_description='git svn gc basic tests'
+
+. ./lib-git-svn.sh
+
+test_expect_success 'setup directories and test repo' '
+       mkdir import &&
+       mkdir tmp &&
+       echo "Sample text for Subversion repository." > import/test.txt &&
+       svn_cmd import -m "import for git svn" import "$svnrepo" > /dev/null
+       '
+
+test_expect_success 'checkout working copy from svn' \
+       'svn_cmd co "$svnrepo" test_wc'
+
+test_expect_success 'set some properties to create an unhandled.log file' '
+       (
+               cd test_wc &&
+               svn_cmd propset foo bar test.txt &&
+               svn_cmd commit -m "property set"
+       )'
+
+test_expect_success 'Setup repo' 'git svn init "$svnrepo"'
+
+test_expect_success 'Fetch repo' 'git svn fetch'
+
+test_expect_success 'make backup copy of unhandled.log' '
+        cp .git/svn/refs/remotes/git-svn/unhandled.log tmp
+       '
+
+test_expect_success 'create leftover index' '> .git/svn/refs/remotes/git-svn/index'
+
+test_expect_success 'git svn gc runs' 'git svn gc'
+
+test_expect_success 'git svn index removed' '! test -f .git/svn/refs/remotes/git-svn/index'
+
+if perl -MCompress::Zlib -e 0 2>/dev/null
+then
+       test_expect_success 'git svn gc produces a valid gzip file' '
+                gunzip .git/svn/refs/remotes/git-svn/unhandled.log.gz
+               '
+else
+       say "# Perl Compress::Zlib unavailable, skipping gunzip test"
+fi
+
+test_expect_success 'git svn gc does not change unhandled.log files' '
+        test_cmp .git/svn/refs/remotes/git-svn/unhandled.log tmp/unhandled.log
+       '
+
+test_done
diff --git a/t/t9144-git-svn-old-rev_map.sh b/t/t9144-git-svn-old-rev_map.sh
new file mode 100755 (executable)
index 0000000..7600a35
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Eric Wong
+
+test_description='git svn old rev_map preservd'
+. ./lib-git-svn.sh
+
+test_expect_success 'setup test repository with old layout' '
+       mkdir i &&
+       (cd i && > a) &&
+       svn_cmd import -m- i "$svnrepo" &&
+       git svn init "$svnrepo" &&
+       git svn fetch &&
+       test -d .git/svn/refs/remotes/git-svn/ &&
+       ! test -e .git/svn/git-svn/ &&
+       mv .git/svn/refs/remotes/git-svn .git/svn/ &&
+       rm -r .git/svn/refs
+'
+
+test_expect_success 'old layout continues to work' '
+       svn_cmd import -m- i "$svnrepo/b" &&
+       git svn rebase &&
+       echo a >> b/a &&
+       git add b/a &&
+       git commit -m- -a &&
+       git svn dcommit &&
+       ! test -d .git/svn/refs/ &&
+       test -e .git/svn/git-svn/
+'
+
+test_done
diff --git a/t/t9145-git-svn-master-branch.sh b/t/t9145-git-svn-master-branch.sh
new file mode 100755 (executable)
index 0000000..16852d2
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Eric Wong
+#
+test_description='git svn initial master branch is "trunk" if possible'
+. ./lib-git-svn.sh
+
+test_expect_success 'setup test repository' '
+       mkdir i &&
+       > i/a &&
+       svn_cmd import -m trunk i "$svnrepo/trunk" &&
+       svn_cmd import -m b/a i "$svnrepo/branches/a" &&
+       svn_cmd import -m b/b i "$svnrepo/branches/b"
+'
+
+test_expect_success 'git svn clone --stdlayout sets up trunk as master' '
+       git svn clone -s "$svnrepo" g &&
+       (
+               cd g &&
+               test x`git rev-parse --verify refs/remotes/trunk^0` = \
+                    x`git rev-parse --verify refs/heads/master^0`
+       )
+'
+
+test_done
diff --git a/t/t9146-git-svn-empty-dirs.sh b/t/t9146-git-svn-empty-dirs.sh
new file mode 100755 (executable)
index 0000000..565365c
--- /dev/null
@@ -0,0 +1,142 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Eric Wong
+
+test_description='git svn creates empty directories'
+. ./lib-git-svn.sh
+
+test_expect_success 'initialize repo' '
+       for i in a b c d d/e d/e/f "weird file name"
+       do
+               svn_cmd mkdir -m "mkdir $i" "$svnrepo"/"$i"
+       done
+'
+
+test_expect_success 'clone' 'git svn clone "$svnrepo" cloned'
+
+test_expect_success 'empty directories exist' '
+       (
+               cd cloned &&
+               for i in a b c d d/e d/e/f "weird file name"
+               do
+                       if ! test -d "$i"
+                       then
+                               echo >&2 "$i does not exist"
+                               exit 1
+                       fi
+               done
+       )
+'
+
+test_expect_success 'more emptiness' '
+       svn_cmd mkdir -m "bang bang"  "$svnrepo"/"! !"
+'
+
+test_expect_success 'git svn rebase creates empty directory' '
+       ( cd cloned && git svn rebase )
+       test -d cloned/"! !"
+'
+
+test_expect_success 'git svn mkdirs recreates empty directories' '
+       (
+               cd cloned &&
+               rm -r * &&
+               git svn mkdirs &&
+               for i in a b c d d/e d/e/f "weird file name" "! !"
+               do
+                       if ! test -d "$i"
+                       then
+                               echo >&2 "$i does not exist"
+                               exit 1
+                       fi
+               done
+       )
+'
+
+test_expect_success 'git svn mkdirs -r works' '
+       (
+               cd cloned &&
+               rm -r * &&
+               git svn mkdirs -r7 &&
+               for i in a b c d d/e d/e/f "weird file name"
+               do
+                       if ! test -d "$i"
+                       then
+                               echo >&2 "$i does not exist"
+                               exit 1
+                       fi
+               done
+
+               if test -d "! !"
+               then
+                       echo >&2 "$i should not exist"
+                       exit 1
+               fi
+
+               git svn mkdirs -r8 &&
+               if ! test -d "! !"
+               then
+                       echo >&2 "$i not exist"
+                       exit 1
+               fi
+       )
+'
+
+test_expect_success 'initialize trunk' '
+       for i in trunk trunk/a trunk/"weird file name"
+       do
+               svn_cmd mkdir -m "mkdir $i" "$svnrepo"/"$i"
+       done
+'
+
+test_expect_success 'clone trunk' 'git svn clone -s "$svnrepo" trunk'
+
+test_expect_success 'empty directories in trunk exist' '
+       (
+               cd trunk &&
+               for i in a "weird file name"
+               do
+                       if ! test -d "$i"
+                       then
+                               echo >&2 "$i does not exist"
+                               exit 1
+                       fi
+               done
+       )
+'
+
+test_expect_success 'remove a top-level directory from svn' '
+       svn_cmd rm -m "remove d" "$svnrepo"/d
+'
+
+test_expect_success 'removed top-level directory does not exist' '
+       git svn clone "$svnrepo" removed &&
+       test ! -e removed/d
+
+'
+unhandled=.git/svn/refs/remotes/git-svn/unhandled.log
+test_expect_success 'git svn gc-ed files work' '
+       (
+               cd removed &&
+               git svn gc &&
+               : Compress::Zlib may not be available &&
+               if test -f "$unhandled".gz
+               then
+                       svn_cmd mkdir -m gz "$svnrepo"/gz &&
+                       git reset --hard $(git rev-list HEAD | tail -1) &&
+                       git svn rebase &&
+                       test -f "$unhandled".gz &&
+                       test -f "$unhandled" &&
+                       for i in a b c "weird file name" gz "! !"
+                       do
+                               if ! test -d "$i"
+                               then
+                                       echo >&2 "$i does not exist"
+                                       exit 1
+                               fi
+                       done
+               fi
+       )
+'
+
+test_done
diff --git a/t/t9150-svk-mergetickets.sh b/t/t9150-svk-mergetickets.sh
new file mode 100755 (executable)
index 0000000..24c2421
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Sam Vilain
+#
+
+test_description='git-svn svk merge tickets'
+
+. ./lib-git-svn.sh
+
+test_expect_success 'load svk depot' "
+       svnadmin load -q '$rawsvnrepo' \
+         < '$TEST_DIRECTORY/t9150/svk-merge.dump' &&
+       git svn init --minimize-url -R svkmerge \
+         --rewrite-root=http://svn.example.org \
+         -T trunk -b branches '$svnrepo' &&
+       git svn fetch --all
+       "
+
+uuid=b48289b2-9c08-4d72-af37-0358a40b9c15
+
+test_expect_success 'svk merges were represented coming in' "
+       [ `git cat-file commit HEAD | grep parent | wc -l` -eq 2 ]
+       "
+
+test_done
diff --git a/t/t9150/make-svk-dump b/t/t9150/make-svk-dump
new file mode 100644 (file)
index 0000000..2242f14
--- /dev/null
@@ -0,0 +1,57 @@
+#!/bin/sh
+#
+# this script sets up a Subversion repository for Makefile in the
+# first ever git merge, as if it were done with svk.
+#
+
+set -e
+
+svk depotmap foo ~/.svk/foo
+svk co /foo/ foo
+cd foo
+mkdir trunk
+mkdir branches
+svk add trunk branches
+svk commit -m "Setup trunk and branches"
+cd trunk
+
+git cat-file blob 6683463e:Makefile > Makefile
+svk add Makefile 
+
+svk commit -m "ancestor"
+cd ..
+svk cp trunk branches/left
+
+svk commit -m "make left branch"
+cd branches/left/
+
+git cat-file blob 5873b67e:Makefile > Makefile
+svk commit -m "left update 1"
+
+cd ../../trunk
+git cat-file blob 75118b13:Makefile > Makefile
+svk commit -m "trunk update"
+
+cd ../branches/left
+git cat-file blob b5039db6:Makefile > Makefile
+svk commit -m "left update 2"
+
+cd ../../trunk
+svk sm /foo/branches/left
+# in theory we could delete the "left" branch here, but it's not
+# required so don't do it, in case people start getting ideas ;)
+svk commit -m "merge branch 'left' into 'trunk'"
+
+git cat-file blob b51ad431:Makefile > Makefile
+
+svk diff Makefile && echo "Hey!  No differences, magic"
+
+cd ../..
+
+svnadmin dump ~/.svk/foo > svk-merge.dump
+
+svk co -d foo
+rm -rf foo
+svk depotmap -d /foo/
+rm -rf ~/.svk/foo
+
diff --git a/t/t9150/svk-merge.dump b/t/t9150/svk-merge.dump
new file mode 100644 (file)
index 0000000..42f70db
--- /dev/null
@@ -0,0 +1,616 @@
+SVN-fs-dump-format-version: 2
+
+UUID: b48289b2-9c08-4d72-af37-0358a40b9c15
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-10-19T23:44:03.722969Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 123
+Content-length: 123
+
+K 7
+svn:log
+V 24
+Setup trunk and branches
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-10-19T23:44:04.927533Z
+PROPS-END
+
+Node-path: branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 106
+Content-length: 106
+
+K 7
+svn:log
+V 8
+ancestor
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-10-19T23:44:05.835585Z
+PROPS-END
+
+Node-path: trunk/Makefile
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2401
+Text-content-md5: bfd8ff778d1492dc6758567373176a89
+Content-length: 2411
+
+PROPS-END
+# -DCOLLISION_CHECK if you believe that SHA1's
+# 1461501637330902918203684832716283019655932542976 hashes do not give you
+# enough guarantees about no collisions between objects ever hapenning.
+#
+# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
+# Note that you need some new 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).
+CFLAGS=-g -O3 -Wall
+
+CC=gcc
+
+
+PROG=   update-cache show-diff init-db write-tree read-tree commit-tree \
+       cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \
+       check-files ls-tree merge-base
+
+all: $(PROG)
+
+install: $(PROG)
+       install $(PROG) $(HOME)/bin/
+
+LIBS= -lssl -lz
+
+init-db: init-db.o
+
+update-cache: update-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
+
+show-diff: show-diff.o read-cache.o
+       $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS)
+
+write-tree: write-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS)
+
+read-tree: read-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS)
+
+commit-tree: commit-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS)
+
+cat-file: cat-file.o read-cache.o
+       $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS)
+
+fsck-cache: fsck-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS)
+
+checkout-cache: checkout-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS)
+
+diff-tree: diff-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS)
+
+rev-tree: rev-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o $(LIBS)
+
+show-files: show-files.o read-cache.o
+       $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS)
+
+check-files: check-files.o read-cache.o
+       $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS)
+
+ls-tree: ls-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS)
+
+merge-base: merge-base.o read-cache.o
+       $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS)
+
+read-cache.o: cache.h
+show-diff.o: cache.h
+
+clean:
+       rm -f *.o $(PROG)
+
+backup: clean
+       cd .. ; tar czvf dircache.tar.gz dir-cache
+
+
+Revision-number: 3
+Prop-content-length: 115
+Content-length: 115
+
+K 7
+svn:log
+V 16
+make left branch
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-10-19T23:44:06.719737Z
+PROPS-END
+
+Node-path: branches/left
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk
+
+
+Revision-number: 4
+Prop-content-length: 112
+Content-length: 112
+
+K 7
+svn:log
+V 13
+left update 1
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-10-19T23:44:07.167666Z
+PROPS-END
+
+Node-path: branches/left/Makefile
+Node-kind: file
+Node-action: change
+Text-content-length: 2465
+Text-content-md5: 16e38d9753b061731650561ce01b1195
+Content-length: 2465
+
+# -DCOLLISION_CHECK if you believe that SHA1's
+# 1461501637330902918203684832716283019655932542976 hashes do not give you
+# enough guarantees about no collisions between objects ever hapenning.
+#
+# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
+# Note that you need some new 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).
+CFLAGS=-g -O3 -Wall
+
+CC=gcc
+
+
+PROG=   update-cache show-diff init-db write-tree read-tree commit-tree \
+       cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \
+       check-files ls-tree merge-base
+
+all: $(PROG)
+
+install: $(PROG)
+       install $(PROG) $(HOME)/bin/
+
+LIBS= -lssl -lz
+
+init-db: init-db.o
+
+update-cache: update-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
+
+show-diff: show-diff.o read-cache.o
+       $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS)
+
+write-tree: write-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS)
+
+read-tree: read-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS)
+
+commit-tree: commit-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS)
+
+cat-file: cat-file.o read-cache.o
+       $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS)
+
+fsck-cache: fsck-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS)
+
+checkout-cache: checkout-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS)
+
+diff-tree: diff-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS)
+
+rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+show-files: show-files.o read-cache.o
+       $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS)
+
+check-files: check-files.o read-cache.o
+       $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS)
+
+ls-tree: ls-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS)
+
+merge-base: merge-base.o read-cache.o
+       $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS)
+
+read-cache.o: cache.h
+show-diff.o: cache.h
+
+clean:
+       rm -f *.o $(PROG)
+
+backup: clean
+       cd .. ; tar czvf dircache.tar.gz dir-cache
+
+
+Revision-number: 5
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 12
+trunk update
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-10-19T23:44:07.619633Z
+PROPS-END
+
+Node-path: trunk/Makefile
+Node-kind: file
+Node-action: change
+Text-content-length: 2521
+Text-content-md5: 0668418a621333f4aa8b6632cd63e2a0
+Content-length: 2521
+
+# -DCOLLISION_CHECK if you believe that SHA1's
+# 1461501637330902918203684832716283019655932542976 hashes do not give you
+# enough guarantees about no collisions between objects ever hapenning.
+#
+# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
+# Note that you need some new 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).
+CFLAGS=-g -O3 -Wall
+
+CC=gcc
+
+
+PROG=   update-cache show-diff init-db write-tree read-tree commit-tree \
+       cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \
+       check-files ls-tree merge-base merge-cache
+
+all: $(PROG)
+
+install: $(PROG)
+       install $(PROG) $(HOME)/bin/
+
+LIBS= -lssl -lz
+
+init-db: init-db.o
+
+update-cache: update-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
+
+show-diff: show-diff.o read-cache.o
+       $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS)
+
+write-tree: write-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS)
+
+read-tree: read-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS)
+
+commit-tree: commit-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS)
+
+cat-file: cat-file.o read-cache.o
+       $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS)
+
+fsck-cache: fsck-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS)
+
+checkout-cache: checkout-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS)
+
+diff-tree: diff-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS)
+
+rev-tree: rev-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o $(LIBS)
+
+show-files: show-files.o read-cache.o
+       $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS)
+
+check-files: check-files.o read-cache.o
+       $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS)
+
+ls-tree: ls-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS)
+
+merge-base: merge-base.o read-cache.o
+       $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS)
+
+merge-cache: merge-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o merge-cache merge-cache.o read-cache.o $(LIBS)
+
+read-cache.o: cache.h
+show-diff.o: cache.h
+
+clean:
+       rm -f *.o $(PROG)
+
+backup: clean
+       cd .. ; tar czvf dircache.tar.gz dir-cache
+
+
+Revision-number: 6
+Prop-content-length: 112
+Content-length: 112
+
+K 7
+svn:log
+V 13
+left update 2
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-10-19T23:44:08.067554Z
+PROPS-END
+
+Node-path: branches/left/Makefile
+Node-kind: file
+Node-action: change
+Text-content-length: 2593
+Text-content-md5: 5ccff689fb290e00b85fe18ee50c54ba
+Content-length: 2593
+
+# -DCOLLISION_CHECK if you believe that SHA1's
+# 1461501637330902918203684832716283019655932542976 hashes do not give you
+# enough guarantees about no collisions between objects ever hapenning.
+#
+# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
+# Note that you need some new 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).
+CFLAGS=-g -O3 -Wall
+
+CC=gcc
+
+
+PROG=   update-cache show-diff init-db write-tree read-tree commit-tree \
+       cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \
+       check-files ls-tree merge-base
+
+all: $(PROG)
+
+install: $(PROG)
+       install $(PROG) $(HOME)/bin/
+
+LIBS= -lssl -lz
+
+init-db: init-db.o
+
+update-cache: update-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
+
+show-diff: show-diff.o read-cache.o
+       $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS)
+
+write-tree: write-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS)
+
+read-tree: read-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS)
+
+commit-tree: commit-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS)
+
+cat-file: cat-file.o read-cache.o
+       $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS)
+
+fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+checkout-cache: checkout-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS)
+
+diff-tree: diff-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS)
+
+rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+show-files: show-files.o read-cache.o
+       $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS)
+
+check-files: check-files.o read-cache.o
+       $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS)
+
+ls-tree: ls-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS)
+
+merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+read-cache.o: cache.h
+show-diff.o: cache.h
+
+clean:
+       rm -f *.o $(PROG)
+
+backup: clean
+       cd .. ; tar czvf dircache.tar.gz dir-cache
+
+
+Revision-number: 7
+Prop-content-length: 131
+Content-length: 131
+
+K 7
+svn:log
+V 32
+merge branch 'left' into 'trunk'
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-10-19T23:44:08.971801Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 83
+Content-length: 83
+
+K 9
+svk:merge
+V 53
+b48289b2-9c08-4d72-af37-0358a40b9c15:/branches/left:6
+PROPS-END
+
+
+Node-path: trunk/Makefile
+Node-kind: file
+Node-action: change
+Text-content-length: 2713
+Text-content-md5: 0afbe34f244cd662b1f97d708c687f90
+Content-length: 2713
+
+# -DCOLLISION_CHECK if you believe that SHA1's
+# 1461501637330902918203684832716283019655932542976 hashes do not give you
+# enough guarantees about no collisions between objects ever hapenning.
+#
+# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
+# Note that you need some new 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).
+CFLAGS=-g -O3 -Wall
+
+CC=gcc
+
+
+PROG=   update-cache show-diff init-db write-tree read-tree commit-tree \
+       cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \
+       check-files ls-tree merge-base merge-cache
+
+all: $(PROG)
+
+install: $(PROG)
+       install $(PROG) $(HOME)/bin/
+
+LIBS= -lssl -lz
+
+init-db: init-db.o
+
+update-cache: update-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
+
+show-diff: show-diff.o read-cache.o
+       $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS)
+
+write-tree: write-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS)
+
+read-tree: read-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS)
+
+commit-tree: commit-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS)
+
+cat-file: cat-file.o read-cache.o
+       $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS)
+
+fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+checkout-cache: checkout-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS)
+
+diff-tree: diff-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS)
+
+rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+show-files: show-files.o read-cache.o
+       $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS)
+
+check-files: check-files.o read-cache.o
+       $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS)
+
+ls-tree: ls-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS)
+
+merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+merge-cache: merge-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o merge-cache merge-cache.o read-cache.o $(LIBS)
+
+read-cache.o: cache.h
+show-diff.o: cache.h
+
+clean:
+       rm -f *.o $(PROG)
+
+backup: clean
+       cd .. ; tar czvf dircache.tar.gz dir-cache
+
+
diff --git a/t/t9151-svn-mergeinfo.sh b/t/t9151-svn-mergeinfo.sh
new file mode 100755 (executable)
index 0000000..250c651
--- /dev/null
@@ -0,0 +1,57 @@
+#!/bin/sh
+#
+# Copyright (c) 2007, 2009 Sam Vilain
+#
+
+test_description='git-svn svn mergeinfo properties'
+
+. ./lib-git-svn.sh
+
+test_expect_success 'load svn dump' "
+       svnadmin load -q '$rawsvnrepo' \
+         < '$TEST_DIRECTORY/t9151/svn-mergeinfo.dump' &&
+       git svn init --minimize-url -R svnmerge \
+         --rewrite-root=http://svn.example.org \
+         -T trunk -b branches '$svnrepo' &&
+       git svn fetch --all
+       "
+
+test_expect_success 'all svn merges became git merge commits' '
+       unmarked=$(git rev-list --parents --all --grep=Merge |
+               grep -v " .* " | cut -f1 -d" ")
+       [ -z "$unmarked" ]
+       '
+
+test_expect_success 'cherry picks did not become git merge commits' '
+       bad_cherries=$(git rev-list --parents --all --grep=Cherry |
+               grep " .* " | cut -f1 -d" ")
+       [ -z "$bad_cherries" ]
+       '
+
+test_expect_success 'svn non-merge merge commits did not become git merge commits' '
+       bad_non_merges=$(git rev-list --parents --all --grep=non-merge |
+               grep " .* " | cut -f1 -d" ")
+       [ -z "$bad_non_merges" ]
+       '
+
+test_expect_success 'commit made to merged branch is reachable from the merge' '
+       before_commit=$(git rev-list --all --grep="trunk commit before merging trunk to b2")
+       merge_commit=$(git rev-list --all --grep="Merge trunk to b2")
+       not_reachable=$(git rev-list -1 $before_commit --not $merge_commit)
+       [ -z "$not_reachable" ]
+       '
+
+test_expect_success 'merging two branches in one commit is detected correctly' '
+       f1_commit=$(git rev-list --all --grep="make f1 branch from trunk")
+       f2_commit=$(git rev-list --all --grep="make f2 branch from trunk")
+       merge_commit=$(git rev-list --all --grep="Merge f1 and f2 to trunk")
+       not_reachable=$(git rev-list -1 $f1_commit $f2_commit --not $merge_commit)
+       [ -z "$not_reachable" ]
+       '
+
+test_expect_failure 'everything got merged in the end' '
+       unmerged=$(git rev-list --all --not master)
+       [ -z "$unmerged" ]
+       '
+
+test_done
diff --git a/t/t9151/.gitignore b/t/t9151/.gitignore
new file mode 100644 (file)
index 0000000..587c37d
--- /dev/null
@@ -0,0 +1,2 @@
+foo
+foo.svn
diff --git a/t/t9151/make-svnmerge-dump b/t/t9151/make-svnmerge-dump
new file mode 100644 (file)
index 0000000..e1e138c
--- /dev/null
@@ -0,0 +1,305 @@
+#!/bin/sh
+#
+# this script sets up a Subversion repository for Makefile in the
+# first ever git merge, as if it were done with svnmerge (SVN 1.5+)
+#
+
+rm -rf foo.svn foo
+set -e
+
+mkdir foo.svn
+svnadmin create foo.svn
+svn co file://`pwd`/foo.svn foo
+
+commit() {
+    i=$(( $1 + 1 ))
+    shift;
+    svn commit -m "(r$i) $*" >/dev/null || exit 1
+    echo $i
+}
+
+say() {
+    echo "\e[1m * $*\e[0m"
+}
+
+i=0
+cd foo
+mkdir trunk
+mkdir branches
+mkdir tags
+svn add trunk branches tags
+i=$(commit $i "Setup trunk, branches, and tags")
+
+git cat-file blob 6683463e:Makefile > trunk/Makefile
+svn add trunk/Makefile 
+
+say "Committing ANCESTOR"
+i=$(commit $i "ancestor")
+svn cp trunk branches/left
+
+say "Committing BRANCH POINT"
+i=$(commit $i "make left branch")
+svn cp trunk branches/right
+
+say "Committing other BRANCH POINT"
+i=$(commit $i "make right branch")
+
+say "Committing LEFT UPDATE"
+git cat-file blob 5873b67e:Makefile > branches/left/Makefile
+i=$(commit $i "left update 1")
+
+git cat-file blob 75118b13:Makefile > branches/right/Makefile
+say "Committing RIGHT UPDATE"
+pre_right_update_1=$i
+i=$(commit $i "right update 1")
+
+say "Making more commits on LEFT"
+git cat-file blob ff5ebe39:Makefile > branches/left/Makefile
+i=$(commit $i "left update 2")
+git cat-file blob b5039db6:Makefile > branches/left/Makefile
+i=$(commit $i "left update 3")
+
+say "Making a LEFT SUB-BRANCH"
+svn cp branches/left branches/left-sub
+sub_left_make=$i
+i=$(commit $i "make left sub-branch")
+
+say "Making a commit on LEFT SUB-BRANCH"
+echo "crunch" > branches/left-sub/README
+svn add branches/left-sub/README
+i=$(commit $i "left sub-branch update 1")
+
+say "Merging LEFT to TRUNK"
+svn update
+cd trunk
+svn merge ../branches/left --accept postpone
+git cat-file blob b5039db6:Makefile > Makefile
+svn resolved Makefile
+i=$(commit $i "Merge left to trunk 1")
+cd ..
+
+say "Making more commits on LEFT and RIGHT"
+echo "touche" > branches/left/zlonk
+svn add branches/left/zlonk
+i=$(commit $i "left update 4")
+echo "thwacke" > branches/right/bang
+svn add branches/right/bang
+i=$(commit $i "right update 2")
+
+say "Squash merge of RIGHT tip 2 commits onto TRUNK"
+svn update
+cd trunk
+svn merge -r$pre_right_update_1:$i ../branches/right
+i=$(commit $i "Cherry-pick right 2 commits to trunk")
+cd ..
+
+say "Merging RIGHT to TRUNK"
+svn update
+cd trunk
+svn merge ../branches/right --accept postpone
+git cat-file blob b51ad431:Makefile > Makefile
+svn resolved Makefile
+i=$(commit $i "Merge right to trunk 1")
+cd ..
+
+say "Making more commits on RIGHT and TRUNK"
+echo "whamm" > branches/right/urkkk
+svn add branches/right/urkkk
+i=$(commit $i "right update 3")
+echo "pow" > trunk/vronk
+svn add trunk/vronk
+i=$(commit $i "trunk update 1")
+
+say "Merging RIGHT to LEFT SUB-BRANCH"
+svn update
+cd branches/left-sub
+svn merge ../right --accept postpone
+git cat-file blob b51ad431:Makefile > Makefile
+svn resolved Makefile
+i=$(commit $i "Merge right to left sub-branch")
+cd ../..
+
+say "Making more commits on LEFT SUB-BRANCH and LEFT"
+echo "zowie" > branches/left-sub/wham_eth
+svn add branches/left-sub/wham_eth
+pre_sub_left_update_2=$i
+i=$(commit $i "left sub-branch update 2")
+sub_left_update_2=$i
+echo "eee_yow" > branches/left/glurpp
+svn add branches/left/glurpp
+i=$(commit $i "left update 5")
+
+say "Cherry pick LEFT SUB-BRANCH commit to LEFT"
+svn update
+cd branches/left
+svn merge -r$pre_sub_left_update_2:$sub_left_update_2 ../left-sub
+i=$(commit $i "Cherry-pick left sub-branch commit to left")
+cd ../..
+
+say "Merging LEFT SUB-BRANCH back to LEFT"
+svn update
+cd branches/left
+# it's only a merge because the previous merge cherry-picked the top commit
+svn merge -r$sub_left_make:$sub_left_update_2 ../left-sub --accept postpone
+i=$(commit $i "Merge left sub-branch to left")
+cd ../..
+
+say "Merging EVERYTHING to TRUNK"
+svn update
+cd trunk
+svn merge ../branches/left --accept postpone
+svn resolved bang
+i=$(commit $i "Merge left to trunk 2")
+# this merge, svn happily updates the mergeinfo, but there is actually
+# nothing to merge.  git-svn will not make a meaningless merge commit.
+svn merge ../branches/right --accept postpone
+i=$(commit $i "non-merge right to trunk 2")
+cd ..
+
+say "Branching b1 from trunk"
+svn update
+svn cp trunk branches/b1
+i=$(commit $i "make b1 branch from trunk")
+
+say "Branching b2 from trunk"
+svn update
+svn cp trunk branches/b2
+i=$(commit $i "make b2 branch from trunk")
+
+say "Make a commit to b2"
+svn update
+cd branches/b2
+echo "b2" > b2file
+svn add b2file
+i=$(commit $i "b2 update 1")
+cd ../..
+
+say "Make a commit to b1"
+svn update
+cd branches/b1
+echo "b1" > b1file
+svn add b1file
+i=$(commit $i "b1 update 1")
+cd ../..
+
+say "Merge b1 to trunk"
+svn update
+cd trunk
+svn merge ../branches/b1/ --accept postpone
+i=$(commit $i "Merge b1 to trunk")
+cd ..
+
+say "Make a commit to trunk before merging trunk to b2"
+svn update
+cd trunk
+echo "trunk" > trunkfile
+svn add trunkfile
+i=$(commit $i "trunk commit before merging trunk to b2")
+cd ..
+
+say "Merge trunk to b2"
+svn update
+cd branches/b2
+svn merge ../../trunk/ --accept postpone
+i=$(commit $i "Merge trunk to b2")
+cd ../..
+
+say "Merge b2 to trunk"
+svn update
+cd trunk
+svn merge ../branches/b2/ --accept postpone
+svn resolved b1file
+svn resolved trunkfile
+i=$(commit $i "Merge b2 to trunk")
+cd ..
+
+say "Creating f1 from trunk with a new file"
+svn update
+svn cp trunk branches/f1
+cd branches/f1
+echo "f1" > f1file
+svn add f1file
+cd ../..
+i=$(commit $i "make f1 branch from trunk with a new file")
+
+say "Creating f2 from trunk with a new file"
+svn update
+svn cp trunk branches/f2
+cd branches/f2
+echo "f2" > f2file
+svn add f2file
+cd ../..
+i=$(commit $i "make f2 branch from trunk with a new file")
+
+say "Merge f1 and f2 to trunk in one go"
+svn update
+cd trunk
+svn merge ../branches/f1/ --accept postpone
+svn merge ../branches/f2/ --accept postpone
+i=$(commit $i "Merge f1 and f2 to trunk")
+cd ..
+
+say "Adding subdirectory to LEFT"
+svn update
+cd branches/left
+mkdir subdir
+echo "Yeehaw" > subdir/cowboy
+svn add subdir
+i=$(commit $i "add subdirectory to left branch")
+cd ../../
+
+say "Merging LEFT to TRUNK"
+svn update
+cd trunk
+svn merge ../branches/left --accept postpone
+i=$(commit $i "merge left to trunk")
+cd ..
+
+say "Make PARTIAL branch"
+svn update
+svn cp trunk/subdir branches/partial
+i=$(commit $i "make partial branch")
+
+say "Make a commit to PARTIAL"
+svn update
+cd branches/partial
+echo "racecar" > palindromes
+svn add palindromes
+i=$(commit $i "partial update")
+cd ../../
+
+say "Merge PARTIAL to TRUNK"
+svn update
+cd trunk/subdir
+svn merge ../../branches/partial --accept postpone
+i=$(commit $i "merge partial to trunk")
+cd ../../
+
+say "Tagging trunk"
+svn update
+svn cp trunk tags/v1.0
+i=$(commit $i "tagging v1.0")
+
+say "Branching BUGFIX from v1.0"
+svn update
+svn cp tags/v1.0 branches/bugfix
+i=$(commit $i "make bugfix branch from tag")
+
+say "Make a commit to BUGFIX"
+svn update
+cd branches/bugfix/
+echo "kayak" >> subdir/palindromes
+i=$(commit $i "commit to bugfix")
+cd ../../
+
+say "Merge BUGFIX to TRUNK"
+svn update
+cd trunk
+svn merge ../branches/bugfix/ --accept postpone
+i=$(commit $i "Merge BUGFIX to TRUNK")
+cd ..
+
+cd ..
+svnadmin dump foo.svn > svn-mergeinfo.dump
+
+rm -rf foo foo.svn
diff --git a/t/t9151/svn-mergeinfo.dump b/t/t9151/svn-mergeinfo.dump
new file mode 100644 (file)
index 0000000..47cafcf
--- /dev/null
@@ -0,0 +1,2388 @@
+SVN-fs-dump-format-version: 2
+
+UUID: d6191530-2693-4a8e-98e7-b194d4c3edd8
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2010-01-19T04:14:02.832406Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 134
+Content-length: 134
+
+K 7
+svn:log
+V 36
+(r1) Setup trunk, branches, and tags
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:03.055172Z
+PROPS-END
+
+Node-path: branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: tags
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 13
+(r2) ancestor
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:04.064506Z
+PROPS-END
+
+Node-path: trunk/Makefile
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2401
+Text-content-md5: bfd8ff778d1492dc6758567373176a89
+Text-content-sha1: 103205ce331f7d64086dba497574734f78439590
+Content-length: 2411
+
+PROPS-END
+# -DCOLLISION_CHECK if you believe that SHA1's
+# 1461501637330902918203684832716283019655932542976 hashes do not give you
+# enough guarantees about no collisions between objects ever hapenning.
+#
+# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
+# Note that you need some new 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).
+CFLAGS=-g -O3 -Wall
+
+CC=gcc
+
+
+PROG=   update-cache show-diff init-db write-tree read-tree commit-tree \
+       cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \
+       check-files ls-tree merge-base
+
+all: $(PROG)
+
+install: $(PROG)
+       install $(PROG) $(HOME)/bin/
+
+LIBS= -lssl -lz
+
+init-db: init-db.o
+
+update-cache: update-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
+
+show-diff: show-diff.o read-cache.o
+       $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS)
+
+write-tree: write-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS)
+
+read-tree: read-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS)
+
+commit-tree: commit-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS)
+
+cat-file: cat-file.o read-cache.o
+       $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS)
+
+fsck-cache: fsck-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS)
+
+checkout-cache: checkout-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS)
+
+diff-tree: diff-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS)
+
+rev-tree: rev-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o $(LIBS)
+
+show-files: show-files.o read-cache.o
+       $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS)
+
+check-files: check-files.o read-cache.o
+       $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS)
+
+ls-tree: ls-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS)
+
+merge-base: merge-base.o read-cache.o
+       $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS)
+
+read-cache.o: cache.h
+show-diff.o: cache.h
+
+clean:
+       rm -f *.o $(PROG)
+
+backup: clean
+       cd .. ; tar czvf dircache.tar.gz dir-cache
+
+
+Revision-number: 3
+Prop-content-length: 119
+Content-length: 119
+
+K 7
+svn:log
+V 21
+(r3) make left branch
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:06.040389Z
+PROPS-END
+
+Node-path: branches/left
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Node-path: branches/left/Makefile
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/Makefile
+Text-copy-source-md5: bfd8ff778d1492dc6758567373176a89
+Text-copy-source-sha1: 103205ce331f7d64086dba497574734f78439590
+
+
+Revision-number: 4
+Prop-content-length: 120
+Content-length: 120
+
+K 7
+svn:log
+V 22
+(r4) make right branch
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:08.040905Z
+PROPS-END
+
+Node-path: branches/right
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Node-path: branches/right/Makefile
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/Makefile
+Text-copy-source-md5: bfd8ff778d1492dc6758567373176a89
+Text-copy-source-sha1: 103205ce331f7d64086dba497574734f78439590
+
+
+Revision-number: 5
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 18
+(r5) left update 1
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:09.049169Z
+PROPS-END
+
+Node-path: branches/left/Makefile
+Node-kind: file
+Node-action: change
+Text-content-length: 2465
+Text-content-md5: 16e38d9753b061731650561ce01b1195
+Text-content-sha1: 36da4b84ea9b64218ab48171dfc5c48ae025f38b
+Content-length: 2465
+
+# -DCOLLISION_CHECK if you believe that SHA1's
+# 1461501637330902918203684832716283019655932542976 hashes do not give you
+# enough guarantees about no collisions between objects ever hapenning.
+#
+# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
+# Note that you need some new 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).
+CFLAGS=-g -O3 -Wall
+
+CC=gcc
+
+
+PROG=   update-cache show-diff init-db write-tree read-tree commit-tree \
+       cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \
+       check-files ls-tree merge-base
+
+all: $(PROG)
+
+install: $(PROG)
+       install $(PROG) $(HOME)/bin/
+
+LIBS= -lssl -lz
+
+init-db: init-db.o
+
+update-cache: update-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
+
+show-diff: show-diff.o read-cache.o
+       $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS)
+
+write-tree: write-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS)
+
+read-tree: read-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS)
+
+commit-tree: commit-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS)
+
+cat-file: cat-file.o read-cache.o
+       $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS)
+
+fsck-cache: fsck-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS)
+
+checkout-cache: checkout-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS)
+
+diff-tree: diff-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS)
+
+rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+show-files: show-files.o read-cache.o
+       $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS)
+
+check-files: check-files.o read-cache.o
+       $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS)
+
+ls-tree: ls-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS)
+
+merge-base: merge-base.o read-cache.o
+       $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS)
+
+read-cache.o: cache.h
+show-diff.o: cache.h
+
+clean:
+       rm -f *.o $(PROG)
+
+backup: clean
+       cd .. ; tar czvf dircache.tar.gz dir-cache
+
+
+Revision-number: 6
+Prop-content-length: 117
+Content-length: 117
+
+K 7
+svn:log
+V 19
+(r6) right update 1
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:10.049350Z
+PROPS-END
+
+Node-path: branches/right/Makefile
+Node-kind: file
+Node-action: change
+Text-content-length: 2521
+Text-content-md5: 0668418a621333f4aa8b6632cd63e2a0
+Text-content-sha1: 4f29afd038e52f45acb5ef8c41acfc70062a741a
+Content-length: 2521
+
+# -DCOLLISION_CHECK if you believe that SHA1's
+# 1461501637330902918203684832716283019655932542976 hashes do not give you
+# enough guarantees about no collisions between objects ever hapenning.
+#
+# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
+# Note that you need some new 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).
+CFLAGS=-g -O3 -Wall
+
+CC=gcc
+
+
+PROG=   update-cache show-diff init-db write-tree read-tree commit-tree \
+       cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \
+       check-files ls-tree merge-base merge-cache
+
+all: $(PROG)
+
+install: $(PROG)
+       install $(PROG) $(HOME)/bin/
+
+LIBS= -lssl -lz
+
+init-db: init-db.o
+
+update-cache: update-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
+
+show-diff: show-diff.o read-cache.o
+       $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS)
+
+write-tree: write-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS)
+
+read-tree: read-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS)
+
+commit-tree: commit-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS)
+
+cat-file: cat-file.o read-cache.o
+       $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS)
+
+fsck-cache: fsck-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS)
+
+checkout-cache: checkout-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS)
+
+diff-tree: diff-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS)
+
+rev-tree: rev-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o $(LIBS)
+
+show-files: show-files.o read-cache.o
+       $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS)
+
+check-files: check-files.o read-cache.o
+       $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS)
+
+ls-tree: ls-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS)
+
+merge-base: merge-base.o read-cache.o
+       $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS)
+
+merge-cache: merge-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o merge-cache merge-cache.o read-cache.o $(LIBS)
+
+read-cache.o: cache.h
+show-diff.o: cache.h
+
+clean:
+       rm -f *.o $(PROG)
+
+backup: clean
+       cd .. ; tar czvf dircache.tar.gz dir-cache
+
+
+Revision-number: 7
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 18
+(r7) left update 2
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:11.049209Z
+PROPS-END
+
+Node-path: branches/left/Makefile
+Node-kind: file
+Node-action: change
+Text-content-length: 2529
+Text-content-md5: f6b197cc3f2e89a83e545d4bb003de73
+Text-content-sha1: 2f656677cfec0bceec85e53036ffb63e25126f8e
+Content-length: 2529
+
+# -DCOLLISION_CHECK if you believe that SHA1's
+# 1461501637330902918203684832716283019655932542976 hashes do not give you
+# enough guarantees about no collisions between objects ever hapenning.
+#
+# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
+# Note that you need some new 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).
+CFLAGS=-g -O3 -Wall
+
+CC=gcc
+
+
+PROG=   update-cache show-diff init-db write-tree read-tree commit-tree \
+       cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \
+       check-files ls-tree merge-base
+
+all: $(PROG)
+
+install: $(PROG)
+       install $(PROG) $(HOME)/bin/
+
+LIBS= -lssl -lz
+
+init-db: init-db.o
+
+update-cache: update-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
+
+show-diff: show-diff.o read-cache.o
+       $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS)
+
+write-tree: write-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS)
+
+read-tree: read-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS)
+
+commit-tree: commit-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS)
+
+cat-file: cat-file.o read-cache.o
+       $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS)
+
+fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+checkout-cache: checkout-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS)
+
+diff-tree: diff-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS)
+
+rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+show-files: show-files.o read-cache.o
+       $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS)
+
+check-files: check-files.o read-cache.o
+       $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS)
+
+ls-tree: ls-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS)
+
+merge-base: merge-base.o read-cache.o
+       $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS)
+
+read-cache.o: cache.h
+show-diff.o: cache.h
+
+clean:
+       rm -f *.o $(PROG)
+
+backup: clean
+       cd .. ; tar czvf dircache.tar.gz dir-cache
+
+
+Revision-number: 8
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 18
+(r8) left update 3
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:12.049234Z
+PROPS-END
+
+Node-path: branches/left/Makefile
+Node-kind: file
+Node-action: change
+Text-content-length: 2593
+Text-content-md5: 5ccff689fb290e00b85fe18ee50c54ba
+Text-content-sha1: a13de8e23f1483efca3e57b2b64b0ae6f740ce10
+Content-length: 2593
+
+# -DCOLLISION_CHECK if you believe that SHA1's
+# 1461501637330902918203684832716283019655932542976 hashes do not give you
+# enough guarantees about no collisions between objects ever hapenning.
+#
+# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
+# Note that you need some new 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).
+CFLAGS=-g -O3 -Wall
+
+CC=gcc
+
+
+PROG=   update-cache show-diff init-db write-tree read-tree commit-tree \
+       cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \
+       check-files ls-tree merge-base
+
+all: $(PROG)
+
+install: $(PROG)
+       install $(PROG) $(HOME)/bin/
+
+LIBS= -lssl -lz
+
+init-db: init-db.o
+
+update-cache: update-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
+
+show-diff: show-diff.o read-cache.o
+       $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS)
+
+write-tree: write-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS)
+
+read-tree: read-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS)
+
+commit-tree: commit-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS)
+
+cat-file: cat-file.o read-cache.o
+       $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS)
+
+fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+checkout-cache: checkout-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS)
+
+diff-tree: diff-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS)
+
+rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+show-files: show-files.o read-cache.o
+       $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS)
+
+check-files: check-files.o read-cache.o
+       $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS)
+
+ls-tree: ls-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS)
+
+merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+read-cache.o: cache.h
+show-diff.o: cache.h
+
+clean:
+       rm -f *.o $(PROG)
+
+backup: clean
+       cd .. ; tar czvf dircache.tar.gz dir-cache
+
+
+Revision-number: 9
+Prop-content-length: 123
+Content-length: 123
+
+K 7
+svn:log
+V 25
+(r9) make left sub-branch
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:14.040894Z
+PROPS-END
+
+Node-path: branches/left-sub
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: branches/left
+
+
+Node-path: branches/left-sub/Makefile
+Node-kind: file
+Node-action: delete
+
+Node-path: branches/left-sub/Makefile
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 8
+Node-copyfrom-path: branches/left/Makefile
+Text-copy-source-md5: 5ccff689fb290e00b85fe18ee50c54ba
+Text-copy-source-sha1: a13de8e23f1483efca3e57b2b64b0ae6f740ce10
+
+
+
+
+Revision-number: 10
+Prop-content-length: 128
+Content-length: 128
+
+K 7
+svn:log
+V 30
+(r10) left sub-branch update 1
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:15.049935Z
+PROPS-END
+
+Node-path: branches/left-sub/README
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 7
+Text-content-md5: fdbcfb6be9afe1121862143f226b51cf
+Text-content-sha1: 1d1f5ea4ceb584337ffe59b8980d92e3b78dfef4
+Content-length: 17
+
+PROPS-END
+crunch
+
+
+Revision-number: 11
+Prop-content-length: 125
+Content-length: 125
+
+K 7
+svn:log
+V 27
+(r11) Merge left to trunk 1
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:18.056594Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 54
+Content-length: 54
+
+K 13
+svn:mergeinfo
+V 19
+/branches/left:2-10
+PROPS-END
+
+
+Node-path: trunk/Makefile
+Node-kind: file
+Node-action: change
+Text-content-length: 2593
+Text-content-md5: 5ccff689fb290e00b85fe18ee50c54ba
+Text-content-sha1: a13de8e23f1483efca3e57b2b64b0ae6f740ce10
+Content-length: 2593
+
+# -DCOLLISION_CHECK if you believe that SHA1's
+# 1461501637330902918203684832716283019655932542976 hashes do not give you
+# enough guarantees about no collisions between objects ever hapenning.
+#
+# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
+# Note that you need some new 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).
+CFLAGS=-g -O3 -Wall
+
+CC=gcc
+
+
+PROG=   update-cache show-diff init-db write-tree read-tree commit-tree \
+       cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \
+       check-files ls-tree merge-base
+
+all: $(PROG)
+
+install: $(PROG)
+       install $(PROG) $(HOME)/bin/
+
+LIBS= -lssl -lz
+
+init-db: init-db.o
+
+update-cache: update-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
+
+show-diff: show-diff.o read-cache.o
+       $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS)
+
+write-tree: write-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS)
+
+read-tree: read-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS)
+
+commit-tree: commit-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS)
+
+cat-file: cat-file.o read-cache.o
+       $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS)
+
+fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+checkout-cache: checkout-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS)
+
+diff-tree: diff-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS)
+
+rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+show-files: show-files.o read-cache.o
+       $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS)
+
+check-files: check-files.o read-cache.o
+       $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS)
+
+ls-tree: ls-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS)
+
+merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+read-cache.o: cache.h
+show-diff.o: cache.h
+
+clean:
+       rm -f *.o $(PROG)
+
+backup: clean
+       cd .. ; tar czvf dircache.tar.gz dir-cache
+
+
+Revision-number: 12
+Prop-content-length: 117
+Content-length: 117
+
+K 7
+svn:log
+V 19
+(r12) left update 4
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:19.049620Z
+PROPS-END
+
+Node-path: branches/left/zlonk
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 7
+Text-content-md5: 8b9d8c7c2aaa6167e7d3407a773bbbba
+Text-content-sha1: 9716527ebd70a75c27625cacbeb2d897c6e86178
+Content-length: 17
+
+PROPS-END
+touche
+
+
+Revision-number: 13
+Prop-content-length: 118
+Content-length: 118
+
+K 7
+svn:log
+V 20
+(r13) right update 2
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:20.049659Z
+PROPS-END
+
+Node-path: branches/right/bang
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 8
+Text-content-md5: 34c28f1d2dc6a9adeccc4265bf7516cb
+Text-content-sha1: 0bc5bb345c0e71d28f784f12e0bd2d384c283062
+Content-length: 18
+
+PROPS-END
+thwacke
+
+
+Revision-number: 14
+Prop-content-length: 140
+Content-length: 140
+
+K 7
+svn:log
+V 42
+(r14) Cherry-pick right 2 commits to trunk
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:23.041991Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 75
+Content-length: 75
+
+K 13
+svn:mergeinfo
+V 40
+/branches/left:2-10
+/branches/right:6-13
+PROPS-END
+
+
+Node-path: trunk/Makefile
+Node-kind: file
+Node-action: change
+Text-content-length: 2713
+Text-content-md5: 0afbe34f244cd662b1f97d708c687f90
+Text-content-sha1: 46d9377d783e67a9b581da110352e799517c8a14
+Content-length: 2713
+
+# -DCOLLISION_CHECK if you believe that SHA1's
+# 1461501637330902918203684832716283019655932542976 hashes do not give you
+# enough guarantees about no collisions between objects ever hapenning.
+#
+# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
+# Note that you need some new 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).
+CFLAGS=-g -O3 -Wall
+
+CC=gcc
+
+
+PROG=   update-cache show-diff init-db write-tree read-tree commit-tree \
+       cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \
+       check-files ls-tree merge-base merge-cache
+
+all: $(PROG)
+
+install: $(PROG)
+       install $(PROG) $(HOME)/bin/
+
+LIBS= -lssl -lz
+
+init-db: init-db.o
+
+update-cache: update-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
+
+show-diff: show-diff.o read-cache.o
+       $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS)
+
+write-tree: write-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS)
+
+read-tree: read-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS)
+
+commit-tree: commit-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS)
+
+cat-file: cat-file.o read-cache.o
+       $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS)
+
+fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+checkout-cache: checkout-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS)
+
+diff-tree: diff-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS)
+
+rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+show-files: show-files.o read-cache.o
+       $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS)
+
+check-files: check-files.o read-cache.o
+       $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS)
+
+ls-tree: ls-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS)
+
+merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+merge-cache: merge-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o merge-cache merge-cache.o read-cache.o $(LIBS)
+
+read-cache.o: cache.h
+show-diff.o: cache.h
+
+clean:
+       rm -f *.o $(PROG)
+
+backup: clean
+       cd .. ; tar czvf dircache.tar.gz dir-cache
+
+
+Node-path: trunk/bang
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 13
+Node-copyfrom-path: branches/right/bang
+Text-copy-source-md5: 34c28f1d2dc6a9adeccc4265bf7516cb
+Text-copy-source-sha1: 0bc5bb345c0e71d28f784f12e0bd2d384c283062
+
+
+Revision-number: 15
+Prop-content-length: 126
+Content-length: 126
+
+K 7
+svn:log
+V 28
+(r15) Merge right to trunk 1
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:26.054456Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 75
+Content-length: 75
+
+K 13
+svn:mergeinfo
+V 40
+/branches/left:2-10
+/branches/right:2-14
+PROPS-END
+
+
+Revision-number: 16
+Prop-content-length: 118
+Content-length: 118
+
+K 7
+svn:log
+V 20
+(r16) right update 3
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:27.049955Z
+PROPS-END
+
+Node-path: branches/right/urkkk
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 5889c8392e16251b0c80927607a03036
+Text-content-sha1: 3934264d277a0cf886b6b1c7f2b9e56da2525302
+Content-length: 16
+
+PROPS-END
+whamm
+
+
+Revision-number: 17
+Prop-content-length: 118
+Content-length: 118
+
+K 7
+svn:log
+V 20
+(r17) trunk update 1
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:28.049615Z
+PROPS-END
+
+Node-path: trunk/vronk
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: b2f80fa02a7f1364b9c29d3da44bf9f9
+Text-content-sha1: e994d980c0f2d7a3f76138bf96d57f36f9633828
+Content-length: 14
+
+PROPS-END
+pow
+
+
+Revision-number: 18
+Prop-content-length: 134
+Content-length: 134
+
+K 7
+svn:log
+V 36
+(r18) Merge right to left sub-branch
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:31.061460Z
+PROPS-END
+
+Node-path: branches/left-sub
+Node-kind: dir
+Node-action: change
+Prop-content-length: 55
+Content-length: 55
+
+K 13
+svn:mergeinfo
+V 20
+/branches/right:2-17
+PROPS-END
+
+
+Node-path: branches/left-sub/Makefile
+Node-kind: file
+Node-action: change
+Text-content-length: 2713
+Text-content-md5: 0afbe34f244cd662b1f97d708c687f90
+Text-content-sha1: 46d9377d783e67a9b581da110352e799517c8a14
+Content-length: 2713
+
+# -DCOLLISION_CHECK if you believe that SHA1's
+# 1461501637330902918203684832716283019655932542976 hashes do not give you
+# enough guarantees about no collisions between objects ever hapenning.
+#
+# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
+# Note that you need some new 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).
+CFLAGS=-g -O3 -Wall
+
+CC=gcc
+
+
+PROG=   update-cache show-diff init-db write-tree read-tree commit-tree \
+       cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \
+       check-files ls-tree merge-base merge-cache
+
+all: $(PROG)
+
+install: $(PROG)
+       install $(PROG) $(HOME)/bin/
+
+LIBS= -lssl -lz
+
+init-db: init-db.o
+
+update-cache: update-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
+
+show-diff: show-diff.o read-cache.o
+       $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS)
+
+write-tree: write-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS)
+
+read-tree: read-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS)
+
+commit-tree: commit-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS)
+
+cat-file: cat-file.o read-cache.o
+       $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS)
+
+fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+checkout-cache: checkout-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS)
+
+diff-tree: diff-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS)
+
+rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+show-files: show-files.o read-cache.o
+       $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS)
+
+check-files: check-files.o read-cache.o
+       $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS)
+
+ls-tree: ls-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS)
+
+merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+merge-cache: merge-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o merge-cache merge-cache.o read-cache.o $(LIBS)
+
+read-cache.o: cache.h
+show-diff.o: cache.h
+
+clean:
+       rm -f *.o $(PROG)
+
+backup: clean
+       cd .. ; tar czvf dircache.tar.gz dir-cache
+
+
+Node-path: branches/left-sub/bang
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 17
+Node-copyfrom-path: branches/right/bang
+Text-copy-source-md5: 34c28f1d2dc6a9adeccc4265bf7516cb
+Text-copy-source-sha1: 0bc5bb345c0e71d28f784f12e0bd2d384c283062
+
+
+Node-path: branches/left-sub/urkkk
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 17
+Node-copyfrom-path: branches/right/urkkk
+Text-copy-source-md5: 5889c8392e16251b0c80927607a03036
+Text-copy-source-sha1: 3934264d277a0cf886b6b1c7f2b9e56da2525302
+
+
+Revision-number: 19
+Prop-content-length: 128
+Content-length: 128
+
+K 7
+svn:log
+V 30
+(r19) left sub-branch update 2
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:32.049244Z
+PROPS-END
+
+Node-path: branches/left-sub/wham_eth
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 757bcd5818572ef3f9580052617c1c8b
+Text-content-sha1: b165019b005c199237ba822c4404e771e93b654a
+Content-length: 16
+
+PROPS-END
+zowie
+
+
+Revision-number: 20
+Prop-content-length: 117
+Content-length: 117
+
+K 7
+svn:log
+V 19
+(r20) left update 5
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:33.049332Z
+PROPS-END
+
+Node-path: branches/left/glurpp
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 8
+Text-content-md5: 14a169f628e0bb59df9c2160649d0a30
+Text-content-sha1: ef7d929e52177767ecfcd28941f6b7f04b4131e3
+Content-length: 18
+
+PROPS-END
+eee_yow
+
+
+Revision-number: 21
+Prop-content-length: 146
+Content-length: 146
+
+K 7
+svn:log
+V 48
+(r21) Cherry-pick left sub-branch commit to left
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:36.041839Z
+PROPS-END
+
+Node-path: branches/left
+Node-kind: dir
+Node-action: change
+Prop-content-length: 56
+Content-length: 56
+
+K 13
+svn:mergeinfo
+V 21
+/branches/left-sub:19
+PROPS-END
+
+
+Node-path: branches/left/wham_eth
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 19
+Node-copyfrom-path: branches/left-sub/wham_eth
+Text-copy-source-md5: 757bcd5818572ef3f9580052617c1c8b
+Text-copy-source-sha1: b165019b005c199237ba822c4404e771e93b654a
+
+
+Revision-number: 22
+Prop-content-length: 133
+Content-length: 133
+
+K 7
+svn:log
+V 35
+(r22) Merge left sub-branch to left
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:39.045014Z
+PROPS-END
+
+Node-path: branches/left
+Node-kind: dir
+Node-action: change
+Prop-content-length: 79
+Content-length: 79
+
+K 13
+svn:mergeinfo
+V 44
+/branches/left-sub:4-19
+/branches/right:2-17
+PROPS-END
+
+
+Node-path: branches/left/Makefile
+Node-kind: file
+Node-action: change
+Text-content-length: 2713
+Text-content-md5: 0afbe34f244cd662b1f97d708c687f90
+Text-content-sha1: 46d9377d783e67a9b581da110352e799517c8a14
+Content-length: 2713
+
+# -DCOLLISION_CHECK if you believe that SHA1's
+# 1461501637330902918203684832716283019655932542976 hashes do not give you
+# enough guarantees about no collisions between objects ever hapenning.
+#
+# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
+# Note that you need some new 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).
+CFLAGS=-g -O3 -Wall
+
+CC=gcc
+
+
+PROG=   update-cache show-diff init-db write-tree read-tree commit-tree \
+       cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \
+       check-files ls-tree merge-base merge-cache
+
+all: $(PROG)
+
+install: $(PROG)
+       install $(PROG) $(HOME)/bin/
+
+LIBS= -lssl -lz
+
+init-db: init-db.o
+
+update-cache: update-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
+
+show-diff: show-diff.o read-cache.o
+       $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS)
+
+write-tree: write-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS)
+
+read-tree: read-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS)
+
+commit-tree: commit-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS)
+
+cat-file: cat-file.o read-cache.o
+       $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS)
+
+fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+checkout-cache: checkout-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS)
+
+diff-tree: diff-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS)
+
+rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+show-files: show-files.o read-cache.o
+       $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS)
+
+check-files: check-files.o read-cache.o
+       $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS)
+
+ls-tree: ls-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS)
+
+merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+merge-cache: merge-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o merge-cache merge-cache.o read-cache.o $(LIBS)
+
+read-cache.o: cache.h
+show-diff.o: cache.h
+
+clean:
+       rm -f *.o $(PROG)
+
+backup: clean
+       cd .. ; tar czvf dircache.tar.gz dir-cache
+
+
+Node-path: branches/left/README
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 18
+Node-copyfrom-path: branches/left-sub/README
+Text-copy-source-md5: fdbcfb6be9afe1121862143f226b51cf
+Text-copy-source-sha1: 1d1f5ea4ceb584337ffe59b8980d92e3b78dfef4
+
+
+Node-path: branches/left/bang
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 18
+Node-copyfrom-path: branches/left-sub/bang
+Text-copy-source-md5: 34c28f1d2dc6a9adeccc4265bf7516cb
+Text-copy-source-sha1: 0bc5bb345c0e71d28f784f12e0bd2d384c283062
+
+
+Node-path: branches/left/urkkk
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 18
+Node-copyfrom-path: branches/left-sub/urkkk
+Text-copy-source-md5: 5889c8392e16251b0c80927607a03036
+Text-copy-source-sha1: 3934264d277a0cf886b6b1c7f2b9e56da2525302
+
+
+Revision-number: 23
+Prop-content-length: 125
+Content-length: 125
+
+K 7
+svn:log
+V 27
+(r23) Merge left to trunk 2
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:42.052798Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 99
+Content-length: 99
+
+K 13
+svn:mergeinfo
+V 64
+/branches/left:2-22
+/branches/left-sub:4-19
+/branches/right:2-17
+PROPS-END
+
+
+Node-path: trunk/README
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 22
+Node-copyfrom-path: branches/left/README
+Text-copy-source-md5: fdbcfb6be9afe1121862143f226b51cf
+Text-copy-source-sha1: 1d1f5ea4ceb584337ffe59b8980d92e3b78dfef4
+
+
+Node-path: trunk/glurpp
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 22
+Node-copyfrom-path: branches/left/glurpp
+Text-copy-source-md5: 14a169f628e0bb59df9c2160649d0a30
+Text-copy-source-sha1: ef7d929e52177767ecfcd28941f6b7f04b4131e3
+
+
+Node-path: trunk/urkkk
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 22
+Node-copyfrom-path: branches/left/urkkk
+Text-copy-source-md5: 5889c8392e16251b0c80927607a03036
+Text-copy-source-sha1: 3934264d277a0cf886b6b1c7f2b9e56da2525302
+
+
+Node-path: trunk/wham_eth
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 22
+Node-copyfrom-path: branches/left/wham_eth
+Text-copy-source-md5: 757bcd5818572ef3f9580052617c1c8b
+Text-copy-source-sha1: b165019b005c199237ba822c4404e771e93b654a
+
+
+Node-path: trunk/zlonk
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 22
+Node-copyfrom-path: branches/left/zlonk
+Text-copy-source-md5: 8b9d8c7c2aaa6167e7d3407a773bbbba
+Text-copy-source-sha1: 9716527ebd70a75c27625cacbeb2d897c6e86178
+
+
+Revision-number: 24
+Prop-content-length: 130
+Content-length: 130
+
+K 7
+svn:log
+V 32
+(r24) non-merge right to trunk 2
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:44.038434Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 99
+Content-length: 99
+
+K 13
+svn:mergeinfo
+V 64
+/branches/left:2-22
+/branches/left-sub:4-19
+/branches/right:2-22
+PROPS-END
+
+
+Revision-number: 25
+Prop-content-length: 129
+Content-length: 129
+
+K 7
+svn:log
+V 31
+(r25) make b1 branch from trunk
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:18:56.084589Z
+PROPS-END
+
+Node-path: branches/b1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 24
+Node-copyfrom-path: trunk
+
+
+Revision-number: 26
+Prop-content-length: 129
+Content-length: 129
+
+K 7
+svn:log
+V 31
+(r26) make b2 branch from trunk
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:18:59.076940Z
+PROPS-END
+
+Node-path: branches/b2
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 25
+Node-copyfrom-path: trunk
+
+
+Revision-number: 27
+Prop-content-length: 115
+Content-length: 115
+
+K 7
+svn:log
+V 17
+(r27) b2 update 1
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:01.095762Z
+PROPS-END
+
+Node-path: branches/b2/b2file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 3
+Text-content-md5: 5edbdd57cba621eb3c6e601bf563b4dc
+Text-content-sha1: 9d4b38049776bd0a2074d67cad23f8eaed35a3b3
+Content-length: 13
+
+PROPS-END
+b2
+
+
+Revision-number: 28
+Prop-content-length: 115
+Content-length: 115
+
+K 7
+svn:log
+V 17
+(r28) b1 update 1
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:03.097465Z
+PROPS-END
+
+Node-path: branches/b1/b1file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 3
+Text-content-md5: 08778dfd9ac4f603231896aba7aad523
+Text-content-sha1: b551771aa4ad5b14123fc3bd98d89db2bc0edd4f
+Content-length: 13
+
+PROPS-END
+b1
+
+
+Revision-number: 29
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 23
+(r29) Merge b1 to trunk
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:06.073175Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 118
+Content-length: 118
+
+K 13
+svn:mergeinfo
+V 83
+/branches/b1:25-28
+/branches/left:2-22
+/branches/left-sub:4-19
+/branches/right:2-22
+PROPS-END
+
+
+Node-path: trunk/b1file
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 28
+Node-copyfrom-path: branches/b1/b1file
+Text-copy-source-md5: 08778dfd9ac4f603231896aba7aad523
+Text-copy-source-sha1: b551771aa4ad5b14123fc3bd98d89db2bc0edd4f
+
+
+Revision-number: 30
+Prop-content-length: 143
+Content-length: 143
+
+K 7
+svn:log
+V 45
+(r30) trunk commit before merging trunk to b2
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:08.096353Z
+PROPS-END
+
+Node-path: trunk/trunkfile
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: edf45fe5c98c5367733b39bbb2bb20d9
+Text-content-sha1: 7361d1685e5c86dfc523620cfaf598f196f86239
+Content-length: 16
+
+PROPS-END
+trunk
+
+
+Revision-number: 31
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 23
+(r31) Merge trunk to b2
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:11.081541Z
+PROPS-END
+
+Node-path: branches/b2
+Node-kind: dir
+Node-action: change
+Prop-content-length: 131
+Content-length: 131
+
+K 13
+svn:mergeinfo
+V 96
+/branches/b1:25-28
+/branches/left:2-22
+/branches/left-sub:4-19
+/branches/right:2-22
+/trunk:26-30
+PROPS-END
+
+
+Node-path: branches/b2/b1file
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 30
+Node-copyfrom-path: trunk/b1file
+Text-copy-source-md5: 08778dfd9ac4f603231896aba7aad523
+Text-copy-source-sha1: b551771aa4ad5b14123fc3bd98d89db2bc0edd4f
+
+
+Node-path: branches/b2/trunkfile
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 30
+Node-copyfrom-path: trunk/trunkfile
+Text-copy-source-md5: edf45fe5c98c5367733b39bbb2bb20d9
+Text-copy-source-sha1: 7361d1685e5c86dfc523620cfaf598f196f86239
+
+
+Revision-number: 32
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 23
+(r32) Merge b2 to trunk
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:14.117939Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 138
+Content-length: 138
+
+K 13
+svn:mergeinfo
+V 102
+/branches/b1:25-28
+/branches/b2:26-31
+/branches/left:2-22
+/branches/left-sub:4-19
+/branches/right:2-22
+PROPS-END
+
+
+Node-path: trunk/b2file
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 31
+Node-copyfrom-path: branches/b2/b2file
+Text-copy-source-md5: 5edbdd57cba621eb3c6e601bf563b4dc
+Text-copy-source-sha1: 9d4b38049776bd0a2074d67cad23f8eaed35a3b3
+
+
+Revision-number: 33
+Prop-content-length: 145
+Content-length: 145
+
+K 7
+svn:log
+V 47
+(r33) make f1 branch from trunk with a new file
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:17.105832Z
+PROPS-END
+
+Node-path: branches/f1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 32
+Node-copyfrom-path: trunk
+
+
+Node-path: branches/f1/f1file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 3
+Text-content-md5: 2b1abc6b6c5c0018851f9f8e6475563b
+Text-content-sha1: aece6dfba588900e00d95601d22b4408d49580af
+Content-length: 13
+
+PROPS-END
+f1
+
+
+Revision-number: 34
+Prop-content-length: 145
+Content-length: 145
+
+K 7
+svn:log
+V 47
+(r34) make f2 branch from trunk with a new file
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:20.110057Z
+PROPS-END
+
+Node-path: branches/f2
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 33
+Node-copyfrom-path: trunk
+
+
+Node-path: branches/f2/f2file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 3
+Text-content-md5: 575c5638d60271457e54ab7d07309502
+Text-content-sha1: 1c49a440c352f3473efa9512255033b94dc7def0
+Content-length: 13
+
+PROPS-END
+f2
+
+
+Revision-number: 35
+Prop-content-length: 128
+Content-length: 128
+
+K 7
+svn:log
+V 30
+(r35) Merge f1 and f2 to trunk
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:24.081490Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 173
+Content-length: 173
+
+K 13
+svn:mergeinfo
+V 137
+/branches/b1:25-28
+/branches/b2:26-31
+/branches/f1:33-34
+/branches/f2:34
+/branches/left:2-22
+/branches/left-sub:4-19
+/branches/right:2-22
+PROPS-END
+
+
+Node-path: trunk/f1file
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 34
+Node-copyfrom-path: branches/f1/f1file
+Text-copy-source-md5: 2b1abc6b6c5c0018851f9f8e6475563b
+Text-copy-source-sha1: aece6dfba588900e00d95601d22b4408d49580af
+
+
+Node-path: trunk/f2file
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 34
+Node-copyfrom-path: branches/f2/f2file
+Text-copy-source-md5: 575c5638d60271457e54ab7d07309502
+Text-copy-source-sha1: 1c49a440c352f3473efa9512255033b94dc7def0
+
+
+Revision-number: 36
+Prop-content-length: 135
+Content-length: 135
+
+K 7
+svn:log
+V 37
+(r36) add subdirectory to left branch
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:26.113516Z
+PROPS-END
+
+Node-path: branches/left/subdir
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: branches/left/subdir/cowboy
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 7
+Text-content-md5: f1d6530278ad409e68cc675476ad995f
+Text-content-sha1: 732d9e3e5c391ffd767a98b45ddcc848de778cea
+Content-length: 17
+
+PROPS-END
+Yeehaw
+
+
+Revision-number: 37
+Prop-content-length: 123
+Content-length: 123
+
+K 7
+svn:log
+V 25
+(r37) merge left to trunk
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:29.073699Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 173
+Content-length: 173
+
+K 13
+svn:mergeinfo
+V 137
+/branches/b1:25-28
+/branches/b2:26-31
+/branches/f1:33-34
+/branches/f2:34
+/branches/left:2-36
+/branches/left-sub:4-19
+/branches/right:2-22
+PROPS-END
+
+
+Node-path: trunk/subdir
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 36
+Node-copyfrom-path: branches/left/subdir
+
+
+Revision-number: 38
+Prop-content-length: 123
+Content-length: 123
+
+K 7
+svn:log
+V 25
+(r38) make partial branch
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:32.072243Z
+PROPS-END
+
+Node-path: branches/partial
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 37
+Node-copyfrom-path: trunk/subdir
+
+
+Revision-number: 39
+Prop-content-length: 118
+Content-length: 118
+
+K 7
+svn:log
+V 20
+(r39) partial update
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:34.097961Z
+PROPS-END
+
+Node-path: branches/partial/palindromes
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 8
+Text-content-md5: 5d1c2024fb5efc4eef812856df1b080c
+Text-content-sha1: 5f8509ddd14c91a52864dd1447344e706f9bbc69
+Content-length: 18
+
+PROPS-END
+racecar
+
+
+Revision-number: 40
+Prop-content-length: 126
+Content-length: 126
+
+K 7
+svn:log
+V 28
+(r40) merge partial to trunk
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:37.080211Z
+PROPS-END
+
+Node-path: trunk/subdir
+Node-kind: dir
+Node-action: change
+Prop-content-length: 246
+Content-length: 246
+
+K 13
+svn:mergeinfo
+V 210
+/branches/b1/subdir:25-28
+/branches/b2/subdir:26-31
+/branches/f1/subdir:33-34
+/branches/f2/subdir:34
+/branches/left/subdir:2-36
+/branches/left-sub/subdir:4-19
+/branches/partial:38-39
+/branches/right/subdir:2-22
+PROPS-END
+
+
+Node-path: trunk/subdir/palindromes
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 39
+Node-copyfrom-path: branches/partial/palindromes
+Text-copy-source-md5: 5d1c2024fb5efc4eef812856df1b080c
+Text-copy-source-sha1: 5f8509ddd14c91a52864dd1447344e706f9bbc69
+
+
+Revision-number: 41
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 18
+(r41) tagging v1.0
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:40.083460Z
+PROPS-END
+
+Node-path: tags/v1.0
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 40
+Node-copyfrom-path: trunk
+
+
+Revision-number: 42
+Prop-content-length: 131
+Content-length: 131
+
+K 7
+svn:log
+V 33
+(r42) make bugfix branch from tag
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:43.118075Z
+PROPS-END
+
+Node-path: branches/bugfix
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 41
+Node-copyfrom-path: tags/v1.0
+
+
+Revision-number: 43
+Prop-content-length: 120
+Content-length: 120
+
+K 7
+svn:log
+V 22
+(r43) commit to bugfix
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:45.079536Z
+PROPS-END
+
+Node-path: branches/bugfix/subdir/palindromes
+Node-kind: file
+Node-action: change
+Text-content-length: 14
+Text-content-md5: 3b12d98578a3f4320ba97e66da54fe5f
+Text-content-sha1: 672931c9e8ac2c408209efab2f015638b6d64042
+Content-length: 14
+
+racecar
+kayak
+
+
+Revision-number: 44
+Prop-content-length: 125
+Content-length: 125
+
+K 7
+svn:log
+V 27
+(r44) Merge BUGFIX to TRUNK
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:48.078914Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 210
+Content-length: 210
+
+K 13
+svn:mergeinfo
+V 174
+/branches/b1:25-28
+/branches/b2:26-31
+/branches/bugfix:42-43
+/branches/f1:33-34
+/branches/f2:34
+/branches/left:2-36
+/branches/left-sub:4-19
+/branches/right:2-22
+/tags/v1.0:41
+PROPS-END
+
+
+Node-path: trunk/subdir
+Node-kind: dir
+Node-action: change
+Prop-content-length: 297
+Content-length: 297
+
+K 13
+svn:mergeinfo
+V 261
+/branches/b1/subdir:25-28
+/branches/b2/subdir:26-31
+/branches/bugfix/subdir:42-43
+/branches/f1/subdir:33-34
+/branches/f2/subdir:34
+/branches/left/subdir:2-36
+/branches/left-sub/subdir:4-19
+/branches/partial:38-39
+/branches/right/subdir:2-22
+/tags/v1.0/subdir:41
+PROPS-END
+
+
+Node-path: trunk/subdir/palindromes
+Node-kind: file
+Node-action: change
+Text-content-length: 14
+Text-content-md5: 3b12d98578a3f4320ba97e66da54fe5f
+Text-content-sha1: 672931c9e8ac2c408209efab2f015638b6d64042
+Content-length: 14
+
+racecar
+kayak
+
+
diff --git a/t/t9152-svn-empty-dirs-after-gc.sh b/t/t9152-svn-empty-dirs-after-gc.sh
new file mode 100755 (executable)
index 0000000..301e779
--- /dev/null
@@ -0,0 +1,40 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Robert Zeh
+
+test_description='git svn creates empty directories, calls git gc, makes sure they are still empty'
+. ./lib-git-svn.sh
+
+test_expect_success 'initialize repo' '
+       for i in a b c d d/e d/e/f "weird file name"
+       do
+               svn_cmd mkdir -m "mkdir $i" "$svnrepo"/"$i"
+       done
+'
+
+test_expect_success 'clone' 'git svn clone "$svnrepo" cloned'
+
+test_expect_success 'git svn gc runs' '
+       (
+               cd cloned &&
+               git svn gc
+       )
+'
+
+test_expect_success 'git svn mkdirs recreates empty directories after git svn gc' '
+       (
+               cd cloned &&
+               rm -r * &&
+               git svn mkdirs &&
+               for i in a b c d d/e d/e/f "weird file name"
+               do
+                       if ! test -d "$i"
+                       then
+                               echo >&2 "$i does not exist"
+                               exit 1
+                       fi
+               done
+       )
+'
+
+test_done
diff --git a/t/t9153-git-svn-rewrite-uuid.sh b/t/t9153-git-svn-rewrite-uuid.sh
new file mode 100755 (executable)
index 0000000..88a2cfa
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Jay Soffian
+#
+
+test_description='git svn --rewrite-uuid test'
+
+. ./lib-git-svn.sh
+
+uuid=6cc8ada4-5932-4b4a-8242-3534ed8a3232
+
+test_expect_success 'load svn repo' "
+       svnadmin load -q '$rawsvnrepo' < '$TEST_DIRECTORY/t9153/svn.dump' &&
+       git svn init --minimize-url --rewrite-uuid='$uuid' '$svnrepo' &&
+       git svn fetch
+       "
+
+test_expect_success 'verify uuid' "
+       git cat-file commit refs/remotes/git-svn~0 | \
+          grep '^${git_svn_id}: .*@2 $uuid$' &&
+       git cat-file commit refs/remotes/git-svn~1 | \
+          grep '^${git_svn_id}: .*@1 $uuid$'
+       "
+
+test_done
diff --git a/t/t9153/svn.dump b/t/t9153/svn.dump
new file mode 100644 (file)
index 0000000..0ddfe70
--- /dev/null
@@ -0,0 +1,75 @@
+SVN-fs-dump-format-version: 2
+
+UUID: b4885626-c94f-4a6c-b179-00c030fc68e8
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2010-01-23T06:41:03.908576Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 109
+Content-length: 109
+
+K 7
+svn:log
+V 11
+initial foo
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T06:41:48.353776Z
+PROPS-END
+
+Node-path: foo
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: d3b07384d113edec49eaa6238ad5ff00
+Text-content-sha1: f1d2d2f924e986ac86fdf7b36c94bcdf32beec15
+Content-length: 14
+
+PROPS-END
+foo
+
+
+Revision-number: 2
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 12
+now with bar
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T06:42:14.214640Z
+PROPS-END
+
+Node-path: foo
+Node-kind: file
+Node-action: change
+Text-content-length: 8
+Text-content-md5: f47c75614087a8dd938ba4acff252494
+Text-content-sha1: 4e48e2c9a3d2ca8a708cb0cc545700544efb5021
+Content-length: 8
+
+foo
+bar
+
+
diff --git a/t/t9154-git-svn-fancy-glob.sh b/t/t9154-git-svn-fancy-glob.sh
new file mode 100755 (executable)
index 0000000..a6a56a6
--- /dev/null
@@ -0,0 +1,42 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Jay Soffian
+#
+
+test_description='git svn fancy glob test'
+
+. ./lib-git-svn.sh
+
+test_expect_success 'load svn repo' "
+       svnadmin load -q '$rawsvnrepo' < '$TEST_DIRECTORY/t9154/svn.dump' &&
+       git svn init --minimize-url -T trunk '$svnrepo' &&
+       git svn fetch
+       "
+
+test_expect_success 'add red branch' "
+       git config svn-remote.svn.branches 'branches/{red}:refs/remotes/*' &&
+       git svn fetch &&
+       git rev-parse refs/remotes/red &&
+       test_must_fail git rev-parse refs/remotes/green &&
+       test_must_fail git rev-parse refs/remotes/blue
+       "
+
+test_expect_success 'add green branch' "
+       GIT_CONFIG=.git/svn/.metadata git config --unset svn-remote.svn.branches-maxRev &&
+       git config svn-remote.svn.branches 'branches/{red,green}:refs/remotes/*' &&
+       git svn fetch &&
+       git rev-parse refs/remotes/red &&
+       git rev-parse refs/remotes/green &&
+       test_must_fail git rev-parse refs/remotes/blue
+       "
+
+test_expect_success 'add all branches' "
+       GIT_CONFIG=.git/svn/.metadata git config --unset svn-remote.svn.branches-maxRev &&
+       git config svn-remote.svn.branches 'branches/*:refs/remotes/*' &&
+       git svn fetch &&
+       git rev-parse refs/remotes/red &&
+       git rev-parse refs/remotes/green &&
+       git rev-parse refs/remotes/blue
+       "
+
+test_done
diff --git a/t/t9154/svn.dump b/t/t9154/svn.dump
new file mode 100644 (file)
index 0000000..3dfabb6
--- /dev/null
@@ -0,0 +1,222 @@
+SVN-fs-dump-format-version: 2
+
+UUID: a18093a0-5f0b-44e0-8d88-8911ac7078db
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2010-01-23T07:40:25.660053Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 104
+Content-length: 104
+
+K 7
+svn:log
+V 7
+initial
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T07:41:33.636365Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/foo
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: d3b07384d113edec49eaa6238ad5ff00
+Text-content-sha1: f1d2d2f924e986ac86fdf7b36c94bcdf32beec15
+Content-length: 14
+
+PROPS-END
+foo
+
+
+Revision-number: 2
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 12
+add branches
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T07:42:37.290694Z
+PROPS-END
+
+Node-path: branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: branches/blue
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Node-path: branches/green
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Node-path: branches/red
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 3
+Prop-content-length: 108
+Content-length: 108
+
+K 7
+svn:log
+V 10
+red change
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T07:43:02.208918Z
+PROPS-END
+
+Node-path: branches/red/foo
+Node-kind: file
+Node-action: change
+Text-content-length: 8
+Text-content-md5: 64c3c8cf7d0233ab7627623a68888bd1
+Text-content-sha1: 95a0492027876adfd3891ec71ee37b79ee44d640
+Content-length: 8
+
+foo
+red
+
+
+Revision-number: 4
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 12
+green change
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T07:43:15.746586Z
+PROPS-END
+
+Node-path: branches/green/foo
+Node-kind: file
+Node-action: change
+Text-content-length: 10
+Text-content-md5: 0209b6450891abc033d5eaaa9d3a8023
+Text-content-sha1: 87fc3bef9faeec48c0cd61dfc9851db377fdccf7
+Content-length: 10
+
+foo
+green
+
+
+Revision-number: 5
+Prop-content-length: 109
+Content-length: 109
+
+K 7
+svn:log
+V 11
+blue change
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T07:43:29.364811Z
+PROPS-END
+
+Node-path: branches/blue/foo
+Node-kind: file
+Node-action: change
+Text-content-length: 9
+Text-content-md5: 9fbe4c13d0bae86386ae5209b2e6b275
+Text-content-sha1: cc4575083459a16f9aaef796c4a2456d64691ba0
+Content-length: 9
+
+foo
+blue
+
+
+Revision-number: 6
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 12
+trunk change
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T07:44:01.313130Z
+PROPS-END
+
+Node-path: trunk/foo
+Node-kind: file
+Node-action: change
+Text-content-length: 10
+Text-content-md5: 1c4db977d7a57c3bae582aab87948516
+Text-content-sha1: 469c08df449e702cf2a1fe746244a9ef3f837fad
+Content-length: 10
+
+foo
+trunk
+
+
diff --git a/t/t9155-git-svn-fetch-deleted-tag.sh b/t/t9155-git-svn-fetch-deleted-tag.sh
new file mode 100755 (executable)
index 0000000..a486a98
--- /dev/null
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+test_description='git svn fetch deleted tag'
+
+. ./lib-git-svn.sh
+
+test_expect_success 'setup svn repo' '
+       mkdir -p import/trunk/subdir &&
+       mkdir -p import/branches &&
+       mkdir -p import/tags &&
+       echo "base" >import/trunk/subdir/file &&
+       svn_cmd import -m "import for git svn" import "$svnrepo" &&
+       rm -rf import &&
+
+       svn_cmd mkdir -m "create mybranch directory" "$svnrepo/branches/mybranch" &&
+       svn_cmd cp -m "create branch mybranch" "$svnrepo/trunk" "$svnrepo/branches/mybranch/trunk" &&
+
+       svn_cmd co "$svnrepo/trunk" svn_project &&
+       (cd svn_project &&
+               echo "trunk change" >>subdir/file &&
+               svn_cmd ci -m "trunk change" subdir/file &&
+
+               svn_cmd switch "$svnrepo/branches/mybranch/trunk" &&
+               echo "branch change" >>subdir/file &&
+               svn_cmd ci -m "branch change" subdir/file
+       ) &&
+
+       svn_cmd cp -m "create mytag attempt 1" -r5 "$svnrepo/trunk/subdir" "$svnrepo/tags/mytag" &&
+       svn_cmd rm -m "delete mytag attempt 1" "$svnrepo/tags/mytag" &&
+       svn_cmd cp -m "create mytag attempt 2" -r5 "$svnrepo/branches/mybranch/trunk/subdir" "$svnrepo/tags/mytag"
+'
+
+test_expect_success 'fetch deleted tags from same revision with checksum error' '
+       git svn init --stdlayout "$svnrepo" git_project &&
+       cd git_project &&
+       git svn fetch &&
+
+       git diff --exit-code mybranch:trunk/subdir/file tags/mytag:file &&
+       git diff --exit-code master:subdir/file tags/mytag^:file
+'
+
+test_done
diff --git a/t/t9156-git-svn-fetch-deleted-tag-2.sh b/t/t9156-git-svn-fetch-deleted-tag-2.sh
new file mode 100755 (executable)
index 0000000..5ce7e2f
--- /dev/null
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+test_description='git svn fetch deleted tag 2'
+
+. ./lib-git-svn.sh
+
+test_expect_success 'setup svn repo' '
+       mkdir -p import/branches &&
+       mkdir -p import/tags &&
+       mkdir -p import/trunk/subdir1 &&
+       mkdir -p import/trunk/subdir2 &&
+       mkdir -p import/trunk/subdir3 &&
+       echo "file1" >import/trunk/subdir1/file &&
+       echo "file2" >import/trunk/subdir2/file &&
+       echo "file3" >import/trunk/subdir3/file &&
+       svn_cmd import -m "import for git svn" import "$svnrepo" &&
+       rm -rf import &&
+
+       svn_cmd co "$svnrepo/trunk" svn_project &&
+       (cd svn_project &&
+               echo "change1" >>subdir1/file &&
+               echo "change2" >>subdir2/file &&
+               echo "change3" >>subdir3/file &&
+               svn_cmd ci -m "change" .
+       ) &&
+
+       svn_cmd cp -m "create mytag 1" -r2 "$svnrepo/trunk/subdir1" "$svnrepo/tags/mytag" &&
+       svn_cmd rm -m "delete mytag 1" "$svnrepo/tags/mytag" &&
+       svn_cmd cp -m "create mytag 2" -r2 "$svnrepo/trunk/subdir2" "$svnrepo/tags/mytag" &&
+       svn_cmd rm -m "delete mytag 2" "$svnrepo/tags/mytag" &&
+       svn_cmd cp -m "create mytag 3" -r2 "$svnrepo/trunk/subdir3" "$svnrepo/tags/mytag"
+'
+
+test_expect_success 'fetch deleted tags from same revision with no checksum error' '
+       git svn init --stdlayout "$svnrepo" git_project &&
+       cd git_project &&
+       git svn fetch &&
+
+       git diff --exit-code master:subdir3/file tags/mytag:file &&
+       git diff --exit-code master:subdir2/file tags/mytag^:file &&
+       git diff --exit-code master:subdir1/file tags/mytag^^:file
+'
+
+test_done
index 56b7c06921d9ef3b72ff3ee6f62f7a1c426b3028..e5da65b99fa1459836e516772fe8d13d038f502e 100755 (executable)
@@ -5,16 +5,17 @@
 test_description='Test export of commits to CVS'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-prereq-FILEMODE.sh
 
 if ! test_have_prereq PERL; then
-       say 'skipping git cvsexportcommit tests, perl not available'
+       skip_all='skipping git cvsexportcommit tests, perl not available'
        test_done
 fi
 
 cvs >/dev/null 2>&1
 if test $? -ne 1
 then
-    say 'skipping git cvsexportcommit tests, cvs not found'
+    skip_all='skipping git cvsexportcommit tests, cvs not found'
     test_done
 fi
 
@@ -63,10 +64,10 @@ test_expect_success \
      check_entries B "newfile2.txt/1.1/" &&
      check_entries C "newfile3.png/1.1/-kb" &&
      check_entries D "newfile4.png/1.1/-kb" &&
-     diff A/newfile1.txt ../A/newfile1.txt &&
-     diff B/newfile2.txt ../B/newfile2.txt &&
-     diff C/newfile3.png ../C/newfile3.png &&
-     diff D/newfile4.png ../D/newfile4.png
+     test_cmp A/newfile1.txt ../A/newfile1.txt &&
+     test_cmp B/newfile2.txt ../B/newfile2.txt &&
+     test_cmp C/newfile3.png ../C/newfile3.png &&
+     test_cmp D/newfile4.png ../D/newfile4.png
      )'
 
 test_expect_success \
@@ -89,10 +90,10 @@ test_expect_success \
      check_entries D "newfile4.png/1.2/-kb" &&
      check_entries E "newfile5.txt/1.1/" &&
      check_entries F "newfile6.png/1.1/-kb" &&
-     diff A/newfile1.txt ../A/newfile1.txt &&
-     diff D/newfile4.png ../D/newfile4.png &&
-     diff E/newfile5.txt ../E/newfile5.txt &&
-     diff F/newfile6.png ../F/newfile6.png
+     test_cmp A/newfile1.txt ../A/newfile1.txt &&
+     test_cmp D/newfile4.png ../D/newfile4.png &&
+     test_cmp E/newfile5.txt ../E/newfile5.txt &&
+     test_cmp F/newfile6.png ../F/newfile6.png
      )'
 
 # Should fail (but only on the git cvsexportcommit stage)
@@ -137,9 +138,9 @@ test_expect_success \
      check_entries D "" &&
      check_entries E "newfile5.txt/1.1/" &&
      check_entries F "newfile6.png/1.1/-kb" &&
-     diff A/newfile1.txt ../A/newfile1.txt &&
-     diff E/newfile5.txt ../E/newfile5.txt &&
-     diff F/newfile6.png ../F/newfile6.png
+     test_cmp A/newfile1.txt ../A/newfile1.txt &&
+     test_cmp E/newfile5.txt ../E/newfile5.txt &&
+     test_cmp F/newfile6.png ../F/newfile6.png
      )'
 
 test_expect_success \
@@ -155,8 +156,8 @@ test_expect_success \
      check_entries D "" &&
      check_entries E "newfile5.txt/1.1/" &&
      check_entries F "newfile6.png/1.1/-kb" &&
-     diff E/newfile5.txt ../E/newfile5.txt &&
-     diff F/newfile6.png ../F/newfile6.png
+     test_cmp E/newfile5.txt ../E/newfile5.txt &&
+     test_cmp F/newfile6.png ../F/newfile6.png
      )'
 
 test_expect_success \
@@ -229,11 +230,6 @@ test_expect_success \
       test_must_fail git cvsexportcommit -c $id
       )'
 
-if ! test "$(git config --bool core.filemode)" = false
-then
-       test_set_prereq FILEMODE
-fi
-
 test_expect_success FILEMODE \
      'Retain execute bit' \
      'mkdir G &&
@@ -288,6 +284,27 @@ test_expect_success 'check files before directories' '
 
 '
 
+test_expect_success 're-commit a removed filename which remains in CVS attic' '
+
+    (cd "$CVSWORK" &&
+     echo >attic_gremlin &&
+     cvs -Q add attic_gremlin &&
+     cvs -Q ci -m "added attic_gremlin" &&
+     rm attic_gremlin &&
+     cvs -Q rm attic_gremlin &&
+     cvs -Q ci -m "removed attic_gremlin") &&
+
+    echo > attic_gremlin &&
+    git add attic_gremlin &&
+    git commit -m "Added attic_gremlin" &&
+       git cvsexportcommit -w "$CVSWORK" -c HEAD &&
+    (cd "$CVSWORK"; cvs -Q update -d) &&
+    test -f "$CVSWORK/attic_gremlin"
+'
+
+# the state of the CVS sandbox may be indeterminate for ' space'
+# after this test on some platforms / with some versions of CVS
+# consider adding new tests above this point
 test_expect_success 'commit a file with leading spaces in the name' '
 
        echo space > " space" &&
@@ -295,7 +312,7 @@ test_expect_success 'commit a file with leading spaces in the name' '
        git commit -m "Add a file with a leading space" &&
        id=$(git rev-parse HEAD) &&
        git cvsexportcommit -w "$CVSWORK" -c $id &&
-       check_entries "$CVSWORK" " space/1.1/|DS/1.1/|release-notes/1.2/" &&
+       check_entries "$CVSWORK" " space/1.1/|DS/1.1/|attic_gremlin/1.3/|release-notes/1.2/" &&
        test_cmp "$CVSWORK/ space" " space"
 
 '
index 821be7ce8d92f8ead1bcaa946260e8d715784612..7c059204e90722db20bae1d10a0988853f852262 100755 (executable)
@@ -166,6 +166,63 @@ test_expect_success \
         test `git rev-parse --verify master:file2` \
            = `git rev-parse --verify verify--import-marks:copy-of-file2`'
 
+test_tick
+mt=$(git hash-object --stdin < /dev/null)
+: >input.blob
+: >marks.exp
+: >tree.exp
+
+cat >input.commit <<EOF
+commit refs/heads/verify--dump-marks
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+test the sparse array dumping routines with exponentially growing marks
+COMMIT
+EOF
+
+i=0
+l=4
+m=6
+n=7
+while test "$i" -lt 27; do
+    cat >>input.blob <<EOF
+blob
+mark :$l
+data 0
+blob
+mark :$m
+data 0
+blob
+mark :$n
+data 0
+EOF
+    echo "M 100644 :$l l$i" >>input.commit
+    echo "M 100644 :$m m$i" >>input.commit
+    echo "M 100644 :$n n$i" >>input.commit
+
+    echo ":$l $mt" >>marks.exp
+    echo ":$m $mt" >>marks.exp
+    echo ":$n $mt" >>marks.exp
+
+    printf "100644 blob $mt\tl$i\n" >>tree.exp
+    printf "100644 blob $mt\tm$i\n" >>tree.exp
+    printf "100644 blob $mt\tn$i\n" >>tree.exp
+
+    l=$(($l + $l))
+    m=$(($m + $m))
+    n=$(($l + $n))
+
+    i=$((1 + $i))
+done
+
+sort tree.exp > tree.exp_s
+
+test_expect_success 'A: export marks with large values' '
+       cat input.blob input.commit | git fast-import --export-marks=marks.large &&
+       git ls-tree refs/heads/verify--dump-marks >tree.out &&
+       test_cmp tree.exp_s tree.out &&
+       test_cmp marks.exp marks.large'
+
 ###
 ### series B
 ###
@@ -796,6 +853,60 @@ test_expect_success \
        'git fast-import <input &&
         test `git rev-parse N2^{tree}` = `git rev-parse N3^{tree}`'
 
+test_expect_success \
+       'N: copy directory by id' \
+       'cat >expect <<-\EOF &&
+       :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100   file2/newf      file3/newf
+       :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100   file2/oldf      file3/oldf
+       EOF
+        subdir=$(git rev-parse refs/heads/branch^0:file2) &&
+        cat >input <<-INPUT_END &&
+       commit refs/heads/N4
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<COMMIT
+       copy by tree hash
+       COMMIT
+
+       from refs/heads/branch^0
+       M 040000 $subdir file3
+       INPUT_END
+        git fast-import <input &&
+        git diff-tree -C --find-copies-harder -r N4^ N4 >actual &&
+        compare_diff_raw expect actual'
+
+test_expect_success \
+       'N: modify copied tree' \
+       'cat >expect <<-\EOF &&
+       :100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 C100   newdir/interesting      file3/file5
+       :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100   file2/newf      file3/newf
+       :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100   file2/oldf      file3/oldf
+       EOF
+        subdir=$(git rev-parse refs/heads/branch^0:file2) &&
+        cat >input <<-INPUT_END &&
+       commit refs/heads/N5
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<COMMIT
+       copy by tree hash
+       COMMIT
+
+       from refs/heads/branch^0
+       M 040000 $subdir file3
+
+       commit refs/heads/N5
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<COMMIT
+       modify directory copy
+       COMMIT
+
+       M 644 inline file3/file5
+       data <<EOF
+       $file5_data
+       EOF
+       INPUT_END
+        git fast-import <input &&
+        git diff-tree -C --find-copies-harder -r N5^^ N5 >actual &&
+        compare_diff_raw expect actual'
+
 ###
 ### series O
 ###
@@ -999,11 +1110,10 @@ test_expect_success \
        'P: supermodule & submodule mix' \
        'git fast-import <input &&
         git checkout subuse1 &&
-        rm -rf sub && mkdir sub && cd sub &&
+        rm -rf sub && mkdir sub && (cd sub &&
         git init &&
         git fetch --update-head-ok .. refs/heads/sub:refs/heads/master &&
-        git checkout master &&
-        cd .. &&
+        git checkout master) &&
         git submodule init &&
         git submodule update'
 
@@ -1088,4 +1198,498 @@ INPUT_END
 test_expect_success 'P: fail on blob mark in gitlink' '
     test_must_fail git fast-import <input'
 
+###
+### series Q (notes)
+###
+
+note1_data="The first note for the first commit"
+note2_data="The first note for the second commit"
+note3_data="The first note for the third commit"
+note1b_data="The second note for the first commit"
+note1c_data="The third note for the first commit"
+note2b_data="The second note for the second commit"
+
+test_tick
+cat >input <<INPUT_END
+blob
+mark :2
+data <<EOF
+$file2_data
+EOF
+
+commit refs/heads/notes-test
+mark :3
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+first (:3)
+COMMIT
+
+M 644 :2 file2
+
+blob
+mark :4
+data $file4_len
+$file4_data
+commit refs/heads/notes-test
+mark :5
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+second (:5)
+COMMIT
+
+M 644 :4 file4
+
+commit refs/heads/notes-test
+mark :6
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+third (:6)
+COMMIT
+
+M 644 inline file5
+data <<EOF
+$file5_data
+EOF
+
+M 755 inline file6
+data <<EOF
+$file6_data
+EOF
+
+blob
+mark :7
+data <<EOF
+$note1_data
+EOF
+
+blob
+mark :8
+data <<EOF
+$note2_data
+EOF
+
+commit refs/notes/foobar
+mark :9
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes (:9)
+COMMIT
+
+N :7 :3
+N :8 :5
+N inline :6
+data <<EOF
+$note3_data
+EOF
+
+commit refs/notes/foobar
+mark :10
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes (:10)
+COMMIT
+
+N inline :3
+data <<EOF
+$note1b_data
+EOF
+
+commit refs/notes/foobar2
+mark :11
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes (:11)
+COMMIT
+
+N inline :3
+data <<EOF
+$note1c_data
+EOF
+
+commit refs/notes/foobar
+mark :12
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes (:12)
+COMMIT
+
+deleteall
+N inline :5
+data <<EOF
+$note2b_data
+EOF
+
+INPUT_END
+
+test_expect_success \
+       'Q: commit notes' \
+       'git fast-import <input &&
+        git whatchanged notes-test'
+test_expect_success \
+       'Q: verify pack' \
+       'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
+
+commit1=$(git rev-parse notes-test~2)
+commit2=$(git rev-parse notes-test^)
+commit3=$(git rev-parse notes-test)
+
+cat >expect <<EOF
+author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+
+first (:3)
+EOF
+test_expect_success \
+       'Q: verify first commit' \
+       'git cat-file commit notes-test~2 | sed 1d >actual &&
+       test_cmp expect actual'
+
+cat >expect <<EOF
+parent $commit1
+author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+
+second (:5)
+EOF
+test_expect_success \
+       'Q: verify second commit' \
+       'git cat-file commit notes-test^ | sed 1d >actual &&
+       test_cmp expect actual'
+
+cat >expect <<EOF
+parent $commit2
+author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+
+third (:6)
+EOF
+test_expect_success \
+       'Q: verify third commit' \
+       'git cat-file commit notes-test | sed 1d >actual &&
+       test_cmp expect actual'
+
+cat >expect <<EOF
+author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+
+notes (:9)
+EOF
+test_expect_success \
+       'Q: verify first notes commit' \
+       'git cat-file commit refs/notes/foobar~2 | sed 1d >actual &&
+       test_cmp expect actual'
+
+cat >expect.unsorted <<EOF
+100644 blob $commit1
+100644 blob $commit2
+100644 blob $commit3
+EOF
+cat expect.unsorted | sort >expect
+test_expect_success \
+       'Q: verify first notes tree' \
+       'git cat-file -p refs/notes/foobar~2^{tree} | sed "s/ [0-9a-f]* / /" >actual &&
+        test_cmp expect actual'
+
+echo "$note1_data" >expect
+test_expect_success \
+       'Q: verify first note for first commit' \
+       'git cat-file blob refs/notes/foobar~2:$commit1 >actual && test_cmp expect actual'
+
+echo "$note2_data" >expect
+test_expect_success \
+       'Q: verify first note for second commit' \
+       'git cat-file blob refs/notes/foobar~2:$commit2 >actual && test_cmp expect actual'
+
+echo "$note3_data" >expect
+test_expect_success \
+       'Q: verify first note for third commit' \
+       'git cat-file blob refs/notes/foobar~2:$commit3 >actual && test_cmp expect actual'
+
+cat >expect <<EOF
+parent `git rev-parse --verify refs/notes/foobar~2`
+author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+
+notes (:10)
+EOF
+test_expect_success \
+       'Q: verify second notes commit' \
+       'git cat-file commit refs/notes/foobar^ | sed 1d >actual &&
+       test_cmp expect actual'
+
+cat >expect.unsorted <<EOF
+100644 blob $commit1
+100644 blob $commit2
+100644 blob $commit3
+EOF
+cat expect.unsorted | sort >expect
+test_expect_success \
+       'Q: verify second notes tree' \
+       'git cat-file -p refs/notes/foobar^^{tree} | sed "s/ [0-9a-f]*  / /" >actual &&
+        test_cmp expect actual'
+
+echo "$note1b_data" >expect
+test_expect_success \
+       'Q: verify second note for first commit' \
+       'git cat-file blob refs/notes/foobar^:$commit1 >actual && test_cmp expect actual'
+
+echo "$note2_data" >expect
+test_expect_success \
+       'Q: verify first note for second commit' \
+       'git cat-file blob refs/notes/foobar^:$commit2 >actual && test_cmp expect actual'
+
+echo "$note3_data" >expect
+test_expect_success \
+       'Q: verify first note for third commit' \
+       'git cat-file blob refs/notes/foobar^:$commit3 >actual && test_cmp expect actual'
+
+cat >expect <<EOF
+author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+
+notes (:11)
+EOF
+test_expect_success \
+       'Q: verify third notes commit' \
+       'git cat-file commit refs/notes/foobar2 | sed 1d >actual &&
+       test_cmp expect actual'
+
+cat >expect.unsorted <<EOF
+100644 blob $commit1
+EOF
+cat expect.unsorted | sort >expect
+test_expect_success \
+       'Q: verify third notes tree' \
+       'git cat-file -p refs/notes/foobar2^{tree} | sed "s/ [0-9a-f]*  / /" >actual &&
+        test_cmp expect actual'
+
+echo "$note1c_data" >expect
+test_expect_success \
+       'Q: verify third note for first commit' \
+       'git cat-file blob refs/notes/foobar2:$commit1 >actual && test_cmp expect actual'
+
+cat >expect <<EOF
+parent `git rev-parse --verify refs/notes/foobar^`
+author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+
+notes (:12)
+EOF
+test_expect_success \
+       'Q: verify fourth notes commit' \
+       'git cat-file commit refs/notes/foobar | sed 1d >actual &&
+       test_cmp expect actual'
+
+cat >expect.unsorted <<EOF
+100644 blob $commit2
+EOF
+cat expect.unsorted | sort >expect
+test_expect_success \
+       'Q: verify fourth notes tree' \
+       'git cat-file -p refs/notes/foobar^{tree} | sed "s/ [0-9a-f]*   / /" >actual &&
+        test_cmp expect actual'
+
+echo "$note2b_data" >expect
+test_expect_success \
+       'Q: verify second note for second commit' \
+       'git cat-file blob refs/notes/foobar:$commit2 >actual && test_cmp expect actual'
+
+###
+### series R (feature and option)
+###
+
+cat >input <<EOF
+feature no-such-feature-exists
+EOF
+
+test_expect_success 'R: abort on unsupported feature' '
+       test_must_fail git fast-import <input
+'
+
+cat >input <<EOF
+feature date-format=now
+EOF
+
+test_expect_success 'R: supported feature is accepted' '
+       git fast-import <input
+'
+
+cat >input << EOF
+blob
+data 3
+hi
+feature date-format=now
+EOF
+
+test_expect_success 'R: abort on receiving feature after data command' '
+       test_must_fail git fast-import <input
+'
+
+cat >input << EOF
+feature import-marks=git.marks
+feature import-marks=git2.marks
+EOF
+
+test_expect_success 'R: only one import-marks feature allowed per stream' '
+       test_must_fail git fast-import <input
+'
+
+cat >input << EOF
+feature export-marks=git.marks
+blob
+mark :1
+data 3
+hi
+
+EOF
+
+test_expect_success \
+    'R: export-marks feature results in a marks file being created' \
+    'cat input | git fast-import &&
+    grep :1 git.marks'
+
+test_expect_success \
+    'R: export-marks options can be overriden by commandline options' \
+    'cat input | git fast-import --export-marks=other.marks &&
+    grep :1 other.marks'
+
+cat >input << EOF
+feature import-marks=marks.out
+feature export-marks=marks.new
+EOF
+
+test_expect_success \
+    'R: import to output marks works without any content' \
+    'cat input | git fast-import &&
+    test_cmp marks.out marks.new'
+
+cat >input <<EOF
+feature import-marks=nonexistant.marks
+feature export-marks=marks.new
+EOF
+
+test_expect_success \
+    'R: import marks prefers commandline marks file over the stream' \
+    'cat input | git fast-import --import-marks=marks.out &&
+    test_cmp marks.out marks.new'
+
+
+cat >input <<EOF
+feature import-marks=nonexistant.marks
+feature export-marks=combined.marks
+EOF
+
+test_expect_success 'R: multiple --import-marks= should be honoured' '
+    head -n2 marks.out > one.marks &&
+    tail -n +3 marks.out > two.marks &&
+    git fast-import --import-marks=one.marks --import-marks=two.marks <input &&
+    test_cmp marks.out combined.marks
+'
+
+cat >input <<EOF
+feature relative-marks
+feature import-marks=relative.in
+feature export-marks=relative.out
+EOF
+
+test_expect_success 'R: feature relative-marks should be honoured' '
+    mkdir -p .git/info/fast-import/ &&
+    cp marks.new .git/info/fast-import/relative.in &&
+    git fast-import <input &&
+    test_cmp marks.new .git/info/fast-import/relative.out
+'
+
+cat >input <<EOF
+feature relative-marks
+feature import-marks=relative.in
+feature no-relative-marks
+feature export-marks=non-relative.out
+EOF
+
+test_expect_success 'R: feature no-relative-marks should be honoured' '
+    git fast-import <input &&
+    test_cmp marks.new non-relative.out
+'
+
+cat >input << EOF
+option git quiet
+blob
+data 3
+hi
+
+EOF
+
+touch empty
+
+test_expect_success 'R: quiet option results in no stats being output' '
+    cat input | git fast-import 2> output &&
+    test_cmp empty output
+'
+
+cat >input <<EOF
+option git non-existing-option
+EOF
+
+test_expect_success 'R: die on unknown option' '
+    test_must_fail git fast-import <input
+'
+
+test_expect_success 'R: unknown commandline options are rejected' '\
+    test_must_fail git fast-import --non-existing-option < /dev/null
+'
+
+cat >input <<EOF
+option non-existing-vcs non-existing-option
+EOF
+
+test_expect_success 'R: ignore non-git options' '
+    git fast-import <input
+'
+
+##
+## R: very large blobs
+##
+blobsize=$((2*1024*1024 + 53))
+test-genrandom bar $blobsize >expect
+cat >input <<INPUT_END
+commit refs/heads/big-file
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+R - big file
+COMMIT
+
+M 644 inline big1
+data $blobsize
+INPUT_END
+cat expect >>input
+cat >>input <<INPUT_END
+M 644 inline big2
+data $blobsize
+INPUT_END
+cat expect >>input
+echo >>input
+
+test_expect_success \
+       'R: blob bigger than threshold' \
+       'test_create_repo R &&
+        git --git-dir=R/.git fast-import --big-file-threshold=1 <input'
+test_expect_success \
+       'R: verify created pack' \
+       ': >verify &&
+        for p in R/.git/objects/pack/*.pack;
+        do
+          git verify-pack -v $p >>verify || exit;
+        done'
+test_expect_success \
+       'R: verify written objects' \
+       'git --git-dir=R/.git cat-file blob big-file:big1 >actual &&
+        test_cmp expect actual &&
+        a=$(git --git-dir=R/.git rev-parse big-file:big1) &&
+        b=$(git --git-dir=R/.git rev-parse big-file:big2) &&
+        test $a = $b'
+test_expect_success \
+       'R: blob appears only once' \
+       'n=$(grep $a verify | wc -l) &&
+        test 1 = $n'
+
 test_done
diff --git a/t/t9301-fast-export.sh b/t/t9301-fast-export.sh
deleted file mode 100755 (executable)
index 8da9ce5..0000000
+++ /dev/null
@@ -1,280 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2007 Johannes E. Schindelin
-#
-
-test_description='git fast-export'
-. ./test-lib.sh
-
-test_expect_success 'setup' '
-
-       echo break it > file0 &&
-       git add file0 &&
-       test_tick &&
-       echo Wohlauf > file &&
-       git add file &&
-       test_tick &&
-       git commit -m initial &&
-       echo die Luft > file &&
-       echo geht frisch > file2 &&
-       git add file file2 &&
-       test_tick &&
-       git commit -m second &&
-       echo und > file2 &&
-       test_tick &&
-       git commit -m third file2 &&
-       test_tick &&
-       git tag rein &&
-       git checkout -b wer HEAD^ &&
-       echo lange > file2
-       test_tick &&
-       git commit -m sitzt file2 &&
-       test_tick &&
-       git tag -a -m valentin muss &&
-       git merge -s ours master
-
-'
-
-test_expect_success 'fast-export | fast-import' '
-
-       MASTER=$(git rev-parse --verify master) &&
-       REIN=$(git rev-parse --verify rein) &&
-       WER=$(git rev-parse --verify wer) &&
-       MUSS=$(git rev-parse --verify muss) &&
-       mkdir new &&
-       git --git-dir=new/.git init &&
-       git fast-export --all |
-       (cd new &&
-        git fast-import &&
-        test $MASTER = $(git rev-parse --verify refs/heads/master) &&
-        test $REIN = $(git rev-parse --verify refs/tags/rein) &&
-        test $WER = $(git rev-parse --verify refs/heads/wer) &&
-        test $MUSS = $(git rev-parse --verify refs/tags/muss))
-
-'
-
-test_expect_success 'fast-export master~2..master' '
-
-       git fast-export master~2..master |
-               sed "s/master/partial/" |
-               (cd new &&
-                git fast-import &&
-                test $MASTER != $(git rev-parse --verify refs/heads/partial) &&
-                git diff --exit-code master partial &&
-                git diff --exit-code master^ partial^ &&
-                test_must_fail git rev-parse partial~2)
-
-'
-
-test_expect_success 'iso-8859-1' '
-
-       git config i18n.commitencoding ISO-8859-1 &&
-       # use author and committer name in ISO-8859-1 to match it.
-       . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
-       test_tick &&
-       echo rosten >file &&
-       git commit -s -m den file &&
-       git fast-export wer^..wer |
-               sed "s/wer/i18n/" |
-               (cd new &&
-                git fast-import &&
-                git cat-file commit i18n | grep "Áéí óú")
-
-'
-test_expect_success 'import/export-marks' '
-
-       git checkout -b marks master &&
-       git fast-export --export-marks=tmp-marks HEAD &&
-       test -s tmp-marks &&
-       test $(wc -l < tmp-marks) -eq 3 &&
-       test $(
-               git fast-export --import-marks=tmp-marks\
-               --export-marks=tmp-marks HEAD |
-               grep ^commit |
-               wc -l) \
-       -eq 0 &&
-       echo change > file &&
-       git commit -m "last commit" file &&
-       test $(
-               git fast-export --import-marks=tmp-marks \
-               --export-marks=tmp-marks HEAD |
-               grep ^commit\  |
-               wc -l) \
-       -eq 1 &&
-       test $(wc -l < tmp-marks) -eq 4
-
-'
-
-cat > signed-tag-import << EOF
-tag sign-your-name
-from $(git rev-parse HEAD)
-tagger C O Mitter <committer@example.com> 1112911993 -0700
-data 210
-A message for a sign
------BEGIN PGP SIGNATURE-----
-Version: GnuPG v1.4.5 (GNU/Linux)
-
-fakedsignaturefakedsignaturefakedsignaturefakedsignaturfakedsign
-aturefakedsignaturefake=
-=/59v
------END PGP SIGNATURE-----
-EOF
-
-test_expect_success 'set up faked signed tag' '
-
-       cat signed-tag-import | git fast-import
-
-'
-
-test_expect_success 'signed-tags=abort' '
-
-       test_must_fail git fast-export --signed-tags=abort sign-your-name
-
-'
-
-test_expect_success 'signed-tags=verbatim' '
-
-       git fast-export --signed-tags=verbatim sign-your-name > output &&
-       grep PGP output
-
-'
-
-test_expect_success 'signed-tags=strip' '
-
-       git fast-export --signed-tags=strip sign-your-name > output &&
-       ! grep PGP output
-
-'
-
-test_expect_success 'setup submodule' '
-
-       git checkout -f master &&
-       mkdir sub &&
-       cd sub &&
-       git init  &&
-       echo test file > file &&
-       git add file &&
-       git commit -m sub_initial &&
-       cd .. &&
-       git submodule add "`pwd`/sub" sub &&
-       git commit -m initial &&
-       test_tick &&
-       cd sub &&
-       echo more data >> file &&
-       git add file &&
-       git commit -m sub_second &&
-       cd .. &&
-       git add sub &&
-       git commit -m second
-
-'
-
-test_expect_success 'submodule fast-export | fast-import' '
-
-       SUBENT1=$(git ls-tree master^ sub) &&
-       SUBENT2=$(git ls-tree master sub) &&
-       rm -rf new &&
-       mkdir new &&
-       git --git-dir=new/.git init &&
-       git fast-export --signed-tags=strip --all |
-       (cd new &&
-        git fast-import &&
-        test "$SUBENT1" = "$(git ls-tree refs/heads/master^ sub)" &&
-        test "$SUBENT2" = "$(git ls-tree refs/heads/master sub)" &&
-        git checkout master &&
-        git submodule init &&
-        git submodule update &&
-        cmp sub/file ../sub/file)
-
-'
-
-GIT_AUTHOR_NAME='A U Thor'; export GIT_AUTHOR_NAME
-GIT_COMMITTER_NAME='C O Mitter'; export GIT_COMMITTER_NAME
-
-test_expect_success 'setup copies' '
-
-       git config --unset i18n.commitencoding &&
-       git checkout -b copy rein &&
-       git mv file file3 &&
-       git commit -m move1 &&
-       test_tick &&
-       cp file2 file4 &&
-       git add file4 &&
-       git mv file2 file5 &&
-       git commit -m copy1 &&
-       test_tick &&
-       cp file3 file6 &&
-       git add file6 &&
-       git commit -m copy2 &&
-       test_tick &&
-       echo more text >> file6 &&
-       echo even more text >> file6 &&
-       git add file6 &&
-       git commit -m modify &&
-       test_tick &&
-       cp file6 file7 &&
-       echo test >> file7 &&
-       git add file7 &&
-       git commit -m copy_modify
-
-'
-
-test_expect_success 'fast-export -C -C | fast-import' '
-
-       ENTRY=$(git rev-parse --verify copy) &&
-       rm -rf new &&
-       mkdir new &&
-       git --git-dir=new/.git init &&
-       git fast-export -C -C --signed-tags=strip --all > output &&
-       grep "^C \"file6\" \"file7\"\$" output &&
-       cat output |
-       (cd new &&
-        git fast-import &&
-        test $ENTRY = $(git rev-parse --verify refs/heads/copy))
-
-'
-
-test_expect_success 'fast-export | fast-import when master is tagged' '
-
-       git tag -m msg last &&
-       git fast-export -C -C --signed-tags=strip --all > output &&
-       test $(grep -c "^tag " output) = 3
-
-'
-
-cat > tag-content << EOF
-object $(git rev-parse HEAD)
-type commit
-tag rosten
-EOF
-
-test_expect_success 'cope with tagger-less tags' '
-
-       TAG=$(git hash-object -t tag -w tag-content) &&
-       git update-ref refs/tags/sonnenschein $TAG &&
-       git fast-export -C -C --signed-tags=strip --all > output &&
-       test $(grep -c "^tag " output) = 4 &&
-       ! grep "Unspecified Tagger" output &&
-       git fast-export -C -C --signed-tags=strip --all \
-               --fake-missing-tagger > output &&
-       test $(grep -c "^tag " output) = 4 &&
-       grep "Unspecified Tagger" output
-
-'
-
-test_expect_success 'set-up a few more tags for tag export tests' '
-       git checkout -f master &&
-       HEAD_TREE=`git show -s --pretty=raw HEAD | grep tree | sed "s/tree //"` &&
-       git tag    tree_tag        -m "tagging a tree" $HEAD_TREE &&
-       git tag -a tree_tag-obj    -m "tagging a tree" $HEAD_TREE &&
-       git tag    tag-obj_tag     -m "tagging a tag" tree_tag-obj &&
-       git tag -a tag-obj_tag-obj -m "tagging a tag" tree_tag-obj
-'
-
-# NEEDSWORK: not just check return status, but validate the output
-test_expect_success 'tree_tag'        'git fast-export tree_tag'
-test_expect_success 'tree_tag-obj'    'git fast-export tree_tag-obj'
-test_expect_success 'tag-obj_tag'     'git fast-export tag-obj_tag'
-test_expect_success 'tag-obj_tag-obj' 'git fast-export tag-obj_tag-obj'
-
-test_done
diff --git a/t/t9301-fast-import-notes.sh b/t/t9301-fast-import-notes.sh
new file mode 100755 (executable)
index 0000000..a5c99d8
--- /dev/null
@@ -0,0 +1,623 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Johan Herland
+#
+
+test_description='test git fast-import of notes objects'
+. ./test-lib.sh
+
+
+test_tick
+cat >input <<INPUT_END
+commit refs/heads/master
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+first commit
+COMMIT
+
+M 644 inline foo
+data <<EOF
+file foo in first commit
+EOF
+
+M 755 inline bar
+data <<EOF
+file bar in first commit
+EOF
+
+M 644 inline baz/xyzzy
+data <<EOF
+file baz/xyzzy in first commit
+EOF
+
+commit refs/heads/master
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+second commit
+COMMIT
+
+M 644 inline foo
+data <<EOF
+file foo in second commit
+EOF
+
+M 755 inline baz/xyzzy
+data <<EOF
+file baz/xyzzy in second commit
+EOF
+
+commit refs/heads/master
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+third commit
+COMMIT
+
+M 644 inline foo
+data <<EOF
+file foo in third commit
+EOF
+
+commit refs/heads/master
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+fourth commit
+COMMIT
+
+M 755 inline bar
+data <<EOF
+file bar in fourth commit
+EOF
+
+INPUT_END
+
+test_expect_success 'set up master branch' '
+
+       git fast-import <input &&
+       git whatchanged master
+'
+
+commit4=$(git rev-parse refs/heads/master)
+commit3=$(git rev-parse "$commit4^")
+commit2=$(git rev-parse "$commit4~2")
+commit1=$(git rev-parse "$commit4~3")
+
+test_tick
+cat >input <<INPUT_END
+commit refs/notes/test
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+first notes commit
+COMMIT
+
+M 644 inline $commit1
+data <<EOF
+first note for first commit
+EOF
+
+M 755 inline $commit2
+data <<EOF
+first note for second commit
+EOF
+
+INPUT_END
+
+cat >expect <<EXPECT_END
+    fourth commit
+    third commit
+    second commit
+    first note for second commit
+    first commit
+    first note for first commit
+EXPECT_END
+
+test_expect_success 'add notes with simple M command' '
+
+       git fast-import <input &&
+       GIT_NOTES_REF=refs/notes/test git log | grep "^    " > actual &&
+       test_cmp expect actual
+
+'
+
+test_tick
+cat >input <<INPUT_END
+commit refs/notes/test
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+second notes commit
+COMMIT
+
+from refs/notes/test^0
+N inline $commit3
+data <<EOF
+first note for third commit
+EOF
+
+N inline $commit4
+data <<EOF
+first note for fourth commit
+EOF
+
+INPUT_END
+
+cat >expect <<EXPECT_END
+    fourth commit
+    first note for fourth commit
+    third commit
+    first note for third commit
+    second commit
+    first note for second commit
+    first commit
+    first note for first commit
+EXPECT_END
+
+test_expect_success 'add notes with simple N command' '
+
+       git fast-import <input &&
+       GIT_NOTES_REF=refs/notes/test git log | grep "^    " > actual &&
+       test_cmp expect actual
+
+'
+
+test_tick
+cat >input <<INPUT_END
+commit refs/notes/test
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+third notes commit
+COMMIT
+
+from refs/notes/test^0
+N inline $commit1
+data <<EOF
+second note for first commit
+EOF
+
+N inline $commit2
+data <<EOF
+second note for second commit
+EOF
+
+N inline $commit3
+data <<EOF
+second note for third commit
+EOF
+
+N inline $commit4
+data <<EOF
+second note for fourth commit
+EOF
+
+INPUT_END
+
+cat >expect <<EXPECT_END
+    fourth commit
+    second note for fourth commit
+    third commit
+    second note for third commit
+    second commit
+    second note for second commit
+    first commit
+    second note for first commit
+EXPECT_END
+
+test_expect_success 'update existing notes with N command' '
+
+       git fast-import <input &&
+       GIT_NOTES_REF=refs/notes/test git log | grep "^    " > actual &&
+       test_cmp expect actual
+
+'
+
+test_tick
+cat >input <<INPUT_END
+commit refs/notes/test
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+fourth notes commit
+COMMIT
+
+from refs/notes/test^0
+M 644 inline $(echo "$commit3" | sed "s|^..|&/|")
+data <<EOF
+prefix of note for third commit
+EOF
+
+M 644 inline $(echo "$commit4" | sed "s|^..|&/|")
+data <<EOF
+prefix of note for fourth commit
+EOF
+
+M 644 inline $(echo "$commit4" | sed "s|^\(..\)\(..\)|\1/\2/|")
+data <<EOF
+pre-prefix of note for fourth commit
+EOF
+
+N inline $commit1
+data <<EOF
+third note for first commit
+EOF
+
+N inline $commit2
+data <<EOF
+third note for second commit
+EOF
+
+N inline $commit3
+data <<EOF
+third note for third commit
+EOF
+
+N inline $commit4
+data <<EOF
+third note for fourth commit
+EOF
+
+
+INPUT_END
+
+cat >expect <<EXPECT_END
+    fourth commit
+    pre-prefix of note for fourth commit
+    prefix of note for fourth commit
+    third note for fourth commit
+    third commit
+    prefix of note for third commit
+    third note for third commit
+    second commit
+    third note for second commit
+    first commit
+    third note for first commit
+EXPECT_END
+
+test_expect_success 'add concatentation notes with M command' '
+
+       git fast-import <input &&
+       GIT_NOTES_REF=refs/notes/test git log | grep "^    " > actual &&
+       test_cmp expect actual
+
+'
+
+test_tick
+cat >input <<INPUT_END
+commit refs/notes/test
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+fifth notes commit
+COMMIT
+
+from refs/notes/test^0
+deleteall
+
+INPUT_END
+
+cat >expect <<EXPECT_END
+    fourth commit
+    third commit
+    second commit
+    first commit
+EXPECT_END
+
+test_expect_success 'verify that deleteall also removes notes' '
+
+       git fast-import <input &&
+       GIT_NOTES_REF=refs/notes/test git log | grep "^    " > actual &&
+       test_cmp expect actual
+
+'
+
+test_tick
+cat >input <<INPUT_END
+commit refs/notes/test
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+sixth notes commit
+COMMIT
+
+from refs/notes/test^0
+M 644 inline $commit1
+data <<EOF
+third note for first commit
+EOF
+
+M 644 inline $commit3
+data <<EOF
+third note for third commit
+EOF
+
+N inline $commit1
+data <<EOF
+fourth note for first commit
+EOF
+
+N inline $commit3
+data <<EOF
+fourth note for third commit
+EOF
+
+INPUT_END
+
+cat >expect <<EXPECT_END
+    fourth commit
+    third commit
+    fourth note for third commit
+    second commit
+    first commit
+    fourth note for first commit
+EXPECT_END
+
+test_expect_success 'verify that later N commands override earlier M commands' '
+
+       git fast-import <input &&
+       GIT_NOTES_REF=refs/notes/test git log | grep "^    " > actual &&
+       test_cmp expect actual
+
+'
+
+# Write fast-import commands to create the given number of commits
+fast_import_commits () {
+       my_ref=$1
+       my_num_commits=$2
+       my_append_to_file=$3
+       my_i=0
+       while test $my_i -lt $my_num_commits
+       do
+               my_i=$(($my_i + 1))
+               test_tick
+               cat >>"$my_append_to_file" <<INPUT_END
+commit $my_ref
+mark :$my_i
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+commit #$my_i
+COMMIT
+
+M 644 inline file
+data <<EOF
+file contents in commit #$my_i
+EOF
+
+INPUT_END
+       done
+}
+
+# Write fast-import commands to create the given number of notes annotating
+# the commits created by fast_import_commits()
+fast_import_notes () {
+       my_notes_ref=$1
+       my_num_commits=$2
+       my_append_to_file=$3
+       my_note_append=$4
+       test_tick
+       cat >>"$my_append_to_file" <<INPUT_END
+commit $my_notes_ref
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+committing $my_num_commits notes
+COMMIT
+
+INPUT_END
+
+       my_i=0
+       while test $my_i -lt $my_num_commits
+       do
+               my_i=$(($my_i + 1))
+               cat >>"$my_append_to_file" <<INPUT_END
+N inline :$my_i
+data <<EOF
+note for commit #$my_i$my_note_append
+EOF
+
+INPUT_END
+       done
+}
+
+
+rm input expect
+num_commits=400
+# Create lots of commits
+fast_import_commits "refs/heads/many_commits" $num_commits input
+# Create one note per above commit
+fast_import_notes "refs/notes/many_notes" $num_commits input
+# Add a couple of non-notes as well
+test_tick
+cat >>input <<INPUT_END
+commit refs/notes/many_notes
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+committing some non-notes to the notes tree
+COMMIT
+
+M 755 inline foobar/non-note.txt
+data <<EOF
+This is not a note, but rather a regular file residing in a notes tree
+EOF
+
+M 644 inline deadbeef
+data <<EOF
+Non-note file
+EOF
+
+M 644 inline de/adbeef
+data <<EOF
+Another non-note file
+EOF
+
+INPUT_END
+# Finally create the expected output from all these notes and commits
+i=$num_commits
+while test $i -gt 0
+do
+       cat >>expect <<EXPECT_END
+    commit #$i
+    note for commit #$i
+EXPECT_END
+       i=$(($i - 1))
+done
+
+test_expect_success 'add lots of commits and notes' '
+
+       git fast-import <input &&
+       GIT_NOTES_REF=refs/notes/many_notes git log refs/heads/many_commits |
+           grep "^    " > actual &&
+       test_cmp expect actual
+
+'
+
+test_expect_success 'verify that lots of notes trigger a fanout scheme' '
+
+       # None of the entries in the top-level notes tree should be a full SHA1
+       git ls-tree --name-only refs/notes/many_notes |
+       while read path
+       do
+               if test $(expr length "$path") -ge 40
+               then
+                       return 1
+               fi
+       done
+
+'
+
+cat >>expect_non-note1 << EOF
+This is not a note, but rather a regular file residing in a notes tree
+EOF
+
+cat >>expect_non-note2 << EOF
+Non-note file
+EOF
+
+cat >>expect_non-note3 << EOF
+Another non-note file
+EOF
+
+test_expect_success 'verify that non-notes are untouched by a fanout change' '
+
+       git cat-file -p refs/notes/many_notes:foobar/non-note.txt > actual &&
+       test_cmp expect_non-note1 actual &&
+       git cat-file -p refs/notes/many_notes:deadbeef > actual &&
+       test_cmp expect_non-note2 actual &&
+       git cat-file -p refs/notes/many_notes:de/adbeef > actual &&
+       test_cmp expect_non-note3 actual
+
+'
+remaining_notes=10
+test_tick
+cat >>input <<INPUT_END
+commit refs/notes/many_notes
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+removing all notes but $remaining_notes
+COMMIT
+from refs/notes/many_notes^0
+INPUT_END
+
+i=$remaining_notes
+while test $i -lt $num_commits
+do
+       i=$(($i + 1))
+       cat >>input <<INPUT_END
+N 0000000000000000000000000000000000000000 :$i
+INPUT_END
+done
+
+i=$num_commits
+rm expect
+while test $i -gt 0
+do
+       cat >>expect <<EXPECT_END
+    commit #$i
+EXPECT_END
+       if test $i -le $remaining_notes
+       then
+               cat >>expect <<EXPECT_END
+    note for commit #$i
+EXPECT_END
+       fi
+       i=$(($i - 1))
+done
+
+test_expect_success 'remove lots of notes' '
+
+       git fast-import <input &&
+       GIT_NOTES_REF=refs/notes/many_notes git log refs/heads/many_commits |
+           grep "^    " > actual &&
+       test_cmp expect actual
+
+'
+
+test_expect_success 'verify that removing notes trigger fanout consolidation' '
+
+       # All entries in the top-level notes tree should be a full SHA1
+       git ls-tree --name-only -r refs/notes/many_notes |
+       while read path
+       do
+               # Explicitly ignore the non-note paths
+               test "$path" = "foobar/non-note.txt" && continue
+               test "$path" = "deadbeef" && continue
+               test "$path" = "de/adbeef" && continue
+
+               if test $(expr length "$path") -ne 40
+               then
+                       return 1
+               fi
+       done
+
+'
+
+test_expect_success 'verify that non-notes are untouched by a fanout change' '
+
+       git cat-file -p refs/notes/many_notes:foobar/non-note.txt > actual &&
+       test_cmp expect_non-note1 actual &&
+       git cat-file -p refs/notes/many_notes:deadbeef > actual &&
+       test_cmp expect_non-note2 actual &&
+       git cat-file -p refs/notes/many_notes:de/adbeef > actual &&
+       test_cmp expect_non-note3 actual
+
+'
+
+
+rm input expect
+num_notes_refs=10
+num_commits=16
+some_commits=8
+# Create commits
+fast_import_commits "refs/heads/more_commits" $num_commits input
+# Create one note per above commit per notes ref
+i=0
+while test $i -lt $num_notes_refs
+do
+       i=$(($i + 1))
+       fast_import_notes "refs/notes/more_notes_$i" $num_commits input
+done
+# Trigger branch reloading in git-fast-import by repeating the note creation
+i=0
+while test $i -lt $num_notes_refs
+do
+       i=$(($i + 1))
+       fast_import_notes "refs/notes/more_notes_$i" $some_commits input " (2)"
+done
+# Finally create the expected output from the notes in refs/notes/more_notes_1
+i=$num_commits
+while test $i -gt 0
+do
+       note_data="note for commit #$i"
+       if test $i -le $some_commits
+       then
+               note_data="$note_data (2)"
+       fi
+       cat >>expect <<EXPECT_END
+    commit #$i
+    $note_data
+EXPECT_END
+       i=$(($i - 1))
+done
+
+test_expect_success "add notes to $num_commits commits in each of $num_notes_refs refs" '
+
+       git fast-import --active-branches=5 <input &&
+       GIT_NOTES_REF=refs/notes/more_notes_1 git log refs/heads/more_commits |
+           grep "^    " > actual &&
+       test_cmp expect actual
+
+'
+
+test_done
diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh
new file mode 100755 (executable)
index 0000000..8c8e679
--- /dev/null
@@ -0,0 +1,417 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E. Schindelin
+#
+
+test_description='git fast-export'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+
+       echo break it > file0 &&
+       git add file0 &&
+       test_tick &&
+       echo Wohlauf > file &&
+       git add file &&
+       test_tick &&
+       git commit -m initial &&
+       echo die Luft > file &&
+       echo geht frisch > file2 &&
+       git add file file2 &&
+       test_tick &&
+       git commit -m second &&
+       echo und > file2 &&
+       test_tick &&
+       git commit -m third file2 &&
+       test_tick &&
+       git tag rein &&
+       git checkout -b wer HEAD^ &&
+       echo lange > file2
+       test_tick &&
+       git commit -m sitzt file2 &&
+       test_tick &&
+       git tag -a -m valentin muss &&
+       git merge -s ours master
+
+'
+
+test_expect_success 'fast-export | fast-import' '
+
+       MASTER=$(git rev-parse --verify master) &&
+       REIN=$(git rev-parse --verify rein) &&
+       WER=$(git rev-parse --verify wer) &&
+       MUSS=$(git rev-parse --verify muss) &&
+       mkdir new &&
+       git --git-dir=new/.git init &&
+       git fast-export --all |
+       (cd new &&
+        git fast-import &&
+        test $MASTER = $(git rev-parse --verify refs/heads/master) &&
+        test $REIN = $(git rev-parse --verify refs/tags/rein) &&
+        test $WER = $(git rev-parse --verify refs/heads/wer) &&
+        test $MUSS = $(git rev-parse --verify refs/tags/muss))
+
+'
+
+test_expect_success 'fast-export master~2..master' '
+
+       git fast-export master~2..master |
+               sed "s/master/partial/" |
+               (cd new &&
+                git fast-import &&
+                test $MASTER != $(git rev-parse --verify refs/heads/partial) &&
+                git diff --exit-code master partial &&
+                git diff --exit-code master^ partial^ &&
+                test_must_fail git rev-parse partial~2)
+
+'
+
+test_expect_success 'iso-8859-1' '
+
+       git config i18n.commitencoding ISO8859-1 &&
+       # use author and committer name in ISO-8859-1 to match it.
+       . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
+       test_tick &&
+       echo rosten >file &&
+       git commit -s -m den file &&
+       git fast-export wer^..wer |
+               sed "s/wer/i18n/" |
+               (cd new &&
+                git fast-import &&
+                git cat-file commit i18n | grep "Áéí óú")
+
+'
+test_expect_success 'import/export-marks' '
+
+       git checkout -b marks master &&
+       git fast-export --export-marks=tmp-marks HEAD &&
+       test -s tmp-marks &&
+       test $(wc -l < tmp-marks) -eq 3 &&
+       test $(
+               git fast-export --import-marks=tmp-marks\
+               --export-marks=tmp-marks HEAD |
+               grep ^commit |
+               wc -l) \
+       -eq 0 &&
+       echo change > file &&
+       git commit -m "last commit" file &&
+       test $(
+               git fast-export --import-marks=tmp-marks \
+               --export-marks=tmp-marks HEAD |
+               grep ^commit\  |
+               wc -l) \
+       -eq 1 &&
+       test $(wc -l < tmp-marks) -eq 4
+
+'
+
+cat > signed-tag-import << EOF
+tag sign-your-name
+from $(git rev-parse HEAD)
+tagger C O Mitter <committer@example.com> 1112911993 -0700
+data 210
+A message for a sign
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.5 (GNU/Linux)
+
+fakedsignaturefakedsignaturefakedsignaturefakedsignaturfakedsign
+aturefakedsignaturefake=
+=/59v
+-----END PGP SIGNATURE-----
+EOF
+
+test_expect_success 'set up faked signed tag' '
+
+       cat signed-tag-import | git fast-import
+
+'
+
+test_expect_success 'signed-tags=abort' '
+
+       test_must_fail git fast-export --signed-tags=abort sign-your-name
+
+'
+
+test_expect_success 'signed-tags=verbatim' '
+
+       git fast-export --signed-tags=verbatim sign-your-name > output &&
+       grep PGP output
+
+'
+
+test_expect_success 'signed-tags=strip' '
+
+       git fast-export --signed-tags=strip sign-your-name > output &&
+       ! grep PGP output
+
+'
+
+test_expect_success 'setup submodule' '
+
+       git checkout -f master &&
+       mkdir sub &&
+       (
+               cd sub &&
+               git init  &&
+               echo test file > file &&
+               git add file &&
+               git commit -m sub_initial
+       ) &&
+       git submodule add "`pwd`/sub" sub &&
+       git commit -m initial &&
+       test_tick &&
+       (
+               cd sub &&
+               echo more data >> file &&
+               git add file &&
+               git commit -m sub_second
+       ) &&
+       git add sub &&
+       git commit -m second
+
+'
+
+test_expect_success 'submodule fast-export | fast-import' '
+
+       SUBENT1=$(git ls-tree master^ sub) &&
+       SUBENT2=$(git ls-tree master sub) &&
+       rm -rf new &&
+       mkdir new &&
+       git --git-dir=new/.git init &&
+       git fast-export --signed-tags=strip --all |
+       (cd new &&
+        git fast-import &&
+        test "$SUBENT1" = "$(git ls-tree refs/heads/master^ sub)" &&
+        test "$SUBENT2" = "$(git ls-tree refs/heads/master sub)" &&
+        git checkout master &&
+        git submodule init &&
+        git submodule update &&
+        cmp sub/file ../sub/file)
+
+'
+
+GIT_AUTHOR_NAME='A U Thor'; export GIT_AUTHOR_NAME
+GIT_COMMITTER_NAME='C O Mitter'; export GIT_COMMITTER_NAME
+
+test_expect_success 'setup copies' '
+
+       git config --unset i18n.commitencoding &&
+       git checkout -b copy rein &&
+       git mv file file3 &&
+       git commit -m move1 &&
+       test_tick &&
+       cp file2 file4 &&
+       git add file4 &&
+       git mv file2 file5 &&
+       git commit -m copy1 &&
+       test_tick &&
+       cp file3 file6 &&
+       git add file6 &&
+       git commit -m copy2 &&
+       test_tick &&
+       echo more text >> file6 &&
+       echo even more text >> file6 &&
+       git add file6 &&
+       git commit -m modify &&
+       test_tick &&
+       cp file6 file7 &&
+       echo test >> file7 &&
+       git add file7 &&
+       git commit -m copy_modify
+
+'
+
+test_expect_success 'fast-export -C -C | fast-import' '
+
+       ENTRY=$(git rev-parse --verify copy) &&
+       rm -rf new &&
+       mkdir new &&
+       git --git-dir=new/.git init &&
+       git fast-export -C -C --signed-tags=strip --all > output &&
+       grep "^C \"file6\" \"file7\"\$" output &&
+       cat output |
+       (cd new &&
+        git fast-import &&
+        test $ENTRY = $(git rev-parse --verify refs/heads/copy))
+
+'
+
+test_expect_success 'fast-export | fast-import when master is tagged' '
+
+       git tag -m msg last &&
+       git fast-export -C -C --signed-tags=strip --all > output &&
+       test $(grep -c "^tag " output) = 3
+
+'
+
+cat > tag-content << EOF
+object $(git rev-parse HEAD)
+type commit
+tag rosten
+EOF
+
+test_expect_success 'cope with tagger-less tags' '
+
+       TAG=$(git hash-object -t tag -w tag-content) &&
+       git update-ref refs/tags/sonnenschein $TAG &&
+       git fast-export -C -C --signed-tags=strip --all > output &&
+       test $(grep -c "^tag " output) = 4 &&
+       ! grep "Unspecified Tagger" output &&
+       git fast-export -C -C --signed-tags=strip --all \
+               --fake-missing-tagger > output &&
+       test $(grep -c "^tag " output) = 4 &&
+       grep "Unspecified Tagger" output
+
+'
+
+test_expect_success 'setup for limiting exports by PATH' '
+       mkdir limit-by-paths &&
+       (
+               cd limit-by-paths &&
+               git init &&
+               echo hi > there &&
+               git add there &&
+               git commit -m "First file" &&
+               echo foo > bar &&
+               git add bar &&
+               git commit -m "Second file" &&
+               git tag -a -m msg mytag &&
+               echo morefoo >> bar &&
+               git add bar &&
+               git commit -m "Change to second file"
+       )
+'
+
+cat > limit-by-paths/expected << EOF
+blob
+mark :1
+data 3
+hi
+
+reset refs/tags/mytag
+commit refs/tags/mytag
+mark :2
+author A U Thor <author@example.com> 1112912713 -0700
+committer C O Mitter <committer@example.com> 1112912713 -0700
+data 11
+First file
+M 100644 :1 there
+
+EOF
+
+test_expect_success 'dropping tag of filtered out object' '
+(
+       cd limit-by-paths &&
+       git fast-export --tag-of-filtered-object=drop mytag -- there > output &&
+       test_cmp output expected
+)
+'
+
+cat >> limit-by-paths/expected << EOF
+tag mytag
+from :2
+tagger C O Mitter <committer@example.com> 1112912713 -0700
+data 4
+msg
+
+EOF
+
+test_expect_success 'rewriting tag of filtered out object' '
+(
+       cd limit-by-paths &&
+       git fast-export --tag-of-filtered-object=rewrite mytag -- there > output &&
+       test_cmp output expected
+)
+'
+
+cat > limit-by-paths/expected << EOF
+blob
+mark :1
+data 4
+foo
+
+blob
+mark :2
+data 3
+hi
+
+reset refs/heads/master
+commit refs/heads/master
+mark :3
+author A U Thor <author@example.com> 1112912713 -0700
+committer C O Mitter <committer@example.com> 1112912713 -0700
+data 12
+Second file
+M 100644 :1 bar
+M 100644 :2 there
+
+EOF
+
+test_expect_failure 'no exact-ref revisions included' '
+       (
+               cd limit-by-paths &&
+               git fast-export master~2..master~1 > output &&
+               test_cmp output expected
+       )
+'
+
+test_expect_success 'path limiting with import-marks does not lose unmodified files'        '
+       git checkout -b simple marks~2 &&
+       git fast-export --export-marks=marks simple -- file > /dev/null &&
+       echo more content >> file &&
+       test_tick &&
+       git commit -mnext file &&
+       git fast-export --import-marks=marks simple -- file file0 | grep file0
+'
+
+test_expect_success 'full-tree re-shows unmodified files'        '
+       git checkout -f simple &&
+       test $(git fast-export --full-tree simple | grep -c file0) -eq 3
+'
+
+test_expect_success 'set-up a few more tags for tag export tests' '
+       git checkout -f master &&
+       HEAD_TREE=`git show -s --pretty=raw HEAD | grep tree | sed "s/tree //"` &&
+       git tag    tree_tag        -m "tagging a tree" $HEAD_TREE &&
+       git tag -a tree_tag-obj    -m "tagging a tree" $HEAD_TREE &&
+       git tag    tag-obj_tag     -m "tagging a tag" tree_tag-obj &&
+       git tag -a tag-obj_tag-obj -m "tagging a tag" tree_tag-obj
+'
+
+test_expect_success 'tree_tag'        '
+       mkdir result &&
+       (cd result && git init) &&
+       git fast-export tree_tag > fe-stream &&
+       (cd result && git fast-import < ../fe-stream)
+'
+
+# NEEDSWORK: not just check return status, but validate the output
+test_expect_success 'tree_tag-obj'    'git fast-export tree_tag-obj'
+test_expect_success 'tag-obj_tag'     'git fast-export tag-obj_tag'
+test_expect_success 'tag-obj_tag-obj' 'git fast-export tag-obj_tag-obj'
+
+test_expect_success SYMLINKS 'directory becomes symlink'        '
+       git init dirtosymlink &&
+       git init result &&
+       (
+               cd dirtosymlink &&
+               mkdir foo &&
+               mkdir bar &&
+               echo hello > foo/world &&
+               echo hello > bar/world &&
+               git add foo/world bar/world &&
+               git commit -q -mone &&
+               git rm -r foo &&
+               ln -s bar foo &&
+               git add foo &&
+               git commit -q -mtwo
+       ) &&
+       (
+               cd dirtosymlink &&
+               git fast-export master -- foo |
+               (cd ../result && git fast-import --quiet)
+       ) &&
+       (cd result && git show master:foo)
+'
+
+test_done
index 64f947d75bc0700fc75e8c7c87d97ec4f3e62e44..36c457e7f2312774223f853aab0e3b055659e916 100755 (executable)
@@ -11,17 +11,17 @@ cvs CLI client via git-cvsserver server'
 . ./test-lib.sh
 
 if ! test_have_prereq PERL; then
-       say 'skipping git cvsserver tests, perl not available'
+       skip_all='skipping git cvsserver tests, perl not available'
        test_done
 fi
 cvs >/dev/null 2>&1
 if test $? -ne 1
 then
-    say 'skipping git-cvsserver tests, cvs not found'
+    skip_all='skipping git-cvsserver tests, cvs not found'
     test_done
 fi
-perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
-    say 'skipping git-cvsserver tests, Perl SQLite interface unavailable'
+"$PERL_PATH" -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
+    skip_all='skipping git-cvsserver tests, Perl SQLite interface unavailable'
     test_done
 }
 
@@ -48,7 +48,9 @@ test_expect_success 'setup' '
   git pull secondroot master &&
   git clone -q --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 &&
   GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
-  GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log"
+  GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" &&
+  GIT_DIR="$SERVERDIR" git config gitcvs.authdb "$SERVERDIR/auth.db" &&
+  echo cvsuser:cvGVEarMLnhlA > "$SERVERDIR/auth.db"
 '
 
 # note that cvs doesn't accept absolute pathnames
@@ -94,9 +96,17 @@ git
 END VERIFICATION REQUEST
 EOF
 
+cat >login-git-ok <<EOF
+BEGIN VERIFICATION REQUEST
+$SERVERDIR
+cvsuser
+Ah<Z:yZZ30 e
+END VERIFICATION REQUEST
+EOF
+
 test_expect_success 'pserver authentication' \
   'cat request-anonymous | git-cvsserver pserver >log 2>&1 &&
-   sed -ne \$p log | grep "^I LOVE YOU$"'
+   sed -ne \$p log | grep "^I LOVE YOU\$"'
 
 test_expect_success 'pserver authentication failure (non-anonymous user)' \
   'if cat request-git | git-cvsserver pserver >log 2>&1
@@ -105,11 +115,15 @@ test_expect_success 'pserver authentication failure (non-anonymous user)' \
    else
        true
    fi &&
-   sed -ne \$p log | grep "^I HATE YOU$"'
+   sed -ne \$p log | grep "^I HATE YOU\$"'
+
+test_expect_success 'pserver authentication success (non-anonymous user with password)' \
+  'cat login-git-ok | git-cvsserver pserver >log 2>&1 &&
+   sed -ne \$p log | grep "^I LOVE YOU\$"'
 
 test_expect_success 'pserver authentication (login)' \
   'cat login-anonymous | git-cvsserver pserver >log 2>&1 &&
-   sed -ne \$p log | grep "^I LOVE YOU$"'
+   sed -ne \$p log | grep "^I LOVE YOU\$"'
 
 test_expect_success 'pserver authentication failure (login/non-anonymous user)' \
   'if cat login-git | git-cvsserver pserver >log 2>&1
@@ -118,7 +132,7 @@ test_expect_success 'pserver authentication failure (login/non-anonymous user)'
    else
        true
    fi &&
-   sed -ne \$p log | grep "^I HATE YOU$"'
+   sed -ne \$p log | grep "^I HATE YOU\$"'
 
 
 # misuse pserver authentication for testing of req_Root
@@ -156,7 +170,7 @@ test_expect_success 'req_Root failure (conflicting roots)' \
 
 test_expect_success 'req_Root (strict paths)' \
   'cat request-anonymous | git-cvsserver --strict-paths pserver "$SERVERDIR" >log 2>&1 &&
-   sed -ne \$p log | grep "^I LOVE YOU$"'
+   sed -ne \$p log | grep "^I LOVE YOU\$"'
 
 test_expect_success 'req_Root failure (strict-paths)' '
     ! cat request-anonymous |
@@ -165,7 +179,7 @@ test_expect_success 'req_Root failure (strict-paths)' '
 
 test_expect_success 'req_Root (w/o strict-paths)' \
   'cat request-anonymous | git-cvsserver pserver "$WORKDIR/" >log 2>&1 &&
-   sed -ne \$p log | grep "^I LOVE YOU$"'
+   sed -ne \$p log | grep "^I LOVE YOU\$"'
 
 test_expect_success 'req_Root failure (w/o strict-paths)' '
     ! cat request-anonymous |
@@ -183,7 +197,7 @@ EOF
 
 test_expect_success 'req_Root (base-path)' \
   'cat request-base | git-cvsserver --strict-paths --base-path "$WORKDIR/" pserver "$SERVERDIR" >log 2>&1 &&
-   sed -ne \$p log | grep "^I LOVE YOU$"'
+   sed -ne \$p log | grep "^I LOVE YOU\$"'
 
 test_expect_success 'req_Root failure (base-path)' '
     ! cat request-anonymous |
@@ -194,14 +208,14 @@ GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled false || exit 1
 
 test_expect_success 'req_Root (export-all)' \
   'cat request-anonymous | git-cvsserver --export-all pserver "$WORKDIR" >log 2>&1 &&
-   sed -ne \$p log | grep "^I LOVE YOU$"'
+   sed -ne \$p log | grep "^I LOVE YOU\$"'
 
 test_expect_success 'req_Root failure (export-all w/o whitelist)' \
   '! (cat request-anonymous | git-cvsserver --export-all pserver >log 2>&1 || false)'
 
 test_expect_success 'req_Root (everything together)' \
   'cat request-base | git-cvsserver --export-all --strict-paths --base-path "$WORKDIR/" pserver "$SERVERDIR" >log 2>&1 &&
-   sed -ne \$p log | grep "^I LOVE YOU$"'
+   sed -ne \$p log | grep "^I LOVE YOU\$"'
 
 GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true || exit 1
 
@@ -226,7 +240,7 @@ test_expect_success 'gitcvs.ext.enabled = true' \
   'GIT_DIR="$SERVERDIR" git config --bool gitcvs.ext.enabled true &&
    GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled false &&
    GIT_CONFIG="$git_config" cvs -Q co -d cvswork2 master >cvs.log 2>&1 &&
-   diff -q cvswork cvswork2'
+   test_cmp cvswork cvswork2'
 
 rm -fr cvswork2
 test_expect_success 'gitcvs.ext.enabled = false' \
@@ -247,7 +261,7 @@ test_expect_success 'gitcvs.dbname' \
   'GIT_DIR="$SERVERDIR" git config --bool gitcvs.ext.enabled true &&
    GIT_DIR="$SERVERDIR" git config gitcvs.dbname %Ggitcvs.%a.%m.sqlite &&
    GIT_CONFIG="$git_config" cvs -Q co -d cvswork2 master >cvs.log 2>&1 &&
-   diff -q cvswork cvswork2 &&
+   test_cmp cvswork cvswork2 &&
    test -f "$SERVERDIR/gitcvs.ext.master.sqlite" &&
    cmp "$SERVERDIR/gitcvs.master.sqlite" "$SERVERDIR/gitcvs.ext.master.sqlite"'
 
@@ -257,7 +271,7 @@ test_expect_success 'gitcvs.ext.dbname' \
    GIT_DIR="$SERVERDIR" git config gitcvs.ext.dbname %Ggitcvs1.%a.%m.sqlite &&
    GIT_DIR="$SERVERDIR" git config gitcvs.dbname %Ggitcvs2.%a.%m.sqlite &&
    GIT_CONFIG="$git_config" cvs -Q co -d cvswork2 master >cvs.log 2>&1 &&
-   diff -q cvswork cvswork2 &&
+   test_cmp cvswork cvswork2 &&
    test -f "$SERVERDIR/gitcvs1.ext.master.sqlite" &&
    test ! -f "$SERVERDIR/gitcvs2.ext.master.sqlite" &&
    cmp "$SERVERDIR/gitcvs.master.sqlite" "$SERVERDIR/gitcvs1.ext.master.sqlite"'
@@ -282,7 +296,7 @@ test_expect_success 'cvs update (create new file)' \
    cd cvswork &&
    GIT_CONFIG="$git_config" cvs -Q update &&
    test "$(echo $(grep testfile1 CVS/Entries|cut -d/ -f2,3,5))" = "testfile1/1.1/" &&
-   diff -q testfile1 ../testfile1'
+   test_cmp testfile1 ../testfile1'
 
 cd "$WORKDIR"
 test_expect_success 'cvs update (update existing file)' \
@@ -293,7 +307,7 @@ test_expect_success 'cvs update (update existing file)' \
    cd cvswork &&
    GIT_CONFIG="$git_config" cvs -Q update &&
    test "$(echo $(grep testfile1 CVS/Entries|cut -d/ -f2,3,5))" = "testfile1/1.2/" &&
-   diff -q testfile1 ../testfile1'
+   test_cmp testfile1 ../testfile1'
 
 cd "$WORKDIR"
 #TODO: cvsserver doesn't support update w/o -d
@@ -322,7 +336,7 @@ test_expect_success 'cvs update (subdirectories)' \
    (for dir in A A/B A/B/C A/D E; do
       filename="file_in_$(echo $dir|sed -e "s#/# #g")" &&
       if test "$(echo $(grep -v ^D $dir/CVS/Entries|cut -d/ -f2,3,5))" = "$filename/1.1/" &&
-           diff -q "$dir/$filename" "../$dir/$filename"; then
+       test_cmp "$dir/$filename" "../$dir/$filename"; then
         :
       else
         echo >failure
@@ -349,7 +363,7 @@ test_expect_success 'cvs update (re-add deleted file)' \
    cd cvswork &&
    GIT_CONFIG="$git_config" cvs -Q update &&
    test "$(echo $(grep testfile1 CVS/Entries|cut -d/ -f2,3,5))" = "testfile1/1.4/" &&
-   diff -q testfile1 ../testfile1'
+   test_cmp testfile1 ../testfile1'
 
 cd "$WORKDIR"
 test_expect_success 'cvs update (merge)' \
@@ -366,7 +380,7 @@ test_expect_success 'cvs update (merge)' \
    cd cvswork &&
    GIT_CONFIG="$git_config" cvs -Q update &&
    test "$(echo $(grep merge CVS/Entries|cut -d/ -f2,3,5))" = "merge/1.1/" &&
-   diff -q merge ../merge &&
+   test_cmp merge ../merge &&
    ( echo Line 0; cat merge ) >merge.tmp &&
    mv merge.tmp merge &&
    cd "$WORKDIR" &&
@@ -377,7 +391,7 @@ test_expect_success 'cvs update (merge)' \
    cd cvswork &&
    sleep 1 && touch merge &&
    GIT_CONFIG="$git_config" cvs -Q update &&
-   diff -q merge ../expected'
+   test_cmp merge ../expected'
 
 cd "$WORKDIR"
 
@@ -402,13 +416,13 @@ test_expect_success 'cvs update (conflict merge)' \
    git push gitcvs.git >/dev/null &&
    cd cvswork &&
    GIT_CONFIG="$git_config" cvs -Q update &&
-   diff -q merge ../expected.C'
+   test_cmp merge ../expected.C'
 
 cd "$WORKDIR"
 test_expect_success 'cvs update (-C)' \
   'cd cvswork &&
    GIT_CONFIG="$git_config" cvs -Q update -C &&
-   diff -q merge ../merge'
+   test_cmp merge ../merge'
 
 cd "$WORKDIR"
 test_expect_success 'cvs update (merge no-op)' \
@@ -420,7 +434,7 @@ test_expect_success 'cvs update (merge no-op)' \
     cd cvswork &&
     sleep 1 && touch merge &&
     GIT_CONFIG="$git_config" cvs -Q update &&
-    diff -q merge ../merge'
+    test_cmp merge ../merge'
 
 cd "$WORKDIR"
 test_expect_success 'cvs update (-p)' '
@@ -435,7 +449,7 @@ test_expect_success 'cvs update (-p)' '
     rm -f failures &&
     for i in merge no-lf empty really-empty; do
         GIT_CONFIG="$git_config" cvs update -p "$i" >$i.out
-        diff $i.out ../$i >>failures 2>&1
+       test_cmp $i.out ../$i >>failures 2>&1
     done &&
     test -z "$(cat failures)"
 '
index aca40c1b1ff0f957652f75383625367d85242c14..1bbfd824e50ba44f6684cbce3f220a99f17f6cc6 100755 (executable)
@@ -11,14 +11,6 @@ repository using cvs CLI client via git-cvsserver server'
 
 . ./test-lib.sh
 
-q_to_nul () {
-    perl -pe 'y/Q/\000/'
-}
-
-q_to_cr () {
-    tr Q '\015'
-}
-
 marked_as () {
     foundEntry="$(grep "^/$2/" "$1/CVS/Entries")"
     if [ x"$foundEntry" = x"" ] ; then
@@ -49,16 +41,16 @@ not_present() {
 cvs >/dev/null 2>&1
 if test $? -ne 1
 then
-    say 'skipping git-cvsserver tests, cvs not found'
+    skip_all='skipping git-cvsserver tests, cvs not found'
     test_done
 fi
 if ! test_have_prereq PERL
 then
-    say 'skipping git-cvsserver tests, perl not available'
+    skip_all='skipping git-cvsserver tests, perl not available'
     test_done
 fi
-perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
-    say 'skipping git-cvsserver tests, Perl SQLite interface unavailable'
+"$PERL_PATH" -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
+    skip_all='skipping git-cvsserver tests, Perl SQLite interface unavailable'
     test_done
 }
 
@@ -137,21 +129,22 @@ test_expect_success 'cvs co (use attributes)' '
 '
 
 test_expect_success 'adding files' '
-    cd cvswork/subdir &&
+    (cd cvswork &&
+    (cd subdir &&
     echo "more text" > src.c &&
     GIT_CONFIG="$git_config" cvs -Q add src.c >cvs.log 2>&1 &&
     marked_as . src.c "" &&
-    echo "psuedo-binary" > temp.bin &&
-    cd .. &&
+    echo "psuedo-binary" > temp.bin
+    ) &&
     GIT_CONFIG="$git_config" cvs -Q add subdir/temp.bin >cvs.log 2>&1 &&
     marked_as subdir temp.bin "-kb" &&
     cd subdir &&
     GIT_CONFIG="$git_config" cvs -Q ci -m "adding files" >cvs.log 2>&1 &&
     marked_as . temp.bin "-kb" &&
     marked_as . src.c ""
+    )
 '
 
-cd "$WORKDIR"
 test_expect_success 'updating' '
     git pull gitcvs.git &&
     echo 'hi' > subdir/newfile.bin &&
@@ -161,9 +154,9 @@ test_expect_success 'updating' '
     git add subdir/newfile.bin subdir/file.h subdir/newfile.c binfile.bin &&
     git commit -q -m "Add and change some files" &&
     git push gitcvs.git >/dev/null &&
-    cd cvswork &&
-    GIT_CONFIG="$git_config" cvs -Q update &&
-    cd .. &&
+    (cd cvswork &&
+    GIT_CONFIG="$git_config" cvs -Q update
+    ) &&
     marked_as cvswork textfile.c "" &&
     marked_as cvswork binfile.bin -kb &&
     marked_as cvswork .gitattributes "" &&
@@ -241,35 +234,35 @@ test_expect_success 'cvs co another copy (guess)' '
 '
 
 test_expect_success 'add text (guess)' '
-    cd cvswork &&
+    (cd cvswork &&
     echo "simpleText" > simpleText.c &&
-    GIT_CONFIG="$git_config" cvs -Q add simpleText.c &&
-    cd .. &&
+    GIT_CONFIG="$git_config" cvs -Q add simpleText.c
+    ) &&
     marked_as cvswork simpleText.c ""
 '
 
 test_expect_success 'add bin (guess)' '
-    cd cvswork &&
+    (cd cvswork &&
     echo "simpleBin: NUL: Q <- there" | q_to_nul > simpleBin.bin &&
-    GIT_CONFIG="$git_config" cvs -Q add simpleBin.bin &&
-    cd .. &&
+    GIT_CONFIG="$git_config" cvs -Q add simpleBin.bin
+    ) &&
     marked_as cvswork simpleBin.bin -kb
 '
 
 test_expect_success 'remove files (guess)' '
-    cd cvswork &&
+    (cd cvswork &&
     GIT_CONFIG="$git_config" cvs -Q rm -f subdir/file.h &&
-    cd subdir &&
-    GIT_CONFIG="$git_config" cvs -Q rm -f withCr.bin &&
-    cd ../.. &&
+    (cd subdir &&
+    GIT_CONFIG="$git_config" cvs -Q rm -f withCr.bin
+    )) &&
     marked_as cvswork/subdir withCr.bin -kb &&
     marked_as cvswork/subdir file.h ""
 '
 
 test_expect_success 'cvs ci (guess)' '
-    cd cvswork &&
-    GIT_CONFIG="$git_config" cvs -Q ci -m "add/rm files" >cvs.log 2>&1 &&
-    cd .. &&
+    (cd cvswork &&
+    GIT_CONFIG="$git_config" cvs -Q ci -m "add/rm files" >cvs.log 2>&1
+    ) &&
     marked_as cvswork textfile.c "" &&
     marked_as cvswork binfile.bin -kb &&
     marked_as cvswork .gitattributes "" &&
@@ -286,9 +279,9 @@ test_expect_success 'cvs ci (guess)' '
 '
 
 test_expect_success 'update subdir of other copy (guess)' '
-    cd cvswork2/subdir &&
-    GIT_CONFIG="$git_config" cvs -Q update &&
-    cd ../.. &&
+    (cd cvswork2/subdir &&
+    GIT_CONFIG="$git_config" cvs -Q update
+    ) &&
     marked_as cvswork2 textfile.c "" &&
     marked_as cvswork2 binfile.bin -kb &&
     marked_as cvswork2 .gitattributes "" &&
@@ -312,11 +305,11 @@ test_expect_success 'update/merge full other copy (guess)' '
     git add multilineTxt.c &&
     git commit -q -m "modify multiline file" >> "${WORKDIR}/marked.log" &&
     git push gitcvs.git >/dev/null &&
-    cd cvswork2 &&
+    (cd cvswork2 &&
     sed "s/1/replaced_1/" < multilineTxt.c > ml.temp &&
     mv ml.temp multilineTxt.c &&
-    GIT_CONFIG="$git_config" cvs update > cvs.log 2>&1 &&
-    cd .. &&
+    GIT_CONFIG="$git_config" cvs update > cvs.log 2>&1
+    ) &&
     marked_as cvswork2 textfile.c "" &&
     marked_as cvswork2 binfile.bin -kb &&
     marked_as cvswork2 .gitattributes "" &&
index f4210fbb04065cfe32f26053eb5f5f054d52e3cf..4f2b9b062b08302aedab42dcdd0a33776559c05e 100755 (executable)
@@ -9,73 +9,8 @@ This test runs gitweb (git web interface) as CGI script from
 commandline, and checks that it would not write any errors
 or warnings to log.'
 
-gitweb_init () {
-       safe_pwd="$(perl -MPOSIX=getcwd -e 'print quotemeta(getcwd)')"
-       cat >gitweb_config.perl <<EOF
-#!/usr/bin/perl
-
-# gitweb configuration for tests
-
-our \$version = "current";
-our \$GIT = "git";
-our \$projectroot = "$safe_pwd";
-our \$project_maxdepth = 8;
-our \$home_link_str = "projects";
-our \$site_name = "[localhost]";
-our \$site_header = "";
-our \$site_footer = "";
-our \$home_text = "indextext.html";
-our @stylesheets = ("file:///$TEST_DIRECTORY/../gitweb/gitweb.css");
-our \$logo = "file:///$TEST_DIRECTORY/../gitweb/git-logo.png";
-our \$favicon = "file:///$TEST_DIRECTORY/../gitweb/git-favicon.png";
-our \$projects_list = "";
-our \$export_ok = "";
-our \$strict_export = "";
 
-EOF
-
-       cat >.git/description <<EOF
-$0 test repository
-EOF
-}
-
-gitweb_run () {
-       GATEWAY_INTERFACE="CGI/1.1"
-       HTTP_ACCEPT="*/*"
-       REQUEST_METHOD="GET"
-       SCRIPT_NAME="$TEST_DIRECTORY/../gitweb/gitweb.perl"
-       QUERY_STRING=""$1""
-       PATH_INFO=""$2""
-       export GATEWAY_INTERFACE HTTP_ACCEPT REQUEST_METHOD \
-               SCRIPT_NAME QUERY_STRING PATH_INFO
-
-       GITWEB_CONFIG=$(pwd)/gitweb_config.perl
-       export GITWEB_CONFIG
-
-       # some of git commands write to STDERR on error, but this is not
-       # written to web server logs, so we are not interested in that:
-       # we are interested only in properly formatted errors/warnings
-       rm -f gitweb.log &&
-       perl -- "$SCRIPT_NAME" \
-               >/dev/null 2>gitweb.log &&
-       if grep "^[[]" gitweb.log >/dev/null 2>&1; then false; else true; fi
-
-       # gitweb.log is left for debugging
-}
-
-. ./test-lib.sh
-
-if ! test_have_prereq PERL; then
-       say 'skipping gitweb tests, perl not available'
-       test_done
-fi
-
-perl -MEncode -e 'decode_utf8("", Encode::FB_CROAK)' >/dev/null 2>&1 || {
-    say 'skipping gitweb tests, perl version is too old'
-    test_done
-}
-
-gitweb_init
+. ./gitweb-lib.sh
 
 # ----------------------------------------------------------------------
 # no commits (empty, just initialized repository)
@@ -590,7 +525,7 @@ test_expect_success \
         echo "ISO-8859-1" >> file &&
         git add file &&
         git config i18n.commitencoding ISO-8859-1 &&
-        git commit -F "$TEST_DIRECTORY"/t3900/ISO-8859-1.txt &&
+        git commit -F "$TEST_DIRECTORY"/t3900/ISO8859-1.txt &&
         git config --unset i18n.commitencoding &&
         gitweb_run "p=.git;a=commit"'
 test_debug 'cat gitweb.log'
@@ -656,12 +591,21 @@ test_debug 'cat gitweb.log'
 # ----------------------------------------------------------------------
 # gitweb config and repo config
 
-cat >>gitweb_config.perl <<EOF
+cat >>gitweb_config.perl <<\EOF
 
-\$feature{'blame'}{'override'} = 1;
-\$feature{'snapshot'}{'override'} = 1;
+# turn on override for each overridable feature
+foreach my $key (keys %feature) {
+       if ($feature{$key}{'sub'}) {
+               $feature{$key}{'override'} = 1;
+       }
+}
 EOF
 
+test_expect_success \
+       'config override: projects list (implicit)' \
+       'gitweb_run'
+test_debug 'cat gitweb.log'
+
 test_expect_success \
        'config override: tree view, features not overridden in repo config' \
        'gitweb_run "p=.git;a=tree"'
@@ -671,6 +615,7 @@ test_expect_success \
        'config override: tree view, features disabled in repo config' \
        'git config gitweb.blame no &&
         git config gitweb.snapshot none &&
+        git config gitweb.avatar gravatar &&
         gitweb_run "p=.git;a=tree"'
 test_debug 'cat gitweb.log'
 
@@ -702,4 +647,33 @@ test_expect_success \
         gitweb_run "p=.git;a=summary"'
 test_debug 'cat gitweb.log'
 
+# ----------------------------------------------------------------------
+# syntax highlighting
+
+cat >>gitweb_config.perl <<\EOF
+$feature{'highlight'}{'override'} = 1;
+EOF
+
+highlight --version >/dev/null 2>&1
+if [ $? -eq 127 ]; then
+       say "Skipping syntax highlighting test, because 'highlight' was not found"
+else
+       test_set_prereq HIGHLIGHT
+fi
+
+test_expect_success HIGHLIGHT \
+       'syntax highlighting (no highlight)' \
+       'git config gitweb.highlight yes &&
+        gitweb_run "p=.git;a=blob;f=file"'
+test_debug 'cat gitweb.log'
+
+test_expect_success HIGHLIGHT \
+       'syntax highlighting (highlighted)' \
+       'git config gitweb.highlight yes &&
+        echo "#!/usr/bin/sh" > test.sh &&
+        git add test.sh &&
+        git commit -m "Add test.sh" &&
+        gitweb_run "p=.git;a=blob;f=test.sh"'
+test_debug 'cat gitweb.log'
+
 test_done
diff --git a/t/t9501-gitweb-standalone-http-status.sh b/t/t9501-gitweb-standalone-http-status.sh
new file mode 100755 (executable)
index 0000000..2487da1
--- /dev/null
@@ -0,0 +1,138 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Mark Rada
+#
+
+test_description='gitweb as standalone script (http status tests).
+
+This test runs gitweb (git web interface) as a CGI script from the
+commandline, and checks that it returns the expected HTTP status
+code and message.'
+
+
+. ./gitweb-lib.sh
+
+# ----------------------------------------------------------------------
+# snapshot settings
+
+test_expect_success 'setup' "
+       test_commit 'SnapshotTests' 'i can has snapshot?'
+"
+
+
+cat >>gitweb_config.perl <<\EOF
+$feature{'snapshot'}{'override'} = 0;
+EOF
+
+test_expect_success \
+    'snapshots: tgz only default format enabled' \
+    'gitweb_run "p=.git;a=snapshot;h=HEAD;sf=tgz" &&
+    grep "Status: 200 OK" gitweb.output &&
+    gitweb_run "p=.git;a=snapshot;h=HEAD;sf=tbz2" &&
+    grep "403 - Unsupported snapshot format" gitweb.output &&
+    gitweb_run "p=.git;a=snapshot;h=HEAD;sf=txz" &&
+    grep "403 - Snapshot format not allowed" gitweb.output &&
+    gitweb_run "p=.git;a=snapshot;h=HEAD;sf=zip" &&
+    grep "403 - Unsupported snapshot format" gitweb.output'
+
+
+cat >>gitweb_config.perl <<\EOF
+$feature{'snapshot'}{'default'} = ['tgz','tbz2','txz','zip'];
+EOF
+
+test_expect_success \
+    'snapshots: all enabled in default, use default disabled value' \
+    'gitweb_run "p=.git;a=snapshot;h=HEAD;sf=tgz" &&
+    grep "Status: 200 OK" gitweb.output &&
+    gitweb_run "p=.git;a=snapshot;h=HEAD;sf=tbz2" &&
+    grep "Status: 200 OK" gitweb.output &&
+    gitweb_run "p=.git;a=snapshot;h=HEAD;sf=txz" &&
+    grep "403 - Snapshot format not allowed" gitweb.output &&
+    gitweb_run "p=.git;a=snapshot;h=HEAD;sf=zip" &&
+    grep "Status: 200 OK" gitweb.output'
+
+
+cat >>gitweb_config.perl <<\EOF
+$known_snapshot_formats{'zip'}{'disabled'} = 1;
+EOF
+
+test_expect_success \
+    'snapshots: zip explicitly disabled' \
+    'gitweb_run "p=.git;a=snapshot;h=HEAD;sf=zip" &&
+    grep "403 - Snapshot format not allowed" gitweb.output'
+test_debug 'cat gitweb.output'
+
+
+cat >>gitweb_config.perl <<\EOF
+$known_snapshot_formats{'tgz'}{'disabled'} = 0;
+EOF
+
+test_expect_success \
+    'snapshots: tgz explicitly enabled' \
+    'gitweb_run "p=.git;a=snapshot;h=HEAD;sf=tgz" &&
+    grep "Status: 200 OK" gitweb.output'
+test_debug 'cat gitweb.headers'
+
+
+# ----------------------------------------------------------------------
+# snapshot hash ids
+
+test_expect_success 'snapshots: good tree-ish id' '
+       gitweb_run "p=.git;a=snapshot;h=master;sf=tgz" &&
+       grep "Status: 200 OK" gitweb.output
+'
+test_debug 'cat gitweb.headers'
+
+test_expect_success 'snapshots: bad tree-ish id' '
+       gitweb_run "p=.git;a=snapshot;h=frizzumFrazzum;sf=tgz" &&
+       grep "404 - Object does not exist" gitweb.output
+'
+test_debug 'cat gitweb.output'
+
+test_expect_success 'snapshots: bad tree-ish id (tagged object)' '
+       echo object > tag-object &&
+       git add tag-object &&
+       git commit -m "Object to be tagged" &&
+       git tag tagged-object `git hash-object tag-object` &&
+       gitweb_run "p=.git;a=snapshot;h=tagged-object;sf=tgz" &&
+       grep "400 - Object is not a tree-ish" gitweb.output
+'
+test_debug 'cat gitweb.output'
+
+test_expect_success 'snapshots: good object id' '
+       ID=`git rev-parse --verify HEAD` &&
+       gitweb_run "p=.git;a=snapshot;h=$ID;sf=tgz" &&
+       grep "Status: 200 OK" gitweb.output
+'
+test_debug 'cat gitweb.headers'
+
+test_expect_success 'snapshots: bad object id' '
+       gitweb_run "p=.git;a=snapshot;h=abcdef01234;sf=tgz" &&
+       grep "404 - Object does not exist" gitweb.output
+'
+test_debug 'cat gitweb.output'
+
+
+# ----------------------------------------------------------------------
+# load checking
+
+# always hit the load limit
+cat >>gitweb_config.perl <<\EOF
+our $maxload = -1;
+EOF
+
+test_expect_success 'load checking: load too high (default action)' '
+       gitweb_run "p=.git" &&
+       grep "Status: 503 Service Unavailable" gitweb.headers &&
+       grep "503 - The load average on the server is too high" gitweb.body
+'
+test_debug 'cat gitweb.log' # just in case
+test_debug 'cat gitweb.headers'
+
+# turn off load checking
+cat >>gitweb_config.perl <<\EOF
+our $maxload = undef;
+EOF
+
+
+test_done
diff --git a/t/t9502-gitweb-standalone-parse-output.sh b/t/t9502-gitweb-standalone-parse-output.sh
new file mode 100755 (executable)
index 0000000..dd83890
--- /dev/null
@@ -0,0 +1,115 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Mark Rada
+#
+
+test_description='gitweb as standalone script (parsing script output).
+
+This test runs gitweb (git web interface) as a CGI script from the
+commandline, and checks that it produces the correct output, either
+in the HTTP header or the actual script output.'
+
+
+. ./gitweb-lib.sh
+
+# ----------------------------------------------------------------------
+# snapshot file name and prefix
+
+cat >>gitweb_config.perl <<\EOF
+
+$known_snapshot_formats{'tar'} = {
+       'display' => 'tar',
+       'type' => 'application/x-tar',
+       'suffix' => '.tar',
+       'format' => 'tar',
+};
+
+$feature{'snapshot'}{'default'} = ['tar'];
+EOF
+
+# Call check_snapshot with the arguments "<basename> [<prefix>]"
+#
+# This will check that gitweb HTTP header contains proposed filename
+# as <basename> with '.tar' suffix added, and that generated tarfile
+# (gitweb message body) has <prefix> as prefix for al files in tarfile
+#
+# <prefix> default to <basename>
+check_snapshot () {
+       basename=$1
+       prefix=${2:-"$1"}
+       echo "basename=$basename"
+       grep "filename=.*$basename.tar" gitweb.headers >/dev/null 2>&1 &&
+       "$TAR" tf gitweb.body >file_list &&
+       ! grep -v "^$prefix/" file_list
+}
+
+test_expect_success setup '
+       test_commit first foo &&
+       git branch xx/test &&
+       FULL_ID=$(git rev-parse --verify HEAD) &&
+       SHORT_ID=$(git rev-parse --verify --short=7 HEAD)
+'
+test_debug '
+       echo "FULL_ID  = $FULL_ID"
+       echo "SHORT_ID = $SHORT_ID"
+'
+
+test_expect_success 'snapshot: full sha1' '
+       gitweb_run "p=.git;a=snapshot;h=$FULL_ID;sf=tar" &&
+       check_snapshot ".git-$SHORT_ID"
+'
+test_debug 'cat gitweb.headers && cat file_list'
+
+test_expect_success 'snapshot: shortened sha1' '
+       gitweb_run "p=.git;a=snapshot;h=$SHORT_ID;sf=tar" &&
+       check_snapshot ".git-$SHORT_ID"
+'
+test_debug 'cat gitweb.headers && cat file_list'
+
+test_expect_success 'snapshot: almost full sha1' '
+       ID=$(git rev-parse --short=30 HEAD) &&
+       gitweb_run "p=.git;a=snapshot;h=$ID;sf=tar" &&
+       check_snapshot ".git-$SHORT_ID"
+'
+test_debug 'cat gitweb.headers && cat file_list'
+
+test_expect_success 'snapshot: HEAD' '
+       gitweb_run "p=.git;a=snapshot;h=HEAD;sf=tar" &&
+       check_snapshot ".git-HEAD-$SHORT_ID"
+'
+test_debug 'cat gitweb.headers && cat file_list'
+
+test_expect_success 'snapshot: short branch name (master)' '
+       gitweb_run "p=.git;a=snapshot;h=master;sf=tar" &&
+       ID=$(git rev-parse --verify --short=7 master) &&
+       check_snapshot ".git-master-$ID"
+'
+test_debug 'cat gitweb.headers && cat file_list'
+
+test_expect_success 'snapshot: short tag name (first)' '
+       gitweb_run "p=.git;a=snapshot;h=first;sf=tar" &&
+       ID=$(git rev-parse --verify --short=7 first) &&
+       check_snapshot ".git-first-$ID"
+'
+test_debug 'cat gitweb.headers && cat file_list'
+
+test_expect_success 'snapshot: full branch name (refs/heads/master)' '
+       gitweb_run "p=.git;a=snapshot;h=refs/heads/master;sf=tar" &&
+       ID=$(git rev-parse --verify --short=7 master) &&
+       check_snapshot ".git-master-$ID"
+'
+test_debug 'cat gitweb.headers && cat file_list'
+
+test_expect_success 'snapshot: full tag name (refs/tags/first)' '
+       gitweb_run "p=.git;a=snapshot;h=refs/tags/first;sf=tar" &&
+       check_snapshot ".git-first"
+'
+test_debug 'cat gitweb.headers && cat file_list'
+
+test_expect_success 'snapshot: hierarchical branch name (xx/test)' '
+       gitweb_run "p=.git;a=snapshot;h=xx/test;sf=tar" &&
+       ! grep "filename=.*/" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
+
+test_done
index 4322a0c1ed6d792cbba4b52a7ba8bad74986bbad..432b82e3d5a361d0eba329f5d5c56c17009738db 100755 (executable)
@@ -1,47 +1,20 @@
 #!/bin/sh
 
 test_description='git cvsimport basic tests'
-. ./test-lib.sh
-
-if ! test_have_prereq PERL; then
-       say 'skipping git cvsimport tests, perl not available'
-       test_done
-fi
-
-CVSROOT=$(pwd)/cvsroot
-export CVSROOT
-unset CVS_SERVER
-# for clean cvsps cache
-HOME=$(pwd)
-export HOME
-
-if ! type cvs >/dev/null 2>&1
-then
-       say 'skipping cvsimport tests, cvs not found'
-       test_done
-fi
-
-cvsps_version=`cvsps -h 2>&1 | sed -ne 's/cvsps version //p'`
-case "$cvsps_version" in
-2.1 | 2.2*)
-       ;;
-'')
-       say 'skipping cvsimport tests, cvsps not found'
-       test_done
-       ;;
-*)
-       say 'skipping cvsimport tests, unsupported cvsps version'
-       test_done
-       ;;
-esac
-
-test_expect_success 'setup cvsroot' 'cvs init'
-
-test_expect_success 'setup a cvs module' '
+. ./lib-cvs.sh
+
+test_expect_success PERL 'setup cvsroot environment' '
+       CVSROOT=$(pwd)/cvsroot &&
+       export CVSROOT
+'
+
+test_expect_success PERL 'setup cvsroot' '$CVS init'
+
+test_expect_success PERL 'setup a cvs module' '
 
        mkdir "$CVSROOT/module" &&
-       cvs co -d module-cvs module &&
-       cd module-cvs &&
+       $CVS co -d module-cvs module &&
+       (cd module-cvs &&
        cat <<EOF >o_fortuna &&
 O Fortuna
 velut luna
@@ -59,28 +32,34 @@ egestatem,
 potestatem
 dissolvit ut glaciem.
 EOF
-       cvs add o_fortuna &&
+       $CVS add o_fortuna &&
        cat <<EOF >message &&
 add "O Fortuna" lyrics
 
 These public domain lyrics make an excellent sample text.
 EOF
-       cvs commit -F message &&
-       cd ..
+       $CVS commit -F message
+       )
 '
 
-test_expect_success 'import a trivial module' '
+test_expect_success PERL 'import a trivial module' '
 
-       git cvsimport -a -z 0 -C module-git module &&
+       git cvsimport -a -R -z 0 -C module-git module &&
        test_cmp module-cvs/o_fortuna module-git/o_fortuna
 
 '
 
-test_expect_success 'pack refs' 'cd module-git && git gc && cd ..'
+test_expect_success PERL 'pack refs' '(cd module-git && git gc)'
+
+test_expect_success PERL 'initial import has correct .git/cvs-revisions' '
 
-test_expect_success 'update cvs module' '
+       (cd module-git &&
+        git log --format="o_fortuna 1.1 %H" -1) > expected &&
+       test_cmp expected module-git/.git/cvs-revisions
+'
 
-       cd module-cvs &&
+test_expect_success PERL 'update cvs module' '
+       (cd module-cvs &&
        cat <<EOF >o_fortuna &&
 O Fortune,
 like the moon
@@ -103,51 +82,75 @@ translate to English
 
 My Latin is terrible.
 EOF
-       cvs commit -F message &&
-       cd ..
+       $CVS commit -F message
+       )
 '
 
-test_expect_success 'update git module' '
+test_expect_success PERL 'update git module' '
 
-       cd module-git &&
-       git cvsimport -a -z 0 module &&
-       git merge origin &&
-       cd .. &&
+       (cd module-git &&
+       git cvsimport -a -R -z 0 module &&
+       git merge origin
+       ) &&
        test_cmp module-cvs/o_fortuna module-git/o_fortuna
 
 '
 
-test_expect_success 'update cvs module' '
+test_expect_success PERL 'update has correct .git/cvs-revisions' '
 
-       cd module-cvs &&
-               echo 1 >tick &&
-               cvs add tick &&
-               cvs commit -m 1
-       cd ..
+       (cd module-git &&
+        git log --format="o_fortuna 1.1 %H" -1 HEAD^ &&
+        git log --format="o_fortuna 1.2 %H" -1 HEAD) > expected &&
+       test_cmp expected module-git/.git/cvs-revisions
+'
 
+test_expect_success PERL 'update cvs module' '
+
+       (cd module-cvs &&
+               echo 1 >tick &&
+               $CVS add tick &&
+               $CVS commit -m 1
+       )
 '
 
-test_expect_success 'cvsimport.module config works' '
+test_expect_success PERL 'cvsimport.module config works' '
 
-       cd module-git &&
+       (cd module-git &&
                git config cvsimport.module module &&
-               git cvsimport -a -z0 &&
-               git merge origin &&
-       cd .. &&
+               git cvsimport -a -R -z0 &&
+               git merge origin
+       ) &&
        test_cmp module-cvs/tick module-git/tick
 
 '
 
-test_expect_success 'import from a CVS working tree' '
+test_expect_success PERL 'second update has correct .git/cvs-revisions' '
+
+       (cd module-git &&
+        git log --format="o_fortuna 1.1 %H" -1 HEAD^^ &&
+        git log --format="o_fortuna 1.2 %H" -1 HEAD^
+        git log --format="tick 1.1 %H" -1 HEAD) > expected &&
+       test_cmp expected module-git/.git/cvs-revisions
+'
 
-       cvs co -d import-from-wt module &&
-       cd import-from-wt &&
+test_expect_success PERL 'import from a CVS working tree' '
+
+       $CVS co -d import-from-wt module &&
+       (cd import-from-wt &&
                git cvsimport -a -z0 &&
                echo 1 >expect &&
                git log -1 --pretty=format:%s%n >actual &&
-               test_cmp actual expect &&
-       cd ..
+               test_cmp actual expect
+       )
 
 '
 
+test_expect_success PERL 'no .git/cvs-revisions created by default' '
+
+       ! test -e import-from-wt/.git/cvs-revisions
+
+'
+
+test_expect_success PERL 'test entire HEAD' 'test_cmp_branch_tree master'
+
 test_done
diff --git a/t/t9601-cvsimport-vendor-branch.sh b/t/t9601-cvsimport-vendor-branch.sh
new file mode 100755 (executable)
index 0000000..827d39f
--- /dev/null
@@ -0,0 +1,85 @@
+#!/bin/sh
+
+# Description of the files in the repository:
+#
+#    imported-once.txt:
+#
+#       Imported once.  1.1 and 1.1.1.1 should be identical.
+#
+#    imported-twice.txt:
+#
+#       Imported twice.  HEAD should reflect the contents of the
+#       second import (i.e., have the same contents as 1.1.1.2).
+#
+#    imported-modified.txt:
+#
+#       Imported, then modified on HEAD.  HEAD should reflect the
+#       modification.
+#
+#    imported-modified-imported.txt:
+#
+#       Imported, then modified on HEAD, then imported again.
+#
+#    added-imported.txt,v:
+#
+#       Added with 'cvs add' to create 1.1, then imported with
+#       completely different contents to create 1.1.1.1, therefore the
+#       vendor branch was never the default branch.
+#
+#    imported-anonymously.txt:
+#
+#       Like imported-twice.txt, but with a vendor branch whose branch
+#       tag has been removed.
+
+test_description='git cvsimport handling of vendor branches'
+. ./lib-cvs.sh
+
+setup_cvs_test_repository t9601
+
+test_expect_success PERL 'import a module with a vendor branch' '
+
+       git cvsimport -C module-git module
+
+'
+
+test_expect_success PERL 'check HEAD out of cvs repository' 'test_cvs_co master'
+
+test_expect_success PERL 'check master out of git repository' 'test_git_co master'
+
+test_expect_success PERL 'check a file that was imported once' '
+
+       test_cmp_branch_file master imported-once.txt
+
+'
+
+test_expect_failure PERL 'check a file that was imported twice' '
+
+       test_cmp_branch_file master imported-twice.txt
+
+'
+
+test_expect_success PERL 'check a file that was imported then modified on HEAD' '
+
+       test_cmp_branch_file master imported-modified.txt
+
+'
+
+test_expect_success PERL 'check a file that was imported, modified, then imported again' '
+
+       test_cmp_branch_file master imported-modified-imported.txt
+
+'
+
+test_expect_success PERL 'check a file that was added to HEAD then imported' '
+
+       test_cmp_branch_file master added-imported.txt
+
+'
+
+test_expect_success PERL 'a vendor branch whose tag has been removed' '
+
+       test_cmp_branch_file master imported-anonymously.txt
+
+'
+
+test_done
diff --git a/t/t9601/cvsroot/.gitattributes b/t/t9601/cvsroot/.gitattributes
new file mode 100644 (file)
index 0000000..562b12e
--- /dev/null
@@ -0,0 +1 @@
+* -whitespace
diff --git a/t/t9601/cvsroot/CVSROOT/.gitignore b/t/t9601/cvsroot/CVSROOT/.gitignore
new file mode 100644 (file)
index 0000000..3bb9b34
--- /dev/null
@@ -0,0 +1,2 @@
+history
+val-tags
diff --git a/t/t9601/cvsroot/module/added-imported.txt,v b/t/t9601/cvsroot/module/added-imported.txt,v
new file mode 100644 (file)
index 0000000..5f83072
--- /dev/null
@@ -0,0 +1,44 @@
+head   1.1;
+access;
+symbols
+       vtag-4:1.1.1.1
+       vbranchA:1.1.1;
+locks; strict;
+comment        @# @;
+
+
+1.1
+date   2004.02.09.15.43.15;    author kfogel;  state Exp;
+branches
+       1.1.1.1;
+next   ;
+
+1.1.1.1
+date   2004.02.09.15.43.16;    author kfogel;  state Exp;
+branches;
+next   ;
+
+
+desc
+@@
+
+
+1.1
+log
+@Add a file to the working copy.
+@
+text
+@Adding this file, before importing it with different contents.
+@
+
+
+1.1.1.1
+log
+@Import (vbranchA, vtag-4).
+@
+text
+@d1 1
+a1 1
+This is vtag-4 (on vbranchA) of added-then-imported.txt.
+@
+
diff --git a/t/t9601/cvsroot/module/imported-anonymously.txt,v b/t/t9601/cvsroot/module/imported-anonymously.txt,v
new file mode 100644 (file)
index 0000000..55e1b0c
--- /dev/null
@@ -0,0 +1,42 @@
+head   1.1;
+branch 1.1.1;
+access;
+symbols
+       vtag-1:1.1.1.1;
+locks; strict;
+comment        @# @;
+
+
+1.1
+date   2004.02.09.15.43.13;    author kfogel;  state Exp;
+branches
+       1.1.1.1;
+next   ;
+
+1.1.1.1
+date   2004.02.09.15.43.13;    author kfogel;  state Exp;
+branches;
+next   ;
+
+
+desc
+@@
+
+
+1.1
+log
+@Initial revision
+@
+text
+@This is vtag-1 (on vbranchA) of imported-anonymously.txt.
+@
+
+
+1.1.1.1
+log
+@Import (vbranchA, vtag-1).
+@
+text
+@@
+
+
diff --git a/t/t9601/cvsroot/module/imported-modified-imported.txt,v b/t/t9601/cvsroot/module/imported-modified-imported.txt,v
new file mode 100644 (file)
index 0000000..e5830ae
--- /dev/null
@@ -0,0 +1,76 @@
+head   1.2;
+access;
+symbols
+       vtag-2:1.1.1.2
+       vtag-1:1.1.1.1
+       vbranchA:1.1.1;
+locks; strict;
+comment        @# @;
+
+
+1.2
+date   2004.02.09.15.43.14;    author kfogel;  state Exp;
+branches;
+next   1.1;
+
+1.1
+date   2004.02.09.15.43.13;    author kfogel;  state Exp;
+branches
+       1.1.1.1;
+next   ;
+
+1.1.1.1
+date   2004.02.09.15.43.13;    author kfogel;  state Exp;
+branches;
+next   1.1.1.2;
+
+1.1.1.2
+date   2004.02.09.15.43.13;    author kfogel;  state Exp;
+branches;
+next   ;
+
+
+desc
+@@
+
+
+1.2
+log
+@First regular commit, to imported-modified-imported.txt, on HEAD.
+@
+text
+@This is a modification of imported-modified-imported.txt on HEAD.
+It should supersede the version from the vendor branch.
+@
+
+
+1.1
+log
+@Initial revision
+@
+text
+@d1 2
+a2 1
+This is vtag-1 (on vbranchA) of imported-modified-imported.txt.
+@
+
+
+1.1.1.1
+log
+@Import (vbranchA, vtag-1).
+@
+text
+@@
+
+
+1.1.1.2
+log
+@Import (vbranchA, vtag-2).
+@
+text
+@d1 1
+a1 1
+This is vtag-2 (on vbranchA) of imported-modified-imported.txt.
+@
+
+
diff --git a/t/t9601/cvsroot/module/imported-modified.txt,v b/t/t9601/cvsroot/module/imported-modified.txt,v
new file mode 100644 (file)
index 0000000..bbcfe44
--- /dev/null
@@ -0,0 +1,59 @@
+head   1.2;
+access;
+symbols
+       vtag-1:1.1.1.1
+       vbranchA:1.1.1;
+locks; strict;
+comment        @# @;
+
+
+1.2
+date   2004.02.09.15.43.14;    author kfogel;  state Exp;
+branches;
+next   1.1;
+
+1.1
+date   2004.02.09.15.43.13;    author kfogel;  state Exp;
+branches
+       1.1.1.1;
+next   ;
+
+1.1.1.1
+date   2004.02.09.15.43.13;    author kfogel;  state Exp;
+branches;
+next   ;
+
+
+desc
+@@
+
+
+1.2
+log
+@Commit on HEAD.
+@
+text
+@This is a modification of imported-modified.txt on HEAD.
+It should supersede the version from the vendor branch.
+@
+
+
+1.1
+log
+@Initial revision
+@
+text
+@d1 2
+a2 1
+This is vtag-1 (on vbranchA) of imported-modified.txt.
+@
+
+
+1.1.1.1
+log
+@Import (vbranchA, vtag-1).
+@
+text
+@@
+
+
diff --git a/t/t9601/cvsroot/module/imported-once.txt,v b/t/t9601/cvsroot/module/imported-once.txt,v
new file mode 100644 (file)
index 0000000..c5dd82b
--- /dev/null
@@ -0,0 +1,43 @@
+head   1.1;
+branch 1.1.1;
+access;
+symbols
+       vtag-1:1.1.1.1
+       vbranchA:1.1.1;
+locks; strict;
+comment        @# @;
+
+
+1.1
+date   2004.02.09.15.43.13;    author kfogel;  state Exp;
+branches
+       1.1.1.1;
+next   ;
+
+1.1.1.1
+date   2004.02.09.15.43.13;    author kfogel;  state Exp;
+branches;
+next   ;
+
+
+desc
+@@
+
+
+1.1
+log
+@Initial revision
+@
+text
+@This is vtag-1 (on vbranchA) of imported-once.txt.
+@
+
+
+1.1.1.1
+log
+@Import (vbranchA, vtag-1).
+@
+text
+@@
+
+
diff --git a/t/t9601/cvsroot/module/imported-twice.txt,v b/t/t9601/cvsroot/module/imported-twice.txt,v
new file mode 100644 (file)
index 0000000..d1f3f1b
--- /dev/null
@@ -0,0 +1,60 @@
+head   1.1;
+branch 1.1.1;
+access;
+symbols
+       vtag-2:1.1.1.2
+       vtag-1:1.1.1.1
+       vbranchA:1.1.1;
+locks; strict;
+comment        @# @;
+
+
+1.1
+date   2004.02.09.15.43.13;    author kfogel;  state Exp;
+branches
+       1.1.1.1;
+next   ;
+
+1.1.1.1
+date   2004.02.09.15.43.13;    author kfogel;  state Exp;
+branches;
+next   1.1.1.2;
+
+1.1.1.2
+date   2004.02.09.15.43.13;    author kfogel;  state Exp;
+branches;
+next   ;
+
+
+desc
+@@
+
+
+1.1
+log
+@Initial revision
+@
+text
+@This is vtag-1 (on vbranchA) of imported-twice.txt.
+@
+
+
+1.1.1.1
+log
+@Import (vbranchA, vtag-1).
+@
+text
+@@
+
+
+1.1.1.2
+log
+@Import (vbranchA, vtag-2).
+@
+text
+@d1 1
+a1 1
+This is vtag-2 (on vbranchA) of imported-twice.txt.
+@
+
+
diff --git a/t/t9602-cvsimport-branches-tags.sh b/t/t9602-cvsimport-branches-tags.sh
new file mode 100755 (executable)
index 0000000..e1db323
--- /dev/null
@@ -0,0 +1,78 @@
+#!/bin/sh
+
+# A description of the repository used for this test can be found in
+# t9602/README.
+
+test_description='git cvsimport handling of branches and tags'
+. ./lib-cvs.sh
+
+setup_cvs_test_repository t9602
+
+test_expect_success PERL 'import module' '
+
+       git cvsimport -C module-git module
+
+'
+
+test_expect_success PERL 'test branch master' '
+
+       test_cmp_branch_tree master
+
+'
+
+test_expect_success PERL 'test branch vendorbranch' '
+
+       test_cmp_branch_tree vendorbranch
+
+'
+
+test_expect_failure PERL 'test branch B_FROM_INITIALS' '
+
+       test_cmp_branch_tree B_FROM_INITIALS
+
+'
+
+test_expect_failure PERL 'test branch B_FROM_INITIALS_BUT_ONE' '
+
+       test_cmp_branch_tree B_FROM_INITIALS_BUT_ONE
+
+'
+
+test_expect_failure PERL 'test branch B_MIXED' '
+
+       test_cmp_branch_tree B_MIXED
+
+'
+
+test_expect_success PERL 'test branch B_SPLIT' '
+
+       test_cmp_branch_tree B_SPLIT
+
+'
+
+test_expect_failure PERL 'test tag vendortag' '
+
+       test_cmp_branch_tree vendortag
+
+'
+
+test_expect_success PERL 'test tag T_ALL_INITIAL_FILES' '
+
+       test_cmp_branch_tree T_ALL_INITIAL_FILES
+
+'
+
+test_expect_failure PERL 'test tag T_ALL_INITIAL_FILES_BUT_ONE' '
+
+       test_cmp_branch_tree T_ALL_INITIAL_FILES_BUT_ONE
+
+'
+
+test_expect_failure PERL 'test tag T_MIXED' '
+
+       test_cmp_branch_tree T_MIXED
+
+'
+
+
+test_done
diff --git a/t/t9602/README b/t/t9602/README
new file mode 100644 (file)
index 0000000..c231e0f
--- /dev/null
@@ -0,0 +1,62 @@
+This repository is for testing the ability to group revisions
+correctly along tags and branches.  Here is its history:
+
+  1.  The initial import (revision 1.1 of everybody) created a
+      directory structure with a file named `default' in each dir:
+
+            ./
+              default
+              sub1/default
+                   subsubA/default
+                   subsubB/default
+              sub2/default
+                   subsubA/default
+              sub3/default
+
+  2.  Then tagged everyone with T_ALL_INITIAL_FILES.
+
+  3.  Then tagged everyone except sub1/subsubB/default with
+      T_ALL_INITIAL_FILES_BUT_ONE.
+
+  4.  Then created branch B_FROM_INITIALS on everyone.
+
+  5.  Then created branch B_FROM_INITIALS_BUT_ONE on everyone except
+      /sub1/subsubB/default.
+
+  6.  Then committed modifications to two files: sub3/default, and
+      sub1/subsubA/default.
+
+  7.  Then committed a modification to all 7 files.
+
+  8.  Then backdated sub3/default to revision 1.2, and
+      sub2/subsubA/default to revision 1.1, and tagged with T_MIXED.
+
+  9.  Same as 8, but tagged with -b to create branch B_MIXED.
+
+  10. Switched the working copy to B_MIXED, and added
+      sub2/branch_B_MIXED_only.  (That's why the RCS file is in
+      sub2/Attic/ -- it never existed on trunk.)
+
+  11. In one commit, modified default, sub1/default, and
+      sub2/subsubA/default, on branch B_MIXED.
+
+  12. Did "cvs up -A" on sub2/default, then in one commit, made a
+      change to sub2/default and sub2/branch_B_MIXED_only.  So this
+      commit should be spread between the branch and the trunk.
+
+  13. Do "cvs up -A" to get everyone back to trunk, then make a new
+      branch B_SPLIT on everyone except sub1/subsubB/default,v.
+
+  14. Switch to branch B_SPLIT (see sub1/subsubB/default disappear)
+      and commit a change that affects everyone except sub3/default.
+
+  15. An hour or so later, "cvs up -A" to get sub1/subsubB/default
+      back, then commit a change on that file, on trunk.  (It's
+      important that this change happened after the previous commits
+      on B_SPLIT.)
+
+  16. Branch sub1/subsubB/default to B_SPLIT, then "cvs up -r B_SPLIT"
+      to switch the whole working copy to the branch.
+
+  17. Commit a change on B_SPLIT, to sub1/subsubB/default and
+      sub3/default.
diff --git a/t/t9602/cvsroot/.gitattributes b/t/t9602/cvsroot/.gitattributes
new file mode 100644 (file)
index 0000000..562b12e
--- /dev/null
@@ -0,0 +1 @@
+* -whitespace
diff --git a/t/t9602/cvsroot/CVSROOT/.gitignore b/t/t9602/cvsroot/CVSROOT/.gitignore
new file mode 100644 (file)
index 0000000..3bb9b34
--- /dev/null
@@ -0,0 +1,2 @@
+history
+val-tags
diff --git a/t/t9602/cvsroot/module/default,v b/t/t9602/cvsroot/module/default,v
new file mode 100644 (file)
index 0000000..3b68382
--- /dev/null
@@ -0,0 +1,102 @@
+head   1.2;
+access;
+symbols
+       B_SPLIT:1.2.0.4
+       B_MIXED:1.2.0.2
+       T_MIXED:1.2
+       B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4
+       B_FROM_INITIALS:1.1.1.1.0.2
+       T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1
+       T_ALL_INITIAL_FILES:1.1.1.1
+       vendortag:1.1.1.1
+       vendorbranch:1.1.1;
+locks; strict;
+comment        @# @;
+
+
+1.2
+date   2003.05.23.00.17.53;    author jrandom; state Exp;
+branches
+       1.2.2.1
+       1.2.4.1;
+next   1.1;
+
+1.1
+date   2003.05.22.23.20.19;    author jrandom; state Exp;
+branches
+       1.1.1.1;
+next   ;
+
+1.1.1.1
+date   2003.05.22.23.20.19;    author jrandom; state Exp;
+branches;
+next   ;
+
+1.2.2.1
+date   2003.05.23.00.31.36;    author jrandom; state Exp;
+branches;
+next   ;
+
+1.2.4.1
+date   2003.06.03.03.20.31;    author jrandom; state Exp;
+branches;
+next   ;
+
+
+desc
+@@
+
+
+1.2
+log
+@Second commit to proj, affecting all 7 files.
+@
+text
+@This is the file `default' in the top level of the project.
+
+Every directory in the `proj' project has a file named `default'.
+
+This line was added in the second commit (affecting all 7 files).
+@
+
+
+1.2.4.1
+log
+@First change on branch B_SPLIT.
+
+This change excludes sub3/default, because it was not part of this
+commit, and sub1/subsubB/default, which is not even on the branch yet.
+@
+text
+@a5 2
+
+First change on branch B_SPLIT.
+@
+
+
+1.2.2.1
+log
+@Modify three files, on branch B_MIXED.
+@
+text
+@a5 2
+
+This line was added on branch B_MIXED only (affecting 3 files).
+@
+
+
+1.1
+log
+@Initial revision
+@
+text
+@d4 2
+@
+
+
+1.1.1.1
+log
+@Initial import.
+@
+text
+@@
diff --git a/t/t9602/cvsroot/module/sub1/default,v b/t/t9602/cvsroot/module/sub1/default,v
new file mode 100644 (file)
index 0000000..b7fdccd
--- /dev/null
@@ -0,0 +1,102 @@
+head   1.2;
+access;
+symbols
+       B_SPLIT:1.2.0.4
+       B_MIXED:1.2.0.2
+       T_MIXED:1.2
+       B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4
+       B_FROM_INITIALS:1.1.1.1.0.2
+       T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1
+       T_ALL_INITIAL_FILES:1.1.1.1
+       vendortag:1.1.1.1
+       vendorbranch:1.1.1;
+locks; strict;
+comment        @# @;
+
+
+1.2
+date   2003.05.23.00.17.53;    author jrandom; state Exp;
+branches
+       1.2.2.1
+       1.2.4.1;
+next   1.1;
+
+1.1
+date   2003.05.22.23.20.19;    author jrandom; state Exp;
+branches
+       1.1.1.1;
+next   ;
+
+1.1.1.1
+date   2003.05.22.23.20.19;    author jrandom; state Exp;
+branches;
+next   ;
+
+1.2.2.1
+date   2003.05.23.00.31.36;    author jrandom; state Exp;
+branches;
+next   ;
+
+1.2.4.1
+date   2003.06.03.03.20.31;    author jrandom; state Exp;
+branches;
+next   ;
+
+
+desc
+@@
+
+
+1.2
+log
+@Second commit to proj, affecting all 7 files.
+@
+text
+@This is sub1/default.
+
+Every directory in the `proj' project has a file named `default'.
+
+This line was added in the second commit (affecting all 7 files).
+@
+
+
+1.2.4.1
+log
+@First change on branch B_SPLIT.
+
+This change excludes sub3/default, because it was not part of this
+commit, and sub1/subsubB/default, which is not even on the branch yet.
+@
+text
+@a5 2
+
+First change on branch B_SPLIT.
+@
+
+
+1.2.2.1
+log
+@Modify three files, on branch B_MIXED.
+@
+text
+@a5 2
+
+This line was added on branch B_MIXED only (affecting 3 files).
+@
+
+
+1.1
+log
+@Initial revision
+@
+text
+@d4 2
+@
+
+
+1.1.1.1
+log
+@Initial import.
+@
+text
+@@
diff --git a/t/t9602/cvsroot/module/sub1/subsubA/default,v b/t/t9602/cvsroot/module/sub1/subsubA/default,v
new file mode 100644 (file)
index 0000000..472b7b2
--- /dev/null
@@ -0,0 +1,101 @@
+head   1.3;
+access;
+symbols
+       B_SPLIT:1.3.0.4
+       B_MIXED:1.3.0.2
+       T_MIXED:1.3
+       B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4
+       B_FROM_INITIALS:1.1.1.1.0.2
+       T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1
+       T_ALL_INITIAL_FILES:1.1.1.1
+       vendortag:1.1.1.1
+       vendorbranch:1.1.1;
+locks; strict;
+comment        @# @;
+
+
+1.3
+date   2003.05.23.00.17.53;    author jrandom; state Exp;
+branches
+       1.3.4.1;
+next   1.2;
+
+1.2
+date   2003.05.23.00.15.26;    author jrandom; state Exp;
+branches;
+next   1.1;
+
+1.1
+date   2003.05.22.23.20.19;    author jrandom; state Exp;
+branches
+       1.1.1.1;
+next   ;
+
+1.1.1.1
+date   2003.05.22.23.20.19;    author jrandom; state Exp;
+branches;
+next   ;
+
+1.3.4.1
+date   2003.06.03.03.20.31;    author jrandom; state Exp;
+branches;
+next   ;
+
+
+desc
+@@
+
+
+1.3
+log
+@Second commit to proj, affecting all 7 files.
+@
+text
+@This is sub1/subsubA/default.
+
+Every directory in the `proj' project has a file named `default'.
+
+This line was added by the first commit (affecting two files).
+
+This line was added in the second commit (affecting all 7 files).
+@
+
+
+1.3.4.1
+log
+@First change on branch B_SPLIT.
+
+This change excludes sub3/default, because it was not part of this
+commit, and sub1/subsubB/default, which is not even on the branch yet.
+@
+text
+@a7 2
+
+First change on branch B_SPLIT.
+@
+
+
+1.2
+log
+@First commit to proj, affecting two files.
+@
+text
+@d6 2
+@
+
+
+1.1
+log
+@Initial revision
+@
+text
+@d4 2
+@
+
+
+1.1.1.1
+log
+@Initial import.
+@
+text
+@@
diff --git a/t/t9602/cvsroot/module/sub1/subsubB/default,v b/t/t9602/cvsroot/module/sub1/subsubB/default,v
new file mode 100644 (file)
index 0000000..fe6efa4
--- /dev/null
@@ -0,0 +1,107 @@
+head   1.3;
+access;
+symbols
+       B_SPLIT:1.3.0.2
+       B_MIXED:1.2.0.2
+       T_MIXED:1.2
+       B_FROM_INITIALS:1.1.1.1.0.2
+       T_ALL_INITIAL_FILES:1.1.1.1
+       vendortag:1.1.1.1
+       vendorbranch:1.1.1;
+locks; strict;
+comment        @# @;
+
+
+1.3
+date   2003.06.03.04.29.14;    author jrandom; state Exp;
+branches
+       1.3.2.1;
+next   1.2;
+
+1.2
+date   2003.05.23.00.17.53;    author jrandom; state Exp;
+branches;
+next   1.1;
+
+1.1
+date   2003.05.22.23.20.19;    author jrandom; state Exp;
+branches
+       1.1.1.1;
+next   ;
+
+1.1.1.1
+date   2003.05.22.23.20.19;    author jrandom; state Exp;
+branches;
+next   ;
+
+1.3.2.1
+date   2003.06.03.04.33.13;    author jrandom; state Exp;
+branches;
+next   ;
+
+
+desc
+@@
+
+
+1.3
+log
+@A trunk change to sub1/subsubB/default.  This was committed about an
+hour after an earlier change that affected most files on branch
+B_SPLIT.  This file is not on that branch yet, but after this commit,
+we'll branch to B_SPLIT, albeit rooted in a revision that didn't exist
+at the time the rest of B_SPLIT was created.
+@
+text
+@This is sub1/subsubB/default.
+
+Every directory in the `proj' project has a file named `default'.
+
+This line was added in the second commit (affecting all 7 files).
+
+This bit was committed on trunk about an hour after an earlier change
+to everyone else on branch B_SPLIT.  Afterwards, we'll finally branch
+this file to B_SPLIT, but rooted in a revision that didn't exist at
+the time the rest of B_SPLIT was created.
+@
+
+
+1.3.2.1
+log
+@This change affects sub3/default and sub1/subsubB/default, on branch
+B_SPLIT.  Note that the latter file did not even exist on this branch
+until after some other files had had revisions committed on B_SPLIT.
+@
+text
+@a10 4
+
+This change affects sub3/default and sub1/subsubB/default, on branch
+B_SPLIT.  Note that the latter file did not even exist on this branch
+until after some other files had had revisions committed on B_SPLIT.
+@
+
+
+1.2
+log
+@Second commit to proj, affecting all 7 files.
+@
+text
+@d6 5
+@
+
+
+1.1
+log
+@Initial revision
+@
+text
+@d4 2
+@
+
+
+1.1.1.1
+log
+@Initial import.
+@
+text
+@@
diff --git a/t/t9602/cvsroot/module/sub2/Attic/branch_B_MIXED_only,v b/t/t9602/cvsroot/module/sub2/Attic/branch_B_MIXED_only,v
new file mode 100644 (file)
index 0000000..34c9789
--- /dev/null
@@ -0,0 +1,59 @@
+head   1.1;
+access;
+symbols
+       B_MIXED:1.1.0.2;
+locks; strict;
+comment        @# @;
+
+
+1.1
+date   2003.05.23.00.25.26;    author jrandom; state dead;
+branches
+       1.1.2.1;
+next   ;
+
+1.1.2.1
+date   2003.05.23.00.25.26;    author jrandom; state Exp;
+branches;
+next   1.1.2.2;
+
+1.1.2.2
+date   2003.05.23.00.48.51;    author jrandom; state Exp;
+branches;
+next   ;
+
+
+desc
+@@
+
+
+1.1
+log
+@file branch_B_MIXED_only was initially added on branch B_MIXED.
+@
+text
+@@
+
+
+1.1.2.1
+log
+@Add a file on branch B_MIXED.
+@
+text
+@a0 1
+This file was added on branch B_MIXED.  It never existed on trunk.
+@
+
+
+1.1.2.2
+log
+@A single commit affecting one file on branch B_MIXED and one on trunk.
+@
+text
+@a1 3
+
+The same commit added these two lines here on branch B_MIXED, and two
+similar lines to ./default on trunk.
+@
+
+
diff --git a/t/t9602/cvsroot/module/sub2/default,v b/t/t9602/cvsroot/module/sub2/default,v
new file mode 100644 (file)
index 0000000..018f7f8
--- /dev/null
@@ -0,0 +1,102 @@
+head   1.3;
+access;
+symbols
+       B_SPLIT:1.3.0.2
+       B_MIXED:1.2.0.2
+       T_MIXED:1.2
+       B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4
+       B_FROM_INITIALS:1.1.1.1.0.2
+       T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1
+       T_ALL_INITIAL_FILES:1.1.1.1
+       vendortag:1.1.1.1
+       vendorbranch:1.1.1;
+locks; strict;
+comment        @# @;
+
+
+1.3
+date   2003.05.23.00.48.51;    author jrandom; state Exp;
+branches
+       1.3.2.1;
+next   1.2;
+
+1.2
+date   2003.05.23.00.17.53;    author jrandom; state Exp;
+branches;
+next   1.1;
+
+1.1
+date   2003.05.22.23.20.19;    author jrandom; state Exp;
+branches
+       1.1.1.1;
+next   ;
+
+1.1.1.1
+date   2003.05.22.23.20.19;    author jrandom; state Exp;
+branches;
+next   ;
+
+1.3.2.1
+date   2003.06.03.03.20.31;    author jrandom; state Exp;
+branches;
+next   ;
+
+
+desc
+@@
+
+
+1.3
+log
+@A single commit affecting one file on branch B_MIXED and one on trunk.
+@
+text
+@This is sub2/default.
+
+Every directory in the `proj' project has a file named `default'.
+
+This line was added in the second commit (affecting all 7 files).
+
+The same commit added these two lines here on trunk, and two similar
+lines to ./branch_B_MIXED_only on branch B_MIXED.
+@
+
+
+1.3.2.1
+log
+@First change on branch B_SPLIT.
+
+This change excludes sub3/default, because it was not part of this
+commit, and sub1/subsubB/default, which is not even on the branch yet.
+@
+text
+@a8 2
+
+First change on branch B_SPLIT.
+@
+
+
+1.2
+log
+@Second commit to proj, affecting all 7 files.
+@
+text
+@d6 3
+@
+
+
+1.1
+log
+@Initial revision
+@
+text
+@d4 2
+@
+
+
+1.1.1.1
+log
+@Initial import.
+@
+text
+@@
diff --git a/t/t9602/cvsroot/module/sub2/subsubA/default,v b/t/t9602/cvsroot/module/sub2/subsubA/default,v
new file mode 100644 (file)
index 0000000..d13242c
--- /dev/null
@@ -0,0 +1,102 @@
+head   1.2;
+access;
+symbols
+       B_SPLIT:1.2.0.2
+       B_MIXED:1.1.0.2
+       T_MIXED:1.1
+       B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4
+       B_FROM_INITIALS:1.1.1.1.0.2
+       T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1
+       T_ALL_INITIAL_FILES:1.1.1.1
+       vendortag:1.1.1.1
+       vendorbranch:1.1.1;
+locks; strict;
+comment        @# @;
+
+
+1.2
+date   2003.05.23.00.17.53;    author jrandom; state Exp;
+branches
+       1.2.2.1;
+next   1.1;
+
+1.1
+date   2003.05.22.23.20.19;    author jrandom; state Exp;
+branches
+       1.1.1.1
+       1.1.2.1;
+next   ;
+
+1.1.1.1
+date   2003.05.22.23.20.19;    author jrandom; state Exp;
+branches;
+next   ;
+
+1.1.2.1
+date   2003.05.23.00.31.36;    author jrandom; state Exp;
+branches;
+next   ;
+
+1.2.2.1
+date   2003.06.03.03.20.31;    author jrandom; state Exp;
+branches;
+next   ;
+
+
+desc
+@@
+
+
+1.2
+log
+@Second commit to proj, affecting all 7 files.
+@
+text
+@This is sub2/subsub2/default.
+
+Every directory in the `proj' project has a file named `default'.
+
+This line was added in the second commit (affecting all 7 files).
+@
+
+
+1.2.2.1
+log
+@First change on branch B_SPLIT.
+
+This change excludes sub3/default, because it was not part of this
+commit, and sub1/subsubB/default, which is not even on the branch yet.
+@
+text
+@a5 2
+
+First change on branch B_SPLIT.
+@
+
+
+1.1
+log
+@Initial revision
+@
+text
+@d4 2
+@
+
+
+1.1.2.1
+log
+@Modify three files, on branch B_MIXED.
+@
+text
+@a3 2
+
+This line was added on branch B_MIXED only (affecting 3 files).
+@
+
+
+1.1.1.1
+log
+@Initial import.
+@
+text
+@@
diff --git a/t/t9602/cvsroot/module/sub3/default,v b/t/t9602/cvsroot/module/sub3/default,v
new file mode 100644 (file)
index 0000000..88e4567
--- /dev/null
@@ -0,0 +1,102 @@
+head   1.3;
+access;
+symbols
+       B_SPLIT:1.3.0.2
+       B_MIXED:1.2.0.2
+       T_MIXED:1.2
+       B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4
+       B_FROM_INITIALS:1.1.1.1.0.2
+       T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1
+       T_ALL_INITIAL_FILES:1.1.1.1
+       vendortag:1.1.1.1
+       vendorbranch:1.1.1;
+locks; strict;
+comment        @# @;
+
+
+1.3
+date   2003.05.23.00.17.53;    author jrandom; state Exp;
+branches
+       1.3.2.1;
+next   1.2;
+
+1.2
+date   2003.05.23.00.15.26;    author jrandom; state Exp;
+branches;
+next   1.1;
+
+1.1
+date   2003.05.22.23.20.19;    author jrandom; state Exp;
+branches
+       1.1.1.1;
+next   ;
+
+1.1.1.1
+date   2003.05.22.23.20.19;    author jrandom; state Exp;
+branches;
+next   ;
+
+1.3.2.1
+date   2003.06.03.04.33.13;    author jrandom; state Exp;
+branches;
+next   ;
+
+
+desc
+@@
+
+
+1.3
+log
+@Second commit to proj, affecting all 7 files.
+@
+text
+@This is sub3/default.
+
+Every directory in the `proj' project has a file named `default'.
+
+This line was added by the first commit (affecting two files).
+
+This line was added in the second commit (affecting all 7 files).
+@
+
+
+1.3.2.1
+log
+@This change affects sub3/default and sub1/subsubB/default, on branch
+B_SPLIT.  Note that the latter file did not even exist on this branch
+until after some other files had had revisions committed on B_SPLIT.
+@
+text
+@a7 4
+
+This change affects sub3/default and sub1/subsubB/default, on branch
+B_SPLIT.  Note that the latter file did not even exist on this branch
+until after some other files had had revisions committed on B_SPLIT.
+@
+
+
+1.2
+log
+@First commit to proj, affecting two files.
+@
+text
+@d6 2
+@
+
+
+1.1
+log
+@Initial revision
+@
+text
+@d4 2
+@
+
+
+1.1.1.1
+log
+@Initial import.
+@
+text
+@@
diff --git a/t/t9603-cvsimport-patchsets.sh b/t/t9603-cvsimport-patchsets.sh
new file mode 100755 (executable)
index 0000000..52034c8
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+# Structure of the test cvs repository
+#
+# Message   File:Content         Commit Time
+# Rev 1     a: 1.1               2009-02-21 19:11:43 +0100
+# Rev 2     a: 1.2    b: 1.1     2009-02-21 19:11:14 +0100
+# Rev 3               b: 1.2     2009-02-21 19:11:43 +0100
+#
+# As you can see the commit of Rev 3 has the same time as
+# Rev 1 this leads to a broken import because of a cvsps
+# bug.
+
+test_description='git cvsimport testing for correct patchset estimation'
+. ./lib-cvs.sh
+
+setup_cvs_test_repository t9603
+
+test_expect_failure 'import with criss cross times on revisions' '
+
+    git cvsimport -p"-x" -C module-git module &&
+    (cd module-git &&
+        git log --pretty=format:%s > ../actual-master &&
+        git log A~2..A --pretty="format:%s %ad" -- > ../actual-A &&
+        echo "" >> ../actual-master &&
+       echo "" >> ../actual-A
+    ) &&
+    echo "Rev 4
+Rev 3
+Rev 2
+Rev 1" > expect-master &&
+    test_cmp actual-master expect-master &&
+
+    echo "Rev 5 Branch A Wed Mar 11 19:09:10 2009 +0000
+Rev 4 Branch A Wed Mar 11 19:03:52 2009 +0000" > expect-A &&
+    test_cmp actual-A expect-A
+'
+
+test_done
diff --git a/t/t9603/cvsroot/.gitattributes b/t/t9603/cvsroot/.gitattributes
new file mode 100644 (file)
index 0000000..562b12e
--- /dev/null
@@ -0,0 +1 @@
+* -whitespace
diff --git a/t/t9603/cvsroot/CVSROOT/.gitignore b/t/t9603/cvsroot/CVSROOT/.gitignore
new file mode 100644 (file)
index 0000000..3bb9b34
--- /dev/null
@@ -0,0 +1,2 @@
+history
+val-tags
diff --git a/t/t9603/cvsroot/module/a,v b/t/t9603/cvsroot/module/a,v
new file mode 100644 (file)
index 0000000..ba8fd5a
--- /dev/null
@@ -0,0 +1,74 @@
+head   1.2;
+access;
+symbols
+       A:1.2.0.2;
+locks; strict;
+comment        @# @;
+
+
+1.2
+date   2009.02.21.18.11.14;    author tester;  state Exp;
+branches
+       1.2.2.1;
+next   1.1;
+
+1.1
+date   2009.02.21.18.11.43;    author tester;  state Exp;
+branches;
+next   ;
+
+1.2.2.1
+date   2009.03.11.19.03.52;    author tester;  state Exp;
+branches;
+next   1.2.2.2;
+
+1.2.2.2
+date   2009.03.11.19.09.10;    author tester;  state Exp;
+branches;
+next   ;
+
+
+desc
+@@
+
+
+1.2
+log
+@Rev 2
+@
+text
+@1.2
+@
+
+
+1.2.2.1
+log
+@Rev 4 Branch A
+@
+text
+@d1 1
+a1 1
+1.2.2.1
+@
+
+
+1.2.2.2
+log
+@Rev 5 Branch A
+@
+text
+@d1 1
+a1 1
+1.2.2.2
+@
+
+
+1.1
+log
+@Rev 1
+@
+text
+@d1 1
+a1 1
+1.1
+@
diff --git a/t/t9603/cvsroot/module/b,v b/t/t9603/cvsroot/module/b,v
new file mode 100644 (file)
index 0000000..d268855
--- /dev/null
@@ -0,0 +1,90 @@
+head   1.3;
+access;
+symbols
+       A:1.2.0.2;
+locks; strict;
+comment        @# @;
+
+
+1.3
+date   2009.03.11.19.05.08;    author tester;  state Exp;
+branches;
+next   1.2;
+
+1.2
+date   2009.02.21.18.11.43;    author tester;  state Exp;
+branches
+       1.2.2.1;
+next   1.1;
+
+1.1
+date   2009.02.21.18.11.14;    author tester;  state Exp;
+branches;
+next   ;
+
+1.2.2.1
+date   2009.03.11.19.03.52;    author tester;  state Exp;
+branches;
+next   1.2.2.2;
+
+1.2.2.2
+date   2009.03.11.19.09.10;    author tester;  state Exp;
+branches;
+next   ;
+
+
+desc
+@@
+
+
+1.3
+log
+@Rev 4
+@
+text
+@1.3
+@
+
+
+1.2
+log
+@Rev 3
+@
+text
+@d1 1
+a1 1
+1.2
+@
+
+
+1.2.2.1
+log
+@Rev 4 Branch A
+@
+text
+@d1 1
+a1 1
+1.2.2.1
+@
+
+
+1.2.2.2
+log
+@Rev 5 Branch A
+@
+text
+@d1 1
+a1 1
+1.2
+@
+
+
+1.1
+log
+@Rev 2
+@
+text
+@d1 1
+a1 1
+1.1
+@
index b4ca244626a6635faa2587722c26f4c17692af65..3787186703f51f75103824f562c3849483ceaae1 100755 (executable)
@@ -7,12 +7,12 @@ test_description='perl interface (Git.pm)'
 . ./test-lib.sh
 
 if ! test_have_prereq PERL; then
-       say 'skipping perl interface tests, perl not available'
+       skip_all='skipping perl interface tests, perl not available'
        test_done
 fi
 
-perl -MTest::More -e 0 2>/dev/null || {
-       say "Perl Test::More unavailable, skipping test"
+"$PERL_PATH" -MTest::More -e 0 2>/dev/null || {
+       skip_all="Perl Test::More unavailable, skipping test"
        test_done
 }
 
@@ -29,6 +29,10 @@ test_expect_success \
      git add . &&
      git commit -m "first commit" &&
 
+     echo "new file in subdir 2" > directory2/file2 &&
+     git add . &&
+     git commit -m "commit in directory2" &&
+
      echo "changed file 1" > file1 &&
      git commit -a -m "second commit" &&
 
@@ -42,8 +46,11 @@ test_expect_success \
      git config --add test.int 2k
      '
 
+# The external test will outputs its own plan
+test_external_has_tap=1
+
 test_external_without_stderr \
     'Perl API' \
-    perl "$TEST_DIRECTORY"/t9700/test.pl
+    "$PERL_PATH" "$TEST_DIRECTORY"/t9700/test.pl
 
 test_done
index 697daf3ffd33c27654ce00f780acc2c6db5f9985..671f38db2bb78c076b281602ca5b87f10fdf4d00 100755 (executable)
@@ -7,13 +7,20 @@ use strict;
 
 use Test::More qw(no_plan);
 
+BEGIN {
+       # t9700-perl-git.sh kicks off our testing, so we have to go from
+       # there.
+       Test::More->builder->current_test(1);
+       Test::More->builder->no_ending(1);
+}
+
 use Cwd;
 use File::Basename;
 
 BEGIN { use_ok('Git') }
 
 # set up
-our $abs_repo_dir = Cwd->cwd;
+our $abs_repo_dir = cwd();
 ok(our $r = Git->repository(Directory => "."), "open repository");
 
 # config
@@ -86,15 +93,27 @@ close TEMPFILE;
 unlink $tmpfile;
 
 # paths
-is($r->repo_path, "./.git", "repo_path");
+is($r->repo_path, $abs_repo_dir . "/.git", "repo_path");
 is($r->wc_path, $abs_repo_dir . "/", "wc_path");
 is($r->wc_subdir, "", "wc_subdir initial");
 $r->wc_chdir("directory1");
 is($r->wc_subdir, "directory1", "wc_subdir after wc_chdir");
-TODO: {
-       local $TODO = "commands do not work after wc_chdir";
-       # Failure output is active even in non-verbose mode and thus
-       # annoying.  Hence we skip these tests as long as they fail.
-       todo_skip 'config after wc_chdir', 1;
-       is($r->config("color.string"), "value", "config after wc_chdir");
-}
+is($r->config("test.string"), "value", "config after wc_chdir");
+
+# Object generation in sub directory
+chdir("directory2");
+my $r2 = Git->repository();
+is($r2->repo_path, $abs_repo_dir . "/.git", "repo_path (2)");
+is($r2->wc_path, $abs_repo_dir . "/", "wc_path (2)");
+is($r2->wc_subdir, "directory2/", "wc_subdir initial (2)");
+
+# commands in sub directory
+my $last_commit = $r2->command_oneline(qw(rev-parse --verify HEAD));
+like($last_commit, qr/^[0-9a-fA-F]{40}$/, 'rev-parse returned hash');
+my $dir_commit = $r2->command_oneline('log', '-n1', '--pretty=format:%H', '.');
+isnt($last_commit, $dir_commit, 'log . does not show last commit');
+
+printf "1..%d\n", Test::More->builder->current_test;
+
+my $is_passing = eval { Test::More->is_passing };
+exit($is_passing ? 0 : 1) unless $@ =~ /Can't locate object method/;
index dad1437fa49596cf6f36b40b1ab18b008620a246..830e5e7360350a424a2fe6016a03c913209f7557 100644 (file)
@@ -2,6 +2,18 @@
 #
 # Copyright (c) 2005 Junio C Hamano
 #
+# 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, see http://www.gnu.org/licenses/ .
 
 # if --tee was passed, write the output not only to the terminal, but
 # additionally to the file test-results/$BASENAME.out, too.
@@ -30,7 +42,7 @@ TZ=UTC
 TERM=dumb
 export LANG LC_ALL PAGER TERM TZ
 EDITOR=:
-VISUAL=:
+unset VISUAL
 unset GIT_EDITOR
 unset AUTHOR_DATE
 unset AUTHOR_EMAIL
@@ -54,17 +66,22 @@ unset GIT_OBJECT_DIRECTORY
 unset GIT_CEILING_DIRECTORIES
 unset SHA1_FILE_DIRECTORIES
 unset SHA1_FILE_DIRECTORY
+unset GIT_NOTES_REF
+unset GIT_NOTES_DISPLAY_REF
+unset GIT_NOTES_REWRITE_REF
+unset GIT_NOTES_REWRITE_MODE
 GIT_MERGE_VERBOSITY=5
 export GIT_MERGE_VERBOSITY
 export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
 export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME
-export EDITOR VISUAL
-GIT_TEST_CMP=${GIT_TEST_CMP:-diff -u}
+export EDITOR
 
 # Protect ourselves from common misconfiguration to export
 # CDPATH into the environment
 unset CDPATH
 
+unset GREP_OPTIONS
+
 case $(echo $GIT_TRACE |tr "[A-Z]" "[a-z]") in
        1|2|true)
                echo "* warning: Some tests will not work if GIT_TRACE" \
@@ -74,6 +91,12 @@ case $(echo $GIT_TRACE |tr "[A-Z]" "[a-z]") in
                ;;
 esac
 
+# Convenience
+#
+# A regexp to match 5 and 40 hexdigits
+_x05='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
+_x40="$_x05$_x05$_x05$_x05$_x05$_x05$_x05$_x05"
+
 # Each test should start with something like this, after copyright notices:
 #
 # test_description='Description of this test...
@@ -104,18 +127,22 @@ do
        -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
                verbose=t; shift ;;
        -q|--q|--qu|--qui|--quie|--quiet)
-               quiet=t; shift ;;
+               # Ignore --quiet under a TAP::Harness. Saying how many tests
+               # passed without the ok/not ok details is always an error.
+               test -z "$HARNESS_ACTIVE" && quiet=t; shift ;;
+       --with-dashes)
+               with_dashes=t; shift ;;
        --no-color)
                color=; shift ;;
-       --no-python)
-               # noop now...
-               shift ;;
        --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind)
                valgrind=t; verbose=t; shift ;;
        --tee)
                shift ;; # was handled already
+       --root=*)
+               root=$(expr "z$1" : 'z[^=]*=\(.*\)')
+               shift ;;
        *)
-               break ;;
+               echo "error: unknown test option '$1'" >&2; exit 1 ;;
        esac
 done
 
@@ -132,7 +159,7 @@ if test -n "$color"; then
                        *) test -n "$quiet" && return;;
                esac
                shift
-               printf "%s" "$*"
+               printf "%s" "$*"
                tput sgr0
                echo
                )
@@ -141,13 +168,13 @@ else
        say_color() {
                test -z "$1" && test -n "$quiet" && return
                shift
-               echo "$*"
+               echo "$*"
        }
 fi
 
 error () {
        say_color error "error: $*"
-       trap - EXIT
+       GIT_EXIT_OK=t
        exit 1
 }
 
@@ -178,11 +205,20 @@ test_fixed=0
 test_broken=0
 test_success=0
 
+test_external_has_tap=0
+
 die () {
-       echo >&5 "FATAL: Unexpected exit with code $?"
-       exit 1
+       code=$?
+       if test -n "$GIT_EXIT_OK"
+       then
+               exit $code
+       else
+               echo >&5 "FATAL: Unexpected exit with code $code"
+               exit 1
+       fi
 }
 
+GIT_EXIT_OK=
 trap 'die' EXIT
 
 # The semantics of the editor variables are that of invoking
@@ -197,8 +233,39 @@ trap 'die' EXIT
 test_set_editor () {
        FAKE_EDITOR="$1"
        export FAKE_EDITOR
-       VISUAL='"$FAKE_EDITOR"'
-       export VISUAL
+       EDITOR='"$FAKE_EDITOR"'
+       export EDITOR
+}
+
+test_decode_color () {
+       sed     -e 's/.\[1m/<WHITE>/g' \
+               -e 's/.\[31m/<RED>/g' \
+               -e 's/.\[32m/<GREEN>/g' \
+               -e 's/.\[33m/<YELLOW>/g' \
+               -e 's/.\[34m/<BLUE>/g' \
+               -e 's/.\[35m/<MAGENTA>/g' \
+               -e 's/.\[36m/<CYAN>/g' \
+               -e 's/.\[m/<RESET>/g'
+}
+
+q_to_nul () {
+       perl -pe 'y/Q/\000/'
+}
+
+q_to_cr () {
+       tr Q '\015'
+}
+
+q_to_tab () {
+       tr Q '\011'
+}
+
+append_cr () {
+       sed -e 's/$/Q/' | tr Q '\015'
+}
+
+remove_cr () {
+       tr '\015' Q | sed -e 's/Q$//'
 }
 
 test_tick () {
@@ -264,12 +331,35 @@ test_set_prereq () {
 satisfied=" "
 
 test_have_prereq () {
-       case $satisfied in
-       *" $1 "*)
-               : yes, have it ;;
-       *)
-               ! : nope ;;
-       esac
+       # prerequisites can be concatenated with ','
+       save_IFS=$IFS
+       IFS=,
+       set -- $*
+       IFS=$save_IFS
+
+       total_prereq=0
+       ok_prereq=0
+       missing_prereq=
+
+       for prerequisite
+       do
+               total_prereq=$(($total_prereq + 1))
+               case $satisfied in
+               *" $prerequisite "*)
+                       ok_prereq=$(($ok_prereq + 1))
+                       ;;
+               *)
+                       # Keep a list of missing prerequisites
+                       if test -z "$missing_prereq"
+                       then
+                               missing_prereq=$prerequisite
+                       else
+                               missing_prereq="$prerequisite,$missing_prereq"
+                       fi
+               esac
+       done
+
+       test $total_prereq = $ok_prereq
 }
 
 # You are not expected to call test_ok_ and test_failure_ directly, use
@@ -277,25 +367,25 @@ test_have_prereq () {
 
 test_ok_ () {
        test_success=$(($test_success + 1))
-       say_color "" "  ok $test_count: $@"
+       say_color "" "ok $test_count - $@"
 }
 
 test_failure_ () {
        test_failure=$(($test_failure + 1))
-       say_color error "FAIL $test_count: $1"
+       say_color error "not ok - $test_count $1"
        shift
-       echo "$@" | sed -e 's/^/        /'
-       test "$immediate" = "" || { trap - EXIT; exit 1; }
+       echo "$@" | sed -e 's/^/#       /'
+       test "$immediate" = "" || { GIT_EXIT_OK=t; exit 1; }
 }
 
 test_known_broken_ok_ () {
        test_fixed=$(($test_fixed+1))
-       say_color "" "  FIXED $test_count: $@"
+       say_color "" "ok $test_count - $@ # TODO known breakage"
 }
 
 test_known_broken_failure_ () {
        test_broken=$(($test_broken+1))
-       say_color skip "  still broken $test_count: $@"
+       say_color skip "not ok $test_count - $@ # TODO known breakage"
 }
 
 test_debug () {
@@ -303,8 +393,13 @@ test_debug () {
 }
 
 test_run_ () {
+       test_cleanup=:
        eval >&3 2>&4 "$1"
-       eval_ret="$?"
+       eval_ret=$?
+       eval >&3 2>&4 "$test_cleanup"
+       if test "$verbose" = "t" && test -n "$HARNESS_ACTIVE"; then
+               echo ""
+       fi
        return 0
 }
 
@@ -316,6 +411,7 @@ test_skip () {
                case $this_test.$test_count in
                $skp)
                        to_skip=t
+                       break
                esac
        done
        if test -z "$to_skip" && test -n "$prereq" &&
@@ -325,8 +421,14 @@ test_skip () {
        fi
        case "$to_skip" in
        t)
+               of_prereq=
+               if test "$missing_prereq" != "$prereq"
+               then
+                       of_prereq=" of $prereq"
+               fi
+
                say_color skip >&3 "skipping test: $@"
-               say_color skip "skip $test_count: $1"
+               say_color skip "ok $test_count # skip $1 (missing $missing_prereq${of_prereq})"
                : true
                ;;
        *)
@@ -347,7 +449,7 @@ test_expect_failure () {
                then
                        test_known_broken_ok_ "$1"
                else
-                   test_known_broken_failure_ "$1"
+                       test_known_broken_failure_ "$1"
                fi
        fi
        echo >&3 ""
@@ -392,7 +494,7 @@ test_expect_code () {
 # test_external runs external test scripts that provide continuous
 # test output about their progress, and succeeds/fails on
 # zero/non-zero exit code.  It outputs the test output on stdout even
-# in non-verbose mode, and announces the external script with "* run
+# in non-verbose mode, and announces the external script with "# run
 # <n>: ..." before running it.  When providing relative paths, keep in
 # mind that all scripts run in "trash directory".
 # Usage: test_external description command arguments...
@@ -407,16 +509,29 @@ test_external () {
        then
                # Announce the script to reduce confusion about the
                # test output that follows.
-               say_color "" " run $test_count: $descr ($*)"
+               say_color "" "# run $test_count: $descr ($*)"
+               # Export TEST_DIRECTORY, TRASH_DIRECTORY and GIT_TEST_LONG
+               # to be able to use them in script
+               export TEST_DIRECTORY TRASH_DIRECTORY GIT_TEST_LONG
                # Run command; redirect its stderr to &4 as in
                # test_run_, but keep its stdout on our stdout even in
                # non-verbose mode.
                "$@" 2>&4
                if [ "$?" = 0 ]
                then
-                       test_ok_ "$descr"
+                       if test $test_external_has_tap -eq 0; then
+                               test_ok_ "$descr"
+                       else
+                               say_color "" "# test_external test $descr was ok"
+                               test_success=$(($test_success + 1))
+                       fi
                else
-                       test_failure_ "$descr" "$@"
+                       if test $test_external_has_tap -eq 0; then
+                               test_failure_ "$descr" "$@"
+                       else
+                               say_color error "# test_external test $descr failed: $@"
+                               test_failure=$(($test_failure + 1))
+                       fi
                fi
        fi
 }
@@ -432,22 +547,65 @@ test_external_without_stderr () {
        [ -f "$stderr" ] || error "Internal error: $stderr disappeared."
        descr="no stderr: $1"
        shift
-       say >&3 "expecting no stderr from previous command"
+       say >&3 "expecting no stderr from previous command"
        if [ ! -s "$stderr" ]; then
                rm "$stderr"
-               test_ok_ "$descr"
+
+               if test $test_external_has_tap -eq 0; then
+                       test_ok_ "$descr"
+               else
+                       say_color "" "# test_external_without_stderr test $descr was ok"
+                       test_success=$(($test_success + 1))
+               fi
        else
                if [ "$verbose" = t ]; then
-                       output=`echo; echo Stderr is:; cat "$stderr"`
+                       output=`echo; echo "# Stderr is:"; cat "$stderr"`
                else
                        output=
                fi
                # rm first in case test_failure exits.
                rm "$stderr"
-               test_failure_ "$descr" "$@" "$output"
+               if test $test_external_has_tap -eq 0; then
+                       test_failure_ "$descr" "$@" "$output"
+               else
+                       say_color error "# test_external_without_stderr test $descr failed: $@: $output"
+                       test_failure=$(($test_failure + 1))
+               fi
        fi
 }
 
+# debugging-friendly alternatives to "test [-f|-d|-e]"
+# The commands test the existence or non-existence of $1. $2 can be
+# given to provide a more precise diagnosis.
+test_path_is_file () {
+       if ! [ -f "$1" ]
+       then
+               echo "File $1 doesn't exist. $*"
+               false
+       fi
+}
+
+test_path_is_dir () {
+       if ! [ -d "$1" ]
+       then
+               echo "Directory $1 doesn't exist. $*"
+               false
+       fi
+}
+
+test_path_is_missing () {
+       if [ -e "$1" ]
+       then
+               echo "Path exists:"
+               ls -ld "$1"
+               if [ $# -ge 1 ]; then
+                       echo "$*"
+               fi
+               false
+       fi
+}
+
+
 # This is not among top-level (test_expect_success | test_expect_failure)
 # but is a prefix that can be used in the test script, like:
 #
@@ -462,7 +620,42 @@ test_external_without_stderr () {
 
 test_must_fail () {
        "$@"
-       test $? -gt 0 -a $? -le 129 -o $? -gt 192
+       exit_code=$?
+       if test $exit_code = 0; then
+               echo >&2 "test_must_fail: command succeeded: $*"
+               return 1
+       elif test $exit_code -gt 129 -a $exit_code -le 192; then
+               echo >&2 "test_must_fail: died by signal: $*"
+               return 1
+       elif test $exit_code = 127; then
+               echo >&2 "test_must_fail: command not found: $*"
+               return 1
+       fi
+       return 0
+}
+
+# Similar to test_must_fail, but tolerates success, too.  This is
+# meant to be used in contexts like:
+#
+#      test_expect_success 'some command works without configuration' '
+#              test_might_fail git config --unset all.configuration &&
+#              do something
+#      '
+#
+# Writing "git config --unset all.configuration || :" would be wrong,
+# because we want to notice if it fails due to segv.
+
+test_might_fail () {
+       "$@"
+       exit_code=$?
+       if test $exit_code -gt 129 -a $exit_code -le 192; then
+               echo >&2 "test_might_fail: died by signal: $*"
+               return 1
+       elif test $exit_code = 127; then
+               echo >&2 "test_might_fail: command not found: $*"
+               return 1
+       fi
+       return 0
 }
 
 # test_cmp is a helper function to compare actual and expected output.
@@ -482,48 +675,82 @@ test_cmp() {
        $GIT_TEST_CMP "$@"
 }
 
+# This function can be used to schedule some commands to be run
+# unconditionally at the end of the test to restore sanity:
+#
+#      test_expect_success 'test core.capslock' '
+#              git config core.capslock true &&
+#              test_when_finished "git config --unset core.capslock" &&
+#              hello world
+#      '
+#
+# That would be roughly equivalent to
+#
+#      test_expect_success 'test core.capslock' '
+#              git config core.capslock true &&
+#              hello world
+#              git config --unset core.capslock
+#      '
+#
+# except that the greeting and config --unset must both succeed for
+# the test to pass.
+
+test_when_finished () {
+       test_cleanup="{ $*
+               } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup"
+}
+
 # Most tests can use the created repository, but some may 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 -p "$repo"
-       cd "$repo" || error "Cannot setup test environment"
-       "$GIT_EXEC_PATH/git-init" "--template=$TEST_DIRECTORY/../templates/blt/" >&3 2>&4 ||
-       error "cannot run git init -- have you built things yet?"
-       mv .git/hooks .git/hooks-disabled
-       cd "$owd"
+       (
+               cd "$repo" || error "Cannot setup test environment"
+               "$GIT_EXEC_PATH/git-init" "--template=$GIT_BUILD_DIR/templates/blt/" >&3 2>&4 ||
+               error "cannot run git init -- have you built things yet?"
+               mv .git/hooks .git/hooks-disabled
+       ) || exit
 }
 
 test_done () {
-       trap - EXIT
-       test_results_dir="$TEST_DIRECTORY/test-results"
-       mkdir -p "$test_results_dir"
-       test_results_path="$test_results_dir/${0%.sh}-$$"
-
-       echo "total $test_count" >> $test_results_path
-       echo "success $test_success" >> $test_results_path
-       echo "fixed $test_fixed" >> $test_results_path
-       echo "broken $test_broken" >> $test_results_path
-       echo "failed $test_failure" >> $test_results_path
-       echo "" >> $test_results_path
+       GIT_EXIT_OK=t
+
+       if test -z "$HARNESS_ACTIVE"; then
+               test_results_dir="$TEST_DIRECTORY/test-results"
+               mkdir -p "$test_results_dir"
+               test_results_path="$test_results_dir/${0%.sh}-$$.counts"
+
+               echo "total $test_count" >> $test_results_path
+               echo "success $test_success" >> $test_results_path
+               echo "fixed $test_fixed" >> $test_results_path
+               echo "broken $test_broken" >> $test_results_path
+               echo "failed $test_failure" >> $test_results_path
+               echo "" >> $test_results_path
+       fi
 
        if test "$test_fixed" != 0
        then
-               say_color pass "fixed $test_fixed known breakage(s)"
+               say_color pass "fixed $test_fixed known breakage(s)"
        fi
        if test "$test_broken" != 0
        then
-               say_color error "still have $test_broken known breakage(s)"
+               say_color error "still have $test_broken known breakage(s)"
                msg="remaining $(($test_count-$test_broken)) test(s)"
        else
                msg="$test_count test(s)"
        fi
        case "$test_failure" in
        0)
-               say_color pass "passed all $msg"
+               # Maybe print SKIP message
+               [ -z "$skip_all" ] || skip_all=" # SKIP $skip_all"
+
+               if test $test_external_has_tap -eq 0; then
+                       say_color pass "# passed all $msg"
+                       say "1..$test_count$skip_all"
+               fi
 
                test -d "$remove_trash" &&
                cd "$(dirname "$remove_trash")" &&
@@ -532,7 +759,11 @@ test_done () {
                exit 0 ;;
 
        *)
-               say_color error "failed $test_failure among $msg"
+               if test $test_external_has_tap -eq 0; then
+                       say_color error "# failed $test_failure among $msg"
+                       say "1..$test_count"
+               fi
+
                exit 1 ;;
 
        esac
@@ -540,20 +771,17 @@ test_done () {
 
 # Test the binaries we have just built.  The tests are kept in
 # t/ subdirectory and are run in 'trash directory' subdirectory.
-TEST_DIRECTORY=$(pwd)
-if test -z "$valgrind"
+if test -z "$TEST_DIRECTORY"
+then
+       # We allow tests to override this, in case they want to run tests
+       # outside of t/, e.g. for running tests on the test library
+       # itself.
+       TEST_DIRECTORY=$(pwd)
+fi
+GIT_BUILD_DIR="$TEST_DIRECTORY"/..
+
+if test -n "$valgrind"
 then
-       if test -z "$GIT_TEST_INSTALLED"
-       then
-               PATH=$TEST_DIRECTORY/..:$PATH
-               GIT_EXEC_PATH=$TEST_DIRECTORY/..
-       else
-               GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path)  ||
-               error "Cannot run git from $GIT_TEST_INSTALLED."
-               PATH=$GIT_TEST_INSTALLED:$TEST_DIRECTORY/..:$PATH
-               GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH}
-       fi
-else
        make_symlink () {
                test -h "$2" &&
                test "$1" = "$(readlink "$2")" || {
@@ -578,7 +806,7 @@ else
                test -x "$1" || return
 
                base=$(basename "$1")
-               symlink_target=$TEST_DIRECTORY/../$base
+               symlink_target=$GIT_BUILD_DIR/$base
                # do not override scripts
                if test -x "$symlink_target" &&
                    test ! -d "$symlink_target" &&
@@ -597,7 +825,7 @@ else
        # override all git executables in TEST_DIRECTORY/..
        GIT_VALGRIND=$TEST_DIRECTORY/valgrind
        mkdir -p "$GIT_VALGRIND"/bin
-       for file in $TEST_DIRECTORY/../git* $TEST_DIRECTORY/../test-*
+       for file in $GIT_BUILD_DIR/git* $GIT_BUILD_DIR/test-*
        do
                make_valgrind_symlink $file
        done
@@ -615,32 +843,74 @@ else
        PATH=$GIT_VALGRIND/bin:$PATH
        GIT_EXEC_PATH=$GIT_VALGRIND/bin
        export GIT_VALGRIND
+elif test -n "$GIT_TEST_INSTALLED" ; then
+       GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path)  ||
+       error "Cannot run git from $GIT_TEST_INSTALLED."
+       PATH=$GIT_TEST_INSTALLED:$GIT_BUILD_DIR:$PATH
+       GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH}
+else # normal case, use ../bin-wrappers only unless $with_dashes:
+       git_bin_dir="$GIT_BUILD_DIR/bin-wrappers"
+       if ! test -x "$git_bin_dir/git" ; then
+               if test -z "$with_dashes" ; then
+                       say "$git_bin_dir/git is not executable; using GIT_EXEC_PATH"
+               fi
+               with_dashes=t
+       fi
+       PATH="$git_bin_dir:$PATH"
+       GIT_EXEC_PATH=$GIT_BUILD_DIR
+       if test -n "$with_dashes" ; then
+               PATH="$GIT_BUILD_DIR:$PATH"
+       fi
 fi
-GIT_TEMPLATE_DIR=$(pwd)/../templates/blt
+GIT_TEMPLATE_DIR="$GIT_BUILD_DIR"/templates/blt
 unset GIT_CONFIG
 GIT_CONFIG_NOSYSTEM=1
 GIT_CONFIG_NOGLOBAL=1
 export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_CONFIG_NOGLOBAL
 
-GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git
+. "$GIT_BUILD_DIR"/GIT-BUILD-OPTIONS
+
+if test -z "$GIT_TEST_CMP"
+then
+       if test -n "$GIT_TEST_CMP_USE_COPIED_CONTEXT"
+       then
+               GIT_TEST_CMP="$DIFF -c"
+       else
+               GIT_TEST_CMP="$DIFF -u"
+       fi
+fi
+
+GITPERLLIB="$GIT_BUILD_DIR"/perl/blib/lib:"$GIT_BUILD_DIR"/perl/blib/arch/auto/Git
 export GITPERLLIB
-test -d ../templates/blt || {
+test -d "$GIT_BUILD_DIR"/templates/blt || {
        error "You haven't built things yet, have you?"
 }
 
-if ! test -x ../test-chmtime; then
+if test -z "$GIT_TEST_INSTALLED" && test -z "$NO_PYTHON"
+then
+       GITPYTHONLIB="$GIT_BUILD_DIR/git_remote_helpers/build/lib"
+       export GITPYTHONLIB
+       test -d "$GIT_BUILD_DIR"/git_remote_helpers/build || {
+               error "You haven't built git_remote_helpers yet, have you?"
+       }
+fi
+
+if ! test -x "$GIT_BUILD_DIR"/test-chmtime; then
        echo >&2 'You need to build test-chmtime:'
        echo >&2 'Run "make test-chmtime" in the source (toplevel) directory'
        exit 1
 fi
 
-. ../GIT-BUILD-OPTIONS
-
 # Test repository
 test="trash directory.$(basename "$0" .sh)"
-test ! -z "$debug" || remove_trash="$TEST_DIRECTORY/$test"
+test -n "$root" && test="$root/$test"
+case "$test" in
+/*) TRASH_DIRECTORY="$test" ;;
+ *) TRASH_DIRECTORY="$TEST_DIRECTORY/$test" ;;
+esac
+test ! -z "$debug" || remove_trash=$TRASH_DIRECTORY
 rm -fr "$test" || {
-       trap - EXIT
+       GIT_EXIT_OK=t
        echo >&5 "FATAL: Cannot prepare test area"
        exit 1
 }
@@ -650,26 +920,36 @@ test_create_repo "$test"
 # in subprocesses like git equals our $PWD (for pathname comparisons).
 cd -P "$test" || exit 1
 
+HOME=$(pwd)
+export HOME
+
 this_test=${0##*/}
 this_test=${this_test%%-*}
 for skp in $GIT_SKIP_TESTS
 do
-       to_skip=
-       for skp in $GIT_SKIP_TESTS
-       do
-               case "$this_test" in
-               $skp)
-                       to_skip=t
-               esac
-       done
-       case "$to_skip" in
-       t)
+       case "$this_test" in
+       $skp)
                say_color skip >&3 "skipping test $this_test altogether"
-               say_color skip "skip all tests in $this_test"
+               skip_all="skip all tests in $this_test"
                test_done
        esac
 done
 
+# Provide an implementation of the 'yes' utility
+yes () {
+       if test $# = 0
+       then
+               y=y
+       else
+               y="$*"
+       fi
+
+       while echo "$y"
+       do
+               :
+       done
+}
+
 # Fix some commands on Windows
 case $(uname -s) in
 *MINGW*)
@@ -699,7 +979,12 @@ case $(uname -s) in
 esac
 
 test -z "$NO_PERL" && test_set_prereq PERL
+test -z "$NO_PYTHON" && test_set_prereq PYTHON
 
 # test whether the filesystem supports symbolic links
 ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS
 rm -f y
+
+# When the tests are run as root, permission tests will report that
+# things are writable when they shouldn't be.
+test -w / || test_set_prereq SANITY
diff --git a/tag.c b/tag.c
index 4470d2bf78e1fbb00d00e487f41daa4373cf48e1..28641cf85a0d32357269129f0e8b92bb9e21f399 100644 (file)
--- a/tag.c
+++ b/tag.c
@@ -28,51 +28,58 @@ struct tag *lookup_tag(const unsigned char *sha1)
                return create_object(sha1, OBJ_TAG, alloc_tag_node());
        if (!obj->type)
                obj->type = OBJ_TAG;
-        if (obj->type != OBJ_TAG) {
-                error("Object %s is a %s, not a tag",
-                      sha1_to_hex(sha1), typename(obj->type));
-                return NULL;
-        }
-        return (struct tag *) obj;
+       if (obj->type != OBJ_TAG) {
+               error("Object %s is a %s, not a tag",
+                     sha1_to_hex(sha1), typename(obj->type));
+               return NULL;
+       }
+       return (struct tag *) obj;
+}
+
+static unsigned long parse_tag_date(const char *buf, const char *tail)
+{
+       const char *dateptr;
+
+       while (buf < tail && *buf++ != '>')
+               /* nada */;
+       if (buf >= tail)
+               return 0;
+       dateptr = buf;
+       while (buf < tail && *buf++ != '\n')
+               /* nada */;
+       if (buf >= tail)
+               return 0;
+       /* dateptr < buf && buf[-1] == '\n', so strtoul will stop at buf-1 */
+       return strtoul(dateptr, NULL, 10);
 }
 
 int parse_tag_buffer(struct tag *item, void *data, unsigned long size)
 {
-       int typelen, taglen;
        unsigned char sha1[20];
-       const char *type_line, *tag_line, *sig_line;
        char type[20];
-       const char *start = data;
+       const char *bufptr = data;
+       const char *tail = bufptr + size;
+       const char *nl;
 
-        if (item->object.parsed)
-                return 0;
-        item->object.parsed = 1;
+       if (item->object.parsed)
+               return 0;
+       item->object.parsed = 1;
 
        if (size < 64)
                return -1;
-       if (memcmp("object ", data, 7) || get_sha1_hex((char *) data + 7, sha1))
+       if (memcmp("object ", bufptr, 7) || get_sha1_hex(bufptr + 7, sha1) || bufptr[47] != '\n')
                return -1;
+       bufptr += 48; /* "object " + sha1 + "\n" */
 
-       type_line = (char *) data + 48;
-       if (memcmp("\ntype ", type_line-1, 6))
+       if (prefixcmp(bufptr, "type "))
                return -1;
-
-       tag_line = memchr(type_line, '\n', size - (type_line - start));
-       if (!tag_line || memcmp("tag ", ++tag_line, 4))
-               return -1;
-
-       sig_line = memchr(tag_line, '\n', size - (tag_line - start));
-       if (!sig_line)
-               return -1;
-       sig_line++;
-
-       typelen = tag_line - type_line - strlen("type \n");
-       if (typelen >= 20)
+       bufptr += 5;
+       nl = memchr(bufptr, '\n', tail - bufptr);
+       if (!nl || sizeof(type) <= (nl - bufptr))
                return -1;
-       memcpy(type, type_line + 5, typelen);
-       type[typelen] = '\0';
-       taglen = sig_line - tag_line - strlen("tag \n");
-       item->tag = xmemdupz(tag_line + 4, taglen);
+       strncpy(type, bufptr, nl - bufptr);
+       type[nl - bufptr] = '\0';
+       bufptr = nl + 1;
 
        if (!strcmp(type, blob_type)) {
                item->tagged = &lookup_blob(sha1)->object;
@@ -87,6 +94,20 @@ int parse_tag_buffer(struct tag *item, void *data, unsigned long size)
                item->tagged = NULL;
        }
 
+       if (prefixcmp(bufptr, "tag "))
+               return -1;
+       bufptr += 4;
+       nl = memchr(bufptr, '\n', tail - bufptr);
+       if (!nl)
+               return -1;
+       item->tag = xmemdupz(bufptr, nl - bufptr);
+       bufptr = nl + 1;
+
+       if (!prefixcmp(bufptr, "tagger "))
+               item->date = parse_tag_date(bufptr, tail);
+       else
+               item->date = 0;
+
        return 0;
 }
 
diff --git a/tag.h b/tag.h
index 7a0cb0070d46ba8c49d71029dc0704188805ea62..47662724a6d7d07eeeacd5c8528d94d750ecf878 100644 (file)
--- a/tag.h
+++ b/tag.h
@@ -9,7 +9,7 @@ struct tag {
        struct object object;
        struct object *tagged;
        char *tag;
-       char *signature; /* not actually implemented */
+       unsigned long date;
 };
 
 extern struct tag *lookup_tag(const unsigned char *sha1);
index a12c6e214e65d39136b1ed41a8ff0ea25e28f91b..d22a71a3999b3dc0e99f5e36053447b33f967bd7 100644 (file)
@@ -11,6 +11,16 @@ prefix ?= $(HOME)
 template_instdir ?= $(prefix)/share/git-core/templates
 # DESTDIR=
 
+ifndef SHELL_PATH
+       SHELL_PATH = /bin/sh
+endif
+ifndef PERL_PATH
+       PERL_PATH = perl
+endif
+
+SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
+PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
+
 # Shell quote (do not use $(call) to accommodate ancient setups);
 DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
 template_instdir_SQ = $(subst ','\'',$(template_instdir))
@@ -33,8 +43,11 @@ boilerplates.made : $(bpsrc)
                case "$$boilerplate" in \
                *--) continue;; \
                esac && \
-               cp $$boilerplate blt/$$dst && \
-               if test -x "blt/$$dst"; then rx=rx; else rx=r; fi && \
+               sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
+                   -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \
+                   -e 's|@PERL_PATH@|$(PERL_PATH_SQ)|g' $$boilerplate > \
+                       blt/$$dst && \
+               if test -x "$$boilerplate"; then rx=rx; else rx=r; fi && \
                chmod a+$$rx "blt/$$dst" || exit; \
        done && \
        date >$@
@@ -50,4 +63,4 @@ clean:
 install: all
        $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(template_instdir_SQ)'
        (cd blt && $(TAR) cf - .) | \
-       (cd '$(DESTDIR_SQ)$(template_instdir_SQ)' && umask 022 && $(TAR) xfo -)
+       (cd '$(DESTDIR_SQ)$(template_instdir_SQ)' && umask 022 && $(TAR) xof -)
index 6ef1d29d09a10a5b6c3cbec0ac481931cd0d85fc..b58d1184a9d43a39c0d95f32453efc78581877d6 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 #
 # An example hook script to check the commit log message.
-# Called by git-commit with one argument, the name of the file
+# 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.
index 18d2e0f72768c103d593cc2cf6d2b7a4bc8a9a01..7a83e17ab5f03cdc8284be789e07721d24e8d389 100755 (executable)
@@ -9,7 +9,7 @@
 # For example:
 #  aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
 #
-# see contrib/hooks/ for an sample, or uncomment the next line and
+# see contrib/hooks/ for a sample, or uncomment the next line and
 # rename the file to "post-receive".
 
 #. /usr/share/doc/git-core/contrib/hooks/post-receive-email
index 5323b56b81b9dd3d7f9fb86d8892241becbb5e7e..ec17ec1939b7c3e86b7cb6c0c4de6b0818a7e75e 100755 (executable)
@@ -5,4 +5,4 @@
 #
 # To enable this hook, rename this file to "post-update".
 
-exec git-update-server-info
+exec git update-server-info
index 0e49279c7f7b805c78f1bc4760a0d1c70a84a0d9..b187c4bb1f256e19c25f80dd64f3451ced77e123 100755 (executable)
@@ -1,13 +1,13 @@
 #!/bin/sh
 #
 # An example hook script to verify what is about to be committed.
-# Called by git-commit with no arguments.  The hook should
+# 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, rename this file to "pre-commit".
 
-if git-rev-parse --verify HEAD 2>/dev/null
+if git rev-parse --verify HEAD >/dev/null 2>&1
 then
        against=HEAD
 else
@@ -15,4 +15,32 @@ else
        against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
 fi
 
+# If you want to allow non-ascii filenames set this variable to true.
+allownonascii=$(git config hooks.allownonascii)
+
+# Cross platform projects tend to avoid non-ascii filenames; prevent
+# them from being added to the repository. We exploit the fact that the
+# printable range starts at the space character and ends with tilde.
+if [ "$allownonascii" != "true" ] &&
+       # Note that the use of brackets around a tr range is ok here, (it's
+       # even required, for portability to Solaris 10's /usr/bin/tr), since
+       # the square bracket bytes happen to fall in the designated range.
+       test "$(git diff --cached --name-only --diff-filter=A -z $against |
+         LC_ALL=C tr -d '[ -~]\0')"
+then
+       echo "Error: Attempt to add a non-ascii file name."
+       echo
+       echo "This can cause problems if you want to work"
+       echo "with people on other platforms."
+       echo
+       echo "To be portable it is advisable to rename the file ..."
+       echo
+       echo "If you know what you are doing you can disable this"
+       echo "check using:"
+       echo
+       echo "  git config hooks.allownonascii true"
+       echo
+       exit 1
+fi
+
 exec git diff-index --check --cached $against --
index be1b06e25043146f22261b55548229e6ab524b7c..053f1111c0d734c057e895cdc992188104cc4f84 100755 (executable)
@@ -2,7 +2,7 @@
 #
 # Copyright (c) 2006, 2008 Junio C Hamano
 #
-# The "pre-rebase" hook is run just before "git-rebase" starts doing
+# The "pre-rebase" hook is run just before "git rebase" starts doing
 # its job, and can prevent the command from running by exiting with
 # non-zero status.
 #
@@ -43,7 +43,7 @@ git show-ref -q "$topic" || {
 }
 
 # Is topic fully merged to master?
-not_in_master=`git-rev-list --pretty=oneline ^master "$topic"`
+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."
@@ -51,11 +51,11 @@ then
 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`
+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`
+       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"
@@ -64,8 +64,8 @@ then
                exit 0
        fi
 else
-       not_in_next=`git-rev-list --pretty=oneline ^${publish} "$topic"`
-       perl -e '
+       not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
+       @PERL_PATH@ -e '
                my $topic = $ARGV[0];
                my $msg = "* $topic has commits already merged to public branch:\n";
                my (%not_in_next) = map {
@@ -157,13 +157,13 @@ B to be deleted.
 
 To compute (1):
 
-       git-rev-list ^master ^topic next
-       git-rev-list ^master        next
+       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
+       git rev-list master..topic
 
        if this is empty, it is fully merged to "master".
index 365242499dcf0ee35c26ccb2917724d6e559be69..86b8f227ecab874a4af98bc5ba3164d370e5e4b5 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 #
 # An example hook script to prepare the commit log message.
-# Called by git-commit with the name of the file that has the
+# Called by "git commit" with the name of the file that has the
 # commit message, followed by the description of the commit
 # message's source.  The hook's purpose is to edit the commit
 # message file.  If the hook fails with a non-zero status,
 
 case "$2,$3" in
   merge,)
-    perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;;
+    @PERL_PATH@ -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;;
 
 # ,|template,)
-#   perl -i.bak -pe '
+#   @PERL_PATH@ -i.bak -pe '
 #      print "\n" . `git diff --cached --name-status -r`
 #       if /^#/ && $first++ == 0' "$1" ;;
 
index f8bf490cffa2d41d19ffc9a31839ca3c2686df16..71ab04edc09be7aeefa1e8a0f609a974ffd55a9f 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 #
 # An example hook script to blocks unannotated tags from entering.
-# Called by git-receive-pack with arguments: refname sha1-old sha1-new
+# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
 #
 # To enable this hook, rename this file to "update".
 #
@@ -13,6 +13,9 @@
 # hooks.allowdeletetag
 #   This boolean sets whether deleting tags will be allowed in the
 #   repository.  By default they won't be.
+# hooks.allowmodifytag
+#   This boolean sets whether a tag may be modified after creation. By default
+#   it won't be.
 # hooks.allowdeletebranch
 #   This boolean sets whether deleting branches will be allowed in the
 #   repository.  By default they won't be.
@@ -44,6 +47,7 @@ allowunannotated=$(git config --bool hooks.allowunannotated)
 allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
 denycreatebranch=$(git config --bool hooks.denycreatebranch)
 allowdeletetag=$(git config --bool hooks.allowdeletetag)
+allowmodifytag=$(git config --bool hooks.allowmodifytag)
 
 # check for no description
 projectdesc=$(sed -e '1q' "$GIT_DIR/description")
@@ -60,7 +64,7 @@ zero="0000000000000000000000000000000000000000"
 if [ "$newrev" = "$zero" ]; then
        newrev_type=delete
 else
-       newrev_type=$(git-cat-file -t $newrev)
+       newrev_type=$(git cat-file -t $newrev)
 fi
 
 case "$refname","$newrev_type" in
@@ -82,6 +86,12 @@ case "$refname","$newrev_type" in
                ;;
        refs/tags/*,tag)
                # annotated tag
+               if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
+               then
+                       echo "*** Tag '$refname' already exists." >&2
+                       echo "*** Modifying a tag is not allowed in this repository." >&2
+                       exit 1
+               fi
                ;;
        refs/heads/*,commit)
                # branch
index 2c87b72dff61f8394b3f1f32e21c1d936314ec2e..a5196d1be8fb59edf8062bef36d3a602e0812139 100644 (file)
@@ -1,4 +1,4 @@
-# git-ls-files --others --exclude-from=.git/info/exclude
+# 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):
index d5358cbaac2022483b74366555fc9707a7d8ad97..92713d16da5431e9c8395967ab0e116402dfed67 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This program can either change modification time of the given
  * file(s) or just print it. The program does not change atime nor
- * ctime (their values are explicitely preserved).
+ * ctime (their values are explicitly preserved).
  *
  * The mtime can be changed to an absolute value:
  *
@@ -87,6 +87,15 @@ int main(int argc, const char *argv[])
                        return -1;
                }
 
+#ifdef WIN32
+               if (!(sb.st_mode & S_IWUSR) &&
+                               chmod(argv[i], sb.st_mode | S_IWUSR)) {
+                       fprintf(stderr, "Could not make user-writable %s: %s",
+                               argv[i], strerror(errno));
+                       return -1;
+               }
+#endif
+
                utb.actime = sb.st_atime;
                utb.modtime = set_eq ? set_time : sb.st_mtime + set_time;
 
index 62e8f2387a1cab97ec1c71d1993d082274e17bf5..6bcd5b03c078bc532e074ef8e8775a51d0eef219 100644 (file)
@@ -1,20 +1,70 @@
 #include "cache.h"
 
-int main(int argc, char **argv)
+static const char *usage_msg = "\n"
+"  test-date show [time_t]...\n"
+"  test-date parse [date]...\n"
+"  test-date approxidate [date]...\n";
+
+static void show_dates(char **argv, struct timeval *now)
 {
-       int i;
+       char buf[128];
+
+       for (; *argv; argv++) {
+               time_t t = atoi(*argv);
+               show_date_relative(t, 0, now, buf, sizeof(buf));
+               printf("%s -> %s\n", *argv, buf);
+       }
+}
 
-       for (i = 1; i < argc; i++) {
+static void parse_dates(char **argv, struct timeval *now)
+{
+       for (; *argv; argv++) {
                char result[100];
+               unsigned long t;
+               int tz;
+
+               result[0] = 0;
+               parse_date(*argv, result, sizeof(result));
+               if (sscanf(result, "%lu %d", &t, &tz) == 2)
+                       printf("%s -> %s\n",
+                              *argv, show_date(t, tz, DATE_ISO8601));
+               else
+                       printf("%s -> bad\n", *argv);
+       }
+}
+
+static void parse_approxidate(char **argv, struct timeval *now)
+{
+       for (; *argv; argv++) {
                time_t t;
+               t = approxidate_relative(*argv, now);
+               printf("%s -> %s\n", *argv, show_date(t, 0, DATE_ISO8601));
+       }
+}
 
-               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));
+int main(int argc, char **argv)
+{
+       struct timeval now;
+       const char *x;
 
-               t = approxidate(argv[i]);
-               printf("%s -> %s\n", argv[i], ctime(&t));
+       x = getenv("TEST_DATE_NOW");
+       if (x) {
+               now.tv_sec = atoi(x);
+               now.tv_usec = 0;
        }
+       else
+               gettimeofday(&now, NULL);
+
+       argv++;
+       if (!*argv)
+               usage(usage_msg);
+       if (!strcmp(*argv, "show"))
+               show_dates(argv+1, &now);
+       else if (!strcmp(*argv, "parse"))
+               parse_dates(argv+1, &now);
+       else if (!strcmp(*argv, "approxidate"))
+               parse_approxidate(argv+1, &now);
+       else
+               usage(usage_msg);
        return 0;
 }
index 3d885ff37ee7fc43dec05dd827679d68cee5516b..af40a3c49ee0df5f0caaebae8e9bc6e9ffdb092b 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * test-delta.c: test code to exercise diff-delta.c and patch-delta.c
  *
- * (C) 2005 Nicolas Pitre <nico@cam.org>
+ * (C) 2005 Nicolas Pitre <nico@fluxnic.net>
  *
  * 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
index 8ad276d062d6b96e2d80d3fcb44e046287f34bf7..b3c28d9a1c1102f2862af68b0bda7e9b0424f5cc 100644 (file)
@@ -4,8 +4,7 @@
  * Copyright (C) 2007 by Nicolas Pitre, licensed under the GPL version 2.
  */
 
-#include <stdio.h>
-#include <stdlib.h>
+#include "git-compat-util.h"
 
 int main(int argc, char *argv[])
 {
diff --git a/test-index-version.c b/test-index-version.c
new file mode 100644 (file)
index 0000000..bfaad9e
--- /dev/null
@@ -0,0 +1,14 @@
+#include "cache.h"
+
+int main(int argc, const char **argv)
+{
+       struct cache_header hdr;
+       int version;
+
+       memset(&hdr,0,sizeof(hdr));
+       if (read(0, &hdr, sizeof(hdr)) != sizeof(hdr))
+               return 0;
+       version = ntohl(hdr.hdr_version);
+       printf("%d\n", version);
+       return 0;
+}
diff --git a/test-line-buffer.c b/test-line-buffer.c
new file mode 100644 (file)
index 0000000..c11bf7f
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * test-line-buffer.c: code to exercise the svn importer's input helper
+ *
+ * Input format:
+ *     number NL
+ *     (number bytes) NL
+ *     number NL
+ *     ...
+ */
+
+#include "git-compat-util.h"
+#include "vcs-svn/line_buffer.h"
+
+static uint32_t strtouint32(const char *s)
+{
+       char *end;
+       uintmax_t n = strtoumax(s, &end, 10);
+       if (*s == '\0' || *end != '\0')
+               die("invalid count: %s", s);
+       return (uint32_t) n;
+}
+
+int main(int argc, char *argv[])
+{
+       char *s;
+
+       if (argc != 1)
+               usage("test-line-buffer < input.txt");
+       if (buffer_init(NULL))
+               die_errno("open error");
+       while ((s = buffer_read_line())) {
+               s = buffer_read_string(strtouint32(s));
+               fputs(s, stdout);
+               fputc('\n', stdout);
+               buffer_skip_bytes(1);
+               if (!(s = buffer_read_line()))
+                       break;
+               buffer_copy_bytes(strtouint32(s) + 1);
+       }
+       if (buffer_deinit())
+               die("input error");
+       if (ferror(stdout))
+               die("output error");
+       buffer_reset();
+       return 0;
+}
diff --git a/test-obj-pool.c b/test-obj-pool.c
new file mode 100644 (file)
index 0000000..5018863
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * test-obj-pool.c: code to exercise the svn importer's object pool
+ */
+
+#include "cache.h"
+#include "vcs-svn/obj_pool.h"
+
+enum pool { POOL_ONE, POOL_TWO };
+obj_pool_gen(one, int, 1)
+obj_pool_gen(two, int, 4096)
+
+static uint32_t strtouint32(const char *s)
+{
+       char *end;
+       uintmax_t n = strtoumax(s, &end, 10);
+       if (*s == '\0' || (*end != '\n' && *end != '\0'))
+               die("invalid offset: %s", s);
+       return (uint32_t) n;
+}
+
+static void handle_command(const char *command, enum pool pool, const char *arg)
+{
+       switch (*command) {
+       case 'a':
+               if (!prefixcmp(command, "alloc ")) {
+                       uint32_t n = strtouint32(arg);
+                       printf("%"PRIu32"\n",
+                               pool == POOL_ONE ?
+                               one_alloc(n) : two_alloc(n));
+                       return;
+               }
+       case 'c':
+               if (!prefixcmp(command, "commit ")) {
+                       pool == POOL_ONE ? one_commit() : two_commit();
+                       return;
+               }
+               if (!prefixcmp(command, "committed ")) {
+                       printf("%"PRIu32"\n",
+                               pool == POOL_ONE ?
+                               one_pool.committed : two_pool.committed);
+                       return;
+               }
+       case 'f':
+               if (!prefixcmp(command, "free ")) {
+                       uint32_t n = strtouint32(arg);
+                       pool == POOL_ONE ? one_free(n) : two_free(n);
+                       return;
+               }
+       case 'n':
+               if (!prefixcmp(command, "null ")) {
+                       printf("%"PRIu32"\n",
+                               pool == POOL_ONE ?
+                               one_offset(NULL) : two_offset(NULL));
+                       return;
+               }
+       case 'o':
+               if (!prefixcmp(command, "offset ")) {
+                       uint32_t n = strtouint32(arg);
+                       printf("%"PRIu32"\n",
+                               pool == POOL_ONE ?
+                               one_offset(one_pointer(n)) :
+                               two_offset(two_pointer(n)));
+                       return;
+               }
+       case 'r':
+               if (!prefixcmp(command, "reset ")) {
+                       pool == POOL_ONE ? one_reset() : two_reset();
+                       return;
+               }
+       case 's':
+               if (!prefixcmp(command, "set ")) {
+                       uint32_t n = strtouint32(arg);
+                       if (pool == POOL_ONE)
+                               *one_pointer(n) = 1;
+                       else
+                               *two_pointer(n) = 1;
+                       return;
+               }
+       case 't':
+               if (!prefixcmp(command, "test ")) {
+                       uint32_t n = strtouint32(arg);
+                       printf("%d\n", pool == POOL_ONE ?
+                               *one_pointer(n) : *two_pointer(n));
+                       return;
+               }
+       default:
+               die("unrecognized command: %s", command);
+       }
+}
+
+static void handle_line(const char *line)
+{
+       const char *arg = strchr(line, ' ');
+       enum pool pool;
+
+       if (arg && !prefixcmp(arg + 1, "one"))
+               pool = POOL_ONE;
+       else if (arg && !prefixcmp(arg + 1, "two"))
+               pool = POOL_TWO;
+       else
+               die("no pool specified: %s", line);
+
+       handle_command(line, pool, arg + strlen("one "));
+}
+
+int main(int argc, char *argv[])
+{
+       struct strbuf sb = STRBUF_INIT;
+       if (argc != 1)
+               usage("test-obj-str < script");
+
+       while (strbuf_getline(&sb, stdin, '\n') != EOF)
+               handle_line(sb.buf);
+       strbuf_release(&sb);
+       return 0;
+}
index 61d2c39814529bd0264e4c9e40241131d51d819c..acd1a2ba70fc2ffb38cd8fb0784aa6e5e693183f 100644 (file)
@@ -7,8 +7,10 @@ static unsigned long timestamp;
 static int abbrev = 7;
 static int verbose = 0, dry_run = 0, quiet = 0;
 static char *string = NULL;
+static char *file = NULL;
+static int ambiguous;
 
-int length_callback(const struct option *opt, const char *arg, int unset)
+static int length_callback(const struct option *opt, const char *arg, int unset)
 {
        printf("Callback: \"%s\", %d\n",
                (arg ? arg : "not set"), unset);
@@ -19,8 +21,15 @@ int length_callback(const struct option *opt, const char *arg, int unset)
        return 0;
 }
 
+static int number_callback(const struct option *opt, const char *arg, int unset)
+{
+       *(int *)opt->value = strtol(arg, NULL, 10);
+       return 0;
+}
+
 int main(int argc, const char **argv)
 {
+       const char *prefix = "prefix/";
        const char *usage[] = {
                "test-parse-options <options>",
                NULL
@@ -29,6 +38,7 @@ int main(int argc, const char **argv)
                OPT_BOOLEAN('b', "boolean", &boolean, "get a boolean"),
                OPT_BIT('4', "or4", &boolean,
                        "bitwise-or boolean with ...0100", 4),
+               OPT_NEGBIT(0, "neg-or4", &boolean, "same as --no-or4", 4),
                OPT_GROUP(""),
                OPT_INTEGER('i', "integer", &integer, "get a integer"),
                OPT_INTEGER('j', NULL, &integer, "get a integer, too"),
@@ -36,6 +46,7 @@ int main(int argc, const char **argv)
                OPT_DATE('t', NULL, &timestamp, "get timestamp of <time>"),
                OPT_CALLBACK('L', "length", &integer, "str",
                        "get length of <str>", length_callback),
+               OPT_FILENAME('F', "file", &file, "set file to <FILE>"),
                OPT_GROUP("String options"),
                OPT_STRING('s', "string", &string, "string", "get a string"),
                OPT_STRING(0, "string2", &string, "str", "get another string"),
@@ -45,6 +56,14 @@ int main(int argc, const char **argv)
                        "set string to default", (unsigned long)"default"),
                OPT_GROUP("Magic arguments"),
                OPT_ARGUMENT("quux", "means --quux"),
+               OPT_NUMBER_CALLBACK(&integer, "set integer to NUM",
+                       number_callback),
+               { OPTION_BOOLEAN, '+', NULL, &boolean, NULL, "same as -b",
+                 PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH },
+               { OPTION_BOOLEAN, 0, "ambiguous", &ambiguous, NULL,
+                 "positive ambiguity", PARSE_OPT_NOARG | PARSE_OPT_NONEG },
+               { OPTION_BOOLEAN, 0, "no-ambiguous", &ambiguous, NULL,
+                 "negative ambiguity", PARSE_OPT_NOARG | PARSE_OPT_NONEG },
                OPT_GROUP("Standard options"),
                OPT__ABBREV(&abbrev),
                OPT__VERBOSE(&verbose),
@@ -54,7 +73,7 @@ int main(int argc, const char **argv)
        };
        int i;
 
-       argc = parse_options(argc, argv, options, usage, 0);
+       argc = parse_options(argc, argv, prefix, options, usage, 0);
 
        printf("boolean: %d\n", boolean);
        printf("integer: %u\n", integer);
@@ -64,6 +83,7 @@ int main(int argc, const char **argv)
        printf("verbose: %d\n", verbose);
        printf("quiet: %s\n", quiet ? "yes" : "no");
        printf("dry run: %s\n", dry_run ? "yes" : "no");
+       printf("file: %s\n", file ? file : "(not set)");
 
        for (i = 0; i < argc; i++)
                printf("arg %02d: %s\n", i, argv[i]);
diff --git a/test-run-command.c b/test-run-command.c
new file mode 100644 (file)
index 0000000..0612bfa
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * test-run-command.c: test run command API.
+ *
+ * (C) 2009 Ilari Liusvaara <ilari.liusvaara@elisanet.fi>
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include "git-compat-util.h"
+#include "run-command.h"
+#include <string.h>
+#include <errno.h>
+
+int main(int argc, char **argv)
+{
+       struct child_process proc;
+
+       memset(&proc, 0, sizeof(proc));
+
+       if (argc < 3)
+               return 1;
+       proc.argv = (const char **)argv+2;
+
+       if (!strcmp(argv[1], "start-command-ENOENT")) {
+               if (start_command(&proc) < 0 && errno == ENOENT)
+                       return 0;
+               fprintf(stderr, "FAIL %s\n", argv[1]);
+               return 1;
+       }
+
+       fprintf(stderr, "check usage\n");
+       return 1;
+}
index 9b98d07c786b36f005f4b3ffd51926f3c1a343dd..80daba980ecd85852ee3a23c155602e4cd1ba07a 100644 (file)
@@ -32,7 +32,7 @@ int main(int ac, char **av)
                        if (sz == 0)
                                break;
                        if (sz < 0)
-                               die("test-sha1: %s", strerror(errno));
+                               die_errno("test-sha1");
                        this_sz += sz;
                        cp += sz;
                        room -= sz;
diff --git a/test-string-pool.c b/test-string-pool.c
new file mode 100644 (file)
index 0000000..c5782e6
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * test-string-pool.c: code to exercise the svn importer's string pool
+ */
+
+#include "git-compat-util.h"
+#include "vcs-svn/string_pool.h"
+
+int main(int argc, char *argv[])
+{
+       const uint32_t unequal = pool_intern("does not equal");
+       const uint32_t equal = pool_intern("equals");
+       uint32_t buf[3];
+       uint32_t n;
+
+       if (argc != 2)
+               usage("test-string-pool <string>,<string>");
+
+       n = pool_tok_seq(3, buf, ",-", argv[1]);
+       if (n >= 3)
+               die("too many strings");
+       if (n <= 1)
+               die("too few strings");
+
+       buf[2] = buf[1];
+       buf[1] = (buf[0] == buf[2]) ? equal : unequal;
+       pool_print_seq(3, buf, ' ', stdout);
+       fputc('\n', stdout);
+
+       pool_reset();
+       return 0;
+}
diff --git a/test-svn-fe.c b/test-svn-fe.c
new file mode 100644 (file)
index 0000000..77cf78a
--- /dev/null
@@ -0,0 +1,17 @@
+/*
+ * test-svn-fe: Code to exercise the svn import lib
+ */
+
+#include "git-compat-util.h"
+#include "vcs-svn/svndump.h"
+
+int main(int argc, char *argv[])
+{
+       if (argc != 2)
+               usage("test-svn-fe <file>");
+       svndump_init(argv[1]);
+       svndump_read(NULL);
+       svndump_deinit();
+       svndump_reset();
+       return 0;
+}
diff --git a/test-treap.c b/test-treap.c
new file mode 100644 (file)
index 0000000..cdba511
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * test-treap.c: code to exercise the svn importer's treap structure
+ */
+
+#include "cache.h"
+#include "vcs-svn/obj_pool.h"
+#include "vcs-svn/trp.h"
+
+struct int_node {
+       uintmax_t n;
+       struct trp_node children;
+};
+
+obj_pool_gen(node, struct int_node, 3)
+
+static int node_cmp(struct int_node *a, struct int_node *b)
+{
+       return (a->n > b->n) - (a->n < b->n);
+}
+
+trp_gen(static, treap_, struct int_node, children, node, node_cmp)
+
+static void strtonode(struct int_node *item, const char *s)
+{
+       char *end;
+       item->n = strtoumax(s, &end, 10);
+       if (*s == '\0' || (*end != '\n' && *end != '\0'))
+               die("invalid integer: %s", s);
+}
+
+int main(int argc, char *argv[])
+{
+       struct strbuf sb = STRBUF_INIT;
+       struct trp_root root = { ~0 };
+       uint32_t item;
+
+       if (argc != 1)
+               usage("test-treap < ints");
+
+       while (strbuf_getline(&sb, stdin, '\n') != EOF) {
+               item = node_alloc(1);
+               strtonode(node_pointer(item), sb.buf);
+               treap_insert(&root, node_pointer(item));
+       }
+
+       item = node_offset(treap_first(&root));
+       while (~item) {
+               uint32_t next;
+               struct int_node *tmp = node_pointer(node_alloc(1));
+
+               tmp->n = node_pointer(item)->n;
+               next = node_offset(treap_next(&root, node_pointer(item)));
+
+               treap_remove(&root, node_pointer(item));
+               item = node_offset(treap_nsearch(&root, tmp));
+
+               if (item != next && (!~item || node_pointer(item)->n != tmp->n))
+                       die("found %"PRIuMAX" in place of %"PRIuMAX"",
+                               ~item ? node_pointer(item)->n : ~(uintmax_t) 0,
+                               ~next ? node_pointer(next)->n : ~(uintmax_t) 0);
+               printf("%"PRIuMAX"\n", tmp->n);
+       }
+       node_reset();
+       return 0;
+}
index 55e7e2904eb5f95cedaec2520ddd1d158ee93c7a..589f838f82b568195232ea81346d0049261b86b1 100644 (file)
@@ -1,9 +1,7 @@
 #include "cache.h"
+#include <pthread.h>
 
-#ifdef _WIN32
-#  define WIN32_LEAN_AND_MEAN
-#  include <windows.h>
-#elif defined(hpux) || defined(__hpux) || defined(_hpux)
+#if defined(hpux) || defined(__hpux) || defined(_hpux)
 #  include <sys/pstat.h>
 #endif
 
@@ -46,3 +44,18 @@ int online_cpus(void)
 
        return 1;
 }
+
+int init_recursive_mutex(pthread_mutex_t *m)
+{
+       pthread_mutexattr_t a;
+       int ret;
+
+       ret = pthread_mutexattr_init(&a);
+       if (!ret) {
+               ret = pthread_mutexattr_settype(&a, PTHREAD_MUTEX_RECURSIVE);
+               if (!ret)
+                       ret = pthread_mutex_init(m, &a);
+               pthread_mutexattr_destroy(&a);
+       }
+       return ret;
+}
index cce4b77bd6452e2ec589d8c0dc0e8156352dd67b..1727a03333b80f2a01ca029162a4b511480a86e8 100644 (file)
@@ -2,5 +2,6 @@
 #define THREAD_COMPAT_H
 
 extern int online_cpus(void);
+extern int init_recursive_mutex(pthread_mutex_t*);
 
 #endif /* THREAD_COMPAT_H */
diff --git a/trace.c b/trace.c
index 4229ae1231d69aedd9f1aa8350989ddbe2bdb845..1e560cb0b977e19beeb095497c8e4f6280b7f0d6 100644 (file)
--- a/trace.c
+++ b/trace.c
 #include "cache.h"
 #include "quote.h"
 
+void do_nothing(size_t unused)
+{
+}
+
 /* Get a trace file descriptor from GIT_TRACE env variable. */
 static int get_trace_fd(int *need_close)
 {
@@ -72,6 +76,7 @@ void trace_printf(const char *fmt, ...)
        if (!fd)
                return;
 
+       set_try_to_free_routine(do_nothing);    /* is never reset */
        strbuf_init(&buf, 64);
        va_start(ap, fmt);
        len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap);
@@ -103,6 +108,7 @@ void trace_argv_printf(const char **argv, const char *fmt, ...)
        if (!fd)
                return;
 
+       set_try_to_free_routine(do_nothing);    /* is never reset */
        strbuf_init(&buf, 64);
        va_start(ap, fmt);
        len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap);
diff --git a/transport-helper.c b/transport-helper.c
new file mode 100644 (file)
index 0000000..acfc88e
--- /dev/null
@@ -0,0 +1,864 @@
+#include "cache.h"
+#include "transport.h"
+#include "quote.h"
+#include "run-command.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "quote.h"
+#include "remote.h"
+#include "string-list.h"
+
+static int debug;
+
+struct helper_data
+{
+       const char *name;
+       struct child_process *helper;
+       FILE *out;
+       unsigned fetch : 1,
+               import : 1,
+               export : 1,
+               option : 1,
+               push : 1,
+               connect : 1,
+               no_disconnect_req : 1;
+       /* These go from remote name (as in "list") to private name */
+       struct refspec *refspecs;
+       int refspec_nr;
+       /* Transport options for fetch-pack/send-pack (should one of
+        * those be invoked).
+        */
+       struct git_transport_options transport_options;
+};
+
+static void sendline(struct helper_data *helper, struct strbuf *buffer)
+{
+       if (debug)
+               fprintf(stderr, "Debug: Remote helper: -> %s", buffer->buf);
+       if (write_in_full(helper->helper->in, buffer->buf, buffer->len)
+               != buffer->len)
+               die_errno("Full write to remote helper failed");
+}
+
+static int recvline_fh(FILE *helper, struct strbuf *buffer)
+{
+       strbuf_reset(buffer);
+       if (debug)
+               fprintf(stderr, "Debug: Remote helper: Waiting...\n");
+       if (strbuf_getline(buffer, helper, '\n') == EOF) {
+               if (debug)
+                       fprintf(stderr, "Debug: Remote helper quit.\n");
+               exit(128);
+       }
+
+       if (debug)
+               fprintf(stderr, "Debug: Remote helper: <- %s\n", buffer->buf);
+       return 0;
+}
+
+static int recvline(struct helper_data *helper, struct strbuf *buffer)
+{
+       return recvline_fh(helper->out, buffer);
+}
+
+static void xchgline(struct helper_data *helper, struct strbuf *buffer)
+{
+       sendline(helper, buffer);
+       recvline(helper, buffer);
+}
+
+static void write_constant(int fd, const char *str)
+{
+       if (debug)
+               fprintf(stderr, "Debug: Remote helper: -> %s", str);
+       if (write_in_full(fd, str, strlen(str)) != strlen(str))
+               die_errno("Full write to remote helper failed");
+}
+
+const char *remove_ext_force(const char *url)
+{
+       if (url) {
+               const char *colon = strchr(url, ':');
+               if (colon && colon[1] == ':')
+                       return colon + 2;
+       }
+       return url;
+}
+
+static void do_take_over(struct transport *transport)
+{
+       struct helper_data *data;
+       data = (struct helper_data *)transport->data;
+       transport_take_over(transport, data->helper);
+       fclose(data->out);
+       free(data);
+}
+
+static struct child_process *get_helper(struct transport *transport)
+{
+       struct helper_data *data = transport->data;
+       struct strbuf buf = STRBUF_INIT;
+       struct child_process *helper;
+       const char **refspecs = NULL;
+       int refspec_nr = 0;
+       int refspec_alloc = 0;
+       int duped;
+       int code;
+
+       if (data->helper)
+               return data->helper;
+
+       helper = xcalloc(1, sizeof(*helper));
+       helper->in = -1;
+       helper->out = -1;
+       helper->err = 0;
+       helper->argv = xcalloc(4, sizeof(*helper->argv));
+       strbuf_addf(&buf, "git-remote-%s", data->name);
+       helper->argv[0] = strbuf_detach(&buf, NULL);
+       helper->argv[1] = transport->remote->name;
+       helper->argv[2] = remove_ext_force(transport->url);
+       helper->git_cmd = 0;
+       helper->silent_exec_failure = 1;
+       code = start_command(helper);
+       if (code < 0 && errno == ENOENT)
+               die("Unable to find remote helper for '%s'", data->name);
+       else if (code != 0)
+               exit(code);
+
+       data->helper = helper;
+       data->no_disconnect_req = 0;
+
+       /*
+        * Open the output as FILE* so strbuf_getline() can be used.
+        * Do this with duped fd because fclose() will close the fd,
+        * and stuff like taking over will require the fd to remain.
+        */
+       duped = dup(helper->out);
+       if (duped < 0)
+               die_errno("Can't dup helper output fd");
+       data->out = xfdopen(duped, "r");
+
+       write_constant(helper->in, "capabilities\n");
+
+       while (1) {
+               const char *capname;
+               int mandatory = 0;
+               recvline(data, &buf);
+
+               if (!*buf.buf)
+                       break;
+
+               if (*buf.buf == '*') {
+                       capname = buf.buf + 1;
+                       mandatory = 1;
+               } else
+                       capname = buf.buf;
+
+               if (debug)
+                       fprintf(stderr, "Debug: Got cap %s\n", capname);
+               if (!strcmp(capname, "fetch"))
+                       data->fetch = 1;
+               else if (!strcmp(capname, "option"))
+                       data->option = 1;
+               else if (!strcmp(capname, "push"))
+                       data->push = 1;
+               else if (!strcmp(capname, "import"))
+                       data->import = 1;
+               else if (!strcmp(capname, "export"))
+                       data->export = 1;
+               else if (!data->refspecs && !prefixcmp(capname, "refspec ")) {
+                       ALLOC_GROW(refspecs,
+                                  refspec_nr + 1,
+                                  refspec_alloc);
+                       refspecs[refspec_nr++] = strdup(buf.buf + strlen("refspec "));
+               } else if (!strcmp(capname, "connect")) {
+                       data->connect = 1;
+               } else if (!strcmp(buf.buf, "gitdir")) {
+                       struct strbuf gitdir = STRBUF_INIT;
+                       strbuf_addf(&gitdir, "gitdir %s\n", get_git_dir());
+                       sendline(data, &gitdir);
+                       strbuf_release(&gitdir);
+               } else if (mandatory) {
+                       die("Unknown mandatory capability %s. This remote "
+                           "helper probably needs newer version of Git.\n",
+                           capname);
+               }
+       }
+       if (refspecs) {
+               int i;
+               data->refspec_nr = refspec_nr;
+               data->refspecs = parse_fetch_refspec(refspec_nr, refspecs);
+               for (i = 0; i < refspec_nr; i++) {
+                       free((char *)refspecs[i]);
+               }
+               free(refspecs);
+       }
+       strbuf_release(&buf);
+       if (debug)
+               fprintf(stderr, "Debug: Capabilities complete.\n");
+       return data->helper;
+}
+
+static int disconnect_helper(struct transport *transport)
+{
+       struct helper_data *data = transport->data;
+       struct strbuf buf = STRBUF_INIT;
+
+       if (data->helper) {
+               if (debug)
+                       fprintf(stderr, "Debug: Disconnecting.\n");
+               if (!data->no_disconnect_req) {
+                       strbuf_addf(&buf, "\n");
+                       sendline(data, &buf);
+               }
+               close(data->helper->in);
+               close(data->helper->out);
+               fclose(data->out);
+               finish_command(data->helper);
+               free((char *)data->helper->argv[0]);
+               free(data->helper->argv);
+               free(data->helper);
+               data->helper = NULL;
+       }
+       return 0;
+}
+
+static const char *unsupported_options[] = {
+       TRANS_OPT_UPLOADPACK,
+       TRANS_OPT_RECEIVEPACK,
+       TRANS_OPT_THIN,
+       TRANS_OPT_KEEP
+       };
+static const char *boolean_options[] = {
+       TRANS_OPT_THIN,
+       TRANS_OPT_KEEP,
+       TRANS_OPT_FOLLOWTAGS
+       };
+
+static int set_helper_option(struct transport *transport,
+                         const char *name, const char *value)
+{
+       struct helper_data *data = transport->data;
+       struct strbuf buf = STRBUF_INIT;
+       int i, ret, is_bool = 0;
+
+       get_helper(transport);
+
+       if (!data->option)
+               return 1;
+
+       for (i = 0; i < ARRAY_SIZE(unsupported_options); i++) {
+               if (!strcmp(name, unsupported_options[i]))
+                       return 1;
+       }
+
+       for (i = 0; i < ARRAY_SIZE(boolean_options); i++) {
+               if (!strcmp(name, boolean_options[i])) {
+                       is_bool = 1;
+                       break;
+               }
+       }
+
+       strbuf_addf(&buf, "option %s ", name);
+       if (is_bool)
+               strbuf_addstr(&buf, value ? "true" : "false");
+       else
+               quote_c_style(value, &buf, NULL, 0);
+       strbuf_addch(&buf, '\n');
+
+       xchgline(data, &buf);
+
+       if (!strcmp(buf.buf, "ok"))
+               ret = 0;
+       else if (!prefixcmp(buf.buf, "error")) {
+               ret = -1;
+       } else if (!strcmp(buf.buf, "unsupported"))
+               ret = 1;
+       else {
+               warning("%s unexpectedly said: '%s'", data->name, buf.buf);
+               ret = 1;
+       }
+       strbuf_release(&buf);
+       return ret;
+}
+
+static void standard_options(struct transport *t)
+{
+       char buf[16];
+       int n;
+       int v = t->verbose;
+
+       set_helper_option(t, "progress", t->progress ? "true" : "false");
+
+       n = snprintf(buf, sizeof(buf), "%d", v + 1);
+       if (n >= sizeof(buf))
+               die("impossibly large verbosity value");
+       set_helper_option(t, "verbosity", buf);
+}
+
+static int release_helper(struct transport *transport)
+{
+       struct helper_data *data = transport->data;
+       free_refspec(data->refspec_nr, data->refspecs);
+       data->refspecs = NULL;
+       disconnect_helper(transport);
+       free(transport->data);
+       return 0;
+}
+
+static int fetch_with_fetch(struct transport *transport,
+                           int nr_heads, struct ref **to_fetch)
+{
+       struct helper_data *data = transport->data;
+       int i;
+       struct strbuf buf = STRBUF_INIT;
+
+       standard_options(transport);
+
+       for (i = 0; i < nr_heads; i++) {
+               const struct ref *posn = to_fetch[i];
+               if (posn->status & REF_STATUS_UPTODATE)
+                       continue;
+
+               strbuf_addf(&buf, "fetch %s %s\n",
+                           sha1_to_hex(posn->old_sha1), posn->name);
+       }
+
+       strbuf_addch(&buf, '\n');
+       sendline(data, &buf);
+
+       while (1) {
+               recvline(data, &buf);
+
+               if (!prefixcmp(buf.buf, "lock ")) {
+                       const char *name = buf.buf + 5;
+                       if (transport->pack_lockfile)
+                               warning("%s also locked %s", data->name, name);
+                       else
+                               transport->pack_lockfile = xstrdup(name);
+               }
+               else if (!buf.len)
+                       break;
+               else
+                       warning("%s unexpectedly said: '%s'", data->name, buf.buf);
+       }
+       strbuf_release(&buf);
+       return 0;
+}
+
+static int get_importer(struct transport *transport, struct child_process *fastimport)
+{
+       struct child_process *helper = get_helper(transport);
+       memset(fastimport, 0, sizeof(*fastimport));
+       fastimport->in = helper->out;
+       fastimport->argv = xcalloc(5, sizeof(*fastimport->argv));
+       fastimport->argv[0] = "fast-import";
+       fastimport->argv[1] = "--quiet";
+
+       fastimport->git_cmd = 1;
+       return start_command(fastimport);
+}
+
+static int get_exporter(struct transport *transport,
+                       struct child_process *fastexport,
+                       const char *export_marks,
+                       const char *import_marks,
+                       struct string_list *revlist_args)
+{
+       struct child_process *helper = get_helper(transport);
+       int argc = 0, i;
+       memset(fastexport, 0, sizeof(*fastexport));
+
+       /* we need to duplicate helper->in because we want to use it after
+        * fastexport is done with it. */
+       fastexport->out = dup(helper->in);
+       fastexport->argv = xcalloc(4 + revlist_args->nr, sizeof(*fastexport->argv));
+       fastexport->argv[argc++] = "fast-export";
+       if (export_marks)
+               fastexport->argv[argc++] = export_marks;
+       if (import_marks)
+               fastexport->argv[argc++] = import_marks;
+
+       for (i = 0; i < revlist_args->nr; i++)
+               fastexport->argv[argc++] = revlist_args->items[i].string;
+
+       fastexport->git_cmd = 1;
+       return start_command(fastexport);
+}
+
+static int fetch_with_import(struct transport *transport,
+                            int nr_heads, struct ref **to_fetch)
+{
+       struct child_process fastimport;
+       struct helper_data *data = transport->data;
+       int i;
+       struct ref *posn;
+       struct strbuf buf = STRBUF_INIT;
+
+       get_helper(transport);
+
+       if (get_importer(transport, &fastimport))
+               die("Couldn't run fast-import");
+
+       for (i = 0; i < nr_heads; i++) {
+               posn = to_fetch[i];
+               if (posn->status & REF_STATUS_UPTODATE)
+                       continue;
+
+               strbuf_addf(&buf, "import %s\n", posn->name);
+               sendline(data, &buf);
+               strbuf_reset(&buf);
+       }
+       disconnect_helper(transport);
+       finish_command(&fastimport);
+       free(fastimport.argv);
+       fastimport.argv = NULL;
+
+       for (i = 0; i < nr_heads; i++) {
+               char *private;
+               posn = to_fetch[i];
+               if (posn->status & REF_STATUS_UPTODATE)
+                       continue;
+               if (data->refspecs)
+                       private = apply_refspecs(data->refspecs, data->refspec_nr, posn->name);
+               else
+                       private = strdup(posn->name);
+               read_ref(private, posn->old_sha1);
+               free(private);
+       }
+       strbuf_release(&buf);
+       return 0;
+}
+
+static int process_connect_service(struct transport *transport,
+                                  const char *name, const char *exec)
+{
+       struct helper_data *data = transport->data;
+       struct strbuf cmdbuf = STRBUF_INIT;
+       struct child_process *helper;
+       int r, duped, ret = 0;
+       FILE *input;
+
+       helper = get_helper(transport);
+
+       /*
+        * Yes, dup the pipe another time, as we need unbuffered version
+        * of input pipe as FILE*. fclose() closes the underlying fd and
+        * stream buffering only can be changed before first I/O operation
+        * on it.
+        */
+       duped = dup(helper->out);
+       if (duped < 0)
+               die_errno("Can't dup helper output fd");
+       input = xfdopen(duped, "r");
+       setvbuf(input, NULL, _IONBF, 0);
+
+       /*
+        * Handle --upload-pack and friends. This is fire and forget...
+        * just warn if it fails.
+        */
+       if (strcmp(name, exec)) {
+               r = set_helper_option(transport, "servpath", exec);
+               if (r > 0)
+                       warning("Setting remote service path not supported by protocol.");
+               else if (r < 0)
+                       warning("Invalid remote service path.");
+       }
+
+       if (data->connect)
+               strbuf_addf(&cmdbuf, "connect %s\n", name);
+       else
+               goto exit;
+
+       sendline(data, &cmdbuf);
+       recvline_fh(input, &cmdbuf);
+       if (!strcmp(cmdbuf.buf, "")) {
+               data->no_disconnect_req = 1;
+               if (debug)
+                       fprintf(stderr, "Debug: Smart transport connection "
+                               "ready.\n");
+               ret = 1;
+       } else if (!strcmp(cmdbuf.buf, "fallback")) {
+               if (debug)
+                       fprintf(stderr, "Debug: Falling back to dumb "
+                               "transport.\n");
+       } else
+               die("Unknown response to connect: %s",
+                       cmdbuf.buf);
+
+exit:
+       fclose(input);
+       return ret;
+}
+
+static int process_connect(struct transport *transport,
+                                    int for_push)
+{
+       struct helper_data *data = transport->data;
+       const char *name;
+       const char *exec;
+
+       name = for_push ? "git-receive-pack" : "git-upload-pack";
+       if (for_push)
+               exec = data->transport_options.receivepack;
+       else
+               exec = data->transport_options.uploadpack;
+
+       return process_connect_service(transport, name, exec);
+}
+
+static int connect_helper(struct transport *transport, const char *name,
+                  const char *exec, int fd[2])
+{
+       struct helper_data *data = transport->data;
+
+       /* Get_helper so connect is inited. */
+       get_helper(transport);
+       if (!data->connect)
+               die("Operation not supported by protocol.");
+
+       if (!process_connect_service(transport, name, exec))
+               die("Can't connect to subservice %s.", name);
+
+       fd[0] = data->helper->out;
+       fd[1] = data->helper->in;
+       return 0;
+}
+
+static int fetch(struct transport *transport,
+                int nr_heads, struct ref **to_fetch)
+{
+       struct helper_data *data = transport->data;
+       int i, count;
+
+       if (process_connect(transport, 0)) {
+               do_take_over(transport);
+               return transport->fetch(transport, nr_heads, to_fetch);
+       }
+
+       count = 0;
+       for (i = 0; i < nr_heads; i++)
+               if (!(to_fetch[i]->status & REF_STATUS_UPTODATE))
+                       count++;
+
+       if (!count)
+               return 0;
+
+       if (data->fetch)
+               return fetch_with_fetch(transport, nr_heads, to_fetch);
+
+       if (data->import)
+               return fetch_with_import(transport, nr_heads, to_fetch);
+
+       return -1;
+}
+
+static int push_refs_with_push(struct transport *transport,
+               struct ref *remote_refs, int flags)
+{
+       int force_all = flags & TRANSPORT_PUSH_FORCE;
+       int mirror = flags & TRANSPORT_PUSH_MIRROR;
+       struct helper_data *data = transport->data;
+       struct strbuf buf = STRBUF_INIT;
+       struct child_process *helper;
+       struct ref *ref;
+
+       helper = get_helper(transport);
+       if (!data->push)
+               return 1;
+
+       for (ref = remote_refs; ref; ref = ref->next) {
+               if (!ref->peer_ref && !mirror)
+                       continue;
+
+               /* Check for statuses set by set_ref_status_for_push() */
+               switch (ref->status) {
+               case REF_STATUS_REJECT_NONFASTFORWARD:
+               case REF_STATUS_UPTODATE:
+                       continue;
+               default:
+                       ; /* do nothing */
+               }
+
+               if (force_all)
+                       ref->force = 1;
+
+               strbuf_addstr(&buf, "push ");
+               if (!ref->deletion) {
+                       if (ref->force)
+                               strbuf_addch(&buf, '+');
+                       if (ref->peer_ref)
+                               strbuf_addstr(&buf, ref->peer_ref->name);
+                       else
+                               strbuf_addstr(&buf, sha1_to_hex(ref->new_sha1));
+               }
+               strbuf_addch(&buf, ':');
+               strbuf_addstr(&buf, ref->name);
+               strbuf_addch(&buf, '\n');
+       }
+       if (buf.len == 0)
+               return 0;
+
+       standard_options(transport);
+
+       if (flags & TRANSPORT_PUSH_DRY_RUN) {
+               if (set_helper_option(transport, "dry-run", "true") != 0)
+                       die("helper %s does not support dry-run", data->name);
+       }
+
+       strbuf_addch(&buf, '\n');
+       sendline(data, &buf);
+
+       ref = remote_refs;
+       while (1) {
+               char *refname, *msg;
+               int status;
+
+               recvline(data, &buf);
+               if (!buf.len)
+                       break;
+
+               if (!prefixcmp(buf.buf, "ok ")) {
+                       status = REF_STATUS_OK;
+                       refname = buf.buf + 3;
+               } else if (!prefixcmp(buf.buf, "error ")) {
+                       status = REF_STATUS_REMOTE_REJECT;
+                       refname = buf.buf + 6;
+               } else
+                       die("expected ok/error, helper said '%s'\n", buf.buf);
+
+               msg = strchr(refname, ' ');
+               if (msg) {
+                       struct strbuf msg_buf = STRBUF_INIT;
+                       const char *end;
+
+                       *msg++ = '\0';
+                       if (!unquote_c_style(&msg_buf, msg, &end))
+                               msg = strbuf_detach(&msg_buf, NULL);
+                       else
+                               msg = xstrdup(msg);
+                       strbuf_release(&msg_buf);
+
+                       if (!strcmp(msg, "no match")) {
+                               status = REF_STATUS_NONE;
+                               free(msg);
+                               msg = NULL;
+                       }
+                       else if (!strcmp(msg, "up to date")) {
+                               status = REF_STATUS_UPTODATE;
+                               free(msg);
+                               msg = NULL;
+                       }
+                       else if (!strcmp(msg, "non-fast forward")) {
+                               status = REF_STATUS_REJECT_NONFASTFORWARD;
+                               free(msg);
+                               msg = NULL;
+                       }
+               }
+
+               if (ref)
+                       ref = find_ref_by_name(ref, refname);
+               if (!ref)
+                       ref = find_ref_by_name(remote_refs, refname);
+               if (!ref) {
+                       warning("helper reported unexpected status of %s", refname);
+                       continue;
+               }
+
+               if (ref->status != REF_STATUS_NONE) {
+                       /*
+                        * Earlier, the ref was marked not to be pushed, so ignore the ref
+                        * status reported by the remote helper if the latter is 'no match'.
+                        */
+                       if (status == REF_STATUS_NONE)
+                               continue;
+               }
+
+               ref->status = status;
+               ref->remote_status = msg;
+       }
+       strbuf_release(&buf);
+       return 0;
+}
+
+static int push_refs_with_export(struct transport *transport,
+               struct ref *remote_refs, int flags)
+{
+       struct ref *ref;
+       struct child_process *helper, exporter;
+       struct helper_data *data = transport->data;
+       char *export_marks = NULL, *import_marks = NULL;
+       struct string_list revlist_args = STRING_LIST_INIT_NODUP;
+       struct strbuf buf = STRBUF_INIT;
+
+       helper = get_helper(transport);
+
+       write_constant(helper->in, "export\n");
+
+       recvline(data, &buf);
+       if (debug)
+               fprintf(stderr, "Debug: Got export_marks '%s'\n", buf.buf);
+       if (buf.len) {
+               struct strbuf arg = STRBUF_INIT;
+               strbuf_addstr(&arg, "--export-marks=");
+               strbuf_addbuf(&arg, &buf);
+               export_marks = strbuf_detach(&arg, NULL);
+       }
+
+       recvline(data, &buf);
+       if (debug)
+               fprintf(stderr, "Debug: Got import_marks '%s'\n", buf.buf);
+       if (buf.len) {
+               struct strbuf arg = STRBUF_INIT;
+               strbuf_addstr(&arg, "--import-marks=");
+               strbuf_addbuf(&arg, &buf);
+               import_marks = strbuf_detach(&arg, NULL);
+       }
+
+       strbuf_reset(&buf);
+
+       for (ref = remote_refs; ref; ref = ref->next) {
+               char *private;
+               unsigned char sha1[20];
+
+               if (!data->refspecs)
+                       continue;
+               private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name);
+               if (private && !get_sha1(private, sha1)) {
+                       strbuf_addf(&buf, "^%s", private);
+                       string_list_append(&revlist_args, strbuf_detach(&buf, NULL));
+               }
+
+               string_list_append(&revlist_args, ref->name);
+
+       }
+
+       if (get_exporter(transport, &exporter,
+                        export_marks, import_marks, &revlist_args))
+               die("Couldn't run fast-export");
+
+       data->no_disconnect_req = 1;
+       finish_command(&exporter);
+       disconnect_helper(transport);
+       return 0;
+}
+
+static int push_refs(struct transport *transport,
+               struct ref *remote_refs, int flags)
+{
+       struct helper_data *data = transport->data;
+
+       if (process_connect(transport, 1)) {
+               do_take_over(transport);
+               return transport->push_refs(transport, remote_refs, flags);
+       }
+
+       if (!remote_refs) {
+               fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
+                       "Perhaps you should specify a branch such as 'master'.\n");
+               return 0;
+       }
+
+       if (data->push)
+               return push_refs_with_push(transport, remote_refs, flags);
+
+       if (data->export)
+               return push_refs_with_export(transport, remote_refs, flags);
+
+       return -1;
+}
+
+
+static int has_attribute(const char *attrs, const char *attr) {
+       int len;
+       if (!attrs)
+               return 0;
+
+       len = strlen(attr);
+       for (;;) {
+               const char *space = strchrnul(attrs, ' ');
+               if (len == space - attrs && !strncmp(attrs, attr, len))
+                       return 1;
+               if (!*space)
+                       return 0;
+               attrs = space + 1;
+       }
+}
+
+static struct ref *get_refs_list(struct transport *transport, int for_push)
+{
+       struct helper_data *data = transport->data;
+       struct child_process *helper;
+       struct ref *ret = NULL;
+       struct ref **tail = &ret;
+       struct ref *posn;
+       struct strbuf buf = STRBUF_INIT;
+
+       helper = get_helper(transport);
+
+       if (process_connect(transport, for_push)) {
+               do_take_over(transport);
+               return transport->get_refs_list(transport, for_push);
+       }
+
+       if (data->push && for_push)
+               write_str_in_full(helper->in, "list for-push\n");
+       else
+               write_str_in_full(helper->in, "list\n");
+
+       while (1) {
+               char *eov, *eon;
+               recvline(data, &buf);
+
+               if (!*buf.buf)
+                       break;
+
+               eov = strchr(buf.buf, ' ');
+               if (!eov)
+                       die("Malformed response in ref list: %s", buf.buf);
+               eon = strchr(eov + 1, ' ');
+               *eov = '\0';
+               if (eon)
+                       *eon = '\0';
+               *tail = alloc_ref(eov + 1);
+               if (buf.buf[0] == '@')
+                       (*tail)->symref = xstrdup(buf.buf + 1);
+               else if (buf.buf[0] != '?')
+                       get_sha1_hex(buf.buf, (*tail)->old_sha1);
+               if (eon) {
+                       if (has_attribute(eon + 1, "unchanged")) {
+                               (*tail)->status |= REF_STATUS_UPTODATE;
+                               read_ref((*tail)->name, (*tail)->old_sha1);
+                       }
+               }
+               tail = &((*tail)->next);
+       }
+       if (debug)
+               fprintf(stderr, "Debug: Read ref listing.\n");
+       strbuf_release(&buf);
+
+       for (posn = ret; posn; posn = posn->next)
+               resolve_remote_symref(posn, ret);
+
+       return ret;
+}
+
+int transport_helper_init(struct transport *transport, const char *name)
+{
+       struct helper_data *data = xcalloc(sizeof(*data), 1);
+       data->name = name;
+
+       if (getenv("GIT_TRANSPORT_HELPER_DEBUG"))
+               debug = 1;
+
+       transport->data = data;
+       transport->set_option = set_helper_option;
+       transport->get_refs_list = get_refs_list;
+       transport->fetch = fetch;
+       transport->push_refs = push_refs;
+       transport->disconnect = release_helper;
+       transport->connect = connect_helper;
+       transport->smart_options = &(data->transport_options);
+       return 0;
+}
index 3dfb03c06ed82102f60045d6889db97cf036651e..4dba6f8815a80093a8ac9edc226d99bba1bb6394 100644 (file)
@@ -1,9 +1,6 @@
 #include "cache.h"
 #include "transport.h"
 #include "run-command.h"
-#ifndef NO_CURL
-#include "http.h"
-#endif
 #include "pkt-line.h"
 #include "fetch-pack.h"
 #include "send-pack.h"
@@ -11,6 +8,8 @@
 #include "bundle.h"
 #include "dir.h"
 #include "refs.h"
+#include "branch.h"
+#include "url.h"
 
 /* rsync support */
 
@@ -138,6 +137,53 @@ static void insert_packed_refs(const char *packed_refs, struct ref **list)
        }
 }
 
+static void set_upstreams(struct transport *transport, struct ref *refs,
+       int pretend)
+{
+       struct ref *ref;
+       for (ref = refs; ref; ref = ref->next) {
+               const char *localname;
+               const char *tmp;
+               const char *remotename;
+               unsigned char sha[20];
+               int flag = 0;
+               /*
+                * Check suitability for tracking. Must be successful /
+                * already up-to-date ref create/modify (not delete).
+                */
+               if (ref->status != REF_STATUS_OK &&
+                       ref->status != REF_STATUS_UPTODATE)
+                       continue;
+               if (!ref->peer_ref)
+                       continue;
+               if (!ref->new_sha1 || is_null_sha1(ref->new_sha1))
+                       continue;
+
+               /* Follow symbolic refs (mainly for HEAD). */
+               localname = ref->peer_ref->name;
+               remotename = ref->name;
+               tmp = resolve_ref(localname, sha, 1, &flag);
+               if (tmp && flag & REF_ISSYMREF &&
+                       !prefixcmp(tmp, "refs/heads/"))
+                       localname = tmp;
+
+               /* Both source and destination must be local branches. */
+               if (!localname || prefixcmp(localname, "refs/heads/"))
+                       continue;
+               if (!remotename || prefixcmp(remotename, "refs/heads/"))
+                       continue;
+
+               if (!pretend)
+                       install_branch_config(BRANCH_CONFIG_VERBOSE,
+                               localname + 11, transport->remote->name,
+                               remotename);
+               else
+                       printf("Would set upstream of '%s' to '%s' of '%s'\n",
+                               localname + 11, remotename + 11,
+                               transport->remote->name);
+       }
+}
+
 static const char *rsync_url(const char *url)
 {
        return prefixcmp(url, "rsync://") ? skip_prefix(url, "rsync:") : url;
@@ -146,7 +192,7 @@ static const char *rsync_url(const char *url)
 static struct ref *get_refs_via_rsync(struct transport *transport, int for_push)
 {
        struct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT;
-       struct ref dummy, *tail = &dummy;
+       struct ref dummy = {0}, *tail = &dummy;
        struct child_process rsync;
        const char *args[5];
        int temp_dir_len;
@@ -158,7 +204,7 @@ static struct ref *get_refs_via_rsync(struct transport *transport, int for_push)
 
        strbuf_addstr(&temp_dir, git_path("rsync-refs-XXXXXX"));
        if (!mkdtemp(temp_dir.buf))
-               die ("Could not make temporary directory");
+               die_errno ("Could not make temporary directory");
        temp_dir_len = temp_dir.len;
 
        strbuf_addstr(&buf, rsync_url(transport->url));
@@ -207,7 +253,7 @@ static struct ref *get_refs_via_rsync(struct transport *transport, int for_push)
 }
 
 static int fetch_objs_via_rsync(struct transport *transport,
-                               int nr_objs, const struct ref **to_fetch)
+                               int nr_objs, struct ref **to_fetch)
 {
        struct strbuf buf = STRBUF_INIT;
        struct child_process rsync;
@@ -321,7 +367,7 @@ static int rsync_transport_push(struct transport *transport,
 
        strbuf_addstr(&temp_dir, git_path("rsync-refs-XXXXXX"));
        if (!mkdtemp(temp_dir.buf))
-               die ("Could not make temporary directory");
+               die_errno ("Could not make temporary directory");
        strbuf_addch(&temp_dir, '/');
 
        if (flags & TRANSPORT_PUSH_ALL) {
@@ -352,187 +398,6 @@ static int rsync_transport_push(struct transport *transport,
        return result;
 }
 
-/* Generic functions for using commit walkers */
-
-#ifndef NO_CURL /* http fetch is the only user */
-static int fetch_objs_via_walker(struct transport *transport,
-                                int nr_objs, const struct ref **to_fetch)
-{
-       char *dest = xstrdup(transport->url);
-       struct walker *walker = transport->data;
-       char **objs = xmalloc(nr_objs * sizeof(*objs));
-       int i;
-
-       walker->get_all = 1;
-       walker->get_tree = 1;
-       walker->get_history = 1;
-       walker->get_verbosely = transport->verbose >= 0;
-       walker->get_recover = 0;
-
-       for (i = 0; i < nr_objs; i++)
-               objs[i] = xstrdup(sha1_to_hex(to_fetch[i]->old_sha1));
-
-       if (walker_fetch(walker, nr_objs, objs, NULL, NULL))
-               die("Fetch failed.");
-
-       for (i = 0; i < nr_objs; i++)
-               free(objs[i]);
-       free(objs);
-       free(dest);
-       return 0;
-}
-#endif /* NO_CURL */
-
-static int disconnect_walker(struct transport *transport)
-{
-       struct walker *walker = transport->data;
-       if (walker)
-               walker_free(walker);
-       return 0;
-}
-
-#ifndef NO_CURL
-static int curl_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags)
-{
-       const char **argv;
-       int argc;
-       int err;
-
-       if (flags & TRANSPORT_PUSH_MIRROR)
-               return error("http transport does not support mirror mode");
-
-       argv = xmalloc((refspec_nr + 12) * sizeof(char *));
-       argv[0] = "http-push";
-       argc = 1;
-       if (flags & TRANSPORT_PUSH_ALL)
-               argv[argc++] = "--all";
-       if (flags & TRANSPORT_PUSH_FORCE)
-               argv[argc++] = "--force";
-       if (flags & TRANSPORT_PUSH_DRY_RUN)
-               argv[argc++] = "--dry-run";
-       if (flags & TRANSPORT_PUSH_VERBOSE)
-               argv[argc++] = "--verbose";
-       argv[argc++] = transport->url;
-       while (refspec_nr--)
-               argv[argc++] = *refspec++;
-       argv[argc] = NULL;
-       err = run_command_v_opt(argv, RUN_GIT_CMD);
-       switch (err) {
-       case -ERR_RUN_COMMAND_FORK:
-               error("unable to fork for %s", argv[0]);
-       case -ERR_RUN_COMMAND_EXEC:
-               error("unable to exec %s", argv[0]);
-               break;
-       case -ERR_RUN_COMMAND_WAITPID:
-       case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
-       case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
-       case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
-               error("%s died with strange error", argv[0]);
-       }
-       return !!err;
-}
-
-static struct ref *get_refs_via_curl(struct transport *transport, int for_push)
-{
-       struct strbuf buffer = STRBUF_INIT;
-       char *data, *start, *mid;
-       char *ref_name;
-       char *refs_url;
-       int i = 0;
-
-       struct active_request_slot *slot;
-       struct slot_results results;
-
-       struct ref *refs = NULL;
-       struct ref *ref = NULL;
-       struct ref *last_ref = NULL;
-
-       struct walker *walker;
-
-       if (for_push)
-               return NULL;
-
-       if (!transport->data)
-               transport->data = get_http_walker(transport->url,
-                                               transport->remote);
-
-       walker = transport->data;
-
-       refs_url = xmalloc(strlen(transport->url) + 11);
-       sprintf(refs_url, "%s/info/refs", transport->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, refs_url);
-       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
-
-       if (start_active_slot(slot)) {
-               run_active_slot(slot);
-               if (results.curl_result != CURLE_OK) {
-                       strbuf_release(&buffer);
-                       if (missing_target(&results))
-                               die("%s not found: did you run git update-server-info on the server?", refs_url);
-                       else
-                               die("%s download error - %s", refs_url, curl_errorstr);
-               }
-       } else {
-               strbuf_release(&buffer);
-               die("Unable to start HTTP request");
-       }
-
-       data = buffer.buf;
-       start = NULL;
-       mid = data;
-       while (i < buffer.len) {
-               if (!start)
-                       start = &data[i];
-               if (data[i] == '\t')
-                       mid = &data[i];
-               if (data[i] == '\n') {
-                       data[i] = 0;
-                       ref_name = mid + 1;
-                       ref = xmalloc(sizeof(struct ref) +
-                                     strlen(ref_name) + 1);
-                       memset(ref, 0, sizeof(struct ref));
-                       strcpy(ref->name, ref_name);
-                       get_sha1_hex(start, ref->old_sha1);
-                       if (!refs)
-                               refs = ref;
-                       if (last_ref)
-                               last_ref->next = ref;
-                       last_ref = ref;
-                       start = NULL;
-               }
-               i++;
-       }
-
-       strbuf_release(&buffer);
-
-       ref = alloc_ref("HEAD");
-       if (!walker->fetch_ref(walker, ref) &&
-           !resolve_remote_symref(ref, refs)) {
-               ref->next = refs;
-               refs = ref;
-       } else {
-               free(ref);
-       }
-
-       return refs;
-}
-
-static int fetch_objs_via_curl(struct transport *transport,
-                                int nr_objs, const struct ref **to_fetch)
-{
-       if (!transport->data)
-               transport->data = get_http_walker(transport->url,
-                                               transport->remote);
-       return fetch_objs_via_walker(transport, nr_objs, to_fetch);
-}
-
-#endif
-
 struct bundle_transport_data {
        int fd;
        struct bundle_header header;
@@ -563,7 +428,7 @@ static struct ref *get_refs_from_bundle(struct transport *transport, int for_pus
 }
 
 static int fetch_refs_from_bundle(struct transport *transport,
-                              int nr_heads, const struct ref **to_fetch)
+                              int nr_heads, struct ref **to_fetch)
 {
        struct bundle_transport_data *data = transport->data;
        return unbundle(&data->header, data->fd);
@@ -579,41 +444,36 @@ static int close_bundle(struct transport *transport)
 }
 
 struct git_transport_data {
-       unsigned thin : 1;
-       unsigned keep : 1;
-       unsigned followtags : 1;
-       int depth;
+       struct git_transport_options options;
        struct child_process *conn;
        int fd[2];
-       const char *uploadpack;
-       const char *receivepack;
+       unsigned got_remote_heads : 1;
        struct extra_have_objects extra_have;
 };
 
-static int set_git_option(struct transport *connection,
+static int set_git_option(struct git_transport_options *opts,
                          const char *name, const char *value)
 {
-       struct git_transport_data *data = connection->data;
        if (!strcmp(name, TRANS_OPT_UPLOADPACK)) {
-               data->uploadpack = value;
+               opts->uploadpack = value;
                return 0;
        } else if (!strcmp(name, TRANS_OPT_RECEIVEPACK)) {
-               data->receivepack = value;
+               opts->receivepack = value;
                return 0;
        } else if (!strcmp(name, TRANS_OPT_THIN)) {
-               data->thin = !!value;
+               opts->thin = !!value;
                return 0;
        } else if (!strcmp(name, TRANS_OPT_FOLLOWTAGS)) {
-               data->followtags = !!value;
+               opts->followtags = !!value;
                return 0;
        } else if (!strcmp(name, TRANS_OPT_KEEP)) {
-               data->keep = !!value;
+               opts->keep = !!value;
                return 0;
        } else if (!strcmp(name, TRANS_OPT_DEPTH)) {
                if (!value)
-                       data->depth = 0;
+                       opts->depth = 0;
                else
-                       data->depth = atoi(value);
+                       opts->depth = atoi(value);
                return 0;
        }
        return 1;
@@ -622,9 +482,15 @@ static int set_git_option(struct transport *connection,
 static int connect_setup(struct transport *transport, int for_push, int verbose)
 {
        struct git_transport_data *data = transport->data;
+
+       if (data->conn)
+               return 0;
+
        data->conn = git_connect(data->fd, transport->url,
-                                for_push ? data->receivepack : data->uploadpack,
+                                for_push ? data->options.receivepack :
+                                data->options.uploadpack,
                                 verbose ? CONNECT_VERBOSE : 0);
+
        return 0;
 }
 
@@ -636,12 +502,13 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus
        connect_setup(transport, for_push, 0);
        get_remote_heads(data->fd[0], &refs, 0, NULL,
                         for_push ? REF_NORMAL : 0, &data->extra_have);
+       data->got_remote_heads = 1;
 
        return refs;
 }
 
 static int fetch_refs_via_pack(struct transport *transport,
-                              int nr_heads, const struct ref **to_fetch)
+                              int nr_heads, struct ref **to_fetch)
 {
        struct git_transport_data *data = transport->data;
        char **heads = xmalloc(nr_heads * sizeof(*heads));
@@ -653,22 +520,23 @@ static int fetch_refs_via_pack(struct transport *transport,
        struct ref *refs_tmp = NULL;
 
        memset(&args, 0, sizeof(args));
-       args.uploadpack = data->uploadpack;
-       args.keep_pack = data->keep;
+       args.uploadpack = data->options.uploadpack;
+       args.keep_pack = data->options.keep;
        args.lock_pack = 1;
-       args.use_thin_pack = data->thin;
-       args.include_tag = data->followtags;
+       args.use_thin_pack = data->options.thin;
+       args.include_tag = data->options.followtags;
        args.verbose = (transport->verbose > 0);
        args.quiet = (transport->verbose < 0);
-       args.no_progress = args.quiet || (!transport->progress && !isatty(1));
-       args.depth = data->depth;
+       args.no_progress = !transport->progress;
+       args.depth = data->options.depth;
 
        for (i = 0; i < nr_heads; i++)
                origh[i] = heads[i] = xstrdup(to_fetch[i]->name);
 
-       if (!data->conn) {
+       if (!data->got_remote_heads) {
                connect_setup(transport, 0, 0);
                get_remote_heads(data->fd[0], &refs_tmp, 0, NULL, 0, NULL);
+               data->got_remote_heads = 1;
        }
 
        refs = fetch_pack(&args, data->fd, data->conn,
@@ -679,6 +547,7 @@ static int fetch_refs_via_pack(struct transport *transport,
        if (finish_connect(data->conn))
                refs = NULL;
        data->conn = NULL;
+       data->got_remote_heads = 0;
 
        free_refs(refs_tmp);
 
@@ -690,7 +559,22 @@ static int fetch_refs_via_pack(struct transport *transport,
        return (refs ? 0 : -1);
 }
 
-static int refs_pushed(struct ref *ref)
+static int push_had_errors(struct ref *ref)
+{
+       for (; ref; ref = ref->next) {
+               switch (ref->status) {
+               case REF_STATUS_NONE:
+               case REF_STATUS_UPTODATE:
+               case REF_STATUS_OK:
+                       break;
+               default:
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+int transport_refs_pushed(struct ref *ref)
 {
        for (; ref; ref = ref->next) {
                switch(ref->status) {
@@ -704,7 +588,7 @@ static int refs_pushed(struct ref *ref)
        return 0;
 }
 
-static void update_tracking_ref(struct remote *remote, struct ref *ref, int verbose)
+void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose)
 {
        struct refspec rs;
 
@@ -726,21 +610,30 @@ static void update_tracking_ref(struct remote *remote, struct ref *ref, int verb
        }
 }
 
-#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
-
-static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg)
+static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg, int porcelain)
 {
-       fprintf(stderr, " %c %-*s ", flag, SUMMARY_WIDTH, summary);
-       if (from)
-               fprintf(stderr, "%s -> %s", prettify_ref(from), prettify_ref(to));
-       else
-               fputs(prettify_ref(to), stderr);
-       if (msg) {
-               fputs(" (", stderr);
-               fputs(msg, stderr);
-               fputc(')', stderr);
+       if (porcelain) {
+               if (from)
+                       fprintf(stdout, "%c\t%s:%s\t", flag, from->name, to->name);
+               else
+                       fprintf(stdout, "%c\t:%s\t", flag, to->name);
+               if (msg)
+                       fprintf(stdout, "%s (%s)\n", summary, msg);
+               else
+                       fprintf(stdout, "%s\n", summary);
+       } else {
+               fprintf(stderr, " %c %-*s ", flag, TRANSPORT_SUMMARY_WIDTH, summary);
+               if (from)
+                       fprintf(stderr, "%s -> %s", prettify_refname(from->name), prettify_refname(to->name));
+               else
+                       fputs(prettify_refname(to->name), stderr);
+               if (msg) {
+                       fputs(" (", stderr);
+                       fputs(msg, stderr);
+                       fputc(')', stderr);
+               }
+               fputc('\n', stderr);
        }
-       fputc('\n', stderr);
 }
 
 static const char *status_abbrev(unsigned char sha1[20])
@@ -748,15 +641,15 @@ static const char *status_abbrev(unsigned char sha1[20])
        return find_unique_abbrev(sha1, DEFAULT_ABBREV);
 }
 
-static void print_ok_ref_status(struct ref *ref)
+static void print_ok_ref_status(struct ref *ref, int porcelain)
 {
        if (ref->deletion)
-               print_ref_status('-', "[deleted]", ref, NULL, NULL);
+               print_ref_status('-', "[deleted]", ref, NULL, NULL, porcelain);
        else if (is_null_sha1(ref->old_sha1))
                print_ref_status('*',
                        (!prefixcmp(ref->name, "refs/tags/") ? "[new tag]" :
-                         "[new branch]"),
-                       ref, ref->peer_ref, NULL);
+                       "[new branch]"),
+                       ref, ref->peer_ref, NULL, porcelain);
        else {
                char quickref[84];
                char type;
@@ -774,50 +667,51 @@ static void print_ok_ref_status(struct ref *ref)
                }
                strcat(quickref, status_abbrev(ref->new_sha1));
 
-               print_ref_status(type, quickref, ref, ref->peer_ref, msg);
+               print_ref_status(type, quickref, ref, ref->peer_ref, msg, porcelain);
        }
 }
 
-static int print_one_push_status(struct ref *ref, const char *dest, int count)
+static int print_one_push_status(struct ref *ref, const char *dest, int count, int porcelain)
 {
        if (!count)
-               fprintf(stderr, "To %s\n", dest);
+               fprintf(porcelain ? stdout : stderr, "To %s\n", dest);
 
        switch(ref->status) {
        case REF_STATUS_NONE:
-               print_ref_status('X', "[no match]", ref, NULL, NULL);
+               print_ref_status('X', "[no match]", ref, NULL, NULL, porcelain);
                break;
        case REF_STATUS_REJECT_NODELETE:
                print_ref_status('!', "[rejected]", ref, NULL,
-                               "remote does not support deleting refs");
+                                                "remote does not support deleting refs", porcelain);
                break;
        case REF_STATUS_UPTODATE:
                print_ref_status('=', "[up to date]", ref,
-                               ref->peer_ref, NULL);
+                                                ref->peer_ref, NULL, porcelain);
                break;
        case REF_STATUS_REJECT_NONFASTFORWARD:
                print_ref_status('!', "[rejected]", ref, ref->peer_ref,
-                               "non-fast forward");
+                                                "non-fast-forward", porcelain);
                break;
        case REF_STATUS_REMOTE_REJECT:
                print_ref_status('!', "[remote rejected]", ref,
-                               ref->deletion ? NULL : ref->peer_ref,
-                               ref->remote_status);
+                                                ref->deletion ? NULL : ref->peer_ref,
+                                                ref->remote_status, porcelain);
                break;
        case REF_STATUS_EXPECTING_REPORT:
                print_ref_status('!', "[remote failure]", ref,
-                               ref->deletion ? NULL : ref->peer_ref,
-                               "remote failed to report status");
+                                                ref->deletion ? NULL : ref->peer_ref,
+                                                "remote failed to report status", porcelain);
                break;
        case REF_STATUS_OK:
-               print_ok_ref_status(ref);
+               print_ok_ref_status(ref, porcelain);
                break;
        }
 
        return 1;
 }
 
-static void print_push_status(const char *dest, struct ref *refs, int verbose)
+void transport_print_push_status(const char *dest, struct ref *refs,
+                                 int verbose, int porcelain, int *nonfastforward)
 {
        struct ref *ref;
        int n = 0;
@@ -825,22 +719,25 @@ static void print_push_status(const char *dest, struct ref *refs, int verbose)
        if (verbose) {
                for (ref = refs; ref; ref = ref->next)
                        if (ref->status == REF_STATUS_UPTODATE)
-                               n += print_one_push_status(ref, dest, n);
+                               n += print_one_push_status(ref, dest, n, porcelain);
        }
 
        for (ref = refs; ref; ref = ref->next)
                if (ref->status == REF_STATUS_OK)
-                       n += print_one_push_status(ref, dest, n);
+                       n += print_one_push_status(ref, dest, n, porcelain);
 
+       *nonfastforward = 0;
        for (ref = refs; ref; ref = ref->next) {
                if (ref->status != REF_STATUS_NONE &&
                    ref->status != REF_STATUS_UPTODATE &&
                    ref->status != REF_STATUS_OK)
-                       n += print_one_push_status(ref, dest, n);
+                       n += print_one_push_status(ref, dest, n, porcelain);
+               if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD)
+                       *nonfastforward = 1;
        }
 }
 
-static void verify_remote_names(int nr_heads, const char **heads)
+void transport_verify_remote_names(int nr_heads, const char **heads)
 {
        int i;
 
@@ -877,19 +774,23 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
        struct send_pack_args args;
        int ret;
 
-       if (!data->conn) {
+       if (!data->got_remote_heads) {
                struct ref *tmp_refs;
                connect_setup(transport, 1, 0);
 
                get_remote_heads(data->fd[0], &tmp_refs, 0, NULL, REF_NORMAL,
                                 NULL);
+               data->got_remote_heads = 1;
        }
 
+       memset(&args, 0, sizeof(args));
        args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);
        args.force_update = !!(flags & TRANSPORT_PUSH_FORCE);
-       args.use_thin_pack = data->thin;
-       args.verbose = !!(flags & TRANSPORT_PUSH_VERBOSE);
+       args.use_thin_pack = data->options.thin;
+       args.verbose = (transport->verbose > 0);
+       args.quiet = (transport->verbose < 0);
        args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
+       args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN);
 
        ret = send_pack(&args, data->fd, data->conn, remote_refs,
                        &data->extra_have);
@@ -898,15 +799,28 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
        close(data->fd[0]);
        ret |= finish_connect(data->conn);
        data->conn = NULL;
+       data->got_remote_heads = 0;
 
        return ret;
 }
 
+static int connect_git(struct transport *transport, const char *name,
+                      const char *executable, int fd[2])
+{
+       struct git_transport_data *data = transport->data;
+       data->conn = git_connect(data->fd, transport->url,
+                                executable, 0);
+       fd[0] = data->fd[0];
+       fd[1] = data->fd[1];
+       return 0;
+}
+
 static int disconnect_git(struct transport *transport)
 {
        struct git_transport_data *data = transport->data;
        if (data->conn) {
-               packet_flush(data->fd[1]);
+               if (data->got_remote_heads)
+                       packet_flush(data->fd[1]);
                close(data->fd[0]);
                close(data->fd[1]);
                finish_connect(data->conn);
@@ -916,6 +830,32 @@ static int disconnect_git(struct transport *transport)
        return 0;
 }
 
+void transport_take_over(struct transport *transport,
+                        struct child_process *child)
+{
+       struct git_transport_data *data;
+
+       if (!transport->smart_options)
+               die("Bug detected: Taking over transport requires non-NULL "
+                   "smart_options field.");
+
+       data = xcalloc(1, sizeof(*data));
+       data->options = *transport->smart_options;
+       data->conn = child;
+       data->fd[0] = data->conn->out;
+       data->fd[1] = data->conn->in;
+       data->got_remote_heads = 0;
+       transport->data = data;
+
+       transport->set_option = NULL;
+       transport->get_refs_list = get_refs_via_connect;
+       transport->fetch = fetch_refs_via_pack;
+       transport->push = NULL;
+       transport->push_refs = git_transport_push;
+       transport->disconnect = disconnect_git;
+       transport->smart_options = &(data->options);
+}
+
 static int is_local(const char *url)
 {
        const char *colon = strchr(url, ':');
@@ -932,54 +872,89 @@ static int is_file(const char *url)
        return S_ISREG(buf.st_mode);
 }
 
+static int external_specification_len(const char *url)
+{
+       return strchr(url, ':') - url;
+}
+
 struct transport *transport_get(struct remote *remote, const char *url)
 {
+       const char *helper;
        struct transport *ret = xcalloc(1, sizeof(*ret));
 
+       ret->progress = isatty(2);
+
+       if (!remote)
+               die("No remote provided to transport_get()");
+
+       ret->got_remote_refs = 0;
        ret->remote = remote;
+       helper = remote->foreign_vcs;
+
+       if (!url && remote->url)
+               url = remote->url[0];
        ret->url = url;
 
-       if (!prefixcmp(url, "rsync:")) {
+       /* maybe it is a foreign URL? */
+       if (url) {
+               const char *p = url;
+
+               while (is_urlschemechar(p == url, *p))
+                       p++;
+               if (!prefixcmp(p, "::"))
+                       helper = xstrndup(url, p - url);
+       }
+
+       if (helper) {
+               transport_helper_init(ret, helper);
+       } else if (!prefixcmp(url, "rsync:")) {
                ret->get_refs_list = get_refs_via_rsync;
                ret->fetch = fetch_objs_via_rsync;
                ret->push = rsync_transport_push;
-
-       } else if (!prefixcmp(url, "http://")
-               || !prefixcmp(url, "https://")
-               || !prefixcmp(url, "ftp://")) {
-#ifdef NO_CURL
-               error("git was compiled without libcurl support.");
-#else
-               ret->get_refs_list = get_refs_via_curl;
-               ret->fetch = fetch_objs_via_curl;
-               ret->push = curl_transport_push;
-#endif
-               ret->disconnect = disconnect_walker;
-
+               ret->smart_options = NULL;
        } else if (is_local(url) && is_file(url)) {
                struct bundle_transport_data *data = xcalloc(1, sizeof(*data));
                ret->data = data;
                ret->get_refs_list = get_refs_from_bundle;
                ret->fetch = fetch_refs_from_bundle;
                ret->disconnect = close_bundle;
-
-       } else {
+               ret->smart_options = NULL;
+       } else if (!is_url(url)
+               || !prefixcmp(url, "file://")
+               || !prefixcmp(url, "git://")
+               || !prefixcmp(url, "ssh://")
+               || !prefixcmp(url, "git+ssh://")
+               || !prefixcmp(url, "ssh+git://")) {
+               /* These are builtin smart transports. */
                struct git_transport_data *data = xcalloc(1, sizeof(*data));
                ret->data = data;
-               ret->set_option = set_git_option;
+               ret->set_option = NULL;
                ret->get_refs_list = get_refs_via_connect;
                ret->fetch = fetch_refs_via_pack;
                ret->push_refs = git_transport_push;
+               ret->connect = connect_git;
                ret->disconnect = disconnect_git;
+               ret->smart_options = &(data->options);
 
-               data->thin = 1;
                data->conn = NULL;
-               data->uploadpack = "git-upload-pack";
-               if (remote && remote->uploadpack)
-                       data->uploadpack = remote->uploadpack;
-               data->receivepack = "git-receive-pack";
-               if (remote && remote->receivepack)
-                       data->receivepack = remote->receivepack;
+               data->got_remote_heads = 0;
+       } else {
+               /* Unknown protocol in URL. Pass to external handler. */
+               int len = external_specification_len(url);
+               char *handler = xmalloc(len + 1);
+               handler[len] = 0;
+               strncpy(handler, url, len);
+               transport_helper_init(ret, handler);
+       }
+
+       if (ret->smart_options) {
+               ret->smart_options->thin = 1;
+               ret->smart_options->uploadpack = "git-upload-pack";
+               if (remote->uploadpack)
+                       ret->smart_options->uploadpack = remote->uploadpack;
+               ret->smart_options->receivepack = "git-receive-pack";
+               if (remote->receivepack)
+                       ret->smart_options->receivepack = remote->receivepack;
        }
 
        return ret;
@@ -988,52 +963,106 @@ struct transport *transport_get(struct remote *remote, const char *url)
 int transport_set_option(struct transport *transport,
                         const char *name, const char *value)
 {
+       int git_reports = 1, protocol_reports = 1;
+
+       if (transport->smart_options)
+               git_reports = set_git_option(transport->smart_options,
+                                            name, value);
+
        if (transport->set_option)
-               return transport->set_option(transport, name, value);
+               protocol_reports = transport->set_option(transport, name,
+                                                       value);
+
+       /* If either report is 0, report 0 (success). */
+       if (!git_reports || !protocol_reports)
+               return 0;
+       /* If either reports -1 (invalid value), report -1. */
+       if ((git_reports == -1) || (protocol_reports == -1))
+               return -1;
+       /* Otherwise if both report unknown, report unknown. */
        return 1;
 }
 
+void transport_set_verbosity(struct transport *transport, int verbosity,
+       int force_progress)
+{
+       if (verbosity >= 2)
+               transport->verbose = verbosity <= 3 ? verbosity : 3;
+       if (verbosity < 0)
+               transport->verbose = -1;
+
+       /**
+        * Rules used to determine whether to report progress (processing aborts
+        * when a rule is satisfied):
+        *
+        *   1. Report progress, if force_progress is 1 (ie. --progress).
+        *   2. Don't report progress, if verbosity < 0 (ie. -q/--quiet ).
+        *   3. Report progress if isatty(2) is 1.
+        **/
+       transport->progress = force_progress || (verbosity >= 0 && isatty(2));
+}
+
 int transport_push(struct transport *transport,
-                  int refspec_nr, const char **refspec, int flags)
+                  int refspec_nr, const char **refspec, int flags,
+                  int *nonfastforward)
 {
-       verify_remote_names(refspec_nr, refspec);
+       *nonfastforward = 0;
+       transport_verify_remote_names(refspec_nr, refspec);
+
+       if (transport->push) {
+               /* Maybe FIXME. But no important transport uses this case. */
+               if (flags & TRANSPORT_PUSH_SET_UPSTREAM)
+                       die("This transport does not support using --set-upstream");
 
-       if (transport->push)
                return transport->push(transport, refspec_nr, refspec, flags);
-       if (transport->push_refs) {
+       } else if (transport->push_refs) {
                struct ref *remote_refs =
                        transport->get_refs_list(transport, 1);
-               struct ref **remote_tail;
                struct ref *local_refs = get_local_heads();
                int match_flags = MATCH_REFS_NONE;
-               int verbose = flags & TRANSPORT_PUSH_VERBOSE;
-               int ret;
+               int verbose = (transport->verbose > 0);
+               int quiet = (transport->verbose < 0);
+               int porcelain = flags & TRANSPORT_PUSH_PORCELAIN;
+               int pretend = flags & TRANSPORT_PUSH_DRY_RUN;
+               int push_ret, ret, err;
 
                if (flags & TRANSPORT_PUSH_ALL)
                        match_flags |= MATCH_REFS_ALL;
                if (flags & TRANSPORT_PUSH_MIRROR)
                        match_flags |= MATCH_REFS_MIRROR;
 
-               remote_tail = &remote_refs;
-               while (*remote_tail)
-                       remote_tail = &((*remote_tail)->next);
-               if (match_refs(local_refs, remote_refs, &remote_tail,
+               if (match_refs(local_refs, &remote_refs,
                               refspec_nr, refspec, match_flags)) {
                        return -1;
                }
 
-               ret = transport->push_refs(transport, remote_refs, flags);
+               set_ref_status_for_push(remote_refs,
+                       flags & TRANSPORT_PUSH_MIRROR,
+                       flags & TRANSPORT_PUSH_FORCE);
+
+               push_ret = transport->push_refs(transport, remote_refs, flags);
+               err = push_had_errors(remote_refs);
+               ret = push_ret | err;
 
-               print_push_status(transport->url, remote_refs, verbose);
+               if (!quiet || err)
+                       transport_print_push_status(transport->url, remote_refs,
+                                       verbose | porcelain, porcelain,
+                                       nonfastforward);
+
+               if (flags & TRANSPORT_PUSH_SET_UPSTREAM)
+                       set_upstreams(transport, remote_refs, pretend);
 
                if (!(flags & TRANSPORT_PUSH_DRY_RUN)) {
                        struct ref *ref;
                        for (ref = remote_refs; ref; ref = ref->next)
-                               update_tracking_ref(transport->remote, ref, verbose);
+                               transport_update_tracking_ref(transport->remote, ref, verbose);
                }
 
-               if (!ret && !refs_pushed(remote_refs))
+               if (porcelain && !push_ret)
+                       puts("Done");
+               else if (!quiet && !ret && !transport_refs_pushed(remote_refs))
                        fprintf(stderr, "Everything up-to-date\n");
+
                return ret;
        }
        return 1;
@@ -1041,27 +1070,46 @@ int transport_push(struct transport *transport,
 
 const struct ref *transport_get_remote_refs(struct transport *transport)
 {
-       if (!transport->remote_refs)
+       if (!transport->got_remote_refs) {
                transport->remote_refs = transport->get_refs_list(transport, 0);
+               transport->got_remote_refs = 1;
+       }
+
        return transport->remote_refs;
 }
 
-int transport_fetch_refs(struct transport *transport, const struct ref *refs)
+int transport_fetch_refs(struct transport *transport, struct ref *refs)
 {
        int rc;
-       int nr_heads = 0, nr_alloc = 0;
-       const struct ref **heads = NULL;
-       const struct ref *rm;
+       int nr_heads = 0, nr_alloc = 0, nr_refs = 0;
+       struct ref **heads = NULL;
+       struct ref *rm;
 
        for (rm = refs; rm; rm = rm->next) {
+               nr_refs++;
                if (rm->peer_ref &&
+                   !is_null_sha1(rm->old_sha1) &&
                    !hashcmp(rm->peer_ref->old_sha1, rm->old_sha1))
                        continue;
                ALLOC_GROW(heads, nr_heads + 1, nr_alloc);
                heads[nr_heads++] = rm;
        }
 
+       if (!nr_heads) {
+               /*
+                * When deepening of a shallow repository is requested,
+                * then local and remote refs are likely to still be equal.
+                * Just feed them all to the fetch method in that case.
+                * This condition shouldn't be met in a non-deepening fetch
+                * (see builtin-fetch.c:quickfetch()).
+                */
+               heads = xmalloc(nr_refs * sizeof(*heads));
+               for (rm = refs; rm; rm = rm->next)
+                       heads[nr_heads++] = rm;
+       }
+
        rc = transport->fetch(transport, nr_heads, heads);
+
        free(heads);
        return rc;
 }
@@ -1069,12 +1117,21 @@ int transport_fetch_refs(struct transport *transport, const struct ref *refs)
 void transport_unlock_pack(struct transport *transport)
 {
        if (transport->pack_lockfile) {
-               unlink(transport->pack_lockfile);
+               unlink_or_warn(transport->pack_lockfile);
                free(transport->pack_lockfile);
                transport->pack_lockfile = NULL;
        }
 }
 
+int transport_connect(struct transport *transport, const char *name,
+                     const char *exec, int fd[2])
+{
+       if (transport->connect)
+               return transport->connect(transport, name, exec, fd);
+       else
+               die("Operation not supported by protocol");
+}
+
 int transport_disconnect(struct transport *transport)
 {
        int ret = 0;
@@ -1083,3 +1140,51 @@ int transport_disconnect(struct transport *transport)
        free(transport);
        return ret;
 }
+
+/*
+ * Strip username (and password) from an url and return
+ * it in a newly allocated string.
+ */
+char *transport_anonymize_url(const char *url)
+{
+       char *anon_url, *scheme_prefix, *anon_part;
+       size_t anon_len, prefix_len = 0;
+
+       anon_part = strchr(url, '@');
+       if (is_local(url) || !anon_part)
+               goto literal_copy;
+
+       anon_len = strlen(++anon_part);
+       scheme_prefix = strstr(url, "://");
+       if (!scheme_prefix) {
+               if (!strchr(anon_part, ':'))
+                       /* cannot be "me@there:/path/name" */
+                       goto literal_copy;
+       } else {
+               const char *cp;
+               /* make sure scheme is reasonable */
+               for (cp = url; cp < scheme_prefix; cp++) {
+                       switch (*cp) {
+                               /* RFC 1738 2.1 */
+                       case '+': case '.': case '-':
+                               break; /* ok */
+                       default:
+                               if (isalnum(*cp))
+                                       break;
+                               /* it isn't */
+                               goto literal_copy;
+                       }
+               }
+               /* @ past the first slash does not count */
+               cp = strchr(scheme_prefix + 3, '/');
+               if (cp && cp < anon_part)
+                       goto literal_copy;
+               prefix_len = scheme_prefix - url + 3;
+       }
+       anon_url = xcalloc(1, 1 + prefix_len + anon_len);
+       memcpy(anon_url, url, prefix_len);
+       memcpy(anon_url + prefix_len, anon_part, anon_len);
+       return anon_url;
+literal_copy:
+       return xstrdup(url);
+}
index b1c225276619c3bc4dda5dcd0469e242ab421ea0..c59d97388e6fdb1dccd4dca0d41dccc3d129f8fb 100644 (file)
@@ -4,12 +4,27 @@
 #include "cache.h"
 #include "remote.h"
 
+struct git_transport_options {
+       unsigned thin : 1;
+       unsigned keep : 1;
+       unsigned followtags : 1;
+       int depth;
+       const char *uploadpack;
+       const char *receivepack;
+};
+
 struct transport {
        struct remote *remote;
        const char *url;
        void *data;
        const struct ref *remote_refs;
 
+       /**
+        * Indicates whether we already called get_refs_list(); set by
+        * transport.c::transport_get_remote_refs().
+        */
+       unsigned got_remote_refs : 1;
+
        /**
         * Returns 0 if successful, positive if the option is not
         * recognized or is inapplicable, and negative if the option
@@ -18,23 +33,76 @@ struct transport {
        int (*set_option)(struct transport *connection, const char *name,
                          const char *value);
 
+       /**
+        * Returns a list of the remote side's refs. In order to allow
+        * the transport to try to share connections, for_push is a
+        * hint as to whether the ultimate operation is a push or a fetch.
+        *
+        * If the transport is able to determine the remote hash for
+        * the ref without a huge amount of effort, it should store it
+        * in the ref's old_sha1 field; otherwise it should be all 0.
+        **/
        struct ref *(*get_refs_list)(struct transport *transport, int for_push);
-       int (*fetch)(struct transport *transport, int refs_nr, const struct ref **refs);
+
+       /**
+        * Fetch the objects for the given refs. Note that this gets
+        * an array, and should ignore the list structure.
+        *
+        * If the transport did not get hashes for refs in
+        * get_refs_list(), it should set the old_sha1 fields in the
+        * provided refs now.
+        **/
+       int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs);
+
+       /**
+        * Push the objects and refs. Send the necessary objects, and
+        * then, for any refs where peer_ref is set and
+        * peer_ref->new_sha1 is different from old_sha1, tell the
+        * remote side to update each ref in the list from old_sha1 to
+        * peer_ref->new_sha1.
+        *
+        * Where possible, set the status for each ref appropriately.
+        *
+        * The transport must modify new_sha1 in the ref to the new
+        * value if the remote accepted the change. Note that this
+        * could be a different value from peer_ref->new_sha1 if the
+        * process involved generating new commits.
+        **/
        int (*push_refs)(struct transport *transport, struct ref *refs, int flags);
        int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags);
+       int (*connect)(struct transport *connection, const char *name,
+                      const char *executable, int fd[2]);
 
+       /** get_refs_list(), fetch(), and push_refs() can keep
+        * resources (such as a connection) reserved for futher
+        * use. disconnect() releases these resources.
+        **/
        int (*disconnect)(struct transport *connection);
        char *pack_lockfile;
-       signed verbose : 2;
-       /* Force progress even if the output is not a tty */
+       signed verbose : 3;
+       /**
+        * Transports should not set this directly, and should use this
+        * value without having to check isatty(2), -q/--quiet
+        * (transport->verbose < 0), etc. - checking has already been done
+        * in transport_set_verbosity().
+        **/
        unsigned progress : 1;
+       /*
+        * If transport is at least potentially smart, this points to
+        * git_transport_options structure to use in case transport
+        * actually turns out to be smart.
+        */
+       struct git_transport_options *smart_options;
 };
 
 #define TRANSPORT_PUSH_ALL 1
 #define TRANSPORT_PUSH_FORCE 2
 #define TRANSPORT_PUSH_DRY_RUN 4
 #define TRANSPORT_PUSH_MIRROR 8
-#define TRANSPORT_PUSH_VERBOSE 16
+#define TRANSPORT_PUSH_PORCELAIN 16
+#define TRANSPORT_PUSH_SET_UPSTREAM 32
+
+#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
 
 /* Returns a transport suitable for the url */
 struct transport *transport_get(struct remote *, const char *);
@@ -65,14 +133,36 @@ struct transport *transport_get(struct remote *, const char *);
  **/
 int transport_set_option(struct transport *transport, const char *name,
                         const char *value);
+void transport_set_verbosity(struct transport *transport, int verbosity,
+       int force_progress);
 
 int transport_push(struct transport *connection,
-                  int refspec_nr, const char **refspec, int flags);
+                  int refspec_nr, const char **refspec, int flags,
+                  int * nonfastforward);
 
 const struct ref *transport_get_remote_refs(struct transport *transport);
 
-int transport_fetch_refs(struct transport *transport, const struct ref *refs);
+int transport_fetch_refs(struct transport *transport, struct ref *refs);
 void transport_unlock_pack(struct transport *transport);
 int transport_disconnect(struct transport *transport);
+char *transport_anonymize_url(const char *url);
+void transport_take_over(struct transport *transport,
+                        struct child_process *child);
+
+int transport_connect(struct transport *transport, const char *name,
+                     const char *exec, int fd[2]);
+
+/* Transport methods defined outside transport.c */
+int transport_helper_init(struct transport *transport, const char *name);
+
+/* common methods used by transport.c and builtin-send-pack.c */
+void transport_verify_remote_names(int nr_heads, const char **heads);
+
+void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose);
+
+int transport_refs_pushed(struct ref *ref);
+
+void transport_print_push_status(const char *dest, struct ref *refs,
+                 int verbose, int porcelain, int *nonfastforward);
 
 #endif
index edd83949bf0896bedff4cab8e2b037eba574a45e..cd659c6fe447b6fcdedee2843113feb358f23849 100644 (file)
@@ -68,7 +68,7 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
                if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
                        newbase[baselen + pathlen1] = 0;
                        opt->change(opt, mode1, mode2,
-                                   sha1, sha2, newbase);
+                                   sha1, sha2, newbase, 0, 0);
                        newbase[baselen + pathlen1] = '/';
                }
                retval = diff_tree_sha1(sha1, sha2, newbase, opt);
@@ -77,7 +77,7 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
        }
 
        fullname = malloc_fullname(base, baselen, path1, pathlen1);
-       opt->change(opt, mode1, mode2, sha1, sha2, fullname);
+       opt->change(opt, mode1, mode2, sha1, sha2, fullname, 0, 0);
        free(fullname);
        return 0;
 }
@@ -239,6 +239,12 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree
                if (!tree || type != OBJ_TREE)
                        die("corrupt tree sha %s", sha1_to_hex(sha1));
 
+               if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
+                       newbase[baselen + pathlen] = 0;
+                       opt->add_remove(opt, *prefix, mode, sha1, newbase, 0);
+                       newbase[baselen + pathlen] = '/';
+               }
+
                init_tree_desc(&inner, tree, size);
                show_tree(opt, prefix, &inner, newbase, baselen + 1 + pathlen);
 
@@ -246,7 +252,7 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree
                free(newbase);
        } else {
                char *fullname = malloc_fullname(base, baselen, path, pathlen);
-               opt->add_remove(opt, prefix[0], mode, sha1, fullname);
+               opt->add_remove(opt, prefix[0], mode, sha1, fullname, 0);
                free(fullname);
        }
 }
@@ -280,7 +286,8 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, stru
        int baselen = strlen(base);
 
        for (;;) {
-               if (DIFF_OPT_TST(opt, QUIET) && DIFF_OPT_TST(opt, HAS_CHANGES))
+               if (DIFF_OPT_TST(opt, QUICK) &&
+                   DIFF_OPT_TST(opt, HAS_CHANGES))
                        break;
                if (opt->nr_paths) {
                        skip_uninteresting(t1, base, baselen, opt);
@@ -339,7 +346,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
 
        diff_setup(&diff_opts);
        DIFF_OPT_SET(&diff_opts, RECURSIVE);
-       diff_opts.detect_rename = DIFF_DETECT_RENAME;
+       DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
        diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
        diff_opts.single_follow = opt->paths[0];
        diff_opts.break_opt = opt->break_opt;
@@ -352,6 +359,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
        diff_tree_release_paths(&diff_opts);
 
        /* Go through the new set of filepairing, and see if we find a more interesting one */
+       opt->found_follow = 0;
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
 
@@ -369,6 +377,16 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
                        diff_tree_release_paths(opt);
                        opt->paths[0] = xstrdup(p->one->path);
                        diff_tree_setup_paths(opt->paths, opt);
+
+                       /*
+                        * The caller expects us to return a set of vanilla
+                        * filepairs to let a later call to diffcore_std()
+                        * it makes to sort the renames out (among other
+                        * things), but we already have found renames
+                        * ourselves; signal diffcore_std() not to muck with
+                        * rename information.
+                        */
+                       opt->found_follow = 1;
                        break;
                }
        }
@@ -405,7 +423,7 @@ int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const cha
        init_tree_desc(&t1, tree1, size1);
        init_tree_desc(&t2, tree2, size2);
        retval = diff_tree(&t1, &t2, base, opt);
-       if (DIFF_OPT_TST(opt, FOLLOW_RENAMES) && diff_might_be_rename()) {
+       if (!*base && DIFF_OPT_TST(opt, FOLLOW_RENAMES) && diff_might_be_rename()) {
                init_tree_desc(&t1, tree1, size1);
                init_tree_desc(&t2, tree2, size2);
                try_to_follow_renames(&t1, &t2, base, opt);
index 02e2aed7737207225f1b96eed774a1b75dd6d8d9..a9bbf4e2354df5ce6b010873b419731343a12c7d 100644 (file)
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "tree-walk.h"
+#include "unpack-trees.h"
 #include "tree.h"
 
 static const char *get_mode(const char *str, unsigned int *modep)
@@ -60,13 +61,6 @@ void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1)
        return buf;
 }
 
-static int entry_compare(struct name_entry *a, struct name_entry *b)
-{
-       return df_name_compare(
-                       a->path, tree_entry_len(a->path, a->sha1), a->mode,
-                       b->path, tree_entry_len(b->path, b->sha1), b->mode);
-}
-
 static void entry_clear(struct name_entry *a)
 {
        memset(a, 0, sizeof(*a));
@@ -138,67 +132,269 @@ char *make_traverse_path(char *path, const struct traverse_info *info, const str
        return path;
 }
 
+struct tree_desc_skip {
+       struct tree_desc_skip *prev;
+       const void *ptr;
+};
+
+struct tree_desc_x {
+       struct tree_desc d;
+       struct tree_desc_skip *skip;
+};
+
+static int name_compare(const char *a, int a_len,
+                       const char *b, int b_len)
+{
+       int len = (a_len < b_len) ? a_len : b_len;
+       int cmp = memcmp(a, b, len);
+       if (cmp)
+               return cmp;
+       return (a_len - b_len);
+}
+
+static int check_entry_match(const char *a, int a_len, const char *b, int b_len)
+{
+       /*
+        * The caller wants to pick *a* from a tree or nothing.
+        * We are looking at *b* in a tree.
+        *
+        * (0) If a and b are the same name, we are trivially happy.
+        *
+        * There are three possibilities where *a* could be hiding
+        * behind *b*.
+        *
+        * (1) *a* == "t",   *b* == "ab"  i.e. *b* sorts earlier than *a* no
+        *                                matter what.
+        * (2) *a* == "t",   *b* == "t-2" and "t" is a subtree in the tree;
+        * (3) *a* == "t-2", *b* == "t"   and "t-2" is a blob in the tree.
+        *
+        * Otherwise we know *a* won't appear in the tree without
+        * scanning further.
+        */
+
+       int cmp = name_compare(a, a_len, b, b_len);
+
+       /* Most common case first -- reading sync'd trees */
+       if (!cmp)
+               return cmp;
+
+       if (0 < cmp) {
+               /* a comes after b; it does not matter if it is case (3)
+               if (b_len < a_len && !memcmp(a, b, b_len) && a[b_len] < '/')
+                       return 1;
+               */
+               return 1; /* keep looking */
+       }
+
+       /* b comes after a; are we looking at case (2)? */
+       if (a_len < b_len && !memcmp(a, b, a_len) && b[a_len] < '/')
+               return 1; /* keep looking */
+
+       return -1; /* a cannot appear in the tree */
+}
+
+/*
+ * From the extended tree_desc, extract the first name entry, while
+ * paying attention to the candidate "first" name.  Most importantly,
+ * when looking for an entry, if there are entries that sorts earlier
+ * in the tree object representation than that name, skip them and
+ * process the named entry first.  We will remember that we haven't
+ * processed the first entry yet, and in the later call skip the
+ * entry we processed early when update_extended_entry() is called.
+ *
+ * E.g. if the underlying tree object has these entries:
+ *
+ *    blob    "t-1"
+ *    blob    "t-2"
+ *    tree    "t"
+ *    blob    "t=1"
+ *
+ * and the "first" asks for "t", remember that we still need to
+ * process "t-1" and "t-2" but extract "t".  After processing the
+ * entry "t" from this call, the caller will let us know by calling
+ * update_extended_entry() that we can remember "t" has been processed
+ * already.
+ */
+
+static void extended_entry_extract(struct tree_desc_x *t,
+                                  struct name_entry *a,
+                                  const char *first,
+                                  int first_len)
+{
+       const char *path;
+       int len;
+       struct tree_desc probe;
+       struct tree_desc_skip *skip;
+
+       /*
+        * Extract the first entry from the tree_desc, but skip the
+        * ones that we already returned in earlier rounds.
+        */
+       while (1) {
+               if (!t->d.size) {
+                       entry_clear(a);
+                       break; /* not found */
+               }
+               entry_extract(&t->d, a);
+               for (skip = t->skip; skip; skip = skip->prev)
+                       if (a->path == skip->ptr)
+                               break; /* found */
+               if (!skip)
+                       break;
+               /* We have processed this entry already. */
+               update_tree_entry(&t->d);
+       }
+
+       if (!first || !a->path)
+               return;
+
+       /*
+        * The caller wants "first" from this tree, or nothing.
+        */
+       path = a->path;
+       len = tree_entry_len(a->path, a->sha1);
+       switch (check_entry_match(first, first_len, path, len)) {
+       case -1:
+               entry_clear(a);
+       case 0:
+               return;
+       default:
+               break;
+       }
+
+       /*
+        * We need to look-ahead -- we suspect that a subtree whose
+        * name is "first" may be hiding behind the current entry "path".
+        */
+       probe = t->d;
+       while (probe.size) {
+               entry_extract(&probe, a);
+               path = a->path;
+               len = tree_entry_len(a->path, a->sha1);
+               switch (check_entry_match(first, first_len, path, len)) {
+               case -1:
+                       entry_clear(a);
+               case 0:
+                       return;
+               default:
+                       update_tree_entry(&probe);
+                       break;
+               }
+               /* keep looking */
+       }
+       entry_clear(a);
+}
+
+static void update_extended_entry(struct tree_desc_x *t, struct name_entry *a)
+{
+       if (t->d.entry.path == a->path) {
+               update_tree_entry(&t->d);
+       } else {
+               /* we have returned this entry early */
+               struct tree_desc_skip *skip = xmalloc(sizeof(*skip));
+               skip->ptr = a->path;
+               skip->prev = t->skip;
+               t->skip = skip;
+       }
+}
+
+static void free_extended_entry(struct tree_desc_x *t)
+{
+       struct tree_desc_skip *p, *s;
+
+       for (s = t->skip; s; s = p) {
+               p = s->prev;
+               free(s);
+       }
+}
+
 int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
 {
        int ret = 0;
+       int error = 0;
        struct name_entry *entry = xmalloc(n*sizeof(*entry));
+       int i;
+       struct tree_desc_x *tx = xcalloc(n, sizeof(*tx));
+
+       for (i = 0; i < n; i++)
+               tx[i].d = t[i];
 
        for (;;) {
-               unsigned long mask = 0;
-               unsigned long dirmask = 0;
-               int i, last;
+               unsigned long mask, dirmask;
+               const char *first = NULL;
+               int first_len = 0;
+               struct name_entry *e;
+               int len;
+
+               for (i = 0; i < n; i++) {
+                       e = entry + i;
+                       extended_entry_extract(tx + i, e, NULL, 0);
+               }
 
-               last = -1;
+               /*
+                * A tree may have "t-2" at the current location even
+                * though it may have "t" that is a subtree behind it,
+                * and another tree may return "t".  We want to grab
+                * all "t" from all trees to match in such a case.
+                */
                for (i = 0; i < n; i++) {
-                       if (!t[i].size)
+                       e = entry + i;
+                       if (!e->path)
+                               continue;
+                       len = tree_entry_len(e->path, e->sha1);
+                       if (!first) {
+                               first = e->path;
+                               first_len = len;
                                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)
+                       }
+                       if (name_compare(e->path, len, first, first_len) < 0) {
+                               first = e->path;
+                               first_len = len;
+                       }
+               }
+
+               if (first) {
+                       for (i = 0; i < n; i++) {
+                               e = entry + i;
+                               extended_entry_extract(tx + i, e, first, first_len);
+                               /* Cull the ones that are not the earliest */
+                               if (!e->path)
                                        continue;
-                               /*
-                                * Is the new name smaller than the old one?
-                                * Ignore all old ones
-                                */
-                               if (cmp < 0)
-                                       mask = 0;
+                               len = tree_entry_len(e->path, e->sha1);
+                               if (name_compare(e->path, len, first, first_len))
+                                       entry_clear(e);
                        }
+               }
+
+               /* Now we have in entry[i] the earliest name from the trees */
+               mask = 0;
+               dirmask = 0;
+               for (i = 0; i < n; i++) {
+                       if (!entry[i].path)
+                               continue;
                        mask |= 1ul << i;
                        if (S_ISDIR(entry[i].mode))
                                dirmask |= 1ul << i;
-                       last = i;
                }
                if (!mask)
                        break;
-               dirmask &= mask;
-
-               /*
-                * Clear all the unused name-entries.
-                */
-               for (i = 0; i < n; i++) {
-                       if (mask & (1ul << i))
-                               continue;
-                       entry_clear(entry + i);
-               }
                ret = info->fn(n, mask, dirmask, entry, info);
-               if (ret < 0)
-                       break;
-               if (ret)
-                       mask &= ret;
+               if (ret < 0) {
+                       error = ret;
+                       if (!info->show_all_errors)
+                               break;
+               }
+               mask &= ret;
                ret = 0;
-               for (i = 0; i < n; i++) {
+               for (i = 0; i < n; i++)
                        if (mask & (1ul << i))
-                               update_tree_entry(t + i);
-               }
+                               update_extended_entry(tx + i, entry + i);
        }
        free(entry);
-       return ret;
+       for (i = 0; i < n; i++)
+               free_extended_entry(tx + i);
+       free(tx);
+       return error;
 }
 
 static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char *result, unsigned *mode)
@@ -250,6 +446,7 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch
 
        if (name[0] == '\0') {
                hashcpy(sha1, root);
+               free(tree);
                return 0;
        }
 
index 42110a465f9a8c91d1bc643dfae7a9b9c32e3719..7e3e0b5ad16710c06464726ac04d2b1c48af3708 100644 (file)
@@ -28,7 +28,10 @@ static inline int tree_entry_len(const char *name, const unsigned char *sha1)
 void update_tree_entry(struct tree_desc *);
 void init_tree_desc(struct tree_desc *desc, const void *buf, unsigned long size);
 
-/* Helper function that does both of the above and returns true for success */
+/*
+ * Helper function that does both tree_entry_extract() and update_tree_entry()
+ * 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);
@@ -45,6 +48,7 @@ struct traverse_info {
        unsigned long conflicts;
        traverse_callback_t fn;
        void *data;
+       int show_all_errors;
 };
 
 int get_tree_entry(const unsigned char *, const char *, unsigned char *, unsigned *);
diff --git a/unpack-file.c b/unpack-file.c
deleted file mode 100644 (file)
index 75cd2f1..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-#include "cache.h"
-#include "blob.h"
-#include "exec_cmd.h"
-
-static char *create_temp_file(unsigned char *sha1)
-{
-       static char path[50];
-       void *buf;
-       enum object_type type;
-       unsigned long size;
-       int fd;
-
-       buf = read_sha1_file(sha1, &type, &size);
-       if (!buf || type != OBJ_BLOB)
-               die("unable to read blob object %s", sha1_to_hex(sha1));
-
-       strcpy(path, ".merge_file_XXXXXX");
-       fd = xmkstemp(path);
-       if (write_in_full(fd, buf, size) != size)
-               die("unable to write temp-file");
-       close(fd);
-       return path;
-}
-
-int main(int argc, char **argv)
-{
-       unsigned char sha1[20];
-
-       git_extract_argv0_path(argv[0]);
-
-       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, NULL);
-
-       puts(create_temp_file(sha1));
-       return 0;
-}
index e4eb8fa3afc7ad895ba925701ee3c6881b29bd91..803445aa7be140c3707bcebc72aaf6fc6af45e4b 100644 (file)
  * Error messages expected by scripts out of plumbing commands such as
  * read-tree.  Non-scripted Porcelain is not required to use these messages
  * and in fact are encouraged to reword them to better suit their particular
- * situation better.  See how "git checkout" replaces not_uptodate_file to
- * explain why it does not allow switching between branches when you have
- * local changes, for example.
+ * situation better.  See how "git checkout" and "git merge" replaces
+ * them using setup_unpack_trees_porcelain(), for example.
  */
-static struct unpack_trees_error_msgs unpack_plumbing_errors = {
-       /* would_overwrite */
+const char *unpack_plumbing_errors[NB_UNPACK_TREES_ERROR_TYPES] = {
+       /* ERROR_WOULD_OVERWRITE */
        "Entry '%s' would be overwritten by merge. Cannot merge.",
 
-       /* not_uptodate_file */
+       /* ERROR_NOT_UPTODATE_FILE */
        "Entry '%s' not uptodate. Cannot merge.",
 
-       /* not_uptodate_dir */
+       /* ERROR_NOT_UPTODATE_DIR */
        "Updating '%s' would lose untracked files in it",
 
-       /* would_lose_untracked */
-       "Untracked working tree file '%s' would be %s by merge.",
+       /* ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN */
+       "Untracked working tree file '%s' would be overwritten by merge.",
 
-       /* bind_overlap */
+       /* ERROR_WOULD_LOSE_UNTRACKED_REMOVED */
+       "Untracked working tree file '%s' would be removed by merge.",
+
+       /* ERROR_BIND_OVERLAP */
        "Entry '%s' overlaps with '%s'.  Cannot bind.",
+
+       /* ERROR_SPARSE_NOT_UPTODATE_FILE */
+       "Entry '%s' not uptodate. Cannot update sparse checkout.",
+
+       /* ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN */
+       "Working tree file '%s' would be overwritten by sparse checkout update.",
+
+       /* ERROR_WOULD_LOSE_ORPHANED_REMOVED */
+       "Working tree file '%s' would be removed by sparse checkout update.",
 };
 
-#define ERRORMSG(o,fld) \
-       ( ((o) && (o)->msgs.fld) \
-       ? ((o)->msgs.fld) \
-       : (unpack_plumbing_errors.fld) )
+#define ERRORMSG(o,type) \
+       ( ((o) && (o)->msgs[(type)]) \
+         ? ((o)->msgs[(type)])      \
+         : (unpack_plumbing_errors[(type)]) )
+
+void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
+                                 const char *cmd)
+{
+       const char **msgs = opts->msgs;
+       const char *msg;
+       char *tmp;
+       const char *cmd2 = strcmp(cmd, "checkout") ? cmd : "switch branches";
+       if (advice_commit_before_merge)
+               msg = "Your local changes to the following files would be overwritten by %s:\n%%s"
+                       "Please, commit your changes or stash them before you can %s.";
+       else
+               msg = "Your local changes to the following files would be overwritten by %s:\n%%s";
+       tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen(cmd2) - 2);
+       sprintf(tmp, msg, cmd, cmd2);
+       msgs[ERROR_WOULD_OVERWRITE] = tmp;
+       msgs[ERROR_NOT_UPTODATE_FILE] = tmp;
+
+       msgs[ERROR_NOT_UPTODATE_DIR] =
+               "Updating the following directories would lose untracked files in it:\n%s";
+
+       if (advice_commit_before_merge)
+               msg = "The following untracked working tree files would be %s by %s:\n%%s"
+                       "Please move or remove them before you can %s.";
+       else
+               msg = "The following untracked working tree files would be %s by %s:\n%%s";
+       tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen("removed") + strlen(cmd2) - 4);
+       sprintf(tmp, msg, "removed", cmd, cmd2);
+       msgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] = tmp;
+       tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen("overwritten") + strlen(cmd2) - 4);
+       sprintf(tmp, msg, "overwritten", cmd, cmd2);
+       msgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] = tmp;
+
+       /*
+        * Special case: ERROR_BIND_OVERLAP refers to a pair of paths, we
+        * cannot easily display it as a list.
+        */
+       msgs[ERROR_BIND_OVERLAP] = "Entry '%s' overlaps with '%s'.  Cannot bind.";
+
+       msgs[ERROR_SPARSE_NOT_UPTODATE_FILE] =
+               "Cannot update sparse checkout: the following entries are not up-to-date:\n%s";
+       msgs[ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN] =
+               "The following Working tree files would be overwritten by sparse checkout update:\n%s";
+       msgs[ERROR_WOULD_LOSE_ORPHANED_REMOVED] =
+               "The following Working tree files would be removed by sparse checkout update:\n%s";
+
+       opts->show_all_errors = 1;
+}
 
 static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
        unsigned int set, unsigned int clear)
@@ -47,12 +106,76 @@ static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
 
        clear |= CE_HASHED | CE_UNHASHED;
 
+       if (set & CE_REMOVE)
+               set |= CE_WT_REMOVE;
+
        memcpy(new, ce, size);
        new->next = NULL;
        new->ce_flags = (new->ce_flags & ~clear) | set;
        add_index_entry(&o->result, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
 }
 
+/*
+ * add error messages on path <path>
+ * corresponding to the type <e> with the message <msg>
+ * indicating if it should be display in porcelain or not
+ */
+static int add_rejected_path(struct unpack_trees_options *o,
+                            enum unpack_trees_error_types e,
+                            const char *path)
+{
+       struct rejected_paths_list *newentry;
+       if (!o->show_all_errors)
+               return error(ERRORMSG(o, e), path);
+
+       /*
+        * Otherwise, insert in a list for future display by
+        * display_error_msgs()
+        */
+       newentry = xmalloc(sizeof(struct rejected_paths_list));
+       newentry->path = (char *)path;
+       newentry->next = o->unpack_rejects[e];
+       o->unpack_rejects[e] = newentry;
+       return -1;
+}
+
+/*
+ * free all the structures allocated for the error <e>
+ */
+static void free_rejected_paths(struct unpack_trees_options *o,
+                               enum unpack_trees_error_types e)
+{
+       while (o->unpack_rejects[e]) {
+               struct rejected_paths_list *del = o->unpack_rejects[e];
+               o->unpack_rejects[e] = o->unpack_rejects[e]->next;
+               free(del);
+       }
+       free(o->unpack_rejects[e]);
+}
+
+/*
+ * display all the error messages stored in a nice way
+ */
+static void display_error_msgs(struct unpack_trees_options *o)
+{
+       int e;
+       int something_displayed = 0;
+       for (e = 0; e < NB_UNPACK_TREES_ERROR_TYPES; e++) {
+               if (o->unpack_rejects[e]) {
+                       struct rejected_paths_list *rp;
+                       struct strbuf path = STRBUF_INIT;
+                       something_displayed = 1;
+                       for (rp = o->unpack_rejects[e]; rp; rp = rp->next)
+                               strbuf_addf(&path, "\t%s\n", rp->path);
+                       error(ERRORMSG(o, e), path.buf);
+                       strbuf_release(&path);
+                       free_rejected_paths(o, e);
+               }
+       }
+       if (something_displayed)
+               printf("Aborting\n");
+}
+
 /*
  * Unlink the last component and schedule the leading directories for
  * removal, such that empty directories get removed.
@@ -61,7 +184,7 @@ static void unlink_entry(struct cache_entry *ce)
 {
        if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce)))
                return;
-       if (unlink(ce->name))
+       if (remove_or_warn(ce->ce_mode, ce->name))
                return;
        schedule_dir_for_removal(ce->name, ce_namelen(ce));
 }
@@ -78,7 +201,7 @@ static int check_updates(struct unpack_trees_options *o)
        if (o->update && o->verbose_update) {
                for (total = cnt = 0; cnt < index->cache_nr; cnt++) {
                        struct cache_entry *ce = index->cache[cnt];
-                       if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
+                       if (ce->ce_flags & (CE_UPDATE | CE_WT_REMOVE))
                                total++;
                }
 
@@ -92,10 +215,11 @@ static int check_updates(struct unpack_trees_options *o)
        for (i = 0; i < index->cache_nr; i++) {
                struct cache_entry *ce = index->cache[i];
 
-               if (ce->ce_flags & CE_REMOVE) {
+               if (ce->ce_flags & CE_WT_REMOVE) {
                        display_progress(progress, ++cnt);
                        if (o->update)
                                unlink_entry(ce);
+                       continue;
                }
        }
        remove_marked_cache_entries(&o->result);
@@ -118,6 +242,71 @@ static int check_updates(struct unpack_trees_options *o)
        return errs != 0;
 }
 
+static int verify_uptodate_sparse(struct cache_entry *ce, struct unpack_trees_options *o);
+static int verify_absent_sparse(struct cache_entry *ce, enum unpack_trees_error_types, struct unpack_trees_options *o);
+
+static int will_have_skip_worktree(const struct cache_entry *ce, struct unpack_trees_options *o)
+{
+       const char *basename;
+
+       basename = strrchr(ce->name, '/');
+       basename = basename ? basename+1 : ce->name;
+       return excluded_from_list(ce->name, ce_namelen(ce), basename, NULL, o->el) <= 0;
+}
+
+static int apply_sparse_checkout(struct cache_entry *ce, struct unpack_trees_options *o)
+{
+       int was_skip_worktree = ce_skip_worktree(ce);
+
+       if (!ce_stage(ce) && will_have_skip_worktree(ce, o))
+               ce->ce_flags |= CE_SKIP_WORKTREE;
+       else
+               ce->ce_flags &= ~CE_SKIP_WORKTREE;
+
+       /*
+        * if (!was_skip_worktree && !ce_skip_worktree()) {
+        *      This is perfectly normal. Move on;
+        * }
+        */
+
+       /*
+        * Merge strategies may set CE_UPDATE|CE_REMOVE outside checkout
+        * area as a result of ce_skip_worktree() shortcuts in
+        * verify_absent() and verify_uptodate().
+        * Make sure they don't modify worktree if they are already
+        * outside checkout area
+        */
+       if (was_skip_worktree && ce_skip_worktree(ce)) {
+               ce->ce_flags &= ~CE_UPDATE;
+
+               /*
+                * By default, when CE_REMOVE is on, CE_WT_REMOVE is also
+                * on to get that file removed from both index and worktree.
+                * If that file is already outside worktree area, don't
+                * bother remove it.
+                */
+               if (ce->ce_flags & CE_REMOVE)
+                       ce->ce_flags &= ~CE_WT_REMOVE;
+       }
+
+       if (!was_skip_worktree && ce_skip_worktree(ce)) {
+               /*
+                * If CE_UPDATE is set, verify_uptodate() must be called already
+                * also stat info may have lost after merged_entry() so calling
+                * verify_uptodate() again may fail
+                */
+               if (!(ce->ce_flags & CE_UPDATE) && verify_uptodate_sparse(ce, o))
+                       return -1;
+               ce->ce_flags |= CE_WT_REMOVE;
+       }
+       if (was_skip_worktree && !ce_skip_worktree(ce)) {
+               if (verify_absent_sparse(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o))
+                       return -1;
+               ce->ce_flags |= CE_UPDATE;
+       }
+       return 0;
+}
+
 static inline int call_unpack_fn(struct cache_entry **src, struct unpack_trees_options *o)
 {
        int ret = o->fn(src, o);
@@ -126,24 +315,146 @@ static inline int call_unpack_fn(struct cache_entry **src, struct unpack_trees_o
        return ret;
 }
 
-static int unpack_index_entry(struct cache_entry *ce, struct unpack_trees_options *o)
+static void mark_ce_used(struct cache_entry *ce, struct unpack_trees_options *o)
+{
+       ce->ce_flags |= CE_UNPACKED;
+
+       if (o->cache_bottom < o->src_index->cache_nr &&
+           o->src_index->cache[o->cache_bottom] == ce) {
+               int bottom = o->cache_bottom;
+               while (bottom < o->src_index->cache_nr &&
+                      o->src_index->cache[bottom]->ce_flags & CE_UNPACKED)
+                       bottom++;
+               o->cache_bottom = bottom;
+       }
+}
+
+static void mark_all_ce_unused(struct index_state *index)
+{
+       int i;
+       for (i = 0; i < index->cache_nr; i++)
+               index->cache[i]->ce_flags &= ~CE_UNPACKED;
+}
+
+static int locate_in_src_index(struct cache_entry *ce,
+                              struct unpack_trees_options *o)
+{
+       struct index_state *index = o->src_index;
+       int len = ce_namelen(ce);
+       int pos = index_name_pos(index, ce->name, len);
+       if (pos < 0)
+               pos = -1 - pos;
+       return pos;
+}
+
+/*
+ * We call unpack_index_entry() with an unmerged cache entry
+ * only in diff-index, and it wants a single callback.  Skip
+ * the other unmerged entry with the same name.
+ */
+static void mark_ce_used_same_name(struct cache_entry *ce,
+                                  struct unpack_trees_options *o)
+{
+       struct index_state *index = o->src_index;
+       int len = ce_namelen(ce);
+       int pos;
+
+       for (pos = locate_in_src_index(ce, o); pos < index->cache_nr; pos++) {
+               struct cache_entry *next = index->cache[pos];
+               if (len != ce_namelen(next) ||
+                   memcmp(ce->name, next->name, len))
+                       break;
+               mark_ce_used(next, o);
+       }
+}
+
+static struct cache_entry *next_cache_entry(struct unpack_trees_options *o)
 {
-       struct cache_entry *src[5] = { ce, };
+       const struct index_state *index = o->src_index;
+       int pos = o->cache_bottom;
+
+       while (pos < index->cache_nr) {
+               struct cache_entry *ce = index->cache[pos];
+               if (!(ce->ce_flags & CE_UNPACKED))
+                       return ce;
+               pos++;
+       }
+       return NULL;
+}
 
-       o->pos++;
+static void add_same_unmerged(struct cache_entry *ce,
+                             struct unpack_trees_options *o)
+{
+       struct index_state *index = o->src_index;
+       int len = ce_namelen(ce);
+       int pos = index_name_pos(index, ce->name, len);
+
+       if (0 <= pos)
+               die("programming error in a caller of mark_ce_used_same_name");
+       for (pos = -pos - 1; pos < index->cache_nr; pos++) {
+               struct cache_entry *next = index->cache[pos];
+               if (len != ce_namelen(next) ||
+                   memcmp(ce->name, next->name, len))
+                       break;
+               add_entry(o, next, 0, 0);
+               mark_ce_used(next, o);
+       }
+}
+
+static int unpack_index_entry(struct cache_entry *ce,
+                             struct unpack_trees_options *o)
+{
+       struct cache_entry *src[5] = { NULL };
+       int ret;
+
+       src[0] = ce;
+
+       mark_ce_used(ce, o);
        if (ce_stage(ce)) {
                if (o->skip_unmerged) {
                        add_entry(o, ce, 0, 0);
                        return 0;
                }
        }
-       return call_unpack_fn(src, o);
+       ret = call_unpack_fn(src, o);
+       if (ce_stage(ce))
+               mark_ce_used_same_name(ce, o);
+       return ret;
 }
 
-int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long df_conflicts, struct name_entry *names, struct traverse_info *info)
+static int find_cache_pos(struct traverse_info *, const struct name_entry *);
+
+static void restore_cache_bottom(struct traverse_info *info, int bottom)
 {
-       int i;
+       struct unpack_trees_options *o = info->data;
+
+       if (o->diff_index_cached)
+               return;
+       o->cache_bottom = bottom;
+}
+
+static int switch_cache_bottom(struct traverse_info *info)
+{
+       struct unpack_trees_options *o = info->data;
+       int ret, pos;
+
+       if (o->diff_index_cached)
+               return 0;
+       ret = o->cache_bottom;
+       pos = find_cache_pos(info->prev, &info->name);
+
+       if (pos < -1)
+               o->cache_bottom = -2 - pos;
+       else if (pos < 0)
+               o->cache_bottom = o->src_index->cache_nr;
+       return ret;
+}
+
+static int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long df_conflicts, struct name_entry *names, struct traverse_info *info)
+{
+       int i, ret, bottom;
        struct tree_desc t[MAX_UNPACK_TREES];
+       void *buf[MAX_UNPACK_TREES];
        struct traverse_info newinfo;
        struct name_entry *p;
 
@@ -161,9 +472,17 @@ int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long df_conf
                const unsigned char *sha1 = NULL;
                if (dirmask & 1)
                        sha1 = names[i].sha1;
-               fill_tree_descriptor(t+i, sha1);
+               buf[i] = fill_tree_descriptor(t+i, sha1);
        }
-       return traverse_trees(n, t, &newinfo);
+
+       bottom = switch_cache_bottom(&newinfo);
+       ret = traverse_trees(n, t, &newinfo);
+       restore_cache_bottom(&newinfo, bottom);
+
+       for (i = 0; i < n; i++)
+               free(buf[i]);
+
+       return ret;
 }
 
 /*
@@ -212,6 +531,20 @@ static int compare_entry(const struct cache_entry *ce, const struct traverse_inf
        return ce_namelen(ce) > traverse_path_len(info, n);
 }
 
+static int ce_in_traverse_path(const struct cache_entry *ce,
+                              const struct traverse_info *info)
+{
+       if (!info->prev)
+               return 1;
+       if (do_compare_entry(ce, info->prev, &info->name))
+               return 0;
+       /*
+        * If ce (blob) is the same name as the path (which is a tree
+        * we will be descending into), it won't be inside it.
+        */
+       return (info->pathlen < ce_namelen(ce));
+}
+
 static struct cache_entry *create_ce_entry(const struct traverse_info *info, const struct name_entry *n, int stage)
 {
        int len = traverse_path_len(info, n);
@@ -277,6 +610,133 @@ static int unpack_nondirectories(int n, unsigned long mask,
        return 0;
 }
 
+static int unpack_failed(struct unpack_trees_options *o, const char *message)
+{
+       discard_index(&o->result);
+       if (!o->gently) {
+               if (message)
+                       return error("%s", message);
+               return -1;
+       }
+       return -1;
+}
+
+/* NEEDSWORK: give this a better name and share with tree-walk.c */
+static int name_compare(const char *a, int a_len,
+                       const char *b, int b_len)
+{
+       int len = (a_len < b_len) ? a_len : b_len;
+       int cmp = memcmp(a, b, len);
+       if (cmp)
+               return cmp;
+       return (a_len - b_len);
+}
+
+/*
+ * The tree traversal is looking at name p.  If we have a matching entry,
+ * return it.  If name p is a directory in the index, do not return
+ * anything, as we will want to match it when the traversal descends into
+ * the directory.
+ */
+static int find_cache_pos(struct traverse_info *info,
+                         const struct name_entry *p)
+{
+       int pos;
+       struct unpack_trees_options *o = info->data;
+       struct index_state *index = o->src_index;
+       int pfxlen = info->pathlen;
+       int p_len = tree_entry_len(p->path, p->sha1);
+
+       for (pos = o->cache_bottom; pos < index->cache_nr; pos++) {
+               struct cache_entry *ce = index->cache[pos];
+               const char *ce_name, *ce_slash;
+               int cmp, ce_len;
+
+               if (ce->ce_flags & CE_UNPACKED) {
+                       /*
+                        * cache_bottom entry is already unpacked, so
+                        * we can never match it; don't check it
+                        * again.
+                        */
+                       if (pos == o->cache_bottom)
+                               ++o->cache_bottom;
+                       continue;
+               }
+               if (!ce_in_traverse_path(ce, info))
+                       continue;
+               ce_name = ce->name + pfxlen;
+               ce_slash = strchr(ce_name, '/');
+               if (ce_slash)
+                       ce_len = ce_slash - ce_name;
+               else
+                       ce_len = ce_namelen(ce) - pfxlen;
+               cmp = name_compare(p->path, p_len, ce_name, ce_len);
+               /*
+                * Exact match; if we have a directory we need to
+                * delay returning it.
+                */
+               if (!cmp)
+                       return ce_slash ? -2 - pos : pos;
+               if (0 < cmp)
+                       continue; /* keep looking */
+               /*
+                * ce_name sorts after p->path; could it be that we
+                * have files under p->path directory in the index?
+                * E.g.  ce_name == "t-i", and p->path == "t"; we may
+                * have "t/a" in the index.
+                */
+               if (p_len < ce_len && !memcmp(ce_name, p->path, p_len) &&
+                   ce_name[p_len] < '/')
+                       continue; /* keep looking */
+               break;
+       }
+       return -1;
+}
+
+static struct cache_entry *find_cache_entry(struct traverse_info *info,
+                                           const struct name_entry *p)
+{
+       int pos = find_cache_pos(info, p);
+       struct unpack_trees_options *o = info->data;
+
+       if (0 <= pos)
+               return o->src_index->cache[pos];
+       else
+               return NULL;
+}
+
+static void debug_path(struct traverse_info *info)
+{
+       if (info->prev) {
+               debug_path(info->prev);
+               if (*info->prev->name.path)
+                       putchar('/');
+       }
+       printf("%s", info->name.path);
+}
+
+static void debug_name_entry(int i, struct name_entry *n)
+{
+       printf("ent#%d %06o %s\n", i,
+              n->path ? n->mode : 0,
+              n->path ? n->path : "(missing)");
+}
+
+static void debug_unpack_callback(int n,
+                                 unsigned long mask,
+                                 unsigned long dirmask,
+                                 struct name_entry *names,
+                                 struct traverse_info *info)
+{
+       int i;
+       printf("* unpack mask %lu, dirmask %lu, cnt %d ",
+              mask, dirmask, n);
+       debug_path(info);
+       putchar('\n');
+       for (i = 0; i < n; i++)
+               debug_name_entry(i, names + i);
+}
+
 static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *names, struct traverse_info *info)
 {
        struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
@@ -287,25 +747,38 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
        while (!p->mode)
                p++;
 
+       if (o->debug_unpack)
+               debug_unpack_callback(n, mask, dirmask, names, info);
+
        /* Are we supposed to look at the index too? */
        if (o->merge) {
-               while (o->pos < o->src_index->cache_nr) {
-                       struct cache_entry *ce = o->src_index->cache[o->pos];
-                       int cmp = compare_entry(ce, info, p);
+               while (1) {
+                       int cmp;
+                       struct cache_entry *ce;
+
+                       if (o->diff_index_cached)
+                               ce = next_cache_entry(o);
+                       else
+                               ce = find_cache_entry(info, p);
+
+                       if (!ce)
+                               break;
+                       cmp = compare_entry(ce, info, p);
                        if (cmp < 0) {
                                if (unpack_index_entry(ce, o) < 0)
-                                       return -1;
+                                       return unpack_failed(o, NULL);
                                continue;
                        }
                        if (!cmp) {
-                               o->pos++;
                                if (ce_stage(ce)) {
                                        /*
-                                        * If we skip unmerged index entries, we'll skip this
-                                        * entry *and* the tree entries associated with it!
+                                        * If we skip unmerged index
+                                        * entries, we'll skip this
+                                        * entry *and* the tree
+                                        * entries associated with it!
                                         */
                                        if (o->skip_unmerged) {
-                                               add_entry(o, ce, 0, 0);
+                                               add_same_unmerged(ce, o);
                                                return mask;
                                        }
                                }
@@ -318,6 +791,13 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
        if (unpack_nondirectories(n, mask, dirmask, src, names, info) < 0)
                return -1;
 
+       if (src[0]) {
+               if (ce_stage(src[0]))
+                       mark_ce_used_same_name(src[0], o);
+               else
+                       mark_ce_used(src[0], o);
+       }
+
        /* Now handle any directories.. */
        if (dirmask) {
                unsigned long conflicts = mask & ~dirmask;
@@ -326,6 +806,25 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
                        if (src[0])
                                conflicts |= 1;
                }
+
+               /* special case: "diff-index --cached" looking at a tree */
+               if (o->diff_index_cached &&
+                   n == 1 && dirmask == 1 && S_ISDIR(names->mode)) {
+                       int matches;
+                       matches = cache_tree_matches_traversal(o->src_index->cache_tree,
+                                                              names, info);
+                       /*
+                        * Everything under the name matches; skip the
+                        * entire hierarchy.  diff_index_cached codepath
+                        * special cases D/F conflicts in such a way that
+                        * it does not do any look-ahead, so this is safe.
+                        */
+                       if (matches) {
+                               o->cache_bottom += matches;
+                               return mask;
+                       }
+               }
+
                if (traverse_trees_recursive(n, dirmask, conflicts,
                                             names, info) < 0)
                        return -1;
@@ -335,25 +834,15 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
        return mask;
 }
 
-static int unpack_failed(struct unpack_trees_options *o, const char *message)
-{
-       discard_index(&o->result);
-       if (!o->gently) {
-               if (message)
-                       return error("%s", message);
-               return -1;
-       }
-       return -1;
-}
-
 /*
  * N-way merge "len" trees.  Returns 0 on success, -1 on failure to manipulate the
  * resulting index, -2 on failure to reflect the changes to the work tree.
  */
 int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o)
 {
-       int ret;
+       int i, ret;
        static struct cache_entry *dfc;
+       struct exclude_list el;
 
        if (len > MAX_UNPACK_TREES)
                die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES);
@@ -363,13 +852,22 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
        state.quiet = 1;
        state.refresh_cache = 1;
 
+       memset(&el, 0, sizeof(el));
+       if (!core_apply_sparse_checkout || !o->update)
+               o->skip_sparse_checkout = 1;
+       if (!o->skip_sparse_checkout) {
+               if (add_excludes_from_file_to_list(git_path("info/sparse-checkout"), "", 0, NULL, &el, 0) < 0)
+                       o->skip_sparse_checkout = 1;
+               else
+                       o->el = &el;
+       }
+
        memset(&o->result, 0, sizeof(o->result));
        o->result.initialized = 1;
-       if (o->src_index) {
-               o->result.timestamp.sec = o->src_index->timestamp.sec;
-               o->result.timestamp.nsec = o->src_index->timestamp.nsec;
-       }
+       o->result.timestamp.sec = o->src_index->timestamp.sec;
+       o->result.timestamp.nsec = o->src_index->timestamp.nsec;
        o->merge_size = len;
+       mark_all_ce_unused(o->src_index);
 
        if (!dfc)
                dfc = xcalloc(1, cache_entry_size(0));
@@ -382,35 +880,91 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
                setup_traverse_info(&info, prefix);
                info.fn = unpack_callback;
                info.data = o;
+               info.show_all_errors = o->show_all_errors;
+
+               if (o->prefix) {
+                       /*
+                        * Unpack existing index entries that sort before the
+                        * prefix the tree is spliced into.  Note that o->merge
+                        * is always true in this case.
+                        */
+                       while (1) {
+                               struct cache_entry *ce = next_cache_entry(o);
+                               if (!ce)
+                                       break;
+                               if (ce_in_traverse_path(ce, &info))
+                                       break;
+                               if (unpack_index_entry(ce, o) < 0)
+                                       goto return_failed;
+                       }
+               }
 
                if (traverse_trees(len, t, &info) < 0)
-                       return unpack_failed(o, NULL);
+                       goto return_failed;
        }
 
        /* Any left-over entries in the index? */
        if (o->merge) {
-               while (o->pos < o->src_index->cache_nr) {
-                       struct cache_entry *ce = o->src_index->cache[o->pos];
+               while (1) {
+                       struct cache_entry *ce = next_cache_entry(o);
+                       if (!ce)
+                               break;
                        if (unpack_index_entry(ce, o) < 0)
-                               return unpack_failed(o, NULL);
+                               goto return_failed;
                }
        }
+       mark_all_ce_unused(o->src_index);
 
-       if (o->trivial_merges_only && o->nontrivial_merge)
-               return unpack_failed(o, "Merge requires file-level merging");
+       if (o->trivial_merges_only && o->nontrivial_merge) {
+               ret = unpack_failed(o, "Merge requires file-level merging");
+               goto done;
+       }
+
+       if (!o->skip_sparse_checkout) {
+               int empty_worktree = 1;
+               for (i = 0;i < o->result.cache_nr;i++) {
+                       struct cache_entry *ce = o->result.cache[i];
+
+                       if (apply_sparse_checkout(ce, o)) {
+                               ret = -1;
+                               goto done;
+                       }
+                       if (!ce_skip_worktree(ce))
+                               empty_worktree = 0;
+
+               }
+               if (o->result.cache_nr && empty_worktree) {
+                       ret = unpack_failed(o, "Sparse checkout leaves no entry on working directory");
+                       goto done;
+               }
+       }
 
        o->src_index = NULL;
        ret = check_updates(o) ? (-2) : 0;
        if (o->dst_index)
                *o->dst_index = o->result;
+
+done:
+       for (i = 0;i < el.nr;i++)
+               free(el.excludes[i]);
+       if (el.excludes)
+               free(el.excludes);
+
        return ret;
+
+return_failed:
+       if (o->show_all_errors)
+               display_error_msgs(o);
+       mark_all_ce_unused(o->src_index);
+       ret = unpack_failed(o, NULL);
+       goto done;
 }
 
 /* Here come the merge functions */
 
 static int reject_merge(struct cache_entry *ce, struct unpack_trees_options *o)
 {
-       return error(ERRORMSG(o, would_overwrite), ce->name);
+       return add_rejected_path(o, ERROR_WOULD_OVERWRITE, ce->name);
 }
 
 static int same(struct cache_entry *a, struct cache_entry *b)
@@ -419,6 +973,8 @@ static int same(struct cache_entry *a, struct cache_entry *b)
                return 0;
        if (!a && !b)
                return 1;
+       if ((a->ce_flags | b->ce_flags) & CE_CONFLICTED)
+               return 0;
        return a->ce_mode == b->ce_mode &&
               !hashcmp(a->sha1, b->sha1);
 }
@@ -428,16 +984,17 @@ static int same(struct cache_entry *a, struct cache_entry *b)
  * When a CE gets turned into an unmerged entry, we
  * want it to be up-to-date
  */
-static int verify_uptodate(struct cache_entry *ce,
-               struct unpack_trees_options *o)
+static int verify_uptodate_1(struct cache_entry *ce,
+                                  struct unpack_trees_options *o,
+                                  enum unpack_trees_error_types error_type)
 {
        struct stat st;
 
-       if (o->index_only || o->reset || ce_uptodate(ce))
+       if (o->index_only || (!((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) && (o->reset || ce_uptodate(ce))))
                return 0;
 
        if (!lstat(ce->name, &st)) {
-               unsigned changed = ie_match_stat(o->src_index, ce, &st, CE_MATCH_IGNORE_VALID);
+               unsigned changed = ie_match_stat(o->src_index, ce, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
                if (!changed)
                        return 0;
                /*
@@ -454,7 +1011,21 @@ static int verify_uptodate(struct cache_entry *ce,
        if (errno == ENOENT)
                return 0;
        return o->gently ? -1 :
-               error(ERRORMSG(o, not_uptodate_file), ce->name);
+               add_rejected_path(o, error_type, ce->name);
+}
+
+static int verify_uptodate(struct cache_entry *ce,
+                          struct unpack_trees_options *o)
+{
+       if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o))
+               return 0;
+       return verify_uptodate_1(ce, o, ERROR_NOT_UPTODATE_FILE);
+}
+
+static int verify_uptodate_sparse(struct cache_entry *ce,
+                                 struct unpack_trees_options *o)
+{
+       return verify_uptodate_1(ce, o, ERROR_SPARSE_NOT_UPTODATE_FILE);
 }
 
 static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_options *o)
@@ -470,13 +1041,15 @@ static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_optio
  * Currently, git does not checkout subprojects during a superproject
  * checkout, so it is not going to overwrite anything.
  */
-static int verify_clean_submodule(struct cache_entry *ce, const char *action,
+static int verify_clean_submodule(struct cache_entry *ce,
+                                     enum unpack_trees_error_types error_type,
                                      struct unpack_trees_options *o)
 {
        return 0;
 }
 
-static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
+static int verify_clean_subdirectory(struct cache_entry *ce,
+                                     enum unpack_trees_error_types error_type,
                                      struct unpack_trees_options *o)
 {
        /*
@@ -497,7 +1070,7 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
                 */
                if (!hashcmp(sha1, ce->sha1))
                        return 0;
-               return verify_clean_submodule(ce, action, o);
+               return verify_clean_submodule(ce, error_type, o);
        }
 
        /*
@@ -505,7 +1078,9 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
         * in that directory.
         */
        namelen = strlen(ce->name);
-       for (i = o->pos; i < o->src_index->cache_nr; i++) {
+       for (i = locate_in_src_index(ce, o);
+            i < o->src_index->cache_nr;
+            i++) {
                struct cache_entry *ce2 = o->src_index->cache[i];
                int len = ce_namelen(ce2);
                if (len < namelen ||
@@ -513,12 +1088,14 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
                    ce2->name[namelen] != '/')
                        break;
                /*
-                * ce2->name is an entry in the subdirectory.
+                * ce2->name is an entry in the subdirectory to be
+                * removed.
                 */
                if (!ce_stage(ce2)) {
                        if (verify_uptodate(ce2, o))
                                return -1;
                        add_entry(o, ce2, CE_REMOVE, 0);
+                       mark_ce_used(ce2, o);
                }
                cnt++;
        }
@@ -534,10 +1111,10 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
        memset(&d, 0, sizeof(d));
        if (o->dir)
                d.exclude_per_dir = o->dir->exclude_per_dir;
-       i = read_directory(&d, ce->name, pathbuf, namelen+1, NULL);
+       i = read_directory(&d, pathbuf, namelen+1, NULL);
        if (i)
                return o->gently ? -1 :
-                       error(ERRORMSG(o, not_uptodate_dir), ce->name);
+                       add_rejected_path(o, ERROR_NOT_UPTODATE_DIR, ce->name);
        free(pathbuf);
        return cnt;
 }
@@ -555,15 +1132,16 @@ static int icase_exists(struct unpack_trees_options *o, struct cache_entry *dst,
        struct cache_entry *src;
 
        src = index_name_exists(o->src_index, dst->name, ce_namelen(dst), 1);
-       return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID);
+       return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
 }
 
 /*
  * We do not want to remove or overwrite a working tree file that
  * is not tracked, unless it is ignored.
  */
-static int verify_absent(struct cache_entry *ce, const char *action,
-                        struct unpack_trees_options *o)
+static int verify_absent_1(struct cache_entry *ce,
+                                enum unpack_trees_error_types error_type,
+                                struct unpack_trees_options *o)
 {
        struct stat st;
 
@@ -574,7 +1152,6 @@ static int verify_absent(struct cache_entry *ce, const char *action,
                return 0;
 
        if (!lstat(ce->name, &st)) {
-               int ret;
                int dtype = ce_to_dtype(ce);
                struct cache_entry *result;
 
@@ -600,30 +1177,10 @@ static int verify_absent(struct cache_entry *ce, const char *action,
                         * found "foo/." in the working tree.
                         * This is tricky -- if we have modified
                         * files that are in "foo/" we would lose
-                        * it.
+                        * them.
                         */
-                       ret = verify_clean_subdirectory(ce, action, o);
-                       if (ret < 0)
-                               return ret;
-
-                       /*
-                        * If this removed entries from the index,
-                        * what that means is:
-                        *
-                        * (1) the caller unpack_callback() saw path/foo
-                        * in the index, and it has not removed it because
-                        * it thinks it is handling 'path' as blob with
-                        * D/F conflict;
-                        * (2) we will return "ok, we placed a merged entry
-                        * in the index" which would cause o->pos to be
-                        * incremented by one;
-                        * (3) however, original o->pos now has 'path/foo'
-                        * marked with "to be removed".
-                        *
-                        * We need to increment it by the number of
-                        * deleted entries here.
-                        */
-                       o->pos += ret;
+                       if (verify_clean_subdirectory(ce, error_type, o) < 0)
+                               return -1;
                        return 0;
                }
 
@@ -639,17 +1196,42 @@ static int verify_absent(struct cache_entry *ce, const char *action,
                }
 
                return o->gently ? -1 :
-                       error(ERRORMSG(o, would_lose_untracked), ce->name, action);
+                       add_rejected_path(o, error_type, ce->name);
        }
        return 0;
 }
+static int verify_absent(struct cache_entry *ce,
+                        enum unpack_trees_error_types error_type,
+                        struct unpack_trees_options *o)
+{
+       if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o))
+               return 0;
+       return verify_absent_1(ce, error_type, o);
+}
+
+static int verify_absent_sparse(struct cache_entry *ce,
+                        enum unpack_trees_error_types error_type,
+                        struct unpack_trees_options *o)
+{
+       enum unpack_trees_error_types orphaned_error = error_type;
+       if (orphaned_error == ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN)
+               orphaned_error = ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN;
+
+       return verify_absent_1(ce, orphaned_error, o);
+}
 
 static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
                struct unpack_trees_options *o)
 {
        int update = CE_UPDATE;
 
-       if (old) {
+       if (!old) {
+               if (verify_absent(merge, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o))
+                       return -1;
+               if (!o->skip_sparse_checkout && will_have_skip_worktree(merge, o))
+                       update |= CE_SKIP_WORKTREE;
+               invalidate_ce_path(merge, o);
+       } else if (!(old->ce_flags & CE_CONFLICTED)) {
                /*
                 * See if we can re-use the old CE directly?
                 * That way we get the uptodate stat info.
@@ -663,13 +1245,16 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
                } else {
                        if (verify_uptodate(old, o))
                                return -1;
+                       if (ce_skip_worktree(old))
+                               update |= CE_SKIP_WORKTREE;
                        invalidate_ce_path(old, o);
                }
-       }
-       else {
-               if (verify_absent(merge, "overwritten", o))
-                       return -1;
-               invalidate_ce_path(merge, o);
+       } else {
+               /*
+                * Previously unmerged entry left as an existence
+                * marker by read_index_unmerged();
+                */
+               invalidate_ce_path(old, o);
        }
 
        add_entry(o, merge, update, CE_STAGEMASK);
@@ -681,11 +1266,11 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old,
 {
        /* Did it exist in the index? */
        if (!old) {
-               if (verify_absent(ce, "removed", o))
+               if (verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_REMOVED, o))
                        return -1;
                return 0;
        }
-       if (verify_uptodate(old, o))
+       if (!(old->ce_flags & CE_CONFLICTED) && verify_uptodate(old, o))
                return -1;
        add_entry(o, ce, CE_REMOVE, 0);
        invalidate_ce_path(ce, o);
@@ -750,7 +1335,8 @@ int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o)
                remote = NULL;
        }
 
-       /* First, if there's a #16 situation, note that to prevent #13
+       /*
+        * First, if there's a #16 situation, note that to prevent #13
         * and #14.
         */
        if (!same(remote, head)) {
@@ -764,7 +1350,8 @@ int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o)
                }
        }
 
-       /* We start with cases where the index is allowed to match
+       /*
+        * 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.
         */
@@ -794,12 +1381,13 @@ int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o)
        if (!head && !remote && any_anc_missing)
                return 0;
 
-       /* Under the new "aggressive" rule, we resolve mostly trivial
+       /*
+        * Under the "aggressive" rule, we resolve mostly trivial
         * cases that we historically had git-merge-one-file resolve.
         */
        if (o->aggressive) {
-               int head_deleted = !head && !df_conflict_head;
-               int remote_deleted = !remote && !df_conflict_remote;
+               int head_deleted = !head;
+               int remote_deleted = !remote;
                struct cache_entry *ce = NULL;
 
                if (index)
@@ -827,7 +1415,7 @@ int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o)
                        if (index)
                                return deleted_entry(index, index, o);
                        if (ce && !head_deleted) {
-                               if (verify_absent(ce, "removed", o))
+                               if (verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_REMOVED, o))
                                        return -1;
                        }
                        return 0;
@@ -878,7 +1466,7 @@ int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o)
  * 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
+ * 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>.
  *
@@ -960,7 +1548,7 @@ int bind_merge(struct cache_entry **src,
                             o->merge_size);
        if (a && old)
                return o->gently ? -1 :
-                       error(ERRORMSG(o, bind_overlap), a->name, old->name);
+                       error(ERRORMSG(o, ERROR_BIND_OVERLAP), a->name, old->name);
        if (!a)
                return keep_entry(old, o);
        else
@@ -982,15 +1570,15 @@ int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o)
                return error("Cannot do a oneway merge of %d trees",
                             o->merge_size);
 
-       if (!a)
+       if (!a || a == o->df_conflict_entry)
                return deleted_entry(old, old, o);
 
        if (old && same(old, a)) {
                int update = 0;
-               if (o->reset) {
+               if (o->reset && !ce_uptodate(old) && !ce_skip_worktree(old)) {
                        struct stat st;
                        if (lstat(old->name, &st) ||
-                           ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID))
+                           ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE))
                                update |= CE_UPDATE;
                }
                add_entry(o, old, update, 0);
index 0d26f3d73e773230972db86f34a5147ada881e8b..7c0187d11adaa5f0b9a8642070d8dfc2db41dfdf 100644 (file)
@@ -4,35 +4,62 @@
 #define MAX_UNPACK_TREES 8
 
 struct unpack_trees_options;
+struct exclude_list;
 
 typedef int (*merge_fn_t)(struct cache_entry **src,
                struct unpack_trees_options *options);
 
-struct unpack_trees_error_msgs {
-       const char *would_overwrite;
-       const char *not_uptodate_file;
-       const char *not_uptodate_dir;
-       const char *would_lose_untracked;
-       const char *bind_overlap;
+enum unpack_trees_error_types {
+       ERROR_WOULD_OVERWRITE = 0,
+       ERROR_NOT_UPTODATE_FILE,
+       ERROR_NOT_UPTODATE_DIR,
+       ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN,
+       ERROR_WOULD_LOSE_UNTRACKED_REMOVED,
+       ERROR_BIND_OVERLAP,
+       ERROR_SPARSE_NOT_UPTODATE_FILE,
+       ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN,
+       ERROR_WOULD_LOSE_ORPHANED_REMOVED,
+       NB_UNPACK_TREES_ERROR_TYPES
+};
+
+/*
+ * Sets the list of user-friendly error messages to be used by the
+ * command "cmd" (either merge or checkout), and show_all_errors to 1.
+ */
+void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
+                                 const char *cmd);
+
+struct rejected_paths_list {
+       char *path;
+       struct rejected_paths_list *next;
 };
 
 struct unpack_trees_options {
-       unsigned int reset:1,
-                    merge:1,
-                    update:1,
-                    index_only:1,
-                    nontrivial_merge:1,
-                    trivial_merges_only:1,
-                    verbose_update:1,
-                    aggressive:1,
-                    skip_unmerged:1,
-                    initial_checkout:1,
-                    gently:1;
+       unsigned int reset,
+                    merge,
+                    update,
+                    index_only,
+                    nontrivial_merge,
+                    trivial_merges_only,
+                    verbose_update,
+                    aggressive,
+                    skip_unmerged,
+                    initial_checkout,
+                    diff_index_cached,
+                    debug_unpack,
+                    skip_sparse_checkout,
+                    gently,
+                    show_all_errors;
        const char *prefix;
-       int pos;
+       int cache_bottom;
        struct dir_struct *dir;
        merge_fn_t fn;
-       struct unpack_trees_error_msgs msgs;
+       const char *msgs[NB_UNPACK_TREES_ERROR_TYPES];
+       /*
+        * Store error messages in an array, each case
+        * corresponding to a error message type
+        */
+       struct rejected_paths_list *unpack_rejects[NB_UNPACK_TREES_ERROR_TYPES];
 
        int head_idx;
        int merge_size;
@@ -43,6 +70,8 @@ struct unpack_trees_options {
        struct index_state *dst_index;
        struct index_state *src_index;
        struct index_state result;
+
+       struct exclude_list *el; /* for internal use */
 };
 
 extern int unpack_trees(unsigned n, struct tree_desc *t,
diff --git a/update-server-info.c b/update-server-info.c
deleted file mode 100644 (file)
index 7b38fd8..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-#include "cache.h"
-#include "exec_cmd.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);
-
-       git_extract_argv0_path(av[0]);
-
-       setup_git_directory();
-
-       return !!update_server_info(force);
-}
index edc78612289c5bd774e18fe5f8e1b9ea80378a5f..92f9530c6572a212c12ac917d774736286325e52 100644 (file)
@@ -28,15 +28,19 @@ static unsigned long oldest_have;
 
 static int multi_ack, nr_our_refs;
 static int use_thin_pack, use_ofs_delta, use_include_tag;
-static int no_progress;
+static int no_progress, daemon_mode;
+static int shallow_nr;
 static struct object_array have_obj;
 static struct object_array want_obj;
+static struct object_array extra_edge_obj;
 static unsigned int timeout;
 /* 0 for no sideband,
  * otherwise maximum packet size (up to 65520 bytes).
  */
 static int use_sideband;
 static int debug_fd;
+static int advertise_refs;
+static int stateless_rpc;
 
 static void reset_timeout(void)
 {
@@ -101,14 +105,12 @@ static void show_edge(struct commit *commit)
        fprintf(pack_pipe, "-%s\n", sha1_to_hex(commit->object.sha1));
 }
 
-static int do_rev_list(int fd, void *create_full_pack)
+static int do_rev_list(int in, int out, void *user_data)
 {
        int i;
        struct rev_info revs;
 
-       pack_pipe = fdopen(fd, "w");
-       if (create_full_pack)
-               use_thin_pack = 0; /* no point doing it */
+       pack_pipe = xfdopen(out, "w");
        init_revisions(&revs, NULL);
        revs.tag_objects = 1;
        revs.tree_objects = 1;
@@ -116,26 +118,25 @@ static int do_rev_list(int fd, void *create_full_pack)
        if (use_thin_pack)
                revs.edge_hint = 1;
 
-       if (create_full_pack) {
-               const char *args[] = {"rev-list", "--all", NULL};
-               setup_revisions(2, args, &revs, NULL);
-       } else {
-               for (i = 0; i < want_obj.nr; i++) {
-                       struct object *o = want_obj.objects[i].item;
-                       /* why??? */
-                       o->flags &= ~UNINTERESTING;
-                       add_pending_object(&revs, o, NULL);
-               }
-               for (i = 0; i < have_obj.nr; i++) {
-                       struct object *o = have_obj.objects[i].item;
-                       o->flags |= UNINTERESTING;
-                       add_pending_object(&revs, o, NULL);
-               }
-               setup_revisions(0, NULL, &revs, NULL);
+       for (i = 0; i < want_obj.nr; i++) {
+               struct object *o = want_obj.objects[i].item;
+               /* why??? */
+               o->flags &= ~UNINTERESTING;
+               add_pending_object(&revs, o, NULL);
+       }
+       for (i = 0; i < have_obj.nr; i++) {
+               struct object *o = have_obj.objects[i].item;
+               o->flags |= UNINTERESTING;
+               add_pending_object(&revs, o, NULL);
        }
+       setup_revisions(0, NULL, &revs, NULL);
        if (prepare_revision_walk(&revs))
                die("revision walk setup failed");
        mark_edges_uninteresting(revs.commits, &revs, show_edge);
+       if (use_thin_pack)
+               for (i = 0; i < extra_edge_obj.nr; i++)
+                       fprintf(pack_pipe, "-%s\n", sha1_to_hex(
+                                       extra_edge_obj.objects[i].item->sha1));
        traverse_commit_list(&revs, show_commit, show_object, NULL);
        fflush(pack_pipe);
        fclose(pack_pipe);
@@ -155,13 +156,22 @@ static void create_pack_file(void)
        const char *argv[10];
        int arg = 0;
 
-       rev_list.proc = do_rev_list;
-       /* .data is just a boolean: any non-NULL value will do */
-       rev_list.data = create_full_pack ? &rev_list : NULL;
-       if (start_async(&rev_list))
-               die("git upload-pack: unable to fork git-rev-list");
+       if (shallow_nr) {
+               memset(&rev_list, 0, sizeof(rev_list));
+               rev_list.proc = do_rev_list;
+               rev_list.out = -1;
+               if (start_async(&rev_list))
+                       die("git upload-pack: unable to fork git-rev-list");
+               argv[arg++] = "pack-objects";
+       } else {
+               argv[arg++] = "pack-objects";
+               argv[arg++] = "--revs";
+               if (create_full_pack)
+                       argv[arg++] = "--all";
+               else if (use_thin_pack)
+                       argv[arg++] = "--thin";
+       }
 
-       argv[arg++] = "pack-objects";
        argv[arg++] = "--stdout";
        if (!no_progress)
                argv[arg++] = "--progress";
@@ -172,7 +182,7 @@ static void create_pack_file(void)
        argv[arg++] = NULL;
 
        memset(&pack_objects, 0, sizeof(pack_objects));
-       pack_objects.in = rev_list.out; /* start_command closes it */
+       pack_objects.in = shallow_nr ? rev_list.out : -1;
        pack_objects.out = -1;
        pack_objects.err = -1;
        pack_objects.git_cmd = 1;
@@ -181,6 +191,24 @@ static void create_pack_file(void)
        if (start_command(&pack_objects))
                die("git upload-pack: unable to fork git-pack-objects");
 
+       /* pass on revisions we (don't) want */
+       if (!shallow_nr) {
+               FILE *pipe_fd = xfdopen(pack_objects.in, "w");
+               if (!create_full_pack) {
+                       int i;
+                       for (i = 0; i < want_obj.nr; i++)
+                               fprintf(pipe_fd, "%s\n", sha1_to_hex(want_obj.objects[i].item->sha1));
+                       fprintf(pipe_fd, "--not\n");
+                       for (i = 0; i < have_obj.nr; i++)
+                               fprintf(pipe_fd, "%s\n", sha1_to_hex(have_obj.objects[i].item->sha1));
+               }
+
+               fprintf(pipe_fd, "\n");
+               fflush(pipe_fd);
+               fclose(pipe_fd);
+       }
+
+
        /* We read from pack_objects.err to capture stderr output for
         * progress bar, and pack_objects.out to capture the pack data.
         */
@@ -218,6 +246,23 @@ static void create_pack_file(void)
                        }
                        continue;
                }
+               if (0 <= pe && (pfd[pe].revents & (POLLIN|POLLHUP))) {
+                       /* Status ready; we ship that in the side-band
+                        * or dump to the standard error.
+                        */
+                       sz = xread(pack_objects.err, progress,
+                                 sizeof(progress));
+                       if (0 < sz)
+                               send_client_data(2, progress, sz);
+                       else if (sz == 0) {
+                               close(pack_objects.err);
+                               pack_objects.err = -1;
+                       }
+                       else
+                               goto fail;
+                       /* give priority to status messages */
+                       continue;
+               }
                if (0 <= pu && (pfd[pu].revents & (POLLIN|POLLHUP))) {
                        /* Data ready; we keep the last byte to ourselves
                         * in case we detect broken rev-list, so that we
@@ -237,7 +282,7 @@ static void create_pack_file(void)
                        sz = xread(pack_objects.out, cp,
                                  sizeof(data) - outsz);
                        if (0 < sz)
-                                       ;
+                               ;
                        else if (sz == 0) {
                                close(pack_objects.out);
                                pack_objects.out = -1;
@@ -255,28 +300,13 @@ static void create_pack_file(void)
                        if (sz < 0)
                                goto fail;
                }
-               if (0 <= pe && (pfd[pe].revents & (POLLIN|POLLHUP))) {
-                       /* Status ready; we ship that in the side-band
-                        * or dump to the standard error.
-                        */
-                       sz = xread(pack_objects.err, progress,
-                                 sizeof(progress));
-                       if (0 < sz)
-                               send_client_data(2, progress, sz);
-                       else if (sz == 0) {
-                               close(pack_objects.err);
-                               pack_objects.err = -1;
-                       }
-                       else
-                               goto fail;
-               }
        }
 
        if (finish_command(&pack_objects)) {
                error("git upload-pack: git-pack-objects died with error.");
                goto fail;
        }
-       if (finish_async(&rev_list))
+       if (shallow_nr && finish_async(&rev_list))
                goto fail;      /* error was already reported */
 
        /* flush the data */
@@ -398,36 +428,41 @@ static int get_common_commits(void)
 {
        static char line[1000];
        unsigned char sha1[20];
-       char hex[41], last_hex[41];
+       char last_hex[41];
 
        save_commit_buffer = 0;
 
-       for(;;) {
+       for (;;) {
                int len = packet_read_line(0, line, sizeof(line));
                reset_timeout();
 
                if (!len) {
                        if (have_obj.nr == 0 || multi_ack)
                                packet_write(1, "NAK\n");
+                       if (stateless_rpc)
+                               exit(0);
                        continue;
                }
                strip(line, len);
                if (!prefixcmp(line, "have ")) {
                        switch (got_sha1(line+5, sha1)) {
                        case -1: /* they have what we do not */
-                               if (multi_ack && ok_to_give_up())
-                                       packet_write(1, "ACK %s continue\n",
-                                                    sha1_to_hex(sha1));
+                               if (multi_ack && ok_to_give_up()) {
+                                       const char *hex = sha1_to_hex(sha1);
+                                       if (multi_ack == 2)
+                                               packet_write(1, "ACK %s ready\n", hex);
+                                       else
+                                               packet_write(1, "ACK %s continue\n", hex);
+                               }
                                break;
                        default:
-                               memcpy(hex, sha1_to_hex(sha1), 41);
-                               if (multi_ack) {
-                                       const char *msg = "ACK %s continue\n";
-                                       packet_write(1, msg, hex);
-                                       memcpy(last_hex, hex, 41);
-                               }
+                               memcpy(last_hex, sha1_to_hex(sha1), 41);
+                               if (multi_ack == 2)
+                                       packet_write(1, "ACK %s common\n", last_hex);
+                               else if (multi_ack)
+                                       packet_write(1, "ACK %s continue\n", last_hex);
                                else if (have_obj.nr == 1)
-                                       packet_write(1, "ACK %s\n", hex);
+                                       packet_write(1, "ACK %s\n", last_hex);
                                break;
                        }
                        continue;
@@ -447,12 +482,13 @@ static int get_common_commits(void)
 
 static void receive_needs(void)
 {
-       struct object_array shallows = {0, 0, NULL};
+       struct object_array shallows = OBJECT_ARRAY_INIT;
        static char line[1000];
        int len, depth = 0;
 
+       shallow_nr = 0;
        if (debug_fd)
-               write_in_full(debug_fd, "#S\n", 3);
+               write_str_in_full(debug_fd, "#S\n");
        for (;;) {
                struct object *o;
                unsigned char sha1_buf[20];
@@ -466,7 +502,6 @@ static void receive_needs(void)
                if (!prefixcmp(line, "shallow ")) {
                        unsigned char sha1[20];
                        struct object *object;
-                       use_thin_pack = 0;
                        if (get_sha1(line + 8, sha1))
                                die("invalid shallow line: %s", line);
                        object = parse_object(sha1);
@@ -478,7 +513,6 @@ static void receive_needs(void)
                }
                if (!prefixcmp(line, "deepen ")) {
                        char *end;
-                       use_thin_pack = 0;
                        depth = strtol(line + 7, &end, 0);
                        if (end == line + 7 || depth <= 0)
                                die("Invalid deepen: %s", line);
@@ -488,7 +522,9 @@ static void receive_needs(void)
                    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"))
+               if (strstr(line+45, "multi_ack_detailed"))
+                       multi_ack = 2;
+               else if (strstr(line+45, "multi_ack"))
                        multi_ack = 1;
                if (strstr(line+45, "thin-pack"))
                        use_thin_pack = 1;
@@ -513,14 +549,19 @@ static void receive_needs(void)
                 */
                o = lookup_object(sha1_buf);
                if (!o || !(o->flags & OUR_REF))
-                       die("git upload-pack: not our ref %s", line+5);
+                       die("git upload-pack: not our ref %s",
+                           sha1_to_hex(sha1_buf));
                if (!(o->flags & WANTED)) {
                        o->flags |= WANTED;
                        add_object_array(o, NULL, &want_obj);
                }
        }
        if (debug_fd)
-               write_in_full(debug_fd, "#E\n", 3);
+               write_str_in_full(debug_fd, "#E\n");
+
+       if (!use_sideband && daemon_mode)
+               no_progress = 1;
+
        if (depth == 0 && shallows.nr == 0)
                return;
        if (depth > 0) {
@@ -534,6 +575,7 @@ static void receive_needs(void)
                                packet_write(1, "shallow %s",
                                                sha1_to_hex(object->sha1));
                                register_shallow(object->sha1);
+                               shallow_nr++;
                        }
                        result = result->next;
                }
@@ -556,6 +598,7 @@ static void receive_needs(void)
                                                        NULL, &want_obj);
                                        parents = parents->next;
                                }
+                               add_object_array(object, NULL, &extra_edge_obj);
                        }
                        /* make sure commit traversal conforms to client */
                        register_shallow(object->sha1);
@@ -567,6 +610,8 @@ static void receive_needs(void)
                        for (i = 0; i < shallows.nr; i++)
                                register_shallow(shallows.objects[i].item->sha1);
                }
+
+       shallow_nr += shallows.nr;
        free(shallows.objects);
 }
 
@@ -574,7 +619,7 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo
 {
        static const char *capabilities = "multi_ack thin-pack side-band"
                " side-band-64k ofs-delta shallow no-progress"
-               " include-tag";
+               " include-tag multi_ack_detailed";
        struct object *o = parse_object(sha1);
 
        if (!o)
@@ -598,12 +643,32 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo
        return 0;
 }
 
+static int mark_our_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+       struct object *o = parse_object(sha1);
+       if (!o)
+               die("git upload-pack: cannot find object %s:", sha1_to_hex(sha1));
+       if (!(o->flags & OUR_REF)) {
+               o->flags |= OUR_REF;
+               nr_our_refs++;
+       }
+       return 0;
+}
+
 static void upload_pack(void)
 {
-       reset_timeout();
-       head_ref(send_ref, NULL);
-       for_each_ref(send_ref, NULL);
-       packet_flush(1);
+       if (advertise_refs || !stateless_rpc) {
+               reset_timeout();
+               head_ref(send_ref, NULL);
+               for_each_ref(send_ref, NULL);
+               packet_flush(1);
+       } else {
+               head_ref(mark_our_ref, NULL);
+               for_each_ref(mark_our_ref, NULL);
+       }
+       if (advertise_refs)
+               return;
+
        receive_needs();
        if (want_obj.nr) {
                get_common_commits();
@@ -618,18 +683,28 @@ int main(int argc, char **argv)
        int strict = 0;
 
        git_extract_argv0_path(argv[0]);
+       read_replace_refs = 0;
 
        for (i = 1; i < argc; i++) {
                char *arg = argv[i];
 
                if (arg[0] != '-')
                        break;
+               if (!strcmp(arg, "--advertise-refs")) {
+                       advertise_refs = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--stateless-rpc")) {
+                       stateless_rpc = 1;
+                       continue;
+               }
                if (!strcmp(arg, "--strict")) {
                        strict = 1;
                        continue;
                }
                if (!prefixcmp(arg, "--timeout=")) {
                        timeout = atoi(arg+10);
+                       daemon_mode = 1;
                        continue;
                }
                if (!strcmp(arg, "--")) {
diff --git a/url.c b/url.c
new file mode 100644 (file)
index 0000000..cd8f74f
--- /dev/null
+++ b/url.c
@@ -0,0 +1,127 @@
+#include "cache.h"
+
+int is_urlschemechar(int first_flag, int ch)
+{
+       /*
+        * The set of valid URL schemes, as per STD66 (RFC3986) is
+        * '[A-Za-z][A-Za-z0-9+.-]*'. But use sightly looser check
+        * of '[A-Za-z0-9][A-Za-z0-9+.-]*' because earlier version
+        * of check used '[A-Za-z0-9]+' so not to break any remote
+        * helpers.
+        */
+       int alphanumeric, special;
+       alphanumeric = ch > 0 && isalnum(ch);
+       special = ch == '+' || ch == '-' || ch == '.';
+       return alphanumeric || (!first_flag && special);
+}
+
+int is_url(const char *url)
+{
+       const char *url2, *first_slash;
+
+       if (!url)
+               return 0;
+       url2 = url;
+       first_slash = strchr(url, '/');
+
+       /* Input with no slash at all or slash first can't be URL. */
+       if (!first_slash || first_slash == url)
+               return 0;
+       /* Character before must be : and next must be /. */
+       if (first_slash[-1] != ':' || first_slash[1] != '/')
+               return 0;
+       /* There must be something before the :// */
+       if (first_slash == url + 1)
+               return 0;
+       /*
+        * Check all characters up to first slash - 1. Only alphanum
+        * is allowed.
+        */
+       url2 = url;
+       while (url2 < first_slash - 1) {
+               if (!is_urlschemechar(url2 == url, (unsigned char)*url2))
+                       return 0;
+               url2++;
+       }
+
+       /* Valid enough. */
+       return 1;
+}
+
+static int url_decode_char(const char *q)
+{
+       int i;
+       unsigned char val = 0;
+       for (i = 0; i < 2; i++) {
+               unsigned char c = *q++;
+               val <<= 4;
+               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;
+               else
+                       return -1;
+       }
+       return val;
+}
+
+static char *url_decode_internal(const char **query, const char *stop_at,
+                                struct strbuf *out, int decode_plus)
+{
+       const char *q = *query;
+
+       do {
+               unsigned char c = *q;
+
+               if (!c)
+                       break;
+               if (stop_at && strchr(stop_at, c)) {
+                       q++;
+                       break;
+               }
+
+               if (c == '%') {
+                       int val = url_decode_char(q + 1);
+                       if (0 <= val) {
+                               strbuf_addch(out, val);
+                               q += 3;
+                               continue;
+                       }
+               }
+
+               if (decode_plus && c == '+')
+                       strbuf_addch(out, ' ');
+               else
+                       strbuf_addch(out, c);
+               q++;
+       } while (1);
+       *query = q;
+       return strbuf_detach(out, NULL);
+}
+
+char *url_decode(const char *url)
+{
+       struct strbuf out = STRBUF_INIT;
+       const char *colon = strchr(url, ':');
+
+       /* Skip protocol part if present */
+       if (colon && url < colon) {
+               strbuf_add(&out, url, colon - url);
+               url = colon;
+       }
+       return url_decode_internal(&url, NULL, &out, 0);
+}
+
+char *url_decode_parameter_name(const char **query)
+{
+       struct strbuf out = STRBUF_INIT;
+       return url_decode_internal(query, "&=", &out, 1);
+}
+
+char *url_decode_parameter_value(const char **query)
+{
+       struct strbuf out = STRBUF_INIT;
+       return url_decode_internal(query, "&", &out, 1);
+}
diff --git a/url.h b/url.h
new file mode 100644 (file)
index 0000000..15817f8
--- /dev/null
+++ b/url.h
@@ -0,0 +1,10 @@
+#ifndef URL_H
+#define URL_H
+
+extern int is_url(const char *url);
+extern int is_urlschemechar(int first_flag, int ch);
+extern char *url_decode(const char *url);
+extern char *url_decode_parameter_name(const char **query);
+extern char *url_decode_parameter_value(const char **query);
+
+#endif /* URL_H */
diff --git a/usage.c b/usage.c
index 820d09f92b03b6cc96fbe9a954c37fd2d5e5a417..ec4cf53b6bf16f7f9b3528a430891d6c68ef64cc 100644 (file)
--- a/usage.c
+++ b/usage.c
@@ -5,50 +5,59 @@
  */
 #include "git-compat-util.h"
 
-static void report(const char *prefix, const char *err, va_list params)
+void vreportf(const char *prefix, const char *err, va_list params)
 {
-       char msg[1024];
+       char msg[4096];
        vsnprintf(msg, sizeof(msg), err, params);
        fprintf(stderr, "%s%s\n", prefix, msg);
 }
 
-static NORETURN void usage_builtin(const char *err)
+static NORETURN void usage_builtin(const char *err, va_list params)
 {
-       fprintf(stderr, "usage: %s\n", err);
+       vreportf("usage: ", err, params);
        exit(129);
 }
 
 static NORETURN void die_builtin(const char *err, va_list params)
 {
-       report("fatal: ", err, params);
+       vreportf("fatal: ", err, params);
        exit(128);
 }
 
 static void error_builtin(const char *err, va_list params)
 {
-       report("error: ", err, params);
+       vreportf("error: ", err, params);
 }
 
 static void warn_builtin(const char *warn, va_list params)
 {
-       report("warning: ", warn, params);
+       vreportf("warning: ", warn, params);
 }
 
 /* If we are in a dlopen()ed .so write to a global variable would segfault
  * (ugh), so keep things static. */
-static void (*usage_routine)(const char *err) NORETURN = usage_builtin;
-static void (*die_routine)(const char *err, va_list params) NORETURN = die_builtin;
+static NORETURN_PTR void (*usage_routine)(const char *err, va_list params) = usage_builtin;
+static NORETURN_PTR void (*die_routine)(const char *err, va_list params) = die_builtin;
 static void (*error_routine)(const char *err, va_list params) = error_builtin;
 static void (*warn_routine)(const char *err, va_list params) = warn_builtin;
 
-void set_die_routine(void (*routine)(const char *err, va_list params) NORETURN)
+void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params))
 {
        die_routine = routine;
 }
 
+void usagef(const char *err, ...)
+{
+       va_list params;
+
+       va_start(params, err);
+       usage_routine(err, params);
+       va_end(params);
+}
+
 void usage(const char *err)
 {
-       usage_routine(err);
+       usagef("%s", err);
 }
 
 void die(const char *err, ...)
@@ -60,6 +69,34 @@ void die(const char *err, ...)
        va_end(params);
 }
 
+void die_errno(const char *fmt, ...)
+{
+       va_list params;
+       char fmt_with_err[1024];
+       char str_error[256], *err;
+       int i, j;
+
+       err = strerror(errno);
+       for (i = j = 0; err[i] && j < sizeof(str_error) - 1; ) {
+               if ((str_error[j++] = err[i++]) != '%')
+                       continue;
+               if (j < sizeof(str_error) - 1) {
+                       str_error[j++] = '%';
+               } else {
+                       /* No room to double the '%', so we overwrite it with
+                        * '\0' below */
+                       j--;
+                       break;
+               }
+       }
+       str_error[j] = 0;
+       snprintf(fmt_with_err, sizeof(fmt_with_err), "%s: %s", fmt, str_error);
+
+       va_start(params, fmt);
+       die_routine(fmt_with_err, params);
+       va_end(params);
+}
+
 int error(const char *err, ...)
 {
        va_list params;
index d556da975197a718979575864e37c2b9b9f3327a..e5522159b362811346b4306246391e960a4e7041 100644 (file)
@@ -1,3 +1,4 @@
+#include "cache.h"
 #include "userdiff.h"
 #include "cache.h"
 #include "attr.h"
@@ -13,7 +14,8 @@ PATTERNS("html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$",
         "[^<>= \t]+|[^[:space:]]|[\x80-\xff]+"),
 PATTERNS("java",
         "!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n"
-        "^[ \t]*(([ \t]*[A-Za-z_][A-Za-z_0-9]*){2,}[ \t]*\\([^;]*)$",
+        "^[ \t]*(([A-Za-z_][A-Za-z_0-9]*[ \t]+)+[A-Za-z_][A-Za-z_0-9]*[ \t]*\\([^;]*)$",
+        /* -- */
         "[a-zA-Z_][a-zA-Z0-9_]*"
         "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
         "|[-+*/<>%&^|=!]="
@@ -25,7 +27,7 @@ PATTERNS("objc",
         /* Objective-C methods */
         "^[ \t]*([-+][ \t]*\\([ \t]*[A-Za-z_][A-Za-z_0-9* \t]*\\)[ \t]*[A-Za-z_].*)$\n"
         /* C functions */
-        "^[ \t]*(([ \t]*[A-Za-z_][A-Za-z_0-9]*){2,}[ \t]*\\([^;]*)$\n"
+        "^[ \t]*(([A-Za-z_][A-Za-z_0-9]*[ \t]+)+[A-Za-z_][A-Za-z_0-9]*[ \t]*\\([^;]*)$\n"
         /* Objective-C class/protocol definitions */
         "^(@(implementation|interface|protocol)[ \t].*)$",
         /* -- */
@@ -43,7 +45,9 @@ PATTERNS("pascal",
         "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+"
         "|<>|<=|>=|:=|\\.\\."
         "|[^[:space:]]|[\x80-\xff]+"),
-PATTERNS("php", "^[\t ]*((function|class).*)",
+PATTERNS("php",
+        "^[\t ]*(((public|protected|private|static)[\t ]+)*function.*)$\n"
+        "^[\t ]*(class.*)$",
         /* -- */
         "[a-zA-Z_][a-zA-Z0-9_]*"
         "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+"
@@ -78,6 +82,22 @@ PATTERNS("cpp",
         "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
         "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"
         "|[^[:space:]]|[\x80-\xff]+"),
+PATTERNS("csharp",
+        /* Keywords */
+        "!^[ \t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\n"
+        /* Methods and constructors */
+        "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[<>@._[:alnum:]]+[ \t]*\\(.*\\))[ \t]*$\n"
+        /* Properties */
+        "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[@._[:alnum:]]+)[ \t]*$\n"
+        /* Type definitions */
+        "^[ \t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \t]+)*(class|enum|interface|struct)[ \t]+.*)$\n"
+        /* Namespace */
+        "^[ \t]*(namespace[ \t]+.*)$",
+        /* -- */
+        "[a-zA-Z_][a-zA-Z0-9_]*"
+        "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
+        "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"
+        "|[^[:space:]]|[\x80-\xff]+"),
 { "default", NULL, -1, { NULL, 0 } },
 };
 #undef PATTERNS
@@ -166,6 +186,12 @@ static int parse_tristate(int *b, const char *k, const char *v)
        return 1;
 }
 
+static int parse_bool(int *b, const char *k, const char *v)
+{
+       *b = git_config_bool(k, v);
+       return 1;
+}
+
 int userdiff_config(const char *k, const char *v)
 {
        struct userdiff_driver *drv;
@@ -180,6 +206,8 @@ int userdiff_config(const char *k, const char *v)
                return parse_string(&drv->external, k, v);
        if ((drv = parse_driver(k, v, "textconv")))
                return parse_string(&drv->textconv, k, v);
+       if ((drv = parse_driver(k, v, "cachetextconv")))
+               return parse_bool(&drv->textconv_want_cache, k, v);
        if ((drv = parse_driver(k, v, "wordregex")))
                return parse_string(&drv->word_regex, k, v);
 
@@ -197,7 +225,7 @@ struct userdiff_driver *userdiff_find_by_path(const char *path)
        struct git_attr_check check;
 
        if (!attr)
-               attr = git_attr("diff", 4);
+               attr = git_attr("diff");
        check.attr = attr;
 
        if (!path)
index c3151594f5c0643fead757accc27bf1093cf4a68..942d5949501027fb5d30e0d59629aab38fd2669a 100644 (file)
@@ -1,6 +1,8 @@
 #ifndef USERDIFF_H
 #define USERDIFF_H
 
+#include "notes-cache.h"
+
 struct userdiff_funcname {
        const char *pattern;
        int cflags;
@@ -13,6 +15,8 @@ struct userdiff_driver {
        struct userdiff_funcname funcname;
        const char *word_regex;
        const char *textconv;
+       struct notes_cache *textconv_cache;
+       int textconv_want_cache;
 };
 
 int userdiff_config(const char *k, const char *v);
diff --git a/utf8.c b/utf8.c
index ddfdc5e2b88d346886f64e39a93ced85cc10a78e..84cfc72e6db144880febad0a4ffa64800919fb6d 100644 (file)
--- a/utf8.c
+++ b/utf8.c
@@ -1,4 +1,5 @@
 #include "git-compat-util.h"
+#include "strbuf.h"
 #include "utf8.h"
 
 /* This code is originally from http://www.cl.cam.ac.uk/~mgk25/ucs/ */
@@ -162,7 +163,7 @@ static int git_wcwidth(ucs_char_t ch)
  * If the string was not a valid UTF-8, *start pointer is set to NULL
  * and the return value is undefined.
  */
-ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p)
+static ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p)
 {
        unsigned char *s = (unsigned char *)*start;
        ucs_char_t ch;
@@ -279,14 +280,41 @@ int is_utf8(const char *text)
        return 1;
 }
 
-static void print_spaces(int count)
+static void strbuf_addchars(struct strbuf *sb, int c, size_t n)
 {
-       static const char s[] = "                    ";
-       while (count >= sizeof(s)) {
-               fwrite(s, sizeof(s) - 1, 1, stdout);
-               count -= sizeof(s) - 1;
+       strbuf_grow(sb, n);
+       memset(sb->buf + sb->len, c, n);
+       strbuf_setlen(sb, sb->len + n);
+}
+
+static void strbuf_add_indented_text(struct strbuf *buf, const char *text,
+                                    int indent, int indent2)
+{
+       if (indent < 0)
+               indent = 0;
+       while (*text) {
+               const char *eol = strchrnul(text, '\n');
+               if (*eol == '\n')
+                       eol++;
+               strbuf_addchars(buf, ' ', indent);
+               strbuf_add(buf, text, eol - text);
+               text = eol;
+               indent = indent2;
        }
-       fwrite(s, count, 1, stdout);
+}
+
+static size_t display_mode_esc_sequence_len(const char *s)
+{
+       const char *p = s;
+       if (*p++ != '\033')
+               return 0;
+       if (*p++ != '[')
+               return 0;
+       while (isdigit(*p) || *p == ';')
+               p++;
+       if (*p++ != 'm')
+               return 0;
+       return p - s;
 }
 
 /*
@@ -295,45 +323,82 @@ static void print_spaces(int count)
  * If indent is negative, assume that already -indent columns have been
  * consumed (and no extra indent is necessary for the first line).
  */
-int print_wrapped_text(const char *text, int indent, int indent2, int width)
+int strbuf_add_wrapped_text(struct strbuf *buf,
+               const char *text, int indent1, int indent2, int width)
 {
-       int w = indent, assume_utf8 = is_utf8(text);
-       const char *bol = text, *space = NULL;
+       int indent, w, assume_utf8 = 1;
+       const char *bol, *space, *start = text;
+       size_t orig_len = buf->len;
 
+       if (width <= 0) {
+               strbuf_add_indented_text(buf, text, indent1, indent2);
+               return 1;
+       }
+
+retry:
+       bol = text;
+       w = indent = indent1;
+       space = NULL;
        if (indent < 0) {
                w = -indent;
                space = text;
        }
 
        for (;;) {
-               char c = *text;
+               char c;
+               size_t skip;
+
+               while ((skip = display_mode_esc_sequence_len(text)))
+                       text += skip;
+
+               c = *text;
                if (!c || isspace(c)) {
                        if (w < width || !space) {
                                const char *start = bol;
+                               if (!c && text == start)
+                                       return w;
                                if (space)
                                        start = space;
                                else
-                                       print_spaces(indent);
-                               fwrite(start, text - start, 1, stdout);
+                                       strbuf_addchars(buf, ' ', indent);
+                               strbuf_add(buf, start, text - start);
                                if (!c)
                                        return w;
-                               else if (c == '\t')
-                                       w |= 0x07;
                                space = text;
+                               if (c == '\t')
+                                       w |= 0x07;
+                               else if (c == '\n') {
+                                       space++;
+                                       if (*space == '\n') {
+                                               strbuf_addch(buf, '\n');
+                                               goto new_line;
+                                       }
+                                       else if (!isalnum(*space))
+                                               goto new_line;
+                                       else
+                                               strbuf_addch(buf, ' ');
+                               }
                                w++;
                                text++;
                        }
                        else {
-                               putchar('\n');
+new_line:
+                               strbuf_addch(buf, '\n');
                                text = bol = space + isspace(*space);
                                space = NULL;
                                w = indent = indent2;
                        }
                        continue;
                }
-               if (assume_utf8)
+               if (assume_utf8) {
                        w += utf8_width(&text, NULL);
-               else {
+                       if (!text) {
+                               assume_utf8 = 0;
+                               text = start;
+                               strbuf_setlen(buf, orig_len);
+                               goto retry;
+                       }
+               } else {
                        w++;
                        text++;
                }
@@ -354,7 +419,7 @@ int is_encoding_utf8(const char *name)
  * with iconv.  If the conversion fails, returns NULL.
  */
 #ifndef NO_ICONV
-#ifdef OLD_ICONV
+#if defined(OLD_ICONV) || (defined(__sun__) && !defined(_XPG6))
        typedef const char * iconv_ibp;
 #else
        typedef char * iconv_ibp;
diff --git a/utf8.h b/utf8.h
index 2f1b14ff49ef3c73bee6f298ba396b96120c34b7..ebc4d2fa85113c971ab4c4eaa52537a688a03745 100644 (file)
--- a/utf8.h
+++ b/utf8.h
@@ -3,13 +3,13 @@
 
 typedef unsigned int ucs_char_t;  /* assuming 32bit int */
 
-ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p);
 int utf8_width(const char **start, size_t *remainder_p);
 int utf8_strwidth(const char *string);
 int is_utf8(const char *text);
 int is_encoding_utf8(const char *name);
 
-int print_wrapped_text(const char *text, int indent, int indent2, int len);
+int strbuf_add_wrapped_text(struct strbuf *buf,
+               const char *text, int indent, int indent2, int width);
 
 #ifndef NO_ICONV
 char *reencode_string(const char *in, const char *out_encoding, const char *in_encoding);
diff --git a/var.c b/var.c
deleted file mode 100644 (file)
index 7362ed8..0000000
--- a/var.c
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Eric Biederman, 2005
- */
-#include "cache.h"
-#include "exec_cmd.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(IDENT_WARN_ON_NO_NAME));
-       }
-}
-
-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(IDENT_ERROR_ON_NO_NAME);
-                       break;
-               }
-       }
-       return val;
-}
-
-static int show_config(const char *var, const char *value, void *cb)
-{
-       if (value)
-               printf("%s=%s\n", var, value);
-       else
-               printf("%s\n", var);
-       return git_default_config(var, value, cb);
-}
-
-int main(int argc, char **argv)
-{
-       const char *val;
-       int nongit;
-       if (argc != 2) {
-               usage(var_usage);
-       }
-
-       git_extract_argv0_path(argv[0]);
-
-       setup_git_directory_gently(&nongit);
-       val = NULL;
-
-       if (strcmp(argv[1], "-l") == 0) {
-               git_config(show_config, NULL);
-               list_vars();
-               return 0;
-       }
-       git_config(git_default_config, NULL);
-       val = read_var(argv[1]);
-       if (!val)
-               usage(var_usage);
-
-       printf("%s\n", val);
-
-       return 0;
-}
diff --git a/vcs-svn/LICENSE b/vcs-svn/LICENSE
new file mode 100644 (file)
index 0000000..0a5e3c4
--- /dev/null
@@ -0,0 +1,33 @@
+Copyright (C) 2010 David Barr <david.barr@cordelta.com>.
+All rights reserved.
+
+Copyright (C) 2008 Jason Evans <jasone@canonware.com>.
+All rights reserved.
+
+Copyright (C) 2005 Stefan Hegny, hydrografix Consulting GmbH,
+Frankfurt/Main, Germany
+and others, see http://svn2cc.sarovar.org
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice(s), this list of conditions and the following disclaimer
+   unmodified other than the allowable addition of one or more
+   copyright notices.
+2. Redistributions in binary form must reproduce the above copyright
+   notice(s), this list of conditions and the following disclaimer in
+   the documentation and/or other materials provided with the
+   distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vcs-svn/fast_export.c b/vcs-svn/fast_export.c
new file mode 100644 (file)
index 0000000..6cfa256
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Licensed under a two-clause BSD-style license.
+ * See LICENSE for details.
+ */
+
+#include "git-compat-util.h"
+#include "fast_export.h"
+#include "line_buffer.h"
+#include "repo_tree.h"
+#include "string_pool.h"
+
+#define MAX_GITSVN_LINE_LEN 4096
+
+static uint32_t first_commit_done;
+
+void fast_export_delete(uint32_t depth, uint32_t *path)
+{
+       putchar('D');
+       putchar(' ');
+       pool_print_seq(depth, path, '/', stdout);
+       putchar('\n');
+}
+
+void fast_export_modify(uint32_t depth, uint32_t *path, uint32_t mode,
+                       uint32_t mark)
+{
+       /* Mode must be 100644, 100755, 120000, or 160000. */
+       printf("M %06"PRIo32" :%"PRIu32" ", mode, mark);
+       pool_print_seq(depth, path, '/', stdout);
+       putchar('\n');
+}
+
+static char gitsvnline[MAX_GITSVN_LINE_LEN];
+void fast_export_commit(uint32_t revision, uint32_t author, char *log,
+                       uint32_t uuid, uint32_t url,
+                       unsigned long timestamp)
+{
+       if (!log)
+               log = "";
+       if (~uuid && ~url) {
+               snprintf(gitsvnline, MAX_GITSVN_LINE_LEN,
+                               "\n\ngit-svn-id: %s@%"PRIu32" %s\n",
+                                pool_fetch(url), revision, pool_fetch(uuid));
+       } else {
+               *gitsvnline = '\0';
+       }
+       printf("commit refs/heads/master\n");
+       printf("committer %s <%s@%s> %ld +0000\n",
+                  ~author ? pool_fetch(author) : "nobody",
+                  ~author ? pool_fetch(author) : "nobody",
+                  ~uuid ? pool_fetch(uuid) : "local", timestamp);
+       printf("data %"PRIu32"\n%s%s\n",
+                  (uint32_t) (strlen(log) + strlen(gitsvnline)),
+                  log, gitsvnline);
+       if (!first_commit_done) {
+               if (revision > 1)
+                       printf("from refs/heads/master^0\n");
+               first_commit_done = 1;
+       }
+       repo_diff(revision - 1, revision);
+       fputc('\n', stdout);
+
+       printf("progress Imported commit %"PRIu32".\n\n", revision);
+}
+
+void fast_export_blob(uint32_t mode, uint32_t mark, uint32_t len)
+{
+       if (mode == REPO_MODE_LNK) {
+               /* svn symlink blobs start with "link " */
+               buffer_skip_bytes(5);
+               len -= 5;
+       }
+       printf("blob\nmark :%"PRIu32"\ndata %"PRIu32"\n", mark, len);
+       buffer_copy_bytes(len);
+       fputc('\n', stdout);
+}
diff --git a/vcs-svn/fast_export.h b/vcs-svn/fast_export.h
new file mode 100644 (file)
index 0000000..2aaaea5
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef FAST_EXPORT_H_
+#define FAST_EXPORT_H_
+
+void fast_export_delete(uint32_t depth, uint32_t *path);
+void fast_export_modify(uint32_t depth, uint32_t *path, uint32_t mode,
+                       uint32_t mark);
+void fast_export_commit(uint32_t revision, uint32_t author, char *log,
+                       uint32_t uuid, uint32_t url, unsigned long timestamp);
+void fast_export_blob(uint32_t mode, uint32_t mark, uint32_t len);
+
+#endif
diff --git a/vcs-svn/line_buffer.c b/vcs-svn/line_buffer.c
new file mode 100644 (file)
index 0000000..1543567
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * Licensed under a two-clause BSD-style license.
+ * See LICENSE for details.
+ */
+
+#include "git-compat-util.h"
+#include "line_buffer.h"
+#include "obj_pool.h"
+
+#define LINE_BUFFER_LEN 10000
+#define COPY_BUFFER_LEN 4096
+
+/* Create memory pool for char sequence of known length */
+obj_pool_gen(blob, char, 4096)
+
+static char line_buffer[LINE_BUFFER_LEN];
+static char byte_buffer[COPY_BUFFER_LEN];
+static FILE *infile;
+
+int buffer_init(const char *filename)
+{
+       infile = filename ? fopen(filename, "r") : stdin;
+       if (!infile)
+               return -1;
+       return 0;
+}
+
+int buffer_deinit(void)
+{
+       int err;
+       if (infile == stdin)
+               return ferror(infile);
+       err = ferror(infile);
+       err |= fclose(infile);
+       return err;
+}
+
+/* Read a line without trailing newline. */
+char *buffer_read_line(void)
+{
+       char *end;
+       if (!fgets(line_buffer, sizeof(line_buffer), infile))
+               /* Error or data exhausted. */
+               return NULL;
+       end = line_buffer + strlen(line_buffer);
+       if (end[-1] == '\n')
+               end[-1] = '\0';
+       else if (feof(infile))
+               ; /* No newline at end of file.  That's fine. */
+       else
+               /*
+                * Line was too long.
+                * There is probably a saner way to deal with this,
+                * but for now let's return an error.
+                */
+               return NULL;
+       return line_buffer;
+}
+
+char *buffer_read_string(uint32_t len)
+{
+       char *s;
+       blob_free(blob_pool.size);
+       s = blob_pointer(blob_alloc(len + 1));
+       s[fread(s, 1, len, infile)] = '\0';
+       return ferror(infile) ? NULL : s;
+}
+
+void buffer_copy_bytes(uint32_t len)
+{
+       uint32_t in;
+       while (len > 0 && !feof(infile) && !ferror(infile)) {
+               in = len < COPY_BUFFER_LEN ? len : COPY_BUFFER_LEN;
+               in = fread(byte_buffer, 1, in, infile);
+               len -= in;
+               fwrite(byte_buffer, 1, in, stdout);
+               if (ferror(stdout)) {
+                       buffer_skip_bytes(len);
+                       return;
+               }
+       }
+}
+
+void buffer_skip_bytes(uint32_t len)
+{
+       uint32_t in;
+       while (len > 0 && !feof(infile) && !ferror(infile)) {
+               in = len < COPY_BUFFER_LEN ? len : COPY_BUFFER_LEN;
+               in = fread(byte_buffer, 1, in, infile);
+               len -= in;
+       }
+}
+
+void buffer_reset(void)
+{
+       blob_reset();
+}
diff --git a/vcs-svn/line_buffer.h b/vcs-svn/line_buffer.h
new file mode 100644 (file)
index 0000000..9c78ae1
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef LINE_BUFFER_H_
+#define LINE_BUFFER_H_
+
+int buffer_init(const char *filename);
+int buffer_deinit(void);
+char *buffer_read_line(void);
+char *buffer_read_string(uint32_t len);
+void buffer_copy_bytes(uint32_t len);
+void buffer_skip_bytes(uint32_t len);
+void buffer_reset(void);
+
+#endif
diff --git a/vcs-svn/line_buffer.txt b/vcs-svn/line_buffer.txt
new file mode 100644 (file)
index 0000000..8906fb1
--- /dev/null
@@ -0,0 +1,58 @@
+line_buffer API
+===============
+
+The line_buffer library provides a convenient interface for
+mostly-line-oriented input.
+
+Each line is not permitted to exceed 10000 bytes.  The provided
+functions are not thread-safe or async-signal-safe, and like
+`fgets()`, they generally do not function correctly if interrupted
+by a signal without SA_RESTART set.
+
+Calling sequence
+----------------
+
+The calling program:
+
+ - specifies a file to read with `buffer_init`
+ - processes input with `buffer_read_line`, `buffer_read_string`,
+   `buffer_skip_bytes`, and `buffer_copy_bytes`
+ - closes the file with `buffer_deinit`, perhaps to start over and
+   read another file.
+
+Before exiting, the caller can use `buffer_reset` to deallocate
+resources for the benefit of profiling tools.
+
+Functions
+---------
+
+`buffer_init`::
+       Open the named file for input.  If filename is NULL,
+       start reading from stdin.  On failure, returns -1 (with
+       errno indicating the nature of the failure).
+
+`buffer_deinit`::
+       Stop reading from the current file (closing it unless
+       it was stdin).  Returns nonzero if `fclose` fails or
+       the error indicator was set.
+
+`buffer_read_line`::
+       Read a line and strip off the trailing newline.
+       On failure or end of file, returns NULL.
+
+`buffer_read_string`::
+       Read `len` characters of input or up to the end of the
+       file, whichever comes first.  Returns NULL on error.
+       Returns whatever characters were read (possibly "")
+       for end of file.
+
+`buffer_copy_bytes`::
+       Read `len` bytes of input and dump them to the standard output
+       stream.  Returns early for error or end of file.
+
+`buffer_skip_bytes`::
+       Discards `len` bytes from the input stream (stopping early
+       if necessary because of an error or eof).
+
+`buffer_reset`::
+       Deallocates non-static buffers.
diff --git a/vcs-svn/obj_pool.h b/vcs-svn/obj_pool.h
new file mode 100644 (file)
index 0000000..deb6eb8
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Licensed under a two-clause BSD-style license.
+ * See LICENSE for details.
+ */
+
+#ifndef OBJ_POOL_H_
+#define OBJ_POOL_H_
+
+#include "git-compat-util.h"
+
+#define MAYBE_UNUSED __attribute__((__unused__))
+
+#define obj_pool_gen(pre, obj_t, initial_capacity) \
+static struct { \
+       uint32_t committed; \
+       uint32_t size; \
+       uint32_t capacity; \
+       obj_t *base; \
+} pre##_pool = {0, 0, 0, NULL}; \
+static MAYBE_UNUSED uint32_t pre##_alloc(uint32_t count) \
+{ \
+       uint32_t offset; \
+       if (pre##_pool.size + count > pre##_pool.capacity) { \
+               while (pre##_pool.size + count > pre##_pool.capacity) \
+                       if (pre##_pool.capacity) \
+                               pre##_pool.capacity *= 2; \
+                       else \
+                               pre##_pool.capacity = initial_capacity; \
+               pre##_pool.base = realloc(pre##_pool.base, \
+                                       pre##_pool.capacity * sizeof(obj_t)); \
+       } \
+       offset = pre##_pool.size; \
+       pre##_pool.size += count; \
+       return offset; \
+} \
+static MAYBE_UNUSED void pre##_free(uint32_t count) \
+{ \
+       pre##_pool.size -= count; \
+} \
+static MAYBE_UNUSED uint32_t pre##_offset(obj_t *obj) \
+{ \
+       return obj == NULL ? ~0 : obj - pre##_pool.base; \
+} \
+static MAYBE_UNUSED obj_t *pre##_pointer(uint32_t offset) \
+{ \
+       return offset >= pre##_pool.size ? NULL : &pre##_pool.base[offset]; \
+} \
+static MAYBE_UNUSED void pre##_commit(void) \
+{ \
+       pre##_pool.committed = pre##_pool.size; \
+} \
+static MAYBE_UNUSED void pre##_reset(void) \
+{ \
+       free(pre##_pool.base); \
+       pre##_pool.base = NULL; \
+       pre##_pool.size = 0; \
+       pre##_pool.capacity = 0; \
+       pre##_pool.committed = 0; \
+}
+
+#endif
diff --git a/vcs-svn/repo_tree.c b/vcs-svn/repo_tree.c
new file mode 100644 (file)
index 0000000..e94d91d
--- /dev/null
@@ -0,0 +1,329 @@
+/*
+ * Licensed under a two-clause BSD-style license.
+ * See LICENSE for details.
+ */
+
+#include "git-compat-util.h"
+
+#include "string_pool.h"
+#include "repo_tree.h"
+#include "obj_pool.h"
+#include "fast_export.h"
+
+#include "trp.h"
+
+struct repo_dirent {
+       uint32_t name_offset;
+       struct trp_node children;
+       uint32_t mode;
+       uint32_t content_offset;
+};
+
+struct repo_dir {
+       struct trp_root entries;
+};
+
+struct repo_commit {
+       uint32_t root_dir_offset;
+};
+
+/* Memory pools for commit, dir and dirent */
+obj_pool_gen(commit, struct repo_commit, 4096)
+obj_pool_gen(dir, struct repo_dir, 4096)
+obj_pool_gen(dent, struct repo_dirent, 4096)
+
+static uint32_t active_commit;
+static uint32_t mark;
+
+static int repo_dirent_name_cmp(const void *a, const void *b);
+
+/* Treap for directory entries */
+trp_gen(static, dent_, struct repo_dirent, children, dent, repo_dirent_name_cmp);
+
+uint32_t next_blob_mark(void)
+{
+       return mark++;
+}
+
+static struct repo_dir *repo_commit_root_dir(struct repo_commit *commit)
+{
+       return dir_pointer(commit->root_dir_offset);
+}
+
+static struct repo_dirent *repo_first_dirent(struct repo_dir *dir)
+{
+       return dent_first(&dir->entries);
+}
+
+static int repo_dirent_name_cmp(const void *a, const void *b)
+{
+       const struct repo_dirent *dent1 = a, *dent2 = b;
+       uint32_t a_offset = dent1->name_offset;
+       uint32_t b_offset = dent2->name_offset;
+       return (a_offset > b_offset) - (a_offset < b_offset);
+}
+
+static int repo_dirent_is_dir(struct repo_dirent *dent)
+{
+       return dent != NULL && dent->mode == REPO_MODE_DIR;
+}
+
+static struct repo_dir *repo_dir_from_dirent(struct repo_dirent *dent)
+{
+       if (!repo_dirent_is_dir(dent))
+               return NULL;
+       return dir_pointer(dent->content_offset);
+}
+
+static struct repo_dir *repo_clone_dir(struct repo_dir *orig_dir)
+{
+       uint32_t orig_o, new_o;
+       orig_o = dir_offset(orig_dir);
+       if (orig_o >= dir_pool.committed)
+               return orig_dir;
+       new_o = dir_alloc(1);
+       orig_dir = dir_pointer(orig_o);
+       *dir_pointer(new_o) = *orig_dir;
+       return dir_pointer(new_o);
+}
+
+static struct repo_dirent *repo_read_dirent(uint32_t revision, uint32_t *path)
+{
+       uint32_t name = 0;
+       struct repo_dirent *key = dent_pointer(dent_alloc(1));
+       struct repo_dir *dir = NULL;
+       struct repo_dirent *dent = NULL;
+       dir = repo_commit_root_dir(commit_pointer(revision));
+       while (~(name = *path++)) {
+               key->name_offset = name;
+               dent = dent_search(&dir->entries, key);
+               if (dent == NULL || !repo_dirent_is_dir(dent))
+                       break;
+               dir = repo_dir_from_dirent(dent);
+       }
+       dent_free(1);
+       return dent;
+}
+
+static void repo_write_dirent(uint32_t *path, uint32_t mode,
+                             uint32_t content_offset, uint32_t del)
+{
+       uint32_t name, revision, dir_o = ~0, parent_dir_o = ~0;
+       struct repo_dir *dir;
+       struct repo_dirent *key;
+       struct repo_dirent *dent = NULL;
+       revision = active_commit;
+       dir = repo_commit_root_dir(commit_pointer(revision));
+       dir = repo_clone_dir(dir);
+       commit_pointer(revision)->root_dir_offset = dir_offset(dir);
+       while (~(name = *path++)) {
+               parent_dir_o = dir_offset(dir);
+
+               key = dent_pointer(dent_alloc(1));
+               key->name_offset = name;
+
+               dent = dent_search(&dir->entries, key);
+               if (dent == NULL)
+                       dent = key;
+               else
+                       dent_free(1);
+
+               if (dent == key) {
+                       dent->mode = REPO_MODE_DIR;
+                       dent->content_offset = 0;
+                       dent_insert(&dir->entries, dent);
+               }
+
+               if (dent_offset(dent) < dent_pool.committed) {
+                       dir_o = repo_dirent_is_dir(dent) ?
+                                       dent->content_offset : ~0;
+                       dent_remove(&dir->entries, dent);
+                       dent = dent_pointer(dent_alloc(1));
+                       dent->name_offset = name;
+                       dent->mode = REPO_MODE_DIR;
+                       dent->content_offset = dir_o;
+                       dent_insert(&dir->entries, dent);
+               }
+
+               dir = repo_dir_from_dirent(dent);
+               dir = repo_clone_dir(dir);
+               dent->content_offset = dir_offset(dir);
+       }
+       if (dent == NULL)
+               return;
+       dent->mode = mode;
+       dent->content_offset = content_offset;
+       if (del && ~parent_dir_o)
+               dent_remove(&dir_pointer(parent_dir_o)->entries, dent);
+}
+
+uint32_t repo_copy(uint32_t revision, uint32_t *src, uint32_t *dst)
+{
+       uint32_t mode = 0, content_offset = 0;
+       struct repo_dirent *src_dent;
+       src_dent = repo_read_dirent(revision, src);
+       if (src_dent != NULL) {
+               mode = src_dent->mode;
+               content_offset = src_dent->content_offset;
+               repo_write_dirent(dst, mode, content_offset, 0);
+       }
+       return mode;
+}
+
+void repo_add(uint32_t *path, uint32_t mode, uint32_t blob_mark)
+{
+       repo_write_dirent(path, mode, blob_mark, 0);
+}
+
+uint32_t repo_replace(uint32_t *path, uint32_t blob_mark)
+{
+       uint32_t mode = 0;
+       struct repo_dirent *src_dent;
+       src_dent = repo_read_dirent(active_commit, path);
+       if (src_dent != NULL) {
+               mode = src_dent->mode;
+               repo_write_dirent(path, mode, blob_mark, 0);
+       }
+       return mode;
+}
+
+void repo_modify(uint32_t *path, uint32_t mode, uint32_t blob_mark)
+{
+       struct repo_dirent *src_dent;
+       src_dent = repo_read_dirent(active_commit, path);
+       if (src_dent != NULL && blob_mark == 0)
+               blob_mark = src_dent->content_offset;
+       repo_write_dirent(path, mode, blob_mark, 0);
+}
+
+void repo_delete(uint32_t *path)
+{
+       repo_write_dirent(path, 0, 0, 1);
+}
+
+static void repo_git_add_r(uint32_t depth, uint32_t *path, struct repo_dir *dir);
+
+static void repo_git_add(uint32_t depth, uint32_t *path, struct repo_dirent *dent)
+{
+       if (repo_dirent_is_dir(dent))
+               repo_git_add_r(depth, path, repo_dir_from_dirent(dent));
+       else
+               fast_export_modify(depth, path,
+                                  dent->mode, dent->content_offset);
+}
+
+static void repo_git_add_r(uint32_t depth, uint32_t *path, struct repo_dir *dir)
+{
+       struct repo_dirent *de = repo_first_dirent(dir);
+       while (de) {
+               path[depth] = de->name_offset;
+               repo_git_add(depth + 1, path, de);
+               de = dent_next(&dir->entries, de);
+       }
+}
+
+static void repo_diff_r(uint32_t depth, uint32_t *path, struct repo_dir *dir1,
+                       struct repo_dir *dir2)
+{
+       struct repo_dirent *de1, *de2;
+       de1 = repo_first_dirent(dir1);
+       de2 = repo_first_dirent(dir2);
+
+       while (de1 && de2) {
+               if (de1->name_offset < de2->name_offset) {
+                       path[depth] = de1->name_offset;
+                       fast_export_delete(depth + 1, path);
+                       de1 = dent_next(&dir1->entries, de1);
+                       continue;
+               }
+               if (de1->name_offset > de2->name_offset) {
+                       path[depth] = de2->name_offset;
+                       repo_git_add(depth + 1, path, de2);
+                       de2 = dent_next(&dir2->entries, de2);
+                       continue;
+               }
+               path[depth] = de1->name_offset;
+
+               if (de1->mode == de2->mode &&
+                   de1->content_offset == de2->content_offset) {
+                       ; /* No change. */
+               } else if (repo_dirent_is_dir(de1) && repo_dirent_is_dir(de2)) {
+                       repo_diff_r(depth + 1, path,
+                                   repo_dir_from_dirent(de1),
+                                   repo_dir_from_dirent(de2));
+               } else if (!repo_dirent_is_dir(de1) && !repo_dirent_is_dir(de2)) {
+                       repo_git_add(depth + 1, path, de2);
+               } else {
+                       fast_export_delete(depth + 1, path);
+                       repo_git_add(depth + 1, path, de2);
+               }
+               de1 = dent_next(&dir1->entries, de1);
+               de2 = dent_next(&dir2->entries, de2);
+       }
+       while (de1) {
+               path[depth] = de1->name_offset;
+               fast_export_delete(depth + 1, path);
+               de1 = dent_next(&dir1->entries, de1);
+       }
+       while (de2) {
+               path[depth] = de2->name_offset;
+               repo_git_add(depth + 1, path, de2);
+               de2 = dent_next(&dir2->entries, de2);
+       }
+}
+
+static uint32_t path_stack[REPO_MAX_PATH_DEPTH];
+
+void repo_diff(uint32_t r1, uint32_t r2)
+{
+       repo_diff_r(0,
+                   path_stack,
+                   repo_commit_root_dir(commit_pointer(r1)),
+                   repo_commit_root_dir(commit_pointer(r2)));
+}
+
+void repo_commit(uint32_t revision, uint32_t author, char *log, uint32_t uuid,
+                uint32_t url, unsigned long timestamp)
+{
+       fast_export_commit(revision, author, log, uuid, url, timestamp);
+       dent_commit();
+       dir_commit();
+       active_commit = commit_alloc(1);
+       commit_pointer(active_commit)->root_dir_offset =
+               commit_pointer(active_commit - 1)->root_dir_offset;
+}
+
+static void mark_init(void)
+{
+       uint32_t i;
+       mark = 0;
+       for (i = 0; i < dent_pool.size; i++)
+               if (!repo_dirent_is_dir(dent_pointer(i)) &&
+                   dent_pointer(i)->content_offset > mark)
+                       mark = dent_pointer(i)->content_offset;
+       mark++;
+}
+
+void repo_init(void)
+{
+       mark_init();
+       if (commit_pool.size == 0) {
+               /* Create empty tree for commit 0. */
+               commit_alloc(1);
+               commit_pointer(0)->root_dir_offset = dir_alloc(1);
+               dir_pointer(0)->entries.trp_root = ~0;
+               dir_commit();
+       }
+       /* Preallocate next commit, ready for changes. */
+       active_commit = commit_alloc(1);
+       commit_pointer(active_commit)->root_dir_offset =
+               commit_pointer(active_commit - 1)->root_dir_offset;
+}
+
+void repo_reset(void)
+{
+       pool_reset();
+       commit_reset();
+       dir_reset();
+       dent_reset();
+}
diff --git a/vcs-svn/repo_tree.h b/vcs-svn/repo_tree.h
new file mode 100644 (file)
index 0000000..5476175
--- /dev/null
@@ -0,0 +1,26 @@
+#ifndef REPO_TREE_H_
+#define REPO_TREE_H_
+
+#include "git-compat-util.h"
+
+#define REPO_MODE_DIR 0040000
+#define REPO_MODE_BLB 0100644
+#define REPO_MODE_EXE 0100755
+#define REPO_MODE_LNK 0120000
+
+#define REPO_MAX_PATH_LEN 4096
+#define REPO_MAX_PATH_DEPTH 1000
+
+uint32_t next_blob_mark(void);
+uint32_t repo_copy(uint32_t revision, uint32_t *src, uint32_t *dst);
+void repo_add(uint32_t *path, uint32_t mode, uint32_t blob_mark);
+uint32_t repo_replace(uint32_t *path, uint32_t blob_mark);
+void repo_modify(uint32_t *path, uint32_t mode, uint32_t blob_mark);
+void repo_delete(uint32_t *path);
+void repo_commit(uint32_t revision, uint32_t author, char *log, uint32_t uuid,
+                uint32_t url, long unsigned timestamp);
+void repo_diff(uint32_t r1, uint32_t r2);
+void repo_init(void);
+void repo_reset(void);
+
+#endif
diff --git a/vcs-svn/string_pool.c b/vcs-svn/string_pool.c
new file mode 100644 (file)
index 0000000..f5b1da8
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Licensed under a two-clause BSD-style license.
+ * See LICENSE for details.
+ */
+
+#include "git-compat-util.h"
+#include "trp.h"
+#include "obj_pool.h"
+#include "string_pool.h"
+
+static struct trp_root tree = { ~0 };
+
+struct node {
+       uint32_t offset;
+       struct trp_node children;
+};
+
+/* Two memory pools: one for struct node, and another for strings */
+obj_pool_gen(node, struct node, 4096)
+obj_pool_gen(string, char, 4096)
+
+static char *node_value(struct node *node)
+{
+       return node ? string_pointer(node->offset) : NULL;
+}
+
+static int node_cmp(struct node *a, struct node *b)
+{
+       return strcmp(node_value(a), node_value(b));
+}
+
+/* Build a Treap from the node structure (a trp_node w/ offset) */
+trp_gen(static, tree_, struct node, children, node, node_cmp);
+
+const char *pool_fetch(uint32_t entry)
+{
+       return node_value(node_pointer(entry));
+}
+
+uint32_t pool_intern(const char *key)
+{
+       /* Canonicalize key */
+       struct node *match = NULL, *node;
+       uint32_t key_len;
+       if (key == NULL)
+               return ~0;
+       key_len = strlen(key) + 1;
+       node = node_pointer(node_alloc(1));
+       node->offset = string_alloc(key_len);
+       strcpy(node_value(node), key);
+       match = tree_search(&tree, node);
+       if (!match) {
+               tree_insert(&tree, node);
+       } else {
+               node_free(1);
+               string_free(key_len);
+               node = match;
+       }
+       return node_offset(node);
+}
+
+uint32_t pool_tok_r(char *str, const char *delim, char **saveptr)
+{
+       char *token = strtok_r(str, delim, saveptr);
+       return token ? pool_intern(token) : ~0;
+}
+
+void pool_print_seq(uint32_t len, uint32_t *seq, char delim, FILE *stream)
+{
+       uint32_t i;
+       for (i = 0; i < len && ~seq[i]; i++) {
+               fputs(pool_fetch(seq[i]), stream);
+               if (i < len - 1 && ~seq[i + 1])
+                       fputc(delim, stream);
+       }
+}
+
+uint32_t pool_tok_seq(uint32_t sz, uint32_t *seq, const char *delim, char *str)
+{
+       char *context = NULL;
+       uint32_t token = ~0;
+       uint32_t length;
+
+       if (sz == 0)
+               return ~0;
+       if (str)
+               token = pool_tok_r(str, delim, &context);
+       for (length = 0; length < sz; length++) {
+               seq[length] = token;
+               if (token == ~0)
+                       return length;
+               token = pool_tok_r(NULL, delim, &context);
+       }
+       seq[sz - 1] = ~0;
+       return sz;
+}
+
+void pool_reset(void)
+{
+       node_reset();
+       string_reset();
+}
diff --git a/vcs-svn/string_pool.h b/vcs-svn/string_pool.h
new file mode 100644 (file)
index 0000000..222fb66
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef STRING_POOL_H_
+#define STRING_POOL_H_
+
+uint32_t pool_intern(const char *key);
+const char *pool_fetch(uint32_t entry);
+uint32_t pool_tok_r(char *str, const char *delim, char **saveptr);
+void pool_print_seq(uint32_t len, uint32_t *seq, char delim, FILE *stream);
+uint32_t pool_tok_seq(uint32_t sz, uint32_t *seq, const char *delim, char *str);
+void pool_reset(void);
+
+#endif
diff --git a/vcs-svn/string_pool.txt b/vcs-svn/string_pool.txt
new file mode 100644 (file)
index 0000000..1b41f15
--- /dev/null
@@ -0,0 +1,43 @@
+string_pool API
+===============
+
+The string_pool API provides facilities for replacing strings
+with integer keys that can be more easily compared and stored.
+The facilities are designed so that one could teach Git without
+too much trouble to store the information needed for these keys to
+remain valid over multiple executions.
+
+Functions
+---------
+
+pool_intern::
+       Include a string in the string pool and get its key.
+       If that string is already in the pool, retrieves its
+       existing key.
+
+pool_fetch::
+       Retrieve the string associated to a given key.
+
+pool_tok_r::
+       Extract the key of the next token from a string.
+       Interface mimics strtok_r.
+
+pool_print_seq::
+       Print a sequence of strings named by key to a file, using the
+       specified delimiter to separate them.
+
+       If NULL (key ~0) appears in the sequence, the sequence ends
+       early.
+
+pool_tok_seq::
+       Split a string into tokens, storing the keys of segments
+       into a caller-provided array.
+
+       Unless sz is 0, the array will always be ~0-terminated.
+       If there is not enough room for all the tokens, the
+       array holds as many tokens as fit in the entries before
+       the terminating ~0.  Return value is the index after the
+       last token, or sz if the tokens did not fit.
+
+pool_reset::
+       Deallocate storage for the string pool.
diff --git a/vcs-svn/svndump.c b/vcs-svn/svndump.c
new file mode 100644 (file)
index 0000000..53d0215
--- /dev/null
@@ -0,0 +1,302 @@
+/*
+ * Parse and rearrange a svnadmin dump.
+ * Create the dump with:
+ * svnadmin dump --incremental -r<startrev>:<endrev> <repository> >outfile
+ *
+ * Licensed under a two-clause BSD-style license.
+ * See LICENSE for details.
+ */
+
+#include "cache.h"
+#include "repo_tree.h"
+#include "fast_export.h"
+#include "line_buffer.h"
+#include "obj_pool.h"
+#include "string_pool.h"
+
+#define NODEACT_REPLACE 4
+#define NODEACT_DELETE 3
+#define NODEACT_ADD 2
+#define NODEACT_CHANGE 1
+#define NODEACT_UNKNOWN 0
+
+#define DUMP_CTX 0
+#define REV_CTX  1
+#define NODE_CTX 2
+
+#define LENGTH_UNKNOWN (~0)
+#define DATE_RFC2822_LEN 31
+
+/* Create memory pool for log messages */
+obj_pool_gen(log, char, 4096)
+
+static char* log_copy(uint32_t length, char *log)
+{
+       char *buffer;
+       log_free(log_pool.size);
+       buffer = log_pointer(log_alloc(length));
+       strncpy(buffer, log, length);
+       return buffer;
+}
+
+static struct {
+       uint32_t action, propLength, textLength, srcRev, srcMode, mark, type;
+       uint32_t src[REPO_MAX_PATH_DEPTH], dst[REPO_MAX_PATH_DEPTH];
+} node_ctx;
+
+static struct {
+       uint32_t revision, author;
+       unsigned long timestamp;
+       char *log;
+} rev_ctx;
+
+static struct {
+       uint32_t uuid, url;
+} dump_ctx;
+
+static struct {
+       uint32_t svn_log, svn_author, svn_date, svn_executable, svn_special, uuid,
+               revision_number, node_path, node_kind, node_action,
+               node_copyfrom_path, node_copyfrom_rev, text_content_length,
+               prop_content_length, content_length;
+} keys;
+
+static void reset_node_ctx(char *fname)
+{
+       node_ctx.type = 0;
+       node_ctx.action = NODEACT_UNKNOWN;
+       node_ctx.propLength = LENGTH_UNKNOWN;
+       node_ctx.textLength = LENGTH_UNKNOWN;
+       node_ctx.src[0] = ~0;
+       node_ctx.srcRev = 0;
+       node_ctx.srcMode = 0;
+       pool_tok_seq(REPO_MAX_PATH_DEPTH, node_ctx.dst, "/", fname);
+       node_ctx.mark = 0;
+}
+
+static void reset_rev_ctx(uint32_t revision)
+{
+       rev_ctx.revision = revision;
+       rev_ctx.timestamp = 0;
+       rev_ctx.log = NULL;
+       rev_ctx.author = ~0;
+}
+
+static void reset_dump_ctx(uint32_t url)
+{
+       dump_ctx.url = url;
+       dump_ctx.uuid = ~0;
+}
+
+static void init_keys(void)
+{
+       keys.svn_log = pool_intern("svn:log");
+       keys.svn_author = pool_intern("svn:author");
+       keys.svn_date = pool_intern("svn:date");
+       keys.svn_executable = pool_intern("svn:executable");
+       keys.svn_special = pool_intern("svn:special");
+       keys.uuid = pool_intern("UUID");
+       keys.revision_number = pool_intern("Revision-number");
+       keys.node_path = pool_intern("Node-path");
+       keys.node_kind = pool_intern("Node-kind");
+       keys.node_action = pool_intern("Node-action");
+       keys.node_copyfrom_path = pool_intern("Node-copyfrom-path");
+       keys.node_copyfrom_rev = pool_intern("Node-copyfrom-rev");
+       keys.text_content_length = pool_intern("Text-content-length");
+       keys.prop_content_length = pool_intern("Prop-content-length");
+       keys.content_length = pool_intern("Content-length");
+}
+
+static void read_props(void)
+{
+       uint32_t len;
+       uint32_t key = ~0;
+       char *val = NULL;
+       char *t;
+       while ((t = buffer_read_line()) && strcmp(t, "PROPS-END")) {
+               if (!strncmp(t, "K ", 2)) {
+                       len = atoi(&t[2]);
+                       key = pool_intern(buffer_read_string(len));
+                       buffer_read_line();
+               } else if (!strncmp(t, "V ", 2)) {
+                       len = atoi(&t[2]);
+                       val = buffer_read_string(len);
+                       if (key == keys.svn_log) {
+                               /* Value length excludes terminating nul. */
+                               rev_ctx.log = log_copy(len + 1, val);
+                       } else if (key == keys.svn_author) {
+                               rev_ctx.author = pool_intern(val);
+                       } else if (key == keys.svn_date) {
+                               if (parse_date_basic(val, &rev_ctx.timestamp, NULL))
+                                       fprintf(stderr, "Invalid timestamp: %s\n", val);
+                       } else if (key == keys.svn_executable) {
+                               node_ctx.type = REPO_MODE_EXE;
+                       } else if (key == keys.svn_special) {
+                               node_ctx.type = REPO_MODE_LNK;
+                       }
+                       key = ~0;
+                       buffer_read_line();
+               }
+       }
+}
+
+static void handle_node(void)
+{
+       if (node_ctx.propLength != LENGTH_UNKNOWN && node_ctx.propLength)
+               read_props();
+
+       if (node_ctx.srcRev)
+               node_ctx.srcMode = repo_copy(node_ctx.srcRev, node_ctx.src, node_ctx.dst);
+
+       if (node_ctx.textLength != LENGTH_UNKNOWN &&
+           node_ctx.type != REPO_MODE_DIR)
+               node_ctx.mark = next_blob_mark();
+
+       if (node_ctx.action == NODEACT_DELETE) {
+               repo_delete(node_ctx.dst);
+       } else if (node_ctx.action == NODEACT_CHANGE ||
+                          node_ctx.action == NODEACT_REPLACE) {
+               if (node_ctx.action == NODEACT_REPLACE &&
+                   node_ctx.type == REPO_MODE_DIR)
+                       repo_replace(node_ctx.dst, node_ctx.mark);
+               else if (node_ctx.propLength != LENGTH_UNKNOWN)
+                       repo_modify(node_ctx.dst, node_ctx.type, node_ctx.mark);
+               else if (node_ctx.textLength != LENGTH_UNKNOWN)
+                       node_ctx.srcMode = repo_replace(node_ctx.dst, node_ctx.mark);
+       } else if (node_ctx.action == NODEACT_ADD) {
+               if (node_ctx.srcRev && node_ctx.propLength != LENGTH_UNKNOWN)
+                       repo_modify(node_ctx.dst, node_ctx.type, node_ctx.mark);
+               else if (node_ctx.srcRev && node_ctx.textLength != LENGTH_UNKNOWN)
+                       node_ctx.srcMode = repo_replace(node_ctx.dst, node_ctx.mark);
+               else if ((node_ctx.type == REPO_MODE_DIR && !node_ctx.srcRev) ||
+                        node_ctx.textLength != LENGTH_UNKNOWN)
+                       repo_add(node_ctx.dst, node_ctx.type, node_ctx.mark);
+       }
+
+       if (node_ctx.propLength == LENGTH_UNKNOWN && node_ctx.srcMode)
+               node_ctx.type = node_ctx.srcMode;
+
+       if (node_ctx.mark)
+               fast_export_blob(node_ctx.type, node_ctx.mark, node_ctx.textLength);
+       else if (node_ctx.textLength != LENGTH_UNKNOWN)
+               buffer_skip_bytes(node_ctx.textLength);
+}
+
+static void handle_revision(void)
+{
+       if (rev_ctx.revision)
+               repo_commit(rev_ctx.revision, rev_ctx.author, rev_ctx.log,
+                       dump_ctx.uuid, dump_ctx.url, rev_ctx.timestamp);
+}
+
+void svndump_read(const char *url)
+{
+       char *val;
+       char *t;
+       uint32_t active_ctx = DUMP_CTX;
+       uint32_t len;
+       uint32_t key;
+
+       reset_dump_ctx(pool_intern(url));
+       while ((t = buffer_read_line())) {
+               val = strstr(t, ": ");
+               if (!val)
+                       continue;
+               *val++ = '\0';
+               *val++ = '\0';
+               key = pool_intern(t);
+
+               if (key == keys.uuid) {
+                       dump_ctx.uuid = pool_intern(val);
+               } else if (key == keys.revision_number) {
+                       if (active_ctx == NODE_CTX)
+                               handle_node();
+                       if (active_ctx != DUMP_CTX)
+                               handle_revision();
+                       active_ctx = REV_CTX;
+                       reset_rev_ctx(atoi(val));
+               } else if (key == keys.node_path) {
+                       if (active_ctx == NODE_CTX)
+                               handle_node();
+                       active_ctx = NODE_CTX;
+                       reset_node_ctx(val);
+               } else if (key == keys.node_kind) {
+                       if (!strcmp(val, "dir"))
+                               node_ctx.type = REPO_MODE_DIR;
+                       else if (!strcmp(val, "file"))
+                               node_ctx.type = REPO_MODE_BLB;
+                       else
+                               fprintf(stderr, "Unknown node-kind: %s\n", val);
+               } else if (key == keys.node_action) {
+                       if (!strcmp(val, "delete")) {
+                               node_ctx.action = NODEACT_DELETE;
+                       } else if (!strcmp(val, "add")) {
+                               node_ctx.action = NODEACT_ADD;
+                       } else if (!strcmp(val, "change")) {
+                               node_ctx.action = NODEACT_CHANGE;
+                       } else if (!strcmp(val, "replace")) {
+                               node_ctx.action = NODEACT_REPLACE;
+                       } else {
+                               fprintf(stderr, "Unknown node-action: %s\n", val);
+                               node_ctx.action = NODEACT_UNKNOWN;
+                       }
+               } else if (key == keys.node_copyfrom_path) {
+                       pool_tok_seq(REPO_MAX_PATH_DEPTH, node_ctx.src, "/", val);
+               } else if (key == keys.node_copyfrom_rev) {
+                       node_ctx.srcRev = atoi(val);
+               } else if (key == keys.text_content_length) {
+                       node_ctx.textLength = atoi(val);
+               } else if (key == keys.prop_content_length) {
+                       node_ctx.propLength = atoi(val);
+               } else if (key == keys.content_length) {
+                       len = atoi(val);
+                       buffer_read_line();
+                       if (active_ctx == REV_CTX) {
+                               read_props();
+                       } else if (active_ctx == NODE_CTX) {
+                               handle_node();
+                               active_ctx = REV_CTX;
+                       } else {
+                               fprintf(stderr, "Unexpected content length header: %"PRIu32"\n", len);
+                               buffer_skip_bytes(len);
+                       }
+               }
+       }
+       if (active_ctx == NODE_CTX)
+               handle_node();
+       if (active_ctx != DUMP_CTX)
+               handle_revision();
+}
+
+void svndump_init(const char *filename)
+{
+       buffer_init(filename);
+       repo_init();
+       reset_dump_ctx(~0);
+       reset_rev_ctx(0);
+       reset_node_ctx(NULL);
+       init_keys();
+}
+
+void svndump_deinit(void)
+{
+       log_reset();
+       repo_reset();
+       reset_dump_ctx(~0);
+       reset_rev_ctx(0);
+       reset_node_ctx(NULL);
+       if (buffer_deinit())
+               fprintf(stderr, "Input error\n");
+       if (ferror(stdout))
+               fprintf(stderr, "Output error\n");
+}
+
+void svndump_reset(void)
+{
+       log_reset();
+       buffer_reset();
+       repo_reset();
+       reset_dump_ctx(~0);
+       reset_rev_ctx(0);
+       reset_node_ctx(NULL);
+}
diff --git a/vcs-svn/svndump.h b/vcs-svn/svndump.h
new file mode 100644 (file)
index 0000000..93c412f
--- /dev/null
@@ -0,0 +1,9 @@
+#ifndef SVNDUMP_H_
+#define SVNDUMP_H_
+
+void svndump_init(const char *filename);
+void svndump_read(const char *url);
+void svndump_deinit(void);
+void svndump_reset(void);
+
+#endif
diff --git a/vcs-svn/trp.h b/vcs-svn/trp.h
new file mode 100644 (file)
index 0000000..ee35c68
--- /dev/null
@@ -0,0 +1,236 @@
+/*
+ * C macro implementation of treaps.
+ *
+ * Usage:
+ *   #include <stdint.h>
+ *   #include "trp.h"
+ *   trp_gen(...)
+ *
+ * Licensed under a two-clause BSD-style license.
+ * See LICENSE for details.
+ */
+
+#ifndef TRP_H_
+#define TRP_H_
+
+#define MAYBE_UNUSED __attribute__((__unused__))
+
+/* Node structure. */
+struct trp_node {
+       uint32_t trpn_left;
+       uint32_t trpn_right;
+};
+
+/* Root structure. */
+struct trp_root {
+       uint32_t trp_root;
+};
+
+/* Pointer/Offset conversion. */
+#define trpn_pointer(a_base, a_offset) (a_base##_pointer(a_offset))
+#define trpn_offset(a_base, a_pointer) (a_base##_offset(a_pointer))
+#define trpn_modify(a_base, a_offset) \
+       do { \
+               if ((a_offset) < a_base##_pool.committed) { \
+                       uint32_t old_offset = (a_offset);\
+                       (a_offset) = a_base##_alloc(1); \
+                       *trpn_pointer(a_base, a_offset) = \
+                               *trpn_pointer(a_base, old_offset); \
+               } \
+       } while (0)
+
+/* Left accessors. */
+#define trp_left_get(a_base, a_field, a_node) \
+       (trpn_pointer(a_base, a_node)->a_field.trpn_left)
+#define trp_left_set(a_base, a_field, a_node, a_left) \
+       do { \
+               trpn_modify(a_base, a_node); \
+               trp_left_get(a_base, a_field, a_node) = (a_left); \
+       } while (0)
+
+/* Right accessors. */
+#define trp_right_get(a_base, a_field, a_node) \
+       (trpn_pointer(a_base, a_node)->a_field.trpn_right)
+#define trp_right_set(a_base, a_field, a_node, a_right) \
+       do { \
+               trpn_modify(a_base, a_node); \
+               trp_right_get(a_base, a_field, a_node) = (a_right); \
+       } while (0)
+
+/*
+ * Fibonacci hash function.
+ * The multiplier is the nearest prime to (2^32 times (√5 - 1)/2).
+ * See Knuth §6.4: volume 3, 3rd ed, p518.
+ */
+#define trpn_hash(a_node) (uint32_t) (2654435761u * (a_node))
+
+/* Priority accessors. */
+#define trp_prio_get(a_node) trpn_hash(a_node)
+
+/* Node initializer. */
+#define trp_node_new(a_base, a_field, a_node) \
+       do { \
+               trp_left_set(a_base, a_field, (a_node), ~0); \
+               trp_right_set(a_base, a_field, (a_node), ~0); \
+       } while (0)
+
+/* Internal utility macros. */
+#define trpn_first(a_base, a_field, a_root, r_node) \
+       do { \
+               (r_node) = (a_root); \
+               if ((r_node) == ~0) \
+                       return NULL; \
+               while (~trp_left_get(a_base, a_field, (r_node))) \
+                       (r_node) = trp_left_get(a_base, a_field, (r_node)); \
+       } while (0)
+
+#define trpn_rotate_left(a_base, a_field, a_node, r_node) \
+       do { \
+               (r_node) = trp_right_get(a_base, a_field, (a_node)); \
+               trp_right_set(a_base, a_field, (a_node), \
+                       trp_left_get(a_base, a_field, (r_node))); \
+               trp_left_set(a_base, a_field, (r_node), (a_node)); \
+       } while (0)
+
+#define trpn_rotate_right(a_base, a_field, a_node, r_node) \
+       do { \
+               (r_node) = trp_left_get(a_base, a_field, (a_node)); \
+               trp_left_set(a_base, a_field, (a_node), \
+                       trp_right_get(a_base, a_field, (r_node))); \
+               trp_right_set(a_base, a_field, (r_node), (a_node)); \
+       } while (0)
+
+#define trp_gen(a_attr, a_pre, a_type, a_field, a_base, a_cmp) \
+a_attr a_type MAYBE_UNUSED *a_pre##first(struct trp_root *treap) \
+{ \
+       uint32_t ret; \
+       trpn_first(a_base, a_field, treap->trp_root, ret); \
+       return trpn_pointer(a_base, ret); \
+} \
+a_attr a_type MAYBE_UNUSED *a_pre##next(struct trp_root *treap, a_type *node) \
+{ \
+       uint32_t ret; \
+       uint32_t offset = trpn_offset(a_base, node); \
+       if (~trp_right_get(a_base, a_field, offset)) { \
+               trpn_first(a_base, a_field, \
+                       trp_right_get(a_base, a_field, offset), ret); \
+       } else { \
+               uint32_t tnode = treap->trp_root; \
+               ret = ~0; \
+               while (1) { \
+                       int cmp = (a_cmp)(trpn_pointer(a_base, offset), \
+                               trpn_pointer(a_base, tnode)); \
+                       if (cmp < 0) { \
+                               ret = tnode; \
+                               tnode = trp_left_get(a_base, a_field, tnode); \
+                       } else if (cmp > 0) { \
+                               tnode = trp_right_get(a_base, a_field, tnode); \
+                       } else { \
+                               break; \
+                       } \
+               } \
+       } \
+       return trpn_pointer(a_base, ret); \
+} \
+a_attr a_type MAYBE_UNUSED *a_pre##search(struct trp_root *treap, a_type *key) \
+{ \
+       int cmp; \
+       uint32_t ret = treap->trp_root; \
+       while (~ret && (cmp = (a_cmp)(key, trpn_pointer(a_base, ret)))) { \
+               if (cmp < 0) { \
+                       ret = trp_left_get(a_base, a_field, ret); \
+               } else { \
+                       ret = trp_right_get(a_base, a_field, ret); \
+               } \
+       } \
+       return trpn_pointer(a_base, ret); \
+} \
+a_attr a_type MAYBE_UNUSED *a_pre##nsearch(struct trp_root *treap, a_type *key) \
+{ \
+       int cmp; \
+       uint32_t ret = treap->trp_root; \
+       while (~ret && (cmp = (a_cmp)(key, trpn_pointer(a_base, ret)))) { \
+               if (cmp < 0) { \
+                       if (!~trp_left_get(a_base, a_field, ret)) \
+                               break; \
+                       ret = trp_left_get(a_base, a_field, ret); \
+               } else { \
+                       ret = trp_right_get(a_base, a_field, ret); \
+               } \
+       } \
+       return trpn_pointer(a_base, ret); \
+} \
+a_attr uint32_t MAYBE_UNUSED a_pre##insert_recurse(uint32_t cur_node, uint32_t ins_node) \
+{ \
+       if (cur_node == ~0) { \
+               return ins_node; \
+       } else { \
+               uint32_t ret; \
+               int cmp = (a_cmp)(trpn_pointer(a_base, ins_node), \
+                                       trpn_pointer(a_base, cur_node)); \
+               if (cmp < 0) { \
+                       uint32_t left = a_pre##insert_recurse( \
+                               trp_left_get(a_base, a_field, cur_node), ins_node); \
+                       trp_left_set(a_base, a_field, cur_node, left); \
+                       if (trp_prio_get(left) < trp_prio_get(cur_node)) \
+                               trpn_rotate_right(a_base, a_field, cur_node, ret); \
+                       else \
+                               ret = cur_node; \
+               } else { \
+                       uint32_t right = a_pre##insert_recurse( \
+                               trp_right_get(a_base, a_field, cur_node), ins_node); \
+                       trp_right_set(a_base, a_field, cur_node, right); \
+                       if (trp_prio_get(right) < trp_prio_get(cur_node)) \
+                               trpn_rotate_left(a_base, a_field, cur_node, ret); \
+                       else \
+                               ret = cur_node; \
+               } \
+               return ret; \
+       } \
+} \
+a_attr void MAYBE_UNUSED a_pre##insert(struct trp_root *treap, a_type *node) \
+{ \
+       uint32_t offset = trpn_offset(a_base, node); \
+       trp_node_new(a_base, a_field, offset); \
+       treap->trp_root = a_pre##insert_recurse(treap->trp_root, offset); \
+} \
+a_attr uint32_t MAYBE_UNUSED a_pre##remove_recurse(uint32_t cur_node, uint32_t rem_node) \
+{ \
+       int cmp = a_cmp(trpn_pointer(a_base, rem_node), \
+                       trpn_pointer(a_base, cur_node)); \
+       if (cmp == 0) { \
+               uint32_t ret; \
+               uint32_t left = trp_left_get(a_base, a_field, cur_node); \
+               uint32_t right = trp_right_get(a_base, a_field, cur_node); \
+               if (left == ~0) { \
+                       if (right == ~0) \
+                               return ~0; \
+               } else if (right == ~0 || trp_prio_get(left) < trp_prio_get(right)) { \
+                       trpn_rotate_right(a_base, a_field, cur_node, ret); \
+                       right = a_pre##remove_recurse(cur_node, rem_node); \
+                       trp_right_set(a_base, a_field, ret, right); \
+                       return ret; \
+               } \
+               trpn_rotate_left(a_base, a_field, cur_node, ret); \
+               left = a_pre##remove_recurse(cur_node, rem_node); \
+               trp_left_set(a_base, a_field, ret, left); \
+               return ret; \
+       } else if (cmp < 0) { \
+               uint32_t left = a_pre##remove_recurse( \
+                       trp_left_get(a_base, a_field, cur_node), rem_node); \
+               trp_left_set(a_base, a_field, cur_node, left); \
+               return cur_node; \
+       } else { \
+               uint32_t right = a_pre##remove_recurse( \
+                       trp_right_get(a_base, a_field, cur_node), rem_node); \
+               trp_right_set(a_base, a_field, cur_node, right); \
+               return cur_node; \
+       } \
+} \
+a_attr void MAYBE_UNUSED a_pre##remove(struct trp_root *treap, a_type *node) \
+{ \
+       treap->trp_root = a_pre##remove_recurse(treap->trp_root, \
+               trpn_offset(a_base, node)); \
+} \
+
+#endif
diff --git a/vcs-svn/trp.txt b/vcs-svn/trp.txt
new file mode 100644 (file)
index 0000000..eb4c191
--- /dev/null
@@ -0,0 +1,103 @@
+Motivation
+==========
+
+Treaps provide a memory-efficient binary search tree structure.
+Insertion/deletion/search are about as about as fast in the average
+case as red-black trees and the chances of worst-case behavior are
+vanishingly small, thanks to (pseudo-)randomness.  The bad worst-case
+behavior is a small price to pay, given that treaps are much simpler
+to implement.
+
+API
+===
+
+The trp API generates a data structure and functions to handle a
+large growing set of objects stored in a pool.
+
+The caller:
+
+. Specifies parameters for the generated functions with the
+  trp_gen(static, foo_, ...) macro.
+
+. Allocates a `struct trp_root` variable and sets it to {~0}.
+
+. Adds new nodes to the set using `foo_insert`.
+
+. Can find a specific item in the set using `foo_search`.
+
+. Can iterate over items in the set using `foo_first` and `foo_next`.
+
+. Can remove an item from the set using `foo_remove`.
+
+Example:
+
+----
+struct ex_node {
+       const char *s;
+       struct trp_node ex_link;
+};
+static struct trp_root ex_base = {~0};
+obj_pool_gen(ex, struct ex_node, 4096);
+trp_gen(static, ex_, struct ex_node, ex_link, ex, strcmp)
+struct ex_node *item;
+
+item = ex_pointer(ex_alloc(1));
+item->s = "hello";
+ex_insert(&ex_base, item);
+item = ex_pointer(ex_alloc(1));
+item->s = "goodbye";
+ex_insert(&ex_base, item);
+for (item = ex_first(&ex_base); item; item = ex_next(&ex_base, item))
+       printf("%s\n", item->s);
+----
+
+Functions
+---------
+
+trp_gen(attr, foo_, node_type, link_field, pool, cmp)::
+
+       Generate a type-specific treap implementation.
++
+. The storage class for generated functions will be 'attr' (e.g., `static`).
+. Generated function names are prefixed with 'foo_' (e.g., `treap_`).
+. Treap nodes will be of type 'node_type' (e.g., `struct treap_node`).
+  This type must be a struct with at least one `struct trp_node` field
+  to point to its children.
+. The field used to access child nodes will be 'link_field'.
+. All treap nodes must lie in the 'pool' object pool.
+. Treap nodes must be totally ordered by the 'cmp' relation, with the
+  following prototype:
++
+int (*cmp)(node_type \*a, node_type \*b)
++
+and returning a value less than, equal to, or greater than zero
+according to the result of comparison.
+
+void foo_insert(struct trp_root *treap, node_type \*node)::
+
+       Insert node into treap.  If inserted multiple times,
+       a node will appear in the treap multiple times.
+
+void foo_remove(struct trp_root *treap, node_type \*node)::
+
+       Remove node from treap.  Caller must ensure node is
+       present in treap before using this function.
+
+node_type *foo_search(struct trp_root \*treap, node_type \*key)::
+
+       Search for a node that matches key.  If no match is found,
+       result is NULL.
+
+node_type *foo_nsearch(struct trp_root \*treap, node_type \*key)::
+
+       Like `foo_search`, but if if the key is missing return what
+       would be key's successor, were key in treap (NULL if no
+       successor).
+
+node_type *foo_first(struct trp_root \*treap)::
+
+       Find the first item from the treap, in sorted order.
+
+node_type *foo_next(struct trp_root \*treap, node_type \*node)::
+
+       Find the next item.
index e57630e983488e5c0222dc47a5e32d6efb184762..11d9052ed86ab17b56ab86ef21b7c98095e410dc 100644 (file)
--- a/walker.c
+++ b/walker.c
@@ -245,7 +245,7 @@ void walker_targets_free(int targets, char **target, const char **write_ref)
 {
        while (targets--) {
                free(target[targets]);
-               if (write_ref && write_ref[targets])
+               if (write_ref)
                        free((char *) write_ref[targets]);
        }
 }
index 8a149e11084eeec4501b5b2c5d22e5266f4852e7..95e576548474e942addcf1978f215720dd2f6e96 100644 (file)
--- a/walker.h
+++ b/walker.h
@@ -34,6 +34,6 @@ int walker_fetch(struct walker *impl, int targets, char **target,
 
 void walker_free(struct walker *walker);
 
-struct walker *get_http_walker(const char *url, struct remote *remote);
+struct walker *get_http_walker(const char *url);
 
 #endif /* WALKER_H */
diff --git a/wrap-for-bin.sh b/wrap-for-bin.sh
new file mode 100644 (file)
index 0000000..09feb1f
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+# wrap-for-bin.sh: Template for git executable wrapper scripts
+# to run test suite against sandbox, but with only bindir-installed
+# executables in PATH.  The Makefile copies this into various
+# files in bin-wrappers, substituting
+# @@BUILD_DIR@@ and @@PROG@@.
+
+GIT_EXEC_PATH='@@BUILD_DIR@@'
+if test -n "$NO_SET_GIT_TEMPLATE_DIR"
+then
+       unset GIT_TEMPLATE_DIR
+else
+       GIT_TEMPLATE_DIR='@@BUILD_DIR@@/templates/blt'
+       export GIT_TEMPLATE_DIR
+fi
+GITPERLLIB='@@BUILD_DIR@@/perl/blib/lib'
+PATH='@@BUILD_DIR@@/bin-wrappers:'"$PATH"
+export GIT_EXEC_PATH GITPERLLIB PATH
+
+exec "${GIT_EXEC_PATH}/@@PROG@@" "$@"
index d8efb1365a01104db568633fa8f6aef0c67b4cd1..fd8ead33ed72c37e690ee1fc5b8568f629c95145 100644 (file)
--- a/wrapper.c
+++ b/wrapper.c
@@ -3,11 +3,25 @@
  */
 #include "cache.h"
 
+static void try_to_free_builtin(size_t size)
+{
+       release_pack_memory(size, -1);
+}
+
+static void (*try_to_free_routine)(size_t size) = try_to_free_builtin;
+
+try_to_free_t set_try_to_free_routine(try_to_free_t routine)
+{
+       try_to_free_t old = try_to_free_routine;
+       try_to_free_routine = routine;
+       return old;
+}
+
 char *xstrdup(const char *str)
 {
        char *ret = strdup(str);
        if (!ret) {
-               release_pack_memory(strlen(str) + 1, -1);
+               try_to_free_routine(strlen(str) + 1);
                ret = strdup(str);
                if (!ret)
                        die("Out of memory, strdup failed");
@@ -21,12 +35,13 @@ void *xmalloc(size_t size)
        if (!ret && !size)
                ret = malloc(1);
        if (!ret) {
-               release_pack_memory(size, -1);
+               try_to_free_routine(size);
                ret = malloc(size);
                if (!ret && !size)
                        ret = malloc(1);
                if (!ret)
-                       die("Out of memory, malloc failed");
+                       die("Out of memory, malloc failed (tried to allocate %lu bytes)",
+                           (unsigned long)size);
        }
 #ifdef XMALLOC_POISON
        memset(ret, 0xA5, size);
@@ -34,6 +49,16 @@ void *xmalloc(size_t size)
        return ret;
 }
 
+void *xmallocz(size_t size)
+{
+       void *ret;
+       if (size + 1 < size)
+               die("Data too large to fit into virtual memory space.");
+       ret = xmalloc(size + 1);
+       ((char*)ret)[size] = 0;
+       return ret;
+}
+
 /*
  * xmemdupz() allocates (len + 1) bytes of memory, duplicates "len" bytes of
  * "data" to the allocated memory, zero terminates the allocated memory,
@@ -42,10 +67,7 @@ void *xmalloc(size_t size)
  */
 void *xmemdupz(const void *data, size_t len)
 {
-       char *p = xmalloc(len + 1);
-       memcpy(p, data, len);
-       p[len] = '\0';
-       return p;
+       return memcpy(xmallocz(len), data, len);
 }
 
 char *xstrndup(const char *str, size_t len)
@@ -60,7 +82,7 @@ void *xrealloc(void *ptr, size_t size)
        if (!ret && !size)
                ret = realloc(ptr, 1);
        if (!ret) {
-               release_pack_memory(size, -1);
+               try_to_free_routine(size);
                ret = realloc(ptr, size);
                if (!ret && !size)
                        ret = realloc(ptr, 1);
@@ -76,7 +98,7 @@ void *xcalloc(size_t nmemb, size_t size)
        if (!ret && (!nmemb || !size))
                ret = calloc(1, 1);
        if (!ret) {
-               release_pack_memory(nmemb * size, -1);
+               try_to_free_routine(nmemb * size);
                ret = calloc(nmemb, size);
                if (!ret && (!nmemb || !size))
                        ret = calloc(1, 1);
@@ -96,7 +118,7 @@ void *xmmap(void *start, size_t length,
                release_pack_memory(length, fd);
                ret = mmap(start, length, prot, flags, fd, offset);
                if (ret == MAP_FAILED)
-                       die("Out of memory? mmap failed: %s", strerror(errno));
+                       die_errno("Out of memory? mmap failed");
        }
        return ret;
 }
@@ -175,7 +197,7 @@ int xdup(int fd)
 {
        int ret = dup(fd);
        if (ret < 0)
-               die("dup failed: %s", strerror(errno));
+               die_errno("dup failed");
        return ret;
 }
 
@@ -183,7 +205,7 @@ FILE *xfdopen(int fd, const char *mode)
 {
        FILE *stream = fdopen(fd, mode);
        if (stream == NULL)
-               die("Out of memory? fdopen failed: %s", strerror(errno));
+               die_errno("Out of memory? fdopen failed");
        return stream;
 }
 
@@ -193,7 +215,17 @@ int xmkstemp(char *template)
 
        fd = mkstemp(template);
        if (fd < 0)
-               die("Unable to create temporary file: %s", strerror(errno));
+               die_errno("Unable to create temporary file");
+       return fd;
+}
+
+int xmkstemp_mode(char *template, int mode)
+{
+       int fd;
+
+       fd = git_mkstemp_mode(template, mode);
+       if (fd < 0)
+               die_errno("Unable to create temporary file");
        return fd;
 }
 
@@ -260,10 +292,14 @@ int git_inflate(z_streamp strm, int flush)
 int odb_mkstemp(char *template, size_t limit, const char *pattern)
 {
        int fd;
-
+       /*
+        * we let the umask do its job, don't try to be more
+        * restrictive except to remove write permission.
+        */
+       int mode = 0444;
        snprintf(template, limit, "%s/%s",
                 get_object_directory(), pattern);
-       fd = mkstemp(template);
+       fd = git_mkstemp_mode(template, mode);
        if (0 <= fd)
                return fd;
 
@@ -272,7 +308,7 @@ int odb_mkstemp(char *template, size_t limit, const char *pattern)
        snprintf(template, limit, "%s/%s",
                 get_object_directory(), pattern);
        safe_create_leading_directories(template);
-       return xmkstemp(template);
+       return xmkstemp_mode(template, mode);
 }
 
 int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1)
@@ -289,3 +325,31 @@ int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1)
        safe_create_leading_directories(name);
        return open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
 }
+
+static int warn_if_unremovable(const char *op, const char *file, int rc)
+{
+       if (rc < 0) {
+               int err = errno;
+               if (ENOENT != err) {
+                       warning("unable to %s %s: %s",
+                               op, file, strerror(errno));
+                       errno = err;
+               }
+       }
+       return rc;
+}
+
+int unlink_or_warn(const char *file)
+{
+       return warn_if_unremovable("unlink", file, unlink(file));
+}
+
+int rmdir_or_warn(const char *file)
+{
+       return warn_if_unremovable("rmdir", file, rmdir(file));
+}
+
+int remove_or_warn(unsigned int mode, const char *file)
+{
+       return S_ISGITLINK(mode) ? rmdir_or_warn(file) : unlink_or_warn(file);
+}
index 4c29255df1b637f93ab3d59e0dcab1fa3b40e10b..d45b536021e15c5a674f7969be39f238194bef99 100644 (file)
@@ -41,14 +41,14 @@ void maybe_flush_or_die(FILE *f, const char *desc)
                 */
                if (errno == EPIPE || errno == EINVAL)
                        exit(0);
-               die("write failure on %s: %s", desc, strerror(errno));
+               die_errno("write failure on '%s'", desc);
        }
 }
 
 void fsync_or_die(int fd, const char *msg)
 {
        if (fsync(fd) < 0) {
-               die("%s: fsync error (%s)", msg, strerror(errno));
+               die_errno("fsync error on '%s'", msg);
        }
 }
 
@@ -57,7 +57,7 @@ void write_or_die(int fd, const void *buf, size_t count)
        if (write_in_full(fd, buf, count) < 0) {
                if (errno == EPIPE)
                        exit(0);
-               die("write error (%s)", strerror(errno));
+               die_errno("write error");
        }
 }
 
diff --git a/ws.c b/ws.c
index b1efcd9d753a29d295702b36fb1beba58fb16995..d7b8c33f14195ba7ebe86bdbca2fea8f1b22ad37 100644 (file)
--- a/ws.c
+++ b/ws.c
 static struct whitespace_rule {
        const char *rule_name;
        unsigned rule_bits;
+       unsigned loosens_error:1,
+               exclude_default:1;
 } whitespace_rule_names[] = {
-       { "trailing-space", WS_TRAILING_SPACE },
-       { "space-before-tab", WS_SPACE_BEFORE_TAB },
-       { "indent-with-non-tab", WS_INDENT_WITH_NON_TAB },
-       { "cr-at-eol", WS_CR_AT_EOL },
+       { "trailing-space", WS_TRAILING_SPACE, 0 },
+       { "space-before-tab", WS_SPACE_BEFORE_TAB, 0 },
+       { "indent-with-non-tab", WS_INDENT_WITH_NON_TAB, 0 },
+       { "cr-at-eol", WS_CR_AT_EOL, 1 },
+       { "blank-at-eol", WS_BLANK_AT_EOL, 0 },
+       { "blank-at-eof", WS_BLANK_AT_EOF, 0 },
+       { "tab-in-indent", WS_TAB_IN_INDENT, 0, 1 },
 };
 
 unsigned parse_whitespace_rule(const char *string)
@@ -53,6 +58,9 @@ unsigned parse_whitespace_rule(const char *string)
                }
                string = ep;
        }
+
+       if (rule & WS_TAB_IN_INDENT && rule & WS_INDENT_WITH_NON_TAB)
+               die("cannot enforce both tab-in-indent and indent-with-non-tab");
        return rule;
 }
 
@@ -61,7 +69,7 @@ static void setup_whitespace_attr_check(struct git_attr_check *check)
        static struct git_attr *attr_whitespace;
 
        if (!attr_whitespace)
-               attr_whitespace = git_attr("whitespace", 10);
+               attr_whitespace = git_attr("whitespace");
        check[0].attr = attr_whitespace;
 }
 
@@ -79,7 +87,9 @@ unsigned whitespace_rule(const char *pathname)
                        unsigned all_rule = 0;
                        int i;
                        for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++)
-                               all_rule |= whitespace_rule_names[i].rule_bits;
+                               if (!whitespace_rule_names[i].loosens_error &&
+                                   !whitespace_rule_names[i].exclude_default)
+                                       all_rule |= whitespace_rule_names[i].rule_bits;
                        return all_rule;
                } else if (ATTR_FALSE(value)) {
                        /* false (-whitespace) */
@@ -100,8 +110,17 @@ unsigned whitespace_rule(const char *pathname)
 char *whitespace_error_string(unsigned ws)
 {
        struct strbuf err = STRBUF_INIT;
-       if (ws & WS_TRAILING_SPACE)
+       if ((ws & WS_TRAILING_SPACE) == WS_TRAILING_SPACE)
                strbuf_addstr(&err, "trailing whitespace");
+       else {
+               if (ws & WS_BLANK_AT_EOL)
+                       strbuf_addstr(&err, "trailing whitespace");
+               if (ws & WS_BLANK_AT_EOF) {
+                       if (err.len)
+                               strbuf_addstr(&err, ", ");
+                       strbuf_addstr(&err, "new blank line at EOF");
+               }
+       }
        if (ws & WS_SPACE_BEFORE_TAB) {
                if (err.len)
                        strbuf_addstr(&err, ", ");
@@ -112,6 +131,11 @@ char *whitespace_error_string(unsigned ws)
                        strbuf_addstr(&err, ", ");
                strbuf_addstr(&err, "indent with spaces");
        }
+       if (ws & WS_TAB_IN_INDENT) {
+               if (err.len)
+                       strbuf_addstr(&err, ", ");
+               strbuf_addstr(&err, "tab in indent");
+       }
        return strbuf_detach(&err, NULL);
 }
 
@@ -139,18 +163,18 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,
        }
 
        /* Check for trailing whitespace. */
-       if (ws_rule & WS_TRAILING_SPACE) {
+       if (ws_rule & WS_BLANK_AT_EOL) {
                for (i = len - 1; i >= 0; i--) {
                        if (isspace(line[i])) {
                                trailing_whitespace = i;
-                               result |= WS_TRAILING_SPACE;
+                               result |= WS_BLANK_AT_EOL;
                        }
                        else
                                break;
                }
        }
 
-       /* Check for space before tab in initial indent. */
+       /* Check indentation */
        for (i = 0; i < len; i++) {
                if (line[i] == ' ')
                        continue;
@@ -162,11 +186,19 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,
                                fputs(ws, stream);
                                fwrite(line + written, i - written, 1, stream);
                                fputs(reset, stream);
+                               fwrite(line + i, 1, 1, stream);
                        }
-               } else if (stream)
-                       fwrite(line + written, i - written, 1, stream);
-               if (stream)
-                       fwrite(line + i, 1, 1, stream);
+               } else if (ws_rule & WS_TAB_IN_INDENT) {
+                       result |= WS_TAB_IN_INDENT;
+                       if (stream) {
+                               fwrite(line + written, i - written, 1, stream);
+                               fputs(ws, stream);
+                               fwrite(line + i, 1, 1, stream);
+                               fputs(reset, stream);
+                       }
+               } else if (stream) {
+                       fwrite(line + written, i - written + 1, 1, stream);
+               }
                written = i + 1;
        }
 
@@ -239,8 +271,8 @@ int ws_blank_line(const char *line, int len, unsigned ws_rule)
        return 1;
 }
 
-/* Copy the line to the buffer while fixing whitespaces */
-int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *error_count)
+/* Copy the line onto the end of the strbuf while fixing whitespaces */
+void ws_fix_copy(struct strbuf *dst, const char *src, int len, unsigned ws_rule, int *error_count)
 {
        /*
         * len is number of bytes to be copied from src, starting
@@ -254,17 +286,15 @@ int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *erro
        int last_tab_in_indent = -1;
        int last_space_in_indent = -1;
        int need_fix_leading_space = 0;
-       char *buf;
 
        /*
         * Strip trailing whitespace
         */
-       if ((ws_rule & WS_TRAILING_SPACE) &&
-           (2 <= len && isspace(src[len-2]))) {
-               if (src[len - 1] == '\n') {
+       if (ws_rule & WS_BLANK_AT_EOL) {
+               if (0 < len && src[len - 1] == '\n') {
                        add_nl_to_tail = 1;
                        len--;
-                       if (1 < len && src[len - 1] == '\r') {
+                       if (0 < len && src[len - 1] == '\r') {
                                add_cr_to_tail = !!(ws_rule & WS_CR_AT_EOL);
                                len--;
                        }
@@ -295,7 +325,6 @@ int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *erro
                        break;
        }
 
-       buf = dst;
        if (need_fix_leading_space) {
                /* Process indent ourselves */
                int consecutive_spaces = 0;
@@ -317,28 +346,41 @@ int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *erro
                        char ch = src[i];
                        if (ch != ' ') {
                                consecutive_spaces = 0;
-                               *dst++ = ch;
+                               strbuf_addch(dst, ch);
                        } else {
                                consecutive_spaces++;
                                if (consecutive_spaces == 8) {
-                                       *dst++ = '\t';
+                                       strbuf_addch(dst, '\t');
                                        consecutive_spaces = 0;
                                }
                        }
                }
                while (0 < consecutive_spaces--)
-                       *dst++ = ' ';
+                       strbuf_addch(dst, ' ');
+               len -= last;
+               src += last;
+               fixed = 1;
+       } else if ((ws_rule & WS_TAB_IN_INDENT) && last_tab_in_indent >= 0) {
+               /* Expand tabs into spaces */
+               int last = last_tab_in_indent + 1;
+               for (i = 0; i < last; i++) {
+                       if (src[i] == '\t')
+                               do {
+                                       strbuf_addch(dst, ' ');
+                               } while (dst->len % 8);
+                       else
+                               strbuf_addch(dst, src[i]);
+               }
                len -= last;
                src += last;
                fixed = 1;
        }
 
-       memcpy(dst, src, len);
+       strbuf_add(dst, src, len);
        if (add_cr_to_tail)
-               dst[len++] = '\r';
+               strbuf_addch(dst, '\r');
        if (add_nl_to_tail)
-               dst[len++] = '\n';
+               strbuf_addch(dst, '\n');
        if (fixed && error_count)
                (*error_count)++;
-       return dst + len - buf;
 }
index 1b6df45450c39c0f456df68485b5735950816ac8..54b6b03b9cc4e168e6b6429679b61dc2a1d5197b 100644 (file)
@@ -1,6 +1,5 @@
 #include "cache.h"
 #include "wt-status.h"
-#include "color.h"
 #include "object.h"
 #include "dir.h"
 #include "commit.h"
 #include "quote.h"
 #include "run-command.h"
 #include "remote.h"
+#include "refs.h"
+#include "submodule.h"
 
-int wt_status_relative_paths = 1;
-int wt_status_use_color = -1;
-int wt_status_submodule_summary;
-static char wt_status_colors[][COLOR_MAXLEN] = {
+static char default_wt_status_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */
        GIT_COLOR_GREEN,  /* WT_STATUS_UPDATED */
        GIT_COLOR_RED,    /* WT_STATUS_CHANGED */
        GIT_COLOR_RED,    /* WT_STATUS_UNTRACKED */
        GIT_COLOR_RED,    /* WT_STATUS_NOBRANCH */
+       GIT_COLOR_RED,    /* WT_STATUS_UNMERGED */
+       GIT_COLOR_GREEN,  /* WT_STATUS_LOCAL_BRANCH */
+       GIT_COLOR_RED,    /* WT_STATUS_REMOTE_BRANCH */
 };
 
-enum untracked_status_type show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
-
-static int parse_status_slot(const char *var, int offset)
-{
-       if (!strcasecmp(var+offset, "header"))
-               return WT_STATUS_HEADER;
-       if (!strcasecmp(var+offset, "updated")
-               || !strcasecmp(var+offset, "added"))
-               return WT_STATUS_UPDATED;
-       if (!strcasecmp(var+offset, "changed"))
-               return WT_STATUS_CHANGED;
-       if (!strcasecmp(var+offset, "untracked"))
-               return WT_STATUS_UNTRACKED;
-       if (!strcasecmp(var+offset, "nobranch"))
-               return WT_STATUS_NOBRANCH;
-       die("bad config variable '%s'", var);
-}
-
-static const char *color(int slot)
+static const char *color(int slot, struct wt_status *s)
 {
-       return wt_status_use_color > 0 ? wt_status_colors[slot] : "";
+       return s->use_color > 0 ? s->color_palette[slot] : "";
 }
 
 void wt_status_prepare(struct wt_status *s)
@@ -51,65 +34,156 @@ void wt_status_prepare(struct wt_status *s)
        const char *head;
 
        memset(s, 0, sizeof(*s));
+       memcpy(s->color_palette, default_wt_status_colors,
+              sizeof(default_wt_status_colors));
+       s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
+       s->use_color = -1;
+       s->relative_paths = 1;
        head = resolve_ref("HEAD", sha1, 0, NULL);
        s->branch = head ? xstrdup(head) : NULL;
        s->reference = "HEAD";
        s->fp = stdout;
        s->index_file = get_index_file();
+       s->change.strdup_strings = 1;
+       s->untracked.strdup_strings = 1;
+       s->ignored.strdup_strings = 1;
+}
+
+static void wt_status_print_unmerged_header(struct wt_status *s)
+{
+       const char *c = color(WT_STATUS_HEADER, s);
+
+       color_fprintf_ln(s->fp, c, "# Unmerged paths:");
+       if (!advice_status_hints)
+               return;
+       if (s->in_merge)
+               ;
+       else if (!s->is_initial)
+               color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
+       else
+               color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
+       color_fprintf_ln(s->fp, c, "#   (use \"git add/rm <file>...\" as appropriate to mark resolution)");
+       color_fprintf_ln(s->fp, c, "#");
 }
 
 static void wt_status_print_cached_header(struct wt_status *s)
 {
-       const char *c = color(WT_STATUS_HEADER);
+       const char *c = color(WT_STATUS_HEADER, s);
+
        color_fprintf_ln(s->fp, c, "# Changes to be committed:");
-       if (!s->is_initial) {
+       if (!advice_status_hints)
+               return;
+       if (s->in_merge)
+               ; /* NEEDSWORK: use "git reset --unresolve"??? */
+       else if (!s->is_initial)
                color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
-       } else {
+       else
                color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
-       }
        color_fprintf_ln(s->fp, c, "#");
 }
 
 static void wt_status_print_dirty_header(struct wt_status *s,
-                                        int has_deleted)
+                                        int has_deleted,
+                                        int has_dirty_submodules)
 {
-       const char *c = color(WT_STATUS_HEADER);
+       const char *c = color(WT_STATUS_HEADER, s);
+
        color_fprintf_ln(s->fp, c, "# Changed but not updated:");
+       if (!advice_status_hints)
+               return;
        if (!has_deleted)
                color_fprintf_ln(s->fp, c, "#   (use \"git add <file>...\" to update what will be committed)");
        else
                color_fprintf_ln(s->fp, c, "#   (use \"git add/rm <file>...\" to update what will be committed)");
        color_fprintf_ln(s->fp, c, "#   (use \"git checkout -- <file>...\" to discard changes in working directory)");
+       if (has_dirty_submodules)
+               color_fprintf_ln(s->fp, c, "#   (commit or discard the untracked or modified content in submodules)");
        color_fprintf_ln(s->fp, c, "#");
 }
 
-static void wt_status_print_untracked_header(struct wt_status *s)
+static void wt_status_print_other_header(struct wt_status *s,
+                                        const char *what,
+                                        const char *how)
 {
-       const char *c = color(WT_STATUS_HEADER);
-       color_fprintf_ln(s->fp, c, "# Untracked files:");
-       color_fprintf_ln(s->fp, c, "#   (use \"git add <file>...\" to include in what will be committed)");
+       const char *c = color(WT_STATUS_HEADER, s);
+       color_fprintf_ln(s->fp, c, "# %s files:", what);
+       if (!advice_status_hints)
+               return;
+       color_fprintf_ln(s->fp, c, "#   (use \"git %s <file>...\" to include in what will be committed)", how);
        color_fprintf_ln(s->fp, c, "#");
 }
 
 static void wt_status_print_trailer(struct wt_status *s)
 {
-       color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
+       color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
 }
 
 #define quote_path quote_path_relative
 
-static void wt_status_print_filepair(struct wt_status *s,
-                                    int t, struct diff_filepair *p)
+static void wt_status_print_unmerged_data(struct wt_status *s,
+                                         struct string_list_item *it)
 {
-       const char *c = color(t);
+       const char *c = color(WT_STATUS_UNMERGED, s);
+       struct wt_status_change_data *d = it->util;
+       struct strbuf onebuf = STRBUF_INIT;
+       const char *one, *how = "bug";
+
+       one = quote_path(it->string, -1, &onebuf, s->prefix);
+       color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
+       switch (d->stagemask) {
+       case 1: how = "both deleted:"; break;
+       case 2: how = "added by us:"; break;
+       case 3: how = "deleted by them:"; break;
+       case 4: how = "added by them:"; break;
+       case 5: how = "deleted by us:"; break;
+       case 6: how = "both added:"; break;
+       case 7: how = "both modified:"; break;
+       }
+       color_fprintf(s->fp, c, "%-20s%s\n", how, one);
+       strbuf_release(&onebuf);
+}
+
+static void wt_status_print_change_data(struct wt_status *s,
+                                       int change_type,
+                                       struct string_list_item *it)
+{
+       struct wt_status_change_data *d = it->util;
+       const char *c = color(change_type, s);
+       int status = status;
+       char *one_name;
+       char *two_name;
        const char *one, *two;
        struct strbuf onebuf = STRBUF_INIT, twobuf = STRBUF_INIT;
+       struct strbuf extra = STRBUF_INIT;
+
+       one_name = two_name = it->string;
+       switch (change_type) {
+       case WT_STATUS_UPDATED:
+               status = d->index_status;
+               if (d->head_path)
+                       one_name = d->head_path;
+               break;
+       case WT_STATUS_CHANGED:
+               if (d->new_submodule_commits || d->dirty_submodule) {
+                       strbuf_addstr(&extra, " (");
+                       if (d->new_submodule_commits)
+                               strbuf_addf(&extra, "new commits, ");
+                       if (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
+                               strbuf_addf(&extra, "modified content, ");
+                       if (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
+                               strbuf_addf(&extra, "untracked content, ");
+                       strbuf_setlen(&extra, extra.len - 2);
+                       strbuf_addch(&extra, ')');
+               }
+               status = d->worktree_status;
+               break;
+       }
 
-       one = quote_path(p->one->path, -1, &onebuf, s->prefix);
-       two = quote_path(p->two->path, -1, &twobuf, s->prefix);
+       one = quote_path(one_name, -1, &onebuf, s->prefix);
+       two = quote_path(two_name, -1, &twobuf, s->prefix);
 
-       color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
-       switch (p->status) {
+       color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
+       switch (status) {
        case DIFF_STATUS_ADDED:
                color_fprintf(s->fp, c, "new file:   %s", one);
                break;
@@ -135,100 +209,342 @@ static void wt_status_print_filepair(struct wt_status *s,
                color_fprintf(s->fp, c, "unmerged:   %s", one);
                break;
        default:
-               die("bug: unhandled diff status %c", p->status);
+               die("bug: unhandled diff status %c", status);
+       }
+       if (extra.len) {
+               color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "%s", extra.buf);
+               strbuf_release(&extra);
        }
        fprintf(s->fp, "\n");
        strbuf_release(&onebuf);
        strbuf_release(&twobuf);
 }
 
-static void wt_status_print_updated_cb(struct diff_queue_struct *q,
-               struct diff_options *options,
-               void *data)
+static void wt_status_collect_changed_cb(struct diff_queue_struct *q,
+                                        struct diff_options *options,
+                                        void *data)
 {
        struct wt_status *s = data;
-       int shown_header = 0;
        int i;
+
+       if (!q->nr)
+               return;
+       s->workdir_dirty = 1;
        for (i = 0; i < q->nr; i++) {
-               if (q->queue[i]->status == 'U')
-                       continue;
-               if (!shown_header) {
-                       wt_status_print_cached_header(s);
-                       s->commitable = 1;
-                       shown_header = 1;
+               struct diff_filepair *p;
+               struct string_list_item *it;
+               struct wt_status_change_data *d;
+
+               p = q->queue[i];
+               it = string_list_insert(&s->change, p->one->path);
+               d = it->util;
+               if (!d) {
+                       d = xcalloc(1, sizeof(*d));
+                       it->util = d;
                }
-               wt_status_print_filepair(s, WT_STATUS_UPDATED, q->queue[i]);
+               if (!d->worktree_status)
+                       d->worktree_status = p->status;
+               d->dirty_submodule = p->two->dirty_submodule;
+               if (S_ISGITLINK(p->two->mode))
+                       d->new_submodule_commits = !!hashcmp(p->one->sha1, p->two->sha1);
        }
-       if (shown_header)
-               wt_status_print_trailer(s);
 }
 
-static void wt_status_print_changed_cb(struct diff_queue_struct *q,
-                        struct diff_options *options,
-                        void *data)
+static int unmerged_mask(const char *path)
+{
+       int pos, mask;
+       struct cache_entry *ce;
+
+       pos = cache_name_pos(path, strlen(path));
+       if (0 <= pos)
+               return 0;
+
+       mask = 0;
+       pos = -pos-1;
+       while (pos < active_nr) {
+               ce = active_cache[pos++];
+               if (strcmp(ce->name, path) || !ce_stage(ce))
+                       break;
+               mask |= (1 << (ce_stage(ce) - 1));
+       }
+       return mask;
+}
+
+static void wt_status_collect_updated_cb(struct diff_queue_struct *q,
+                                        struct diff_options *options,
+                                        void *data)
 {
        struct wt_status *s = data;
        int i;
-       if (q->nr) {
-               int has_deleted = 0;
-               s->workdir_dirty = 1;
-               for (i = 0; i < q->nr; i++)
-                       if (q->queue[i]->status == DIFF_STATUS_DELETED) {
-                               has_deleted = 1;
-                               break;
-                       }
-               wt_status_print_dirty_header(s, has_deleted);
+
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p;
+               struct string_list_item *it;
+               struct wt_status_change_data *d;
+
+               p = q->queue[i];
+               it = string_list_insert(&s->change, p->two->path);
+               d = it->util;
+               if (!d) {
+                       d = xcalloc(1, sizeof(*d));
+                       it->util = d;
+               }
+               if (!d->index_status)
+                       d->index_status = p->status;
+               switch (p->status) {
+               case DIFF_STATUS_COPIED:
+               case DIFF_STATUS_RENAMED:
+                       d->head_path = xstrdup(p->one->path);
+                       break;
+               case DIFF_STATUS_UNMERGED:
+                       d->stagemask = unmerged_mask(p->two->path);
+                       break;
+               }
        }
-       for (i = 0; i < q->nr; i++)
-               wt_status_print_filepair(s, WT_STATUS_CHANGED, q->queue[i]);
-       if (q->nr)
-               wt_status_print_trailer(s);
 }
 
-static void wt_status_print_updated(struct wt_status *s)
+static void wt_status_collect_changes_worktree(struct wt_status *s)
 {
        struct rev_info rev;
+
        init_revisions(&rev, NULL);
-       setup_revisions(0, NULL, &rev,
-               s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference);
+       setup_revisions(0, NULL, &rev, NULL);
        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
-       rev.diffopt.format_callback = wt_status_print_updated_cb;
+       DIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES);
+       if (!s->show_untracked_files)
+               DIFF_OPT_SET(&rev.diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
+       if (s->ignore_submodule_arg) {
+               DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
+               handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
+    }
+       rev.diffopt.format_callback = wt_status_collect_changed_cb;
+       rev.diffopt.format_callback_data = s;
+       rev.prune_data = s->pathspec;
+       run_diff_files(&rev, 0);
+}
+
+static void wt_status_collect_changes_index(struct wt_status *s)
+{
+       struct rev_info rev;
+       struct setup_revision_opt opt;
+
+       init_revisions(&rev, NULL);
+       memset(&opt, 0, sizeof(opt));
+       opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;
+       setup_revisions(0, NULL, &rev, &opt);
+
+       if (s->ignore_submodule_arg) {
+               DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
+               handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
+       }
+
+       rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
+       rev.diffopt.format_callback = wt_status_collect_updated_cb;
        rev.diffopt.format_callback_data = s;
        rev.diffopt.detect_rename = 1;
        rev.diffopt.rename_limit = 200;
        rev.diffopt.break_opt = 0;
+       rev.prune_data = s->pathspec;
        run_diff_index(&rev, 1);
 }
 
+static void wt_status_collect_changes_initial(struct wt_status *s)
+{
+       int i;
+
+       for (i = 0; i < active_nr; i++) {
+               struct string_list_item *it;
+               struct wt_status_change_data *d;
+               struct cache_entry *ce = active_cache[i];
+
+               if (!ce_path_match(ce, s->pathspec))
+                       continue;
+               it = string_list_insert(&s->change, ce->name);
+               d = it->util;
+               if (!d) {
+                       d = xcalloc(1, sizeof(*d));
+                       it->util = d;
+               }
+               if (ce_stage(ce)) {
+                       d->index_status = DIFF_STATUS_UNMERGED;
+                       d->stagemask |= (1 << (ce_stage(ce) - 1));
+               }
+               else
+                       d->index_status = DIFF_STATUS_ADDED;
+       }
+}
+
+static void wt_status_collect_untracked(struct wt_status *s)
+{
+       int i;
+       struct dir_struct dir;
+
+       if (!s->show_untracked_files)
+               return;
+       memset(&dir, 0, sizeof(dir));
+       if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
+               dir.flags |=
+                       DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
+       setup_standard_excludes(&dir);
+
+       fill_directory(&dir, s->pathspec);
+       for (i = 0; i < dir.nr; i++) {
+               struct dir_entry *ent = dir.entries[i];
+               if (!cache_name_is_other(ent->name, ent->len))
+                       continue;
+               if (!match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
+                       continue;
+               string_list_insert(&s->untracked, ent->name);
+               free(ent);
+       }
+
+       if (s->show_ignored_files) {
+               dir.nr = 0;
+               dir.flags = DIR_SHOW_IGNORED | DIR_SHOW_OTHER_DIRECTORIES;
+               fill_directory(&dir, s->pathspec);
+               for (i = 0; i < dir.nr; i++) {
+                       struct dir_entry *ent = dir.entries[i];
+                       if (!cache_name_is_other(ent->name, ent->len))
+                               continue;
+                       if (!match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
+                               continue;
+                       string_list_insert(&s->ignored, ent->name);
+                       free(ent);
+               }
+       }
+
+       free(dir.entries);
+}
+
+void wt_status_collect(struct wt_status *s)
+{
+       wt_status_collect_changes_worktree(s);
+
+       if (s->is_initial)
+               wt_status_collect_changes_initial(s);
+       else
+               wt_status_collect_changes_index(s);
+       wt_status_collect_untracked(s);
+}
+
+static void wt_status_print_unmerged(struct wt_status *s)
+{
+       int shown_header = 0;
+       int i;
+
+       for (i = 0; i < s->change.nr; i++) {
+               struct wt_status_change_data *d;
+               struct string_list_item *it;
+               it = &(s->change.items[i]);
+               d = it->util;
+               if (!d->stagemask)
+                       continue;
+               if (!shown_header) {
+                       wt_status_print_unmerged_header(s);
+                       shown_header = 1;
+               }
+               wt_status_print_unmerged_data(s, it);
+       }
+       if (shown_header)
+               wt_status_print_trailer(s);
+
+}
+
+static void wt_status_print_updated(struct wt_status *s)
+{
+       int shown_header = 0;
+       int i;
+
+       for (i = 0; i < s->change.nr; i++) {
+               struct wt_status_change_data *d;
+               struct string_list_item *it;
+               it = &(s->change.items[i]);
+               d = it->util;
+               if (!d->index_status ||
+                   d->index_status == DIFF_STATUS_UNMERGED)
+                       continue;
+               if (!shown_header) {
+                       wt_status_print_cached_header(s);
+                       s->commitable = 1;
+                       shown_header = 1;
+               }
+               wt_status_print_change_data(s, WT_STATUS_UPDATED, it);
+       }
+       if (shown_header)
+               wt_status_print_trailer(s);
+}
+
+/*
+ * -1 : has delete
+ *  0 : no change
+ *  1 : some change but no delete
+ */
+static int wt_status_check_worktree_changes(struct wt_status *s,
+                                            int *dirty_submodules)
+{
+       int i;
+       int changes = 0;
+
+       *dirty_submodules = 0;
+
+       for (i = 0; i < s->change.nr; i++) {
+               struct wt_status_change_data *d;
+               d = s->change.items[i].util;
+               if (!d->worktree_status ||
+                   d->worktree_status == DIFF_STATUS_UNMERGED)
+                       continue;
+               if (!changes)
+                       changes = 1;
+               if (d->dirty_submodule)
+                       *dirty_submodules = 1;
+               if (d->worktree_status == DIFF_STATUS_DELETED)
+                       changes = -1;
+       }
+       return changes;
+}
+
 static void wt_status_print_changed(struct wt_status *s)
 {
-       struct rev_info rev;
-       init_revisions(&rev, "");
-       setup_revisions(0, NULL, &rev, NULL);
-       rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
-       rev.diffopt.format_callback = wt_status_print_changed_cb;
-       rev.diffopt.format_callback_data = s;
-       run_diff_files(&rev, 0);
+       int i, dirty_submodules;
+       int worktree_changes = wt_status_check_worktree_changes(s, &dirty_submodules);
+
+       if (!worktree_changes)
+               return;
+
+       wt_status_print_dirty_header(s, worktree_changes < 0, dirty_submodules);
+
+       for (i = 0; i < s->change.nr; i++) {
+               struct wt_status_change_data *d;
+               struct string_list_item *it;
+               it = &(s->change.items[i]);
+               d = it->util;
+               if (!d->worktree_status ||
+                   d->worktree_status == DIFF_STATUS_UNMERGED)
+                       continue;
+               wt_status_print_change_data(s, WT_STATUS_CHANGED, it);
+       }
+       wt_status_print_trailer(s);
 }
 
-static void wt_status_print_submodule_summary(struct wt_status *s)
+static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitted)
 {
        struct child_process sm_summary;
        char summary_limit[64];
        char index[PATH_MAX];
-       const char *env[] = { index, NULL };
-       const char *argv[] = {
-               "submodule",
-               "summary",
-               "--cached",
-               "--for-status",
-               "--summary-limit",
-               summary_limit,
-               s->amend ? "HEAD^" : "HEAD",
-               NULL
-       };
-
-       sprintf(summary_limit, "%d", wt_status_submodule_summary);
+       const char *env[] = { NULL, NULL };
+       const char *argv[8];
+
+       env[0] =        index;
+       argv[0] =       "submodule";
+       argv[1] =       "summary";
+       argv[2] =       uncommitted ? "--files" : "--cached";
+       argv[3] =       "--for-status";
+       argv[4] =       "--summary-limit";
+       argv[5] =       summary_limit;
+       argv[6] =       uncommitted ? NULL : (s->amend ? "HEAD^" : "HEAD");
+       argv[7] =       NULL;
+
+       sprintf(summary_limit, "%d", s->submodule_summary);
        snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", s->index_file);
 
        memset(&sm_summary, 0, sizeof(sm_summary));
@@ -241,34 +557,26 @@ static void wt_status_print_submodule_summary(struct wt_status *s)
        run_command(&sm_summary);
 }
 
-static void wt_status_print_untracked(struct wt_status *s)
+static void wt_status_print_other(struct wt_status *s,
+                                 struct string_list *l,
+                                 const char *what,
+                                 const char *how)
 {
-       struct dir_struct dir;
        int i;
-       int shown_header = 0;
        struct strbuf buf = STRBUF_INIT;
 
-       memset(&dir, 0, sizeof(dir));
+       if (!s->untracked.nr)
+               return;
 
-       if (!s->untracked)
-               dir.flags |=
-                       DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
-       setup_standard_excludes(&dir);
+       wt_status_print_other_header(s, what, how);
 
-       read_directory(&dir, ".", "", 0, NULL);
-       for(i = 0; i < dir.nr; i++) {
-               struct dir_entry *ent = dir.entries[i];
-               if (!cache_name_is_other(ent->name, ent->len))
-                       continue;
-               if (!shown_header) {
-                       s->workdir_untracked = 1;
-                       wt_status_print_untracked_header(s);
-                       shown_header = 1;
-               }
-               color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
-               color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED), "%s",
-                               quote_path(ent->name, ent->len,
-                                       &buf, s->prefix));
+       for (i = 0; i < l->nr; i++) {
+               struct string_list_item *it;
+               it = &(l->items[i]);
+               color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
+               color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED, s), "%s",
+                                quote_path(it->string, strlen(it->string),
+                                           &buf, s->prefix));
        }
        strbuf_release(&buf);
 }
@@ -276,11 +584,15 @@ static void wt_status_print_untracked(struct wt_status *s)
 static void wt_status_print_verbose(struct wt_status *s)
 {
        struct rev_info rev;
+       struct setup_revision_opt opt;
 
        init_revisions(&rev, NULL);
        DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
-       setup_revisions(0, NULL, &rev,
-               s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference);
+
+       memset(&opt, 0, sizeof(opt));
+       opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;
+       setup_revisions(0, NULL, &rev, &opt);
+
        rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
        rev.diffopt.detect_rename = 1;
        rev.diffopt.file = s->fp;
@@ -310,17 +622,15 @@ static void wt_status_print_tracking(struct wt_status *s)
                return;
 
        for (cp = sb.buf; (ep = strchr(cp, '\n')) != NULL; cp = ep + 1)
-               color_fprintf_ln(s->fp, color(WT_STATUS_HEADER),
+               color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s),
                                 "# %.*s", (int)(ep - cp), cp);
-       color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
+       color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
 }
 
 void wt_status_print(struct wt_status *s)
 {
-       unsigned char sha1[20];
-       const char *branch_color = color(WT_STATUS_HEADER);
+       const char *branch_color = color(WT_STATUS_HEADER, s);
 
-       s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
        if (s->branch) {
                const char *on_what = "On branch ";
                const char *branch_name = s->branch;
@@ -328,29 +638,38 @@ void wt_status_print(struct wt_status *s)
                        branch_name += 11;
                else if (!strcmp(branch_name, "HEAD")) {
                        branch_name = "";
-                       branch_color = color(WT_STATUS_NOBRANCH);
+                       branch_color = color(WT_STATUS_NOBRANCH, s);
                        on_what = "Not currently on any branch.";
                }
-               color_fprintf(s->fp, color(WT_STATUS_HEADER), "# ");
+               color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "# ");
                color_fprintf_ln(s->fp, branch_color, "%s%s", on_what, branch_name);
                if (!s->is_initial)
                        wt_status_print_tracking(s);
        }
 
        if (s->is_initial) {
-               color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
-               color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "# Initial commit");
-               color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
+               color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
+               color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "# Initial commit");
+               color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
        }
 
        wt_status_print_updated(s);
+       wt_status_print_unmerged(s);
        wt_status_print_changed(s);
-       if (wt_status_submodule_summary)
-               wt_status_print_submodule_summary(s);
-       if (show_untracked_files)
-               wt_status_print_untracked(s);
-       else if (s->commitable)
-                fprintf(s->fp, "# Untracked files not listed (use -u option to show untracked files)\n");
+       if (s->submodule_summary &&
+           (!s->ignore_submodule_arg ||
+            strcmp(s->ignore_submodule_arg, "all"))) {
+               wt_status_print_submodule_summary(s, 0);  /* staged */
+               wt_status_print_submodule_summary(s, 1);  /* unstaged */
+       }
+       if (s->show_untracked_files) {
+               wt_status_print_other(s, &s->untracked, "Untracked", "add");
+               if (s->show_ignored_files)
+                       wt_status_print_other(s, &s->ignored, "Ignored", "add -f");
+       } else if (s->commitable)
+               fprintf(s->fp, "# Untracked files not listed%s\n",
+                       advice_status_hints
+                       ? " (use -u option to show untracked files)" : "");
 
        if (s->verbose)
                wt_status_print_verbose(s);
@@ -360,54 +679,191 @@ void wt_status_print(struct wt_status *s)
                else if (s->nowarn)
                        ; /* nothing */
                else if (s->workdir_dirty)
-                       printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
-               else if (s->workdir_untracked)
-                       printf("nothing added to commit but untracked files present (use \"git add\" to track)\n");
+                       printf("no changes added to commit%s\n",
+                               advice_status_hints
+                               ? " (use \"git add\" and/or \"git commit -a\")" : "");
+               else if (s->untracked.nr)
+                       printf("nothing added to commit but untracked files present%s\n",
+                               advice_status_hints
+                               ? " (use \"git add\" to track)" : "");
                else if (s->is_initial)
-                       printf("nothing to commit (create/copy files and use \"git add\" to track)\n");
-               else if (!show_untracked_files)
-                       printf("nothing to commit (use -u to show untracked files)\n");
+                       printf("nothing to commit%s\n", advice_status_hints
+                               ? " (create/copy files and use \"git add\" to track)" : "");
+               else if (!s->show_untracked_files)
+                       printf("nothing to commit%s\n", advice_status_hints
+                               ? " (use -u to show untracked files)" : "");
                else
-                       printf("nothing to commit (working directory clean)\n");
+                       printf("nothing to commit%s\n", advice_status_hints
+                               ? " (working directory clean)" : "");
        }
 }
 
-int git_status_config(const char *k, const char *v, void *cb)
+static void wt_shortstatus_unmerged(int null_termination, struct string_list_item *it,
+                          struct wt_status *s)
 {
-       if (!strcmp(k, "status.submodulesummary")) {
-               int is_bool;
-               wt_status_submodule_summary = git_config_bool_or_int(k, v, &is_bool);
-               if (is_bool && wt_status_submodule_summary)
-                       wt_status_submodule_summary = -1;
-               return 0;
+       struct wt_status_change_data *d = it->util;
+       const char *how = "??";
+
+       switch (d->stagemask) {
+       case 1: how = "DD"; break; /* both deleted */
+       case 2: how = "AU"; break; /* added by us */
+       case 3: how = "UD"; break; /* deleted by them */
+       case 4: how = "UA"; break; /* added by them */
+       case 5: how = "DU"; break; /* deleted by us */
+       case 6: how = "AA"; break; /* both added */
+       case 7: how = "UU"; break; /* both modified */
        }
-       if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
-               wt_status_use_color = git_config_colorbool(k, v, -1);
-               return 0;
+       color_fprintf(s->fp, color(WT_STATUS_UNMERGED, s), "%s", how);
+       if (null_termination) {
+               fprintf(stdout, " %s%c", it->string, 0);
+       } else {
+               struct strbuf onebuf = STRBUF_INIT;
+               const char *one;
+               one = quote_path(it->string, -1, &onebuf, s->prefix);
+               printf(" %s\n", one);
+               strbuf_release(&onebuf);
        }
-       if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
-               int slot = parse_status_slot(k, 13);
-               if (!v)
-                       return config_error_nonbool(k);
-               color_parse(v, k, wt_status_colors[slot]);
-               return 0;
+}
+
+static void wt_shortstatus_status(int null_termination, struct string_list_item *it,
+                        struct wt_status *s)
+{
+       struct wt_status_change_data *d = it->util;
+
+       if (d->index_status)
+               color_fprintf(s->fp, color(WT_STATUS_UPDATED, s), "%c", d->index_status);
+       else
+               putchar(' ');
+       if (d->worktree_status)
+               color_fprintf(s->fp, color(WT_STATUS_CHANGED, s), "%c", d->worktree_status);
+       else
+               putchar(' ');
+       putchar(' ');
+       if (null_termination) {
+               fprintf(stdout, "%s%c", it->string, 0);
+               if (d->head_path)
+                       fprintf(stdout, "%s%c", d->head_path, 0);
+       } else {
+               struct strbuf onebuf = STRBUF_INIT;
+               const char *one;
+               if (d->head_path) {
+                       one = quote_path(d->head_path, -1, &onebuf, s->prefix);
+                       printf("%s -> ", one);
+                       strbuf_release(&onebuf);
+               }
+               one = quote_path(it->string, -1, &onebuf, s->prefix);
+               printf("%s\n", one);
+               strbuf_release(&onebuf);
        }
-       if (!strcmp(k, "status.relativepaths")) {
-               wt_status_relative_paths = git_config_bool(k, v);
-               return 0;
+}
+
+static void wt_shortstatus_other(int null_termination, struct string_list_item *it,
+                                struct wt_status *s, const char *sign)
+{
+       if (null_termination) {
+               fprintf(stdout, "%s %s%c", sign, it->string, 0);
+       } else {
+               struct strbuf onebuf = STRBUF_INIT;
+               const char *one;
+               one = quote_path(it->string, -1, &onebuf, s->prefix);
+               color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", sign);
+               printf(" %s\n", one);
+               strbuf_release(&onebuf);
        }
-       if (!strcmp(k, "status.showuntrackedfiles")) {
-               if (!v)
-                       return config_error_nonbool(k);
-               else if (!strcmp(v, "no"))
-                       show_untracked_files = SHOW_NO_UNTRACKED_FILES;
-               else if (!strcmp(v, "normal"))
-                       show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
-               else if (!strcmp(v, "all"))
-                       show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
+}
+
+static void wt_shortstatus_print_tracking(struct wt_status *s)
+{
+       struct branch *branch;
+       const char *header_color = color(WT_STATUS_HEADER, s);
+       const char *branch_color_local = color(WT_STATUS_LOCAL_BRANCH, s);
+       const char *branch_color_remote = color(WT_STATUS_REMOTE_BRANCH, s);
+
+       const char *base;
+       const char *branch_name;
+       int num_ours, num_theirs;
+
+       color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "## ");
+
+       if (!s->branch)
+               return;
+       branch_name = s->branch;
+
+       if (!prefixcmp(branch_name, "refs/heads/"))
+               branch_name += 11;
+       else if (!strcmp(branch_name, "HEAD")) {
+               branch_name = "HEAD (no branch)";
+               branch_color_local = color(WT_STATUS_NOBRANCH, s);
+       }
+
+       branch = branch_get(s->branch + 11);
+       if (s->is_initial)
+               color_fprintf(s->fp, header_color, "Initial commit on ");
+       if (!stat_tracking_info(branch, &num_ours, &num_theirs)) {
+               color_fprintf_ln(s->fp, branch_color_local,
+                       "%s", branch_name);
+               return;
+       }
+
+       base = branch->merge[0]->dst;
+       base = shorten_unambiguous_ref(base, 0);
+       color_fprintf(s->fp, branch_color_local, "%s", branch_name);
+       color_fprintf(s->fp, header_color, "...");
+       color_fprintf(s->fp, branch_color_remote, "%s", base);
+
+       color_fprintf(s->fp, header_color, " [");
+       if (!num_ours) {
+               color_fprintf(s->fp, header_color, "behind ");
+               color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
+       } else if (!num_theirs) {
+               color_fprintf(s->fp, header_color, "ahead ");
+               color_fprintf(s->fp, branch_color_local, "%d", num_ours);
+       } else {
+               color_fprintf(s->fp, header_color, "ahead ");
+               color_fprintf(s->fp, branch_color_local, "%d", num_ours);
+               color_fprintf(s->fp, header_color, ", behind ");
+               color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
+       }
+
+       color_fprintf_ln(s->fp, header_color, "]");
+}
+
+void wt_shortstatus_print(struct wt_status *s, int null_termination, int show_branch)
+{
+       int i;
+
+       if (show_branch)
+               wt_shortstatus_print_tracking(s);
+
+       for (i = 0; i < s->change.nr; i++) {
+               struct wt_status_change_data *d;
+               struct string_list_item *it;
+
+               it = &(s->change.items[i]);
+               d = it->util;
+               if (d->stagemask)
+                       wt_shortstatus_unmerged(null_termination, it, s);
                else
-                       return error("Invalid untracked files mode '%s'", v);
-               return 0;
+                       wt_shortstatus_status(null_termination, it, s);
        }
-       return git_diff_ui_config(k, v, cb);
+       for (i = 0; i < s->untracked.nr; i++) {
+               struct string_list_item *it;
+
+               it = &(s->untracked.items[i]);
+               wt_shortstatus_other(null_termination, it, s, "??");
+       }
+       for (i = 0; i < s->ignored.nr; i++) {
+               struct string_list_item *it;
+
+               it = &(s->ignored.items[i]);
+               wt_shortstatus_other(null_termination, it, s, "!!");
+       }
+}
+
+void wt_porcelain_print(struct wt_status *s, int null_termination)
+{
+       s->use_color = 0;
+       s->relative_paths = 0;
+       s->prefix = NULL;
+       wt_shortstatus_print(s, null_termination, 0);
 }
index 78add09bd67c727babb61cd1eaa773bcd0c6e55e..9df9c9fad2512d7c1d7d6456cdd645d641b5a089 100644 (file)
@@ -2,13 +2,18 @@
 #define STATUS_H
 
 #include <stdio.h>
+#include "string-list.h"
+#include "color.h"
 
 enum color_wt_status {
-       WT_STATUS_HEADER,
+       WT_STATUS_HEADER = 0,
        WT_STATUS_UPDATED,
        WT_STATUS_CHANGED,
        WT_STATUS_UNTRACKED,
        WT_STATUS_NOBRANCH,
+       WT_STATUS_UNMERGED,
+       WT_STATUS_LOCAL_BRANCH,
+       WT_STATUS_REMOTE_BRANCH
 };
 
 enum untracked_status_type {
@@ -16,29 +21,49 @@ enum untracked_status_type {
        SHOW_NORMAL_UNTRACKED_FILES,
        SHOW_ALL_UNTRACKED_FILES
 };
-extern enum untracked_status_type show_untracked_files;
+
+struct wt_status_change_data {
+       int worktree_status;
+       int index_status;
+       int stagemask;
+       char *head_path;
+       unsigned dirty_submodule       : 2;
+       unsigned new_submodule_commits : 1;
+};
 
 struct wt_status {
        int is_initial;
        char *branch;
        const char *reference;
+       const char **pathspec;
        int verbose;
        int amend;
-       int untracked;
+       int in_merge;
        int nowarn;
+       int use_color;
+       int relative_paths;
+       int submodule_summary;
+       int show_ignored_files;
+       enum untracked_status_type show_untracked_files;
+       const char *ignore_submodule_arg;
+       char color_palette[WT_STATUS_REMOTE_BRANCH+1][COLOR_MAXLEN];
+
        /* These are computed during processing of the individual sections */
        int commitable;
        int workdir_dirty;
-       int workdir_untracked;
        const char *index_file;
        FILE *fp;
        const char *prefix;
+       struct string_list change;
+       struct string_list untracked;
+       struct string_list ignored;
 };
 
-int git_status_config(const char *var, const char *value, void *cb);
-extern int wt_status_use_color;
-extern int wt_status_relative_paths;
 void wt_status_prepare(struct wt_status *s);
 void wt_status_print(struct wt_status *s);
+void wt_status_collect(struct wt_status *s);
+
+void wt_shortstatus_print(struct wt_status *s, int null_termination, int show_branch);
+void wt_porcelain_print(struct wt_status *s, int null_termination);
 
 #endif /* STATUS_H */
index b9b0db8d86615d6ca1046b932772f3d9750a8062..e1e054e4d982de30d8a9c8c4109c6d62448f62a9 100644 (file)
@@ -138,19 +138,20 @@ int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t co
 
 int xdi_diff_outf(mmfile_t *mf1, mmfile_t *mf2,
                  xdiff_emit_consume_fn fn, void *consume_callback_data,
-                 xpparam_t const *xpp,
-                 xdemitconf_t const *xecfg, xdemitcb_t *xecb)
+                 xpparam_t const *xpp, xdemitconf_t const *xecfg)
 {
        int ret;
        struct xdiff_emit_state state;
+       xdemitcb_t ecb;
 
        memset(&state, 0, sizeof(state));
        state.consume = fn;
        state.consume_callback_data = consume_callback_data;
-       xecb->outf = xdiff_outf;
-       xecb->priv = &state;
+       memset(&ecb, 0, sizeof(ecb));
+       ecb.outf = xdiff_outf;
+       ecb.priv = &state;
        strbuf_init(&state.remainder, 0);
-       ret = xdi_diff(mf1, mf2, xpp, xecfg, xecb);
+       ret = xdi_diff(mf1, mf2, xpp, xecfg, &ecb);
        strbuf_release(&state.remainder);
        return ret;
 }
@@ -218,6 +219,23 @@ int read_mmfile(mmfile_t *ptr, const char *filename)
        return 0;
 }
 
+void read_mmblob(mmfile_t *ptr, const unsigned char *sha1)
+{
+       unsigned long size;
+       enum object_type type;
+
+       if (!hashcmp(sha1, null_sha1)) {
+               ptr->ptr = xstrdup("");
+               ptr->size = 0;
+               return;
+       }
+
+       ptr->ptr = read_sha1_file(sha1, &type, &size);
+       if (!ptr->ptr || type != OBJ_BLOB)
+               die("unable to read blob object %s", sha1_to_hex(sha1));
+       ptr->size = size;
+}
+
 #define FIRST_FEW_BYTES 8000
 int buffer_is_binary(const char *ptr, unsigned long size)
 {
@@ -268,9 +286,8 @@ static long ff_regexp(const char *line, long len,
        result = pmatch[i].rm_eo - pmatch[i].rm_so;
        if (result > buffer_size)
                result = buffer_size;
-       else
-               while (result > 0 && (isspace(line[result - 1])))
-                       result--;
+       while (result > 0 && (isspace(line[result - 1])))
+               result--;
        memcpy(buffer, line, result);
  fail:
        free(line_buffer);
@@ -309,6 +326,21 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *value, int cflags)
        }
 }
 
+void xdiff_clear_find_func(xdemitconf_t *xecfg)
+{
+       if (xecfg->find_func) {
+               int i;
+               struct ff_regs *regs = xecfg->find_func_priv;
+
+               for (i = 0; i < regs->nr; i++)
+                       regfree(&regs->array[i].re);
+               free(regs->array);
+               free(regs);
+               xecfg->find_func = NULL;
+               xecfg->find_func_priv = NULL;
+       }
+}
+
 int git_xmerge_style = -1;
 
 int git_xmerge_config(const char *var, const char *value, void *cb)
index 7352b9a9c204c2b1d4ca9df5ce040fe22d6f521c..49d1116fc34f536ab9358313522a25564dd1f6c3 100644 (file)
@@ -9,8 +9,7 @@ typedef void (*xdiff_emit_hunk_consume_fn)(void *, long, long, long);
 int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *ecb);
 int xdi_diff_outf(mmfile_t *mf1, mmfile_t *mf2,
                  xdiff_emit_consume_fn fn, void *consume_callback_data,
-                 xpparam_t const *xpp,
-                 xdemitconf_t const *xecfg, xdemitcb_t *xecb);
+                 xpparam_t const *xpp, xdemitconf_t const *xecfg);
 int xdi_diff_hunks(mmfile_t *mf1, mmfile_t *mf2,
                   xdiff_emit_hunk_consume_fn fn, void *consume_callback_data,
                   xpparam_t const *xpp, xdemitconf_t *xecfg);
@@ -18,9 +17,11 @@ int parse_hunk_header(char *line, int len,
                      int *ob, int *on,
                      int *nb, int *nn);
 int read_mmfile(mmfile_t *ptr, const char *filename);
+void read_mmblob(mmfile_t *ptr, const unsigned char *sha1);
 int buffer_is_binary(const char *ptr, unsigned long size);
 
 extern void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);
+extern void xdiff_clear_find_func(xdemitconf_t *xecfg);
 extern int git_xmerge_config(const char *var, const char *value, void *cb);
 extern int git_xmerge_style;
 
index 4da052a3fff6768fca05b2f3399c5d87ec1daa2e..711048ea36dda1d814b395efa8bc69ab142425ca 100644 (file)
@@ -56,11 +56,14 @@ extern "C" {
 #define XDL_MERGE_EAGER 1
 #define XDL_MERGE_ZEALOUS 2
 #define XDL_MERGE_ZEALOUS_ALNUM 3
-#define XDL_MERGE_LEVEL_MASK 0x0f
+
+/* merge favor modes */
+#define XDL_MERGE_FAVOR_OURS 1
+#define XDL_MERGE_FAVOR_THEIRS 2
+#define XDL_MERGE_FAVOR_UNION 3
 
 /* merge output styles */
-#define XDL_MERGE_DIFF3 0x8000
-#define XDL_MERGE_STYLE_MASK 0x8000
+#define XDL_MERGE_DIFF3 1
 
 typedef struct s_mmfile {
        char *ptr;
@@ -108,9 +111,21 @@ long xdl_mmfile_size(mmfile_t *mmf);
 int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
             xdemitconf_t const *xecfg, xdemitcb_t *ecb);
 
-int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1,
-               mmfile_t *mf2, const char *name2,
-               xpparam_t const *xpp, int level, mmbuffer_t *result);
+typedef struct s_xmparam {
+       xpparam_t xpp;
+       int marker_size;
+       int level;
+       int favor;
+       int style;
+       const char *ancestor;   /* label for orig */
+       const char *file1;      /* label for mf1 */
+       const char *file2;      /* label for mf2 */
+} xmparam_t;
+
+#define DEFAULT_CONFLICT_MARKER_SIZE 7
+
+int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2,
+               xmparam_t const *xmp, mmbuffer_t *result);
 
 #ifdef __cplusplus
 }
index 1ebab687f77218d1520a4527214170eeab176d31..da67c04357dfe4d3283c589f5d47be9c5f2b7fcf 100644 (file)
@@ -26,7 +26,7 @@
 
 #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_LINE_MAX (long)((1UL << (CHAR_BIT * sizeof(long) - 1)) - 1)
 #define XDL_SNAKE_CNT 20
 #define XDL_K_HEUR 4
 
index d9737f04c220645aa762d79ff14a84855721ffda..6d6fc1bc5e01be7305ec5101f43645a1dce69bd3 100644 (file)
@@ -28,6 +28,7 @@ typedef struct s_xdmerge {
         * 0 = conflict,
         * 1 = no conflict, take first,
         * 2 = no conflict, take second.
+        * 3 = no conflict, take both.
         */
        int mode;
        /*
@@ -144,13 +145,16 @@ static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest)
 
 static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
                              xdfenv_t *xe2, const char *name2,
+                             const char *name3,
                              int size, int i, int style,
-                             xdmerge_t *m, char *dest)
+                             xdmerge_t *m, char *dest, int marker_size)
 {
-       const int marker_size = 7;
        int marker1_size = (name1 ? strlen(name1) + 1 : 0);
        int marker2_size = (name2 ? strlen(name2) + 1 : 0);
-       int j;
+       int marker3_size = (name3 ? strlen(name3) + 1 : 0);
+
+       if (marker_size <= 0)
+               marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
 
        /* Before conflicting part */
        size += xdl_recs_copy(xe1, i, m->i1 - i, 0,
@@ -159,8 +163,8 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
        if (!dest) {
                size += marker_size + 1 + marker1_size;
        } else {
-               for (j = 0; j < marker_size; j++)
-                       dest[size++] = '<';
+               memset(dest + size, '<', marker_size);
+               size += marker_size;
                if (marker1_size) {
                        dest[size] = ' ';
                        memcpy(dest + size + 1, name1, marker1_size - 1);
@@ -176,10 +180,15 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
        if (style == XDL_MERGE_DIFF3) {
                /* Shared preimage */
                if (!dest) {
-                       size += marker_size + 1;
+                       size += marker_size + 1 + marker3_size;
                } else {
-                       for (j = 0; j < marker_size; j++)
-                               dest[size++] = '|';
+                       memset(dest + size, '|', marker_size);
+                       size += marker_size;
+                       if (marker3_size) {
+                               dest[size] = ' ';
+                               memcpy(dest + size + 1, name3, marker3_size - 1);
+                               size += marker3_size;
+                       }
                        dest[size++] = '\n';
                }
                size += xdl_orig_copy(xe1, m->i0, m->chg0, 1,
@@ -189,8 +198,8 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
        if (!dest) {
                size += marker_size + 1;
        } else {
-               for (j = 0; j < marker_size; j++)
-                       dest[size++] = '=';
+               memset(dest + size, '=', marker_size);
+               size += marker_size;
                dest[size++] = '\n';
        }
 
@@ -200,8 +209,8 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
        if (!dest) {
                size += marker_size + 1 + marker2_size;
        } else {
-               for (j = 0; j < marker_size; j++)
-                       dest[size++] = '>';
+               memset(dest + size, '>', marker_size);
+               size += marker_size;
                if (marker2_size) {
                        dest[size] = ' ';
                        memcpy(dest + size + 1, name2, marker2_size - 1);
@@ -214,22 +223,35 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
 
 static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1,
                                 xdfenv_t *xe2, const char *name2,
-                                xdmerge_t *m, char *dest, int style)
+                                const char *ancestor_name,
+                                int favor,
+                                xdmerge_t *m, char *dest, int style,
+                                int marker_size)
 {
        int size, i;
 
        for (size = i = 0; m; m = m->next) {
+               if (favor && !m->mode)
+                       m->mode = favor;
+
                if (m->mode == 0)
                        size = fill_conflict_hunk(xe1, name1, xe2, name2,
-                                                 size, i, style, m, dest);
-               else if (m->mode == 1)
-                       size += xdl_recs_copy(xe1, i, m->i1 + m->chg1 - i, 0,
+                                                 ancestor_name,
+                                                 size, i, style, m, dest,
+                                                 marker_size);
+               else if (m->mode & 3) {
+                       /* Before conflicting part */
+                       size += xdl_recs_copy(xe1, i, m->i1 - i, 0,
                                              dest ? dest + size : NULL);
-               else if (m->mode == 2)
-                       size += xdl_recs_copy(xe2, m->i2 - m->i1 + i,
-                                             m->i1 + m->chg2 - i, 0,
-                                             dest ? dest + size : NULL);
-               else
+                       /* Postimage from side #1 */
+                       if (m->mode & 1)
+                               size += xdl_recs_copy(xe1, m->i1, m->chg1, 1,
+                                                     dest ? dest + size : NULL);
+                       /* Postimage from side #2 */
+                       if (m->mode & 2)
+                               size += xdl_recs_copy(xe2, m->i2, m->chg2, 1,
+                                                     dest ? dest + size : NULL);
+               } else
                        continue;
                i = m->i1 + m->chg1;
        }
@@ -384,13 +406,19 @@ static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m,
  *
  * returns < 0 on error, == 0 for no conflicts, else number of conflicts
  */
-static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1,
-               xdfenv_t *xe2, xdchange_t *xscr2, const char *name2,
-               int flags, xpparam_t const *xpp, mmbuffer_t *result) {
+static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1,
+               xdfenv_t *xe2, xdchange_t *xscr2,
+               xmparam_t const *xmp, mmbuffer_t *result)
+{
        xdmerge_t *changes, *c;
+       xpparam_t const *xpp = &xmp->xpp;
+       const char *const ancestor_name = xmp->ancestor;
+       const char *const name1 = xmp->file1;
+       const char *const name2 = xmp->file2;
        int i0, i1, i2, chg0, chg1, chg2;
-       int level = flags & XDL_MERGE_LEVEL_MASK;
-       int style = flags & XDL_MERGE_STYLE_MASK;
+       int level = xmp->level;
+       int style = xmp->style;
+       int favor = xmp->favor;
 
        if (style == XDL_MERGE_DIFF3) {
                /*
@@ -522,26 +550,31 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1,
        }
        /* output */
        if (result) {
+               int marker_size = xmp->marker_size;
                int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2,
-                       changes, NULL, style);
+                                                ancestor_name,
+                                                favor, changes, NULL, style,
+                                                marker_size);
                result->ptr = xdl_malloc(size);
                if (!result->ptr) {
                        xdl_cleanup_merge(changes);
                        return -1;
                }
                result->size = size;
-               xdl_fill_merge_buffer(xe1, name1, xe2, name2, changes,
-                                     result->ptr, style);
+               xdl_fill_merge_buffer(xe1, name1, xe2, name2,
+                                     ancestor_name, favor, changes,
+                                     result->ptr, style, marker_size);
        }
        return xdl_cleanup_merge(changes);
 }
 
-int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1,
-               mmfile_t *mf2, const char *name2,
-               xpparam_t const *xpp, int flags, mmbuffer_t *result) {
+int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2,
+               xmparam_t const *xmp, mmbuffer_t *result)
+{
        xdchange_t *xscr1, *xscr2;
        xdfenv_t xe1, xe2;
        int status;
+       xpparam_t const *xpp = &xmp->xpp;
 
        result->ptr = NULL;
        result->size = 0;
@@ -563,23 +596,22 @@ int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1,
                return -1;
        }
        status = 0;
-       if (xscr1 || xscr2) {
-               if (!xscr1) {
-                       result->ptr = xdl_malloc(mf2->size);
-                       memcpy(result->ptr, mf2->ptr, mf2->size);
-                       result->size = mf2->size;
-               } else if (!xscr2) {
-                       result->ptr = xdl_malloc(mf1->size);
-                       memcpy(result->ptr, mf1->ptr, mf1->size);
-                       result->size = mf1->size;
-               } else {
-                       status = xdl_do_merge(&xe1, xscr1, name1,
-                                             &xe2, xscr2, name2,
-                                             flags, xpp, result);
-               }
-               xdl_free_script(xscr1);
-               xdl_free_script(xscr2);
+       if (!xscr1) {
+               result->ptr = xdl_malloc(mf2->size);
+               memcpy(result->ptr, mf2->ptr, mf2->size);
+               result->size = mf2->size;
+       } else if (!xscr2) {
+               result->ptr = xdl_malloc(mf1->size);
+               memcpy(result->ptr, mf1->ptr, mf1->size);
+               result->size = mf1->size;
+       } else {
+               status = xdl_do_merge(&xe1, xscr1,
+                                     &xe2, xscr2,
+                                     xmp, result);
        }
+       xdl_free_script(xscr1);
+       xdl_free_script(xscr2);
+
        xdl_free_env(&xe1);
        xdl_free_env(&xe2);
 
index 04ad468702209b77427e635370d41001986042ce..22f9bd692c2706136b4938bbcb2e0fc3dab212b8 100644 (file)
@@ -190,48 +190,68 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
 {
        int i1, i2;
 
+       if (s1 == s2 && !memcmp(l1, l2, s1))
+               return 1;
+       if (!(flags & XDF_WHITESPACE_FLAGS))
+               return 0;
+
+       i1 = 0;
+       i2 = 0;
+
+       /*
+        * -w matches everything that matches with -b, and -b in turn
+        * matches everything that matches with --ignore-space-at-eol.
+        *
+        * Each flavor of ignoring needs different logic to skip whitespaces
+        * while we have both sides to compare.
+        */
        if (flags & XDF_IGNORE_WHITESPACE) {
-               for (i1 = i2 = 0; i1 < s1 && i2 < s2; ) {
-                       if (isspace(l1[i1]))
-                               while (isspace(l1[i1]) && i1 < s1)
-                                       i1++;
-                       if (isspace(l2[i2]))
-                               while (isspace(l2[i2]) && i2 < s2)
-                                       i2++;
-                       if (i1 < s1 && i2 < s2 && l1[i1++] != l2[i2++])
+               goto skip_ws;
+               while (i1 < s1 && i2 < s2) {
+                       if (l1[i1++] != l2[i2++])
                                return 0;
+               skip_ws:
+                       while (i1 < s1 && isspace(l1[i1]))
+                               i1++;
+                       while (i2 < s2 && isspace(l2[i2]))
+                               i2++;
                }
-               return (i1 >= s1 && i2 >= s2);
        } else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) {
-               for (i1 = i2 = 0; i1 < s1 && i2 < s2; ) {
-                       if (isspace(l1[i1])) {
-                               if (!isspace(l2[i2]))
-                                       return 0;
-                               while (isspace(l1[i1]) && i1 < s1)
-                                       i1++;
-                               while (isspace(l2[i2]) && i2 < s2)
-                                       i2++;
-                       } else if (l1[i1++] != l2[i2++])
-                               return 0;
-               }
-               return (i1 >= s1 && i2 >= s2);
-       } else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL) {
-               for (i1 = i2 = 0; i1 < s1 && i2 < s2; ) {
-                       if (l1[i1] != l2[i2]) {
+               while (i1 < s1 && i2 < s2) {
+                       if (isspace(l1[i1]) && isspace(l2[i2])) {
+                               /* Skip matching spaces and try again */
                                while (i1 < s1 && isspace(l1[i1]))
                                        i1++;
                                while (i2 < s2 && isspace(l2[i2]))
                                        i2++;
-                               if (i1 < s1 || i2 < s2)
-                                       return 0;
-                               return 1;
+                               continue;
                        }
+                       if (l1[i1++] != l2[i2++])
+                               return 0;
+               }
+       } else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL) {
+               while (i1 < s1 && i2 < s2 && l1[i1++] == l2[i2++])
+                       ; /* keep going */
+       }
+
+       /*
+        * After running out of one side, the remaining side must have
+        * nothing but whitespace for the lines to match.  Note that
+        * ignore-whitespace-at-eol case may break out of the loop
+        * while there still are characters remaining on both lines.
+        */
+       if (i1 < s1) {
+               while (i1 < s1 && isspace(l1[i1]))
                        i1++;
+               if (s1 != i1)
+                       return 0;
+       }
+       if (i2 < s2) {
+               while (i2 < s2 && isspace(l2[i2]))
                        i2++;
-               }
-               return i1 >= s1 && i2 >= s2;
-       } else
-               return s1 == s2 && !memcmp(l1, l2, s1);
+               return (s2 == i2);
+       }
+       return 1;
 }
 
 static unsigned long xdl_hash_record_with_whitespace(char const **data,
@@ -242,18 +262,20 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data,
        for (; ptr < top && *ptr != '\n'; ptr++) {
                if (isspace(*ptr)) {
                        const char *ptr2 = ptr;
+                       int at_eol;
                        while (ptr + 1 < top && isspace(ptr[1])
                                        && ptr[1] != '\n')
                                ptr++;
+                       at_eol = (top <= ptr + 1 || ptr[1] == '\n');
                        if (flags & XDF_IGNORE_WHITESPACE)
                                ; /* already handled */
                        else if (flags & XDF_IGNORE_WHITESPACE_CHANGE
-                                       && ptr[1] != '\n') {
+                                && !at_eol) {
                                ha += (ha << 5);
                                ha ^= (unsigned long) ' ';
                        }
                        else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL
-                                       && ptr[1] != '\n') {
+                                && !at_eol) {
                                while (ptr2 != ptr + 1) {
                                        ha += (ha << 5);
                                        ha ^= (unsigned long) *ptr2;